@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,202 @@
1
+ /**
2
+ * The service-home preflight for native-client teardown (WP2, audit r1 #5;
3
+ * devlog 260803_integrations_toggle_all/012).
4
+ *
5
+ * `rmx stop` has honored `ServiceOwnershipError` since it started catching it
6
+ * (src/cli/index.ts:464); nothing on the HTTP side ever did. A route that calls
7
+ * `stripGrokConfig` directly would otherwise pull the fence out from under a
8
+ * service running from another CODEX_HOME/OPENCODEX_HOME — the installed
9
+ * service is still live and the shared state belongs to it.
10
+ *
11
+ * ENABLE is not gated by this: writing our own fence is not a shared teardown,
12
+ * and `injectGrokConfig` already runs unguarded from `rmx start`/`ensure`.
13
+ */
14
+ import {
15
+ assertServiceEnvironmentMatchesInstall,
16
+ isServiceOwnershipError,
17
+ } from "../../service";
18
+ import {
19
+ currentServiceHomes,
20
+ inspectServiceStateEvidence,
21
+ serviceHomeMatches,
22
+ type ServiceStateEvidence,
23
+ } from "../../service";
24
+ import {
25
+ inspectServiceManagerInstallation,
26
+ type ProbeDeps,
27
+ type ServiceManagerClaim,
28
+ } from "../../service-manager-probe";
29
+
30
+ export type NativeTeardownOwnership = { ok: true } | { ok: false; message: string };
31
+
32
+ export function assertNativeTeardownOwned(): NativeTeardownOwnership {
33
+ try {
34
+ assertServiceEnvironmentMatchesInstall();
35
+ return { ok: true };
36
+ } catch (error) {
37
+ if (isServiceOwnershipError(error)) {
38
+ // The message names both the recorded and the current home — that is the
39
+ // refusal text, verbatim, because the user has to act on it.
40
+ return { ok: false, message: error.message };
41
+ }
42
+ // Unrelated failure (corrupt state file, IO): mirror
43
+ // `serviceEnvironmentOwnedHere` and fail open rather than wedging the route
44
+ // behind a check whose own input is broken.
45
+ return { ok: true };
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Tri-state ownership for UNATTENDED writes.
51
+ *
52
+ * Deliberately not `assertNativeTeardownOwned`. That one fails OPEN — a corrupt
53
+ * state file yields `{ok:true}` — which is right for a teardown route a human
54
+ * just invoked, and catastrophic as an authority for automatic convergence:
55
+ * "could not read" would become "belongs to me".
56
+ *
57
+ * `owned` here means NO PERSISTENT SERVICE CLAIM WAS OBSERVED. It does not mean
58
+ * this process is the only writer. Two foreground `rmx start` processes on one
59
+ * home both read `owned`, and correctly so, because neither installs a service —
60
+ * keeping them apart is the write lock's job, not this function's.
61
+ */
62
+ export type NativeCodexOwnership = "owned" | "foreign" | "unknown";
63
+
64
+ export interface OwnershipInspection {
65
+ readonly ownership: NativeCodexOwnership;
66
+ /** Why, in the words a refusal message can use. */
67
+ readonly reason: string;
68
+ }
69
+
70
+ function claimNamesDifferentHome(
71
+ claim: ServiceManagerClaim,
72
+ current: { codexHome: string; opencodexHome: string },
73
+ ): boolean {
74
+ // A definition that OMITS a home is not a definition that disagrees about it:
75
+ // an install run without CODEX_HOME set writes no such key at all.
76
+ if (claim.homes.codexHome !== null && !serviceHomeMatches(claim.homes.codexHome, current.codexHome)) return true;
77
+ if (claim.homes.opencodexHome !== null && !serviceHomeMatches(claim.homes.opencodexHome, current.opencodexHome)) return true;
78
+ return false;
79
+ }
80
+
81
+ /**
82
+ * Map a service-manager claim backend to the `ServiceInstallState.backend`
83
+ * value it corresponds to. `scheduler` (Task Scheduler) and `winsw` (native)
84
+ * are the two Windows manager backends; launchd/systemd claims have no Windows
85
+ * backend and can never mismatch a v2 state file.
86
+ */
87
+ function claimBackendToStateBackend(backend: ServiceManagerClaim["backend"]): "scheduler" | "native" | null {
88
+ if (backend === "scheduler") return "scheduler";
89
+ if (backend === "winsw") return "native";
90
+ return null;
91
+ }
92
+
93
+ /** True when the recorded state backend disagrees with the manager claim. Legacy v1 means scheduler. */
94
+ function claimBackendMismatchesState(claim: ServiceManagerClaim, state: { backend?: "scheduler" | "native" }): boolean {
95
+ const expected = claimBackendToStateBackend(claim.backend);
96
+ if (expected === null) return false;
97
+ return (state.backend ?? "scheduler") !== expected;
98
+ }
99
+
100
+ export interface OwnershipDeps extends ProbeDeps {
101
+ /**
102
+ * Which state paths to consult. Injectable because the default set includes
103
+ * the DEFAULT home mirror, resolved from `homedir()` — which no test sandbox
104
+ * moves. Without this a fixture reads the developer's real installation and
105
+ * calls their own machine foreign.
106
+ */
107
+ readonly statePaths?: readonly string[];
108
+ readonly currentHomes?: { codexHome: string; opencodexHome: string };
109
+ }
110
+
111
+ export function inspectNativeCodexOwnership(deps: OwnershipDeps = {}): OwnershipInspection {
112
+ const current = deps.currentHomes ?? currentServiceHomes();
113
+ const evidence = deps.statePaths
114
+ ? inspectServiceStateEvidence(deps.statePaths)
115
+ : inspectServiceStateEvidence();
116
+
117
+ const unreadable = evidence.find((e): e is Extract<ServiceStateEvidence, { kind: "unreadable" }> => e.kind === "unreadable");
118
+ if (unreadable) {
119
+ return { ownership: "unknown", reason: `service state at ${unreadable.path} could not be read (${unreadable.reason})` };
120
+ }
121
+ const invalid = evidence.find(e => e.kind === "invalid");
122
+ if (invalid) {
123
+ return { ownership: "unknown", reason: `service state at ${invalid.path} is malformed` };
124
+ }
125
+
126
+ const valid = evidence.filter((e): e is Extract<ServiceStateEvidence, { kind: "valid" }> => e.kind === "valid");
127
+ // Mirrors that disagree with each other are not a majority vote.
128
+ for (const one of valid) {
129
+ for (const other of valid) {
130
+ if (!serviceHomeMatches(one.state.codexHome, other.state.codexHome)
131
+ || !serviceHomeMatches(one.state.opencodexHome, other.state.opencodexHome)) {
132
+ return { ownership: "unknown", reason: "two service state files disagree about which homes are installed" };
133
+ }
134
+ }
135
+ }
136
+
137
+ const foreign = valid.find(e =>
138
+ !serviceHomeMatches(e.state.codexHome, current.codexHome)
139
+ || !serviceHomeMatches(e.state.opencodexHome, current.opencodexHome));
140
+ if (foreign) {
141
+ return {
142
+ ownership: "foreign",
143
+ reason: `a service is installed for CODEX_HOME=${foreign.state.codexHome} / OPENCODEX_HOME=${foreign.state.opencodexHome}`,
144
+ };
145
+ }
146
+
147
+ // The manager assets live under the effective OPENCODEX_HOME. Production
148
+ // callers do not inject ProbeDeps.configDir, so derive it from the same
149
+ // current-home snapshot used for ownership comparison rather than silently
150
+ // falling back to <homedir>/.opencodex.
151
+ const manager = inspectServiceManagerInstallation({
152
+ ...deps,
153
+ configDir: deps.configDir ?? current.opencodexHome,
154
+ });
155
+ if (manager.kind === "unknown") {
156
+ return { ownership: "unknown", reason: manager.reason };
157
+ }
158
+ if (manager.kind === "conflict") {
159
+ return { ownership: "unknown", reason: "more than one service manager holds a registration for this proxy" };
160
+ }
161
+ if (manager.kind === "present") {
162
+ const disagreeing = manager.claims.find(claim => claimNamesDifferentHome(claim, current));
163
+ if (disagreeing) {
164
+ /*
165
+ * The state file says this home and the definition says another. An
166
+ * interrupted reinstall looks exactly like this — installation writes the
167
+ * definition BEFORE the state file — and picking a winner unattended would
168
+ * be guessing which half of a half-finished operation to believe.
169
+ */
170
+ return {
171
+ ownership: "unknown",
172
+ reason: `${disagreeing.backend} is installed from ${disagreeing.definitionPath}, which names different homes than the recorded service state`,
173
+ };
174
+ }
175
+ // A manager backend that disagrees with the recorded state (e.g. state says
176
+ // native/WinSW but a scheduler task is found) is an interrupted backend
177
+ // switch: it does not prove which manager owns the installation. v1 state
178
+ // predates the field and is scheduler by contract.
179
+ const stateBackendMismatch = valid.find(state => manager.claims.some(claim => claimBackendMismatchesState(claim, state.state)));
180
+ if (stateBackendMismatch) {
181
+ return {
182
+ ownership: "unknown",
183
+ reason: `the service state records backend ${stateBackendMismatch.state.backend ?? "scheduler"} but ${manager.claims[0]?.backend ?? "a service manager"} is installed`,
184
+ };
185
+ }
186
+ // Definition agrees. Valid state agreeing with it is ownership; no state at
187
+ // all beside an installed definition is not, because the definition is the
188
+ // claim and nothing here recorded making it.
189
+ if (valid.length === 0) {
190
+ return {
191
+ ownership: "unknown",
192
+ reason: `${manager.claims[0]?.backend ?? "a service manager"} holds a registration that no service state file accounts for`,
193
+ };
194
+ }
195
+ return { ownership: "owned", reason: "the installed service names these homes" };
196
+ }
197
+
198
+ // manager.kind === "absent"
199
+ return valid.length === 0
200
+ ? { ownership: "owned", reason: "no service state and no service manager claim" }
201
+ : { ownership: "owned", reason: "the recorded service state names these homes" };
202
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * What Remodex remembers about a client between operations.
3
+ *
4
+ * Two hashes, because the two questions are genuinely independent: the FILE
5
+ * hash answers "did anyone touch this after us", and the BLOCK hash answers "is
6
+ * our content still what we would write today". One hash cannot do both, and
7
+ * conflating them is what lets a foreign edit read as ordinary drift — which
8
+ * would then be silently overwritten.
9
+ *
10
+ * Design of record: devlog/_fin/260802_client_toggle_api/021 §2.
11
+ */
12
+ import { createHash } from "node:crypto";
13
+ import { mkdirSync, readFileSync } from "node:fs";
14
+ import { dirname, join } from "node:path";
15
+ import type { ManagedContribution, ManagedFragment } from "../clients/config-export";
16
+ import { atomicWriteFile, getConfigDir } from "../config";
17
+ import type { IntegrationClientId } from "./registry";
18
+
19
+ /** 16 hex chars — the same shape as the Claude Desktop applied fingerprint. */
20
+ export function fingerprint(text: string): string {
21
+ return createHash("sha256").update(text).digest("hex").slice(0, 16);
22
+ }
23
+
24
+ /**
25
+ * Canonical bytes of a contribution. Fragments are sorted by path so two builds
26
+ * of the same contribution hash identically regardless of emission order.
27
+ */
28
+ export function canonicalContribution(contribution: ManagedContribution): string {
29
+ const sorted = [...contribution.fragments].sort((a, b) => {
30
+ const left = a.path.join("\u0000");
31
+ const right = b.path.join("\u0000");
32
+ return left < right ? -1 : left > right ? 1 : 0;
33
+ });
34
+ return JSON.stringify(sorted.map(fragment => [fragment.path, fragment.value]));
35
+ }
36
+
37
+ export interface OwnershipRecord {
38
+ clientId: IntegrationClientId;
39
+ configPath: string;
40
+ /** Hash of the WHOLE file as we left it — detects foreign edits after us. */
41
+ fileFingerprint: string;
42
+ /** Hash of our contribution — detects catalog/port drift. */
43
+ blockFingerprint: string;
44
+ /** The exact paths we own. Removal touches these and nothing else. */
45
+ fragmentPaths: readonly (readonly string[])[];
46
+ /**
47
+ * Containers this apply had to CREATE, `\0`-joined.
48
+ *
49
+ * Kimi's `models` map exists only because our aliases need somewhere to
50
+ * live. Without this, disable left it behind as an empty container in a file
51
+ * the user never asked us to restructure — while a `providers: {}` the user
52
+ * wrote themselves must survive. Optional because records written before
53
+ * this field existed simply prune nothing, which is the old behavior.
54
+ */
55
+ createdContainers?: readonly string[];
56
+ appliedAt: string;
57
+ opId: string;
58
+ }
59
+
60
+ /**
61
+ * The integrations directory itself. Every primitive takes THIS path, never a
62
+ * config root, so a caller cannot accidentally produce
63
+ * `<root>/integrations/integrations` by passing an already-resolved value.
64
+ */
65
+ export function integrationsDir(configDir: string = getConfigDir()): string {
66
+ return join(configDir, "integrations");
67
+ }
68
+
69
+ /** `atomicWriteFile` does not create parents. */
70
+ export function ensureDir(filePath: string): void {
71
+ mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
72
+ }
73
+
74
+ function recordsPath(dir: string): string {
75
+ return join(dir, "records.json");
76
+ }
77
+
78
+ export function readRecords(
79
+ dir: string = integrationsDir(),
80
+ ): Partial<Record<IntegrationClientId, OwnershipRecord>> {
81
+ try {
82
+ const parsed: unknown = JSON.parse(readFileSync(recordsPath(dir), "utf8"));
83
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
84
+ return parsed as Partial<Record<IntegrationClientId, OwnershipRecord>>;
85
+ }
86
+ } catch {
87
+ // Missing or corrupt records mean "we remember nothing", which the
88
+ // classifier reads as a conflict for an existing block. Fail closed: an
89
+ // unreadable memory is never permission to delete.
90
+ }
91
+ return {};
92
+ }
93
+
94
+ export function writeRecord(record: OwnershipRecord, dir: string = integrationsDir()): void {
95
+ const all = readRecords(dir);
96
+ all[record.clientId] = record;
97
+ ensureDir(recordsPath(dir));
98
+ atomicWriteFile(recordsPath(dir), `${JSON.stringify(all, null, 2)}\n`);
99
+ }
100
+
101
+ export function deleteRecord(clientId: IntegrationClientId, dir: string = integrationsDir()): void {
102
+ const all = readRecords(dir);
103
+ if (!(clientId in all)) return;
104
+ delete all[clientId];
105
+ ensureDir(recordsPath(dir));
106
+ atomicWriteFile(recordsPath(dir), `${JSON.stringify(all, null, 2)}\n`);
107
+ }
108
+
109
+ export function fragmentPathsOf(contribution: ManagedContribution): readonly (readonly string[])[] {
110
+ return contribution.fragments.map((fragment: ManagedFragment) => fragment.path);
111
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Where each file-toggle client keeps its config, and what we are allowed to do
3
+ * to it.
4
+ *
5
+ * The export registry (src/clients/config-export.ts) says how to RENDER a
6
+ * client's config. This one says where it lives, how to tell whether the client
7
+ * is installed at all, and whether a remote bind is safe for it.
8
+ *
9
+ * Design of record: devlog/_fin/260802_client_toggle_api/021 §1.
10
+ */
11
+ import { homedir } from "node:os";
12
+ import { join } from "node:path";
13
+ import {
14
+ EXPORT_CLIENTS,
15
+ gajaeConfigPath,
16
+ gajaeHomeDir,
17
+ hermesConfigPath,
18
+ hermesHomeDir,
19
+ kimiConfigPath,
20
+ kimiHomeDir,
21
+ ompAgentDir,
22
+ ompModelsConfigPath,
23
+ opencodeGlobalConfigPath,
24
+ openclawConfigPath,
25
+ openclawHomeDir,
26
+ type ExportClientId,
27
+ } from "../clients/config-export";
28
+
29
+ /**
30
+ * Readability alias. WP1 owns the type; this never introduces a second one, so
31
+ * the dependency only ever points backwards.
32
+ */
33
+ export type IntegrationClientId = ExportClientId;
34
+
35
+ export interface IntegrationClientSpec {
36
+ id: IntegrationClientId;
37
+ /** The client's config file, honoring that client's own environment override. */
38
+ configPath: (env?: NodeJS.ProcessEnv, home?: string) => string;
39
+ /** Directory whose existence is the cheap "is it installed?" signal. */
40
+ detectDir: (env?: NodeJS.ProcessEnv, home?: string) => string;
41
+ }
42
+
43
+ /**
44
+ * True when the client has nowhere to put the dedicated admission header a
45
+ * non-loopback bind requires, so a generated config would simply be rejected.
46
+ *
47
+ * Read from the export registry rather than restated here: two lists of the
48
+ * same fact drift, and this one decides whether we write a file that 401s.
49
+ */
50
+ export function isLoopbackOnly(clientId: IntegrationClientId): boolean {
51
+ return EXPORT_CLIENTS[clientId].loopbackOnly;
52
+ }
53
+
54
+ function xdgConfigHome(env: NodeJS.ProcessEnv, home: string): string {
55
+ const xdg = env.XDG_CONFIG_HOME;
56
+ return xdg && xdg.length > 0 ? xdg : join(home, ".config");
57
+ }
58
+
59
+ export const INTEGRATION_CLIENTS: Record<IntegrationClientId, IntegrationClientSpec> = {
60
+ opencode: {
61
+ id: "opencode",
62
+ // These take `home` explicitly. The export registry's `destination` reads
63
+ // the real home directory, which is right for telling a user where their
64
+ // file lives and wrong for a writer that a test must be able to redirect.
65
+ configPath: (env = process.env, home = homedir()) => opencodeGlobalConfigPath(env, home),
66
+ detectDir: (env = process.env, home = homedir()) => join(xdgConfigHome(env, home), "opencode"),
67
+ },
68
+ pi: {
69
+ id: "pi",
70
+ configPath: (_env = process.env, home = homedir()) => join(home, ".pi", "agent", "models.json"),
71
+ detectDir: (_env = process.env, home = homedir()) => join(home, ".pi"),
72
+ },
73
+ omp: {
74
+ id: "omp",
75
+ configPath: (env = process.env, home = homedir()) => ompModelsConfigPath(env, home),
76
+ detectDir: (env = process.env, home = homedir()) => ompAgentDir(env, home),
77
+ },
78
+ hermes: {
79
+ id: "hermes",
80
+ configPath: (env = process.env, home = homedir()) => hermesConfigPath(env, home),
81
+ detectDir: (env = process.env, home = homedir()) => hermesHomeDir(env, home),
82
+ },
83
+ openclaw: {
84
+ id: "openclaw",
85
+ configPath: (env = process.env, home = homedir()) => openclawConfigPath(env, home),
86
+ detectDir: (env = process.env, home = homedir()) => openclawHomeDir(env, home),
87
+ },
88
+ kimi: {
89
+ id: "kimi",
90
+ configPath: (env = process.env, home = homedir()) => kimiConfigPath(env, home),
91
+ detectDir: (env = process.env, home = homedir()) => kimiHomeDir(env, home),
92
+ // Kimi reads credentials only from config.toml — it never consults the
93
+ // environment — so there is no way to point it at a remote bind without
94
+ // serializing the user's key.
95
+ },
96
+ gajae: {
97
+ id: "gajae",
98
+ configPath: (env = process.env, home = homedir()) => gajaeConfigPath(env, home),
99
+ detectDir: (env = process.env, home = homedir()) => gajaeHomeDir(env, home),
100
+ },
101
+ };
102
+
103
+ export const INTEGRATION_CLIENT_IDS: readonly IntegrationClientId[] =
104
+ Object.keys(INTEGRATION_CLIENTS) as IntegrationClientId[];
105
+
106
+ export function isIntegrationClientId(value: string): value is IntegrationClientId {
107
+ return Object.prototype.hasOwnProperty.call(INTEGRATION_CLIENTS, value);
108
+ }
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Text serializers for client config documents.
3
+ *
4
+ * Bun 1.3.14 gives us JSON and JSON5 stringify natively, but the two formats a
5
+ * client config actually needs beyond JSON are hand-rendered here:
6
+ *
7
+ * - **TOML** has no `Bun.TOML.stringify` at all.
8
+ * - **YAML** has `Bun.YAML.stringify`, but it emits flow style with no trailing
9
+ * newline (`{a: 1,b: {c: d}}`). That is valid YAML and completely wrong for a
10
+ * file a user opens, so we render block style ourselves.
11
+ *
12
+ * The renderers originally covered only the shallow shapes our builders emit.
13
+ * That was fine while they only ever saw generated documents — but the
14
+ * integration writer parses the USER's whole config and re-serializes it, so a
15
+ * legitimate `null` or a numeric array in a file we never wrote threw straight
16
+ * out of the writer and surfaced as a 500. They now cover the value domains
17
+ * their formats actually allow.
18
+ *
19
+ * What they still refuse is genuinely unrenderable: a function, a symbol, a
20
+ * non-finite number. Those must fail loudly rather than produce bytes a client
21
+ * might misread — and the writer turns the failure into a structured refusal
22
+ * rather than an exception.
23
+ *
24
+ * Design of record: devlog/_fin/260802_client_toggle_api/011_wp1_builders.md.
25
+ */
26
+
27
+ export type ConfigFormat = "json" | "yaml" | "toml" | "json5";
28
+
29
+ /** Thrown when a document holds a value its format cannot represent. */
30
+ export class UnserializableValueError extends Error {}
31
+
32
+ export const FORMAT_MEDIA_TYPE: Record<ConfigFormat, string> = {
33
+ json: "application/json",
34
+ yaml: "application/yaml",
35
+ toml: "application/toml",
36
+ json5: "application/json5",
37
+ };
38
+
39
+ /**
40
+ * Type guard, not a boolean check — the renderers walk `unknown`, so without
41
+ * the predicate every narrowed branch stays `unknown`.
42
+ */
43
+ function isPlainRecord(value: unknown): value is Record<string, unknown> {
44
+ return typeof value === "object" && value !== null && !Array.isArray(value);
45
+ }
46
+
47
+ function isYamlScalar(value: unknown): value is string | number | boolean | null {
48
+ return value === null
49
+ || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
50
+ }
51
+
52
+ /**
53
+ * Keep only unambiguous, single-token strings plain. Everything else uses a
54
+ * JSON-compatible double-quoted scalar, which gives deterministic escaping —
55
+ * JSON string escaping is valid YAML 1.2 string escaping.
56
+ */
57
+ function yamlString(value: string): string {
58
+ const plainSafe = value.length > 0
59
+ && value.trim() === value
60
+ && /^[A-Za-z_./][A-Za-z0-9_./-]*$/u.test(value)
61
+ && !/^(?:null|true|false|yes|no|on|off|~|\.nan|[-+]?\.inf)$/iu.test(value);
62
+ return plainSafe ? value : JSON.stringify(value);
63
+ }
64
+
65
+ function yamlScalar(value: string | number | boolean | null): string {
66
+ // `null` renders as the explicit `null` token rather than an empty value:
67
+ // a bare `key:` is also null in YAML, but round-tripping it through other
68
+ // parsers is less predictable than saying so.
69
+ if (value === null) return "null";
70
+ if (typeof value === "string") return yamlString(value);
71
+ if (typeof value === "boolean") return value ? "true" : "false";
72
+ if (Number.isFinite(value)) return String(value);
73
+ throw new UnserializableValueError(`YAML cannot represent the number ${String(value)}`);
74
+ }
75
+
76
+ function yamlEmptyCollection(value: unknown): "[]" | "{}" | undefined {
77
+ if (Array.isArray(value) && value.length === 0) return "[]";
78
+ if (isPlainRecord(value) && Object.keys(value).length === 0) return "{}";
79
+ return undefined;
80
+ }
81
+
82
+ function yamlMapEntryLines(key: string, value: unknown, indent: number): string[] {
83
+ const padding = " ".repeat(indent);
84
+ const renderedKey = yamlString(key);
85
+ if (isYamlScalar(value)) return [`${padding}${renderedKey}: ${yamlScalar(value)}`];
86
+ const empty = yamlEmptyCollection(value);
87
+ if (empty !== undefined) return [`${padding}${renderedKey}: ${empty}`];
88
+ if (Array.isArray(value) || isPlainRecord(value)) {
89
+ return [`${padding}${renderedKey}:`, ...yamlLines(value, indent + 2)];
90
+ }
91
+ throw new UnserializableValueError(`YAML cannot represent the value at key ${JSON.stringify(key)}: ${describeValue(value)}`);
92
+ }
93
+
94
+ /** A map inside a sequence: the first key rides the dash, the rest indent under it. */
95
+ function yamlArrayMapLines(value: Record<string, unknown>, indent: number): string[] {
96
+ const entries = Object.entries(value);
97
+ const padding = " ".repeat(indent);
98
+ if (entries.length === 0) return [`${padding}- {}`];
99
+
100
+ const [first, ...rest] = entries;
101
+ const [firstKey, firstValue] = first!;
102
+ const renderedFirstKey = yamlString(firstKey);
103
+ const firstEmpty = yamlEmptyCollection(firstValue);
104
+ const lines: string[] = [];
105
+ if (isYamlScalar(firstValue)) {
106
+ lines.push(`${padding}- ${renderedFirstKey}: ${yamlScalar(firstValue)}`);
107
+ } else if (firstEmpty !== undefined) {
108
+ lines.push(`${padding}- ${renderedFirstKey}: ${firstEmpty}`);
109
+ } else if (Array.isArray(firstValue) || isPlainRecord(firstValue)) {
110
+ lines.push(`${padding}- ${renderedFirstKey}:`);
111
+ lines.push(...yamlLines(firstValue, indent + 4));
112
+ } else {
113
+ throw new UnserializableValueError(`YAML cannot represent the value at key ${JSON.stringify(firstKey)}: ${describeValue(firstValue)}`);
114
+ }
115
+ for (const [key, child] of rest) lines.push(...yamlMapEntryLines(key, child, indent + 2));
116
+ return lines;
117
+ }
118
+
119
+ function yamlLines(value: unknown, indent: number): string[] {
120
+ const padding = " ".repeat(indent);
121
+ if (isYamlScalar(value)) return [`${padding}${yamlScalar(value)}`];
122
+ if (Array.isArray(value)) {
123
+ if (value.length === 0) return [`${padding}[]`];
124
+ return value.flatMap(item => {
125
+ if (isYamlScalar(item)) return [`${padding}- ${yamlScalar(item)}`];
126
+ if (isPlainRecord(item)) return yamlArrayMapLines(item, indent);
127
+ /*
128
+ * A nested sequence is ordinary YAML (`matrix: [[1,2],[3,4]]`). This
129
+ * used to throw a plain Error carrying `String(item)` — untyped, so the
130
+ * writer could not turn it into a refusal and it surfaced as a 500, and
131
+ * carrying the value's CONTENTS into the message, which a config may
132
+ * not want repeated.
133
+ */
134
+ if (Array.isArray(item)) {
135
+ if (item.length === 0) return [`${padding}- []`];
136
+ return [`${padding}-`, ...yamlLines(item, indent + 2)];
137
+ }
138
+ throw new UnserializableValueError(
139
+ `YAML cannot represent this sequence item: ${describeValue(item)}`,
140
+ );
141
+ });
142
+ }
143
+ if (isPlainRecord(value)) {
144
+ const entries = Object.entries(value);
145
+ if (entries.length === 0) return [`${padding}{}`];
146
+ return entries.flatMap(([key, child]) => yamlMapEntryLines(key, child, indent));
147
+ }
148
+ throw new UnserializableValueError(`YAML cannot represent this value: ${describeValue(value)}`);
149
+ }
150
+
151
+ /** Block-style YAML for the shallow shapes we emit. Throws on anything else. */
152
+ export function renderYaml(value: unknown, indent = 0): string {
153
+ if (!Number.isInteger(indent) || indent < 0) {
154
+ throw new Error(`YAML indent must be a non-negative integer: ${String(indent)}`);
155
+ }
156
+ return `${yamlLines(value, indent).join("\n")}\n`;
157
+ }
158
+
159
+ /** A short, safe description for an error message — never the value's contents. */
160
+ function describeValue(value: unknown): string {
161
+ if (value === null) return "null";
162
+ if (Array.isArray(value)) return "an array";
163
+ if (typeof value === "object") return "an object";
164
+ return typeof value;
165
+ }
166
+
167
+ /** TOML basic-string escape. Mirrors `tomlString` in src/grok/inject.ts. */
168
+ export function tomlString(value: string): string {
169
+ return JSON.stringify(value);
170
+ }
171
+
172
+ /** Bare key when safe, basic-string key otherwise (TOML 1.0 §Keys). */
173
+ export function quoteTomlKey(key: string): string {
174
+ return /^[A-Za-z0-9_-]+$/.test(key) ? key : tomlString(key);
175
+ }
176
+
177
+ function tomlScalar(value: unknown): string {
178
+ if (typeof value === "string") return tomlString(value);
179
+ if (typeof value === "boolean") return value ? "true" : "false";
180
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
181
+ /*
182
+ * Arrays of ANY scalar, not just strings. The string-only check was written
183
+ * against our own builder output; a user's config legitimately holds
184
+ * `ports = [1, 2]`, and refusing it threw out of the writer as a 500.
185
+ * TOML permits mixed-type arrays since 1.0, so the elements are rendered
186
+ * individually rather than required to share a type.
187
+ */
188
+ if (Array.isArray(value)) {
189
+ return `[${value.map(item => tomlScalar(item)).join(", ")}]`;
190
+ }
191
+ /*
192
+ * An inline table is valid TOML (`items = [{ x = 1 }]`), and rendering one
193
+ * inline is straightforward — but only where the whole array can be
194
+ * rendered, so this stays a scalar-position renderer rather than silently
195
+ * flattening a table into a string.
196
+ */
197
+ if (isPlainRecord(value)) {
198
+ const entries = Object.entries(value);
199
+ return `{ ${entries.map(([k, v]) => `${quoteTomlKey(k)} = ${tomlScalar(v)}`).join(", ")} }`;
200
+ }
201
+ throw new UnserializableValueError(`TOML cannot represent this value: ${describeValue(value)}`);
202
+ }
203
+
204
+ /**
205
+ * Render `{ [table]: { key: scalar } }` as TOML. Scalars come first, then
206
+ * tables in insertion order, so two calls with the same document produce
207
+ * identical bytes — the stability guarantee `normalizeExportModels` exists to
208
+ * protect.
209
+ */
210
+ export function renderToml(document: Record<string, unknown>, prefix = ""): string {
211
+ const scalars: string[] = [];
212
+ const tables: string[] = [];
213
+ for (const [key, value] of Object.entries(document)) {
214
+ const path = prefix ? `${prefix}.${quoteTomlKey(key)}` : quoteTomlKey(key);
215
+ if (isPlainRecord(value)) {
216
+ tables.push(`[${path}]\n${renderToml(value, path)}`.trimEnd());
217
+ } else {
218
+ scalars.push(`${quoteTomlKey(key)} = ${tomlScalar(value)}`);
219
+ }
220
+ }
221
+ return `${[scalars.join("\n"), tables.join("\n\n")].filter(Boolean).join("\n\n")}\n`;
222
+ }
223
+
224
+ /** Every serializer returns text ending in exactly one newline. */
225
+ export function serializeDocument(document: unknown, format: ConfigFormat): string {
226
+ switch (format) {
227
+ case "json": return `${JSON.stringify(document, null, 2)}\n`;
228
+ case "json5": return `${Bun.JSON5.stringify(document, null, 2)}\n`;
229
+ case "yaml": return renderYaml(document);
230
+ case "toml": {
231
+ if (!isPlainRecord(document)) throw new Error("TOML root must be a table");
232
+ return renderToml(document);
233
+ }
234
+ }
235
+ }