@iislee/opencodex 2.11.0 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
@@ -0,0 +1,92 @@
1
+ /**
2
+ * The one place that decides whether a sidecar may treat ChatGPT or Anthropic
3
+ * auth as PRESENT (#2188). Web-search and vision consumed two hand-rolled
4
+ * copies of the Anthropic predicate and no Codex-login predicate at all —
5
+ * provider presence was standing in for "logged in", which let a fresh install
6
+ * with the built-in forward provider but no credential offer Luna as a
7
+ * describer it could never run.
8
+ *
9
+ * Both flags are request-context-free: they read config plus the stored
10
+ * account state, never headers. Per-request usability (exact accounts,
11
+ * generation fences) stays in resolveFirstUsableOpenAiSidecar and the
12
+ * executors; this module only answers "is this side worth offering at all?".
13
+ */
14
+ import type { OcxConfig, OcxProviderConfig } from "../types";
15
+ import { listOpenAiForwardSidecarCandidates } from "../providers/openai-sidecar";
16
+ import { OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
17
+ import { isCodexAccountUsable } from "../codex/account-usability";
18
+ import { MAIN_CODEX_ACCOUNT_ID, isSelectableCodexPoolAccount } from "../codex/account-id";
19
+ import { getAccountSet } from "../oauth/store";
20
+
21
+ export interface SidecarAuthState {
22
+ /** ChatGPT login usable: canonical forward provider AND a live stored credential. */
23
+ isCodexAuth: boolean;
24
+ /** Enabled anthropic-adapter OAuth provider whose active account is not marked for reauth. */
25
+ isAnthropicAuth: boolean;
26
+ /** The provider an Anthropic-side executor would dispatch through, when isAnthropicAuth. */
27
+ anthropicProviderName?: string;
28
+ anthropicProvider?: OcxProviderConfig;
29
+ }
30
+
31
+ /**
32
+ * Fixed auth-slot models (#2188): logged-in sides keep these candidates even
33
+ * when the picker hides or disables them. The slot is the LOGIN's entitlement,
34
+ * not the catalog's.
35
+ */
36
+ export const AUTH_SLOT_MODELS = {
37
+ codex: "gpt-5.6-luna",
38
+ anthropic: "claude-haiku-4-5",
39
+ } as const;
40
+
41
+ export interface SidecarAuthSlot {
42
+ provider: string;
43
+ id: string;
44
+ slot: keyof typeof AUTH_SLOT_MODELS;
45
+ }
46
+
47
+ /** Login-shaped, not provider-shaped: a forward provider with no credential is NOT Codex auth. */
48
+ function hasUsableCodexLogin(config: OcxConfig): boolean {
49
+ if (listOpenAiForwardSidecarCandidates(config).length === 0) return false;
50
+ if (isCodexAccountUsable(config, MAIN_CODEX_ACCOUNT_ID)) return true;
51
+ return (config.codexAccounts ?? []).some(account =>
52
+ isSelectableCodexPoolAccount(account) && isCodexAccountUsable(config, account.id));
53
+ }
54
+
55
+ /**
56
+ * The predicate previously duplicated as findAnthropicSidecarProvider
57
+ * (web-search) and findAnthropicVisionProvider (vision): first enabled
58
+ * anthropic-adapter OAuth provider whose ACTIVE stored account holds a usable
59
+ * credential. getAccountSet + needsReauth, not getCredential — a terminally
60
+ * invalid account must not present as auth (audit F1).
61
+ */
62
+ function findAnthropicAuthProvider(
63
+ config: OcxConfig,
64
+ ): { providerName: string; provider: OcxProviderConfig } | undefined {
65
+ for (const [providerName, provider] of Object.entries(config.providers)) {
66
+ if (provider.disabled === true) continue;
67
+ if (provider.adapter !== "anthropic" || provider.authMode !== "oauth") continue;
68
+ const set = getAccountSet(providerName);
69
+ const active = set?.accounts.find(account => account.id === set.activeAccountId);
70
+ if (active && active.needsReauth !== true) return { providerName, provider };
71
+ }
72
+ return undefined;
73
+ }
74
+
75
+ export function resolveSidecarAuth(config: OcxConfig): SidecarAuthState {
76
+ const anthropic = findAnthropicAuthProvider(config);
77
+ return {
78
+ isCodexAuth: hasUsableCodexLogin(config),
79
+ isAnthropicAuth: anthropic !== undefined,
80
+ ...(anthropic ? { anthropicProviderName: anthropic.providerName, anthropicProvider: anthropic.provider } : {}),
81
+ };
82
+ }
83
+
84
+ /** The auth-entitled fixed candidates. Emitted regardless of picker visibility. */
85
+ export function sidecarAuthSlots(auth: SidecarAuthState): SidecarAuthSlot[] {
86
+ const slots: SidecarAuthSlot[] = [];
87
+ if (auth.isCodexAuth) slots.push({ provider: OPENAI_CODEX_PROVIDER_ID, id: AUTH_SLOT_MODELS.codex, slot: "codex" });
88
+ if (auth.isAnthropicAuth && auth.anthropicProviderName) {
89
+ slots.push({ provider: auth.anthropicProviderName, id: AUTH_SLOT_MODELS.anthropic, slot: "anthropic" });
90
+ }
91
+ return slots;
92
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The unified picker-visibility candidate set both sidecars consume (#2188).
3
+ *
4
+ * Rule 1 (picker): a model may be OFFERED as a sidecar backend only if the
5
+ * management picker would show it — the same `listManagementModelRows` rows
6
+ * the GUI Models tab renders, minus disabled rows. Rule 1's only exception is
7
+ * the auth slots: a logged-in side keeps its fixed slot model (Luna/Haiku)
8
+ * even when the picker hides it, because the slot is the login's entitlement.
9
+ *
10
+ * Vision additionally applies rule 2 (− provably text-only); web-search
11
+ * applies its own rule 2 (∩ probed backend with an executor) in
12
+ * src/web-search/backends.ts. Both start from THIS set so the two sidecars
13
+ * cannot diverge on what "visible" means.
14
+ */
15
+ import type { OcxConfig } from "../types";
16
+ import { listManagementModelRows } from "../server/management/model-rows";
17
+ import { modelAcceptsImageInput, type VisionCandidateModel } from "../vision/eligibility";
18
+ import { sidecarAuthSlots, type SidecarAuthState } from "./auth";
19
+
20
+ export interface SidecarCandidate {
21
+ provider: string;
22
+ id: string;
23
+ native?: boolean;
24
+ inputModalities?: string[];
25
+ /** True when this row is an auth-slot entitlement rather than a picker row. */
26
+ authSlot?: boolean;
27
+ }
28
+
29
+ /**
30
+ * (picker-visible rows) ∪ (auth slots). De-duplicated by provider+id with the
31
+ * auth-slot flag winning, so a slot model that is ALSO picker-visible still
32
+ * reads as slot-backed. A catalog outage degrades to slots only — the settings
33
+ * routes must not 500 and must keep the logged-in floor populated.
34
+ */
35
+ export async function pickerVisibleSidecarCandidates(
36
+ config: OcxConfig,
37
+ auth: SidecarAuthState,
38
+ ): Promise<SidecarCandidate[]> {
39
+ let rows: Awaited<ReturnType<typeof listManagementModelRows>> = [];
40
+ try { rows = await listManagementModelRows(config); } catch { rows = []; }
41
+ const byKey = new Map<string, SidecarCandidate>();
42
+ for (const row of rows) {
43
+ if (row.disabled === true) continue;
44
+ byKey.set(`${row.provider}/${row.id}`, {
45
+ provider: row.provider,
46
+ id: row.id,
47
+ ...(row.inputModalities ? { inputModalities: row.inputModalities } : {}),
48
+ ...(row.native ? { native: true } : {}),
49
+ });
50
+ }
51
+ for (const slot of sidecarAuthSlots(auth)) {
52
+ byKey.set(`${slot.provider}/${slot.id}`, {
53
+ provider: slot.provider,
54
+ id: slot.id,
55
+ // Slot models are known image-capable; their only exclusion path is the
56
+ // provider's explicit consumer list (same stance as baselineCandidate).
57
+ inputModalities: ["text", "image"],
58
+ authSlot: true,
59
+ });
60
+ }
61
+ return [...byKey.values()];
62
+ }
63
+
64
+ /**
65
+ * Rule 2 for vision: drop rows PROVABLY text-only. Unknown stays eligible —
66
+ * the picker filter (rule 1) already bounded the set, so permissive-unknown
67
+ * no longer expands to the whole catalog.
68
+ */
69
+ export function visionSidecarCandidates(
70
+ config: Pick<OcxConfig, "providers">,
71
+ all: readonly SidecarCandidate[],
72
+ ): SidecarCandidate[] {
73
+ return all.filter(candidate => modelAcceptsImageInput(config, toVisionCandidate(candidate)) !== false);
74
+ }
75
+
76
+ function toVisionCandidate(candidate: SidecarCandidate): VisionCandidateModel {
77
+ return {
78
+ provider: candidate.provider,
79
+ id: candidate.id,
80
+ ...(candidate.inputModalities ? { inputModalities: candidate.inputModalities } : {}),
81
+ ...(candidate.native ? { native: true } : {}),
82
+ };
83
+ }
@@ -1091,7 +1091,7 @@ function writeSatelliteBackup(
1091
1091
  throw new Error("test_fail_satellite_backup_replace");
1092
1092
  }
1093
1093
  try {
1094
- renameAtomicFile(tmp, dest);
1094
+ renameAtomicFile(tmp, dest, undefined, "storage-cleanup");
1095
1095
  } catch (error) {
1096
1096
  try { unlinkSync(tmp); } catch { /* */ }
1097
1097
  throw error;
@@ -2435,7 +2435,7 @@ function writeRestorePending(
2435
2435
  throw new Error("test_fail_pending_rename");
2436
2436
  }
2437
2437
  try {
2438
- renameSync(tmp, dest);
2438
+ renameAtomicFile(tmp, dest, undefined, "storage-cleanup");
2439
2439
  } catch (error) {
2440
2440
  try { unlinkSync(tmp); } catch { /* */ }
2441
2441
  throw error;
@@ -38,6 +38,7 @@ export interface PolicyJobOutcome {
38
38
  freedBytes?: number;
39
39
  removed?: number;
40
40
  trashDir?: string;
41
+ metadataPersistenceError?: PolicyRunResult["metadataPersistenceError"];
41
42
  }
42
43
 
43
44
  export interface PolicyJobState {
@@ -238,6 +239,7 @@ export async function abortStorageCleanupPolicyJobAsync(): Promise<void> {
238
239
  }
239
240
  }
240
241
 
242
+ /** Project a run result into the bounded management-API job outcome. */
241
243
  function outcomeFromResult(result: PolicyRunResult): PolicyJobOutcome {
242
244
  return {
243
245
  ok: result.ok,
@@ -248,18 +250,26 @@ function outcomeFromResult(result: PolicyRunResult): PolicyJobOutcome {
248
250
  ...(result.freedBytes !== undefined ? { freedBytes: result.freedBytes } : {}),
249
251
  ...(result.removed !== undefined ? { removed: result.removed } : {}),
250
252
  ...(result.trashDir ? { trashDir: result.trashDir } : {}),
253
+ ...(result.metadataPersistenceError
254
+ ? { metadataPersistenceError: result.metadataPersistenceError }
255
+ : {}),
251
256
  };
252
257
  }
253
258
 
259
+ /** Publish one completed evaluation without losing successful cleanup effects. */
254
260
  function applyFinished(result: PolicyRunResult): void {
255
261
  // Prefer the latest persisted policy over `result.policy`. The worker (or
256
262
  // in-process run) already merged run metadata into disk; a concurrent PUT
257
263
  // may also have landed after that write. Re-reading avoids applying a stale
258
264
  // start-of-job snapshot when the run skipped without saving.
259
- try {
260
- livePolicyApply?.(readStorageCleanupPolicyFromConfig());
261
- } catch {
262
- livePolicyApply?.(result.policy);
265
+ // A best-effort fallback policy may predate concurrent edits; keep the current
266
+ // live config untouched when the durable metadata write did not land.
267
+ if (!result.metadataPersistenceError) {
268
+ try {
269
+ livePolicyApply?.(readStorageCleanupPolicyFromConfig());
270
+ } catch {
271
+ livePolicyApply?.(result.policy);
272
+ }
263
273
  }
264
274
  state = {
265
275
  status: "idle",
@@ -8,7 +8,7 @@
8
8
  * Privacy: logs never include host paths, digests of file contents, or secrets.
9
9
  */
10
10
  import { resolveCodexHomeDir } from "../codex/home";
11
- import { loadConfig, saveConfigPreservingClaudeCode } from "../config";
11
+ import { loadConfig, mutatePersistedConfig, saveConfigPreservingClaudeCode } from "../config";
12
12
  import type { StorageCleanupPolicy } from "../types";
13
13
  import {
14
14
  computePreviewDigest,
@@ -38,6 +38,7 @@ export function setStorageCleanupPolicyLiveSink(
38
38
 
39
39
  export type PolicySchedule = StorageCleanupPolicy["schedule"];
40
40
  export type PolicyRunReason = "startup" | "schedule" | "manual";
41
+ export type PolicyMetadataPersistenceError = "missing" | "invalid" | "conflict" | "write_failed";
41
42
 
42
43
  export type PolicySkipReason =
43
44
  | "disabled"
@@ -54,6 +55,7 @@ export interface PolicyRunResult {
54
55
  freedBytes?: number;
55
56
  removed?: number;
56
57
  trashDir?: string;
58
+ metadataPersistenceError?: PolicyMetadataPersistenceError;
57
59
  policy: StorageCleanupPolicy;
58
60
  }
59
61
 
@@ -399,6 +401,21 @@ export type PolicyRunMetadataPatch = {
399
401
  lastRun?: StorageCleanupPolicy["lastRun"];
400
402
  };
401
403
 
404
+ /** Apply only run-owned fields while preserving the supplied policy settings. */
405
+ function applyPolicyRunMetadata(
406
+ policy: StorageCleanupPolicy,
407
+ patch: PolicyRunMetadataPatch,
408
+ ): StorageCleanupPolicy {
409
+ let next =
410
+ patch.nextRun === "defer_busy"
411
+ ? deferBusy(policy, patch.now)
412
+ : advanceNextRun(policy, patch.now);
413
+ if (patch.lastRun) {
414
+ next = { ...next, lastRun: patch.lastRun };
415
+ }
416
+ return next;
417
+ }
418
+
402
419
  /**
403
420
  * Reload the latest persisted policy and write only run-owned metadata
404
421
  * (`lastRun` / `nextRun`). Preserves concurrent edits to enabled, trigger,
@@ -410,17 +427,57 @@ export function commitPolicyRunMetadata(
410
427
  patch: PolicyRunMetadataPatch,
411
428
  ): StorageCleanupPolicy {
412
429
  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
- }
430
+ const next = applyPolicyRunMetadata(latest, patch);
420
431
  save(next);
421
432
  return next;
422
433
  }
423
434
 
435
+ type PolicyRunMetadataCommit = {
436
+ policy: StorageCleanupPolicy;
437
+ persistenceError?: PolicyMetadataPersistenceError;
438
+ };
439
+
440
+ /** Attach the durable metadata outcome without replacing cleanup status or metrics. */
441
+ function withMetadataCommit(
442
+ result: Omit<PolicyRunResult, "policy" | "metadataPersistenceError">,
443
+ committed: PolicyRunMetadataCommit,
444
+ ): PolicyRunResult {
445
+ return {
446
+ ...result,
447
+ policy: committed.policy,
448
+ ...(committed.persistenceError ? { metadataPersistenceError: committed.persistenceError } : {}),
449
+ };
450
+ }
451
+
452
+ /** Recompute run-owned metadata from the latest config inside the mutation lock. */
453
+ function commitPolicyRunMetadataToConfig(
454
+ patch: PolicyRunMetadataPatch,
455
+ fallbackPolicy: StorageCleanupPolicy,
456
+ ): PolicyRunMetadataCommit {
457
+ const unavailable = (reason: PolicyMetadataPersistenceError): PolicyRunMetadataCommit => {
458
+ console.warn(`[storage-policy] metadata_persist_failed reason=${reason}`);
459
+ return {
460
+ policy: applyPolicyRunMetadata(fallbackPolicy, patch),
461
+ persistenceError: reason,
462
+ };
463
+ };
464
+ try {
465
+ const outcome = mutatePersistedConfig(config => {
466
+ const next = applyPolicyRunMetadata(
467
+ normalizeStorageCleanupPolicy(config.storageCleanupPolicy),
468
+ patch,
469
+ );
470
+ config.storageCleanupPolicy = next;
471
+ return { changed: true, value: next };
472
+ });
473
+ if (outcome.status === "unavailable") return unavailable(outcome.reason);
474
+ livePolicySink?.(outcome.value);
475
+ return { policy: outcome.value };
476
+ } catch {
477
+ return unavailable("write_failed");
478
+ }
479
+ }
480
+
424
481
  function logPolicyEvent(message: string): void {
425
482
  console.log(`[storage-policy] ${message}`);
426
483
  }
@@ -434,8 +491,14 @@ export function runStorageCleanupPolicy(deps: PolicyRunDeps): PolicyRunResult {
434
491
  const load = deps.loadPolicy ?? readStorageCleanupPolicyFromConfig;
435
492
  const save = deps.savePolicy ?? writeStorageCleanupPolicyToConfig;
436
493
  const execute = deps.execute ?? executeArchivedCleanup;
437
-
438
494
  const policy = normalizeStorageCleanupPolicy(load());
495
+ // Injected stores retain the existing load/save contract. The production path
496
+ // recomputes metadata from the latest persisted policy inside the config lock.
497
+ const commitMetadata = deps.loadPolicy !== undefined || deps.savePolicy !== undefined
498
+ ? (patch: PolicyRunMetadataPatch): PolicyRunMetadataCommit => ({
499
+ policy: commitPolicyRunMetadata(load, save, patch),
500
+ })
501
+ : (patch: PolicyRunMetadataPatch) => commitPolicyRunMetadataToConfig(patch, policy);
439
502
 
440
503
  if (typeof deps.holdAfterLoadMs === "number" && Number.isFinite(deps.holdAfterLoadMs) && deps.holdAfterLoadMs > 0) {
441
504
  Bun.sleepSync(Math.floor(deps.holdAfterLoadMs));
@@ -451,15 +514,15 @@ export function runStorageCleanupPolicy(deps: PolicyRunDeps): PolicyRunResult {
451
514
 
452
515
  const selection = selectPolicyPreview(policy, deps.codexHome);
453
516
  if (selection.archivedBytes <= policy.trigger.archivedBytesOver) {
454
- const saved = commitPolicyRunMetadata(load, save, { now, nextRun: "advance" });
517
+ const committed = commitMetadata({ now, nextRun: "advance" });
455
518
  logPolicyEvent("skip under_threshold");
456
- return { ok: true, skipped: "under_threshold", policy: saved };
519
+ return withMetadataCommit({ ok: true, skipped: "under_threshold" }, committed);
457
520
  }
458
521
 
459
522
  if (selection.count === 0) {
460
- const saved = commitPolicyRunMetadata(load, save, { now, nextRun: "advance" });
523
+ const committed = commitMetadata({ now, nextRun: "advance" });
461
524
  logPolicyEvent("skip nothing_selected");
462
- return { ok: true, skipped: "nothing_selected", policy: saved };
525
+ return withMetadataCommit({ ok: true, skipped: "nothing_selected" }, committed);
463
526
  }
464
527
 
465
528
  const result = execute({
@@ -473,25 +536,28 @@ export function runStorageCleanupPolicy(deps: PolicyRunDeps): PolicyRunResult {
473
536
  });
474
537
 
475
538
  if (!result.ok && result.error === "codex_busy") {
476
- const saved = commitPolicyRunMetadata(load, save, { now, nextRun: "defer_busy" });
539
+ const committed = commitMetadata({ now, nextRun: "defer_busy" });
477
540
  logPolicyEvent("defer codex_busy");
478
- return { ok: false, deferred: "codex_busy", error: "codex_busy", policy: saved };
541
+ return withMetadataCommit({
542
+ ok: false,
543
+ deferred: "codex_busy",
544
+ error: "codex_busy",
545
+ }, committed);
479
546
  }
480
547
 
481
548
  if (!result.ok) {
482
549
  // Non-busy failure: still advance schedule so we do not tight-loop.
483
- const saved = commitPolicyRunMetadata(load, save, { now, nextRun: "advance" });
550
+ const committed = commitMetadata({ now, nextRun: "advance" });
484
551
  logPolicyEvent(`fail ${result.error ?? "cleanup_failed"}`);
485
- return {
552
+ return withMetadataCommit({
486
553
  ok: false,
487
554
  error: result.error,
488
555
  mode: result.mode,
489
556
  ...(result.trashDir ? { trashDir: result.trashDir } : {}),
490
- policy: saved,
491
- };
557
+ }, committed);
492
558
  }
493
559
 
494
- const saved = commitPolicyRunMetadata(load, save, {
560
+ const committed = commitMetadata({
495
561
  now,
496
562
  nextRun: "advance",
497
563
  lastRun: {
@@ -503,14 +569,13 @@ export function runStorageCleanupPolicy(deps: PolicyRunDeps): PolicyRunResult {
503
569
  logPolicyEvent(
504
570
  `ok mode=${result.mode} removed=${result.count} freedBytes=${result.bytes}`,
505
571
  );
506
- return {
572
+ return withMetadataCommit({
507
573
  ok: true,
508
574
  mode: result.mode,
509
575
  freedBytes: result.bytes,
510
576
  removed: result.count,
511
577
  ...(result.trashDir ? { trashDir: result.trashDir } : {}),
512
- policy: saved,
513
- };
578
+ }, committed);
514
579
  }
515
580
 
516
581
  /** Startup / schedule tick entry — swallows unexpected errors. */
@@ -16,7 +16,7 @@ const IMMUTABLE_READONLY_FLAGS = constants.SQLITE_OPEN_READONLY | constants.SQLI
16
16
 
17
17
  /**
18
18
  * Read-only CODEX_HOME storage scanner — Phase 1 of the Storage page epic
19
- * (devlog/_plan/500_storage-page-session-cleanup). Pure measurement: sizes via
19
+ * (devlog/_fin/500_storage-page-session-cleanup). Pure measurement: sizes via
20
20
  * fs.stat walks, DB row counts via immutable readonly opens that degrade to
21
21
  * null on lock/corruption. Performs zero writes under CODEX_HOME.
22
22
  */
@@ -11,10 +11,9 @@
11
11
  * time (so we cannot miss `self.close()` / early exit), stays in `liveWorkers`
12
12
  * until that close settles, and `drainStorageWorkers()` joins every in-flight
13
13
  * terminate. Spawns are serialized through `withStorageWorkerSpawnGate` so the
14
- * next Worker cannot be created until prior threads have exited. On Windows and
15
- * macOS, a post-close settle covers the OS join gap Bun does not expose
16
- * (Windows unbalanced join panic; macOS Silicon balanced-count segfault under
17
- * `bun test --isolate`).
14
+ * next Worker cannot be created until prior threads have exited. A post-close
15
+ * settle covers the OS/runtime join gap Bun does not expose on Windows, macOS,
16
+ * and Linux (including Bun 1.3.14 isolate crashes and Linux `epoll_ctl` reuse).
18
17
  */
19
18
 
20
19
  import { createAdmissionGate, type AdmissionMetrics, type AdmissionReservation } from "../lib/admission";
@@ -52,15 +51,15 @@ let spawnCancelEpoch = 0;
52
51
  /**
53
52
  * OS-join gap after the `close` event on platforms where Bun's Worker reclaim
54
53
  * races the isolate/file boundary (not a CI job-timeout bump).
55
- * Windows GHA at 250ms and 750ms still left `workers_spawned(N)
56
- * workers_terminated(N-1)` panics under isolate (seen mid
57
- * `storage-mutation-race` with 11/10). 1500ms covers deferred reclaim under
58
- * stacked policy/restore workers. Darwin uses 250ms.
54
+ * Windows GHA at 250ms and 750ms still left `workers_spawned(N)`
55
+ * `workers_terminated(N-1)` panics under isolate, so Windows keeps 1500ms.
56
+ * Darwin and Linux use a shorter settle for the balanced-count/epoll reclaim
57
+ * window seen on Bun 1.3.14.
59
58
  */
60
- const WORKER_OS_JOIN_MS = process.platform === "win32" ? 1_500 : 250;
61
-
62
- function needsWorkerOsJoinSettle(): boolean {
63
- return process.platform === "win32" || process.platform === "darwin";
59
+ export function storageWorkerOsJoinSettleMs(platform = process.platform): number {
60
+ if (platform === "win32") return 1_500;
61
+ if (platform === "darwin" || platform === "linux") return 250;
62
+ return 0;
64
63
  }
65
64
 
66
65
  /** Invalidate spawn callbacks still waiting on the gate (reset / server drain). */
@@ -181,9 +180,10 @@ export function terminateStorageWorker(worker: Worker, timeoutMs = 5_000): Promi
181
180
  // only forces `closed`, it does not prove the OS thread has exited.
182
181
  // Callers that catch and continue (e.g. drainAndShutdown) still need
183
182
  // that gap before the next isolate reclaim or server.stop.
184
- if (needsWorkerOsJoinSettle()) {
183
+ const settleMs = storageWorkerOsJoinSettleMs();
184
+ if (settleMs > 0) {
185
185
  await Bun.sleep(0);
186
- await Bun.sleep(WORKER_OS_JOIN_MS);
186
+ await Bun.sleep(settleMs);
187
187
  }
188
188
  if (timedOut) {
189
189
  throw new Error(`storage worker did not exit within ${timeoutMs}ms`);
@@ -3,6 +3,9 @@ param(
3
3
  [Parameter(Mandatory = $true)][string]$CliPath,
4
4
  [Parameter(Mandatory = $true)][string]$CodexHome,
5
5
  [Parameter(Mandatory = $true)][string]$OpenCodexHome,
6
+ # Provenance of $BunPath, chosen when the tray entry was built. Optional so an
7
+ # already-installed launcher command from an older version still starts.
8
+ [ValidateSet("", "override", "bundled", "process")][string]$BunRuntimeSource = "",
6
9
  [ValidateSet("Run", "Stop")][string]$Mode = "Run",
7
10
  [int]$HostPid = 0
8
11
  )
@@ -81,7 +84,7 @@ function ConvertTo-NativeArgument([string]$Value) {
81
84
  return '"' + $Value + '"'
82
85
  }
83
86
 
84
- function Start-OcxCommand([string[]]$CommandArgs) {
87
+ function Start-OcxCommand([string[]]$CommandArgs, [switch]$TrackExit) {
85
88
  try {
86
89
  $allArgs = @($CliPath) + $CommandArgs
87
90
  $psi = New-Object System.Diagnostics.ProcessStartInfo
@@ -92,9 +95,17 @@ function Start-OcxCommand([string[]]$CommandArgs) {
92
95
  $psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
93
96
  $psi.EnvironmentVariables["CODEX_HOME"] = $CodexHome
94
97
  $psi.EnvironmentVariables["OPENCODEX_HOME"] = $OpenCodexHome
98
+ if ($BunRuntimeSource) {
99
+ $psi.EnvironmentVariables["OCX_BUN_RUNTIME_SOURCE"] = $BunRuntimeSource
100
+ # Paired with the source so a later relaunch can tell the marker still describes
101
+ # this binary rather than one it merely inherited.
102
+ $psi.EnvironmentVariables["OCX_BUN_RUNTIME_PATH"] = $BunPath
103
+ }
95
104
  $process = [System.Diagnostics.Process]::Start($psi)
96
- if ($null -ne $process) { $process.Dispose() }
105
+ if ($null -eq $process) { throw "Process did not start" }
97
106
  Write-ActionLog "dispatched $($CommandArgs -join ' ')"
107
+ if ($TrackExit) { return $process }
108
+ $process.Dispose()
98
109
  return $true
99
110
  } catch {
100
111
  Write-ActionLog "launch failed: $($_.Exception.GetType().Name)"
@@ -163,18 +174,40 @@ $script:pendingAction = $null
163
174
  $script:pendingStarted = 0L
164
175
  $script:pendingDeadline = 0L
165
176
  $script:pendingOldProxyPid = $null
177
+ $script:pendingProcess = $null
166
178
 
167
179
  function Set-PendingAction([string]$Action, [int]$TimeoutSeconds) {
180
+ if ($null -ne $script:pendingAction) {
181
+ Write-ActionLog "$Action ignored because $($script:pendingAction) is still pending"
182
+ return $false
183
+ }
184
+ if ($null -ne $script:pendingProcess) {
185
+ try {
186
+ $script:pendingProcess.Dispose()
187
+ } catch {
188
+ Write-ActionLog "pending process dispose failed: $($_.Exception.GetType().Name)"
189
+ }
190
+ $script:pendingProcess = $null
191
+ }
168
192
  $script:pendingAction = $Action
169
193
  $script:pendingStarted = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
170
194
  $script:pendingDeadline = $script:pendingStarted + ($TimeoutSeconds * 1000)
171
195
  $script:pendingOldProxyPid = $script:proxyPid
196
+ return $true
172
197
  }
173
198
 
174
199
  function Complete-PendingAction([bool]$Success) {
175
200
  if ($null -eq $script:pendingAction) { return }
176
201
  $action = $script:pendingAction
177
202
  $script:pendingAction = $null
203
+ if ($null -ne $script:pendingProcess) {
204
+ try {
205
+ $script:pendingProcess.Dispose()
206
+ } catch {
207
+ Write-ActionLog "pending process dispose failed: $($_.Exception.GetType().Name)"
208
+ }
209
+ $script:pendingProcess = $null
210
+ }
178
211
  if ($Success) {
179
212
  Write-ActionLog "$action completed (port=$($script:port), pid=$($script:proxyPid))"
180
213
  $notify.ShowBalloonTip(2500, "opencodex", "$action completed.", [System.Windows.Forms.ToolTipIcon]::Info)
@@ -217,6 +250,11 @@ function Update-TrayState {
217
250
  $stopItem.Enabled = $false
218
251
  $restartItem.Enabled = $false
219
252
  }
253
+ if ($null -ne $script:pendingAction) {
254
+ $startItem.Enabled = $false
255
+ $stopItem.Enabled = $false
256
+ $restartItem.Enabled = $false
257
+ }
220
258
  $heartbeat = @{ pid = $PID; timestamp = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() }
221
259
  if ($HostPid -gt 0) { $heartbeat.hostPid = $HostPid }
222
260
  $heartbeatJson = $heartbeat | ConvertTo-Json -Compress
@@ -228,26 +266,59 @@ function Update-TrayState {
228
266
  $reached = ($script:pendingAction -eq "Start Proxy" -and $script:online) -or
229
267
  ($script:pendingAction -eq "Stop Proxy" -and -not $script:online) -or
230
268
  ($script:pendingAction -eq "Restart Proxy" -and $elapsed -gt 3000 -and $script:online -and $script:proxyPid -ne $script:pendingOldProxyPid)
231
- if ($reached) { Complete-PendingAction $true }
269
+ $commandFailed = $false
270
+ if ($null -ne $script:pendingProcess) {
271
+ try {
272
+ $commandFailed = $script:pendingProcess.HasExited -and $script:pendingProcess.ExitCode -ne 0
273
+ } catch {
274
+ Write-ActionLog "pending process result inspection failed: $($_.Exception.GetType().Name)"
275
+ # If we cannot inspect the tracked command, we cannot prove it is still
276
+ # healthy. Fail the pending action instead of silently waiting for a later
277
+ # timeout and presenting an indeterminate process as success-capable.
278
+ $commandFailed = $true
279
+ }
280
+ }
281
+ if ($commandFailed) { Complete-PendingAction $false }
282
+ elseif ($reached) { Complete-PendingAction $true }
232
283
  elseif ($now -gt $script:pendingDeadline) { Complete-PendingAction $false }
233
284
  }
234
285
  }
235
286
 
236
287
  $openItem.add_Click({ Start-OcxCommand @("gui") })
237
288
  $startItem.add_Click({
289
+ if (-not (Set-PendingAction "Start Proxy" 75)) { return }
238
290
  $statusItem.Text = "Proxy: Starting..."
239
- Set-PendingAction "Start Proxy" 15
240
- if (-not (Start-OcxCommand @("__tray-start"))) { $script:pendingAction = $null }
291
+ # service start can spend 20s and the CLI then observes health for another 40s.
292
+ $startProcess = Start-OcxCommand @("__tray-start") -TrackExit
293
+ if ($startProcess -is [System.Diagnostics.Process]) {
294
+ $script:pendingProcess = $startProcess
295
+ } else {
296
+ Complete-PendingAction $false
297
+ }
241
298
  })
242
299
  $stopItem.add_Click({
300
+ if (-not (Set-PendingAction "Stop Proxy" 15)) { return }
243
301
  $statusItem.Text = "Proxy: Stopping..."
244
- Set-PendingAction "Stop Proxy" 15
245
- if (-not (Start-OcxCommand @("stop"))) { $script:pendingAction = $null }
302
+ $stopProcess = Start-OcxCommand @("stop") -TrackExit
303
+ if ($stopProcess -is [System.Diagnostics.Process]) {
304
+ $script:pendingProcess = $stopProcess
305
+ } else {
306
+ Complete-PendingAction $false
307
+ }
246
308
  })
247
309
  $restartItem.add_Click({
310
+ if (-not (Set-PendingAction "Restart Proxy" 160)) { return }
248
311
  $statusItem.Text = "Proxy: Restarting..."
249
- Set-PendingAction "Restart Proxy" 20
250
- if (-not (Start-OcxCommand @("__tray-restart"))) { $script:pendingAction = $null }
312
+ # /api/system/restart may drain active work for 60s and then spend up to 70s
313
+ # handing off to an identity-verified replacement. The tray observes health/PID
314
+ # rather than the detached CLI exit, so keep a watchdog margin around that shared
315
+ # lifecycle budget. The CLI remains the lifecycle owner; the tray never kills.
316
+ $restartProcess = Start-OcxCommand @("__tray-restart") -TrackExit
317
+ if ($restartProcess -is [System.Diagnostics.Process]) {
318
+ $script:pendingProcess = $restartProcess
319
+ } else {
320
+ Complete-PendingAction $false
321
+ }
251
322
  })
252
323
  $logsItem.add_Click({
253
324
  $psi = New-Object System.Diagnostics.ProcessStartInfo
@@ -280,6 +351,9 @@ try {
280
351
  $timer.Stop()
281
352
  $timer.Dispose()
282
353
  $notify.Visible = $false
354
+ if ($null -ne $script:pendingProcess) {
355
+ try { $script:pendingProcess.Dispose() } catch { $null = $_ }
356
+ }
283
357
  $notify.Dispose()
284
358
  foreach ($icon in $script:ownedIcons) { $icon.Dispose() }
285
359
  $menu.Dispose()