@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
@@ -4,6 +4,7 @@ import { join } from "node:path";
4
4
  import { createInterface } from "node:readline/promises";
5
5
  import { atomicWriteFile, getConfigDir } from "../config";
6
6
  import { hasStarPromptRun } from "../cli/star-prompt";
7
+ import { selfLaunchArgv } from "../lib/self-launch-argv";
7
8
  import {
8
9
  type Channel,
9
10
  currentVersion,
@@ -83,8 +84,12 @@ function gt(a: number[], b: number[]): boolean {
83
84
 
84
85
  /**
85
86
  * Channel-aware "is latest newer than current?".
86
- * - latest channel: compare maj.min.pat only; prereleases are never "newer"
87
- * (parity with codex-rs), so stable users are not pushed onto previews.
87
+ * - latest channel: compare maj.min.pat only; prerelease TARGETS are never
88
+ * "newer" (parity with codex-rs), so stable users are not pushed onto
89
+ * previews. An installed preview CURRENT compares by its maj.min.pat core,
90
+ * so a stable release with a strictly higher base is offered (same base is
91
+ * content-lateral promotion and stays not-newer, mirroring the preview
92
+ * channel's O3 rule).
88
93
  * - preview channel: preview-vs-preview compares the trailing -preview.N; a
89
94
  * stable release with a strictly higher base counts as newer (O3), while a
90
95
  * stable release with the same base as the current preview does not.
@@ -92,7 +97,7 @@ function gt(a: number[], b: number[]): boolean {
92
97
  export function isNewer(latest: string, current: string, channel: Channel): boolean {
93
98
  if (channel === "latest") {
94
99
  const l = parseStable(latest);
95
- const c = parseStable(current);
100
+ const c = parseStable(current) ?? parsePreview(current)?.slice(0, 3);
96
101
  if (!l || !c) return false;
97
102
  return gt(l, c);
98
103
  }
@@ -162,9 +167,10 @@ function cacheIsStale(cache: VersionCache | null): boolean {
162
167
  export function triggerBackgroundRefreshIfStale(channel: Channel, cache: VersionCache | null): void {
163
168
  if (!cacheIsStale(cache)) return;
164
169
  try {
165
- const entry = process.argv[1];
166
- if (!entry || !existsSync(entry)) return;
167
- const child = spawn(process.execPath, [entry, "__refresh-version", channel], {
170
+ const commandArgs = ["__refresh-version", channel];
171
+ const args = selfLaunchArgv(commandArgs);
172
+ if (args.length > commandArgs.length && (!args[0] || !existsSync(args[0]))) return;
173
+ const child = spawn(process.execPath, args, {
168
174
  detached: true,
169
175
  stdio: "ignore",
170
176
  windowsHide: true,
@@ -0,0 +1,47 @@
1
+ import type { spawnSync } from "node:child_process";
2
+
3
+ export type NpmCachePreflightReason =
4
+ | "cache_accessible"
5
+ | "cache_entry_foreign_owner"
6
+ | "cache_entry_inaccessible"
7
+ | "cache_path_malformed"
8
+ | "inspection_incomplete"
9
+ | "npm_config_failed"
10
+ | "npm_unavailable"
11
+ | "windows_skip"
12
+ | "worker_failed"
13
+ | "worker_output_malformed"
14
+ | "worker_timeout";
15
+
16
+ export interface NpmCachePreflightResult {
17
+ ok: boolean;
18
+ reason: NpmCachePreflightReason;
19
+ }
20
+
21
+ export interface NpmCacheInspectionOptions {
22
+ expectedUid?: number;
23
+ maxDepth?: number;
24
+ maxEntries?: number;
25
+ nowMs?: () => number;
26
+ /** Test seam: resolve a symlinked cache root. Defaults to realpathSync. */
27
+ realpathFn?: (path: string) => string;
28
+ /** Test seam: resolve an entry's owner uid. Defaults to the lstat result. */
29
+ uidOf?: (path: string, stat: { uid: number }) => number;
30
+ timeoutMs?: number;
31
+ }
32
+
33
+ export interface NpmCachePreflightOptions {
34
+ env?: NodeJS.ProcessEnv;
35
+ execPath?: string;
36
+ platform?: NodeJS.Platform;
37
+ spawnSyncFn?: typeof spawnSync;
38
+ timeoutMs?: number;
39
+ }
40
+
41
+ export function inspectNpmCacheDirectory(
42
+ cachePath: string,
43
+ options?: NpmCacheInspectionOptions,
44
+ ): NpmCachePreflightResult;
45
+
46
+ export function runNpmCachePreflight(options?: NpmCachePreflightOptions): NpmCachePreflightResult;
47
+ export function npmCachePreflightFailureMessage(reason: NpmCachePreflightReason): string;
@@ -0,0 +1,201 @@
1
+ import { lstatSync, readdirSync, realpathSync } from "node:fs";
2
+ import { spawnSync } from "node:child_process";
3
+ import { isAbsolute, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { npmInvocation } from "./npm-invocation.mjs";
6
+
7
+ const WORKER_ARG = "--ocx-npm-cache-preflight-worker";
8
+ const PROTOCOL_VERSION = 1;
9
+ const WORKER_TIMEOUT_MS = 10_000;
10
+ const NPM_CONFIG_TIMEOUT_MS = 5_000;
11
+ const INSPECTION_TIMEOUT_MS = 7_500;
12
+ const MAX_ENTRIES = 100_000;
13
+ const MAX_DEPTH = 64;
14
+
15
+ const RESULT_REASONS = new Set([
16
+ "cache_accessible",
17
+ "cache_entry_foreign_owner",
18
+ "cache_entry_inaccessible",
19
+ "cache_path_malformed",
20
+ "inspection_incomplete",
21
+ "npm_config_failed",
22
+ "npm_unavailable",
23
+ ]);
24
+
25
+ function inaccessibleByMode(stat) {
26
+ if (stat.isSymbolicLink()) return false;
27
+ const ownerBits = stat.mode & 0o700;
28
+ if (stat.isDirectory()) return (ownerBits & 0o700) !== 0o700;
29
+ return (ownerBits & 0o400) === 0;
30
+ }
31
+
32
+ /**
33
+ * Inspect an existing Unix npm cache without following symlinks. The limits are
34
+ * deliberately part of the result contract: an incomplete inspection cannot prove
35
+ * that replacing the live package will succeed.
36
+ */
37
+ export function inspectNpmCacheDirectory(cachePath, options = {}) {
38
+ const expectedUid = options.expectedUid ?? process.getuid?.();
39
+ const deadline = (options.nowMs ?? Date.now)() + (options.timeoutMs ?? INSPECTION_TIMEOUT_MS);
40
+ const maxEntries = options.maxEntries ?? MAX_ENTRIES;
41
+ const maxDepth = options.maxDepth ?? MAX_DEPTH;
42
+ const nowMs = options.nowMs ?? Date.now;
43
+ // Injected uid seam. A test cannot create a genuinely foreign-owned file without a second
44
+ // account, and without this the symlink-before-ownership rule cannot be pinned: `!isDirectory`
45
+ // skips a link anyway, so removing the rule leaves every assertion green.
46
+ const uidOf = options.uidOf ?? ((_path, stat) => stat.uid);
47
+ const stack = [{ path: cachePath, depth: 0 }];
48
+ let inspected = 0;
49
+ let rootResolved = false;
50
+
51
+ while (stack.length > 0) {
52
+ // Budget exhausted is NOT a failure. A mature npm cache legitimately holds hundreds of
53
+ // thousands of entries — this machine's has ~256k — and treating "we ran out of time to
54
+ // look" as "your cache is broken" would block updates for ordinary users, which is worse
55
+ // than the bug this preflight exists to prevent. We looked at a bounded prefix, found
56
+ // nothing wrong, and let the update proceed.
57
+ if (inspected >= maxEntries || nowMs() > deadline) {
58
+ return { ok: true, reason: "inspection_incomplete" };
59
+ }
60
+ const current = stack.pop();
61
+ let stat;
62
+ try {
63
+ stat = lstatSync(current.path);
64
+ } catch (error) {
65
+ if (current.depth === 0 && error?.code === "ENOENT") {
66
+ return { ok: true, reason: "cache_accessible" };
67
+ }
68
+ return { ok: false, reason: "cache_entry_inaccessible" };
69
+ }
70
+ inspected += 1;
71
+
72
+ // A symlinked cache ROOT used to be rejected outright, but pointing ~/.npm at another volume
73
+ // is ordinary npm configuration, and blocking those users would be the same false-positive
74
+ // failure this preflight exists to avoid. Resolve the root once and inspect the target;
75
+ // only an unresolvable root is a real problem. Nested links are still never followed.
76
+ if (current.depth === 0 && stat.isSymbolicLink()) {
77
+ // Resolve exactly once. realpath already collapses a chain, so a second pass would only
78
+ // happen if the target is itself reported as a link — treat that as unresolvable rather
79
+ // than looping.
80
+ if (rootResolved) return { ok: false, reason: "cache_entry_inaccessible" };
81
+ rootResolved = true;
82
+ let resolved;
83
+ try {
84
+ resolved = (options.realpathFn ?? realpathSync)(current.path);
85
+ } catch {
86
+ return { ok: false, reason: "cache_entry_inaccessible" };
87
+ }
88
+ stack.push({ path: resolved, depth: 0 });
89
+ continue;
90
+ }
91
+ // A nested symlink is not. npm creates them constantly below _npx, node_modules and .bin,
92
+ // and we never follow them — so its owner is irrelevant and must not abort the update.
93
+ // This has to come BEFORE the ownership check: a foreign-owned but never-followed link is
94
+ // exactly the false positive that made the previous attempt at this feature unusable.
95
+ if (stat.isSymbolicLink()) continue;
96
+
97
+ if (expectedUid !== undefined && uidOf(current.path, stat) !== expectedUid) {
98
+ return { ok: false, reason: "cache_entry_foreign_owner" };
99
+ }
100
+ if (inaccessibleByMode(stat)) {
101
+ return { ok: false, reason: "cache_entry_inaccessible" };
102
+ }
103
+ if (!stat.isDirectory()) continue;
104
+ // Same reasoning as the entry budget: too deep to finish is not evidence of a bad cache.
105
+ if (current.depth >= maxDepth) return { ok: true, reason: "inspection_incomplete" };
106
+
107
+ let entries;
108
+ try {
109
+ entries = readdirSync(current.path, { withFileTypes: true });
110
+ } catch {
111
+ return { ok: false, reason: "cache_entry_inaccessible" };
112
+ }
113
+ for (const entry of entries) {
114
+ stack.push({ path: resolve(current.path, entry.name), depth: current.depth + 1 });
115
+ }
116
+ }
117
+
118
+ return { ok: true, reason: "cache_accessible" };
119
+ }
120
+
121
+ function workerResult() {
122
+ const invocation = npmInvocation(["config", "get", "cache"]);
123
+ if (!invocation) return { ok: false, reason: "npm_unavailable" };
124
+ const npm = spawnSync(invocation.file, invocation.args, {
125
+ encoding: "utf8",
126
+ timeout: NPM_CONFIG_TIMEOUT_MS,
127
+ windowsHide: true,
128
+ ...invocation.options,
129
+ });
130
+ if (npm.status !== 0) return { ok: false, reason: "npm_config_failed" };
131
+
132
+ const output = typeof npm.stdout === "string" ? npm.stdout.trim() : "";
133
+ if (!output || output.length > 4096 || output.includes("\0") || /[\r\n]/.test(output) || !isAbsolute(output)) {
134
+ return { ok: false, reason: "cache_path_malformed" };
135
+ }
136
+ return inspectNpmCacheDirectory(output);
137
+ }
138
+
139
+ // Reasons that legitimately accompany `ok: true`. The parser below cross-checks the flag against
140
+ // this set so a worker cannot claim success with a failure reason (or the reverse). It is a SET,
141
+ // not a single value: a bounded inspection that ran out of budget without finding a problem is a
142
+ // pass, and hardcoding `cache_accessible` here silently rejected exactly that — the pass never
143
+ // reached the caller and every large cache still failed, as `worker_output_malformed`.
144
+ const OK_REASONS = new Set([
145
+ "cache_accessible",
146
+ "inspection_incomplete",
147
+ "windows_skip",
148
+ ]);
149
+
150
+ function parseWorkerOutput(stdout) {
151
+ if (typeof stdout !== "string" || stdout.length > 1024) return null;
152
+ try {
153
+ const parsed = JSON.parse(stdout);
154
+ if (!parsed || parsed.protocol !== PROTOCOL_VERSION || typeof parsed.ok !== "boolean") return null;
155
+ if (typeof parsed.reason !== "string" || !RESULT_REASONS.has(parsed.reason)) return null;
156
+ if (parsed.ok !== OK_REASONS.has(parsed.reason)) return null;
157
+ if (Object.keys(parsed).sort().join(",") !== "ok,protocol,reason") return null;
158
+ return { ok: parsed.ok, reason: parsed.reason };
159
+ } catch {
160
+ return null;
161
+ }
162
+ }
163
+
164
+ /** Run the bounded cache inspection in an isolated, synchronously-timeboxed worker. */
165
+ export function runNpmCachePreflight(options = {}) {
166
+ if ((options.platform ?? process.platform) === "win32") {
167
+ return { ok: true, reason: "windows_skip" };
168
+ }
169
+ const spawn = options.spawnSyncFn ?? spawnSync;
170
+ const result = spawn(
171
+ options.execPath ?? process.execPath,
172
+ [fileURLToPath(import.meta.url), WORKER_ARG],
173
+ {
174
+ encoding: "utf8",
175
+ timeout: options.timeoutMs ?? WORKER_TIMEOUT_MS,
176
+ windowsHide: true,
177
+ env: options.env ?? process.env,
178
+ },
179
+ );
180
+ if (result.status === null) return { ok: false, reason: "worker_timeout" };
181
+ if (result.status !== 0) return { ok: false, reason: "worker_failed" };
182
+ return parseWorkerOutput(result.stdout) ?? { ok: false, reason: "worker_output_malformed" };
183
+ }
184
+
185
+ /** Fixed operator guidance; worker/npm output is intentionally never interpolated. */
186
+ export function npmCachePreflightFailureMessage(reason) {
187
+ return `npm cache access pre-flight failed (${reason}); fix cache ownership and permissions, then retry`;
188
+ }
189
+
190
+ const isWorker = process.argv[1]
191
+ && resolve(process.argv[1]) === fileURLToPath(import.meta.url)
192
+ && process.argv[2] === WORKER_ARG;
193
+ if (isWorker) {
194
+ let result;
195
+ try {
196
+ result = workerResult();
197
+ } catch {
198
+ result = { ok: false, reason: "cache_entry_inaccessible" };
199
+ }
200
+ process.stdout.write(JSON.stringify({ protocol: PROTOCOL_VERSION, ...result }));
201
+ }
@@ -0,0 +1,22 @@
1
+ export type InstallTreeVerification = { ok: boolean; failures: string[] };
2
+ export function verifyInstallTree(packageDir: string, expectedVersion?: string): InstallTreeVerification;
3
+ export function bootRestoreProbe(
4
+ packageDir: string,
5
+ deps?: { rename?: (from: string, to: string) => void },
6
+ ): { action: "none" | "reaped" | "restored" | "failed"; count?: number; from?: string; error?: string };
7
+ export function transactionalNpmUpdate(args: {
8
+ packageDir: string;
9
+ pkgName: string;
10
+ targetVersion?: string;
11
+ tag: string;
12
+ runNpm: (args: string[]) => { status: number | null };
13
+ log?: (line: string) => void;
14
+ deps?: { rename?: (from: string, to: string) => void };
15
+ }): {
16
+ ok: boolean;
17
+ phase: "stage" | "verify" | "swap-backup" | "swap-live" | "post-verify" | "double-fault" | "done";
18
+ error?: string;
19
+ rolledBack?: boolean;
20
+ backup?: string;
21
+ };
22
+
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Transactional npm self-update: stage -> verify -> swap -> rollback (#1942 / #1849).
3
+ *
4
+ * The legacy path ran `npm install -g` straight into the live global tree, so a
5
+ * failure after npm removed the old files left a file-less package skeleton with no
6
+ * recovery. This module stages the new version into a SIBLING directory of the live
7
+ * package (same volume, so directory renames are atomic-ish and never cross devices),
8
+ * verifies the staged tree with a manifest before anything live is touched, then swaps
9
+ * live -> backup -> stage-into-live with a reverse-rename rollback on failure and a
10
+ * recovery marker on double fault.
11
+ *
12
+ * Layout (siblings, never children — a child would travel WITH the live rename and the
13
+ * live dir cannot move into its own subtree):
14
+ * <scopeDir>/opencodex live package
15
+ * <scopeDir>/.ocx-staging-<ts>/ npm --prefix root (contains node_modules/...)
16
+ * <scopeDir>/.ocx-backup-<ts>/opencodex previous live tree during/after the swap
17
+ * <scopeDir>/.ocx-recovery.json double-fault marker with a one-line restore
18
+ */
19
+ import { spawnSync } from "node:child_process";
20
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
21
+ import { dirname, join } from "node:path";
22
+
23
+ /** Verification manifest for a staged (or live) package tree. */
24
+ export function verifyInstallTree(packageDir, expectedVersion) {
25
+ const failures = [];
26
+ let pkg;
27
+ try {
28
+ pkg = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8"));
29
+ } catch (error) {
30
+ return { ok: false, failures: ["package.json unreadable: " + (error?.message ?? String(error))] };
31
+ }
32
+ if (expectedVersion && pkg.version !== expectedVersion) {
33
+ failures.push("package.json version " + pkg.version + " != expected " + expectedVersion);
34
+ }
35
+ const launcher = join(packageDir, "bin", "ocx.mjs");
36
+ try {
37
+ const st = statSync(launcher);
38
+ if (!st.isFile() || st.size < 1024) failures.push("bin/ocx.mjs missing or truncated");
39
+ } catch {
40
+ failures.push("bin/ocx.mjs absent");
41
+ }
42
+ // The bundled Bun binary is the load-bearing artifact: without it the launcher exits
43
+ // before serving anything, and a boot probe that called this tree healthy would reap
44
+ // the only backup (review High 3). Size-gate the real binary, not just its package.json.
45
+ const bunPkgDir = join(packageDir, "node_modules", "bun");
46
+ if (existsSync(bunPkgDir)) {
47
+ const bunBinary = findLargestFile(bunPkgDir);
48
+ if (!bunBinary || bunBinary.size < 10 * 1024 * 1024) {
49
+ failures.push("bundled Bun binary missing or truncated (< 10MB)");
50
+ }
51
+ }
52
+ // Sentinel direct deps: each must have an intact package.json. The bundled Bun dep is
53
+ // the load-bearing one — without it the launcher cannot start the proxy at all.
54
+ const deps = Object.keys(pkg.dependencies ?? {});
55
+ const sentinels = deps.filter(name => name === "bun" || name === "zod").length > 0
56
+ ? deps.filter(name => name === "bun" || name === "zod")
57
+ : deps.slice(0, 2);
58
+ for (const name of sentinels) {
59
+ const depPkg = join(packageDir, "node_modules", ...name.split("/"), "package.json");
60
+ if (!existsSync(depPkg)) failures.push("sentinel dependency missing: " + name);
61
+ }
62
+ return failures.length === 0 ? { ok: true, failures: [] } : { ok: false, failures };
63
+ }
64
+
65
+ function stampedName(prefix) {
66
+ return prefix + "-" + new Date().toISOString().replace(/[:.]/g, "-");
67
+ }
68
+
69
+ /** Largest regular file under a directory tree (bounded depth) — locates the Bun binary. */
70
+ function findLargestFile(root, depth = 3) {
71
+ let best;
72
+ let names = [];
73
+ try { names = readdirSync(root); } catch { return undefined; }
74
+ for (const name of names) {
75
+ const full = join(root, name);
76
+ let st;
77
+ try { st = statSync(full); } catch { continue; }
78
+ if (st.isFile()) {
79
+ if (!best || st.size > best.size) best = { path: full, size: st.size };
80
+ } else if (st.isDirectory() && depth > 0) {
81
+ const sub = findLargestFile(full, depth - 1);
82
+ if (sub && (!best || sub.size > best.size)) best = sub;
83
+ }
84
+ }
85
+ return best;
86
+ }
87
+
88
+ /** Bounded Windows-class rename retry: EPERM/EBUSY/EACCES from AV/indexers clears in ms. */
89
+ function renameWithRetry(rename, from, to, attempts = 5) {
90
+ for (let attempt = 0; ; attempt += 1) {
91
+ try {
92
+ rename(from, to);
93
+ return;
94
+ } catch (error) {
95
+ const code = error?.code;
96
+ const retryable = code === "EPERM" || code === "EBUSY" || code === "EACCES";
97
+ if (!retryable || attempt >= attempts - 1) throw error;
98
+ // Synchronous bounded backoff (launcher context has no async loop here).
99
+ const until = Date.now() + 100 * (attempt + 1);
100
+ while (Date.now() < until) { /* spin briefly; total worst case ~1.5s */ }
101
+ }
102
+ }
103
+ }
104
+
105
+ function recoveryMarkerPath(scopeDir) {
106
+ return join(scopeDir, ".ocx-recovery.json");
107
+ }
108
+
109
+ /** Startup probe: restore a backup when the live tree is broken (D4 power-loss rows). */
110
+ export function bootRestoreProbe(packageDir, deps = {}) {
111
+ const rename = deps.rename ?? renameSync;
112
+ const scopeDir = dirname(packageDir);
113
+ let backups = [];
114
+ try {
115
+ backups = readdirSync(scopeDir).filter(name => name.startsWith(".ocx-backup-")).sort();
116
+ } catch {
117
+ return { action: "none" };
118
+ }
119
+ if (backups.length === 0) return { action: "none" };
120
+ const liveOk = existsSync(join(packageDir, "package.json"))
121
+ && verifyInstallTree(packageDir).ok;
122
+ const newestBackup = join(scopeDir, backups[backups.length - 1], "opencodex");
123
+ if (liveOk) {
124
+ // Live is healthy: the backups are leftovers from a completed swap. Reap them.
125
+ for (const name of backups) {
126
+ try { rmSync(join(scopeDir, name), { recursive: true, force: true }); } catch { /* keep */ }
127
+ }
128
+ try { rmSync(recoveryMarkerPath(scopeDir), { force: true }); } catch { /* keep */ }
129
+ return { action: "reaped", count: backups.length };
130
+ }
131
+ if (!existsSync(join(newestBackup, "package.json"))) return { action: "none" };
132
+ try {
133
+ try { rmSync(packageDir, { recursive: true, force: true }); } catch { /* may not exist */ }
134
+ rename(newestBackup, packageDir);
135
+ return { action: "restored", from: newestBackup };
136
+ } catch (error) {
137
+ return { action: "failed", error: error?.message ?? String(error) };
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Run the transactional update. `runNpm(args, opts)` is injected so the caller keeps
143
+ * its hardened npm resolution (npm-invocation.mjs) and logging.
144
+ */
145
+ export function transactionalNpmUpdate({
146
+ packageDir,
147
+ pkgName,
148
+ targetVersion,
149
+ tag,
150
+ runNpm,
151
+ log = () => {},
152
+ deps = {},
153
+ }) {
154
+ const rename = deps.rename ?? renameSync;
155
+ const scopeDir = dirname(packageDir);
156
+ const stageRoot = join(scopeDir, stampedName(".ocx-staging"));
157
+ // GLOBAL-style staging (-g --prefix): npm nests the package's dependencies INSIDE the
158
+ // package dir, exactly like the live global tree this stage will replace. A local-style
159
+ // install would hoist bun/zod to stageRoot/node_modules — siblings that the swap would
160
+ // leave behind, shipping a dependency-less live tree (release-audit blocker).
161
+ // Layout: <stageRoot>/lib/node_modules/<pkg> on POSIX, <stageRoot>/node_modules/<pkg>
162
+ // on Windows.
163
+ const stagedCandidates = [
164
+ join(stageRoot, "lib", "node_modules", ...pkgName.split("/")),
165
+ join(stageRoot, "node_modules", ...pkgName.split("/")),
166
+ ];
167
+
168
+ // D1: stage to the side. --prefix keeps npm entirely inside stageRoot; the live tree
169
+ // and the npm bin shims are untouched until the swap. A failure HERE (mkdir EACCES,
170
+ // ENOSPC) must NOT fall back to the destructive legacy install (review High 4): the
171
+ // caller sees a normal phase failure with live untouched.
172
+ try {
173
+ mkdirSync(stageRoot, { recursive: true });
174
+ } catch (error) {
175
+ return { ok: false, phase: "stage", error: "could not create staging directory: " + (error?.message ?? String(error)) };
176
+ }
177
+ const spec = pkgName + "@" + (targetVersion || tag);
178
+ log("Staging " + spec + " into " + stageRoot);
179
+ const install = runNpm(["install", "-g", "--prefix", stageRoot, "--no-audit", "--no-fund", spec]);
180
+ if (install.status !== 0) {
181
+ try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
182
+ return { ok: false, phase: "stage", error: "npm staging install failed (" + (install.status ?? "?") + ")" };
183
+ }
184
+ const stagedPackage = stagedCandidates.find(dir => existsSync(join(dir, "package.json")));
185
+ if (!stagedPackage) {
186
+ try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
187
+ return { ok: false, phase: "verify", error: "staged package directory not found under " + stageRoot };
188
+ }
189
+
190
+ // D2: verify INSIDE the stage. Live is still untouched on any failure here.
191
+ const staged = verifyInstallTree(stagedPackage, targetVersion || undefined);
192
+ if (!staged.ok) {
193
+ try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
194
+ return { ok: false, phase: "verify", error: "staged tree failed verification: " + staged.failures.join("; ") };
195
+ }
196
+
197
+ // D3: swap. live -> backup, stage -> live, re-verify live, rollback on failure.
198
+ const backupRoot = join(scopeDir, stampedName(".ocx-backup"));
199
+ const backupPackage = join(backupRoot, "opencodex");
200
+ try {
201
+ mkdirSync(backupRoot, { recursive: true });
202
+ } catch (error) {
203
+ try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
204
+ return { ok: false, phase: "swap-backup", error: "could not create backup directory: " + (error?.message ?? String(error)) };
205
+ }
206
+ try {
207
+ renameWithRetry(rename, packageDir, backupPackage);
208
+ } catch (error) {
209
+ try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
210
+ try { rmSync(backupRoot, { recursive: true, force: true }); } catch { /* best effort */ }
211
+ return { ok: false, phase: "swap-backup", error: "could not move live tree aside: " + (error?.message ?? String(error)) };
212
+ }
213
+ try {
214
+ renameWithRetry(rename, stagedPackage, packageDir);
215
+ } catch (error) {
216
+ // Rollback: reverse the first rename. Double fault leaves the recovery marker.
217
+ try {
218
+ renameWithRetry(rename, backupPackage, packageDir);
219
+ try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
220
+ try { rmSync(backupRoot, { recursive: true, force: true }); } catch { /* best effort */ }
221
+ return { ok: false, phase: "swap-live", rolledBack: true, error: "could not place staged tree: " + (error?.message ?? String(error)) };
222
+ } catch (rollbackError) {
223
+ writeFileSync(recoveryMarkerPath(scopeDir), JSON.stringify({
224
+ at: new Date().toISOString(),
225
+ backup: backupPackage,
226
+ live: packageDir,
227
+ restore: 'move "' + backupPackage + '" back to "' + packageDir + '"',
228
+ error: String(error?.message ?? error),
229
+ rollbackError: String(rollbackError?.message ?? rollbackError),
230
+ }, null, 2));
231
+ return { ok: false, phase: "double-fault", rolledBack: false, error: "swap and rollback both failed; recovery marker written at " + recoveryMarkerPath(scopeDir) };
232
+ }
233
+ }
234
+ const liveCheck = verifyInstallTree(packageDir, targetVersion || undefined);
235
+ if (!liveCheck.ok) {
236
+ try {
237
+ rmSync(packageDir, { recursive: true, force: true });
238
+ rename(backupPackage, packageDir);
239
+ try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
240
+ try { rmSync(backupRoot, { recursive: true, force: true }); } catch { /* best effort */ }
241
+ return { ok: false, phase: "post-verify", rolledBack: true, error: "live tree failed post-swap verification: " + liveCheck.failures.join("; ") };
242
+ } catch (rollbackError) {
243
+ writeFileSync(recoveryMarkerPath(scopeDir), JSON.stringify({
244
+ at: new Date().toISOString(),
245
+ backup: backupPackage,
246
+ live: packageDir,
247
+ restore: 'move "' + backupPackage + '" back to "' + packageDir + '"',
248
+ error: "post-swap verification failed: " + liveCheck.failures.join("; "),
249
+ rollbackError: String(rollbackError?.message ?? rollbackError),
250
+ }, null, 2));
251
+ return { ok: false, phase: "double-fault", rolledBack: false, error: "post-verify rollback failed; recovery marker written" };
252
+ }
253
+ }
254
+ // Success: stage scaffolding is disposable now; the backup stays until the next
255
+ // healthy boot reaps it (bootRestoreProbe) — the process that spawned this update may
256
+ // still hold the old cwd.
257
+ try { rmSync(stageRoot, { recursive: true, force: true }); } catch { /* best effort */ }
258
+ return { ok: true, phase: "done", backup: backupPackage };
259
+ }
package/src/usage/cost.ts CHANGED
Binary file