@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,88 @@
1
+ import type { OcxConfig } from "../../types";
2
+ import type { NativeProfileApiDeps } from "../../codex/native-profile-api";
3
+ import type { StartupInstallAction } from "../startup-action-control";
4
+ import type { ManagementPrincipal } from "../management-auth";
5
+ import type { CatalogModel } from "../../codex/catalog";
6
+ import type { injectGrokConfig } from "../../grok/inject";
7
+ import type { removeDesktop3pStandardPivot, writeDesktop3pConfig } from "../../claude/desktop-3p";
8
+ import type { RuntimePortState } from "../../config";
9
+ import type { CatalogDisposition, ConvergeCodex } from "../../codex/convergence-types";
10
+ import type { AndroidRemoteStore } from "../../android-remote/store";
11
+ import type { AndroidRemoteGatewayController } from "../../android-remote/gateway";
12
+ import type { AndroidRemoteCloudflareProvisioner } from "../../android-remote/cloudflare-provisioning";
13
+
14
+ export interface ManagementApiDeps {
15
+ /** Native desktop-update seams keep route tests off the network and real owner state. */
16
+ checkDesktopReleaseUpdate?: typeof import("../../update/desktop-release").checkDesktopReleaseUpdate;
17
+ startDesktopUpdateJob?: typeof import("../../update/desktop-release").startDesktopUpdateJob;
18
+ readDesktopUpdateState?: typeof import("../../update/desktop-release").readDesktopUpdateState;
19
+ /** Android Remote persistence seam. Tests inject an in-memory store. */
20
+ androidRemoteStore?: AndroidRemoteStore;
21
+ /** Shared Android Remote gateway. Production and management routes use one controller. */
22
+ androidRemoteController?: AndroidRemoteGatewayController;
23
+ /** Cloudflare API seam. Tests inject a bounded fake and never contact a real account. */
24
+ androidRemoteCloudflareProvisioner?: AndroidRemoteCloudflareProvisioner;
25
+ toggleCodexMultiAgentV2?: (enabled: boolean) => void;
26
+ toggleDefaultModeRequestUserInput?: (enabled: boolean) => void;
27
+ createManagementConvergeCodex?: (config: Readonly<OcxConfig>) => ConvergeCodex;
28
+ /**
29
+ * Persistence seam for route-level tests. Production leaves this unset and uses
30
+ * `saveConfigPreservingClaudeCode`; tests that pass an in-memory fixture config
31
+ * MUST inject a no-op/spy so the fixture can never overwrite the user's real
32
+ * OPENCODEX_HOME (incident: devlog 260730.../070).
33
+ */
34
+ saveConfigPreservingClaudeCode?: (config: OcxConfig) => void;
35
+ /**
36
+ * Catalog seam for the Grok toggle (WP2, devlog 260803_integrations_toggle_all
37
+ * Rev 3 N2). Production leaves this unset and the route dynamic-imports the
38
+ * real one — a static import would close a cycle with management-api.ts.
39
+ * Tests stub it to orphan the fixture file mid-fetch (the r7 recheck test).
40
+ */
41
+ fetchAllModels?: (config: OcxConfig) => Promise<CatalogModel[]>;
42
+ /**
43
+ * Writer seam for the Grok toggle: lets a test place the file in any state
44
+ * between the pre-write recheck and the write itself (the r8 post-inspection
45
+ * tests). Production leaves this unset and uses the real writer.
46
+ */
47
+ injectGrokConfig?: typeof injectGrokConfig;
48
+ /** Desktop mutation seams keep route tests inside temporary config libraries. */
49
+ removeDesktop3pStandardPivot?: typeof removeDesktop3pStandardPivot;
50
+ writeDesktop3pConfig?: typeof writeDesktop3pConfig;
51
+ /**
52
+ * Runtime-state seam: the fence must name the host/port the RUNNING process
53
+ * bound (agent-settings-routes.ts:99-103 pattern), and a test must not depend
54
+ * on the developer's real runtime state file.
55
+ */
56
+ readRuntimePort?: (pid: number) => RuntimePortState | null;
57
+ clearThreadAccountMap?: () => void;
58
+ clearProviderQuotaCache?: () => void;
59
+ primeCodexPoolQuotas?: (config: OcxConfig, reason: string) => Promise<void> | void;
60
+ runStartupInstallAction?: (
61
+ action: StartupInstallAction,
62
+ options?: { repair?: boolean },
63
+ ) => Promise<{ message: string }>;
64
+ /**
65
+ * Native-main profile persistence seam for server-boundary tests. Production
66
+ * leaves this unset, so the route creates its normal NativeProfileManager.
67
+ */
68
+ nativeProfileApi?: NativeProfileApiDeps;
69
+ }
70
+
71
+
72
+ export interface ManagementContext {
73
+ req: Request;
74
+ url: URL;
75
+ config: OcxConfig;
76
+ deps: ManagementApiDeps;
77
+ /**
78
+ * Which credential authorized this request, resolved by the auth gate before
79
+ * dispatch. Routes that spend the USER's identity (not just the proxy's) must
80
+ * branch on this instead of on request headers: the admin token is readable by
81
+ * anything running as the user, so a token holder can forge any header a route
82
+ * might otherwise treat as browser evidence. Undefined only in direct-dispatch
83
+ * tests, which are treated as the untrusted `admin-token` case.
84
+ */
85
+ principal?: ManagementPrincipal;
86
+ convergeCodexCatalog: () => Promise<CatalogDisposition>;
87
+ syncClaudeAgentDefsBestEffort: () => Promise<void>;
88
+ }
@@ -0,0 +1,538 @@
1
+ /**
2
+ * Management routes for the client-integration toggle.
3
+ *
4
+ * This module is an HTTP adapter and nothing more: every policy decision —
5
+ * what counts as ownership, when a mutation is refused, what gets journaled —
6
+ * belongs to src/integrations/writer.ts. Duplicating any of it here is how the
7
+ * API and the writer would start disagreeing about what happened to a file.
8
+ *
9
+ * Design of record: devlog/_fin/260802_client_toggle_api/040_wp4_management_api.md.
10
+ */
11
+ import { readFileSync } from "node:fs";
12
+ import type { IntegrationIO } from "../../integrations/config-io";
13
+ import { matchesOperationResult } from "../../integrations/journal";
14
+ import {
15
+ INTEGRATION_CLIENT_IDS,
16
+ isIntegrationClientId,
17
+ type IntegrationClientId,
18
+ } from "../../integrations/registry";
19
+ import { readIntegrationState } from "../../integrations/state";
20
+ import { createIntegrationStateStore, type IntegrationStateStore } from "../../integrations/store";
21
+ import {
22
+ applyIntegration,
23
+ disableIntegration,
24
+ restoreIntegration,
25
+ type IntegrationRestoreInput,
26
+ type IntegrationWriteInput,
27
+ type WriteRefused,
28
+ } from "../../integrations/writer";
29
+ import { jsonResponse } from "../auth-cors";
30
+ import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
31
+ import type { ManagementContext } from "./context";
32
+ import { loadExportModels } from "./model-rows";
33
+
34
+
35
+ const INTEGRATION_ROUTE_PREFIX = "/api/client-integrations/";
36
+ const INTEGRATION_MUTATION_JOIN_MS = 120_000;
37
+ export const INTEGRATION_MUTATION_TERMINAL_MS = 10 * 60_000;
38
+
39
+ type IntegrationStateRecord = Awaited<ReturnType<typeof readIntegrationState>>;
40
+ type ApplyResult = Awaited<ReturnType<typeof applyIntegration>>;
41
+ type DisableResult = Awaited<ReturnType<typeof disableIntegration>>;
42
+ type RestoreResult = Awaited<ReturnType<typeof restoreIntegration>>;
43
+
44
+ export type IntegrationStateEnvelope = {
45
+ clientId: IntegrationClientId;
46
+ } & IntegrationStateRecord;
47
+
48
+ export interface IntegrationStateListEnvelope {
49
+ clients: IntegrationStateEnvelope[];
50
+ }
51
+
52
+ export type IntegrationToggleEnvelope =
53
+ | ({ clientId: IntegrationClientId } & ApplyResult)
54
+ | ({ clientId: IntegrationClientId } & DisableResult);
55
+
56
+ export type IntegrationRestoreEnvelope = {
57
+ clientId: IntegrationClientId;
58
+ } & RestoreResult;
59
+
60
+ export interface IntegrationJournalEnvelope {
61
+ operations: IntegrationJournalRow[];
62
+ }
63
+
64
+ export interface IntegrationJournalRow {
65
+ opId: string;
66
+ clientId: IntegrationClientId;
67
+ kind: "apply" | "disable" | "refresh" | "restore";
68
+ at: string;
69
+ configPath: string;
70
+ snapshot: "none" | "stored" | "expired";
71
+ undoable: boolean;
72
+ }
73
+
74
+ export interface IntegrationToggleBody {
75
+ enabled: boolean;
76
+ }
77
+
78
+ export interface IntegrationRestoreBody {
79
+ opId: string;
80
+ confirmDrift?: boolean;
81
+ }
82
+
83
+ interface IntegrationMutationFlight {
84
+ key: string;
85
+ startedAt: number;
86
+ promise: Promise<unknown>;
87
+ }
88
+
89
+ class IntegrationMutationBusyError extends Error {
90
+ constructor(readonly clientId: IntegrationClientId) {
91
+ super("integration_mutation_busy");
92
+ }
93
+ }
94
+
95
+ const integrationMutationFlights = new Map<IntegrationClientId, IntegrationMutationFlight>();
96
+ let integrationMutationTestHooks: {
97
+ io?: IntegrationIO;
98
+ /**
99
+ * Bind every read and write in the request to one store. Without this a
100
+ * route test could isolate the writer but not the journal listing or the
101
+ * restore preflight (A-gate round 12).
102
+ */
103
+ store?: IntegrationStateStore;
104
+ run?: (operation: () => Promise<unknown>) => Promise<unknown>;
105
+ } | null = null;
106
+
107
+ /**
108
+ * Home and environment overrides for tests.
109
+ *
110
+ * Bun's `os.homedir()` snapshots the real home at startup and ignores a later
111
+ * `process.env.HOME` assignment, so a test that only rewrites `HOME` still
112
+ * resolves the DEVELOPER'S client configs — which is exactly how a route test
113
+ * wrote a real `~/.hermes/config.yaml` during this work package. The writer
114
+ * and the state reader both already take `env`/`home` explicitly; the route
115
+ * simply had no way to pass them. It does now, and production leaves it unset.
116
+ */
117
+ let integrationPathTestHooks: { env?: NodeJS.ProcessEnv; home?: string } | null = null;
118
+
119
+ export function setIntegrationPathTestHooks(hooks: { env?: NodeJS.ProcessEnv; home?: string } | null): void {
120
+ integrationPathTestHooks = hooks;
121
+ }
122
+
123
+ /** The `env`/`home` overrides, spread into every registry-resolving call. */
124
+ function pathOverrides(): { env?: NodeJS.ProcessEnv; home?: string } {
125
+ return {
126
+ ...(integrationPathTestHooks?.env ? { env: integrationPathTestHooks.env } : {}),
127
+ ...(integrationPathTestHooks?.home ? { home: integrationPathTestHooks.home } : {}),
128
+ };
129
+ }
130
+
131
+ function isPlainRecord(value: unknown): value is Record<string, unknown> {
132
+ return typeof value === "object" && value !== null && !Array.isArray(value);
133
+ }
134
+
135
+ function decodeClientPath(pathname: string): string | null {
136
+ if (!pathname.startsWith(INTEGRATION_ROUTE_PREFIX)) return null;
137
+ const encoded = pathname.slice(INTEGRATION_ROUTE_PREFIX.length);
138
+ if (!encoded || encoded.includes("/")) return null;
139
+ try {
140
+ return decodeURIComponent(encoded);
141
+ } catch {
142
+ return encoded;
143
+ }
144
+ }
145
+
146
+ function runIntegrationMutationFlight<T>(
147
+ clientId: IntegrationClientId,
148
+ key: string,
149
+ now: () => number,
150
+ operation: () => Promise<T>,
151
+ ): Promise<T> {
152
+ const startedAt = now();
153
+ const current = integrationMutationFlights.get(clientId);
154
+ if (current) {
155
+ const age = startedAt - current.startedAt;
156
+ if (current.key === key && age < INTEGRATION_MUTATION_JOIN_MS) {
157
+ return current.promise as Promise<T>;
158
+ }
159
+ if (age <= INTEGRATION_MUTATION_TERMINAL_MS) {
160
+ return Promise.reject(new IntegrationMutationBusyError(clientId));
161
+ }
162
+ if (integrationMutationFlights.get(clientId) === current) {
163
+ integrationMutationFlights.delete(clientId);
164
+ }
165
+ }
166
+
167
+ const flight: IntegrationMutationFlight = {
168
+ key,
169
+ startedAt,
170
+ promise: Promise.resolve(),
171
+ };
172
+ const run = async (): Promise<unknown> => operation();
173
+ flight.promise = (integrationMutationTestHooks?.run
174
+ ? integrationMutationTestHooks.run(run)
175
+ : run()
176
+ ).finally(() => {
177
+ if (integrationMutationFlights.get(clientId) === flight) {
178
+ integrationMutationFlights.delete(clientId);
179
+ }
180
+ });
181
+ integrationMutationFlights.set(clientId, flight);
182
+ return flight.promise as Promise<T>;
183
+ }
184
+
185
+ export function setIntegrationMutationFlightTestHooks(
186
+ hooks: {
187
+ io?: IntegrationIO;
188
+ /** Binds the WHOLE request — reads, writes and journal — to one store. */
189
+ store?: IntegrationStateStore;
190
+ run?: (operation: () => Promise<unknown>) => Promise<unknown>;
191
+ } | null,
192
+ ): void {
193
+ integrationMutationTestHooks = hooks;
194
+ integrationMutationFlights.clear();
195
+ // Path overrides are part of the same isolation contract: clearing flights
196
+ // while leaving a temp home bound would let the next suite write real files.
197
+ if (hooks === null) integrationPathTestHooks = null;
198
+ }
199
+
200
+ /**
201
+ * ONE store per request, used by every read and every write in that request.
202
+ * The route previously called module-level `listOperations`/`readSnapshot`
203
+ * while handing the writer a separate default store, so a test could not bind
204
+ * the whole operation to a temp root (A-gate round 12).
205
+ */
206
+ function integrationStore(): IntegrationStateStore {
207
+ return integrationMutationTestHooks?.store ?? createIntegrationStateStore();
208
+ }
209
+
210
+ async function buildIntegrationWriteInput(
211
+ clientId: IntegrationClientId,
212
+ ctx: ManagementContext,
213
+ store: IntegrationStateStore,
214
+ ): Promise<IntegrationWriteInput> {
215
+ return {
216
+ clientId,
217
+ models: await loadExportModels(ctx.config),
218
+ config: ctx.config,
219
+ port: Number(ctx.url.port) || ctx.config.port,
220
+ store,
221
+ io: integrationMutationTestHooks?.io,
222
+ ...pathOverrides(),
223
+ };
224
+ }
225
+
226
+ /**
227
+ * The file's current bytes, or `null` when it is missing.
228
+ *
229
+ * `null` is NOT the same as `""`: an absent file and an empty one are
230
+ * different states, and collapsing them is what made this route disagree with
231
+ * the writer about whether an absence-result operation had drifted.
232
+ * `undefined` means we could not read it at all, which is neither.
233
+ */
234
+ function currentConfigText(configPath: string): string | null | undefined {
235
+ try {
236
+ return readFileSync(configPath, "utf8");
237
+ } catch (error) {
238
+ if (isPlainRecord(error) && error.code === "ENOENT") return null;
239
+ return undefined;
240
+ }
241
+ }
242
+
243
+ function invalidClientResponse(ctx: ManagementContext): Response {
244
+ return jsonResponse({
245
+ error: "invalid integration client",
246
+ code: "invalid_integration_client",
247
+ validClients: INTEGRATION_CLIENT_IDS,
248
+ }, 400, ctx.req, ctx.config);
249
+ }
250
+
251
+ function internalErrorResponse(error: unknown, ctx: ManagementContext): Response {
252
+ return jsonResponse({
253
+ error: error instanceof Error ? error.message : String(error),
254
+ code: "integration_internal_error",
255
+ }, 500, ctx.req, ctx.config);
256
+ }
257
+
258
+ async function readJsonBody(ctx: ManagementContext): Promise<unknown | Response> {
259
+ try {
260
+ return await readManagementJsonBody(ctx.req);
261
+ } catch (error) {
262
+ rethrowManagementBodyTooLarge(error);
263
+ return jsonResponse({
264
+ error: "invalid JSON body",
265
+ code: "invalid_json_body",
266
+ }, 400, ctx.req, ctx.config);
267
+ }
268
+ }
269
+
270
+ function writerFailureResponse(
271
+ clientId: IntegrationClientId,
272
+ /*
273
+ * The writer's own refusal type, not a structural echo of it.
274
+ *
275
+ * A local shape with an optional `message` accepted a refusal that had lost
276
+ * its message on the way here, which is exactly how the drift branch shipped
277
+ * without one. `WriteRefused` requires it, so the compiler now objects.
278
+ */
279
+ result: WriteRefused,
280
+ ctx: ManagementContext,
281
+ ): Response {
282
+ /*
283
+ * Routed by `reason`, never by `state` (006 §5). A `write_failed` that
284
+ * happens to occur while the file is in a `conflict` state is still a write
285
+ * failure, and mapping on state first silently dropped its message,
286
+ * snapshotPath, and residual — the recovery information the flag exists to
287
+ * carry (A-gate round 5, blocker 3).
288
+ */
289
+ const recovery = {
290
+ message: result.message,
291
+ ...(result.snapshotPath ? { snapshotPath: result.snapshotPath } : {}),
292
+ ...(result.residual ? { residual: true } : {}),
293
+ };
294
+
295
+ if (result.reason === "unsafe") {
296
+ return jsonResponse({
297
+ error: "integration config is unsafe",
298
+ code: "integration_unsafe",
299
+ clientId, state: result.state, reason: result.reason, ...recovery,
300
+ }, 409, ctx.req, ctx.config);
301
+ }
302
+ if (result.reason === "conflict") {
303
+ return jsonResponse({
304
+ error: "integration config conflicts with ownership record",
305
+ code: "integration_conflict",
306
+ clientId, state: result.state, reason: result.reason, ...recovery,
307
+ }, 409, ctx.req, ctx.config);
308
+ }
309
+ if (result.reason === "drift_requires_confirm") {
310
+ return jsonResponse({
311
+ error: "restore requires drift confirmation",
312
+ code: "integration_drift_confirmation_required",
313
+ clientId, state: result.state, reason: result.reason, ...recovery,
314
+ }, 409, ctx.req, ctx.config);
315
+ }
316
+ if (result.reason === "snapshot_expired") {
317
+ return jsonResponse({
318
+ error: "integration snapshot expired",
319
+ code: "integration_snapshot_expired",
320
+ clientId, state: result.state, reason: result.reason, ...recovery,
321
+ }, 410, ctx.req, ctx.config);
322
+ }
323
+ // not_installed, non_loopback, write_failed — always carry recovery fields.
324
+ return jsonResponse({
325
+ error: "integration mutation failed",
326
+ code: "integration_mutation_failed",
327
+ clientId, state: result.state, reason: result.reason, ...recovery,
328
+ }, 500, ctx.req, ctx.config);
329
+ }
330
+
331
+ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<Response | null> {
332
+ const { req, url } = ctx;
333
+
334
+ if (url.pathname === "/api/client-integrations" && req.method === "GET") {
335
+ try {
336
+ const models = await loadExportModels(ctx.config);
337
+ const port = Number(url.port) || ctx.config.port;
338
+ // One store for the whole collection read: without it this route retried
339
+ // maintenance and counted snapshots in the default store even when the
340
+ // caller had bound everything else to a temp root (A-gate round 13).
341
+ const store = integrationStore();
342
+ /*
343
+ * The envelope carries `clientId`, but it is not restated here: WP2 and
344
+ * WP3 both echo `input.clientId` back in their result, and `input`
345
+ * IS this route's client. Writing it twice made the compiler pick the
346
+ * later one silently — a duplicate that could only ever hide a
347
+ * disagreement, never surface it.
348
+ */
349
+ const clients = INTEGRATION_CLIENT_IDS.map(clientId =>
350
+ readIntegrationState({ clientId, models, config: ctx.config, port, store, ...pathOverrides() }));
351
+ return jsonResponse({ clients } satisfies IntegrationStateListEnvelope, 200, req, ctx.config);
352
+ } catch (error) {
353
+ return internalErrorResponse(error, ctx);
354
+ }
355
+ }
356
+
357
+ if (url.pathname === "/api/client-integrations/journal") {
358
+ if (req.method !== "GET") return null;
359
+ const requestedClient = url.searchParams.get("client");
360
+ if (requestedClient !== null && !isIntegrationClientId(requestedClient)) {
361
+ return invalidClientResponse(ctx);
362
+ }
363
+ try {
364
+ const store = integrationStore();
365
+ const storedOperations = store.listOperations(requestedClient ?? undefined);
366
+ const newestByClient = new Map<IntegrationClientId, string>();
367
+ for (const operation of storedOperations) {
368
+ if (!newestByClient.has(operation.clientId)) {
369
+ newestByClient.set(operation.clientId, operation.opId);
370
+ }
371
+ }
372
+ const operations: IntegrationJournalRow[] = storedOperations.map(operation => {
373
+ /*
374
+ * Resolved against the DISK, not read off the row.
375
+ *
376
+ * Retention deletes snapshot files and deliberately leaves the row's
377
+ * persisted tag saying `stored`, so copying that tag advertised undo
378
+ * for bytes that no longer exist — the GUI would offer the button and
379
+ * the restore route would answer 410. `readSnapshot` is the same
380
+ * resolver that preflight uses, which is what keeps the two agreeing.
381
+ */
382
+ const snapshot = store.readSnapshot(operation).kind;
383
+ return {
384
+ opId: operation.opId,
385
+ clientId: operation.clientId,
386
+ kind: operation.kind,
387
+ at: operation.at,
388
+ configPath: operation.configPath,
389
+ snapshot,
390
+ /*
391
+ * The SAME resolution decides `undoable`. Reporting the tag honestly
392
+ * and then offering undo anyway is the identical defect one field
393
+ * over: restore would answer 410 for a row the GUI drew a button on.
394
+ * `none` stays undoable — restoring an op that created a file means
395
+ * deleting it, and that needs no snapshot bytes.
396
+ */
397
+ /*
398
+ * Eligibility goes through the SAME matcher restore uses. This route
399
+ * used to represent a missing file as `""` and call that a match,
400
+ * while restore hashed `""` into a real digest — so the row was
401
+ * offered as Undo and then refused as drift.
402
+ */
403
+ undoable: (() => {
404
+ if (snapshot === "expired") return false;
405
+ if (newestByClient.get(operation.clientId) !== operation.opId) return false;
406
+ const current = currentConfigText(operation.configPath);
407
+ return current === undefined ? false : matchesOperationResult(operation, current);
408
+ })(),
409
+ };
410
+ });
411
+ return jsonResponse({ operations } satisfies IntegrationJournalEnvelope, 200, req, ctx.config);
412
+ } catch (error) {
413
+ return internalErrorResponse(error, ctx);
414
+ }
415
+ }
416
+
417
+ if (url.pathname === "/api/client-integrations/restore") {
418
+ if (req.method !== "POST") return null;
419
+ const parsed = await readJsonBody(ctx);
420
+ if (parsed instanceof Response) return parsed;
421
+ if (!isPlainRecord(parsed) || typeof parsed.opId !== "string" || parsed.opId.trim().length === 0) {
422
+ return jsonResponse({
423
+ error: "opId must be a non-empty string",
424
+ code: "invalid_op_id",
425
+ }, 400, req, ctx.config);
426
+ }
427
+ if (parsed.confirmDrift !== undefined && typeof parsed.confirmDrift !== "boolean") {
428
+ return jsonResponse({
429
+ error: "confirmDrift must be a boolean",
430
+ code: "invalid_confirm_drift",
431
+ }, 400, req, ctx.config);
432
+ }
433
+
434
+ const opId = parsed.opId.trim();
435
+ const confirmDrift = parsed.confirmDrift ?? false;
436
+ try {
437
+ const store = integrationStore();
438
+ const operation = store.findOperation(opId);
439
+ if (!operation) {
440
+ return jsonResponse({
441
+ error: "integration operation not found",
442
+ code: "integration_operation_not_found",
443
+ opId,
444
+ }, 404, req, ctx.config);
445
+ }
446
+ const snapshot = store.readSnapshot(operation);
447
+ if (snapshot.kind === "expired") {
448
+ return jsonResponse({
449
+ error: "integration snapshot expired",
450
+ code: "integration_snapshot_expired",
451
+ opId,
452
+ }, 410, req, ctx.config);
453
+ }
454
+
455
+ const writeInput = await buildIntegrationWriteInput(operation.clientId, ctx, store);
456
+ const restoreInput: IntegrationRestoreInput = {
457
+ ...writeInput,
458
+ opId,
459
+ confirmDrift,
460
+ };
461
+ const result = await runIntegrationMutationFlight(
462
+ operation.clientId,
463
+ `restore:${opId}:${confirmDrift}`,
464
+ writeInput.io?.now ?? Date.now,
465
+ () => Promise.resolve(restoreIntegration(restoreInput)),
466
+ );
467
+ if (!result.ok) {
468
+ /*
469
+ * Drift is NOT special-cased here.
470
+ *
471
+ * It used to be, and the hand-written branch dropped the writer's
472
+ * `message` — which is the only thing that tells the user WHICH file
473
+ * drifted and where its backup went. Every refusal leaves through the
474
+ * one serializer, so a refusal cannot lose its recovery fields by
475
+ * being routed through a shorter path.
476
+ */
477
+ return writerFailureResponse(operation.clientId, result, ctx);
478
+ }
479
+ return jsonResponse(result satisfies IntegrationRestoreEnvelope, 200, req, ctx.config);
480
+ } catch (error) {
481
+ if (error instanceof IntegrationMutationBusyError) {
482
+ return jsonResponse({
483
+ error: "integration mutation busy",
484
+ code: "integration_mutation_busy",
485
+ clientId: error.clientId,
486
+ }, 409, req, ctx.config);
487
+ }
488
+ return internalErrorResponse(error, ctx);
489
+ }
490
+ }
491
+
492
+ if (req.method !== "GET" && req.method !== "PUT") return null;
493
+ const requestedClient = decodeClientPath(url.pathname);
494
+ if (requestedClient === null) return null;
495
+ if (!isIntegrationClientId(requestedClient)) return invalidClientResponse(ctx);
496
+
497
+ if (req.method === "GET") {
498
+ try {
499
+ const input = await buildIntegrationWriteInput(requestedClient, ctx, integrationStore());
500
+ const state = readIntegrationState(input);
501
+ return jsonResponse(state satisfies IntegrationStateEnvelope, 200, req, ctx.config);
502
+ } catch (error) {
503
+ return internalErrorResponse(error, ctx);
504
+ }
505
+ }
506
+
507
+ const parsed = await readJsonBody(ctx);
508
+ if (parsed instanceof Response) return parsed;
509
+ if (!isPlainRecord(parsed) || typeof parsed.enabled !== "boolean") {
510
+ return jsonResponse({
511
+ error: "enabled must be a boolean",
512
+ code: "invalid_enabled",
513
+ }, 400, req, ctx.config);
514
+ }
515
+
516
+ try {
517
+ const input = await buildIntegrationWriteInput(requestedClient, ctx, integrationStore());
518
+ const result = await runIntegrationMutationFlight(
519
+ requestedClient,
520
+ parsed.enabled ? "apply" : "disable",
521
+ input.io?.now ?? Date.now,
522
+ () => Promise.resolve(parsed.enabled
523
+ ? applyIntegration(input)
524
+ : disableIntegration(input)),
525
+ );
526
+ if (!result.ok) return writerFailureResponse(requestedClient, result, ctx);
527
+ return jsonResponse(result satisfies IntegrationToggleEnvelope, 200, req, ctx.config);
528
+ } catch (error) {
529
+ if (error instanceof IntegrationMutationBusyError) {
530
+ return jsonResponse({
531
+ error: "integration mutation busy",
532
+ code: "integration_mutation_busy",
533
+ clientId: error.clientId,
534
+ }, 409, req, ctx.config);
535
+ }
536
+ return internalErrorResponse(error, ctx);
537
+ }
538
+ }