@iislee/opencodex 2.11.0 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
package/src/lib/redact.ts CHANGED
@@ -1,9 +1,414 @@
1
1
  export const REDACTED_SECRET = "[REDACTED]";
2
2
 
3
- const SENSITIVE_KEY_PATTERN = /^(?:authorization|proxy-authorization|cookie|set-cookie|set-cookie2|api[-_]?key|x-api-key|x-goog-api-key|x-amz-security-token|access[-_]?token|refresh[-_]?token|id[-_]?token|token|secret|client[-_]?secret|password|profile[-_]?arn)$/i;
3
+ const SENSITIVE_KEY_PATTERN = /^(?:authorization|proxy-authorization|cookie|set-cookie|set-cookie2|api[-_]?key|x-api-key|x-goog-api-key|x-amz-security-token|access[-_]?token|refresh[-_]?token|id[-_]?token|token|secret|client[-_]?secret|password|profile[-_]?arn|exa[-_]?api[-_]?key)$/i;
4
+
5
+ /**
6
+ * Colon-labelled credential headers echoed back inside an error body
7
+ * (`x-api-key: <value>`), which the `key=value` rules never match.
8
+ *
9
+ * This is one pass with an explicit decision rather than a stack of regexes
10
+ * that have to reason about each other's output. Three earlier attempts failed
11
+ * exactly there: exempting `Bearer` let anything the Bearer rule could not
12
+ * parse escape both rules; trusting the public `[REDACTED]` marker let a
13
+ * suffix ride along behind it; and splitting into two ordered patterns had the
14
+ * second eat the first one's result.
15
+ *
16
+ * The rule: the value after the label is a credential and gets masked to
17
+ * end-of-line. There is no "keep the readable part" exception, because every
18
+ * round of review found another way to hide a credential inside whatever the
19
+ * previous round chose to preserve — a second label, a repeated `Bearer`
20
+ * scheme, a third token two levels deep. Preserving attacker-controlled text
21
+ * next to a credential is the bug; the scheme word is not worth it.
22
+ *
23
+ * `Bearer` survives only as a fixed prefix on `authorization` /
24
+ * `proxy-authorization`, where it says which scheme failed and carries nothing
25
+ * from the input. `[REDACTED]` is a PUBLIC string an upstream can emit too, so
26
+ * its presence never grants trust.
27
+ *
28
+ * The label boundary is matched over a NORMALIZED VIEW: colon confusables and
29
+ * invisible format characters are folded for matching only, with offsets mapped
30
+ * back so unrelated text keeps its original bytes. Folding the string itself
31
+ * rewrote innocent diagnostics (`ratio∶1` became `ratio:1`).
32
+ */
33
+ // Every letter position also accepts \u0001, the placeholder the fold emits for
34
+ // an unresolved HTML named reference: `author&ii;zation` is the label with one
35
+ // character we cannot name, and that is still the label.
36
+ const CREDENTIAL_HEADER_LABEL_RAW = "x-api-key|x-goog-api-key|x-amz-security-token|api[_-]?key|apiKey|exa[_-]?api[_-]?key|exaApiKey|access[_-]?token|accessToken|refresh[_-]?token|refreshToken|id[_-]?token|client[_-]?secret|clientSecret|authorization|proxy-authorization|cookie|set-cookie|password|secret|token";
37
+
38
+ const CREDENTIAL_HEADER_LABEL = CREDENTIAL_HEADER_LABEL_RAW
39
+ .replace(/(?<![\[\\])([A-Za-z])(?![\]\-])/g, "[$1\u0001]");
40
+
41
+ /**
42
+ * Characters that render as a colon separator. Folded to `:` in the matching
43
+ * view so a look-alike cannot hide a header from the label pattern.
44
+ */
45
+ const COLON_CONFUSABLES = new Set([
46
+ "\uFF1A", "\uFE55", "\uFE13", "\uA789", "\u02D0", "\u2236",
47
+ "\u205A", "\u0589", "\u1361", "\u16EC", "\u1803", "\u2982", "\u2AF6", "\uFE30",
48
+ ]);
49
+
50
+ /**
51
+ * Characters dropped from the matching view: anything with no visible width
52
+ * that could split a label into pieces the pattern no longer recognizes.
53
+ * `\p{Default_Ignorable_Code_Point}` is the systematic answer — it covers the
54
+ * zero-width set, the bidi isolates and marks, the Mongolian vowel separator,
55
+ * and the variation selectors in one property instead of a list that review
56
+ * keeps finding another member of. `\p{Cf}` and combining marks are folded too.
57
+ */
58
+ const INVISIBLE_FORMAT = /[\p{Default_Ignorable_Code_Point}\p{Cf}\p{Mn}\p{Me}]/u;
59
+
60
+ /**
61
+ * HTML named character references.
62
+ *
63
+ * A hand-picked list is a coverage promise nobody can keep — review found
64
+ * `&ii;`, `&ee;`, and `&DifferentialD;` decoding to compatibility letters that
65
+ * NFKD already maps onto `i`, `e`, and `d`, and the WHATWG table holds roughly
66
+ * 2200 entries. Neither Bun nor Node exposes that table, and pulling in a
67
+ * dependency to spell a header name is the wrong trade for this path.
68
+ *
69
+ * So names are not resolved at all. A named reference sitting inside a
70
+ * credential label is folded to a single placeholder character of unknown
71
+ * identity, and the label alternation accepts that placeholder wherever a
72
+ * letter may appear. Every named entity is covered, present and future,
73
+ * without claiming to know what any of them mean.
74
+ */
75
+ const NAMED_ENTITY_PLACEHOLDER = "\u0001";
76
+
77
+ /**
78
+ * The handful of named references that spell a SEPARATOR rather than a letter.
79
+ * These have to resolve exactly, because the placeholder stands in for a letter
80
+ * position and a separator is structure, not a character of the name.
81
+ */
82
+ const SEPARATOR_ENTITIES = new Map<string, string>([
83
+ ["colon", ":"], ["semi", ";"], ["equals", "="], ["quot", '"'], ["apos", "'"],
84
+ ["lt", "<"], ["gt", ">"], ["amp", "&"], ["sol", "/"], ["lowbar", "_"],
85
+ ["hyphen", "-"], ["dash", "-"], ["ndash", "-"], ["mdash", "-"], ["minus", "-"],
86
+ ["period", "."], ["comma", ","], ["num", "#"], ["nbsp", " "],
87
+ ]);
88
+
89
+ /**
90
+ * Latin look-alikes for the ASCII letters that appear in credential labels.
91
+ * Cyrillic `а`/`е`, Greek `ο`, fullwidth forms and the mathematical alphabets
92
+ * all render as the label to a human, so the matching view folds them back.
93
+ * NFKD handles the width/font variants; this table covers the cross-script
94
+ * homoglyphs NFKD deliberately leaves alone.
95
+ */
96
+ const LETTER_CONFUSABLES = new Map<string, string>([
97
+ // Cyrillic
98
+ ["\u0430", "a"], ["\u0435", "e"], ["\u043E", "o"], ["\u0440", "p"], ["\u0441", "c"],
99
+ ["\u0445", "x"], ["\u0443", "y"], ["\u04BB", "h"], ["\u0455", "s"], ["\u0456", "i"],
100
+ ["\u0458", "j"], ["\u043A", "k"], ["\u0442", "t"], ["\u0432", "b"], ["\u043C", "m"],
101
+ ["\u043D", "h"], ["\u0501", "d"], ["\u0503", "g"], ["\u051B", "q"], ["\u051D", "w"],
102
+ ["\u04CF", "l"], ["\u0261", "g"], ["\u04AB", "c"], ["\u04BD", "e"], ["\u0459", "k"],
103
+ // Greek
104
+ ["\u03B1", "a"], ["\u03BF", "o"], ["\u03C1", "p"], ["\u03BD", "v"], ["\u03BA", "k"],
105
+ ["\u03B5", "e"], ["\u03C4", "t"], ["\u03B9", "i"], ["\u03C5", "u"], ["\u03C7", "x"],
106
+ ["\u03B7", "n"], ["\u03BC", "u"], ["\u03C3", "o"], ["\u03B2", "b"], ["\u03B3", "y"],
107
+ // Latin extended / other
108
+ ["\u0131", "i"], ["\u0269", "i"], ["\u1D0F", "o"], ["\u0280", "r"], ["\u01BF", "p"],
109
+ ["\u0578", "n"], ["\u057D", "u"], ["\u0585", "o"], ["\u0581", "g"], ["\u2044", "/"],
110
+ ]);
111
+
112
+ // `\b` is the wrong left boundary for a header name: it matches after a `-` or
113
+ // `_`, so `not-authorization:` and `internal_token:` were treated as the
114
+ // credential labels they merely end with. Requiring a non-identifier character
115
+ // (or start of input) keeps the match to whole field names.
116
+ //
117
+ // The optional quotes around the label matter: a serialized headers object
118
+ // (`{"x-api-key":"<secret>"}`) puts a closing quote between the name and the
119
+ // colon, so a bare `label:` pattern never saw it. The pre-existing JSON rules
120
+ // below only listed a few field names and did not share this label grammar,
121
+ // which is how ordinary JSON serialization — no homoglyphs, no attacker
122
+ // alphabet — walked a credential straight through.
123
+ const COLON_LABELLED_CREDENTIAL = new RegExp(
124
+ `(?<![A-Za-z0-9_-])["']?(?:${CREDENTIAL_HEADER_LABEL})["']?[^\\S\\r\\n]*:`,
125
+ "gi",
126
+ );
127
+
128
+ /**
129
+ * Framings other than `label: value` that carry the same credential names.
130
+ *
131
+ * An upstream error body is not always a header dump. It can echo the request
132
+ * as a form-encoded string, an XML element, or a multipart part header, and a
133
+ * colon-only matcher sees none of those. Each entry masks the value with the
134
+ * terminator its own grammar defines, so the surrounding structure survives.
135
+ */
136
+ const OTHER_FRAMED_CREDENTIALS: Array<[RegExp, string]> = [
137
+ // URL query / form-encoded: `authorization=<value>` up to `&` or `;`.
138
+ // Unconditionally to the separator — a quoted value is NOT allowed to end it
139
+ // early, or `authorization="decoy"<secret>&model=…` leaks the suffix.
140
+ [
141
+ new RegExp(`(?<![A-Za-z0-9_-])(?:${CREDENTIAL_HEADER_LABEL})=[^&;\\r\\n]*`, "gi"),
142
+ "=",
143
+ ],
144
+ // XML/HTML. A tag qualifies when its NAME is a credential (optionally
145
+ // namespace-qualified), or when a whole `name`/`key`/`id` attribute names one
146
+ // — `data-name` does not count, or `<field data-name="authorization">` loses
147
+ // harmless status text.
148
+ //
149
+ // Once a tag qualifies, the mask keeps only the tag name and runs to END OF
150
+ // INPUT. Two stopping points were tried and both leaked: the CLOSING TAG
151
+ // (same-name nesting ended the mask at the inner `</authorization>`, and a
152
+ // self-closing tag had none), then END OF LINE (an opening tag may legally
153
+ // span lines, so `<authorization\n value="…">` left the credential on the
154
+ // next line). XML has no line discipline to borrow, so there is no boundary
155
+ // left worth trusting.
156
+ //
157
+ // Whitespace is allowed around an attribute `=`, which XML permits and an
158
+ // echo may well reproduce.
159
+ [
160
+ new RegExp(
161
+ `(<[^\\S\\r\\n]*(?:[A-Za-z_][\\w.-]*:)?(?:${CREDENTIAL_HEADER_LABEL})(?=[\\s/>]))[\\s\\S]*`,
162
+ "gi",
163
+ ),
164
+ "element",
165
+ ],
166
+ [
167
+ new RegExp(
168
+ `(<[^\\S\\r\\n]*(?:[A-Za-z_][\\w.-]*:)?[A-Za-z_][\\w:.-]*)(?=[^>]*?(?<![\\w:.-])(?:name|key|id)[^\\S]*=[^\\S]*["']?(?:${CREDENTIAL_HEADER_LABEL})["']?(?=[\\s/>]))[\\s\\S]*`,
169
+ "gi",
170
+ ),
171
+ "element",
172
+ ],
173
+ // Multipart part: everything from a credential-named part header to the end
174
+ // of the input. Part-based, not line-based — a body can span lines and the
175
+ // blank line is often missing in a malformed echo.
176
+ //
177
+ // It deliberately does NOT stop at the first `--`: the boundary token is
178
+ // attacker-controlled text, so a body line starting `--not-the-boundary`
179
+ // ended the mask and exposed everything after it. Consuming the remainder
180
+ // costs trailing context in one framing and closes the bypass.
181
+ [
182
+ new RegExp(
183
+ `(name=["']?(?:${CREDENTIAL_HEADER_LABEL})["']?[^\\r\\n]*\\r?\\n(?:\\r?\\n)?)([\\s\\S]+)`,
184
+ "gi",
185
+ ),
186
+ "multipart",
187
+ ],
188
+ ];
189
+
190
+ /**
191
+ * These run over the FOLDED view too, then map back to the original string.
192
+ *
193
+ * Matching the raw text meant a percent-encoded form key (`author%69zation=`)
194
+ * and an XML character reference (`name="author&#105;zation"`) were invisible,
195
+ * even though both spell the credential name to anything that parses the body.
196
+ * The fold already decodes those, so the grammars are applied there and the
197
+ * mask is written back at the corresponding original offsets.
198
+ */
199
+ function maskOtherFramings(value: string): string {
200
+ // Same union rule as the header pass: decoding may add coverage, never
201
+ // remove it.
202
+ return maskOtherFramingsOnce(maskOtherFramingsOnce(value, true), false);
203
+ }
204
+
205
+ function maskOtherFramingsOnce(value: string, decodeEscapes: boolean): string {
206
+ let current = value;
207
+ for (const [pattern, kind] of OTHER_FRAMED_CREDENTIALS) {
208
+ const { folded, map } = foldForMatching(current, decodeEscapes);
209
+ pattern.lastIndex = 0;
210
+ let out = "";
211
+ let cursor = 0;
212
+ let match: RegExpExecArray | null;
213
+ while ((match = pattern.exec(folded)) !== null) {
214
+ const start = map[match.index] ?? current.length;
215
+ const end = map[match.index + match[0].length] ?? current.length;
216
+ if (start < cursor) continue;
217
+ const head = (() => {
218
+ if (kind === "=") {
219
+ const eq = match[0].indexOf("=");
220
+ const headEnd = map[match.index + eq + 1] ?? end;
221
+ return current.slice(start, headEnd);
222
+ }
223
+ const captured = match[1] ?? "";
224
+ const headEnd = map[match.index + captured.length] ?? end;
225
+ return current.slice(start, headEnd);
226
+ })();
227
+ const body = current.slice(start + head.length, end);
228
+ if (kind === "multipart" && !body.trim()) continue;
229
+ out += current.slice(cursor, start) + head + REDACTED_SECRET;
230
+ cursor = end;
231
+ if (pattern.lastIndex === match.index) pattern.lastIndex += 1;
232
+ }
233
+ current = out + current.slice(cursor);
234
+ }
235
+ return current;
236
+ }
237
+
238
+ /**
239
+ * Build a folded copy plus an index map back to the original string, so the
240
+ * match runs on normalized text while the output keeps every byte the match did
241
+ * not cover.
242
+ */
243
+ function foldForMatching(value: string, decodeEscapes = true): { folded: string; map: number[] } {
244
+ let folded = "";
245
+ const map: number[] = [];
246
+ // Serialization escapes are ALIASES for the label, not decoration: a JSON
247
+ // `\u0069`, a percent-encoded `%69`, and an XML `&#105;` all spell the same
248
+ // field name to whatever parses the body, while spelling something else to a
249
+ // literal matcher. Decode them into the matching view (one folded character
250
+ // per escape, with the whole escape mapped back to its start) so
251
+ // `author\u0069zation`, `author%69zation`, and `author&#105;zation` are the
252
+ // label they claim to be.
253
+ const decodeEscape = (at: number): { ch: string; width: number } | null => {
254
+ // JSON `\uXXXX`, INCLUDING a surrogate pair. Decoding the halves
255
+ // independently left `\uD835\uDD69` as two lone surrogates, so the
256
+ // mathematical letter they spell was never normalized as one code point.
257
+ const json = /^\\u([0-9a-fA-F]{4})/.exec(value.slice(at, at + 6));
258
+ if (json) {
259
+ const high = parseInt(json[1]!, 16);
260
+ if (high >= 0xd800 && high <= 0xdbff) {
261
+ const low = /^\\u([0-9a-fA-F]{4})/.exec(value.slice(at + 6, at + 12));
262
+ const lowCode = low ? parseInt(low[1]!, 16) : NaN;
263
+ if (lowCode >= 0xdc00 && lowCode <= 0xdfff) {
264
+ return { ch: String.fromCharCode(high, lowCode), width: 12 };
265
+ }
266
+ }
267
+ return { ch: String.fromCharCode(high), width: 6 };
268
+ }
269
+ // Percent encoding is UTF-8: consecutive `%XX` bytes form ONE character.
270
+ // Decoding each byte on its own turned `%D0%B5` into two unrelated
271
+ // Latin-1 characters instead of the Cyrillic `е` the fold would have
272
+ // recognized.
273
+ const pct = /^(?:%[0-9a-fA-F]{2})+/.exec(value.slice(at, at + 24));
274
+ if (pct) {
275
+ try {
276
+ const decoded = decodeURIComponent(pct[0]);
277
+ if (decoded.length >= 1) {
278
+ // Consume only the bytes that produced the FIRST character, so the
279
+ // rest of the sequence is decoded on the next iteration.
280
+ const first = String.fromCodePoint(decoded.codePointAt(0)!);
281
+ const bytes = new TextEncoder().encode(first).length;
282
+ return { ch: first, width: bytes * 3 };
283
+ }
284
+ } catch {
285
+ const single = parseInt(pct[0].slice(1, 3), 16);
286
+ return { ch: String.fromCharCode(single), width: 3 };
287
+ }
288
+ }
289
+ const xml = /^&#(x[0-9a-fA-F]{1,6}|[0-9]{1,7});/.exec(value.slice(at, at + 11));
290
+ if (xml) {
291
+ const raw = xml[1]!;
292
+ const code = raw[0] === "x" || raw[0] === "X"
293
+ ? parseInt(raw.slice(1), 16)
294
+ : parseInt(raw, 10);
295
+ if (Number.isFinite(code) && code > 0 && code <= 0x10ffff) {
296
+ return { ch: String.fromCodePoint(code), width: xml[0].length };
297
+ }
298
+ }
299
+ // HTML named references. `&colon;` and the other separator names are
300
+ // resolved exactly; anything else folds to the opaque placeholder so the
301
+ // label still matches without pretending to know the character.
302
+ const named = /^&([A-Za-z][A-Za-z0-9]{1,31});/.exec(value.slice(at, at + 34));
303
+ if (named) {
304
+ const separator = SEPARATOR_ENTITIES.get(named[1]!.toLowerCase());
305
+ return { ch: separator ?? NAMED_ENTITY_PLACEHOLDER, width: named[0].length };
306
+ }
307
+ return null;
308
+ };
309
+ // Iterate by CODE POINT, not UTF-16 code unit: a supplementary character
310
+ // (mathematical letters, variation selectors above the BMP) is two units, so
311
+ // a per-unit loop hands each half to the property tests separately and
312
+ // neither half matches anything. `𝕩-api-key` and a U+E0100 inside a label
313
+ // both walked straight past the fold that way.
314
+ let i = 0;
315
+ while (i < value.length) {
316
+ const escaped = decodeEscapes ? decodeEscape(i) : null;
317
+ const ch = escaped ? escaped.ch : String.fromCodePoint(value.codePointAt(i)!);
318
+ const width = escaped ? escaped.width : ch.length;
319
+ if (INVISIBLE_FORMAT.test(ch)) {
320
+ i += width;
321
+ continue;
322
+ }
323
+ const mapped = COLON_CONFUSABLES.has(ch)
324
+ ? ":"
325
+ : LETTER_CONFUSABLES.get(ch.toLowerCase())
326
+ // NFKD collapses fullwidth, circled, and mathematical letter variants
327
+ // onto their ASCII base.
328
+ ?? (ch.normalize("NFKD").length === 1 ? ch.normalize("NFKD") : ch);
329
+ // One folded unit per source code point keeps the offset map aligned; a
330
+ // multi-unit fold would desynchronize it, so those keep the original.
331
+ folded += mapped.length === 1 ? mapped : ch;
332
+ // One map entry per EMITTED UTF-16 unit. An escaped supplementary
333
+ // character emits two units, and giving it one entry desynchronized every
334
+ // later offset — the mask then landed mid-token and left part of the
335
+ // credential behind.
336
+ const emittedText = mapped.length === 1 ? mapped : ch;
337
+ for (let k = 0; k < emittedText.length; k += 1) map.push(i);
338
+ i += width;
339
+ }
340
+ map.push(value.length);
341
+ return { folded, map };
342
+ }
343
+
344
+ /**
345
+ * Run the header rule over BOTH matching views and take the union.
346
+ *
347
+ * Decoding may only ADD coverage. Applying it unconditionally removed some:
348
+ * `&#x1d569;x-api-key: <secret>` decoded to `𝕩x-api-key:`, which folds to
349
+ * `xx-api-key:` and no longer matches the label boundary — so a decode-only
350
+ * view masked LESS than the plain view did. Running both and masking whatever
351
+ * either one finds makes the direction of the change one-way.
352
+ */
353
+ function maskCredentialHeaders(value: string): string {
354
+ const decoded = maskCredentialHeadersOnce(value, true);
355
+ return maskCredentialHeadersOnce(decoded, false);
356
+ }
357
+
358
+ function maskCredentialHeadersOnce(value: string, decodeEscapes: boolean): string {
359
+ const { folded, map } = foldForMatching(value, decodeEscapes);
360
+ COLON_LABELLED_CREDENTIAL.lastIndex = 0;
361
+ let out = "";
362
+ let cursor = 0;
363
+ let match: RegExpExecArray | null;
364
+ while ((match = COLON_LABELLED_CREDENTIAL.exec(folded)) !== null) {
365
+ const start = map[match.index] ?? value.length;
366
+ const afterLabel = map[match.index + match[0].length] ?? value.length;
367
+ if (start < cursor) continue;
368
+ const lineEnd = (() => {
369
+ const nl = value.slice(afterLabel).search(/[\r\n]/);
370
+ return nl === -1 ? value.length : afterLabel + nl;
371
+ })();
372
+ // THE VALUE ALWAYS RUNS TO END-OF-LINE. There is no early termination and
373
+ // no attempt to preserve sibling fields.
374
+ //
375
+ // Three attempts tried to be smarter, and each one leaked: stop at the
376
+ // first closing quote; stop at a closing quote followed by punctuation;
377
+ // stop only when the LABEL was quoted. The third still leaked on an
378
+ // unmatched opening quote (`"x-api-key: "decoy",<secret>`) and on a
379
+ // correctly quoted key whose value quote was a decoy
380
+ // (`{"x-api-key":"decoy"<secret>}`).
381
+ //
382
+ // The pattern is the lesson: any rule that stops early is reading
383
+ // attacker-controlled text to decide where a secret ends, and the attacker
384
+ // gets to write that text. Losing the siblings in a serialized object
385
+ // makes a diagnostic less pretty; stopping early makes it leak. Monotonic
386
+ // and blunt wins.
387
+ const valueEnd = lineEnd;
388
+ const rawValue = value.slice(afterLabel, valueEnd);
389
+ if (!rawValue.trim()) continue;
390
+ // Keep the original separator spacing so a diagnostic still reads as
391
+ // `header: [REDACTED]` rather than `header:[REDACTED]`.
392
+ const gap = /^[^\S\r\n]*/.exec(rawValue)?.[0] ?? "";
393
+ const quote = "";
394
+ // `Bearer` is a fixed prefix, reproduced from a literal — never copied from
395
+ // the input — and only where an auth scheme is meaningful.
396
+ const label = match[0].replace(/[^\S\r\n]*:$/, "").trim();
397
+ const isAuthHeader = /^(?:proxy-)?authorization$/i.test(label);
398
+ const prefix = isAuthHeader && new RegExp(`^[^\\S\\r\\n]*${quote}?Bearer[^\\S\\r\\n]`, "i").test(rawValue)
399
+ ? "Bearer "
400
+ : "";
401
+ out += value.slice(cursor, afterLabel) + gap + quote + prefix + REDACTED_SECRET + quote;
402
+ cursor = valueEnd;
403
+ }
404
+ return out + value.slice(cursor);
405
+ }
4
406
 
5
407
  const SECRET_VALUE_PATTERNS: Array<[RegExp, string]> = [
6
- [/\bBearer\s+[A-Za-z0-9._~+/=-]{8,}\b/gi, `Bearer ${REDACTED_SECRET}`],
408
+ // A Bearer token outside a labelled header (prose, JSON fragments, logs).
409
+ // Horizontal whitespace only: `\s+` crossed line boundaries and masked the
410
+ // first word of the NEXT line when a header was quoted with a trailing break.
411
+ [/\b(Bearer)([^\S\r\n]+)[A-Za-z0-9._~+/=-]{8,}\b/gi, `$1$2${REDACTED_SECRET}`],
7
412
  [/\b(sk-[A-Za-z0-9][A-Za-z0-9._-]{6,})\b/g, REDACTED_SECRET],
8
413
  // GitHub tokens (classic + fine-grained + OAuth/refresh): ghp_/gho_/ghu_/ghs_/ghr_/github_pat_.
9
414
  [/\b(gh[pousr]_[A-Za-z0-9_]{8,}|github_pat_[A-Za-z0-9_]{20,})\b/g, REDACTED_SECRET],
@@ -31,13 +436,24 @@ function isSensitiveKey(key: string): boolean {
31
436
  }
32
437
 
33
438
  export function redactSecretString(value: string): string {
34
- let redacted = value;
439
+ let redacted = maskOtherFramings(maskCredentialHeaders(value));
35
440
  for (const [pattern, replacement] of SECRET_VALUE_PATTERNS) {
36
441
  redacted = redacted.replace(pattern, replacement);
37
442
  }
38
443
  return redacted;
39
444
  }
40
445
 
446
+ /** Shared bounded representation for caller-controlled scalar metadata stored in logs. */
447
+ export function sanitizeLogMetadataString(value: unknown, maxLength = 64): string | undefined {
448
+ if (typeof value !== "string" || !Number.isInteger(maxLength) || maxLength < 1) return undefined;
449
+ // Remove every control/line-separator code point that common terminals and log viewers
450
+ // can render as a record boundary before the value reaches a single-line log field.
451
+ const filtered = value.trim().replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, "");
452
+ if (!filtered) return undefined;
453
+ const redacted = redactSecretString(filtered).trim();
454
+ return redacted ? redacted.slice(0, maxLength) : undefined;
455
+ }
456
+
41
457
  export function redactSecrets(value: unknown): unknown {
42
458
  if (typeof value === "string") return redactSecretString(value);
43
459
  if (Array.isArray(value)) return value.map(item => redactSecrets(item));
@@ -0,0 +1,15 @@
1
+ interface SelfLaunchArgvOptions {
2
+ isStandaloneExecutable?: boolean;
3
+ sourceEntrypoint?: string;
4
+ }
5
+
6
+ /** Build argv for re-entering the current CLI in compiled or source mode. */
7
+ export function selfLaunchArgv(
8
+ args: readonly string[],
9
+ options: SelfLaunchArgvOptions = {},
10
+ ): string[] {
11
+ const bunStandalone = (Bun as unknown as { isStandaloneExecutable?: boolean }).isStandaloneExecutable;
12
+ const isStandaloneExecutable = options.isStandaloneExecutable ?? Boolean(bunStandalone);
13
+ if (isStandaloneExecutable) return [...args];
14
+ return [options.sourceEntrypoint ?? process.argv[1], ...args];
15
+ }
@@ -0,0 +1,71 @@
1
+ type Cleanup = () => void;
2
+
3
+ type ServerResourceOwner = {
4
+ token: symbol;
5
+ cleanups: Set<Cleanup>;
6
+ };
7
+
8
+ export type ServerResourceOwnerLease = {
9
+ release(): void;
10
+ };
11
+
12
+ const owners: ServerResourceOwner[] = [];
13
+
14
+ function currentOwner(): ServerResourceOwner | undefined {
15
+ return owners.at(-1);
16
+ }
17
+
18
+ /** Acquire ownership for resources created while one server instance starts. */
19
+ export function acquireServerResourceOwner(): ServerResourceOwnerLease {
20
+ const owner: ServerResourceOwner = {
21
+ token: Symbol("server-resource-owner"),
22
+ cleanups: new Set(),
23
+ };
24
+ owners.push(owner);
25
+
26
+ let released = false;
27
+ return {
28
+ release() {
29
+ if (released) return;
30
+ released = true;
31
+ const index = owners.findIndex(candidate => candidate.token === owner.token);
32
+ if (index !== -1) owners.splice(index, 1);
33
+ for (const cleanup of [...owner.cleanups]) {
34
+ try {
35
+ cleanup();
36
+ } catch {
37
+ // Resource cleanup is best-effort and one failed hook must not retain siblings.
38
+ }
39
+ }
40
+ owner.cleanups.clear();
41
+ },
42
+ };
43
+ }
44
+
45
+ /** Attach cleanup to the server instance currently being started. */
46
+ export function registerCurrentServerResourceCleanup(cleanup: Cleanup): () => void {
47
+ const owner = currentOwner();
48
+ if (!owner) return () => {};
49
+ owner.cleanups.add(cleanup);
50
+ let attached = true;
51
+ return () => {
52
+ if (!attached) return;
53
+ attached = false;
54
+ owner.cleanups.delete(cleanup);
55
+ };
56
+ }
57
+
58
+ /** Test-only reset for isolated lifecycle tests. */
59
+ export function resetServerResourceOwnershipForTests(): void {
60
+ for (const owner of [...owners].reverse()) {
61
+ for (const cleanup of [...owner.cleanups]) {
62
+ try {
63
+ cleanup();
64
+ } catch {
65
+ // Test reset must leave no retained owner even when a synthetic cleanup throws.
66
+ }
67
+ }
68
+ owner.cleanups.clear();
69
+ }
70
+ owners.length = 0;
71
+ }
@@ -23,3 +23,18 @@ export function loadServiceTokenFromFile(env: Record<string, string | undefined>
23
23
  return null;
24
24
  }
25
25
  }
26
+
27
+ /**
28
+ * Contents of the installed service token file. The launch wrapper always re-exports
29
+ * this file as OPENCODEX_API_AUTH_TOKEN, so doctor and start must inspect it even
30
+ * when the calling shell has no data-plane env var.
31
+ * Returns the token or null — never throws, never logs the value.
32
+ */
33
+ export function readInstalledServiceToken(): string | null {
34
+ try {
35
+ const token = readFileSync(serviceApiTokenFilePath(), "utf8").trim();
36
+ return token || null;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Shadow-call intercept source models.
3
3
  *
4
- * Codex's hard-coded helper model is not stable across client versions: it was
5
- * `gpt-5.4-mini` up to 0.144.x and became `gpt-5.6-luna` in 0.145.0. The
6
- * intercept therefore matches a prefix SET, and every surface that names the
4
+ * Codex 0.145.0+ uses `gpt-5.6-luna` for helper calls. Older clients through
5
+ * 0.144.x used `gpt-5.4-mini`; operators supporting them can restore that
6
+ * prefix with the `sourceModels` override. Every surface that names the
7
7
  * intercepted model (management API, GUI badges/tooltips, CLI) reads it from
8
8
  * here instead of hard-coding a slug that goes stale on the next client bump.
9
9
  */
10
- export const DEFAULT_SHADOW_SOURCE_MODELS = ["gpt-5.4-mini", "gpt-5.6-luna"] as const;
10
+ export const DEFAULT_SHADOW_SOURCE_MODELS = ["gpt-5.6-luna"] as const;
11
11
 
12
12
  /** Normalize a persisted `sourceModels` override; falls back to the defaults. */
13
13
  export function shadowSourceModels(configured?: unknown): string[] {
@@ -28,3 +28,34 @@ export function isShadowSourceModel(modelId: string, configured?: unknown): bool
28
28
  if (modelId.includes("/")) return false;
29
29
  return shadowSourceModels(configured).some(prefix => modelId.startsWith(prefix));
30
30
  }
31
+
32
+ /**
33
+ * The configured source prefix this model matched, or undefined.
34
+ *
35
+ * Callers that RECORD the intercepted model must record this rather than the caller's raw
36
+ * `modelId`. Matching is by prefix, so `gpt-5.6-luna` plus arbitrary trailing text still
37
+ * intercepts — and the raw string is caller-controlled, reaches `usage.jsonl` and `/api/logs`,
38
+ * and only passes a pattern-based redactor on the way. A credential family that redactor does
39
+ * not recognize survives verbatim. Returning the operator-configured prefix keeps the log
40
+ * field inside a set the operator chose, so no caller string is ever persisted.
41
+ */
42
+ export function shadowSourceModelPrefix(modelId: string, configured?: unknown): string | undefined {
43
+ if (modelId.includes("/")) return undefined;
44
+ return shadowSourceModels(configured).find(prefix => modelId.startsWith(prefix));
45
+ }
46
+
47
+ /**
48
+ * Decide whether a matching source model should use the opt-in intercept.
49
+ *
50
+ * Before Codex 0.147.0 this checked x-codex-turn-metadata and exempted
51
+ * request_kind "turn". Codex 0.147.0 can label background helper calls as
52
+ * "turn", causing them to bypass the intercept (#1684). The fix is to
53
+ * intercept every configured shadow source model unconditionally — the model
54
+ * slug alone is a sufficient signal.
55
+ */
56
+ export function shouldInterceptShadowCall(
57
+ modelId: string,
58
+ configured: unknown,
59
+ ): boolean {
60
+ return isShadowSourceModel(modelId, configured);
61
+ }
@@ -13,6 +13,47 @@ export type SseRecord =
13
13
  | { kind: "event"; event?: string; data: string }
14
14
  | { kind: "comment"; comment: string };
15
15
 
16
+ /**
17
+ * Extract one SSE field value from a single line, or null when the line is a different field.
18
+ *
19
+ * The space after the colon is OPTIONAL in text/event-stream: `data:{"a":1}` is as valid as
20
+ * `data: {"a":1}`. Parsers that hardcoded `startsWith("data: ")` silently dropped every frame
21
+ * from a producer that omits it, which surfaced as a completed turn with no content (#1170).
22
+ *
23
+ * Strips at most ONE leading space — the same rule `decodeServerSentEvents` applies below — so a
24
+ * payload that legitimately begins with whitespace keeps the rest of it. Does not trim the value:
25
+ * callers own that choice, and some of them intentionally keep trailing bytes.
26
+ */
27
+ export function sseFieldValue(line: string, field: string): string | null {
28
+ if (!line.startsWith(field)) return null;
29
+ const rest = line.slice(field.length);
30
+ // A colonless field line is the field with an empty value per the SSE rules, and
31
+ // `decodeServerSentEvents` below treats it that way (`colon < 0` -> valueStart = line.length).
32
+ // These helpers must not disagree with the decoder they mirror.
33
+ if (rest.length === 0) return "";
34
+ if (!rest.startsWith(":")) return null;
35
+ return rest.startsWith(": ") ? rest.slice(2) : rest.slice(1);
36
+ }
37
+
38
+ /**
39
+ * Offset-only variant of {@link sseFieldValue} for parsers that index into a larger buffer.
40
+ *
41
+ * Returns the index where the field's value begins within `text`, or -1 when the line at
42
+ * `[lineStart, lineEnd)` is a different field. Slicing nothing matters for the live Claude relay,
43
+ * whose translator-budget accounting reserves bytes by offset — materializing the line first would
44
+ * allocate the very string the budget exists to bound.
45
+ */
46
+ export function sseFieldOffset(text: string, lineStart: number, lineEnd: number, field: string): number {
47
+ if (!text.startsWith(field, lineStart)) return -1;
48
+ let valueStart = lineStart + field.length;
49
+ // Colonless field line: empty value, positioned at end-of-line (matches the decoder).
50
+ if (valueStart >= lineEnd) return lineEnd;
51
+ if (text[valueStart] !== ":") return -1;
52
+ valueStart += 1;
53
+ if (valueStart < lineEnd && text[valueStart] === " ") valueStart += 1;
54
+ return valueStart;
55
+ }
56
+
16
57
  /**
17
58
  * Decode text/event-stream records across arbitrary fetch chunk boundaries.
18
59
  *