@vellumai/assistant 0.10.9 → 0.10.10-dev.202607162206.d08e98e

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 (456) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +8 -0
  3. package/docs/activation-funnel-telemetry.md +13 -7
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  9. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  12. package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  13. package/openapi.yaml +530 -107
  14. package/package.json +1 -1
  15. package/scripts/generate-openapi.ts +8 -0
  16. package/src/__tests__/activation-early-marking.test.ts +6 -5
  17. package/src/__tests__/agent-loop-override-profile.test.ts +22 -25
  18. package/src/__tests__/agent-wake-override-profile.test.ts +21 -48
  19. package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
  20. package/src/__tests__/app-bundler.test.ts +4 -12
  21. package/src/__tests__/app-executors.test.ts +2 -49
  22. package/src/__tests__/app-routes-csp.test.ts +114 -146
  23. package/src/__tests__/auth-fallback-events-store.test.ts +8 -1
  24. package/src/__tests__/build-persisted-content.test.ts +96 -0
  25. package/src/__tests__/bundle-scanner.test.ts +27 -1
  26. package/src/__tests__/call-controller.test.ts +291 -0
  27. package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +165 -0
  28. package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
  29. package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
  30. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  31. package/src/__tests__/compactor-image-manifest-trust.test.ts +50 -0
  32. package/src/__tests__/config-loader-backfill.test.ts +9 -4
  33. package/src/__tests__/config-schema-cmd.test.ts +10 -11
  34. package/src/__tests__/config-schema.test.ts +190 -257
  35. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +208 -0
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +5 -19
  37. package/src/__tests__/conversation-agent-loop.test.ts +22 -9
  38. package/src/__tests__/conversation-error.test.ts +31 -0
  39. package/src/__tests__/conversation-load-history-repair.test.ts +110 -0
  40. package/src/__tests__/conversation-process-callsite.test.ts +12 -19
  41. package/src/__tests__/conversation-routes-slash-commands.test.ts +7 -21
  42. package/src/__tests__/conversation-summarize-route.test.ts +36 -44
  43. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  44. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +7 -7
  45. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
  46. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -1
  47. package/src/__tests__/conversation-usage.test.ts +4 -12
  48. package/src/__tests__/credential-routes.test.ts +250 -0
  49. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  50. package/src/__tests__/db-migration-rollback.test.ts +22 -0
  51. package/src/__tests__/empty-response-hook.test.ts +186 -1
  52. package/src/__tests__/external-plugin-loader.test.ts +11 -5
  53. package/src/__tests__/heartbeat-service.test.ts +0 -28
  54. package/src/__tests__/host-shell-tool.test.ts +2 -0
  55. package/src/__tests__/inactive-tool-error-messages.test.ts +2 -2
  56. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
  57. package/src/__tests__/internal-telemetry-routes.test.ts +23 -4
  58. package/src/__tests__/invite-routes-http.test.ts +12 -16
  59. package/src/__tests__/list-all-apps.test.ts +0 -4
  60. package/src/__tests__/llm-context-resolution.test.ts +32 -56
  61. package/src/__tests__/llm-request-log-turn-query.test.ts +109 -0
  62. package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
  63. package/src/__tests__/llm-resolver.test.ts +342 -602
  64. package/src/__tests__/llm-schema.test.ts +79 -37
  65. package/src/__tests__/max-tokens-continue-hook.test.ts +19 -0
  66. package/src/__tests__/media-stream-output.test.ts +259 -3
  67. package/src/__tests__/media-stream-server-integration.test.ts +22 -1
  68. package/src/__tests__/media-stream-stt-session.test.ts +47 -0
  69. package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -0
  70. package/src/__tests__/memory-recall-log-store.test.ts +47 -13
  71. package/src/__tests__/mock-gateway-ipc.ts +46 -1
  72. package/src/__tests__/mtime-cache.test.ts +61 -0
  73. package/src/__tests__/navigate-settings-tab.test.ts +2 -0
  74. package/src/__tests__/normalize-onboarding.test.ts +33 -0
  75. package/src/__tests__/onboarding-persona-write.test.ts +26 -0
  76. package/src/__tests__/plugin-api-resolve-credential.test.ts +140 -0
  77. package/src/__tests__/plugin-app-serve-routes.test.ts +166 -14
  78. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -0
  79. package/src/__tests__/post-turn-tool-result-truncation.test.ts +38 -0
  80. package/src/__tests__/provider-commit-message-generator.test.ts +27 -20
  81. package/src/__tests__/provider-connections-backfill.test.ts +138 -0
  82. package/src/__tests__/provider-platform-proxy-integration.test.ts +10 -31
  83. package/src/__tests__/provider-registry-ollama.test.ts +8 -18
  84. package/src/__tests__/provider-send-message-override-profile.test.ts +23 -23
  85. package/src/__tests__/provider-usage-tracking.test.ts +9 -19
  86. package/src/__tests__/prune-old-conversations-job.test.ts +12 -0
  87. package/src/__tests__/published-app-updater.test.ts +22 -16
  88. package/src/__tests__/registry.test.ts +5 -16
  89. package/src/__tests__/retry-openrouter-only-normalization.test.ts +22 -23
  90. package/src/__tests__/retry-thinking-adaptive-only.test.ts +43 -48
  91. package/src/__tests__/retry-thinking-tool-choice.test.ts +57 -66
  92. package/src/__tests__/retry-verbosity-normalization.test.ts +24 -23
  93. package/src/__tests__/reveal-success-registry.test.ts +123 -0
  94. package/src/__tests__/run-conversation-turn-persistence.test.ts +130 -0
  95. package/src/__tests__/secret-fixtures.ts +9 -0
  96. package/src/__tests__/server-history-render.test.ts +28 -0
  97. package/src/__tests__/skills.test.ts +9 -4
  98. package/src/__tests__/slack-share-routes.test.ts +0 -1
  99. package/src/__tests__/stt-stream-session.test.ts +6 -5
  100. package/src/__tests__/subagent-call-site-routing.test.ts +69 -95
  101. package/src/__tests__/subagent-disposal.test.ts +2 -0
  102. package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
  103. package/src/__tests__/subagent-fork-spawn.test.ts +2 -0
  104. package/src/__tests__/subagent-manager-notify.test.ts +2 -0
  105. package/src/__tests__/subagent-role-registry.test.ts +37 -0
  106. package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
  107. package/src/__tests__/subagent-terminal-message.test.ts +50 -0
  108. package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -5
  109. package/src/__tests__/surface-completion-nudge-hook.test.ts +19 -0
  110. package/src/__tests__/telemetry-routes.test.ts +99 -19
  111. package/src/__tests__/tool-audit.test.ts +34 -4
  112. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  113. package/src/__tests__/tool-profiler.test.ts +72 -1
  114. package/src/__tests__/tool-result-spool.test.ts +49 -4
  115. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
  116. package/src/__tests__/ui-channel-variants.test.ts +108 -0
  117. package/src/__tests__/ui-shape-teaching.test.ts +255 -0
  118. package/src/__tests__/usage-attribution.test.ts +18 -41
  119. package/src/__tests__/user-plugin-loader.test.ts +4 -4
  120. package/src/__tests__/voice-config-update.test.ts +46 -0
  121. package/src/__tests__/voice-session-bridge.test.ts +216 -84
  122. package/src/__tests__/workspace-migration-131-drop-web-fetch-mode.test.ts +120 -0
  123. package/src/agent/loop.ts +4 -3
  124. package/src/api/events/open-conversation.test.ts +64 -0
  125. package/src/api/events/open-conversation.ts +33 -0
  126. package/src/api/index.ts +6 -0
  127. package/src/api/responses/conversation-message.ts +5 -0
  128. package/src/apps/app-store.ts +25 -35
  129. package/src/bundler/app-bundler.ts +34 -48
  130. package/src/bundler/app-compiler.ts +39 -4
  131. package/src/bundler/bundle-scanner.ts +13 -0
  132. package/src/bundler/manifest.ts +1 -1
  133. package/src/calls/__tests__/voice-session-bridge.test.ts +47 -0
  134. package/src/calls/call-constants.ts +5 -0
  135. package/src/calls/call-controller.ts +100 -32
  136. package/src/calls/call-transport.ts +9 -0
  137. package/src/calls/media-stream-output.ts +107 -4
  138. package/src/calls/media-stream-server.ts +29 -9
  139. package/src/calls/media-stream-stt-session.ts +7 -1
  140. package/src/calls/media-turn-detector.ts +11 -1
  141. package/src/calls/voice-session-bridge.ts +84 -63
  142. package/src/cli/commands/__tests__/inference-providers.test.ts +270 -33
  143. package/src/cli/commands/__tests__/notifications.test.ts +24 -3
  144. package/src/cli/commands/config.help.ts +6 -6
  145. package/src/cli/commands/credentials.help.ts +13 -0
  146. package/src/cli/commands/credentials.ts +6 -1
  147. package/src/cli/commands/email.help.ts +7 -0
  148. package/src/cli/commands/email.ts +35 -1
  149. package/src/cli/commands/inference-providers.ts +168 -107
  150. package/src/cli/commands/inference.help.ts +118 -46
  151. package/src/cli/commands/memory/index.help.ts +23 -0
  152. package/src/cli/commands/memory/nodes.ts +146 -0
  153. package/src/cli/commands/notifications.help.ts +10 -10
  154. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +40 -0
  155. package/src/cli/commands/oauth/connect-surface-guidance.ts +54 -0
  156. package/src/cli/commands/oauth/connect.test.ts +126 -0
  157. package/src/cli/commands/oauth/connect.ts +29 -6
  158. package/src/cli/commands/oauth/index.help.ts +7 -1
  159. package/src/cli/commands/oauth/status.test.ts +69 -3
  160. package/src/cli/commands/oauth/status.ts +50 -12
  161. package/src/cli/commands/plugins.help.ts +13 -2
  162. package/src/cli/commands/plugins.ts +61 -7
  163. package/src/cli/commands/telemetry.help.ts +13 -0
  164. package/src/cli/commands/telemetry.ts +45 -5
  165. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +8 -2
  166. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +213 -9
  167. package/src/cli/lib/bundled-marketplace.json +93 -36
  168. package/src/cli/lib/inspect-plugin.ts +5 -1
  169. package/src/cli/lib/install-from-github.ts +68 -4
  170. package/src/cli/lib/plugin-catalog-local.ts +6 -2
  171. package/src/cli/lib/plugin-fingerprint.ts +3 -3
  172. package/src/cli/lib/upgrade-plugin.ts +236 -35
  173. package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
  174. package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +137 -0
  175. package/src/config/__tests__/profile-materialization.test.ts +1 -88
  176. package/src/config/bundled-skills/AGENTS.md +3 -30
  177. package/src/config/bundled-skills/app-builder/SKILL.md +5 -3
  178. package/src/config/bundled-skills/app-builder/TOOLS.json +23 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-open.ts +32 -0
  180. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  181. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
  182. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  183. package/src/config/bundled-skills/schedule/TOOLS.json +1 -1
  184. package/src/config/bundled-skills/settings/TOOLS.json +3 -1
  185. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +2 -0
  186. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +18 -10
  187. package/src/config/bundled-skills/subagent/SKILL.md +2 -0
  188. package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
  189. package/src/config/call-site-defaults.ts +3 -3
  190. package/src/config/feature-flag-registry.json +24 -39
  191. package/src/config/llm-resolver.ts +88 -475
  192. package/src/config/profile-materialization.ts +11 -11
  193. package/src/config/schema.ts +93 -127
  194. package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
  195. package/src/config/schemas/__tests__/stt.test.ts +31 -3
  196. package/src/config/schemas/live-voice.ts +10 -4
  197. package/src/config/schemas/llm.ts +52 -15
  198. package/src/config/schemas/memory-lifecycle.ts +1 -1
  199. package/src/config/schemas/memory-retrospective.ts +8 -0
  200. package/src/config/schemas/memory-v2.ts +2 -2
  201. package/src/config/schemas/memory-v3.ts +1 -1
  202. package/src/config/schemas/services.ts +6 -3
  203. package/src/config/schemas/stt.ts +38 -21
  204. package/src/config/schemas/tts.ts +13 -18
  205. package/src/config/skills.ts +31 -21
  206. package/src/context/compactor.ts +44 -7
  207. package/src/context/post-turn-tool-result-truncation.ts +4 -2
  208. package/src/context/tool-result-spool.ts +26 -5
  209. package/src/conversations/__tests__/message-consolidation.test.ts +48 -0
  210. package/src/conversations/message-consolidation.ts +22 -2
  211. package/src/daemon/__tests__/conversation-tool-setup.test.ts +5 -12
  212. package/src/daemon/app-source-watcher.ts +17 -23
  213. package/src/daemon/chat-credential-redaction.ts +1365 -0
  214. package/src/daemon/conversation-agent-loop-handlers.ts +627 -34
  215. package/src/daemon/conversation-agent-loop.ts +71 -31
  216. package/src/daemon/conversation-error.ts +36 -14
  217. package/src/daemon/conversation-process.ts +22 -0
  218. package/src/daemon/conversation-store.ts +35 -0
  219. package/src/daemon/conversation-surfaces.ts +25 -25
  220. package/src/daemon/conversation-tool-setup.ts +33 -7
  221. package/src/daemon/conversation.ts +55 -5
  222. package/src/daemon/handlers/shared.ts +33 -2
  223. package/src/daemon/lifecycle.ts +4 -4
  224. package/src/daemon/message-types/conversations.ts +6 -17
  225. package/src/daemon/providers-setup.ts +8 -0
  226. package/src/daemon/tool-setup-types.ts +7 -1
  227. package/src/daemon/wake-conversation-ops.ts +10 -1
  228. package/src/hooks/types.ts +9 -0
  229. package/src/ipc/__tests__/email-ipc.test.ts +90 -0
  230. package/src/ipc/gateway-client.test.ts +59 -0
  231. package/src/ipc/gateway-client.ts +70 -27
  232. package/src/live-voice/__tests__/live-voice-events.test.ts +14 -2
  233. package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
  234. package/src/live-voice/__tests__/live-voice-vad.test.ts +804 -13
  235. package/src/live-voice/__tests__/protocol.test.ts +122 -0
  236. package/src/live-voice/live-voice-session.ts +443 -40
  237. package/src/live-voice/protocol.ts +143 -1
  238. package/src/monitoring/__tests__/plugin-source-watch.test.ts +3 -1
  239. package/src/monitoring/plugin-source-watch.ts +3 -62
  240. package/src/notifications/README.md +1 -1
  241. package/src/permissions/checker.ts +8 -4
  242. package/src/persistence/__tests__/db-init-migrations-ok.test.ts +26 -0
  243. package/src/persistence/conversation-crud.ts +104 -2
  244. package/src/persistence/db-init.ts +14 -3
  245. package/src/persistence/job-handlers/cleanup.ts +15 -7
  246. package/src/persistence/llm-request-log-store.ts +88 -52
  247. package/src/persistence/migrations/298-move-memory-jobs-to-memory-db.ts +7 -31
  248. package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
  249. package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +8 -34
  250. package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +90 -0
  251. package/src/persistence/migrations/337-move-memory-recall-logs-to-memory-db.ts +114 -0
  252. package/src/persistence/migrations/338-move-memory-v3-selections-to-memory-db.ts +84 -0
  253. package/src/persistence/migrations/339-move-activation-sessions-to-memory-db.ts +52 -0
  254. package/src/persistence/migrations/__tests__/run-migrations.test.ts +155 -0
  255. package/src/persistence/migrations/helpers/relocation.ts +44 -1
  256. package/src/persistence/migrations/run-migrations.ts +25 -1
  257. package/src/persistence/schema/infrastructure.ts +9 -0
  258. package/src/persistence/schema/memory-core.ts +3 -0
  259. package/src/persistence/schema/memory-injection.ts +2 -0
  260. package/src/persistence/steps.ts +36 -0
  261. package/src/platform/client.test.ts +1 -44
  262. package/src/platform/client.ts +10 -20
  263. package/src/platform/consent-cache.test.ts +89 -23
  264. package/src/platform/consent-cache.ts +71 -33
  265. package/src/plugin-api/constants.ts +12 -0
  266. package/src/plugin-api/conversation-turn.ts +37 -14
  267. package/src/plugin-api/index.ts +11 -1
  268. package/src/plugin-api/resolve-credential.ts +75 -0
  269. package/src/plugin-api/vision-support.test.ts +8 -19
  270. package/src/plugin-api/vision-support.ts +26 -17
  271. package/src/plugins/collect-source-versions.ts +77 -0
  272. package/src/plugins/defaults/compaction/compact.ts +6 -0
  273. package/src/plugins/defaults/compaction/window-manager.ts +9 -0
  274. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +18 -24
  275. package/src/plugins/defaults/empty-response/hooks/user-prompt-submit.ts +38 -0
  276. package/src/plugins/defaults/empty-response/refusal-quarantine.ts +99 -0
  277. package/src/plugins/defaults/image-fallback/__tests__/caption-cache-persistence.test.ts +7 -3
  278. package/src/plugins/defaults/index.ts +8 -1
  279. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +4 -1
  280. package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +48 -6
  281. package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +28 -22
  282. package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +148 -0
  283. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +80 -8
  284. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +202 -0
  285. package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +64 -25
  286. package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +24 -11
  287. package/src/plugins/defaults/memory/__tests__/prompt-override.test.ts +70 -6
  288. package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +278 -0
  289. package/src/plugins/defaults/memory/activation-session-store.ts +27 -20
  290. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -9
  291. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -8
  292. package/src/plugins/defaults/memory/graph/retriever.test.ts +6 -6
  293. package/src/plugins/defaults/memory/graph/store.ts +114 -0
  294. package/src/plugins/defaults/memory/graph/tool-handlers.ts +36 -1
  295. package/src/plugins/defaults/memory/graph/tools.ts +47 -13
  296. package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +16 -35
  297. package/src/plugins/defaults/memory/jobs-worker.ts +10 -1
  298. package/src/plugins/defaults/memory/memory-db.ts +3 -2
  299. package/src/plugins/defaults/memory/memory-recall-log-store.ts +129 -66
  300. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +8 -0
  301. package/src/plugins/defaults/memory/memory-retrospective-job.ts +11 -122
  302. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
  303. package/src/plugins/defaults/memory/memory-v2-activation-log-store.ts +84 -46
  304. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +42 -32
  305. package/src/plugins/defaults/memory/path-containment.ts +21 -0
  306. package/src/plugins/defaults/memory/prompt-override.ts +51 -13
  307. package/src/plugins/defaults/memory/tools.test.ts +34 -0
  308. package/src/plugins/defaults/memory/tools.ts +12 -2
  309. package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +19 -15
  310. package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +24 -19
  311. package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +19 -15
  312. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +22 -2
  313. package/src/plugins/defaults/memory/v2/__tests__/prompts-consolidation.test.ts +5 -3
  314. package/src/plugins/defaults/memory/v2/harness/oracle.ts +59 -41
  315. package/src/plugins/defaults/memory/v2/harness/replay-input.ts +29 -25
  316. package/src/plugins/defaults/memory/v2/migration.ts +46 -16
  317. package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
  318. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +17 -15
  319. package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +24 -22
  320. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +9 -5
  321. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +277 -10
  322. package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +57 -5
  323. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +9 -7
  324. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +38 -7
  325. package/src/plugins/defaults/memory/v3/hot-set.test.ts +36 -18
  326. package/src/plugins/defaults/memory/v3/hot-set.ts +12 -14
  327. package/src/plugins/defaults/memory/v3/learned-edges.test.ts +47 -27
  328. package/src/plugins/defaults/memory/v3/learned-edges.ts +12 -14
  329. package/src/plugins/defaults/memory/v3/orchestrate.ts +139 -22
  330. package/src/plugins/defaults/memory/v3/prune.test.ts +10 -3
  331. package/src/plugins/defaults/memory/v3/prune.ts +17 -11
  332. package/src/plugins/defaults/memory/v3/selection-log-store.ts +23 -11
  333. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +70 -53
  334. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +9 -6
  335. package/src/plugins/external-plugin-loader.ts +15 -15
  336. package/src/plugins/mtime-cache.ts +47 -0
  337. package/src/plugins/pipeline.ts +9 -1
  338. package/src/plugins/plugin-execution-context.ts +44 -0
  339. package/src/plugins/plugin-tree-walk.ts +31 -24
  340. package/src/plugins/source-fingerprint.ts +3 -4
  341. package/src/prompts/normalize-onboarding.ts +12 -0
  342. package/src/prompts/persona-resolver.ts +8 -0
  343. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +1 -1
  344. package/src/providers/__tests__/dispatch-connection-routing.test.ts +6 -9
  345. package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
  346. package/src/providers/__tests__/retry-callsite.test.ts +235 -215
  347. package/src/providers/__tests__/satellite-connection-routing.test.ts +8 -16
  348. package/src/providers/atlascloud/client.ts +10 -49
  349. package/src/providers/baseten/client.ts +43 -0
  350. package/src/providers/call-site-routing.ts +26 -13
  351. package/src/providers/connection-resolution.ts +9 -11
  352. package/src/providers/fetch-provider-catalog.ts +4 -2
  353. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +29 -4
  354. package/src/providers/inference/__tests__/connection-availability-keyless.test.ts +78 -0
  355. package/src/providers/inference/adapter-factory.ts +27 -6
  356. package/src/providers/inference/auth.ts +29 -1
  357. package/src/providers/inference/backfill.ts +61 -10
  358. package/src/providers/inference/connection-availability.ts +3 -2
  359. package/src/providers/inference/resolve-auth.ts +9 -1
  360. package/src/providers/model-catalog.ts +37 -0
  361. package/src/providers/openai/__tests__/api-error-normalization.test.ts +24 -2
  362. package/src/providers/openai/api-key-validation.ts +70 -0
  363. package/src/providers/retry.ts +2 -0
  364. package/src/providers/types.ts +11 -4
  365. package/src/providers/vellum-model-routing.test.ts +26 -0
  366. package/src/providers/vellum-model-routing.ts +30 -0
  367. package/src/providers/voice-error-copy.ts +47 -0
  368. package/src/runtime/agent-wake.ts +40 -24
  369. package/src/runtime/for-chat-mint-registry.ts +118 -0
  370. package/src/runtime/reveal-nonce.ts +49 -0
  371. package/src/runtime/reveal-success-registry.ts +306 -0
  372. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +88 -3
  373. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -30
  374. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +7 -0
  375. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +15 -0
  376. package/src/runtime/routes/app-management-routes.ts +152 -45
  377. package/src/runtime/routes/app-routes.ts +16 -77
  378. package/src/runtime/routes/canned-message-complete.ts +16 -16
  379. package/src/runtime/routes/conversation-management-routes.ts +17 -14
  380. package/src/runtime/routes/conversation-query-routes.ts +54 -6
  381. package/src/runtime/routes/conversation-routes.ts +48 -2
  382. package/src/runtime/routes/credential-routes.ts +116 -3
  383. package/src/runtime/routes/email-routes.ts +17 -1
  384. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +33 -5
  385. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +6 -5
  386. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -28
  387. package/src/runtime/routes/inference-send-routes.ts +1 -1
  388. package/src/runtime/routes/internal-telemetry-routes.ts +7 -6
  389. package/src/runtime/routes/plugins-routes.ts +37 -6
  390. package/src/runtime/routes/publish-routes.ts +15 -18
  391. package/src/runtime/routes/schedule-worker-routes.ts +9 -0
  392. package/src/runtime/routes/secret-routes.ts +10 -0
  393. package/src/runtime/routes/telemetry-routes.ts +149 -45
  394. package/src/schedule/__tests__/schedule-timezone.test.ts +101 -0
  395. package/src/schedule/__tests__/worker-watchdog.test.ts +209 -0
  396. package/src/schedule/schedule-store.ts +12 -1
  397. package/src/schedule/schedule-timezone.ts +63 -0
  398. package/src/schedule/scheduler.ts +100 -6
  399. package/src/schedule/worker-control.ts +19 -0
  400. package/src/security/auth-fallback-events-store.ts +7 -6
  401. package/src/security/secret-scanner.ts +26 -1
  402. package/src/services/published-app-updater.ts +6 -11
  403. package/src/stt/stt-stream-session.ts +36 -16
  404. package/src/subagent/manager.ts +34 -8
  405. package/src/telemetry/AGENTS.md +30 -1
  406. package/src/telemetry/__tests__/config-setting-snapshot.test.ts +18 -0
  407. package/src/telemetry/__tests__/outbox-test-harness.ts +5 -3
  408. package/src/telemetry/config-setting-snapshot.ts +44 -10
  409. package/src/telemetry/telemetry-event-sources.test.ts +124 -30
  410. package/src/telemetry/telemetry-event-sources.ts +137 -83
  411. package/src/telemetry/telemetry-events-outbox.test.ts +46 -1
  412. package/src/telemetry/telemetry-events-outbox.ts +60 -9
  413. package/src/telemetry/telemetry-wire-source.json +1 -1
  414. package/src/telemetry/telemetry-wire-validation.ts +39 -2
  415. package/src/telemetry/telemetry-wire.generated.ts +8 -0
  416. package/src/telemetry/tool-audit.ts +15 -9
  417. package/src/telemetry/tool-executed-events-store.test.ts +1 -1
  418. package/src/telemetry/turn-events-store.ts +20 -0
  419. package/src/telemetry/types.ts +45 -12
  420. package/src/telemetry/usage-telemetry-reporter.test.ts +294 -25
  421. package/src/telemetry/usage-telemetry-reporter.ts +117 -18
  422. package/src/telemetry/watchdog-direct-emit.test.ts +11 -3
  423. package/src/telemetry/watchdog-direct-emit.ts +11 -6
  424. package/src/tools/apps/executors.ts +11 -36
  425. package/src/tools/executor.ts +14 -1
  426. package/src/tools/host-terminal/host-shell.ts +6 -2
  427. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +1 -1
  428. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +25 -0
  429. package/src/tools/network/web-fetch.ts +6 -2
  430. package/src/tools/skills/sandbox-runner.ts +5 -2
  431. package/src/tools/subagent/spawn.ts +7 -11
  432. package/src/tools/terminal/shell.ts +5 -2
  433. package/src/tools/tool-manifest.ts +0 -2
  434. package/src/tools/tool-profiler.ts +37 -6
  435. package/src/tools/ui-surface/channel-variants.ts +101 -0
  436. package/src/tools/ui-surface/definitions.ts +23 -67
  437. package/src/tools/ui-surface/surface-shape-docs.ts +229 -0
  438. package/src/tts/__tests__/provider-adapters.test.ts +18 -0
  439. package/src/tts/provider-catalog.ts +2 -4
  440. package/src/tts/providers/deepgram-provider.ts +10 -1
  441. package/src/types/onboarding-context.ts +8 -0
  442. package/src/usage/attribution.ts +18 -112
  443. package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
  444. package/src/util/provider-error-patterns.ts +7 -1
  445. package/src/util/worker-process.ts +105 -0
  446. package/src/watcher/__tests__/telemetry.test.ts +17 -4
  447. package/src/watcher/telemetry.ts +5 -5
  448. package/src/workspace/migrations/131-drop-web-fetch-mode.ts +61 -0
  449. package/src/workspace/migrations/registry.ts +2 -0
  450. package/src/workspace/provider-commit-message-generator.ts +6 -4
  451. package/src/__tests__/app-open-proxy.test.ts +0 -67
  452. package/src/onboarding/onboarding-research-events-store.test.ts +0 -230
  453. package/src/onboarding/onboarding-research-events-store.ts +0 -104
  454. package/src/runtime/routes/assets/vellum-design-system.css +0 -2236
  455. package/src/tools/apps/definitions.ts +0 -73
  456. package/src/tools/apps/open-proxy.ts +0 -43
@@ -16,10 +16,13 @@
16
16
 
17
17
  import { z } from "zod";
18
18
 
19
+ import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
20
+ import { getConfig } from "../../config/loader.js";
19
21
  import {
20
22
  fetchManagedCatalog,
21
23
  type ManagedCredentialDescriptor,
22
24
  } from "../../credential-execution/managed-catalog.js";
25
+ import { buildForChatSentinel } from "../../daemon/chat-credential-redaction.js";
23
26
  import { syncManualTokenConnection } from "../../oauth/manual-token-connection.js";
24
27
  import {
25
28
  disconnectOAuthProvider,
@@ -48,6 +51,8 @@ import {
48
51
  } from "../../tools/credentials/metadata-store.js";
49
52
  import type { CredentialInjectionTemplate } from "../../tools/credentials/policy-types.js";
50
53
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
54
+ import { recordForChatMint } from "../for-chat-mint-registry.js";
55
+ import { recordRevealSuccess } from "../reveal-success-registry.js";
51
56
  import { InjectionTemplateSchema } from "./credential-prompt-routes.js";
52
57
  import { BadRequestError, InternalError } from "./errors.js";
53
58
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
@@ -296,11 +301,30 @@ async function handleCredentialsInspect({ body }: RouteHandlerArgs) {
296
301
  return output;
297
302
  }
298
303
 
299
- async function handleCredentialsReveal({ body }: RouteHandlerArgs) {
304
+ async function handleCredentialsReveal({ body, headers }: RouteHandlerArgs) {
300
305
  if (!body || typeof body !== "object") {
301
306
  throw new BadRequestError("Request body is required");
302
307
  }
303
308
 
309
+ const forChat = (body as { forChat?: unknown }).forChat === true;
310
+ if (
311
+ forChat &&
312
+ !isAssistantFeatureFlagEnabled("chat-credential-reveal", getConfig())
313
+ ) {
314
+ throw new BadRequestError(
315
+ "--for-chat requires the chat-credential-reveal feature flag",
316
+ );
317
+ }
318
+
319
+ // Conversation-bound reveal nonce (see reveal-nonce.ts), forwarded by
320
+ // the CLI from the tool-shell env. Required for RECORDING any
321
+ // reveal-derived chat authority below; the reveal itself works without
322
+ // it (Settings rows, direct terminal use) — those calls just create no
323
+ // authority for any conversation's transcript.
324
+ const rawNonce = (body as { revealNonce?: unknown }).revealNonce;
325
+ const revealNonce =
326
+ typeof rawNonce === "string" && rawNonce.length > 0 ? rawNonce : undefined;
327
+
304
328
  const lookup = resolveCredentialLookup(body);
305
329
  const { value: secret, unreachable } = await getSecureKeyResultAsync(
306
330
  lookup.storageKey,
@@ -315,6 +339,79 @@ async function handleCredentialsReveal({ body }: RouteHandlerArgs) {
315
339
  throw new BadRequestError("Credential not found");
316
340
  }
317
341
 
342
+ // Recording gate shared by both registries below: only a DIRECT tool-shell
343
+ // CLI invocation may create authority. The `local` principal is the
344
+ // identity a CLI invocation arrives with over the unix-socket IPC
345
+ // (verified by the adapters, never caller-supplied), but the principal
346
+ // check alone is not enough: in local mode the gateway derives `local`
347
+ // from the verified JWT sub and forwards it for web calls too, stamping
348
+ // `x-vellum-proxy-server: ipc` — a header a direct CLI never sends — so
349
+ // proxied Settings-row/chat-chip reveals are excluded.
350
+ const directLocalInvocation =
351
+ headers?.["x-vellum-principal-type"] === "local" &&
352
+ headers?.["x-vellum-proxy-server"] !== "ipc";
353
+
354
+ if (forChat) {
355
+ // Both lookup forms resolve service+field (or throw); the narrow check
356
+ // keeps the sentinel's vault coordinates provably non-empty.
357
+ if (!lookup.service || !lookup.field) {
358
+ throw new BadRequestError(
359
+ "Credential identity could not be resolved for --for-chat",
360
+ );
361
+ }
362
+ // Chat-safe reveal: return the enriched redaction sentinel instead of
363
+ // the plaintext. The caller (the model, echoing a tool result) can
364
+ // paste this into its reply to render a click-to-reveal chip without
365
+ // the secret ever entering model context or the conversation stream.
366
+ // The persist-path forgery guard re-mints it on identity match — see
367
+ // guardForChatSentinels in chat-credential-redaction.ts. No plaintext
368
+ // reaches the tool, so no reveal-success proof is recorded: that
369
+ // registry authorizes plaintext-echo swaps, and retaining the secret
370
+ // for a path that never emits it would widen retention for nothing.
371
+ const sentinel = buildForChatSentinel({
372
+ service: lookup.service,
373
+ field: lookup.field,
374
+ value: secret,
375
+ });
376
+ // Record the mint AFTER the reveal provably succeeded. This registry —
377
+ // not any parse of the requested shell command — is what authorizes the
378
+ // persist guard to re-mint a sentinel with this identity: a command
379
+ // that merely quotes or comments out a reveal invocation never reaches
380
+ // this route, so it never allowlists anything. Like the plaintext proof
381
+ // below, the mint records only for a direct tool-shell invocation, and
382
+ // only with the conversation-bound nonce that scopes it to the
383
+ // conversation whose tool shell made this call (see the registry
384
+ // module doc — no predictable identifier can do this job).
385
+ if (directLocalInvocation && revealNonce !== undefined) {
386
+ recordForChatMint({
387
+ service: lookup.service,
388
+ field: lookup.field,
389
+ sentinel,
390
+ nonce: revealNonce,
391
+ });
392
+ }
393
+ return { value: sentinel };
394
+ }
395
+
396
+ // Ground truth for the chat-credential-reveal persist seams: this route
397
+ // is the only place a reveal legitimately reads plaintext, so a success
398
+ // recorded here is the proof the agent loop requires before promoting
399
+ // staged reveal candidates (a shell command can "succeed" without ever
400
+ // reaching this handler — `… || true`, or an echo of the command text).
401
+ // Recording a proxied UI reveal would let a click promote a staged ref
402
+ // in a concurrent turn whose command merely echoed (or was denied) the
403
+ // invocation. An unproven ref degrades to a plain sentinel — the safe
404
+ // direction. Both lookup branches populate service/field; the guard only
405
+ // satisfies the loose `CredentialLookup` type.
406
+ if (
407
+ directLocalInvocation &&
408
+ revealNonce !== undefined &&
409
+ lookup.service !== undefined &&
410
+ lookup.field !== undefined
411
+ ) {
412
+ recordRevealSuccess(lookup.service, lookup.field, secret, revealNonce);
413
+ }
414
+
318
415
  return { value: secret };
319
416
  }
320
417
 
@@ -523,15 +620,31 @@ export const ROUTES: RouteDefinition[] = [
523
620
  },
524
621
  summary: "Reveal a credential's plaintext value",
525
622
  description:
526
- "Return the raw plaintext value of a stored credential. Blocked in untrusted shell mode.",
623
+ "Return the raw plaintext value of a stored credential. Blocked in untrusted shell mode. With forChat, returns a chat-safe redaction sentinel instead of the plaintext.",
527
624
  tags: ["credentials"],
528
625
  requestBody: z.object({
529
626
  service: z.string().optional().describe("Service namespace"),
530
627
  field: z.string().optional().describe("Field name"),
531
628
  id: z.string().optional().describe("Credential UUID for lookup by ID"),
629
+ forChat: z
630
+ .boolean()
631
+ .optional()
632
+ .describe(
633
+ "Return the credential's redaction sentinel (renders as a click-to-reveal chip in chat) instead of the plaintext. Requires the chat-credential-reveal feature flag.",
634
+ ),
635
+ revealNonce: z
636
+ .string()
637
+ .optional()
638
+ .describe(
639
+ "Conversation-bound reveal nonce from the tool-shell environment. Scopes any recorded chat-redaction authority (plaintext proof or forChat mint) to the conversation whose tool executed this reveal; without it the reveal succeeds but records no authority.",
640
+ ),
532
641
  }),
533
642
  responseBody: z.object({
534
- value: z.string().describe("The plaintext credential value"),
643
+ value: z
644
+ .string()
645
+ .describe(
646
+ "The plaintext credential value (or its redaction sentinel when forChat is set)",
647
+ ),
535
648
  }),
536
649
  handler: handleCredentialsReveal,
537
650
  },
@@ -240,13 +240,18 @@ async function handleEmailDownload({ queryParams = {} }: RouteHandlerArgs) {
240
240
  }
241
241
 
242
242
  async function handleEmailSend({ body = {} }: RouteHandlerArgs) {
243
- const { to, text, subject, html, cc, bcc, reply_to } = body as {
243
+ const { to, text, subject, html, cc, bcc, attachments, reply_to } = body as {
244
244
  to: string[];
245
245
  text: string;
246
246
  subject?: string;
247
247
  html?: string;
248
248
  cc?: string[];
249
249
  bcc?: string[];
250
+ attachments?: {
251
+ filename: string;
252
+ content_type: string;
253
+ content: string;
254
+ }[];
250
255
  reply_to?: string;
251
256
  };
252
257
 
@@ -293,6 +298,7 @@ async function handleEmailSend({ body = {} }: RouteHandlerArgs) {
293
298
  if (resolvedHtml) payload.html = resolvedHtml;
294
299
  if (cc && cc.length > 0) payload.cc = cc;
295
300
  if (bcc && bcc.length > 0) payload.bcc = bcc;
301
+ if (attachments && attachments.length > 0) payload.attachments = attachments;
296
302
  if (reply_to) payload.reply_to = reply_to;
297
303
 
298
304
  const response = await client.fetch("/v1/runtime-proxy/email/send/", {
@@ -567,6 +573,16 @@ export const ROUTES: RouteDefinition[] = [
567
573
  .describe("HTML body (auto-generated from text if omitted)"),
568
574
  cc: z.array(z.string()).optional().describe("CC recipients"),
569
575
  bcc: z.array(z.string()).optional().describe("BCC recipients"),
576
+ attachments: z
577
+ .array(
578
+ z.object({
579
+ filename: z.string(),
580
+ content_type: z.string(),
581
+ content: z.string().describe("Base64-encoded file content"),
582
+ }),
583
+ )
584
+ .optional()
585
+ .describe("File attachments"),
570
586
  reply_to: z.string().optional().describe("Reply-to email ID"),
571
587
  }),
572
588
  responseBody: z.object({
@@ -151,7 +151,7 @@ describe("tryTranscribeAudioAttachments", () => {
151
151
  expect(reason).not.toContain("OpenAI");
152
152
  });
153
153
 
154
- test("API failure returns error with reason", async () => {
154
+ test("API failure returns a friendly, non-raw reason naming the provider", async () => {
155
155
  const audio = makeAudioAttachment("a1");
156
156
  mockAttachments = [audio];
157
157
  mockTranscriber = {
@@ -165,9 +165,37 @@ describe("tryTranscribeAudioAttachments", () => {
165
165
  const result = await tryTranscribeAudioAttachments(["a1"]);
166
166
 
167
167
  expect(result.status).toBe("error");
168
- expect((result as { reason: string }).reason).toBe(
169
- "API rate limit exceeded",
170
- );
168
+ const reason = (result as { reason: string }).reason;
169
+ // Friendly copy replaces the raw provider string.
170
+ expect(reason).not.toContain("API rate limit exceeded");
171
+ expect(reason).toContain("OpenAI Whisper");
172
+ });
173
+
174
+ test("auth failure returns a friendly, JSON-free reason that names the API key", async () => {
175
+ const audio = makeAudioAttachment("a1");
176
+ mockAttachments = [audio];
177
+ mockTranscriber = {
178
+ providerId: "deepgram",
179
+ boundaryId: "daemon-batch",
180
+ transcribe: async () => {
181
+ // Mocked normalizeSttError returns an SttError as-is, so throw the
182
+ // categorized error directly (a plain 401 string would classify as
183
+ // provider-error under the simplified mock).
184
+ throw new SttError(
185
+ "auth",
186
+ 'Deepgram API error (401): {"err_code":"INVALID_AUTH"}',
187
+ );
188
+ },
189
+ };
190
+
191
+ const result = await tryTranscribeAudioAttachments(["a1"]);
192
+
193
+ expect(result.status).toBe("error");
194
+ const reason = (result as { reason: string }).reason;
195
+ expect(reason).not.toContain("{");
196
+ expect(reason).not.toContain("INVALID_AUTH");
197
+ expect(reason).toContain("API key");
198
+ expect(reason).toContain("Deepgram");
171
199
  });
172
200
 
173
201
  test("30-second timeout fires and returns error without blocking", async () => {
@@ -184,7 +212,7 @@ describe("tryTranscribeAudioAttachments", () => {
184
212
  const result = await tryTranscribeAudioAttachments(["a1"]);
185
213
 
186
214
  expect(result.status).toBe("error");
187
- expect((result as { reason: string }).reason).toBe(
215
+ expect((result as { reason: string }).reason).toContain(
188
216
  "Transcription timed out",
189
217
  );
190
218
  });
@@ -12,7 +12,9 @@ import {
12
12
  getAttachmentsByIds,
13
13
  } from "../../../persistence/attachments-store.js";
14
14
  import { resolveBatchTranscriber } from "../../../providers/speech-to-text/resolve.js";
15
+ import { describeSttFailure } from "../../../providers/voice-error-copy.js";
15
16
  import { normalizeSttError } from "../../../stt/daemon-batch-transcriber.js";
17
+ import type { SttProviderId } from "../../../stt/types.js";
16
18
  import { getLogger } from "../../../util/logger.js";
17
19
 
18
20
  const log = getLogger("transcribe-audio");
@@ -37,6 +39,8 @@ export type TranscribeResult =
37
39
  export async function tryTranscribeAudioAttachments(
38
40
  attachmentIds: string[],
39
41
  ): Promise<TranscribeResult> {
42
+ // Hoisted so the catch can name the provider in a friendly failure message.
43
+ let providerId: SttProviderId | undefined;
40
44
  try {
41
45
  // Look up attachments and filter to audio MIME types
42
46
  const resolved = getAttachmentsByIds(attachmentIds);
@@ -57,6 +61,7 @@ export async function tryTranscribeAudioAttachments(
57
61
  "No speech-to-text provider configured. Add an API key for your STT service to enable voice message transcription.",
58
62
  };
59
63
  }
64
+ providerId = transcriber.providerId;
60
65
 
61
66
  // Transcribe each audio attachment with a shared timeout
62
67
  const abortController = new AbortController();
@@ -103,11 +108,7 @@ export async function tryTranscribeAudioAttachments(
103
108
  }
104
109
  } catch (err: unknown) {
105
110
  const sttErr = normalizeSttError(err);
106
- const reason =
107
- sttErr.category === "timeout"
108
- ? "Transcription timed out"
109
- : sttErr.message;
110
111
  log.warn({ err }, "Audio transcription failed");
111
- return { status: "error", reason };
112
+ return { status: "error", reason: describeSttFailure(sttErr, providerId) };
112
113
  }
113
114
  }
@@ -19,10 +19,12 @@ import { getIsPlatform } from "../../config/env-registry.js";
19
19
  import { getConfigReadOnly } from "../../config/loader.js";
20
20
  import { getDb } from "../../persistence/db-connection.js";
21
21
  import {
22
+ type Auth,
22
23
  AuthSchema,
23
24
  type ConnectionModel,
24
25
  ConnectionModelSchema,
25
26
  ConnectionProviderSchema,
27
+ deriveAuthForProvider,
26
28
  ProviderConnectionSchema,
27
29
  PROVIDERS_REQUIRING_BASE_URL_AND_MODELS,
28
30
  VALID_CONNECTION_PROVIDERS,
@@ -104,7 +106,9 @@ async function parseCustomProviderFields(
104
106
  throw new BadRequestError(`Invalid base_url: must be an http(s) URL`);
105
107
  }
106
108
  } catch (err) {
107
- if (err instanceof BadRequestError) throw err;
109
+ if (err instanceof BadRequestError) {
110
+ throw err;
111
+ }
108
112
  throw new BadRequestError(
109
113
  `Invalid base_url: must be a valid http(s) URL`,
110
114
  );
@@ -159,6 +163,28 @@ async function parseCustomProviderFields(
159
163
  return out;
160
164
  }
161
165
 
166
+ /**
167
+ * Derive the auth object for a body that omits `auth`, from the provider and
168
+ * the optional top-level `credential` field. Throws the 400s for the cases
169
+ * the derivation can't express: a malformed credential, or a provider that
170
+ * needs an API key when none was supplied.
171
+ */
172
+ function deriveConnectionAuth(provider: string, credential: unknown): Auth {
173
+ if (
174
+ credential !== undefined &&
175
+ (typeof credential !== "string" || credential.length === 0)
176
+ ) {
177
+ throw new BadRequestError("credential must be a non-empty string");
178
+ }
179
+ const derived = deriveAuthForProvider(provider, credential);
180
+ if (!derived) {
181
+ throw new BadRequestError(
182
+ `Provider "${provider}" requires an API key. Pass "credential" (a vault credential key) or an explicit "auth" object.`,
183
+ );
184
+ }
185
+ return derived;
186
+ }
187
+
162
188
  // ---------------------------------------------------------------------------
163
189
  // Handlers
164
190
  // ---------------------------------------------------------------------------
@@ -174,10 +200,14 @@ function handleListConnections({ queryParams = {} }: RouteHandlerArgs) {
174
200
 
175
201
  function handleGetConnection({ pathParams = {} }: RouteHandlerArgs) {
176
202
  const { name } = pathParams;
177
- if (!name) throw new BadRequestError("name is required");
203
+ if (!name) {
204
+ throw new BadRequestError("name is required");
205
+ }
178
206
 
179
207
  const conn = getConnection(getDb(), name);
180
- if (!conn) throw new NotFoundError(`Connection "${name}" not found.`);
208
+ if (!conn) {
209
+ throw new NotFoundError(`Connection "${name}" not found.`);
210
+ }
181
211
 
182
212
  return conn;
183
213
  }
@@ -197,7 +227,9 @@ async function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
197
227
  `Invalid provider "${String(provider)}". Valid: ${VALID_CONNECTION_PROVIDERS.join(", ")}`,
198
228
  );
199
229
  }
200
- const authResult = AuthSchema.safeParse(auth);
230
+ const authResult = AuthSchema.safeParse(
231
+ auth ?? deriveConnectionAuth(providerResult.data, body.credential),
232
+ );
201
233
  if (!authResult.success) {
202
234
  throw new BadRequestError(`Invalid auth: ${authResult.error.message}`);
203
235
  }
@@ -239,12 +271,12 @@ async function handleCreateConnection({ body = {} }: RouteHandlerArgs) {
239
271
  }
240
272
  if (result.error.code === "base_url_required") {
241
273
  throw new BadRequestError(
242
- "base_url is required for openai-compatible connections.",
274
+ "base_url is required for openai-compatible providers.",
243
275
  );
244
276
  }
245
277
  if (result.error.code === "models_required") {
246
278
  throw new BadRequestError(
247
- "At least one model is required for openai-compatible connections.",
279
+ "At least one model is required for openai-compatible providers.",
248
280
  );
249
281
  }
250
282
  throw new BadRequestError("Invalid auth configuration.");
@@ -258,12 +290,36 @@ async function handleUpdateConnection({
258
290
  body = {},
259
291
  }: RouteHandlerArgs) {
260
292
  const { name } = pathParams;
261
- if (!name) throw new BadRequestError("name is required");
293
+ if (!name) {
294
+ throw new BadRequestError("name is required");
295
+ }
262
296
 
263
297
  const existing = getConnection(getDb(), name);
264
- if (!existing) throw new NotFoundError(`Connection "${name}" not found.`);
298
+ if (!existing) {
299
+ throw new NotFoundError(`Connection "${name}" not found.`);
300
+ }
265
301
 
266
- const auth = body.auth;
302
+ // `auth` is optional: an explicit object wins; a bare `credential` rotates
303
+ // the key by re-deriving from the provider; omitting both leaves the stored
304
+ // auth untouched (so label-only edits never disturb e.g. an
305
+ // oauth_subscription connection).
306
+ if (
307
+ body.auth === undefined &&
308
+ body.credential !== undefined &&
309
+ existing.auth.type === "oauth_subscription"
310
+ ) {
311
+ // Derivation would silently flip the auth type to api_key. Rotating a
312
+ // subscription token goes through the ChatGPT sign-in routes; switching
313
+ // to key auth requires an explicit `auth` object.
314
+ throw new BadRequestError(
315
+ `Connection "${name}" uses subscription auth, which "credential" cannot rotate. Re-run the ChatGPT sign-in flow, or pass an explicit "auth" object to switch auth types.`,
316
+ );
317
+ }
318
+ const auth =
319
+ body.auth ??
320
+ (body.credential !== undefined
321
+ ? deriveConnectionAuth(existing.provider, body.credential)
322
+ : existing.auth);
267
323
  const authResult = AuthSchema.safeParse(auth);
268
324
  if (!authResult.success) {
269
325
  throw new BadRequestError(`Invalid auth: ${authResult.error.message}`);
@@ -307,12 +363,12 @@ async function handleUpdateConnection({
307
363
  }
308
364
  if (result.error.code === "base_url_required") {
309
365
  throw new BadRequestError(
310
- "base_url is required for openai-compatible connections.",
366
+ "base_url is required for openai-compatible providers.",
311
367
  );
312
368
  }
313
369
  if (result.error.code === "models_required") {
314
370
  throw new BadRequestError(
315
- "At least one model is required for openai-compatible connections.",
371
+ "At least one model is required for openai-compatible providers.",
316
372
  );
317
373
  }
318
374
  throw new BadRequestError("Invalid auth configuration.");
@@ -323,9 +379,11 @@ async function handleUpdateConnection({
323
379
 
324
380
  function handleDeleteConnection({ pathParams = {} }: RouteHandlerArgs) {
325
381
  const { name } = pathParams;
326
- if (!name) throw new BadRequestError("name is required");
382
+ if (!name) {
383
+ throw new BadRequestError("name is required");
384
+ }
327
385
 
328
- // Existence check first so a stale `llm.default.provider_connection`
386
+ // Existence check first so a stale profile `provider_connection`
329
387
  // reference to a missing connection returns 404 (not 409).
330
388
  const existing = getConnection(getDb(), name);
331
389
  if (!existing) {
@@ -345,17 +403,6 @@ function handleDeleteConnection({ pathParams = {} }: RouteHandlerArgs) {
345
403
 
346
404
  const config = getConfigReadOnly();
347
405
 
348
- // llm.default carries provider_connection (LLMConfigBase).
349
- if (
350
- (config.llm?.default as Record<string, unknown> | undefined)
351
- ?.provider_connection === name
352
- ) {
353
- throw new ConflictError(
354
- `Connection "${name}" is referenced by llm.default. Update llm.default.provider_connection before deleting.`,
355
- { referencedBy: ["llm.default"] },
356
- );
357
- }
358
-
359
406
  // llm.defaultProvider: guards both the resolved connection name (explicit
360
407
  // `connectionName` or the `<provider>-personal` convention) and the case
361
408
  // where the convention name is dangling but this is the last remaining
@@ -471,12 +518,13 @@ export const ROUTES: RouteDefinition[] = [
471
518
  },
472
519
  summary: "Create a provider connection",
473
520
  description:
474
- "Create a new named provider connection. Fails with 409 if a connection with this name already exists.",
521
+ "Create a new named provider connection. When auth is omitted it is derived from the provider (keyless providers get none, vellum gets platform, everything else needs credential for api_key auth). Fails with 409 if a connection with this name already exists.",
475
522
  tags: ["inference"],
476
523
  requestBody: z.object({
477
524
  name: z.string().min(1),
478
525
  provider: ConnectionProviderSchema,
479
- auth: AuthSchema,
526
+ auth: AuthSchema.optional(),
527
+ credential: z.string().min(1).optional(),
480
528
  label: z.string().min(1).optional(),
481
529
  base_url: z.string().url().nullable().optional(),
482
530
  models: z.array(ConnectionModelSchema).nullable().optional(),
@@ -499,11 +547,12 @@ export const ROUTES: RouteDefinition[] = [
499
547
  },
500
548
  summary: "Update a provider connection",
501
549
  description:
502
- "Update an existing connection. Cannot rename or change the provider. For the Vellum-managed connection (vellum) the auth is locked to platform; label remains editable.",
550
+ "Update an existing connection. Cannot rename or change the provider. Omitting auth keeps the stored auth; passing credential alone rotates the key via provider-derived api_key auth. For the Vellum-managed connection (vellum) the auth is locked to platform; label remains editable.",
503
551
  tags: ["inference"],
504
552
  pathParams: [{ name: "name", description: "Connection name" }],
505
553
  requestBody: z.object({
506
- auth: AuthSchema,
554
+ auth: AuthSchema.optional(),
555
+ credential: z.string().min(1).optional(),
507
556
  label: z.string().min(1).nullable().optional(),
508
557
  base_url: z.string().url().nullable().optional(),
509
558
  models: z.array(ConnectionModelSchema).nullable().optional(),
@@ -53,7 +53,7 @@ async function handleInferenceSend({ body = {} }: RouteHandlerArgs) {
53
53
  });
54
54
  if (!provider) {
55
55
  throw new BadRequestError(
56
- "No LLM provider is configured. Run 'assistant config set llm.default.provider <provider>' to set one up.",
56
+ "No LLM provider is configured. Connect a provider (assistant credentials) or set llm.defaultProvider to choose one.",
57
57
  );
58
58
  }
59
59
 
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { z } from "zod";
18
18
 
19
- import { getCachedShareAnalytics } from "../../platform/consent-cache.js";
19
+ import { getRawShareAnalytics } from "../../platform/consent-cache.js";
20
20
  import {
21
21
  type AuthFallbackCount,
22
22
  recordAuthFallbackCounts,
@@ -61,15 +61,16 @@ function handleRecordAuthFallback({ body }: RouteHandlerArgs) {
61
61
 
62
62
  const recorded = recordAuthFallbackCounts(window_start, window_end, mapped);
63
63
  if (recorded === 0) {
64
- if (getCachedShareAnalytics()) {
65
- // Consent is on but nothing was recorded (the body guarantees counts),
66
- // so the telemetry DB is unavailable. Fail so the gateway's reporter
67
- // merges the batch back and retries next flush instead of losing it.
64
+ if (getRawShareAnalytics() !== false) {
65
+ // Consent is not a confirmed opt-out but nothing was recorded (the
66
+ // body guarantees counts), so the telemetry DB is unavailable. Fail so
67
+ // the gateway's reporter merges the batch back and retries next flush
68
+ // instead of losing it.
68
69
  throw new ServiceUnavailableError(
69
70
  "Telemetry database unavailable; retry the auth-fallback batch",
70
71
  );
71
72
  }
72
- // share_analytics consent is off: drop the counts to honor the opt-out.
73
+ // Confirmed share_analytics opt-out: drop the counts to honor it.
73
74
  return { skipped: true };
74
75
  }
75
76
  log.debug({ recorded }, "Recorded auth-fallback counts");
@@ -91,6 +91,7 @@ import {
91
91
  import { getPlatformBaseUrl } from "../../config/env.js";
92
92
  import { isPluginDisabled } from "../../plugins/disabled-state.js";
93
93
  import { ensurePluginApiShim } from "../../plugins/ensure-plugin-api-shim.js";
94
+ import { reconcilePluginSourcesNow } from "../../plugins/mtime-cache.js";
94
95
  import { getLocalCategorySlugs } from "../../skills/categories-cache.js";
95
96
  import { getLogger } from "../../util/logger.js";
96
97
  import { getWorkspacePluginsDir } from "../../util/platform.js";
@@ -1092,7 +1093,15 @@ async function handleUninstallPlugin({
1092
1093
  await ensurePluginApiShim().catch(() => {});
1093
1094
 
1094
1095
  try {
1096
+ // `uninstallPlugin` runs the plugin's `shutdown` hook (while its files are
1097
+ // still present) and then removes the directory — both in this daemon
1098
+ // process. Following it with a reconcile drops the plugin's now-absent
1099
+ // tools and hooks from the in-memory caches immediately (the `shutdown`
1100
+ // already ran, so the reconcile's deactivation does not run it again),
1101
+ // rather than leaving that to the background source watcher. Symmetric to
1102
+ // the install route, which reconciles to bring a new plugin up.
1095
1103
  const result = await uninstallPlugin({ name: rawName });
1104
+ await reconcilePluginSourcesNow();
1096
1105
  publishPluginsChanged(getOriginClientId(headers));
1097
1106
  return { name: result.name, target: result.target };
1098
1107
  } catch (err) {
@@ -1228,6 +1237,12 @@ async function handleInstallPlugin({ body = {}, headers }: RouteHandlerArgs) {
1228
1237
  { fetch: globalThis.fetch.bind(globalThis) },
1229
1238
  );
1230
1239
  }
1240
+ // Bring the freshly materialized plugin up in-process right now — register
1241
+ // its tools and run its `init` hook — instead of waiting for the resource
1242
+ // monitor to republish the sentinel and a later turn to apply it. This is
1243
+ // what makes `init` fire as part of the install rather than at the next
1244
+ // daemon boot. Never throws; a failure is contained and logged.
1245
+ await reconcilePluginSourcesNow();
1231
1246
  publishPluginsChanged(getOriginClientId(headers));
1232
1247
  return {
1233
1248
  ok: true as const,
@@ -1403,6 +1418,21 @@ async function handleUpgradePlugin({
1403
1418
  { name: rawName, dryRun, strategy },
1404
1419
  { fetch: globalThis.fetch.bind(globalThis) },
1405
1420
  );
1421
+ // An upgrade that actually moved files (outcome `upgraded`) rewrites the
1422
+ // plugin's on-disk source; bring the change up in-process right now — run
1423
+ // the old version's `shutdown` and the new version's `init` via the
1424
+ // reconcile — instead of waiting for the resource monitor to republish the
1425
+ // sentinel and a later turn to apply it. This is what makes the lifecycle
1426
+ // fire as part of the upgrade rather than at the next daemon boot,
1427
+ // symmetric to the install and uninstall routes. A no-op or dry run leaves
1428
+ // the tree unchanged, so there is nothing to reconcile. Ensure the
1429
+ // workspace `@vellumai/plugin-api` shim first (as uninstall does) so a hook
1430
+ // that imports the package resolves even inside the daemon boot window.
1431
+ // Never throws; a failure is contained and logged.
1432
+ if (result.outcome === "upgraded") {
1433
+ await ensurePluginApiShim().catch(() => {});
1434
+ await reconcilePluginSourcesNow();
1435
+ }
1406
1436
  publishPluginsChanged(getOriginClientId(headers));
1407
1437
  return {
1408
1438
  name: result.name,
@@ -1435,9 +1465,10 @@ async function handleUpgradePlugin({
1435
1465
  if (err instanceof PluginMergeBaselineError) {
1436
1466
  throw new ConflictError(err.message);
1437
1467
  }
1438
- // The install exists but has no marketplace entry to advance to a
1439
- // permanent state the caller cannot resolve by retrying. 409 marks the
1440
- // request as well-formed but not actionable in the current state.
1468
+ // The install has neither a marketplace entry nor a recorded GitHub source
1469
+ // to advance to — a permanent state the caller cannot resolve by retrying.
1470
+ // 409 marks the request as well-formed but not actionable in the current
1471
+ // state.
1441
1472
  if (err instanceof PluginNotUpgradableError) {
1442
1473
  throw new ConflictError(err.message);
1443
1474
  }
@@ -1866,9 +1897,9 @@ export const ROUTES: RouteDefinition[] = [
1866
1897
  requiredScopes: ["settings.write"],
1867
1898
  allowedPrincipalTypes: ACTOR_PRINCIPALS,
1868
1899
  },
1869
- summary: "Upgrade a plugin to the marketplace pin",
1900
+ summary: "Upgrade a plugin to its source's current revision",
1870
1901
  description:
1871
- 'Move an installed plugin to the marketplace\'s current pinned commit, re-materializing it under `<workspaceDir>/plugins/<name>/`. Always resolves against the curated marketplace pin (no caller-supplied ref), mirroring `plugins install`\'s curation boundary. A no-op (`outcome: "already-up-to-date"`) when the installed commit already equals the pin; pass `dryRun` to preview the move (`outcome: "would-upgrade"`) without touching the install. Installs lacking provenance are re-pinned to the current SHA. The upgraded code is picked up live on the next read (no restart required). `strategy` controls how local edits are reconciled: `overwrite` (default) discards them and re-installs the pin wholesale; `ours`/`theirs`/`assistant` perform a three-way merge against the re-materialized install commit, carrying non-conflicting edits from both sides forward and resolving conflicting hunks toward the local edit (`ours`) or the pin (`theirs`), or writing git conflict markers into the file and reporting them in `conflicts`/`binaryConflicts` for the assistant to resolve (`assistant`). A merge strategy whose install-time baseline cannot be reconstructed returns 409. Mirrors the CLI\'s `assistant plugins upgrade <name> [--strategy <s>]`.',
1902
+ 'Move an installed plugin to its source\'s current revision, re-materializing it under `<workspaceDir>/plugins/<name>/`. A marketplace plugin advances to the curated pin; a plugin installed directly from a GitHub URL (untrusted, not in the marketplace) advances to whatever its recorded ref now resolves to — a pinned SHA is immutable (a no-op), a branch/tag/HEAD moves as upstream does — re-materialized verbatim with no curated adapter overlay, exactly as the original untrusted install was. The target ref is never taken from the request (no caller-supplied ref), mirroring `plugins install`\'s curation boundary. A no-op (`outcome: "already-up-to-date"`) when the installed commit already equals the target; pass `dryRun` to preview the move (`outcome: "would-upgrade"`) without touching the install. Installs lacking provenance are re-pinned to the current SHA. The upgraded code is picked up live on the next read (no restart required). `strategy` controls how local edits are reconciled: `overwrite` (default) discards them and re-installs the target wholesale; `ours`/`theirs`/`assistant` perform a three-way merge against the re-materialized install commit, carrying non-conflicting edits from both sides forward and resolving conflicting hunks toward the local edit (`ours`) or the target (`theirs`), or writing git conflict markers into the file and reporting them in `conflicts`/`binaryConflicts` for the assistant to resolve (`assistant`). A merge strategy whose install-time baseline cannot be reconstructed returns 409. Mirrors the CLI\'s `assistant plugins upgrade <name> [--strategy <s>]`.',
1872
1903
  tags: ["plugins"],
1873
1904
  pathParams: [
1874
1905
  {
@@ -1891,7 +1922,7 @@ export const ROUTES: RouteDefinition[] = [
1891
1922
  },
1892
1923
  "409": {
1893
1924
  description:
1894
- "The install exists but has no marketplace entry to advance to, or a merge strategy was requested whose install-time baseline cannot be reconstructed.",
1925
+ "The install has neither a marketplace entry nor a recorded GitHub source to advance to, or a merge strategy was requested whose install-time baseline cannot be reconstructed.",
1895
1926
  },
1896
1927
  "503": {
1897
1928
  description: