@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,1215 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { delimiter, dirname, extname, join, posix } from "node:path";
3
+ import {
4
+ chmodSync,
5
+ closeSync,
6
+ existsSync,
7
+ fstatSync,
8
+ lstatSync,
9
+ mkdirSync,
10
+ openSync,
11
+ readFileSync,
12
+ readdirSync,
13
+ readSync,
14
+ renameSync,
15
+ rmdirSync,
16
+ statSync,
17
+ type Stats,
18
+ unlinkSync,
19
+ writeFileSync,
20
+ } from "node:fs";
21
+ import { getConfigDir } from "../config";
22
+ import { BUN_RUNTIME_PATH_ENV, BUN_RUNTIME_SOURCE_ENV, durableBunRuntime } from "../lib/bun-runtime";
23
+ import type { BunRuntimeSource } from "../lib/bun-runtime";
24
+ import { isProcessAlive } from "../lib/process-control";
25
+ import { serviceApiTokenFilePath } from "../lib/service-secrets";
26
+ import { recordOwnedConfigPath } from "../lib/config-ownership";
27
+ import { windowsEnvIndirectBatchValue } from "../lib/win-paths";
28
+ import { isWslRuntime, wslAutomountRoot } from "./home";
29
+ import { truncateRetainedUtf8 } from "../lib/admission";
30
+
31
+ const SHIM_MARKER = "opencodex codex autostart shim";
32
+ const CODEX_SHIM_PROBE_BYTES = 16 * 1024;
33
+ export const CODEX_SHIM_REPLACEMENT_STABLE_MS = 100;
34
+ export const CODEX_SHIM_STATE_MAX_BYTES = 1024 * 1024;
35
+ const CODEX_SHIM_RESTORE_LOCK_STALE_MS = 30_000;
36
+ const MAX_DIAGNOSTIC_VALUE_BYTES = 8 * 1024;
37
+ let lastShimDiscoveryError: string | null = null;
38
+ /** Last human-readable reason discovery returned null (exposed for doctor/tests). */
39
+ export function lastCodexDiscoveryError(): string | null {
40
+ return lastShimDiscoveryError;
41
+ }
42
+ const CODEX_INTERNAL_COMMANDS = [
43
+ "app-server",
44
+ "archive",
45
+ "apply",
46
+ "cloud",
47
+ "completion",
48
+ "debug",
49
+ "delete",
50
+ "doctor",
51
+ "exec-server",
52
+ "features",
53
+ "fork",
54
+ "help",
55
+ "login",
56
+ "logout",
57
+ "mcp",
58
+ "plugin",
59
+ "sandbox",
60
+ "unarchive",
61
+ "update",
62
+ ];
63
+
64
+ // Codex accepts global options before a subcommand. The shim must skip the value belonging to
65
+ // these options before it decides which first positional token is the real subcommand. Keep this
66
+ // list aligned with `codex --help`; `--option=value` and attached short forms stay one token.
67
+ const CODEX_GLOBAL_OPTIONS_WITH_VALUE = [
68
+ "-c", "--config",
69
+ "--enable", "--disable",
70
+ "--remote", "--remote-auth-token-env",
71
+ "-i", "--image",
72
+ "-m", "--model",
73
+ "--local-provider",
74
+ "-p", "--profile",
75
+ "-s", "--sandbox",
76
+ "-C", "--cd",
77
+ "--add-dir",
78
+ "-a", "--ask-for-approval",
79
+ ];
80
+
81
+ interface ShimState {
82
+ platform: NodeJS.Platform;
83
+ wrapperPath: string;
84
+ originalPath: string;
85
+ backupPath: string;
86
+ wrappers?: ShimFileState[];
87
+ }
88
+
89
+ interface ShimFileState {
90
+ wrapperPath: string;
91
+ originalPath: string;
92
+ backupPath: string;
93
+ realPath?: string;
94
+ preserveOnly?: boolean;
95
+ }
96
+
97
+ interface ShimPathFingerprint {
98
+ dev: number;
99
+ ino: number;
100
+ kind: "file" | "symlink";
101
+ mode: number;
102
+ size: number;
103
+ mtimeMs: number;
104
+ ctimeMs: number;
105
+ target?: Omit<ShimPathFingerprint, "target">;
106
+ }
107
+
108
+ interface StableShimPathProbe {
109
+ fingerprint: ShimPathFingerprint;
110
+ prefix: string;
111
+ }
112
+
113
+ interface InstallCodexShimInternalOptions {
114
+ expectedReplacements?: ReadonlyMap<string, ShimPathFingerprint>;
115
+ allowFreshInstall: boolean;
116
+ beforeGuardedRefresh?: (wrapperPath: string, index: number) => void;
117
+ }
118
+
119
+ export type CodexShimAutoRestoreResult =
120
+ | { status: "not-installed" | "healthy" | "disabled" }
121
+ | { status: "ineligible" | "deferred"; message?: string }
122
+ | { status: "restored"; message: string };
123
+
124
+ function cliEntry(): { bun: string; bunRuntimeSource: BunRuntimeSource; cli: string } {
125
+ // Bundled Bun path (survives `rmx update`); all three shim builders
126
+ // (Unix / Windows cmd / Windows PowerShell) receive it via this entry.
127
+ // This module lives in src/codex/, the CLI entry in src/cli/index.ts.
128
+ // Path and provenance resolve together so the marker always describes this binary.
129
+ const runtime = durableBunRuntime();
130
+ return { bun: runtime.path, bunRuntimeSource: runtime.source, cli: join(import.meta.dir, "..", "cli", "index.ts") };
131
+ }
132
+
133
+ function commandNames(name: string): string[] {
134
+ if (process.platform !== "win32") return [name];
135
+ const exts = (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD;.PS1").split(";").filter(Boolean);
136
+ return [name, ...exts.flatMap(ext => [`${name}${ext.toLowerCase()}`, `${name}${ext.toUpperCase()}`])];
137
+ }
138
+
139
+ function isShim(path: string): boolean {
140
+ try {
141
+ return readFileSync(path, "utf8").includes(SHIM_MARKER);
142
+ } catch {
143
+ return false;
144
+ }
145
+ }
146
+
147
+ function isHealthyShim(path: string, platform: NodeJS.Platform): boolean {
148
+ try {
149
+ const content = readFileSync(path, "utf8");
150
+ if (content.length < 180 || !content.includes(SHIM_MARKER) || !content.includes("ensure")) return false;
151
+ if (platform !== "win32" && (lstatSync(path).mode & 0o111) === 0) return false;
152
+ return true;
153
+ } catch {
154
+ return false;
155
+ }
156
+ }
157
+
158
+ function readShimProbePrefix(path: string): string {
159
+ const fd = openSync(path, "r");
160
+ try {
161
+ const buffer = Buffer.allocUnsafe(CODEX_SHIM_PROBE_BYTES);
162
+ const bytesRead = readSync(fd, buffer, 0, buffer.length, 0);
163
+ return buffer.toString("utf8", 0, bytesRead);
164
+ } finally {
165
+ closeSync(fd);
166
+ }
167
+ }
168
+
169
+ function statFingerprint(path: string, follow: boolean): Omit<ShimPathFingerprint, "target"> | null {
170
+ try {
171
+ const stat = follow ? statSync(path) : lstatSync(path);
172
+ if (follow ? !stat.isFile() : (!stat.isFile() && !stat.isSymbolicLink())) return null;
173
+ return {
174
+ dev: stat.dev,
175
+ ino: stat.ino,
176
+ kind: stat.isSymbolicLink() ? "symlink" : "file",
177
+ mode: stat.mode,
178
+ size: stat.size,
179
+ mtimeMs: stat.mtimeMs,
180
+ ctimeMs: stat.ctimeMs,
181
+ };
182
+ } catch {
183
+ return null;
184
+ }
185
+ }
186
+
187
+ function sameFingerprint(
188
+ left: ShimPathFingerprint | Omit<ShimPathFingerprint, "target">,
189
+ right: ShimPathFingerprint | Omit<ShimPathFingerprint, "target">,
190
+ ): boolean {
191
+ return left.dev === right.dev
192
+ && left.ino === right.ino
193
+ && left.kind === right.kind
194
+ && left.mode === right.mode
195
+ && left.size === right.size
196
+ && left.mtimeMs === right.mtimeMs
197
+ && left.ctimeMs === right.ctimeMs
198
+ && (!("target" in left) || !("target" in right)
199
+ ? true
200
+ : left.target === undefined && right.target === undefined
201
+ ? true
202
+ : left.target !== undefined && right.target !== undefined
203
+ ? sameFingerprint(left.target, right.target)
204
+ : false);
205
+ }
206
+
207
+ function stableShimPathProbe(path: string): StableShimPathProbe | null {
208
+ const before = statFingerprint(path, false);
209
+ if (!before) return null;
210
+ const targetBefore = before.kind === "symlink" ? statFingerprint(path, true) : undefined;
211
+ if (before.kind === "symlink" && !targetBefore) return null;
212
+ let prefix: string;
213
+ try {
214
+ prefix = readShimProbePrefix(path);
215
+ } catch {
216
+ return null;
217
+ }
218
+ const targetAfter = before.kind === "symlink" ? statFingerprint(path, true) : undefined;
219
+ const after = statFingerprint(path, false);
220
+ if (!after || !sameFingerprint(before, after)) return null;
221
+ if (before.kind === "symlink") {
222
+ if (!targetBefore || !targetAfter || !sameFingerprint(targetBefore, targetAfter)) return null;
223
+ }
224
+ const fingerprint: ShimPathFingerprint = {
225
+ ...before,
226
+ ...(targetBefore ? { target: targetBefore } : {}),
227
+ };
228
+ const contentSize = fingerprint.target?.size ?? fingerprint.size;
229
+ return contentSize > 0 ? { fingerprint, prefix } : null;
230
+ }
231
+
232
+ function sameStableShimPathProbe(left: StableShimPathProbe, right: StableShimPathProbe): boolean {
233
+ return left.prefix === right.prefix && sameFingerprint(left.fingerprint, right.fingerprint);
234
+ }
235
+
236
+ function isHealthyShimProbe(probe: StableShimPathProbe, platform: NodeJS.Platform): boolean {
237
+ if (probe.prefix.length < 180 || !probe.prefix.includes(SHIM_MARKER) || !probe.prefix.includes("ensure")) return false;
238
+ const mode = probe.fingerprint.target?.mode ?? probe.fingerprint.mode;
239
+ return platform === "win32" || (mode & 0o111) !== 0;
240
+ }
241
+
242
+ function hasUsableBackingPath(file: ShimFileState): boolean {
243
+ return [existsSync(file.backupPath) ? file.backupPath : undefined, file.realPath]
244
+ .some(path => {
245
+ if (!path) return false;
246
+ const fingerprint = statFingerprint(path, true);
247
+ return fingerprint !== null && fingerprint.size > 0;
248
+ });
249
+ }
250
+
251
+ /**
252
+ * A PATH entry that reaches Windows through WSL drive interop
253
+ * (`<automount-root>/<drive>/...`; root defaults to /mnt, configurable via
254
+ * /etc/wsl.conf [automount] root).
255
+ */
256
+ export function isWindowsInteropDir(dir: string, automountRoot = "/mnt"): boolean {
257
+ const root = automountRoot.replace(/\/+$/, "");
258
+ const escaped = root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
259
+ return new RegExp(`^${escaped}/[a-z](/|$)`, "i").test(dir);
260
+ }
261
+
262
+ export type CodexPathScanDeps = {
263
+ pathValue?: string;
264
+ wsl?: boolean;
265
+ /** Treat PATH entries as POSIX paths (WSL context). Defaults to wsl || non-win32. */
266
+ posixPaths?: boolean;
267
+ automountRoot?: string;
268
+ exists?: (path: string) => boolean;
269
+ isShimFile?: (path: string) => boolean;
270
+ isDirectory?: (path: string) => boolean;
271
+ };
272
+
273
+ function realIsDirectory(path: string): boolean {
274
+ try {
275
+ return lstatSync(path).isDirectory();
276
+ } catch {
277
+ return true; // unreadable -> treat as unusable
278
+ }
279
+ }
280
+
281
+ export function findCodexOnPath(deps: CodexPathScanDeps = {}): string | null {
282
+ lastShimDiscoveryError = null;
283
+ const exists = deps.exists ?? existsSync;
284
+ const shimFile = deps.isShimFile ?? isShim;
285
+ const isDir = deps.isDirectory ?? realIsDirectory;
286
+ const wsl = deps.wsl ?? (process.platform === "linux" && isWslRuntime());
287
+ const usePosix = deps.posixPaths ?? (wsl || process.platform !== "win32");
288
+ const joinPath = usePosix ? posix.join : join;
289
+ const pathSep = usePosix ? ":" : delimiter;
290
+ const automountRoot = deps.automountRoot ?? (wsl ? wslAutomountRoot() : "/mnt");
291
+ // Windows npm prefixes ship codex.exe/codex.cmd next to the extensionless sh launcher.
292
+ const interopNames = ["codex", "codex.exe", "codex.cmd", "codex.ps1"];
293
+ let skippedInterop: string | null = null;
294
+
295
+ for (const dir of (deps.pathValue ?? process.env.PATH ?? "").split(pathSep).filter(Boolean)) {
296
+ if (wsl && isWindowsInteropDir(dir, automountRoot)) {
297
+ // A Windows-side codex reached through WSL PATH interop: a Unix shim written
298
+ // here would embed WSL-only paths and break every Windows-side invocation.
299
+ if (!skippedInterop) {
300
+ for (const name of interopNames) {
301
+ const path = joinPath(dir, name);
302
+ if (exists(path) && !shimFile(path) && !isDir(path)) { skippedInterop = path; break; }
303
+ }
304
+ }
305
+ continue;
306
+ }
307
+ // Interop dirs carry Windows launcher names even when the scan is not skipping them.
308
+ const names = isWindowsInteropDir(dir, automountRoot) ? interopNames : commandNames("codex");
309
+ for (const name of names) {
310
+ const path = joinPath(dir, name);
311
+ if (!exists(path) || shimFile(path)) continue;
312
+ if (!isDir(path)) return path;
313
+ }
314
+ }
315
+
316
+ if (skippedInterop) {
317
+ lastShimDiscoveryError = truncateRetainedUtf8(
318
+ `Found a Windows codex at ${skippedInterop} via WSL PATH interop, but no Linux-side codex. ` +
319
+ "Refusing to shim a Windows launcher from WSL (a WSL shim breaks Windows invocations). " +
320
+ "Install codex inside WSL (npm i -g @openai/codex), or run 'rmx ensure' from Windows to shim the Windows side.",
321
+ MAX_DIAGNOSTIC_VALUE_BYTES,
322
+ );
323
+ }
324
+ return null;
325
+ }
326
+
327
+ function findWindowsCodexTargets(): ShimFileState[] | null {
328
+ lastShimDiscoveryError = null;
329
+ for (const dir of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
330
+ const exe = join(dir, "codex.exe");
331
+ if (existsSync(exe) && !isShim(exe)) {
332
+ try {
333
+ if (!lstatSync(exe).isDirectory()) {
334
+ lastShimDiscoveryError = truncateRetainedUtf8(
335
+ `Found codex.exe at ${exe}. Refusing to rename a real .exe because exact codex.exe invocations would break; ` +
336
+ "install a codex.cmd/codex.ps1 launcher or use `rmx service install` for autostart.",
337
+ MAX_DIAGNOSTIC_VALUE_BYTES,
338
+ );
339
+ return null;
340
+ }
341
+ } catch { /* keep scanning */ }
342
+ }
343
+
344
+ const cmd = join(dir, "codex.cmd");
345
+ const ps1 = join(dir, "codex.ps1");
346
+ // npm also installs an extensionless `codex` sh launcher for Git-Bash/MSYS shells;
347
+ // leaving it unshimmed means Git-Bash users silently get no autostart.
348
+ const gitBashLauncher = join(dir, "codex");
349
+ const targets: ShimFileState[] = [];
350
+ for (const path of [cmd, ps1, gitBashLauncher]) {
351
+ if (!existsSync(path) || isShim(path)) continue;
352
+ try {
353
+ if (!lstatSync(path).isDirectory()) {
354
+ targets.push({ wrapperPath: path, originalPath: path, backupPath: backupPathFor(path) });
355
+ }
356
+ } catch { /* keep scanning */ }
357
+ }
358
+ if (targets.length > 0) return targets;
359
+ }
360
+ return null;
361
+ }
362
+
363
+ function backupPathFor(path: string): string {
364
+ const ext = extname(path);
365
+ return ext ? `${path.slice(0, -ext.length)}.opencodex-real${ext}` : `${path}.opencodex-real`;
366
+ }
367
+
368
+ function shQuote(value: string): string {
369
+ return `'${value.replace(/'/g, "'\\''")}'`;
370
+ }
371
+
372
+ // Provenance is required rather than defaulted: a default would let a caller pass an
373
+ // override binary and silently label it something else, which is precisely the
374
+ // path/marker disagreement this feature exists to prevent.
375
+ //
376
+ // The marker is scoped to the `ensure` invocation in every flavor below and is never
377
+ // exported into the shim's own environment. A shim wraps the real `codex`, so an
378
+ // exported marker would be inherited by Codex and everything it spawns — a shell that
379
+ // then ran a *different* Bun directly would carry a provenance describing a binary it
380
+ // is not executing.
381
+ export function buildUnixCodexShim(realCodexPath: string, bunPath: string, cliPath: string, bunRuntimeSource: BunRuntimeSource, tokenFile = serviceApiTokenFilePath()): string {
382
+ const internalCommands = CODEX_INTERNAL_COMMANDS.join("|");
383
+ const valueOptions = CODEX_GLOBAL_OPTIONS_WITH_VALUE.join("|");
384
+ return `#!/usr/bin/env sh
385
+ # ${SHIM_MARKER}
386
+ if [ -z "$OPENCODEX_API_AUTH_TOKEN" ] && [ -f ${shQuote(tokenFile)} ]; then
387
+ OPENCODEX_API_AUTH_TOKEN="$(cat ${shQuote(tokenFile)})"
388
+ export OPENCODEX_API_AUTH_TOKEN
389
+ fi
390
+ ocx_subcommand=""
391
+ ocx_skip_next=0
392
+ for ocx_arg in "$@"; do
393
+ if [ "$ocx_skip_next" -eq 1 ]; then
394
+ ocx_skip_next=0
395
+ continue
396
+ fi
397
+ case "$ocx_arg" in
398
+ --)
399
+ break
400
+ ;;
401
+ ${valueOptions})
402
+ ocx_skip_next=1
403
+ ;;
404
+ --help|-h|--version|-V)
405
+ ocx_subcommand="$ocx_arg"
406
+ break
407
+ ;;
408
+ -*)
409
+ ;;
410
+ *)
411
+ ocx_subcommand="$ocx_arg"
412
+ break
413
+ ;;
414
+ esac
415
+ done
416
+ case "$ocx_subcommand" in
417
+ ${internalCommands}|--help|-h|--version|-V)
418
+ ;;
419
+ *)
420
+ if [ -z "$OCX_SHIM_BYPASS" ]; then
421
+ ${BUN_RUNTIME_SOURCE_ENV}=${shQuote(bunRuntimeSource)} ${BUN_RUNTIME_PATH_ENV}=${shQuote(bunPath)} ${shQuote(bunPath)} ${shQuote(cliPath)} ensure >/dev/null 2>&1 || true
422
+ fi
423
+ ;;
424
+ esac
425
+ exec ${shQuote(realCodexPath)} "$@"
426
+ `;
427
+ }
428
+
429
+ function windowsBatchValue(value: string): string {
430
+ return value
431
+ .replace(/%/g, "%%")
432
+ .replace(/\^/g, "^^")
433
+ .replace(/"/g, "")
434
+ .replace(/[\r\n]/g, "");
435
+ }
436
+
437
+ function windowsBatchSet(name: string, value: string): string {
438
+ // Paths are rewritten to %USERPROFILE%-style env indirection: cmd.exe parses .cmd
439
+ // files in the OEM codepage, so a literal non-ASCII profile prefix (Korean/Chinese
440
+ // usernames) written as UTF-8 turns to mojibake. The env token expands natively in
441
+ // the right codepage at parse time; no `chcp` here — this shim runs in the USER's
442
+ // console and must not leak a codepage change into it.
443
+ return `set "${name}=${windowsEnvIndirectBatchValue(value, windowsBatchValue)}"`;
444
+ }
445
+
446
+ export function buildWindowsCodexShim(realCodexPath: string, bunPath: string, cliPath: string, bunRuntimeSource: BunRuntimeSource): string {
447
+ const internalCommandChecks = CODEX_INTERNAL_COMMANDS.map(command => `if /I "%~1"=="${command}" goto run_codex`).join("\r\n");
448
+ const valueOptionChecks = CODEX_GLOBAL_OPTIONS_WITH_VALUE.map(option => `if /I "%~1"=="${option}" goto skip_option_value`).join("\r\n");
449
+ return `@echo off\r
450
+ rem ${SHIM_MARKER}\r
451
+ ${windowsBatchSet("OCX_REAL_CODEX", realCodexPath)}\r
452
+ ${windowsBatchSet("OCX_BUN", bunPath)}\r
453
+ ${windowsBatchSet("OCX_CLI", cliPath)}\r
454
+ ${windowsBatchSet("OCX_API_TOKEN_FILE", serviceApiTokenFilePath())}\r
455
+ if "%OPENCODEX_API_AUTH_TOKEN%"=="" if exist "%OCX_API_TOKEN_FILE%" set /p OPENCODEX_API_AUTH_TOKEN=<"%OCX_API_TOKEN_FILE%"\r
456
+ if not "%OCX_SHIM_BYPASS%"=="" goto run_codex\r
457
+ goto scan_codex_args\r
458
+ :scan_codex_args\r
459
+ if "%~1"=="" goto ensure_ocx\r
460
+ if "%~1"=="--" goto ensure_ocx\r
461
+ ${valueOptionChecks}\r
462
+ ${internalCommandChecks}\r
463
+ if /I "%~1"=="--help" goto run_codex\r
464
+ if /I "%~1"=="-h" goto run_codex\r
465
+ if /I "%~1"=="--version" goto run_codex\r
466
+ if /I "%~1"=="-V" goto run_codex\r
467
+ set "OCX_SCAN_ARG=%~1"\r
468
+ if "%OCX_SCAN_ARG:~0,1%"=="-" goto shift_codex_arg\r
469
+ goto ensure_ocx\r
470
+ :skip_option_value\r
471
+ shift\r
472
+ if "%~1"=="" goto ensure_ocx\r
473
+ :shift_codex_arg\r
474
+ shift\r
475
+ goto scan_codex_args\r
476
+ :ensure_ocx\r
477
+ setlocal\r
478
+ ${windowsBatchSet(BUN_RUNTIME_SOURCE_ENV, bunRuntimeSource)}\r
479
+ ${windowsBatchSet(BUN_RUNTIME_PATH_ENV, bunPath)}\r
480
+ "%OCX_BUN%" "%OCX_CLI%" ensure >nul 2>nul\r
481
+ endlocal\r
482
+ :run_codex\r
483
+ "%OCX_REAL_CODEX%" %*\r
484
+ `;
485
+ }
486
+
487
+ function psString(value: string): string {
488
+ return `'${value.replace(/'/g, "''")}'`;
489
+ }
490
+
491
+ export function buildWindowsPowerShellCodexShim(realCodexPath: string, bunPath: string, cliPath: string, bunRuntimeSource: BunRuntimeSource): string {
492
+ const internalCommands = CODEX_INTERNAL_COMMANDS.map(command => psString(command)).join(", ");
493
+ const valueOptions = CODEX_GLOBAL_OPTIONS_WITH_VALUE.map(option => psString(option)).join(", ");
494
+ const tokenFile = serviceApiTokenFilePath();
495
+ return `#!/usr/bin/env pwsh
496
+ # ${SHIM_MARKER}
497
+ if (-not $env:OPENCODEX_API_AUTH_TOKEN -and (Test-Path -LiteralPath ${psString(tokenFile)})) {
498
+ $env:OPENCODEX_API_AUTH_TOKEN = (Get-Content -Raw -LiteralPath ${psString(tokenFile)}).Trim()
499
+ }
500
+ $internalCommands = @(${internalCommands})
501
+ $valueOptions = @(${valueOptions})
502
+ $subcommand = ""
503
+ $skipNext = $false
504
+ foreach ($argValue in $args) {
505
+ $argText = [string]$argValue
506
+ if ($skipNext) { $skipNext = $false; continue }
507
+ if ($argText -eq "--") { break }
508
+ if ($valueOptions -contains $argText) { $skipNext = $true; continue }
509
+ if (@("--help", "-h", "--version", "-V") -contains $argText) { $subcommand = $argText; break }
510
+ if ($argText.StartsWith("-")) { continue }
511
+ $subcommand = $argText
512
+ break
513
+ }
514
+ $skipEnsure = $env:OCX_SHIM_BYPASS -or $internalCommands -contains $subcommand -or @("--help", "-h", "--version", "-V") -contains $subcommand
515
+ if (-not $skipEnsure) {
516
+ $priorRuntimeSource = $env:${BUN_RUNTIME_SOURCE_ENV}
517
+ $priorRuntimePath = $env:${BUN_RUNTIME_PATH_ENV}
518
+ $env:${BUN_RUNTIME_SOURCE_ENV} = ${psString(bunRuntimeSource)}
519
+ $env:${BUN_RUNTIME_PATH_ENV} = ${psString(bunPath)}
520
+ try { & ${psString(bunPath)} ${psString(cliPath)} ensure *> $null }
521
+ finally {
522
+ if ($null -eq $priorRuntimeSource) { Remove-Item Env:\\${BUN_RUNTIME_SOURCE_ENV} -ErrorAction SilentlyContinue }
523
+ else { $env:${BUN_RUNTIME_SOURCE_ENV} = $priorRuntimeSource }
524
+ if ($null -eq $priorRuntimePath) { Remove-Item Env:\\${BUN_RUNTIME_PATH_ENV} -ErrorAction SilentlyContinue }
525
+ else { $env:${BUN_RUNTIME_PATH_ENV} = $priorRuntimePath }
526
+ }
527
+ }
528
+ & ${psString(realCodexPath)} @args
529
+ exit $LASTEXITCODE
530
+ `;
531
+ }
532
+
533
+ interface ShimStateReadResult {
534
+ state: ShimState | null;
535
+ warning?: string;
536
+ }
537
+
538
+ function fileErrorCode(error: unknown): string | undefined {
539
+ return error && typeof error === "object" && "code" in error
540
+ ? String((error as { code?: unknown }).code)
541
+ : undefined;
542
+ }
543
+
544
+ function readBoundedRegularFile(path: string, maxBytes: number): { content: string } | { warning: string } | null {
545
+ let fd: number;
546
+ try {
547
+ fd = openSync(path, "r");
548
+ } catch (error) {
549
+ if (fileErrorCode(error) === "ENOENT") return null;
550
+ return { warning: `Codex shim state could not be opened as a regular file at ${path}.` };
551
+ }
552
+ try {
553
+ const before = fstatSync(fd);
554
+ if (!before.isFile()) return { warning: `Codex shim state is not a regular file at ${path}; auto-restore skipped.` };
555
+ if (before.size > maxBytes) {
556
+ return { warning: `Codex shim state exceeds the 1 MiB startup limit at ${path}; auto-restore skipped.` };
557
+ }
558
+ const buffer = Buffer.allocUnsafe(before.size);
559
+ let offset = 0;
560
+ while (offset < buffer.length) {
561
+ const bytesRead = readSync(fd, buffer, offset, buffer.length - offset, offset);
562
+ if (bytesRead === 0) return { warning: `Codex shim state changed while being read at ${path}; auto-restore skipped.` };
563
+ offset += bytesRead;
564
+ }
565
+ const extra = Buffer.allocUnsafe(1);
566
+ if (readSync(fd, extra, 0, 1, offset) !== 0) {
567
+ return { warning: `Codex shim state exceeds the 1 MiB startup limit at ${path}; auto-restore skipped.` };
568
+ }
569
+ const after = fstatSync(fd);
570
+ if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size
571
+ || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs) {
572
+ return { warning: `Codex shim state changed while being read at ${path}; auto-restore skipped.` };
573
+ }
574
+ return { content: buffer.toString("utf8") };
575
+ } finally {
576
+ closeSync(fd);
577
+ }
578
+ }
579
+
580
+ function readStateResult(): ShimStateReadResult {
581
+ const bounded = readBoundedRegularFile(statePath(), CODEX_SHIM_STATE_MAX_BYTES);
582
+ if (!bounded) return { state: null };
583
+ if ("warning" in bounded) return { state: null, warning: bounded.warning };
584
+ try {
585
+ const value = JSON.parse(bounded.content) as unknown;
586
+ if (!value || typeof value !== "object") return { state: null };
587
+ const state = value as Record<string, unknown>;
588
+ if (typeof state.platform !== "string") return { state: null };
589
+ const validFile = (item: unknown): item is ShimFileState => {
590
+ if (!item || typeof item !== "object") return false;
591
+ const file = item as Record<string, unknown>;
592
+ return typeof file.wrapperPath === "string"
593
+ && typeof file.originalPath === "string"
594
+ && typeof file.backupPath === "string"
595
+ && (file.realPath === undefined || typeof file.realPath === "string")
596
+ && (file.preserveOnly === undefined || typeof file.preserveOnly === "boolean");
597
+ };
598
+ if (state.wrappers !== undefined) {
599
+ if (!Array.isArray(state.wrappers) || state.wrappers.length === 0 || !state.wrappers.every(validFile)) return { state: null };
600
+ } else if (!validFile(state)) {
601
+ return { state: null };
602
+ }
603
+ return { state: state as unknown as ShimState };
604
+ } catch {
605
+ return { state: null };
606
+ }
607
+ }
608
+
609
+ function readState(): ShimState | null {
610
+ return readStateResult().state;
611
+ }
612
+
613
+ function statePath(): string {
614
+ return join(getConfigDir(), "codex-shim.json");
615
+ }
616
+
617
+ function writeState(state: ShimState): void {
618
+ const path = statePath();
619
+ recordOwnedConfigPath(getConfigDir(), path);
620
+ if (!existsSync(getConfigDir())) mkdirSync(getConfigDir(), { recursive: true });
621
+ writeFileSync(path, JSON.stringify(state, null, 2) + "\n", "utf8");
622
+ }
623
+
624
+ /** Git-Bash accepts `C:/...` but not backslashed paths inside sh scripts. */
625
+ function gitBashPath(path: string): string {
626
+ return path.replace(/\\/g, "/");
627
+ }
628
+
629
+ function writeShim(wrapperPath: string, realCodexPath: string): void {
630
+ const { bun, bunRuntimeSource, cli } = cliEntry();
631
+ if (process.platform === "win32") {
632
+ const lower = wrapperPath.toLowerCase();
633
+ if (lower.endsWith(".ps1")) {
634
+ // UTF-8 BOM: Windows PowerShell 5.1 decodes BOM-less .ps1 files in the ANSI
635
+ // codepage, which mangles non-ASCII paths embedded in the shim.
636
+ writeFileSync(wrapperPath, `\uFEFF${buildWindowsPowerShellCodexShim(realCodexPath, bun, cli, bunRuntimeSource)}`, "utf8");
637
+ } else if (lower.endsWith(".cmd") || lower.endsWith(".bat")) {
638
+ writeFileSync(wrapperPath, buildWindowsCodexShim(realCodexPath, bun, cli, bunRuntimeSource), "utf8");
639
+ } else {
640
+ // Extensionless Git-Bash sh launcher: sh shim with forward-slash paths.
641
+ writeFileSync(
642
+ wrapperPath,
643
+ buildUnixCodexShim(gitBashPath(realCodexPath), gitBashPath(bun), gitBashPath(cli), bunRuntimeSource, gitBashPath(serviceApiTokenFilePath())),
644
+ "utf8",
645
+ );
646
+ }
647
+ } else {
648
+ writeFileSync(wrapperPath, buildUnixCodexShim(realCodexPath, bun, cli, bunRuntimeSource), "utf8");
649
+ chmodSync(wrapperPath, 0o755);
650
+ }
651
+ }
652
+
653
+ function stateFiles(state: ShimState): ShimFileState[] {
654
+ return state.wrappers?.length
655
+ ? state.wrappers
656
+ : [{ wrapperPath: state.wrapperPath, originalPath: state.originalPath, backupPath: state.backupPath }];
657
+ }
658
+
659
+ function primaryState(files: ShimFileState[]): ShimState {
660
+ const first = files[0];
661
+ return { platform: process.platform, ...first, wrappers: files };
662
+ }
663
+
664
+ function replaceOwnedBackup(sourcePath: string, backupPath: string): void {
665
+ const oldBackupPath = `${backupPath}.old-${process.pid}`;
666
+ if (existsSync(oldBackupPath)) unlinkSync(oldBackupPath);
667
+ if (existsSync(backupPath)) renameSync(backupPath, oldBackupPath);
668
+ try {
669
+ renameSync(sourcePath, backupPath);
670
+ if (existsSync(oldBackupPath)) unlinkSync(oldBackupPath);
671
+ } catch (error) {
672
+ if (!existsSync(backupPath) && existsSync(oldBackupPath)) renameSync(oldBackupPath, backupPath);
673
+ throw error;
674
+ }
675
+ }
676
+
677
+ function refreshShimFile(file: ShimFileState): boolean {
678
+ if (file.preserveOnly) {
679
+ if (existsSync(file.originalPath) && !isShim(file.originalPath)) {
680
+ replaceOwnedBackup(file.originalPath, file.backupPath);
681
+ return true;
682
+ }
683
+ return false;
684
+ }
685
+ if (existsSync(file.wrapperPath) && !isShim(file.wrapperPath)) {
686
+ if (file.wrapperPath !== file.originalPath) return false;
687
+ replaceOwnedBackup(file.wrapperPath, file.backupPath);
688
+ writeShim(file.wrapperPath, file.realPath ?? file.backupPath);
689
+ return true;
690
+ }
691
+ if (!existsSync(file.wrapperPath) && existsSync(file.backupPath)) {
692
+ writeShim(file.wrapperPath, file.realPath ?? file.backupPath);
693
+ return true;
694
+ }
695
+ if (file.originalPath !== file.wrapperPath && existsSync(file.originalPath) && existsSync(file.wrapperPath) && isShim(file.wrapperPath)) {
696
+ replaceOwnedBackup(file.originalPath, file.backupPath);
697
+ writeShim(file.wrapperPath, file.realPath ?? file.backupPath);
698
+ return true;
699
+ }
700
+ return false;
701
+ }
702
+
703
+ interface GuardedRefreshOperation {
704
+ file: ShimFileState;
705
+ expectedReplacement: ShimPathFingerprint;
706
+ sourcePath: string;
707
+ }
708
+
709
+ interface GuardedRefreshJournalEntry {
710
+ operation: GuardedRefreshOperation;
711
+ stagedOldBackupPath?: string;
712
+ replacementMovedToBackup: boolean;
713
+ wrapperWriteStarted: boolean;
714
+ }
715
+
716
+ let guardedRefreshTransactionId = 0;
717
+
718
+ interface ShimRestoreLock {
719
+ release(): void;
720
+ }
721
+
722
+ interface ShimRestoreLockRecord {
723
+ version: 1;
724
+ token: string;
725
+ pid: number;
726
+ createdAt: number;
727
+ }
728
+
729
+ interface ShimRestoreLockSnapshot {
730
+ record: ShimRestoreLockRecord;
731
+ ownerPath: string;
732
+ lockIdentity: Pick<Stats, "dev" | "ino">;
733
+ fingerprint: ShimPathFingerprint;
734
+ }
735
+
736
+ function restoreLockPath(): string {
737
+ return join(getConfigDir(), "codex-shim.autorestore.lock");
738
+ }
739
+
740
+ function sameFileIdentity(left: Pick<Stats, "dev" | "ino">, right: Pick<Stats, "dev" | "ino">): boolean {
741
+ return left.dev === right.dev && left.ino === right.ino;
742
+ }
743
+
744
+ function readShimRestoreLockSnapshot(path: string): ShimRestoreLockSnapshot | null {
745
+ let lockIdentity: Stats;
746
+ let entries: string[];
747
+ try {
748
+ lockIdentity = lstatSync(path);
749
+ if (!lockIdentity.isDirectory()) return null;
750
+ entries = readdirSync(path);
751
+ } catch {
752
+ return null;
753
+ }
754
+ if (entries.length !== 1 || !entries[0].endsWith(".json")) return null;
755
+ const ownerPath = join(path, entries[0]);
756
+ const probe = stableShimPathProbe(ownerPath);
757
+ if (!probe || probe.fingerprint.kind !== "file" || probe.fingerprint.size > 4096) return null;
758
+ try {
759
+ const value = JSON.parse(probe.prefix) as Partial<ShimRestoreLockRecord>;
760
+ if (value.version !== 1 || typeof value.token !== "string" || value.token.length === 0
761
+ || typeof value.pid !== "number" || !Number.isSafeInteger(value.pid) || value.pid <= 0
762
+ || typeof value.createdAt !== "number" || !Number.isFinite(value.createdAt)) return null;
763
+ if (entries[0] !== `${value.token}.json`) return null;
764
+ const currentLockIdentity = lstatSync(path);
765
+ if (!currentLockIdentity.isDirectory() || !sameFileIdentity(lockIdentity, currentLockIdentity)) return null;
766
+ return {
767
+ record: value as ShimRestoreLockRecord,
768
+ ownerPath,
769
+ lockIdentity,
770
+ fingerprint: probe.fingerprint,
771
+ };
772
+ } catch {
773
+ return null;
774
+ }
775
+ }
776
+
777
+ function sameShimRestoreLock(left: ShimRestoreLockSnapshot, right: ShimRestoreLockSnapshot): boolean {
778
+ return left.record.token === right.record.token
779
+ && sameFileIdentity(left.lockIdentity, right.lockIdentity)
780
+ && sameFingerprint(left.fingerprint, right.fingerprint);
781
+ }
782
+
783
+ function reclaimStaleRestoreLock(path: string, beforeDelete?: () => void): boolean {
784
+ const observed = readShimRestoreLockSnapshot(path);
785
+ if (!observed) return false;
786
+ const createdAt = Math.max(observed.record.createdAt, observed.fingerprint.mtimeMs);
787
+ if (Date.now() - createdAt <= CODEX_SHIM_RESTORE_LOCK_STALE_MS) return false;
788
+ if (isProcessAlive(observed.record.pid)) return false;
789
+ const current = readShimRestoreLockSnapshot(path);
790
+ if (!current || !sameShimRestoreLock(observed, current)) return false;
791
+ beforeDelete?.();
792
+ try {
793
+ // The token is part of the owner filename. Even if the lock directory is
794
+ // replaced after the comparison, this unlink cannot target a successor's
795
+ // differently named owner record.
796
+ unlinkSync(observed.ownerPath);
797
+ rmdirSync(path);
798
+ return true;
799
+ } catch {
800
+ return false;
801
+ }
802
+ }
803
+
804
+ function tryAcquireShimRestoreLock(beforeStaleDelete?: () => void): ShimRestoreLock | null {
805
+ const dir = getConfigDir();
806
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 });
807
+ const path = restoreLockPath();
808
+ for (let attempt = 0; attempt < 2; attempt += 1) {
809
+ let fd: number | null = null;
810
+ let identity: Stats | null = null;
811
+ let createdDirectory = false;
812
+ const record: ShimRestoreLockRecord = {
813
+ version: 1,
814
+ token: `${process.pid}-${Date.now()}-${randomUUID()}`,
815
+ pid: process.pid,
816
+ createdAt: Date.now(),
817
+ };
818
+ const ownerPath = join(path, `${record.token}.json`);
819
+ try {
820
+ mkdirSync(path, { mode: 0o700 });
821
+ createdDirectory = true;
822
+ fd = openSync(ownerPath, "wx", 0o600);
823
+ identity = fstatSync(fd);
824
+ writeFileSync(fd, `${JSON.stringify(record)}\n`, "utf8");
825
+ identity = fstatSync(fd);
826
+ let released = false;
827
+ return {
828
+ release(): void {
829
+ if (released) return;
830
+ released = true;
831
+ try { closeSync(fd!); } catch { /* stale recovery handles an uncertain lock */ }
832
+ try {
833
+ const current = readShimRestoreLockSnapshot(path);
834
+ if (identity && current && current.record.token === record.token
835
+ && sameFileIdentity(identity, current.fingerprint)) {
836
+ unlinkSync(ownerPath);
837
+ rmdirSync(path);
838
+ }
839
+ } catch { /* stale recovery handles release failures */ }
840
+ },
841
+ };
842
+ } catch (error) {
843
+ if (fd !== null) {
844
+ try { closeSync(fd); } catch { /* best-effort close before ownership cleanup */ }
845
+ try {
846
+ const current = readShimRestoreLockSnapshot(path);
847
+ if (identity && current && current.record.token === record.token
848
+ && sameFileIdentity(identity, current.fingerprint)) {
849
+ unlinkSync(ownerPath);
850
+ rmdirSync(path);
851
+ }
852
+ } catch { /* leave an uncertain lock for stale recovery */ }
853
+ } else if (createdDirectory) {
854
+ try { rmdirSync(path); } catch { /* another owner exists or cleanup is uncertain */ }
855
+ }
856
+ if (fileErrorCode(error) !== "EEXIST") throw error;
857
+ if (attempt === 0 && reclaimStaleRestoreLock(path, beforeStaleDelete)) continue;
858
+ return null;
859
+ }
860
+ }
861
+ return null;
862
+ }
863
+
864
+ function planGuardedRefreshTransaction(
865
+ files: readonly ShimFileState[],
866
+ expectedReplacements: ReadonlyMap<string, ShimPathFingerprint>,
867
+ ): GuardedRefreshOperation[] | null {
868
+ const operations: GuardedRefreshOperation[] = [];
869
+ const seen = new Set<string>();
870
+ for (const file of files) {
871
+ if (seen.has(file.wrapperPath)) return null;
872
+ seen.add(file.wrapperPath);
873
+ if (file.preserveOnly) {
874
+ if (!existsSync(file.backupPath) || existsSync(file.originalPath)) return null;
875
+ continue;
876
+ }
877
+ if (!hasUsableBackingPath(file)) return null;
878
+ const probe = stableShimPathProbe(file.wrapperPath);
879
+ if (!probe) return null;
880
+ const expectedReplacement = expectedReplacements.get(file.wrapperPath);
881
+ if (!expectedReplacement) {
882
+ if (!isHealthyShimProbe(probe, process.platform)) return null;
883
+ continue;
884
+ }
885
+ if (file.wrapperPath !== file.originalPath
886
+ || probe.prefix.includes(SHIM_MARKER)
887
+ || !sameFingerprint(probe.fingerprint, expectedReplacement)) return null;
888
+ operations.push({ file, expectedReplacement, sourcePath: file.wrapperPath });
889
+ }
890
+ if (operations.length !== expectedReplacements.size) return null;
891
+ return operations;
892
+ }
893
+
894
+ function rollbackGuardedRefresh(journal: readonly GuardedRefreshJournalEntry[]): Error[] {
895
+ const rollbackErrors: Error[] = [];
896
+ const attempt = (operation: () => void): void => {
897
+ try {
898
+ operation();
899
+ } catch (error) {
900
+ rollbackErrors.push(error instanceof Error ? error : new Error(String(error)));
901
+ }
902
+ };
903
+ for (const entry of [...journal].reverse()) {
904
+ attempt(() => {
905
+ if (entry.wrapperWriteStarted && existsSync(entry.operation.file.wrapperPath)) {
906
+ unlinkSync(entry.operation.file.wrapperPath);
907
+ }
908
+ });
909
+ attempt(() => {
910
+ if (entry.replacementMovedToBackup && existsSync(entry.operation.file.backupPath)) {
911
+ renameSync(entry.operation.file.backupPath, entry.operation.sourcePath);
912
+ }
913
+ });
914
+ attempt(() => {
915
+ if (entry.stagedOldBackupPath && existsSync(entry.stagedOldBackupPath)) {
916
+ renameSync(entry.stagedOldBackupPath, entry.operation.file.backupPath);
917
+ }
918
+ });
919
+ }
920
+ return rollbackErrors;
921
+ }
922
+
923
+ function applyGuardedRefreshTransaction(
924
+ operations: readonly GuardedRefreshOperation[],
925
+ beforeGuardedRefresh?: (wrapperPath: string, index: number) => void,
926
+ commitState?: () => void,
927
+ ): boolean {
928
+ const journal: GuardedRefreshJournalEntry[] = [];
929
+ let applyError: Error | null = null;
930
+ let fingerprintMismatch = false;
931
+ const transactionId = `${process.pid}-${++guardedRefreshTransactionId}`;
932
+
933
+ for (const [index, operation] of operations.entries()) {
934
+ beforeGuardedRefresh?.(operation.sourcePath, index);
935
+ const probe = stableShimPathProbe(operation.sourcePath);
936
+ if (!probe || !sameFingerprint(probe.fingerprint, operation.expectedReplacement)) {
937
+ fingerprintMismatch = true;
938
+ break;
939
+ }
940
+ const entry: GuardedRefreshJournalEntry = {
941
+ operation,
942
+ replacementMovedToBackup: false,
943
+ wrapperWriteStarted: false,
944
+ };
945
+ journal.push(entry);
946
+ try {
947
+ if (existsSync(operation.file.backupPath)) {
948
+ entry.stagedOldBackupPath = `${operation.file.backupPath}.autorestore-${transactionId}-${index}`;
949
+ if (existsSync(entry.stagedOldBackupPath)) unlinkSync(entry.stagedOldBackupPath);
950
+ renameSync(operation.file.backupPath, entry.stagedOldBackupPath);
951
+ }
952
+ renameSync(operation.sourcePath, operation.file.backupPath);
953
+ entry.replacementMovedToBackup = true;
954
+ entry.wrapperWriteStarted = true;
955
+ writeShim(operation.file.wrapperPath, operation.file.realPath ?? operation.file.backupPath);
956
+ } catch (error) {
957
+ applyError = error instanceof Error ? error : new Error(String(error));
958
+ break;
959
+ }
960
+ }
961
+
962
+ if (!fingerprintMismatch && !applyError && commitState) {
963
+ try {
964
+ commitState();
965
+ } catch (error) {
966
+ applyError = error instanceof Error ? error : new Error(String(error));
967
+ }
968
+ }
969
+
970
+ if (fingerprintMismatch || applyError) {
971
+ const rollbackErrors = rollbackGuardedRefresh(journal);
972
+ if (applyError || rollbackErrors.length > 0) {
973
+ throw new AggregateError(
974
+ [...(applyError ? [applyError] : []), ...rollbackErrors],
975
+ "Codex shim guarded refresh failed",
976
+ );
977
+ }
978
+ return false;
979
+ }
980
+
981
+ const cleanupErrors: Error[] = [];
982
+ for (const entry of journal) {
983
+ try {
984
+ if (entry.stagedOldBackupPath && existsSync(entry.stagedOldBackupPath)) unlinkSync(entry.stagedOldBackupPath);
985
+ } catch (error) {
986
+ cleanupErrors.push(error instanceof Error ? error : new Error(String(error)));
987
+ }
988
+ }
989
+ if (cleanupErrors.length > 0) throw new AggregateError(cleanupErrors, "Codex shim guarded refresh cleanup failed");
990
+ return true;
991
+ }
992
+
993
+ function installCodexShimInternal(options: InstallCodexShimInternalOptions): { installed: boolean; message: string } {
994
+ const existing = readState();
995
+ if (existing) {
996
+ const files = stateFiles(existing);
997
+ if (options.expectedReplacements) {
998
+ const operations = planGuardedRefreshTransaction(files, options.expectedReplacements);
999
+ if (!operations || operations.length === 0) {
1000
+ return { installed: false, message: "Codex shim auto-restore deferred because tracked launchers changed." };
1001
+ }
1002
+ const originalStateBytes = readFileSync(statePath());
1003
+ const commitState = (): void => {
1004
+ try {
1005
+ writeState(primaryState(files));
1006
+ } catch (writeError) {
1007
+ try {
1008
+ writeFileSync(statePath(), originalStateBytes);
1009
+ } catch (restoreError) {
1010
+ throw new AggregateError(
1011
+ [writeError, restoreError],
1012
+ "Codex shim state commit and restoration failed",
1013
+ );
1014
+ }
1015
+ throw writeError;
1016
+ }
1017
+ };
1018
+ if (!applyGuardedRefreshTransaction(operations, options.beforeGuardedRefresh, commitState)) {
1019
+ return { installed: false, message: "Codex shim auto-restore deferred because tracked launchers changed." };
1020
+ }
1021
+ return {
1022
+ installed: true,
1023
+ message: `Codex update detected. Backed up new launcher and refreshed shim at ${files.map(f => f.wrapperPath).join(", ")}.`,
1024
+ };
1025
+ }
1026
+ let refreshed = false;
1027
+ for (const file of files) refreshed = refreshShimFile(file) || refreshed;
1028
+ const allInstalled = files.every(file => file.preserveOnly
1029
+ ? existsSync(file.backupPath) && !existsSync(file.originalPath)
1030
+ : existsSync(file.wrapperPath)
1031
+ && (existsSync(file.backupPath) || (file.realPath ? existsSync(file.realPath) : false))
1032
+ && isShim(file.wrapperPath));
1033
+ if (refreshed || allInstalled) {
1034
+ writeState(primaryState(files));
1035
+ if (refreshed) {
1036
+ return {
1037
+ installed: true,
1038
+ message: `Codex update detected. Backed up new launcher and refreshed shim at ${files.map(f => f.wrapperPath).join(", ")}.`,
1039
+ };
1040
+ }
1041
+ return {
1042
+ installed: false,
1043
+ message: `Codex autostart shim already installed at ${files.map(f => f.wrapperPath).join(", ")}.`,
1044
+ };
1045
+ }
1046
+ }
1047
+
1048
+ if (!options.allowFreshInstall) {
1049
+ return { installed: false, message: "Codex shim auto-restore requires a valid prior installation." };
1050
+ }
1051
+
1052
+ const targets: ShimFileState[] | null = process.platform === "win32"
1053
+ ? findWindowsCodexTargets()
1054
+ : (() => {
1055
+ const originalPath = findCodexOnPath();
1056
+ return originalPath ? [{ wrapperPath: originalPath, originalPath, backupPath: backupPathFor(originalPath) }] : null;
1057
+ })();
1058
+ if (!targets) return { installed: false, message: lastShimDiscoveryError ?? "Could not find a codex executable on PATH." };
1059
+
1060
+ for (const target of targets) {
1061
+ if (existsSync(target.backupPath)) return { installed: false, message: `Refusing to overwrite existing backup: ${target.backupPath}` };
1062
+ }
1063
+ for (const target of targets) {
1064
+ if (existsSync(target.originalPath)) renameSync(target.originalPath, target.backupPath);
1065
+ if (!target.preserveOnly) writeShim(target.wrapperPath, target.realPath ?? target.backupPath);
1066
+ }
1067
+ writeState(primaryState(targets));
1068
+ return {
1069
+ installed: true,
1070
+ message: `Codex autostart shim installed at ${targets.map(t => t.wrapperPath).join(", ")}. Original saved at ${targets.map(t => t.backupPath).join(", ")}.`,
1071
+ };
1072
+ }
1073
+
1074
+ export function installCodexShim(): { installed: boolean; message: string } {
1075
+ return installCodexShimInternal({ allowFreshInstall: true });
1076
+ }
1077
+
1078
+ export function autoRestoreCodexShim(options: {
1079
+ enabled: () => boolean;
1080
+ stabilitySleep?: (ms: number) => void;
1081
+ /** Narrow deterministic seam used to hold the interprocess lock in tests. */
1082
+ afterRestoreLockAcquired?: () => void;
1083
+ /** Narrow deterministic seam for stale-lock compare-and-delete tests. */
1084
+ beforeStaleRestoreLockDelete?: () => void;
1085
+ /** Narrow deterministic race seam for the guarded transaction tests. */
1086
+ beforeGuardedRefresh?: (wrapperPath: string, index: number) => void;
1087
+ }): CodexShimAutoRestoreResult {
1088
+ const stateRead = readStateResult();
1089
+ const state = stateRead.state;
1090
+ if (!state) {
1091
+ if (stateRead.warning) return { status: "ineligible", message: stateRead.warning };
1092
+ return { status: existsSync(statePath()) ? "ineligible" : "not-installed" };
1093
+ }
1094
+ if (state.platform !== process.platform) return { status: "ineligible" };
1095
+
1096
+ const files = stateFiles(state);
1097
+ const replacementProbes = new Map<string, StableShimPathProbe>();
1098
+ const seen = new Set<string>();
1099
+ let healthyCount = 0;
1100
+ for (const file of files) {
1101
+ if (seen.has(file.wrapperPath)) return { status: "ineligible" };
1102
+ seen.add(file.wrapperPath);
1103
+ if (file.preserveOnly) {
1104
+ if (!existsSync(file.backupPath) || existsSync(file.originalPath)) return { status: "ineligible" };
1105
+ continue;
1106
+ }
1107
+ if (!existsSync(file.wrapperPath) || !hasUsableBackingPath(file)) return { status: "ineligible" };
1108
+ const probe = stableShimPathProbe(file.wrapperPath);
1109
+ if (!probe) return { status: "deferred" };
1110
+ if (probe.prefix.includes(SHIM_MARKER)) {
1111
+ if (!isHealthyShimProbe(probe, state.platform)) return { status: "ineligible" };
1112
+ healthyCount += 1;
1113
+ continue;
1114
+ }
1115
+ replacementProbes.set(file.wrapperPath, probe);
1116
+ }
1117
+
1118
+ if (replacementProbes.size === 0) return { status: "healthy" };
1119
+ if (!options.enabled()) return { status: "disabled" };
1120
+ if (files.length > 1 && healthyCount > 0) {
1121
+ return {
1122
+ status: "deferred",
1123
+ message: "Codex shim auto-restore deferred because tracked launcher siblings are in a mixed shim/replacement state.",
1124
+ };
1125
+ }
1126
+
1127
+ const lock = tryAcquireShimRestoreLock(options.beforeStaleRestoreLockDelete);
1128
+ if (!lock) return { status: "deferred" };
1129
+ try {
1130
+ options.afterRestoreLockAcquired?.();
1131
+ (options.stabilitySleep ?? Bun.sleepSync)(CODEX_SHIM_REPLACEMENT_STABLE_MS);
1132
+ const expectedReplacements = new Map<string, ShimPathFingerprint>();
1133
+ for (const [path, firstProbe] of replacementProbes) {
1134
+ const secondProbe = stableShimPathProbe(path);
1135
+ if (!secondProbe || secondProbe.prefix.includes(SHIM_MARKER)
1136
+ || !sameStableShimPathProbe(firstProbe, secondProbe)) return { status: "deferred" };
1137
+ expectedReplacements.set(path, secondProbe.fingerprint);
1138
+ }
1139
+ const result = installCodexShimInternal({
1140
+ allowFreshInstall: false,
1141
+ expectedReplacements,
1142
+ beforeGuardedRefresh: options.beforeGuardedRefresh,
1143
+ });
1144
+ return result.installed
1145
+ ? { status: "restored", message: result.message }
1146
+ : { status: "deferred" };
1147
+ } finally {
1148
+ lock.release();
1149
+ }
1150
+ }
1151
+
1152
+ export function uninstallCodexShim(): { removed: boolean; message: string } {
1153
+ const state = readState();
1154
+ if (!state) return { removed: false, message: "Codex autostart shim is not installed." };
1155
+ const files = stateFiles(state);
1156
+ for (const file of files) {
1157
+ if (file.preserveOnly) continue;
1158
+ if (existsSync(file.wrapperPath) && isShim(file.wrapperPath)) unlinkSync(file.wrapperPath);
1159
+ }
1160
+ for (const file of files) {
1161
+ if (existsSync(file.backupPath) && !existsSync(file.originalPath)) renameSync(file.backupPath, file.originalPath);
1162
+ }
1163
+ if (existsSync(statePath())) unlinkSync(statePath());
1164
+ return { removed: true, message: `Codex autostart shim removed. Restored ${files.map(f => f.originalPath).join(", ")}.` };
1165
+ }
1166
+
1167
+ /** True if a Codex autostart shim is currently installed (state file present). */
1168
+ export function isCodexShimInstalled(): boolean {
1169
+ return diagnoseCodexShim().installed;
1170
+ }
1171
+
1172
+ export interface CodexShimDiagnostic {
1173
+ installed: boolean;
1174
+ healthy: boolean;
1175
+ summary: string;
1176
+ }
1177
+
1178
+ /** Structured, secret-free shim state for CLI/GUI lifecycle diagnostics. */
1179
+ export function diagnoseCodexShim(): CodexShimDiagnostic {
1180
+ const state = readState();
1181
+ if (!state) {
1182
+ if (existsSync(statePath())) {
1183
+ return {
1184
+ installed: true,
1185
+ healthy: false,
1186
+ summary: `Codex autostart shim state is invalid or corrupt at ${statePath()}. Reinstall or remove the shim.`,
1187
+ };
1188
+ }
1189
+ return {
1190
+ installed: false,
1191
+ healthy: false,
1192
+ summary: "Codex autostart shim is not installed.",
1193
+ };
1194
+ }
1195
+ const files = stateFiles(state);
1196
+ const healthy = files.length > 0 && files.every(file => file.preserveOnly
1197
+ ? existsSync(file.backupPath) && !existsSync(file.originalPath)
1198
+ : existsSync(file.wrapperPath)
1199
+ && (existsSync(file.backupPath) || (file.realPath ? existsSync(file.realPath) : false))
1200
+ && isHealthyShim(file.wrapperPath, state.platform));
1201
+ const summary = files.map(file => {
1202
+ const wrapper = existsSync(file.wrapperPath)
1203
+ ? isShim(file.wrapperPath)
1204
+ ? "shim present"
1205
+ : "present but not a Remodex shim"
1206
+ : "missing";
1207
+ const backup = existsSync(file.backupPath) ? "present" : "missing";
1208
+ return `Codex autostart shim: wrapper ${wrapper} at ${file.wrapperPath}; original backup ${backup} at ${file.backupPath}.`;
1209
+ }).join("\n");
1210
+ return { installed: true, healthy, summary };
1211
+ }
1212
+
1213
+ export function codexShimStatus(): string {
1214
+ return diagnoseCodexShim().summary;
1215
+ }