@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
@@ -0,0 +1,675 @@
1
+ import {
2
+ closeSync,
3
+ fstatSync,
4
+ lstatSync,
5
+ openSync,
6
+ readFileSync,
7
+ readdirSync,
8
+ realpathSync,
9
+ readSync,
10
+ statSync,
11
+ } from "node:fs";
12
+ import type { Stats } from "node:fs";
13
+ import { basename, dirname, join, resolve } from "node:path";
14
+
15
+ import { Database } from "bun:sqlite";
16
+
17
+ import { getConfigDir } from "../config";
18
+ import { catalogHasRoutedEntries, parseCatalogJson } from "./catalog/parsing";
19
+ import { codexHistoryBackupId, validateCodexHistoryBackupManifest } from "./history-manifest";
20
+ import {
21
+ hasInjectedCodexRouting,
22
+ OCX_SECTION_MARKER,
23
+ providerTableString,
24
+ rootTomlString,
25
+ } from "./injected-marker";
26
+ import {
27
+ CODEX_CONFIG_PATH,
28
+ CODEX_MODELS_CACHE_PATH,
29
+ CODEX_PROFILE_PATH,
30
+ DEFAULT_CATALOG_PATH,
31
+ getCodexHome,
32
+ readRootTomlString,
33
+ resolveCodexStateDbPath,
34
+ } from "./paths";
35
+
36
+ export type NativeResidueSurface =
37
+ | "config"
38
+ | "profile"
39
+ | "catalog"
40
+ | "models-cache"
41
+ | "journal"
42
+ | "partial-write"
43
+ | "history"
44
+ | "history-backup";
45
+
46
+ export type NativeRoutedResidueResult =
47
+ | { kind: "clean" }
48
+ | { kind: "residue"; surface: NativeResidueSurface; path: string }
49
+ | { kind: "indeterminate"; surface: NativeResidueSurface; path: string; reason: string };
50
+
51
+ type ReadResult =
52
+ | { kind: "absent" }
53
+ | { kind: "content"; content: string; path: string }
54
+ | { kind: "indeterminate"; reason: string };
55
+
56
+ type PathResult =
57
+ | { kind: "absent" }
58
+ | { kind: "path"; path: string; stat: Stats }
59
+ | { kind: "indeterminate"; reason: string };
60
+
61
+ type CatalogTarget = {
62
+ path: string;
63
+ configured: boolean;
64
+ };
65
+
66
+ type ConfigObservation = {
67
+ classification: NativeRoutedResidueResult;
68
+ catalogTargets: CatalogTarget[];
69
+ };
70
+
71
+ type RolloutReference = {
72
+ id: string;
73
+ path: string;
74
+ };
75
+
76
+ const CONFIG_FILE_NAME = basename(CODEX_CONFIG_PATH);
77
+ const PROFILE_FILE_NAME = basename(CODEX_PROFILE_PATH);
78
+ const CATALOG_FILE_NAME = basename(DEFAULT_CATALOG_PATH);
79
+ const MODELS_CACHE_FILE_NAME = basename(CODEX_MODELS_CACHE_PATH);
80
+ const JOURNAL_FILE_NAME = "opencodex-journal.json";
81
+ const ROUTED_CATALOG_DESCRIPTION_PREFIX = "Routed via opencodex → ";
82
+ const MAX_ROLLOUT_INSPECTION_BYTES = 64 * 1024 * 1024;
83
+ const ROLLOUT_READ_CHUNK_BYTES = 64 * 1024;
84
+
85
+ function errorCode(error: unknown): string | undefined {
86
+ return (error as NodeJS.ErrnoException | undefined)?.code;
87
+ }
88
+
89
+ function errorReason(error: unknown): string {
90
+ if (error instanceof Error) return `${error.name}: ${error.message}`;
91
+ return String(error);
92
+ }
93
+
94
+ function sameStat(
95
+ left: Stats,
96
+ right: Stats,
97
+ ): boolean {
98
+ return left.dev === right.dev
99
+ && left.ino === right.ino
100
+ && left.size === right.size
101
+ && left.mtimeMs === right.mtimeMs;
102
+ }
103
+
104
+ function resolveRegularFile(path: string): PathResult {
105
+ let entry;
106
+ try {
107
+ entry = lstatSync(path);
108
+ } catch (error) {
109
+ if (errorCode(error) === "ENOENT") return { kind: "absent" };
110
+ return { kind: "indeterminate", reason: errorReason(error) };
111
+ }
112
+
113
+ let target = path;
114
+ if (entry.isSymbolicLink()) {
115
+ try {
116
+ target = realpathSync.native(path);
117
+ } catch (error) {
118
+ return { kind: "indeterminate", reason: `unresolvable symlink: ${errorReason(error)}` };
119
+ }
120
+ }
121
+
122
+ try {
123
+ const before = statSync(target);
124
+ if (!before.isFile()) {
125
+ return { kind: "indeterminate", reason: "surface is not a regular file" };
126
+ }
127
+ return { kind: "path", path: target, stat: before };
128
+ } catch (error) {
129
+ return { kind: "indeterminate", reason: errorReason(error) };
130
+ }
131
+ }
132
+
133
+ function readRegularFile(path: string): ReadResult {
134
+ const resolved = resolveRegularFile(path);
135
+ if (resolved.kind !== "path") return resolved;
136
+ try {
137
+ const content = readFileSync(resolved.path, "utf8");
138
+ const after = statSync(resolved.path);
139
+ if (!sameStat(resolved.stat, after)) {
140
+ return { kind: "indeterminate", reason: "surface changed while it was being observed" };
141
+ }
142
+ return { kind: "content", content, path: resolved.path };
143
+ } catch (error) {
144
+ return { kind: "indeterminate", reason: errorReason(error) };
145
+ }
146
+ }
147
+
148
+ function indeterminate(
149
+ surface: NativeResidueSurface,
150
+ path: string,
151
+ reason: string,
152
+ ): NativeRoutedResidueResult {
153
+ return { kind: "indeterminate", surface, path, reason };
154
+ }
155
+
156
+ function rolloutSessionMetaPayload(
157
+ line: string,
158
+ ): { kind: "payload"; payload: Record<string, unknown> | null } | { kind: "malformed"; reason: string } {
159
+ let parsed: unknown;
160
+ try {
161
+ parsed = JSON.parse(line);
162
+ } catch (error) {
163
+ return { kind: "malformed", reason: `malformed rollout JSONL: ${errorReason(error)}` };
164
+ }
165
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
166
+ return { kind: "malformed", reason: "rollout JSONL record is not an object" };
167
+ }
168
+ const record = parsed as Record<string, unknown>;
169
+ if (record.type !== "session_meta") return { kind: "payload", payload: null };
170
+ if (!record.payload || typeof record.payload !== "object" || Array.isArray(record.payload)) {
171
+ return { kind: "malformed", reason: "session_meta payload has an unknown shape" };
172
+ }
173
+ return { kind: "payload", payload: record.payload as Record<string, unknown> };
174
+ }
175
+
176
+ function consumeRolloutLines(
177
+ surface: "history" | "history-backup",
178
+ path: string,
179
+ partial: string,
180
+ first: Record<string, unknown> | undefined,
181
+ latest: Record<string, unknown> | undefined,
182
+ ): NativeRoutedResidueResult | { kind: "continue"; partial: string; first: Record<string, unknown> | undefined; latest: Record<string, unknown> | undefined } {
183
+ let rest = partial;
184
+ let newline = rest.indexOf("\n");
185
+ while (newline !== -1) {
186
+ const line = rest.slice(0, newline);
187
+ rest = rest.slice(newline + 1);
188
+ if (line.trim()) {
189
+ const payload = rolloutSessionMetaPayload(line);
190
+ if (payload.kind === "malformed") {
191
+ return indeterminate(surface, path, payload.reason);
192
+ }
193
+ if (payload.payload !== null) {
194
+ first ??= payload.payload;
195
+ latest = payload.payload;
196
+ }
197
+ }
198
+ newline = rest.indexOf("\n");
199
+ }
200
+ return { kind: "continue", partial: rest, first, latest };
201
+ }
202
+
203
+ function classifyToml(
204
+ surface: "config" | "profile",
205
+ path: string,
206
+ classify: (content: string) => "clean" | "residue" | "indeterminate",
207
+ ): NativeRoutedResidueResult {
208
+ const read = readRegularFile(path);
209
+ if (read.kind === "absent") return { kind: "clean" };
210
+ if (read.kind === "indeterminate") return indeterminate(surface, path, read.reason);
211
+ try {
212
+ Bun.TOML.parse(read.content);
213
+ } catch (error) {
214
+ return indeterminate(surface, path, `malformed TOML: ${errorReason(error)}`);
215
+ }
216
+ const result = classify(read.content);
217
+ if (result === "residue") return { kind: "residue", surface, path: read.path };
218
+ if (result === "indeterminate") {
219
+ return indeterminate(surface, read.path, "OpenCodex-shaped TOML does not match a complete routed grammar");
220
+ }
221
+ return { kind: "clean" };
222
+ }
223
+
224
+ function catalogPathKey(path: string): string {
225
+ const normalized = resolve(path);
226
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
227
+ }
228
+
229
+ function catalogTargets(
230
+ codexHome: string,
231
+ configuredPaths: readonly string[] = [],
232
+ ): CatalogTarget[] {
233
+ const targets = new Map<string, CatalogTarget>();
234
+ const add = (path: string, configured: boolean) => {
235
+ const key = catalogPathKey(path);
236
+ const existing = targets.get(key);
237
+ targets.set(key, { path: resolve(path), configured: configured || existing?.configured === true });
238
+ };
239
+ for (const configuredPath of configuredPaths) {
240
+ add(resolve(codexHome, configuredPath), true);
241
+ }
242
+ add(join(codexHome, CATALOG_FILE_NAME), false);
243
+ return [...targets.values()];
244
+ }
245
+
246
+ function inspectConfig(codexHome: string, path: string): ConfigObservation {
247
+ const read = readRegularFile(path);
248
+ if (read.kind === "absent") {
249
+ return { classification: { kind: "clean" }, catalogTargets: catalogTargets(codexHome) };
250
+ }
251
+ if (read.kind === "indeterminate") {
252
+ return {
253
+ classification: indeterminate("config", path, read.reason),
254
+ catalogTargets: catalogTargets(codexHome),
255
+ };
256
+ }
257
+
258
+ const productionConfiguredPath = readRootTomlString(read.content, "model_catalog_json");
259
+ const productionConfiguredPaths = productionConfiguredPath === null
260
+ ? []
261
+ : [productionConfiguredPath];
262
+ let parsed: unknown;
263
+ try {
264
+ parsed = Bun.TOML.parse(read.content.replace(/^\uFEFF/, ""));
265
+ } catch (error) {
266
+ return {
267
+ classification: indeterminate("config", read.path, `malformed TOML: ${errorReason(error)}`),
268
+ catalogTargets: catalogTargets(codexHome, productionConfiguredPaths),
269
+ };
270
+ }
271
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
272
+ return {
273
+ classification: indeterminate("config", read.path, "TOML root is not a table"),
274
+ catalogTargets: catalogTargets(codexHome, productionConfiguredPaths),
275
+ };
276
+ }
277
+
278
+ const document = parsed as Record<string, unknown>;
279
+ let targets: CatalogTarget[];
280
+ if (!Object.hasOwn(document, "model_catalog_json")) {
281
+ targets = catalogTargets(codexHome, productionConfiguredPaths);
282
+ } else if (typeof document.model_catalog_json !== "string" || !document.model_catalog_json.trim()) {
283
+ return {
284
+ classification: indeterminate("config", read.path, "model_catalog_json must be one non-empty string"),
285
+ catalogTargets: catalogTargets(codexHome, productionConfiguredPaths),
286
+ };
287
+ } else {
288
+ try {
289
+ targets = catalogTargets(codexHome, [
290
+ ...productionConfiguredPaths,
291
+ document.model_catalog_json,
292
+ ]);
293
+ } catch (error) {
294
+ return {
295
+ classification: indeterminate("config", read.path, `model_catalog_json cannot be resolved: ${errorReason(error)}`),
296
+ catalogTargets: catalogTargets(codexHome, productionConfiguredPaths),
297
+ };
298
+ }
299
+ }
300
+
301
+ let classification: NativeRoutedResidueResult = { kind: "clean" };
302
+ if (hasInjectedCodexRouting(read.content)) {
303
+ classification = { kind: "residue", surface: "config", path: read.path };
304
+ } else {
305
+ const hasMarker = read.content.includes(OCX_SECTION_MARKER);
306
+ const provider = rootTomlString(read.content, "model_provider");
307
+ const providerBaseUrl = providerTableString(read.content, "opencodex", "base_url");
308
+ if (hasMarker || provider === "opencodex" || providerBaseUrl !== null) {
309
+ classification = indeterminate(
310
+ "config",
311
+ read.path,
312
+ "OpenCodex-shaped TOML does not match a complete routed grammar",
313
+ );
314
+ }
315
+ }
316
+ return { classification, catalogTargets: targets };
317
+ }
318
+
319
+ function classifyProfile(path: string): NativeRoutedResidueResult {
320
+ return classifyToml("profile", path, content => {
321
+ const generatedFallback = content.startsWith("# OpenCodex proxy fallback config (Design B)")
322
+ && rootTomlString(content, "openai_base_url") !== null;
323
+ const generatedNamedProfile = content.startsWith("# OpenCodex proxy profile — use with:")
324
+ && hasInjectedCodexRouting(content);
325
+ if (generatedFallback || generatedNamedProfile) return "residue";
326
+ return "indeterminate";
327
+ });
328
+ }
329
+
330
+ function isOcxRoutedCatalogEntry(entry: Record<string, unknown>): boolean {
331
+ return typeof entry.description === "string"
332
+ && entry.description.startsWith(ROUTED_CATALOG_DESCRIPTION_PREFIX);
333
+ }
334
+
335
+ function classifyCatalogLike(
336
+ surface: "catalog" | "models-cache",
337
+ path: string,
338
+ configured = false,
339
+ ): NativeRoutedResidueResult {
340
+ const read = readRegularFile(path);
341
+ if (read.kind === "absent") {
342
+ return configured
343
+ ? indeterminate(surface, path, "configured catalog target is absent")
344
+ : { kind: "clean" };
345
+ }
346
+ if (read.kind === "indeterminate") return indeterminate(surface, path, read.reason);
347
+ const catalog = parseCatalogJson(read.content);
348
+ if (!catalog) return indeterminate(surface, path, "malformed catalog JSON");
349
+ if ((catalog.models ?? []).some(isOcxRoutedCatalogEntry)) {
350
+ return { kind: "residue", surface, path: read.path };
351
+ }
352
+ if (catalogHasRoutedEntries(catalog)) {
353
+ return indeterminate(surface, read.path, "routed catalog rows lack the OpenCodex authorship signature");
354
+ }
355
+ return { kind: "clean" };
356
+ }
357
+
358
+ function isJournal(value: unknown): boolean {
359
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
360
+ const journal = value as Record<string, unknown>;
361
+ return journal.version === 1
362
+ && typeof journal.originalConfig === "string"
363
+ && (journal.originalProfile === null || typeof journal.originalProfile === "string")
364
+ && typeof journal.pid === "number"
365
+ && Number.isInteger(journal.pid)
366
+ && typeof journal.timestamp === "string";
367
+ }
368
+
369
+ function classifyJournal(path: string): NativeRoutedResidueResult {
370
+ const read = readRegularFile(path);
371
+ if (read.kind === "absent") return { kind: "clean" };
372
+ if (read.kind === "indeterminate") return indeterminate("journal", path, read.reason);
373
+ let parsed: unknown;
374
+ try {
375
+ parsed = JSON.parse(read.content);
376
+ } catch (error) {
377
+ return indeterminate("journal", read.path, `malformed journal JSON: ${errorReason(error)}`);
378
+ }
379
+ return isJournal(parsed)
380
+ ? { kind: "residue", surface: "journal", path: read.path }
381
+ : indeterminate("journal", read.path, "journal JSON has an unknown or partial shape");
382
+ }
383
+
384
+ function classifyPartialWrites(targetPaths: string[]): NativeRoutedResidueResult {
385
+ const targetsByParent = new Map<string, { path: string; names: Set<string> }>();
386
+ const addTarget = (path: string) => {
387
+ const parent = dirname(path);
388
+ const key = catalogPathKey(parent);
389
+ const observed = targetsByParent.get(key) ?? { path: parent, names: new Set<string>() };
390
+ observed.names.add(basename(path));
391
+ targetsByParent.set(key, observed);
392
+ };
393
+ for (const path of targetPaths) {
394
+ addTarget(path);
395
+ const resolved = resolveRegularFile(path);
396
+ if (resolved.kind === "path") addTarget(resolved.path);
397
+ }
398
+
399
+ for (const target of targetsByParent.values()) {
400
+ let names: string[];
401
+ try {
402
+ names = readdirSync(target.path);
403
+ } catch (error) {
404
+ return indeterminate("partial-write", target.path, errorReason(error));
405
+ }
406
+ for (const name of names) {
407
+ const match = /^(.*)\.ocx\.\d+\.\d+\.tmp$/.exec(name);
408
+ if (match?.[1] && target.names.has(match[1])) {
409
+ return indeterminate("partial-write", join(target.path, name), "OpenCodex atomic-write artifact is still present");
410
+ }
411
+ }
412
+ }
413
+ return { kind: "clean" };
414
+ }
415
+
416
+ function classifyReferencedRollout(
417
+ surface: "history" | "history-backup",
418
+ reference: RolloutReference,
419
+ ): NativeRoutedResidueResult {
420
+ const resolved = resolveRegularFile(reference.path);
421
+ if (resolved.kind === "absent") {
422
+ return indeterminate(surface, reference.path, "referenced rollout is absent");
423
+ }
424
+ if (resolved.kind === "indeterminate") return indeterminate(surface, reference.path, resolved.reason);
425
+
426
+ let handle: number;
427
+ try {
428
+ handle = openSync(resolved.path, "r");
429
+ } catch (error) {
430
+ return indeterminate(surface, resolved.path, `unreadable rollout: ${errorReason(error)}`);
431
+ }
432
+
433
+ let first: Record<string, unknown> | undefined;
434
+ let latest: Record<string, unknown> | undefined;
435
+ let partial = "";
436
+ let totalRead = 0;
437
+ try {
438
+ const opened = fstatSync(handle);
439
+ if (opened.size > MAX_ROLLOUT_INSPECTION_BYTES) {
440
+ return indeterminate(
441
+ surface,
442
+ resolved.path,
443
+ `referenced rollout exceeds the ${MAX_ROLLOUT_INSPECTION_BYTES} byte inspection limit`,
444
+ );
445
+ }
446
+ const decoder = new TextDecoder("utf-8", { ignoreBOM: true });
447
+ const buffer = Buffer.allocUnsafe(ROLLOUT_READ_CHUNK_BYTES);
448
+ while (totalRead < opened.size) {
449
+ const remaining = Math.min(buffer.length, opened.size - totalRead);
450
+ const count = readSync(handle, buffer, 0, remaining, totalRead);
451
+ if (count === 0) {
452
+ return indeterminate(surface, resolved.path, "rollout read ended before the observed size");
453
+ }
454
+ if (count < 0) {
455
+ return indeterminate(surface, resolved.path, "rollout read ended before the observed size");
456
+ }
457
+ totalRead += count;
458
+ partial += decoder.decode(buffer.subarray(0, count), { stream: true });
459
+ const consumed = consumeRolloutLines(surface, resolved.path, partial, first, latest);
460
+ if (consumed.kind !== "continue") return consumed;
461
+ partial = consumed.partial;
462
+ first = consumed.first;
463
+ latest = consumed.latest;
464
+ }
465
+ partial += decoder.decode();
466
+ const consumed = consumeRolloutLines(surface, resolved.path, partial, first, latest);
467
+ if (consumed.kind !== "continue") return consumed;
468
+ partial = consumed.partial;
469
+ first = consumed.first;
470
+ latest = consumed.latest;
471
+ if (partial.trim()) {
472
+ const payload = rolloutSessionMetaPayload(partial);
473
+ if (payload.kind === "malformed") {
474
+ return indeterminate(surface, resolved.path, payload.reason);
475
+ }
476
+ if (payload.payload !== null) {
477
+ first ??= payload.payload;
478
+ latest = payload.payload;
479
+ }
480
+ }
481
+ const after = fstatSync(handle);
482
+ if (!sameStat(resolved.stat, after)) {
483
+ return indeterminate(surface, resolved.path, "rollout changed while it was being observed");
484
+ }
485
+ const pathAfter = statSync(resolved.path);
486
+ if (!sameStat(resolved.stat, pathAfter)) {
487
+ return indeterminate(surface, resolved.path, "rollout pathname was replaced while it was being observed");
488
+ }
489
+ } catch (error) {
490
+ if (errorCode(error) === "ENOENT") {
491
+ return indeterminate(surface, resolved.path, "referenced rollout is absent");
492
+ }
493
+ return indeterminate(surface, resolved.path, `unreadable rollout: ${errorReason(error)}`);
494
+ } finally {
495
+ try {
496
+ closeSync(handle);
497
+ } catch {
498
+ // Closing an already-closed descriptor cannot affect the classification.
499
+ }
500
+ }
501
+
502
+ if (!first || !latest) {
503
+ return indeterminate(surface, resolved.path, "referenced rollout has no session_meta metadata");
504
+ }
505
+ let hasOpenCodexProvider = false;
506
+ for (const [position, payload] of [["first", first], ["latest", latest]] as const) {
507
+ if (payload.id !== reference.id) {
508
+ return indeterminate(surface, resolved.path, `${position} session_meta does not identify the referenced thread`);
509
+ }
510
+ if (typeof payload.model_provider !== "string" || !payload.model_provider) {
511
+ return indeterminate(surface, resolved.path, `${position} session_meta has no provider metadata`);
512
+ }
513
+ hasOpenCodexProvider = hasOpenCodexProvider || payload.model_provider === "opencodex";
514
+ }
515
+ return hasOpenCodexProvider
516
+ ? { kind: "residue", surface, path: resolved.path }
517
+ : { kind: "clean" };
518
+ }
519
+
520
+ function classifyReferencedRollouts(
521
+ surface: "history" | "history-backup",
522
+ references: RolloutReference[],
523
+ ): NativeRoutedResidueResult {
524
+ for (const reference of references) {
525
+ const result = classifyReferencedRollout(surface, reference);
526
+ if (result.kind !== "clean") return result;
527
+ }
528
+ return { kind: "clean" };
529
+ }
530
+
531
+ function classifyHistoryDatabase(path: string): NativeRoutedResidueResult {
532
+ const resolved = resolveRegularFile(path);
533
+ if (resolved.kind === "absent") {
534
+ for (const suffix of ["-wal", "-shm"]) {
535
+ const sidecar = resolveRegularFile(`${path}${suffix}`);
536
+ if (sidecar.kind !== "absent") {
537
+ const reason = sidecar.kind === "indeterminate"
538
+ ? sidecar.reason
539
+ : "SQLite sidecar exists without its history database";
540
+ return indeterminate("history", `${path}${suffix}`, reason);
541
+ }
542
+ }
543
+ return { kind: "clean" };
544
+ }
545
+ if (resolved.kind === "indeterminate") return indeterminate("history", path, resolved.reason);
546
+ let database: Database | undefined;
547
+ try {
548
+ database = new Database(resolved.path, { readonly: true });
549
+ database.exec("PRAGMA busy_timeout = 100");
550
+ const rows = database.query<{ id: string; rollout_path: string; model_provider: string }, []>(`
551
+ SELECT id, rollout_path, model_provider
552
+ FROM threads
553
+ `).all();
554
+ for (const row of rows) {
555
+ if (typeof row.id !== "string" || !row.id || typeof row.rollout_path !== "string" || !row.rollout_path) {
556
+ return indeterminate("history", resolved.path, "history row has an unknown rollout reference");
557
+ }
558
+ if (typeof row.model_provider !== "string" || !row.model_provider) {
559
+ return indeterminate("history", resolved.path, "history row has no provider metadata");
560
+ }
561
+ }
562
+ // A bare opencodex row is not proof that OpenCodex owns a reversible transition: it may
563
+ // belong to any routed provider and has no native target without the backup manifest.
564
+ // Keep detecting interrupted metadata on native rows, but let the manifest classifier
565
+ // below be the authority for provenance-backed routed rows.
566
+ const rollouts = classifyReferencedRollouts(
567
+ "history",
568
+ rows
569
+ .filter(row => row.model_provider !== "opencodex")
570
+ .map(row => ({ id: row.id, path: row.rollout_path })),
571
+ );
572
+ if (rollouts.kind !== "clean") return rollouts;
573
+ const after = statSync(resolved.path);
574
+ if (!sameStat(resolved.stat, after)) {
575
+ return indeterminate("history", resolved.path, "history database changed while it was being observed");
576
+ }
577
+ return { kind: "clean" };
578
+ } catch (error) {
579
+ return indeterminate("history", resolved.path, `unreadable history database: ${errorReason(error)}`);
580
+ } finally {
581
+ try { database?.close(); } catch { /* the observation already failed closed */ }
582
+ }
583
+ }
584
+
585
+ function historyBackupPath(stateDatabasePath: string): string {
586
+ return join(getConfigDir(), `codex-history-backup-${codexHistoryBackupId(stateDatabasePath)}.json`);
587
+ }
588
+
589
+ function classifyHistoryBackup(path: string, stateDatabasePath: string): NativeRoutedResidueResult {
590
+ const read = readRegularFile(path);
591
+ if (read.kind === "absent") return { kind: "clean" };
592
+ if (read.kind === "indeterminate") return indeterminate("history-backup", path, read.reason);
593
+ let parsed: unknown;
594
+ try {
595
+ parsed = JSON.parse(read.content);
596
+ } catch (error) {
597
+ return indeterminate("history-backup", read.path, `malformed history backup JSON: ${errorReason(error)}`);
598
+ }
599
+ const validated = validateCodexHistoryBackupManifest(parsed, stateDatabasePath);
600
+ if (!validated.ok && validated.reason === "foreign-database") {
601
+ return indeterminate("history-backup", read.path, "history backup names a different state database");
602
+ }
603
+ if (!validated.ok) {
604
+ return indeterminate(
605
+ "history-backup",
606
+ read.path,
607
+ validated.scope === "entry-shape"
608
+ ? "history backup entry has an unknown shape"
609
+ : validated.scope === "entry-provenance"
610
+ ? "history backup entry has invalid provenance metadata"
611
+ : "history backup has an unknown shape",
612
+ );
613
+ }
614
+ const entries = Object.entries(validated.manifest.entries);
615
+ const references: RolloutReference[] = [];
616
+ for (const entry of Object.values(validated.manifest.entries)) {
617
+ references.push({ id: entry.id, path: entry.rolloutPath });
618
+ }
619
+ const rollouts = classifyReferencedRollouts("history-backup", references);
620
+ if (rollouts.kind !== "clean") return rollouts;
621
+ return entries.length > 0
622
+ ? { kind: "residue", surface: "history-backup", path: read.path }
623
+ : { kind: "clean" };
624
+ }
625
+
626
+ /** Read-only, fail-closed observation of every OpenCodex-routed Codex surface. */
627
+ export function classifyNativeRoutedResidue(): NativeRoutedResidueResult {
628
+ let codexHome: string;
629
+ try {
630
+ codexHome = getCodexHome();
631
+ } catch (error) {
632
+ const unresolved = process.env.CODEX_HOME?.trim() || "CODEX_HOME";
633
+ return indeterminate("partial-write", unresolved, `CODEX_HOME cannot be resolved: ${errorReason(error)}`);
634
+ }
635
+
636
+ const configPath = join(codexHome, CONFIG_FILE_NAME);
637
+ let stateDatabasePath: string;
638
+ try {
639
+ stateDatabasePath = resolveCodexStateDbPath({ codexHome });
640
+ } catch (error) {
641
+ // Residue classification is a total, read-only safety boundary. An
642
+ // indeterminate SQLite authority must refuse coordination without escaping
643
+ // as an exception or falling through to a different state database.
644
+ return indeterminate(
645
+ "config",
646
+ configPath,
647
+ `SQLite home cannot be resolved: ${errorReason(error)}`,
648
+ );
649
+ }
650
+ const profilePath = join(codexHome, PROFILE_FILE_NAME);
651
+ const modelsCachePath = join(codexHome, MODELS_CACHE_FILE_NAME);
652
+ const journalPath = join(codexHome, JOURNAL_FILE_NAME);
653
+ const config = inspectConfig(codexHome, configPath);
654
+ const atomicWriteTargets = [
655
+ configPath,
656
+ profilePath,
657
+ modelsCachePath,
658
+ journalPath,
659
+ ...config.catalogTargets.map(target => target.path),
660
+ ];
661
+ const classifiers = [
662
+ () => classifyPartialWrites(atomicWriteTargets),
663
+ () => config.classification,
664
+ () => classifyProfile(profilePath),
665
+ ...config.catalogTargets.map(target => () => classifyCatalogLike("catalog", target.path, target.configured)),
666
+ () => classifyCatalogLike("models-cache", modelsCachePath),
667
+ () => classifyJournal(journalPath),
668
+ () => classifyHistoryDatabase(stateDatabasePath),
669
+ () => classifyHistoryBackup(historyBackupPath(stateDatabasePath), stateDatabasePath),
670
+ ];
671
+ const results = classifiers.map(classify => classify());
672
+ return results.find(result => result.kind === "indeterminate")
673
+ ?? results.find(result => result.kind === "residue")
674
+ ?? { kind: "clean" };
675
+ }