@remodex/rmx 1.0.2

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 (588) hide show
  1. package/AGENTS_INSTALL.md +91 -0
  2. package/LICENSE +21 -0
  3. package/README.md +242 -0
  4. package/assets/architecture.png +0 -0
  5. package/assets/banner.png +0 -0
  6. package/assets/claude-code-models.gif +0 -0
  7. package/assets/codex-app-picker.png +0 -0
  8. package/bin/ocx.mjs +584 -0
  9. package/bin/package-main.mjs +9 -0
  10. package/gui/dist/assets/index-CZqebSPQ.css +1 -0
  11. package/gui/dist/assets/index-CkETtt7P.js +71 -0
  12. package/gui/dist/favicon.png +0 -0
  13. package/gui/dist/fonts/google-sans-cyrillic.woff2 +0 -0
  14. package/gui/dist/fonts/google-sans-latin.woff2 +0 -0
  15. package/gui/dist/icons.svg +24 -0
  16. package/gui/dist/index.html +25 -0
  17. package/gui/dist/logo.png +0 -0
  18. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  19. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  20. package/gui/dist/provider-icons/claude-color.svg +1 -0
  21. package/gui/dist/provider-icons/cline-color.svg +16 -0
  22. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  23. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  24. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  25. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  26. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  27. package/gui/dist/provider-icons/discord.svg +1 -0
  28. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  29. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  30. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  31. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  32. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  33. package/gui/dist/provider-icons/grok.svg +1 -0
  34. package/gui/dist/provider-icons/groq-color.svg +1 -0
  35. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  36. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  37. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  38. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  39. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  40. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  41. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  42. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  43. package/gui/dist/provider-icons/openai.svg +1 -0
  44. package/gui/dist/provider-icons/opencode.svg +2 -0
  45. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  46. package/gui/dist/provider-icons/pi.svg +21 -0
  47. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  48. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  49. package/gui/dist/provider-icons/telegram.svg +1 -0
  50. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  51. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  52. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  53. package/package.json +118 -0
  54. package/src/AGENTS.md +28 -0
  55. package/src/adapters/anthropic-image-guard.ts +251 -0
  56. package/src/adapters/anthropic-image-normalize.ts +518 -0
  57. package/src/adapters/anthropic.ts +1205 -0
  58. package/src/adapters/azure.ts +36 -0
  59. package/src/adapters/base.ts +83 -0
  60. package/src/adapters/client-fingerprint.ts +59 -0
  61. package/src/adapters/command-code.ts +453 -0
  62. package/src/adapters/cursor/arg-codec.ts +38 -0
  63. package/src/adapters/cursor/arg-normalize.ts +104 -0
  64. package/src/adapters/cursor/cursor-errors.ts +165 -0
  65. package/src/adapters/cursor/discovery.ts +276 -0
  66. package/src/adapters/cursor/effort-map.ts +139 -0
  67. package/src/adapters/cursor/exec-policy.ts +88 -0
  68. package/src/adapters/cursor/framing.ts +250 -0
  69. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  70. package/src/adapters/cursor/kv-store.ts +52 -0
  71. package/src/adapters/cursor/live-models.ts +153 -0
  72. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  73. package/src/adapters/cursor/live-transport.ts +1235 -0
  74. package/src/adapters/cursor/mcp-config.ts +42 -0
  75. package/src/adapters/cursor/mcp-manager.ts +333 -0
  76. package/src/adapters/cursor/message-mapper.ts +49 -0
  77. package/src/adapters/cursor/native-exec-common.ts +59 -0
  78. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  79. package/src/adapters/cursor/native-exec-fs.ts +332 -0
  80. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  81. package/src/adapters/cursor/native-exec-network.ts +43 -0
  82. package/src/adapters/cursor/native-exec-shell.ts +548 -0
  83. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  84. package/src/adapters/cursor/native-exec.ts +604 -0
  85. package/src/adapters/cursor/protobuf-events.ts +735 -0
  86. package/src/adapters/cursor/protobuf-request.ts +719 -0
  87. package/src/adapters/cursor/request-builder.ts +280 -0
  88. package/src/adapters/cursor/thread-continuity.ts +67 -0
  89. package/src/adapters/cursor/tool-definitions.ts +621 -0
  90. package/src/adapters/cursor/transport-retry.ts +132 -0
  91. package/src/adapters/cursor/transport.ts +57 -0
  92. package/src/adapters/cursor/types.ts +59 -0
  93. package/src/adapters/cursor.ts +196 -0
  94. package/src/adapters/google-antigravity-replay.ts +520 -0
  95. package/src/adapters/google-antigravity-wire.ts +140 -0
  96. package/src/adapters/google-errors.ts +85 -0
  97. package/src/adapters/google-http.ts +100 -0
  98. package/src/adapters/google-tool-schema.ts +173 -0
  99. package/src/adapters/google-truncation.ts +24 -0
  100. package/src/adapters/google-wire-compiler.ts +232 -0
  101. package/src/adapters/google.ts +859 -0
  102. package/src/adapters/identity.ts +77 -0
  103. package/src/adapters/image.ts +23 -0
  104. package/src/adapters/kiro-constants.ts +16 -0
  105. package/src/adapters/kiro-errors.ts +208 -0
  106. package/src/adapters/kiro-events.ts +197 -0
  107. package/src/adapters/kiro-images.ts +129 -0
  108. package/src/adapters/kiro-retry.ts +312 -0
  109. package/src/adapters/kiro-thinking.ts +104 -0
  110. package/src/adapters/kiro-tool-fallback.ts +36 -0
  111. package/src/adapters/kiro-tools.ts +224 -0
  112. package/src/adapters/kiro-truncation.ts +33 -0
  113. package/src/adapters/kiro-wire.ts +129 -0
  114. package/src/adapters/kiro.ts +1924 -0
  115. package/src/adapters/mimo-free.ts +263 -0
  116. package/src/adapters/openai-chat.ts +1265 -0
  117. package/src/adapters/openai-responses.ts +1309 -0
  118. package/src/adapters/run-turn-queue.ts +114 -0
  119. package/src/adapters/tool-catalog-nudge.ts +71 -0
  120. package/src/adapters/upstream-http-error.ts +48 -0
  121. package/src/android-remote/assets.ts +218 -0
  122. package/src/android-remote/attachments.ts +168 -0
  123. package/src/android-remote/auth.ts +237 -0
  124. package/src/android-remote/cloudflare-provisioning.ts +409 -0
  125. package/src/android-remote/cloudflare-secret.ts +106 -0
  126. package/src/android-remote/cloudflare-tunnel.ts +488 -0
  127. package/src/android-remote/cloudflared.ts +286 -0
  128. package/src/android-remote/codex-app-server.ts +565 -0
  129. package/src/android-remote/desktop-history-page.ts +936 -0
  130. package/src/android-remote/desktop-ipc.ts +3643 -0
  131. package/src/android-remote/desktop-ownership-store.ts +98 -0
  132. package/src/android-remote/desktop-project-registration.ts +129 -0
  133. package/src/android-remote/desktop-session-stream.ts +1110 -0
  134. package/src/android-remote/desktop-workspace-state.ts +443 -0
  135. package/src/android-remote/file-change-parser.ts +112 -0
  136. package/src/android-remote/gateway.ts +9108 -0
  137. package/src/android-remote/mutation-store.ts +299 -0
  138. package/src/android-remote/projection.ts +1780 -0
  139. package/src/android-remote/queued-turn-store.ts +248 -0
  140. package/src/android-remote/session-command-recovery.ts +1384 -0
  141. package/src/android-remote/store.ts +466 -0
  142. package/src/android-remote/thread-reconciliation.ts +133 -0
  143. package/src/android-remote/thread-source-paths.ts +307 -0
  144. package/src/android-remote/thread-stream.ts +546 -0
  145. package/src/android-remote/turn-activity.ts +235 -0
  146. package/src/android-remote/user-message-identity.ts +94 -0
  147. package/src/bridge.ts +1793 -0
  148. package/src/chat/inbound.ts +295 -0
  149. package/src/chat/outbound.ts +821 -0
  150. package/src/claude/agents-inject.ts +267 -0
  151. package/src/claude/alias.ts +149 -0
  152. package/src/claude/auth-detect.ts +229 -0
  153. package/src/claude/auth-mode-migration.ts +32 -0
  154. package/src/claude/auth-mode.ts +62 -0
  155. package/src/claude/context-windows.ts +189 -0
  156. package/src/claude/desktop-3p-guard.ts +35 -0
  157. package/src/claude/desktop-3p-paths.ts +84 -0
  158. package/src/claude/desktop-3p.ts +601 -0
  159. package/src/claude/desktop-health.ts +26 -0
  160. package/src/claude/desktop-profile.ts +263 -0
  161. package/src/claude/gateway-cache.ts +70 -0
  162. package/src/claude/inbound-debug.ts +163 -0
  163. package/src/claude/inbound.ts +519 -0
  164. package/src/claude/model-info.ts +154 -0
  165. package/src/claude/outbound.ts +898 -0
  166. package/src/cli/access.ts +108 -0
  167. package/src/cli/account-api.ts +296 -0
  168. package/src/cli/account-auth.ts +250 -0
  169. package/src/cli/account-catalog-refresh.ts +14 -0
  170. package/src/cli/account-extended.ts +476 -0
  171. package/src/cli/account-main.ts +317 -0
  172. package/src/cli/account.ts +297 -0
  173. package/src/cli/agent-driven.ts +70 -0
  174. package/src/cli/agent.ts +184 -0
  175. package/src/cli/catalog-prewarm.ts +27 -0
  176. package/src/cli/claude-desktop.ts +211 -0
  177. package/src/cli/claude.ts +302 -0
  178. package/src/cli/codex-shim-autorestore.ts +45 -0
  179. package/src/cli/codex-shim-readiness.ts +69 -0
  180. package/src/cli/combo.ts +124 -0
  181. package/src/cli/config-command.ts +183 -0
  182. package/src/cli/debug.ts +228 -0
  183. package/src/cli/desktop-first-run.ts +25 -0
  184. package/src/cli/doctor.ts +1022 -0
  185. package/src/cli/export-command.ts +201 -0
  186. package/src/cli/help.ts +370 -0
  187. package/src/cli/index.ts +1565 -0
  188. package/src/cli/init.ts +224 -0
  189. package/src/cli/integrations.ts +225 -0
  190. package/src/cli/interactive-confirm.ts +133 -0
  191. package/src/cli/internal-dispatch.ts +35 -0
  192. package/src/cli/launcher-context.ts +77 -0
  193. package/src/cli/models-runtime.ts +224 -0
  194. package/src/cli/models.ts +340 -0
  195. package/src/cli/observe.ts +170 -0
  196. package/src/cli/opencode.ts +587 -0
  197. package/src/cli/provider-runtime.ts +179 -0
  198. package/src/cli/provider.ts +476 -0
  199. package/src/cli/ready.ts +301 -0
  200. package/src/cli/route-policy.ts +92 -0
  201. package/src/cli/runtime-api.ts +328 -0
  202. package/src/cli/star-prompt.ts +211 -0
  203. package/src/cli/status-oauth.ts +78 -0
  204. package/src/cli/status.ts +321 -0
  205. package/src/cli/system-command.ts +196 -0
  206. package/src/cli/system-restart-client.ts +146 -0
  207. package/src/cli/tray-proxy.ts +205 -0
  208. package/src/cli/v2.ts +200 -0
  209. package/src/cli.ts +10 -0
  210. package/src/clients/config-export.ts +1109 -0
  211. package/src/codex/account-id.ts +34 -0
  212. package/src/codex/account-label.ts +34 -0
  213. package/src/codex/account-lifecycle.ts +172 -0
  214. package/src/codex/account-namespace-match.ts +63 -0
  215. package/src/codex/account-namespaces.ts +195 -0
  216. package/src/codex/account-pause.ts +20 -0
  217. package/src/codex/account-priority.ts +83 -0
  218. package/src/codex/account-runtime-state.ts +31 -0
  219. package/src/codex/account-store.ts +517 -0
  220. package/src/codex/account-usability.ts +40 -0
  221. package/src/codex/admission.ts +263 -0
  222. package/src/codex/app-server-processes.ts +799 -0
  223. package/src/codex/auth-api.ts +2098 -0
  224. package/src/codex/auth-collision.ts +107 -0
  225. package/src/codex/auth-context.ts +480 -0
  226. package/src/codex/autostart-health.ts +156 -0
  227. package/src/codex/catalog/account-models.ts +67 -0
  228. package/src/codex/catalog/aggregation.ts +471 -0
  229. package/src/codex/catalog/bundled.ts +533 -0
  230. package/src/codex/catalog/effort.ts +432 -0
  231. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  232. package/src/codex/catalog/kinds.ts +2 -0
  233. package/src/codex/catalog/metadata.ts +287 -0
  234. package/src/codex/catalog/native-models.ts +7 -0
  235. package/src/codex/catalog/parsing.ts +503 -0
  236. package/src/codex/catalog/provider-fetch.ts +2267 -0
  237. package/src/codex/catalog/sync.ts +1606 -0
  238. package/src/codex/catalog-admission.ts +197 -0
  239. package/src/codex/catalog-refresh-status.ts +87 -0
  240. package/src/codex/catalog-write-serialization.ts +242 -0
  241. package/src/codex/catalog.ts +15 -0
  242. package/src/codex/codex-write-lock.ts +384 -0
  243. package/src/codex/convergence-types.ts +593 -0
  244. package/src/codex/convergence.ts +580 -0
  245. package/src/codex/custom-model-catalog-migration.ts +176 -0
  246. package/src/codex/data/upstream-models.json +830 -0
  247. package/src/codex/desired-state.ts +230 -0
  248. package/src/codex/desktop-client-processes.ts +521 -0
  249. package/src/codex/exec-invocation.ts +22 -0
  250. package/src/codex/features.ts +1091 -0
  251. package/src/codex/generation.ts +202 -0
  252. package/src/codex/history-job.ts +347 -0
  253. package/src/codex/history-lock.ts +242 -0
  254. package/src/codex/history-migration-guardian.ts +115 -0
  255. package/src/codex/history-provider.ts +1075 -0
  256. package/src/codex/history-transition.ts +105 -0
  257. package/src/codex/history-worker.ts +204 -0
  258. package/src/codex/home.ts +206 -0
  259. package/src/codex/inject-coordination.ts +257 -0
  260. package/src/codex/inject.ts +1857 -0
  261. package/src/codex/injected-marker.ts +79 -0
  262. package/src/codex/integration-record.ts +266 -0
  263. package/src/codex/internal/catalog-writer.ts +203 -0
  264. package/src/codex/internal/history-writer.ts +105 -0
  265. package/src/codex/journal.ts +172 -0
  266. package/src/codex/main-account-cache.ts +56 -0
  267. package/src/codex/main-account.ts +40 -0
  268. package/src/codex/management-convergence.ts +114 -0
  269. package/src/codex/model-cache.ts +267 -0
  270. package/src/codex/native-main-admission.ts +47 -0
  271. package/src/codex/native-main-auth-temp.ts +187 -0
  272. package/src/codex/native-main-claim.ts +167 -0
  273. package/src/codex/native-main-lock-file.ts +162 -0
  274. package/src/codex/native-main-owner.ts +329 -0
  275. package/src/codex/native-profile-api.ts +247 -0
  276. package/src/codex/native-profile-manager.ts +1531 -0
  277. package/src/codex/native-profile-processes.ts +121 -0
  278. package/src/codex/native-profile-recovery.ts +99 -0
  279. package/src/codex/native-profile-stage-store.ts +387 -0
  280. package/src/codex/native-profile-startup.ts +348 -0
  281. package/src/codex/native-profile-store.ts +855 -0
  282. package/src/codex/native-profile-types.ts +120 -0
  283. package/src/codex/native-residue.ts +691 -0
  284. package/src/codex/paths.ts +78 -0
  285. package/src/codex/plugins-doctor.ts +242 -0
  286. package/src/codex/pool-rotation.ts +295 -0
  287. package/src/codex/project-config-warnings.ts +426 -0
  288. package/src/codex/prompt-journal.ts +311 -0
  289. package/src/codex/prompt-layers.ts +967 -0
  290. package/src/codex/prompt-lock.ts +143 -0
  291. package/src/codex/provider-adoption.ts +242 -0
  292. package/src/codex/quota-rejection.ts +224 -0
  293. package/src/codex/quota.ts +494 -0
  294. package/src/codex/refresh.ts +60 -0
  295. package/src/codex/routing.ts +1855 -0
  296. package/src/codex/runtime.ts +659 -0
  297. package/src/codex/shim.ts +1215 -0
  298. package/src/codex/subagent-defaults.ts +557 -0
  299. package/src/codex/subagent-model-fallback.ts +560 -0
  300. package/src/codex/sync.ts +238 -0
  301. package/src/codex/transition-state.ts +612 -0
  302. package/src/codex/upstream-host-health.ts +368 -0
  303. package/src/codex/user-identity.ts +374 -0
  304. package/src/codex/warmup.ts +192 -0
  305. package/src/codex/websocket-registry.ts +100 -0
  306. package/src/codex/write-coordination.ts +114 -0
  307. package/src/combos/failover.ts +140 -0
  308. package/src/combos/index.ts +44 -0
  309. package/src/combos/request.ts +64 -0
  310. package/src/combos/resolve.ts +232 -0
  311. package/src/combos/types.ts +392 -0
  312. package/src/config.ts +3270 -0
  313. package/src/generated/model-metadata.ts +144 -0
  314. package/src/github/star-state.ts +203 -0
  315. package/src/grok/inject.ts +540 -0
  316. package/src/grok/inspect.ts +45 -0
  317. package/src/grok/status.ts +127 -0
  318. package/src/grok/sync.ts +66 -0
  319. package/src/images/artifacts.ts +516 -0
  320. package/src/images/fulfill-video.ts +163 -0
  321. package/src/images/fulfill.ts +149 -0
  322. package/src/images/index.ts +4 -0
  323. package/src/images/loop.ts +922 -0
  324. package/src/images/plan.ts +133 -0
  325. package/src/images/synthetic-tool.ts +133 -0
  326. package/src/images/types.ts +41 -0
  327. package/src/images/xai-client.ts +141 -0
  328. package/src/images/xai-video-client.ts +163 -0
  329. package/src/index.ts +22 -0
  330. package/src/integrations/config-io.ts +151 -0
  331. package/src/integrations/journal.ts +315 -0
  332. package/src/integrations/merge.ts +135 -0
  333. package/src/integrations/native/ownership-preflight.ts +202 -0
  334. package/src/integrations/ownership.ts +111 -0
  335. package/src/integrations/registry.ts +108 -0
  336. package/src/integrations/serialize.ts +235 -0
  337. package/src/integrations/state.ts +290 -0
  338. package/src/integrations/store.ts +103 -0
  339. package/src/integrations/writer.ts +492 -0
  340. package/src/lib/abort.ts +146 -0
  341. package/src/lib/admin-secrets.ts +25 -0
  342. package/src/lib/admission.ts +83 -0
  343. package/src/lib/app-owned-memory-stores.ts +173 -0
  344. package/src/lib/app-owned-memory.ts +265 -0
  345. package/src/lib/bounded-body.ts +242 -0
  346. package/src/lib/bun-binary-validator.d.mts +3 -0
  347. package/src/lib/bun-binary-validator.mjs +18 -0
  348. package/src/lib/bun-runtime.ts +184 -0
  349. package/src/lib/bun-stream-caps.ts +127 -0
  350. package/src/lib/config-ownership.ts +438 -0
  351. package/src/lib/crash-guard.ts +344 -0
  352. package/src/lib/debug-log-buffer.ts +83 -0
  353. package/src/lib/debug-settings.ts +108 -0
  354. package/src/lib/debug.ts +31 -0
  355. package/src/lib/destination-policy.ts +316 -0
  356. package/src/lib/errors.ts +364 -0
  357. package/src/lib/eventstream-decoder.ts +253 -0
  358. package/src/lib/gcp-adc.ts +341 -0
  359. package/src/lib/injection-debug-log.ts +58 -0
  360. package/src/lib/local-management-attestation.ts +51 -0
  361. package/src/lib/open-url.ts +25 -0
  362. package/src/lib/pinned-http.ts +182 -0
  363. package/src/lib/privacy.ts +20 -0
  364. package/src/lib/process-control.ts +168 -0
  365. package/src/lib/provider-environment.ts +470 -0
  366. package/src/lib/provider-outbound.ts +203 -0
  367. package/src/lib/provider-url.ts +14 -0
  368. package/src/lib/proxy-env.ts +18 -0
  369. package/src/lib/redact.ts +510 -0
  370. package/src/lib/remodex-home.ts +616 -0
  371. package/src/lib/retry-after.ts +55 -0
  372. package/src/lib/service-secrets.ts +178 -0
  373. package/src/lib/shadow-call.ts +54 -0
  374. package/src/lib/sidecar-tracker.ts +52 -0
  375. package/src/lib/sse-decoder.ts +364 -0
  376. package/src/lib/state-store-registrations.ts +109 -0
  377. package/src/lib/state-store-sweeper.ts +184 -0
  378. package/src/lib/system-restart-contract.ts +73 -0
  379. package/src/lib/test-home-guard.ts +98 -0
  380. package/src/lib/token-estimate.ts +69 -0
  381. package/src/lib/translator-budget.ts +366 -0
  382. package/src/lib/upstream-reachability.ts +91 -0
  383. package/src/lib/upstream-retry.ts +508 -0
  384. package/src/lib/win-exec.ts +115 -0
  385. package/src/lib/win-paths.ts +68 -0
  386. package/src/lib/windows-elevation.ts +705 -0
  387. package/src/lib/windows-secret-acl.ts +817 -0
  388. package/src/lib/windows-user-principal.ts +283 -0
  389. package/src/lib/winsw.ts +402 -0
  390. package/src/model-sources.ts +73 -0
  391. package/src/oauth/anthropic-routing.ts +594 -0
  392. package/src/oauth/anthropic.ts +177 -0
  393. package/src/oauth/callback-server.ts +294 -0
  394. package/src/oauth/chatgpt.ts +150 -0
  395. package/src/oauth/command-code.ts +239 -0
  396. package/src/oauth/cursor.ts +231 -0
  397. package/src/oauth/github-copilot.ts +428 -0
  398. package/src/oauth/google-antigravity.ts +230 -0
  399. package/src/oauth/health.ts +443 -0
  400. package/src/oauth/index.ts +1280 -0
  401. package/src/oauth/key-providers.ts +128 -0
  402. package/src/oauth/kimi.ts +213 -0
  403. package/src/oauth/kiro-credentials.ts +726 -0
  404. package/src/oauth/kiro.ts +621 -0
  405. package/src/oauth/local-token-detect.ts +121 -0
  406. package/src/oauth/log.ts +48 -0
  407. package/src/oauth/login-cli.ts +163 -0
  408. package/src/oauth/pkce.ts +15 -0
  409. package/src/oauth/store.ts +655 -0
  410. package/src/oauth/token-guardian.ts +309 -0
  411. package/src/oauth/types.ts +62 -0
  412. package/src/oauth/xai.ts +241 -0
  413. package/src/providers/alibaba-region-backup.ts +75 -0
  414. package/src/providers/alibaba-region-migration.ts +156 -0
  415. package/src/providers/alibaba-region-startup.ts +36 -0
  416. package/src/providers/antigravity-models.ts +317 -0
  417. package/src/providers/api-keys.ts +140 -0
  418. package/src/providers/base-url-choices.ts +64 -0
  419. package/src/providers/codex-capacity.ts +288 -0
  420. package/src/providers/command-code-efforts.ts +85 -0
  421. package/src/providers/context-cap.ts +73 -0
  422. package/src/providers/derive.ts +451 -0
  423. package/src/providers/free-directory.ts +187 -0
  424. package/src/providers/github-copilot-transport.ts +56 -0
  425. package/src/providers/google-vertex-location.ts +14 -0
  426. package/src/providers/key-failover.ts +271 -0
  427. package/src/providers/kiro-models.ts +67 -0
  428. package/src/providers/label.ts +19 -0
  429. package/src/providers/model-discovery-limits.ts +16 -0
  430. package/src/providers/model-discovery.ts +361 -0
  431. package/src/providers/openai-sidecar.ts +235 -0
  432. package/src/providers/openai-tier-startup.ts +27 -0
  433. package/src/providers/openai-tiers.ts +301 -0
  434. package/src/providers/openai-virtual-models.ts +83 -0
  435. package/src/providers/openrouter-routing.ts +102 -0
  436. package/src/providers/provider-id-rewrite.ts +179 -0
  437. package/src/providers/quota.ts +1942 -0
  438. package/src/providers/reasoning-capabilities.ts +336 -0
  439. package/src/providers/registry.ts +2375 -0
  440. package/src/providers/slug-codec.ts +74 -0
  441. package/src/providers/xai-transport.ts +149 -0
  442. package/src/reasoning-effort.ts +243 -0
  443. package/src/responses/compaction.ts +124 -0
  444. package/src/responses/hosted-tool-policy.ts +9 -0
  445. package/src/responses/parser.ts +714 -0
  446. package/src/responses/reasoning-envelope.ts +60 -0
  447. package/src/responses/reasoning-replay-cache.ts +106 -0
  448. package/src/responses/schema.ts +159 -0
  449. package/src/responses/spill-store.ts +431 -0
  450. package/src/responses/state.ts +1039 -0
  451. package/src/responses/tool-groups.ts +19 -0
  452. package/src/router.ts +802 -0
  453. package/src/routing/analytics.ts +377 -0
  454. package/src/routing/capability.ts +205 -0
  455. package/src/routing/cost.ts +77 -0
  456. package/src/routing/evaluator.ts +444 -0
  457. package/src/routing/health.ts +401 -0
  458. package/src/routing/history/cursor.ts +43 -0
  459. package/src/routing/history/indexer.ts +605 -0
  460. package/src/routing/history/schema.ts +72 -0
  461. package/src/routing/profile-namespace.ts +15 -0
  462. package/src/routing/profile.ts +424 -0
  463. package/src/routing/quota.ts +145 -0
  464. package/src/routing/request-evidence.ts +45 -0
  465. package/src/routing/trace.ts +686 -0
  466. package/src/server/adapter-resolve.ts +83 -0
  467. package/src/server/auth-cors.ts +606 -0
  468. package/src/server/chat-completions.ts +379 -0
  469. package/src/server/claude-messages.ts +980 -0
  470. package/src/server/effort-policy.ts +251 -0
  471. package/src/server/github-copilot-responses-repair.ts +338 -0
  472. package/src/server/gui-static.ts +152 -0
  473. package/src/server/image-retry.ts +42 -0
  474. package/src/server/images.ts +485 -0
  475. package/src/server/index.ts +1633 -0
  476. package/src/server/lifecycle.ts +482 -0
  477. package/src/server/live.ts +609 -0
  478. package/src/server/management/agent-settings-routes.ts +1180 -0
  479. package/src/server/management/android-remote-routes.ts +390 -0
  480. package/src/server/management/api-access.ts +141 -0
  481. package/src/server/management/api-key-usage.ts +167 -0
  482. package/src/server/management/body.ts +35 -0
  483. package/src/server/management/combo-routes.ts +244 -0
  484. package/src/server/management/config-routes.ts +602 -0
  485. package/src/server/management/context.ts +88 -0
  486. package/src/server/management/integration-routes.ts +538 -0
  487. package/src/server/management/logs-usage-routes.ts +516 -0
  488. package/src/server/management/model-routes.ts +519 -0
  489. package/src/server/management/model-rows.ts +143 -0
  490. package/src/server/management/native-integration-routes.ts +781 -0
  491. package/src/server/management/oauth-account-routes.ts +573 -0
  492. package/src/server/management/provider-routes.ts +781 -0
  493. package/src/server/management/request-history-routes.ts +191 -0
  494. package/src/server/management/routing-analytics-routes.ts +74 -0
  495. package/src/server/management/routing-profile-routes.ts +384 -0
  496. package/src/server/management/shared.ts +277 -0
  497. package/src/server/management/sidebar-routes.ts +106 -0
  498. package/src/server/management/sync-response.ts +69 -0
  499. package/src/server/management/system-restart.ts +433 -0
  500. package/src/server/management/system-routes.ts +141 -0
  501. package/src/server/management/usage-summary-cache.ts +86 -0
  502. package/src/server/management-api.ts +269 -0
  503. package/src/server/management-auth.ts +353 -0
  504. package/src/server/memory-watchdog.ts +156 -0
  505. package/src/server/port-reclaim.ts +307 -0
  506. package/src/server/ports.ts +156 -0
  507. package/src/server/proxy-liveness.ts +326 -0
  508. package/src/server/proxy-stop.ts +92 -0
  509. package/src/server/readiness.ts +99 -0
  510. package/src/server/relay-eager.ts +353 -0
  511. package/src/server/relay.ts +1179 -0
  512. package/src/server/request-decompress.ts +132 -0
  513. package/src/server/request-log-conversation.ts +168 -0
  514. package/src/server/request-log.ts +1072 -0
  515. package/src/server/responses/collaboration.ts +409 -0
  516. package/src/server/responses/compact.ts +710 -0
  517. package/src/server/responses/core.ts +3561 -0
  518. package/src/server/responses/encrypted-payload.ts +308 -0
  519. package/src/server/responses/fetch-helpers.ts +171 -0
  520. package/src/server/responses/passthrough-error.ts +78 -0
  521. package/src/server/responses/policy-fallback.ts +152 -0
  522. package/src/server/responses/terminal-guard.ts +230 -0
  523. package/src/server/responses/upstream-error.ts +48 -0
  524. package/src/server/responses-image-gen-repair.ts +132 -0
  525. package/src/server/responses-item-id-repair.ts +272 -0
  526. package/src/server/responses-json-events.ts +52 -0
  527. package/src/server/responses-model-rewrite.ts +29 -0
  528. package/src/server/responses-snapshot-repair.ts +621 -0
  529. package/src/server/responses.ts +10 -0
  530. package/src/server/search.ts +181 -0
  531. package/src/server/sse-frame-buffer.ts +292 -0
  532. package/src/server/sse-payload-rewrite.ts +263 -0
  533. package/src/server/startup-action-control.ts +308 -0
  534. package/src/server/startup-health-cache.ts +119 -0
  535. package/src/server/system-env.ts +418 -0
  536. package/src/server/windows-tcp-drop.ts +184 -0
  537. package/src/server/windows-tray-control.ts +41 -0
  538. package/src/server/ws-bridge.ts +470 -0
  539. package/src/service-manager-probe.ts +824 -0
  540. package/src/service.ts +3011 -0
  541. package/src/stall-timeout.ts +20 -0
  542. package/src/storage/cleanup-job.ts +57 -0
  543. package/src/storage/cleanup.ts +3085 -0
  544. package/src/storage/policy-job.ts +457 -0
  545. package/src/storage/policy-scheduler.ts +40 -0
  546. package/src/storage/policy-worker.ts +59 -0
  547. package/src/storage/policy.ts +527 -0
  548. package/src/storage/restore-job.ts +299 -0
  549. package/src/storage/restore-worker.ts +58 -0
  550. package/src/storage/scanner.ts +238 -0
  551. package/src/storage/storage-mutation-coordinator.ts +139 -0
  552. package/src/storage/worker-lifecycle.ts +215 -0
  553. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  554. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  555. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  556. package/src/tray/assets/opencodex-tray.png +0 -0
  557. package/src/tray/windows-tray.ps1 +364 -0
  558. package/src/tray/windows.ts +738 -0
  559. package/src/types.ts +1531 -0
  560. package/src/update/badge.ts +72 -0
  561. package/src/update/desktop-release.ts +1620 -0
  562. package/src/update/index.ts +402 -0
  563. package/src/update/job.ts +1906 -0
  564. package/src/update/notify.ts +261 -0
  565. package/src/update/npm-cache-preflight.d.mts +47 -0
  566. package/src/update/npm-cache-preflight.mjs +201 -0
  567. package/src/update/npm-invocation.d.mts +23 -0
  568. package/src/update/npm-invocation.mjs +94 -0
  569. package/src/update/tray-update-plan.d.mts +18 -0
  570. package/src/update/tray-update-plan.mjs +38 -0
  571. package/src/usage/cost.ts +0 -0
  572. package/src/usage/debug.ts +97 -0
  573. package/src/usage/expected-prices.ts +283 -0
  574. package/src/usage/log.ts +695 -0
  575. package/src/usage/summary.ts +585 -0
  576. package/src/usage/totals.ts +14 -0
  577. package/src/vision/anthropic-describe.ts +185 -0
  578. package/src/vision/describe.ts +127 -0
  579. package/src/vision/index.ts +558 -0
  580. package/src/vision/reasoning.ts +55 -0
  581. package/src/web-search/anthropic-executor.ts +189 -0
  582. package/src/web-search/executor.ts +105 -0
  583. package/src/web-search/format-result.ts +89 -0
  584. package/src/web-search/index.ts +196 -0
  585. package/src/web-search/loop.ts +791 -0
  586. package/src/web-search/parse.ts +235 -0
  587. package/src/web-search/progress-stream.ts +342 -0
  588. package/src/web-search/synthetic-tool.ts +47 -0
@@ -0,0 +1,508 @@
1
+ /**
2
+ * Retry guard for upstream fetches that die on stale pooled keep-alive sockets.
3
+ *
4
+ * chatgpt.com (Cloudflare) closes idle keep-alive connections server-side; Bun's fetch pool
5
+ * reuses the half-closed socket and the request write fails with ECONNRESET before any
6
+ * response bytes arrive. Retrying on a fresh connection is safe for our replayable
7
+ * (string-body) upstream requests, because fetch() rejects only before response headers —
8
+ * a caught error here means no response was ever received.
9
+ *
10
+ * Deliberately narrow: timeouts, aborts, ECONNREFUSED/DNS/TLS failures, and HTTP error
11
+ * statuses (returned as Response, never thrown) are NOT retried. Mid-stream SSE resets are
12
+ * out of scope — the response has already resolved by then.
13
+ *
14
+ * MUST stay a leaf module: imports nothing from server.ts or adapters (kiro-retry imports
15
+ * the shared abort helpers from here).
16
+ */
17
+ import { clearableDeadline } from "./abort";
18
+ import { readBoundedResponseBody } from "./bounded-body";
19
+
20
+ // 1 initial + 2 retries: the pool may hold more than one stale socket.
21
+ const RESET_RETRY_MAX_ATTEMPTS = 3;
22
+ const RESET_RETRY_BASE_DELAY_MS = 150;
23
+ const RESET_RETRY_MAX_DELAY_MS = 1_000;
24
+
25
+ // Transient-5xx status retry layer (pre-stream only; devlog/_plan/260716_claudecode_hardening/010).
26
+ const TRANSIENT_RETRY_MAX_ATTEMPTS = 3; // 1 initial + 2 retries
27
+ const TRANSIENT_RETRY_BASE_DELAY_MS = 400;
28
+ const TRANSIENT_RETRY_MAX_DELAY_MS = 5_000;
29
+ // A failed attempt slower than this is the "slow 502" incident shape (191s observed on
30
+ // 2026-07-15): retrying it only duplicates upstream load past client timeouts — return it.
31
+ const TRANSIENT_RETRY_SLOW_ATTEMPT_MS = 15_000;
32
+ const NON_JSON_INSPECTION_MAX_BYTES = 8 * 1024;
33
+ const NON_JSON_INSPECTION_TIMEOUT_MS = 1_000;
34
+
35
+ /**
36
+ * Upstream statuses treated as transient: gateway errors and Cloudflare 52x.
37
+ * 500 is included per the OpenAI SDK default (auto-retries >=500; Tier-2 proven in
38
+ * devlog/260716_ocx_claude_sol_502_midstream/02). 507 was observed in the 48h ledger
39
+ * but is deliberately excluded (storage-class, not gateway-transient).
40
+ */
41
+ export function isTransientUpstreamStatus(status: number): boolean {
42
+ return status === 500 || status === 502 || status === 503 || status === 504
43
+ || status === 520 || status === 521 || status === 522;
44
+ }
45
+
46
+ export interface RetryBackoffOptions {
47
+ baseDelayMs: number;
48
+ maxDelayMs: number;
49
+ headers?: Headers;
50
+ }
51
+
52
+ export function abortError(signal?: AbortSignal): unknown {
53
+ return signal?.reason ?? new DOMException("The operation was aborted", "AbortError");
54
+ }
55
+
56
+ export async function sleepWithAbort(ms: number, signal?: AbortSignal): Promise<void> {
57
+ if (ms <= 0) return;
58
+ if (signal?.aborted) throw abortError(signal);
59
+ await new Promise<void>((resolve, reject) => {
60
+ let timer: ReturnType<typeof setTimeout>;
61
+ const cleanup = () => {
62
+ clearTimeout(timer);
63
+ signal?.removeEventListener("abort", onAbort);
64
+ };
65
+ const onAbort = () => {
66
+ cleanup();
67
+ reject(abortError(signal));
68
+ };
69
+ timer = setTimeout(() => {
70
+ cleanup();
71
+ resolve();
72
+ }, ms);
73
+ signal?.addEventListener("abort", onAbort, { once: true });
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Best-effort, bounded cancellation of a response body before a retry backoff.
79
+ *
80
+ * The 429 paths release the unread body before waiting so sockets do not accumulate under a
81
+ * rate-limit storm, but a never-settling `cancel()` promise must not be able to block the
82
+ * abort-aware backoff (client cancel, `maxIntervalMs`, or the cumulative header deadline).
83
+ * Cancellation is started and its rejection observed; the await is bounded by `timeoutMs`
84
+ * and the abort signal. This mirrors the rotation-path guarantee (release is initiated, not
85
+ * awaited forever) while preserving the resource-release intent of the same-target paths.
86
+ */
87
+ export async function releaseResponseBodyBestEffort(
88
+ body: ReadableStream<Uint8Array> | null,
89
+ signal: AbortSignal | undefined,
90
+ timeoutMs = 1_000,
91
+ ): Promise<void> {
92
+ if (!body) return;
93
+ if (signal?.aborted) {
94
+ void body.cancel().catch(() => {});
95
+ return;
96
+ }
97
+ const cancel = body.cancel().catch(() => {});
98
+ if (!signal) {
99
+ await Promise.race([cancel, new Promise<void>(resolve => setTimeout(resolve, timeoutMs))]);
100
+ return;
101
+ }
102
+ await new Promise<void>(resolve => {
103
+ let timer: ReturnType<typeof setTimeout>;
104
+ /**
105
+ * Abort hook: clear the bounded-body release timer and settle the promise so a
106
+ * never-settling cancel() can never block the abort-aware backoff.
107
+ */
108
+ const onAbort = () => {
109
+ clearTimeout(timer);
110
+ resolve();
111
+ };
112
+ timer = setTimeout(() => {
113
+ signal.removeEventListener("abort", onAbort);
114
+ resolve();
115
+ }, timeoutMs);
116
+ signal.addEventListener("abort", onAbort, { once: true });
117
+ void cancel.then(() => {
118
+ clearTimeout(timer);
119
+ signal.removeEventListener("abort", onAbort);
120
+ resolve();
121
+ });
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Abort-aware sleep that yields an adapter `heartbeat` at least every `heartbeatIntervalMs`.
127
+ * The Responses bridge treats a returned iterator event as upstream liveness and aborts turns
128
+ * that stay silent past the stall budget (default 300s), while a retryOn429 wait may legally
129
+ * reach 600s — so deliberate waits must keep the watchdog fed or a long backoff is killed
130
+ * mid-turn. The final chunk always yields once, which doubles as the post-wait liveness beat.
131
+ */
132
+ export async function* sleepWithHeartbeats(
133
+ ms: number,
134
+ signal?: AbortSignal,
135
+ heartbeatIntervalMs = 10_000,
136
+ ): AsyncGenerator<{ type: "heartbeat" }> {
137
+ if (ms <= 0) return;
138
+ // Guard against a non-positive interval: a zero/negative step would spin the loop forever
139
+ // while sleepWithAbort early-returns without ever observing the abort signal. NaN must be
140
+ // normalized too: Math.max(1, NaN) is NaN, which would abort the wait after one beat.
141
+ const stepMs = Number.isNaN(heartbeatIntervalMs) ? 1 : Math.max(1, heartbeatIntervalMs);
142
+ let remaining = ms;
143
+ while (remaining > 0) {
144
+ const chunk = Math.min(remaining, stepMs);
145
+ await sleepWithAbort(chunk, signal);
146
+ remaining -= chunk;
147
+ yield { type: "heartbeat" };
148
+ }
149
+ }
150
+
151
+ export interface SameTarget429WaitOptions {
152
+ body: ReadableStream<Uint8Array> | null;
153
+ signal?: AbortSignal;
154
+ delayMs: number;
155
+ /**
156
+ * When set, the wait yields adapter heartbeats so bridge stall watchdogs stay fed.
157
+ * Omit for pre-stream recovery paths that have no stall watchdog.
158
+ */
159
+ heartbeatIntervalMs?: number;
160
+ }
161
+
162
+ /**
163
+ * Shared pre-replay prep for opt-in same-target 429 waits:
164
+ * release the unread 429 body, then sleep (optionally with heartbeats).
165
+ * Callers still own attempt budgeting, abort re-checks, and the replay itself.
166
+ */
167
+ export async function* prepareSameTarget429Wait(
168
+ options: SameTarget429WaitOptions,
169
+ ): AsyncGenerator<{ type: "heartbeat" }> {
170
+ await releaseResponseBodyBestEffort(options.body, options.signal);
171
+ if (options.heartbeatIntervalMs === undefined) {
172
+ await sleepWithAbort(options.delayMs, options.signal);
173
+ return;
174
+ }
175
+ yield* sleepWithHeartbeats(options.delayMs, options.signal, options.heartbeatIntervalMs);
176
+ }
177
+
178
+ export function isConnectionResetError(err: unknown): boolean {
179
+ if (!(err instanceof Error)) return false;
180
+ // Aborts and timeouts are caller decisions / honest failures — never retryable.
181
+ if (err.name === "AbortError" || err.name === "TimeoutError") return false;
182
+ const code = (err as { code?: unknown }).code;
183
+ if (code === "ECONNRESET" || code === "EPIPE") return true;
184
+ const msg = err.message.toLowerCase();
185
+ return msg.includes("socket connection was closed unexpectedly")
186
+ || msg.includes("connection reset by peer");
187
+ }
188
+
189
+ function retryAfterDelayMs(headers: Headers): number | undefined {
190
+ const raw = headers.get("retry-after")?.trim();
191
+ if (!raw) return undefined;
192
+ const seconds = Number(raw);
193
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);
194
+ const dateMs = Date.parse(raw);
195
+ if (!Number.isFinite(dateMs)) return undefined;
196
+ return Math.max(0, dateMs - Date.now());
197
+ }
198
+
199
+ export function retryBackoffDelayMs(attempt: number, opts: RetryBackoffOptions): number {
200
+ const retryAfter = opts.headers ? retryAfterDelayMs(opts.headers) : undefined;
201
+ if (retryAfter !== undefined) return Math.min(retryAfter, opts.maxDelayMs);
202
+ const exp = Math.min(opts.baseDelayMs * (2 ** attempt), opts.maxDelayMs);
203
+ return Math.floor(exp * (0.8 + Math.random() * 0.4));
204
+ }
205
+
206
+ export function cancelResponseBodyBestEffort(res: Response): void {
207
+ try {
208
+ const cancellation = res.body?.cancel();
209
+ if (cancellation) void cancellation.catch(() => {});
210
+ } catch {
211
+ // Cancellation is cleanup only; retries must not wait for or fail because of it.
212
+ }
213
+ }
214
+
215
+ export async function fetchWithAttemptDeadline(
216
+ url: string,
217
+ init: RequestInit,
218
+ timeoutMs: number,
219
+ abortSignal?: AbortSignal,
220
+ preferIdentityEncoding = false,
221
+ ): Promise<Response> {
222
+ const attemptTimeout = clearableDeadline(timeoutMs, abortSignal);
223
+ const headers = new Headers(init.headers);
224
+ if (preferIdentityEncoding && !headers.has("accept-encoding")) {
225
+ headers.set("accept-encoding", "identity");
226
+ }
227
+ try {
228
+ return await fetch(url, {
229
+ ...init,
230
+ headers,
231
+ signal: attemptTimeout.signal,
232
+ });
233
+ } finally {
234
+ // Only the header timer is cleared. The composed signal still contains the parent, so a
235
+ // caller abort after headers continue to cancel consumption of the returned response body.
236
+ attemptTimeout.clear();
237
+ }
238
+ }
239
+
240
+ export interface ResetRetryOptions {
241
+ abortSignal?: AbortSignal;
242
+ /** Short host/path label for the retry warn log (no secrets/query strings). */
243
+ label?: string;
244
+ attempts?: number;
245
+ }
246
+
247
+ export interface TransientRetryOptions extends ResetRetryOptions {
248
+ /** Test seam: per-attempt slow budget override (defaults to TRANSIENT_RETRY_SLOW_ATTEMPT_MS). */
249
+ slowAttemptMs?: number;
250
+ }
251
+
252
+ export interface TransientNonJsonRetryOptions extends TransientRetryOptions {
253
+ /**
254
+ * Provider-owned HTTP statuses whose non-JSON response body is known to be an
255
+ * intermittent edge failure. Empty/absent policy is intentionally unsupported:
256
+ * callers must opt in with an explicit closed list.
257
+ */
258
+ statuses: readonly number[];
259
+ }
260
+
261
+ export type UpstreamSendRecovery =
262
+ | "connection-reset"
263
+ | "transient-5xx"
264
+ | "transient-non-json";
265
+ type ReplayableFetch = (recovery?: UpstreamSendRecovery) => Promise<Response>;
266
+
267
+ /**
268
+ * Rejection thrown by the upstream retry helpers when the terminal attempt
269
+ * rejects after earlier attempts already produced credential-visible evidence:
270
+ * transient HTTP responses, or a connection reset after the request was read.
271
+ *
272
+ * That evidence proves the host and credential path were reached, so the
273
+ * failure must stay account-attributed even though the terminal promise looks
274
+ * like a transport rejection (issue #914 review: mixed 5xx/reset -> rejection
275
+ * must not be downgraded to the account-neutral pre-connection class). The
276
+ * original rejection is preserved as `cause` so its code and message stay
277
+ * inspectable. Extracted from PR #966 (Yuxin-Qiao) with attribution.
278
+ */
279
+ export class UpstreamRetryEvidenceError extends Error {
280
+ constructor(
281
+ public readonly transientStatuses: readonly number[],
282
+ cause: unknown,
283
+ /** True when a connection-reset retry already reached the origin. */
284
+ public readonly resetSeen = false,
285
+ ) {
286
+ const detail = cause instanceof Error ? cause.message : String(cause);
287
+ const kinds: string[] = [];
288
+ if (transientStatuses.length > 0) kinds.push("transient HTTP response(s)");
289
+ if (resetSeen) kinds.push("a credential-visible connection reset");
290
+ super(
291
+ kinds.length > 0
292
+ ? `upstream fetch failed after ${kinds.join(" and ")}: ${detail}`
293
+ : `upstream fetch failed: ${detail}`,
294
+ { cause },
295
+ );
296
+ this.name = "UpstreamRetryEvidenceError";
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Opt out of Bun's keep-alive pool after a connection-reset retry.
302
+ *
303
+ * Prefer the Bun fetch extension `keepalive: false` (transport-level) over
304
+ * relying on the hop-by-hop `Connection: close` header alone — Bun has ignored
305
+ * that header in past releases (oven-sh/bun#20492), so a header-only retry can
306
+ * still reuse the same half-closed pooled socket. Still set Connection: close
307
+ * as a belt-and-suspenders signal for intermediaries that honor it.
308
+ */
309
+ export function applyUpstreamRecoveryInit<T extends RequestInit>(
310
+ init: T,
311
+ recovery?: UpstreamSendRecovery,
312
+ ): T & { headers: Headers } {
313
+ const headers = new Headers(init.headers);
314
+ if (recovery !== "connection-reset" && recovery !== "transient-non-json") {
315
+ return { ...init, headers };
316
+ }
317
+ headers.set("connection", "close");
318
+ return { ...init, headers, keepalive: false };
319
+ }
320
+
321
+ /**
322
+ * Run `doFetch`, retrying only connection-reset-shaped rejections (see
323
+ * isConnectionResetError) with jittered backoff. The caller's thunk must be replay-safe
324
+ * (string body); every retry is logged so persistent resets stay visible.
325
+ */
326
+ export async function fetchWithResetRetry(
327
+ doFetch: ReplayableFetch,
328
+ opts: ResetRetryOptions = {},
329
+ firstRecovery?: UpstreamSendRecovery,
330
+ ): Promise<Response> {
331
+ const attempts = Math.max(1, opts.attempts ?? RESET_RETRY_MAX_ATTEMPTS);
332
+ let lastError: unknown;
333
+ let sawReset = false;
334
+ for (let attempt = 0; attempt < attempts; attempt++) {
335
+ if (opts.abortSignal?.aborted) throw abortError(opts.abortSignal);
336
+ try {
337
+ return await doFetch(attempt === 0 ? firstRecovery : "connection-reset");
338
+ } catch (err) {
339
+ if (opts.abortSignal?.aborted) throw err;
340
+ if (!isConnectionResetError(err)) {
341
+ // A reset that already reached the origin is credential-visible
342
+ // evidence: keep it attached so the terminal rejection cannot be
343
+ // downgraded to the pre-connection neutral class (#914 review).
344
+ if (sawReset) throw new UpstreamRetryEvidenceError([], err, true);
345
+ throw err;
346
+ }
347
+ if (attempt === attempts - 1) throw err;
348
+ sawReset = true;
349
+ lastError = err;
350
+ console.warn(
351
+ `[upstream-retry] connection reset${opts.label ? ` (${opts.label})` : ""} — retrying (${attempt + 2}/${attempts})`,
352
+ );
353
+ await sleepWithAbort(retryBackoffDelayMs(attempt, {
354
+ baseDelayMs: RESET_RETRY_BASE_DELAY_MS,
355
+ maxDelayMs: RESET_RETRY_MAX_DELAY_MS,
356
+ }), opts.abortSignal);
357
+ }
358
+ }
359
+ throw lastError ?? new Error("upstream fetch failed");
360
+ }
361
+
362
+ /**
363
+ * fetchWithResetRetry plus a transient-5xx status retry layer, PRE-STREAM only: a
364
+ * returned Response has by definition not been relayed to the client yet, so replaying
365
+ * the (string-body) request is safe. The failed attempt's body is cancelled before the
366
+ * retry; every returned response (ok, non-transient, aborted, slow, exhausted) keeps
367
+ * its body intact. Honors Retry-After via retryBackoffDelayMs.
368
+ *
369
+ * A failed attempt slower than the slow budget is returned as-is (slow-502 shape);
370
+ * note `opts.attempts` is shared with the inner reset layer (no caller passes it today).
371
+ */
372
+ export async function fetchWithTransientRetry(
373
+ doFetch: ReplayableFetch,
374
+ opts: TransientRetryOptions = {},
375
+ ): Promise<Response> {
376
+ const attempts = Math.max(1, opts.attempts ?? TRANSIENT_RETRY_MAX_ATTEMPTS);
377
+ const slowAttemptMs = opts.slowAttemptMs ?? TRANSIENT_RETRY_SLOW_ATTEMPT_MS;
378
+ const transientStatuses: number[] = [];
379
+ let attemptStart = Date.now();
380
+ let res = await fetchWithResetRetry(doFetch, opts);
381
+ for (let attempt = 0; attempt < attempts - 1; attempt++) {
382
+ if (res.ok || !isTransientUpstreamStatus(res.status)) return res;
383
+ if (opts.abortSignal?.aborted) return res;
384
+ if (Date.now() - attemptStart > slowAttemptMs) return res;
385
+ console.warn(
386
+ `[upstream-retry] transient ${res.status}${opts.label ? ` (${opts.label})` : ""} — retrying (${attempt + 2}/${attempts})`,
387
+ );
388
+ const delay = retryBackoffDelayMs(attempt, {
389
+ baseDelayMs: TRANSIENT_RETRY_BASE_DELAY_MS,
390
+ maxDelayMs: TRANSIENT_RETRY_MAX_DELAY_MS,
391
+ headers: res.headers,
392
+ });
393
+ cancelResponseBodyBestEffort(res);
394
+ await sleepWithAbort(delay, opts.abortSignal);
395
+ attemptStart = Date.now();
396
+ transientStatuses.push(res.status);
397
+ try {
398
+ res = await fetchWithResetRetry(doFetch, opts, "transient-5xx");
399
+ } catch (err) {
400
+ // Keep the prior 5xx evidence attached: the origin already responded, so
401
+ // this rejection is not pre-connection and must not classify as neutral.
402
+ throw new UpstreamRetryEvidenceError(transientStatuses, err);
403
+ }
404
+ }
405
+ return res;
406
+ }
407
+
408
+ function normalizedMediaType(response: Response): string {
409
+ return (response.headers.get("content-type") ?? "")
410
+ .split(";", 1)[0]!
411
+ .trim()
412
+ .toLowerCase();
413
+ }
414
+
415
+ function isJsonMediaType(mediaType: string): boolean {
416
+ return mediaType === "application/json" || mediaType.endsWith("+json");
417
+ }
418
+
419
+ async function isNonJsonRetryCandidate(
420
+ response: Response,
421
+ statuses: ReadonlySet<number>,
422
+ signal?: AbortSignal,
423
+ ): Promise<boolean> {
424
+ if (!statuses.has(response.status)) return false;
425
+ if (signal?.aborted) throw abortError(signal);
426
+
427
+ const mediaType = normalizedMediaType(response);
428
+ // The observed Zen edge failure is an HTML Method Not Allowed page. Avoid
429
+ // teeing that body just to rediscover what the media type already proves.
430
+ if (mediaType === "text/html" || mediaType === "application/xhtml+xml") return true;
431
+ // A protocol-shaped SSE error is not the incident covered by this recovery.
432
+ if (mediaType === "text/event-stream") return false;
433
+
434
+ try {
435
+ const inspected = await readBoundedResponseBody(response.clone(), {
436
+ signal,
437
+ fatalUtf8: true,
438
+ maxBytes: NON_JSON_INSPECTION_MAX_BYTES,
439
+ totalTimeoutMs: NON_JSON_INSPECTION_TIMEOUT_MS,
440
+ inactivityTimeoutMs: NON_JSON_INSPECTION_TIMEOUT_MS,
441
+ firstByteTimeoutMs: NON_JSON_INSPECTION_TIMEOUT_MS,
442
+ });
443
+ if (!inspected.displaySafe || inspected.truncated) return false;
444
+ const text = inspected.text.trim();
445
+ if (!text) return true;
446
+ try {
447
+ JSON.parse(text);
448
+ return false;
449
+ } catch {
450
+ // Only textual, missing, or falsely JSON-labelled bodies qualify. Binary
451
+ // payloads fail closed even when they are not parseable as JSON.
452
+ return mediaType === "" || mediaType.startsWith("text/") || isJsonMediaType(mediaType);
453
+ }
454
+ } catch (error) {
455
+ if (signal?.aborted) throw error;
456
+ return false;
457
+ }
458
+ }
459
+
460
+ /**
461
+ * Provider-opted pre-stream recovery for intermittent edge responses that use a
462
+ * deterministic-looking HTTP status but return an invalid/non-JSON body.
463
+ *
464
+ * The status list is registry-owned at the call site. A valid JSON error is
465
+ * always returned untouched, so genuine method/route mismatches are never hidden.
466
+ * Failed response bodies are cancelled before the abort-aware backoff, and the
467
+ * recovery send disables keep-alive to avoid reusing the same unhealthy edge.
468
+ */
469
+ export async function fetchWithTransientNonJsonRetry(
470
+ doFetch: ReplayableFetch,
471
+ opts: TransientNonJsonRetryOptions,
472
+ ): Promise<Response> {
473
+ const statuses = new Set(
474
+ opts.statuses.filter(status => Number.isInteger(status) && status >= 400 && status <= 599),
475
+ );
476
+ if (statuses.size === 0) return fetchWithResetRetry(doFetch, opts);
477
+
478
+ const attempts = Math.max(1, opts.attempts ?? TRANSIENT_RETRY_MAX_ATTEMPTS);
479
+ const slowAttemptMs = opts.slowAttemptMs ?? TRANSIENT_RETRY_SLOW_ATTEMPT_MS;
480
+ const transientStatuses: number[] = [];
481
+ let attemptStart = Date.now();
482
+ let response = await fetchWithResetRetry(doFetch, opts);
483
+
484
+ for (let attempt = 0; attempt < attempts - 1; attempt++) {
485
+ if (response.ok || opts.abortSignal?.aborted) return response;
486
+ if (Date.now() - attemptStart > slowAttemptMs) return response;
487
+ if (!await isNonJsonRetryCandidate(response, statuses, opts.abortSignal)) return response;
488
+
489
+ console.warn(
490
+ `[upstream-retry] transient non-JSON ${response.status}${opts.label ? ` (${opts.label})` : ""} — retrying (${attempt + 2}/${attempts})`,
491
+ );
492
+ const delay = retryBackoffDelayMs(attempt, {
493
+ baseDelayMs: TRANSIENT_RETRY_BASE_DELAY_MS,
494
+ maxDelayMs: TRANSIENT_RETRY_MAX_DELAY_MS,
495
+ headers: response.headers,
496
+ });
497
+ cancelResponseBodyBestEffort(response);
498
+ await sleepWithAbort(delay, opts.abortSignal);
499
+ attemptStart = Date.now();
500
+ transientStatuses.push(response.status);
501
+ try {
502
+ response = await fetchWithResetRetry(doFetch, opts, "transient-non-json");
503
+ } catch (error) {
504
+ throw new UpstreamRetryEvidenceError(transientStatuses, error);
505
+ }
506
+ }
507
+ return response;
508
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Cross-platform command launching (devlog 260715_cross_platform_audit/020).
3
+ *
4
+ * Windows npm installs expose CLIs as `.cmd` shims, and Node/Bun refuse shell-less
5
+ * `.cmd` spawns (CVE-2024-27980 hardening). Bare names like `spawn("claude")` also
6
+ * skip PATHEXT resolution entirely, so they ENOENT even when `claude.cmd` is on PATH.
7
+ * This module mirrors the battle-tested cross-spawn approach: resolve the real target
8
+ * via PATH×PATHEXT, launch `.exe` targets directly (argument boundaries preserved by
9
+ * the normal shell-less spawn), and route `.cmd`/`.bat` targets through
10
+ * `cmd.exe /d /s /c "<escaped line>"` with `windowsVerbatimArguments: true`.
11
+ */
12
+ import { existsSync } from "node:fs";
13
+ import { win32 } from "node:path";
14
+
15
+ const CMD_META = /([()\][%!^"`<>&|;, *?])/g;
16
+ /** cross-spawn parse.js: only npm local-bin shims get double escaping. */
17
+ const IS_CMD_SHIM = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
18
+
19
+ /** cross-spawn escape.js argument(): quote + escape one argument for cmd.exe /d /s /c. */
20
+ export function escapeCmdArg(arg: string, doubleEscape = false): string {
21
+ let out = String(arg).replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1");
22
+ out = `"${out}"`.replace(CMD_META, "^$1");
23
+ return doubleEscape ? out.replace(CMD_META, "^$1") : out;
24
+ }
25
+
26
+ /** cross-spawn escape.js command(): escape the command token itself (no quoting). */
27
+ export function escapeCmdCommand(command: string): string {
28
+ return command.replace(CMD_META, "^$1");
29
+ }
30
+
31
+ export interface ResolveDeps {
32
+ env?: Record<string, string | undefined>;
33
+ exists?: (path: string) => boolean;
34
+ }
35
+
36
+ /**
37
+ * Resolve a bare command name to its first PATH×PATHEXT hit (win32 semantics).
38
+ * Commands that already carry an extension, a separator, or an absolute prefix are
39
+ * returned unchanged; unresolvable names fall back unchanged (spawn will surface it).
40
+ */
41
+ export function resolveWindowsCommand(command: string, deps: ResolveDeps = {}): string {
42
+ const env = deps.env ?? process.env;
43
+ const exists = deps.exists ?? existsSync;
44
+ if (win32.extname(command) || command.includes("\\") || command.includes("/") || win32.isAbsolute(command)) {
45
+ return command;
46
+ }
47
+ // Windows environment variables are case-insensitive, and a spawned child can
48
+ // arrive with `Path`, `PATH`, or both depending on who built its env. Reading
49
+ // only two fixed spellings silently resolved against the wrong list once a
50
+ // caller added a second casing, so match however the key is spelled.
51
+ const lookup = (name: string): string | undefined => {
52
+ const direct = env[name] ?? env[name.toUpperCase()] ?? env[name.toLowerCase()];
53
+ if (direct !== undefined) return direct;
54
+ const key = Object.keys(env).find(k => k.toLowerCase() === name.toLowerCase());
55
+ return key ? env[key] : undefined;
56
+ };
57
+ const exts = (lookup("PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
58
+ for (const dir of (lookup("PATH") ?? "").split(win32.delimiter).filter(Boolean)) {
59
+ for (const ext of exts) {
60
+ const candidate = win32.join(dir, command + ext.toLowerCase());
61
+ if (exists(candidate)) return candidate;
62
+ }
63
+ }
64
+ return command;
65
+ }
66
+
67
+ export interface SpawnInvocation {
68
+ file: string;
69
+ args: string[];
70
+ options: { windowsVerbatimArguments?: boolean };
71
+ }
72
+
73
+ /**
74
+ * Platform-safe invocation preserving argument boundaries (cross-spawn parse.js).
75
+ * POSIX: passthrough. win32 `.exe`: resolved direct spawn. win32 `.cmd`/`.bat`:
76
+ * `ComSpec /d /s /c "<escaped command line>"` with verbatim args; npm local-bin
77
+ * shims get cross-spawn's double escaping, all other batch targets single.
78
+ */
79
+ export function commandInvocation(
80
+ command: string,
81
+ args: readonly string[],
82
+ platform: NodeJS.Platform = process.platform,
83
+ deps: ResolveDeps = {},
84
+ ): SpawnInvocation {
85
+ if (platform !== "win32") return { file: command, args: [...args], options: {} };
86
+ const resolved = resolveWindowsCommand(command, deps);
87
+ if (!/\.(cmd|bat)$/i.test(resolved)) return { file: resolved, args: [...args], options: {} };
88
+ const env = deps.env ?? process.env;
89
+ const doubleEscape = IS_CMD_SHIM.test(resolved);
90
+ const line = [escapeCmdCommand(resolved), ...args.map(a => escapeCmdArg(a, doubleEscape))].join(" ");
91
+ return {
92
+ file: env.ComSpec ?? "cmd.exe",
93
+ args: ["/d", "/s", "/c", `"${line}"`],
94
+ options: { windowsVerbatimArguments: true },
95
+ };
96
+ }
97
+
98
+ /**
99
+ * `sh -c <command>` analog per platform. The configured command string is passed
100
+ * VERBATIM in content; on win32 it gets the outer quotes `/s` requires, so
101
+ * `"C:\Program Files\x.exe" --json` runs as `cmd.exe /d /s /c ""C:\Program Files\x.exe" --json"`.
102
+ * Contract: the command is platform-native shell syntax (sh on POSIX, CMD on Windows).
103
+ */
104
+ export function shellInvocation(
105
+ command: string,
106
+ platform: NodeJS.Platform = process.platform,
107
+ env: Record<string, string | undefined> = process.env,
108
+ ): SpawnInvocation {
109
+ if (platform !== "win32") return { file: "sh", args: ["-c", command], options: {} };
110
+ return {
111
+ file: env.ComSpec ?? "cmd.exe",
112
+ args: ["/d", "/s", "/c", `"${command}"`],
113
+ options: { windowsVerbatimArguments: true },
114
+ };
115
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Windows path → batch-safe env indirection.
3
+ *
4
+ * cmd.exe parses .cmd files in the console's OEM codepage (CP949/GBK/CP437…), NOT UTF-8.
5
+ * Any absolute path we bake into a generated batch file breaks for users whose profile
6
+ * directory contains non-ASCII characters (e.g. Korean/Chinese usernames), because the
7
+ * UTF-8 bytes we wrote are decoded as mojibake at parse time. Instead of baking the
8
+ * profile prefix literally, rewrite it as a `%USERPROFILE%`-style token: cmd expands the
9
+ * variable natively at parse time in the correct codepage, so the non-ASCII prefix never
10
+ * has to survive a file-encoding round trip.
11
+ */
12
+
13
+ export type WindowsEnvMap = Record<string, string | undefined>;
14
+
15
+ /** Ordered by specificity; longest resolved prefix wins regardless of this order. */
16
+ const INDIRECTION_VARS = ["LOCALAPPDATA", "APPDATA", "USERPROFILE"] as const;
17
+
18
+ /** True when `prefix` matches a leading whole-component span of `path` (case-insensitive). */
19
+ function isComponentPrefix(path: string, prefix: string): boolean {
20
+ if (path.length < prefix.length) return false;
21
+ if (path.slice(0, prefix.length).toLowerCase() !== prefix.toLowerCase()) return false;
22
+ if (path.length === prefix.length) return true;
23
+ const next = path[prefix.length];
24
+ return next === "\\" || next === "/";
25
+ }
26
+
27
+ /**
28
+ * Split a Windows path into an env-token prefix and the remaining literal suffix.
29
+ * Returns `token: ""` (and the full path as `rest`) when no known prefix applies.
30
+ */
31
+ export function splitWindowsEnvPrefix(path: string, env: WindowsEnvMap = process.env): { token: string; rest: string } {
32
+ let best: { name: string; value: string } | null = null;
33
+ for (const name of INDIRECTION_VARS) {
34
+ const raw = env[name]?.trim();
35
+ if (!raw) continue;
36
+ const value = raw.replace(/[\\/]+$/, "");
37
+ if (!value || !isComponentPrefix(path, value)) continue;
38
+ if (!best || value.length > best.value.length) best = { name, value };
39
+ }
40
+ if (!best) return { token: "", rest: path };
41
+ return { token: `%${best.name}%`, rest: path.slice(best.value.length) };
42
+ }
43
+
44
+ /**
45
+ * Render a single path for embedding in a batch `set "X=…"` line: env-token prefix kept
46
+ * verbatim (cmd must expand it), remaining literal suffix run through the caller's batch
47
+ * escaping (which doubles `%` and would otherwise destroy the token itself).
48
+ */
49
+ export function windowsEnvIndirectBatchValue(
50
+ path: string,
51
+ escape: (value: string) => string,
52
+ env: WindowsEnvMap = process.env,
53
+ ): string {
54
+ const { token, rest } = splitWindowsEnvPrefix(path, env);
55
+ return token + escape(rest);
56
+ }
57
+
58
+ /** Same as {@link windowsEnvIndirectBatchValue} for `;`-separated path lists (PATH). */
59
+ export function windowsEnvIndirectBatchPathList(
60
+ value: string,
61
+ escape: (value: string) => string,
62
+ env: WindowsEnvMap = process.env,
63
+ ): string {
64
+ return value
65
+ .split(";")
66
+ .map(part => (part ? windowsEnvIndirectBatchValue(part, escape, env) : part))
67
+ .join(";");
68
+ }