@iislee/opencodex 2.11.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 (476) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +225 -0
  3. package/assets/architecture.png +0 -0
  4. package/assets/banner.png +0 -0
  5. package/assets/claude-code-models.gif +0 -0
  6. package/assets/codex-app-picker.png +0 -0
  7. package/bin/ocx.mjs +451 -0
  8. package/bin/package-main.mjs +9 -0
  9. package/gui/dist/assets/index-DTpMHS4F.js +67 -0
  10. package/gui/dist/assets/index-ZNVDE3C7.css +1 -0
  11. package/gui/dist/favicon.png +0 -0
  12. package/gui/dist/icons.svg +24 -0
  13. package/gui/dist/index.html +25 -0
  14. package/gui/dist/logo.png +0 -0
  15. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  16. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  17. package/gui/dist/provider-icons/antigravity.svg +1 -0
  18. package/gui/dist/provider-icons/claude-color.svg +1 -0
  19. package/gui/dist/provider-icons/claude.svg +1 -0
  20. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  21. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  22. package/gui/dist/provider-icons/copilot.svg +1 -0
  23. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  24. package/gui/dist/provider-icons/cursor.svg +2 -0
  25. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  26. package/gui/dist/provider-icons/discord.svg +1 -0
  27. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  28. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  29. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  30. package/gui/dist/provider-icons/gemini.svg +1 -0
  31. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  32. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  33. package/gui/dist/provider-icons/grok-color.svg +1 -0
  34. package/gui/dist/provider-icons/grok.svg +1 -0
  35. package/gui/dist/provider-icons/groq-color.svg +1 -0
  36. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  37. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  38. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  39. package/gui/dist/provider-icons/kiro.svg +14 -0
  40. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  41. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  42. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  43. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  44. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  45. package/gui/dist/provider-icons/openai.svg +1 -0
  46. package/gui/dist/provider-icons/opencode.svg +1 -0
  47. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  48. package/gui/dist/provider-icons/pi.svg +21 -0
  49. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  50. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  51. package/gui/dist/provider-icons/telegram.svg +1 -0
  52. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  53. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  54. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  55. package/package.json +102 -0
  56. package/src/AGENTS.md +28 -0
  57. package/src/adapters/anthropic-image-guard.ts +251 -0
  58. package/src/adapters/anthropic-image-normalize.ts +518 -0
  59. package/src/adapters/anthropic.ts +1003 -0
  60. package/src/adapters/azure.ts +36 -0
  61. package/src/adapters/base.ts +72 -0
  62. package/src/adapters/client-fingerprint.ts +59 -0
  63. package/src/adapters/cursor/arg-codec.ts +38 -0
  64. package/src/adapters/cursor/arg-normalize.ts +104 -0
  65. package/src/adapters/cursor/cursor-errors.ts +165 -0
  66. package/src/adapters/cursor/discovery.ts +276 -0
  67. package/src/adapters/cursor/effort-map.ts +127 -0
  68. package/src/adapters/cursor/exec-policy.ts +88 -0
  69. package/src/adapters/cursor/framing.ts +211 -0
  70. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  71. package/src/adapters/cursor/kv-store.ts +52 -0
  72. package/src/adapters/cursor/live-models.ts +153 -0
  73. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  74. package/src/adapters/cursor/live-transport.ts +1214 -0
  75. package/src/adapters/cursor/mcp-config.ts +42 -0
  76. package/src/adapters/cursor/mcp-manager.ts +333 -0
  77. package/src/adapters/cursor/message-mapper.ts +49 -0
  78. package/src/adapters/cursor/native-exec-common.ts +55 -0
  79. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  80. package/src/adapters/cursor/native-exec-fs.ts +329 -0
  81. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  82. package/src/adapters/cursor/native-exec-network.ts +43 -0
  83. package/src/adapters/cursor/native-exec-shell.ts +548 -0
  84. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  85. package/src/adapters/cursor/native-exec.ts +576 -0
  86. package/src/adapters/cursor/protobuf-events.ts +563 -0
  87. package/src/adapters/cursor/protobuf-request.ts +714 -0
  88. package/src/adapters/cursor/request-builder.ts +255 -0
  89. package/src/adapters/cursor/thread-continuity.ts +67 -0
  90. package/src/adapters/cursor/tool-definitions.ts +505 -0
  91. package/src/adapters/cursor/transport-retry.ts +132 -0
  92. package/src/adapters/cursor/transport.ts +57 -0
  93. package/src/adapters/cursor/types.ts +52 -0
  94. package/src/adapters/cursor.ts +196 -0
  95. package/src/adapters/google-antigravity-replay.ts +303 -0
  96. package/src/adapters/google-antigravity-wire.ts +108 -0
  97. package/src/adapters/google-errors.ts +85 -0
  98. package/src/adapters/google-http.ts +100 -0
  99. package/src/adapters/google-tool-schema.ts +173 -0
  100. package/src/adapters/google-truncation.ts +13 -0
  101. package/src/adapters/google-wire-compiler.ts +232 -0
  102. package/src/adapters/google.ts +758 -0
  103. package/src/adapters/identity.ts +44 -0
  104. package/src/adapters/image.ts +23 -0
  105. package/src/adapters/kiro-constants.ts +16 -0
  106. package/src/adapters/kiro-errors.ts +197 -0
  107. package/src/adapters/kiro-events.ts +179 -0
  108. package/src/adapters/kiro-images.ts +129 -0
  109. package/src/adapters/kiro-retry.ts +312 -0
  110. package/src/adapters/kiro-thinking.ts +96 -0
  111. package/src/adapters/kiro-tool-fallback.ts +36 -0
  112. package/src/adapters/kiro-tools.ts +215 -0
  113. package/src/adapters/kiro-truncation.ts +33 -0
  114. package/src/adapters/kiro-wire.ts +129 -0
  115. package/src/adapters/kiro.ts +1898 -0
  116. package/src/adapters/mimo-free.ts +263 -0
  117. package/src/adapters/openai-chat.ts +1005 -0
  118. package/src/adapters/openai-responses.ts +1137 -0
  119. package/src/adapters/run-turn-queue.ts +114 -0
  120. package/src/adapters/tool-catalog-nudge.ts +71 -0
  121. package/src/adapters/upstream-http-error.ts +48 -0
  122. package/src/bridge.ts +1619 -0
  123. package/src/chat/inbound.ts +295 -0
  124. package/src/chat/outbound.ts +765 -0
  125. package/src/claude/agents-inject.ts +243 -0
  126. package/src/claude/alias.ts +149 -0
  127. package/src/claude/auth-detect.ts +229 -0
  128. package/src/claude/auth-mode-migration.ts +32 -0
  129. package/src/claude/auth-mode.ts +62 -0
  130. package/src/claude/context-windows.ts +189 -0
  131. package/src/claude/desktop-3p-guard.ts +35 -0
  132. package/src/claude/desktop-3p-paths.ts +84 -0
  133. package/src/claude/desktop-3p.ts +381 -0
  134. package/src/claude/desktop-health.ts +26 -0
  135. package/src/claude/desktop-profile.ts +263 -0
  136. package/src/claude/gateway-cache.ts +70 -0
  137. package/src/claude/inbound-debug.ts +163 -0
  138. package/src/claude/inbound.ts +509 -0
  139. package/src/claude/model-info.ts +151 -0
  140. package/src/claude/outbound.ts +872 -0
  141. package/src/cli/access.ts +108 -0
  142. package/src/cli/account-api.ts +268 -0
  143. package/src/cli/account-auth.ts +223 -0
  144. package/src/cli/account-extended.ts +350 -0
  145. package/src/cli/account.ts +275 -0
  146. package/src/cli/agent-driven.ts +70 -0
  147. package/src/cli/agent.ts +184 -0
  148. package/src/cli/catalog-prewarm.ts +27 -0
  149. package/src/cli/claude-desktop.ts +188 -0
  150. package/src/cli/claude.ts +286 -0
  151. package/src/cli/codex-shim-autorestore.ts +45 -0
  152. package/src/cli/combo.ts +119 -0
  153. package/src/cli/config-command.ts +145 -0
  154. package/src/cli/debug.ts +228 -0
  155. package/src/cli/doctor.ts +930 -0
  156. package/src/cli/export-command.ts +187 -0
  157. package/src/cli/help.ts +354 -0
  158. package/src/cli/index.ts +1113 -0
  159. package/src/cli/init.ts +224 -0
  160. package/src/cli/integrations.ts +142 -0
  161. package/src/cli/interactive-confirm.ts +133 -0
  162. package/src/cli/internal-dispatch.ts +20 -0
  163. package/src/cli/models-runtime.ts +212 -0
  164. package/src/cli/models.ts +336 -0
  165. package/src/cli/observe.ts +117 -0
  166. package/src/cli/opencode.ts +586 -0
  167. package/src/cli/pi.ts +188 -0
  168. package/src/cli/provider-runtime.ts +162 -0
  169. package/src/cli/provider.ts +463 -0
  170. package/src/cli/runtime-api.ts +325 -0
  171. package/src/cli/star-prompt.ts +155 -0
  172. package/src/cli/status-oauth.ts +78 -0
  173. package/src/cli/status.ts +321 -0
  174. package/src/cli/sync-cloud.ts +283 -0
  175. package/src/cli/system-command.ts +112 -0
  176. package/src/cli/tray-proxy.ts +52 -0
  177. package/src/cli/v2.ts +173 -0
  178. package/src/cli.ts +10 -0
  179. package/src/clients/config-export.ts +377 -0
  180. package/src/clients/effective-status.ts +385 -0
  181. package/src/clients/probes/agy.ts +55 -0
  182. package/src/clients/probes/cc-switch.ts +110 -0
  183. package/src/clients/probes/claude.ts +90 -0
  184. package/src/clients/probes/codex.ts +125 -0
  185. package/src/clients/probes/grok.ts +29 -0
  186. package/src/clients/probes/opencode.ts +109 -0
  187. package/src/clients/probes/paseo.ts +55 -0
  188. package/src/clients/probes/pi.ts +55 -0
  189. package/src/cloud/onedrive-auth.ts +666 -0
  190. package/src/cloud/onedrive-graph.ts +108 -0
  191. package/src/cloud/settings.ts +75 -0
  192. package/src/cloud/sync.ts +212 -0
  193. package/src/cloud/types.ts +56 -0
  194. package/src/cloud/vault.ts +89 -0
  195. package/src/codex/account-id.ts +34 -0
  196. package/src/codex/account-label.ts +34 -0
  197. package/src/codex/account-lifecycle.ts +55 -0
  198. package/src/codex/account-namespace-match.ts +63 -0
  199. package/src/codex/account-namespaces.ts +149 -0
  200. package/src/codex/account-pause.ts +20 -0
  201. package/src/codex/account-runtime-state.ts +31 -0
  202. package/src/codex/account-store.ts +517 -0
  203. package/src/codex/account-usability.ts +20 -0
  204. package/src/codex/app-server-processes.ts +756 -0
  205. package/src/codex/auth-api.ts +1540 -0
  206. package/src/codex/auth-collision.ts +107 -0
  207. package/src/codex/auth-context.ts +352 -0
  208. package/src/codex/autostart-health.ts +149 -0
  209. package/src/codex/catalog/aggregation.ts +378 -0
  210. package/src/codex/catalog/bundled.ts +251 -0
  211. package/src/codex/catalog/effort.ts +355 -0
  212. package/src/codex/catalog/metadata.ts +180 -0
  213. package/src/codex/catalog/parsing.ts +456 -0
  214. package/src/codex/catalog/provider-fetch.ts +902 -0
  215. package/src/codex/catalog/sync.ts +620 -0
  216. package/src/codex/catalog.ts +12 -0
  217. package/src/codex/data/upstream-models.json +830 -0
  218. package/src/codex/exec-invocation.ts +22 -0
  219. package/src/codex/features.ts +969 -0
  220. package/src/codex/history-migration-guardian.ts +102 -0
  221. package/src/codex/history-provider.ts +776 -0
  222. package/src/codex/home.ts +206 -0
  223. package/src/codex/inject.ts +799 -0
  224. package/src/codex/injected-marker.ts +72 -0
  225. package/src/codex/journal.ts +163 -0
  226. package/src/codex/main-account-cache.ts +32 -0
  227. package/src/codex/main-account.ts +40 -0
  228. package/src/codex/model-cache.ts +227 -0
  229. package/src/codex/paths.ts +65 -0
  230. package/src/codex/plugins-doctor.ts +242 -0
  231. package/src/codex/pool-rotation.ts +225 -0
  232. package/src/codex/project-config-warnings.ts +411 -0
  233. package/src/codex/quota.ts +411 -0
  234. package/src/codex/refresh.ts +53 -0
  235. package/src/codex/routing.ts +1477 -0
  236. package/src/codex/runtime.ts +538 -0
  237. package/src/codex/shim.ts +1189 -0
  238. package/src/codex/subagent-defaults.ts +550 -0
  239. package/src/codex/subagent-model-fallback.ts +469 -0
  240. package/src/codex/sync.ts +130 -0
  241. package/src/codex/warmup.ts +192 -0
  242. package/src/codex/websocket-registry.ts +100 -0
  243. package/src/combos/failover.ts +140 -0
  244. package/src/combos/index.ts +41 -0
  245. package/src/combos/request.ts +62 -0
  246. package/src/combos/resolve.ts +232 -0
  247. package/src/combos/types.ts +326 -0
  248. package/src/config.ts +2356 -0
  249. package/src/generated/jawcode-model-metadata.ts +104 -0
  250. package/src/github/star-state.ts +203 -0
  251. package/src/grok/inject.ts +545 -0
  252. package/src/grok/status.ts +121 -0
  253. package/src/grok/sync.ts +103 -0
  254. package/src/grok/usage-hook/report.mjs +348 -0
  255. package/src/grok/usage-hook.ts +278 -0
  256. package/src/images/artifacts.ts +516 -0
  257. package/src/images/fulfill-video.ts +163 -0
  258. package/src/images/fulfill.ts +149 -0
  259. package/src/images/index.ts +4 -0
  260. package/src/images/loop.ts +829 -0
  261. package/src/images/plan.ts +133 -0
  262. package/src/images/synthetic-tool.ts +133 -0
  263. package/src/images/types.ts +41 -0
  264. package/src/images/xai-client.ts +141 -0
  265. package/src/images/xai-video-client.ts +163 -0
  266. package/src/index.ts +22 -0
  267. package/src/lib/abort.ts +146 -0
  268. package/src/lib/admin-secrets.ts +25 -0
  269. package/src/lib/admission.ts +83 -0
  270. package/src/lib/app-owned-memory-stores.ts +173 -0
  271. package/src/lib/app-owned-memory.ts +265 -0
  272. package/src/lib/bounded-body.ts +202 -0
  273. package/src/lib/bun-binary-validator.d.mts +3 -0
  274. package/src/lib/bun-binary-validator.mjs +18 -0
  275. package/src/lib/bun-runtime.ts +71 -0
  276. package/src/lib/bun-stream-caps.ts +126 -0
  277. package/src/lib/config-ownership.ts +360 -0
  278. package/src/lib/crash-guard.ts +344 -0
  279. package/src/lib/debug-log-buffer.ts +83 -0
  280. package/src/lib/debug-settings.ts +108 -0
  281. package/src/lib/debug.ts +31 -0
  282. package/src/lib/destination-policy.ts +316 -0
  283. package/src/lib/errors.ts +364 -0
  284. package/src/lib/eventstream-decoder.ts +253 -0
  285. package/src/lib/gcp-adc.ts +341 -0
  286. package/src/lib/injection-debug-log.ts +58 -0
  287. package/src/lib/open-url.ts +25 -0
  288. package/src/lib/pinned-http.ts +151 -0
  289. package/src/lib/privacy.ts +20 -0
  290. package/src/lib/process-control.ts +165 -0
  291. package/src/lib/provider-outbound.ts +170 -0
  292. package/src/lib/provider-url.ts +14 -0
  293. package/src/lib/proxy-env.ts +18 -0
  294. package/src/lib/redact.ts +105 -0
  295. package/src/lib/retry-after.ts +55 -0
  296. package/src/lib/service-secrets.ts +25 -0
  297. package/src/lib/shadow-call.ts +30 -0
  298. package/src/lib/sidecar-tracker.ts +52 -0
  299. package/src/lib/sse-decoder.ts +323 -0
  300. package/src/lib/state-store-registrations.ts +109 -0
  301. package/src/lib/state-store-sweeper.ts +184 -0
  302. package/src/lib/test-home-guard.ts +90 -0
  303. package/src/lib/token-estimate.ts +69 -0
  304. package/src/lib/translator-budget.ts +356 -0
  305. package/src/lib/upstream-retry.ts +239 -0
  306. package/src/lib/win-exec.ts +115 -0
  307. package/src/lib/win-paths.ts +68 -0
  308. package/src/lib/windows-elevation.ts +705 -0
  309. package/src/lib/windows-secret-acl.ts +514 -0
  310. package/src/lib/winsw.ts +375 -0
  311. package/src/oauth/anthropic-routing.ts +594 -0
  312. package/src/oauth/anthropic.ts +177 -0
  313. package/src/oauth/callback-server.ts +294 -0
  314. package/src/oauth/chatgpt.ts +150 -0
  315. package/src/oauth/cursor.ts +211 -0
  316. package/src/oauth/github-copilot.ts +428 -0
  317. package/src/oauth/google-antigravity.ts +230 -0
  318. package/src/oauth/health.ts +399 -0
  319. package/src/oauth/index.ts +1174 -0
  320. package/src/oauth/key-providers.ts +108 -0
  321. package/src/oauth/kimi.ts +213 -0
  322. package/src/oauth/kiro-credentials.ts +726 -0
  323. package/src/oauth/kiro.ts +577 -0
  324. package/src/oauth/local-token-detect.ts +121 -0
  325. package/src/oauth/log.ts +48 -0
  326. package/src/oauth/login-cli.ts +163 -0
  327. package/src/oauth/pkce.ts +15 -0
  328. package/src/oauth/store.ts +630 -0
  329. package/src/oauth/token-guardian.ts +303 -0
  330. package/src/oauth/types.ts +62 -0
  331. package/src/oauth/xai.ts +241 -0
  332. package/src/pi/extensions.ts +72 -0
  333. package/src/pi/home.ts +42 -0
  334. package/src/pi/index.ts +40 -0
  335. package/src/pi/models.ts +278 -0
  336. package/src/pi/packages.ts +219 -0
  337. package/src/pi/settings.ts +365 -0
  338. package/src/pi/status.ts +68 -0
  339. package/src/pi/sync.ts +75 -0
  340. package/src/providers/alibaba-region-backup.ts +75 -0
  341. package/src/providers/alibaba-region-migration.ts +156 -0
  342. package/src/providers/alibaba-region-startup.ts +36 -0
  343. package/src/providers/antigravity-models.ts +205 -0
  344. package/src/providers/api-keys.ts +140 -0
  345. package/src/providers/base-url-choices.ts +64 -0
  346. package/src/providers/context-cap.ts +65 -0
  347. package/src/providers/derive.ts +339 -0
  348. package/src/providers/free-directory.ts +184 -0
  349. package/src/providers/github-copilot-transport.ts +56 -0
  350. package/src/providers/key-failover.ts +203 -0
  351. package/src/providers/kiro-models.ts +67 -0
  352. package/src/providers/label.ts +19 -0
  353. package/src/providers/model-discovery.ts +356 -0
  354. package/src/providers/openai-sidecar.ts +175 -0
  355. package/src/providers/openai-tier-startup.ts +27 -0
  356. package/src/providers/openai-tiers.ts +301 -0
  357. package/src/providers/openai-virtual-models.ts +82 -0
  358. package/src/providers/openrouter-routing.ts +102 -0
  359. package/src/providers/provider-id-rewrite.ts +150 -0
  360. package/src/providers/quota.ts +1260 -0
  361. package/src/providers/registry.ts +1600 -0
  362. package/src/providers/slug-codec.ts +67 -0
  363. package/src/providers/xai-transport.ts +141 -0
  364. package/src/reasoning-effort.ts +135 -0
  365. package/src/responses/compaction.ts +117 -0
  366. package/src/responses/parser.ts +656 -0
  367. package/src/responses/reasoning-envelope.ts +52 -0
  368. package/src/responses/schema.ts +159 -0
  369. package/src/responses/spill-store.ts +394 -0
  370. package/src/responses/state.ts +895 -0
  371. package/src/responses/tool-groups.ts +19 -0
  372. package/src/router.ts +425 -0
  373. package/src/server/adapter-resolve.ts +80 -0
  374. package/src/server/auth-cors.ts +530 -0
  375. package/src/server/chat-completions.ts +368 -0
  376. package/src/server/claude-messages.ts +914 -0
  377. package/src/server/effort-policy.ts +172 -0
  378. package/src/server/gui-static.ts +123 -0
  379. package/src/server/image-retry.ts +42 -0
  380. package/src/server/images.ts +476 -0
  381. package/src/server/index.ts +1126 -0
  382. package/src/server/lifecycle.ts +227 -0
  383. package/src/server/live.ts +598 -0
  384. package/src/server/management/agent-settings-routes.ts +1169 -0
  385. package/src/server/management/api-access.ts +141 -0
  386. package/src/server/management/api-key-usage.ts +167 -0
  387. package/src/server/management/body.ts +35 -0
  388. package/src/server/management/clients-routes.ts +63 -0
  389. package/src/server/management/cloud-sync-routes.ts +266 -0
  390. package/src/server/management/combo-routes.ts +220 -0
  391. package/src/server/management/config-routes.ts +422 -0
  392. package/src/server/management/context.ts +31 -0
  393. package/src/server/management/logs-usage-routes.ts +707 -0
  394. package/src/server/management/model-routes.ts +525 -0
  395. package/src/server/management/oauth-account-routes.ts +563 -0
  396. package/src/server/management/provider-routes.ts +556 -0
  397. package/src/server/management/shared.ts +277 -0
  398. package/src/server/management/sidebar-routes.ts +90 -0
  399. package/src/server/management/system-restart.ts +179 -0
  400. package/src/server/management/system-routes.ts +117 -0
  401. package/src/server/management/usage-summary-cache.ts +86 -0
  402. package/src/server/management-api.ts +215 -0
  403. package/src/server/management-auth.ts +267 -0
  404. package/src/server/memory-watchdog.ts +156 -0
  405. package/src/server/port-reclaim.ts +307 -0
  406. package/src/server/ports.ts +116 -0
  407. package/src/server/proxy-liveness.ts +201 -0
  408. package/src/server/relay-eager.ts +313 -0
  409. package/src/server/relay.ts +1049 -0
  410. package/src/server/request-decompress.ts +132 -0
  411. package/src/server/request-log-conversation.ts +168 -0
  412. package/src/server/request-log.ts +1046 -0
  413. package/src/server/responses/collaboration.ts +354 -0
  414. package/src/server/responses/compact.ts +384 -0
  415. package/src/server/responses/core.ts +2758 -0
  416. package/src/server/responses/encrypted-payload.ts +308 -0
  417. package/src/server/responses/fetch-helpers.ts +157 -0
  418. package/src/server/responses/passthrough-error.ts +78 -0
  419. package/src/server/responses/terminal-guard.ts +230 -0
  420. package/src/server/responses/upstream-error.ts +48 -0
  421. package/src/server/responses-image-gen-repair.ts +132 -0
  422. package/src/server/responses-item-id-repair.ts +224 -0
  423. package/src/server/responses.ts +9 -0
  424. package/src/server/search.ts +136 -0
  425. package/src/server/sse-payload-rewrite.ts +175 -0
  426. package/src/server/startup-action-control.ts +308 -0
  427. package/src/server/startup-health-cache.ts +113 -0
  428. package/src/server/system-env.ts +413 -0
  429. package/src/server/windows-tcp-drop.ts +184 -0
  430. package/src/server/windows-tray-control.ts +41 -0
  431. package/src/server/ws-bridge.ts +471 -0
  432. package/src/service.ts +2554 -0
  433. package/src/stall-timeout.ts +20 -0
  434. package/src/storage/cleanup-job.ts +57 -0
  435. package/src/storage/cleanup.ts +3085 -0
  436. package/src/storage/policy-job.ts +457 -0
  437. package/src/storage/policy-scheduler.ts +40 -0
  438. package/src/storage/policy-worker.ts +59 -0
  439. package/src/storage/policy.ts +527 -0
  440. package/src/storage/restore-job.ts +299 -0
  441. package/src/storage/restore-worker.ts +58 -0
  442. package/src/storage/scanner.ts +238 -0
  443. package/src/storage/storage-mutation-coordinator.ts +139 -0
  444. package/src/storage/worker-lifecycle.ts +215 -0
  445. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  446. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  447. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  448. package/src/tray/assets/opencodex-tray.png +0 -0
  449. package/src/tray/windows-tray.ps1 +290 -0
  450. package/src/tray/windows.ts +730 -0
  451. package/src/types.ts +1237 -0
  452. package/src/update/badge.ts +72 -0
  453. package/src/update/index.ts +407 -0
  454. package/src/update/job.ts +1520 -0
  455. package/src/update/notify.ts +257 -0
  456. package/src/update/npm-invocation.d.mts +23 -0
  457. package/src/update/npm-invocation.mjs +94 -0
  458. package/src/update/tray-update-plan.d.mts +18 -0
  459. package/src/update/tray-update-plan.mjs +38 -0
  460. package/src/usage/cost.ts +0 -0
  461. package/src/usage/debug.ts +97 -0
  462. package/src/usage/expected-prices.ts +164 -0
  463. package/src/usage/log.ts +658 -0
  464. package/src/usage/summary.ts +585 -0
  465. package/src/usage/totals.ts +14 -0
  466. package/src/vision/anthropic-describe.ts +185 -0
  467. package/src/vision/describe.ts +125 -0
  468. package/src/vision/index.ts +467 -0
  469. package/src/web-search/anthropic-executor.ts +189 -0
  470. package/src/web-search/executor.ts +105 -0
  471. package/src/web-search/format-result.ts +89 -0
  472. package/src/web-search/index.ts +196 -0
  473. package/src/web-search/loop.ts +664 -0
  474. package/src/web-search/parse.ts +220 -0
  475. package/src/web-search/progress-stream.ts +342 -0
  476. package/src/web-search/synthetic-tool.ts +47 -0
@@ -0,0 +1,527 @@
1
+ /**
2
+ * Phase 3 opt-in auto-cleanup policy (issue #42).
3
+ *
4
+ * Default OFF — never enabled implicitly. Selects oldest archived sessions via
5
+ * Phase 2 helpers and runs `executeArchivedCleanup` in quarantine/permanent mode.
6
+ * On `codex_busy`, defers (updates `nextRun`) without mutating archives.
7
+ *
8
+ * Privacy: logs never include host paths, digests of file contents, or secrets.
9
+ */
10
+ import { resolveCodexHomeDir } from "../codex/home";
11
+ import { loadConfig, saveConfigPreservingClaudeCode } from "../config";
12
+ import type { StorageCleanupPolicy } from "../types";
13
+ import {
14
+ computePreviewDigest,
15
+ executeArchivedCleanup,
16
+ filterCandidatesExcludingPinned,
17
+ listArchivedCandidates,
18
+ previewExactArchivedCleanup,
19
+ selectOldestPercentSkippingPendingRestore,
20
+ selectReduceToBytesSkippingPendingRestore,
21
+ type CleanupMode,
22
+ type CleanupResult,
23
+ type ExecuteCleanupOptions,
24
+ } from "./cleanup";
25
+
26
+ export const DEFAULT_ARCHIVED_BYTES_OVER = 5 * 1024 ** 3; // 5 GiB
27
+ export const DEFAULT_REMOVE_OLDEST_PERCENT = 25;
28
+ export const BUSY_DEFER_MS = 15 * 60 * 1000;
29
+
30
+ /** Optional sink so background policy writes stay synced with the live server config. */
31
+ let livePolicySink: ((policy: StorageCleanupPolicy) => void) | undefined;
32
+
33
+ export function setStorageCleanupPolicyLiveSink(
34
+ sink: ((policy: StorageCleanupPolicy) => void) | null,
35
+ ): void {
36
+ livePolicySink = sink ?? undefined;
37
+ }
38
+
39
+ export type PolicySchedule = StorageCleanupPolicy["schedule"];
40
+ export type PolicyRunReason = "startup" | "schedule" | "manual";
41
+
42
+ export type PolicySkipReason =
43
+ | "disabled"
44
+ | "not_due"
45
+ | "under_threshold"
46
+ | "nothing_selected";
47
+
48
+ export interface PolicyRunResult {
49
+ ok: boolean;
50
+ skipped?: PolicySkipReason;
51
+ deferred?: "codex_busy";
52
+ error?: CleanupResult["error"];
53
+ mode?: CleanupMode;
54
+ freedBytes?: number;
55
+ removed?: number;
56
+ trashDir?: string;
57
+ policy: StorageCleanupPolicy;
58
+ }
59
+
60
+ export interface PolicyRunDeps {
61
+ now?: number;
62
+ reason: PolicyRunReason;
63
+ codexHome?: string;
64
+ /** Bypass due-window checks (tests / explicit force). Still respects enabled. */
65
+ force?: boolean;
66
+ loadPolicy?: () => StorageCleanupPolicy;
67
+ savePolicy?: (policy: StorageCleanupPolicy) => void;
68
+ execute?: (options: ExecuteCleanupOptions) => CleanupResult;
69
+ busyTimeoutMs?: number;
70
+ /**
71
+ * Test-only: block after the start-of-job policy load so a concurrent PUT can
72
+ * land before completion merges run metadata.
73
+ */
74
+ holdAfterLoadMs?: number;
75
+ }
76
+
77
+ /** Canonical defaults — enabled is always false. */
78
+ export function defaultStorageCleanupPolicy(): StorageCleanupPolicy {
79
+ return {
80
+ enabled: false,
81
+ trigger: { archivedBytesOver: DEFAULT_ARCHIVED_BYTES_OVER },
82
+ target: { removeOldestPercent: DEFAULT_REMOVE_OLDEST_PERCENT },
83
+ schedule: "manual",
84
+ mode: "quarantine",
85
+ };
86
+ }
87
+
88
+ function isFiniteNonNegInt(n: unknown): n is number {
89
+ return typeof n === "number" && Number.isFinite(n) && n >= 0 && Math.floor(n) === n;
90
+ }
91
+
92
+ function isFinitePositiveInt(n: unknown): n is number {
93
+ return typeof n === "number" && Number.isFinite(n) && n > 0 && Math.floor(n) === n;
94
+ }
95
+
96
+ /** True when target has exactly one of reduceToBytes / removeOldestPercent. */
97
+ export function isValidPolicyTarget(target: StorageCleanupPolicy["target"]): boolean {
98
+ if (!target || typeof target !== "object") return false;
99
+ const reduce = (target as { reduceToBytes?: unknown }).reduceToBytes;
100
+ const percent = (target as { removeOldestPercent?: unknown }).removeOldestPercent;
101
+ const hasReduce = reduce !== undefined;
102
+ const hasPercent = percent !== undefined;
103
+ if (hasReduce === hasPercent) return false; // none or both
104
+ if (hasReduce) return isFiniteNonNegInt(reduce);
105
+ return typeof percent === "number" && Number.isFinite(percent) && percent > 0 && percent <= 100;
106
+ }
107
+
108
+ /**
109
+ * Normalize a partial/unknown policy into a complete StorageCleanupPolicy.
110
+ * Never flips enabled to true unless the input explicitly sets enabled: true.
111
+ */
112
+ export function normalizeStorageCleanupPolicy(raw: unknown): StorageCleanupPolicy {
113
+ const base = defaultStorageCleanupPolicy();
114
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return base;
115
+ const o = raw as Record<string, unknown>;
116
+
117
+ // only explicit true enables — and never when a present target is malformed
118
+ let enabled = o.enabled === true;
119
+
120
+ let archivedBytesOver = base.trigger.archivedBytesOver;
121
+ if (o.trigger && typeof o.trigger === "object" && !Array.isArray(o.trigger)) {
122
+ const t = (o.trigger as { archivedBytesOver?: unknown }).archivedBytesOver;
123
+ if (isFiniteNonNegInt(t)) archivedBytesOver = t;
124
+ }
125
+
126
+ let target: StorageCleanupPolicy["target"] = base.target;
127
+ if (Object.prototype.hasOwnProperty.call(o, "target")) {
128
+ if (o.target && typeof o.target === "object" && !Array.isArray(o.target)) {
129
+ const candidate = o.target as StorageCleanupPolicy["target"];
130
+ if (isValidPolicyTarget(candidate)) {
131
+ const reduce = (candidate as { reduceToBytes?: number }).reduceToBytes;
132
+ const percent = (candidate as { removeOldestPercent?: number }).removeOldestPercent;
133
+ target = reduce !== undefined
134
+ ? { reduceToBytes: reduce }
135
+ : { removeOldestPercent: Math.min(100, Math.max(1, Math.floor(percent!))) };
136
+ } else {
137
+ // Fail closed: malformed persisted target must not become delete-oldest 25%.
138
+ enabled = false;
139
+ }
140
+ } else {
141
+ enabled = false;
142
+ }
143
+ }
144
+
145
+ const schedule: PolicySchedule =
146
+ o.schedule === "startup" || o.schedule === "daily" || o.schedule === "weekly" || o.schedule === "manual"
147
+ ? o.schedule
148
+ : base.schedule;
149
+
150
+ const mode: CleanupMode = o.mode === "permanent" ? "permanent" : "quarantine";
151
+
152
+ let lastRun: StorageCleanupPolicy["lastRun"];
153
+ if (o.lastRun && typeof o.lastRun === "object" && !Array.isArray(o.lastRun)) {
154
+ const lr = o.lastRun as Record<string, unknown>;
155
+ if (
156
+ isFinitePositiveInt(lr.at)
157
+ && isFiniteNonNegInt(lr.freedBytes)
158
+ && isFiniteNonNegInt(lr.removed)
159
+ ) {
160
+ lastRun = { at: lr.at, freedBytes: lr.freedBytes, removed: lr.removed };
161
+ }
162
+ }
163
+
164
+ let nextRun: number | undefined;
165
+ if (o.nextRun === undefined || o.nextRun === null) {
166
+ nextRun = undefined;
167
+ } else if (isFinitePositiveInt(o.nextRun)) {
168
+ nextRun = o.nextRun;
169
+ }
170
+
171
+ return {
172
+ enabled,
173
+ trigger: { archivedBytesOver },
174
+ target,
175
+ schedule,
176
+ mode,
177
+ ...(lastRun ? { lastRun } : {}),
178
+ ...(nextRun !== undefined ? { nextRun } : {}),
179
+ };
180
+ }
181
+
182
+ /**
183
+ * Validate a PUT body. Returns a normalized policy or an error string.
184
+ * Does not invent enabled=true.
185
+ */
186
+ export function parseStorageCleanupPolicyInput(
187
+ raw: unknown,
188
+ previous?: StorageCleanupPolicy,
189
+ ): { ok: true; policy: StorageCleanupPolicy } | { ok: false; error: string } {
190
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
191
+ return { ok: false, error: "body must be a JSON object" };
192
+ }
193
+ const o = raw as Record<string, unknown>;
194
+ const prev = previous ?? defaultStorageCleanupPolicy();
195
+
196
+ if (o.enabled !== undefined && typeof o.enabled !== "boolean") {
197
+ return { ok: false, error: "enabled must be a boolean" };
198
+ }
199
+ if (o.mode !== undefined && o.mode !== "quarantine" && o.mode !== "permanent") {
200
+ return { ok: false, error: "mode must be quarantine or permanent" };
201
+ }
202
+ if (
203
+ o.schedule !== undefined
204
+ && o.schedule !== "startup"
205
+ && o.schedule !== "daily"
206
+ && o.schedule !== "weekly"
207
+ && o.schedule !== "manual"
208
+ ) {
209
+ return { ok: false, error: "schedule must be startup, daily, weekly, or manual" };
210
+ }
211
+
212
+ const merged: Record<string, unknown> = {
213
+ ...prev,
214
+ ...o,
215
+ trigger: o.trigger !== undefined ? o.trigger : prev.trigger,
216
+ target: o.target !== undefined ? o.target : prev.target,
217
+ // Preserve run metadata unless the client explicitly sends replacements.
218
+ lastRun: o.lastRun !== undefined ? o.lastRun : prev.lastRun,
219
+ nextRun: o.nextRun !== undefined ? o.nextRun : prev.nextRun,
220
+ };
221
+
222
+ if (o.trigger !== undefined) {
223
+ if (!o.trigger || typeof o.trigger !== "object" || Array.isArray(o.trigger)) {
224
+ return { ok: false, error: "trigger must be an object" };
225
+ }
226
+ const bytes = (o.trigger as { archivedBytesOver?: unknown }).archivedBytesOver;
227
+ if (!isFiniteNonNegInt(bytes)) {
228
+ return { ok: false, error: "trigger.archivedBytesOver must be a non-negative integer" };
229
+ }
230
+ }
231
+
232
+ if (o.target !== undefined) {
233
+ if (!isValidPolicyTarget(o.target as StorageCleanupPolicy["target"])) {
234
+ return {
235
+ ok: false,
236
+ error: "target must set exactly one of reduceToBytes (non-negative int) or removeOldestPercent (1-100)",
237
+ };
238
+ }
239
+ }
240
+
241
+ // Clients must not clear lastRun/nextRun via null unless intentional — accept omit only.
242
+ const policy = normalizeStorageCleanupPolicy(merged);
243
+ // Recompute nextRun only when schedule newly becomes timed, or no valid nextRun exists.
244
+ // Do not reset nextRun on every PUT that merely re-sends an unchanged schedule.
245
+ if (
246
+ (policy.schedule === "daily" || policy.schedule === "weekly")
247
+ && o.nextRun === undefined
248
+ && (policy.nextRun === undefined || (o.schedule !== undefined && o.schedule !== prev.schedule))
249
+ ) {
250
+ policy.nextRun = computeNextRun(policy.schedule, Date.now());
251
+ }
252
+ if (policy.schedule === "manual" || policy.schedule === "startup") {
253
+ if (o.nextRun === undefined) delete policy.nextRun;
254
+ }
255
+
256
+ return { ok: true, policy };
257
+ }
258
+
259
+ export function readStorageCleanupPolicyFromConfig(): StorageCleanupPolicy {
260
+ return normalizeStorageCleanupPolicy(loadConfig().storageCleanupPolicy);
261
+ }
262
+
263
+ export function writeStorageCleanupPolicyToConfig(policy: StorageCleanupPolicy): StorageCleanupPolicy {
264
+ const normalized = normalizeStorageCleanupPolicy(policy);
265
+ const config = loadConfig();
266
+ config.storageCleanupPolicy = normalized;
267
+ saveConfigPreservingClaudeCode(config);
268
+ livePolicySink?.(normalized);
269
+ return normalized;
270
+ }
271
+
272
+ /** Wall-clock next run for daily/weekly. Startup/manual → undefined. */
273
+ export function computeNextRun(schedule: PolicySchedule, now: number): number | undefined {
274
+ if (schedule === "daily") return now + 24 * 60 * 60 * 1000;
275
+ if (schedule === "weekly") return now + 7 * 24 * 60 * 60 * 1000;
276
+ return undefined;
277
+ }
278
+
279
+ export function isPolicyDue(
280
+ policy: StorageCleanupPolicy,
281
+ now: number,
282
+ reason: PolicyRunReason,
283
+ ): boolean {
284
+ if (!policy.enabled) return false;
285
+ if (reason === "manual") return true;
286
+ if (policy.schedule === "manual") return false;
287
+ if (policy.schedule === "startup") {
288
+ if (reason === "startup") return true;
289
+ // Honor deferred nextRun (e.g. codex_busy at launch) on later scheduler ticks.
290
+ return reason === "schedule"
291
+ && policy.nextRun !== undefined
292
+ && now >= policy.nextRun;
293
+ }
294
+ // daily / weekly: due when nextRun unset or elapsed
295
+ if (policy.nextRun === undefined) return true;
296
+ return now >= policy.nextRun;
297
+ }
298
+
299
+ /** Oldest-first until archived total would drop to `reduceToBytes`. */
300
+ export function selectReduceToBytes(
301
+ candidates: ReturnType<typeof listArchivedCandidates>,
302
+ reduceToBytes: number,
303
+ ): ReturnType<typeof listArchivedCandidates> {
304
+ if (!Number.isFinite(reduceToBytes) || reduceToBytes < 0) return [];
305
+ const total = candidates.reduce((sum, c) => sum + c.bytes, 0);
306
+ if (total <= reduceToBytes) return [];
307
+ const need = total - reduceToBytes;
308
+ const out: typeof candidates = [];
309
+ let freed = 0;
310
+ for (const c of candidates) {
311
+ out.push(c);
312
+ freed += c.bytes;
313
+ if (freed >= need) break;
314
+ }
315
+ return out;
316
+ }
317
+
318
+ /**
319
+ * Minimal percent in 1..100 such that `selectOldestPercent` returns at least `n` of `m`.
320
+ * Returns 0 when n<=0, 100 when n>=m.
321
+ */
322
+ export function percentForAtLeastCount(totalCount: number, selectedCount: number): number {
323
+ if (selectedCount <= 0 || totalCount <= 0) return 0;
324
+ if (selectedCount >= totalCount) return 100;
325
+ for (let pct = 1; pct <= 100; pct++) {
326
+ const got = Math.max(1, Math.floor((totalCount * pct) / 100));
327
+ if (got >= selectedCount) return pct;
328
+ }
329
+ return 100;
330
+ }
331
+
332
+ export interface PolicySelection {
333
+ archivedBytes: number;
334
+ percent: number;
335
+ count: number;
336
+ bytes: number;
337
+ digest: string;
338
+ /** Present when selection is an exact candidate list (reduceToBytes). */
339
+ candidateRelPaths?: string[];
340
+ }
341
+
342
+ /** Build a Phase-2-compatible preview for the active policy target. */
343
+ export function selectPolicyPreview(
344
+ policy: StorageCleanupPolicy,
345
+ codexHome: string = resolveCodexHomeDir(),
346
+ ): PolicySelection {
347
+ const all = listArchivedCandidates(codexHome);
348
+ const archivedBytes = all.reduce((sum, c) => sum + c.bytes, 0);
349
+
350
+ const reduceTo = (policy.target as { reduceToBytes?: number }).reduceToBytes;
351
+ const removePct = (policy.target as { removeOldestPercent?: number }).removeOldestPercent;
352
+
353
+ if (reduceTo !== undefined) {
354
+ // Exact candidate set — do not approximate via percent (would over-delete).
355
+ const desired = selectReduceToBytesSkippingPendingRestore(all, reduceTo, codexHome);
356
+ const preview = previewExactArchivedCleanup(desired, codexHome);
357
+ return {
358
+ archivedBytes,
359
+ percent: 0,
360
+ count: preview.count,
361
+ bytes: preview.bytes,
362
+ digest: preview.digest,
363
+ candidateRelPaths: preview.candidates.map(c => c.relPath),
364
+ };
365
+ }
366
+
367
+ // Reuse the already-listed candidates — avoid a second archive directory walk.
368
+ const percent = Math.min(100, Math.max(0, Math.floor(removePct ?? 0)));
369
+ const selected = selectOldestPercentSkippingPendingRestore(
370
+ filterCandidatesExcludingPinned(all, codexHome),
371
+ percent,
372
+ codexHome,
373
+ );
374
+ return {
375
+ archivedBytes,
376
+ percent,
377
+ count: selected.length,
378
+ bytes: selected.reduce((sum, c) => sum + c.bytes, 0),
379
+ digest: computePreviewDigest(selected, percent),
380
+ };
381
+ }
382
+
383
+ function advanceNextRun(policy: StorageCleanupPolicy, now: number): StorageCleanupPolicy {
384
+ const next = { ...policy };
385
+ const nextRun = computeNextRun(policy.schedule, now);
386
+ if (nextRun === undefined) delete next.nextRun;
387
+ else next.nextRun = nextRun;
388
+ return next;
389
+ }
390
+
391
+ function deferBusy(policy: StorageCleanupPolicy, now: number): StorageCleanupPolicy {
392
+ return { ...policy, nextRun: now + BUSY_DEFER_MS };
393
+ }
394
+
395
+ export type PolicyRunMetadataPatch = {
396
+ now: number;
397
+ /** Advance nextRun from the *latest* schedule, or defer on codex_busy. */
398
+ nextRun: "advance" | "defer_busy";
399
+ lastRun?: StorageCleanupPolicy["lastRun"];
400
+ };
401
+
402
+ /**
403
+ * Reload the latest persisted policy and write only run-owned metadata
404
+ * (`lastRun` / `nextRun`). Preserves concurrent edits to enabled, trigger,
405
+ * target, schedule, and mode that landed while a long run was in flight.
406
+ */
407
+ export function commitPolicyRunMetadata(
408
+ load: () => StorageCleanupPolicy,
409
+ save: (policy: StorageCleanupPolicy) => void,
410
+ patch: PolicyRunMetadataPatch,
411
+ ): StorageCleanupPolicy {
412
+ const latest = normalizeStorageCleanupPolicy(load());
413
+ let next =
414
+ patch.nextRun === "defer_busy"
415
+ ? deferBusy(latest, patch.now)
416
+ : advanceNextRun(latest, patch.now);
417
+ if (patch.lastRun) {
418
+ next = { ...next, lastRun: patch.lastRun };
419
+ }
420
+ save(next);
421
+ return next;
422
+ }
423
+
424
+ function logPolicyEvent(message: string): void {
425
+ console.log(`[storage-policy] ${message}`);
426
+ }
427
+
428
+ /**
429
+ * Evaluate and optionally execute the storage cleanup policy.
430
+ * Disabled / under-threshold / not-due paths never call execute.
431
+ */
432
+ export function runStorageCleanupPolicy(deps: PolicyRunDeps): PolicyRunResult {
433
+ const now = deps.now ?? Date.now();
434
+ const load = deps.loadPolicy ?? readStorageCleanupPolicyFromConfig;
435
+ const save = deps.savePolicy ?? writeStorageCleanupPolicyToConfig;
436
+ const execute = deps.execute ?? executeArchivedCleanup;
437
+
438
+ const policy = normalizeStorageCleanupPolicy(load());
439
+
440
+ if (typeof deps.holdAfterLoadMs === "number" && Number.isFinite(deps.holdAfterLoadMs) && deps.holdAfterLoadMs > 0) {
441
+ Bun.sleepSync(Math.floor(deps.holdAfterLoadMs));
442
+ }
443
+
444
+ if (!policy.enabled) {
445
+ return { ok: true, skipped: "disabled", policy };
446
+ }
447
+
448
+ if (!deps.force && !isPolicyDue(policy, now, deps.reason)) {
449
+ return { ok: true, skipped: "not_due", policy };
450
+ }
451
+
452
+ const selection = selectPolicyPreview(policy, deps.codexHome);
453
+ if (selection.archivedBytes <= policy.trigger.archivedBytesOver) {
454
+ const saved = commitPolicyRunMetadata(load, save, { now, nextRun: "advance" });
455
+ logPolicyEvent("skip under_threshold");
456
+ return { ok: true, skipped: "under_threshold", policy: saved };
457
+ }
458
+
459
+ if (selection.count === 0) {
460
+ const saved = commitPolicyRunMetadata(load, save, { now, nextRun: "advance" });
461
+ logPolicyEvent("skip nothing_selected");
462
+ return { ok: true, skipped: "nothing_selected", policy: saved };
463
+ }
464
+
465
+ const result = execute({
466
+ percent: selection.percent,
467
+ mode: policy.mode,
468
+ digest: selection.digest,
469
+ ...(selection.candidateRelPaths ? { candidateRelPaths: selection.candidateRelPaths } : {}),
470
+ ...(deps.codexHome ? { codexHome: deps.codexHome } : {}),
471
+ ...(deps.busyTimeoutMs !== undefined ? { busyTimeoutMs: deps.busyTimeoutMs } : {}),
472
+ now,
473
+ });
474
+
475
+ if (!result.ok && result.error === "codex_busy") {
476
+ const saved = commitPolicyRunMetadata(load, save, { now, nextRun: "defer_busy" });
477
+ logPolicyEvent("defer codex_busy");
478
+ return { ok: false, deferred: "codex_busy", error: "codex_busy", policy: saved };
479
+ }
480
+
481
+ if (!result.ok) {
482
+ // Non-busy failure: still advance schedule so we do not tight-loop.
483
+ const saved = commitPolicyRunMetadata(load, save, { now, nextRun: "advance" });
484
+ logPolicyEvent(`fail ${result.error ?? "cleanup_failed"}`);
485
+ return {
486
+ ok: false,
487
+ error: result.error,
488
+ mode: result.mode,
489
+ ...(result.trashDir ? { trashDir: result.trashDir } : {}),
490
+ policy: saved,
491
+ };
492
+ }
493
+
494
+ const saved = commitPolicyRunMetadata(load, save, {
495
+ now,
496
+ nextRun: "advance",
497
+ lastRun: {
498
+ at: now,
499
+ freedBytes: result.bytes,
500
+ removed: result.count,
501
+ },
502
+ });
503
+ logPolicyEvent(
504
+ `ok mode=${result.mode} removed=${result.count} freedBytes=${result.bytes}`,
505
+ );
506
+ return {
507
+ ok: true,
508
+ mode: result.mode,
509
+ freedBytes: result.bytes,
510
+ removed: result.count,
511
+ ...(result.trashDir ? { trashDir: result.trashDir } : {}),
512
+ policy: saved,
513
+ };
514
+ }
515
+
516
+ /** Startup / schedule tick entry — swallows unexpected errors. */
517
+ export function maybeRunDueStorageCleanupPolicy(
518
+ reason: PolicyRunReason,
519
+ deps?: Omit<PolicyRunDeps, "reason">,
520
+ ): PolicyRunResult | null {
521
+ try {
522
+ return runStorageCleanupPolicy({ ...deps, reason });
523
+ } catch {
524
+ logPolicyEvent("error evaluation_failed");
525
+ return null;
526
+ }
527
+ }