@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,283 @@
1
+ /**
2
+ * Resolve the effective Windows token to the locale-independent SID form that
3
+ * icacls accepts ("*S-1-..."). Environment values such as USERDOMAIN are not
4
+ * an authority for the current token: on workgroup machines USERDOMAIN may be
5
+ * the literal WORKGROUP even though the account belongs to the local computer.
6
+ *
7
+ * There is deliberately NO name-shaped fallback. A `DOMAIN\User` string has a
8
+ * valid shape, but shape is not evidence that the account is the current
9
+ * token's subject, and both environment variables are writable by whatever
10
+ * launched us. A wrong principal here is not cosmetic: `runIcacls` grants it
11
+ * Full Control and then removes inheritance, so a wrong grant either leaves a
12
+ * different account holding the secret or strands the file with no usable ACE.
13
+ * When the SID cannot be resolved, the caller declines to touch the ACL at all.
14
+ *
15
+ * Budget caveat: callers pass their REMAINING harden budget, which becomes the
16
+ * child process timeout. Trusted-executable resolution (a `GetSystemDirectoryW`
17
+ * FFI call) and spawn setup happen before that timeout starts, and the async
18
+ * timer only arms once `Bun.spawn` returns. Both are small in practice, but the
19
+ * lookup is not bounded by the deadline to the microsecond. Tightening that
20
+ * would mean passing an absolute deadline through the runner interface.
21
+ */
22
+
23
+ import { resolveTrustedWindowsPowerShellExe } from "./windows-elevation";
24
+
25
+ const SID_PATTERN = /^S-1-(?:\d+-)+\d+$/i;
26
+ const SID_EXPRESSION =
27
+ "[System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value";
28
+
29
+ export interface WindowsPrincipalLookupResult {
30
+ success: boolean;
31
+ exitCode: number | null;
32
+ timedOut: boolean;
33
+ stdout: string;
34
+ }
35
+
36
+ export type WindowsPrincipalRunner = (
37
+ timeoutMs: number,
38
+ ) => WindowsPrincipalLookupResult;
39
+
40
+ export type AsyncWindowsPrincipalRunner = (
41
+ timeoutMs: number,
42
+ ) => Promise<WindowsPrincipalLookupResult>;
43
+
44
+ const POWERSHELL_ARGS = [
45
+ "-NoLogo",
46
+ "-NoProfile",
47
+ "-NonInteractive",
48
+ "-WindowStyle",
49
+ "Hidden",
50
+ "-Command",
51
+ SID_EXPRESSION,
52
+ ] as const;
53
+
54
+ function windowsPrincipalPowerShellCommand(): string[] {
55
+ return [resolveTrustedWindowsPowerShellExe(), ...POWERSHELL_ARGS];
56
+ }
57
+
58
+ /** Test-only readback of the exact trusted executable and static arguments. */
59
+ export function windowsPrincipalPowerShellCommandForTests(): string[] {
60
+ return windowsPrincipalPowerShellCommand();
61
+ }
62
+
63
+ function defaultWindowsPrincipalRunner(timeoutMs: number): WindowsPrincipalLookupResult {
64
+ const result = Bun.spawnSync(windowsPrincipalPowerShellCommand(), {
65
+ stdin: "ignore",
66
+ stdout: "pipe",
67
+ stderr: "ignore",
68
+ timeout: Math.max(1, timeoutMs),
69
+ windowsHide: true,
70
+ });
71
+ return {
72
+ success: result.success,
73
+ exitCode: result.exitCode,
74
+ timedOut: result.exitedDueToTimeout ?? false,
75
+ stdout: result.stdout ? result.stdout.toString() : "",
76
+ };
77
+ }
78
+
79
+ async function defaultAsyncWindowsPrincipalRunner(
80
+ timeoutMs: number,
81
+ ): Promise<WindowsPrincipalLookupResult> {
82
+ const proc = Bun.spawn(windowsPrincipalPowerShellCommand(), {
83
+ stdin: "ignore",
84
+ stdout: "pipe",
85
+ stderr: "ignore",
86
+ windowsHide: true,
87
+ });
88
+ let timedOut = false;
89
+ const timer = setTimeout(() => {
90
+ timedOut = true;
91
+ try { proc.kill(); } catch { /* already exited */ }
92
+ }, Math.max(1, timeoutMs));
93
+ let exitCode: number | null = null;
94
+ try {
95
+ exitCode = await proc.exited;
96
+ } finally {
97
+ clearTimeout(timer);
98
+ }
99
+ const stdout = proc.stdout
100
+ ? await new Response(proc.stdout).text().catch(() => "")
101
+ : "";
102
+ return {
103
+ success: !timedOut && exitCode === 0,
104
+ exitCode: timedOut ? null : exitCode,
105
+ timedOut,
106
+ stdout,
107
+ };
108
+ }
109
+
110
+ let principalRunner: WindowsPrincipalRunner = defaultWindowsPrincipalRunner;
111
+ let asyncPrincipalRunner: AsyncWindowsPrincipalRunner = defaultAsyncWindowsPrincipalRunner;
112
+ let cachedPrincipal: string | null = null;
113
+ let asyncLookupInFlight: Promise<string> | null = null;
114
+
115
+ /**
116
+ * POSIX CI drives the Windows ACL branch through `setPlatformForTests("win32")`,
117
+ * on hosts that have neither System32 nor PowerShell. Those runs need SOME
118
+ * principal, so this seam supplies a synthetic one.
119
+ *
120
+ * It lives here rather than in `windows-secret-acl.ts` for one reason that is
121
+ * not cosmetic: an explicitly injected runner must be able to beat it. When the
122
+ * synthetic value was chosen first, in the ACL module, a test could not inject a
123
+ * lookup FAILURE on POSIX at all — so the fail-closed and memo-isolation cases
124
+ * were guarded with `if (process.platform !== "win32") return;` and never ran
125
+ * outside Windows. Resolution order below is what makes those cases executable
126
+ * on every runner.
127
+ */
128
+ let syntheticPrincipalForTests: string | null = null;
129
+
130
+ /**
131
+ * Test seam: supply the principal used when no runner was injected and the host
132
+ * is not really Windows. Pass null to disable.
133
+ */
134
+ export function setSyntheticWindowsPrincipalForTests(principal: string | null): void {
135
+ syntheticPrincipalForTests = principal;
136
+ cachedPrincipal = null;
137
+ }
138
+
139
+ /** True when an explicit runner override is installed and must take precedence. */
140
+ function hasSyncRunnerOverride(): boolean {
141
+ return principalRunner !== defaultWindowsPrincipalRunner;
142
+ }
143
+
144
+ function hasAsyncRunnerOverride(): boolean {
145
+ return asyncPrincipalRunner !== defaultAsyncWindowsPrincipalRunner;
146
+ }
147
+
148
+ function identityError(reason: string): NodeJS.ErrnoException {
149
+ const error = new Error(`Windows effective-account SID lookup ${reason}`) as NodeJS.ErrnoException;
150
+ // Keep identity lookup failures distinct from icacls timeouts. In particular,
151
+ // they must not populate windows-secret-acl's destination timeout memo.
152
+ error.code = "EACLIDENTITY";
153
+ return error;
154
+ }
155
+
156
+ function principalFromResult(result: WindowsPrincipalLookupResult): string {
157
+ if (!result.success) {
158
+ throw identityError(result.timedOut
159
+ ? "timed out"
160
+ : `exited ${result.exitCode ?? "null"}`);
161
+ }
162
+ const sid = result.stdout.trim();
163
+ if (!SID_PATTERN.test(sid)) {
164
+ throw identityError(sid ? "returned an invalid SID" : "returned an empty SID");
165
+ }
166
+ return `*${sid.toUpperCase()}`;
167
+ }
168
+
169
+ /** Resolve and process-cache the effective token SID for synchronous ACL paths. */
170
+ export function resolveCurrentWindowsPrincipal(timeoutMs: number): string {
171
+ // Order matters: an explicitly injected runner outranks the synthetic value,
172
+ // so a test can inject a FAILURE on a POSIX host. See the seam comment above.
173
+ if (hasSyncRunnerOverride()) {
174
+ if (cachedPrincipal) return cachedPrincipal;
175
+ if (timeoutMs <= 0) throw identityError("had no remaining deadline");
176
+ let overridden: WindowsPrincipalLookupResult;
177
+ try {
178
+ overridden = principalRunner(timeoutMs);
179
+ } catch {
180
+ throw identityError("could not start");
181
+ }
182
+ const principal = principalFromResult(overridden);
183
+ cachedPrincipal = principal;
184
+ return principal;
185
+ }
186
+ if (cachedPrincipal) return cachedPrincipal;
187
+ if (syntheticPrincipalForTests) return syntheticPrincipalForTests;
188
+ if (timeoutMs <= 0) throw identityError("had no remaining deadline");
189
+ let result: WindowsPrincipalLookupResult;
190
+ try {
191
+ result = principalRunner(timeoutMs);
192
+ } catch {
193
+ throw identityError("could not start");
194
+ }
195
+ const principal = principalFromResult(result);
196
+ cachedPrincipal = principal;
197
+ return principal;
198
+ }
199
+
200
+ async function waitForExistingLookup(
201
+ lookup: Promise<string>,
202
+ timeoutMs: number,
203
+ ): Promise<string> {
204
+ if (timeoutMs <= 0) throw identityError("had no remaining deadline");
205
+ let timer: ReturnType<typeof setTimeout> | undefined;
206
+ try {
207
+ return await Promise.race([
208
+ lookup,
209
+ new Promise<never>((_, reject) => {
210
+ timer = setTimeout(
211
+ () => reject(identityError("timed out while awaiting the shared lookup")),
212
+ Math.max(1, timeoutMs),
213
+ );
214
+ }),
215
+ ]);
216
+ } finally {
217
+ if (timer) clearTimeout(timer);
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Async counterpart. Concurrent callers share one owned child lookup; a later
223
+ * caller may exhaust its own budget without cancelling the lookup owned by the
224
+ * first caller. The first caller owns that child and its process timeout; a
225
+ * later, longer budget deliberately does not extend an already-running child.
226
+ */
227
+ export async function resolveCurrentWindowsPrincipalAsync(timeoutMs: number): Promise<string> {
228
+ const overridden = hasAsyncRunnerOverride();
229
+ if (cachedPrincipal) return cachedPrincipal;
230
+ if (asyncLookupInFlight) return waitForExistingLookup(asyncLookupInFlight, timeoutMs);
231
+ // Same precedence rule as the sync path: an injected runner beats the synthetic.
232
+ if (!overridden && syntheticPrincipalForTests) return syntheticPrincipalForTests;
233
+ if (timeoutMs <= 0) throw identityError("had no remaining deadline");
234
+
235
+ const lookup = (async (): Promise<string> => {
236
+ let result: WindowsPrincipalLookupResult;
237
+ try {
238
+ result = await asyncPrincipalRunner(timeoutMs);
239
+ } catch {
240
+ throw identityError("could not start");
241
+ }
242
+ const principal = principalFromResult(result);
243
+ cachedPrincipal = principal;
244
+ return principal;
245
+ })();
246
+ asyncLookupInFlight = lookup;
247
+ try {
248
+ return await lookup;
249
+ } finally {
250
+ if (asyncLookupInFlight === lookup) asyncLookupInFlight = null;
251
+ }
252
+ }
253
+
254
+ /** Test seam: replace the sync resolver process and clear its successful cache. */
255
+ export function setWindowsPrincipalRunnerForTests(
256
+ runner: WindowsPrincipalRunner | null,
257
+ ): void {
258
+ if (asyncLookupInFlight) {
259
+ throw new Error("Cannot replace the Windows principal runner while a lookup is in flight.");
260
+ }
261
+ principalRunner = runner ?? defaultWindowsPrincipalRunner;
262
+ cachedPrincipal = null;
263
+ }
264
+
265
+ /** Test seam: replace the async resolver process and clear its successful cache. */
266
+ export function setAsyncWindowsPrincipalRunnerForTests(
267
+ runner: AsyncWindowsPrincipalRunner | null,
268
+ ): void {
269
+ if (asyncLookupInFlight) {
270
+ throw new Error("Cannot replace the Windows principal runner while a lookup is in flight.");
271
+ }
272
+ asyncPrincipalRunner = runner ?? defaultAsyncWindowsPrincipalRunner;
273
+ cachedPrincipal = null;
274
+ }
275
+
276
+ /** Test seam: clear only process-local principal state. */
277
+ export function resetWindowsPrincipalForTests(): void {
278
+ if (asyncLookupInFlight) {
279
+ throw new Error("Cannot reset the Windows principal while a lookup is in flight.");
280
+ }
281
+ cachedPrincipal = null;
282
+ syntheticPrincipalForTests = null;
283
+ }
@@ -0,0 +1,402 @@
1
+ /**
2
+ * WinSW-backed native Windows service (opt-in via `rmx service install --native`).
3
+ *
4
+ * Design (devlog/_plan/260720_windows_service/060):
5
+ * - WinSW 2.12.0 NET461 build, downloaded on first native install and verified against
6
+ * a pinned SHA-256 (fail-closed: mismatch deletes the file and throws). The binary is
7
+ * NOT bundled in npm; offline installs get an explicit manual-placement hint.
8
+ * - Runs as the USER account (v2 `<serviceaccount>` domain/user/allowservicelogon —
9
+ * never LocalSystem: the ACL hardening in windows-secret-acl grants only the user SID,
10
+ * so a SYSTEM service could not read the token file, and SYSTEM-owned writes would
11
+ * change the user-access contract).
12
+ * - No password in XML: `winsw install /p` prompts on the console (stdin inherit).
13
+ * - Absolute paths only — no %USERPROFILE% indirection (that exists for OEM-codepage
14
+ * batch parsing; WinSW XML is Unicode).
15
+ */
16
+ import { createHash } from "node:crypto";
17
+ import { execFileSync } from "node:child_process";
18
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
19
+ import { homedir } from "node:os";
20
+ import { join, resolve } from "node:path";
21
+ import { expandUserPath, getConfigDir, loadConfig } from "../config";
22
+ import { recordOwnedConfigPath } from "./config-ownership";
23
+ import { BUN_RUNTIME_PATH_ENV, BUN_RUNTIME_SOURCE_ENV, durableBunRuntime } from "./bun-runtime";
24
+ import type { BunRuntimeSource } from "./bun-runtime";
25
+ import {
26
+ SERVICE_PROVIDER_ENV_FILE_ENV,
27
+ serviceApiTokenFilePath,
28
+ serviceProviderEnvFilePath,
29
+ } from "./service-secrets";
30
+
31
+ export const WINSW_VERSION = "2.12.0";
32
+ export const WINSW_URL = `https://github.com/winsw/winsw/releases/download/v${WINSW_VERSION}/WinSW.NET461.exe`;
33
+ /** SHA-256 of the official v2.12.0 WinSW.NET461.exe release asset (655872 bytes). */
34
+ export const WINSW_SHA256 = "b5066b7bbdfba1293e5d15cda3caaea88fbeab35bd5b38c41c913d492aadfc4f";
35
+
36
+ /** SCM service id — distinct from the Task Scheduler task name (opencodex-proxy). */
37
+ export const WINSW_SERVICE_ID = "opencodex-proxy-native";
38
+
39
+ export function winswDir(): string {
40
+ return join(getConfigDir(), "winsw");
41
+ }
42
+
43
+ /** WinSW discovers its config as the same-basename XML next to the exe. */
44
+ export function winswExePath(): string {
45
+ return join(winswDir(), `${WINSW_SERVICE_ID}.exe`);
46
+ }
47
+
48
+ export function winswXmlPath(): string {
49
+ return join(winswDir(), `${WINSW_SERVICE_ID}.xml`);
50
+ }
51
+
52
+ function winswLogDir(): string {
53
+ return getConfigDir();
54
+ }
55
+
56
+ function xmlEscape(value: string): string {
57
+ return value
58
+ .replace(/&/g, "&amp;")
59
+ .replace(/</g, "&lt;")
60
+ .replace(/>/g, "&gt;")
61
+ .replace(/"/g, "&quot;")
62
+ .replace(/'/g, "&apos;");
63
+ }
64
+
65
+ function currentCodexHomeAbsolute(): string {
66
+ const raw = process.env.CODEX_HOME?.trim();
67
+ return raw ? resolve(expandUserPath(raw)) : join(homedir(), ".codex");
68
+ }
69
+
70
+ export interface WinswEntry {
71
+ bun: string;
72
+ /** Provenance of `bun`, resolved together with it so the two can never disagree. */
73
+ bunRuntimeSource: BunRuntimeSource;
74
+ cli: string;
75
+ }
76
+
77
+ /**
78
+ * Build the WinSW v2 XML. Never embeds the API token value — the app loads it from
79
+ * OCX_API_TOKEN_FILE at startup (cli handleStart). PATH is baked for parity with the
80
+ * Task Scheduler wrapper / launchd / systemd: the SCM service environment lacks the
81
+ * user's interactive PATH, which provider subprocesses may need.
82
+ */
83
+ export function buildWinswXml(entry: WinswEntry, env: NodeJS.ProcessEnv = process.env, port?: number): string {
84
+ const domain = env.USERDOMAIN?.trim() || ".";
85
+ const user = env.USERNAME?.trim() || "";
86
+ const listenPort = (() => {
87
+ if (typeof port === "number" && Number.isFinite(port) && port > 0 && port <= 65535) return Math.trunc(port);
88
+ const baked = env.OCX_BAKE_PORT?.trim();
89
+ if (baked && /^\d+$/.test(baked)) {
90
+ const n = Number(baked);
91
+ if (n > 0 && n <= 65535) return n;
92
+ }
93
+ return loadConfig().port ?? 10100;
94
+ })();
95
+ // Services never bake `--port 0` (parsePortOption rejects it); treat as default.
96
+ const safeListenPort = listenPort > 0 && listenPort <= 65535 ? listenPort : 10100;
97
+ // SCM services do not inherit the interactive user environment (#764). Bake:
98
+ // - OPENCODEX_HOME so file-backed admin auth (`admin-api-token`) resolves
99
+ // - OPENCODEX_ACL_TIMEOUT_MS when set (not a secret)
100
+ // Never embed OPENCODEX_ADMIN_AUTH_TOKEN or OPENCODEX_API_AUTH_TOKEN values in XML —
101
+ // those stay file-pointer / generated-file only (uninstall retains the XML).
102
+ const aclTimeout = env.OPENCODEX_ACL_TIMEOUT_MS?.trim();
103
+ const envLines = [
104
+ ` <env name="OCX_SERVICE" value="1"/>`,
105
+ ` <env name="${BUN_RUNTIME_SOURCE_ENV}" value="${xmlEscape(entry.bunRuntimeSource)}"/>`,
106
+ ` <env name="${BUN_RUNTIME_PATH_ENV}" value="${xmlEscape(entry.bun)}"/>`,
107
+ ` <env name="OCX_API_TOKEN_FILE" value="${xmlEscape(serviceApiTokenFilePath())}"/>`,
108
+ ` <env name="${SERVICE_PROVIDER_ENV_FILE_ENV}" value="${xmlEscape(serviceProviderEnvFilePath())}"/>`,
109
+ ` <env name="PATH" value="${xmlEscape(env.PATH ?? "")}"/>`,
110
+ env.CODEX_HOME?.trim() ? ` <env name="CODEX_HOME" value="${xmlEscape(currentCodexHomeAbsolute())}"/>` : null,
111
+ ` <env name="OPENCODEX_HOME" value="${xmlEscape(getConfigDir())}"/>`,
112
+ aclTimeout ? ` <env name="OPENCODEX_ACL_TIMEOUT_MS" value="${xmlEscape(aclTimeout)}"/>` : null,
113
+ ].filter((line): line is string => Boolean(line));
114
+ return `<?xml version="1.0" encoding="UTF-8"?>
115
+ <service>
116
+ <id>${WINSW_SERVICE_ID}</id>
117
+ <name>Remodex Proxy (native)</name>
118
+ <description>Remodex proxy running as a native Windows service (windowless, starts at boot).</description>
119
+ <executable>${xmlEscape(entry.bun)}</executable>
120
+ <arguments>${xmlEscape(`"${entry.cli}" start --port ${safeListenPort}`)}</arguments>
121
+ ${envLines.join("\n")}
122
+ <logpath>${xmlEscape(winswLogDir())}</logpath>
123
+ <log mode="roll-by-size">
124
+ <sizeThreshold>10240</sizeThreshold>
125
+ <keepFiles>4</keepFiles>
126
+ </log>
127
+ <onfailure action="restart" delay="5 sec"/>
128
+ <stoptimeout>20 sec</stoptimeout>
129
+ <serviceaccount>
130
+ <domain>${xmlEscape(domain)}</domain>
131
+ <user>${xmlEscape(user)}</user>
132
+ <allowservicelogon>true</allowservicelogon>
133
+ </serviceaccount>
134
+ </service>
135
+ `;
136
+ }
137
+
138
+ export function sha256Hex(data: Uint8Array | Buffer): string {
139
+ return createHash("sha256").update(data).digest("hex");
140
+ }
141
+
142
+ /**
143
+ * Ensure the pinned WinSW binary exists locally; download + verify on first use.
144
+ * Fail-closed: any hash mismatch deletes the file and throws.
145
+ */
146
+ export async function ensureWinswBinary(fetchImpl: typeof fetch = fetch): Promise<string> {
147
+ const exe = winswExePath();
148
+ if (existsSync(exe)) {
149
+ const digest = sha256Hex(readFileSync(exe));
150
+ if (digest === WINSW_SHA256) return exe;
151
+ unlinkSync(exe);
152
+ console.warn("⚠️ Existing WinSW binary failed hash verification; re-downloading.");
153
+ }
154
+ recordOwnedConfigPath(getConfigDir(), winswDir());
155
+ if (!existsSync(winswDir())) mkdirSync(winswDir(), { recursive: true });
156
+ let body: ArrayBuffer;
157
+ try {
158
+ const res = await fetchImpl(WINSW_URL);
159
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
160
+ body = await res.arrayBuffer();
161
+ } catch (err) {
162
+ throw new Error(
163
+ `Failed to download WinSW ${WINSW_VERSION} (${err instanceof Error ? err.message : String(err)}). ` +
164
+ `Offline? Place the official WinSW.NET461.exe (v${WINSW_VERSION}) at ${exe} and retry.`,
165
+ );
166
+ }
167
+ const bytes = Buffer.from(body);
168
+ const digest = sha256Hex(bytes);
169
+ if (digest !== WINSW_SHA256) {
170
+ throw new Error(
171
+ `WinSW download failed SHA-256 verification (got ${digest}, expected ${WINSW_SHA256}). ` +
172
+ "Refusing to install an unverified service binary.",
173
+ );
174
+ }
175
+ writeFileSync(exe, bytes);
176
+ return exe;
177
+ }
178
+
179
+ function runWinsw(args: string[]): string {
180
+ return execFileSync(winswExePath(), args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true }).trim();
181
+ }
182
+
183
+ /** `install /p` prompts for the service-account password on the console — stdin must be inherited. */
184
+ function runWinswInteractive(args: string[]): void {
185
+ if (!process.stdin.isTTY) {
186
+ throw new Error(
187
+ "WinSW install requires an interactive console to prompt for the service account password. "
188
+ + "Run `rmx service install --native` from an elevated Command Prompt or PowerShell window, not a hidden or piped session.",
189
+ );
190
+ }
191
+ execFileSync(winswExePath(), args, { stdio: "inherit" });
192
+ }
193
+
194
+ function scQc(): string {
195
+ const sc = join(process.env.SystemRoot ?? "C:\\Windows", "System32", "sc.exe");
196
+ return execFileSync(existsSync(sc) ? sc : "sc.exe", ["qc", WINSW_SERVICE_ID], {
197
+ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true,
198
+ });
199
+ }
200
+
201
+ export type WinswStatus = "started" | "stopped" | "nonexistent" | "unknown";
202
+
203
+ /** WinSW v2 `status` prints exactly Started / Stopped / NonExistent. */
204
+ export function parseWinswStatus(output: string): WinswStatus {
205
+ const normalized = output.trim().toLowerCase();
206
+ if (normalized.includes("nonexistent")) return "nonexistent";
207
+ if (normalized.includes("started")) return "started";
208
+ if (normalized.includes("stopped")) return "stopped";
209
+ // Anything else is an unparseable result — NOT proof of absence. Callers must
210
+ // fail closed: only an exact NonExistent may skip stop/uninstall.
211
+ return "unknown";
212
+ }
213
+
214
+ export function statusWinswRaw(): WinswStatus {
215
+ if (existsSync(winswExePath())) {
216
+ try {
217
+ return parseWinswStatus(runWinsw(["status"]));
218
+ } catch {
219
+ // The query itself failed (access denied, damaged/quarantined exe, ...). Treat the
220
+ // service as possibly-installed so lifecycle operations still attempt stop/uninstall
221
+ // instead of skipping a live SCM service that would keep respawning the proxy.
222
+ return "unknown";
223
+ }
224
+ }
225
+ // A missing exe does NOT prove the SCM registration is gone (quarantined binary,
226
+ // partial uninstall): a stale opencodex-proxy-native registration can outlive it.
227
+ // Confirm absence against the SCM itself before reporting "nonexistent".
228
+ if (process.platform !== "win32") return "nonexistent";
229
+ const probe = probeScmRegistration();
230
+ // probe === "error": the SCM could not be queried — fail closed, never claim absence.
231
+ return probe === false ? "nonexistent" : "unknown";
232
+ }
233
+
234
+ /**
235
+ * Probe the SCM for the native service registration.
236
+ * Returns true (registered), false (confirmed absent — exit 1060 only), or "error"
237
+ * (query itself failed: access denied, sc.exe missing, ...). Only a confirmed
238
+ * ERROR_SERVICE_DOES_NOT_EXIST may prove absence to lifecycle callers.
239
+ */
240
+ export function probeScmRegistration(run: () => string = queryScmForService): boolean | "error" {
241
+ try {
242
+ run();
243
+ return true;
244
+ } catch (err) {
245
+ const e = err as { status?: number | null; stderr?: string | Buffer | null; stdout?: string | Buffer | null; message?: string };
246
+ // sc.exe does not reliably channel the 1060 line — it can land on stderr OR stdout
247
+ // depending on the host — so scan every captured stream and the error message.
248
+ const text = [e.stderr, e.stdout, e.message]
249
+ .map(v => (typeof v === "string" ? v : ""))
250
+ .join("\n");
251
+ // ERROR_SERVICE_DOES_NOT_EXIST: the numeric identifier 1060 is locale-invariant
252
+ // (FALHA 1060 pt-BR, localized ko output, English FAILED 1060). The query is a
253
+ // fixed `sc.exe query <service>` so a standalone 1060 in its output is proof of
254
+ // absence. Bun may deliver e.status as 36 (1060 & 0xff) — status 36 ALONE is
255
+ // NOT accepted (collides with any status ≡ 36 mod 256); the textual 1060 is
256
+ // required corroboration and covers those hosts.
257
+ if (e.status === 1060 || /\b1060\b/.test(text)) return false;
258
+ return "error";
259
+ }
260
+ }
261
+
262
+ function scExePath(): string {
263
+ const sc = join(process.env.SystemRoot ?? "C:\\Windows", "System32", "sc.exe");
264
+ return existsSync(sc) ? sc : "sc.exe";
265
+ }
266
+
267
+ function queryScmForService(): string {
268
+ return execFileSync(scExePath(), ["query", WINSW_SERVICE_ID], {
269
+ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true,
270
+ });
271
+ }
272
+
273
+ /**
274
+ * Verify the installed SCM service runs as the intended user, not LocalSystem (WinSW's
275
+ * default when the XML account section is ignored/malformed). Rolls back on mismatch.
276
+ */
277
+ function assertServiceAccountApplied(env: NodeJS.ProcessEnv = process.env): void {
278
+ const qc = scQc();
279
+ const startName = /SERVICE_START_NAME\s*:\s*(.+)/i.exec(qc)?.[1]?.trim() ?? "";
280
+ const user = env.USERNAME?.trim() ?? "";
281
+ if (/localsystem/i.test(startName) || (user && !startName.toLowerCase().includes(user.toLowerCase()))) {
282
+ try { runWinsw(["uninstall"]); } catch { /* rollback is best-effort */ }
283
+ throw new Error(
284
+ `Native service was registered as "${startName || "unknown"}" instead of the current user; ` +
285
+ "rolled back. Re-run `rmx service install --native` and enter the account credentials when prompted.",
286
+ );
287
+ }
288
+ }
289
+
290
+ export interface WinswInstallDeps {
291
+ ensureBinary?: () => Promise<string>;
292
+ writeXml?: (path: string, content: string) => void;
293
+ interactive?: (args: string[]) => void;
294
+ run?: (args: string[]) => string;
295
+ verifyAccount?: () => void;
296
+ status?: () => WinswStatus;
297
+ }
298
+
299
+ /**
300
+ * Install (or repair) the native service. Re-running against an existing service skips
301
+ * `install /p` — assets are rewritten and the service restarted without re-prompting
302
+ * credentials (WinSW `install` fails with "service already exists").
303
+ */
304
+ export async function installWinswService(entry: WinswEntry, deps: WinswInstallDeps = {}): Promise<void> {
305
+ const ensureBinary = deps.ensureBinary ?? ensureWinswBinary;
306
+ const writeXml = deps.writeXml ?? ((path: string, content: string) => writeFileSync(path, content, "utf8"));
307
+ const interactive = deps.interactive ?? runWinswInteractive;
308
+ const run = deps.run ?? runWinsw;
309
+ const verifyAccount = deps.verifyAccount ?? assertServiceAccountApplied;
310
+ const status = deps.status ?? statusWinswRaw;
311
+
312
+ await ensureBinary();
313
+ writeXml(winswXmlPath(), buildWinswXml(entry));
314
+ const existing = status();
315
+ if (existing === "unknown") {
316
+ throw new Error(
317
+ "Could not query the native service state (WinSW status failed or returned an unexpected result). " +
318
+ "Refusing to guess the install state — check 'rmx service status' and retry.",
319
+ );
320
+ }
321
+ if (existing === "nonexistent") {
322
+ // WinSW self-elevates via UAC; a refused prompt aborts install (no silent fallback).
323
+ // v2.12 recognizes prompting only as args[1]: `install /p` (XML is auto-discovered
324
+ // as the same-basename file next to the exe).
325
+ interactive(["install", "/p"]);
326
+ verifyAccount();
327
+ } else {
328
+ // Use `stopwait` (not `stop`) so the SCM service fully stops before `start` — bare
329
+ // `stop` only sends the stop request; `start` against a STOP_PENDING service fails.
330
+ try { run(["stopwait"]); } catch { /* already stopped */ }
331
+ }
332
+ run(["start"]);
333
+ }
334
+
335
+ export function startWinswService(): void { runWinsw(["start"]); }
336
+
337
+ /**
338
+ * Stop the native service and prove it is no longer running. `stopwait` can fail both
339
+ * for the benign already-stopped case and for real access/timeout failures, so a bare
340
+ * catch cannot decide whether it is safe for lifecycle callers to continue. Re-read
341
+ * SCM state and only accept the two states that cannot still own the proxy listener.
342
+ */
343
+ export function stopWinswService(): void {
344
+ try { runWinsw(["stopwait"]); } catch { /* classify by verified state below */ }
345
+ const status = statusWinswRaw();
346
+ if (status === "stopped" || status === "nonexistent") return;
347
+ if (status === "unknown") {
348
+ throw new Error("Native service stop could not be verified.");
349
+ }
350
+ throw new Error("Native service is still running after stop.");
351
+ }
352
+
353
+ export function uninstallWinswService(): void {
354
+ if (!existsSync(winswExePath())) {
355
+ // The binary is gone but the SCM registration can outlive it (quarantine, partial
356
+ // uninstall). WinSW can't run without its exe, so remove the stale registration
357
+ // directly via sc.exe — otherwise the SCM service survives every cleanup path.
358
+ if (process.platform === "win32") {
359
+ const probe = probeScmRegistration();
360
+ if (probe === "error") {
361
+ // Presence unknown — fail closed: keep service state, surface the failure
362
+ // instead of reporting a clean uninstall over a possibly-live registration.
363
+ throw new Error(
364
+ `Cannot verify the native service registration (sc.exe query failed). ` +
365
+ `Uninstall aborted; check 'sc query ${WINSW_SERVICE_ID}' and retry.`,
366
+ );
367
+ }
368
+ if (probe === true) {
369
+ try {
370
+ execFileSync(scExePath(), ["stop", WINSW_SERVICE_ID], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
371
+ } catch { /* not running */ }
372
+ execFileSync(scExePath(), ["delete", WINSW_SERVICE_ID], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
373
+ }
374
+ }
375
+ return;
376
+ }
377
+ try { runWinsw(["stopwait"]); } catch { /* not running */ }
378
+ try { runWinsw(["uninstall"]); } catch (err) {
379
+ // Surface the failure so the caller can decide; silent swallow hides UAC refusals.
380
+ const msg = err instanceof Error ? err.message : String(err);
381
+ if (!msg.toLowerCase().includes("nonexistent")) throw new Error(`WinSW uninstall failed: ${msg}`);
382
+ // "NonExistent" means already absent — that's fine.
383
+ }
384
+ // exe/xml intentionally retained for credential-free reinstall; `--purge` is out of scope.
385
+ }
386
+
387
+ export function winswStatusSummary(): string {
388
+ const status = statusWinswRaw();
389
+ if (status === "nonexistent") {
390
+ // A stale SCM service can outlive a deleted exe; surface the repair path.
391
+ return existsSync(winswXmlPath()) && !existsSync(winswExePath())
392
+ ? "native assets present but WinSW binary missing — run 'rmx service repair'"
393
+ : "";
394
+ }
395
+ return `native (WinSW ${WINSW_VERSION}): ${status}`;
396
+ }
397
+
398
+ /** Default entry mirrors the Task Scheduler baking: durable Bun + cli.ts. */
399
+ export function defaultWinswEntry(cliDir: string): WinswEntry {
400
+ const runtime = durableBunRuntime();
401
+ return { bun: runtime.path, bunRuntimeSource: runtime.source, cli: join(cliDir, "cli", "index.ts") };
402
+ }