@mycadet/assistant 0.12.9 → 0.12.11

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 (658) hide show
  1. package/Dockerfile +0 -8
  2. package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +1 -1
  3. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +8 -8
  4. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +1 -1
  5. package/node_modules/@mycadet/ces-client/src/http-credentials.ts +18 -1
  6. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +8 -8
  7. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +1 -1
  8. package/node_modules/@mycadet/service-contracts/src/__tests__/ingress.test.ts +8 -8
  9. package/node_modules/@mycadet/service-contracts/src/ingress.ts +1 -1
  10. package/openapi.yaml +62 -910
  11. package/package.json +3 -7
  12. package/scripts/build-test-fixtures.ts +1 -1
  13. package/scripts/generate-openapi.ts +22 -6
  14. package/scripts/postinstall.ts +4 -10
  15. package/scripts/run-tests.ts +0 -21
  16. package/src/__tests__/adaptive-thinking-repair.test.ts +19 -57
  17. package/src/__tests__/agent-loop.test.ts +0 -76
  18. package/src/__tests__/anthropic-provider.test.ts +6 -6
  19. package/src/__tests__/assert-not-live-db.ts +1 -1
  20. package/src/__tests__/assistant-id-boundary-guard.test.ts +3 -3
  21. package/src/__tests__/attachments-store-heic-normalize.test.ts +1 -1
  22. package/src/__tests__/attachments-store.test.ts +1 -1
  23. package/src/__tests__/audit-log-rotation.test.ts +1 -1
  24. package/src/__tests__/btw-routes.test.ts +1 -1
  25. package/src/__tests__/byok-default-profile-ensure.test.ts +21 -27
  26. package/src/__tests__/call-conversation-messages.test.ts +0 -5
  27. package/src/__tests__/call-recovery.test.ts +2 -7
  28. package/src/__tests__/call-routes-http.test.ts +0 -12
  29. package/src/__tests__/call-store.test.ts +2 -7
  30. package/src/__tests__/catalog-files.test.ts +7 -5
  31. package/src/__tests__/checker.test.ts +1 -1
  32. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +4 -4
  33. package/src/__tests__/cloud-client-verify-credential.test.ts +0 -4
  34. package/src/__tests__/cloud-credential-catalog-cli.test.ts +14 -14
  35. package/src/__tests__/cloud-profile-guard.test.ts +87 -404
  36. package/src/__tests__/compaction-events.test.ts +2 -2
  37. package/src/__tests__/config-loader-backfill.test.ts +74 -163
  38. package/src/__tests__/config-loader-cloud-defaults.test.ts +11 -11
  39. package/src/__tests__/config-loader-corrupt.test.ts +10 -10
  40. package/src/__tests__/config-loader-quarantine-notice.test.ts +7 -7
  41. package/src/__tests__/config-loader-validation-reset-notice.test.ts +11 -11
  42. package/src/__tests__/config-schema.test.ts +26 -27
  43. package/src/__tests__/config-set-route.test.ts +2 -6
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +28 -300
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +39 -246
  46. package/src/__tests__/conversation-agent-loop.test.ts +2 -58
  47. package/src/__tests__/conversation-clear-safety.test.ts +0 -30
  48. package/src/__tests__/conversation-inference-profile-route.test.ts +1 -1
  49. package/src/__tests__/conversation-queue.test.ts +1 -1
  50. package/src/__tests__/conversation-runtime-assembly.test.ts +1 -1
  51. package/src/__tests__/conversation-skill-tools.test.ts +4 -444
  52. package/src/__tests__/conversation-slash-commands.test.ts +1 -1
  53. package/src/__tests__/conversation-store.test.ts +1 -84
  54. package/src/__tests__/conversation-surface-state.test.ts +0 -16
  55. package/src/__tests__/credential-health-service.test.ts +14 -1
  56. package/src/__tests__/credential-record-retire.test.ts +1 -1
  57. package/src/__tests__/credential-record-write-through.test.ts +1 -1
  58. package/src/__tests__/credential-security-invariants.test.ts +0 -145
  59. package/src/__tests__/custom-profile-ensure.test.ts +1 -1
  60. package/src/__tests__/default-profile-catalog-fallback.test.ts +5 -15
  61. package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -2
  62. package/src/__tests__/dictation-profile-store.test.ts +6 -6
  63. package/src/__tests__/disk-usage.test.ts +28 -164
  64. package/src/__tests__/docker-signing-key-bootstrap.test.ts +1 -1
  65. package/src/__tests__/embedding-cloud-proxy-selection.test.ts +1 -1
  66. package/src/__tests__/emit-event-signal.test.ts +1 -1
  67. package/src/__tests__/gateway-client-cloud-outbound.test.ts +3 -3
  68. package/src/__tests__/gateway-only-enforcement.test.ts +1 -1
  69. package/src/__tests__/guardian-dispatch.test.ts +0 -13
  70. package/src/__tests__/helpers/seed-app.ts +1 -1
  71. package/src/__tests__/host-cu-routes-targeted.test.ts +1 -1
  72. package/src/__tests__/host-shell-tool.test.ts +1 -1
  73. package/src/__tests__/identity-routes.test.ts +1 -1
  74. package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
  75. package/src/__tests__/inference-profile-session-ipc.test.ts +1 -1
  76. package/src/__tests__/injector-chain.test.ts +1 -1
  77. package/src/__tests__/injector-config-quarantine-notice.test.ts +1 -1
  78. package/src/__tests__/injector-config-validation-reset-notice.test.ts +1 -1
  79. package/src/__tests__/injector-pkb-v2-silenced.test.ts +1 -1
  80. package/src/__tests__/{platform.test.ts → layout.test.ts} +1 -1
  81. package/src/__tests__/list-all-apps.test.ts +1 -1
  82. package/src/__tests__/live-workspace-guard.test.ts +2 -2
  83. package/src/__tests__/llm-resolver-override-or-default.test.ts +5 -6
  84. package/src/__tests__/llm-resolver.test.ts +7 -7
  85. package/src/__tests__/llm-schema-fallback-profile.test.ts +9 -10
  86. package/src/__tests__/llm-usage-store.test.ts +0 -495
  87. package/src/__tests__/log-export-routes.test.ts +1 -1
  88. package/src/__tests__/mcp-config-secret-boundary.test.ts +12 -53
  89. package/src/__tests__/mcp-list-plugin-servers.test.ts +1 -1
  90. package/src/__tests__/memory-config-file-parity.test.ts +1 -1
  91. package/src/__tests__/memory-v2-static-injector.test.ts +1 -1
  92. package/src/__tests__/messages-read-boundary-guard.test.ts +0 -2
  93. package/src/__tests__/notification-guardian-path.test.ts +0 -7
  94. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  95. package/src/__tests__/openai-provider.test.ts +8 -8
  96. package/src/__tests__/path-policy.test.ts +1 -1
  97. package/src/__tests__/persona-resolver.test.ts +1 -1
  98. package/src/__tests__/plugin-app-serve-routes.test.ts +1 -1
  99. package/src/__tests__/plugin-effective-enabled-set.test.ts +1 -1
  100. package/src/__tests__/plugin-import-boundary-guard.test.ts +4 -6
  101. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -3
  102. package/src/__tests__/provider-cloud-proxy-integration.test.ts +1 -5
  103. package/src/__tests__/provider-connections-backfill.test.ts +10 -10
  104. package/src/__tests__/prune-old-conversations-job.test.ts +6 -31
  105. package/src/__tests__/resolve-app.test.ts +1 -1
  106. package/src/__tests__/run-due-schedules.test.ts +1 -1
  107. package/src/__tests__/scaffold-managed-skill-tool.test.ts +0 -53
  108. package/src/__tests__/schedule-routes.test.ts +1 -1
  109. package/src/__tests__/schedule-store.test.ts +1 -1
  110. package/src/__tests__/secret-routes-cloud-proxy.test.ts +1 -1
  111. package/src/__tests__/skill-load-plugin-scope.test.ts +1 -1
  112. package/src/__tests__/surface-completion-compaction-boundary.test.ts +1 -1
  113. package/src/__tests__/tool-approval-handler.test.ts +1 -1
  114. package/src/__tests__/tool-audit.test.ts +5 -235
  115. package/src/__tests__/tool-executor-lifecycle-events.test.ts +54 -114
  116. package/src/__tests__/tools-get-route.test.ts +1 -1
  117. package/src/__tests__/turn-outcome-read.test.ts +1 -1
  118. package/src/__tests__/twilio-routes.test.ts +0 -1
  119. package/src/__tests__/usage-attribution.test.ts +0 -31
  120. package/src/__tests__/verification-control-plane-policy.test.ts +3 -10
  121. package/src/__tests__/visible-app-context.test.ts +1 -1
  122. package/src/__tests__/voice-ingress-preflight.test.ts +1 -1
  123. package/src/__tests__/workspace-git-service.test.ts +1 -1
  124. package/src/__tests__/workspace-greetings.test.ts +1 -1
  125. package/src/acp/__tests__/helpers/acp-config-stub.ts +1 -1
  126. package/src/activation/progress-store.ts +1 -1
  127. package/src/activation/turn-hooks.ts +1 -1
  128. package/src/agent/loop.ts +0 -51
  129. package/src/apps/app-store.ts +1 -1
  130. package/src/avatar/__tests__/avatar-store.test.ts +2 -172
  131. package/src/avatar/accent-backfill.ts +1 -1
  132. package/src/avatar/avatar-manifest.ts +1 -1
  133. package/src/avatar/avatar-store.ts +10 -69
  134. package/src/avatar/ensure-raster.ts +1 -1
  135. package/src/avatar/identity-avatar.ts +1 -1
  136. package/src/avatar/sync-avatar.ts +1 -1
  137. package/src/avatar/traits-png-sync.ts +1 -1
  138. package/src/bundler/compiler-tools.ts +1 -1
  139. package/src/bundler/package-resolver.ts +1 -1
  140. package/src/calls/__tests__/call-controller.test.ts +25 -27
  141. package/src/calls/active-call-lease.ts +1 -1
  142. package/src/calls/call-controller.ts +3 -3
  143. package/src/calls/call-domain.ts +4 -8
  144. package/src/calls/call-store.ts +2 -76
  145. package/src/calls/resolve-call-tts-provider.ts +3 -3
  146. package/src/calls/twilio-config.ts +3 -3
  147. package/src/calls/twilio-rest.ts +2 -2
  148. package/src/calls/voice-ingress-preflight.ts +2 -2
  149. package/src/cli/bundled-modules.ts +0 -2
  150. package/src/cli/commands/__tests__/apps.test.ts +1 -1
  151. package/src/cli/commands/__tests__/plugins.test.ts +9 -66
  152. package/src/cli/commands/__tests__/ps.test.ts +67 -0
  153. package/src/cli/commands/auth.help.ts +2 -2
  154. package/src/cli/commands/avatar.ts +1 -1
  155. package/src/cli/commands/changelog.ts +1 -1
  156. package/src/cli/commands/cloud/__tests__/subscription.test.ts +2 -2
  157. package/src/cli/commands/cloud/index.help.ts +1 -1
  158. package/src/cli/commands/cloud/index.ts +2 -2
  159. package/src/cli/commands/db/refresh.ts +1 -1
  160. package/src/cli/commands/db/repair-step-conversation-backfill.ts +1 -1
  161. package/src/cli/commands/db/repair.ts +1 -1
  162. package/src/cli/commands/db/status.ts +1 -1
  163. package/src/cli/commands/inference-profiles.ts +1 -12
  164. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +1 -116
  165. package/src/cli/commands/memory/index.help.ts +1 -43
  166. package/src/cli/commands/memory/memory-v3.ts +0 -64
  167. package/src/cli/commands/plugins.ts +54 -205
  168. package/src/cli/commands/ps.ts +5 -2
  169. package/src/cli/commands/status.ts +1 -1
  170. package/src/cli/index.help.ts +0 -2
  171. package/src/cli/lib/__tests__/inspect-plugin.test.ts +36 -66
  172. package/src/cli/lib/__tests__/install-from-cloud.test.ts +1 -1
  173. package/src/cli/lib/__tests__/install-from-github.test.ts +41 -117
  174. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +2 -185
  175. package/src/cli/lib/__tests__/plugin-catalog-resolve.test.ts +36 -66
  176. package/src/cli/lib/__tests__/plugin-catalog-visibility.test.ts +4 -6
  177. package/src/cli/lib/__tests__/plugin-details.test.ts +113 -96
  178. package/src/cli/lib/__tests__/search-plugins.test.ts +9 -16
  179. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +1 -1
  180. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +38 -15
  181. package/src/cli/lib/diff-plugin.ts +1 -1
  182. package/src/cli/lib/inspect-plugin.ts +13 -97
  183. package/src/cli/lib/install-from-cloud.ts +1 -1
  184. package/src/cli/lib/install-from-github.ts +75 -154
  185. package/src/cli/lib/install-plugin-dependencies.ts +1 -1
  186. package/src/cli/lib/list-installed-plugins.ts +1 -1
  187. package/src/cli/lib/open-browser.ts +1 -1
  188. package/src/cli/lib/plugin-catalog-cache.ts +8 -51
  189. package/src/cli/lib/plugin-catalog-resolve.ts +3 -22
  190. package/src/cli/lib/plugin-details.ts +5 -27
  191. package/src/cli/lib/plugin-marketplace.ts +18 -266
  192. package/src/cli/lib/search-plugins.ts +23 -43
  193. package/src/cli/lib/toggle-plugin.ts +1 -1
  194. package/src/cli/lib/uninstall-plugin.ts +1 -1
  195. package/src/cli/lib/upgrade-plugin.ts +3 -49
  196. package/src/cli/program.ts +1 -3
  197. package/src/cli.ts +1 -1
  198. package/src/cloud/client.ts +0 -6
  199. package/src/cloud/consent-cache.test.ts +22 -201
  200. package/src/cloud/consent-cache.ts +16 -111
  201. package/src/cloud/sync-identity.ts +1 -1
  202. package/src/config/__tests__/chat-settings.test.ts +1 -1
  203. package/src/config/__tests__/cloud-identity.test.ts +15 -2
  204. package/src/config/__tests__/default-plugin-skill-discovery.test.ts +1 -1
  205. package/src/config/__tests__/default-profile-catalog.test.ts +12 -51
  206. package/src/config/__tests__/deployment-context-defaults.test.ts +13 -13
  207. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +4 -4
  208. package/src/config/__tests__/loader-last-known-good.test.ts +9 -9
  209. package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +7 -7
  210. package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +1 -1
  211. package/src/config/__tests__/profile-materialization.test.ts +2 -4
  212. package/src/config/__tests__/sync-gated-profiles.test.ts +1 -106
  213. package/src/config/acp-agent-write.ts +1 -1
  214. package/src/config/bundled-skills/settings/tools/open-system-settings.test.ts +1 -1
  215. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -1
  216. package/src/config/call-site-defaults.ts +0 -6
  217. package/src/config/cloud-identity.ts +12 -5
  218. package/src/config/composer-preferences.ts +1 -1
  219. package/src/config/default-profile-catalog.ts +8 -75
  220. package/src/config/default-profile-names.ts +2 -25
  221. package/src/config/env-registry.ts +1 -14
  222. package/src/config/env.ts +8 -6
  223. package/src/config/llm-resolver.ts +1 -2
  224. package/src/config/loader.ts +13 -17
  225. package/src/config/profile-materialization.ts +1 -1
  226. package/src/config/schema.ts +1 -7
  227. package/src/config/schemas/call-site-catalog.ts +0 -7
  228. package/src/config/schemas/llm.ts +25 -32
  229. package/src/config/seed-inference-profiles.ts +16 -17
  230. package/src/config/skills.ts +2 -2
  231. package/src/config/sync-gated-profiles.ts +6 -24
  232. package/src/credential-health/credential-health-service.ts +10 -3
  233. package/src/daemon/approval-generators.ts +3 -3
  234. package/src/daemon/config-watcher.ts +2 -2
  235. package/src/daemon/conversation-agent-loop-handlers.ts +0 -18
  236. package/src/daemon/conversation-agent-loop.ts +6 -97
  237. package/src/daemon/conversation-process.ts +1 -1
  238. package/src/daemon/conversation-skill-tools.ts +4 -72
  239. package/src/daemon/conversation-store.ts +1 -1
  240. package/src/daemon/conversation-surface-state.ts +0 -33
  241. package/src/daemon/conversation-surfaces.ts +1 -86
  242. package/src/daemon/conversation-tool-setup.test.ts +1 -1
  243. package/src/daemon/conversation-tool-setup.ts +0 -23
  244. package/src/daemon/conversation.ts +3 -5
  245. package/src/daemon/daemon-control.ts +3 -3
  246. package/src/daemon/dictation-profile-store.ts +3 -3
  247. package/src/daemon/event-loop-watchdog.test.ts +2 -198
  248. package/src/daemon/event-loop-watchdog.ts +0 -185
  249. package/src/daemon/external-plugins-bootstrap.ts +1 -1
  250. package/src/daemon/first-greeting.ts +1 -1
  251. package/src/daemon/handlers/skills.ts +1 -1
  252. package/src/daemon/host-app-control-proxy.ts +1 -1
  253. package/src/daemon/host-cu-proxy.ts +2 -2
  254. package/src/daemon/identity-helpers.ts +1 -1
  255. package/src/daemon/lifecycle.ts +11 -15
  256. package/src/daemon/main.ts +1 -1
  257. package/src/daemon/now-scratchpad.ts +1 -1
  258. package/src/daemon/process-message.ts +4 -4
  259. package/src/daemon/profiler-run-store.ts +2 -2
  260. package/src/daemon/providers-setup.ts +2 -2
  261. package/src/daemon/shutdown-handlers.ts +0 -10
  262. package/src/daemon/sqlite-corruption-watchdog.test.ts +2 -142
  263. package/src/daemon/sqlite-corruption-watchdog.ts +17 -81
  264. package/src/daemon/tool-side-effects.ts +1 -1
  265. package/src/desktop/desktop-session-manager.ts +1 -1
  266. package/src/desktop/desktop-wallpaper-renderer.ts +1 -1
  267. package/src/heartbeat/heartbeat-service.ts +1 -1
  268. package/src/home/feed-writer.ts +1 -1
  269. package/src/home/home-greeting-cache.ts +1 -1
  270. package/src/home/relationship-state-writer.ts +2 -2
  271. package/src/hooks/__tests__/hook-live-reload.test.ts +1 -1
  272. package/src/hooks/hook-loader.ts +2 -2
  273. package/src/inbound/__tests__/oauth-callback-url.test.ts +2 -2
  274. package/src/inbound/oauth-callback-url.ts +3 -3
  275. package/src/ipc/__tests__/attachment-ipc.test.ts +1 -1
  276. package/src/ipc/socket-path.ts +1 -1
  277. package/src/live-voice/__tests__/live-voice-archive.test.ts +1 -1
  278. package/src/live-voice/__tests__/live-voice-vad.test.ts +1 -1
  279. package/src/live-voice/live-voice-session.ts +3 -66
  280. package/src/mcp/__tests__/effective-config.test.ts +1 -1
  281. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +2 -2
  282. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +2 -3
  283. package/src/mcp/__tests__/reload-signal-emission.test.ts +1 -1
  284. package/src/mcp/__tests__/reload-signal.test.ts +1 -1
  285. package/src/mcp/__tests__/workspace-mcp-config.test.ts +19 -21
  286. package/src/mcp/reload-signal.ts +1 -1
  287. package/src/mcp/workspace-mcp-config.ts +1 -23
  288. package/src/messaging/draft-store.ts +1 -1
  289. package/src/monitoring/__tests__/active-conversations.test.ts +1 -1
  290. package/src/monitoring/__tests__/page-cache.test.ts +1 -1
  291. package/src/monitoring/__tests__/stall-capture.test.ts +1 -1
  292. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +1 -1
  293. package/src/monitoring/active-conversations.ts +1 -1
  294. package/src/monitoring/control.ts +1 -1
  295. package/src/monitoring/daemon-boot-time.ts +1 -1
  296. package/src/monitoring/daemon-heartbeat.ts +1 -1
  297. package/src/monitoring/page-cache.ts +1 -1
  298. package/src/monitoring/plugin-auto-update.ts +1 -7
  299. package/src/monitoring/plugin-source-watch.ts +1 -1
  300. package/src/monitoring/recovery/db.ts +1 -1
  301. package/src/monitoring/recovery/inflight-content.ts +1 -1
  302. package/src/monitoring/resource-sampler.ts +3 -14
  303. package/src/monitoring/stall-capture.ts +1 -1
  304. package/src/monitoring/worker.ts +9 -76
  305. package/src/monitoring/workspace-git-heartbeat.ts +1 -1
  306. package/src/oauth/connect-orchestrator.ts +4 -4
  307. package/src/permissions/checker.ts +1 -1
  308. package/src/permissions/workspace-policy.ts +1 -1
  309. package/src/persistence/__tests__/main-db-readonly-probe.ts +27 -52
  310. package/src/persistence/__tests__/message-content-file.test.ts +1 -1
  311. package/src/persistence/__tests__/slow-sync-log.test.ts +0 -2
  312. package/src/persistence/attachments-store.ts +1 -1
  313. package/src/persistence/conversation-crud.ts +1 -38
  314. package/src/persistence/conversation-directories.ts +1 -1
  315. package/src/persistence/conversation-types.ts +0 -8
  316. package/src/persistence/db-async-query.ts +1 -1
  317. package/src/persistence/db-connection.ts +11 -1
  318. package/src/persistence/db-init.ts +1 -1
  319. package/src/persistence/db-maintenance.ts +1 -1
  320. package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +1 -1
  321. package/src/persistence/embeddings/embedding-local.ts +4 -4
  322. package/src/persistence/embeddings/embedding-runtime-manager.ts +1 -1
  323. package/src/persistence/embeddings/qdrant-client.ts +1 -1
  324. package/src/persistence/embeddings/qdrant-manager.ts +1 -1
  325. package/src/persistence/job-handlers/__tests__/message-lexical-backfill.test.ts +1 -1
  326. package/src/persistence/job-handlers/__tests__/message-lexical-enqueue.test.ts +1 -1
  327. package/src/persistence/job-handlers/cleanup.ts +8 -17
  328. package/src/persistence/llm-usage-store.ts +2 -229
  329. package/src/persistence/message-content-file.ts +1 -1
  330. package/src/persistence/migrations/209-strip-thinking-from-consolidated.ts +1 -1
  331. package/src/persistence/migrations/222-strip-placeholder-sentinels-from-messages.ts +1 -1
  332. package/src/persistence/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +1 -1
  333. package/src/persistence/migrations/306-rewrite-frontier-profile-pins.test.ts +1 -1
  334. package/src/persistence/migrations/329-move-onboarding-events-to-telemetry-db.test.ts +13 -0
  335. package/src/persistence/migrations/330-move-auth-fallback-events-to-telemetry-db.test.ts +13 -0
  336. package/src/persistence/migrations/331-move-lifecycle-events-to-telemetry-db.test.ts +13 -0
  337. package/src/persistence/migrations/332-move-skill-loaded-events-to-telemetry-db.test.ts +13 -0
  338. package/src/persistence/migrations/333-create-telemetry-events-table.test.ts +7 -4
  339. package/src/persistence/migrations/334-backfill-telemetry-events-outbox.test.ts +13 -0
  340. package/src/persistence/migrations/387-drop-telemetry-tables.ts +51 -0
  341. package/src/persistence/migrations/__tests__/327-create-flush-checkpoints.test.ts +9 -1
  342. package/src/persistence/migrations/__tests__/387-drop-telemetry-tables.test.ts +79 -0
  343. package/src/persistence/migrations/helpers/relocation.ts +1 -1
  344. package/src/persistence/migrations/run-migrations.ts +1 -1
  345. package/src/persistence/migrations/validate-migration-state.ts +1 -1
  346. package/src/persistence/raw-query.ts +1 -29
  347. package/src/persistence/slow-sync-log.ts +0 -23
  348. package/src/persistence/steps.ts +2 -0
  349. package/src/{telemetry → persistence}/tool-audit.ts +6 -121
  350. package/src/plugin-api/index.ts +1 -1
  351. package/src/plugin-api/plugin-name-env.ts +1 -1
  352. package/src/plugins/__tests__/installed-plugin-dirs.test.ts +1 -1
  353. package/src/plugins/collect-source-versions.ts +1 -1
  354. package/src/plugins/defaults/channel/package.json +1 -1
  355. package/src/plugins/defaults/cloud-hosted/package.json +1 -1
  356. package/src/plugins/defaults/compaction/package.json +1 -1
  357. package/src/plugins/defaults/documents/package.json +1 -1
  358. package/src/plugins/defaults/empty-response/package.json +1 -1
  359. package/src/plugins/defaults/exploration-drift/package.json +1 -1
  360. package/src/plugins/defaults/image-fallback/package.json +1 -1
  361. package/src/plugins/defaults/image-recovery/package.json +1 -1
  362. package/src/plugins/defaults/max-tokens-continue/package.json +1 -1
  363. package/src/plugins/defaults/memory/__tests__/db-async-query.test.ts +1 -1
  364. package/src/plugins/defaults/memory/__tests__/db-maintenance.test.ts +1 -1
  365. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +0 -82
  366. package/src/plugins/defaults/memory/__tests__/memory-retrospective-skill-card.test.ts +0 -41
  367. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -2
  368. package/src/plugins/defaults/memory/memory-retrospective-job.ts +3 -52
  369. package/src/plugins/defaults/memory/memory-retrospective-skill-card.ts +0 -18
  370. package/src/plugins/defaults/memory/package.json +1 -1
  371. package/src/plugins/defaults/memory/paths.ts +4 -4
  372. package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +1 -1
  373. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +5 -5
  374. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +0 -207
  375. package/src/plugins/defaults/memory/src/memory-worker-routes.ts +1 -1
  376. package/src/plugins/defaults/memory/startup.ts +1 -22
  377. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +2 -253
  378. package/src/plugins/defaults/memory/v3/orchestrate.ts +5 -129
  379. package/src/plugins/defaults/memory/worker-control.ts +1 -1
  380. package/src/plugins/defaults/memory/worker.ts +2 -2
  381. package/src/plugins/defaults/session/package.json +1 -1
  382. package/src/plugins/defaults/surface-completion-nudge/package.json +1 -1
  383. package/src/plugins/defaults/task-progress/package.json +1 -1
  384. package/src/plugins/defaults/task-progress-nudge/package.json +1 -1
  385. package/src/plugins/defaults/title-generate/package.json +1 -1
  386. package/src/plugins/defaults/tool-error/package.json +1 -1
  387. package/src/plugins/defaults/tool-result-truncate/package.json +1 -1
  388. package/src/plugins/defaults/turn-context/package.json +1 -1
  389. package/src/plugins/defaults/workspace/injectors.ts +1 -1
  390. package/src/plugins/defaults/workspace/package.json +1 -1
  391. package/src/plugins/disabled-state.ts +1 -1
  392. package/src/plugins/ensure-plugin-api-shim.ts +1 -1
  393. package/src/plugins/ensure-shared-dep-links.ts +1 -1
  394. package/src/plugins/installed-plugin-dirs.ts +1 -1
  395. package/src/plugins/mcp-servers.ts +7 -9
  396. package/src/plugins/mtime-cache.ts +2 -2
  397. package/src/plugins/source-versions.ts +1 -1
  398. package/src/prompts/bootstrap-cleanup.ts +1 -1
  399. package/src/prompts/persona-resolver.ts +1 -1
  400. package/src/prompts/sections.ts +2 -2
  401. package/src/prompts/system-prompt.ts +10 -42
  402. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +0 -37
  403. package/src/providers/__tests__/cadet-connection-routing.test.ts +2 -2
  404. package/src/providers/cadet/client.ts +1 -1
  405. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -1
  406. package/src/providers/inference/adapter-factory.ts +1 -1
  407. package/src/providers/inference/backfill.ts +3 -10
  408. package/src/providers/inference/profile-probe.ts +1 -1
  409. package/src/providers/retry.ts +0 -2
  410. package/src/providers/types.ts +0 -7
  411. package/src/providers/usage-tracking.ts +1 -1
  412. package/src/routes/__tests__/route-host.test.ts +1 -1
  413. package/src/routes/control.ts +1 -1
  414. package/src/routes/route-host-client.ts +1 -1
  415. package/src/routes/worker.ts +2 -2
  416. package/src/runtime/agent-wake.ts +1 -1
  417. package/src/runtime/assistant-stream-state.ts +1 -1
  418. package/src/runtime/auth/token-service.ts +1 -1
  419. package/src/runtime/background-job-runner.ts +1 -1
  420. package/src/runtime/middleware/twilio-validation.ts +2 -2
  421. package/src/runtime/routes/__tests__/app-pin-routes.test.ts +1 -1
  422. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +1 -1
  423. package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +3 -33
  424. package/src/runtime/routes/__tests__/cloud-credits-routes.test.ts +5 -4
  425. package/src/runtime/routes/__tests__/cloud-invoice-routes.test.ts +4 -3
  426. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +72 -298
  427. package/src/runtime/routes/__tests__/default-plugin-routes.test.ts +1 -1
  428. package/src/runtime/routes/__tests__/deprecated-route-context.test.ts +15 -62
  429. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +105 -12
  430. package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +1 -1
  431. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -1
  432. package/src/runtime/routes/__tests__/plugins-routes.test.ts +47 -211
  433. package/src/runtime/routes/__tests__/route-host-worker-routes.test.ts +1 -1
  434. package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +1 -1
  435. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +1 -1
  436. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +1 -1
  437. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +1 -1
  438. package/src/runtime/routes/__tests__/user-routes-cli.test.ts +1 -1
  439. package/src/runtime/routes/__tests__/user-routes-notices.test.ts +1 -1
  440. package/src/runtime/routes/__tests__/user-routes-request.test.ts +1 -1
  441. package/src/runtime/routes/app-management-routes.ts +1 -1
  442. package/src/runtime/routes/attachment-routes.ts +1 -1
  443. package/src/runtime/routes/audit-routes.ts +1 -1
  444. package/src/runtime/routes/avatar-routes.ts +3 -10
  445. package/src/runtime/routes/cloud-routes.ts +3 -3
  446. package/src/runtime/routes/content-source-routes.ts +1 -1
  447. package/src/runtime/routes/conversation-query-routes.ts +49 -706
  448. package/src/runtime/routes/conversation-routes.ts +1 -38
  449. package/src/runtime/routes/debug-routes.ts +1 -1
  450. package/src/runtime/routes/deprecated-route-context.ts +5 -61
  451. package/src/runtime/routes/heartbeat-routes.ts +1 -1
  452. package/src/runtime/routes/identity-routes.ts +1 -1
  453. package/src/runtime/routes/index.ts +0 -6
  454. package/src/runtime/routes/inference-profile-session-handler.ts +3 -3
  455. package/src/runtime/routes/inference-profiles-routes.ts +48 -21
  456. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -1
  457. package/src/runtime/routes/llm-call-sites-routes.ts +3 -3
  458. package/src/runtime/routes/log-export/__tests__/installed-inventory.test.ts +1 -1
  459. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +1 -1
  460. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +1 -1
  461. package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
  462. package/src/runtime/routes/log-export-routes.ts +17 -15
  463. package/src/runtime/routes/migration-rollback-routes.ts +1 -1
  464. package/src/runtime/routes/migration-routes.ts +2 -3
  465. package/src/runtime/routes/monitoring-routes.ts +2 -2
  466. package/src/runtime/routes/oauth-providers.ts +5 -5
  467. package/src/runtime/routes/plugins-routes.ts +38 -163
  468. package/src/runtime/routes/profiler-routes.ts +1 -1
  469. package/src/runtime/routes/route-host-worker-routes.ts +1 -1
  470. package/src/runtime/routes/sanity-routes.ts +1 -1
  471. package/src/runtime/routes/schedule-routes.ts +1 -1
  472. package/src/runtime/routes/schedule-worker-routes.ts +1 -1
  473. package/src/runtime/routes/settings-routes.ts +1 -1
  474. package/src/runtime/routes/sounds-config-routes.ts +1 -1
  475. package/src/runtime/routes/surface-conversation-resolver.ts +1 -7
  476. package/src/runtime/routes/user-route-dispatcher.ts +1 -3
  477. package/src/runtime/routes/user-route-import.ts +1 -1
  478. package/src/runtime/routes/user-route-resolution.ts +1 -1
  479. package/src/runtime/routes/user-routes-cli.ts +1 -1
  480. package/src/runtime/routes/workspace-commit-routes.ts +1 -1
  481. package/src/runtime/routes/workspace-greetings.ts +1 -1
  482. package/src/runtime/routes/workspace-routes.ts +1 -1
  483. package/src/runtime/routes/workspace-utils.ts +1 -1
  484. package/src/runtime/sync/resource-sync-events.ts +1 -1
  485. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +1 -1
  486. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1 -1
  487. package/src/schedule/plugin-schedule-availability.ts +1 -1
  488. package/src/schedule/plugin-schedule-declarations.ts +1 -1
  489. package/src/schedule/run-script.ts +1 -1
  490. package/src/schedule/scheduler.ts +1 -11
  491. package/src/schedule/worker-control.ts +1 -1
  492. package/src/schedule/worker.ts +3 -3
  493. package/src/security/oauth2.ts +2 -2
  494. package/src/security/secret-allowlist.ts +1 -1
  495. package/src/signals/cancel.ts +1 -1
  496. package/src/signals/conversation-undo.ts +1 -1
  497. package/src/signals/emit-event.ts +1 -1
  498. package/src/signals/event-stream.ts +1 -1
  499. package/src/signals/user-message.ts +1 -1
  500. package/src/skills/catalog-install.ts +2 -1
  501. package/src/skills/clawhub.ts +1 -1
  502. package/src/skills/cloud-store.ts +1 -1
  503. package/src/skills/path-classifier.ts +1 -1
  504. package/src/skills/skill-history.ts +1 -1
  505. package/src/skills/skillssh-registry.ts +1 -1
  506. package/src/skills/uninstall.ts +1 -1
  507. package/src/skills/workspace-skill-md-signature.ts +1 -1
  508. package/src/subagent/manager.ts +1 -1
  509. package/src/theme/workspace-theme.ts +1 -1
  510. package/src/tools/browser/auth-cache.ts +1 -1
  511. package/src/tools/browser/browser-manager.ts +1 -1
  512. package/src/tools/browser/runtime-check.ts +1 -1
  513. package/src/tools/executor.ts +4 -4
  514. package/src/tools/filesystem/write.ts +1 -1
  515. package/src/tools/permission-checker.ts +2 -51
  516. package/src/tools/run-standalone.ts +1 -1
  517. package/src/tools/skills/format-input-schema.ts +1 -1
  518. package/src/tools/skills/load.ts +1 -1
  519. package/src/tools/skills/scaffold-cloud.ts +0 -32
  520. package/src/tools/terminal/safe-env.ts +1 -2
  521. package/src/tools/terminal/sanitized-bash.ts +1 -1
  522. package/src/tools/terminal/shell.ts +1 -1
  523. package/src/tools/tool-approval-handler.ts +4 -1
  524. package/src/tools/workflows/manage-workflows.ts +2 -2
  525. package/src/tools/workflows/run-workflow.test.ts +1 -1
  526. package/src/tools/workspace-tools/loader.ts +1 -1
  527. package/src/tts/synthesis-stream.ts +2 -2
  528. package/src/usage/attribution.ts +1 -11
  529. package/src/util/browser.ts +1 -1
  530. package/src/util/bun-runtime.ts +2 -1
  531. package/src/util/device-id.ts +1 -1
  532. package/src/util/disk-usage.ts +4 -139
  533. package/src/util/layout.ts +3 -3
  534. package/src/util/logger.ts +1 -1
  535. package/src/util/logs-db-path.ts +1 -1
  536. package/src/util/memory-db-path.ts +1 -1
  537. package/src/util/{platform.ts → os.ts} +5 -30
  538. package/src/util/process-tree.ts +19 -0
  539. package/src/util/sqlite3-runtime.ts +1 -1
  540. package/src/util/telemetry-db-path.ts +1 -1
  541. package/src/watcher/__tests__/engine.test.ts +0 -23
  542. package/src/watcher/engine.ts +0 -7
  543. package/src/windows-compiled-cli.ts +0 -4
  544. package/src/windows-compiled-worker-entry.ts +0 -6
  545. package/src/workflows/leaf-runner.test.ts +1 -1
  546. package/src/workflows/leaf-runner.ts +1 -1
  547. package/src/workflows/library.ts +1 -1
  548. package/src/workspace/adaptive-thinking-repair.ts +5 -24
  549. package/src/workspace/byok-default-profile-ensure.ts +24 -26
  550. package/src/workspace/custom-profile-ensure.ts +2 -2
  551. package/src/workspace/git-service.ts +1 -1
  552. package/src/workspace/hatched-date.ts +1 -1
  553. package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +2 -2
  554. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +1 -1
  555. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +1 -1
  556. package/src/workspace/migrations/077-seed-memory-router-callsite.ts +1 -1
  557. package/src/workspace/migrations/registry.ts +0 -4
  558. package/src/workspace/turn-commit.ts +1 -1
  559. package/docs/activation-funnel-telemetry.md +0 -324
  560. package/scripts/bundled-plugin-packages.ts +0 -154
  561. package/scripts/generate-bundled-plugin-packages.ts +0 -29
  562. package/src/__tests__/activation-early-marking.test.ts +0 -114
  563. package/src/__tests__/auth-fallback-events-store.test.ts +0 -99
  564. package/src/__tests__/config-get-vision-flag.test.ts +0 -139
  565. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +0 -400
  566. package/src/__tests__/internal-telemetry-routes.test.ts +0 -213
  567. package/src/__tests__/telemetry-routes.test.ts +0 -182
  568. package/src/__tests__/turn-events-store.test.ts +0 -376
  569. package/src/__tests__/ui-channel-variants.test.ts +0 -53
  570. package/src/avatar/__tests__/avatar-changed-telemetry.test.ts +0 -217
  571. package/src/avatar/avatar-changed-telemetry.ts +0 -128
  572. package/src/calls/__tests__/call-funnel-telemetry.test.ts +0 -204
  573. package/src/cli/commands/telemetry.help.ts +0 -27
  574. package/src/cli/commands/telemetry.ts +0 -83
  575. package/src/cli/lib/__tests__/local-plugin-upgrade.test.ts +0 -251
  576. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +0 -113
  577. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +0 -384
  578. package/src/cli/lib/__tests__/plugin-pin-history.test.ts +0 -164
  579. package/src/cli/lib/__tests__/plugins-install-offline.test.ts +0 -228
  580. package/src/cli/lib/bundled-marketplace.json +0 -1575
  581. package/src/cli/lib/bundled-plugin-packages.json +0 -827
  582. package/src/cli/lib/bundled-plugin-packages.ts +0 -87
  583. package/src/cli/lib/plugin-catalog-local.ts +0 -94
  584. package/src/cli/lib/plugin-pin-history.ts +0 -276
  585. package/src/cloud/feature-gate.ts +0 -8
  586. package/src/config/__tests__/cloud-rehydration.test.ts +0 -180
  587. package/src/config/cloud-rehydration.ts +0 -35
  588. package/src/daemon/__tests__/auto-profile-router.test.ts +0 -380
  589. package/src/daemon/auto-profile-router.ts +0 -428
  590. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +0 -391
  591. package/src/monitoring/__tests__/db-integrity-sample.test.ts +0 -113
  592. package/src/monitoring/__tests__/oom-kill-reporter.test.ts +0 -374
  593. package/src/monitoring/db-integrity-check.ts +0 -163
  594. package/src/monitoring/db-integrity-sample.ts +0 -176
  595. package/src/monitoring/oom-kill-reporter.ts +0 -469
  596. package/src/onboarding/__tests__/onboarding-events-store.test.ts +0 -158
  597. package/src/onboarding/onboarding-events-store.ts +0 -296
  598. package/src/persistence/__tests__/raw-query-telemetry.test.ts +0 -76
  599. package/src/persistence/lifecycle-events-store.test.ts +0 -134
  600. package/src/persistence/lifecycle-events-store.ts +0 -110
  601. package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +0 -76
  602. package/src/plugins/defaults/memory/activation-session-store.ts +0 -50
  603. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +0 -281
  604. package/src/runtime/routes/auto-profile-routes.ts +0 -83
  605. package/src/runtime/routes/internal-telemetry-routes.ts +0 -142
  606. package/src/runtime/routes/telemetry-routes.ts +0 -274
  607. package/src/security/auth-fallback-events-store.ts +0 -57
  608. package/src/telemetry/__tests__/activation-funnel.test.ts +0 -95
  609. package/src/telemetry/__tests__/config-setting-events-store.test.ts +0 -63
  610. package/src/telemetry/__tests__/config-setting-snapshot.test.ts +0 -143
  611. package/src/telemetry/__tests__/flush-checkpoints.test.ts +0 -56
  612. package/src/telemetry/__tests__/live-voice-funnel.test.ts +0 -174
  613. package/src/telemetry/__tests__/outbox-test-harness.ts +0 -85
  614. package/src/telemetry/__tests__/phone-call-funnel.test.ts +0 -72
  615. package/src/telemetry/__tests__/telemetry-event-fixtures.ts +0 -282
  616. package/src/telemetry/__tests__/watchdog-events-store.test.ts +0 -71
  617. package/src/telemetry/activation-funnel.ts +0 -172
  618. package/src/telemetry/config-setting-events-store.ts +0 -40
  619. package/src/telemetry/config-setting-snapshot.ts +0 -170
  620. package/src/telemetry/flush-checkpoints.ts +0 -70
  621. package/src/telemetry/live-voice-funnel.ts +0 -185
  622. package/src/telemetry/memory-corpus-size.test.ts +0 -120
  623. package/src/telemetry/memory-corpus-size.ts +0 -184
  624. package/src/telemetry/memory-tier-reporter.test.ts +0 -195
  625. package/src/telemetry/memory-tier-reporter.ts +0 -130
  626. package/src/telemetry/phone-call-funnel.ts +0 -144
  627. package/src/telemetry/skill-loaded-events-store.test.ts +0 -93
  628. package/src/telemetry/skill-loaded-events-store.ts +0 -39
  629. package/src/telemetry/telemetry-event-sources.test.ts +0 -223
  630. package/src/telemetry/telemetry-event-sources.ts +0 -597
  631. package/src/telemetry/telemetry-events-outbox.test.ts +0 -375
  632. package/src/telemetry/telemetry-events-outbox.ts +0 -237
  633. package/src/telemetry/telemetry-main-db.ts +0 -48
  634. package/src/telemetry/telemetry-wire-source.json +0 -4
  635. package/src/telemetry/telemetry-wire-validation.test.ts +0 -157
  636. package/src/telemetry/telemetry-wire-validation.ts +0 -162
  637. package/src/telemetry/telemetry-wire.generated.ts +0 -483
  638. package/src/telemetry/tool-executed-events-store.test.ts +0 -210
  639. package/src/telemetry/tool-executed-events-store.ts +0 -102
  640. package/src/telemetry/trace-collection-policy.test.ts +0 -28
  641. package/src/telemetry/trace-collection-policy.ts +0 -30
  642. package/src/telemetry/turn-events-store.ts +0 -216
  643. package/src/telemetry/turn-trace-store.test.ts +0 -884
  644. package/src/telemetry/turn-trace-store.ts +0 -399
  645. package/src/telemetry/types.test.ts +0 -38
  646. package/src/telemetry/types.ts +0 -780
  647. package/src/telemetry/usage-telemetry-reporter.test.ts +0 -3445
  648. package/src/telemetry/usage-telemetry-reporter.ts +0 -566
  649. package/src/telemetry/watchdog-direct-emit.test.ts +0 -112
  650. package/src/telemetry/watchdog-direct-emit.ts +0 -104
  651. package/src/telemetry/watchdog-events-store.ts +0 -31
  652. package/src/tools/ui-surface/channel-variants.ts +0 -52
  653. package/src/watcher/__tests__/telemetry.test.ts +0 -178
  654. package/src/watcher/telemetry.ts +0 -93
  655. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +0 -165
  656. package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +0 -151
  657. /package/src/{telemetry → persistence}/tool-usage-store.ts +0 -0
  658. /package/src/{telemetry → persistence}/turn-outcome.ts +0 -0
@@ -19,8 +19,6 @@
19
19
  * POST /v1/messages/queued/:id/steer — steer to a queued message
20
20
  */
21
21
 
22
- import { isDeepStrictEqual } from "node:util";
23
-
24
22
  import { z } from "zod";
25
23
 
26
24
  import { LlmContextResponseSchema } from "../../api/responses/llm-context-response.js";
@@ -35,39 +33,26 @@ import {
35
33
  scrubNulledChatSettings,
36
34
  validateChatSettingsWrite,
37
35
  } from "../../config/chat-settings.js";
38
- import {
39
- catalogEntryFor,
40
- modalitiesOf,
41
- resolveModalityOverride,
42
- } from "../../config/input-modalities.js";
43
36
  import {
44
37
  deepMergeOverwrite,
45
38
  getConfig,
46
39
  invalidateConfigCache,
47
- loadConfig,
48
40
  loadRawConfig,
49
41
  saveRawConfig,
50
42
  setNestedValue,
51
43
  withSuppressedConfigDiskWrites,
52
44
  withSuppressedConfigDiskWritesSync,
53
45
  } from "../../config/loader.js";
54
- import {
55
- completeCustomProfile,
56
- mergePreservingUnknownKeys,
57
- } from "../../config/profile-materialization.js";
58
46
  import {
59
47
  type AssistantConfig,
60
48
  AssistantConfigSchema,
61
49
  } from "../../config/schema.js";
62
50
  import { getSchemaAtPath } from "../../config/schema-utils.js";
63
51
  import {
64
- collectFallbackProfileIssues,
65
- LLMConfigBase,
66
52
  LLMConfigFragment,
53
+ LLMDefaultConfigSchema,
67
54
  LLMSchema,
68
55
  ProfileEntry,
69
- routingIdentityModelIssue,
70
- unknownLlmProviderIssue,
71
56
  } from "../../config/schemas/llm.js";
72
57
  import { VALID_MEMORY_EMBEDDING_PROVIDERS } from "../../config/schemas/memory-storage.js";
73
58
  import { ServiceModeSchema } from "../../config/schemas/services.js";
@@ -100,7 +85,6 @@ import {
100
85
  log,
101
86
  } from "../../daemon/handlers/shared.js";
102
87
  import { rescheduleHeartbeatIfTimezoneChanged } from "../../heartbeat/heartbeat-service.js";
103
- import { overlayWorkspaceMcpForConfigRead } from "../../mcp/workspace-mcp-config.js";
104
88
  import {
105
89
  getAssistantMessageIdsInTurn,
106
90
  getConversation,
@@ -120,7 +104,6 @@ import { getMemoryRecallLogByMessageIds } from "../../plugins/defaults/memory/me
120
104
  import { getMemoryV2ActivationLogByMessageIds } from "../../plugins/defaults/memory/v2/activation-log-store.js";
121
105
  import { getMemoryV3SelectionForInspectorByMessageIds } from "../../plugins/defaults/memory/v3/selection-log-store.js";
122
106
  import { CLOUD_ROUTABLE_PROVIDERS } from "../../providers/cadet-model-routing.js";
123
- import { writableProfileProviderIssue } from "../../providers/connection-resolution.js";
124
107
  import { ROUTING_IDENTITY_PROVIDERS } from "../../providers/inference/auth.js";
125
108
  import { PROVIDERS_REQUIRING_BASE_URL_AND_MODELS } from "../../providers/inference/auth.js";
126
109
  import {
@@ -173,10 +156,7 @@ type LlmContextRouteResult = Omit<LlmContextNormalizationResult, "summary"> & {
173
156
  import {
174
157
  CODE_OWNED_PROFILE_NAMES,
175
158
  getEffectiveProfilesForProvider,
176
- getUserSelectableProfilesForProvider,
177
- INVARIANT_PROFILE_NAMES,
178
159
  MANAGED_PROFILE_NAMES,
179
- resolveDefaultProfileForProvider,
180
160
  } from "../../config/default-profile-catalog.js";
181
161
  import { DEFAULT_PROFILE_KEYS } from "../../config/default-profile-names.js";
182
162
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
@@ -523,22 +503,18 @@ async function handleSetEmbeddingConfig({ body }: RouteHandlerArgs) {
523
503
  }
524
504
 
525
505
  /**
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.
506
+ * The `config_set` body. `value` is `z.unknown()` — deliberately wider than
507
+ * an object shape — because the CLI contract writes scalar values and
508
+ * explicit `null`s (`set heartbeat.activeHoursStart null` clears a key), so
509
+ * the value is any JSON at all; only its PRESENCE is required, which zod
510
+ * cannot express on an unknown leaf, so the handler keeps that domain check.
533
511
  */
534
- const JsonRecordSchema = z.record(z.string(), z.unknown());
535
- const RawProfileEntrySchema = z.record(z.string(), z.unknown());
536
- const RawLlmWriteSliceSchema = z
512
+ const ConfigSetByKeyBodySchema = z
537
513
  .object({
538
- default: z.unknown().optional(),
539
- profiles: z.record(z.string(), z.unknown()).nullable().optional(),
514
+ path: z.string().min(1),
515
+ value: z.unknown(),
540
516
  })
541
- .passthrough();
517
+ .meta({ id: "ConfigSetByKeyRequest" });
542
518
 
543
519
  /**
544
520
  * Wire shape of the `memory` section in config responses. Passthrough
@@ -594,33 +570,28 @@ const CallSiteOverrideDraftSchema = nullablePartial(
594
570
  * the generated SDK produces literal-union types — no hand-written patch
595
571
  * types needed downstream.
596
572
  */
597
- const ConfigPatchRequestSchema = z
573
+ export type ConfigPatchRequest = z.infer<typeof ConfigPatchRequestSchema>;
574
+
575
+ export const ConfigPatchRequestSchema = z
598
576
  .object({
599
577
  ...ChatSettingsPatchSchema.shape,
600
578
  llm: z
601
579
  .object({
602
- default: nullablePartial(
603
- LLMConfigFragment.extend({
604
- provider_connection: z.string().optional(),
605
- }),
606
- )
580
+ default: nullablePartial(LLMDefaultConfigSchema)
607
581
  .passthrough()
608
582
  .nullable()
609
583
  .optional(),
610
584
  profiles: z
611
585
  .record(z.string(), ProfilePatchEntrySchema.nullable())
586
+ .nullable()
612
587
  .optional(),
613
- profileOrder: z.array(z.string()).optional(),
614
- activeProfile: z.string().nullable().optional(),
615
- advisorProfile: z.string().nullable().optional(),
588
+ profileOrder: z.array(z.string().min(1)).optional(),
589
+ activeProfile: z.string().min(1).nullable().optional(),
590
+ advisorProfile: z.string().min(1).nullable().optional(),
616
591
  callSites: z
617
592
  .record(z.string(), CallSiteOverrideDraftSchema.nullable())
618
593
  .optional(),
619
- profileSession: z
620
- .object({
621
- defaultTtlSeconds: z.number().optional(),
622
- maxTtlSeconds: z.number().optional(),
623
- })
594
+ profileSession: nullablePartial(LLMSchema.shape.profileSession.unwrap())
624
595
  .nullable()
625
596
  .optional(),
626
597
  })
@@ -678,253 +649,6 @@ const ConfigGetResponseSchema = AssistantConfigSchema.pick({
678
649
  services: true,
679
650
  });
680
651
 
681
- function handleGetConfig() {
682
- try {
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;
693
- } catch (err) {
694
- const message = err instanceof Error ? err.message : String(err);
695
- throw new InternalError(`Failed to read config: ${message}`);
696
- }
697
- }
698
- function overlayChatSettingsForWire(config: AssistantConfig): void {
699
- const effective = getConfig();
700
- config.conversations = {
701
- ...config.conversations,
702
- autoArchive: {
703
- ...config.conversations?.autoArchive,
704
- ...effective.conversations.autoArchive,
705
- },
706
- };
707
- config.notifications = {
708
- ...config.notifications,
709
- newMessageEnabled: effective.notifications.newMessageEnabled,
710
- };
711
- }
712
-
713
- /**
714
- * Replace `llm.profiles` in an outgoing config response with the effective
715
- * profile view (code-catalog default bodies + workspace overlays). Default
716
- * profile CONTENT is code-owned and the workspace holds at most a thin stub,
717
- * but clients (settings UI, sticky-profile pickers) need the full bodies to
718
- * render labels/models — so the wire view materializes them, resolved through
719
- * `llm.default`'s column of the intent × provider matrix so a BYO
720
- * install sees the provider/model that actually dispatches. Wire-only:
721
- * `normalizeCloudProfileWrites` reduces echoed bodies back to the
722
- * workspace-owned fields on the write paths, so a `config get` →
723
- * `config set` round-trip never persists catalog content — the two must
724
- * resolve through the same column or honest round-trips are rejected.
725
- *
726
- * The profiles come from one schema parse of the `llm` block being served
727
- * through the real `LLMSchema`, whose `isDefaultProviderChoice` clamp
728
- * shields the matrix lookup from a persisted value the matrix cannot
729
- * implement, which a raw read would feed straight into resolution.
730
- */
731
- function overlayEffectiveProfilesForWire(config: AssistantConfig): void {
732
- const parsedLlm = LLMSchema.safeParse(config.llm);
733
- config.llm.profiles = getUserSelectableProfilesForProvider(
734
- parsedLlm.success ? parsedLlm.data : undefined,
735
- );
736
- }
737
-
738
- /**
739
- * Normalize managed default-profile entries in a config-write fragment, in
740
- * place, so a `config get` → write round-trip of the enriched wire view is a
741
- * no-op while genuine edit attempts still reach the invariant guard's
742
- * precise rejections:
743
- *
744
- * - A default name backed by a user-source on-disk entry is a legacy shadow
745
- * and stays fully editable — left untouched here.
746
- * - Echo-stripping: any field whose incoming value equals the current
747
- * effective (wire) value is deleted from the fragment — it is catalog
748
- * content the client read from us, not user input. Values that DIFFER stay
749
- * in the fragment for {@link assertInvariantProfilesPreserved} to reject
750
- * (or, for `status`, to judge as the one legal transition).
751
- * - A default name with NO on-disk entry is catalog-owned: after
752
- * echo-stripping, any remaining content field, a `disabled` status, or a
753
- * non-managed `source` is rejected — default names cannot be newly
754
- * shadowed or given content. A clean echo reduces to a no-op
755
- * `{source: "managed"}` stub.
756
- *
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.
761
- */
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) {
767
- return;
768
- }
769
- const profiles = llm.data.profiles;
770
-
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;
777
-
778
- for (const name of Object.keys(profiles)) {
779
- if (!MANAGED_PROFILE_NAMES.has(name)) {
780
- continue;
781
- }
782
- const entry = RawProfileEntrySchema.safeParse(profiles[name]);
783
- if (!entry.success) {
784
- continue;
785
- }
786
-
787
- const current = RawProfileEntrySchema.safeParse(currentProfiles?.[name]);
788
-
789
- if (current.success && current.data.source !== "managed") {
790
- // Legacy user-owned shadow: fully editable, normal custom-profile rules.
791
- continue;
792
- }
793
-
794
- // Must resolve through the same provider column as
795
- // `overlayEffectiveProfilesForWire`, or an echo of the wire view would
796
- // not match `effective` and an honest round-trip would be rejected.
797
- const resolved = resolveDefaultProfileForProvider(
798
- typedCurrent?.success ? typedCurrent.data : undefined,
799
- name,
800
- getConfig().llm.default,
801
- );
802
- const effective: Record<string, unknown> | undefined = resolved
803
- ? { ...resolved }
804
- : undefined;
805
- for (const key of Object.keys(entry.data)) {
806
- if (key === "source" || key === "status") {
807
- continue;
808
- }
809
- const matchesEffective =
810
- effective != null &&
811
- key in effective &&
812
- isDeepStrictEqual(entry.data[key], effective[key]);
813
- if (!current.success || !(key in current.data)) {
814
- // Not on disk: a value matching the wire view is a client echo of
815
- // catalog content — drop it. Anything else stays for the guard.
816
- if (matchesEffective) {
817
- delete entry.data[key];
818
- }
819
- continue;
820
- }
821
- // The key exists on disk (a frozen overlay field, or stale content an
822
- // overlay persisted). Deleting it would read as a key removal on a
823
- // full-entry SET, so instead: an echo of the wire value that differs
824
- // from disk is pinned back to the on-disk value (no change for the
825
- // guard to reject); everything else is left for the guard's
826
- // frozen-field comparison.
827
- if (
828
- matchesEffective &&
829
- !isDeepStrictEqual(entry.data[key], current.data[key])
830
- ) {
831
- entry.data[key] = current.data[key];
832
- }
833
- }
834
-
835
- if (current.success) {
836
- // Existing stub: the guard compares the merged result against it and
837
- // rejects everything but the status re-enable.
838
- if (entry.data.source === undefined) {
839
- entry.data.source = "managed";
840
- }
841
- profiles[name] = entry.data;
842
- continue;
843
- }
844
-
845
- // No on-disk entry: the name is catalog-owned. Nothing but a clean echo
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") {
850
- throw new BadRequestError(
851
- `Cannot create profile "${name}" — the name is reserved for a code-defined default profile.`,
852
- );
853
- }
854
- if (
855
- "status" in entry.data &&
856
- entry.data.status != null &&
857
- entry.data.status !== "active"
858
- ) {
859
- throw new BadRequestError(`Cannot disable cloud profile "${name}".`);
860
- }
861
- const residual = Object.keys(entry.data).filter(
862
- (key) =>
863
- key !== "source" &&
864
- key !== "status" &&
865
- key !== "invariant" &&
866
- key !== "supportsVision",
867
- );
868
- if (residual.length > 0) {
869
- throw new BadRequestError(
870
- `Cannot edit cloud profile "${name}" fields [${residual.join(", ")}]. ` +
871
- `Cloud profiles are read-only; duplicate to a custom profile to customize.`,
872
- );
873
- }
874
- entry.data.source = "managed";
875
- profiles[name] = entry.data;
876
- }
877
-
878
- patch.llm = llm.data;
879
- }
880
-
881
- /**
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:
896
- *
897
- * - `supportsVision`: resolved from the model catalog, with a profile
898
- * `inputModalities.image` override winning when set. Unknown (provider,
899
- * model) pairs with no override default to `true` (fail-open) so image
900
- * upload remains available for custom / unlisted models.
901
- * - `invariant`: `true` for managed-source entries of the cloud profile
902
- * names (`INVARIANT_PROFILE_NAMES`); absent otherwise. Source-gated to
903
- * match `assertInvariantProfilesPreserved` — a user-owned profile sharing
904
- * a cloud name is fully editable, so it must render as a normal custom
905
- * profile.
906
- */
907
- function enrichProfilesForWire(config: AssistantConfig): void {
908
- const profiles: Record<string, WireProfileEntry> = config.llm.profiles;
909
- for (const [name, entry] of Object.entries(profiles)) {
910
- if (INVARIANT_PROFILE_NAMES.has(name) && entry.source === "managed") {
911
- entry.invariant = true;
912
- }
913
-
914
- if (entry.provider == null || entry.model == null) {
915
- continue;
916
- }
917
-
918
- const catalogModel = catalogEntryFor(entry.provider, entry.model);
919
- const catalogVision = catalogModel?.supportsVision;
920
- const imageOverride = modalitiesOf({
921
- inputModalities: entry.inputModalities,
922
- })?.image;
923
- entry.supportsVision =
924
- resolveModalityOverride(imageOverride, catalogVision) ?? true;
925
- }
926
- }
927
-
928
652
  /**
929
653
  * Return the JSON Schema for the assistant config (full or scoped).
930
654
  *
@@ -957,346 +681,6 @@ function handleGetConfigSchema({ queryParams = {} }: RouteHandlerArgs) {
957
681
  };
958
682
  }
959
683
 
960
- export function rejectCloudProfileDeletion(
961
- body: Record<string, unknown>,
962
- ): void {
963
- const llm = RawLlmWriteSliceSchema.safeParse(body.llm);
964
- if (!llm.success) {
965
- return;
966
- }
967
- if (llm.data.profiles === null) {
968
- throw new BadRequestError(
969
- "Cannot null llm.profiles — cloud profiles would be deleted.",
970
- );
971
- }
972
- const profiles = llm.data.profiles;
973
- if (!profiles) {
974
- return;
975
- }
976
- const existingLlm = RawLlmWriteSliceSchema.safeParse(getConfig().llm);
977
- const existingProfiles = existingLlm.success
978
- ? (existingLlm.data.profiles ?? {})
979
- : {};
980
- for (const name of Object.keys(profiles)) {
981
- if (profiles[name] !== null || !MANAGED_PROFILE_NAMES.has(name)) {
982
- continue;
983
- }
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`
986
- // and is freely deletable.
987
- const existing = RawProfileEntrySchema.safeParse(existingProfiles[name]);
988
- if (existing.success && existing.data.source === "managed") {
989
- throw new BadRequestError(`Cannot delete cloud profile "${name}".`);
990
- }
991
- }
992
- }
993
-
994
- /**
995
- * Enforce the cloud-profile invariants at the config-write choke point.
996
- *
997
- * Protects the thin managed stubs (`INVARIANT_PROFILE_NAMES`) that live in
998
- * workspace config. Default profile CONTENT is code-owned and only exists on
999
- * the wire, so the guard's job is the stub itself: it checks entries present
1000
- * in the OLD config, and `normalizeCloudProfileWrites` reduces incoming
1001
- * managed entries to the workspace-owned fields before this comparison runs.
1002
- *
1003
- * Invariance is gated on managed ownership: a name is enforced only when the
1004
- * OLD entry's `source` is `"managed"`. A user-owned profile sharing a
1005
- * managed name stays fully editable and deletable — for os-beta that state
1006
- * is real and supported, because the flag-gated reconcile refuses to
1007
- * overwrite a same-named user profile.
1008
- *
1009
- * For each invariant name where the OLD raw config carries a cloud-source
1010
- * plain-object entry at `llm.profiles[name]`:
1011
- *
1012
- * - The NEW raw config must still carry a plain-object entry at the same
1013
- * path — deletion, non-object overwrite, and subtree replacement are all
1014
- * rejected by this single check, regardless of route.
1015
- * - `status` is one-directional: effective status is
1016
- * `entry.status !== "disabled"` (absence/null = active). An active managed
1017
- * profile can never be disabled; a changed `status` must be `"active"`,
1018
- * `null`, or absent — re-enabling a disabled profile. Any other value is
1019
- * rejected.
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.
1025
- * - Every other field is frozen: any changed, added, or removed key across
1026
- * the union of both entries' keys (except `status` and the wire-only keys)
1027
- * is rejected. A pre-existing on-disk override (e.g. `topP`) is preserved
1028
- * but frozen — it passes the guard only while it doesn't change.
1029
- */
1030
- function assertInvariantProfilesPreserved(
1031
- oldRaw: Record<string, unknown>,
1032
- newRaw: Record<string, unknown>,
1033
- ): void {
1034
- const oldProfiles = asMutablePlainObject(
1035
- asMutablePlainObject(oldRaw.llm)?.profiles,
1036
- );
1037
- if (!oldProfiles) {
1038
- return;
1039
- }
1040
- const newProfiles = asMutablePlainObject(
1041
- asMutablePlainObject(newRaw.llm)?.profiles,
1042
- );
1043
-
1044
- for (const name of INVARIANT_PROFILE_NAMES) {
1045
- const oldEntry = asMutablePlainObject(oldProfiles[name]);
1046
- if (!oldEntry) {
1047
- continue;
1048
- }
1049
- if (oldEntry.source !== "managed") {
1050
- continue;
1051
- }
1052
-
1053
- const newEntry = newProfiles
1054
- ? asMutablePlainObject(newProfiles[name])
1055
- : null;
1056
- if (!newEntry) {
1057
- throw new BadRequestError(
1058
- `Cannot delete or replace cloud profile "${name}". Cloud profiles are read-only.`,
1059
- );
1060
- }
1061
-
1062
- if (!isDeepStrictEqual(oldEntry.status, newEntry.status)) {
1063
- if (newEntry.status === "disabled") {
1064
- throw new BadRequestError(`Cannot disable cloud profile "${name}".`);
1065
- }
1066
- if (newEntry.status !== "active" && newEntry.status != null) {
1067
- throw new BadRequestError(
1068
- `Cannot set status ${JSON.stringify(newEntry.status)} on cloud profile "${name}". ` +
1069
- `Only re-enabling (status "active") is allowed.`,
1070
- );
1071
- }
1072
- }
1073
-
1074
- const changedKeys = [
1075
- ...new Set([...Object.keys(oldEntry), ...Object.keys(newEntry)]),
1076
- ].filter(
1077
- (key) =>
1078
- key !== "status" &&
1079
- key !== "invariant" &&
1080
- key !== "supportsVision" &&
1081
- !isDeepStrictEqual(oldEntry[key], newEntry[key]),
1082
- );
1083
- if (changedKeys.length > 0) {
1084
- throw new BadRequestError(
1085
- `Cannot edit cloud profile "${name}" fields [${changedKeys.join(", ")}]. ` +
1086
- `Cloud profiles are read-only; duplicate to a custom profile to customize.`,
1087
- );
1088
- }
1089
- }
1090
- }
1091
-
1092
- /**
1093
- * Custom profiles persist as complete overrides: any user-source, non-mix
1094
- * profile entry this write creates or changes is completed against the
1095
- * workspace default base (`completeCustomProfile`) before it lands on disk, so no
1096
- * write path can persist a new partial profile. Entries the write did not
1097
- * touch are left byte-identical — an unrelated config write must not rewrite
1098
- * profile entries. Entries that do not parse as a `ProfileEntry` are also
1099
- * left untouched; full-config parsing owns their rejection/recovery.
1100
- */
1101
- function completeChangedCustomProfiles(
1102
- preWrite: Record<string, unknown>,
1103
- raw: Record<string, unknown>,
1104
- ): void {
1105
- const llm = RawLlmWriteSliceSchema.safeParse(raw.llm);
1106
- if (!llm.success || !llm.data.profiles) {
1107
- return;
1108
- }
1109
- const profiles = llm.data.profiles;
1110
- const parsedDefault = LLMConfigBase.safeParse(llm.data.default ?? {});
1111
- if (!parsedDefault.success) {
1112
- return;
1113
- }
1114
- const priorLlm = RawLlmWriteSliceSchema.safeParse(preWrite.llm);
1115
- const prior = priorLlm.success ? (priorLlm.data.profiles ?? {}) : {};
1116
- for (const [name, entry] of Object.entries(profiles)) {
1117
- if (isDeepStrictEqual(entry, prior[name])) {
1118
- continue;
1119
- }
1120
- const parsedEntry = ProfileEntry.safeParse(entry);
1121
- if (!parsedEntry.success) {
1122
- continue;
1123
- }
1124
- // `source: "managed"` is only meaningful on catalog-owned names. On any
1125
- // other name it would skip completion (and read as a locked entry), so a
1126
- // body claiming it is normalized to user-owned before completing.
1127
- const entryData =
1128
- parsedEntry.data.source === "managed" && !MANAGED_PROFILE_NAMES.has(name)
1129
- ? { ...parsedEntry.data, source: "user" as const }
1130
- : parsedEntry.data;
1131
- // Merge the completed known fields over the original raw entry at every
1132
- // depth: `safeParse` strips keys the schema doesn't know (top-level and
1133
- // inside nested objects like `contextWindow`), and dropping them here
1134
- // would delete forward-compatible fields whenever the entry is edited.
1135
- const entryRecord = RawProfileEntrySchema.safeParse(entry);
1136
- if (!entryRecord.success) {
1137
- continue;
1138
- }
1139
- profiles[name] = mergePreservingUnknownKeys(
1140
- entryRecord.data,
1141
- completeCustomProfile(parsedDefault.data, entryData) as Record<
1142
- string,
1143
- unknown
1144
- >,
1145
- );
1146
- }
1147
- // A zod parse copies — write the completed slice back so `raw` carries it.
1148
- raw.llm = llm.data;
1149
- }
1150
-
1151
- /**
1152
- * Persist a mutated raw config object to disk and synchronize the running
1153
- * daemon (file-watcher, embedding cache, provider registry).
1154
- *
1155
- * Shared by `handlePatchConfig` and `handleSetConfig` so both write paths get
1156
- * identical post-write side effects.
1157
- */
1158
- /**
1159
- * Reject writes that would store a routing-identity provider with a missing
1160
- * or unroutable model (see `routingIdentityModelIssue`). saveRawConfig
1161
- * persists without schema validation, so the parse-time rejection alone
1162
- * would let the pair reach disk and be stripped on the next read — a silent
1163
- * no-op where the user expects a saved override.
1164
- */
1165
- function collectProviderEntries(
1166
- raw: Record<string, unknown>,
1167
- ): [string, { provider?: unknown; model?: unknown }][] {
1168
- const llm = raw.llm as
1169
- | {
1170
- default?: { provider?: unknown; model?: unknown } | null;
1171
- profiles?: Record<
1172
- string,
1173
- { provider?: unknown; model?: unknown } | null | undefined
1174
- >;
1175
- callSites?: Record<
1176
- string,
1177
- { provider?: unknown; model?: unknown } | null | undefined
1178
- >;
1179
- }
1180
- | undefined;
1181
- const entries: [string, { provider?: unknown; model?: unknown }][] = [];
1182
- // llm.default is a raw compatibility field outside the parsed schema;
1183
- // profile materialization uses the on-disk blob as its fill base, so it is
1184
- // checked like the schema-carried sections.
1185
- if (llm?.default) {
1186
- entries.push(["llm.default", llm.default]);
1187
- }
1188
- for (const section of ["profiles", "callSites"] as const) {
1189
- for (const [name, entry] of Object.entries(llm?.[section] ?? {})) {
1190
- if (entry) {
1191
- entries.push([`llm.${section}.${name}`, entry]);
1192
- }
1193
- }
1194
- }
1195
- return entries;
1196
- }
1197
-
1198
- function assertRoutableIdentityEntries(
1199
- preWrite: Record<string, unknown>,
1200
- raw: Record<string, unknown>,
1201
- ): void {
1202
- const entries = collectProviderEntries(raw);
1203
- const priorProviders = new Map(
1204
- collectProviderEntries(preWrite).flatMap(([label, entry]) =>
1205
- typeof entry.provider === "string" ? [[label, entry.provider]] : [],
1206
- ),
1207
- );
1208
- for (const [label, entry] of entries) {
1209
- if (typeof entry.provider !== "string") {
1210
- continue;
1211
- }
1212
- // The provider schema is an open string (a stored value outside the
1213
- // known set parses instead of stripping its profile), so write-time
1214
- // membership is enforced here and at the profiles route. Scoped to
1215
- // providers this write introduces or changes: a stored value outside
1216
- // the known set is readable and dispatchable by design, and
1217
- // re-validating it on every write would make all later settings saves
1218
- // fail with no in-product repair path. Profiles accept entry names;
1219
- // call-site fragments and the legacy default blob stay vendor-only
1220
- // (overrides become model-only with the entries demolition).
1221
- if (entry.provider !== priorProviders.get(label)) {
1222
- const providerIssue = label.startsWith("llm.profiles.")
1223
- ? writableProfileProviderIssue(entry.provider)
1224
- : unknownLlmProviderIssue(entry.provider);
1225
- if (providerIssue) {
1226
- throw new BadRequestError(`${providerIssue} (${label})`);
1227
- }
1228
- }
1229
- const issue = routingIdentityModelIssue(
1230
- entry.provider,
1231
- typeof entry.model === "string" ? entry.model : undefined,
1232
- );
1233
- if (issue) {
1234
- throw new BadRequestError(`${issue} (${label})`);
1235
- }
1236
- }
1237
- }
1238
-
1239
- /**
1240
- * Reject writes that would persist unsupported `fallbackProfile` metadata.
1241
- * Automatic fallbacks are code-owned for managed default profiles. The write
1242
- * paths save raw config without a full-schema parse, so this check keeps a
1243
- * custom pointer from reaching disk. It runs unconditionally so an existing
1244
- * hand-edited pointer blocks unrelated rewrites until the user clears it with
1245
- * `null`.
1246
- */
1247
- function assertCodeOwnedFallbackProfiles(raw: Record<string, unknown>): void {
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.
1254
- const issues = collectFallbackProfileIssues(
1255
- llm.data.profiles ?? undefined,
1256
- llm.data.default,
1257
- );
1258
- if (issues.length > 0) {
1259
- throw new BadRequestError(issues.map((issue) => issue.message).join(" "));
1260
- }
1261
- }
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
-
1300
684
  export async function commitConfigWrite(
1301
685
  raw: Record<string, unknown>,
1302
686
  opLabel: string,
@@ -1313,11 +697,6 @@ export async function commitConfigWrite(
1313
697
  if (!chatSettings.success) {
1314
698
  throw new BadRequestError(chatSettings.error.message);
1315
699
  }
1316
- stampManagedProfileEntries(preWrite, raw);
1317
- completeChangedCustomProfiles(preWrite, raw);
1318
- assertInvariantProfilesPreserved(preWrite, raw);
1319
- assertRoutableIdentityEntries(preWrite, raw);
1320
- assertCodeOwnedFallbackProfiles(raw);
1321
700
 
1322
701
  // Suppress the file-watcher callback for the duration of the debounce
1323
702
  // window. Without this, the ConfigWatcher detects the config.json write
@@ -1348,7 +727,7 @@ export async function commitConfigWrite(
1348
727
  invalidateConfigCache();
1349
728
  rescheduleHeartbeatIfTimezoneChanged(preWrite, raw);
1350
729
  // Reinitialize providers so the live registry reflects the new config.
1351
- // Suppress disk writes inside loadConfig() — we just wrote the raw config
730
+ // Suppress disk writes inside getConfig() — we just wrote the raw config
1352
731
  // and the first-launch seed path would overwrite it with full defaults.
1353
732
  try {
1354
733
  await withSuppressedConfigDiskWrites(async () => {
@@ -1369,16 +748,24 @@ export async function commitConfigWrite(
1369
748
  }
1370
749
 
1371
750
  async function handlePatchConfig({ body }: RouteHandlerArgs) {
1372
- const patch = JsonRecordSchema.safeParse(body);
1373
- if (!patch.success || Object.keys(patch.data).length === 0) {
751
+ // First-class body parse — the same schema the route spec declares — so a
752
+ // malformed subtree is rejected with path-qualified messages before any
753
+ // guard or merge runs.
754
+ const patch = ConfigPatchRequestSchema.safeParse(body);
755
+ if (!patch.success) {
756
+ throw new BadRequestError(
757
+ patch.error.issues
758
+ .map((issue) =>
759
+ issue.path.length > 0
760
+ ? `${issue.path.join(".")}: ${issue.message}`
761
+ : issue.message,
762
+ )
763
+ .join("; "),
764
+ );
765
+ }
766
+ if (Object.keys(patch.data).length === 0) {
1374
767
  throw new BadRequestError("Body must be a non-empty JSON object");
1375
768
  }
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
769
  // A patch is partial: merge it onto the current config so the schema
1383
770
  // parse in `handleSetConfig` always sees a complete config — untouched
1384
771
  // values ride, sparse fragments are completed by the merge base.
@@ -1399,7 +786,7 @@ async function handlePatchConfig({ body }: RouteHandlerArgs) {
1399
786
 
1400
787
  await handleSetConfig(merged);
1401
788
 
1402
- return handleGetConfig();
789
+ return getConfig();
1403
790
  }
1404
791
 
1405
792
  /**
@@ -1442,20 +829,18 @@ async function handleSetConfig(merged: AssistantConfig): Promise<void> {
1442
829
  * {@link handleSetConfig} write.
1443
830
  */
1444
831
  async function handleSetConfigByKey({ body }: RouteHandlerArgs) {
1445
- const parsedBody = JsonRecordSchema.safeParse(body);
832
+ const parsedBody = ConfigSetByKeyBodySchema.safeParse(body);
1446
833
  if (!parsedBody.success) {
1447
834
  throw new BadRequestError(
1448
835
  "Body must be a JSON object with `path` and `value`",
1449
836
  );
1450
837
  }
1451
838
  const path = parsedBody.data.path;
1452
- if (typeof path !== "string" || path.length === 0) {
1453
- throw new BadRequestError("`path` must be a non-empty string");
1454
- }
1455
839
  // `value` must be present (use explicit `null` to clear a key). Without
1456
840
  // this check, `undefined` flows into `setNestedValue` and gets dropped by
1457
841
  // `JSON.stringify` at save time, silently removing the key - which is
1458
- // distinct from the documented "set to null" semantics.
842
+ // distinct from the documented "set to null" semantics. zod cannot express
843
+ // "any value, but the key must exist", so this stays a domain check.
1459
844
  if (!Object.hasOwn(parsedBody.data, "value")) {
1460
845
  throw new BadRequestError(
1461
846
  "`value` is required (use `null` to clear a key)",
@@ -1469,39 +854,7 @@ async function handleSetConfigByKey({ body }: RouteHandlerArgs) {
1469
854
  if (path === "ingress.publicBaseUrl") {
1470
855
  dropTunnelRecordsForNewUrl(merged.ingress, value);
1471
856
  }
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
- }
1502
- }
1503
- }
1504
- setNestedValue(merged, path, writtenValue);
857
+ setNestedValue(merged, path, value);
1505
858
  scrubNulledAcpAgentLeaves(merged);
1506
859
 
1507
860
  await handleSetConfig(merged);
@@ -1568,14 +921,14 @@ async function handleReplaceInferenceProfile({
1568
921
  throw new BadRequestError(`Invalid profile fragment: ${detail}`);
1569
922
  }
1570
923
  // A managed name with no existing entry stays protected so users can't
1571
- // shadow a seeded cloud profile. An existing entry carrying a non-managed
1572
- // `source` is user-owned and remains fully editable.
924
+ // shadow a seeded cloud profile. An existing entry on a non-`cadet`
925
+ // provider is user-owned and remains fully editable.
1573
926
  const existingProfile = asMutablePlainObject(
1574
927
  getConfig().llm.profiles?.[name],
1575
928
  );
1576
929
  const isCloud =
1577
930
  MANAGED_PROFILE_NAMES.has(name) &&
1578
- (existingProfile == null || existingProfile.source === "managed");
931
+ (existingProfile == null || existingProfile.provider === "cadet");
1579
932
  if (CODE_OWNED_PROFILE_NAMES.has(name)) {
1580
933
  // A code-owned profile resolves from the catalog whatever the workspace
1581
934
  // holds, so even a status re-enable would persist a stub that never
@@ -1603,9 +956,8 @@ async function handleReplaceInferenceProfile({
1603
956
  );
1604
957
  }
1605
958
  if (isCloud) {
1606
- // Cloud profiles are daemon-seeded and read-only — the commit guard
1607
- // (`assertInvariantProfilesPreserved`) rejects every write to them
1608
- // except re-enabling a disabled profile. Enforce the same contract up
959
+ // Cloud profiles are daemon-seeded and read-only — enforce the contract
960
+ // up front: the only body a cloud PUT accepts
1609
961
  // front: the only body a cloud PUT accepts is a pure status re-enable
1610
962
  // (`{status: "active"}`, or `{status: null}` to clear back to
1611
963
  // active-by-absence). Rejecting here keeps the error message ahead of
@@ -1633,13 +985,7 @@ async function handleReplaceInferenceProfile({
1633
985
  // here against the live profile set — otherwise an invalid mix would persist
1634
986
  // and break the next full config reparse.
1635
987
  if (parsed.data.mix != null) {
1636
- const MIX_ALLOWED_KEYS = new Set([
1637
- "mix",
1638
- "label",
1639
- "description",
1640
- "status",
1641
- "source",
1642
- ]);
988
+ const MIX_ALLOWED_KEYS = new Set(["mix", "label", "description", "status"]);
1643
989
  const extraneous = Object.keys(parsed.data).filter(
1644
990
  (k) => !MIX_ALLOWED_KEYS.has(k),
1645
991
  );
@@ -1826,10 +1172,6 @@ function applyCloudProfileReenable(
1826
1172
  const nextProfile: Record<string, unknown> = {
1827
1173
  ...(asMutablePlainObject(profiles[name]) ?? {}),
1828
1174
  };
1829
- // Only reached for managed-owned names (the route's managed gate), so a
1830
- // freshly created stub must carry the cloud marker the effective view
1831
- // and write guards key on.
1832
- nextProfile.source = "managed";
1833
1175
  if (status === null) {
1834
1176
  delete nextProfile.status;
1835
1177
  } else {
@@ -2194,7 +1536,7 @@ export const ROUTES: RouteDefinition[] = [
2194
1536
  description: "Return the raw settings.json configuration object.",
2195
1537
  tags: ["config"],
2196
1538
  responseBody: ConfigGetResponseSchema,
2197
- handler: handleGetConfig,
1539
+ handler: getConfig,
2198
1540
  },
2199
1541
  {
2200
1542
  operationId: "config_patch",
@@ -2226,6 +1568,7 @@ export const ROUTES: RouteDefinition[] = [
2226
1568
  "(preserves explicit null, replaces object subtrees instead of merging). " +
2227
1569
  "Used by the `assistant config set <key> <value>` CLI command.",
2228
1570
  tags: ["config"],
1571
+ requestBody: ConfigSetByKeyBodySchema,
2229
1572
  handler: handleSetConfigByKey,
2230
1573
  },
2231
1574
  {