@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
@@ -34,9 +34,9 @@ import {
34
34
  __setCachedShareAnalyticsForTest,
35
35
  __setCachedShareDiagnosticsForTest,
36
36
  __setCachedShareDiagnosticsVersionForTest,
37
- getCachedShareAnalytics,
38
37
  getCachedShareDiagnostics,
39
38
  getCachedShareDiagnosticsVersion,
39
+ getRawShareAnalytics,
40
40
  refreshConsentCache,
41
41
  startConsentRefresh,
42
42
  stopConsentRefresh,
@@ -69,8 +69,9 @@ describe("consent-cache", () => {
69
69
  // Legacy fail-closed opt-out marker read via getConfigReadOnly(). Default
70
70
  // off so existing behavior is unchanged.
71
71
  setConfig("legacyTelemetryOptOut", false);
72
- __setCachedShareAnalyticsForTest(false);
73
- __setCachedShareDiagnosticsForTest(false);
72
+ // Boot state: no confirmed consent yet.
73
+ __setCachedShareAnalyticsForTest("unknown");
74
+ __setCachedShareDiagnosticsForTest("unknown");
74
75
  __setCachedShareDiagnosticsVersionForTest("");
75
76
  });
76
77
 
@@ -78,63 +79,107 @@ describe("consent-cache", () => {
78
79
  await stopConsentRefresh();
79
80
  });
80
81
 
81
- test("defaults to false before any refresh", () => {
82
- expect(getCachedShareAnalytics()).toBe(false);
82
+ test("boot state is unknown", () => {
83
+ expect(getRawShareAnalytics()).toBe("unknown");
83
84
  });
84
85
 
85
- test("stays false when no platform client is available", async () => {
86
+ test("no platform client available → unknown", async () => {
86
87
  mockClient = null;
87
88
  await refreshConsentCache();
88
- expect(getCachedShareAnalytics()).toBe(false);
89
+ expect(getRawShareAnalytics()).toBe("unknown");
90
+ });
91
+
92
+ test("platform features disabled → confirmed false, not unknown", async () => {
93
+ // A config-level platform opt-out is permanent, not a cold cache: no
94
+ // flush will ever drain the outbox, so record-time gates must stay
95
+ // closed (confirmed false) instead of accumulating rows forever.
96
+ __setCachedShareAnalyticsForTest(true);
97
+ process.env.VELLUM_DISABLE_PLATFORM = "1";
98
+ try {
99
+ await refreshConsentCache();
100
+ } finally {
101
+ delete process.env.VELLUM_DISABLE_PLATFORM;
102
+ }
103
+ expect(getRawShareAnalytics()).toBe(false);
104
+ expect(getCachedShareDiagnostics()).toBe(false);
105
+ expect(getCachedShareDiagnosticsVersion()).toBe("");
89
106
  });
90
107
 
91
108
  test("becomes true after a successful fetch reporting shareAnalytics: true", async () => {
92
109
  mockClient = makeClient(makeConsent({ shareAnalytics: true }));
93
110
  await refreshConsentCache();
94
- expect(getCachedShareAnalytics()).toBe(true);
111
+ expect(getRawShareAnalytics()).toBe(true);
95
112
  });
96
113
 
97
114
  test("a null fetch keeps the last known value", async () => {
98
115
  mockClient = makeClient(makeConsent({ shareAnalytics: true }));
99
116
  await refreshConsentCache();
100
- expect(getCachedShareAnalytics()).toBe(true);
117
+ expect(getRawShareAnalytics()).toBe(true);
101
118
 
102
119
  // Transient failure: consent endpoint returns null.
103
120
  mockClient = makeClient(null);
104
121
  await refreshConsentCache();
105
- expect(getCachedShareAnalytics()).toBe(true);
122
+ expect(getRawShareAnalytics()).toBe(true);
106
123
  });
107
124
 
108
- test("a missing client flips a prior opt-in back to false", async () => {
125
+ test("a null fetch keeps a confirmed opt-out", async () => {
126
+ mockClient = makeClient(makeConsent({ shareAnalytics: false }));
127
+ await refreshConsentCache();
128
+ expect(getRawShareAnalytics()).toBe(false);
129
+
130
+ mockClient = makeClient(null);
131
+ await refreshConsentCache();
132
+ expect(getRawShareAnalytics()).toBe(false);
133
+ });
134
+
135
+ test("a null fetch keeps the boot unknown state", async () => {
136
+ mockClient = makeClient(null);
137
+ await refreshConsentCache();
138
+ expect(getRawShareAnalytics()).toBe("unknown");
139
+ expect(getCachedShareDiagnostics()).toBe(false);
140
+ });
141
+
142
+ test("a missing client demotes a prior opt-in to unknown", async () => {
109
143
  __setCachedShareAnalyticsForTest(true);
110
144
  mockClient = null;
111
145
  await refreshConsentCache();
112
- expect(getCachedShareAnalytics()).toBe(false);
146
+ expect(getRawShareAnalytics()).toBe("unknown");
113
147
  });
114
148
 
115
- test("a client without a resolvable assistant identity fails closed", async () => {
149
+ test("a client without a resolvable assistant identity demotes to unknown", async () => {
116
150
  __setCachedShareAnalyticsForTest(true);
117
151
  // Identity cleared mid-session (e.g. assistantId emptied while base URL +
118
152
  // API key persist). getOwnerConsent must not be relied upon to preserve the
119
- // prior opt-in here — fail closed instead.
153
+ // prior opt-in here — losing the owner mapping is not an opt-out, but the
154
+ // stale opt-in must not keep riding either.
120
155
  mockClient = makeClient(makeConsent({ shareAnalytics: true }), "");
121
156
  await refreshConsentCache();
122
- expect(getCachedShareAnalytics()).toBe(false);
157
+ expect(getRawShareAnalytics()).toBe("unknown");
158
+ });
159
+
160
+ test("legacy opt-out marker is honored at boot, before any refresh", () => {
161
+ // The marker is read live from config: a workspace carrying
162
+ // legacyTelemetryOptOut=true must gate recording in the window between
163
+ // daemon start and the first consent refresh (unknown-consent recording
164
+ // would otherwise leak events in that window).
165
+ setConfig("legacyTelemetryOptOut", true);
166
+ expect(getRawShareAnalytics()).toBe(false);
123
167
  });
124
168
 
125
169
  test("legacy opt-out marker keeps analytics off despite platform opt-in", async () => {
126
170
  setConfig("legacyTelemetryOptOut", true);
127
171
  mockClient = makeClient(makeConsent({ shareAnalytics: true }));
128
172
  await refreshConsentCache();
129
- // Platform reports opt-in, but the fail-closed marker forces off.
130
- expect(getCachedShareAnalytics()).toBe(false);
173
+ // Platform reports opt-in, but the fail-closed marker forces off. The raw
174
+ // accessor folds the marker into a confirmed false, not an unknown.
175
+ expect(getRawShareAnalytics()).toBe(false);
131
176
  });
132
177
 
133
178
  test("a fetch reporting shareAnalytics: false turns the cache off", async () => {
134
179
  __setCachedShareAnalyticsForTest(true);
135
180
  mockClient = makeClient(makeConsent({ shareDiagnostics: true }));
136
181
  await refreshConsentCache();
137
- expect(getCachedShareAnalytics()).toBe(false);
182
+ expect(getRawShareAnalytics()).toBe(false);
138
183
  });
139
184
 
140
185
  test("startConsentRefresh is idempotent and runs an immediate refresh", async () => {
@@ -145,7 +190,7 @@ describe("consent-cache", () => {
145
190
  // Let the fire-and-forget immediate refresh settle.
146
191
  await new Promise((resolve) => setTimeout(resolve, 0));
147
192
 
148
- expect(getCachedShareAnalytics()).toBe(true);
193
+ expect(getRawShareAnalytics()).toBe(true);
149
194
  // One immediate refresh from the first call; the second is a no-op.
150
195
  expect(createCallCount).toBe(1);
151
196
  });
@@ -157,12 +202,12 @@ describe("consent-cache", () => {
157
202
  mockClient = makeClient(makeConsent({ shareAnalytics: true }));
158
203
  startConsentRefresh();
159
204
  await new Promise((resolve) => setTimeout(resolve, 0));
160
- expect(getCachedShareAnalytics()).toBe(true);
205
+ expect(getRawShareAnalytics()).toBe(true);
161
206
  });
162
207
 
163
208
  // share_diagnostics tracks the same refresh as share_analytics; Sentry's
164
209
  // beforeSend re-reads it per event so a revocation is honored within a cycle.
165
- test("diagnostics defaults to false before any refresh", () => {
210
+ test("diagnostics reads false at boot (unknown collapses to false)", () => {
166
211
  expect(getCachedShareDiagnostics()).toBe(false);
167
212
  });
168
213
 
@@ -192,14 +237,14 @@ describe("consent-cache", () => {
192
237
  expect(getCachedShareDiagnostics()).toBe(true);
193
238
  });
194
239
 
195
- test("a missing client flips a prior diagnostics opt-in back to false", async () => {
240
+ test("a missing client demotes a prior diagnostics opt-in", async () => {
196
241
  __setCachedShareDiagnosticsForTest(true);
197
242
  mockClient = null;
198
243
  await refreshConsentCache();
199
244
  expect(getCachedShareDiagnostics()).toBe(false);
200
245
  });
201
246
 
202
- test("a client without a resolvable assistant identity fails diagnostics closed", async () => {
247
+ test("a client without a resolvable assistant identity demotes diagnostics", async () => {
203
248
  __setCachedShareDiagnosticsForTest(true);
204
249
  mockClient = makeClient(makeConsent({ shareDiagnostics: true }), "");
205
250
  await refreshConsentCache();
@@ -251,4 +296,25 @@ describe("consent-cache", () => {
251
296
  await refreshConsentCache();
252
297
  expect(getCachedShareDiagnosticsVersion()).toBe("2026-06-18");
253
298
  });
299
+
300
+ test("diagnostics accessor collapses unknown to false; raw analytics exposes the tri-state", async () => {
301
+ // Refresh once so the module's legacy opt-out marker reflects the
302
+ // beforeEach config (the marker is only re-read during a refresh).
303
+ mockClient = null;
304
+ await refreshConsentCache();
305
+
306
+ for (const state of [true, false, "unknown"] as const) {
307
+ __setCachedShareAnalyticsForTest(state);
308
+ __setCachedShareDiagnosticsForTest(state);
309
+ expect(getRawShareAnalytics()).toBe(state);
310
+ expect(getCachedShareDiagnostics()).toBe(state === true);
311
+ }
312
+ });
313
+
314
+ test("legacy opt-out marker folds the raw analytics state to false", async () => {
315
+ setConfig("legacyTelemetryOptOut", true);
316
+ mockClient = null;
317
+ await refreshConsentCache();
318
+ expect(getRawShareAnalytics()).toBe(false);
319
+ });
254
320
  });
@@ -14,48 +14,73 @@
14
14
  * synchronous, I/O-free read, so this module owns the values and refreshes them
15
15
  * periodically in the background. Consent is opt-out — an owner who never made
16
16
  * an explicit choice reports as consented (the client maps a never-chose wire
17
- * value to `true`) — but UNKNOWN consent stays off: an absent session, a
18
- * disabled platform, or a transient fetch failure all leave the values
19
- * untouched (initial `false`), so we never report analytics or send crash
20
- * diagnostics without a confirmed consent state.
17
+ * value to `true`).
18
+ *
19
+ * The consent values are tri-state (`ConsentState`): `true`/`false` are
20
+ * confirmed states from a successful platform fetch; `"unknown"` means no
21
+ * confirmed state exists — boot before the first refresh, no platform session,
22
+ * or no resolvable owner identity. A transient fetch failure keeps the
23
+ * previous value so a known opt-in is not flipped mid-session. Analytics
24
+ * exposes only the raw tri-state accessor (gates drop solely on a confirmed
25
+ * opt-out); the diagnostics boolean accessors collapse `"unknown"` to `false`
26
+ * — diagnostics gates are deliberately fail-closed, so no raw diagnostics
27
+ * accessor exists.
21
28
  */
22
29
 
23
30
  import { getConfigReadOnly } from "../config/loader.js";
24
31
  import { getLogger } from "../util/logger.js";
25
32
  import { VellumPlatformClient } from "./client.js";
33
+ import { arePlatformFeaturesEnabled } from "./feature-gate.js";
26
34
 
27
35
  const log = getLogger("consent-cache");
28
36
 
29
37
  const REFRESH_INTERVAL_MS = 5 * 60_000; // refresh consent every 5 min
30
38
 
31
- let cachedShareAnalytics = false; // default-off until first success
32
- let cachedShareDiagnostics = false; // default-off until first success
39
+ /** Cached consent value: a confirmed boolean, or `"unknown"` when no confirmed state exists. */
40
+ export type ConsentState = boolean | "unknown";
41
+
42
+ let cachedShareAnalytics: ConsentState = "unknown"; // no confirmed state until first success
43
+ let cachedShareDiagnostics: ConsentState = "unknown"; // no confirmed state until first success
33
44
  let cachedShareDiagnosticsVersion = ""; // "" fails the trace disclosure gate
34
45
  // Fail-closed marker for a workspace that locally opted out of usage data
35
46
  // before telemetry moved to platform `share_analytics` consent (migration 106).
36
47
  // While set, telemetry stays off regardless of platform consent. Cleared by a
37
48
  // future cross-repo reconciliation once the platform exposes an explicit
38
- // re-consent signal; not auto-cleared here.
39
- let legacyOptOut = false;
49
+ // re-consent signal; not auto-cleared here. Read live from config (an
50
+ // in-memory cached read, safe on the hot path): a boot-time default would
51
+ // leave the record-time gates open between daemon start and the first
52
+ // consent refresh, exactly the window unknown-consent recording now keeps
53
+ // events flowing in.
54
+ function isLegacyOptedOut(): boolean {
55
+ try {
56
+ return getConfigReadOnly().legacyTelemetryOptOut === true;
57
+ } catch {
58
+ // Config not yet loaded — no marker to honor yet.
59
+ return false;
60
+ }
61
+ }
40
62
  let refreshTimer: ReturnType<typeof setInterval> | null = null;
41
63
 
42
64
  /**
43
- * Synchronous hot-path accessor for the effective `share_analytics` consent.
44
- * Never does I/O; returns `false` until a successful refresh proves otherwise,
45
- * and stays `false` while the legacy fail-closed opt-out marker is set.
65
+ * Raw tri-state `share_analytics` consent the ONLY analytics accessor:
66
+ * every gate distinguishes a confirmed opt-out (`false`, the only state that
67
+ * drops or purges) from a not-yet-known one. The legacy fail-closed opt-out
68
+ * marker folds into `false` — a workspace-level opt-out is a confirmed
69
+ * opt-out, not an unknown. Never does I/O; hot-path safe.
46
70
  */
47
- export function getCachedShareAnalytics(): boolean {
48
- return cachedShareAnalytics && !legacyOptOut;
71
+ export function getRawShareAnalytics(): ConsentState {
72
+ return isLegacyOptedOut() ? false : cachedShareAnalytics;
49
73
  }
50
74
 
51
75
  /**
52
76
  * Synchronous hot-path accessor for the `share_diagnostics` consent (read by
53
- * Sentry `beforeSend`). Never does I/O; returns `false` until a successful
54
- * refresh proves otherwise. Because every Sentry event re-reads this, a
55
- * mid-session opt-out is honored within one refresh cycle.
77
+ * Sentry `beforeSend`). Never does I/O; returns `false` unless a successful
78
+ * refresh confirmed an opt-in (`"unknown"` reads as `false`). Because every
79
+ * Sentry event re-reads this, a mid-session opt-out is honored within one
80
+ * refresh cycle.
56
81
  */
57
82
  export function getCachedShareDiagnostics(): boolean {
58
- return cachedShareDiagnostics;
83
+ return cachedShareDiagnostics === true;
59
84
  }
60
85
 
61
86
  /**
@@ -70,28 +95,35 @@ export function getCachedShareDiagnosticsVersion(): string {
70
95
  /**
71
96
  * Refresh the cached consent from the platform.
72
97
  *
73
- * No platform session / features disabled (`create()` is null) → default-off.
98
+ * No platform session / features disabled (`create()` is null) → `"unknown"`.
74
99
  * No resolvable assistant identity (no owner whose consent we can attest to) →
75
- * fail closed. A successful fetch adopts the reported values. A `null` fetch
76
- * (transient failure / undeployed endpoint) leaves the previous values
77
- * unchanged so a known opt-in is not flipped off mid-session.
100
+ * `"unknown"` losing the owner mapping is not an opt-out, but it also isn't
101
+ * a confirmed opt-in, so we stop riding a stale one. A successful fetch adopts
102
+ * the reported values. A `null` fetch (transient failure / undeployed
103
+ * endpoint) leaves the previous values unchanged so a known opt-in is not
104
+ * flipped off mid-session.
78
105
  */
79
106
  export async function refreshConsentCache(): Promise<void> {
80
- legacyOptOut = getConfigReadOnly().legacyTelemetryOptOut === true;
81
-
82
- const client = await VellumPlatformClient.create();
83
- if (!client) {
107
+ // Platform features disabled is a PERMANENT condition (config-level), not a
108
+ // cold-cache transient: telemetry can never ship, so confirmed-off keeps the
109
+ // record-time gates closed and the outbox from accumulating rows that no
110
+ // flush will ever drain.
111
+ if (!arePlatformFeaturesEnabled()) {
84
112
  setCachedShareAnalytics(false);
85
113
  setCachedShareDiagnostics(false);
86
114
  setCachedShareDiagnosticsVersion("");
87
115
  return;
88
116
  }
89
117
 
90
- // No resolvable owner identity → fail closed (don't ride a stale opt-in).
118
+ const client = await VellumPlatformClient.create();
119
+ if (!client) {
120
+ resetConsentToUnknown();
121
+ return;
122
+ }
123
+
124
+ // No resolvable owner identity → unknown (don't ride a stale opt-in).
91
125
  if (!client.platformAssistantId) {
92
- setCachedShareAnalytics(false);
93
- setCachedShareDiagnostics(false);
94
- setCachedShareDiagnosticsVersion("");
126
+ resetConsentToUnknown();
95
127
  return;
96
128
  }
97
129
 
@@ -103,7 +135,13 @@ export async function refreshConsentCache(): Promise<void> {
103
135
  }
104
136
  }
105
137
 
106
- function setCachedShareAnalytics(value: boolean): void {
138
+ function resetConsentToUnknown(): void {
139
+ setCachedShareAnalytics("unknown");
140
+ setCachedShareDiagnostics("unknown");
141
+ setCachedShareDiagnosticsVersion("");
142
+ }
143
+
144
+ function setCachedShareAnalytics(value: ConsentState): void {
107
145
  if (value !== cachedShareAnalytics) {
108
146
  log.debug(
109
147
  { from: cachedShareAnalytics, to: value },
@@ -113,7 +151,7 @@ function setCachedShareAnalytics(value: boolean): void {
113
151
  }
114
152
  }
115
153
 
116
- function setCachedShareDiagnostics(value: boolean): void {
154
+ function setCachedShareDiagnostics(value: ConsentState): void {
117
155
  if (value !== cachedShareDiagnostics) {
118
156
  log.debug(
119
157
  { from: cachedShareDiagnostics, to: value },
@@ -161,12 +199,12 @@ export async function stopConsentRefresh(): Promise<void> {
161
199
  }
162
200
 
163
201
  /** Test-only: override the cached analytics value without going through a refresh. */
164
- export function __setCachedShareAnalyticsForTest(value: boolean): void {
202
+ export function __setCachedShareAnalyticsForTest(value: ConsentState): void {
165
203
  cachedShareAnalytics = value;
166
204
  }
167
205
 
168
206
  /** Test-only: override the cached diagnostics value without going through a refresh. */
169
- export function __setCachedShareDiagnosticsForTest(value: boolean): void {
207
+ export function __setCachedShareDiagnosticsForTest(value: ConsentState): void {
170
208
  cachedShareDiagnostics = value;
171
209
  }
172
210
 
@@ -36,3 +36,15 @@ export const HOOKS = {
36
36
 
37
37
  /** Union of every hook name declared in {@link HOOKS}. */
38
38
  export type HookName = (typeof HOOKS)[keyof typeof HOOKS];
39
+
40
+ /**
41
+ * Appended (inside the `<system_notice>` wrapper) to the internal continuation /
42
+ * completion nudge strings that re-query the model. Those notices are injected
43
+ * as user-role turns; weaker models without a separate reasoning channel
44
+ * otherwise narrate or answer the notice as visible text, leaking agent-loop
45
+ * scaffolding into the user-facing reply. This clause forbids that narration
46
+ * without licensing an empty reply — each nudge's own instruction still drives
47
+ * the expected output (summary / final reply / continuation).
48
+ */
49
+ export const INTERNAL_NUDGE_OUTPUT_SUPPRESSION =
50
+ " This notice is internal: do not repeat, quote, describe, or acknowledge it, and do not narrate whether you will continue or whether the turn is done. Reply with only the content the user should see.";
@@ -45,6 +45,15 @@ export interface RunConversationTurnOptions {
45
45
  * controller fires, terminating the in-flight agent loop.
46
46
  */
47
47
  signal?: AbortSignal;
48
+ /**
49
+ * Conversation type for newly created conversations. When omitted,
50
+ * defaults to `"standard"` (visible in the sidebar). Set to
51
+ * `"background"` for plugin-driven conversations that should not
52
+ * appear in the sidebar's Recents grouping.
53
+ *
54
+ * Ignored when `conversationId` references an existing conversation.
55
+ */
56
+ conversationType?: "standard" | "background";
48
57
  }
49
58
 
50
59
  export interface RunConversationTurnResult {
@@ -71,7 +80,9 @@ export interface RunConversationTurnResult {
71
80
  */
72
81
  function extractContentAndAttachments(
73
82
  blocks: ContentBlock[],
74
- resolveMedia: (source: MediaSource) => { data: string; media_type: string } | null,
83
+ resolveMedia: (
84
+ source: MediaSource,
85
+ ) => { data: string; media_type: string } | null,
75
86
  ): { text: string; attachments: UserMessageAttachment[] } {
76
87
  const textParts: string[] = [];
77
88
  const attachments: UserMessageAttachment[] = [];
@@ -126,21 +137,33 @@ export async function runConversationTurn(
126
137
  options: RunConversationTurnOptions,
127
138
  ): Promise<RunConversationTurnResult> {
128
139
  const { v7: uuidv7 } = await import("uuid");
129
- const { getOrCreateConversation } = await import(
130
- "../daemon/conversation-store.js"
131
- );
132
- const { broadcastMessage } = await import(
133
- "../runtime/assistant-event-hub.js"
134
- );
135
- const { resolveMediaSourceData } = await import(
136
- "../providers/media-resolve.js"
137
- );
138
- const { getMessageById, getMessages } = await import(
139
- "../persistence/conversation-crud.js"
140
- );
140
+ const { getOrCreateConversation } =
141
+ await import("../daemon/conversation-store.js");
142
+ const { broadcastMessage } =
143
+ await import("../runtime/assistant-event-hub.js");
144
+ const { resolveMediaSourceData } =
145
+ await import("../providers/media-resolve.js");
146
+ const { getConversation, getMessageById, getMessages } =
147
+ await import("../persistence/conversation-crud.js");
148
+ const { publishConversationListAndMetadataChanged } =
149
+ await import("../runtime/sync/resource-sync-events.js");
141
150
 
142
151
  const conversationId = options.conversationId ?? uuidv7();
143
- const conversation = await getOrCreateConversation(conversationId);
152
+ const rowExisted = getConversation(conversationId) != null;
153
+ const conversation = await getOrCreateConversation(conversationId, {
154
+ ...(options.conversationType
155
+ ? { conversationType: options.conversationType }
156
+ : {}),
157
+ });
158
+
159
+ // `getOrCreateConversation` creates the DB row (with conversationType if
160
+ // provided) before hydrating. The normal send-message route emits a
161
+ // "created" list invalidation so sibling clients/sidebars learn about new
162
+ // conversations — emit it here too, but only when the row is actually new
163
+ // so we don't spam invalidations for existing conversations.
164
+ if (!rowExisted) {
165
+ publishConversationListAndMetadataChanged("created", conversationId);
166
+ }
144
167
 
145
168
  // Wire the external abort signal to the conversation's internal abort
146
169
  // controller so aborting the signal terminates the in-flight agent loop.
@@ -67,7 +67,7 @@
67
67
  */
68
68
 
69
69
  export type { HookName } from "./constants.js";
70
- export { HOOKS } from "./constants.js";
70
+ export { HOOKS, INTERNAL_NUDGE_OUTPUT_SUPPRESSION } from "./constants.js";
71
71
  // Conversation message/content shapes. A hook receives the live message
72
72
  // history (e.g. `PostToolUseContext.latestMessages: Message[]`), so plugins
73
73
  // that inspect or narrow content blocks — reading a `tool_use` block's input,
@@ -150,6 +150,16 @@ export { getModelProfiles } from "./model-profiles.js";
150
150
  // looks up the model catalog's `supportsVision` flag (mix profiles are
151
151
  // vision-capable if any arm is). Returns false when nothing resolves.
152
152
  export { doesSupportVision } from "./vision-support.js";
153
+ // Resolve a stored credential to its plaintext value — the same value
154
+ // `assistant credentials reveal` prints — from a UUID or a "service/field"
155
+ // reference. When a plugin is in context, resolution is scoped to credentials
156
+ // whose `field` matches the plugin's manifest name; outside any plugin it is
157
+ // unscoped. Throws CredentialResolutionError when the ref does not resolve, the
158
+ // store is unreachable, or the credential is out of the plugin's scope.
159
+ export {
160
+ CredentialResolutionError,
161
+ resolveCredential,
162
+ } from "./resolve-credential.js";
153
163
  // Resolve a provider for a call site (optionally overriding the profile) so a
154
164
  // plugin can run inference through the workspace's configured profiles and
155
165
  // credentials — managed-proxy or BYOK — without supplying its own API key.
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Plugin-facing credential resolution.
3
+ *
4
+ * {@link resolveCredential} returns a stored credential's plaintext value — the
5
+ * same value `assistant credentials reveal` prints — resolving a reference that
6
+ * is either a credential UUID or a `"service/field"` string, exactly as the
7
+ * reveal path does (see {@link ../tools/credentials/resolve.resolveCredentialRef}
8
+ * and the `credentials/reveal` route).
9
+ *
10
+ * ## Plugin scoping
11
+ *
12
+ * When a plugin is in context — its hook or tool is executing, tracked by
13
+ * {@link ../plugins/plugin-execution-context.getCurrentPluginName} — resolution
14
+ * is restricted: the plugin may only resolve credentials whose `field` equals
15
+ * the plugin's own manifest name. A plugin named `acme` can therefore read
16
+ * `openai/acme` or `stripe/acme` but not `openai/api_key`. Outside any plugin
17
+ * context (host-internal callers, CLI, tests) the resolver is unscoped and
18
+ * behaves like a direct reveal.
19
+ */
20
+
21
+ import { getCurrentPluginName } from "../plugins/plugin-execution-context.js";
22
+ import { getSecureKeyResultAsync } from "../security/secure-keys.js";
23
+ import { resolveCredentialRef } from "../tools/credentials/resolve.js";
24
+
25
+ /**
26
+ * Raised when a credential cannot be resolved: the reference does not match a
27
+ * stored credential, the store is unreachable, or the calling plugin is not
28
+ * permitted to resolve the requested credential.
29
+ */
30
+ export class CredentialResolutionError extends Error {
31
+ constructor(message: string) {
32
+ super(message);
33
+ this.name = "CredentialResolutionError";
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Resolve a credential reference to its plaintext value.
39
+ *
40
+ * @param ref A credential UUID or a `"service/field"` string.
41
+ * @returns The plaintext credential value.
42
+ * @throws {CredentialResolutionError} when the reference does not resolve, the
43
+ * store is unreachable, or a plugin in context is not scoped to the credential.
44
+ */
45
+ export async function resolveCredential(ref: string): Promise<string> {
46
+ const resolved = resolveCredentialRef(ref);
47
+ if (!resolved) {
48
+ throw new CredentialResolutionError(`Credential not found: ${ref}`);
49
+ }
50
+
51
+ // Scope the resolution to the plugin in context, if any. The field-name gate
52
+ // is enforced before the plaintext is read so an out-of-scope plugin never
53
+ // touches the secure backend.
54
+ const pluginName = getCurrentPluginName();
55
+ if (pluginName !== undefined && resolved.field !== pluginName) {
56
+ throw new CredentialResolutionError(
57
+ `Plugin "${pluginName}" may only resolve credentials whose field matches its name; ` +
58
+ `"${resolved.service}/${resolved.field}" is out of scope.`,
59
+ );
60
+ }
61
+
62
+ const { value, unreachable } = await getSecureKeyResultAsync(
63
+ resolved.storageKey,
64
+ );
65
+ if (value == null || value.length === 0) {
66
+ if (unreachable) {
67
+ throw new CredentialResolutionError(
68
+ "Credential store is unreachable — ensure the assistant is running",
69
+ );
70
+ }
71
+ throw new CredentialResolutionError(`Credential not found: ${ref}`);
72
+ }
73
+
74
+ return value;
75
+ }
@@ -14,10 +14,6 @@ interface MockProfileEntry {
14
14
  }
15
15
 
16
16
  let mockProfiles: Record<string, MockProfileEntry> = {};
17
- let mockDefault: { provider?: string; model?: string } = {
18
- provider: "anthropic",
19
- model: "claude-opus-4-6",
20
- };
21
17
 
22
18
  // Real model catalog — don't mock it, the test exercises real catalog lookups.
23
19
  const { doesSupportVision } = await import("./vision-support.js");
@@ -44,21 +40,16 @@ function profile(key: string): ModelProfileInfo {
44
40
  function applyConfig(): void {
45
41
  setConfig("llm", { profiles: {} });
46
42
  const config = getConfig() as { llm: unknown };
47
- config.llm = { profiles: mockProfiles, default: mockDefault };
43
+ config.llm = { profiles: mockProfiles };
48
44
  }
49
45
 
50
- function setMockConfig(
51
- profiles: Record<string, MockProfileEntry>,
52
- def: { provider?: string; model?: string } = {},
53
- ) {
46
+ function setMockConfig(profiles: Record<string, MockProfileEntry>) {
54
47
  mockProfiles = profiles;
55
- mockDefault = { provider: "anthropic", model: "claude-opus-4-6", ...def };
56
48
  applyConfig();
57
49
  }
58
50
 
59
51
  beforeEach(() => {
60
52
  mockProfiles = {};
61
- mockDefault = { provider: "anthropic", model: "claude-opus-4-6" };
62
53
  applyConfig();
63
54
  });
64
55
 
@@ -94,18 +85,16 @@ describe("doesSupportVision", () => {
94
85
  expect(doesSupportVision(profile("unknown-model"))).toBe(false);
95
86
  });
96
87
 
97
- test("inherits provider from llm.default when profile only sets model", () => {
98
- setMockConfig(
99
- { "model-only": { model: "claude-opus-4-6" } },
100
- { provider: "anthropic" },
101
- );
88
+ test("implies the provider from the catalog when profile only sets model", () => {
89
+ setMockConfig({ "model-only": { model: "claude-opus-4-6" } });
102
90
  expect(doesSupportVision(profile("model-only"))).toBe(true);
103
91
  });
104
92
 
105
- test("inherits model from llm.default when profile only sets provider", () => {
106
- // llm.default → anthropic/claude-opus-4-6 (vision-capable)
93
+ test("fails safe to false for a profile without a model", () => {
94
+ // A model-less entry is not a usable resolution target, so vision
95
+ // resolution treats it as "can't show images" (caption instead).
107
96
  setMockConfig({ "provider-only": { provider: "anthropic" } });
108
- expect(doesSupportVision(profile("provider-only"))).toBe(true);
97
+ expect(doesSupportVision(profile("provider-only"))).toBe(false);
109
98
  });
110
99
 
111
100
  test("mix profile returns true when any arm supports vision", () => {