@iislee/opencodex 2.11.0 → 2.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (675) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +114 -19
  3. package/bin/ocx.mjs +164 -36
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-BF38heuV.js +104 -0
  6. package/gui/dist/assets/index-DMiI18Kv.css +1 -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 +19 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +376 -52
  44. package/src/adapters/base.ts +54 -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 +601 -0
  48. package/src/adapters/cursor/checkpoint-store.ts +303 -0
  49. package/src/adapters/cursor/cursor-errors.ts +129 -6
  50. package/src/adapters/cursor/discovery.ts +61 -4
  51. package/src/adapters/cursor/effort-map.ts +27 -3
  52. package/src/adapters/cursor/framing.ts +39 -0
  53. package/src/adapters/cursor/h2-pool.ts +123 -0
  54. package/src/adapters/cursor/http1-bidi.ts +361 -0
  55. package/src/adapters/cursor/images.ts +704 -0
  56. package/src/adapters/cursor/live-models.ts +174 -58
  57. package/src/adapters/cursor/live-transport.ts +609 -170
  58. package/src/adapters/cursor/native-exec-common.ts +23 -2
  59. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  60. package/src/adapters/cursor/native-exec-network.ts +1 -1
  61. package/src/adapters/cursor/native-exec-shell.ts +0 -1
  62. package/src/adapters/cursor/native-exec.ts +101 -14
  63. package/src/adapters/cursor/protobuf-events.ts +829 -11
  64. package/src/adapters/cursor/protobuf-request.ts +383 -65
  65. package/src/adapters/cursor/request-builder.ts +237 -31
  66. package/src/adapters/cursor/tool-definitions.ts +242 -12
  67. package/src/adapters/cursor/tool-result-normalize.ts +92 -0
  68. package/src/adapters/cursor/transport.ts +22 -0
  69. package/src/adapters/cursor/types.ts +28 -1
  70. package/src/adapters/cursor.ts +133 -7
  71. package/src/adapters/google-antigravity-replay.ts +558 -34
  72. package/src/adapters/google-antigravity-wire.ts +43 -10
  73. package/src/adapters/google-http.ts +38 -10
  74. package/src/adapters/google-tool-schema.ts +80 -15
  75. package/src/adapters/google-truncation.ts +11 -0
  76. package/src/adapters/google.ts +618 -74
  77. package/src/adapters/identity.ts +39 -6
  78. package/src/adapters/kiro-errors.ts +11 -0
  79. package/src/adapters/kiro-events.ts +19 -1
  80. package/src/adapters/kiro-thinking.ts +18 -2
  81. package/src/adapters/kiro-tools.ts +10 -1
  82. package/src/adapters/kiro.ts +92 -54
  83. package/src/adapters/mimo-free.ts +17 -0
  84. package/src/adapters/openai-chat-url.ts +11 -0
  85. package/src/adapters/openai-chat.ts +1311 -336
  86. package/src/adapters/openai-responses-url.ts +16 -0
  87. package/src/adapters/openai-responses.ts +830 -56
  88. package/src/adapters/registry.ts +175 -0
  89. package/src/adapters/responses-tool-schema.ts +67 -0
  90. package/src/adapters/tool-call-id.ts +119 -0
  91. package/src/adapters/tool-catalog-nudge.ts +104 -21
  92. package/src/adapters/xai-web-search.ts +185 -0
  93. package/src/bridge.ts +428 -61
  94. package/src/chat/inbound.ts +43 -19
  95. package/src/chat/outbound.ts +82 -26
  96. package/src/claude/agents-inject.ts +32 -9
  97. package/src/claude/context-windows.ts +21 -5
  98. package/src/claude/desktop-3p.ts +243 -9
  99. package/src/claude/gateway-cache.ts +41 -4
  100. package/src/claude/inbound.ts +72 -3
  101. package/src/claude/model-info.ts +38 -15
  102. package/src/claude/outbound.ts +70 -16
  103. package/src/cli/account-api.ts +35 -1
  104. package/src/cli/account-auth.ts +33 -6
  105. package/src/cli/account-catalog-refresh.ts +14 -0
  106. package/src/cli/account-extended.ts +389 -2
  107. package/src/cli/account-main.ts +317 -0
  108. package/src/cli/account.ts +30 -6
  109. package/src/cli/agent.ts +45 -1
  110. package/src/cli/claude-agent-startup-sync.ts +73 -0
  111. package/src/cli/claude-desktop.ts +29 -4
  112. package/src/cli/claude.ts +103 -34
  113. package/src/cli/codex-log-guard-doctor.ts +103 -0
  114. package/src/cli/codex-shim-autorestore.ts +2 -0
  115. package/src/cli/codex-shim-readiness.ts +76 -0
  116. package/src/cli/combo.ts +8 -0
  117. package/src/cli/config-command.ts +74 -10
  118. package/src/cli/dispatch.ts +593 -0
  119. package/src/cli/doctor.ts +315 -43
  120. package/src/cli/ensure-desired-integrations.ts +152 -0
  121. package/src/cli/export-command.ts +46 -20
  122. package/src/cli/help.ts +20 -271
  123. package/src/cli/index.ts +378 -519
  124. package/src/cli/init.ts +4 -17
  125. package/src/cli/integrations.ts +120 -2
  126. package/src/cli/lab.ts +607 -0
  127. package/src/cli/launcher-context.ts +77 -0
  128. package/src/cli/minimax.ts +497 -0
  129. package/src/cli/models-runtime.ts +35 -2
  130. package/src/cli/models.ts +100 -14
  131. package/src/cli/observe.ts +92 -3
  132. package/src/cli/opencode.ts +4 -2
  133. package/src/cli/provider-runtime.ts +18 -1
  134. package/src/cli/provider.ts +24 -3
  135. package/src/cli/ready.ts +301 -0
  136. package/src/cli/registry.ts +437 -0
  137. package/src/cli/root.ts +86 -0
  138. package/src/cli/route-policy.ts +92 -0
  139. package/src/cli/runtime-api.ts +6 -3
  140. package/src/cli/star-prompt.ts +71 -15
  141. package/src/cli/status.ts +10 -3
  142. package/src/cli/system-restart-client.ts +146 -0
  143. package/src/cli/tray-proxy.ts +153 -6
  144. package/src/cli/v2.ts +105 -10
  145. package/src/cli.ts +1 -1
  146. package/src/clients/config-export.ts +1358 -21
  147. package/src/codex/account-label.ts +14 -1
  148. package/src/codex/account-lifecycle.ts +130 -13
  149. package/src/codex/account-namespaces.ts +49 -3
  150. package/src/codex/account-priority.ts +83 -0
  151. package/src/codex/account-store.ts +29 -2
  152. package/src/codex/account-usability.ts +25 -2
  153. package/src/codex/admission.ts +256 -0
  154. package/src/codex/affinity-debug.ts +162 -0
  155. package/src/codex/app-server-processes.ts +493 -106
  156. package/src/codex/app-server-restart-service.ts +232 -0
  157. package/src/codex/auth-api.ts +849 -242
  158. package/src/codex/auth-collision.ts +5 -3
  159. package/src/codex/auth-context.ts +345 -32
  160. package/src/codex/autostart-health.ts +8 -1
  161. package/src/codex/catalog/account-models.ts +67 -0
  162. package/src/codex/catalog/aggregation.ts +68 -10
  163. package/src/codex/catalog/bundled.ts +331 -33
  164. package/src/codex/catalog/effort.ts +121 -30
  165. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  166. package/src/codex/catalog/kinds.ts +2 -0
  167. package/src/codex/catalog/metadata.ts +529 -45
  168. package/src/codex/catalog/native-models.ts +72 -0
  169. package/src/codex/catalog/parsing.ts +224 -30
  170. package/src/codex/catalog/provider-fetch.ts +1460 -134
  171. package/src/codex/catalog/sync.ts +1449 -186
  172. package/src/codex/catalog-admission.ts +199 -0
  173. package/src/codex/catalog-refresh-status.ts +105 -0
  174. package/src/codex/catalog-write-serialization.ts +242 -0
  175. package/src/codex/catalog.ts +6 -3
  176. package/src/codex/codex-write-lock.ts +384 -0
  177. package/src/codex/convergence-types.ts +614 -0
  178. package/src/codex/convergence.ts +651 -0
  179. package/src/codex/coordinator-doctor.ts +332 -0
  180. package/src/codex/custom-model-catalog-migration.ts +176 -0
  181. package/src/codex/desired-state.ts +230 -0
  182. package/src/codex/features.ts +636 -39
  183. package/src/codex/generation.ts +202 -0
  184. package/src/codex/history-job.ts +407 -0
  185. package/src/codex/history-lock.ts +242 -0
  186. package/src/codex/history-migration-guardian.ts +26 -20
  187. package/src/codex/history-provider.ts +231 -28
  188. package/src/codex/history-transition.ts +105 -0
  189. package/src/codex/history-worker.ts +220 -0
  190. package/src/codex/inject-coordination.ts +290 -0
  191. package/src/codex/inject.ts +1073 -152
  192. package/src/codex/injected-marker.ts +37 -3
  193. package/src/codex/integration-record.ts +266 -0
  194. package/src/codex/internal/catalog-writer.ts +203 -0
  195. package/src/codex/internal/history-writer.ts +80 -0
  196. package/src/codex/journal.ts +66 -4
  197. package/src/codex/log-guard/inspect.ts +506 -0
  198. package/src/codex/log-guard/lock.ts +150 -0
  199. package/src/codex/log-guard/maintenance.ts +403 -0
  200. package/src/codex/log-guard/path-safety.ts +88 -0
  201. package/src/codex/log-guard/policy.ts +44 -0
  202. package/src/codex/log-guard/processes.ts +205 -0
  203. package/src/codex/log-guard/protection.ts +489 -0
  204. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  205. package/src/codex/main-account-cache.ts +24 -0
  206. package/src/codex/main-account.ts +29 -1
  207. package/src/codex/management-convergence.ts +167 -0
  208. package/src/codex/model-cache.ts +56 -10
  209. package/src/codex/model-entitlements.ts +353 -0
  210. package/src/codex/native-main-admission.ts +47 -0
  211. package/src/codex/native-main-auth-temp.ts +187 -0
  212. package/src/codex/native-main-claim.ts +178 -0
  213. package/src/codex/native-main-lock-file.ts +162 -0
  214. package/src/codex/native-main-owner.ts +329 -0
  215. package/src/codex/native-profile-api.ts +247 -0
  216. package/src/codex/native-profile-manager.ts +1531 -0
  217. package/src/codex/native-profile-processes.ts +121 -0
  218. package/src/codex/native-profile-recovery.ts +99 -0
  219. package/src/codex/native-profile-stage-store.ts +387 -0
  220. package/src/codex/native-profile-startup.ts +492 -0
  221. package/src/codex/native-profile-store.ts +855 -0
  222. package/src/codex/native-profile-types.ts +120 -0
  223. package/src/codex/native-residue.ts +682 -0
  224. package/src/codex/paths.ts +80 -1
  225. package/src/codex/plan-from-token.ts +140 -0
  226. package/src/codex/plan.ts +40 -0
  227. package/src/codex/plugins-doctor.ts +1 -1
  228. package/src/codex/pool-rotation.ts +74 -4
  229. package/src/codex/project-config-warnings.ts +20 -6
  230. package/src/codex/prompt-journal.ts +352 -0
  231. package/src/codex/prompt-layers.ts +967 -0
  232. package/src/codex/prompt-lock.ts +143 -0
  233. package/src/codex/quota-rejection.ts +298 -0
  234. package/src/codex/quota.ts +175 -13
  235. package/src/codex/refresh.ts +11 -2
  236. package/src/codex/reset-credit-recovery.ts +1044 -0
  237. package/src/codex/routing.ts +505 -94
  238. package/src/codex/runtime.ts +159 -38
  239. package/src/codex/shim.ts +1009 -28
  240. package/src/codex/subagent-model-fallback.ts +350 -35
  241. package/src/codex/sync.ts +191 -2
  242. package/src/codex/transition-state.ts +612 -0
  243. package/src/codex/upstream-host-health.ts +368 -0
  244. package/src/codex/user-identity.ts +557 -0
  245. package/src/codex/warmup.ts +187 -81
  246. package/src/codex/write-coordination.ts +114 -0
  247. package/src/combos/failover.ts +20 -0
  248. package/src/combos/index.ts +4 -0
  249. package/src/combos/request.ts +32 -0
  250. package/src/combos/types.ts +81 -9
  251. package/src/config/provider-name.ts +24 -0
  252. package/src/config.ts +1762 -140
  253. package/src/generated/compatibility-version.json +3164 -0
  254. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  255. package/src/grok/inject.ts +16 -5
  256. package/src/grok/inspect.ts +45 -0
  257. package/src/grok/sync.ts +2 -2
  258. package/src/images/loop.ts +152 -29
  259. package/src/images/plan.ts +23 -13
  260. package/src/integrations/config-io.ts +269 -0
  261. package/src/integrations/journal.ts +315 -0
  262. package/src/integrations/merge.ts +135 -0
  263. package/src/integrations/mutation-flight.ts +71 -0
  264. package/src/integrations/native/ownership-preflight.ts +202 -0
  265. package/src/integrations/omp-yaml-source.ts +358 -0
  266. package/src/integrations/owned-refresh.ts +74 -0
  267. package/src/integrations/ownership.ts +111 -0
  268. package/src/integrations/registry.ts +159 -0
  269. package/src/integrations/serialize.ts +314 -0
  270. package/src/integrations/state.ts +361 -0
  271. package/src/integrations/store.ts +103 -0
  272. package/src/integrations/writer-lock.ts +98 -0
  273. package/src/integrations/writer.ts +691 -0
  274. package/src/lab/artifacts/sanitize.ts +586 -0
  275. package/src/lab/artifacts/secure-fs.ts +475 -0
  276. package/src/lab/artifacts/store.ts +310 -0
  277. package/src/lab/automation/budgets.ts +78 -0
  278. package/src/lab/automation/config-persistence.ts +256 -0
  279. package/src/lab/automation/constants.ts +39 -0
  280. package/src/lab/automation/cooldown.ts +103 -0
  281. package/src/lab/automation/dispatch.ts +211 -0
  282. package/src/lab/automation/index.ts +13 -0
  283. package/src/lab/automation/orchestrator.ts +499 -0
  284. package/src/lab/automation/persistence.ts +512 -0
  285. package/src/lab/automation/planner.ts +371 -0
  286. package/src/lab/automation/policy.ts +136 -0
  287. package/src/lab/automation/queue.ts +191 -0
  288. package/src/lab/automation/recovery.ts +24 -0
  289. package/src/lab/automation/route-context.ts +21 -0
  290. package/src/lab/automation/run-key.ts +44 -0
  291. package/src/lab/automation/runs-query.ts +34 -0
  292. package/src/lab/automation/types.ts +160 -0
  293. package/src/lab/conformance/assertion.ts +325 -0
  294. package/src/lab/conformance/digest.ts +22 -0
  295. package/src/lab/conformance/executor.ts +741 -0
  296. package/src/lab/conformance/fixture-provider.ts +27 -0
  297. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  298. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  299. package/src/lab/conformance/harness-budget.ts +47 -0
  300. package/src/lab/conformance/index.ts +5 -0
  301. package/src/lab/conformance/jcs.ts +64 -0
  302. package/src/lab/conformance/json-pointer.ts +39 -0
  303. package/src/lab/conformance/manifest.ts +180 -0
  304. package/src/lab/conformance/mcp-stub.ts +179 -0
  305. package/src/lab/conformance/negative-controls.ts +164 -0
  306. package/src/lab/conformance/observation.ts +355 -0
  307. package/src/lab/conformance/runner.ts +68 -0
  308. package/src/lab/conformance/sse-normalize.ts +59 -0
  309. package/src/lab/conformance/suite-manifest.ts +78 -0
  310. package/src/lab/conformance/types.ts +214 -0
  311. package/src/lab/constants.ts +126 -0
  312. package/src/lab/digest.ts +64 -0
  313. package/src/lab/events/errors.ts +9 -0
  314. package/src/lab/events/limits.ts +117 -0
  315. package/src/lab/events/types.ts +229 -0
  316. package/src/lab/events/validate.ts +781 -0
  317. package/src/lab/fabric/constants.ts +40 -0
  318. package/src/lab/fabric/executor.ts +492 -0
  319. package/src/lab/fabric/index.ts +80 -0
  320. package/src/lab/fabric/manifest.ts +222 -0
  321. package/src/lab/fabric/observe.ts +489 -0
  322. package/src/lab/fabric/patch.ts +79 -0
  323. package/src/lab/fabric/producer-child.ts +139 -0
  324. package/src/lab/fabric/producer-isolate.ts +276 -0
  325. package/src/lab/fabric/producer-protocol.ts +61 -0
  326. package/src/lab/fabric/scratch.ts +439 -0
  327. package/src/lab/fabric/subject.ts +106 -0
  328. package/src/lab/fabric/types.ts +134 -0
  329. package/src/lab/fabric/verifier.ts +98 -0
  330. package/src/lab/index.ts +54 -0
  331. package/src/lab/ledger/artifact-refs.ts +127 -0
  332. package/src/lab/ledger/invalidation.ts +136 -0
  333. package/src/lab/ledger/purge.ts +310 -0
  334. package/src/lab/ledger/store.ts +532 -0
  335. package/src/lab/live/credential-lease.ts +53 -0
  336. package/src/lab/live/destination.ts +155 -0
  337. package/src/lab/live/executor.ts +336 -0
  338. package/src/lab/live/inert-tools.ts +56 -0
  339. package/src/lab/live/manifest.ts +85 -0
  340. package/src/lab/live/mcp-loopback.ts +57 -0
  341. package/src/lab/live/runner.ts +19 -0
  342. package/src/lab/live/sandbox.ts +61 -0
  343. package/src/lab/live/suite-manifest.ts +41 -0
  344. package/src/lab/live/transport.ts +118 -0
  345. package/src/lab/live/types.ts +197 -0
  346. package/src/lab/observe/from-conformance.ts +301 -0
  347. package/src/lab/observe/from-live.ts +117 -0
  348. package/src/lab/paths.ts +153 -0
  349. package/src/lab/projection/rebuild.ts +495 -0
  350. package/src/lab/projection/schema.ts +135 -0
  351. package/src/lab/projection/verdicts.ts +474 -0
  352. package/src/lab/projection/verification.ts +412 -0
  353. package/src/lab/public/bundle.ts +217 -0
  354. package/src/lab/public/community-authority.ts +175 -0
  355. package/src/lab/public/community-files.ts +29 -0
  356. package/src/lab/public/community.ts +479 -0
  357. package/src/lab/public/file-safety.ts +155 -0
  358. package/src/lab/public/ids.ts +26 -0
  359. package/src/lab/public/index.ts +16 -0
  360. package/src/lab/public/mutation-lock.ts +424 -0
  361. package/src/lab/public/operator.ts +353 -0
  362. package/src/lab/public/origin-purge.ts +79 -0
  363. package/src/lab/public/origin.ts +203 -0
  364. package/src/lab/public/privacy.ts +143 -0
  365. package/src/lab/public/private-file.ts +261 -0
  366. package/src/lab/public/project.ts +124 -0
  367. package/src/lab/public/purge-test-fault.ts +21 -0
  368. package/src/lab/public/purge.ts +223 -0
  369. package/src/lab/public/registry.ts +44 -0
  370. package/src/lab/public/revocation.ts +252 -0
  371. package/src/lab/public/signature.ts +243 -0
  372. package/src/lab/public/storage.ts +105 -0
  373. package/src/lab/public/strict-json.ts +206 -0
  374. package/src/lab/public/time.ts +26 -0
  375. package/src/lab/public/types.ts +172 -0
  376. package/src/lab/public/validate.ts +391 -0
  377. package/src/lab/query/catalog.ts +101 -0
  378. package/src/lab/query/connection.ts +107 -0
  379. package/src/lab/query/constants.ts +4 -0
  380. package/src/lab/query/cursor.ts +132 -0
  381. package/src/lab/query/dto-map.ts +277 -0
  382. package/src/lab/query/errors.ts +22 -0
  383. package/src/lab/query/freshness.ts +53 -0
  384. package/src/lab/query/index.ts +45 -0
  385. package/src/lab/query/latest-observation.ts +59 -0
  386. package/src/lab/query/passive-production.ts +159 -0
  387. package/src/lab/query/queries.ts +444 -0
  388. package/src/lab/query/types.ts +266 -0
  389. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  390. package/src/lab/subject/installation-salt.ts +112 -0
  391. package/src/lab/subject/protocol-subject.ts +80 -0
  392. package/src/lab/subject/route-subject.ts +74 -0
  393. package/src/lib/app-owned-memory-stores.ts +22 -0
  394. package/src/lib/bounded-body.ts +153 -9
  395. package/src/lib/bun-runtime.ts +125 -12
  396. package/src/lib/bun-stream-caps.ts +13 -9
  397. package/src/lib/codex-restart-contract.ts +120 -0
  398. package/src/lib/config-ownership.ts +6 -2
  399. package/src/lib/destination-policy.ts +65 -1
  400. package/src/lib/errors.ts +44 -2
  401. package/src/lib/fabric-task-execution-authority.ts +7 -0
  402. package/src/lib/fabric-task-host.ts +29 -0
  403. package/src/lib/lab-activation.ts +223 -0
  404. package/src/lib/lab-live-execution-authority.ts +13 -0
  405. package/src/lib/lab-live-host.ts +30 -0
  406. package/src/lib/lab-live-pinned-sender.ts +56 -0
  407. package/src/lib/lab-live-route-production.ts +130 -0
  408. package/src/lib/lab-passive-linker-registration.ts +26 -0
  409. package/src/lib/local-management-attestation.ts +51 -0
  410. package/src/lib/local-management-capability.ts +100 -0
  411. package/src/lib/local-provider-reload-contract.ts +100 -0
  412. package/src/lib/optional-shutdown-hooks.ts +57 -0
  413. package/src/lib/pinned-http.ts +145 -26
  414. package/src/lib/process-control.ts +4 -1
  415. package/src/lib/provider-outbound.ts +49 -9
  416. package/src/lib/redact.ts +419 -3
  417. package/src/lib/self-launch-argv.ts +15 -0
  418. package/src/lib/server-resource-ownership.ts +71 -0
  419. package/src/lib/shadow-call.ts +35 -4
  420. package/src/lib/sse-decoder.ts +41 -0
  421. package/src/lib/state-store-registrations.ts +10 -2
  422. package/src/lib/system-restart-contract.ts +73 -0
  423. package/src/lib/token-estimate.ts +19 -2
  424. package/src/lib/tool-argument-integers.ts +202 -0
  425. package/src/lib/translator-budget.ts +44 -0
  426. package/src/lib/upstream-http-version.ts +57 -0
  427. package/src/lib/upstream-reachability.ts +95 -0
  428. package/src/lib/upstream-retry.ts +156 -3
  429. package/src/lib/windows-atomic-replace.ts +155 -0
  430. package/src/lib/windows-elevation.ts +70 -2
  431. package/src/lib/windows-secret-acl.ts +409 -69
  432. package/src/lib/windows-service-wrappers.ts +72 -0
  433. package/src/lib/windows-text.ts +106 -0
  434. package/src/lib/windows-user-principal.ts +341 -0
  435. package/src/lib/winsw.ts +33 -5
  436. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  437. package/src/oauth/account-import/index.ts +15 -0
  438. package/src/oauth/account-import/parser.ts +83 -0
  439. package/src/oauth/account-import/registry.ts +18 -0
  440. package/src/oauth/account-import/service.ts +75 -0
  441. package/src/oauth/account-import/types.ts +91 -0
  442. package/src/oauth/anthropic.ts +12 -1
  443. package/src/oauth/callback-server.ts +8 -2
  444. package/src/oauth/chatgpt.ts +12 -1
  445. package/src/oauth/command-code.ts +239 -0
  446. package/src/oauth/cursor.ts +46 -5
  447. package/src/oauth/google-antigravity.ts +35 -3
  448. package/src/oauth/health.ts +20 -12
  449. package/src/oauth/index.ts +398 -66
  450. package/src/oauth/key-providers.ts +16 -0
  451. package/src/oauth/kimi.ts +16 -2
  452. package/src/oauth/kiro.ts +50 -6
  453. package/src/oauth/local-token-detect.ts +11 -2
  454. package/src/oauth/log.ts +3 -1
  455. package/src/oauth/login-cli.ts +88 -28
  456. package/src/oauth/nous.ts +798 -0
  457. package/src/oauth/store.ts +119 -21
  458. package/src/oauth/token-guardian.ts +9 -3
  459. package/src/pi/models.ts +2 -2
  460. package/src/providers/alibaba-region-migration.ts +1 -1
  461. package/src/providers/antigravity-models.ts +521 -31
  462. package/src/providers/base-url-choices.ts +10 -0
  463. package/src/providers/codex-capacity.ts +292 -0
  464. package/src/providers/command-code-efforts.ts +144 -0
  465. package/src/providers/context-cap.ts +22 -5
  466. package/src/providers/cursor-pool.ts +72 -0
  467. package/src/providers/derive.ts +253 -6
  468. package/src/providers/fastwire.ts +501 -0
  469. package/src/providers/free-directory.ts +10 -7
  470. package/src/providers/google-vertex-location.ts +14 -0
  471. package/src/providers/key-failover.ts +71 -3
  472. package/src/providers/label.ts +1 -1
  473. package/src/providers/model-discovery-limits.ts +16 -0
  474. package/src/providers/model-discovery.ts +115 -22
  475. package/src/providers/model-rename-migration.ts +255 -0
  476. package/src/providers/model-rename-startup.ts +28 -0
  477. package/src/providers/openai-sidecar.ts +72 -4
  478. package/src/providers/openai-tier-startup.ts +31 -2
  479. package/src/providers/openai-tiers.ts +119 -4
  480. package/src/providers/openai-virtual-models.ts +1 -0
  481. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  482. package/src/providers/provider-id-rewrite.ts +29 -0
  483. package/src/providers/quota.ts +1319 -38
  484. package/src/providers/registry.ts +1429 -111
  485. package/src/providers/request-pacing.ts +310 -0
  486. package/src/providers/service-tier.ts +277 -0
  487. package/src/providers/slug-codec.ts +42 -6
  488. package/src/providers/static-model-discovery.ts +86 -0
  489. package/src/providers/xai-responses-opt-in.ts +15 -0
  490. package/src/providers/xai-transport.ts +11 -4
  491. package/src/reasoning-effort.ts +49 -1
  492. package/src/responses/compaction.ts +26 -1
  493. package/src/responses/custom-tool-compat.ts +266 -0
  494. package/src/responses/hosted-tool-policy.ts +9 -0
  495. package/src/responses/namespace-tool-compat.ts +355 -0
  496. package/src/responses/parser.ts +220 -38
  497. package/src/responses/provider-continuation.ts +98 -0
  498. package/src/responses/provider-opaque-metadata.ts +73 -0
  499. package/src/responses/reasoning-envelope.ts +9 -1
  500. package/src/responses/reasoning-replay-cache.ts +426 -0
  501. package/src/responses/schema.ts +7 -1
  502. package/src/responses/spill-store.ts +75 -10
  503. package/src/responses/state.ts +565 -27
  504. package/src/responses/thought-signature-replay.ts +347 -0
  505. package/src/responses/tool-search-compat.ts +301 -0
  506. package/src/responses/truncated-stop-reason.ts +60 -0
  507. package/src/router.ts +366 -30
  508. package/src/routing/analytics.ts +378 -0
  509. package/src/routing/capability.ts +244 -0
  510. package/src/routing/compatibility/assemble.ts +73 -0
  511. package/src/routing/compatibility/behavior.ts +278 -0
  512. package/src/routing/compatibility/catalog.ts +99 -0
  513. package/src/routing/compatibility/endpoint.ts +52 -0
  514. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  515. package/src/routing/compatibility/policy.ts +181 -0
  516. package/src/routing/compatibility/provider-slot.ts +56 -0
  517. package/src/routing/compatibility/reader.ts +110 -0
  518. package/src/routing/compatibility/subject.ts +191 -0
  519. package/src/routing/compatibility/types.ts +64 -0
  520. package/src/routing/compatibility/version.ts +104 -0
  521. package/src/routing/cost.ts +77 -0
  522. package/src/routing/evaluator.ts +495 -0
  523. package/src/routing/health.ts +412 -0
  524. package/src/routing/history/cursor.ts +43 -0
  525. package/src/routing/history/indexer.ts +605 -0
  526. package/src/routing/history/schema.ts +72 -0
  527. package/src/routing/profile-namespace.ts +15 -0
  528. package/src/routing/profile.ts +547 -0
  529. package/src/routing/quota.ts +145 -0
  530. package/src/routing/request-evidence.ts +45 -0
  531. package/src/routing/trace.ts +776 -0
  532. package/src/server/adapter-resolve.ts +2 -29
  533. package/src/server/auth-cors.ts +267 -46
  534. package/src/server/background-lifecycle.ts +182 -0
  535. package/src/server/chat-completions.ts +130 -56
  536. package/src/server/chat-native-sse.ts +331 -0
  537. package/src/server/chat-native.ts +426 -0
  538. package/src/server/claude-messages.ts +159 -43
  539. package/src/server/direct-local-http.ts +347 -0
  540. package/src/server/effort-policy.ts +18 -0
  541. package/src/server/github-copilot-responses-repair.ts +338 -0
  542. package/src/server/gui-static.ts +39 -10
  543. package/src/server/images.ts +94 -12
  544. package/src/server/index.ts +865 -181
  545. package/src/server/lifecycle.ts +284 -13
  546. package/src/server/live.ts +136 -17
  547. package/src/server/local-management-read-client.ts +90 -0
  548. package/src/server/local-provider-reload-client.ts +137 -0
  549. package/src/server/management/agent-settings-routes.ts +398 -103
  550. package/src/server/management/api-key-usage.ts +31 -5
  551. package/src/server/management/body.ts +6 -0
  552. package/src/server/management/combo-routes.ts +62 -24
  553. package/src/server/management/config-routes.ts +464 -51
  554. package/src/server/management/context.ts +80 -2
  555. package/src/server/management/integration-routes.ts +498 -0
  556. package/src/server/management/lab-automation-routes.ts +206 -0
  557. package/src/server/management/lab-routes.ts +563 -0
  558. package/src/server/management/logs-usage-routes.ts +101 -32
  559. package/src/server/management/model-routes.ts +189 -131
  560. package/src/server/management/model-rows.ts +163 -0
  561. package/src/server/management/native-integration-routes.ts +769 -0
  562. package/src/server/management/oauth-account-routes.ts +80 -4
  563. package/src/server/management/provider-capability-config.ts +48 -0
  564. package/src/server/management/provider-routes.ts +764 -157
  565. package/src/server/management/request-history-routes.ts +191 -0
  566. package/src/server/management/routing-analytics-routes.ts +74 -0
  567. package/src/server/management/routing-profile-routes.ts +380 -0
  568. package/src/server/management/shared.ts +27 -11
  569. package/src/server/management/sidebar-routes.ts +47 -31
  570. package/src/server/management/storage-log-guard-routes.ts +186 -0
  571. package/src/server/management/sync-response.ts +69 -0
  572. package/src/server/management/system-restart.ts +288 -32
  573. package/src/server/management/system-routes.ts +77 -0
  574. package/src/server/management/usage-summary-cache.ts +9 -1
  575. package/src/server/management/vision-sidecar-options.ts +167 -0
  576. package/src/server/management/web-search-sidecar-options.ts +120 -0
  577. package/src/server/management-api.ts +115 -14
  578. package/src/server/management-auth.ts +220 -5
  579. package/src/server/passive-route-linker.ts +66 -0
  580. package/src/server/ports.ts +41 -1
  581. package/src/server/proxy-liveness.ts +132 -5
  582. package/src/server/readiness.ts +99 -0
  583. package/src/server/relay-eager.ts +82 -42
  584. package/src/server/relay.ts +236 -76
  585. package/src/server/request-decompress.ts +113 -6
  586. package/src/server/request-log.ts +235 -22
  587. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  588. package/src/server/responses/agent-task-recovery.ts +465 -0
  589. package/src/server/responses/collaboration.ts +204 -35
  590. package/src/server/responses/compact.ts +442 -55
  591. package/src/server/responses/core.ts +2872 -331
  592. package/src/server/responses/empty-completion-guard.ts +276 -0
  593. package/src/server/responses/encrypted-payload.ts +62 -39
  594. package/src/server/responses/fetch-helpers.ts +79 -4
  595. package/src/server/responses/input-admission.ts +185 -0
  596. package/src/server/responses/pacing-overload.ts +13 -0
  597. package/src/server/responses/policy-fallback.ts +178 -0
  598. package/src/server/responses/responses-field-backfill.ts +251 -0
  599. package/src/server/responses/terminal-guard.ts +26 -5
  600. package/src/server/responses/upstream-error.ts +5 -0
  601. package/src/server/responses/ws-upstream.ts +308 -0
  602. package/src/server/responses-custom-tool-repair.ts +282 -0
  603. package/src/server/responses-item-id-repair.ts +54 -6
  604. package/src/server/responses-json-events.ts +90 -0
  605. package/src/server/responses-model-rewrite.ts +29 -0
  606. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  607. package/src/server/responses-snapshot-repair.ts +621 -0
  608. package/src/server/responses-terminal-repair.ts +342 -0
  609. package/src/server/responses-tool-search-repair.ts +267 -0
  610. package/src/server/responses-undeclared-tool-guard.ts +153 -0
  611. package/src/server/responses.ts +18 -2
  612. package/src/server/search.ts +78 -13
  613. package/src/server/sse-frame-buffer.ts +292 -0
  614. package/src/server/sse-payload-rewrite.ts +110 -22
  615. package/src/server/startup-action-control.ts +8 -1
  616. package/src/server/startup-health-cache.ts +19 -1
  617. package/src/server/system-env.ts +80 -9
  618. package/src/server/ws-bridge.ts +39 -38
  619. package/src/service-manager-probe.ts +892 -0
  620. package/src/service.ts +1111 -90
  621. package/src/sidecar/auth.ts +92 -0
  622. package/src/sidecar/candidates.ts +83 -0
  623. package/src/storage/cleanup.ts +2 -2
  624. package/src/storage/scanner.ts +1 -1
  625. package/src/storage/worker-lifecycle.ts +14 -14
  626. package/src/tray/windows-tray.ps1 +83 -9
  627. package/src/tray/windows.ts +43 -16
  628. package/src/types/accounts.ts +37 -0
  629. package/src/types/config.ts +845 -0
  630. package/src/types/provider.ts +545 -0
  631. package/src/types/request.ts +384 -0
  632. package/src/types/tools.ts +131 -0
  633. package/src/types/wire.ts +80 -0
  634. package/src/types.ts +104 -1236
  635. package/src/update/index.ts +32 -19
  636. package/src/update/job.ts +442 -67
  637. package/src/update/notify.ts +12 -6
  638. package/src/update/npm-cache-preflight.d.mts +47 -0
  639. package/src/update/npm-cache-preflight.mjs +201 -0
  640. package/src/update/transactional-install.d.mts +22 -0
  641. package/src/update/transactional-install.mjs +259 -0
  642. package/src/usage/cost.ts +0 -0
  643. package/src/usage/expected-prices.ts +268 -16
  644. package/src/usage/log.ts +606 -41
  645. package/src/usage/summary.ts +177 -9
  646. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  647. package/src/usage/user-cost-overlays.ts +314 -0
  648. package/src/vision/anthropic-describe.ts +10 -6
  649. package/src/vision/backends.ts +97 -0
  650. package/src/vision/describe.ts +9 -3
  651. package/src/vision/eligibility.ts +250 -0
  652. package/src/vision/index.ts +238 -24
  653. package/src/vision/reasoning.ts +55 -0
  654. package/src/vision/routed-describe.ts +175 -0
  655. package/src/vision/timeout-bounds.ts +9 -0
  656. package/src/web-search/anthropic-executor.ts +13 -7
  657. package/src/web-search/backends.ts +108 -0
  658. package/src/web-search/exa-executor.ts +88 -0
  659. package/src/web-search/executor.ts +11 -3
  660. package/src/web-search/gemini-executor.ts +141 -0
  661. package/src/web-search/index.ts +150 -15
  662. package/src/web-search/loop.ts +279 -50
  663. package/src/web-search/parse.ts +125 -30
  664. package/src/web-search/sources.ts +60 -0
  665. package/src/web-search/xai-executor.ts +219 -0
  666. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  667. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  668. package/gui/dist/provider-icons/antigravity.svg +0 -1
  669. package/gui/dist/provider-icons/claude.svg +0 -1
  670. package/gui/dist/provider-icons/copilot.svg +0 -1
  671. package/gui/dist/provider-icons/cursor.svg +0 -2
  672. package/gui/dist/provider-icons/gemini.svg +0 -1
  673. package/gui/dist/provider-icons/grok-color.svg +0 -1
  674. package/gui/dist/provider-icons/kiro.svg +0 -14
  675. package/src/cli/internal-dispatch.ts +0 -20
package/src/cli/doctor.ts CHANGED
@@ -10,16 +10,36 @@
10
10
  import { accessSync, constants, existsSync, readFileSync } from "node:fs";
11
11
  import { homedir } from "node:os";
12
12
  import { dirname, join } from "node:path";
13
- import { getConfigDir, getConfigPath, readConfigDiagnostics, readPid, readRuntimePort, resolveEnvValue } from "../config";
14
- import { findLiveProxy } from "../server/proxy-liveness";
15
- import { gracefulStopHost } from "../lib/process-control";
13
+ import { getConfigDir, getConfigPath, readConfigDiagnostics, readPid, resolveEnvValue } from "../config";
14
+ import { findLiveProxy, type LiveProxy } from "../server/proxy-liveness";
15
+ import { BUN_RUNTIME_SOURCES } from "../lib/bun-runtime";
16
+ import type { BunRuntimeSource } from "../lib/bun-runtime";
16
17
  import { maskAccountId } from "../lib/privacy";
17
18
  import { PROXY_ENV_KEYS, proxyEnvPresent } from "../lib/proxy-env";
18
- import { configuredAdminToken } from "../lib/admin-secrets";
19
+ import { LOCAL_MANAGEMENT_READ_PATHS } from "../lib/local-management-capability";
19
20
  import { readCodexTokens } from "../codex/auth-collision";
21
+ import { withNativeMainSharedClaim } from "../codex/native-main-claim";
22
+ import { probeNativeProfileRecoveryState, resolveNativeProfileContext } from "../codex/native-profile-store";
23
+ import { NativeProfileError } from "../codex/native-profile-types";
20
24
  import { collectOrcaCodexHomeDiagnostic, resolveCodexHomeDir as resolveCodexHomeDirImpl, isWslRuntime, listWslWindowsCodexHomes, wslAutomountRoot, type CodexHomeDeps } from "../codex/home";
25
+ import { scanCodexAgentRolesWithTomlModelFallback } from "../codex/subagent-model-fallback";
21
26
  import { findCodexOnPath, isWindowsInteropDir } from "../codex/shim";
22
27
  import { countPendingOpencodexHistory } from "../codex/history-provider";
28
+ import {
29
+ inspectCodexCoordinator,
30
+ recoverZeroByteCodexCoordinator,
31
+ type CodexCoordinatorDiagnostic,
32
+ } from "../codex/coordinator-doctor";
33
+ import {
34
+ inspectAbandonedResponseStateTemps,
35
+ reclaimAbandonedResponseStateTemps,
36
+ type ResponseStateTempRecoveryResult,
37
+ } from "../responses/state";
38
+ import {
39
+ CodexUserIdentityRefusal,
40
+ probeCodexCoordinatorNamespace,
41
+ resolveEffectiveUserIdentity,
42
+ } from "../codex/user-identity";
23
43
  import { collectProjectCodexConfigWarnings, formatProjectCodexConfigWarningsForDoctor } from "../codex/project-config-warnings";
24
44
  import { collectStartupHealth, startupHealthSummary } from "../codex/autostart-health";
25
45
  import {
@@ -31,6 +51,10 @@ import {
31
51
  } from "../codex/runtime";
32
52
  import { CODEX_REAUTH_ACTION, collectOAuthHealthEntriesForCli, MASKED_ACCOUNT_FALLBACK, type OAuthHealthEntry } from "../oauth/health";
33
53
  import { getAuthRefreshIntentLockPath, getAuthStorePath } from "../oauth/store";
54
+ import {
55
+ fetchBoundLocalManagementRead,
56
+ type LocalManagementReadDeps,
57
+ } from "../server/local-management-read-client";
34
58
  export { resolveCodexHomeDir } from "../codex/home";
35
59
 
36
60
  export type OAuthDoctorCheck = { level: "OK" | "WARN"; message: string };
@@ -479,36 +503,75 @@ export type WhamProbeResult = {
479
503
  authenticated: boolean;
480
504
  };
481
505
 
506
+ type NativeMainDoctorClaim = <T>(operation: () => Promise<T>) => Promise<T>;
507
+
508
+ export interface WhamProbeDeps {
509
+ withNativeMainClaim?: NativeMainDoctorClaim;
510
+ probeNativeMainRecoveryState?: typeof probeNativeProfileRecoveryState;
511
+ }
512
+
482
513
  /**
483
514
  * Replicate the runtime WHAM fetch shape (same URL, 8s timeout, main-token
484
515
  * headers when present) so the probe fails exactly where the real path fails.
485
516
  * `fetchImpl` is injectable for testing.
486
517
  */
487
- export async function probeWham(fetchImpl: typeof fetch = fetch): Promise<WhamProbeResult> {
488
- const tokens = readCodexTokens();
489
- const headers: Record<string, string> = {};
490
- if (tokens) {
491
- headers.Authorization = `Bearer ${tokens.access_token}`;
492
- headers["ChatGPT-Account-Id"] = tokens.account_id;
493
- }
518
+ export async function probeWham(
519
+ fetchImpl: typeof fetch = fetch,
520
+ deps: WhamProbeDeps = {},
521
+ ): Promise<WhamProbeResult> {
494
522
  const start = performance.now();
523
+ let authenticated = false;
495
524
  try {
496
- const resp = await fetchImpl(WHAM_USAGE_URL, { headers, signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
497
- const durationMs = Math.round(performance.now() - start);
498
- return {
499
- ok: resp.ok,
500
- status: resp.status,
501
- durationMs,
502
- classification: resp.ok ? "ok" : `http_${resp.status}`,
503
- authenticated: !!tokens,
504
- };
525
+ const context = resolveNativeProfileContext();
526
+ const withClaim = deps.withNativeMainClaim
527
+ ?? (<T>(operation: () => Promise<T>) => withNativeMainSharedClaim(context, operation));
528
+ return await withClaim(async () => {
529
+ const recoveryState = (deps.probeNativeMainRecoveryState ?? probeNativeProfileRecoveryState)(context);
530
+ if (recoveryState !== "none") {
531
+ return {
532
+ ok: false,
533
+ status: null,
534
+ durationMs: Math.round(performance.now() - start),
535
+ classification: `native_main_recovery_${recoveryState}`,
536
+ authenticated: false,
537
+ };
538
+ }
539
+ const tokens = readCodexTokens();
540
+ const headers: Record<string, string> = {};
541
+ if (tokens) {
542
+ headers.Authorization = `Bearer ${tokens.access_token}`;
543
+ headers["ChatGPT-Account-Id"] = tokens.account_id;
544
+ }
545
+ authenticated = !!tokens;
546
+ const resp = await fetchImpl(WHAM_USAGE_URL, { headers, signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
547
+ const durationMs = Math.round(performance.now() - start);
548
+ return {
549
+ ok: resp.ok,
550
+ status: resp.status,
551
+ durationMs,
552
+ classification: resp.ok ? "ok" : `http_${resp.status}`,
553
+ authenticated,
554
+ };
555
+ });
505
556
  } catch (err) {
506
557
  const durationMs = Math.round(performance.now() - start);
558
+ if (
559
+ err instanceof NativeProfileError
560
+ && (err.code === "NATIVE_MAIN_CLAIM_BUSY" || err.code === "NATIVE_MAIN_CLAIM_UNAVAILABLE")
561
+ ) {
562
+ return {
563
+ ok: false,
564
+ status: null,
565
+ durationMs,
566
+ classification: err.code.toLowerCase(),
567
+ authenticated: false,
568
+ };
569
+ }
507
570
  const name = err instanceof Error ? err.name : String(err);
508
571
  const classification = name === "TimeoutError" || name === "AbortError"
509
572
  ? "timeout"
510
573
  : "connect_error";
511
- return { ok: false, status: null, durationMs, classification, authenticated: !!tokens };
574
+ return { ok: false, status: null, durationMs, classification, authenticated };
512
575
  }
513
576
  }
514
577
 
@@ -523,6 +586,8 @@ export async function probeWham(fetchImpl: typeof fetch = fetch): Promise<WhamPr
523
586
  export type ServiceMemoryData = {
524
587
  pid: number;
525
588
  bunVersion: string;
589
+ /** Launch-time provenance; absent for services installed before the marker existed. */
590
+ bunRuntimeSource?: BunRuntimeSource;
526
591
  platform: string;
527
592
  rss: number;
528
593
  heapUsed: number;
@@ -558,20 +623,28 @@ function observedMemory(data: { rss: number; external?: number; arrayBuffers?: n
558
623
  }
559
624
 
560
625
  export async function fetchServiceMemory(
561
- host: string,
562
- port: number,
563
- token: string | null,
564
- fetchImpl: typeof fetch = fetch,
626
+ target: LiveProxy,
627
+ deps: LocalManagementReadDeps = {},
565
628
  ): Promise<ServiceMemoryReport> {
566
629
  try {
567
- const res = await fetchImpl(`http://${host}:${port}/api/system/memory`, {
568
- headers: token ? { "x-opencodex-api-key": token } : {},
569
- signal: AbortSignal.timeout(SERVICE_MEMORY_TIMEOUT_MS),
630
+ const read = await fetchBoundLocalManagementRead(target, LOCAL_MANAGEMENT_READ_PATHS.systemMemory, {
631
+ ...deps,
632
+ timeoutMs: SERVICE_MEMORY_TIMEOUT_MS,
570
633
  });
634
+ if (read.kind === "unavailable") {
635
+ return read.reason === "transport"
636
+ ? { status: "unreachable", error: "fetch failed" }
637
+ : { status: "unauthorized" };
638
+ }
639
+ const { response: res, targetPid } = read;
571
640
  if (res.status === 401 || res.status === 403) return { status: "unauthorized" };
572
641
  if (!res.ok) return { status: "unreachable", error: `http ${res.status}` };
573
642
  const body = await res.json() as Partial<ServiceMemoryData>;
574
- if (typeof body.pid !== "number" || typeof body.bunVersion !== "string" || typeof body.rss !== "number") {
643
+ if (
644
+ body.pid !== targetPid
645
+ || typeof body.bunVersion !== "string"
646
+ || typeof body.rss !== "number"
647
+ ) {
575
648
  return { status: "unreachable", error: "malformed response" };
576
649
  }
577
650
  return {
@@ -579,6 +652,9 @@ export async function fetchServiceMemory(
579
652
  data: {
580
653
  pid: body.pid,
581
654
  bunVersion: body.bunVersion,
655
+ // Allowlisted independently of the server: an unrecognized wire value is
656
+ // treated as absent rather than echoed into user-facing guidance.
657
+ bunRuntimeSource: BUN_RUNTIME_SOURCES.find(source => source === body.bunRuntimeSource),
582
658
  platform: typeof body.platform === "string" ? body.platform : "unknown",
583
659
  rss: body.rss,
584
660
  heapUsed: typeof body.heapUsed === "number" ? body.heapUsed : 0,
@@ -612,12 +688,118 @@ export async function fetchServiceMemory(
612
688
 
613
689
  const mb = (bytes: number): string => `${Math.round(bytes / (1024 * 1024))}MB`;
614
690
 
691
+ export const RECLAIM_RESPONSE_TEMPS_FLAG = "--reclaim-response-temps";
692
+ export const RECOVER_ZERO_BYTE_COORDINATOR_FLAG = "--recover-zero-byte-coordinator";
693
+ /** Matches the dry run's entry bound so report and reclaim agree on a large backlog. */
694
+ const RESPONSE_TEMP_RECLAIM_MAX_CLEANUPS = 4_096;
695
+ /** Names the subsystem: other components mint temps with the same shape and are not covered. */
696
+ const CLEAN_RESPONSE_TEMP_LINE = " ok No abandoned response-state temp files.";
697
+
698
+ /**
699
+ * Render the abandoned-temp section (testable without console capture).
700
+ *
701
+ * Report is the DEFAULT and reclaim is opt-in: `doctor` is a diagnostic an operator runs
702
+ * to understand a machine, so deleting files as a side effect of asking a question is the
703
+ * wrong default even for cache files.
704
+ *
705
+ * Counts come from `eligible`/`eligibleBytes`, never `matched`: `matched` is incremented
706
+ * before the file-type, age, boot-floor, and liveness gates, so reporting it would tell an
707
+ * operator that live-pid temps and young temps are "abandoned".
708
+ */
709
+ export function formatResponseTempLines(
710
+ result: ResponseStateTempRecoveryResult,
711
+ reclaimed: boolean,
712
+ ): string[] {
713
+ if (reclaimed) {
714
+ if (result.removed === 0 && result.failed === 0) return [CLEAN_RESPONSE_TEMP_LINE];
715
+ const lines = [` ok Reclaimed ${result.removed} abandoned response-state temp file(s), ${mb(result.bytesRemoved)} freed.`];
716
+ if (result.failed > 0) {
717
+ // Never "retried automatically": this command exists for the operator whose proxy will
718
+ // NOT start, and in that state nothing retries anything.
719
+ lines.push(` !! ${result.failed} file(s) could not be removed (in use or locked). Retried on the next reclaim — automatically while the proxy runs, otherwise re-run this command.`);
720
+ }
721
+ // `truncated`, not `eligible > removed + failed`: outside a dry run every eligible entry
722
+ // is unlinked or failed on the same iteration it is counted, so those two are always
723
+ // equal and the comparison never fired. An operator with a backlog past the budget was
724
+ // told the reclaim had finished.
725
+ if (result.truncated) {
726
+ lines.push(" !! Cleanup budget reached; files remain. Run the command again to continue.");
727
+ }
728
+ return lines;
729
+ }
730
+ if (result.eligible === 0) return [CLEAN_RESPONSE_TEMP_LINE];
731
+ const lines = [
732
+ ` !! ${result.eligible} abandoned response-state temp file(s), ${mb(result.eligibleBytes)} reclaimable.`,
733
+ " These are interrupted snapshot writes (continuation cache only) and are safe to remove.",
734
+ " Reclaim them with: ocx doctor --reclaim-response-temps",
735
+ ];
736
+ // The dry run skips the cleanup budget but is still bounded by the entry cap, so a large
737
+ // enough backlog makes this a floor rather than a total. Say so instead of letting an
738
+ // operator size the problem from a truncated count.
739
+ if (result.truncated) lines.push(" Scan stopped at its entry budget; the real total is higher.");
740
+ return lines;
741
+ }
742
+
743
+ export function formatCoordinatorDoctorLines(diagnostic: CodexCoordinatorDiagnostic): string[] {
744
+ const pathLine = diagnostic.path ? [` path: ${diagnostic.path}`] : [];
745
+ const evidenceLines = "evidence" in diagnostic && diagnostic.evidence
746
+ ? [
747
+ ` size: ${diagnostic.evidence.sizeBytes} bytes; user_version: ${diagnostic.evidence.schemaVersion}`,
748
+ ` tables: ${diagnostic.evidence.tables.length === 0 ? "none" : diagnostic.evidence.tables.join(", ")}`,
749
+ ` transition rows: ${diagnostic.evidence.transitionRows ?? "not inspected"}; singleton=1 rows: ${diagnostic.evidence.singletonRows ?? "not inspected"}`,
750
+ ]
751
+ : [];
752
+ switch (diagnostic.kind) {
753
+ case "absent":
754
+ return [" ok native-write coordinator not created yet", ...pathLine];
755
+ case "ready":
756
+ return [" ok native-write coordinator has an authoritative transition row", ...pathLine, ...evidenceLines];
757
+ case "zero-byte":
758
+ return [
759
+ " !! native-write coordinator is a zero-byte remnant and has no authority",
760
+ ...pathLine,
761
+ ...evidenceLines,
762
+ ` Action: stop the OpenCodex proxy/service, then run ocx doctor ${RECOVER_ZERO_BYTE_COORDINATOR_FLAG} --yes`,
763
+ ];
764
+ case "unversioned-empty":
765
+ return [
766
+ " !! native-write coordinator is a non-empty unversioned database; automatic recovery is refused",
767
+ ...pathLine,
768
+ ...evidenceLines,
769
+ ];
770
+ case "rowless":
771
+ return [
772
+ " !! native-write coordinator has schema version 1 but no authoritative row; automatic recovery is refused",
773
+ ...pathLine,
774
+ ...evidenceLines,
775
+ ];
776
+ case "unversioned-nonempty":
777
+ return [
778
+ " !! native-write coordinator is unversioned and contains unknown tables; automatic recovery is refused",
779
+ ...pathLine,
780
+ ...evidenceLines,
781
+ ];
782
+ case "unsupported":
783
+ return [
784
+ ` !! native-write coordinator schema version ${diagnostic.version} is unsupported; automatic recovery is refused`,
785
+ ...pathLine,
786
+ ...evidenceLines,
787
+ ];
788
+ case "changed":
789
+ return [" -- native-write coordinator changed during diagnosis; re-run ocx doctor", ...pathLine];
790
+ case "unsafe":
791
+ return [` !! native-write coordinator path is unsafe: ${diagnostic.reason}`, ...pathLine];
792
+ case "unreadable":
793
+ return [` !! native-write coordinator is unreadable: ${diagnostic.reason}`, ...pathLine, ...evidenceLines];
794
+ }
795
+ }
796
+
615
797
  /** Render the doctor "Memory / runtime" section lines (testable without console capture). */
616
798
  export function formatServiceMemoryLines(report: ServiceMemoryReport): string[] {
617
799
  const lines: string[] = [];
618
800
  lines.push(` -- doctor process Bun ${Bun.version} (this is NOT the service process)`);
619
801
  if (report.status === "unauthorized") {
620
- lines.push(" -- proxy reachable but rejected the request set OPENCODEX_ADMIN_AUTH_TOKEN to match the service");
802
+ lines.push(" -- local diagnostic capability unavailable restart the running proxy with this OpenCodex version");
621
803
  return lines;
622
804
  }
623
805
  if (report.status === "unreachable") {
@@ -652,12 +834,22 @@ export function formatServiceMemoryLines(report: ServiceMemoryReport): string[]
652
834
  } else {
653
835
  lines.push(" !! high RSS, indeterminate split — capture two doctor runs over time to see the trend");
654
836
  }
655
- // Version-claiming (never binary-claiming): the endpoint cannot distinguish
656
- // the bundled binary from an OPENCODEX_BUN_PATH override of the same version.
657
837
  if (d.platform === "win32" && d.eagerRelay?.reason === "auto-known-bad") {
658
838
  lines.push(` service is running Bun ${d.bunVersion} on Windows — a version affected by the upstream Bun memory issue.`);
659
- lines.push(" Options: wait for a bundled runtime update, or set OPENCODEX_BUN_PATH to a runtime you trust (unvalidated — own risk),");
660
- lines.push(" or opt into streamMode \"eager-relay\" via PUT /api/settings (crash risk on this runtime; see docs).");
839
+ // The remediation depends on how the SERVICE was launched, which only the
840
+ // launch-time marker can answer. Telling someone to set OPENCODEX_BUN_PATH
841
+ // when it is already set is the bug this branch exists to avoid (#848).
842
+ if (d.bunRuntimeSource === "override") {
843
+ lines.push(` OPENCODEX_BUN_PATH is already active for this service — the override runtime is itself an affected version (unvalidated — own risk).`);
844
+ lines.push(" Options: point the override at a different runtime, or opt into streamMode \"eager-relay\" via PUT /api/settings (crash risk on this runtime; see docs).");
845
+ } else if (d.bunRuntimeSource === undefined) {
846
+ lines.push(" this service records no runtime origin (installed before provenance tracking), so OpenCodex cannot tell whether an override is already active.");
847
+ lines.push(" Reinstall the service to record it, or opt into streamMode \"eager-relay\" via PUT /api/settings (crash risk on this runtime; see docs).");
848
+ } else {
849
+ const origin = d.bunRuntimeSource === "process" ? "the runtime that launched it" : "the bundled runtime";
850
+ lines.push(` the service is using ${origin}. Options: wait for a bundled runtime update, or set OPENCODEX_BUN_PATH to a runtime you trust (unvalidated — own risk),`);
851
+ lines.push(" or opt into streamMode \"eager-relay\" via PUT /api/settings (crash risk on this runtime; see docs).");
852
+ }
661
853
  }
662
854
  return lines;
663
855
  }
@@ -671,11 +863,21 @@ export function proxyDownRestartHint(input: {
671
863
  proxyRunning: boolean;
672
864
  port: number;
673
865
  serviceViable: boolean;
866
+ /** Absent means "unknown"; the hint then keeps its pre-repair wording. */
867
+ serviceInstalled?: boolean;
868
+ serviceConflict?: boolean;
674
869
  }): string | null {
675
870
  if (input.proxyRunning) return null;
871
+ // `serviceViable` alone conflates "no service at all" with "registered but stale or
872
+ // stopped". Only the first wants `install`: re-registering an existing service costs a
873
+ // UAC prompt on Windows and can switch a WinSW backend to Task Scheduler. A conflict
874
+ // still needs uninstall-then-install, which repairService() refuses outright.
875
+ const installedButBroken = input.serviceInstalled === true && input.serviceConflict !== true;
676
876
  const restart = input.serviceViable
677
877
  ? "Restart it with 'ocx service start' (service installed) or 'ocx start'."
678
- : "Restart it with 'ocx start', or install the persistent service: 'ocx service install'.";
878
+ : installedButBroken
879
+ ? "Restart it with 'ocx start', or refresh the installed service: 'ocx service repair'."
880
+ : "Restart it with 'ocx start', or install the persistent service: 'ocx service install'.";
679
881
  return `The ocx proxy is not running. Codex/Claude clients pinned to 127.0.0.1:${input.port} fail with errors like "error sending request for url (http://127.0.0.1:${input.port}/v1/responses)". ${restart}`;
680
882
  }
681
883
 
@@ -704,6 +906,33 @@ export async function runDoctor(args: string[] = []): Promise<void> {
704
906
  return;
705
907
  }
706
908
 
909
+ if (args.includes(RECOVER_ZERO_BYTE_COORDINATOR_FLAG)) {
910
+ if (!args.includes("--yes")) {
911
+ console.log(`Recovery is explicit and creates a same-directory backup. Re-run: ocx doctor ${RECOVER_ZERO_BYTE_COORDINATOR_FLAG} --yes`);
912
+ process.exitCode = 1;
913
+ return;
914
+ }
915
+ const diagnostics = readConfigDiagnostics().config;
916
+ const live = await findLiveProxy({
917
+ configFn: () => ({ port: diagnostics.port, hostname: diagnostics.hostname }),
918
+ });
919
+ if (live) {
920
+ console.log(`Recovery refused: OpenCodex proxy pid ${live.pid} is still running. Stop the proxy/service and retry.`);
921
+ process.exitCode = 1;
922
+ return;
923
+ }
924
+ const recovered = recoverZeroByteCodexCoordinator();
925
+ if (!recovered.ok) {
926
+ console.log(`Recovery refused: ${recovered.reason}.`);
927
+ process.exitCode = 1;
928
+ return;
929
+ }
930
+ console.log(`Moved the non-authoritative coordinator to ${recovered.backupPath}`);
931
+ console.log("Run `ocx sync` to retry Codex config injection. The backup was preserved and no Codex config/catalog file was changed by recovery.");
932
+ process.exitCode = 0;
933
+ return;
934
+ }
935
+
707
936
  console.log("opencodex doctor\n");
708
937
 
709
938
  // Ordering note: the memory/runtime section renders after "Running proxy
@@ -719,6 +948,26 @@ export async function runDoctor(args: string[] = []): Promise<void> {
719
948
  console.log(` ${row.exists ? "ok " : "-- "} ${row.label}: ${row.path}${flags ? ` (${flags})` : ""}`);
720
949
  }
721
950
 
951
+ // Runs without the proxy on purpose: the worst accumulation happens when the proxy will
952
+ // not start, which is exactly when the in-process periodic reclaim never ticks.
953
+ const reclaimTemps = args.includes(RECLAIM_RESPONSE_TEMPS_FLAG);
954
+ console.log("\nResponse-state temp files");
955
+ // A typo must not silently degrade into "nothing to reclaim" — the operator would read the
956
+ // report as an answer to a question they never actually asked.
957
+ for (const arg of args) {
958
+ if (arg !== RECLAIM_RESPONSE_TEMPS_FLAG && /^--reclaim/.test(arg)) {
959
+ console.log(` !! Unrecognized flag ${arg}; did you mean ${RECLAIM_RESPONSE_TEMPS_FLAG}? Reporting only.`);
960
+ }
961
+ }
962
+ for (const line of formatResponseTempLines(
963
+ // The reclaim budget matches the report budget: a report bounded by entries and a removal
964
+ // bounded by a smaller cleanup cap would tell an operator 816 and then silently free 512.
965
+ reclaimTemps
966
+ ? reclaimAbandonedResponseStateTemps({ maxCleanups: RESPONSE_TEMP_RECLAIM_MAX_CLEANUPS })
967
+ : inspectAbandonedResponseStateTemps(),
968
+ reclaimTemps,
969
+ )) console.log(line);
970
+
722
971
  const orcaHome = collectOrcaCodexHomeDiagnostic();
723
972
  console.log("\nCodex app home targeting");
724
973
  console.log(` ${orcaHome.mismatch ? "!! " : "ok "} Effective Codex home: ${orcaHome.effectiveCodexHome}`);
@@ -769,10 +1018,6 @@ export async function runDoctor(args: string[] = []): Promise<void> {
769
1018
  const live = await findLiveProxy({
770
1019
  configFn: () => ({ port: doctorConfig.port, hostname: doctorConfig.hostname }),
771
1020
  });
772
- const livePid = live ? live.pid : readPid();
773
- const liveRuntime = live
774
- ? { pid: live.pid ?? 0, port: live.port, hostname: live.hostname }
775
- : (livePid ? readRuntimePort(livePid) : null);
776
1021
 
777
1022
  const currentProxyEnv = collectProxyEnv();
778
1023
  const configuredProxy = collectConfiguredProxy();
@@ -812,13 +1057,11 @@ export async function runDoctor(args: string[] = []): Promise<void> {
812
1057
 
813
1058
  console.log("\nMemory / runtime");
814
1059
  {
815
- const runtime = liveRuntime;
816
- if (!runtime || !live) {
1060
+ if (!live) {
817
1061
  console.log(` -- doctor process Bun ${Bun.version} (this is NOT the service process)`);
818
1062
  console.log(" -- no running ocx proxy found (no live pid/runtime record)");
819
1063
  } else {
820
- const token = configuredAdminToken();
821
- const report = await fetchServiceMemory(gracefulStopHost(runtime.hostname), runtime.port, token);
1064
+ const report = await fetchServiceMemory(live);
822
1065
  for (const line of formatServiceMemoryLines(report)) console.log(line);
823
1066
  }
824
1067
  }
@@ -833,6 +1076,24 @@ export async function runDoctor(args: string[] = []): Promise<void> {
833
1076
  // Codex app until the one-time migration lands. Read-only probe (readonly sqlite, 100ms
834
1077
  // busy timeout) — reports state, never mutates.
835
1078
  console.log("\nCodex history migration");
1079
+ // The history failure messages point here; make the visit worthwhile by
1080
+ // probing the coordinator namespace the locks live in. The probe exercises
1081
+ // identity, runtime-root, and permission checks without taking any lock or
1082
+ // creating anything (a doctor run must observe, not initialize).
1083
+ try {
1084
+ const identity = resolveEffectiveUserIdentity();
1085
+ const probe = probeCodexCoordinatorNamespace(identity);
1086
+ if (probe.status === "missing") {
1087
+ console.log(" ok history coordinator namespace not created yet (no history operation has run)");
1088
+ } else {
1089
+ console.log(" ok history coordinator namespace resolves");
1090
+ }
1091
+ } catch (cause) {
1092
+ const reason = cause instanceof CodexUserIdentityRefusal ? cause.message : String(cause);
1093
+ console.log(` -- history coordinator namespace refused: ${reason}`);
1094
+ }
1095
+ console.log("\nCodex native-write coordinator");
1096
+ for (const line of formatCoordinatorDoctorLines(inspectCodexCoordinator())) console.log(line);
836
1097
  const pending = countPendingOpencodexHistory();
837
1098
  if (pending.failed) {
838
1099
  console.log(" -- state DB locked or unreadable (Codex app open?) — migration state unknown");
@@ -852,6 +1113,15 @@ export async function runDoctor(args: string[] = []): Promise<void> {
852
1113
  }
853
1114
  }
854
1115
 
1116
+ console.log("\nCodex agent role files");
1117
+ const tomlFallbackRoles = scanCodexAgentRolesWithTomlModelFallback(resolveCodexHomeDirImpl());
1118
+ if (tomlFallbackRoles.length === 0) {
1119
+ console.log(" ok no per-role model_fallback fields in $CODEX_HOME/agents/*.toml");
1120
+ } else {
1121
+ console.log(` [WARN] ${tomlFallbackRoles.length} agent role file${tomlFallbackRoles.length === 1 ? "" : "s"} contain${tomlFallbackRoles.length === 1 ? "s" : ""} \`model_fallback\`: ${tomlFallbackRoles.join(", ")}`);
1122
+ console.log(" Codex >= 0.146 rejects that field as unknown and skips the whole role. Move the chains to opencodex config `subagentModelFallbackByModel` (keyed by primary model) and remove the field from the TOML files.");
1123
+ }
1124
+
855
1125
  const dual = collectWslDualInstall();
856
1126
  if (dual.wsl) {
857
1127
  console.log("\nWSL Codex installs");
@@ -891,6 +1161,8 @@ export async function runDoctor(args: string[] = []): Promise<void> {
891
1161
  proxyRunning: Boolean(live),
892
1162
  port: live?.port ?? doctorConfig.port ?? 10100,
893
1163
  serviceViable: startup.serviceViable,
1164
+ serviceInstalled: startup.serviceInstalled,
1165
+ serviceConflict: startup.serviceConflict,
894
1166
  });
895
1167
  if (proxyDown) hints.push(proxyDown);
896
1168
  for (const row of providerApiKeys) {
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Align Grok and Claude Desktop files with the durable switches during `ocx ensure`.
3
+ *
4
+ * handleEnsure used to load config once, then health-probe / model-sync / spawn,
5
+ * and only afterwards mutate ~/.grok/config.toml and the Desktop library from
6
+ * that snapshot. An OFF→ON flip in that window stripped a freshly enabled fence
7
+ * or deleted a freshly applied Desktop profile; ON→OFF rewrote the files the
8
+ * user had just turned off. Re-read persisted desired state immediately before
9
+ * each external-file mutation, and use that current config for sync inputs.
10
+ */
11
+ import { loadConfig } from "../config";
12
+ import { stripGrokConfig, type GrokInjectResult } from "../grok/inject";
13
+ import { removeDesktop3pStandardPivot } from "../claude/desktop-3p";
14
+ import {
15
+ claudeDesktopIntegrationEnabled,
16
+ shouldSyncGrokOnStart,
17
+ } from "../codex/desired-state";
18
+ import type { OcxConfig } from "../types";
19
+
20
+ export function grokSyncFailureMessage(err: unknown): string {
21
+ const detail = err instanceof Error ? err.message : String(err);
22
+ return `Grok Build config sync failed: ${detail}. `
23
+ + "~/.grok/config.toml may still point at a previous proxy port — "
24
+ + "run 'ocx ensure' (or apply from the dashboard's Grok page) to repoint it.";
25
+ }
26
+
27
+ export interface EnsureDesiredIntegrationsDeps {
28
+ loadConfig: () => OcxConfig;
29
+ stripGrokConfig: typeof stripGrokConfig;
30
+ syncGrokConfig: (
31
+ port: number,
32
+ config: OcxConfig,
33
+ opts?: { hostname?: string },
34
+ ) => Promise<GrokInjectResult>;
35
+ removeDesktop3pStandardPivot: typeof removeDesktop3pStandardPivot;
36
+ log?: (message: string) => void;
37
+ error?: (message: string) => void;
38
+ }
39
+
40
+ async function defaultSyncGrokConfig(
41
+ port: number,
42
+ config: OcxConfig,
43
+ opts: { hostname?: string } = {},
44
+ ): Promise<GrokInjectResult> {
45
+ const { syncGrokConfig } = await import("../grok/sync");
46
+ return syncGrokConfig(port, config, opts);
47
+ }
48
+
49
+ const productionDeps: EnsureDesiredIntegrationsDeps = {
50
+ loadConfig,
51
+ stripGrokConfig,
52
+ syncGrokConfig: defaultSyncGrokConfig,
53
+ removeDesktop3pStandardPivot,
54
+ };
55
+
56
+ function io(deps: EnsureDesiredIntegrationsDeps): {
57
+ log: (message: string) => void;
58
+ error: (message: string) => void;
59
+ } {
60
+ return {
61
+ log: deps.log ?? (message => console.log(message)),
62
+ error: deps.error ?? (message => console.error(message)),
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Keep ~/.grok/config.toml aligned with the durable Grok switch.
68
+ *
69
+ * `handleStart` already gates inject on `shouldSyncGrokOnStart`. `ocx ensure`
70
+ * used to call `syncGrokConfig` unconditionally, so a dashboard/update/restart
71
+ * path that lands in ensure rewrote the fence while the switch stayed OFF.
72
+ * When the switch is OFF, strip any leftover managed block instead of injecting.
73
+ */
74
+ export async function ensureGrokFenceMatchesDesired(
75
+ port: number,
76
+ opts: { hostname?: string } = {},
77
+ deps: EnsureDesiredIntegrationsDeps = productionDeps,
78
+ ): Promise<void> {
79
+ const config = deps.loadConfig();
80
+ const { log, error } = io(deps);
81
+ if (!shouldSyncGrokOnStart(config)) {
82
+ try {
83
+ const grok = deps.stripGrokConfig();
84
+ if (grok.changed) log(` ↩️ ${grok.message}`);
85
+ else if (!grok.ok) error(`⚠️ ${grok.message}`);
86
+ } catch (err) {
87
+ error(`⚠️ ${grokSyncFailureMessage(err)}`);
88
+ }
89
+ return;
90
+ }
91
+ try {
92
+ const hostname = opts.hostname ?? config.hostname;
93
+ const g = await deps.syncGrokConfig(
94
+ port,
95
+ config,
96
+ hostname !== undefined ? { hostname } : {},
97
+ );
98
+ if (g.changed) log(" + Grok Build config updated (~/.grok/config.toml)");
99
+ else if (!g.ok) error(`⚠️ ${g.message}`);
100
+ } catch (err) {
101
+ error(`⚠️ ${grokSyncFailureMessage(err)}`);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * When Claude Desktop is durably OFF, clear any leftover owned gateway profile.
107
+ * ensure/update used to leave Claude-3p residue in place after a failed disable
108
+ * (drifted fingerprint), so the Integrations card kept looking applied/stale.
109
+ */
110
+ export function ensureClaudeDesktopMatchesDesired(
111
+ deps: EnsureDesiredIntegrationsDeps = productionDeps,
112
+ ): void {
113
+ const config = deps.loadConfig();
114
+ const { log, error } = io(deps);
115
+ if (claudeDesktopIntegrationEnabled(config)) return;
116
+ try {
117
+ const removed = deps.removeDesktop3pStandardPivot({
118
+ appliedFingerprint: config.claudeCode?.desktopProfile?.appliedFingerprint ?? null,
119
+ });
120
+ if (removed.ok && removed.changed) {
121
+ log(" ↩️ Claude Desktop integration residue removed.");
122
+ } else if (!removed.ok) {
123
+ error(`⚠️ Claude Desktop cleanup skipped: ${removed.reason ?? removed.kind}.`);
124
+ }
125
+ } catch (err) {
126
+ const detail = err instanceof Error ? err.message : String(err);
127
+ error(`⚠️ Claude Desktop cleanup failed: ${detail}.`);
128
+ }
129
+ }
130
+
131
+ export type EnsureDesiredIntegrationsBranch =
132
+ | { kind: "live"; hostname?: string }
133
+ | { kind: "spawned" };
134
+
135
+ /**
136
+ * Reconcile the two external integration files after either ensure race window.
137
+ * Only the live proxy's observed bind host crosses this boundary; persisted
138
+ * config is deliberately loaded inside each mutation helper.
139
+ */
140
+ export async function reconcileEnsureDesiredIntegrations(
141
+ port: number,
142
+ branch: EnsureDesiredIntegrationsBranch,
143
+ deps: EnsureDesiredIntegrationsDeps = productionDeps,
144
+ ): Promise<void> {
145
+ const liveHost = branch.kind === "live" ? branch.hostname : undefined;
146
+ await ensureGrokFenceMatchesDesired(
147
+ port,
148
+ liveHost ? { hostname: liveHost } : {},
149
+ deps,
150
+ );
151
+ ensureClaudeDesktopMatchesDesired(deps);
152
+ }