@vellumai/assistant 0.11.2 → 0.11.3-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (720) hide show
  1. package/ARCHITECTURE.md +58 -3
  2. package/docs/trusted-contact-access.md +9 -5
  3. package/docs/vellum-doctor.md +195 -0
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  7. package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
  8. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +19 -2
  9. package/node_modules/@vellumai/environments/package.json +2 -1
  10. package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
  11. package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
  12. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
  13. package/node_modules/@vellumai/environments/src/index.ts +17 -6
  14. package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
  15. package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
  16. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  19. package/node_modules/@vellumai/gateway-client/src/index.ts +4 -0
  20. package/node_modules/@vellumai/gateway-client/src/verification-session-contract.ts +91 -6
  21. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
  22. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
  23. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +2 -0
  24. package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
  25. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +15 -1
  26. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +17 -2
  27. package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  28. package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  29. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  30. package/node_modules/@vellumai/slack-text/src/index.ts +16 -2
  31. package/openapi.yaml +589 -30
  32. package/package.json +1 -1
  33. package/src/__tests__/agent-image-optimize.test.ts +4 -4
  34. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +113 -66
  35. package/src/__tests__/agent-loop-override-profile.test.ts +38 -48
  36. package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +166 -0
  37. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  38. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  39. package/src/__tests__/answered-question-persistence.test.ts +240 -0
  40. package/src/__tests__/anthropic-provider.test.ts +165 -30
  41. package/src/__tests__/app-control-flow.test.ts +9 -8
  42. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  43. package/src/__tests__/attachments-store-heic-normalize.test.ts +16 -12
  44. package/src/__tests__/attachments-store.test.ts +130 -68
  45. package/src/__tests__/attachments.test.ts +36 -36
  46. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  47. package/src/__tests__/call-start-guardian-guard.test.ts +18 -4
  48. package/src/__tests__/canned-reply-release.test.ts +86 -0
  49. package/src/__tests__/channel-guardian.test.ts +59 -29
  50. package/src/__tests__/channel-readiness-telegram-remote.test.ts +154 -0
  51. package/src/__tests__/channel-setup-panel-ack.test.ts +6 -5
  52. package/src/__tests__/client-os-metadata-persistence.test.ts +103 -4
  53. package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
  54. package/src/__tests__/consult-deadline.test.ts +65 -0
  55. package/src/__tests__/conversation-agent-loop.test.ts +438 -2
  56. package/src/__tests__/conversation-disk-view-integration.test.ts +5 -1
  57. package/src/__tests__/conversation-disk-view.test.ts +4 -4
  58. package/src/__tests__/conversation-error.test.ts +22 -3
  59. package/src/__tests__/conversation-fork-crud.test.ts +5 -1
  60. package/src/__tests__/conversation-fork-referential.test.ts +352 -0
  61. package/src/__tests__/conversation-fork-retrospective.test.ts +5 -1
  62. package/src/__tests__/conversation-group-tools.test.ts +32 -2
  63. package/src/__tests__/conversation-inference-profile-route.test.ts +27 -1
  64. package/src/__tests__/conversation-initial-prompt.test.ts +51 -22
  65. package/src/__tests__/conversation-key-store-bootstrap-cleanup.test.ts +119 -0
  66. package/src/__tests__/conversation-key-store-origin.test.ts +105 -0
  67. package/src/__tests__/conversation-list-recency-ordering.test.ts +104 -0
  68. package/src/__tests__/conversation-origin-at-creation.test.ts +153 -0
  69. package/src/__tests__/conversation-origin-channel-filter.test.ts +95 -0
  70. package/src/__tests__/conversation-placement-promotion.test.ts +172 -0
  71. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +123 -0
  72. package/src/__tests__/conversation-queue.test.ts +433 -1
  73. package/src/__tests__/conversation-retry-route.test.ts +3 -1
  74. package/src/__tests__/conversation-routes-hidden-queue.test.ts +64 -1
  75. package/src/__tests__/conversation-store-ephemeral.test.ts +4 -0
  76. package/src/__tests__/conversation-store.test.ts +26 -10
  77. package/src/__tests__/conversation-summarize-route.test.ts +1 -1
  78. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +15 -6
  79. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -5
  80. package/src/__tests__/conversation-surfaces-app-control.test.ts +12 -11
  81. package/src/__tests__/conversation-surfaces-app-open.test.ts +11 -5
  82. package/src/__tests__/conversation-surfaces-data-persist.test.ts +5 -4
  83. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +967 -0
  84. package/src/__tests__/conversation-surfaces-persisted-info.test.ts +312 -0
  85. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +6 -5
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -8
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +21 -11
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +7 -11
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -6
  90. package/src/__tests__/conversation-surfaces-task-progress.test.ts +11 -6
  91. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -6
  92. package/src/__tests__/conversation-tool-setup-attribution.test.ts +70 -7
  93. package/src/__tests__/conversation-wait-for-idle.test.ts +6 -11
  94. package/src/__tests__/credential-prompt-route.test.ts +57 -7
  95. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  96. package/src/__tests__/cu-unified-flow.test.ts +89 -28
  97. package/src/__tests__/custom-group-listing-visibility.test.ts +1 -1
  98. package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
  99. package/src/__tests__/discord-access-request-privacy.test.ts +366 -0
  100. package/src/__tests__/discord-requester-notice-privacy.test.ts +87 -0
  101. package/src/__tests__/disk-pressure-guard.test.ts +55 -55
  102. package/src/__tests__/disk-pressure-routes.test.ts +10 -10
  103. package/src/__tests__/disk-pressure-tools.test.ts +2 -2
  104. package/src/__tests__/disk-usage.test.ts +35 -20
  105. package/src/__tests__/document-sync-tags.test.ts +379 -0
  106. package/src/__tests__/document-update-default-surface.test.ts +28 -0
  107. package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
  108. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
  109. package/src/__tests__/ensure-conversation-exists.test.ts +8 -5
  110. package/src/__tests__/events-dev-bypass-actor.test.ts +183 -1
  111. package/src/__tests__/evict-conversations-for-reload.test.ts +90 -0
  112. package/src/__tests__/file-list-tool.test.ts +12 -10
  113. package/src/__tests__/file-ops-service.test.ts +151 -44
  114. package/src/__tests__/file-read-container-boundary.test.ts +137 -0
  115. package/src/__tests__/file-read-tool.test.ts +21 -9
  116. package/src/__tests__/file-url-path-guard.test.ts +83 -0
  117. package/src/__tests__/filesystem-tools.test.ts +82 -66
  118. package/src/__tests__/guardian-outbound-http.test.ts +8 -8
  119. package/src/__tests__/helpers/gateway-verification-sessions-stub.ts +14 -1
  120. package/src/__tests__/helpers/hub-clients.ts +42 -0
  121. package/src/__tests__/helpers/mock-conversation.ts +46 -0
  122. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +61 -57
  123. package/src/__tests__/history-repair.test.ts +112 -12
  124. package/src/__tests__/host-bash-proxy.test.ts +10 -5
  125. package/src/__tests__/host-bash-routes.test.ts +67 -0
  126. package/src/__tests__/host-browser-proxy.test.ts +2 -2
  127. package/src/__tests__/host-cu-proxy.test.ts +3 -7
  128. package/src/__tests__/host-file-proxy.test.ts +3 -7
  129. package/src/__tests__/host-file-read-tool.test.ts +5 -1
  130. package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
  131. package/src/__tests__/http-conversation-lineage.test.ts +2 -5
  132. package/src/__tests__/http-user-message-parity.test.ts +44 -0
  133. package/src/__tests__/identity-routes.test.ts +13 -13
  134. package/src/__tests__/image-conversion.test.ts +29 -26
  135. package/src/__tests__/image-source-path-reinject.test.ts +2 -2
  136. package/src/__tests__/inference-profile-session-handler.test.ts +95 -0
  137. package/src/__tests__/inference-profile-session-ipc.test.ts +25 -0
  138. package/src/__tests__/introduction-card-resolver.test.ts +6 -1
  139. package/src/__tests__/list-messages-attachments.test.ts +56 -32
  140. package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
  141. package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
  142. package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
  143. package/src/__tests__/list-messages-page-latest.test.ts +61 -61
  144. package/src/__tests__/list-messages-provider-error.test.ts +6 -6
  145. package/src/__tests__/list-messages-queued.test.ts +49 -6
  146. package/src/__tests__/list-messages-system-card.test.ts +4 -4
  147. package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
  148. package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
  149. package/src/__tests__/llm-usage-store.test.ts +55 -0
  150. package/src/__tests__/manual-token-reconciliation.test.ts +45 -0
  151. package/src/__tests__/mcp-tool-annotations-risk.test.ts +133 -0
  152. package/src/__tests__/media-stream-server-integration.test.ts +36 -0
  153. package/src/__tests__/migration-export-http.test.ts +1 -1
  154. package/src/__tests__/migration-import-commit-http.test.ts +1 -1
  155. package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
  156. package/src/__tests__/migration-validate-http.test.ts +1 -1
  157. package/src/__tests__/mtime-cache.test.ts +65 -3
  158. package/src/__tests__/notification-platform-adapter.test.ts +104 -0
  159. package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
  160. package/src/__tests__/oauth-provider-profiles.test.ts +4 -2
  161. package/src/__tests__/oauth-provider-seed-logos.test.ts +65 -0
  162. package/src/__tests__/oauth-provider-serializer.test.ts +17 -0
  163. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  164. package/src/__tests__/openai-responses-prompt-cache.test.ts +5 -2
  165. package/src/__tests__/path-policy.test.ts +164 -1
  166. package/src/__tests__/persist-media-references.test.ts +2 -2
  167. package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
  168. package/src/__tests__/persist-unsendable-image.test.ts +23 -23
  169. package/src/__tests__/persona-resolver.test.ts +154 -0
  170. package/src/__tests__/plugin-api-webhook-url.test.ts +170 -0
  171. package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
  172. package/src/__tests__/processing-flag-persist-failure.test.ts +15 -10
  173. package/src/__tests__/profile-availability-incomplete.test.ts +113 -0
  174. package/src/__tests__/profiler-routes.test.ts +13 -13
  175. package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +593 -0
  176. package/src/__tests__/proxy-approval-callback.test.ts +3 -3
  177. package/src/__tests__/queued-message-cancel-and-steer.test.ts +189 -0
  178. package/src/__tests__/queued-message-steer-actor-scoping.test.ts +164 -0
  179. package/src/__tests__/resolve-trust-class.test.ts +20 -0
  180. package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -1
  181. package/src/__tests__/run-due-schedules.test.ts +174 -0
  182. package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
  183. package/src/__tests__/scaffold-managed-skill-tool.test.ts +166 -0
  184. package/src/__tests__/schedule-routes.test.ts +730 -8
  185. package/src/__tests__/schedule-store.test.ts +540 -0
  186. package/src/__tests__/schedule-tools.test.ts +103 -0
  187. package/src/__tests__/scheduler-wake.test.ts +5 -0
  188. package/src/__tests__/secret-routes-platform-proxy.test.ts +29 -1
  189. package/src/__tests__/secure-keys.test.ts +91 -0
  190. package/src/__tests__/server-history-render.test.ts +97 -7
  191. package/src/__tests__/size-guard.test.ts +8 -8
  192. package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
  193. package/src/__tests__/skill-tool-factory.test.ts +247 -0
  194. package/src/__tests__/skills-install-staging.test.ts +15 -6
  195. package/src/__tests__/slack-edit-ordering-characterization.test.ts +103 -0
  196. package/src/__tests__/slack-inbound-verification.test.ts +4 -1
  197. package/src/__tests__/slack-mention-binding-invariance.test.ts +103 -0
  198. package/src/__tests__/slack-mention-provider-leak-audit.test.ts +135 -0
  199. package/src/__tests__/starter-task-flow.test.ts +5 -4
  200. package/src/__tests__/steer-on-enqueue-question.test.ts +3 -2
  201. package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
  202. package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
  203. package/src/__tests__/subagent-cron-run-attribution.test.ts +301 -0
  204. package/src/__tests__/subagent-disposal.test.ts +10 -0
  205. package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
  206. package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
  207. package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
  208. package/src/__tests__/subagent-manager-notify.test.ts +148 -0
  209. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  210. package/src/__tests__/subagent-persistence.test.ts +141 -0
  211. package/src/__tests__/subagent-role-registry.test.ts +172 -63
  212. package/src/__tests__/subagent-role-resolution.test.ts +88 -0
  213. package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
  214. package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
  215. package/src/__tests__/subagent-terminal-message.test.ts +88 -1
  216. package/src/__tests__/subagent-tool-gate-mode.test.ts +225 -10
  217. package/src/__tests__/subagent-tools.test.ts +1901 -108
  218. package/src/__tests__/surface-completion-compaction-boundary.test.ts +124 -0
  219. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +298 -0
  220. package/src/__tests__/system-prompt.test.ts +82 -7
  221. package/src/__tests__/tool-grant-request-escalation.test.ts +40 -1
  222. package/src/__tests__/tool-result-spool.test.ts +74 -18
  223. package/src/__tests__/tool-side-effects-documents.test.ts +174 -0
  224. package/src/__tests__/tool-side-effects-hook-failures.test.ts +129 -0
  225. package/src/__tests__/tools-audio-read.test.ts +10 -10
  226. package/src/__tests__/tools-get-route.test.ts +85 -12
  227. package/src/__tests__/tools-list-cli.test.ts +171 -0
  228. package/src/__tests__/top-level-renderer.test.ts +54 -1
  229. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +68 -1
  230. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -1
  231. package/src/__tests__/turn-tail-chain.test.ts +135 -0
  232. package/src/__tests__/ui-choice-copy-surfaces.test.ts +6 -4
  233. package/src/__tests__/ui-visual-surface.test.ts +5 -4
  234. package/src/__tests__/ui-work-result-surface.test.ts +5 -4
  235. package/src/__tests__/unified-turn-context-visible-app.test.ts +7 -8
  236. package/src/__tests__/usage-routes.test.ts +62 -0
  237. package/src/__tests__/verification-outbound-delivery.test.ts +176 -0
  238. package/src/__tests__/visible-app-context.test.ts +20 -4
  239. package/src/__tests__/web-search-history.test.ts +96 -0
  240. package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +6 -6
  241. package/src/__tests__/workspace-migration-140-repair-seed-pinned-memory-v3-live.test.ts +330 -0
  242. package/src/acp/failure-error.ts +3 -5
  243. package/src/agent/attachments.ts +26 -24
  244. package/src/agent/history-repair/history-repair.ts +27 -26
  245. package/src/agent/image-optimize.ts +6 -6
  246. package/src/agent/loop.ts +40 -22
  247. package/src/agent/message-types.ts +3 -3
  248. package/src/api/constants/document-tools.ts +55 -0
  249. package/src/api/events/question-answered.ts +53 -0
  250. package/src/api/events/tool-result.ts +7 -0
  251. package/src/api/index.ts +13 -0
  252. package/src/api/responses/conversation-message.ts +11 -0
  253. package/src/api/surfaces-oauth-connect.test.ts +96 -0
  254. package/src/api/surfaces.ts +36 -8
  255. package/src/approvals/guardian-channel-delivery.ts +83 -0
  256. package/src/approvals/guardian-expiry-notifier.ts +13 -11
  257. package/src/approvals/guardian-request-resolvers.ts +222 -135
  258. package/src/calls/__tests__/voice-session-bridge.test.ts +50 -0
  259. package/src/calls/media-stream-server.ts +9 -1
  260. package/src/calls/voice-session-bridge.ts +38 -0
  261. package/src/channels/__tests__/gateway-verification-sessions.test.ts +3 -3
  262. package/src/channels/config.ts +7 -2
  263. package/src/channels/gateway-verification-sessions.ts +7 -2
  264. package/src/channels/types.ts +7 -1
  265. package/src/cli/commands/__tests__/cache.test.ts +7 -4
  266. package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
  267. package/src/cli/commands/__tests__/conversations-wake.test.ts +51 -6
  268. package/src/cli/commands/__tests__/inference-models.test.ts +14 -0
  269. package/src/cli/commands/__tests__/inference-profiles.test.ts +44 -1
  270. package/src/cli/commands/__tests__/inference-providers.test.ts +7 -0
  271. package/src/cli/commands/__tests__/plugins.test.ts +692 -0
  272. package/src/cli/commands/__tests__/schedules.test.ts +136 -37
  273. package/src/cli/commands/conversations.ts +7 -3
  274. package/src/cli/commands/credentials.help.ts +7 -0
  275. package/src/cli/commands/inference-profiles.ts +9 -3
  276. package/src/cli/commands/inference.help.ts +16 -2
  277. package/src/cli/commands/mcp.help.ts +4 -2
  278. package/src/cli/commands/memory/memory-retrospective.ts +18 -0
  279. package/src/cli/commands/notifications.ts +3 -6
  280. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +16 -0
  281. package/src/cli/commands/oauth/connect-surface-guidance.ts +25 -7
  282. package/src/cli/commands/oauth/connect.test.ts +26 -1
  283. package/src/cli/commands/oauth/connect.ts +4 -1
  284. package/src/cli/commands/oauth/index.help.ts +3 -1
  285. package/src/cli/commands/oauth/providers.ts +3 -0
  286. package/src/cli/commands/plugins.help.ts +25 -2
  287. package/src/cli/commands/plugins.ts +289 -10
  288. package/src/cli/commands/schedules.help.ts +14 -12
  289. package/src/cli/commands/schedules.ts +19 -5
  290. package/src/cli/commands/tools.help.ts +4 -2
  291. package/src/cli/commands/tools.ts +38 -0
  292. package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
  293. package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
  294. package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
  295. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
  296. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
  297. package/src/cli/lib/install-from-github.ts +61 -0
  298. package/src/cli/lib/install-from-platform.ts +13 -2
  299. package/src/cli/lib/plugin-surfaces.ts +124 -4
  300. package/src/cli/lib/upgrade-plugin.ts +18 -0
  301. package/src/cli.ts +1 -1
  302. package/src/config/__tests__/deployment-context-defaults.test.ts +73 -0
  303. package/src/config/__tests__/memory-retrospective-schema.test.ts +40 -0
  304. package/src/config/__tests__/plugin-updates-schema.test.ts +43 -0
  305. package/src/config/__tests__/profile-tool-support.test.ts +131 -0
  306. package/src/config/bundled-skills/AGENTS.md +1 -1
  307. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  308. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +3 -3
  309. package/src/config/bundled-skills/schedule/SKILL.md +5 -1
  310. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  311. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  312. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
  313. package/src/config/bundled-skills/subagent/SKILL.md +53 -22
  314. package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
  315. package/src/config/bundled-skills/visualize/SKILL.md +1 -1
  316. package/src/config/default-profile-catalog.ts +6 -2
  317. package/src/config/feature-flag-registry.json +25 -17
  318. package/src/config/llm-resolver.ts +45 -15
  319. package/src/config/loader.ts +70 -1
  320. package/src/config/profile-tool-support.ts +110 -0
  321. package/src/config/sanitize-for-transfer.ts +11 -0
  322. package/src/config/schema.ts +4 -0
  323. package/src/config/schemas/__tests__/stt.test.ts +20 -5
  324. package/src/config/schemas/channels.ts +13 -0
  325. package/src/config/schemas/memory-retrospective.ts +25 -0
  326. package/src/config/schemas/plugin-updates.ts +55 -0
  327. package/src/config/schemas/services.ts +8 -4
  328. package/src/config/schemas/stt.ts +17 -5
  329. package/src/config/schemas/timeouts.ts +1 -1
  330. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +16 -0
  331. package/src/contacts/guardian-delivery-reader.ts +29 -2
  332. package/src/context/compactor.ts +4 -4
  333. package/src/context/post-turn-tool-result-truncation.ts +32 -16
  334. package/src/context/strip-injections.ts +42 -0
  335. package/src/context/tool-result-spool.ts +32 -18
  336. package/src/credential-execution/ces-runtime.ts +3 -0
  337. package/src/credential-execution/client.ts +22 -1
  338. package/src/credential-execution/process-manager.test.ts +99 -0
  339. package/src/credential-execution/process-manager.ts +62 -16
  340. package/src/daemon/__tests__/abort-null-controller.test.ts +141 -5
  341. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +8 -12
  342. package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
  343. package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +2 -4
  344. package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +20 -29
  345. package/src/daemon/channel-ui-capability.ts +4 -4
  346. package/src/daemon/conversation-agent-loop-handlers.ts +117 -61
  347. package/src/daemon/conversation-agent-loop.ts +318 -148
  348. package/src/daemon/conversation-attachments.ts +1 -1
  349. package/src/daemon/conversation-error.ts +8 -1
  350. package/src/daemon/conversation-initial-prompt.ts +16 -38
  351. package/src/daemon/conversation-lifecycle.ts +154 -32
  352. package/src/daemon/conversation-messaging.ts +106 -43
  353. package/src/daemon/conversation-process.ts +131 -33
  354. package/src/daemon/conversation-queue-manager.ts +10 -0
  355. package/src/daemon/conversation-runtime-assembly.ts +9 -4
  356. package/src/daemon/conversation-store.ts +52 -3
  357. package/src/daemon/conversation-surface-state.ts +9 -0
  358. package/src/daemon/conversation-surfaces.ts +403 -210
  359. package/src/daemon/conversation-tool-setup.ts +56 -7
  360. package/src/daemon/conversation-turn-finalize.ts +134 -89
  361. package/src/daemon/conversation.ts +111 -27
  362. package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
  363. package/src/daemon/disk-pressure-guard.ts +3 -3
  364. package/src/daemon/doordash-steps.ts +5 -5
  365. package/src/daemon/external-plugins-bootstrap.ts +7 -1
  366. package/src/daemon/handlers/config-channels.ts +48 -5
  367. package/src/daemon/handlers/conversation-history.ts +1 -1
  368. package/src/daemon/handlers/conversations.ts +62 -17
  369. package/src/daemon/handlers/shared.ts +12 -0
  370. package/src/daemon/handlers/skills.ts +11 -1
  371. package/src/daemon/identity-helpers.ts +38 -10
  372. package/src/daemon/install-assistant-command.ts +322 -0
  373. package/src/daemon/lifecycle.ts +14 -3
  374. package/src/daemon/message-types/conversations.ts +6 -7
  375. package/src/daemon/message-types/sync.ts +1 -0
  376. package/src/daemon/persist-media-references.ts +23 -14
  377. package/src/daemon/process-message.ts +6 -6
  378. package/src/daemon/tool-setup-types.ts +19 -122
  379. package/src/daemon/tool-side-effects.ts +17 -94
  380. package/src/daemon/trust-context-types.ts +39 -0
  381. package/src/daemon/trust-context.ts +14 -6
  382. package/src/daemon/turn-tail-chain.ts +73 -0
  383. package/src/daemon/web-search-history.ts +47 -33
  384. package/src/heartbeat/heartbeat-service.ts +6 -6
  385. package/src/ipc/__tests__/socket-path.test.ts +21 -0
  386. package/src/ipc/assistant-server.ts +17 -10
  387. package/src/ipc/cli-client.ts +10 -1
  388. package/src/ipc/routes/__tests__/documents-sync-ipc-routes.test.ts +56 -0
  389. package/src/ipc/routes/documents-sync-ipc-routes.ts +42 -0
  390. package/src/ipc/socket-cleanup.ts +9 -7
  391. package/src/ipc/socket-path.ts +4 -96
  392. package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
  393. package/src/live-voice/__tests__/live-voice-attach-image.test.ts +209 -0
  394. package/src/live-voice/__tests__/live-voice-connection.test.ts +20 -0
  395. package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
  396. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +265 -0
  397. package/src/live-voice/__tests__/live-voice-stt.test.ts +6 -1
  398. package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
  399. package/src/live-voice/__tests__/protocol.test.ts +52 -0
  400. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +3 -0
  401. package/src/live-voice/live-voice-archive.ts +13 -13
  402. package/src/live-voice/live-voice-connection.ts +7 -1
  403. package/src/live-voice/live-voice-photo.ts +169 -0
  404. package/src/live-voice/live-voice-session.ts +108 -4
  405. package/src/live-voice/protocol.ts +89 -1
  406. package/src/mcp/client.ts +10 -0
  407. package/src/messaging/providers/__tests__/callback-routing.test.ts +5 -1
  408. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +66 -4
  409. package/src/messaging/providers/callback-routing.ts +1 -0
  410. package/src/messaging/providers/discord/api.ts +312 -0
  411. package/src/messaging/providers/discord/dm-delivery.test.ts +204 -0
  412. package/src/messaging/providers/discord/render.test.ts +204 -0
  413. package/src/messaging/providers/discord/render.ts +195 -0
  414. package/src/messaging/providers/discord/send.test.ts +179 -0
  415. package/src/messaging/providers/discord/send.ts +212 -0
  416. package/src/messaging/providers/discord/transport.ts +81 -0
  417. package/src/messaging/providers/index.ts +3 -1
  418. package/src/messaging/providers/retry-policy.test.ts +247 -0
  419. package/src/messaging/providers/retry-policy.ts +206 -0
  420. package/src/messaging/providers/slack/mention-source.test.ts +285 -0
  421. package/src/messaging/providers/slack/mention-source.ts +287 -0
  422. package/src/messaging/providers/telegram-bot/api.ts +47 -130
  423. package/src/messaging/providers/whatsapp/api.ts +47 -136
  424. package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
  425. package/src/monitoring/__tests__/plugin-auto-update.test.ts +332 -0
  426. package/src/monitoring/db-integrity-sample.ts +6 -1
  427. package/src/monitoring/plugin-auto-update.ts +359 -0
  428. package/src/monitoring/resource-sampler.ts +21 -5
  429. package/src/monitoring/worker.ts +13 -0
  430. package/src/notifications/__tests__/assistant-reply-producer.test.ts +375 -2
  431. package/src/notifications/__tests__/broadcaster.test.ts +37 -1
  432. package/src/notifications/__tests__/copy-composer.test.ts +165 -0
  433. package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
  434. package/src/notifications/__tests__/notification-utils.test.ts +249 -0
  435. package/src/notifications/adapters/platform.ts +31 -2
  436. package/src/notifications/assistant-reply-producer.ts +79 -32
  437. package/src/notifications/broadcaster.ts +30 -10
  438. package/src/notifications/copy-composer.ts +69 -0
  439. package/src/notifications/emit-signal.ts +82 -15
  440. package/src/notifications/events-store.ts +10 -2
  441. package/src/notifications/home-feed-side-effect.ts +6 -3
  442. package/src/notifications/notification-utils.ts +173 -5
  443. package/src/notifications/signal.ts +12 -0
  444. package/src/oauth/AGENTS.md +4 -2
  445. package/src/oauth/byo-connection.ts +6 -1
  446. package/src/oauth/credential-token-resolver.ts +3 -10
  447. package/src/oauth/manual-token-connection.ts +29 -64
  448. package/src/oauth/manual-token-providers.ts +62 -0
  449. package/src/oauth/provider-serializer.ts +13 -0
  450. package/src/oauth/seed-providers.ts +62 -11
  451. package/src/onboarding/onboarding-events-store.ts +91 -1
  452. package/src/permissions/question-prompter.test.ts +4 -0
  453. package/src/permissions/question-prompter.ts +21 -4
  454. package/src/persistence/__tests__/conversation-lineage.test.ts +248 -0
  455. package/src/persistence/attachments-store.ts +15 -15
  456. package/src/persistence/conversation-attention-store.ts +30 -10
  457. package/src/persistence/conversation-bootstrap.ts +17 -0
  458. package/src/persistence/conversation-crud.ts +363 -97
  459. package/src/persistence/conversation-group-migration.ts +1 -1
  460. package/src/persistence/conversation-key-store.ts +38 -10
  461. package/src/persistence/conversation-lineage.ts +226 -0
  462. package/src/persistence/conversation-plugin-facade.ts +28 -3
  463. package/src/persistence/conversation-queries.ts +207 -33
  464. package/src/persistence/conversation-types.ts +107 -1
  465. package/src/persistence/jobs-store.ts +32 -8
  466. package/src/persistence/llm-usage-store.ts +22 -0
  467. package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +168 -0
  468. package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +76 -0
  469. package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
  470. package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
  471. package/src/persistence/migrations/363-backfill-schedule-inference-profile.test.ts +229 -0
  472. package/src/persistence/migrations/363-backfill-schedule-inference-profile.ts +127 -0
  473. package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
  474. package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
  475. package/src/persistence/migrations/365-add-conversation-fork-strategy.ts +27 -0
  476. package/src/persistence/schema/conversations.ts +32 -0
  477. package/src/persistence/schema/infrastructure.ts +3 -0
  478. package/src/persistence/steps.ts +30 -0
  479. package/src/persistence/subagent-store.ts +148 -6
  480. package/src/platform/managed-speech.test.ts +20 -0
  481. package/src/platform/managed-speech.ts +9 -0
  482. package/src/plugin-api/index.ts +8 -0
  483. package/src/plugin-api/webhook-url.ts +151 -0
  484. package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
  485. package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
  486. package/src/plugins/collect-source-versions.ts +4 -27
  487. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
  488. package/src/plugins/defaults/exploration-drift/package.json +1 -1
  489. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
  490. package/src/plugins/defaults/image-recovery/recover.ts +67 -57
  491. package/src/plugins/defaults/index.ts +1 -1
  492. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +64 -18
  493. package/src/plugins/defaults/memory/__tests__/jobs-worker-outcome-contract.test.ts +242 -0
  494. package/src/plugins/defaults/memory/__tests__/jobs-worker-retrospective-sweep.test.ts +24 -1
  495. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +48 -3
  496. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +511 -19
  497. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +39 -3
  498. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +493 -0
  499. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +5 -4
  500. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -1
  501. package/src/plugins/defaults/memory/job-handlers.ts +87 -5
  502. package/src/plugins/defaults/memory/jobs-worker.ts +60 -6
  503. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +11 -0
  504. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +29 -0
  505. package/src/plugins/defaults/memory/memory-retrospective-job.ts +342 -60
  506. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +23 -3
  507. package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +9 -4
  508. package/src/plugins/defaults/memory/substrate/sweep-job.ts +7 -0
  509. package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
  510. package/src/plugins/defaults/memory/v2/router.ts +8 -0
  511. package/src/plugins/defaults/turn-context/unified-turn-context.ts +1 -2
  512. package/src/plugins/external-plugin-loader.ts +17 -8
  513. package/src/plugins/installed-plugin-dirs.ts +101 -0
  514. package/src/plugins/mtime-cache.ts +34 -22
  515. package/src/plugins/types.ts +69 -8
  516. package/src/prompts/persona-resolver.ts +6 -2
  517. package/src/prompts/system-prompt.ts +72 -17
  518. package/src/providers/__tests__/inference.test.ts +40 -0
  519. package/src/providers/__tests__/preflight-resolved-config.test.ts +58 -1
  520. package/src/providers/__tests__/retry-callsite.test.ts +67 -0
  521. package/src/providers/__tests__/vellum-connection-routing.test.ts +42 -11
  522. package/src/providers/anthropic/client.ts +112 -95
  523. package/src/providers/connection-resolution.ts +35 -1
  524. package/src/providers/gemini/client.ts +5 -5
  525. package/src/providers/inference/adapter-factory.ts +16 -10
  526. package/src/providers/inference/auth.ts +16 -0
  527. package/src/providers/inference/connection-availability.ts +164 -12
  528. package/src/providers/inference/connections.ts +25 -1
  529. package/src/providers/media-resolve.ts +21 -11
  530. package/src/providers/model-catalog.ts +15 -0
  531. package/src/providers/openai/chat-completions-provider.ts +4 -4
  532. package/src/providers/openai/responses-provider.ts +12 -25
  533. package/src/providers/registry.ts +9 -4
  534. package/src/providers/retry.ts +25 -0
  535. package/src/providers/server-tool-pairing.ts +134 -0
  536. package/src/providers/speech-to-text/__tests__/resolve.test.ts +187 -6
  537. package/src/providers/speech-to-text/deepgram.ts +1 -0
  538. package/src/providers/speech-to-text/resolve.ts +80 -10
  539. package/src/providers/speech-to-text/vellum-managed.ts +7 -1
  540. package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
  541. package/src/providers/types.ts +26 -6
  542. package/src/providers/vellum-model-routing.ts +6 -7
  543. package/src/runtime/AGENTS.md +2 -0
  544. package/src/runtime/__tests__/agent-wake.test.ts +16 -16
  545. package/src/runtime/__tests__/background-job-runner.test.ts +0 -144
  546. package/src/runtime/__tests__/desktop-presence.test.ts +162 -0
  547. package/src/runtime/agent-wake.ts +63 -24
  548. package/src/runtime/assistant-event-hub.ts +87 -12
  549. package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
  550. package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
  551. package/src/runtime/auth/same-actor.ts +56 -8
  552. package/src/runtime/auth/scopes.ts +12 -0
  553. package/src/runtime/auth/types.ts +2 -0
  554. package/src/runtime/background-job-runner.ts +5 -27
  555. package/src/runtime/channel-readiness-service.ts +59 -3
  556. package/src/runtime/channel-readiness-types.ts +18 -0
  557. package/src/runtime/desktop-presence.ts +69 -0
  558. package/src/runtime/http-server.ts +12 -0
  559. package/src/runtime/local-actor-identity.ts +11 -9
  560. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  561. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  562. package/src/runtime/routes/__tests__/client-routes.test.ts +186 -14
  563. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +547 -1
  564. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +162 -7
  565. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
  566. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +340 -3
  567. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +28 -1
  568. package/src/runtime/routes/__tests__/plugins-routes.test.ts +173 -1
  569. package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
  570. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +20 -0
  571. package/src/runtime/routes/__tests__/skill-history-route.test.ts +116 -0
  572. package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +60 -2
  573. package/src/runtime/routes/archive-utils.ts +16 -12
  574. package/src/runtime/routes/attachment-routes.ts +13 -6
  575. package/src/runtime/routes/canned-reply-release.ts +54 -0
  576. package/src/runtime/routes/channel-verification-routes.ts +2 -27
  577. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +5 -0
  578. package/src/runtime/routes/client-routes.ts +114 -2
  579. package/src/runtime/routes/conversation-cli-routes.ts +10 -12
  580. package/src/runtime/routes/conversation-list-routes.ts +59 -10
  581. package/src/runtime/routes/conversation-management-routes.ts +47 -18
  582. package/src/runtime/routes/conversation-query-routes.ts +32 -53
  583. package/src/runtime/routes/conversation-routes.ts +360 -293
  584. package/src/runtime/routes/credential-prompt-routes.ts +23 -2
  585. package/src/runtime/routes/debug-routes.ts +4 -1
  586. package/src/runtime/routes/default-provider-routes.ts +7 -11
  587. package/src/runtime/routes/documents-routes.ts +44 -1
  588. package/src/runtime/routes/events-routes.ts +28 -1
  589. package/src/runtime/routes/host-app-control-routes.ts +2 -0
  590. package/src/runtime/routes/host-bash-routes.ts +2 -0
  591. package/src/runtime/routes/host-browser-routes.ts +2 -0
  592. package/src/runtime/routes/host-cu-routes.ts +2 -0
  593. package/src/runtime/routes/host-file-routes.ts +2 -0
  594. package/src/runtime/routes/host-transfer-routes.ts +4 -0
  595. package/src/runtime/routes/identity-routes.ts +6 -6
  596. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  597. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -3
  598. package/src/runtime/routes/inbound-stages/background-dispatch.ts +14 -0
  599. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -2
  600. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +14 -11
  601. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -17
  602. package/src/runtime/routes/inference-profile-availability-guard.ts +171 -0
  603. package/src/runtime/routes/inference-profile-session-handler.ts +26 -2
  604. package/src/runtime/routes/inference-profile-session-routes.ts +2 -1
  605. package/src/runtime/routes/inference-profiles-routes.ts +152 -73
  606. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -16
  607. package/src/runtime/routes/llm-call-sites-routes.ts +13 -0
  608. package/src/runtime/routes/log-export-routes.ts +1 -1
  609. package/src/runtime/routes/notification-routes.ts +0 -14
  610. package/src/runtime/routes/oauth-providers.ts +1 -0
  611. package/src/runtime/routes/plugins-routes.ts +106 -1
  612. package/src/runtime/routes/profiler-routes.ts +4 -2
  613. package/src/runtime/routes/question-routes.ts +28 -10
  614. package/src/runtime/routes/retrospective-routes.ts +9 -3
  615. package/src/runtime/routes/schedule-routes.ts +210 -12
  616. package/src/runtime/routes/secret-routes.ts +40 -23
  617. package/src/runtime/routes/settings-routes.ts +41 -13
  618. package/src/runtime/routes/skills-routes.ts +79 -0
  619. package/src/runtime/routes/ui-snapshot-routes.ts +1 -0
  620. package/src/runtime/routes/wake-conversation-routes.ts +23 -0
  621. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  622. package/src/runtime/services/conversation-serializer.ts +24 -9
  623. package/src/runtime/sync/documents-sidecar-publish.test.ts +101 -0
  624. package/src/runtime/sync/resource-sync-events.ts +79 -1
  625. package/src/runtime/sync/worker-daemon-notify.test.ts +34 -0
  626. package/src/runtime/sync/worker-daemon-notify.ts +44 -6
  627. package/src/runtime/verification-outbound-actions.ts +250 -222
  628. package/src/runtime/verification-templates.ts +41 -30
  629. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
  630. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
  631. package/src/schedule/inference-profile.ts +64 -3
  632. package/src/schedule/plugin-schedule-declarations.ts +518 -0
  633. package/src/schedule/plugin-schedule-reconciler.ts +605 -0
  634. package/src/schedule/plugin-schedules-gate.ts +23 -0
  635. package/src/schedule/schedule-store.ts +520 -26
  636. package/src/schedule/scheduler.ts +38 -0
  637. package/src/security/credential-backend.ts +51 -4
  638. package/src/security/encrypted-store.ts +64 -42
  639. package/src/security/secure-keys.ts +55 -7
  640. package/src/skills/catalog-install.ts +20 -7
  641. package/src/skills/inline-command-runner.ts +2 -7
  642. package/src/skills/skill-history.test.ts +269 -0
  643. package/src/skills/skill-history.ts +250 -0
  644. package/src/skills/skillssh-registry.ts +1 -1
  645. package/src/stt/daemon-batch-transcriber.ts +8 -6
  646. package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
  647. package/src/subagent/consult-prompt.ts +3 -1
  648. package/src/subagent/consult-transcript.ts +1 -1
  649. package/src/subagent/manager.ts +304 -44
  650. package/src/subagent/progress-events.ts +35 -0
  651. package/src/subagent/role-resolution.ts +88 -0
  652. package/src/subagent/types.ts +302 -67
  653. package/src/subagent/validate-allowlists.ts +3 -4
  654. package/src/telegram/__tests__/webhook-health.test.ts +73 -1
  655. package/src/telegram/webhook-health.ts +101 -3
  656. package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
  657. package/src/telemetry/activation-funnel.ts +6 -1
  658. package/src/telemetry/live-voice-funnel.ts +75 -0
  659. package/src/telemetry/telemetry-event-sources.ts +6 -0
  660. package/src/telemetry/telemetry-wire-source.json +1 -1
  661. package/src/telemetry/telemetry-wire.generated.ts +2 -0
  662. package/src/telemetry/types.ts +23 -4
  663. package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
  664. package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
  665. package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
  666. package/src/tools/ask-question/ask-question-tool.ts +56 -2
  667. package/src/tools/browser/browser-execution.ts +1 -1
  668. package/src/tools/calls/call-start.ts +8 -5
  669. package/src/tools/conversation-groups/move_to_group.ts +10 -12
  670. package/src/tools/document/document-tool.ts +11 -4
  671. package/src/tools/execution-timeout.ts +9 -2
  672. package/src/tools/executor.ts +7 -6
  673. package/src/tools/filesystem/edit.ts +1 -1
  674. package/src/tools/filesystem/list.ts +1 -1
  675. package/src/tools/filesystem/read.ts +7 -13
  676. package/src/tools/filesystem/search.ts +23 -21
  677. package/src/tools/filesystem/write.ts +4 -1
  678. package/src/tools/host-filesystem/edit.ts +7 -2
  679. package/src/tools/host-filesystem/read.ts +21 -8
  680. package/src/tools/host-filesystem/write.ts +10 -2
  681. package/src/tools/host-terminal/host-shell.ts +2 -2
  682. package/src/tools/mcp/mcp-tool-factory.ts +25 -1
  683. package/src/tools/permission-checker.ts +8 -3
  684. package/src/tools/schedule/delete.ts +13 -1
  685. package/src/tools/schedule/list.ts +14 -3
  686. package/src/tools/schedule/update.ts +56 -33
  687. package/src/tools/shared/filesystem/audio-read.ts +6 -4
  688. package/src/tools/shared/filesystem/file-ops-service.ts +176 -116
  689. package/src/tools/shared/filesystem/image-read.ts +12 -12
  690. package/src/tools/shared/filesystem/path-policy.ts +70 -5
  691. package/src/tools/shared/filesystem/size-guard.ts +6 -6
  692. package/src/tools/shared/input-misuse.ts +72 -0
  693. package/src/tools/skills/scaffold-managed.ts +82 -0
  694. package/src/tools/skills/skill-tool-factory.ts +14 -1
  695. package/src/tools/subagent/consult-deadline.ts +12 -7
  696. package/src/tools/subagent/message.ts +3 -1
  697. package/src/tools/subagent/read.ts +66 -4
  698. package/src/tools/subagent/spawn.ts +500 -78
  699. package/src/tools/tool-approval-handler.ts +45 -4
  700. package/src/tools/types.ts +22 -3
  701. package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
  702. package/src/tools/ui-surface/visual-validation.ts +2 -0
  703. package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
  704. package/src/usage/subagent-attribution.ts +108 -0
  705. package/src/util/__tests__/sqlite-retry.test.ts +112 -0
  706. package/src/util/abort-reasons.ts +16 -0
  707. package/src/util/ansi.ts +44 -0
  708. package/src/util/disk-usage.ts +17 -13
  709. package/src/util/image-conversion.ts +20 -13
  710. package/src/util/schedule-source-key.ts +23 -0
  711. package/src/util/worker-process.ts +7 -1
  712. package/src/workspace/git-service.ts +33 -9
  713. package/src/workspace/migrations/140-repair-seed-pinned-memory-v3-live.ts +260 -0
  714. package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +111 -0
  715. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +126 -0
  716. package/src/workspace/migrations/registry.ts +4 -0
  717. package/src/workspace/top-level-renderer.ts +15 -2
  718. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -357
  719. package/src/daemon/install-symlink.ts +0 -207
  720. package/src/notifications/deferred-emit.ts +0 -147
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Write-time availability guard for inference-profile selections.
3
+ *
4
+ * The profile write routes (create, update, set-active, conversation-scoped
5
+ * pin) refuse to persist a selection that provably cannot dispatch. A profile
6
+ * naming a valid provider id with no connection behind it parses fine and
7
+ * writes fine, but the next chat turn fails with "No provider connection is
8
+ * configured" — a lockout the user has to unwind by hand. Judging the entry
9
+ * the way dispatch will, at write time, turns that into a rejected write with
10
+ * a repair path.
11
+ *
12
+ * This module owns the guard's copy so the four write sites word it
13
+ * identically.
14
+ */
15
+
16
+ import { ROUTING_IDENTITY_PROVIDERS } from "../../providers/inference/auth.js";
17
+ import type { ConnectionAvailability } from "../../providers/inference/connection-availability.js";
18
+ import { vellumConnectionAvailability } from "../../providers/inference/connection-availability.js";
19
+ import { getManagedUpstream } from "../../providers/vellum-model-routing.js";
20
+
21
+ /**
22
+ * How the caller should repair the selection. `create`/`update` describe an
23
+ * in-flight write; `repoint` describes an already-persisted profile that some
24
+ * other write (set-active, session pin) is trying to select.
25
+ */
26
+ export type ProfileRepairHint =
27
+ | { kind: "create" }
28
+ | { kind: "update" }
29
+ | { kind: "repoint"; profileName: string };
30
+
31
+ /**
32
+ * Shell-quote a value interpolated into a generated command. Profile names
33
+ * (and custom-endpoint model ids) are arbitrary strings, and the generated
34
+ * commands are explicitly meant to be run — an unquoted name with whitespace
35
+ * or metacharacters would break, or worse, execute as extra shell words.
36
+ * Values made only of conventional id characters pass through unquoted so the
37
+ * common case stays readable.
38
+ */
39
+ function shellQuote(value: string): string {
40
+ if (/^[A-Za-z0-9./:_-]+$/.test(value)) {
41
+ return value;
42
+ }
43
+ return `'${value.replaceAll("'", `'\\''`)}'`;
44
+ }
45
+
46
+ /**
47
+ * Command that proves a profile actually dispatches. Surfaced in write results
48
+ * and in guard errors — `--json` callers (agents) never see the CLI's
49
+ * human-mode hint, so the nudge has to travel in the payload.
50
+ */
51
+ export function verifyProfileCommand(name: string): string {
52
+ return `assistant inference send --profile ${shellQuote(name)} "Reply with OK"`;
53
+ }
54
+
55
+ function managedAlternative(model: string, repair: ProfileRepairHint): string {
56
+ const served =
57
+ "this model is served by your signed-in Vellum-managed route (no API key needed)";
58
+ const quotedModel = shellQuote(model);
59
+ switch (repair.kind) {
60
+ case "create":
61
+ return `Recreate with --provider vellum --model ${quotedModel} — ${served}.`;
62
+ case "update":
63
+ return `Update it with --provider vellum --model ${quotedModel} — ${served}.`;
64
+ case "repoint":
65
+ return `Repoint it first with "assistant inference profiles update ${shellQuote(repair.profileName)} --provider vellum --model ${quotedModel}" — ${served}.`;
66
+ }
67
+ }
68
+
69
+ const PROMPT_FOR_KEY = (provider: string): string =>
70
+ `"assistant credentials prompt --service ${provider} --field api_key" ` +
71
+ `(secure inline collection — never ask for the key in chat)`;
72
+
73
+ /**
74
+ * Repair for a connection that exists but has no stored key: collecting the
75
+ * key is the whole fix — suggesting a `providers create` here would collide
76
+ * with the existing row.
77
+ */
78
+ function storeMissingKey(provider: string): string {
79
+ return `To store the missing key, run ${PROMPT_FOR_KEY(provider)}, then retry.`;
80
+ }
81
+
82
+ function byoSequence(provider: string): string {
83
+ return (
84
+ `To use "${provider}" directly, run ` +
85
+ `${PROMPT_FOR_KEY(provider)}, then ` +
86
+ `"assistant inference providers create ${provider}-personal --provider ${provider} --credential credential/${provider}/api_key", then retry.`
87
+ );
88
+ }
89
+
90
+ /**
91
+ * Build the repair guidance for an unavailable selection: the exact
92
+ * Vellum-managed alternative when the model is managed-routable and the
93
+ * managed route is usable; otherwise key collection alone when a connection
94
+ * already exists and only its credential is missing, or the full
95
+ * bring-your-own-key sequence when there is no connection at all.
96
+ */
97
+ async function repairGuidance(
98
+ provider: string,
99
+ model: string | undefined,
100
+ repair: ProfileRepairHint,
101
+ status: ConnectionAvailability["status"],
102
+ ): Promise<string> {
103
+ // An incomplete profile has no connection to diagnose: the repair is
104
+ // finishing the profile itself. Its `provider` may also be absent, which
105
+ // the branches below would interpolate as the string "undefined".
106
+ if (status === "incomplete") {
107
+ return "Set both a provider and a model on the profile in Settings → Models & Services, then retry.";
108
+ }
109
+ if (ROUTING_IDENTITY_PROVIDERS.has(provider)) {
110
+ return `Sign in to the "${provider}" route in Settings → Models & Services, or pick a profile backed by a provider API key, then retry.`;
111
+ }
112
+ if (model && getManagedUpstream(model) !== null) {
113
+ const managed = await vellumConnectionAvailability();
114
+ if (managed.status === "ok") {
115
+ return managedAlternative(model, repair);
116
+ }
117
+ }
118
+ if (status === "missing_credential") {
119
+ return storeMissingKey(provider);
120
+ }
121
+ return byoSequence(provider);
122
+ }
123
+
124
+ /**
125
+ * Agent-legible explanation of why a profile cannot serve requests, followed
126
+ * by the repair path and (for in-flight writes) the escape hatch.
127
+ */
128
+ export async function describeUnavailableProfile({
129
+ availability,
130
+ provider,
131
+ model,
132
+ repair,
133
+ escapeHatch,
134
+ }: {
135
+ availability: ConnectionAvailability;
136
+ provider: string;
137
+ model?: string;
138
+ repair: ProfileRepairHint;
139
+ /** Whether the caller accepts `allowUnavailable` to force the write. */
140
+ escapeHatch: boolean;
141
+ }): Promise<string> {
142
+ const parts = [
143
+ availability.message ??
144
+ `Provider "${provider}" cannot serve requests (${availability.status}).`,
145
+ await repairGuidance(provider, model, repair, availability.status),
146
+ ];
147
+ if (escapeHatch) {
148
+ parts.push(
149
+ "Pass --allow-unavailable (allowUnavailable over HTTP) to write it anyway — only do that when intentionally pre-staging config you will finish later.",
150
+ );
151
+ }
152
+ if (repair.kind === "repoint") {
153
+ parts.push(`Then verify with: ${verifyProfileCommand(repair.profileName)}`);
154
+ }
155
+ return parts.join(" ");
156
+ }
157
+
158
+ /**
159
+ * Warning recorded on a forced (`allowUnavailable`) write so the response still
160
+ * names the breakage the guard would have blocked.
161
+ */
162
+ export function unavailableProfileWarning(
163
+ name: string,
164
+ availability: ConnectionAvailability,
165
+ ): string {
166
+ return (
167
+ `Profile "${name}" cannot serve requests yet (${availability.status})` +
168
+ `${availability.message ? `: ${availability.message}` : "."} ` +
169
+ `Written anyway (allowUnavailable). Verify with: ${verifyProfileCommand(name)}`
170
+ );
171
+ }
@@ -23,8 +23,14 @@ import {
23
23
  setConversationInferenceProfileSession,
24
24
  } from "../../persistence/conversation-crud.js";
25
25
  import { resolveConversationId } from "../../persistence/conversation-key-store.js";
26
+ import type { ConnectionAvailability } from "../../providers/inference/connection-availability.js";
27
+ import {
28
+ computeProfileAvailability,
29
+ isUnavailable,
30
+ } from "../../providers/inference/connection-availability.js";
26
31
  import { publishConversationInferenceProfileChanged } from "../sync/resource-sync-events.js";
27
32
  import { BadRequestError, NotFoundError } from "./errors.js";
33
+ import { describeUnavailableProfile } from "./inference-profile-availability-guard.js";
28
34
 
29
35
  // ---------------------------------------------------------------------------
30
36
  // Types
@@ -52,8 +58,9 @@ export interface InferenceProfileSessionResult {
52
58
  * conversation.
53
59
  *
54
60
  * - `profile === null`: clears any existing override (session or non-session).
55
- * - `profile` is a string: validates it against `llm.profiles`, then applies
56
- * it. TTL handling:
61
+ * - `profile` is a string: validates it against `llm.profiles` and against the
62
+ * dispatch-availability guard (a profile that cannot serve requests is
63
+ * refused rather than pinned), then applies it. TTL handling:
57
64
  * - `ttlSeconds` is a positive number → clamp to
58
65
  * `[1, llm.profileSession.maxTtlSeconds]`, mint a sessionId, compute
59
66
  * expiresAt.
@@ -156,6 +163,23 @@ export async function setInferenceProfileSession({
156
163
  );
157
164
  }
158
165
 
166
+ // Pinning a profile that provably cannot dispatch turns the next turn in
167
+ // this conversation into a hard failure, so refuse the pin the same way the
168
+ // active-profile setter does.
169
+ const entry = profiles[profile] as Record<string, unknown>;
170
+ const availability = await computeProfileAvailability(entry);
171
+ if (isUnavailable(availability)) {
172
+ throw new BadRequestError(
173
+ await describeUnavailableProfile({
174
+ availability: availability as ConnectionAvailability,
175
+ provider: String(entry.provider),
176
+ model: typeof entry.model === "string" ? entry.model : undefined,
177
+ repair: { kind: "repoint", profileName: profile },
178
+ escapeHatch: false,
179
+ }),
180
+ );
181
+ }
182
+
159
183
  // --- Compute session fields based on ttlSeconds ---
160
184
  let newSessionId: string | null;
161
185
  let newExpiresAt: number | null;
@@ -73,7 +73,8 @@ export const ROUTES: RouteDefinition[] = [
73
73
  summary: "Open an inference-profile session",
74
74
  description:
75
75
  "Open (or replace) a session-backed inference-profile override for a conversation. " +
76
- "Supports an optional TTL — omit for a sticky (non-expiring) override.",
76
+ "Supports an optional TTL — omit for a sticky (non-expiring) override. " +
77
+ "A profile that provably cannot dispatch is rejected rather than pinned.",
77
78
  tags: ["conversations"],
78
79
  requestBody: z.object({
79
80
  conversationId: z.string().min(1),
@@ -9,8 +9,9 @@
9
9
  *
10
10
  * Unlike the generic `config set llm.profiles.<name> '<json>'` path, these
11
11
  * routes validate at write time: the provider must be a known `LLMProvider`,
12
- * the model must be in the catalog (unless `allowUnlisted`), and a referenced
13
- * connection must exist. Writes reuse the shared config-write plumbing
12
+ * the model must be in the catalog (unless `allowUnlisted`), a referenced
13
+ * connection must exist, and the resulting profile must be able to dispatch
14
+ * (unless `allowUnavailable`). Writes reuse the shared config-write plumbing
14
15
  * (`commitConfigWrite` + the managed-profile guards), so a CLI-created profile
15
16
  * is completed/materialized identically to a UI-created one.
16
17
  */
@@ -33,14 +34,18 @@ import {
33
34
  routingIdentityModelIssue,
34
35
  } from "../../config/schemas/llm.js";
35
36
  import { getDb } from "../../persistence/db-connection.js";
36
- import {
37
- ConnectionResolutionError,
38
- resolveRoutingIdentity,
39
- } from "../../providers/connection-resolution.js";
40
37
  import { ROUTING_IDENTITY_PROVIDERS } from "../../providers/inference/auth.js";
41
- import { computeConnectionAvailability } from "../../providers/inference/connection-availability.js";
38
+ import type { ConnectionAvailability } from "../../providers/inference/connection-availability.js";
39
+ import {
40
+ computeProfileAvailability,
41
+ CONNECTION_AVAILABILITY_STATUSES,
42
+ isUnavailable,
43
+ } from "../../providers/inference/connection-availability.js";
42
44
  import { getConnection } from "../../providers/inference/connections.js";
43
- import { isModelInCatalog } from "../../providers/model-catalog.js";
45
+ import {
46
+ getModelDisplayName,
47
+ isModelInCatalog,
48
+ } from "../../providers/model-catalog.js";
44
49
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
45
50
  import {
46
51
  commitConfigWrite,
@@ -48,6 +53,12 @@ import {
48
53
  rejectManagedProfileDeletion,
49
54
  } from "./conversation-query-routes.js";
50
55
  import { BadRequestError, ConflictError, NotFoundError } from "./errors.js";
56
+ import {
57
+ describeUnavailableProfile,
58
+ type ProfileRepairHint,
59
+ unavailableProfileWarning,
60
+ verifyProfileCommand,
61
+ } from "./inference-profile-availability-guard.js";
51
62
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
52
63
 
53
64
  // Prototype-pollution guards — a profile name may never be one of these.
@@ -63,15 +74,7 @@ const RESERVED_PROFILE_NAMES = new Set([
63
74
 
64
75
  const availabilitySchema = z
65
76
  .object({
66
- status: z.enum([
67
- "ok",
68
- "missing_connection",
69
- "missing_credential",
70
- "provider_mismatch",
71
- "unsupported_auth",
72
- "vellum_unauthenticated",
73
- "unknown",
74
- ]),
77
+ status: z.enum(CONNECTION_AVAILABILITY_STATUSES),
75
78
  message: z.string().optional(),
76
79
  })
77
80
  .meta({ id: "ProfileConnectionAvailability" });
@@ -85,7 +88,7 @@ const profileSummarySchema = z
85
88
  status: z.enum(["active", "disabled"]),
86
89
  source: z.enum(["managed", "user"]),
87
90
  provider_connection: z.string().optional(),
88
- /** Null when the profile has no connection to judge (e.g. mix profiles). */
91
+ /** Null when the profile has no provider to judge (e.g. mix profiles). */
89
92
  availability: availabilitySchema.nullable(),
90
93
  })
91
94
  .meta({ id: "InferenceProfileSummary" });
@@ -104,6 +107,12 @@ const profileWriteResultSchema = z
104
107
  name: z.string(),
105
108
  entry: z.record(z.string(), z.unknown()),
106
109
  warnings: z.array(z.string()),
110
+ /**
111
+ * One-shot command that proves the profile dispatches. The CLI suppresses
112
+ * its human-mode hint under `--json`, which is the mode agents use, so the
113
+ * nudge travels in the payload.
114
+ */
115
+ verify: z.string(),
107
116
  })
108
117
  .meta({ id: "InferenceProfileWriteResult" });
109
118
 
@@ -119,6 +128,7 @@ const createRequestSchema = z.object({
119
128
  thinking: z.boolean().optional(),
120
129
  description: z.string().optional(),
121
130
  allowUnlisted: z.boolean().optional(),
131
+ allowUnavailable: z.boolean().optional(),
122
132
  });
123
133
 
124
134
  const updateRequestSchema = z.object({
@@ -132,6 +142,7 @@ const updateRequestSchema = z.object({
132
142
  thinking: z.boolean().optional(),
133
143
  description: z.string().optional(),
134
144
  allowUnlisted: z.boolean().optional(),
145
+ allowUnavailable: z.boolean().optional(),
135
146
  });
136
147
 
137
148
  // ---------------------------------------------------------------------------
@@ -155,6 +166,7 @@ function validateModel(
155
166
  provider: string,
156
167
  model: string,
157
168
  allowUnlisted: boolean,
169
+ connectionName?: string,
158
170
  ): string[] {
159
171
  // Routing identities key no catalog entries; they validate against their
160
172
  // route's actual reach — the same checks dispatch applies per-request.
@@ -173,11 +185,24 @@ function validateModel(
173
185
  if (isModelInCatalog(provider, model)) {
174
186
  return [];
175
187
  }
188
+ // The named connection's advertised model list is authoritative for models
189
+ // the code-owned catalog doesn't know — a custom (openai-compatible)
190
+ // endpoint declares its own models at connection-create time.
191
+ if (connectionName) {
192
+ const connection = getConnection(getDb(), connectionName);
193
+ if (connection?.models?.some((m) => m.id === model)) {
194
+ return [];
195
+ }
196
+ }
176
197
  if (!allowUnlisted) {
198
+ const remedy =
199
+ provider === "openai-compatible"
200
+ ? `Pass allowUnlisted to create it anyway, or declare the model on the connection ` +
201
+ `("assistant inference providers update <name> --model ${model}").`
202
+ : `Pass allowUnlisted to create it anyway, or run ` +
203
+ `"assistant inference models list --provider ${provider}" to see valid ids.`;
177
204
  throw new BadRequestError(
178
- `Model "${model}" is not in the catalog for provider "${provider}". ` +
179
- `Pass allowUnlisted to create it anyway, or run ` +
180
- `"assistant inference models list --provider ${provider}" to see valid ids.`,
205
+ `Model "${model}" is not in the catalog for provider "${provider}". ${remedy}`,
181
206
  );
182
207
  }
183
208
  return [
@@ -202,46 +227,38 @@ function asPlainObject(value: unknown): Record<string, unknown> | null {
202
227
  }
203
228
 
204
229
  /**
205
- * Compute connection availability for an effective profile entry null when
206
- * there is nothing to judge (no connection or no provider, e.g. mix profiles).
230
+ * Refuse a write that would persist a profile which provably cannot dispatch.
231
+ * Returns the offending verdict when the caller forced the write with
232
+ * `allowUnavailable`, so the handler can warn instead; returns null when the
233
+ * profile is fine or indeterminate.
207
234
  */
208
- async function profileAvailability(
209
- entry: Record<string, unknown>,
210
- ): Promise<Awaited<ReturnType<typeof computeConnectionAvailability>> | null> {
211
- const provider = entry.provider;
212
- const connection = entry.provider_connection;
213
- if (typeof provider !== "string") {
235
+ async function guardProfileAvailability({
236
+ entry,
237
+ repair,
238
+ allowUnavailable,
239
+ }: {
240
+ entry: Record<string, unknown>;
241
+ repair: ProfileRepairHint;
242
+ allowUnavailable: boolean;
243
+ }): Promise<ConnectionAvailability | null> {
244
+ const availability = await computeProfileAvailability(entry);
245
+ if (!isUnavailable(availability)) {
214
246
  return null;
215
247
  }
216
- // Routing-identity profiles carry no provider_connection; availability is
217
- // judged against the identity's canonical row and derived upstream. A
218
- // model the identity cannot route reports as a mismatch rather than
219
- // throwing — availability annotates, it must not fail the profiles read.
220
- if (ROUTING_IDENTITY_PROVIDERS.has(provider)) {
221
- const model = typeof entry.model === "string" ? entry.model : undefined;
222
- try {
223
- const identity = resolveRoutingIdentity(provider, model);
224
- if (!identity) {
225
- return null;
226
- }
227
- return computeConnectionAvailability(
228
- identity.expectedProvider,
229
- identity.connectionName,
230
- );
231
- } catch (err) {
232
- return {
233
- status: "provider_mismatch",
234
- message:
235
- err instanceof ConnectionResolutionError
236
- ? err.message
237
- : `Model "${model ?? "<unset>"}" cannot be routed by provider "${provider}".`,
238
- };
239
- }
240
- }
241
- if (typeof connection !== "string") {
242
- return null;
243
- }
244
- return computeConnectionAvailability(provider, connection);
248
+ // isUnavailable() is only true for a non-null verdict.
249
+ const verdict = availability as ConnectionAvailability;
250
+ if (allowUnavailable) {
251
+ return verdict;
252
+ }
253
+ throw new BadRequestError(
254
+ await describeUnavailableProfile({
255
+ availability: verdict,
256
+ provider: String(entry.provider),
257
+ model: typeof entry.model === "string" ? entry.model : undefined,
258
+ repair,
259
+ escapeHatch: repair.kind !== "repoint",
260
+ }),
261
+ );
245
262
  }
246
263
 
247
264
  function ensureRawProfiles(
@@ -370,7 +387,7 @@ async function handleListProfiles() {
370
387
  ...(typeof record.provider_connection === "string"
371
388
  ? { provider_connection: record.provider_connection }
372
389
  : {}),
373
- availability: await profileAvailability(record),
390
+ availability: await computeProfileAvailability(record),
374
391
  };
375
392
  }),
376
393
  );
@@ -395,7 +412,7 @@ async function handleGetProfile({ pathParams = {} }: RouteHandlerArgs) {
395
412
  return {
396
413
  name,
397
414
  entry: record,
398
- availability: await profileAvailability(record),
415
+ availability: await computeProfileAvailability(record),
399
416
  };
400
417
  }
401
418
 
@@ -421,14 +438,15 @@ async function handleCreateProfile({ body = {} }: RouteHandlerArgs) {
421
438
  }
422
439
 
423
440
  assertValidProvider(input.provider);
441
+ if (input.connection) {
442
+ assertConnectionExists(input.connection);
443
+ }
424
444
  const warnings = validateModel(
425
445
  input.provider,
426
446
  input.model,
427
447
  input.allowUnlisted ?? false,
448
+ input.connection,
428
449
  );
429
- if (input.connection) {
430
- assertConnectionExists(input.connection);
431
- }
432
450
 
433
451
  const entry: Record<string, unknown> = {
434
452
  ...fragmentFromBody(body as Record<string, unknown>),
@@ -436,6 +454,22 @@ async function handleCreateProfile({ body = {} }: RouteHandlerArgs) {
436
454
  model: input.model,
437
455
  source: "user",
438
456
  };
457
+ // Pickers render the label, so a label-less profile shows its raw config
458
+ // key (e.g. "gemini-latest"). Default it to the model's human-readable
459
+ // display name — the catalog's, or the named connection's for custom
460
+ // endpoints — so an unlabeled create still reads well in the UI.
461
+ if (entry.label === undefined) {
462
+ const displayName =
463
+ getModelDisplayName(input.model) ??
464
+ (input.connection
465
+ ? getConnection(getDb(), input.connection)?.models?.find(
466
+ (m) => m.id === input.model,
467
+ )?.displayName
468
+ : undefined);
469
+ if (displayName) {
470
+ entry.label = displayName;
471
+ }
472
+ }
439
473
  validateProfileEntry(entry);
440
474
 
441
475
  const raw = loadRawConfig();
@@ -445,6 +479,16 @@ async function handleCreateProfile({ body = {} }: RouteHandlerArgs) {
445
479
  `Profile "${name}" already exists. Use update to modify it.`,
446
480
  );
447
481
  }
482
+
483
+ const forced = await guardProfileAvailability({
484
+ entry,
485
+ repair: { kind: "create" },
486
+ allowUnavailable: input.allowUnavailable ?? false,
487
+ });
488
+ if (forced) {
489
+ warnings.push(unavailableProfileWarning(name, forced));
490
+ }
491
+
448
492
  profiles[name] = entry;
449
493
  // Defensive: for a user-owned name this is a no-op; it re-asserts the
450
494
  // managed-name protection at the shared write choke point.
@@ -461,6 +505,7 @@ async function handleCreateProfile({ body = {} }: RouteHandlerArgs) {
461
505
  getConfig().llm.defaultProvider ?? null,
462
506
  ) ?? entry) as Record<string, unknown>,
463
507
  warnings,
508
+ verify: verifyProfileCommand(name),
464
509
  };
465
510
  }
466
511
 
@@ -511,6 +556,14 @@ async function handleUpdateProfile({
511
556
  assertValidProvider(input.provider);
512
557
  }
513
558
  let warnings: string[] = [];
559
+ if (input.connection) {
560
+ assertConnectionExists(input.connection);
561
+ }
562
+ const nextConnection =
563
+ input.connection ??
564
+ (typeof existing.provider_connection === "string"
565
+ ? existing.provider_connection
566
+ : undefined);
514
567
  if (
515
568
  (input.provider !== undefined || input.model !== undefined) &&
516
569
  typeof nextProvider === "string" &&
@@ -520,11 +573,9 @@ async function handleUpdateProfile({
520
573
  nextProvider,
521
574
  nextModel,
522
575
  input.allowUnlisted ?? false,
576
+ nextConnection,
523
577
  );
524
578
  }
525
- if (input.connection) {
526
- assertConnectionExists(input.connection);
527
- }
528
579
 
529
580
  const merged: Record<string, unknown> = {
530
581
  ...existing,
@@ -536,6 +587,24 @@ async function handleUpdateProfile({
536
587
  };
537
588
  validateProfileEntry(merged);
538
589
 
590
+ // The availability guard runs only when the write touches the fields that
591
+ // determine dispatchability (provider/model/connection) — metadata-only
592
+ // edits to a pre-staged profile must not require the escape hatch.
593
+ if (
594
+ input.provider !== undefined ||
595
+ input.model !== undefined ||
596
+ input.connection !== undefined
597
+ ) {
598
+ const forced = await guardProfileAvailability({
599
+ entry: merged,
600
+ repair: { kind: "update" },
601
+ allowUnavailable: input.allowUnavailable ?? false,
602
+ });
603
+ if (forced) {
604
+ warnings.push(unavailableProfileWarning(name, forced));
605
+ }
606
+ }
607
+
539
608
  profiles[name] = merged;
540
609
  normalizeManagedProfileWrites({ llm: { profiles: { [name]: merged } } });
541
610
 
@@ -550,6 +619,7 @@ async function handleUpdateProfile({
550
619
  getConfig().llm.defaultProvider ?? null,
551
620
  ) ?? merged) as Record<string, unknown>,
552
621
  warnings,
622
+ verify: verifyProfileCommand(name),
553
623
  };
554
624
  }
555
625
 
@@ -624,6 +694,13 @@ async function handleSetActiveProfile({ body = {} }: RouteHandlerArgs) {
624
694
  `Profile "${name}" is disabled and cannot be set as the active profile. Enable it first, or pick another.`,
625
695
  );
626
696
  }
697
+ // No escape hatch here: an active profile that cannot dispatch locks the
698
+ // user out of chat entirely, and nothing about the write signals that.
699
+ await guardProfileAvailability({
700
+ entry,
701
+ repair: { kind: "repoint", profileName: name },
702
+ allowUnavailable: false,
703
+ });
627
704
 
628
705
  const raw = loadRawConfig();
629
706
  const existingLlm = asPlainObject(raw.llm);
@@ -685,7 +762,7 @@ export const ROUTES: RouteDefinition[] = [
685
762
  },
686
763
  summary: "Create an inference profile",
687
764
  description:
688
- "Create a validated custom profile. The provider must be a known LLM provider, the model must be in the catalog (unless allowUnlisted), and a referenced connection must exist. Managed default names cannot be created.",
765
+ "Create a validated custom profile. The provider must be a known LLM provider, the model must be in the catalog (unless allowUnlisted), a referenced connection must exist, and the profile must be able to dispatch — a valid provider id with no credentialed connection behind it is rejected unless allowUnavailable is set, in which case it is created with a warning.",
689
766
  tags: ["inference"],
690
767
  requestBody: createRequestSchema,
691
768
  responseBody: profileWriteResultSchema,
@@ -693,7 +770,7 @@ export const ROUTES: RouteDefinition[] = [
693
770
  additionalResponses: {
694
771
  "400": {
695
772
  description:
696
- "Invalid provider, uncataloged model, or missing connection",
773
+ "Invalid provider, uncataloged model, missing connection, or a profile that cannot serve requests",
697
774
  },
698
775
  "409": { description: "A profile with this name already exists" },
699
776
  },
@@ -709,14 +786,15 @@ export const ROUTES: RouteDefinition[] = [
709
786
  },
710
787
  summary: "Update an inference profile",
711
788
  description:
712
- "Partial update of a custom profile with the same write-time validation as create. Managed default profiles are read-only.",
789
+ "Partial update of a custom profile with the same write-time validation as create. The dispatch-availability guard (and its allowUnavailable escape hatch) applies when the update changes provider, model, or connection; metadata-only edits skip it. Managed default profiles are read-only.",
713
790
  tags: ["inference"],
714
791
  pathParams: [{ name: "name", description: "Profile name" }],
715
792
  requestBody: updateRequestSchema,
716
793
  responseBody: profileWriteResultSchema,
717
794
  additionalResponses: {
718
795
  "400": {
719
- description: "Invalid fields, or attempt to edit a managed profile",
796
+ description:
797
+ "Invalid fields, a profile that cannot serve requests, or attempt to edit a managed profile",
720
798
  },
721
799
  "404": { description: "Profile not found" },
722
800
  },
@@ -741,7 +819,7 @@ export const ROUTES: RouteDefinition[] = [
741
819
  "404": { description: "Profile not found" },
742
820
  "409": {
743
821
  description:
744
- "Profile is still referenced by activeProfile, advisorProfile, a call site, or a mix arm",
822
+ "Profile is still referenced by activeProfile, advisorProfile, a call site, a default-tier override, or a mix arm",
745
823
  },
746
824
  },
747
825
  handler: handleDeleteProfile,
@@ -756,7 +834,7 @@ export const ROUTES: RouteDefinition[] = [
756
834
  },
757
835
  summary: "Set the active (chat) inference profile",
758
836
  description:
759
- "Set llm.activeProfile after validating the name against the effective profile catalog (provider-aware default expansion included). Unknown or disabled profiles are rejected so the chat-model selection cannot be silently stripped on the next config load.",
837
+ "Set llm.activeProfile after validating the name against the effective profile catalog (provider-aware default expansion included). Unknown or disabled profiles are rejected so the chat-model selection cannot be silently stripped on the next config load. A profile that provably cannot dispatch is also rejected — there is no escape hatch, because an unusable active profile locks the user out of chat.",
760
838
  tags: ["inference"],
761
839
  requestBody: setActiveRequestSchema,
762
840
  responseBody: z.object({
@@ -765,7 +843,8 @@ export const ROUTES: RouteDefinition[] = [
765
843
  }),
766
844
  additionalResponses: {
767
845
  "400": {
768
- description: "Unknown or disabled profile name",
846
+ description:
847
+ "Unknown or disabled profile name, or a profile that cannot serve requests",
769
848
  },
770
849
  },
771
850
  handler: handleSetActiveProfile,