@iislee/opencodex 2.11.0 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
@@ -7,10 +7,24 @@ export const BOUNDED_BODY_TIMEOUT_MS = 5_000;
7
7
  export interface BoundedBodyOptions {
8
8
  /** Abort the read with this signal. Its reason is rethrown by identity. */
9
9
  signal?: AbortSignal;
10
+ /**
11
+ * Reject the returned promise with TypeError on malformed or truncated UTF-8
12
+ * instead of replacing invalid bytes, including during timeout-path flushes.
13
+ * Reader cancellation and lock release still run. Defaults to false.
14
+ */
15
+ fatalUtf8?: boolean;
16
+ /**
17
+ * Byte ceiling for retained body data. Defaults to BOUNDED_BODY_MAX_BYTES (64 KiB),
18
+ * which suits error bodies; callers materializing whole success payloads (e.g. a
19
+ * non-streaming upstream JSON completion) pass a larger explicit budget.
20
+ */
21
+ maxBytes?: number;
10
22
  /** Total wall-clock deadline. Exposed for focused tests. */
11
23
  totalTimeoutMs?: number;
12
24
  /** Deadline between non-empty raw chunks. Exposed for focused tests. */
13
25
  inactivityTimeoutMs?: number;
26
+ /** Deadline for the first non-empty raw chunk. Defaults to inactivityTimeoutMs. */
27
+ firstByteTimeoutMs?: number;
14
28
  }
15
29
 
16
30
  export interface BoundedBodyResult {
@@ -30,9 +44,42 @@ export interface BoundedBodyResult {
30
44
  displaySafe: boolean;
31
45
  }
32
46
 
47
+ export interface BoundedBytesOptions {
48
+ /** Abort the read with this signal. Its reason is rethrown by identity. */
49
+ signal?: AbortSignal;
50
+ /** Maximum number of raw bytes retained from the response body. */
51
+ maxBytes: number;
52
+ }
53
+
54
+ export interface BoundedBytesResult {
55
+ /**
56
+ * Exact raw bytes retained from the response. Empty when the cap was exceeded.
57
+ *
58
+ * This is a view over internal storage, so `bytes.buffer.byteLength` can exceed
59
+ * `bytes.byteLength`. Consumers must honor the view's byteOffset and byteLength
60
+ * instead of reading or transferring the backing buffer directly.
61
+ */
62
+ bytes: Uint8Array<ArrayBuffer>;
63
+ /** True when the body was observed to exceed the byte cap. */
64
+ oversized: boolean;
65
+ }
66
+
33
67
  const TOTAL_TIMEOUT = Symbol("bounded body total timeout");
34
68
  const INACTIVITY_TIMEOUT = Symbol("bounded body inactivity timeout");
35
69
 
70
+ /**
71
+ * Test-only instrumentation: how many times the retained buffer was reallocated
72
+ * during the most recent read. The accumulator grows geometrically, so this is
73
+ * logarithmic in the body size and independent of how many chunks the peer sends.
74
+ * The per-chunk array it replaced retained one object per chunk instead, which a
75
+ * fragmenting peer can inflate far past the payload ceiling — a property no
76
+ * correctness assertion can see, which is why it is observable here.
77
+ */
78
+ let bufferGrowthsForTests = 0;
79
+ export function boundedBodyBufferGrowthsForTests(): number {
80
+ return bufferGrowthsForTests;
81
+ }
82
+
36
83
  function timeoutPromise(ms: number, value: symbol): { promise: Promise<symbol>; clear: () => void } {
37
84
  let timer: ReturnType<typeof setTimeout> | undefined;
38
85
  const promise = new Promise<symbol>((resolve) => {
@@ -56,8 +103,102 @@ function cancelWithoutWaiting(reader: ReadableStreamDefaultReader<Uint8Array>, r
56
103
  }
57
104
  }
58
105
 
59
- function decodeUtf8(chunks: readonly Uint8Array[]): string {
60
- const decoder = new TextDecoder();
106
+ function cancelBodyWithoutWaiting(body: ReadableStream<Uint8Array>, reason?: unknown): void {
107
+ // A signal can already be aborted before a reader is attached. Still settle the
108
+ // original body so fetch-backed streams cannot retain a rejected read in that gap.
109
+ try {
110
+ void body.cancel(reason).catch(() => undefined);
111
+ } catch {
112
+ // A locked or non-conforming stream may throw synchronously from cancel().
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Consume the original response body as raw bytes under a strict memory ceiling.
118
+ *
119
+ * The caller owns any wall-clock deadline through `signal`, which lets one budget
120
+ * cover both response headers and body consumption. No decoding, cloning, or teeing
121
+ * occurs, so arbitrary upstream bytes remain unchanged.
122
+ */
123
+ export async function readBoundedResponseBytes(
124
+ response: Response,
125
+ options: BoundedBytesOptions,
126
+ ): Promise<BoundedBytesResult> {
127
+ const signal = options.signal;
128
+ if (signal?.aborted) throw signal.reason;
129
+
130
+ const body = response.body;
131
+ if (!body) return { bytes: new Uint8Array(0), oversized: false };
132
+
133
+ const reader = body.getReader();
134
+ const maxBytes = options.maxBytes;
135
+ let retained = new Uint8Array(Math.min(maxBytes, 64 * 1024));
136
+ let retainedBytes = 0;
137
+ let mustCancel = false;
138
+ let cancelReason: unknown;
139
+
140
+ let rejectForAbort: ((reason: unknown) => void) | undefined;
141
+ const aborted = new Promise<never>((_resolve, reject) => {
142
+ rejectForAbort = reject;
143
+ });
144
+ const onAbort = () => rejectForAbort?.(signal?.reason);
145
+ signal?.addEventListener("abort", onAbort, { once: true });
146
+ // Close the narrow race between the preflight check and listener install.
147
+ if (signal?.aborted) onAbort();
148
+
149
+ try {
150
+ while (true) {
151
+ const read = reader.read();
152
+ // Observe a late read rejection when abort/cancellation wins the race.
153
+ void read.catch(() => undefined);
154
+ const outcome = await Promise.race([read, aborted]);
155
+ if (signal?.aborted) {
156
+ mustCancel = true;
157
+ cancelReason = signal.reason;
158
+ throw signal.reason;
159
+ }
160
+
161
+ const { value, done } = outcome;
162
+ if (done) {
163
+ return { bytes: retained.subarray(0, retainedBytes), oversized: false };
164
+ }
165
+ if (!value || value.byteLength === 0) continue;
166
+
167
+ if (value.byteLength > maxBytes - retainedBytes) {
168
+ mustCancel = true;
169
+ cancelReason = new DOMException("Response body size limit reached", "QuotaExceededError");
170
+ retained = new Uint8Array(0);
171
+ retainedBytes = 0;
172
+ return { bytes: retained, oversized: true };
173
+ }
174
+
175
+ if (retainedBytes + value.byteLength > retained.length) {
176
+ const grown = new Uint8Array(
177
+ Math.min(maxBytes, Math.max(retained.length * 2, retainedBytes + value.byteLength)),
178
+ );
179
+ grown.set(retained.subarray(0, retainedBytes));
180
+ retained = grown;
181
+ }
182
+ retained.set(value, retainedBytes);
183
+ retainedBytes += value.byteLength;
184
+ }
185
+ } catch (error) {
186
+ mustCancel = true;
187
+ cancelReason = error;
188
+ throw error;
189
+ } finally {
190
+ signal?.removeEventListener("abort", onAbort);
191
+ if (mustCancel) cancelWithoutWaiting(reader, cancelReason);
192
+ try {
193
+ reader.releaseLock();
194
+ } catch {
195
+ // A pending read can keep the lock briefly while cancel settles.
196
+ }
197
+ }
198
+ }
199
+
200
+ function decodeUtf8(chunks: readonly Uint8Array[], fatal: boolean): string {
201
+ const decoder = new TextDecoder("utf-8", { fatal });
61
202
  let text = "";
62
203
  for (const chunk of chunks) text += decoder.decode(chunk, { stream: true });
63
204
  // Flush an incomplete trailing UTF-8 sequence deterministically.
@@ -77,9 +218,11 @@ export async function readBoundedResponseBody(
77
218
  options: BoundedBodyOptions = {},
78
219
  ): Promise<BoundedBodyResult> {
79
220
  const signal = options.signal;
80
- if (signal?.aborted) throw signal.reason;
81
-
82
221
  const body = response.body;
222
+ if (signal?.aborted) {
223
+ if (body) cancelBodyWithoutWaiting(body, signal.reason);
224
+ throw signal.reason;
225
+ }
83
226
  if (!body) {
84
227
  return {
85
228
  text: "",
@@ -93,13 +236,18 @@ export async function readBoundedResponseBody(
93
236
  }
94
237
 
95
238
  const reader = body.getReader();
96
- const chunks: Uint8Array[] = [];
239
+ const maxBytes = options.maxBytes ?? BOUNDED_BODY_MAX_BYTES;
240
+ // Geometrically growing single buffer: per-chunk arrays would retain one object per
241
+ // transport chunk, which a hostile peer could inflate into metadata amplification far
242
+ // beyond the payload ceiling on large budgets.
243
+ let retained = new Uint8Array(Math.min(maxBytes, 64 * 1024));
97
244
  let retainedBytes = 0;
245
+ bufferGrowthsForTests = 0;
98
246
  let mustCancel = false;
99
247
  let cancelReason: unknown;
100
248
  const total = timeoutPromise(options.totalTimeoutMs ?? BOUNDED_BODY_TIMEOUT_MS, TOTAL_TIMEOUT);
101
249
  let inactivity = timeoutPromise(
102
- options.inactivityTimeoutMs ?? BOUNDED_BODY_TIMEOUT_MS,
250
+ options.firstByteTimeoutMs ?? options.inactivityTimeoutMs ?? BOUNDED_BODY_TIMEOUT_MS,
103
251
  INACTIVITY_TIMEOUT,
104
252
  );
105
253
 
@@ -134,7 +282,7 @@ export async function readBoundedResponseBody(
134
282
  "TimeoutError",
135
283
  );
136
284
  return {
137
- text: decodeUtf8(chunks),
285
+ text: decodeUtf8([retained.subarray(0, retainedBytes)], options.fatalUtf8 === true),
138
286
  truncated: true,
139
287
  timedOut: true,
140
288
  totalTimedOut: outcome === TOTAL_TIMEOUT,
@@ -147,7 +295,7 @@ export async function readBoundedResponseBody(
147
295
  const { value, done } = outcome as ReadableStreamReadResult<Uint8Array>;
148
296
  if (done) {
149
297
  return {
150
- text: decodeUtf8(chunks),
298
+ text: decodeUtf8([retained.subarray(0, retainedBytes)], options.fatalUtf8 === true),
151
299
  truncated: false,
152
300
  timedOut: false,
153
301
  totalTimedOut: false,
@@ -165,10 +313,10 @@ export async function readBoundedResponseBody(
165
313
  INACTIVITY_TIMEOUT,
166
314
  );
167
315
 
168
- if (value.byteLength > BOUNDED_BODY_MAX_BYTES - retainedBytes) {
316
+ if (value.byteLength > maxBytes - retainedBytes) {
169
317
  mustCancel = true;
170
318
  cancelReason = new DOMException("Error body size limit reached", "QuotaExceededError");
171
- chunks.length = 0;
319
+ retained = new Uint8Array(0);
172
320
  retainedBytes = 0;
173
321
  return {
174
322
  text: "",
@@ -181,7 +329,15 @@ export async function readBoundedResponseBody(
181
329
  };
182
330
  }
183
331
 
184
- chunks.push(value);
332
+ if (retainedBytes + value.byteLength > retained.length) {
333
+ const grown = new Uint8Array(
334
+ Math.min(maxBytes, Math.max(retained.length * 2, retainedBytes + value.byteLength)),
335
+ );
336
+ grown.set(retained.subarray(0, retainedBytes));
337
+ retained = grown;
338
+ bufferGrowthsForTests += 1;
339
+ }
340
+ retained.set(value, retainedBytes);
185
341
  retainedBytes += value.byteLength;
186
342
  }
187
343
  } catch (error) {
@@ -11,7 +11,8 @@
11
11
  * back to `process.execPath` (which is itself Bun when run via `bun src/cli/index.ts`).
12
12
  */
13
13
  import { createRequire } from "node:module";
14
- import { dirname, join, resolve } from "node:path";
14
+ import { realpathSync } from "node:fs";
15
+ import { dirname, join } from "node:path";
15
16
  import { isRealBunBinary } from "./bun-binary-validator.mjs";
16
17
 
17
18
  export { isRealBunBinary };
@@ -20,12 +21,124 @@ const require = createRequire(import.meta.url);
20
21
 
21
22
  const BUN_OVERRIDE_ENV = "OPENCODEX_BUN_PATH";
22
23
 
24
+ /**
25
+ * Env marker stamped by whichever launcher selected the Bun binary, then read back
26
+ * inside the launched process.
27
+ *
28
+ * Provenance has to travel with the launch because it cannot be recovered afterwards:
29
+ * resolving it at report time answers "what would this shell pick now", not "what was
30
+ * this service started with", and those differ exactly when the answer matters.
31
+ */
32
+ export const BUN_RUNTIME_SOURCE_ENV = "OCX_BUN_RUNTIME_SOURCE";
33
+
34
+ /**
35
+ * The binary the marker was minted for. Stamped beside the source so a reader can tell
36
+ * whether a marker still describes the process holding it, without re-deriving the
37
+ * selection from an environment that may no longer contain it.
38
+ */
39
+ export const BUN_RUNTIME_PATH_ENV = "OCX_BUN_RUNTIME_PATH";
40
+
41
+ export type BunRuntimeSource = "override" | "bundled" | "process";
42
+
43
+ /** The only provenance values any surface may accept off the wire or out of the env. */
44
+ export const BUN_RUNTIME_SOURCES: readonly BunRuntimeSource[] = ["override", "bundled", "process"];
45
+
23
46
  export type DurableBunRuntime = {
24
47
  path: string;
25
- source: "override" | "bundled" | "process";
48
+ source: BunRuntimeSource;
26
49
  overrideEnv: typeof BUN_OVERRIDE_ENV;
27
50
  };
28
51
 
52
+ /**
53
+ * The provenance this process was launched with, or `undefined` when nothing
54
+ * trustworthy is recorded.
55
+ *
56
+ * Deliberately never falls back to `durableBunRuntime()`. A service installed before
57
+ * the marker existed has no provenance, and guessing one from the current environment
58
+ * would report a confident wrong answer — "unknown" is the honest result and callers
59
+ * are expected to say so. Values outside the allowlist are treated as absent rather
60
+ * than passed through.
61
+ */
62
+ export function reportedBunRuntimeSource(
63
+ env: NodeJS.ProcessEnv = process.env,
64
+ ): BunRuntimeSource | undefined {
65
+ const raw = env[BUN_RUNTIME_SOURCE_ENV]?.trim();
66
+ const source = BUN_RUNTIME_SOURCES.find(candidate => candidate === raw);
67
+ if (!source) return undefined;
68
+ // Source and binary are a PAIR: without a recorded path that names THIS
69
+ // executable, the marker describes some other launch and must not be
70
+ // reported (a bare OCX_BUN_RUNTIME_SOURCE inherited from an unrelated
71
+ // parent would otherwise claim a confident wrong origin).
72
+ const recordedPath = env[BUN_RUNTIME_PATH_ENV]?.trim();
73
+ if (!recordedPath || !samePath(recordedPath, process.execPath)) return undefined;
74
+ return source;
75
+ }
76
+
77
+ /** Env pair a launcher stamps for the binary it just selected. */
78
+ export function bunRuntimeProvenanceEnv(runtime: DurableBunRuntime): Record<string, string> {
79
+ return { [BUN_RUNTIME_SOURCE_ENV]: runtime.source, [BUN_RUNTIME_PATH_ENV]: runtime.path };
80
+ }
81
+
82
+ /**
83
+ * Child environment for a proxy started with `process.execPath` — the runtime this
84
+ * process is already using.
85
+ *
86
+ * These launchers re-exec the current runtime rather than resolving a binary, so the
87
+ * provenance to report is whatever launched THIS process. Without this the marker would
88
+ * be silently dropped on `ocx ensure`, GUI start, restart, and update-relaunch, and the
89
+ * service would report an unknown origin it actually knows.
90
+ *
91
+ * An inherited marker is carried forward only when the binary it was minted for is the
92
+ * one about to be re-executed. Inheritance travels down a process tree, so a marker can
93
+ * outlive its binary — something started under a marked process but running a different
94
+ * Bun would otherwise relaunch the daemon with a provenance contradicting the binary
95
+ * actually serving it. The check compares the recorded path rather than re-deriving the
96
+ * selection, because a service installed with a shell-local override keeps neither that
97
+ * shell nor its `OPENCODEX_BUN_PATH`, and re-deriving would demote a correct `override`
98
+ * to `process` on its first relaunch.
99
+ */
100
+ export function withProcessRuntimeProvenance(
101
+ env: NodeJS.ProcessEnv,
102
+ ): NodeJS.ProcessEnv {
103
+ return { ...env, ...bunRuntimeProvenanceEnv(currentRuntimeProvenance(env)) };
104
+ }
105
+
106
+ /**
107
+ * Provenance for `process.execPath`: the inherited claim when it was minted for this
108
+ * exact executable, otherwise what this executable actually is.
109
+ */
110
+ function currentRuntimeProvenance(env: NodeJS.ProcessEnv): DurableBunRuntime {
111
+ const recorded = recordedCurrentRuntime(env);
112
+ if (recorded) return recorded;
113
+ // No marker that describes this binary: report what is running. One resolution
114
+ // supplies both halves so the pair can never disagree.
115
+ const runtime = unmarkedDurableBunRuntime();
116
+ return samePath(runtime.path, process.execPath)
117
+ ? runtime
118
+ : { path: process.execPath, source: "process", overrideEnv: BUN_OVERRIDE_ENV };
119
+ }
120
+
121
+ function recordedCurrentRuntime(env: NodeJS.ProcessEnv): DurableBunRuntime | null {
122
+ const source = reportedBunRuntimeSource(env);
123
+ const path = env[BUN_RUNTIME_PATH_ENV]?.trim();
124
+ if (!source || !path || !samePath(path, process.execPath)) return null;
125
+ return { path, source, overrideEnv: BUN_OVERRIDE_ENV };
126
+ }
127
+
128
+ /**
129
+ * Same file, allowing for the aliases a path can pick up between launch and relaunch:
130
+ * symlinks/junctions, mapped drives, and Windows case differences. Falls back to a
131
+ * lexical comparison when a path cannot be resolved (it may be gone).
132
+ */
133
+ function samePath(left: string, right: string): boolean {
134
+ const canonical = (value: string): string => {
135
+ let resolved = value;
136
+ try { resolved = realpathSync(value); } catch { /* keep the literal path */ }
137
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
138
+ };
139
+ return canonical(left) === canonical(right);
140
+ }
141
+
29
142
  /**
30
143
  * Absolute path to the bundled Bun binary, or null if the `bun` dependency is
31
144
  * not installed/resolvable (or only the un-downloaded placeholder is present).
@@ -45,21 +158,21 @@ export function bundledBunPath(): string | null {
45
158
  }
46
159
  }
47
160
 
48
- export function overrideBunPath(): string | null {
49
- const value = process.env[BUN_OVERRIDE_ENV]?.trim();
50
- if (!value) return null;
51
- const resolved = resolve(value);
52
- return isRealBunBinary(resolved) ? resolved : null;
53
- }
54
-
55
- export function durableBunRuntime(): DurableBunRuntime {
56
- const override = overrideBunPath();
57
- if (override) return { path: override, source: "override", overrideEnv: BUN_OVERRIDE_ENV };
161
+ function unmarkedDurableBunRuntime(): DurableBunRuntime {
58
162
  const bundled = bundledBunPath();
59
163
  if (bundled) return { path: bundled, source: "bundled", overrideEnv: BUN_OVERRIDE_ENV };
60
164
  return { path: process.execPath, source: "process", overrideEnv: BUN_OVERRIDE_ENV };
61
165
  }
62
166
 
167
+ export function durableBunRuntime(): DurableBunRuntime {
168
+ // A durable artifact must use the runtime selected BEFORE Bun auto-loaded a
169
+ // project dotenv. The Node launcher and owned service/shim launchers stamp the
170
+ // selected source/path pair; it is accepted only when it names this exact
171
+ // running executable. Re-reading OPENCODEX_BUN_PATH here would let a project
172
+ // `.env` persist an arbitrary executable into a shim or service.
173
+ return recordedCurrentRuntime(process.env) ?? unmarkedDurableBunRuntime();
174
+ }
175
+
63
176
  /**
64
177
  * Bun path to bake into durable artifacts (launchd/systemd/Task Scheduler and
65
178
  * the Codex auto-start shim). Prefer the bundled binary — it lives under the
@@ -3,13 +3,14 @@
3
3
  *
4
4
  * The eager bounded relay (src/server/relay-eager.ts) uses a JS async producer
5
5
  * loop — the exact shape of the Bun#32111 use-after-free (fixed upstream by Bun
6
- * PR #32120, merged 2026-06-21). No RELEASED Bun version is proven to carry
7
- * that fix yet, so `MIN_FIXED_BUN_VERSION` is null: every runtime is
8
- * "known-bad" until a bundle-bump commit sets it. Windows no-rewrite traffic
9
- * follows this runtime/config decision. Darwin no-rewrite traffic stays on tee
6
+ * PR #32120, merged 2026-06-21). Bun 1.4.0 is the first RELEASED version proven
7
+ * to carry that fix, so `MIN_FIXED_BUN_VERSION` is "1.4.0": older runtimes stay
8
+ * "known-bad". Windows no-rewrite traffic
9
+ * follows this runtime/config decision, preserving the explicit legacy-tee
10
+ * safety pin. Darwin no-rewrite traffic stays on tee
10
11
  * for `auto` regardless of runtime capability and reaches eager relay only via
11
12
  * explicit `streamMode: "eager-relay"` opt-in (see
12
- * devlog/_plan/260731_macos_rss_retention/100_darwin_eager_optin.md).
13
+ * devlog/_fin/260731_macos_rss_retention/100_darwin_eager_optin.md).
13
14
  *
14
15
  * Prerelease conservatism: a version carrying a prerelease suffix (e.g.
15
16
  * `1.4.0-canary.3`) is NEVER treated as fixed even when its numeric triple
@@ -20,8 +21,11 @@
20
21
  /**
21
22
  * Bump in the SAME commit that bumps package.json's bundled Bun to a version
22
23
  * verified to include Bun PR #32120. null = no released version is known-fixed.
24
+ * Bun 1.4.0 (npm stable, bundled by this package.json) carries the fix: PR
25
+ * #32120 merged 2026-06-21, well before the 1.4.0 cut, and the full suite ran
26
+ * green under the 1.4 line on every supported OS (devlog/260814_bun14-preview-dev).
23
27
  */
24
- export const MIN_FIXED_BUN_VERSION: string | null = null;
28
+ export const MIN_FIXED_BUN_VERSION: string | null = "1.4.0";
25
29
 
26
30
  export type StreamMode = "auto" | "legacy-tee" | "eager-relay";
27
31
 
@@ -93,7 +97,7 @@ export function decideEagerRelay(
93
97
  * Windows preserves the decision for no-rewrite traffic. Darwin permits only
94
98
  * explicit config opt-in; `auto` remains tee even on a future fixed runtime.
95
99
  * Returns the normalized effective decision, or null when platform policy,
96
- * rewrite needs, or a Darwin non-config-eager mode selects tee.
100
+ * Windows rewrite needs, or a Darwin non-config-eager mode selects tee.
97
101
  */
98
102
  export function selectEagerPath(
99
103
  platform: NodeJS.Platform,
@@ -102,12 +106,12 @@ export function selectEagerPath(
102
106
  version: string = Bun.version,
103
107
  minFixed: string | null = MIN_FIXED_BUN_VERSION,
104
108
  ): EagerRelayDecision | null {
105
- if (needsClientRewrite || (platform !== "win32" && platform !== "darwin")) {
109
+ if (platform !== "win32" && platform !== "darwin") {
106
110
  return null;
107
111
  }
108
112
 
109
113
  const decision = decideEagerRelay(mode, version, minFixed);
110
- if (platform === "win32") return decision;
114
+ if (platform === "win32") return needsClientRewrite ? null : decision;
111
115
  return decision.reason === "config-eager" ? decision : null;
112
116
  }
113
117
 
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Contract for the dashboard-driven Codex app-server restart (#1046 follow-up).
3
+ *
4
+ * Distinct from `system-restart-contract.ts`: that one restarts THIS proxy process
5
+ * and needs a pid-bound capability because it kills its own listener. This one asks
6
+ * matching Codex app-server children to exit so Codex rereads the catalog on next
7
+ * launch. It never touches the proxy and never spawns a replacement — whoever owns
8
+ * the app-server (the Codex app, an SSH bootstrap) relaunches it on next use.
9
+ *
10
+ * Scalar-only payload. A command line can contain a home directory and a username,
11
+ * and an OS error message often embeds a path, so neither crosses this boundary.
12
+ *
13
+ * Design and audit history: `devlog/_fin/260815_gui_codex_restart/`.
14
+ */
15
+
16
+ export const CODEX_RESTART_METHOD = "POST";
17
+ export const CODEX_RESTART_PATH = "/api/system/codex-restart";
18
+ export const CODEX_APP_SERVER_STATE_PATH = "/api/system/codex-app-server";
19
+
20
+ /** Mirrors CodexAppServerCatalogState so the GUI never imports runtime code. */
21
+ export type CodexAppServerState = "fresh" | "stale" | "not_running" | "unknown";
22
+
23
+ export type CodexRestartCode =
24
+ | "stopped"
25
+ | "nothing_running"
26
+ | "enumeration_unavailable"
27
+ | "partially_stopped";
28
+
29
+ /** GET response: a cheap reading with no side effects. It never signals. */
30
+ export interface CodexAppServerStateResponse {
31
+ state: CodexAppServerState;
32
+ runningCount: number;
33
+ }
34
+
35
+ /** POST response. All four arrays are pid lists — never command lines. */
36
+ export interface CodexRestartResponse {
37
+ success: boolean;
38
+ /** Classifier reading taken BEFORE any signal, so the UI can explain why it acted. */
39
+ stateBefore: CodexAppServerState;
40
+ /** Whether a catalog or cache write happened during this request. */
41
+ synced: boolean;
42
+ requested: number[];
43
+ stopped: number[];
44
+ surviving: number[];
45
+ failed: number[];
46
+ code: CodexRestartCode;
47
+ }
48
+
49
+ const APP_SERVER_STATES: readonly string[] = ["fresh", "stale", "not_running", "unknown"];
50
+ const RESTART_CODES: readonly string[] = [
51
+ "stopped",
52
+ "nothing_running",
53
+ "enumeration_unavailable",
54
+ "partially_stopped",
55
+ ];
56
+
57
+ /**
58
+ * A pid is a positive safe integer. Accepting a float or a negative number would
59
+ * let a malformed body reach UI code that renders counts and indexes lengths.
60
+ */
61
+ function isPidList(value: unknown): value is number[] {
62
+ return Array.isArray(value)
63
+ && value.every(entry => typeof entry === "number" && Number.isSafeInteger(entry) && entry > 0);
64
+ }
65
+
66
+ /**
67
+ * Runtime guard for GUI consumers: a 2xx body is not automatically this shape.
68
+ *
69
+ * Structure is not enough. A body can be structurally valid and still contradict
70
+ * itself — `code: "stopped"` alongside surviving pids, or `success: true` with a
71
+ * failure code — and a caller that trusts it reports a success that did not happen.
72
+ * A version-skewed or regressed proxy is exactly how that arrives, so the
73
+ * cross-field invariants are checked here rather than assumed.
74
+ */
75
+ export function isCodexRestartResponse(value: unknown): value is CodexRestartResponse {
76
+ if (typeof value !== "object" || value === null) return false;
77
+ const view = value as Record<string, unknown>;
78
+ const structural = typeof view.success === "boolean"
79
+ && typeof view.synced === "boolean"
80
+ && typeof view.stateBefore === "string"
81
+ && APP_SERVER_STATES.includes(view.stateBefore)
82
+ && typeof view.code === "string"
83
+ && RESTART_CODES.includes(view.code)
84
+ && isPidList(view.requested)
85
+ && isPidList(view.stopped)
86
+ && isPidList(view.surviving)
87
+ && isPidList(view.failed);
88
+ if (!structural) return false;
89
+
90
+ const success = view.success as boolean;
91
+ const code = view.code as CodexRestartCode;
92
+ const surviving = view.surviving as number[];
93
+ const failed = view.failed as number[];
94
+ const stopped = view.stopped as number[];
95
+
96
+ // `success` and `code` must agree: only partially_stopped is an unsuccessful code.
97
+ if (success !== (code !== "partially_stopped")) return false;
98
+ // A clean outcome cannot leave anything behind.
99
+ if (success && (surviving.length > 0 || failed.length > 0)) return false;
100
+ // An unsuccessful outcome must name what survived.
101
+ if (!success && surviving.length === 0 && failed.length === 0) return false;
102
+ // Nothing can be reported stopped when the service says nothing was running.
103
+ if ((code === "nothing_running" || code === "enumeration_unavailable") && stopped.length > 0) {
104
+ return false;
105
+ }
106
+ return true;
107
+ }
108
+
109
+ export function isCodexAppServerStateResponse(
110
+ value: unknown,
111
+ ): value is CodexAppServerStateResponse {
112
+ if (typeof value !== "object" || value === null) return false;
113
+ const view = value as Record<string, unknown>;
114
+ return typeof view.state === "string"
115
+ && APP_SERVER_STATES.includes(view.state)
116
+ && typeof view.runningCount === "number"
117
+ && Number.isSafeInteger(view.runningCount)
118
+ && view.runningCount >= 0;
119
+ }
120
+
@@ -5,7 +5,6 @@ import {
5
5
  readFileSync,
6
6
  readdirSync,
7
7
  realpathSync,
8
- renameSync,
9
8
  rmdirSync,
10
9
  unlinkSync,
11
10
  writeFileSync,
@@ -13,6 +12,7 @@ import {
13
12
  import { randomUUID } from "node:crypto";
14
13
  import { isAbsolute, join, relative, resolve, sep } from "node:path";
15
14
  import type { GenerationContext } from "./state-store-sweeper";
15
+ import { renameAtomicFile } from "./windows-atomic-replace";
16
16
 
17
17
  export const CONFIG_OWNER_FILE = ".opencodex-owner.json";
18
18
  export const CONFIG_UNINSTALL_MANIFEST = ".opencodex-uninstall.json";
@@ -40,6 +40,7 @@ const INITIAL_OWNED_PATHS = [
40
40
  "artifacts",
41
41
  "auth.json",
42
42
  "auth.store.lock",
43
+ "admin-api-token",
43
44
  "catalog-backup.json",
44
45
  "claude-env.sh",
45
46
  "codex-accounts.json",
@@ -69,6 +70,7 @@ const INITIAL_OWNED_PATHS = [
69
70
  "service-state.json",
70
71
  "service.log",
71
72
  "system-env-port",
73
+ "thought-signature-replay.json",
72
74
  "tray-heartbeat.json",
73
75
  "tray-state.json",
74
76
  "update-job.json",
@@ -231,7 +233,10 @@ function writeManifest(configDir: string, manifest: ConfigUninstallManifest): vo
231
233
  const temp = `${path}.${process.pid}.${randomUUID()}.tmp`;
232
234
  writeFileSync(temp, `${JSON.stringify(manifest, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
233
235
  try {
234
- renameSync(temp, path);
236
+ // Same Windows sharing-violation tolerance the config writer has: a
237
+ // scanner holding the manifest must not turn uninstall bookkeeping into a
238
+ // hard failure.
239
+ renameAtomicFile(temp, path, undefined, "config-ownership");
235
240
  } catch (error) {
236
241
  try { unlinkSync(temp); } catch { /* best effort */ }
237
242
  throw error;
@@ -329,6 +334,25 @@ export function removeOwnedConfigState(configDir: string): ConfigRemovalResult {
329
334
  }
330
335
  }
331
336
 
337
+ // Per-catalog backups are named `catalog-backup-<16 hex>.json` (catalogBackupPathFor), one per
338
+ // CODEX_HOME, so they cannot be enumerated as literal manifest entries the way every other
339
+ // owned file can. Without this, `ocx uninstall` always reported "unowned files remain" and
340
+ // refused to remove a home OpenCodex created itself — the file is unambiguously ours, produced
341
+ // by our own writer, and the strict hex shape keeps the match from widening.
342
+ for (const name of readdirSync(configDir)) {
343
+ if (!/^catalog-backup-[0-9a-f]{16}\.json$/.test(name)) continue;
344
+ const path = join(configDir, name);
345
+ try {
346
+ removeOwnedEntry(rootPath, path);
347
+ } catch (error) {
348
+ return {
349
+ status: "partial",
350
+ reason: `could not remove owned path ${name}: ${error instanceof Error ? error.message : String(error)}`,
351
+ residualPaths: [path],
352
+ };
353
+ }
354
+ }
355
+
332
356
  try {
333
357
  unlinkSync(join(configDir, CONFIG_UNINSTALL_MANIFEST));
334
358
  unlinkSync(join(configDir, CONFIG_OWNER_FILE));