@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
@@ -33,11 +33,31 @@ export class ToolProfiler {
33
33
  startRequest(): void {
34
34
  this.tools.clear();
35
35
  this.requestStartMs = Date.now();
36
- const rss = process.memoryUsage().rss;
36
+ const rss = this.sampleRssBytes() ?? 0;
37
37
  this.rssStartBytes = rss;
38
38
  this.peakRssBytes = rss;
39
39
  }
40
40
 
41
+ /**
42
+ * Sample the current RSS in bytes, returning `null` if the reading fails.
43
+ *
44
+ * Bun 1.3.11 can throw `SystemError: Failed to get memory usage, errno: 2`
45
+ * from the underlying syscall. Resource metrics are best-effort — a failed
46
+ * sample must never propagate, so it is logged and reported as `null` and the
47
+ * caller keeps its last known peak.
48
+ */
49
+ private sampleRssBytes(): number | null {
50
+ try {
51
+ return process.memoryUsage().rss;
52
+ } catch (err) {
53
+ log.warn(
54
+ { err },
55
+ "Failed to sample RSS memory; skipping profiler sample",
56
+ );
57
+ return null;
58
+ }
59
+ }
60
+
41
61
  recordToolCompletion(
42
62
  toolName: string,
43
63
  durationMs: number,
@@ -57,8 +77,8 @@ export class ToolProfiler {
57
77
  stats.errors++;
58
78
  }
59
79
 
60
- const currentRss = process.memoryUsage().rss;
61
- if (currentRss > this.peakRssBytes) {
80
+ const currentRss = this.sampleRssBytes();
81
+ if (currentRss !== null && currentRss > this.peakRssBytes) {
62
82
  this.peakRssBytes = currentRss;
63
83
  }
64
84
  }
@@ -156,6 +176,10 @@ export function startToolProfilingRequest(conversationId: string): void {
156
176
  /**
157
177
  * Record a completed tool invocation into the conversation's profiler.
158
178
  * A no-op when no profiling window is active (e.g. standalone tool runs).
179
+ *
180
+ * The tool has already completed by the time this runs, so profiling is pure
181
+ * observation: any failure here is swallowed and logged rather than allowed to
182
+ * propagate into the executor and fail the agent loop.
159
183
  */
160
184
  export function recordToolCompletion(
161
185
  conversationId: string,
@@ -163,9 +187,16 @@ export function recordToolCompletion(
163
187
  durationMs: number,
164
188
  isError: boolean,
165
189
  ): void {
166
- profilersByConversation
167
- .get(conversationId)
168
- ?.recordToolCompletion(toolName, durationMs, isError);
190
+ try {
191
+ profilersByConversation
192
+ .get(conversationId)
193
+ ?.recordToolCompletion(toolName, durationMs, isError);
194
+ } catch (err) {
195
+ log.warn(
196
+ { err, conversationId, toolName },
197
+ "Tool profiler failed to record completion; continuing",
198
+ );
199
+ }
169
200
  }
170
201
 
171
202
  /** Emit the end-of-turn profiling summary log for a conversation. */
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Per-conversation wire projections of the UI surface tool definitions.
3
+ *
4
+ * Slack renders exactly one surface — the task_progress card (everything else
5
+ * is rejected at execution; see `isSlackTaskProgressUiException` in
6
+ * `daemon/conversation-surfaces.ts`) — so Slack conversations get a `ui_show`
7
+ * variant documenting only that shape instead of the full multi-surface docs.
8
+ * Activation-rail conversations additionally get the `activation_moment`
9
+ * telemetry param, which no other conversation pays schema tokens for (the
10
+ * emit path is gated on `isActivationSession` regardless).
11
+ *
12
+ * Projection contract: shared definition objects are never mutated (variants
13
+ * are module-level constants swapped in via spread-copy), and the projected
14
+ * set is a pure function of per-conversation-stable state, so a
15
+ * conversation's tools+system prompt-cache prefix stays stable across turns.
16
+ */
17
+
18
+ import type { ToolDefinition } from "../../providers/types.js";
19
+ import { ACTIVATION_MOMENT_PARAMS } from "../../telemetry/activation-funnel.js";
20
+ import { TASK_PROGRESS_TEMPLATE_SHAPE } from "./surface-shape-docs.js";
21
+
22
+ const SLACK_UI_SHOW_DESCRIPTION = `Show a live task-progress card for the current work — the only surface this channel renders. data: { template: "task_progress", templateData: ${TASK_PROGRESS_TEMPLATE_SHAPE} }. Show it early on multi-step turns and advance step statuses via ui_update as work progresses.`;
23
+
24
+ const SLACK_UI_SHOW_INPUT_SCHEMA = {
25
+ type: "object",
26
+ properties: {
27
+ surface_type: {
28
+ type: "string",
29
+ enum: ["card"],
30
+ description: "Always card on this channel",
31
+ },
32
+ title: {
33
+ type: "string",
34
+ description: "Optional surface title",
35
+ },
36
+ data: {
37
+ type: "object",
38
+ description:
39
+ 'Card data: { template: "task_progress", templateData: { title, status, steps } }',
40
+ },
41
+ },
42
+ required: ["surface_type", "data"],
43
+ };
44
+
45
+ /**
46
+ * Swap UI surface tool definitions for channel-appropriate variants. Returns
47
+ * the input array unchanged (same identity) for channels without a variant.
48
+ */
49
+ export function projectUiToolsForChannel(
50
+ definitions: ToolDefinition[],
51
+ channel: string | undefined,
52
+ ): ToolDefinition[] {
53
+ if (channel !== "slack") {
54
+ return definitions;
55
+ }
56
+ return definitions.map((def) =>
57
+ def.name === "ui_show"
58
+ ? {
59
+ ...def,
60
+ description: SLACK_UI_SHOW_DESCRIPTION,
61
+ input_schema: SLACK_UI_SHOW_INPUT_SCHEMA,
62
+ }
63
+ : def,
64
+ );
65
+ }
66
+
67
+ const ACTIVATION_MOMENT_PROPERTY = {
68
+ type: "string",
69
+ enum: ACTIVATION_MOMENT_PARAMS,
70
+ description:
71
+ "Activation-rail telemetry tag. Set this when this surface IS one of the activation funnel moments; the milestone is recorded automatically when the user commits the surface. Omit for all non-activation surfaces.",
72
+ };
73
+
74
+ /**
75
+ * Add the optional `activation_moment` param to ui_show's schema. Applied
76
+ * only for activation-rail conversations (`isActivationSession`) — the rail
77
+ * bootstrap prompt instructs the tagging, and the daemon's emit path reads
78
+ * the tag from tool input independently of this schema.
79
+ */
80
+ export function injectActivationMomentParam(
81
+ definitions: ToolDefinition[],
82
+ ): ToolDefinition[] {
83
+ return definitions.map((def) => {
84
+ if (def.name !== "ui_show") {
85
+ return def;
86
+ }
87
+ const schema = def.input_schema as {
88
+ properties?: Record<string, unknown>;
89
+ };
90
+ return {
91
+ ...def,
92
+ input_schema: {
93
+ ...schema,
94
+ properties: {
95
+ ...schema.properties,
96
+ activation_moment: ACTIVATION_MOMENT_PROPERTY,
97
+ },
98
+ },
99
+ };
100
+ });
101
+ }
@@ -2,19 +2,25 @@
2
2
  * UI surface tool definitions.
3
3
  *
4
4
  * These tools allow the model to show, update, and dismiss just-in-time UI
5
- * surfaces (cards, forms, lists, confirmations) on a connected macOS client.
5
+ * surfaces (cards, tables, forms, confirmations) on a connected macOS client.
6
6
  * They are proxy tools -- execution is forwarded to the client and never
7
7
  * handled locally by the daemon.
8
8
  */
9
9
 
10
10
  import { RiskLevel } from "../../permissions/types.js";
11
11
  import { isWeakOpenModel } from "../../providers/weak-open-model.js";
12
- import { ACTIVATION_MOMENT_PARAMS } from "../../telemetry/activation-funnel.js";
13
12
  import type {
14
13
  ToolContext,
15
14
  ToolDefinition,
16
15
  ToolExecutionResult,
17
16
  } from "../types.js";
17
+ import {
18
+ asRecord,
19
+ hasContent,
20
+ SURFACE_TYPE_NAMES,
21
+ UI_SHOW_TYPE_DOCS,
22
+ uiShowTeachingError,
23
+ } from "./surface-shape-docs.js";
18
24
 
19
25
  // ---------------------------------------------------------------------------
20
26
  // Helpers
@@ -36,6 +42,13 @@ function proxyExecute(toolName: string) {
36
42
  input: Record<string, unknown>,
37
43
  context: ToolContext,
38
44
  ): Promise<ToolExecutionResult> => {
45
+ if (toolName === "ui_show") {
46
+ const teachingError = uiShowTeachingError(input);
47
+ if (teachingError !== null) {
48
+ return { content: teachingError, isError: true };
49
+ }
50
+ }
51
+
39
52
  if (toolName === "ui_show" && isEmptyDynamicPage(input)) {
40
53
  return {
41
54
  content: isWeakOpenModel(context.attribution?.resolvedModel)
@@ -135,22 +148,6 @@ function isEmptyUpdate(input: Record<string, unknown>): boolean {
135
148
  return data === null || !hasContent(data);
136
149
  }
137
150
 
138
- function hasContent(value: unknown): boolean {
139
- if (value === null || value === undefined) {
140
- return false;
141
- }
142
- if (Array.isArray(value)) {
143
- return value.length > 0;
144
- }
145
- if (typeof value === "object") {
146
- return Object.values(value).some(hasContent);
147
- }
148
- if (typeof value === "string") {
149
- return value.trim().length > 0;
150
- }
151
- return true;
152
- }
153
-
154
151
  function isEmptyDynamicPage(input: Record<string, unknown>): boolean {
155
152
  if (input.surface_type !== "dynamic_page") {
156
153
  return false;
@@ -211,12 +208,6 @@ function collectRoutingText(input: Record<string, unknown>): string[] {
211
208
  return values;
212
209
  }
213
210
 
214
- function asRecord(value: unknown): Record<string, unknown> | null {
215
- return value !== null && typeof value === "object"
216
- ? (value as Record<string, unknown>)
217
- : null;
218
- }
219
-
220
211
  function addString(values: string[], value: unknown): void {
221
212
  if (typeof value === "string") {
222
213
  values.push(value);
@@ -231,21 +222,7 @@ export const uiShowTool = {
231
222
  name: "ui_show",
232
223
  description:
233
224
  'Surface structured data or UI in the conversation. For long-form writing use the document skill. For interactive apps, dashboards, games, calculators, or durable tools, call `skill_load` with `skill: "app-builder"` and use the app-builder workflow; do not use `dynamic_page` as a substitute for a persistent app. App-like `dynamic_page` calls are rejected.\n\n' +
234
- "Surface types (data shapes):\n" +
235
- '- card: { title, subtitle?, body, metadata?: [{ label, value }], template?, templateData? }. Templates: "weather_forecast" (native weather widget), "task_progress" (live step tracker - update via ui_update on data.templateData; shape: { title, status: "in_progress"|"completed"|"failed", steps: [{ label, status: "pending"|"in_progress"|"completed"|"failed", detail? }] })\n' +
236
- "- copy_block: { text, label?, language? }. Shows copyable text with a visible copy button; use for prompts, commands, paths, or snippets the user should copy.\n" +
237
- '- choice: { description?, options: [{ id, title, description?, recommended?, data? }], selectionMode?: "single"|"multiple", commitOnSelect?, submitLabel? }. Single-select choices commit on option click by default. Use for outcome offers and follow-up choices; mark the strongest option with recommended: true.\n' +
238
- "- oauth_connect: { providerKey, displayName?, description?, logoUrl? }. Shows a managed OAuth connection CTA in chat; use when the current task needs a managed integration account (Google, Linear, GitHub, etc.) instead of asking the user to visit settings or attempting OAuth through shell/tools. The client supplies the CTA label. Do not include OAuth scopes in the surface; managed providers use the platform's configured scopes.\n" +
239
- '- table: { columns: [{ id, label }], rows: [{ id, cells: Record<id, string | { text, icon?, iconColor?: "success"|"warning"|"error"|"muted" }>, selectable?, selected? }], selectionMode?: "none"|"single"|"multiple", caption? }\n' +
240
- '- form: { description?, fields: [{ id, type: "text"|"textarea"|"select"|"toggle"|"number"|"password", label, placeholder?, required?, defaultValue?, options?: [{ label, value }] }], submitLabel? }. Multi-page: { pages: [{ id, title, description?, fields }], pageLabels?: { next?, back?, submit? }, submitLabel? }\n' +
241
- '- list: { items: [{ id, title, subtitle?, icon?, selected? }], selectionMode: "single"|"multiple"|"none" }\n' +
242
- "- confirmation: { message, detail?, confirmLabel?, confirmedLabel?, cancelLabel?, destructive? }\n" +
243
- "- dynamic_page: { html, width?, height?, preview?: { title, subtitle?, description?, icon?, metrics?: [{ label, value }] } }\n" +
244
- "- file_upload: { prompt, acceptedTypes?, maxFiles? }\n" +
245
- "- task_preferences: {} (no data needed — categories are rendered client-side)\n" +
246
- '- work_result: { eyebrow?, status?: "completed"|"partial"|"failed"|"in_progress", summary?, metrics?: [{ label, value, detail?, tone?: "neutral"|"positive"|"warning"|"negative" }], sections?: [{ id?, title, description?, type?: "items"|"timeline"|"diff"|"artifacts"|"warnings", items?: [{ id?, title, description?, status?, tone?, metadata?: [{ label, value }], href? }], diffs?: [{ label?, before?, after? }] }] }. Shows a structured receipt after real work: what changed, what was skipped, proof points, and next actions. Keep display-only unless explicit follow-up buttons are needed.\n' +
247
- '- channel_setup: { channel: "slack" | "telegram" | "phone" }. Opens the channel setup panel in a side drawer. Returns success only after a connected client confirms the panel rendered (an error means the user does NOT see the panel — never claim it is open after an error). The user then completes credential entry at their own pace. Slack shows a full setup wizard; Telegram and Phone show credential forms (the assistant handles remaining setup steps like webhooks in chat after the user saves credentials).\n\n' +
248
- "For multi-step or long-running turns (web searches, file operations, research), show a task_progress card early and keep its steps updated as work progresses. Coarse steps are fine, and you can add or revise them as the work takes shape — a rough card beats no signal.",
225
+ UI_SHOW_TYPE_DOCS,
249
226
  category: "ui-surface",
250
227
  defaultRiskLevel: RiskLevel.Low,
251
228
  executionTarget: "host",
@@ -255,26 +232,12 @@ export const uiShowTool = {
255
232
  properties: {
256
233
  surface_type: {
257
234
  type: "string",
258
- enum: [
259
- "card",
260
- "channel_setup",
261
- "choice",
262
- "copy_block",
263
- "oauth_connect",
264
- "form",
265
- "list",
266
- "table",
267
- "confirmation",
268
- "dynamic_page",
269
- "file_upload",
270
- "task_preferences",
271
- "work_result",
272
- ],
235
+ enum: SURFACE_TYPE_NAMES,
273
236
  description: "The type of surface to display",
274
237
  },
275
238
  title: {
276
239
  type: "string",
277
- description: "Optional title for the surface window",
240
+ description: "Optional surface title",
278
241
  },
279
242
  data: {
280
243
  type: "object",
@@ -286,12 +249,11 @@ export const uiShowTool = {
286
249
  items: {
287
250
  type: "object",
288
251
  properties: {
289
- id: { type: "string", description: "Unique action identifier" },
290
- label: { type: "string", description: "Button label text" },
252
+ id: { type: "string" },
253
+ label: { type: "string" },
291
254
  style: {
292
255
  type: "string",
293
256
  enum: ["primary", "secondary", "destructive"],
294
- description: "Visual style of the button",
295
257
  },
296
258
  },
297
259
  required: ["id", "label"],
@@ -302,23 +264,17 @@ export const uiShowTool = {
302
264
  type: "string",
303
265
  enum: ["inline", "panel"],
304
266
  description:
305
- 'Where to render the surface. "inline" embeds it in the chat message. "panel" shows a floating window. Defaults to "inline". Prefer inline — only use panel when a separate window is explicitly requested.',
267
+ 'Where to render: "inline" (default, embedded in chat) or "panel" (floating window; only when explicitly requested)',
306
268
  },
307
269
  await_action: {
308
270
  type: "boolean",
309
271
  description:
310
- "Whether to block until an action is selected. Defaults to true when actions are provided.",
272
+ "Block until an action is selected. Defaults to true when actions are provided.",
311
273
  },
312
274
  persistent: {
313
275
  type: "boolean",
314
276
  description:
315
- "When true, clicking an action does not dismiss the surface the card stays visible and only the clicked action is marked as spent. Use for launcher or menu-style cards where multiple buttons may be clicked. Defaults to false.",
316
- },
317
- activation_moment: {
318
- type: "string",
319
- enum: ACTIVATION_MOMENT_PARAMS,
320
- description:
321
- "Activation-rail telemetry tag (cohort only). Set this when this surface IS one of the activation funnel moments; the milestone is recorded automatically when the user commits the surface. Omit for all non-activation surfaces.",
277
+ "Keep the surface visible after an action is clicked (clicked actions are marked spent). For launcher/menu cards. Defaults to false.",
322
278
  },
323
279
  },
324
280
  required: ["surface_type", "data"],
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Point-of-use shape documentation and essential-content guards for ui_show
3
+ * surfaces.
4
+ *
5
+ * Each entry documents one surface type: a one-phrase `purpose` (used in the
6
+ * unknown-type index error) and the full data `shape` spec (returned inside a
7
+ * teaching error when a payload is missing its load-bearing content). Keeping
8
+ * the specs here rather than in the always-present tool description lets the
9
+ * model recover the exact shape at the moment it gets one wrong.
10
+ *
11
+ * Guards are advisory and intentionally minimal: they check only the fields
12
+ * without which the surface renders blank or broken (mirroring the tolerant
13
+ * contract in `api/surfaces.ts` — a renderable payload must never be
14
+ * rejected). Normalization the daemon already performs (e.g. top-level
15
+ * `template`/`title` lifted into card data by `normalizeCardShowData`) must
16
+ * stay accepted, so card has no guard here.
17
+ */
18
+
19
+ export function asRecord(value: unknown): Record<string, unknown> | null {
20
+ return value !== null && typeof value === "object"
21
+ ? (value as Record<string, unknown>)
22
+ : null;
23
+ }
24
+
25
+ /**
26
+ * Whether a value carries any renderable content: a non-empty string, a
27
+ * non-empty array, a non-empty primitive, or an object with at least one
28
+ * content-bearing value (recursively).
29
+ */
30
+ export function hasContent(value: unknown): boolean {
31
+ if (value === null || value === undefined) {
32
+ return false;
33
+ }
34
+ if (Array.isArray(value)) {
35
+ return value.length > 0;
36
+ }
37
+ if (typeof value === "object") {
38
+ return Object.values(value).some(hasContent);
39
+ }
40
+ if (typeof value === "string") {
41
+ return value.trim().length > 0;
42
+ }
43
+ return true;
44
+ }
45
+
46
+ function isNonEmptyString(value: unknown): boolean {
47
+ return typeof value === "string" && value.trim().length > 0;
48
+ }
49
+
50
+ function isNonEmptyArray(value: unknown): boolean {
51
+ return Array.isArray(value) && value.length > 0;
52
+ }
53
+
54
+ interface SurfaceShapeDoc {
55
+ /** One-phrase purpose, shown in the unknown-surface_type index error. */
56
+ purpose: string;
57
+ /** Full `data` shape spec, shown in per-type teaching errors. */
58
+ shape: string;
59
+ /**
60
+ * Returns a description of the missing load-bearing content, or null when
61
+ * the payload has enough to render. Absent for types the daemon normalizes
62
+ * leniently (card), that render fine with defaults (file_upload), or that
63
+ * have bespoke handling (dynamic_page).
64
+ */
65
+ missingContent?: (data: Record<string, unknown>) => string | null;
66
+ }
67
+
68
+ /** templateData shape of a task_progress card (shared with channel variants). */
69
+ export const TASK_PROGRESS_TEMPLATE_SHAPE =
70
+ '{ title, status: "in_progress"|"completed"|"failed", steps: [{ label, status: "pending"|"in_progress"|"completed"|"failed", detail? }] }';
71
+
72
+ export const SURFACE_SHAPE_DOCS: Record<string, SurfaceShapeDoc> = {
73
+ card: {
74
+ purpose: "structured info card, supports templates like task_progress",
75
+ shape: `{ title, subtitle?, body, metadata?: [{ label, value }], template?, templateData? }. Template "task_progress" renders a live step tracker — templateData: ${TASK_PROGRESS_TEMPLATE_SHAPE}; advance it via ui_update as steps finish. Other card templates are documented by the skills that use them`,
76
+ },
77
+ copy_block: {
78
+ purpose: "copyable text with a visible copy button",
79
+ shape:
80
+ "{ text, label?, language? } — shows copyable text with a visible copy button; use for prompts, commands, paths, or snippets the user should copy",
81
+ missingContent: (data) =>
82
+ isNonEmptyString(data.text)
83
+ ? null
84
+ : "`data.text` must be a non-empty string",
85
+ },
86
+ choice: {
87
+ purpose: "clickable options for the user to pick from",
88
+ shape:
89
+ '{ description?, options: [{ id, title, description?, recommended?, data? }], selectionMode?: "single"|"multiple", commitOnSelect?, submitLabel? }. Single-select choices commit on option click by default; mark the strongest option with recommended: true',
90
+ missingContent: (data) =>
91
+ isNonEmptyArray(data.options)
92
+ ? null
93
+ : "`data.options` must be a non-empty array",
94
+ },
95
+ table: {
96
+ purpose: "tabular data, optionally with selectable rows",
97
+ shape:
98
+ '{ columns: [{ id, label }], rows: [{ id, cells: Record<columnId, string | { text, icon?, iconColor?: "success"|"warning"|"error"|"muted" }>, selectable?, selected? }], selectionMode?: "none"|"single"|"multiple", caption? }',
99
+ missingContent: (data) =>
100
+ isNonEmptyArray(data.columns)
101
+ ? null
102
+ : "`data.columns` must be a non-empty array",
103
+ },
104
+ work_result: {
105
+ purpose: "structured receipt after completed work",
106
+ shape:
107
+ '{ eyebrow?, status?: "completed"|"partial"|"failed"|"in_progress", summary?, metrics?: [{ label, value, detail?, tone?: "neutral"|"positive"|"warning"|"negative" }], sections?: [{ id?, title, description?, type?: "items"|"timeline"|"diff"|"artifacts"|"warnings", items?: [{ id?, title, description?, status?, tone?, metadata?: [{ label, value }], href? }], diffs?: [{ label?, before?, after? }] }] } — structured receipt after real work; keep display-only unless follow-up buttons are needed',
108
+ missingContent: (data) =>
109
+ hasContent(data)
110
+ ? null
111
+ : "`data` must carry at least a summary, metrics, or sections",
112
+ },
113
+ oauth_connect: {
114
+ purpose: "managed OAuth connection button for an integration account",
115
+ shape:
116
+ "{ providerKey, displayName?, description?, logoUrl? } — managed OAuth connection CTA; use when the task needs a managed integration account (Google, Linear, GitHub, ...) instead of settings or shell OAuth. Do not include OAuth scopes; managed providers use the platform's configured scopes",
117
+ missingContent: (data) =>
118
+ isNonEmptyString(data.providerKey)
119
+ ? null
120
+ : "`data.providerKey` must name the provider to connect",
121
+ },
122
+ form: {
123
+ purpose: "input form, single or multi-page",
124
+ shape:
125
+ '{ description?, fields: [{ id, type: "text"|"textarea"|"select"|"toggle"|"number"|"password", label, placeholder?, required?, defaultValue?, options?: [{ label, value }] }], submitLabel? }. Multi-page: { pages: [{ id, title, description?, fields }], pageLabels?: { next?, back?, submit? }, submitLabel? }',
126
+ missingContent: (data) =>
127
+ isNonEmptyArray(data.fields) || isNonEmptyArray(data.pages)
128
+ ? null
129
+ : "`data.fields` (or `data.pages`) must be a non-empty array",
130
+ },
131
+ confirmation: {
132
+ purpose: "confirm/cancel prompt",
133
+ shape:
134
+ "{ message, detail?, confirmLabel?, confirmedLabel?, cancelLabel?, destructive? }",
135
+ missingContent: (data) =>
136
+ isNonEmptyString(data.message)
137
+ ? null
138
+ : "`data.message` must be a non-empty string",
139
+ },
140
+ file_upload: {
141
+ purpose: "prompt the user to upload files",
142
+ shape: "{ prompt, acceptedTypes?, maxFiles? }",
143
+ },
144
+ dynamic_page: {
145
+ purpose: "custom HTML page for transient UI only, never app-like builds",
146
+ shape:
147
+ "{ html, width?, height?, preview?: { title, subtitle?, description?, icon?, metrics?: [{ label, value }] } } — custom visual HTML for transient surfaces only, never app-like builds",
148
+ },
149
+ channel_setup: {
150
+ purpose: "open the Slack/Telegram/Phone setup panel",
151
+ shape: '{ channel: "slack" | "telegram" | "phone" }',
152
+ missingContent: (data) =>
153
+ data.channel === "slack" ||
154
+ data.channel === "telegram" ||
155
+ data.channel === "phone"
156
+ ? null
157
+ : '`data.channel` must be one of "slack", "telegram", "phone"',
158
+ },
159
+ };
160
+
161
+ /** Model-facing surface_type enum, derived so docs and schema cannot drift. */
162
+ export const SURFACE_TYPE_NAMES = Object.keys(SURFACE_SHAPE_DOCS);
163
+
164
+ const SURFACE_TYPE_INDEX = SURFACE_TYPE_NAMES.map(
165
+ (name) => `${name} (${SURFACE_SHAPE_DOCS[name]!.purpose})`,
166
+ ).join("; ");
167
+
168
+ /**
169
+ * Types whose full shape rides in the always-present tool description —
170
+ * together they cover ~95% of fleet ui_show calls. The rest appear there as
171
+ * a one-line index and get their shape from the teaching error on first
172
+ * misuse.
173
+ */
174
+ const HOT_SURFACE_TYPES = [
175
+ "card",
176
+ "copy_block",
177
+ "choice",
178
+ "table",
179
+ "work_result",
180
+ "oauth_connect",
181
+ "dynamic_page",
182
+ ] as const;
183
+
184
+ const COLD_SURFACE_INDEX = SURFACE_TYPE_NAMES.filter(
185
+ (name) => !(HOT_SURFACE_TYPES as readonly string[]).includes(name),
186
+ )
187
+ .map((name) => `${name} (${SURFACE_SHAPE_DOCS[name]!.purpose})`)
188
+ .join(", ");
189
+
190
+ /** The surface-types section of the ui_show tool description. */
191
+ export const UI_SHOW_TYPE_DOCS = [
192
+ "Surface types (data shapes):",
193
+ ...HOT_SURFACE_TYPES.map(
194
+ (name) => `- ${name}: ${SURFACE_SHAPE_DOCS[name]!.shape}`,
195
+ ),
196
+ `Other types: ${COLD_SURFACE_INDEX}. Send your best-guess data — if required content is missing, the error returns the exact shape.`,
197
+ ].join("\n");
198
+
199
+ /**
200
+ * Teaching error for a ui_show payload that would render nothing, or null
201
+ * when the payload is displayable. Unknown/missing surface_type gets the
202
+ * full type index; a known type missing its load-bearing content gets that
203
+ * type's exact shape. dynamic_page emptiness is handled by the bespoke
204
+ * model-aware envelopes in `definitions.ts`, not here.
205
+ */
206
+ export function uiShowTeachingError(
207
+ input: Record<string, unknown>,
208
+ ): string | null {
209
+ const surfaceType = input.surface_type;
210
+ if (
211
+ typeof surfaceType !== "string" ||
212
+ !Object.hasOwn(SURFACE_SHAPE_DOCS, surfaceType)
213
+ ) {
214
+ const got =
215
+ typeof surfaceType === "string" && surfaceType.trim().length > 0
216
+ ? `"${surfaceType}" is not a surface type`
217
+ : "`surface_type` is missing";
218
+ return `Error: ui_show was not displayed — ${got}. Valid types: ${SURFACE_TYPE_INDEX}. Resend ui_show with one of these surface_type values; if a type's data is missing required content, the error will include its exact shape.`;
219
+ }
220
+ const doc = SURFACE_SHAPE_DOCS[surfaceType]!;
221
+ if (!doc.missingContent) {
222
+ return null;
223
+ }
224
+ const missing = doc.missingContent(asRecord(input.data) ?? {});
225
+ if (missing === null) {
226
+ return null;
227
+ }
228
+ return `Error: ui_show ${surfaceType} was not displayed — ${missing}, so the user saw nothing. Data shape: ${doc.shape}. Resend ui_show with the missing content filled in.`;
229
+ }
@@ -1520,6 +1520,24 @@ describe("Deepgram TTS provider adapter", () => {
1520
1520
  }
1521
1521
  });
1522
1522
 
1523
+ test("surfaces a friendly, JSON-free message on a 401 auth failure", async () => {
1524
+ mockFetchError(401, '{"err_code":"INVALID_AUTH","err_msg":"expired"}');
1525
+
1526
+ const provider = createDeepgramProvider();
1527
+
1528
+ try {
1529
+ await provider.synthesize(makeRequest());
1530
+ throw new Error("Expected synthesize to throw");
1531
+ } catch (err) {
1532
+ expect(err).toBeInstanceOf(DeepgramTtsError);
1533
+ const message = (err as DeepgramTtsError).message;
1534
+ expect(message).not.toContain("{");
1535
+ expect(message).not.toContain("INVALID_AUTH");
1536
+ expect(message).toContain("API key");
1537
+ expect(message).toContain("Settings");
1538
+ }
1539
+ });
1540
+
1523
1541
  test("throws DEEPGRAM_TTS_EMPTY_RESPONSE on empty audio body", async () => {
1524
1542
  mockFetchReturning(new Uint8Array(0));
1525
1543
 
@@ -54,11 +54,9 @@ const DEFINITIONS = {
54
54
  /**
55
55
  * Definitions in display order (e.g. settings dropdowns).
56
56
  */
57
- // vellum is deliberately absent: managed mode is selected via
58
- // `services.tts.mode`, not the provider picker — today's settings UI writes
59
- // only `provider` + an API key, which the schema rejects for vellum. The
60
- // definition stays in DEFINITIONS so managed-mode resolution works.
57
+ // vellum leads: it is the managed option, selected like any other provider.
61
58
  const CATALOG: readonly TtsProviderDefinition[] = [
59
+ DEFINITIONS.vellum,
62
60
  DEFINITIONS.elevenlabs,
63
61
  DEFINITIONS["fish-audio"],
64
62
  DEFINITIONS.deepgram,
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { getConfig } from "../../config/loader.js";
14
14
  import type { TtsDeepgramProviderConfig } from "../../config/schemas/tts.js";
15
+ import { describeTtsAuthFailure } from "../../providers/voice-error-copy.js";
15
16
  import { getProviderKeyAsync } from "../../security/secure-keys.js";
16
17
  import { getLogger } from "../../util/logger.js";
17
18
  import { resolvePcmOutputSampleRateHz } from "../pcm-sample-rates.js";
@@ -208,9 +209,17 @@ async function performTtsRequest(
208
209
 
209
210
  if (!response.ok) {
210
211
  const errorText = await response.text().catch(() => "");
212
+ log.debug(
213
+ { status: response.status, body: errorText.slice(0, 300) },
214
+ "Deepgram TTS non-200 response",
215
+ );
216
+ const message =
217
+ response.status === 401 || response.status === 403
218
+ ? describeTtsAuthFailure("Deepgram")
219
+ : `Deepgram text-to-speech failed (HTTP ${response.status}).`;
211
220
  throw new DeepgramTtsError(
212
221
  "DEEPGRAM_TTS_HTTP_ERROR",
213
- `Deepgram TTS returned ${response.status}: ${errorText}`,
222
+ message,
214
223
  response.status,
215
224
  );
216
225
  }
@@ -18,4 +18,12 @@ export interface OnboardingContext {
18
18
  initialMessage?: string;
19
19
  /** Skills to eagerly load on first turn (e.g. ["geo-writing", "document-editor"]). Informational — the bootstrap template drives actual loading. */
20
20
  skills?: string[];
21
+ /**
22
+ * Findings from the pre-chat onboarding research that the user explicitly
23
+ * kept on the results screen (removed/rejected claims are excluded
24
+ * client-side). Rendered into the persona's onboarding section so they're
25
+ * available from the very first turn — the greeting turn is barred from
26
+ * `recall`/file reads and would otherwise be blind to them.
27
+ */
28
+ researchFindings?: string[];
21
29
  }