@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
@@ -13,7 +13,33 @@ import {
13
13
  } from "node:fs";
14
14
  import { dirname, join } from "node:path";
15
15
  import { adminApiTokenFilePath } from "../lib/admin-secrets";
16
- import { forgetHardenedSecretPath, hardenSecretDir, hardenSecretPath } from "../lib/windows-secret-acl";
16
+ import {
17
+ LOCAL_MANAGEMENT_CAPABILITY_HEADER,
18
+ LOCAL_MANAGEMENT_CAPABILITY_EXPIRES_AT_HEADER,
19
+ LOCAL_MANAGEMENT_EXPECTED_PID_HEADER,
20
+ LOCAL_MANAGEMENT_NONCE_HEADER,
21
+ parseExpectedLocalManagementPid,
22
+ verifyLocalManagementReadCapability,
23
+ } from "../lib/local-management-capability";
24
+ import {
25
+ SYSTEM_RESTART_CAPABILITY_HEADER,
26
+ SYSTEM_RESTART_EXPECTED_PID_HEADER,
27
+ SYSTEM_RESTART_NONCE_HEADER,
28
+ SYSTEM_RESTART_PATH,
29
+ parseExpectedSystemRestartPid,
30
+ verifySystemRestartCapability,
31
+ } from "../lib/system-restart-contract";
32
+ import {
33
+ LOCAL_PROVIDER_RELOAD_CAPABILITY_HEADER,
34
+ LOCAL_PROVIDER_RELOAD_EXPECTED_PID_HEADER,
35
+ LOCAL_PROVIDER_RELOAD_EXPIRES_AT_HEADER,
36
+ LOCAL_PROVIDER_RELOAD_NAME_HEADER,
37
+ LOCAL_PROVIDER_RELOAD_NONCE_HEADER,
38
+ LOCAL_PROVIDER_RELOAD_PATH,
39
+ parseExpectedLocalProviderReloadPid,
40
+ verifyLocalProviderReloadCapability,
41
+ } from "../lib/local-provider-reload-contract";
42
+ import { forgetEphemeralSecretPath, forgetHardenedSecretPath, hardenSecretDir, hardenSecretPath } from "../lib/windows-secret-acl";
17
43
  import type { OcxConfig } from "../types";
18
44
  import {
19
45
  isAllowedManagementOrigin,
@@ -26,6 +52,12 @@ import {
26
52
 
27
53
  const GUI_SESSION_TTL_MS = 5 * 60_000;
28
54
  const GUI_SESSION_LIMIT = 128;
55
+ const LOCAL_READ_REPLAY_LIMIT = 256;
56
+ const consumedLocalReadCapabilities = new Map<string, number>();
57
+ const admittedLocalReadRequests = new WeakSet<Request>();
58
+ const LOCAL_PROVIDER_RELOAD_REPLAY_LIMIT = 256;
59
+ const consumedLocalProviderReloadCapabilities = new Map<string, number>();
60
+ const admittedLocalProviderReloadRequests = new WeakSet<Request>();
29
61
 
30
62
  interface GuiSessionRecord {
31
63
  csrfToken: string;
@@ -95,12 +127,17 @@ function readExistingToken(path: string): string {
95
127
  export function removeManagementTokenPathBestEffort(
96
128
  path: string,
97
129
  remove: (path: string) => void = unlinkSync,
130
+ options?: { ephemeral?: boolean },
98
131
  ): void {
132
+ // Temps get the full ephemeral release (success + both timeout namespaces);
133
+ // stable token paths drop only the success memo — destination-keyed timeout
134
+ // memos are intentional anti-restall state.
135
+ const forget = options?.ephemeral ? forgetEphemeralSecretPath : forgetHardenedSecretPath;
99
136
  try {
100
137
  remove(path);
101
- forgetHardenedSecretPath(path);
138
+ forget(path);
102
139
  } catch (error) {
103
- if ((error as NodeJS.ErrnoException).code === "ENOENT") forgetHardenedSecretPath(path);
140
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") forget(path);
104
141
  /* other failures retain fail-closed state for the caller */
105
142
  }
106
143
  }
@@ -120,7 +157,8 @@ function createTokenFile(path: string): string {
120
157
  chmodSync(temporary, 0o600);
121
158
  let temporaryHardened: { ok: boolean };
122
159
  try {
123
- temporaryHardened = hardenSecretPath(temporary, { required: true });
160
+ // Destination-keyed timeout memo (the final token path), not the temp.
161
+ temporaryHardened = hardenSecretPath(temporary, { required: true, timeoutMemoKey: path });
124
162
  } catch {
125
163
  temporaryHardened = { ok: false };
126
164
  }
@@ -155,7 +193,7 @@ function createTokenFile(path: string): string {
155
193
  if (fd !== null) {
156
194
  try { closeSync(fd); } catch { /* best effort */ }
157
195
  }
158
- removeManagementTokenPathBestEffort(temporary);
196
+ removeManagementTokenPathBestEffort(temporary, unlinkSync, { ephemeral: true });
159
197
  }
160
198
  }
161
199
 
@@ -231,11 +269,188 @@ export function issueGuiSession(
231
269
  return { token, ...session };
232
270
  }
233
271
 
272
+ /**
273
+ * Which credential actually authorized a management request.
274
+ *
275
+ * `admin-token` is the raw token from disk/env: anything running as the user can
276
+ * read it, including a coding agent. `gui-session` is a session token this process
277
+ * minted for a browser, and it only authorizes a mutation after the origin and the
278
+ * per-session CSRF token match. Consent-bearing routes must key off this value
279
+ * rather than off request headers, which the token holder can forge freely.
280
+ * The capability principals are process-scoped HMACs bound to the current process
281
+ * PID and listening port. Local reads are accepted only for two exact GET paths;
282
+ * restart and provider reload remain separate wire contracts for their exact POSTs.
283
+ */
284
+ export type ManagementPrincipal =
285
+ | "admin-token"
286
+ | "gui-session"
287
+ | "local-read-capability"
288
+ | "local-provider-reload-capability"
289
+ | "system-restart-capability";
290
+
291
+ export interface LocalManagementAuthContext {
292
+ attestationSecret: string;
293
+ pid: number;
294
+ port: number;
295
+ }
296
+
297
+ function hasSystemRestartCapability(
298
+ req: Request,
299
+ local: LocalManagementAuthContext | undefined,
300
+ ): boolean {
301
+ if (!local || req.method !== "POST") return false;
302
+ let path: string;
303
+ try {
304
+ path = new URL(req.url).pathname;
305
+ } catch {
306
+ return false;
307
+ }
308
+ if (path !== SYSTEM_RESTART_PATH) return false;
309
+ const expectedPid = parseExpectedSystemRestartPid(
310
+ req.headers.get(SYSTEM_RESTART_EXPECTED_PID_HEADER),
311
+ );
312
+ if (expectedPid.kind !== "present" || expectedPid.pid !== local.pid) return false;
313
+ return verifySystemRestartCapability(
314
+ local.attestationSecret,
315
+ req.headers.get(SYSTEM_RESTART_NONCE_HEADER),
316
+ req.method,
317
+ path,
318
+ local.pid,
319
+ local.port,
320
+ req.headers.get(SYSTEM_RESTART_CAPABILITY_HEADER),
321
+ );
322
+ }
323
+
324
+ function hasLocalReadCapability(
325
+ req: Request,
326
+ local: LocalManagementAuthContext | undefined,
327
+ ): boolean {
328
+ // requireManagementAuth and managementPrincipal inspect the same Request in
329
+ // sequence. Preserve that one admission without accepting a replayed request.
330
+ if (admittedLocalReadRequests.has(req)) return true;
331
+ if (!local || req.method !== "GET") return false;
332
+ let url: URL;
333
+ try {
334
+ url = new URL(req.url);
335
+ } catch {
336
+ return false;
337
+ }
338
+ // Do not let a future query-bearing variant silently inherit this narrow grant.
339
+ if (url.search !== "") return false;
340
+ const expectedPid = parseExpectedLocalManagementPid(
341
+ req.headers.get(LOCAL_MANAGEMENT_EXPECTED_PID_HEADER),
342
+ );
343
+ if (expectedPid.kind !== "present" || expectedPid.pid !== local.pid) return false;
344
+ const expiresAtRaw = req.headers.get(LOCAL_MANAGEMENT_CAPABILITY_EXPIRES_AT_HEADER);
345
+ if (!expiresAtRaw || !/^[1-9]\d*$/.test(expiresAtRaw)) return false;
346
+ const expiresAt = Number(expiresAtRaw);
347
+ if (!Number.isSafeInteger(expiresAt)) return false;
348
+ const capability = req.headers.get(LOCAL_MANAGEMENT_CAPABILITY_HEADER);
349
+ const now = Date.now();
350
+ if (!verifyLocalManagementReadCapability(
351
+ local.attestationSecret,
352
+ req.headers.get(LOCAL_MANAGEMENT_NONCE_HEADER),
353
+ req.method,
354
+ url.pathname,
355
+ local.pid,
356
+ local.port,
357
+ expiresAt,
358
+ capability,
359
+ now,
360
+ )) return false;
361
+ for (const [consumed, retainedUntil] of consumedLocalReadCapabilities) {
362
+ if (retainedUntil <= now) consumedLocalReadCapabilities.delete(consumed);
363
+ }
364
+ if (!capability || consumedLocalReadCapabilities.has(capability)) return false;
365
+ if (consumedLocalReadCapabilities.size >= LOCAL_READ_REPLAY_LIMIT) return false;
366
+ consumedLocalReadCapabilities.set(capability, expiresAt);
367
+ admittedLocalReadRequests.add(req);
368
+ return true;
369
+ }
370
+
371
+ function hasLocalProviderReloadCapability(
372
+ req: Request,
373
+ local: LocalManagementAuthContext | undefined,
374
+ ): boolean {
375
+ if (admittedLocalProviderReloadRequests.has(req)) return true;
376
+ if (!local || req.method !== "POST") return false;
377
+ let url: URL;
378
+ try {
379
+ url = new URL(req.url);
380
+ } catch {
381
+ return false;
382
+ }
383
+ if (url.pathname !== LOCAL_PROVIDER_RELOAD_PATH || url.search !== "") return false;
384
+ const contentLength = req.headers.get("content-length");
385
+ if (contentLength !== "0" || req.headers.has("transfer-encoding")) return false;
386
+ const expectedPid = parseExpectedLocalProviderReloadPid(
387
+ req.headers.get(LOCAL_PROVIDER_RELOAD_EXPECTED_PID_HEADER),
388
+ );
389
+ if (expectedPid.kind !== "present" || expectedPid.pid !== local.pid) return false;
390
+ const expiresAtRaw = req.headers.get(LOCAL_PROVIDER_RELOAD_EXPIRES_AT_HEADER);
391
+ if (!expiresAtRaw || !/^[1-9]\d*$/.test(expiresAtRaw)) return false;
392
+ const expiresAt = Number(expiresAtRaw);
393
+ if (!Number.isSafeInteger(expiresAt)) return false;
394
+ const name = req.headers.get(LOCAL_PROVIDER_RELOAD_NAME_HEADER);
395
+ const capability = req.headers.get(LOCAL_PROVIDER_RELOAD_CAPABILITY_HEADER);
396
+ const now = Date.now();
397
+ if (!verifyLocalProviderReloadCapability(
398
+ local.attestationSecret,
399
+ req.headers.get(LOCAL_PROVIDER_RELOAD_NONCE_HEADER),
400
+ req.method,
401
+ url.pathname,
402
+ name,
403
+ local.pid,
404
+ local.port,
405
+ expiresAt,
406
+ capability,
407
+ now,
408
+ )) return false;
409
+ for (const [consumed, retainedUntil] of consumedLocalProviderReloadCapabilities) {
410
+ if (retainedUntil <= now) consumedLocalProviderReloadCapabilities.delete(consumed);
411
+ }
412
+ if (!capability || consumedLocalProviderReloadCapabilities.has(capability)) return false;
413
+ if (consumedLocalProviderReloadCapabilities.size >= LOCAL_PROVIDER_RELOAD_REPLAY_LIMIT) return false;
414
+ consumedLocalProviderReloadCapabilities.set(capability, expiresAt);
415
+ admittedLocalProviderReloadRequests.add(req);
416
+ return true;
417
+ }
418
+
419
+ /**
420
+ * The principal for a request that already passed `requireManagementAuth`. Kept as a
421
+ * separate resolution (rather than a changed return type) so every existing caller
422
+ * keeps its `Response | null` contract. Browser and admin principals are derived
423
+ * from the same session table and CSRF comparison the gate uses; the restart
424
+ * principal is derived from the same process-scoped capability check.
425
+ */
426
+ export function managementPrincipal(
427
+ req: Request,
428
+ state: ManagementAuthState,
429
+ config?: OcxConfig,
430
+ local?: LocalManagementAuthContext,
431
+ ): ManagementPrincipal | null {
432
+ if (hasSystemRestartCapability(req, local)) return "system-restart-capability";
433
+ if (hasLocalProviderReloadCapability(req, local)) return "local-provider-reload-capability";
434
+ if (hasLocalReadCapability(req, local)) return "local-read-capability";
435
+ if (!state.available) return null;
436
+ const actual = req.headers.get("x-opencodex-api-key")?.trim()
437
+ || req.headers.get("authorization")?.replace(/^Bearer\s+/i, "").trim();
438
+ if (!actual) return null;
439
+ if (equalSecret(actual, state.token)) return "admin-token";
440
+ if (!config) return null;
441
+ removeExpiredSessions(state);
442
+ return state.sessions.has(actual) ? "gui-session" : null;
443
+ }
444
+
234
445
  export function requireManagementAuth(
235
446
  req: Request,
236
447
  state: ManagementAuthState,
237
448
  config?: OcxConfig,
449
+ local?: LocalManagementAuthContext,
238
450
  ): Response | null {
451
+ if (hasSystemRestartCapability(req, local)) return null;
452
+ if (hasLocalProviderReloadCapability(req, local)) return null;
453
+ if (hasLocalReadCapability(req, local)) return null;
239
454
  if (!state.available) {
240
455
  return Response.json({
241
456
  error: "management API unavailable",
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Optional per-attempt route-identity linker.
3
+ *
4
+ * Compatibility Lab attaches an opaque route-subject digest to request attempts so its
5
+ * passive-production surface (CL-09) can correlate them later. That is an opt-in
6
+ * subsystem, so the core request path holds only a slot: null on installs that never
7
+ * activate Lab, which is every install without a routing profile.
8
+ *
9
+ * Contract for any registered implementation: synchronous, free of side effects with
10
+ * respect to the request, and non-throwing. The upstream request must never be delayed,
11
+ * retried, or altered by identity linkage. The try/catch lives here rather than at the
12
+ * call site so the guarantee belongs to the mechanism instead of being restated by every
13
+ * caller.
14
+ *
15
+ * See devlog/_fin/260814_lab_core_decoupling/020_request_path_gate.md
16
+ */
17
+ import type { OcxConfig, OcxProviderConfig } from "../types";
18
+ import type { InboundWire } from "../providers/registry";
19
+
20
+ export type PassiveRouteLinker = (
21
+ config: OcxConfig,
22
+ providerName: string,
23
+ modelId: string,
24
+ routed: OcxProviderConfig,
25
+ inboundWire: InboundWire,
26
+ ) => string | null;
27
+
28
+ let linker: PassiveRouteLinker | null = null;
29
+
30
+ /** Install the linker. Returns a detach function. */
31
+ export function setPassiveRouteLinker(next: PassiveRouteLinker): () => void {
32
+ linker = next;
33
+ return () => {
34
+ // Only detach our own registration: a later activation may have replaced it.
35
+ if (linker === next) linker = null;
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Resolve the attempt identity, or null when no subsystem is active.
41
+ * Never throws: linkage is best-effort metadata and must not affect the request.
42
+ */
43
+ export function resolvePassiveRouteSubjectId(
44
+ config: OcxConfig,
45
+ providerName: string,
46
+ modelId: string,
47
+ routed: OcxProviderConfig,
48
+ inboundWire: InboundWire,
49
+ ): string | null {
50
+ if (!linker) return null;
51
+ try {
52
+ return linker(config, providerName, modelId, routed, inboundWire);
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+
58
+ /** True when an optional subsystem has installed a linker. Test/diagnostic use. */
59
+ export function hasPassiveRouteLinker(): boolean {
60
+ return linker !== null;
61
+ }
62
+
63
+ /** Test-only reset. */
64
+ export function resetPassiveRouteLinkerForTests(): void {
65
+ linker = null;
66
+ }
@@ -57,6 +57,16 @@ export type FindAvailablePortOptions = {
57
57
  * update restart cannot hop to a random ephemeral listener (PR #152 gap).
58
58
  */
59
59
  allowEphemeralFallback?: boolean;
60
+ /**
61
+ * A port this selection must never return, even when it is free (#1102).
62
+ *
63
+ * The unauthenticated loopback listener binds a fixed port from config. If the public
64
+ * listener took that port first — via an explicit `--port`, a `config.port` of 0, or the
65
+ * ephemeral fallback happening to land on it — the loopback bind would then fail with
66
+ * EADDRINUSE, and the startup transaction would roll back a public listener that had
67
+ * nothing wrong with it. Excluding the port here fails the right thing at the right time.
68
+ */
69
+ reservedPort?: number;
60
70
  };
61
71
 
62
72
  export class PortUnavailableError extends Error {
@@ -75,6 +85,12 @@ export async function findAvailablePort(
75
85
  ): Promise<number> {
76
86
  const preferRetryMs = opts.preferRetryMs ?? 0;
77
87
  const allowEphemeral = opts.allowEphemeralFallback !== false;
88
+ const reserved = opts.reservedPort;
89
+ // An explicit preference for the reserved port is a configuration mistake, not a busy
90
+ // socket: retrying or hopping would hide it. Refuse before probing anything.
91
+ if (reserved !== undefined && preferredPort === reserved) {
92
+ throw new PortUnavailableError(preferredPort, hostname);
93
+ }
78
94
  // Port 0 asks the OS to select an ephemeral port. Resolve it to that concrete
79
95
  // port here so callers never persist or advertise an unusable `:0` endpoint.
80
96
  if (preferredPort > 0 && preferRetryMs > 0) {
@@ -92,7 +108,31 @@ export async function findAvailablePort(
92
108
  throw new PortUnavailableError(preferredPort, hostname);
93
109
  }
94
110
 
95
- return await new Promise((resolve, reject) => {
111
+ // Bounded, not recursive. The OS can hand back the reserved port, and a redraw practically
112
+ // always differs — but "practically always" is not a termination argument, and an unbounded
113
+ // async recursion has no way to stop if the assumption is ever wrong.
114
+ for (let attempt = 0; attempt < EPHEMERAL_REDRAW_LIMIT; attempt += 1) {
115
+ const port = await allocateEphemeralPort(hostname);
116
+ if (port !== reserved) return port;
117
+ }
118
+ throw new Error("failed to allocate an available port");
119
+ }
120
+
121
+ /** How many times an ephemeral draw may come back reserved before we give up. */
122
+ const EPHEMERAL_REDRAW_LIMIT = 8;
123
+
124
+ /** Test seam: replace the OS ephemeral allocator so the redraw path is reachable. */
125
+ let ephemeralAllocator: ((hostname: string) => Promise<number>) | null = null;
126
+
127
+ export function setEphemeralPortAllocatorForTests(
128
+ allocator: ((hostname: string) => Promise<number>) | null,
129
+ ): void {
130
+ ephemeralAllocator = allocator;
131
+ }
132
+
133
+ async function allocateEphemeralPort(hostname: string): Promise<number> {
134
+ if (ephemeralAllocator) return ephemeralAllocator(hostname);
135
+ return await new Promise<number>((resolve, reject) => {
96
136
  const server = createServer();
97
137
  server.once("error", reject);
98
138
  server.once("listening", () => {
@@ -10,6 +10,7 @@
10
10
  * Lives outside cli.ts (which dispatches argv at module top level) so tests can import it.
11
11
  */
12
12
  import { loadConfig, readAlivePid, readRuntimePort, verifyPidIdentity } from "../config";
13
+ import { directLocalHttpFetch } from "./direct-local-http";
13
14
 
14
15
  export interface HealthzIdentity {
15
16
  service?: unknown;
@@ -17,6 +18,9 @@ export interface HealthzIdentity {
17
18
  version?: unknown;
18
19
  uptime?: unknown;
19
20
  pid?: unknown;
21
+ port?: unknown;
22
+ restartCapability?: unknown;
23
+ providerReloadCapability?: unknown;
20
24
  }
21
25
 
22
26
  export interface LivenessIo {
@@ -47,6 +51,9 @@ export interface LivenessIo {
47
51
  nowFn?: () => number;
48
52
  }
49
53
 
54
+ /** Default per-probe fetch ceiling shared by liveness and readiness probes. */
55
+ export const DEFAULT_PROBE_TIMEOUT_MS = 750;
56
+
50
57
  /** Default probe options for service stop / orphan cleanup — a just-bound proxy can miss a single 750ms probe. */
51
58
  export const SERVICE_STOP_LIVENESS: Pick<LivenessIo, "timeoutMs" | "attempts"> = {
52
59
  timeoutMs: 1500,
@@ -92,10 +99,10 @@ export async function proxyIdentityAt(
92
99
  opts: { hostname?: string; expectedPid?: number } = {},
93
100
  io: LivenessIo = {},
94
101
  ): Promise<{ pid: number | null } | null> {
95
- const fetchFn = io.fetchFn ?? fetch;
102
+ const fetchFn = io.fetchFn ?? directLocalHttpFetch;
96
103
  const sleepFn = io.sleepFn ?? ((ms: number) => new Promise<void>(r => setTimeout(r, ms)));
97
104
  const nowFn = io.nowFn ?? Date.now;
98
- const baseTimeoutMs = io.timeoutMs ?? 750;
105
+ const baseTimeoutMs = io.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;
99
106
  const requestedAttempts = Math.trunc(io.attempts ?? 1);
100
107
  const attempts = Number.isNaN(requestedAttempts)
101
108
  ? 1
@@ -138,6 +145,11 @@ export async function findLiveProxy(io: LivenessIo = {}): Promise<LiveProxy | nu
138
145
  const verifyPidFn = io.verifyPidFn ?? verifyPidIdentity;
139
146
  const readRuntimeFn = io.readRuntimeFn ?? readRuntimePort;
140
147
  const configFn = io.configFn ?? loadConfig;
148
+ const nowFn = io.nowFn ?? Date.now;
149
+ const deadlineAt = io.deadlineAt;
150
+ const probeIo: LivenessIo = io;
151
+ const budgetExhausted = (): boolean =>
152
+ deadlineAt !== undefined && nowFn() >= deadlineAt;
141
153
 
142
154
  // The cheap pid is discovery-only. Before it can appear in a returned (killable) result
143
155
  // it must pass the full identity check AND the verifier must echo the exact candidate —
@@ -160,8 +172,9 @@ export async function findLiveProxy(io: LivenessIo = {}): Promise<LiveProxy | nu
160
172
  if (pid) {
161
173
  const runtime = readRuntimeFn(pid);
162
174
  if (runtime?.port) {
175
+ if (budgetExhausted()) return null;
163
176
  probedPort = runtime.port;
164
- const identity = await proxyIdentityAt(runtime.port, { hostname: runtime.hostname, expectedPid: pid }, io);
177
+ const identity = await proxyIdentityAt(runtime.port, { hostname: runtime.hostname, expectedPid: pid }, probeIo);
165
178
  if (identity) {
166
179
  // healthz confirmed the pid itself → trusted; a pidless legacy body did not,
167
180
  // so the cheap pid must pass full identity verification before it is returned.
@@ -176,8 +189,9 @@ export async function findLiveProxy(io: LivenessIo = {}): Promise<LiveProxy | nu
176
189
  // identity-probe it so ensure/update/stop see the live proxy instead of shadowing it.
177
190
  const record = readRuntimeFn();
178
191
  if (record?.port && record.port !== probedPort) {
192
+ if (budgetExhausted()) return null;
179
193
  const expectedPid = typeof record.pid === "number" ? record.pid : undefined;
180
- const identity = await proxyIdentityAt(record.port, { hostname: record.hostname, expectedPid }, io);
194
+ const identity = await proxyIdentityAt(record.port, { hostname: record.hostname, expectedPid }, probeIo);
181
195
  // Only the healthz-reported pid is authoritative here. The record's pid may be stale
182
196
  // (its process dead, the port reused by a pidless legacy proxy) — synthesizing it
183
197
  // would hand destructive callers (stopProxy → kill fallback) a reusable pid.
@@ -188,7 +202,8 @@ export async function findLiveProxy(io: LivenessIo = {}): Promise<LiveProxy | nu
188
202
 
189
203
  const config = configFn();
190
204
  const port = config.port ?? 10100;
191
- const identity = await proxyIdentityAt(port, { hostname: config.hostname }, io);
205
+ if (budgetExhausted()) return null;
206
+ const identity = await proxyIdentityAt(port, { hostname: config.hostname }, probeIo);
192
207
  if (identity) {
193
208
  return {
194
209
  pid: verifiedReportedPid(identity.pid) ?? killablePid(pid),
@@ -199,3 +214,115 @@ export async function findLiveProxy(io: LivenessIo = {}): Promise<LiveProxy | nu
199
214
  }
200
215
  return null;
201
216
  }
217
+
218
+ // ─────────────────────────────────────────────────────────────────────────────
219
+ // Readiness (/readyz) strict probe.
220
+ //
221
+ // Liveness (/healthz) confirms the process answers; readiness confirms the
222
+ // post-startup Codex sync has settled. A readiness probe is identity-checked the
223
+ // same way liveness is, AND additionally enforces the full /readyz contract so
224
+ // an adversarial or malformed body can never count as ready:
225
+ //
226
+ // - HTTP 200 is required for status="ready"; HTTP 503 is required for pending
227
+ // or failed. Any other HTTP/body-status pairing is an invalid contract.
228
+ // - body.service must be exactly "opencodex".
229
+ // - body.version must be a non-empty string.
230
+ // - body.uptime must be a finite nonnegative number.
231
+ // - body.pid must be a positive integer; when `expectedPid` is supplied it must
232
+ // match exactly.
233
+ // - body.port must be an integer in 1..65535 and equal the probed port.
234
+ // - body.status must be exactly one of pending|ready|failed.
235
+ //
236
+ // Any unreachable, foreign, legacy, malformed, mismatched, or self-inconsistent
237
+ // response returns `null` so callers can never treat an invalid identity/contract
238
+ // as ready.
239
+ // ─────────────────────────────────────────────────────────────────────────────
240
+
241
+ interface ReadyzBody {
242
+ service?: unknown;
243
+ version?: unknown;
244
+ uptime?: unknown;
245
+ pid?: unknown;
246
+ port?: unknown;
247
+ status?: unknown;
248
+ }
249
+
250
+ export interface ReadinessProbeResult {
251
+ /** True ONLY for a valid 200 + status="ready" body with a matching pid. */
252
+ ready: boolean;
253
+ /** Fixed sanitized status. A foreign/unreadable body yields a `null` RESULT, never a `null` status. */
254
+ status: "ready" | "pending" | "failed";
255
+ /** Positive integer pid from a valid body. */
256
+ pid: number;
257
+ /** Integer port from a valid body. */
258
+ port: number;
259
+ }
260
+
261
+ export interface ReadinessProbeIo {
262
+ fetchFn?: typeof fetch;
263
+ timeoutMs?: number;
264
+ }
265
+
266
+ const READYZ_STATUS_VALUES = new Set<"ready" | "pending" | "failed">(["ready", "pending", "failed"]);
267
+
268
+ /**
269
+ * Validate a parsed /readyz body against the strict contract. Returns the
270
+ * sanitized probe result, or `null` when the body is foreign, legacy,
271
+ * malformed, or fails the pid/port checks. Pure (no I/O) so it is fully
272
+ * deterministic and unit-testable.
273
+ */
274
+ export function validateReadyzBody(
275
+ body: unknown,
276
+ port: number,
277
+ opts: { expectedPid?: number } = {},
278
+ ): ReadinessProbeResult | null {
279
+ if (!body || typeof body !== "object") return null;
280
+ const b = body as ReadyzBody;
281
+ if (b.service !== "opencodex") return null;
282
+ if (typeof b.version !== "string" || b.version.length === 0) return null;
283
+ if (typeof b.uptime !== "number" || !Number.isFinite(b.uptime) || b.uptime < 0) return null;
284
+ if (typeof b.pid !== "number" || !Number.isInteger(b.pid) || b.pid <= 0) return null;
285
+ if (
286
+ typeof b.port !== "number"
287
+ || !Number.isInteger(b.port)
288
+ || b.port < 1
289
+ || b.port > 65535
290
+ || b.port !== port
291
+ ) return null;
292
+ if (typeof b.status !== "string" || !READYZ_STATUS_VALUES.has(b.status as "ready" | "pending" | "failed")) return null;
293
+ const status = b.status as "ready" | "pending" | "failed";
294
+ if (opts.expectedPid !== undefined && b.pid !== opts.expectedPid) return null;
295
+ return { ready: status === "ready", status, pid: b.pid, port: b.port };
296
+ }
297
+
298
+ /**
299
+ * Identity- and contract-checked /readyz probe. Returns `null` when the
300
+ * endpoint is unreachable or the body fails the strict contract (foreign 200,
301
+ * legacy health-only body, non-JSON, missing/malformed/mismatched fields,
302
+ * wrong port/pid, or an HTTP/body-status inconsistency). Returns
303
+ * `{ready:false, ...}` when the body is ours but pending or failed. Returns
304
+ * `{ready:true, ...}` ONLY for a valid 200 body with `status:"ready"` and (when
305
+ * requested) a matching pid.
306
+ */
307
+ export async function probeReadiness(
308
+ port: number,
309
+ opts: { hostname?: string; expectedPid?: number } = {},
310
+ io: ReadinessProbeIo = {},
311
+ ): Promise<ReadinessProbeResult | null> {
312
+ const fetchFn = io.fetchFn ?? directLocalHttpFetch;
313
+ try {
314
+ const res = await fetchFn(`http://${probeHostname(opts.hostname)}:${port}/readyz`, {
315
+ signal: AbortSignal.timeout(io.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS),
316
+ });
317
+ // Parse even on 503: /readyz returns JSON with a sanitized status while pending.
318
+ const body = (await res.json().catch(() => null)) as unknown;
319
+ const parsed = validateReadyzBody(body, port, opts);
320
+ if (!parsed) return null;
321
+ // HTTP/body-status consistency: ready requires 200; pending/failed require 503.
322
+ if (parsed.status === "ready" && res.status !== 200) return null;
323
+ if (parsed.status !== "ready" && res.status !== 503) return null;
324
+ return parsed;
325
+ } catch {
326
+ return null;
327
+ }
328
+ }