@yansigit/opencodex 2.31.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 (826) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/LICENSE +21 -0
  3. package/README.md +303 -0
  4. package/assets/architecture.png +0 -0
  5. package/assets/banner.png +0 -0
  6. package/assets/claude-code-models.gif +0 -0
  7. package/assets/codex-app-picker.png +0 -0
  8. package/bin/ocx.mjs +587 -0
  9. package/bin/package-main.mjs +9 -0
  10. package/gui/dist/assets/index-BNJ7r4Gd.js +102 -0
  11. package/gui/dist/assets/index-CGoDO3uO.css +1 -0
  12. package/gui/dist/favicon.png +0 -0
  13. package/gui/dist/icons.svg +24 -0
  14. package/gui/dist/index.html +25 -0
  15. package/gui/dist/logo.png +0 -0
  16. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  17. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  18. package/gui/dist/provider-icons/claude-color.svg +1 -0
  19. package/gui/dist/provider-icons/cline-color.svg +16 -0
  20. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  21. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  22. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  23. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  24. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  25. package/gui/dist/provider-icons/discord.svg +1 -0
  26. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  27. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  28. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  29. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  30. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  31. package/gui/dist/provider-icons/grok.svg +1 -0
  32. package/gui/dist/provider-icons/groq-color.svg +1 -0
  33. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  34. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  35. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  36. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  37. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  38. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  39. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  40. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  41. package/gui/dist/provider-icons/openai.svg +1 -0
  42. package/gui/dist/provider-icons/opencode.svg +2 -0
  43. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  44. package/gui/dist/provider-icons/pi.svg +21 -0
  45. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  46. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  47. package/gui/dist/provider-icons/telegram.svg +1 -0
  48. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  49. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  50. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  51. package/package.json +108 -0
  52. package/src/AGENTS.md +28 -0
  53. package/src/adapters/anthropic-image-guard.ts +251 -0
  54. package/src/adapters/anthropic-image-normalize.ts +518 -0
  55. package/src/adapters/anthropic-output-schema.ts +137 -0
  56. package/src/adapters/anthropic.ts +1327 -0
  57. package/src/adapters/azure.ts +36 -0
  58. package/src/adapters/base.ts +121 -0
  59. package/src/adapters/client-fingerprint.ts +65 -0
  60. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  61. package/src/adapters/command-code.ts +601 -0
  62. package/src/adapters/cursor/arg-codec.ts +38 -0
  63. package/src/adapters/cursor/arg-normalize.ts +104 -0
  64. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  65. package/src/adapters/cursor/cursor-errors.ts +288 -0
  66. package/src/adapters/cursor/discovery.ts +333 -0
  67. package/src/adapters/cursor/effort-map.ts +151 -0
  68. package/src/adapters/cursor/exec-policy.ts +88 -0
  69. package/src/adapters/cursor/framing.ts +250 -0
  70. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  71. package/src/adapters/cursor/h2-pool.ts +123 -0
  72. package/src/adapters/cursor/http1-bidi.ts +361 -0
  73. package/src/adapters/cursor/images.ts +704 -0
  74. package/src/adapters/cursor/kv-store.ts +52 -0
  75. package/src/adapters/cursor/live-models.ts +269 -0
  76. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  77. package/src/adapters/cursor/live-transport.ts +1653 -0
  78. package/src/adapters/cursor/mcp-config.ts +42 -0
  79. package/src/adapters/cursor/mcp-manager.ts +333 -0
  80. package/src/adapters/cursor/message-mapper.ts +49 -0
  81. package/src/adapters/cursor/native-exec-common.ts +76 -0
  82. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  83. package/src/adapters/cursor/native-exec-fs.ts +332 -0
  84. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  85. package/src/adapters/cursor/native-exec-network.ts +43 -0
  86. package/src/adapters/cursor/native-exec-shell.ts +547 -0
  87. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  88. package/src/adapters/cursor/native-exec.ts +663 -0
  89. package/src/adapters/cursor/protobuf-events.ts +1381 -0
  90. package/src/adapters/cursor/protobuf-request.ts +1032 -0
  91. package/src/adapters/cursor/request-builder.ts +461 -0
  92. package/src/adapters/cursor/thread-continuity.ts +67 -0
  93. package/src/adapters/cursor/tool-definitions.ts +735 -0
  94. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  95. package/src/adapters/cursor/transport-retry.ts +132 -0
  96. package/src/adapters/cursor/transport.ts +79 -0
  97. package/src/adapters/cursor/types.ts +79 -0
  98. package/src/adapters/cursor.ts +322 -0
  99. package/src/adapters/google-antigravity-hosts.ts +48 -0
  100. package/src/adapters/google-antigravity-replay.ts +827 -0
  101. package/src/adapters/google-antigravity-tools.ts +105 -0
  102. package/src/adapters/google-antigravity-wire.ts +141 -0
  103. package/src/adapters/google-errors.ts +92 -0
  104. package/src/adapters/google-http.ts +460 -0
  105. package/src/adapters/google-tool-schema.ts +238 -0
  106. package/src/adapters/google-truncation.ts +24 -0
  107. package/src/adapters/google-wire-compiler.ts +232 -0
  108. package/src/adapters/google.ts +1377 -0
  109. package/src/adapters/identity.ts +77 -0
  110. package/src/adapters/image.ts +23 -0
  111. package/src/adapters/kiro-constants.ts +16 -0
  112. package/src/adapters/kiro-errors.ts +208 -0
  113. package/src/adapters/kiro-events.ts +197 -0
  114. package/src/adapters/kiro-images.ts +129 -0
  115. package/src/adapters/kiro-retry.ts +312 -0
  116. package/src/adapters/kiro-thinking.ts +112 -0
  117. package/src/adapters/kiro-tool-fallback.ts +36 -0
  118. package/src/adapters/kiro-tools.ts +224 -0
  119. package/src/adapters/kiro-truncation.ts +33 -0
  120. package/src/adapters/kiro-wire.ts +129 -0
  121. package/src/adapters/kiro.ts +1936 -0
  122. package/src/adapters/mimo-free.ts +280 -0
  123. package/src/adapters/openai-chat-url.ts +11 -0
  124. package/src/adapters/openai-chat.ts +1980 -0
  125. package/src/adapters/openai-responses-url.ts +16 -0
  126. package/src/adapters/openai-responses.ts +1911 -0
  127. package/src/adapters/registry.ts +175 -0
  128. package/src/adapters/responses-tool-schema.ts +67 -0
  129. package/src/adapters/run-turn-queue.ts +114 -0
  130. package/src/adapters/tool-call-id.ts +119 -0
  131. package/src/adapters/tool-catalog-nudge.ts +154 -0
  132. package/src/adapters/upstream-http-error.ts +48 -0
  133. package/src/adapters/xai-web-search.ts +185 -0
  134. package/src/bridge.ts +1986 -0
  135. package/src/chat/inbound.ts +319 -0
  136. package/src/chat/outbound.ts +821 -0
  137. package/src/claude/agents-inject.ts +266 -0
  138. package/src/claude/alias.ts +149 -0
  139. package/src/claude/auth-detect.ts +229 -0
  140. package/src/claude/auth-mode-migration.ts +32 -0
  141. package/src/claude/auth-mode.ts +62 -0
  142. package/src/claude/context-windows.ts +205 -0
  143. package/src/claude/desktop-3p-guard.ts +35 -0
  144. package/src/claude/desktop-3p-paths.ts +84 -0
  145. package/src/claude/desktop-3p.ts +615 -0
  146. package/src/claude/desktop-health.ts +26 -0
  147. package/src/claude/desktop-profile.ts +263 -0
  148. package/src/claude/gateway-cache.ts +107 -0
  149. package/src/claude/inbound-debug.ts +163 -0
  150. package/src/claude/inbound.ts +578 -0
  151. package/src/claude/model-info.ts +174 -0
  152. package/src/claude/outbound.ts +926 -0
  153. package/src/cli/access.ts +108 -0
  154. package/src/cli/account-api.ts +302 -0
  155. package/src/cli/account-auth.ts +250 -0
  156. package/src/cli/account-catalog-refresh.ts +14 -0
  157. package/src/cli/account-extended.ts +737 -0
  158. package/src/cli/account-main.ts +317 -0
  159. package/src/cli/account.ts +299 -0
  160. package/src/cli/agent-driven.ts +70 -0
  161. package/src/cli/agent.ts +290 -0
  162. package/src/cli/catalog-prewarm.ts +27 -0
  163. package/src/cli/claude-agent-startup-sync.ts +73 -0
  164. package/src/cli/claude-desktop.ts +213 -0
  165. package/src/cli/claude.ts +355 -0
  166. package/src/cli/codex-log-guard-doctor.ts +103 -0
  167. package/src/cli/codex-shim-autorestore.ts +47 -0
  168. package/src/cli/codex-shim-readiness.ts +76 -0
  169. package/src/cli/combo.ts +127 -0
  170. package/src/cli/config-command.ts +209 -0
  171. package/src/cli/debug.ts +228 -0
  172. package/src/cli/dispatch.ts +585 -0
  173. package/src/cli/doctor.ts +1202 -0
  174. package/src/cli/ensure-desired-integrations.ts +152 -0
  175. package/src/cli/export-command.ts +213 -0
  176. package/src/cli/help.ts +101 -0
  177. package/src/cli/index.ts +973 -0
  178. package/src/cli/init.ts +211 -0
  179. package/src/cli/integrations.ts +260 -0
  180. package/src/cli/interactive-confirm.ts +133 -0
  181. package/src/cli/lab.ts +607 -0
  182. package/src/cli/launcher-context.ts +77 -0
  183. package/src/cli/minimax.ts +497 -0
  184. package/src/cli/models-runtime.ts +245 -0
  185. package/src/cli/models.ts +422 -0
  186. package/src/cli/observe.ts +206 -0
  187. package/src/cli/opencode.ts +588 -0
  188. package/src/cli/provider-replit.ts +232 -0
  189. package/src/cli/provider-runtime.ts +179 -0
  190. package/src/cli/provider.ts +492 -0
  191. package/src/cli/ready.ts +301 -0
  192. package/src/cli/registry.ts +422 -0
  193. package/src/cli/replit-gateway-key-input.ts +138 -0
  194. package/src/cli/root.ts +86 -0
  195. package/src/cli/route-policy.ts +92 -0
  196. package/src/cli/runtime-api.ts +328 -0
  197. package/src/cli/star-prompt.ts +211 -0
  198. package/src/cli/status-oauth.ts +78 -0
  199. package/src/cli/status.ts +328 -0
  200. package/src/cli/system-command.ts +112 -0
  201. package/src/cli/system-restart-client.ts +146 -0
  202. package/src/cli/tray-proxy.ts +199 -0
  203. package/src/cli/v2.ts +268 -0
  204. package/src/cli.ts +10 -0
  205. package/src/clients/config-export.ts +1704 -0
  206. package/src/codex/account-id.ts +34 -0
  207. package/src/codex/account-label.ts +47 -0
  208. package/src/codex/account-lifecycle.ts +172 -0
  209. package/src/codex/account-namespace-match.ts +63 -0
  210. package/src/codex/account-namespaces.ts +195 -0
  211. package/src/codex/account-pause.ts +20 -0
  212. package/src/codex/account-priority.ts +83 -0
  213. package/src/codex/account-runtime-state.ts +31 -0
  214. package/src/codex/account-store.ts +544 -0
  215. package/src/codex/account-usability.ts +43 -0
  216. package/src/codex/admission.ts +256 -0
  217. package/src/codex/affinity-debug.ts +162 -0
  218. package/src/codex/agent-roles-sync.ts +225 -0
  219. package/src/codex/agent-roles.ts +238 -0
  220. package/src/codex/app-server-processes.ts +1143 -0
  221. package/src/codex/app-server-restart-service.ts +232 -0
  222. package/src/codex/auth-api.ts +2147 -0
  223. package/src/codex/auth-collision.ts +109 -0
  224. package/src/codex/auth-context.ts +665 -0
  225. package/src/codex/autostart-health.ts +156 -0
  226. package/src/codex/catalog/account-models.ts +67 -0
  227. package/src/codex/catalog/aggregation.ts +436 -0
  228. package/src/codex/catalog/bundled.ts +549 -0
  229. package/src/codex/catalog/effort.ts +446 -0
  230. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  231. package/src/codex/catalog/kinds.ts +2 -0
  232. package/src/codex/catalog/metadata.ts +664 -0
  233. package/src/codex/catalog/native-models.ts +72 -0
  234. package/src/codex/catalog/parsing.ts +650 -0
  235. package/src/codex/catalog/provider-fetch.ts +2064 -0
  236. package/src/codex/catalog/sync.ts +1883 -0
  237. package/src/codex/catalog-admission.ts +199 -0
  238. package/src/codex/catalog-refresh-status.ts +105 -0
  239. package/src/codex/catalog-write-serialization.ts +242 -0
  240. package/src/codex/catalog.ts +14 -0
  241. package/src/codex/codex-write-lock.ts +384 -0
  242. package/src/codex/convergence-types.ts +614 -0
  243. package/src/codex/convergence.ts +651 -0
  244. package/src/codex/coordinator-doctor.ts +332 -0
  245. package/src/codex/custom-model-catalog-migration.ts +176 -0
  246. package/src/codex/data/upstream-models.json +830 -0
  247. package/src/codex/desired-state.ts +230 -0
  248. package/src/codex/exec-invocation.ts +22 -0
  249. package/src/codex/features.ts +1566 -0
  250. package/src/codex/generation.ts +202 -0
  251. package/src/codex/history-job.ts +407 -0
  252. package/src/codex/history-lock.ts +242 -0
  253. package/src/codex/history-migration-guardian.ts +108 -0
  254. package/src/codex/history-provider.ts +979 -0
  255. package/src/codex/history-transition.ts +105 -0
  256. package/src/codex/history-worker.ts +220 -0
  257. package/src/codex/home.ts +206 -0
  258. package/src/codex/inject-coordination.ts +290 -0
  259. package/src/codex/inject.ts +1733 -0
  260. package/src/codex/injected-marker.ts +106 -0
  261. package/src/codex/integration-record.ts +266 -0
  262. package/src/codex/internal/catalog-writer.ts +203 -0
  263. package/src/codex/internal/history-writer.ts +80 -0
  264. package/src/codex/journal.ts +225 -0
  265. package/src/codex/log-guard/inspect.ts +506 -0
  266. package/src/codex/log-guard/lock.ts +150 -0
  267. package/src/codex/log-guard/maintenance.ts +403 -0
  268. package/src/codex/log-guard/path-safety.ts +88 -0
  269. package/src/codex/log-guard/policy.ts +44 -0
  270. package/src/codex/log-guard/processes.ts +205 -0
  271. package/src/codex/log-guard/protection.ts +489 -0
  272. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  273. package/src/codex/main-account-cache.ts +56 -0
  274. package/src/codex/main-account.ts +68 -0
  275. package/src/codex/management-convergence.ts +167 -0
  276. package/src/codex/model-cache.ts +273 -0
  277. package/src/codex/model-entitlements.ts +353 -0
  278. package/src/codex/native-main-admission.ts +47 -0
  279. package/src/codex/native-main-auth-temp.ts +187 -0
  280. package/src/codex/native-main-claim.ts +178 -0
  281. package/src/codex/native-main-lock-file.ts +162 -0
  282. package/src/codex/native-main-owner.ts +329 -0
  283. package/src/codex/native-profile-api.ts +247 -0
  284. package/src/codex/native-profile-manager.ts +1531 -0
  285. package/src/codex/native-profile-processes.ts +121 -0
  286. package/src/codex/native-profile-recovery.ts +99 -0
  287. package/src/codex/native-profile-stage-store.ts +387 -0
  288. package/src/codex/native-profile-startup.ts +492 -0
  289. package/src/codex/native-profile-store.ts +855 -0
  290. package/src/codex/native-profile-types.ts +120 -0
  291. package/src/codex/native-residue.ts +682 -0
  292. package/src/codex/paths.ts +144 -0
  293. package/src/codex/plan-from-token.ts +140 -0
  294. package/src/codex/plan.ts +40 -0
  295. package/src/codex/plugins-doctor.ts +242 -0
  296. package/src/codex/pool-rotation.ts +295 -0
  297. package/src/codex/project-config-warnings.ts +425 -0
  298. package/src/codex/prompt-journal.ts +352 -0
  299. package/src/codex/prompt-layers.ts +967 -0
  300. package/src/codex/prompt-lock.ts +143 -0
  301. package/src/codex/quota-rejection.ts +298 -0
  302. package/src/codex/quota.ts +573 -0
  303. package/src/codex/refresh.ts +62 -0
  304. package/src/codex/reset-credit-recovery.ts +1044 -0
  305. package/src/codex/routing.ts +1888 -0
  306. package/src/codex/runtime.ts +659 -0
  307. package/src/codex/shim.ts +2170 -0
  308. package/src/codex/subagent-defaults.ts +550 -0
  309. package/src/codex/subagent-model-fallback.ts +784 -0
  310. package/src/codex/sync.ts +319 -0
  311. package/src/codex/transition-state.ts +612 -0
  312. package/src/codex/upstream-host-health.ts +368 -0
  313. package/src/codex/user-identity.ts +557 -0
  314. package/src/codex/warmup.ts +298 -0
  315. package/src/codex/websocket-registry.ts +100 -0
  316. package/src/codex/write-coordination.ts +114 -0
  317. package/src/combos/failover.ts +160 -0
  318. package/src/combos/index.ts +45 -0
  319. package/src/combos/request.ts +94 -0
  320. package/src/combos/resolve.ts +232 -0
  321. package/src/combos/types.ts +398 -0
  322. package/src/config/provider-name.ts +24 -0
  323. package/src/config.ts +4041 -0
  324. package/src/fork/register.ts +3 -0
  325. package/src/generated/compatibility-version.json +3116 -0
  326. package/src/generated/model-metadata.ts +106 -0
  327. package/src/github/star-state.ts +203 -0
  328. package/src/grok/inject.ts +530 -0
  329. package/src/grok/inspect.ts +45 -0
  330. package/src/grok/status.ts +121 -0
  331. package/src/grok/sync.ts +66 -0
  332. package/src/images/artifacts.ts +516 -0
  333. package/src/images/fulfill-video.ts +163 -0
  334. package/src/images/fulfill.ts +149 -0
  335. package/src/images/index.ts +4 -0
  336. package/src/images/loop.ts +955 -0
  337. package/src/images/plan.ts +143 -0
  338. package/src/images/synthetic-tool.ts +133 -0
  339. package/src/images/types.ts +41 -0
  340. package/src/images/xai-client.ts +141 -0
  341. package/src/images/xai-video-client.ts +163 -0
  342. package/src/index.ts +22 -0
  343. package/src/integrations/config-io.ts +269 -0
  344. package/src/integrations/journal.ts +315 -0
  345. package/src/integrations/merge.ts +135 -0
  346. package/src/integrations/mutation-flight.ts +71 -0
  347. package/src/integrations/native/ownership-preflight.ts +202 -0
  348. package/src/integrations/omp-yaml-source.ts +358 -0
  349. package/src/integrations/owned-refresh.ts +74 -0
  350. package/src/integrations/ownership.ts +111 -0
  351. package/src/integrations/registry.ts +159 -0
  352. package/src/integrations/serialize.ts +314 -0
  353. package/src/integrations/state.ts +361 -0
  354. package/src/integrations/store.ts +103 -0
  355. package/src/integrations/writer-lock.ts +98 -0
  356. package/src/integrations/writer.ts +691 -0
  357. package/src/lab/artifacts/sanitize.ts +586 -0
  358. package/src/lab/artifacts/secure-fs.ts +475 -0
  359. package/src/lab/artifacts/store.ts +310 -0
  360. package/src/lab/automation/budgets.ts +78 -0
  361. package/src/lab/automation/config-persistence.ts +256 -0
  362. package/src/lab/automation/constants.ts +39 -0
  363. package/src/lab/automation/cooldown.ts +103 -0
  364. package/src/lab/automation/dispatch.ts +211 -0
  365. package/src/lab/automation/index.ts +13 -0
  366. package/src/lab/automation/orchestrator.ts +499 -0
  367. package/src/lab/automation/persistence.ts +512 -0
  368. package/src/lab/automation/planner.ts +371 -0
  369. package/src/lab/automation/policy.ts +136 -0
  370. package/src/lab/automation/queue.ts +191 -0
  371. package/src/lab/automation/recovery.ts +24 -0
  372. package/src/lab/automation/route-context.ts +21 -0
  373. package/src/lab/automation/run-key.ts +44 -0
  374. package/src/lab/automation/runs-query.ts +34 -0
  375. package/src/lab/automation/types.ts +160 -0
  376. package/src/lab/conformance/assertion.ts +325 -0
  377. package/src/lab/conformance/digest.ts +22 -0
  378. package/src/lab/conformance/executor.ts +741 -0
  379. package/src/lab/conformance/fixture-provider.ts +27 -0
  380. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  381. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  382. package/src/lab/conformance/harness-budget.ts +47 -0
  383. package/src/lab/conformance/index.ts +5 -0
  384. package/src/lab/conformance/jcs.ts +64 -0
  385. package/src/lab/conformance/json-pointer.ts +39 -0
  386. package/src/lab/conformance/manifest.ts +180 -0
  387. package/src/lab/conformance/mcp-stub.ts +179 -0
  388. package/src/lab/conformance/negative-controls.ts +164 -0
  389. package/src/lab/conformance/observation.ts +355 -0
  390. package/src/lab/conformance/runner.ts +68 -0
  391. package/src/lab/conformance/sse-normalize.ts +59 -0
  392. package/src/lab/conformance/suite-manifest.ts +78 -0
  393. package/src/lab/conformance/types.ts +214 -0
  394. package/src/lab/constants.ts +126 -0
  395. package/src/lab/digest.ts +64 -0
  396. package/src/lab/events/errors.ts +9 -0
  397. package/src/lab/events/limits.ts +117 -0
  398. package/src/lab/events/types.ts +229 -0
  399. package/src/lab/events/validate.ts +781 -0
  400. package/src/lab/fabric/constants.ts +40 -0
  401. package/src/lab/fabric/executor.ts +492 -0
  402. package/src/lab/fabric/index.ts +80 -0
  403. package/src/lab/fabric/manifest.ts +222 -0
  404. package/src/lab/fabric/observe.ts +489 -0
  405. package/src/lab/fabric/patch.ts +79 -0
  406. package/src/lab/fabric/producer-child.ts +139 -0
  407. package/src/lab/fabric/producer-isolate.ts +276 -0
  408. package/src/lab/fabric/producer-protocol.ts +61 -0
  409. package/src/lab/fabric/scratch.ts +439 -0
  410. package/src/lab/fabric/subject.ts +106 -0
  411. package/src/lab/fabric/types.ts +134 -0
  412. package/src/lab/fabric/verifier.ts +98 -0
  413. package/src/lab/index.ts +54 -0
  414. package/src/lab/ledger/artifact-refs.ts +127 -0
  415. package/src/lab/ledger/invalidation.ts +136 -0
  416. package/src/lab/ledger/purge.ts +310 -0
  417. package/src/lab/ledger/store.ts +532 -0
  418. package/src/lab/live/credential-lease.ts +53 -0
  419. package/src/lab/live/destination.ts +155 -0
  420. package/src/lab/live/executor.ts +336 -0
  421. package/src/lab/live/inert-tools.ts +56 -0
  422. package/src/lab/live/manifest.ts +85 -0
  423. package/src/lab/live/mcp-loopback.ts +57 -0
  424. package/src/lab/live/runner.ts +19 -0
  425. package/src/lab/live/sandbox.ts +61 -0
  426. package/src/lab/live/suite-manifest.ts +41 -0
  427. package/src/lab/live/transport.ts +118 -0
  428. package/src/lab/live/types.ts +197 -0
  429. package/src/lab/observe/from-conformance.ts +301 -0
  430. package/src/lab/observe/from-live.ts +117 -0
  431. package/src/lab/paths.ts +153 -0
  432. package/src/lab/projection/rebuild.ts +495 -0
  433. package/src/lab/projection/schema.ts +135 -0
  434. package/src/lab/projection/verdicts.ts +474 -0
  435. package/src/lab/projection/verification.ts +412 -0
  436. package/src/lab/public/bundle.ts +217 -0
  437. package/src/lab/public/community-authority.ts +175 -0
  438. package/src/lab/public/community-files.ts +29 -0
  439. package/src/lab/public/community.ts +479 -0
  440. package/src/lab/public/file-safety.ts +155 -0
  441. package/src/lab/public/ids.ts +26 -0
  442. package/src/lab/public/index.ts +16 -0
  443. package/src/lab/public/mutation-lock.ts +424 -0
  444. package/src/lab/public/operator.ts +353 -0
  445. package/src/lab/public/origin-purge.ts +79 -0
  446. package/src/lab/public/origin.ts +203 -0
  447. package/src/lab/public/privacy.ts +143 -0
  448. package/src/lab/public/private-file.ts +261 -0
  449. package/src/lab/public/project.ts +124 -0
  450. package/src/lab/public/purge-test-fault.ts +21 -0
  451. package/src/lab/public/purge.ts +223 -0
  452. package/src/lab/public/registry.ts +44 -0
  453. package/src/lab/public/revocation.ts +252 -0
  454. package/src/lab/public/signature.ts +243 -0
  455. package/src/lab/public/storage.ts +105 -0
  456. package/src/lab/public/strict-json.ts +206 -0
  457. package/src/lab/public/time.ts +26 -0
  458. package/src/lab/public/types.ts +172 -0
  459. package/src/lab/public/validate.ts +391 -0
  460. package/src/lab/query/catalog.ts +101 -0
  461. package/src/lab/query/connection.ts +107 -0
  462. package/src/lab/query/constants.ts +4 -0
  463. package/src/lab/query/cursor.ts +132 -0
  464. package/src/lab/query/dto-map.ts +277 -0
  465. package/src/lab/query/errors.ts +22 -0
  466. package/src/lab/query/freshness.ts +53 -0
  467. package/src/lab/query/index.ts +45 -0
  468. package/src/lab/query/latest-observation.ts +59 -0
  469. package/src/lab/query/passive-production.ts +159 -0
  470. package/src/lab/query/queries.ts +444 -0
  471. package/src/lab/query/types.ts +266 -0
  472. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  473. package/src/lab/subject/installation-salt.ts +112 -0
  474. package/src/lab/subject/protocol-subject.ts +80 -0
  475. package/src/lab/subject/route-subject.ts +74 -0
  476. package/src/lib/abort.ts +146 -0
  477. package/src/lib/admin-secrets.ts +25 -0
  478. package/src/lib/admission.ts +83 -0
  479. package/src/lib/app-owned-memory-stores.ts +195 -0
  480. package/src/lib/app-owned-memory.ts +265 -0
  481. package/src/lib/bounded-body.ts +346 -0
  482. package/src/lib/bun-binary-validator.d.mts +3 -0
  483. package/src/lib/bun-binary-validator.mjs +18 -0
  484. package/src/lib/bun-runtime.ts +184 -0
  485. package/src/lib/bun-stream-caps.ts +130 -0
  486. package/src/lib/codex-restart-contract.ts +120 -0
  487. package/src/lib/config-ownership.ts +364 -0
  488. package/src/lib/crash-guard.ts +344 -0
  489. package/src/lib/debug-log-buffer.ts +83 -0
  490. package/src/lib/debug-settings.ts +108 -0
  491. package/src/lib/debug.ts +31 -0
  492. package/src/lib/destination-policy.ts +380 -0
  493. package/src/lib/errors.ts +406 -0
  494. package/src/lib/eventstream-decoder.ts +253 -0
  495. package/src/lib/fabric-task-execution-authority.ts +7 -0
  496. package/src/lib/fabric-task-host.ts +29 -0
  497. package/src/lib/gcp-adc.ts +341 -0
  498. package/src/lib/injection-debug-log.ts +58 -0
  499. package/src/lib/lab-activation.ts +223 -0
  500. package/src/lib/lab-live-execution-authority.ts +13 -0
  501. package/src/lib/lab-live-host.ts +30 -0
  502. package/src/lib/lab-live-pinned-sender.ts +56 -0
  503. package/src/lib/lab-live-route-production.ts +130 -0
  504. package/src/lib/lab-passive-linker-registration.ts +26 -0
  505. package/src/lib/local-management-attestation.ts +51 -0
  506. package/src/lib/local-management-capability.ts +100 -0
  507. package/src/lib/local-provider-reload-contract.ts +100 -0
  508. package/src/lib/open-url.ts +25 -0
  509. package/src/lib/optional-shutdown-hooks.ts +57 -0
  510. package/src/lib/pinned-http.ts +270 -0
  511. package/src/lib/privacy.ts +20 -0
  512. package/src/lib/process-control.ts +168 -0
  513. package/src/lib/provider-outbound.ts +210 -0
  514. package/src/lib/provider-url.ts +14 -0
  515. package/src/lib/proxy-env.ts +18 -0
  516. package/src/lib/redact.ts +521 -0
  517. package/src/lib/retry-after.ts +55 -0
  518. package/src/lib/self-launch-argv.ts +15 -0
  519. package/src/lib/server-resource-ownership.ts +71 -0
  520. package/src/lib/service-secrets.ts +25 -0
  521. package/src/lib/shadow-call.ts +61 -0
  522. package/src/lib/sidecar-tracker.ts +52 -0
  523. package/src/lib/sse-decoder.ts +364 -0
  524. package/src/lib/state-store-registrations.ts +119 -0
  525. package/src/lib/state-store-sweeper.ts +184 -0
  526. package/src/lib/system-restart-contract.ts +73 -0
  527. package/src/lib/test-home-guard.ts +90 -0
  528. package/src/lib/token-estimate.ts +86 -0
  529. package/src/lib/tool-argument-integers.ts +202 -0
  530. package/src/lib/translator-budget.ts +400 -0
  531. package/src/lib/upstream-http-version.ts +57 -0
  532. package/src/lib/upstream-reachability.ts +95 -0
  533. package/src/lib/upstream-retry.ts +392 -0
  534. package/src/lib/win-exec.ts +115 -0
  535. package/src/lib/win-paths.ts +68 -0
  536. package/src/lib/windows-atomic-replace.ts +156 -0
  537. package/src/lib/windows-elevation.ts +773 -0
  538. package/src/lib/windows-secret-acl.ts +854 -0
  539. package/src/lib/windows-service-wrappers.ts +72 -0
  540. package/src/lib/windows-text.ts +106 -0
  541. package/src/lib/windows-user-principal.ts +341 -0
  542. package/src/lib/winsw.ts +403 -0
  543. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  544. package/src/oauth/account-import/index.ts +15 -0
  545. package/src/oauth/account-import/parser.ts +83 -0
  546. package/src/oauth/account-import/registry.ts +18 -0
  547. package/src/oauth/account-import/service.ts +75 -0
  548. package/src/oauth/account-import/types.ts +91 -0
  549. package/src/oauth/anthropic-routing.ts +594 -0
  550. package/src/oauth/anthropic.ts +188 -0
  551. package/src/oauth/antigravity-routing.ts +151 -0
  552. package/src/oauth/callback-server.ts +300 -0
  553. package/src/oauth/chatgpt.ts +161 -0
  554. package/src/oauth/command-code.ts +239 -0
  555. package/src/oauth/cursor.ts +252 -0
  556. package/src/oauth/github-copilot.ts +428 -0
  557. package/src/oauth/google-antigravity.ts +262 -0
  558. package/src/oauth/health.ts +407 -0
  559. package/src/oauth/index.ts +1504 -0
  560. package/src/oauth/key-providers.ts +124 -0
  561. package/src/oauth/kimi.ts +227 -0
  562. package/src/oauth/kiro-credentials.ts +726 -0
  563. package/src/oauth/kiro.ts +621 -0
  564. package/src/oauth/local-token-detect.ts +130 -0
  565. package/src/oauth/log.ts +50 -0
  566. package/src/oauth/login-cli.ts +223 -0
  567. package/src/oauth/nous.ts +798 -0
  568. package/src/oauth/pkce.ts +15 -0
  569. package/src/oauth/store.ts +728 -0
  570. package/src/oauth/token-guardian.ts +309 -0
  571. package/src/oauth/types.ts +62 -0
  572. package/src/oauth/xai.ts +241 -0
  573. package/src/providers/alibaba-region-backup.ts +75 -0
  574. package/src/providers/alibaba-region-migration.ts +156 -0
  575. package/src/providers/alibaba-region-startup.ts +36 -0
  576. package/src/providers/antigravity-models.ts +695 -0
  577. package/src/providers/antigravity-quota.ts +216 -0
  578. package/src/providers/api-keys.ts +140 -0
  579. package/src/providers/base-url-choices.ts +74 -0
  580. package/src/providers/codex-capacity.ts +292 -0
  581. package/src/providers/command-code-efforts.ts +144 -0
  582. package/src/providers/context-cap.ts +82 -0
  583. package/src/providers/cursor-pool.ts +72 -0
  584. package/src/providers/derive.ts +586 -0
  585. package/src/providers/fastwire.ts +501 -0
  586. package/src/providers/free-directory.ts +187 -0
  587. package/src/providers/github-copilot-transport.ts +56 -0
  588. package/src/providers/google-vertex-location.ts +14 -0
  589. package/src/providers/key-failover.ts +271 -0
  590. package/src/providers/kiro-models.ts +67 -0
  591. package/src/providers/label.ts +19 -0
  592. package/src/providers/model-discovery-limits.ts +16 -0
  593. package/src/providers/model-discovery.ts +449 -0
  594. package/src/providers/model-rename-migration.ts +255 -0
  595. package/src/providers/model-rename-startup.ts +28 -0
  596. package/src/providers/openai-sidecar.ts +243 -0
  597. package/src/providers/openai-tier-startup.ts +56 -0
  598. package/src/providers/openai-tiers.ts +423 -0
  599. package/src/providers/openai-virtual-models.ts +83 -0
  600. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  601. package/src/providers/openrouter-routing.ts +102 -0
  602. package/src/providers/provider-id-rewrite.ts +185 -0
  603. package/src/providers/quota.ts +2345 -0
  604. package/src/providers/registry.ts +2918 -0
  605. package/src/providers/replit/constants.ts +27 -0
  606. package/src/providers/replit/derive.ts +85 -0
  607. package/src/providers/replit/headers.ts +28 -0
  608. package/src/providers/replit/origin.ts +55 -0
  609. package/src/providers/replit/pair-install-response.ts +72 -0
  610. package/src/providers/replit/probe.ts +199 -0
  611. package/src/providers/replit/setup.ts +350 -0
  612. package/src/providers/request-pacing.ts +310 -0
  613. package/src/providers/service-tier.ts +277 -0
  614. package/src/providers/slug-codec.ts +103 -0
  615. package/src/providers/static-model-discovery.ts +86 -0
  616. package/src/providers/xai-responses-opt-in.ts +15 -0
  617. package/src/providers/xai-transport.ts +148 -0
  618. package/src/reasoning-effort.ts +183 -0
  619. package/src/responses/compaction.ts +142 -0
  620. package/src/responses/custom-tool-compat.ts +266 -0
  621. package/src/responses/hosted-tool-policy.ts +9 -0
  622. package/src/responses/namespace-tool-compat.ts +355 -0
  623. package/src/responses/parser.ts +838 -0
  624. package/src/responses/provider-continuation.ts +98 -0
  625. package/src/responses/provider-opaque-metadata.ts +73 -0
  626. package/src/responses/reasoning-envelope.ts +60 -0
  627. package/src/responses/reasoning-replay-cache.ts +426 -0
  628. package/src/responses/schema.ts +165 -0
  629. package/src/responses/spill-store.ts +459 -0
  630. package/src/responses/state.ts +1433 -0
  631. package/src/responses/thought-signature-replay.ts +347 -0
  632. package/src/responses/tool-groups.ts +19 -0
  633. package/src/responses/tool-search-compat.ts +301 -0
  634. package/src/responses/truncated-stop-reason.ts +60 -0
  635. package/src/router.ts +761 -0
  636. package/src/routing/analytics.ts +378 -0
  637. package/src/routing/capability.ts +244 -0
  638. package/src/routing/compatibility/assemble.ts +73 -0
  639. package/src/routing/compatibility/behavior.ts +278 -0
  640. package/src/routing/compatibility/catalog.ts +99 -0
  641. package/src/routing/compatibility/endpoint.ts +52 -0
  642. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  643. package/src/routing/compatibility/policy.ts +181 -0
  644. package/src/routing/compatibility/provider-slot.ts +56 -0
  645. package/src/routing/compatibility/reader.ts +110 -0
  646. package/src/routing/compatibility/subject.ts +191 -0
  647. package/src/routing/compatibility/types.ts +64 -0
  648. package/src/routing/compatibility/version.ts +104 -0
  649. package/src/routing/cost.ts +77 -0
  650. package/src/routing/evaluator.ts +495 -0
  651. package/src/routing/health.ts +412 -0
  652. package/src/routing/history/cursor.ts +43 -0
  653. package/src/routing/history/indexer.ts +605 -0
  654. package/src/routing/history/schema.ts +72 -0
  655. package/src/routing/profile-namespace.ts +15 -0
  656. package/src/routing/profile.ts +547 -0
  657. package/src/routing/quota.ts +145 -0
  658. package/src/routing/request-evidence.ts +45 -0
  659. package/src/routing/trace.ts +776 -0
  660. package/src/server/adapter-resolve.ts +53 -0
  661. package/src/server/auth-cors.ts +751 -0
  662. package/src/server/background-lifecycle.ts +182 -0
  663. package/src/server/chat-completions.ts +442 -0
  664. package/src/server/chat-native-sse.ts +331 -0
  665. package/src/server/chat-native.ts +426 -0
  666. package/src/server/claude-messages.ts +1030 -0
  667. package/src/server/direct-local-http.ts +347 -0
  668. package/src/server/effort-policy.ts +190 -0
  669. package/src/server/github-copilot-responses-repair.ts +338 -0
  670. package/src/server/gui-static.ts +152 -0
  671. package/src/server/image-retry.ts +42 -0
  672. package/src/server/images.ts +568 -0
  673. package/src/server/index.ts +1813 -0
  674. package/src/server/lifecycle.ts +498 -0
  675. package/src/server/live.ts +717 -0
  676. package/src/server/local-management-read-client.ts +90 -0
  677. package/src/server/local-provider-reload-client.ts +137 -0
  678. package/src/server/management/agent-settings-routes.ts +1433 -0
  679. package/src/server/management/api-access.ts +141 -0
  680. package/src/server/management/api-key-usage.ts +193 -0
  681. package/src/server/management/body.ts +41 -0
  682. package/src/server/management/combo-routes.ts +263 -0
  683. package/src/server/management/config-routes.ts +835 -0
  684. package/src/server/management/context.ts +113 -0
  685. package/src/server/management/integration-routes.ts +498 -0
  686. package/src/server/management/lab-automation-routes.ts +206 -0
  687. package/src/server/management/lab-routes.ts +563 -0
  688. package/src/server/management/logs-usage-routes.ts +586 -0
  689. package/src/server/management/model-routes.ts +560 -0
  690. package/src/server/management/model-rows.ts +163 -0
  691. package/src/server/management/native-integration-routes.ts +769 -0
  692. package/src/server/management/oauth-account-routes.ts +637 -0
  693. package/src/server/management/provider-capability-config.ts +48 -0
  694. package/src/server/management/provider-routes.ts +1033 -0
  695. package/src/server/management/replit-provider-routes.ts +86 -0
  696. package/src/server/management/request-history-routes.ts +191 -0
  697. package/src/server/management/routing-analytics-routes.ts +74 -0
  698. package/src/server/management/routing-profile-routes.ts +385 -0
  699. package/src/server/management/shared.ts +286 -0
  700. package/src/server/management/sidebar-routes.ts +106 -0
  701. package/src/server/management/storage-log-guard-routes.ts +186 -0
  702. package/src/server/management/sync-response.ts +69 -0
  703. package/src/server/management/system-restart.ts +435 -0
  704. package/src/server/management/system-routes.ts +194 -0
  705. package/src/server/management/usage-summary-cache.ts +94 -0
  706. package/src/server/management/vision-sidecar-options.ts +167 -0
  707. package/src/server/management/web-search-sidecar-options.ts +120 -0
  708. package/src/server/management-api.ts +314 -0
  709. package/src/server/management-auth.ts +482 -0
  710. package/src/server/memory-watchdog.ts +156 -0
  711. package/src/server/passive-route-linker.ts +66 -0
  712. package/src/server/port-reclaim.ts +307 -0
  713. package/src/server/ports.ts +156 -0
  714. package/src/server/proxy-liveness.ts +328 -0
  715. package/src/server/readiness.ts +99 -0
  716. package/src/server/relay-eager.ts +353 -0
  717. package/src/server/relay.ts +1209 -0
  718. package/src/server/request-decompress.ts +239 -0
  719. package/src/server/request-log-conversation.ts +168 -0
  720. package/src/server/request-log.ts +1259 -0
  721. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  722. package/src/server/responses/agent-task-recovery.ts +465 -0
  723. package/src/server/responses/collaboration.ts +551 -0
  724. package/src/server/responses/compact.ts +771 -0
  725. package/src/server/responses/core.ts +5389 -0
  726. package/src/server/responses/empty-completion-guard.ts +276 -0
  727. package/src/server/responses/encrypted-payload.ts +331 -0
  728. package/src/server/responses/fetch-helpers.ts +232 -0
  729. package/src/server/responses/input-admission.ts +185 -0
  730. package/src/server/responses/pacing-overload.ts +13 -0
  731. package/src/server/responses/passthrough-error.ts +78 -0
  732. package/src/server/responses/policy-fallback.ts +178 -0
  733. package/src/server/responses/responses-field-backfill.ts +251 -0
  734. package/src/server/responses/terminal-guard.ts +251 -0
  735. package/src/server/responses/upstream-error.ts +53 -0
  736. package/src/server/responses/ws-upstream.ts +308 -0
  737. package/src/server/responses-custom-tool-repair.ts +282 -0
  738. package/src/server/responses-image-gen-repair.ts +132 -0
  739. package/src/server/responses-item-id-repair.ts +272 -0
  740. package/src/server/responses-json-events.ts +90 -0
  741. package/src/server/responses-model-rewrite.ts +29 -0
  742. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  743. package/src/server/responses-snapshot-repair.ts +621 -0
  744. package/src/server/responses-terminal-repair.ts +342 -0
  745. package/src/server/responses-tool-search-repair.ts +267 -0
  746. package/src/server/responses-undeclared-tool-guard.ts +153 -0
  747. package/src/server/responses.ts +25 -0
  748. package/src/server/search.ts +201 -0
  749. package/src/server/sse-frame-buffer.ts +292 -0
  750. package/src/server/sse-payload-rewrite.ts +263 -0
  751. package/src/server/startup-action-control.ts +315 -0
  752. package/src/server/startup-health-cache.ts +131 -0
  753. package/src/server/system-env.ts +484 -0
  754. package/src/server/windows-tcp-drop.ts +184 -0
  755. package/src/server/windows-tray-control.ts +41 -0
  756. package/src/server/ws-bridge.ts +472 -0
  757. package/src/service-manager-probe.ts +892 -0
  758. package/src/service.ts +3575 -0
  759. package/src/sidecar/auth.ts +92 -0
  760. package/src/sidecar/candidates.ts +83 -0
  761. package/src/stall-timeout.ts +20 -0
  762. package/src/storage/cleanup-job.ts +57 -0
  763. package/src/storage/cleanup.ts +3085 -0
  764. package/src/storage/policy-job.ts +457 -0
  765. package/src/storage/policy-scheduler.ts +40 -0
  766. package/src/storage/policy-worker.ts +59 -0
  767. package/src/storage/policy.ts +527 -0
  768. package/src/storage/restore-job.ts +299 -0
  769. package/src/storage/restore-worker.ts +58 -0
  770. package/src/storage/scanner.ts +238 -0
  771. package/src/storage/storage-mutation-coordinator.ts +139 -0
  772. package/src/storage/worker-lifecycle.ts +215 -0
  773. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  774. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  775. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  776. package/src/tray/assets/opencodex-tray.png +0 -0
  777. package/src/tray/windows-tray.ps1 +364 -0
  778. package/src/tray/windows.ts +757 -0
  779. package/src/types/accounts.ts +37 -0
  780. package/src/types/config.ts +876 -0
  781. package/src/types/provider.ts +545 -0
  782. package/src/types/request.ts +384 -0
  783. package/src/types/tools.ts +131 -0
  784. package/src/types/wire.ts +80 -0
  785. package/src/types.ts +106 -0
  786. package/src/update/badge.ts +72 -0
  787. package/src/update/index.ts +415 -0
  788. package/src/update/job.ts +1887 -0
  789. package/src/update/notify.ts +263 -0
  790. package/src/update/npm-cache-preflight.d.mts +47 -0
  791. package/src/update/npm-cache-preflight.mjs +201 -0
  792. package/src/update/npm-invocation.d.mts +23 -0
  793. package/src/update/npm-invocation.mjs +94 -0
  794. package/src/update/transactional-install.d.mts +22 -0
  795. package/src/update/transactional-install.mjs +259 -0
  796. package/src/update/tray-update-plan.d.mts +18 -0
  797. package/src/update/tray-update-plan.mjs +38 -0
  798. package/src/usage/cost.ts +625 -0
  799. package/src/usage/debug.ts +97 -0
  800. package/src/usage/expected-prices.ts +416 -0
  801. package/src/usage/log.ts +1223 -0
  802. package/src/usage/summary.ts +753 -0
  803. package/src/usage/totals.ts +14 -0
  804. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  805. package/src/usage/user-cost-overlays.ts +314 -0
  806. package/src/vision/anthropic-describe.ts +189 -0
  807. package/src/vision/backends.ts +97 -0
  808. package/src/vision/describe.ts +131 -0
  809. package/src/vision/eligibility.ts +250 -0
  810. package/src/vision/index.ts +681 -0
  811. package/src/vision/reasoning.ts +55 -0
  812. package/src/vision/routed-describe.ts +175 -0
  813. package/src/vision/timeout-bounds.ts +9 -0
  814. package/src/web-search/anthropic-executor.ts +195 -0
  815. package/src/web-search/backends.ts +108 -0
  816. package/src/web-search/exa-executor.ts +88 -0
  817. package/src/web-search/executor.ts +113 -0
  818. package/src/web-search/format-result.ts +89 -0
  819. package/src/web-search/gemini-executor.ts +141 -0
  820. package/src/web-search/index.ts +331 -0
  821. package/src/web-search/loop.ts +896 -0
  822. package/src/web-search/parse.ts +315 -0
  823. package/src/web-search/progress-stream.ts +342 -0
  824. package/src/web-search/sources.ts +60 -0
  825. package/src/web-search/synthetic-tool.ts +47 -0
  826. package/src/web-search/xai-executor.ts +219 -0
@@ -0,0 +1,979 @@
1
+ import { createHash } from "node:crypto";
2
+ import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readSync, statSync, unlinkSync, writeSync } from "node:fs";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { zstdDecompressSync } from "node:zlib";
5
+ import { Database } from "bun:sqlite";
6
+ import { resolveCodexStateDbPath } from "./paths";
7
+ import { atomicWriteFile, getConfigDir } from "../config";
8
+
9
+ /**
10
+ * Cap for decompressing a lone `.jsonl.zst` rollout during quarantine restore.
11
+ * Bounds peak memory while reconstructing thread rows; never write the decoded
12
+ * JSONL to disk.
13
+ */
14
+ export const MAX_ROLLOUT_ZST_DECOMPRESSED_BYTES = 64 * 1024 * 1024;
15
+
16
+ /**
17
+ * The manifest that shadows one state database.
18
+ *
19
+ * Exported because the history job must resolve it at CALL time for a Worker
20
+ * that does not inherit this module's load-time constants — and must resolve it
21
+ * the same way, since a manifest addressed differently is a different manifest.
22
+ */
23
+ export function historyBackupPathFor(stateDbPath: string): string {
24
+ const normalized = process.platform === "win32" ? resolve(stateDbPath).toLowerCase() : resolve(stateDbPath);
25
+ const id = createHash("sha256").update(normalized).digest("hex").slice(0, 16);
26
+ return join(getConfigDir(), `codex-history-backup-${id}.json`);
27
+ }
28
+ const RESUMABLE_SOURCES = ["cli", "vscode"] as const;
29
+
30
+ /**
31
+ * Open the live `state_5.sqlite` the way the Codex app expects a *secondary* writer to behave:
32
+ * wait on the WAL/file lock instead of failing instantly, so we never race the app's own
33
+ * connection pool into a half-applied checkpoint. The app opens this DB with `busy_timeout=5s`
34
+ * (see codex-rs `state::runtime::base_sqlite_options`); we mirror that here.
35
+ */
36
+ let historyDbBusyTimeoutMs = 5000;
37
+
38
+ /**
39
+ * Test-only knob: Windows CI can spend the FULL busy timeout on a transient file lock, which
40
+ * alone exceeds bun's 5s default per-test timeout. Tests shrink this so a busy DB fails fast
41
+ * into withHistoryRetry instead of stalling; production keeps the codex-rs-matching 5s.
42
+ */
43
+ export function setHistoryDbBusyTimeoutForTests(ms: number): void {
44
+ historyDbBusyTimeoutMs = ms;
45
+ }
46
+
47
+ function openStateDb(stateDbPath: string): Database {
48
+ const db = new Database(stateDbPath);
49
+ try {
50
+ db.exec(`PRAGMA busy_timeout = ${historyDbBusyTimeoutMs}`);
51
+ } catch {
52
+ /* best-effort: an older sqlite without busy_timeout still works, just less politely */
53
+ }
54
+ return db;
55
+ }
56
+
57
+ /**
58
+ * Append one JSONL line to a rollout using an O_APPEND handle, exactly like the Codex app's own
59
+ * metadata writer (`append_rollout_item_to_path` in codex-rs `rollout/src/recorder.rs`).
60
+ *
61
+ * Why append instead of rewriting line 1:
62
+ * - The app caches the live session's append handle and only reopens it when the handle is gone
63
+ * (codex-rs `RolloutWriterState::ensure_writer_open`). A temp+rename swap would orphan that
64
+ * handle; an in-place truncate would race the app's concurrent appends and clip new turns.
65
+ * - The app folds metadata by replaying every `session_meta` line in file order, last-writer-wins
66
+ * (codex-rs `apply_session_meta_from_item`), so a trailing `session_meta` overrides earlier ones.
67
+ * Real rollouts already contain multiple `session_meta` lines for this reason.
68
+ * O_APPEND makes each write land at EOF atomically, so it composes safely with the app appending
69
+ * concurrently. We do not touch mtime: a fresh mtime is correct here (the app uses mtime as the
70
+ * rollout's updated_at), and forcing it backwards could hide a real edit from list ordering.
71
+ */
72
+ function appendRolloutLine(path: string, line: string): void {
73
+ const fd = openSync(path, "a");
74
+ try {
75
+ const buf = Buffer.from(line.endsWith("\n") ? line : `${line}\n`, "utf8");
76
+ let offset = 0;
77
+ while (offset < buf.length) {
78
+ offset += writeSync(fd, buf, offset, buf.length - offset, null);
79
+ }
80
+ try { fsyncSync(fd); } catch { /* best-effort durability */ }
81
+ } finally {
82
+ closeSync(fd);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Patch the `model_provider` value inside the FIRST line of a rollout *in place, length-preserving*.
88
+ *
89
+ * Why this exists in addition to {@link appendRolloutLine}: Codex resolves a thread's provider via
90
+ * two different readers. The SQLite replay path folds every `session_meta` line last-writer-wins
91
+ * (covered by appending a trailing meta), but `read_session_meta_line` reads only the FIRST line
92
+ * and `update_thread_metadata` clones it when the app later writes git/memory-mode metadata
93
+ * (codex-rs `thread-store/src/local/update_thread_metadata.rs`). If the first line still says
94
+ * `opencodex` after a native restore, that clone re-appends `opencodex` and last-writer-wins
95
+ * resurrects the routed provider. So a durable restore must also fix line 1.
96
+ *
97
+ * Safety: Codex parses each rollout line as `serde_json::from_str(line.trim())`, which tolerates
98
+ * insignificant JSON whitespace. We therefore replace the provider value and pad the removed bytes
99
+ * with spaces so the line's byte length is unchanged. Equal length means we can write at offset 0
100
+ * with no truncate and no inode swap, so this composes safely with the app's cached append handle.
101
+ * Only length-preserving shrinks are handled (e.g. "opencodex" -> "openai"); callers that would
102
+ * grow the value fall back to append-only, which is correct for the opencodex direction.
103
+ *
104
+ * Returns true when line 1 was patched, false when it could not be done safely (missing file,
105
+ * non-`session_meta` first line, id mismatch, value already correct, or a length-growing change).
106
+ */
107
+ function patchFirstLineProviderInPlace(path: string, expectedId: string, provider: string): boolean {
108
+ if (!existsSync(path)) return false;
109
+ const fd = openSync(path, "r+");
110
+ try {
111
+ // Read the first line by growing the probe until we hit a newline. session_meta lines embed
112
+ // base_instructions and can be tens of KB; a fixed cap would silently skip the in-place patch
113
+ // (and fall back to append-only, re-opening the first-line-clone resurrection gap), so we read
114
+ // until the line actually ends rather than guessing a ceiling.
115
+ const CHUNK = 1 << 16;
116
+ const MAX_FIRST_LINE = 1 << 24; // 16 MiB hard stop so a newline-less/corrupt file can't OOM us.
117
+ let collected = Buffer.alloc(0);
118
+ let nlIndex = -1;
119
+ let pos = 0;
120
+ while (nlIndex === -1) {
121
+ const chunk = Buffer.alloc(CHUNK);
122
+ const read = readSync(fd, chunk, 0, CHUNK, pos);
123
+ if (read === 0) break; // EOF with no newline: single-line file, skip
124
+ collected = Buffer.concat([collected, chunk.subarray(0, read)]);
125
+ nlIndex = collected.indexOf(0x0a);
126
+ pos += read;
127
+ if (collected.length > MAX_FIRST_LINE) return false;
128
+ }
129
+ if (nlIndex === -1) return false; // no newline anywhere: skip
130
+ const firstLine = collected.subarray(0, nlIndex).toString("utf8");
131
+
132
+ const meta = parseSessionMetaLine(firstLine);
133
+ if (!meta) return false;
134
+ if (meta.record.payload.id !== expectedId) return false;
135
+ if (meta.record.payload.model_provider === provider) return false;
136
+
137
+ // Locate the exact `"model_provider":"<value>"` token (allowing whitespace after the colon).
138
+ const match = firstLine.match(/"model_provider"\s*:\s*"([^"\\]*)"/);
139
+ if (!match || match.index === undefined) return false;
140
+ const oldToken = match[0];
141
+ const newCore = `"model_provider":"${provider}"`;
142
+ if (Buffer.byteLength(newCore, "utf8") > Buffer.byteLength(oldToken, "utf8")) return false; // grow: not length-preserving
143
+ const pad = " ".repeat(Buffer.byteLength(oldToken, "utf8") - Buffer.byteLength(newCore, "utf8"));
144
+ const newToken = `${newCore}${pad}`;
145
+
146
+ const patchedLine = firstLine.slice(0, match.index) + newToken + firstLine.slice(match.index + oldToken.length);
147
+ // Length must be identical so the trailing bytes (newline + rest of file) are untouched.
148
+ if (Buffer.byteLength(patchedLine, "utf8") !== Buffer.byteLength(firstLine, "utf8")) return false;
149
+ // Sanity: the patched line must still parse and carry the new provider.
150
+ const reparsed = parseSessionMetaLine(patchedLine);
151
+ if (!reparsed || reparsed.record.payload.model_provider !== provider) return false;
152
+
153
+ const out = Buffer.from(patchedLine, "utf8");
154
+ let offset = 0;
155
+ while (offset < out.length) {
156
+ offset += writeSync(fd, out, offset, out.length - offset, offset);
157
+ }
158
+ try { fsyncSync(fd); } catch { /* best-effort durability */ }
159
+ return true;
160
+ } finally {
161
+ closeSync(fd);
162
+ }
163
+ }
164
+
165
+ export type CodexHistoryProvider = "openai" | "opencodex";
166
+
167
+ export type CodexHistoryFailureReason = "busy" | "permission";
168
+
169
+ export interface CodexHistorySyncResult {
170
+ rows: number;
171
+ files: number;
172
+ ejectedRows?: number;
173
+ /** Set when a lock/busy error survived retries and the sync was SKIPPED, not empty. */
174
+ failed?: true;
175
+ /** Why the retry budget was exhausted when `failed` is set. */
176
+ failureReason?: CodexHistoryFailureReason;
177
+ }
178
+
179
+ interface ThreadRow {
180
+ id: string;
181
+ rollout_path: string;
182
+ model_provider: string;
183
+ source: string;
184
+ has_user_event: number;
185
+ }
186
+
187
+ interface BackupEntry {
188
+ id: string;
189
+ rolloutPath: string;
190
+ modelProvider: string;
191
+ source: string;
192
+ hasUserEvent: number;
193
+ }
194
+
195
+ interface BackupManifest {
196
+ version: 1;
197
+ stateDbPath?: string;
198
+ entries: Record<string, BackupEntry>;
199
+ }
200
+
201
+ export interface CodexHistoryVerifiedNoopProof {
202
+ readonly kind: "verified-noop";
203
+ readonly pendingRows: 0;
204
+ readonly backupEntries: 0;
205
+ readonly canonicalStateDbPath: string;
206
+ readonly stateDbPresent: true;
207
+ readonly canonicalBackupPath: string;
208
+ readonly backupPresent: boolean;
209
+ }
210
+
211
+ export type CodexHistoryNoopSnapshot =
212
+ | CodexHistoryVerifiedNoopProof
213
+ | {
214
+ readonly kind: "work-pending";
215
+ readonly pendingRows: number;
216
+ readonly backupEntries: number;
217
+ readonly canonicalStateDbPath: string;
218
+ readonly stateDbPresent: boolean;
219
+ readonly canonicalBackupPath: string;
220
+ readonly backupPresent: boolean;
221
+ }
222
+ | {
223
+ readonly kind: "unknown";
224
+ readonly pendingRows: null;
225
+ readonly backupEntries: null;
226
+ readonly canonicalStateDbPath: string;
227
+ readonly stateDbPresent: boolean;
228
+ readonly canonicalBackupPath: string;
229
+ readonly backupPresent: boolean;
230
+ readonly reason: "backup-path" | "database-absent" | "manifest-read" | "manifest-schema" | "manifest-foreign" | "database-query" | "snapshot-race";
231
+ };
232
+
233
+ type StrictBackupInspection =
234
+ | { readonly kind: "known"; readonly present: boolean; readonly entries: number; readonly fingerprint: string }
235
+ | { readonly kind: "unknown"; readonly present: boolean; readonly reason: "manifest-read" | "manifest-schema" | "manifest-foreign" };
236
+
237
+ let afterNoopPendingCountForTests: (() => void) | undefined;
238
+
239
+ /** Test seam: runs after the pending count and before stability validation. */
240
+ export function setAfterNoopPendingCountForTests(hook: (() => void) | undefined): void {
241
+ afterNoopPendingCountForTests = hook;
242
+ }
243
+
244
+ interface NativeRestoreTarget {
245
+ modelProvider: string;
246
+ source: string;
247
+ hasUserEvent: number;
248
+ }
249
+
250
+ function samePath(a: string, b: string): boolean {
251
+ const left = resolve(a);
252
+ const right = resolve(b);
253
+ return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
254
+ }
255
+
256
+ function inspectBackupForNoop(path: string, stateDbPath: string): StrictBackupInspection {
257
+ if (!existsSync(path)) return { kind: "known", present: false, entries: 0, fingerprint: "absent" };
258
+ let parsed: unknown;
259
+ let raw: string;
260
+ try {
261
+ raw = readFileSync(path, "utf8");
262
+ parsed = JSON.parse(raw);
263
+ } catch {
264
+ return { kind: "unknown", present: true, reason: "manifest-read" };
265
+ }
266
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
267
+ return { kind: "unknown", present: true, reason: "manifest-schema" };
268
+ }
269
+ const manifest = parsed as Partial<BackupManifest>;
270
+ if (manifest.version !== 1 || typeof manifest.stateDbPath !== "string") {
271
+ return { kind: "unknown", present: true, reason: "manifest-schema" };
272
+ }
273
+ if (!samePath(manifest.stateDbPath, stateDbPath)) {
274
+ return { kind: "unknown", present: true, reason: "manifest-foreign" };
275
+ }
276
+ if (!manifest.entries || typeof manifest.entries !== "object" || Array.isArray(manifest.entries)) {
277
+ return { kind: "unknown", present: true, reason: "manifest-schema" };
278
+ }
279
+ for (const [id, value] of Object.entries(manifest.entries)) {
280
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
281
+ return { kind: "unknown", present: true, reason: "manifest-schema" };
282
+ }
283
+ const entry = value as Partial<BackupEntry>;
284
+ if (entry.id !== id
285
+ || typeof entry.rolloutPath !== "string"
286
+ || typeof entry.modelProvider !== "string"
287
+ || typeof entry.source !== "string"
288
+ || typeof entry.hasUserEvent !== "number") {
289
+ return { kind: "unknown", present: true, reason: "manifest-schema" };
290
+ }
291
+ }
292
+ return {
293
+ kind: "known",
294
+ present: true,
295
+ entries: Object.keys(manifest.entries).length,
296
+ fingerprint: createHash("sha256").update(raw).digest("hex"),
297
+ };
298
+ }
299
+
300
+ function historyFileIdentity(path: string): string | null {
301
+ try {
302
+ const stat = statSync(path);
303
+ return [stat.dev, stat.ino, stat.size, stat.mtimeMs, stat.ctimeMs].join(":");
304
+ } catch {
305
+ return null;
306
+ }
307
+ }
308
+
309
+ function readHistoryDataVersion(db: Database): number | null {
310
+ const row = db.query<{ data_version: number }, []>("PRAGMA data_version").get();
311
+ const value = row?.data_version;
312
+ return typeof value === "number" && Number.isSafeInteger(value) ? value : null;
313
+ }
314
+
315
+ function readBackup(path: string, stateDbPath?: string): BackupManifest {
316
+ if (!existsSync(path)) return { version: 1, stateDbPath, entries: {} };
317
+ try {
318
+ const parsed = JSON.parse(readFileSync(path, "utf8")) as Partial<BackupManifest>;
319
+ if (parsed.version !== 1 || !parsed.entries || typeof parsed.entries !== "object") {
320
+ return { version: 1, stateDbPath, entries: {} };
321
+ }
322
+ if (stateDbPath && typeof parsed.stateDbPath === "string" && !samePath(parsed.stateDbPath, stateDbPath)) {
323
+ return { version: 1, stateDbPath, entries: {} };
324
+ }
325
+ return { version: 1, stateDbPath: parsed.stateDbPath ?? stateDbPath, entries: parsed.entries };
326
+ } catch {
327
+ return { version: 1, stateDbPath, entries: {} };
328
+ }
329
+ }
330
+
331
+ function writeBackup(path: string, manifest: BackupManifest, stateDbPath?: string): void {
332
+ if (Object.keys(manifest.entries).length === 0) {
333
+ if (existsSync(path)) unlinkSync(path);
334
+ return;
335
+ }
336
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
337
+ atomicWriteFile(path, JSON.stringify({ ...manifest, stateDbPath: manifest.stateDbPath ?? stateDbPath }, null, 2) + "\n");
338
+ }
339
+
340
+ function rememberOriginal(manifest: BackupManifest, row: ThreadRow): void {
341
+ if (manifest.entries[row.id]) return;
342
+ manifest.entries[row.id] = {
343
+ id: row.id,
344
+ rolloutPath: row.rollout_path,
345
+ modelProvider: row.model_provider,
346
+ source: row.source,
347
+ hasUserEvent: Number(row.has_user_event) || 0,
348
+ };
349
+ }
350
+
351
+ interface ParsedSessionMeta {
352
+ record: { type?: unknown; timestamp?: unknown; payload: { model_provider?: unknown; source?: unknown } & Record<string, unknown> };
353
+ }
354
+
355
+ /** Parse one JSONL line into a `session_meta` record, or null if it isn't one. */
356
+ function parseSessionMetaLine(line: string): ParsedSessionMeta | null {
357
+ let parsed: unknown;
358
+ try {
359
+ parsed = JSON.parse(line);
360
+ } catch {
361
+ return null;
362
+ }
363
+ if (!parsed || typeof parsed !== "object") return null;
364
+ const record = parsed as ParsedSessionMeta["record"];
365
+ if (record.type !== "session_meta" || !record.payload || typeof record.payload !== "object") return null;
366
+ return { record };
367
+ }
368
+
369
+ /**
370
+ * Find the LAST `session_meta` line in a rollout, mirroring the app's last-writer-wins fold
371
+ * (codex-rs `apply_session_meta_from_item`). We base our patch on the most recent metadata so we
372
+ * never resurrect a stale provider that a later app-written `session_meta` already changed.
373
+ */
374
+ export function readLatestSessionMeta(path: string): ParsedSessionMeta | null {
375
+ const raw = readFileSync(path, "utf8");
376
+ const lines = raw.split("\n");
377
+ for (let i = lines.length - 1; i >= 0; i--) {
378
+ const line = lines[i];
379
+ if (!line) continue;
380
+ if (!line.includes("\"session_meta\"")) continue;
381
+ const meta = parseSessionMetaLine(line);
382
+ if (meta) return meta;
383
+ }
384
+ return null;
385
+ }
386
+
387
+ /**
388
+ * Fields needed to re-insert a production-shaped `threads` row from a rollout JSONL when a
389
+ * Phase-2 quarantine predates full `satellite-backup.json` thread snapshots.
390
+ *
391
+ * Uses the same last-writer-wins `session_meta` fold as {@link readLatestSessionMeta}, plus the
392
+ * first user-message preview (codex-rs `list.rs` / `EventMsg::UserMessage` path).
393
+ */
394
+ export interface RolloutThreadFields {
395
+ id: string;
396
+ modelProvider: string;
397
+ source: string;
398
+ firstUserMessage: string;
399
+ hasUserEvent: number;
400
+ cwd?: string;
401
+ historyMode?: string;
402
+ cliVersion?: string;
403
+ }
404
+
405
+ function textFromContentParts(content: unknown): string | null {
406
+ if (typeof content === "string" && content.trim()) return content.trim();
407
+ if (!Array.isArray(content)) return null;
408
+ const parts: string[] = [];
409
+ for (const part of content) {
410
+ if (!part || typeof part !== "object") continue;
411
+ const p = part as Record<string, unknown>;
412
+ if (typeof p.text === "string" && p.text.trim()) parts.push(p.text.trim());
413
+ else if (typeof p.input_text === "string" && p.input_text.trim()) parts.push(p.input_text.trim());
414
+ }
415
+ const joined = parts.join("\n").trim();
416
+ return joined || null;
417
+ }
418
+
419
+ /** Extract the first user-message preview from a rollout line, or null. */
420
+ function extractUserMessagePreview(line: string): string | null {
421
+ let parsed: unknown;
422
+ try {
423
+ parsed = JSON.parse(line);
424
+ } catch {
425
+ return null;
426
+ }
427
+ if (!parsed || typeof parsed !== "object") return null;
428
+ const record = parsed as { type?: unknown; payload?: unknown };
429
+ const payload = record.payload;
430
+ if (!payload || typeof payload !== "object") return null;
431
+ const p = payload as Record<string, unknown>;
432
+
433
+ if (record.type === "event_msg") {
434
+ // codex-rs EventMsg::UserMessage — payload.type is "user_message" (or omitted in fixtures).
435
+ if (p.type === "user_message" || typeof p.message === "string") {
436
+ if (typeof p.message === "string" && p.message.trim()) return p.message.trim();
437
+ const fromContent = textFromContentParts(p.content);
438
+ if (fromContent) return fromContent;
439
+ }
440
+ return null;
441
+ }
442
+
443
+ if (record.type === "response_item") {
444
+ if (p.type === "message" && p.role === "user") {
445
+ return textFromContentParts(p.content);
446
+ }
447
+ }
448
+ return null;
449
+ }
450
+
451
+ /**
452
+ * Reconstruct thread identity + listing fields from a staged/restored rollout JSONL.
453
+ * Returns null when the file is missing or has no parseable `session_meta`.
454
+ *
455
+ * Accepts plain `.jsonl` or a lone `.jsonl.zst` (legacy Phase-2 quarantine). Compressed
456
+ * rollouts are decompressed in memory with {@link MAX_ROLLOUT_ZST_DECOMPRESSED_BYTES};
457
+ * no decompressed copy is written to disk.
458
+ */
459
+ export function readThreadFieldsFromRollout(path: string): RolloutThreadFields | null {
460
+ if (!path || !existsSync(path)) return null;
461
+ let raw: string;
462
+ try {
463
+ raw = path.endsWith(".zst")
464
+ ? decompressRolloutZstUtf8(path)
465
+ : readFileSync(path, "utf8");
466
+ } catch {
467
+ return null;
468
+ }
469
+ return parseThreadFieldsFromRolloutText(raw);
470
+ }
471
+
472
+ function decompressRolloutZstUtf8(
473
+ path: string,
474
+ maxBytes: number = MAX_ROLLOUT_ZST_DECOMPRESSED_BYTES,
475
+ ): string {
476
+ const compressed = readFileSync(path);
477
+ const decoded = zstdDecompressSync(compressed as Uint8Array<ArrayBuffer>, {
478
+ maxOutputLength: maxBytes,
479
+ });
480
+ if (decoded.byteLength > maxBytes) {
481
+ throw new Error("rollout_zst_too_large");
482
+ }
483
+ return new TextDecoder().decode(decoded);
484
+ }
485
+
486
+ function parseThreadFieldsFromRolloutText(raw: string): RolloutThreadFields | null {
487
+ const lines = raw.split("\n");
488
+ let latest: ParsedSessionMeta | null = null;
489
+ let firstUserMessage = "";
490
+ for (const line of lines) {
491
+ if (!line) continue;
492
+ if (line.includes("\"session_meta\"")) {
493
+ const meta = parseSessionMetaLine(line);
494
+ if (meta) latest = meta;
495
+ }
496
+ if (!firstUserMessage) {
497
+ const preview = extractUserMessagePreview(line);
498
+ if (preview) firstUserMessage = preview;
499
+ }
500
+ }
501
+ if (!latest) return null;
502
+ const payload = latest.record.payload;
503
+ const id = typeof payload.id === "string" ? payload.id : "";
504
+ if (!id) return null;
505
+ const modelProvider = typeof payload.model_provider === "string" && payload.model_provider
506
+ ? payload.model_provider
507
+ : "openai";
508
+ const source = typeof payload.source === "string" && payload.source
509
+ ? payload.source
510
+ : "cli";
511
+ return {
512
+ id,
513
+ modelProvider,
514
+ source,
515
+ firstUserMessage,
516
+ hasUserEvent: firstUserMessage.trim() ? 1 : 0,
517
+ ...(typeof payload.cwd === "string" ? { cwd: payload.cwd } : {}),
518
+ ...(typeof payload.history_mode === "string" ? { historyMode: payload.history_mode } : {}),
519
+ ...(typeof payload.cli_version === "string" ? { cliVersion: payload.cli_version } : {}),
520
+ };
521
+ }
522
+
523
+ /**
524
+ * Make a thread's rollout reflect a provider/source change by APPENDING a new `session_meta` line,
525
+ * rather than rewriting line 1. The appended line clones the latest metadata payload (so no field
526
+ * is accidentally reset to empty) and applies only the requested changes. Returns false when the
527
+ * rollout is missing, has no parseable `session_meta`, its latest `session_meta` belongs to a
528
+ * different thread id, or it already matches the desired values.
529
+ */
530
+ function updateSessionMeta(path: string, expectedId: string, patch: { provider?: string; source?: string }): boolean {
531
+ if (!path || !existsSync(path)) return false;
532
+
533
+ const latest = readLatestSessionMeta(path);
534
+ if (!latest) return false;
535
+ const record = latest.record;
536
+
537
+ // The app ignores `session_meta` lines whose payload id != the canonical thread id
538
+ // (codex-rs `apply_session_meta_from_item`). Forked rollouts can embed a source session's
539
+ // metadata, so an id-mismatched latest line means we'd be cloning the wrong thread's meta and
540
+ // appending a line the app would discard. Skip rather than write a no-op/misleading line.
541
+ const payloadId = record.payload.id;
542
+ if (typeof payloadId !== "string" || payloadId !== expectedId) return false;
543
+
544
+ let changed = false;
545
+ if (patch.provider !== undefined && record.payload.model_provider !== patch.provider) {
546
+ record.payload.model_provider = patch.provider;
547
+ changed = true;
548
+ }
549
+ if (patch.source !== undefined && record.payload.source !== patch.source) {
550
+ record.payload.source = patch.source;
551
+ changed = true;
552
+ }
553
+ if (!changed) return false;
554
+
555
+ // Cover Codex's *other* provider reader: `read_session_meta_line` reads only line 1, and the
556
+ // app clones it when writing later git/memory-mode metadata. Appending alone leaves a stale
557
+ // line-1 provider that the clone would re-append, so for a length-preserving provider change we
558
+ // also patch line 1 in place (no inode swap, no truncate). Best-effort: when it can't be done
559
+ // safely (e.g. a length-growing change), the trailing append below is still correct for the
560
+ // SQLite replay path.
561
+ if (patch.provider !== undefined) {
562
+ try { patchFirstLineProviderInPlace(path, expectedId, patch.provider); } catch { /* best-effort line-1 patch */ }
563
+ }
564
+
565
+ // Refresh the line timestamp so the appended record reads as the newest metadata.
566
+ record.timestamp = new Date().toISOString();
567
+ appendRolloutLine(path, JSON.stringify(record));
568
+ return true;
569
+ }
570
+
571
+ function toNativeRestoreTarget(entry: BackupEntry): NativeRestoreTarget {
572
+ if (entry.modelProvider !== "opencodex") {
573
+ return {
574
+ modelProvider: entry.modelProvider,
575
+ source: entry.source,
576
+ hasUserEvent: entry.hasUserEvent,
577
+ };
578
+ }
579
+ return {
580
+ modelProvider: "openai",
581
+ source: entry.source === "exec" ? "cli" : entry.source,
582
+ hasUserEvent: 1,
583
+ };
584
+ }
585
+
586
+ function ejectRemainingOpencodexHistory(db: Database): { rows: number; files: number } {
587
+ const rows = db
588
+ .query<ThreadRow, []>(`
589
+ SELECT id, rollout_path, model_provider, source, has_user_event
590
+ FROM threads
591
+ WHERE model_provider = 'opencodex'
592
+ AND trim(coalesce(first_user_message, '')) != ''
593
+ `)
594
+ .all();
595
+
596
+ let files = 0;
597
+ for (const row of rows) {
598
+ try {
599
+ if (updateSessionMeta(row.rollout_path, row.id, {
600
+ provider: "openai",
601
+ source: row.source === "exec" ? "cli" : undefined,
602
+ })) files++;
603
+ } catch {
604
+ /* native restore should continue even if an old rollout is missing */
605
+ }
606
+ }
607
+
608
+ const restore = db.transaction(() => {
609
+ const update = db.query(`
610
+ UPDATE threads
611
+ SET model_provider = 'openai',
612
+ source = CASE WHEN source = 'exec' THEN 'cli' ELSE source END,
613
+ has_user_event = 1
614
+ WHERE id = ?
615
+ `);
616
+ for (const row of rows) update.run(row.id);
617
+ });
618
+ restore();
619
+ return { rows: rows.length, files };
620
+ }
621
+
622
+ export function classifyRecoverableHistoryError(error: unknown): CodexHistoryFailureReason | null {
623
+ const code = typeof error === "object" && error && "code" in error ? String((error as { code?: unknown }).code) : "";
624
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
625
+ if (code === "SQLITE_BUSY"
626
+ || code === "SQLITE_LOCKED"
627
+ || code === "EBUSY"
628
+ || message.includes("database is locked")
629
+ || message.includes("database is busy")
630
+ || message.includes("resource busy")) return "busy";
631
+ if (code === "EPERM"
632
+ || code === "EACCES"
633
+ || message.includes("operation not permitted")
634
+ || message.includes("permission denied")) return "permission";
635
+ return null;
636
+ }
637
+
638
+ export function isRecoverableHistoryError(error: unknown): boolean {
639
+ return classifyRecoverableHistoryError(error) !== null;
640
+ }
641
+
642
+ const HISTORY_RETRY_DELAY_MS = 500;
643
+ const HISTORY_RETRY_ATTEMPTS = 2;
644
+
645
+ /**
646
+ * Run a history mutation with one retry across recoverable lock/busy errors (the app's own
647
+ * connection holds `state_5.sqlite` in WAL with a 5 s busy_timeout; a transient writer
648
+ * usually clears within that window). Returns null when both attempts hit a recoverable
649
+ * error — callers surface that as `failed: true` instead of a silent no-op. Hard errors
650
+ * (corruption, programming bugs) still throw.
651
+ */
652
+ function withHistoryRetryResult<T>(fn: () => T, io: { sleepFn?: (ms: number) => void; attempts?: number; delayMs?: number } = {}):
653
+ | { ok: true; value: T }
654
+ | { ok: false; reason: CodexHistoryFailureReason } {
655
+ const sleepFn = io.sleepFn ?? Bun.sleepSync;
656
+ const attempts = Math.max(1, io.attempts ?? HISTORY_RETRY_ATTEMPTS);
657
+ const delayMs = io.delayMs ?? HISTORY_RETRY_DELAY_MS;
658
+ for (let attempt = 0; ; attempt++) {
659
+ try {
660
+ return { ok: true, value: fn() };
661
+ } catch (error) {
662
+ const reason = classifyRecoverableHistoryError(error);
663
+ if (!reason) throw error;
664
+ if (attempt >= attempts - 1) return { ok: false, reason };
665
+ try { sleepFn(delayMs); } catch { /* sleep is best-effort */ }
666
+ }
667
+ }
668
+ }
669
+
670
+ export function withHistoryRetry<T>(fn: () => T, io: { sleepFn?: (ms: number) => void; attempts?: number; delayMs?: number } = {}): T | null {
671
+ const result = withHistoryRetryResult(fn, io);
672
+ return result.ok ? result.value : null;
673
+ }
674
+
675
+ /**
676
+ * True when a READONLY probe proves the openai-direction restore would be a no-op:
677
+ * zero threads still tagged opencodex AND an empty backup manifest. Used to skip the
678
+ * write-open entirely in the Design B steady state — on Windows the Codex app holds
679
+ * `state_5.sqlite` (WAL, busy_timeout 5s), so an unnecessary write open can stall for
680
+ * seconds and surface a false lock warning, while WAL always admits readers. A failed
681
+ * probe (locked even for readers / schema drift) returns false so callers fall through
682
+ * to the write attempt and keep today's behavior for genuinely unknown state.
683
+ */
684
+ function openaiRestoreIsNoop(stateDbPath: string, backupPath: string): boolean {
685
+ const pending = countPendingOpencodexHistory(stateDbPath, backupPath);
686
+ return !pending.failed && pending.pendingRows === 0 && pending.backupEntries === 0;
687
+ }
688
+
689
+ export function syncCodexHistoryProvider(
690
+ provider: CodexHistoryProvider,
691
+ stateDbPath = resolveCodexStateDbPath(),
692
+ backupPath = historyBackupPathFor(stateDbPath),
693
+ opts: { skipWhenProvablyNoop?: boolean } = {},
694
+ ): CodexHistorySyncResult {
695
+ // Opt-in steady-state gate (Design B loopback callers only): default semantics of
696
+ // this exported API are unchanged — legacy stop/restore paths never pass the flag.
697
+ if (opts.skipWhenProvablyNoop && provider === "openai" && existsSync(stateDbPath)
698
+ && openaiRestoreIsNoop(stateDbPath, backupPath)) {
699
+ return { rows: 0, files: 0 };
700
+ }
701
+ const retried = withHistoryRetryResult(() => syncCodexHistoryProviderUnsafe(provider, stateDbPath, backupPath));
702
+ return retried.ok ? retried.value : { rows: 0, files: 0, failed: true, failureReason: retried.reason };
703
+ }
704
+
705
+ function syncCodexHistoryProviderUnsafe(provider: CodexHistoryProvider, stateDbPath: string, backupPath: string): CodexHistorySyncResult {
706
+ if (!existsSync(stateDbPath)) return { rows: 0, files: 0 };
707
+ if (provider === "openai") return restoreCodexHistoryProvider(stateDbPath, backupPath);
708
+
709
+ const db = openStateDb(stateDbPath);
710
+ try {
711
+ const placeholders = RESUMABLE_SOURCES.map(() => "?").join(",");
712
+ const openaiRows = db
713
+ .query<ThreadRow, string[]>(`
714
+ SELECT id, rollout_path, model_provider, source, has_user_event
715
+ FROM threads
716
+ WHERE model_provider = 'openai'
717
+ AND source IN (${placeholders})
718
+ `)
719
+ .all(...RESUMABLE_SOURCES);
720
+ const execRows = db
721
+ .query<ThreadRow, []>(`
722
+ SELECT id, rollout_path, model_provider, source, has_user_event
723
+ FROM threads
724
+ WHERE model_provider = 'opencodex'
725
+ AND source = 'exec'
726
+ AND trim(coalesce(first_user_message, '')) != ''
727
+ `)
728
+ .all();
729
+
730
+ const manifest = readBackup(backupPath, stateDbPath);
731
+ for (const row of [...openaiRows, ...execRows]) rememberOriginal(manifest, row);
732
+ writeBackup(backupPath, manifest, stateDbPath);
733
+
734
+ let files = 0;
735
+ for (const row of openaiRows) {
736
+ try {
737
+ if (updateSessionMeta(row.rollout_path, row.id, { provider: "opencodex" })) files++;
738
+ } catch {
739
+ /* best-effort; keep DB migration moving even if one old rollout is malformed */
740
+ }
741
+ }
742
+ for (const row of execRows) {
743
+ try {
744
+ if (updateSessionMeta(row.rollout_path, row.id, { source: "cli" })) files++;
745
+ } catch {
746
+ /* best-effort; keep DB migration moving even if one old rollout is malformed */
747
+ }
748
+ }
749
+
750
+ const update = db.transaction(() => {
751
+ const markUserEvent = db.query(`
752
+ UPDATE threads
753
+ SET has_user_event = 1
754
+ WHERE id = ?
755
+ AND trim(coalesce(first_user_message, '')) != ''
756
+ `);
757
+ for (const row of [...openaiRows, ...execRows]) markUserEvent.run(row.id);
758
+ db.query(`
759
+ UPDATE threads
760
+ SET model_provider = 'opencodex'
761
+ WHERE model_provider = 'openai'
762
+ AND source IN (${placeholders})
763
+ `).run(...RESUMABLE_SOURCES);
764
+ db.query(`
765
+ UPDATE threads
766
+ SET source = 'cli'
767
+ WHERE model_provider = 'opencodex'
768
+ AND source = 'exec'
769
+ AND trim(coalesce(first_user_message, '')) != ''
770
+ `).run();
771
+ });
772
+ update();
773
+
774
+ return { rows: openaiRows.length + execRows.length, files };
775
+ } finally {
776
+ db.close();
777
+ }
778
+ }
779
+
780
+ function restoreCodexHistoryProvider(stateDbPath: string, backupPath: string): CodexHistorySyncResult {
781
+ const manifest = readBackup(backupPath, stateDbPath);
782
+ const entries = Object.values(manifest.entries);
783
+
784
+ const db = openStateDb(stateDbPath);
785
+ try {
786
+ if (entries.length === 0) {
787
+ const ejected = ejectRemainingOpencodexHistory(db);
788
+ return ejected.rows > 0 ? { rows: 0, files: ejected.files, ejectedRows: ejected.rows } : { rows: 0, files: 0 };
789
+ }
790
+
791
+ let files = 0;
792
+ for (const entry of entries) {
793
+ const target = toNativeRestoreTarget(entry);
794
+ try {
795
+ if (updateSessionMeta(entry.rolloutPath, entry.id, { provider: target.modelProvider, source: target.source })) files++;
796
+ } catch {
797
+ /* best-effort; keep DB restore moving even if one rollout disappeared */
798
+ }
799
+ }
800
+
801
+ const restore = db.transaction(() => {
802
+ const update = db.query(`
803
+ UPDATE threads
804
+ SET model_provider = ?,
805
+ source = ?,
806
+ has_user_event = ?
807
+ WHERE id = ?
808
+ `);
809
+ for (const entry of entries) {
810
+ const target = toNativeRestoreTarget(entry);
811
+ update.run(target.modelProvider, target.source, target.hasUserEvent, entry.id);
812
+ }
813
+ });
814
+ restore();
815
+ writeBackup(backupPath, { version: 1, stateDbPath, entries: {} }, stateDbPath);
816
+ const ejected = ejectRemainingOpencodexHistory(db);
817
+ return ejected.rows > 0
818
+ ? { rows: entries.length, files: files + ejected.files, ejectedRows: ejected.rows }
819
+ : { rows: entries.length, files };
820
+ } finally {
821
+ db.close();
822
+ }
823
+ }
824
+
825
+ export function restoreLegacyOpenaiHistory(stateDbPath = resolveCodexStateDbPath()): CodexHistorySyncResult {
826
+ if (!existsSync(stateDbPath)) return { rows: 0, files: 0 };
827
+ const retried = withHistoryRetryResult(() => {
828
+ const db = openStateDb(stateDbPath);
829
+ try {
830
+ return ejectRemainingOpencodexHistory(db);
831
+ } finally {
832
+ db.close();
833
+ }
834
+ });
835
+ return retried.ok ? retried.value : { rows: 0, files: 0, failed: true, failureReason: retried.reason };
836
+ }
837
+
838
+ /**
839
+ * One-time Design-B migration: restore backed-up originals, then eject any remaining
840
+ * opencodex-tagged threads to openai. Thin wrapper over the restore path with a
841
+ * configurable retry budget — the daemon migration guardian uses `{ attempts: 1 }`
842
+ * per tick so a locked DB never stalls the event loop beyond one sqlite busy wait.
843
+ */
844
+ export function migrateHistoryToOpenai(
845
+ stateDbPath = resolveCodexStateDbPath(),
846
+ backupPath = historyBackupPathFor(stateDbPath),
847
+ opts: { attempts?: number; delayMs?: number; sleepFn?: (ms: number) => void } = {},
848
+ ): CodexHistorySyncResult {
849
+ if (!existsSync(stateDbPath)) return { rows: 0, files: 0 };
850
+ // Steady-state gate: this migration is Design-B-specific (inject + guardian callers),
851
+ // and after the one-time migration every start would otherwise write-open the DB for
852
+ // nothing. A missing DB with a leftover backup manifest does NOT satisfy the gate
853
+ // (backupEntries > 0), so the guardian's fresh-reinstall re-count protection holds.
854
+ if (openaiRestoreIsNoop(stateDbPath, backupPath)) return { rows: 0, files: 0 };
855
+ const retried = withHistoryRetryResult(() => syncCodexHistoryProviderUnsafe("openai", stateDbPath, backupPath), opts);
856
+ return retried.ok ? retried.value : { rows: 0, files: 0, failed: true, failureReason: retried.reason };
857
+ }
858
+
859
+ /**
860
+ * Captures no-op evidence while the caller holds the history serialization
861
+ * lock H. This function does not acquire H itself. Unknown or foreign backup
862
+ * state is never collapsed into an empty manifest.
863
+ */
864
+ export function snapshotCodexHistoryNoop(
865
+ stateDbPath: string,
866
+ backupPath: string,
867
+ ): CodexHistoryNoopSnapshot {
868
+ const canonicalStateDbPath = resolve(stateDbPath);
869
+ const canonicalBackupPath = resolve(backupPath);
870
+ const stateDbPresent = existsSync(stateDbPath);
871
+ const backupPresent = existsSync(backupPath);
872
+ const base = { canonicalStateDbPath, stateDbPresent, canonicalBackupPath, backupPresent };
873
+ if (!samePath(backupPath, historyBackupPathFor(stateDbPath))) {
874
+ return { kind: "unknown", pendingRows: null, backupEntries: null, ...base, reason: "backup-path" };
875
+ }
876
+ const backup = inspectBackupForNoop(backupPath, stateDbPath);
877
+ if (backup.kind === "unknown") {
878
+ return { kind: "unknown", pendingRows: null, backupEntries: null, ...base, reason: backup.reason };
879
+ }
880
+ if (!stateDbPresent) {
881
+ return backup.entries > 0
882
+ ? { kind: "work-pending", pendingRows: 0, backupEntries: backup.entries, ...base }
883
+ : { kind: "unknown", pendingRows: null, backupEntries: null, ...base, reason: "database-absent" };
884
+ }
885
+ const stateDbIdentity = historyFileIdentity(stateDbPath);
886
+ if (stateDbIdentity === null) {
887
+ return { kind: "unknown", pendingRows: null, backupEntries: null, ...base, reason: "snapshot-race" };
888
+ }
889
+ let monitor: Database | undefined;
890
+ try {
891
+ monitor = new Database(stateDbPath, { readonly: true });
892
+ monitor.exec("PRAGMA busy_timeout = 100");
893
+ const dataVersionBefore = readHistoryDataVersion(monitor);
894
+ if (dataVersionBefore === null) {
895
+ return { kind: "unknown", pendingRows: null, backupEntries: null, ...base, reason: "database-query" };
896
+ }
897
+ const pending = countPendingOpencodexHistory(stateDbPath, backupPath);
898
+ if (pending.failed) {
899
+ return { kind: "unknown", pendingRows: null, backupEntries: null, ...base, reason: "database-query" };
900
+ }
901
+ afterNoopPendingCountForTests?.();
902
+ const backupAfter = inspectBackupForNoop(backupPath, stateDbPath);
903
+ if (backupAfter.kind === "unknown") {
904
+ return { kind: "unknown", pendingRows: null, backupEntries: null, ...base, reason: backupAfter.reason };
905
+ }
906
+ const dataVersionAfter = readHistoryDataVersion(monitor);
907
+ if (dataVersionAfter === null) {
908
+ return { kind: "unknown", pendingRows: null, backupEntries: null, ...base, reason: "database-query" };
909
+ }
910
+ if (dataVersionAfter !== dataVersionBefore
911
+ || pending.backupEntries !== backup.entries
912
+ || backupAfter.entries !== backup.entries
913
+ || backupAfter.present !== backup.present
914
+ || backupAfter.fingerprint !== backup.fingerprint
915
+ || historyFileIdentity(stateDbPath) !== stateDbIdentity
916
+ || existsSync(stateDbPath) !== stateDbPresent
917
+ || existsSync(backupPath) !== backupPresent) {
918
+ return { kind: "unknown", pendingRows: null, backupEntries: null, ...base, reason: "snapshot-race" };
919
+ }
920
+ return pending.pendingRows === 0 && backup.entries === 0
921
+ ? { kind: "verified-noop", pendingRows: 0, backupEntries: 0, ...base, stateDbPresent: true }
922
+ : { kind: "work-pending", pendingRows: pending.pendingRows, backupEntries: backup.entries, ...base };
923
+ } catch {
924
+ return { kind: "unknown", pendingRows: null, backupEntries: null, ...base, reason: "database-query" };
925
+ } finally {
926
+ try {
927
+ monitor?.close();
928
+ } catch {
929
+ // Read-only monitor cleanup cannot make an uncertain snapshot authoritative.
930
+ }
931
+ }
932
+ }
933
+
934
+ export interface PendingHistoryCount {
935
+ /** Threads still tagged opencodex that the eject path WOULD move (mirrors its WHERE). */
936
+ pendingRows: number;
937
+ /** Entries still recorded in the backup manifest (restore targets). */
938
+ backupEntries: number;
939
+ /** Set when the DB could not be opened/read (locked); counts are then unknown, not zero. */
940
+ failed?: true;
941
+ }
942
+
943
+ /**
944
+ * Read-only migration progress probe for the guardian and `ocx doctor`. Opens sqlite
945
+ * readonly with a SHORT busy timeout so a locked DB cannot stall a daemon tick. The
946
+ * pending predicate mirrors ejectRemainingOpencodexHistory exactly — rows eject ignores
947
+ * (empty first_user_message) are not counted, so 0 really means "migration done".
948
+ */
949
+ export function countPendingOpencodexHistory(
950
+ stateDbPath = resolveCodexStateDbPath(),
951
+ backupPath = historyBackupPathFor(stateDbPath),
952
+ ): PendingHistoryCount {
953
+ let backupEntries = 0;
954
+ try {
955
+ const manifest = readBackup(backupPath, stateDbPath);
956
+ backupEntries = Object.keys(manifest.entries).length;
957
+ } catch { /* unreadable manifest counts as 0 — restore treats it the same way */ }
958
+
959
+ if (!existsSync(stateDbPath)) return { pendingRows: 0, backupEntries };
960
+ try {
961
+ const db = new Database(stateDbPath, { readonly: true });
962
+ try {
963
+ db.exec("PRAGMA busy_timeout = 100");
964
+ const row = db.query<{ n: number }, []>(`
965
+ SELECT count(*) AS n
966
+ FROM threads
967
+ WHERE model_provider = 'opencodex'
968
+ AND trim(coalesce(first_user_message, '')) != ''
969
+ `).get();
970
+ return { pendingRows: row?.n ?? 0, backupEntries };
971
+ } finally {
972
+ db.close();
973
+ }
974
+ } catch (error) {
975
+ if (isRecoverableHistoryError(error)) return { pendingRows: 0, backupEntries, failed: true };
976
+ // Schema drift (e.g. a future codex renames the table) is a "cannot know" too, not a crash.
977
+ return { pendingRows: 0, backupEntries, failed: true };
978
+ }
979
+ }