@mycadet/assistant 0.12.7 → 0.12.9

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 (904) hide show
  1. package/README.md +1 -1
  2. package/docs/activation-funnel-telemetry.md +6 -6
  3. package/docs/cadet-doctor.md +7 -7
  4. package/docs/desktop-browser-cli.md +7 -7
  5. package/docs/skills.md +2 -2
  6. package/docs/voice-input-diagnostics.md +1 -1
  7. package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +2 -2
  8. package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
  9. package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
  10. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/package.json +2 -2
  11. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
  12. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  13. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  14. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  15. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  16. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  17. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  18. package/node_modules/@mycadet/ces-client/package.json +1 -1
  19. package/node_modules/@mycadet/ces-client/src/__tests__/ces-client.test.ts +14 -14
  20. package/node_modules/@mycadet/ces-client/src/rpc-client.ts +1 -1
  21. package/node_modules/@mycadet/credential-storage/src/oauth-runtime.ts +1 -1
  22. package/node_modules/@mycadet/credential-storage/src/static-credentials.ts +1 -1
  23. package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
  24. package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
  25. package/node_modules/@mycadet/environments/src/types.ts +6 -6
  26. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/package.json +2 -2
  27. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
  28. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  29. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  30. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  31. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  32. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  33. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  34. package/node_modules/@mycadet/gateway-client/src/binding-strength-contract.ts +5 -5
  35. package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +1 -1
  36. package/node_modules/@mycadet/gateway-client/src/inbound-contract.ts +2 -2
  37. package/node_modules/@mycadet/gateway-client/src/outbound-contract.ts +1 -1
  38. package/node_modules/@mycadet/service-contracts/package.json +2 -2
  39. package/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
  40. package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  41. package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  42. package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  43. package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  44. package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  45. package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  46. package/openapi.yaml +6804 -799
  47. package/package.json +2 -2
  48. package/scripts/build-test-fixtures.ts +1 -1
  49. package/scripts/probe-skill-retrieval.ts +1 -1
  50. package/scripts/sync-llm-catalog.ts +2 -2
  51. package/scripts/voice-ttft-spike.ts +2 -2
  52. package/src/__tests__/adaptive-thinking-repair.test.ts +4 -4
  53. package/src/__tests__/agent-loop-override-profile.test.ts +4 -4
  54. package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +1 -1
  55. package/src/__tests__/anthropic-provider.test.ts +2 -2
  56. package/src/__tests__/available-skills.test.ts +11 -12
  57. package/src/__tests__/avatar-identity-sync.test.ts +1 -1
  58. package/src/__tests__/byok-default-profile-ensure.test.ts +7 -7
  59. package/src/__tests__/call-domain.test.ts +1 -1
  60. package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +2 -2
  61. package/src/__tests__/catalog-files.test.ts +10 -10
  62. package/src/__tests__/catalog-install-normalize.test.ts +3 -3
  63. package/src/__tests__/channel-availability-routes.test.ts +8 -8
  64. package/src/__tests__/channel-readiness-routes.test.ts +4 -4
  65. package/src/__tests__/channel-readiness-service.test.ts +14 -14
  66. package/src/__tests__/channel-readiness-telegram-remote.test.ts +1 -1
  67. package/src/__tests__/chat-reveal-guard-priming.test.ts +5 -6
  68. package/src/__tests__/checker.test.ts +6 -6
  69. package/src/__tests__/clawhub.test.ts +1 -1
  70. package/src/__tests__/{platform-callback-registration.test.ts → cloud-callback-registration.test.ts} +66 -90
  71. package/src/__tests__/{platform-client-verify-credential.test.ts → cloud-client-verify-credential.test.ts} +14 -14
  72. package/src/__tests__/{managed-credential-catalog-cli.test.ts → cloud-credential-catalog-cli.test.ts} +41 -41
  73. package/src/__tests__/{managed-fallback-dispatch.test.ts → cloud-fallback-dispatch.test.ts} +21 -16
  74. package/src/__tests__/{managed-profile-guard.test.ts → cloud-profile-guard.test.ts} +77 -75
  75. package/src/__tests__/{platform-proxy-context.test.ts → cloud-proxy-context.test.ts} +44 -44
  76. package/src/__tests__/{managed-speech-defaults.test.ts → cloud-speech-defaults.test.ts} +41 -41
  77. package/src/__tests__/{managed-store.test.ts → cloud-store.test.ts} +99 -99
  78. package/src/__tests__/compaction-events.test.ts +7 -5
  79. package/src/__tests__/config-loader-backfill.test.ts +65 -65
  80. package/src/__tests__/config-loader-cloud-defaults.test.ts +373 -0
  81. package/src/__tests__/config-loader-corrupt.test.ts +1 -1
  82. package/src/__tests__/config-loader-validation-reset-notice.test.ts +1 -1
  83. package/src/__tests__/config-schema.test.ts +3 -3
  84. package/src/__tests__/{config-set-platform-guard.test.ts → config-set-cloud-guard.test.ts} +6 -6
  85. package/src/__tests__/config-set-route.test.ts +12 -8
  86. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -8
  87. package/src/__tests__/config-watcher.test.ts +1 -1
  88. package/src/__tests__/contacts-write.test.ts +1 -1
  89. package/src/__tests__/conversation-agent-loop.test.ts +2 -2
  90. package/src/__tests__/conversation-app-control-instantiation.test.ts +18 -18
  91. package/src/__tests__/conversation-error.test.ts +45 -45
  92. package/src/__tests__/conversation-inference-profile-route.test.ts +4 -4
  93. package/src/__tests__/conversation-rendered-delegation-state.test.ts +4 -4
  94. package/src/__tests__/conversation-skill-tools.test.ts +7 -8
  95. package/src/__tests__/conversation-slash.test.ts +2 -2
  96. package/src/__tests__/conversation-speed-override.test.ts +8 -10
  97. package/src/__tests__/conversation-summarize-up-to.test.ts +4 -4
  98. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -1
  99. package/src/__tests__/conversation-wait-for-idle.test.ts +7 -5
  100. package/src/__tests__/credential-execution-api-key-propagation.test.ts +5 -5
  101. package/src/__tests__/{credential-execution-managed-contract.test.ts → credential-execution-cloud-contract.test.ts} +24 -24
  102. package/src/__tests__/credential-health-service.test.ts +28 -40
  103. package/src/__tests__/credential-routes.test.ts +85 -165
  104. package/src/__tests__/credential-security-invariants.test.ts +6 -7
  105. package/src/__tests__/custom-profile-ensure.test.ts +1 -1
  106. package/src/__tests__/date-context.test.ts +1 -1
  107. package/src/__tests__/default-profile-catalog-fallback.test.ts +15 -15
  108. package/src/__tests__/delete-managed-skill-tool.test.ts +8 -8
  109. package/src/__tests__/discord-callback-round-trip.test.ts +1 -1
  110. package/src/__tests__/disk-usage.test.ts +3 -3
  111. package/src/__tests__/email-invite-adapter.test.ts +5 -5
  112. package/src/__tests__/{embedding-managed-proxy-selection.test.ts → embedding-cloud-proxy-selection.test.ts} +44 -43
  113. package/src/__tests__/emit-signal-routing-intent.test.ts +13 -15
  114. package/src/__tests__/events-dev-bypass-actor.test.ts +2 -2
  115. package/src/__tests__/fallback-breaker.test.ts +7 -7
  116. package/src/__tests__/feature-flag-test-helpers.ts +106 -0
  117. package/src/__tests__/gateway-flag-listener.test.ts +1 -1
  118. package/src/__tests__/gemini-image-service.test.ts +2 -2
  119. package/src/__tests__/gemini-provider.test.ts +5 -5
  120. package/src/__tests__/get-skill-detail-audit.test.ts +134 -288
  121. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
  122. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +5 -5
  123. package/src/__tests__/host-cu-routes-targeted.test.ts +8 -8
  124. package/src/__tests__/image-credentials.test.ts +13 -13
  125. package/src/__tests__/inbound-admitted-introduction-nudge.test.ts +14 -6
  126. package/src/__tests__/inbound-trust-verdict.test.ts +14 -6
  127. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +11 -11
  128. package/src/__tests__/inference-profile-session-handler.test.ts +7 -7
  129. package/src/__tests__/inference-profile-session-ipc.test.ts +5 -5
  130. package/src/__tests__/inference-profile-validation-jev.test.ts +1 -1
  131. package/src/__tests__/init-feature-flag-overrides.test.ts +40 -24
  132. package/src/__tests__/input-repairs.test.ts +1 -1
  133. package/src/__tests__/install-skill-routing.test.ts +10 -8
  134. package/src/__tests__/integration-status.test.ts +5 -5
  135. package/src/__tests__/internal-telemetry-routes.test.ts +3 -3
  136. package/src/__tests__/invite-routes-http.test.ts +1 -1
  137. package/src/__tests__/llm-catalog-parity.test.ts +5 -7
  138. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -2
  139. package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -3
  140. package/src/__tests__/llm-resolver.test.ts +1 -1
  141. package/src/__tests__/llm-schema-fallback-profile.test.ts +25 -19
  142. package/src/__tests__/llm-usage-store.test.ts +1 -1
  143. package/src/__tests__/managed-skill-lifecycle.test.ts +12 -12
  144. package/src/__tests__/mcp-abort-signal.test.ts +1 -2
  145. package/src/__tests__/mcp-client-auth.test.ts +1 -2
  146. package/src/__tests__/mcp-config-secret-boundary.test.ts +44 -40
  147. package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -2
  148. package/src/__tests__/media-generate-image.test.ts +30 -30
  149. package/src/__tests__/memory-config-file-parity.test.ts +6 -6
  150. package/src/__tests__/migration-export-to-gcs.test.ts +4 -4
  151. package/src/__tests__/migration-import-commit-http.test.ts +1 -1
  152. package/src/__tests__/mtime-cache.test.ts +2 -2
  153. package/src/__tests__/{notification-platform-adapter.test.ts → notification-cloud-adapter.test.ts} +39 -39
  154. package/src/__tests__/notification-deep-link.test.ts +40 -40
  155. package/src/__tests__/oauth-apps-routes.test.ts +3 -3
  156. package/src/__tests__/oauth-cli.test.ts +9 -9
  157. package/src/__tests__/oauth-commands-routes.test.ts +16 -16
  158. package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -2
  159. package/src/__tests__/oauth-provider-serializer.test.ts +11 -11
  160. package/src/__tests__/oauth-providers-routes.test.ts +12 -12
  161. package/src/__tests__/oauth-store.test.ts +1 -1
  162. package/src/__tests__/oauth2-gateway-transport.test.ts +3 -3
  163. package/src/__tests__/onboarding-persona-write.test.ts +2 -2
  164. package/src/__tests__/openai-image-service.test.ts +2 -2
  165. package/src/__tests__/openai-responses-cutover-guard.test.ts +2 -2
  166. package/src/__tests__/openai-responses-provider.test.ts +1 -1
  167. package/src/__tests__/path-policy.test.ts +4 -4
  168. package/src/__tests__/personal-memory-auth-bypass.test.ts +1 -1
  169. package/src/__tests__/platform-bash-auto-approve.test.ts +18 -18
  170. package/src/__tests__/plugin-api-model-profiles.test.ts +1 -1
  171. package/src/__tests__/plugin-api-provider.test.ts +1 -1
  172. package/src/__tests__/plugin-api-store-credential.test.ts +4 -1
  173. package/src/__tests__/plugin-api-webhook-url.test.ts +4 -5
  174. package/src/__tests__/plugin-import-boundary-guard.test.ts +8 -39
  175. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -2
  176. package/src/__tests__/{provider-platform-proxy-integration.test.ts → provider-cloud-proxy-integration.test.ts} +70 -72
  177. package/src/__tests__/provider-commit-message-generator.test.ts +1 -1
  178. package/src/__tests__/provider-connections-backfill.test.ts +9 -9
  179. package/src/__tests__/provider-error-scenarios.test.ts +8 -8
  180. package/src/__tests__/provider-registry-ollama.test.ts +1 -1
  181. package/src/__tests__/provider-send-message-override-profile.test.ts +5 -5
  182. package/src/__tests__/registry.test.ts +3 -3
  183. package/src/__tests__/requests-mock.ts +972 -0
  184. package/src/__tests__/resolve-trust-class.test.ts +1 -1
  185. package/src/__tests__/resource-pressure-guard.test.ts +6 -6
  186. package/src/__tests__/resource-pressure-routes.test.ts +5 -5
  187. package/src/__tests__/retry-fallback-escalation.test.ts +5 -5
  188. package/src/__tests__/sanitize-config-for-transfer.test.ts +1 -1
  189. package/src/__tests__/scaffold-managed-skill-tool.test.ts +97 -97
  190. package/src/__tests__/schedule-store.test.ts +1 -1
  191. package/src/__tests__/search-skills-unified.test.ts +9 -7
  192. package/src/__tests__/{secret-routes-platform-proxy.test.ts → secret-routes-cloud-proxy.test.ts} +48 -95
  193. package/src/__tests__/secret-routes-scrub.test.ts +2 -59
  194. package/src/__tests__/secure-keys.test.ts +1 -1
  195. package/src/__tests__/skill-load-plugin-scope.test.ts +2 -2
  196. package/src/__tests__/skill-load-tool.test.ts +1 -1
  197. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -19
  198. package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
  199. package/src/__tests__/skill-tool-factory.test.ts +1 -1
  200. package/src/__tests__/skills-file-content-endpoint.test.ts +222 -536
  201. package/src/__tests__/skills-files-catalog-fallback.test.ts +259 -577
  202. package/src/__tests__/skills.test.ts +1 -1
  203. package/src/__tests__/slim-skill-category.test.ts +11 -9
  204. package/src/__tests__/stt-stream-session.test.ts +3 -3
  205. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  206. package/src/__tests__/subagent-disposal.test.ts +5 -5
  207. package/src/__tests__/subagent-fork-notifications.test.ts +4 -4
  208. package/src/__tests__/subagent-fork-spawn.test.ts +5 -5
  209. package/src/__tests__/subagent-manager-notify.test.ts +5 -5
  210. package/src/__tests__/subagent-tool-gate-mode.test.ts +2 -2
  211. package/src/__tests__/system-prompt.test.ts +1 -1
  212. package/src/__tests__/telegram-bot-username-resolution.test.ts +34 -52
  213. package/src/__tests__/telegram-config.test.ts +16 -16
  214. package/src/__tests__/telephony-tts-guard.test.ts +13 -13
  215. package/src/__tests__/test-preload.ts +17 -7
  216. package/src/__tests__/tool-audit.test.ts +1 -1
  217. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +7 -5
  218. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  219. package/src/__tests__/turn-events-store.test.ts +1 -1
  220. package/src/__tests__/twilio-routes.test.ts +2 -1
  221. package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
  222. package/src/__tests__/unified-turn-context-client-os.test.ts +1 -1
  223. package/src/__tests__/validate-input.test.ts +5 -5
  224. package/src/__tests__/verification-outbound-delivery.test.ts +1 -1
  225. package/src/__tests__/voice-config-update.test.ts +15 -15
  226. package/src/__tests__/voice-ingress-preflight.test.ts +4 -4
  227. package/src/__tests__/web-search.test.ts +340 -859
  228. package/src/__tests__/workspace-git-service.test.ts +1 -1
  229. package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +12 -12
  230. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +1 -1
  231. package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +1 -1
  232. package/src/__tests__/workspace-migration-100-upgrade-quality-profile-to-fable-5.test.ts +1 -1
  233. package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +1 -1
  234. package/src/__tests__/workspace-migration-110-flip-balanced-profile-to-together.test.ts +1 -1
  235. package/src/__tests__/workspace-migration-113-swap-balanced-profile-to-glm-5p2.test.ts +1 -1
  236. package/src/__tests__/workspace-migration-114-swap-quality-profile-to-opus.test.ts +1 -1
  237. package/src/__tests__/workspace-migration-123-swap-quality-profile-to-fable.test.ts +1 -1
  238. package/src/__tests__/workspace-migration-125-repoint-managed-connections-to-cadet.test.ts +3 -3
  239. package/src/__tests__/workspace-migration-126-strip-managed-profile-bodies.test.ts +9 -9
  240. package/src/__tests__/workspace-migration-127-backfill-default-provider.test.ts +11 -11
  241. package/src/__tests__/workspace-migration-130-speech-mode-to-provider.test.ts +3 -3
  242. package/src/__tests__/workspace-migration-132-web-search-mode-to-provider.test.ts +6 -6
  243. package/src/__tests__/workspace-migration-133-collapse-provider-connections.test.ts +3 -3
  244. package/src/__tests__/workspace-migration-134-image-generation-mode-to-provider.test.ts +6 -6
  245. package/src/__tests__/workspace-migration-136-repair-stale-fireworks-kimi-model-id.test.ts +1 -1
  246. package/src/__tests__/workspace-migration-137-repair-retired-fireworks-minimax-model-id.test.ts +1 -1
  247. package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +1 -1
  248. package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +1 -1
  249. package/src/__tests__/workspace-migration-151-repair-renamed-fireworks-deepseek-pro-model-id.test.ts +1 -1
  250. package/src/__tests__/workspace-migration-152-repair-retired-fireworks-minimax-m2p7-model-id.test.ts +1 -1
  251. package/src/__tests__/workspace-migration-159-rename-colliding-jev-profile-name.test.ts +1 -1
  252. package/src/__tests__/workspace-migration-160-repair-retired-fireworks-model-ids.test.ts +1 -1
  253. package/src/__tests__/workspace-migration-161-repair-retired-fireworks-glm-5p2-model-id.test.ts +1 -1
  254. package/src/api/events/cloud-disconnected.ts +21 -0
  255. package/src/api/events/host-bash.ts +1 -1
  256. package/src/api/events/host-cu.ts +1 -1
  257. package/src/api/events/host-file.ts +1 -1
  258. package/src/api/events/notification-intent.ts +1 -1
  259. package/src/api/events/show-cloud-login.ts +18 -0
  260. package/src/api/index.ts +11 -11
  261. package/src/api/responses/home.ts +1 -1
  262. package/src/api/surfaces.ts +4 -4
  263. package/src/approvals/guardian-request-resolvers.ts +2 -2
  264. package/src/avatar/accent-backfill.ts +1 -1
  265. package/src/avatar/identity-avatar.ts +1 -1
  266. package/src/avatar/notification-avatar.test.ts +2 -2
  267. package/src/avatar/notification-avatar.ts +2 -2
  268. package/src/avatar/resvg-lazy.test.ts +1 -1
  269. package/src/avatar/resvg-lazy.ts +3 -3
  270. package/src/avatar/sync-avatar.test.ts +76 -76
  271. package/src/avatar/sync-avatar.ts +15 -15
  272. package/src/avatar/traits-png-sync.ts +1 -1
  273. package/src/background-wake/background-wake-routes.test.ts +39 -39
  274. package/src/background-wake/{platform-client.test.ts → cloud-client.test.ts} +11 -11
  275. package/src/background-wake/{platform-client.ts → cloud-client.ts} +14 -14
  276. package/src/background-wake/publisher.ts +4 -4
  277. package/src/background-wake/wake-intent-hooks.test.ts +3 -3
  278. package/src/calls/__tests__/call-controller.test.ts +2 -2
  279. package/src/calls/__tests__/voice-session-bridge.test.ts +1 -1
  280. package/src/calls/call-domain.ts +1 -1
  281. package/src/calls/resolve-call-tts-provider.ts +6 -6
  282. package/src/calls/telephony-credential-preflight.ts +3 -3
  283. package/src/calls/telephony-tts-capability.ts +5 -6
  284. package/src/calls/tts-call-strategy.ts +1 -1
  285. package/src/calls/voice-escalation-judge.ts +1 -1
  286. package/src/calls/voice-ingress-preflight.ts +4 -4
  287. package/src/cli/bundled-modules.ts +1 -1
  288. package/src/cli/commands/__tests__/apps.test.ts +18 -53
  289. package/src/cli/commands/__tests__/plugins.test.ts +13 -13
  290. package/src/cli/commands/__tests__/tts.test.ts +1 -1
  291. package/src/cli/commands/auth.help.ts +3 -3
  292. package/src/cli/commands/auth.ts +2 -4
  293. package/src/cli/commands/browser.help.ts +2 -2
  294. package/src/cli/commands/channels/index.help.ts +2 -2
  295. package/src/cli/commands/channels/index.ts +1 -1
  296. package/src/cli/commands/channels/send.ts +1 -1
  297. package/src/cli/commands/cloud/__tests__/callback-routes-list.test.ts +2 -2
  298. package/src/cli/commands/cloud/__tests__/connect.test.ts +1 -1
  299. package/src/cli/commands/cloud/__tests__/disconnect.test.ts +3 -4
  300. package/src/cli/commands/cloud/connect.ts +2 -2
  301. package/src/cli/commands/cloud/credits-format.ts +1 -1
  302. package/src/cli/commands/cloud/index.help.ts +35 -35
  303. package/src/cli/commands/cloud/index.ts +1 -1
  304. package/src/cli/commands/config.ts +2 -2
  305. package/src/cli/commands/credentials.ts +10 -12
  306. package/src/cli/commands/domain.ts +1 -1
  307. package/src/cli/commands/email.help.ts +1 -1
  308. package/src/cli/commands/email.ts +2 -2
  309. package/src/cli/commands/gateway.help.ts +1 -1
  310. package/src/cli/commands/gateway.ts +1 -1
  311. package/src/cli/commands/image-generation.help.ts +1 -1
  312. package/src/cli/commands/inference-providers.ts +1 -1
  313. package/src/cli/commands/inference.help.ts +2 -2
  314. package/src/cli/commands/memory/index.help.ts +1 -1
  315. package/src/cli/commands/oauth/connect.test.ts +2 -2
  316. package/src/cli/commands/oauth/connect.ts +1 -1
  317. package/src/cli/commands/oauth/index.help.ts +5 -5
  318. package/src/cli/commands/oauth/providers.ts +2 -2
  319. package/src/cli/commands/oauth/shared.ts +3 -3
  320. package/src/cli/commands/plugins.help.ts +2 -3
  321. package/src/cli/commands/plugins.ts +5 -5
  322. package/src/cli/commands/roadmap.help.ts +1 -1
  323. package/src/cli/commands/telemetry.help.ts +2 -2
  324. package/src/cli/commands/telemetry.ts +2 -2
  325. package/src/cli/commands/tts.help.ts +3 -3
  326. package/src/cli/commands/webhooks.help.ts +9 -9
  327. package/src/cli/commands/webhooks.ts +1 -1
  328. package/src/cli/lib/__tests__/inspect-plugin.test.ts +8 -8
  329. package/src/cli/lib/__tests__/{install-from-platform.test.ts → install-from-cloud.test.ts} +54 -54
  330. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +18 -18
  331. package/src/cli/lib/__tests__/{plugin-catalog-platform.test.ts → plugin-catalog-cloud.test.ts} +2 -2
  332. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +1 -1
  333. package/src/cli/lib/__tests__/plugin-catalog-resolve.test.ts +9 -9
  334. package/src/cli/lib/__tests__/plugin-details.test.ts +10 -10
  335. package/src/cli/lib/__tests__/plugins-install-offline.test.ts +24 -24
  336. package/src/cli/lib/bundled-marketplace.json +1 -1
  337. package/src/cli/lib/inspect-plugin.ts +2 -2
  338. package/src/cli/lib/{install-from-platform.ts → install-from-cloud.ts} +29 -29
  339. package/src/cli/lib/install-from-github.ts +4 -4
  340. package/src/cli/lib/plugin-catalog-cache.ts +7 -7
  341. package/src/cli/lib/{plugin-catalog-platform.ts → plugin-catalog-cloud.ts} +6 -6
  342. package/src/cli/lib/plugin-catalog-local.ts +2 -2
  343. package/src/cli/lib/publish-plugin.ts +9 -10
  344. package/src/cli/lib/search-plugins.ts +2 -2
  345. package/src/{platform → cloud}/client.test.ts +85 -86
  346. package/src/{platform → cloud}/client.ts +38 -46
  347. package/src/{platform/platform-patch-queue.test.ts → cloud/cloud-patch-queue.test.ts} +6 -9
  348. package/src/{platform/platform-patch-queue.ts → cloud/cloud-patch-queue.ts} +11 -11
  349. package/src/{platform/managed-speech.test.ts → cloud/cloud-speech.test.ts} +19 -19
  350. package/src/{platform/managed-speech.ts → cloud/cloud-speech.ts} +41 -41
  351. package/src/{platform → cloud}/consent-cache.test.ts +5 -5
  352. package/src/{platform → cloud}/consent-cache.ts +8 -8
  353. package/src/cloud/feature-gate.ts +8 -0
  354. package/src/{platform → cloud}/sync-identity.test.ts +4 -4
  355. package/src/{platform → cloud}/sync-identity.ts +6 -6
  356. package/src/config/__tests__/{platform-identity.test.ts → cloud-identity.test.ts} +54 -56
  357. package/src/config/__tests__/{platform-rehydration.test.ts → cloud-rehydration.test.ts} +47 -80
  358. package/src/config/__tests__/default-profile-catalog.test.ts +12 -12
  359. package/src/config/__tests__/default-provider.test.ts +4 -4
  360. package/src/config/__tests__/deployment-context-defaults.test.ts +23 -23
  361. package/src/config/__tests__/{jev-managed-profile.test.ts → jev-cloud-profile.test.ts} +3 -5
  362. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +2 -2
  363. package/src/config/__tests__/loader-last-known-good.test.ts +6 -6
  364. package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +2 -2
  365. package/src/config/__tests__/profile-materialization.test.ts +7 -7
  366. package/src/config/__tests__/seed-default-provider.test.ts +17 -17
  367. package/src/config/__tests__/substrate-twin-shadowing.test.ts +36 -26
  368. package/src/config/__tests__/sync-gated-profiles.test.ts +20 -20
  369. package/src/config/__tests__/webhook-routing.test.ts +51 -51
  370. package/src/config/bundled-skills/computer-use/TOOLS.json +12 -12
  371. package/src/config/bundled-skills/messaging/SKILL.md +8 -8
  372. package/src/config/bundled-skills/messaging/TOOLS.json +2 -2
  373. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
  374. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  375. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -2
  376. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  377. package/src/config/bundled-skills/settings/tools/shared.ts +1 -1
  378. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +12 -12
  379. package/src/config/bundled-skills/skill-management/SKILL.md +3 -3
  380. package/src/config/bundled-skills/skill-management/TOOLS.json +8 -8
  381. package/src/config/bundled-skills/skill-management/tools/{delete-managed.ts → delete-cloud.ts} +2 -2
  382. package/src/config/bundled-skills/skill-management/tools/{scaffold-managed.ts → scaffold-cloud.ts} +2 -2
  383. package/src/config/call-site-defaults.ts +6 -6
  384. package/src/config/{platform-identity.ts → cloud-identity.ts} +39 -42
  385. package/src/config/cloud-rehydration.ts +35 -0
  386. package/src/config/{managed-speech-defaults.ts → cloud-speech-defaults.ts} +13 -13
  387. package/src/config/default-profile-catalog.ts +36 -38
  388. package/src/config/default-profile-names.ts +11 -11
  389. package/src/config/default-provider-resolution.ts +2 -2
  390. package/src/config/default-provider.ts +1 -1
  391. package/src/config/env-registry.ts +21 -26
  392. package/src/config/env.ts +33 -46
  393. package/src/config/feature-flag-registry.json +5 -5
  394. package/src/config/inference-profile-validation.ts +1 -1
  395. package/src/config/llm-resolver.ts +9 -10
  396. package/src/config/loader.ts +33 -17
  397. package/src/config/profile-materialization.ts +8 -8
  398. package/src/config/profile-text-generation.test.ts +1 -1
  399. package/src/config/sanitize-for-transfer.ts +1 -1
  400. package/src/config/schema.ts +6 -6
  401. package/src/config/schemas/__tests__/stt.test.ts +2 -2
  402. package/src/config/schemas/call-site-catalog.ts +1 -1
  403. package/src/config/schemas/channels.ts +8 -0
  404. package/src/config/schemas/{platform.ts → cloud.ts} +7 -6
  405. package/src/config/schemas/host-browser.ts +1 -0
  406. package/src/config/schemas/ingress.ts +29 -10
  407. package/src/config/schemas/llm.ts +36 -36
  408. package/src/config/schemas/memory-lifecycle.ts +11 -1
  409. package/src/config/schemas/services.ts +4 -4
  410. package/src/config/schemas/stt.ts +3 -3
  411. package/src/config/schemas/tts.ts +10 -5
  412. package/src/config/schemas/voice.ts +1 -1
  413. package/src/config/seed-inference-profiles.ts +19 -19
  414. package/src/config/skill-state.ts +2 -2
  415. package/src/config/skills.ts +4 -4
  416. package/src/config/substrate-twin-shadowing.ts +12 -17
  417. package/src/config/sync-gated-profiles.ts +8 -9
  418. package/src/config/webhook-routing.ts +19 -19
  419. package/src/contacts/contacts-write.ts +1 -1
  420. package/src/credential-execution/ces-runtime.ts +6 -6
  421. package/src/credential-execution/{managed-catalog.ts → cloud-catalog.ts} +24 -24
  422. package/src/credential-execution/prompted-credential.ts +2 -23
  423. package/src/credential-health/credential-health-service.ts +59 -70
  424. package/src/daemon/__tests__/can-spawn-subagents.test.ts +1 -1
  425. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +4 -4
  426. package/src/daemon/config-watcher.ts +2 -2
  427. package/src/daemon/conversation-agent-loop.ts +9 -9
  428. package/src/daemon/conversation-error.ts +36 -36
  429. package/src/daemon/conversation-skill-tools.ts +7 -7
  430. package/src/daemon/conversation-surfaces.ts +1 -1
  431. package/src/daemon/conversation-tool-setup.ts +2 -2
  432. package/src/daemon/conversation-turn-finalize.ts +1 -1
  433. package/src/daemon/conversation-workspace.ts +1 -1
  434. package/src/daemon/conversation.ts +1 -1
  435. package/src/daemon/credential-transcript-scrub.ts +32 -35
  436. package/src/daemon/daemon-readiness.ts +1 -1
  437. package/src/daemon/event-loop-watchdog.ts +2 -2
  438. package/src/daemon/handlers/config-ingress.ts +9 -9
  439. package/src/daemon/handlers/config-telegram.ts +6 -6
  440. package/src/daemon/handlers/skills.ts +20 -20
  441. package/src/daemon/host-cu-proxy.ts +1 -1
  442. package/src/daemon/lifecycle.ts +19 -19
  443. package/src/daemon/parse-actual-tokens-from-error.ts +3 -3
  444. package/src/daemon/providers-setup.ts +6 -6
  445. package/src/daemon/resource-pressure-guard-lifecycle.ts +1 -1
  446. package/src/daemon/resource-pressure-guard.ts +6 -6
  447. package/src/daemon/shutdown-handlers.ts +1 -1
  448. package/src/daemon/sqlite-corruption-watchdog.ts +3 -3
  449. package/src/daemon/trust-context.ts +1 -1
  450. package/src/desktop/desktop-automation-lease.ts +1 -1
  451. package/src/desktop/desktop-browser-endpoint.ts +2 -4
  452. package/src/desktop/desktop-computer-use-routing.test.ts +6 -6
  453. package/src/desktop/desktop-stream-bridge.test.ts +4 -4
  454. package/src/desktop/desktop-stream-bridge.ts +1 -1
  455. package/src/desktop/{virtual-desktop-platform.test.ts → virtual-desktop-cloud.test.ts} +6 -6
  456. package/src/desktop/virtual-desktop-feature.ts +7 -7
  457. package/src/email/byo-email-credential.ts +1 -1
  458. package/src/email/registered-inbox.test.ts +16 -16
  459. package/src/email/registered-inbox.ts +20 -20
  460. package/src/heartbeat/heartbeat-service.ts +1 -1
  461. package/src/hooks/hook-loader.ts +2 -2
  462. package/src/inbound/__tests__/oauth-callback-url.test.ts +2 -2
  463. package/src/inbound/__tests__/tunnel-probe.test.ts +8 -8
  464. package/src/inbound/{platform-callback-registration.ts → cloud-callback-registration.ts} +49 -60
  465. package/src/inbound/oauth-callback-url.ts +6 -6
  466. package/src/inbound/pod-webhook-claim.ts +2 -2
  467. package/src/inbound/public-ingress-urls.ts +3 -3
  468. package/src/inbound/tunnel-probe.ts +21 -15
  469. package/src/inbound/tunnel-webhooks-gate.ts +3 -3
  470. package/src/ipc/__tests__/db-readiness-ipc-gate.test.ts +1 -1
  471. package/src/ipc/__tests__/email-ipc.test.ts +8 -8
  472. package/src/ipc/gateway-client.test.ts +55 -59
  473. package/src/ipc/gateway-flag-listener.ts +3 -3
  474. package/src/ipc/routes/db-proxy.ts +1 -1
  475. package/src/live-voice/__tests__/live-activity-reporter.test.ts +2 -2
  476. package/src/live-voice/__tests__/{live-voice-managed-speech-fallback.test.ts → live-voice-cloud-speech-fallback.test.ts} +15 -15
  477. package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +7 -7
  478. package/src/live-voice/__tests__/live-voice-stt.test.ts +1 -1
  479. package/src/live-voice/live-activity-reporter.ts +7 -7
  480. package/src/live-voice/live-voice-credential-preflight.ts +6 -6
  481. package/src/live-voice/live-voice-session.ts +3 -3
  482. package/src/live-voice/live-voice-tts.ts +1 -1
  483. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +4 -4
  484. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +1 -1
  485. package/src/mcp/mcp-oauth-provider.ts +2 -2
  486. package/src/media/gemini-image-service.ts +11 -13
  487. package/src/media/image-credentials.ts +9 -9
  488. package/src/media/openai-image-service.ts +1 -1
  489. package/src/media/types.ts +3 -3
  490. package/src/messaging/providers/channel-transport.ts +1 -1
  491. package/src/messaging/providers/discord/transport.ts +1 -1
  492. package/src/messaging/providers/gmail/adapter.ts +1 -1
  493. package/src/messaging/providers/gmail/client.ts +4 -4
  494. package/src/messaging/providers/outlook/adapter.ts +1 -1
  495. package/src/messaging/providers/slack/adapter.ts +1 -1
  496. package/src/messaging/providers/slack/transport.ts +1 -1
  497. package/src/messaging/providers/telegram-bot/transport.ts +1 -1
  498. package/src/monitoring/db-integrity-check.ts +1 -1
  499. package/src/monitoring/db-integrity-sample.ts +2 -2
  500. package/src/monitoring/mount-watch.ts +1 -1
  501. package/src/monitoring/oom-kill-reporter.ts +1 -1
  502. package/src/monitoring/worker.ts +5 -5
  503. package/src/notifications/README.md +2 -2
  504. package/src/notifications/__tests__/broadcaster.test.ts +9 -9
  505. package/src/notifications/__tests__/completion-policy.test.ts +1 -1
  506. package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +4 -4
  507. package/src/notifications/activity-failed-dedupe.ts +1 -1
  508. package/src/notifications/adapters/{platform.ts → cloud.ts} +8 -8
  509. package/src/notifications/broadcaster.ts +10 -10
  510. package/src/notifications/decision-engine.ts +1 -1
  511. package/src/notifications/destination-resolver.ts +1 -1
  512. package/src/notifications/emit-signal.ts +6 -6
  513. package/src/notifications/types.ts +2 -2
  514. package/src/oauth/__tests__/identity-verifier.test.ts +1 -1
  515. package/src/oauth/__tests__/{seed-providers-managed.test.ts → seed-providers-cloud.test.ts} +13 -13
  516. package/src/oauth/byo-connection.test.ts +2 -2
  517. package/src/oauth/{platform-connection.test.ts → cloud-connection.test.ts} +41 -43
  518. package/src/oauth/{platform-connection.ts → cloud-connection.ts} +19 -19
  519. package/src/oauth/connection-resolver.test.ts +59 -61
  520. package/src/oauth/connection-resolver.ts +22 -22
  521. package/src/oauth/connection.ts +1 -1
  522. package/src/oauth/oauth-store.ts +8 -8
  523. package/src/oauth/provider-serializer.ts +4 -4
  524. package/src/oauth/revoke.ts +1 -1
  525. package/src/oauth/seed-providers.ts +32 -32
  526. package/src/permissions/approval-provenance.ts +1 -1
  527. package/src/permissions/checker.test.ts +18 -16
  528. package/src/permissions/checker.ts +3 -3
  529. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  530. package/src/persistence/embeddings/embedding-backend.test.ts +16 -16
  531. package/src/persistence/embeddings/embedding-backend.ts +13 -13
  532. package/src/persistence/embeddings/embedding-gemini.test.ts +9 -12
  533. package/src/persistence/embeddings/embedding-gemini.ts +11 -11
  534. package/src/persistence/embeddings/embedding-runtime-manager.ts +2 -2
  535. package/src/persistence/jobs-store.ts +1 -1
  536. package/src/persistence/lifecycle-events-store.ts +1 -1
  537. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  538. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  539. package/src/persistence/migrations/224-oauth-providers-managed-service-is-paid.ts +1 -1
  540. package/src/persistence/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +1 -1
  541. package/src/persistence/migrations/296-rewrite-balanced-economy-profile-pins.ts +2 -2
  542. package/src/persistence/migrations/306-rewrite-frontier-profile-pins.test.ts +1 -1
  543. package/src/persistence/migrations/306-rewrite-frontier-profile-pins.ts +1 -1
  544. package/src/persistence/migrations/351-schedule-skill-script-handoff.ts +1 -1
  545. package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +3 -3
  546. package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +7 -7
  547. package/src/persistence/migrations/__tests__/319-remove-legacy-managed-connections.test.ts +2 -2
  548. package/src/persistence/schema/oauth.ts +1 -1
  549. package/src/plugin-api/index.ts +4 -4
  550. package/src/plugin-api/model-profiles.test.ts +1 -1
  551. package/src/plugin-api/model-profiles.ts +1 -1
  552. package/src/plugin-api/transcription-session.ts +1 -1
  553. package/src/plugin-api/vision-support.test.ts +1 -1
  554. package/src/plugin-api/webhook-url.ts +12 -12
  555. package/src/plugins/__tests__/source-fingerprint.test.ts +1 -1
  556. package/src/plugins/defaults/{platform-hosted → cloud-hosted}/__tests__/reengage.test.ts +5 -6
  557. package/src/plugins/defaults/cloud-hosted/package.json +14 -0
  558. package/src/plugins/defaults/{platform-hosted → cloud-hosted}/routes/reengage.ts +3 -3
  559. package/src/plugins/defaults/index.ts +6 -6
  560. package/src/plugins/defaults/main.ts +5 -5
  561. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +5 -5
  562. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +1 -1
  563. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +1 -1
  564. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -0
  565. package/src/plugins/defaults/memory/config.ts +4 -4
  566. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +2 -2
  567. package/src/plugins/defaults/memory/memory-retrospective-job.ts +7 -7
  568. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +5 -5
  569. package/src/plugins/defaults/memory/memory-retrospective-skill-card.ts +2 -2
  570. package/src/plugins/defaults/memory/src/__tests__/memory-v2-routes.test.ts +1 -1
  571. package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +1 -1
  572. package/src/plugins/defaults/memory/substrate/__tests__/boot-maintenance.test.ts +20 -20
  573. package/src/plugins/defaults/memory/substrate/__tests__/cli-command-store.test.ts +1 -1
  574. package/src/plugins/defaults/memory/substrate/__tests__/skill-store.test.ts +3 -3
  575. package/src/plugins/defaults/memory/substrate/boot-maintenance.ts +17 -17
  576. package/src/plugins/defaults/memory/substrate/cli-command-store.ts +2 -2
  577. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +1 -1
  578. package/src/plugins/defaults/memory/substrate/skill-store.ts +4 -4
  579. package/src/plugins/defaults/memory/v3/__tests__/maintain-job.test.ts +3 -3
  580. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +2 -2
  581. package/src/plugins/defaults/memory/v3/maintain-job.ts +10 -10
  582. package/src/plugins/defaults/memory/v3/orchestrate.ts +3 -3
  583. package/src/plugins/defaults/memory/worker.ts +5 -5
  584. package/src/plugins/defaults/workspace/injectors.ts +2 -2
  585. package/src/plugins/mtime-cache.ts +1 -1
  586. package/src/plugins/source-versions.ts +1 -1
  587. package/src/plugins/types.ts +1 -1
  588. package/src/prompts/__tests__/system-prompt.test.ts +1 -1
  589. package/src/prompts/normalize-onboarding.ts +1 -1
  590. package/src/prompts/persona-resolver.ts +1 -1
  591. package/src/prompts/templates/system-sections.ts +3 -3
  592. package/src/providers/__tests__/cadet-connection-routing.test.ts +7 -7
  593. package/src/providers/__tests__/cadet-mismatch-routing.test.ts +12 -12
  594. package/src/providers/__tests__/connection-model-compat.test.ts +2 -2
  595. package/src/providers/__tests__/dispatch-connection-routing.test.ts +10 -10
  596. package/src/providers/__tests__/inference.test.ts +2 -2
  597. package/src/providers/__tests__/preflight-resolved-config.test.ts +16 -16
  598. package/src/providers/__tests__/retry-callsite.test.ts +1 -1
  599. package/src/providers/__tests__/satellite-connection-routing.test.ts +4 -4
  600. package/src/providers/__tests__/search-provider-catalog.test.ts +2 -2
  601. package/src/providers/__tests__/voice-error-copy.test.ts +3 -3
  602. package/src/providers/anthropic/client.ts +1 -1
  603. package/src/providers/cadet/client.ts +1 -1
  604. package/src/providers/cadet-model-routing.test.ts +19 -19
  605. package/src/providers/cadet-model-routing.ts +23 -23
  606. package/src/providers/call-site-routing.ts +10 -10
  607. package/src/providers/{platform-proxy → cloud-proxy}/constants.ts +11 -11
  608. package/src/providers/{platform-proxy → cloud-proxy}/context.ts +26 -26
  609. package/src/providers/connection-resolution.ts +43 -43
  610. package/src/providers/fallback-breaker.ts +2 -2
  611. package/src/providers/fetch-provider-catalog.ts +3 -3
  612. package/src/providers/gemini/client.ts +5 -5
  613. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +3 -3
  614. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +8 -8
  615. package/src/providers/inference/__tests__/base-url-security.test.ts +4 -4
  616. package/src/providers/inference/adapter-factory.ts +35 -35
  617. package/src/providers/inference/auth.ts +10 -10
  618. package/src/providers/inference/backfill.ts +11 -11
  619. package/src/providers/inference/connection-availability.ts +24 -24
  620. package/src/providers/inference/connections.ts +18 -20
  621. package/src/providers/inference/profile-probe.ts +4 -4
  622. package/src/providers/inference/resolve-auth.ts +8 -8
  623. package/src/providers/jev/ask.test.ts +1 -1
  624. package/src/providers/jev/ask.ts +2 -2
  625. package/src/providers/model-catalog.ts +8 -9
  626. package/src/providers/openai/__tests__/api-error-normalization.test.ts +3 -3
  627. package/src/providers/openai/api-error-normalization.ts +2 -2
  628. package/src/providers/provider-availability.ts +4 -4
  629. package/src/providers/provider-env-vars.ts +1 -1
  630. package/src/providers/provider-secret-catalog.ts +1 -1
  631. package/src/providers/registry.ts +20 -20
  632. package/src/providers/retry.ts +26 -26
  633. package/src/providers/routing-identity.ts +10 -10
  634. package/src/providers/search-provider-catalog.ts +2 -2
  635. package/src/providers/speech-to-text/__tests__/{cadet-managed-realtime.test.ts → cadet-cloud-realtime.test.ts} +3 -3
  636. package/src/providers/speech-to-text/__tests__/{cadet-managed.test.ts → cadet-cloud.test.ts} +35 -35
  637. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +1 -1
  638. package/src/providers/speech-to-text/__tests__/resolve.test.ts +27 -27
  639. package/src/providers/speech-to-text/{cadet-managed-flux-realtime.ts → cadet-cloud-flux-realtime.ts} +6 -8
  640. package/src/providers/speech-to-text/{cadet-managed-realtime.ts → cadet-cloud-realtime.ts} +3 -3
  641. package/src/providers/speech-to-text/{cadet-managed.ts → cadet-cloud.ts} +18 -20
  642. package/src/providers/speech-to-text/cadet-speech-relay-connection.ts +7 -7
  643. package/src/providers/speech-to-text/deepgram-flux-frames.ts +3 -3
  644. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +4 -4
  645. package/src/providers/speech-to-text/provider-catalog.ts +3 -3
  646. package/src/providers/speech-to-text/resolve.ts +26 -28
  647. package/src/providers/together/client.ts +1 -1
  648. package/src/providers/types.ts +4 -4
  649. package/src/providers/voice-error-copy.ts +1 -1
  650. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +10 -10
  651. package/src/runtime/agent-wake.ts +1 -1
  652. package/src/runtime/auth/__tests__/middleware.test.ts +1 -1
  653. package/src/runtime/auth/middleware.ts +1 -1
  654. package/src/runtime/auth/scopes.ts +1 -1
  655. package/src/runtime/capabilities.ts +1 -1
  656. package/src/runtime/channel-invite-transports/discord.ts +1 -1
  657. package/src/runtime/channel-invite-transports/email.ts +2 -2
  658. package/src/runtime/channel-readiness-service.ts +15 -13
  659. package/src/runtime/channel-reply-session.test.ts +1 -1
  660. package/src/runtime/channel-reply-session.ts +1 -1
  661. package/src/runtime/http-server.ts +4 -4
  662. package/src/runtime/introduction-policy.ts +4 -4
  663. package/src/runtime/migrations/__tests__/origin-mode.test.ts +14 -14
  664. package/src/runtime/migrations/__tests__/v1-test-helpers.ts +1 -1
  665. package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +2 -2
  666. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +3 -4
  667. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +6 -7
  668. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +1 -1
  669. package/src/runtime/migrations/migration-transport.ts +3 -3
  670. package/src/runtime/migrations/origin-mode.ts +6 -9
  671. package/src/runtime/migrations/vbundle-import-policy.ts +2 -2
  672. package/src/runtime/migrations/vbundle-importer.ts +4 -4
  673. package/src/runtime/migrations/vbundle-streaming-importer.ts +6 -6
  674. package/src/runtime/migrations/vbundle-validator.ts +1 -1
  675. package/src/runtime/routes/__tests__/{platform-credits-routes.test.ts → cloud-credits-routes.test.ts} +9 -9
  676. package/src/runtime/routes/__tests__/{platform-invoice-routes.test.ts → cloud-invoice-routes.test.ts} +13 -13
  677. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +1 -1
  678. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +126 -82
  679. package/src/runtime/routes/__tests__/default-plugin-routes.test.ts +5 -5
  680. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +10 -10
  681. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +18 -18
  682. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +18 -16
  683. package/src/runtime/routes/__tests__/inference-send-routes.test.ts +1 -1
  684. package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +1 -1
  685. package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +5 -20
  686. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +19 -22
  687. package/src/runtime/routes/__tests__/oauth-proxy-routes.test.ts +27 -27
  688. package/src/runtime/routes/__tests__/plugins-routes.test.ts +5 -5
  689. package/src/runtime/routes/__tests__/ps-routes.test.ts +5 -3
  690. package/src/runtime/routes/__tests__/roadmap-routes.test.ts +15 -15
  691. package/src/runtime/routes/__tests__/webhook-routes.test.ts +31 -31
  692. package/src/runtime/routes/app-management-routes.ts +1 -1
  693. package/src/runtime/routes/attachment-routes.ts +1 -1
  694. package/src/runtime/routes/auth-routes.ts +14 -14
  695. package/src/runtime/routes/avatar-routes.ts +0 -14
  696. package/src/runtime/routes/background-wake-routes.ts +4 -4
  697. package/src/runtime/routes/channel-availability-routes.ts +4 -4
  698. package/src/runtime/routes/client-routes.ts +5 -5
  699. package/src/runtime/routes/cloud-credentials.ts +45 -0
  700. package/src/runtime/routes/{platform-routes.ts → cloud-routes.ts} +86 -119
  701. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  702. package/src/runtime/routes/conversation-query-routes.ts +345 -667
  703. package/src/runtime/routes/credential-routes.ts +28 -30
  704. package/src/runtime/routes/default-provider-routes.ts +1 -1
  705. package/src/runtime/routes/desktop-setup-routes.test.ts +5 -5
  706. package/src/runtime/routes/desktop-setup-routes.ts +1 -1
  707. package/src/runtime/routes/domain-routes.ts +15 -21
  708. package/src/runtime/routes/email-routes.ts +13 -13
  709. package/src/runtime/routes/errors.ts +1 -1
  710. package/src/runtime/routes/gateway-status-routes.ts +1 -1
  711. package/src/runtime/routes/identity-routes.ts +3 -3
  712. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  713. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +1 -1
  714. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  715. package/src/runtime/routes/index.ts +1 -1
  716. package/src/runtime/routes/inference-profile-availability-guard.ts +5 -5
  717. package/src/runtime/routes/inference-profiles-routes.ts +11 -11
  718. package/src/runtime/routes/inference-provider-connection-routes.ts +24 -24
  719. package/src/runtime/routes/ingress-status-routes.ts +1 -1
  720. package/src/runtime/routes/integrations/a2a.ts +2 -2
  721. package/src/runtime/routes/integrations/twilio.ts +21 -35
  722. package/src/runtime/routes/internal-telemetry-routes.ts +2 -2
  723. package/src/runtime/routes/live-voice-routes.test.ts +4 -4
  724. package/src/runtime/routes/live-voice-routes.ts +6 -6
  725. package/src/runtime/routes/migration-routes.ts +34 -40
  726. package/src/runtime/routes/oauth-commands-routes.ts +31 -31
  727. package/src/runtime/routes/oauth-providers.ts +5 -5
  728. package/src/runtime/routes/oauth-proxy-passthrough.test.ts +1 -1
  729. package/src/runtime/routes/oauth-proxy-passthrough.ts +8 -8
  730. package/src/runtime/routes/oauth-proxy-routes.ts +15 -17
  731. package/src/runtime/routes/oauth-request-hints.test.ts +1 -1
  732. package/src/runtime/routes/oauth-request-hints.ts +1 -1
  733. package/src/runtime/routes/plugins-routes.ts +9 -9
  734. package/src/runtime/routes/profiler-routes.ts +1 -1
  735. package/src/runtime/routes/ps-routes.ts +4 -0
  736. package/src/runtime/routes/roadmap-routes.ts +18 -20
  737. package/src/runtime/routes/secret-routes.ts +20 -61
  738. package/src/runtime/routes/settings-routes.ts +18 -23
  739. package/src/runtime/routes/telemetry-routes.ts +4 -4
  740. package/src/runtime/routes/tts-routes.ts +4 -4
  741. package/src/runtime/routes/user-route-resolution.ts +1 -1
  742. package/src/runtime/routes/webhook-routes.ts +18 -18
  743. package/src/runtime/routes/workflow-routes.ts +5 -5
  744. package/src/runtime/sync/resource-sync-events.ts +2 -2
  745. package/src/runtime/verification-outbound-actions.ts +4 -4
  746. package/src/schedule/integration-status.ts +6 -6
  747. package/src/schedule/run-script.ts +1 -1
  748. package/src/schedule/schedule-timezone.ts +1 -1
  749. package/src/schedule/worker.ts +5 -5
  750. package/src/security/auth-fallback-events-store.ts +1 -1
  751. package/src/security/oauth2.ts +4 -4
  752. package/src/skills/available-skills.ts +4 -4
  753. package/src/skills/catalog-files.ts +8 -8
  754. package/src/skills/catalog-install.ts +12 -12
  755. package/src/skills/categories-cache.ts +3 -3
  756. package/src/skills/clawhub.ts +1 -1
  757. package/src/skills/{platform-compatibility.test.ts → cloud-compatibility.test.ts} +7 -7
  758. package/src/skills/{platform-compatibility.ts → cloud-compatibility.ts} +2 -4
  759. package/src/skills/{managed-store.ts → cloud-store.ts} +21 -23
  760. package/src/skills/inline-command-runner.ts +1 -1
  761. package/src/skills/install-meta.ts +1 -1
  762. package/src/skills/path-classifier.ts +4 -4
  763. package/src/skills/skill-history.ts +4 -4
  764. package/src/skills/skillssh-registry.ts +1 -1
  765. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +1 -1
  766. package/src/stt/daemon-batch-transcriber.ts +7 -7
  767. package/src/stt/types.ts +3 -3
  768. package/src/subagent/manager.ts +15 -15
  769. package/src/subagent/types.ts +1 -1
  770. package/src/telegram/__tests__/webhook-health.test.ts +16 -16
  771. package/src/telegram/webhook-health.ts +10 -10
  772. package/src/telemetry/__tests__/config-setting-events-store.test.ts +1 -1
  773. package/src/telemetry/__tests__/outbox-test-harness.ts +2 -2
  774. package/src/telemetry/config-setting-events-store.ts +1 -1
  775. package/src/telemetry/config-setting-snapshot.ts +1 -1
  776. package/src/telemetry/memory-corpus-size.ts +1 -1
  777. package/src/telemetry/memory-tier-reporter.test.ts +2 -2
  778. package/src/telemetry/memory-tier-reporter.ts +3 -3
  779. package/src/telemetry/telemetry-event-sources.test.ts +2 -2
  780. package/src/telemetry/telemetry-event-sources.ts +9 -9
  781. package/src/telemetry/telemetry-events-outbox.ts +2 -2
  782. package/src/telemetry/telemetry-wire-validation.test.ts +1 -1
  783. package/src/telemetry/telemetry-wire-validation.ts +3 -3
  784. package/src/telemetry/telemetry-wire.generated.ts +1 -1
  785. package/src/telemetry/tool-audit.ts +1 -1
  786. package/src/telemetry/tool-executed-events-store.test.ts +1 -1
  787. package/src/telemetry/trace-collection-policy.ts +4 -4
  788. package/src/telemetry/turn-events-store.ts +1 -1
  789. package/src/telemetry/turn-outcome.ts +1 -1
  790. package/src/telemetry/turn-trace-store.ts +2 -2
  791. package/src/telemetry/types.test.ts +1 -1
  792. package/src/telemetry/types.ts +19 -19
  793. package/src/telemetry/usage-telemetry-reporter.test.ts +42 -42
  794. package/src/telemetry/usage-telemetry-reporter.ts +17 -17
  795. package/src/telemetry/watchdog-direct-emit.test.ts +9 -9
  796. package/src/telemetry/watchdog-direct-emit.ts +10 -10
  797. package/src/tools/__tests__/side-effect-abort-guard.test.ts +2 -2
  798. package/src/tools/browser/cdp-client/factory.ts +1 -1
  799. package/src/tools/computer-use/target.ts +2 -2
  800. package/src/tools/credentials/store.ts +2 -20
  801. package/src/tools/network/__tests__/{managed-search-proxy.test.ts → cloud-search-proxy.test.ts} +20 -20
  802. package/src/tools/network/__tests__/web-search.test.ts +14 -14
  803. package/src/tools/network/{managed-search-proxy.ts → cloud-search-proxy.ts} +13 -13
  804. package/src/tools/network/web-search.ts +9 -9
  805. package/src/tools/permission-checker.ts +2 -2
  806. package/src/tools/shared/input-misuse.ts +1 -1
  807. package/src/tools/shared/input-repairs.ts +4 -4
  808. package/src/tools/skills/{delete-managed.ts → delete-cloud.ts} +4 -4
  809. package/src/tools/skills/find-similar-skills.test.ts +7 -7
  810. package/src/tools/skills/find-similar-skills.ts +15 -15
  811. package/src/tools/skills/load.ts +6 -6
  812. package/src/tools/skills/{scaffold-managed.ts → scaffold-cloud.ts} +16 -16
  813. package/src/tools/skills/skill-script-runner.ts +2 -2
  814. package/src/tools/terminal/safe-env.ts +3 -3
  815. package/src/tools/tool-types.ts +2 -2
  816. package/src/tools/types.ts +2 -2
  817. package/src/tools/ui-surface/surface-shape-docs.ts +2 -2
  818. package/src/tools/workflows/manage-workflows.ts +1 -1
  819. package/src/tools/workspace-tools/loader.ts +1 -1
  820. package/src/tts/__tests__/cadet-provider.test.ts +5 -5
  821. package/src/tts/provider-catalog.ts +1 -1
  822. package/src/tts/providers/cadet-provider.ts +23 -23
  823. package/src/tts/providers/cadet-tts-socket.ts +2 -2
  824. package/src/tts/tts-config-resolver.ts +1 -1
  825. package/src/tts/tts-voice-field.ts +3 -3
  826. package/src/usage/subagent-attribution.ts +1 -1
  827. package/src/util/__tests__/process-table.test.ts +47 -0
  828. package/src/util/canonicalize-identity.ts +1 -1
  829. package/src/util/cgroup-cpu.ts +5 -5
  830. package/src/util/cgroup-memory.ts +2 -2
  831. package/src/util/container-cpu-sampler.ts +4 -4
  832. package/src/util/disk-usage.ts +4 -4
  833. package/src/util/errors.ts +3 -3
  834. package/src/util/layout.ts +479 -0
  835. package/src/util/oom-priority.ts +1 -1
  836. package/src/util/platform.ts +12 -468
  837. package/src/util/process-table.ts +39 -5
  838. package/src/util/provider-error-patterns.ts +2 -2
  839. package/src/watch/__tests__/watch-retro.test.ts +2 -2
  840. package/src/watch/watch-retro.ts +1 -1
  841. package/src/watcher/__tests__/telemetry.test.ts +2 -2
  842. package/src/watcher/telemetry.ts +1 -1
  843. package/src/windows-compiled-cli.ts +1 -1
  844. package/src/workspace/adaptive-thinking-repair.ts +4 -4
  845. package/src/workspace/byok-default-profile-ensure.ts +7 -7
  846. package/src/workspace/git-service.ts +2 -2
  847. package/src/workspace/migrations/006-services-config.ts +1 -1
  848. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +1 -1
  849. package/src/workspace/migrations/052-seed-default-inference-profiles.ts +1 -1
  850. package/src/workspace/migrations/063-release-notes-dynamic-model-context.ts +1 -1
  851. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +1 -1
  852. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +4 -4
  853. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +3 -3
  854. package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +4 -4
  855. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +5 -5
  856. package/src/workspace/migrations/100-upgrade-quality-profile-to-fable-5.ts +4 -4
  857. package/src/workspace/migrations/101-upgrade-balanced-economy-to-minimax-m3.ts +4 -4
  858. package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +5 -5
  859. package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +1 -1
  860. package/src/workspace/migrations/106-drop-collect-usage-data.ts +1 -1
  861. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +4 -4
  862. package/src/workspace/migrations/109-swap-quality-profile-to-glm-5p2.ts +11 -11
  863. package/src/workspace/migrations/110-flip-balanced-profile-to-together.ts +6 -6
  864. package/src/workspace/migrations/113-swap-balanced-profile-to-glm-5p2.ts +5 -5
  865. package/src/workspace/migrations/114-swap-quality-profile-to-opus.ts +4 -4
  866. package/src/workspace/migrations/115-drop-frontier-profile.ts +5 -5
  867. package/src/workspace/migrations/123-swap-quality-profile-to-fable.ts +5 -5
  868. package/src/workspace/migrations/125-repoint-managed-connections-to-cadet.ts +3 -3
  869. package/src/workspace/migrations/126-strip-managed-profile-bodies.ts +2 -2
  870. package/src/workspace/migrations/127-backfill-default-provider.ts +6 -6
  871. package/src/workspace/migrations/130-speech-mode-to-provider.ts +4 -4
  872. package/src/workspace/migrations/131-drop-web-fetch-mode.ts +1 -1
  873. package/src/workspace/migrations/132-web-search-mode-to-provider.ts +6 -6
  874. package/src/workspace/migrations/133-collapse-provider-connections.ts +3 -3
  875. package/src/workspace/migrations/134-image-generation-mode-to-provider.ts +4 -4
  876. package/src/workspace/migrations/136-repair-stale-fireworks-kimi-model-id.ts +2 -2
  877. package/src/workspace/migrations/137-repair-retired-fireworks-minimax-model-id.ts +2 -2
  878. package/src/workspace/migrations/139-clear-renamed-cost-profile-label.ts +2 -2
  879. package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +4 -4
  880. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +1 -1
  881. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +2 -2
  882. package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +2 -2
  883. package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +2 -2
  884. package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +1 -1
  885. package/src/workspace/migrations/151-repair-renamed-fireworks-deepseek-pro-model-id.ts +2 -2
  886. package/src/workspace/migrations/152-repair-retired-fireworks-minimax-m2p7-model-id.ts +2 -2
  887. package/src/workspace/migrations/159-rename-colliding-jev-profile-name.ts +2 -2
  888. package/src/workspace/migrations/160-repair-retired-fireworks-model-ids.ts +2 -2
  889. package/src/workspace/migrations/161-repair-retired-fireworks-glm-5p2-model-id.ts +2 -2
  890. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +1 -1
  891. package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +1 -1
  892. package/src/workspace/migrations/registry.ts +6 -6
  893. package/src/workspace/top-level-renderer.ts +1 -1
  894. package/src/workspace/turn-commit.ts +2 -2
  895. package/src/__tests__/config-loader-platform-defaults.test.ts +0 -606
  896. package/src/__tests__/mock-gateway-ipc.ts +0 -260
  897. package/src/api/events/platform-disconnected.ts +0 -21
  898. package/src/api/events/show-platform-login.ts +0 -20
  899. package/src/config/platform-rehydration.ts +0 -60
  900. package/src/platform/feature-gate.ts +0 -8
  901. package/src/plugins/defaults/platform-hosted/package.json +0 -14
  902. package/src/runtime/routes/platform-managed-credentials.ts +0 -50
  903. /package/src/__tests__/{gateway-client-managed-outbound.test.ts → gateway-client-cloud-outbound.test.ts} +0 -0
  904. /package/src/__tests__/{secure-keys-managed-failover.test.ts → secure-keys-cloud-failover.test.ts} +0 -0
@@ -35,19 +35,16 @@ import {
35
35
  scrubNulledChatSettings,
36
36
  validateChatSettingsWrite,
37
37
  } from "../../config/chat-settings.js";
38
- import { DefaultProviderSelectionSchema } from "../../config/default-provider.js";
39
38
  import {
40
39
  catalogEntryFor,
41
- type InputModalities,
42
40
  modalitiesOf,
43
41
  resolveModalityOverride,
44
42
  } from "../../config/input-modalities.js";
45
43
  import {
46
44
  deepMergeOverwrite,
47
- fillContextDefaultsForMissingKeys,
48
45
  getConfig,
49
- getDeploymentContextDefaults,
50
46
  invalidateConfigCache,
47
+ loadConfig,
51
48
  loadRawConfig,
52
49
  saveRawConfig,
53
50
  setNestedValue,
@@ -58,22 +55,21 @@ import {
58
55
  completeCustomProfile,
59
56
  mergePreservingUnknownKeys,
60
57
  } from "../../config/profile-materialization.js";
61
- import { AssistantConfigSchema } from "../../config/schema.js";
58
+ import {
59
+ type AssistantConfig,
60
+ AssistantConfigSchema,
61
+ } from "../../config/schema.js";
62
62
  import { getSchemaAtPath } from "../../config/schema-utils.js";
63
- import { AutoArchiveConfigSchema } from "../../config/schemas/conversations.js";
64
63
  import {
65
64
  collectFallbackProfileIssues,
66
- isDefaultProviderChoice,
67
65
  LLMConfigBase,
68
66
  LLMConfigFragment,
69
- LLMDefaultConfigSchema,
70
67
  LLMSchema,
71
68
  ProfileEntry,
72
69
  routingIdentityModelIssue,
73
70
  unknownLlmProviderIssue,
74
71
  } from "../../config/schemas/llm.js";
75
72
  import { VALID_MEMORY_EMBEDDING_PROVIDERS } from "../../config/schemas/memory-storage.js";
76
- import { NotificationsConfigSchema } from "../../config/schemas/notifications.js";
77
73
  import { ServiceModeSchema } from "../../config/schemas/services.js";
78
74
  import { isSidebarDoneEnabled } from "../../config/sidebar-done-gate.js";
79
75
  import {
@@ -123,12 +119,12 @@ import { type LogRow } from "../../persistence/llm-request-log-store.js";
123
119
  import { getMemoryRecallLogByMessageIds } from "../../plugins/defaults/memory/memory-recall-log-store.js";
124
120
  import { getMemoryV2ActivationLogByMessageIds } from "../../plugins/defaults/memory/v2/activation-log-store.js";
125
121
  import { getMemoryV3SelectionForInspectorByMessageIds } from "../../plugins/defaults/memory/v3/selection-log-store.js";
126
- import { MANAGED_ROUTABLE_PROVIDERS } from "../../providers/cadet-model-routing.js";
122
+ import { CLOUD_ROUTABLE_PROVIDERS } from "../../providers/cadet-model-routing.js";
127
123
  import { writableProfileProviderIssue } from "../../providers/connection-resolution.js";
128
124
  import { ROUTING_IDENTITY_PROVIDERS } from "../../providers/inference/auth.js";
129
125
  import { PROVIDERS_REQUIRING_BASE_URL_AND_MODELS } from "../../providers/inference/auth.js";
130
126
  import {
131
- CADET_MANAGED_CONNECTION_NAME,
127
+ CADET_CLOUD_CONNECTION_NAME,
132
128
  createConnection,
133
129
  getConnection,
134
130
  LEGACY_MANAGED_CONNECTION_NAMES,
@@ -527,217 +523,22 @@ async function handleSetEmbeddingConfig({ body }: RouteHandlerArgs) {
527
523
  }
528
524
 
529
525
  /**
530
- * Apply deployment-context defaults to a raw config payload before it goes
531
- * out over the wire from `GET /v1/config`. The in-memory `loadConfig()`
532
- * already layers these defaults for daemon-internal consumers; the GET
533
- * response needs the same treatment so external clients (macOS, web, CLI)
534
- * see the effective value rather than `undefined` when the daemon hasn't
535
- * persisted an explicit choice yet. For example, on a freshly-hatched
536
- * platform-managed assistant, `services.image-generation.provider` may be
537
- * absent from disk (only `llm.profiles` was written by
538
- * `seedInferenceProfiles`); the fill pass ensures clients receive `"cadet"`
539
- * rather than falling back to their own defaults.
540
- *
541
- * Guards against `loadRawConfig()` handing us a value that is technically
542
- * valid JSON but not a plain object (e.g. literal `null`, a number, or an
543
- * array). `loadRawConfig` is typed `Record<string, unknown>` but `JSON.parse`
544
- * itself doesn't enforce that — a malformed-but-parseable `config.json`
545
- * would blow up `fillContextDefaultsForMissingKeys` on its `target[key]` /
546
- * `fileConfig[key]` accesses, turning `GET /v1/config` into a 500 where it
547
- * used to succeed (returning the malformed payload as-is). When `raw` is
548
- * not a plain object, we return it unchanged.
549
- *
550
- * Exported for direct unit testing.
526
+ * Tolerant shape parses for raw config-write fragments — zod replaces the
527
+ * hand-rolled plain-object narrowing (`readPlainObject`) at every guard.
528
+ * They model exactly the fields each guard reads and pass everything else
529
+ * through, so a write guard sees intent even when the surrounding payload
530
+ * is sparse or malformed. A parse copies, so mutating guards write the
531
+ * normalized slice back onto the caller's object instead of relying on
532
+ * shared references.
551
533
  */
552
- export function applyContextDefaultsToRawConfig(raw: unknown): unknown {
553
- const contextDefaults = getDeploymentContextDefaults();
554
- if (
555
- Object.keys(contextDefaults).length === 0 ||
556
- raw === null ||
557
- typeof raw !== "object" ||
558
- Array.isArray(raw)
559
- ) {
560
- return raw;
561
- }
562
- fillContextDefaultsForMissingKeys(
563
- raw as Record<string, unknown>,
564
- raw as Record<string, unknown>,
565
- contextDefaults,
566
- );
567
- synthesizeLegacyInferenceModeForPlatform(raw as Record<string, unknown>);
568
- return raw;
569
- }
570
-
571
- /**
572
- * Backwards-compat wire field for `GET /v1/config`. PR removed
573
- * `services.inference.mode` from the typed schema (routing is governed by
574
- * `provider_connections` rows + per-profile `provider_connection`), but
575
- * the macOS settings client (`SettingsStore.swift:loadServiceModes`) still
576
- * reads this field and falls back to its `@Published` default of "your-own"
577
- * when absent. On a platform-managed assistant served by a newer daemon and
578
- * an older macOS client, that fallback would show the wrong mode in the UI
579
- * until the user explicitly saved. Synthesize the value here so the wire
580
- * shape stays compatible during the rollout window. Remove once the macOS
581
- * Providers UI (the follow-up PR that retires this field on the client) has
582
- * shipped to the majority of installs.
583
- *
584
- * The synthesis is wire-only: it never persists to disk and never reaches
585
- * the typed `AssistantConfig` consumed by daemon-internal code. The on-disk
586
- * config is stripped of `mode` by workspace migration 076.
587
- *
588
- * Only runs when this function is reached, which is guarded by
589
- * `getDeploymentContextDefaults()` returning non-empty (IS_PLATFORM=true).
590
- */
591
- function synthesizeLegacyInferenceModeForPlatform(
592
- root: Record<string, unknown>,
593
- ): void {
594
- const services = readPlainObject(root.services);
595
- if (!services) {
596
- return;
597
- }
598
- let inference = readPlainObject(services.inference);
599
- if (!inference) {
600
- inference = {};
601
- services.inference = inference;
602
- }
603
- if (inference.mode === undefined) {
604
- inference.mode = "managed";
605
- }
606
- }
607
-
608
- function readPlainObject(value: unknown): Record<string, unknown> | undefined {
609
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
610
- return undefined;
611
- }
612
- return value as Record<string, unknown>;
613
- }
614
-
615
- function stripTransportHeadersRecursively(value: unknown): void {
616
- if (Array.isArray(value)) {
617
- for (const item of value) {
618
- stripTransportHeadersRecursively(item);
619
- }
620
- return;
621
- }
622
-
623
- const object = readPlainObject(value);
624
- if (!object) {
625
- return;
626
- }
627
- const transport = readPlainObject(object.transport);
628
- if (transport) {
629
- delete transport.headers;
630
- }
631
- for (const child of Object.values(object)) {
632
- stripTransportHeadersRecursively(child);
633
- }
634
- }
635
-
636
- function containsTransportHeadersRecursively(value: unknown): boolean {
637
- if (Array.isArray(value)) {
638
- return value.some((item) => containsTransportHeadersRecursively(item));
639
- }
640
-
641
- const object = readPlainObject(value);
642
- if (!object) {
643
- return false;
644
- }
645
- const transport = readPlainObject(object.transport);
646
- if (transport && Object.hasOwn(transport, "headers")) {
647
- return true;
648
- }
649
- return Object.values(object).some((child) =>
650
- containsTransportHeadersRecursively(child),
651
- );
652
- }
653
-
654
- function sanitizeMcpTransportHeadersForSettingsRead(config: unknown): void {
655
- const root = readPlainObject(config);
656
- if (!root) {
657
- return;
658
- }
659
- const mcp = readPlainObject(root.mcp);
660
- if (!mcp || !Object.hasOwn(mcp, "servers")) {
661
- return;
662
- }
663
- if (Array.isArray(mcp.servers)) {
664
- stripTransportHeadersRecursively(mcp.servers);
665
- return;
666
- }
667
- const servers = readPlainObject(mcp.servers);
668
- if (!servers) {
669
- return;
670
- }
671
- for (const server of Object.values(servers)) {
672
- stripTransportHeadersRecursively(server);
673
- }
674
- }
675
-
676
- function patchContainsMcpTransportHeaders(patch: unknown): boolean {
677
- const root = readPlainObject(patch);
678
- const mcp = readPlainObject(root?.mcp);
679
- if (!mcp || !Object.hasOwn(mcp, "servers")) {
680
- return false;
681
- }
682
- if (Array.isArray(mcp.servers)) {
683
- return containsTransportHeadersRecursively(mcp.servers);
684
- }
685
- const servers = readPlainObject(mcp.servers);
686
- if (!servers) {
687
- return false;
688
- }
689
- return Object.values(servers).some((server) =>
690
- containsTransportHeadersRecursively(server),
691
- );
692
- }
693
-
694
- function rejectMcpTransportHeaderWrite(patch: unknown): void {
695
- if (!patchContainsMcpTransportHeaders(patch)) {
696
- return;
697
- }
698
- throw new BadRequestError(
699
- "MCP authentication headers must be managed through MCP server add/update APIs, not generic config writes.",
700
- );
701
- }
702
-
703
- const MCP_CONFIG_WRITE_MESSAGE =
704
- "MCP servers are stored in mcp.json. Use assistant mcp add or assistant mcp remove.";
705
-
706
- function stripMcpFromConfigWrite(patch: Record<string, unknown>): void {
707
- if (Object.hasOwn(patch, "mcp")) {
708
- delete patch.mcp;
709
- }
710
- }
711
-
712
- function rejectMcpConfigSetPath(path: string, value: unknown): void {
713
- if (path !== "mcp" && !path.startsWith("mcp.")) {
714
- return;
715
- }
716
- const relative =
717
- path === "mcp" ? value : nestPath(path.slice("mcp.".length), value);
718
- if (patchContainsMcpTransportHeaders({ mcp: relative })) {
719
- throw new BadRequestError(
720
- "MCP authentication headers must be managed through MCP server add/update APIs, not generic config writes.",
721
- );
722
- }
723
- throw new BadRequestError(MCP_CONFIG_WRITE_MESSAGE);
724
- }
725
-
726
- function nestPath(path: string, value: unknown): unknown {
727
- const segments = path.split(".");
728
- let current: unknown = value;
729
- for (let i = segments.length - 1; i >= 0; i--) {
730
- current = { [segments[i]!]: current };
731
- }
732
- return current;
733
- }
734
-
735
- const WireProfileEntry = ProfileEntry.extend({
736
- supportsVision: z.boolean().optional(),
737
- invariant: z.boolean().optional(),
738
- })
739
- .passthrough()
740
- .meta({ id: "ProfileEntry" });
534
+ const JsonRecordSchema = z.record(z.string(), z.unknown());
535
+ const RawProfileEntrySchema = z.record(z.string(), z.unknown());
536
+ const RawLlmWriteSliceSchema = z
537
+ .object({
538
+ default: z.unknown().optional(),
539
+ profiles: z.record(z.string(), z.unknown()).nullable().optional(),
540
+ })
541
+ .passthrough();
741
542
 
742
543
  /**
743
544
  * Wire shape of the `memory` section in config responses. Passthrough
@@ -752,97 +553,6 @@ const MemoryWireConfigSchema = z
752
553
  .passthrough()
753
554
  .meta({ id: "MemoryConfig" });
754
555
 
755
- /**
756
- * Response schema for `GET /v1/config`.
757
- *
758
- * Describes the wire shape of the raw `settings.json` response after
759
- * context-default filling and vision-flag enrichment. Chat settings include
760
- * effective defaults. Fields remain optional for older assistants and sparse
761
- * on-disk config. Additional top-level sections are preserved via
762
- * passthrough. This schema types the fields that web/macOS clients consume
763
- * without restricting the full config surface.
764
- */
765
- const ConfigGetResponseSchema = z
766
- .object({
767
- // A malformed or absent stored chat-settings subtree falls back to its
768
- // schema defaults so one bad leaf can't fail the whole wire parse (which
769
- // would strip every derived field from the response); the chat-settings
770
- // read overlay refills both from the effective config.
771
- conversations: z
772
- .object({
773
- autoArchive: AutoArchiveConfigSchema.passthrough().catch(
774
- AutoArchiveConfigSchema.parse({}),
775
- ),
776
- })
777
- .passthrough()
778
- .optional(),
779
- notifications: NotificationsConfigSchema.passthrough()
780
- .optional()
781
- .catch(undefined),
782
- llm: z
783
- .object({
784
- default: LLMDefaultConfigSchema.optional(),
785
- // Wire-compat view of the default provider, derived from `default`
786
- // by the read overlay (the key is never stored anymore).
787
- defaultProvider: DefaultProviderSelectionSchema.optional(),
788
- profiles: z.record(z.string(), WireProfileEntry).optional(),
789
- profileOrder: z.array(z.string()).optional(),
790
- activeProfile: z.string().optional(),
791
- // The profile the advisor consults; excluded from chat-profile pickers.
792
- advisorProfile: z.string().optional(),
793
- callSites: z
794
- .record(
795
- z.string(),
796
- LLMConfigFragment.extend({
797
- profile: z.string().optional(),
798
- }).nullable(),
799
- )
800
- .optional(),
801
- profileSession: z
802
- .object({
803
- defaultTtlSeconds: z.number().optional(),
804
- maxTtlSeconds: z.number().optional(),
805
- })
806
- .optional(),
807
- pricingOverrides: z.array(z.unknown()).optional(),
808
- })
809
- .passthrough()
810
- .optional(),
811
- memory: MemoryWireConfigSchema.optional(),
812
- services: z
813
- .object({
814
- "web-search": z
815
- .object({
816
- provider: z.string().optional(),
817
- })
818
- .passthrough()
819
- .optional(),
820
- "web-fetch": z
821
- .object({
822
- provider: z.string().optional(),
823
- })
824
- .passthrough()
825
- .optional(),
826
- "image-generation": z
827
- .object({
828
- provider: z.string().optional(),
829
- model: z.string().optional(),
830
- })
831
- .passthrough()
832
- .optional(),
833
- inference: z
834
- .object({ mode: ServiceModeSchema.optional() })
835
- .passthrough()
836
- .optional(),
837
- })
838
- .passthrough()
839
- .optional(),
840
- })
841
- .passthrough()
842
- .meta({ id: "ConfigGetResponse" });
843
-
844
- type ConfigGetResponse = z.infer<typeof ConfigGetResponseSchema>;
845
-
846
556
  /**
847
557
  * Given a `z.object(...)` schema, returns a new schema where every property
848
558
  * is `.nullable().optional()`. Used to express PATCH body semantics where any
@@ -880,7 +590,7 @@ const CallSiteOverrideDraftSchema = nullablePartial(
880
590
  *
881
591
  * Mirrors the response shape but every field is `.nullable().optional()`:
882
592
  * omitted keys are left unchanged by the daemon's deep-merge, `null` values
883
- * delete the key. Uses the same Zod enums as `ConfigGetResponseSchema` so
593
+ * delete the key. Uses the same Zod enums as `AssistantConfigSchema` so
884
594
  * the generated SDK produces literal-union types — no hand-written patch
885
595
  * types needed downstream.
886
596
  */
@@ -953,54 +663,39 @@ const ConfigPatchRequestSchema = z
953
663
  .passthrough()
954
664
  .meta({ id: "ConfigPatchRequest" });
955
665
 
666
+ /**
667
+ * The response contract for `GET`/`PATCH /v1/config` — the five subtrees
668
+ * external clients actually read, picked out of the full graph. Rendering
669
+ * the whole `AssistantConfigSchema` into the spec blasted openapi.yaml past
670
+ * 12K lines; we're not ready to trim the response schema itself yet, so the
671
+ * runtime still returns the full config and the spec documents these.
672
+ */
673
+ const ConfigGetResponseSchema = AssistantConfigSchema.pick({
674
+ conversations: true,
675
+ notifications: true,
676
+ llm: true,
677
+ memory: true,
678
+ services: true,
679
+ });
680
+
956
681
  function handleGetConfig() {
957
682
  try {
958
- const config = applyContextDefaultsToRawConfig(loadRawConfig());
959
- // One zod pass shapes the response: ConfigGetResponseSchema is this
960
- // route's wire contract, so the overlays stamp their derived fields on
961
- // the parsed view instead of digging handles out of raw objects. A
962
- // config the schema rejects gets no overlays and is served raw.
963
- const parsed = ConfigGetResponseSchema.safeParse(config);
964
- if (!parsed.success) {
965
- return config;
966
- }
967
- const wire = parsed.data;
968
- overlayWorkspaceMcpForConfigRead(wire);
969
- sanitizeMcpTransportHeadersForSettingsRead(wire);
970
- overlayEffectiveProfilesForWire(wire);
971
- enrichProfilesForWire(wire);
972
- overlayDefaultProviderForWire(wire);
973
- overlayChatSettingsForWire(wire);
974
- return wire;
683
+ // The overlays stamp wire-only state (catalog bodies, effective chat
684
+ // settings, capability flags) onto the returned object. Clone first so
685
+ // the loader's cached config — and every other `getConfig()` reader,
686
+ // including the config-write merge base — never sees the wire view.
687
+ const config = structuredClone(loadConfig());
688
+ overlayWorkspaceMcpForConfigRead(config);
689
+ overlayEffectiveProfilesForWire(config);
690
+ enrichProfilesForWire(config);
691
+ overlayChatSettingsForWire(config);
692
+ return config;
975
693
  } catch (err) {
976
694
  const message = err instanceof Error ? err.message : String(err);
977
695
  throw new InternalError(`Failed to read config: ${message}`);
978
696
  }
979
697
  }
980
-
981
- /**
982
- * Surface the default-provider selection on config reads. The selection lives
983
- * in the `llm.default` blob; the dedicated `defaultProvider` response field is
984
- * a derived wire-compat view (readers of the config GET predate the blob) and
985
- * is never stored on disk — it is stamped here from the blob on every read.
986
- */
987
- function overlayDefaultProviderForWire(config: ConfigGetResponse): void {
988
- if (!config.llm) {
989
- return;
990
- }
991
- const blob = config.llm.default;
992
- const provider = blob?.provider;
993
- if (provider != null && isDefaultProviderChoice(provider)) {
994
- config.llm.defaultProvider = {
995
- provider,
996
- connectionName: blob?.provider_connection,
997
- };
998
- } else {
999
- delete config.llm.defaultProvider;
1000
- }
1001
- }
1002
-
1003
- function overlayChatSettingsForWire(config: ConfigGetResponse): void {
698
+ function overlayChatSettingsForWire(config: AssistantConfig): void {
1004
699
  const effective = getConfig();
1005
700
  config.conversations = {
1006
701
  ...config.conversations,
@@ -1023,7 +718,7 @@ function overlayChatSettingsForWire(config: ConfigGetResponse): void {
1023
718
  * render labels/models — so the wire view materializes them, resolved through
1024
719
  * `llm.default`'s column of the intent × provider matrix so a BYO
1025
720
  * install sees the provider/model that actually dispatches. Wire-only:
1026
- * `normalizeManagedProfileWrites` reduces echoed bodies back to the
721
+ * `normalizeCloudProfileWrites` reduces echoed bodies back to the
1027
722
  * workspace-owned fields on the write paths, so a `config get` →
1028
723
  * `config set` round-trip never persists catalog content — the two must
1029
724
  * resolve through the same column or honest round-trips are rejected.
@@ -1033,47 +728,13 @@ function overlayChatSettingsForWire(config: ConfigGetResponse): void {
1033
728
  * shields the matrix lookup from a persisted value the matrix cannot
1034
729
  * implement, which a raw read would feed straight into resolution.
1035
730
  */
1036
- function overlayEffectiveProfilesForWire(config: ConfigGetResponse): void {
1037
- if (!config.llm) {
1038
- config.llm = {};
1039
- }
731
+ function overlayEffectiveProfilesForWire(config: AssistantConfig): void {
1040
732
  const parsedLlm = LLMSchema.safeParse(config.llm);
1041
733
  config.llm.profiles = getUserSelectableProfilesForProvider(
1042
734
  parsedLlm.success ? parsedLlm.data : undefined,
1043
735
  );
1044
736
  }
1045
737
 
1046
- /**
1047
- * Per-profile keys that exist only on the wire — stamped onto config
1048
- * responses by {@link enrichProfilesForWire}, never persisted to disk.
1049
- * {@link stripWireOnlyProfileKeys} removes them from incoming writes so a
1050
- * `config get` → `config set`/PATCH round-trip isn't rejected for phantom
1051
- * fields; keep the stamp and strip lists in lock-step.
1052
- */
1053
- const WIRE_ONLY_PROFILE_KEYS = new Set(["invariant", "supportsVision"]);
1054
-
1055
- /**
1056
- * Delete the wire-only keys ({@link WIRE_ONLY_PROFILE_KEYS}) from every
1057
- * profile entry in a config-write fragment, in place.
1058
- */
1059
- function stripWireOnlyProfileKeys(patch: unknown): void {
1060
- const root = readPlainObject(patch);
1061
- const llm = readPlainObject(root?.llm);
1062
- const profiles = readPlainObject(llm?.profiles);
1063
- if (!profiles) {
1064
- return;
1065
- }
1066
- for (const profile of Object.values(profiles)) {
1067
- const entry = readPlainObject(profile);
1068
- if (!entry) {
1069
- continue;
1070
- }
1071
- for (const key of WIRE_ONLY_PROFILE_KEYS) {
1072
- delete entry[key];
1073
- }
1074
- }
1075
- }
1076
-
1077
738
  /**
1078
739
  * Normalize managed default-profile entries in a config-write fragment, in
1079
740
  * place, so a `config get` → write round-trip of the enriched wire view is a
@@ -1093,33 +754,39 @@ function stripWireOnlyProfileKeys(patch: unknown): void {
1093
754
  * shadowed or given content. A clean echo reduces to a no-op
1094
755
  * `{source: "managed"}` stub.
1095
756
  *
1096
- * Entries are mutated (never replaced) so the normalization also reaches
1097
- * `handleSetConfig`'s `raw` write, which shares object references with the
1098
- * inspected patch shape.
757
+ * The llm slice is parsed through {@link RawLlmWriteSliceSchema} and the
758
+ * normalized slice written back — a zod parse copies, so the write-back (not
759
+ * shared references) is what carries the normalization to the caller's patch.
760
+ * `handleSetConfig` reads the written-back shape for entry-level SETs.
1099
761
  */
1100
- export function normalizeManagedProfileWrites(patch: unknown): void {
1101
- const root = readPlainObject(patch);
1102
- const llm = readPlainObject(root?.llm);
1103
- const profiles = readPlainObject(llm?.profiles);
1104
- if (!profiles) {
762
+ export function normalizeCloudProfileWrites(
763
+ patch: Record<string, unknown>,
764
+ ): void {
765
+ const llm = RawLlmWriteSliceSchema.safeParse(patch.llm);
766
+ if (!llm.success || !llm.data.profiles) {
1105
767
  return;
1106
768
  }
769
+ const profiles = llm.data.profiles;
1107
770
 
1108
- const currentProfiles = readPlainObject(
1109
- readPlainObject(loadRawConfig().llm)?.profiles,
1110
- ) as Record<string, ProfileEntry> | undefined;
771
+ const currentLlm = RawLlmWriteSliceSchema.safeParse(loadRawConfig().llm);
772
+ const typedCurrent = currentLlm.success
773
+ ? LLMSchema.shape.profiles.safeParse(currentLlm.data.profiles)
774
+ : undefined;
775
+ const currentProfiles: Record<string, unknown> | undefined =
776
+ typedCurrent?.success ? typedCurrent.data : undefined;
1111
777
 
1112
778
  for (const name of Object.keys(profiles)) {
1113
779
  if (!MANAGED_PROFILE_NAMES.has(name)) {
1114
780
  continue;
1115
781
  }
1116
- const entry = readPlainObject(profiles[name]);
1117
- if (!entry) {
782
+ const entry = RawProfileEntrySchema.safeParse(profiles[name]);
783
+ if (!entry.success) {
1118
784
  continue;
1119
785
  }
1120
786
 
1121
- const current = readPlainObject(currentProfiles?.[name]);
1122
- if (current && current.source !== "managed") {
787
+ const current = RawProfileEntrySchema.safeParse(currentProfiles?.[name]);
788
+
789
+ if (current.success && current.data.source !== "managed") {
1123
790
  // Legacy user-owned shadow: fully editable, normal custom-profile rules.
1124
791
  continue;
1125
792
  }
@@ -1127,26 +794,27 @@ export function normalizeManagedProfileWrites(patch: unknown): void {
1127
794
  // Must resolve through the same provider column as
1128
795
  // `overlayEffectiveProfilesForWire`, or an echo of the wire view would
1129
796
  // not match `effective` and an honest round-trip would be rejected.
1130
- const effective = readPlainObject(
1131
- resolveDefaultProfileForProvider(
1132
- currentProfiles,
1133
- name,
1134
- getConfig().llm.default,
1135
- ),
797
+ const resolved = resolveDefaultProfileForProvider(
798
+ typedCurrent?.success ? typedCurrent.data : undefined,
799
+ name,
800
+ getConfig().llm.default,
1136
801
  );
1137
- for (const key of Object.keys(entry)) {
802
+ const effective: Record<string, unknown> | undefined = resolved
803
+ ? { ...resolved }
804
+ : undefined;
805
+ for (const key of Object.keys(entry.data)) {
1138
806
  if (key === "source" || key === "status") {
1139
807
  continue;
1140
808
  }
1141
809
  const matchesEffective =
1142
810
  effective != null &&
1143
811
  key in effective &&
1144
- isDeepStrictEqual(entry[key], effective[key]);
1145
- if (current == null || !(key in current)) {
812
+ isDeepStrictEqual(entry.data[key], effective[key]);
813
+ if (!current.success || !(key in current.data)) {
1146
814
  // Not on disk: a value matching the wire view is a client echo of
1147
815
  // catalog content — drop it. Anything else stays for the guard.
1148
816
  if (matchesEffective) {
1149
- delete entry[key];
817
+ delete entry.data[key];
1150
818
  }
1151
819
  continue;
1152
820
  }
@@ -1156,93 +824,101 @@ export function normalizeManagedProfileWrites(patch: unknown): void {
1156
824
  // from disk is pinned back to the on-disk value (no change for the
1157
825
  // guard to reject); everything else is left for the guard's
1158
826
  // frozen-field comparison.
1159
- if (matchesEffective && !isDeepStrictEqual(entry[key], current[key])) {
1160
- entry[key] = current[key];
827
+ if (
828
+ matchesEffective &&
829
+ !isDeepStrictEqual(entry.data[key], current.data[key])
830
+ ) {
831
+ entry.data[key] = current.data[key];
1161
832
  }
1162
833
  }
1163
834
 
1164
- if (current) {
835
+ if (current.success) {
1165
836
  // Existing stub: the guard compares the merged result against it and
1166
837
  // rejects everything but the status re-enable.
1167
- if (entry.source === undefined) {
1168
- entry.source = "managed";
838
+ if (entry.data.source === undefined) {
839
+ entry.data.source = "managed";
1169
840
  }
841
+ profiles[name] = entry.data;
1170
842
  continue;
1171
843
  }
1172
844
 
1173
845
  // No on-disk entry: the name is catalog-owned. Nothing but a clean echo
1174
- // (which reduced to source/status above) may pass.
1175
- if ("source" in entry && entry.source !== "managed") {
846
+ // (which reduced to source/status above) may pass. The wire-only flags
847
+ // ride echoes too — the write path's schema parse strips them before
848
+ // anything persists.
849
+ if ("source" in entry.data && entry.data.source !== "managed") {
1176
850
  throw new BadRequestError(
1177
851
  `Cannot create profile "${name}" — the name is reserved for a code-defined default profile.`,
1178
852
  );
1179
853
  }
1180
854
  if (
1181
- "status" in entry &&
1182
- entry.status != null &&
1183
- entry.status !== "active"
855
+ "status" in entry.data &&
856
+ entry.data.status != null &&
857
+ entry.data.status !== "active"
1184
858
  ) {
1185
- throw new BadRequestError(`Cannot disable managed profile "${name}".`);
859
+ throw new BadRequestError(`Cannot disable cloud profile "${name}".`);
1186
860
  }
1187
- const residual = Object.keys(entry).filter(
1188
- (key) => key !== "source" && key !== "status",
861
+ const residual = Object.keys(entry.data).filter(
862
+ (key) =>
863
+ key !== "source" &&
864
+ key !== "status" &&
865
+ key !== "invariant" &&
866
+ key !== "supportsVision",
1189
867
  );
1190
868
  if (residual.length > 0) {
1191
869
  throw new BadRequestError(
1192
- `Cannot edit managed profile "${name}" fields [${residual.join(", ")}]. ` +
1193
- `Managed profiles are read-only; duplicate to a custom profile to customize.`,
870
+ `Cannot edit cloud profile "${name}" fields [${residual.join(", ")}]. ` +
871
+ `Cloud profiles are read-only; duplicate to a custom profile to customize.`,
1194
872
  );
1195
873
  }
1196
- entry.source = "managed";
874
+ entry.data.source = "managed";
875
+ profiles[name] = entry.data;
1197
876
  }
877
+
878
+ patch.llm = llm.data;
1198
879
  }
1199
880
 
1200
881
  /**
1201
- * Annotate each profile in `config.llm.profiles` with wire-only flags
1202
- * (`WIRE_ONLY_PROFILE_KEYS`) — never persisted to disk:
882
+ * The enriched profile shape clients consume: {@link ProfileEntry} plus the
883
+ * wire-only flags `enrichProfilesForWire` stamps onto config-read responses.
884
+ * The flags are deliberately absent from `ProfileEntry` — the persisted
885
+ * schema — so a config write's schema parse strips echoed copies instead of
886
+ * persisting them.
887
+ */
888
+ type WireProfileEntry = ProfileEntry & {
889
+ invariant?: boolean;
890
+ supportsVision?: boolean;
891
+ };
892
+
893
+ /**
894
+ * Annotate each profile in `config.llm.profiles` with wire-only flags —
895
+ * never persisted to disk:
1203
896
  *
1204
897
  * - `supportsVision`: resolved from the model catalog, with a profile
1205
898
  * `inputModalities.image` override winning when set. Unknown (provider,
1206
899
  * model) pairs with no override default to `true` (fail-open) so image
1207
900
  * upload remains available for custom / unlisted models.
1208
- * - `invariant`: `true` for managed-source entries of the managed profile
901
+ * - `invariant`: `true` for managed-source entries of the cloud profile
1209
902
  * names (`INVARIANT_PROFILE_NAMES`); absent otherwise. Source-gated to
1210
903
  * match `assertInvariantProfilesPreserved` — a user-owned profile sharing
1211
- * a managed name is fully editable, so it must render as a normal custom
904
+ * a cloud name is fully editable, so it must render as a normal custom
1212
905
  * profile.
1213
906
  */
1214
- function enrichProfilesForWire(config: unknown): void {
1215
- const root = readPlainObject(config);
1216
- if (!root) {
1217
- return;
1218
- }
1219
- const llm = readPlainObject(root.llm);
1220
- if (!llm) {
1221
- return;
1222
- }
1223
- const profiles = readPlainObject(llm.profiles);
1224
- if (!profiles) {
1225
- return;
1226
- }
1227
-
1228
- for (const [name, profile] of Object.entries(profiles)) {
1229
- const entry = readPlainObject(profile);
1230
- if (!entry) {
1231
- continue;
1232
- }
907
+ function enrichProfilesForWire(config: AssistantConfig): void {
908
+ const profiles: Record<string, WireProfileEntry> = config.llm.profiles;
909
+ for (const [name, entry] of Object.entries(profiles)) {
1233
910
  if (INVARIANT_PROFILE_NAMES.has(name) && entry.source === "managed") {
1234
911
  entry.invariant = true;
1235
912
  }
1236
- const provider = entry.provider;
1237
- const model = entry.model;
1238
- if (typeof provider !== "string" || typeof model !== "string") {
913
+
914
+ if (entry.provider == null || entry.model == null) {
1239
915
  continue;
1240
916
  }
1241
917
 
1242
- const catalogModel = catalogEntryFor(provider, model);
918
+ const catalogModel = catalogEntryFor(entry.provider, entry.model);
1243
919
  const catalogVision = catalogModel?.supportsVision;
1244
920
  const imageOverride = modalitiesOf({
1245
- inputModalities: entry.inputModalities as InputModalities | null,
921
+ inputModalities: entry.inputModalities,
1246
922
  })?.image;
1247
923
  entry.supportsVision =
1248
924
  resolveModalityOverride(imageOverride, catalogVision) ?? true;
@@ -1281,44 +957,47 @@ function handleGetConfigSchema({ queryParams = {} }: RouteHandlerArgs) {
1281
957
  };
1282
958
  }
1283
959
 
1284
- export function rejectManagedProfileDeletion(
960
+ export function rejectCloudProfileDeletion(
1285
961
  body: Record<string, unknown>,
1286
962
  ): void {
1287
- const llm = asMutablePlainObject(body.llm);
1288
- if (!llm) {
963
+ const llm = RawLlmWriteSliceSchema.safeParse(body.llm);
964
+ if (!llm.success) {
1289
965
  return;
1290
966
  }
1291
- if ("profiles" in llm && llm.profiles === null) {
967
+ if (llm.data.profiles === null) {
1292
968
  throw new BadRequestError(
1293
- "Cannot null llm.profiles — managed profiles would be deleted.",
969
+ "Cannot null llm.profiles — cloud profiles would be deleted.",
1294
970
  );
1295
971
  }
1296
- const profiles = asMutablePlainObject(llm.profiles);
972
+ const profiles = llm.data.profiles;
1297
973
  if (!profiles) {
1298
974
  return;
1299
975
  }
1300
- const existingProfiles = asMutablePlainObject(getConfig().llm.profiles) ?? {};
976
+ const existingLlm = RawLlmWriteSliceSchema.safeParse(getConfig().llm);
977
+ const existingProfiles = existingLlm.success
978
+ ? (existingLlm.data.profiles ?? {})
979
+ : {};
1301
980
  for (const name of Object.keys(profiles)) {
1302
981
  if (profiles[name] !== null || !MANAGED_PROFILE_NAMES.has(name)) {
1303
982
  continue;
1304
983
  }
1305
- // Only block deletion when the on-disk entry is Cadet-managed. A
1306
- // user-owned profile sharing a managed name carries a non-managed `source`
984
+ // Only block deletion when the on-disk entry is Cadet Cloud. A
985
+ // user-owned profile sharing a cloud name carries a non-managed `source`
1307
986
  // and is freely deletable.
1308
- const existing = asMutablePlainObject(existingProfiles[name]);
1309
- if (existing?.source === "managed") {
1310
- throw new BadRequestError(`Cannot delete managed profile "${name}".`);
987
+ const existing = RawProfileEntrySchema.safeParse(existingProfiles[name]);
988
+ if (existing.success && existing.data.source === "managed") {
989
+ throw new BadRequestError(`Cannot delete cloud profile "${name}".`);
1311
990
  }
1312
991
  }
1313
992
  }
1314
993
 
1315
994
  /**
1316
- * Enforce the managed-profile invariants at the config-write choke point.
995
+ * Enforce the cloud-profile invariants at the config-write choke point.
1317
996
  *
1318
997
  * Protects the thin managed stubs (`INVARIANT_PROFILE_NAMES`) that live in
1319
998
  * workspace config. Default profile CONTENT is code-owned and only exists on
1320
999
  * the wire, so the guard's job is the stub itself: it checks entries present
1321
- * in the OLD config, and `normalizeManagedProfileWrites` reduces incoming
1000
+ * in the OLD config, and `normalizeCloudProfileWrites` reduces incoming
1322
1001
  * managed entries to the workspace-owned fields before this comparison runs.
1323
1002
  *
1324
1003
  * Invariance is gated on managed ownership: a name is enforced only when the
@@ -1327,7 +1006,7 @@ export function rejectManagedProfileDeletion(
1327
1006
  * is real and supported, because the flag-gated reconcile refuses to
1328
1007
  * overwrite a same-named user profile.
1329
1008
  *
1330
- * For each invariant name where the OLD raw config carries a managed-source
1009
+ * For each invariant name where the OLD raw config carries a cloud-source
1331
1010
  * plain-object entry at `llm.profiles[name]`:
1332
1011
  *
1333
1012
  * - The NEW raw config must still carry a plain-object entry at the same
@@ -1338,11 +1017,11 @@ export function rejectManagedProfileDeletion(
1338
1017
  * profile can never be disabled; a changed `status` must be `"active"`,
1339
1018
  * `null`, or absent — re-enabling a disabled profile. Any other value is
1340
1019
  * rejected.
1341
- * - Wire-only keys (`WIRE_ONLY_PROFILE_KEYS`) are ignored on both sides:
1342
- * incoming writes have them stripped, but configs persisted before the
1343
- * strip existed may still carry them on disk, and treating that stale key
1344
- * as a removed field would reject every round-trip write until reboot
1345
- * reseeds the profile.
1020
+ * - The wire-only flags (`invariant`, `supportsVision`) are ignored on both
1021
+ * sides: the write path's schema parse strips them from incoming writes,
1022
+ * but configs persisted before that was true may still carry them on
1023
+ * disk, and treating that stale key as a removed field would reject every
1024
+ * round-trip write until reboot reseeds the profile.
1346
1025
  * - Every other field is frozen: any changed, added, or removed key across
1347
1026
  * the union of both entries' keys (except `status` and the wire-only keys)
1348
1027
  * is rejected. A pre-existing on-disk override (e.g. `topP`) is preserved
@@ -1376,17 +1055,17 @@ function assertInvariantProfilesPreserved(
1376
1055
  : null;
1377
1056
  if (!newEntry) {
1378
1057
  throw new BadRequestError(
1379
- `Cannot delete or replace managed profile "${name}". Managed profiles are read-only.`,
1058
+ `Cannot delete or replace cloud profile "${name}". Cloud profiles are read-only.`,
1380
1059
  );
1381
1060
  }
1382
1061
 
1383
1062
  if (!isDeepStrictEqual(oldEntry.status, newEntry.status)) {
1384
1063
  if (newEntry.status === "disabled") {
1385
- throw new BadRequestError(`Cannot disable managed profile "${name}".`);
1064
+ throw new BadRequestError(`Cannot disable cloud profile "${name}".`);
1386
1065
  }
1387
1066
  if (newEntry.status !== "active" && newEntry.status != null) {
1388
1067
  throw new BadRequestError(
1389
- `Cannot set status ${JSON.stringify(newEntry.status)} on managed profile "${name}". ` +
1068
+ `Cannot set status ${JSON.stringify(newEntry.status)} on cloud profile "${name}". ` +
1390
1069
  `Only re-enabling (status "active") is allowed.`,
1391
1070
  );
1392
1071
  }
@@ -1397,13 +1076,14 @@ function assertInvariantProfilesPreserved(
1397
1076
  ].filter(
1398
1077
  (key) =>
1399
1078
  key !== "status" &&
1400
- !WIRE_ONLY_PROFILE_KEYS.has(key) &&
1079
+ key !== "invariant" &&
1080
+ key !== "supportsVision" &&
1401
1081
  !isDeepStrictEqual(oldEntry[key], newEntry[key]),
1402
1082
  );
1403
1083
  if (changedKeys.length > 0) {
1404
1084
  throw new BadRequestError(
1405
- `Cannot edit managed profile "${name}" fields [${changedKeys.join(", ")}]. ` +
1406
- `Managed profiles are read-only; duplicate to a custom profile to customize.`,
1085
+ `Cannot edit cloud profile "${name}" fields [${changedKeys.join(", ")}]. ` +
1086
+ `Cloud profiles are read-only; duplicate to a custom profile to customize.`,
1407
1087
  );
1408
1088
  }
1409
1089
  }
@@ -1422,17 +1102,17 @@ function completeChangedCustomProfiles(
1422
1102
  preWrite: Record<string, unknown>,
1423
1103
  raw: Record<string, unknown>,
1424
1104
  ): void {
1425
- const profiles = asMutablePlainObject(readPlainObject(raw.llm)?.profiles);
1426
- if (!profiles) {
1105
+ const llm = RawLlmWriteSliceSchema.safeParse(raw.llm);
1106
+ if (!llm.success || !llm.data.profiles) {
1427
1107
  return;
1428
1108
  }
1429
- const parsedDefault = LLMConfigBase.safeParse(
1430
- readPlainObject(raw.llm)?.default ?? {},
1431
- );
1109
+ const profiles = llm.data.profiles;
1110
+ const parsedDefault = LLMConfigBase.safeParse(llm.data.default ?? {});
1432
1111
  if (!parsedDefault.success) {
1433
1112
  return;
1434
1113
  }
1435
- const prior = readPlainObject(readPlainObject(preWrite.llm)?.profiles) ?? {};
1114
+ const priorLlm = RawLlmWriteSliceSchema.safeParse(preWrite.llm);
1115
+ const prior = priorLlm.success ? (priorLlm.data.profiles ?? {}) : {};
1436
1116
  for (const [name, entry] of Object.entries(profiles)) {
1437
1117
  if (isDeepStrictEqual(entry, prior[name])) {
1438
1118
  continue;
@@ -1452,14 +1132,20 @@ function completeChangedCustomProfiles(
1452
1132
  // depth: `safeParse` strips keys the schema doesn't know (top-level and
1453
1133
  // inside nested objects like `contextWindow`), and dropping them here
1454
1134
  // would delete forward-compatible fields whenever the entry is edited.
1135
+ const entryRecord = RawProfileEntrySchema.safeParse(entry);
1136
+ if (!entryRecord.success) {
1137
+ continue;
1138
+ }
1455
1139
  profiles[name] = mergePreservingUnknownKeys(
1456
- readPlainObject(entry) ?? {},
1140
+ entryRecord.data,
1457
1141
  completeCustomProfile(parsedDefault.data, entryData) as Record<
1458
1142
  string,
1459
1143
  unknown
1460
1144
  >,
1461
1145
  );
1462
1146
  }
1147
+ // A zod parse copies — write the completed slice back so `raw` carries it.
1148
+ raw.llm = llm.data;
1463
1149
  }
1464
1150
 
1465
1151
  /**
@@ -1559,19 +1245,58 @@ function assertRoutableIdentityEntries(
1559
1245
  * `null`.
1560
1246
  */
1561
1247
  function assertCodeOwnedFallbackProfiles(raw: Record<string, unknown>): void {
1562
- const llm = readPlainObject(raw.llm);
1563
- const profiles = readPlainObject(llm?.profiles);
1564
- // The sibling `llm.default` decides whether the managed backups are valid
1565
- // fallback targets: they resolve on the managed column alone.
1248
+ const llm = RawLlmWriteSliceSchema.safeParse(raw.llm);
1249
+ if (!llm.success) {
1250
+ return;
1251
+ }
1252
+ // The sibling `llm.default` decides whether the cloud backups are valid
1253
+ // fallback targets: they resolve on the cloud column alone.
1566
1254
  const issues = collectFallbackProfileIssues(
1567
- profiles ?? undefined,
1568
- llm?.default,
1255
+ llm.data.profiles ?? undefined,
1256
+ llm.data.default,
1569
1257
  );
1570
1258
  if (issues.length > 0) {
1571
1259
  throw new BadRequestError(issues.map((issue) => issue.message).join(" "));
1572
1260
  }
1573
1261
  }
1574
1262
 
1263
+ /**
1264
+ * Stamp the managed marker onto `llm.profiles` entries of catalog-owned
1265
+ * names that a write created without a `source` — a source-less entry reads
1266
+ * as a user shadow that blocks the catalog body. A user-owned entry sharing
1267
+ * the name (a legacy shadow) keeps its source, so its edits stay fully
1268
+ * user-owned.
1269
+ */
1270
+ function stampManagedProfileEntries(
1271
+ preWrite: Record<string, unknown>,
1272
+ raw: Record<string, unknown>,
1273
+ ): void {
1274
+ const newLlm = RawLlmWriteSliceSchema.safeParse(raw.llm);
1275
+ if (!newLlm.success || !newLlm.data.profiles) {
1276
+ return;
1277
+ }
1278
+ const oldLlm = RawLlmWriteSliceSchema.safeParse(preWrite.llm);
1279
+ let stamped = false;
1280
+ for (const name of MANAGED_PROFILE_NAMES) {
1281
+ const written = RawProfileEntrySchema.safeParse(newLlm.data.profiles[name]);
1282
+ if (!written.success || written.data.source !== undefined) {
1283
+ continue;
1284
+ }
1285
+ const prior = RawProfileEntrySchema.safeParse(
1286
+ oldLlm.success ? oldLlm.data.profiles?.[name] : undefined,
1287
+ );
1288
+ if (prior.success && prior.data.source !== "managed") {
1289
+ continue;
1290
+ }
1291
+ written.data.source = "managed";
1292
+ newLlm.data.profiles[name] = written.data;
1293
+ stamped = true;
1294
+ }
1295
+ if (stamped) {
1296
+ raw.llm = newLlm.data;
1297
+ }
1298
+ }
1299
+
1575
1300
  export async function commitConfigWrite(
1576
1301
  raw: Record<string, unknown>,
1577
1302
  opLabel: string,
@@ -1581,10 +1306,14 @@ export async function commitConfigWrite(
1581
1306
  // false diffs. Runs before the watcher-suppress/save sequence so a
1582
1307
  // rejection needs no suppress-flag or cache cleanup.
1583
1308
  const preWrite = loadRawConfig();
1309
+ // Direct callers (the profile routes) skip `handleSetConfig`'s full-schema
1310
+ // parse — this keeps a chat-settings write's invalid values from reaching
1311
+ // disk, while untouched legacy values ride (their issues filter out).
1584
1312
  const chatSettings = validateChatSettingsWrite(preWrite, raw);
1585
1313
  if (!chatSettings.success) {
1586
1314
  throw new BadRequestError(chatSettings.error.message);
1587
1315
  }
1316
+ stampManagedProfileEntries(preWrite, raw);
1588
1317
  completeChangedCustomProfiles(preWrite, raw);
1589
1318
  assertInvariantProfilesPreserved(preWrite, raw);
1590
1319
  assertRoutableIdentityEntries(preWrite, raw);
@@ -1639,95 +1368,66 @@ export async function commitConfigWrite(
1639
1368
  publishConfigChanged();
1640
1369
  }
1641
1370
 
1642
- /**
1643
- * Web clients pair provider writes with a legacy `mode` key so their saves
1644
- * stay valid on daemons whose schemas still carry it. The provider-only
1645
- * services (web-search, image-generation) parse-strip that key, but the
1646
- * deep-merge would still persist it to raw config.json on every save —
1647
- * scrub it so the removed field cannot be resurrected on disk.
1648
- */
1649
- function scrubRemovedServiceModes(raw: Record<string, unknown>): void {
1650
- const services = raw.services as
1651
- | Record<string, Record<string, unknown> | undefined>
1652
- | undefined;
1653
- if (!services) {
1654
- return;
1655
- }
1656
- for (const key of ["web-search", "image-generation"]) {
1657
- const entry = services[key];
1658
- if (entry && typeof entry === "object" && "mode" in entry) {
1659
- delete entry.mode;
1660
- }
1661
- }
1662
- }
1663
-
1664
- /**
1665
- * A persisted `services.stt` block must satisfy SttServiceSchema, whose
1666
- * `provider` is required. The services-level default fills the provider only
1667
- * when `services.stt` is wholly absent, so a sparse patch like
1668
- * `{ services: { stt: { language } } }` deep-merged into a config with no
1669
- * stt block persists a provider-less block that fails validation and trips
1670
- * the loader's salvage ladder: the patched value never applies and the whole
1671
- * `services` section can reset to defaults on the next load (the LUM-2758
1672
- * failure family). Seed the effective provider into any stt block that lacks
1673
- * a non-empty string one so every config_patch writer stays self-consistent.
1674
- * The seed applies to any stt key, not just language: the schema requires
1675
- * the provider whenever the block exists. A block carrying a non-empty
1676
- * string provider is left alone, even an invalid one, so an explicit
1677
- * provider write is never silently rewritten.
1678
- */
1679
- function seedSttProviderForSparseBlock(raw: Record<string, unknown>): void {
1680
- const services = readPlainObject(raw.services);
1681
- const stt = readPlainObject(services?.stt);
1682
- if (!stt) {
1683
- return;
1684
- }
1685
- const provider = stt.provider;
1686
- if (typeof provider === "string" && provider.trim().length > 0) {
1687
- return;
1688
- }
1689
- stt.provider = getConfig().services.stt.provider;
1690
- }
1691
-
1692
1371
  async function handlePatchConfig({ body }: RouteHandlerArgs) {
1693
- if (
1694
- !body ||
1695
- typeof body !== "object" ||
1696
- Array.isArray(body) ||
1697
- Object.keys(body).length === 0
1698
- ) {
1372
+ const patch = JsonRecordSchema.safeParse(body);
1373
+ if (!patch.success || Object.keys(patch.data).length === 0) {
1699
1374
  throw new BadRequestError("Body must be a non-empty JSON object");
1700
1375
  }
1701
- const chatSettings = ChatSettingsPatchSchema.safeParse(body);
1702
- if (!chatSettings.success) {
1703
- throw new BadRequestError(chatSettings.error.message);
1704
- }
1705
- stripWireOnlyProfileKeys(body);
1706
- normalizeManagedProfileWrites(body);
1707
- rejectManagedProfileDeletion(body as Record<string, unknown>);
1708
- rejectMcpTransportHeaderWrite(body);
1709
- stripMcpFromConfigWrite(body as Record<string, unknown>);
1710
-
1711
- const raw = loadRawConfig();
1712
- const patch = body as Record<string, unknown>;
1713
- const patchedIngressUrl = readPlainObject(patch.ingress)?.publicBaseUrl;
1376
+ // Managed-profile normalization runs on the fragment before it merges:
1377
+ // an honest `config get` → PATCH round-trip reduces to a no-op, and a
1378
+ // genuine edit reaches the invariant guard's precise rejections.
1379
+ normalizeCloudProfileWrites(patch.data);
1380
+ rejectCloudProfileDeletion(patch.data);
1381
+
1382
+ // A patch is partial: merge it onto the current config so the schema
1383
+ // parse in `handleSetConfig` always sees a complete config — untouched
1384
+ // values ride, sparse fragments are completed by the merge base.
1385
+ const merged: AssistantConfig = structuredClone(getConfig());
1386
+ const ingress = z
1387
+ .object({ publicBaseUrl: z.unknown().optional() })
1388
+ .passthrough()
1389
+ .safeParse(patch.data.ingress);
1390
+ const patchedIngressUrl = ingress.success
1391
+ ? ingress.data.publicBaseUrl
1392
+ : undefined;
1714
1393
  if (patchedIngressUrl !== undefined) {
1715
- dropTunnelRecordsForNewUrl(readPlainObject(raw.ingress), patchedIngressUrl);
1394
+ dropTunnelRecordsForNewUrl(merged.ingress, patchedIngressUrl);
1716
1395
  }
1717
- deepMergeOverwrite(raw, patch);
1718
- scrubRemovedServiceModes(raw);
1719
- scrubNulledAcpAgentLeaves(raw);
1720
- scrubNulledChatSettings(raw, patch);
1721
- seedSttProviderForSparseBlock(raw);
1396
+ deepMergeOverwrite(merged, patch.data);
1397
+ scrubNulledAcpAgentLeaves(merged);
1398
+ scrubNulledChatSettings(merged, patch.data);
1722
1399
 
1723
- await commitConfigWrite(raw, "patch");
1400
+ await handleSetConfig(merged);
1724
1401
 
1725
1402
  return handleGetConfig();
1726
1403
  }
1727
1404
 
1728
1405
  /**
1729
- * Direct path assignment - replaces `config_patch` for the `assistant
1730
- * config set <key> <value>` CLI path.
1406
+ * The one config write: validate the merged config through
1407
+ * `AssistantConfigSchema`, and either reject with `BadRequestError` or save.
1408
+ * `handlePatchConfig` merges the patch with `getConfig()` first;
1409
+ * `handleSetConfigByKey` replaces one path first. Everything the schema
1410
+ * accepts is what lands on disk.
1411
+ */
1412
+ async function handleSetConfig(merged: AssistantConfig): Promise<void> {
1413
+ const parsed = AssistantConfigSchema.safeParse(merged);
1414
+ if (!parsed.success) {
1415
+ throw new BadRequestError(
1416
+ parsed.error.issues
1417
+ .map((issue) =>
1418
+ issue.path.length > 0
1419
+ ? `${issue.path.join(".")}: ${issue.message}`
1420
+ : issue.message,
1421
+ )
1422
+ .join("; "),
1423
+ );
1424
+ }
1425
+ await commitConfigWrite(parsed.data, "save");
1426
+ }
1427
+
1428
+ /**
1429
+ * Direct path assignment - the `assistant config set <key> <value>` CLI
1430
+ * contract.
1731
1431
  *
1732
1432
  * `config_patch` uses `deepMergeOverwrite` semantics, which strips `null`
1733
1433
  * leaves when the target subtree doesn't exist and merges (rather than
@@ -1737,17 +1437,18 @@ async function handlePatchConfig({ body }: RouteHandlerArgs) {
1737
1437
  * - `set heartbeat.activeHoursStart null` to persist explicit `null`
1738
1438
  * - `set llm {}` to replace `llm`, not merge into it
1739
1439
  *
1740
- * `config_set` performs `setNestedValue` directly on the loaded raw config
1741
- * (no merge), then runs the same post-write side effects as patch.
1440
+ * `config_set` performs `setNestedValue` directly on the current config
1441
+ * (no merge), then funnels into the same schema-validated
1442
+ * {@link handleSetConfig} write.
1742
1443
  */
1743
- async function handleSetConfig({ body }: RouteHandlerArgs) {
1744
- if (!body || typeof body !== "object" || Array.isArray(body)) {
1444
+ async function handleSetConfigByKey({ body }: RouteHandlerArgs) {
1445
+ const parsedBody = JsonRecordSchema.safeParse(body);
1446
+ if (!parsedBody.success) {
1745
1447
  throw new BadRequestError(
1746
1448
  "Body must be a JSON object with `path` and `value`",
1747
1449
  );
1748
1450
  }
1749
- const bodyRecord = body as Record<string, unknown>;
1750
- const { path, value } = bodyRecord as { path?: unknown; value?: unknown };
1451
+ const path = parsedBody.data.path;
1751
1452
  if (typeof path !== "string" || path.length === 0) {
1752
1453
  throw new BadRequestError("`path` must be a non-empty string");
1753
1454
  }
@@ -1755,85 +1456,62 @@ async function handleSetConfig({ body }: RouteHandlerArgs) {
1755
1456
  // this check, `undefined` flows into `setNestedValue` and gets dropped by
1756
1457
  // `JSON.stringify` at save time, silently removing the key - which is
1757
1458
  // distinct from the documented "set to null" semantics.
1758
- if (!("value" in bodyRecord)) {
1459
+ if (!Object.hasOwn(parsedBody.data, "value")) {
1759
1460
  throw new BadRequestError(
1760
1461
  "`value` is required (use `null` to clear a key)",
1761
1462
  );
1762
1463
  }
1763
- // A leaf-path SET targeting a wire-only profile key is dropped without
1764
- // writing — the same treatment PATCH gives wire-only keys embedded in a
1765
- // profile fragment.
1766
- const pathSegments = path.split(".");
1767
- if (
1768
- pathSegments[0] === "llm" &&
1769
- pathSegments[1] === "profiles" &&
1770
- pathSegments.length >= 4 &&
1771
- WIRE_ONLY_PROFILE_KEYS.has(pathSegments[3]!)
1772
- ) {
1773
- return { ok: true };
1774
- }
1775
- // Build the equivalent patch shape so the managed-profile guard can
1776
- // inspect the touched subtree. `setNestedValue` places `value` into
1777
- // `patchShape` by reference, so stripping wire-only profile keys here
1778
- // also strips the object written to `raw` below.
1779
- const patchShape: Record<string, unknown> = {};
1780
- setNestedValue(patchShape, path, value);
1781
- stripWireOnlyProfileKeys(patchShape);
1782
- normalizeManagedProfileWrites(patchShape);
1783
- rejectManagedProfileDeletion(patchShape);
1784
- rejectMcpConfigSetPath(path, value);
1785
- rejectMcpTransportHeaderWrite(patchShape);
1464
+ const value = parsedBody.data.value;
1786
1465
 
1787
- const raw = loadRawConfig();
1788
- // A SET below the entry level (`llm.profiles.<name>.<leaf>`) writes the
1789
- // primitive leaf directly into `raw`, bypassing the by-reference
1790
- // normalization above — creating an entry for a managed-owned name would
1791
- // leave it source-less, and a source-less entry reads as a user shadow
1792
- // that blocks the catalog body. Stamp the managed marker onto the written
1793
- // entry when the name was absent or managed-owned before this write.
1794
- const managedEntryName =
1795
- pathSegments[0] === "llm" &&
1796
- pathSegments[1] === "profiles" &&
1797
- pathSegments.length >= 3 &&
1798
- MANAGED_PROFILE_NAMES.has(pathSegments[2]!)
1799
- ? pathSegments[2]!
1800
- : undefined;
1801
- const priorManagedEntry = managedEntryName
1802
- ? readPlainObject(
1803
- readPlainObject(readPlainObject(raw.llm)?.profiles)?.[managedEntryName],
1804
- )
1805
- : undefined;
1466
+ const merged: AssistantConfig = structuredClone(getConfig());
1806
1467
  // `assistant config set ingress.publicBaseUrl <url>`, what the webhook
1807
1468
  // diagnostics tell users to run, retargets ingress like the settings route.
1808
1469
  if (path === "ingress.publicBaseUrl") {
1809
- dropTunnelRecordsForNewUrl(readPlainObject(raw.ingress), value);
1810
- }
1811
- setNestedValue(raw, path, value);
1812
- if (
1813
- managedEntryName &&
1814
- (priorManagedEntry == null || priorManagedEntry.source === "managed")
1815
- ) {
1816
- const written = readPlainObject(
1817
- readPlainObject(readPlainObject(raw.llm)?.profiles)?.[managedEntryName],
1818
- );
1819
- if (written && written.source === undefined) {
1820
- written.source = "managed";
1470
+ dropTunnelRecordsForNewUrl(merged.ingress, value);
1471
+ }
1472
+ // Any llm-touching SET rides the same normalization the profile routes
1473
+ // use: `normalizeCloudProfileWrites` reduces an echo of the wire view to
1474
+ // the workspace-owned stub and rejects genuine edits to managed entries.
1475
+ // Its normalization lives in the parsed llm slice (a zod parse copies), so
1476
+ // the written value below is the normalized slice/entry — and a leaf write
1477
+ // below the entry level rides raw; the ladder stamps the managed marker
1478
+ // onto an entry the write creates.
1479
+ const pathSegments = path.split(".");
1480
+ let writtenValue: unknown = value;
1481
+ if (pathSegments[0] === "llm") {
1482
+ const patchShape: Record<string, unknown> = {};
1483
+ setNestedValue(patchShape, path, value);
1484
+ normalizeCloudProfileWrites(patchShape);
1485
+ rejectCloudProfileDeletion(patchShape);
1486
+ const patchLlm = RawLlmWriteSliceSchema.safeParse(patchShape.llm);
1487
+ if (patchLlm.success) {
1488
+ if (pathSegments.length === 1) {
1489
+ writtenValue = patchLlm.data;
1490
+ } else if (pathSegments[1] === "profiles") {
1491
+ if (pathSegments.length === 2) {
1492
+ if (patchLlm.data.profiles) {
1493
+ writtenValue = patchLlm.data.profiles;
1494
+ }
1495
+ } else if (pathSegments.length === 3) {
1496
+ const normalized = patchLlm.data.profiles?.[pathSegments[2]!];
1497
+ if (normalized !== undefined) {
1498
+ writtenValue = normalized;
1499
+ }
1500
+ }
1501
+ }
1821
1502
  }
1822
1503
  }
1823
- scrubNulledAcpAgentLeaves(raw);
1824
- // A SET can create `services.stt` with a leaf like `language` and no
1825
- // `provider`, which SttServiceSchema requires whenever the block exists;
1826
- // the same seeding that guards PATCH keeps this write's persisted block
1827
- // schema-valid.
1828
- seedSttProviderForSparseBlock(raw);
1504
+ setNestedValue(merged, path, writtenValue);
1505
+ scrubNulledAcpAgentLeaves(merged);
1506
+
1507
+ await handleSetConfig(merged);
1829
1508
 
1830
- await commitConfigWrite(raw, "set");
1831
1509
  // A `memory.v2` substrate tunable whose `memory.substrate` twin is set does
1832
1510
  // not mean what an unpaired write means: the substrate namespace wins in
1833
1511
  // `resolveSubstrateTuning`, and for the three twins the v2 injection engine
1834
1512
  // reads directly the two namespaces diverge instead. Report which case this
1835
1513
  // is alongside the success so the caller can tell the operator.
1836
- const shadowing = findSubstrateShadowing(raw, path);
1514
+ const shadowing = findSubstrateShadowing(path);
1837
1515
  if (shadowing) {
1838
1516
  const warning = describeShadowedConfigSet(shadowing, path);
1839
1517
  log.warn({ path, substratePath: shadowing.substratePath }, warning);
@@ -1890,12 +1568,12 @@ async function handleReplaceInferenceProfile({
1890
1568
  throw new BadRequestError(`Invalid profile fragment: ${detail}`);
1891
1569
  }
1892
1570
  // A managed name with no existing entry stays protected so users can't
1893
- // shadow a seeded managed profile. An existing entry carrying a non-managed
1571
+ // shadow a seeded cloud profile. An existing entry carrying a non-managed
1894
1572
  // `source` is user-owned and remains fully editable.
1895
1573
  const existingProfile = asMutablePlainObject(
1896
1574
  getConfig().llm.profiles?.[name],
1897
1575
  );
1898
- const isManaged =
1576
+ const isCloud =
1899
1577
  MANAGED_PROFILE_NAMES.has(name) &&
1900
1578
  (existingProfile == null || existingProfile.source === "managed");
1901
1579
  if (CODE_OWNED_PROFILE_NAMES.has(name)) {
@@ -1924,11 +1602,11 @@ async function handleReplaceInferenceProfile({
1924
1602
  `Profile "${name}" is not currently available and cannot be edited.`,
1925
1603
  );
1926
1604
  }
1927
- if (isManaged) {
1928
- // Managed profiles are daemon-seeded and read-only — the commit guard
1605
+ if (isCloud) {
1606
+ // Cloud profiles are daemon-seeded and read-only — the commit guard
1929
1607
  // (`assertInvariantProfilesPreserved`) rejects every write to them
1930
1608
  // except re-enabling a disabled profile. Enforce the same contract up
1931
- // front: the only body a managed PUT accepts is a pure status re-enable
1609
+ // front: the only body a cloud PUT accepts is a pure status re-enable
1932
1610
  // (`{status: "active"}`, or `{status: null}` to clear back to
1933
1611
  // active-by-absence). Rejecting here keeps the error message ahead of
1934
1612
  // any side effects and mirrors the guard's wording.
@@ -1942,8 +1620,8 @@ async function handleReplaceInferenceProfile({
1942
1620
  const detail =
1943
1621
  disallowed.length > 0 ? ` fields [${disallowed.join(", ")}]` : "";
1944
1622
  throw new BadRequestError(
1945
- `Cannot edit managed profile "${name}"${detail}. ` +
1946
- `Managed profiles are read-only (a disabled profile can be re-enabled); ` +
1623
+ `Cannot edit cloud profile "${name}"${detail}. ` +
1624
+ `Cloud profiles are read-only (a disabled profile can be re-enabled); ` +
1947
1625
  `duplicate to a custom profile to customize.`,
1948
1626
  );
1949
1627
  }
@@ -2046,10 +1724,10 @@ async function handleReplaceInferenceProfile({
2046
1724
 
2047
1725
  // When the UI sends provider but no provider_connection, derive the connection
2048
1726
  // now so the config deep-merge doesn't inherit a stale connection from the
2049
- // default layer. Managed entries are excluded: the managed gate above
1727
+ // default layer. Managed entries are excluded: the cloud gate above
2050
1728
  // already rejected any provider-carrying fragment, so their only surviving
2051
1729
  // body is a status re-enable, which derives no connection. A user-owned
2052
- // profile sharing a managed name is fully editable, so it takes the
1730
+ // profile sharing a cloud name is fully editable, so it takes the
2053
1731
  // derivation like any other custom profile.
2054
1732
  const fragment = parsed.data as Record<string, unknown>;
2055
1733
  // Routing identities resolve their connection per-request from the
@@ -2057,7 +1735,7 @@ async function handleReplaceInferenceProfile({
2057
1735
  // ("cadet" is its stored provider) or create a junk "<identity>-personal"
2058
1736
  // connection for chatgpt.
2059
1737
  if (
2060
- !isManaged &&
1738
+ !isCloud &&
2061
1739
  fragment.provider &&
2062
1740
  !fragment.provider_connection &&
2063
1741
  !ROUTING_IDENTITY_PROVIDERS.has(fragment.provider as string)
@@ -2074,12 +1752,12 @@ async function handleReplaceInferenceProfile({
2074
1752
  if (active) {
2075
1753
  fragment.provider_connection = active.name;
2076
1754
  } else if (
2077
- MANAGED_ROUTABLE_PROVIDERS.has(provider) &&
2078
- getConnection(db, CADET_MANAGED_CONNECTION_NAME)
1755
+ CLOUD_ROUTABLE_PROVIDERS.has(provider) &&
1756
+ getConnection(db, CADET_CLOUD_CONNECTION_NAME)
2079
1757
  ) {
2080
- // Managed-routable providers are served by the single Cadet-managed
1758
+ // Managed-routable providers are served by the single Cadet Cloud
2081
1759
  // connection; prefer it over lazily creating a personal connection.
2082
- fragment.provider_connection = CADET_MANAGED_CONNECTION_NAME;
1760
+ fragment.provider_connection = CADET_CLOUD_CONNECTION_NAME;
2083
1761
  } else if (!PROVIDERS_REQUIRING_BASE_URL_AND_MODELS.has(provider)) {
2084
1762
  const connectionName = `${provider}-personal`;
2085
1763
  const isKeyless = provider === "ollama";
@@ -2100,13 +1778,13 @@ async function handleReplaceInferenceProfile({
2100
1778
  }
2101
1779
 
2102
1780
  const raw = loadRawConfig();
2103
- if (isManaged) {
1781
+ if (isCloud) {
2104
1782
  // Partial overlay: keep every existing key intact, only apply the status
2105
- // re-enable (the sole body the managed gate above admits). Using
1783
+ // re-enable (the sole body the cloud gate above admits). Using
2106
1784
  // `replaceInferenceProfileConfig` here would wipe the seed-owned fields
2107
1785
  // (provider, model, advanced params) because that function assumes the
2108
1786
  // body carries the full UI surface.
2109
- applyManagedProfileReenable(raw, name, fragment.status as "active" | null);
1787
+ applyCloudProfileReenable(raw, name, fragment.status as "active" | null);
2110
1788
  } else {
2111
1789
  replaceInferenceProfileConfig(raw, name, fragment);
2112
1790
  }
@@ -2115,7 +1793,7 @@ async function handleReplaceInferenceProfile({
2115
1793
  // `handleSetConfig`: file-watcher suppression so the in-process reload
2116
1794
  // doesn't race the explicit reinit, embedding backend cache clear,
2117
1795
  // in-process `getConfig` cache invalidation, and provider registry
2118
- // reinitialization. `status: "disabled"` on a managed profile (and any
1796
+ // reinitialization. `status: "disabled"` on a cloud profile (and any
2119
1797
  // `provider` / `model` / `provider_connection` change on a custom
2120
1798
  // profile) must take effect immediately rather than waiting for the
2121
1799
  // next watcher tick.
@@ -2124,11 +1802,11 @@ async function handleReplaceInferenceProfile({
2124
1802
  }
2125
1803
 
2126
1804
  /**
2127
- * Apply the disabled→active re-enable to a managed profile entry, preserving
1805
+ * Apply the disabled→active re-enable to a cloud profile entry, preserving
2128
1806
  * every other field already on disk (provider, model, advanced params, etc).
2129
1807
  * `status: "active"` sets the key; `null` clears it (active-by-absence).
2130
1808
  */
2131
- function applyManagedProfileReenable(
1809
+ function applyCloudProfileReenable(
2132
1810
  raw: Record<string, unknown>,
2133
1811
  name: string,
2134
1812
  status: "active" | null,
@@ -2149,7 +1827,7 @@ function applyManagedProfileReenable(
2149
1827
  ...(asMutablePlainObject(profiles[name]) ?? {}),
2150
1828
  };
2151
1829
  // Only reached for managed-owned names (the route's managed gate), so a
2152
- // freshly created stub must carry the managed marker the effective view
1830
+ // freshly created stub must carry the cloud marker the effective view
2153
1831
  // and write guards key on.
2154
1832
  nextProfile.source = "managed";
2155
1833
  if (status === null) {
@@ -2548,7 +2226,7 @@ export const ROUTES: RouteDefinition[] = [
2548
2226
  "(preserves explicit null, replaces object subtrees instead of merging). " +
2549
2227
  "Used by the `assistant config set <key> <value>` CLI command.",
2550
2228
  tags: ["config"],
2551
- handler: handleSetConfig,
2229
+ handler: handleSetConfigByKey,
2552
2230
  },
2553
2231
  {
2554
2232
  operationId: "config_allowlist_validate",