@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
@@ -20,6 +20,7 @@ import type { MemoryV3SelectionLog } from "../../../../api/responses/memory-v3-s
20
20
  import { getConfig } from "../../../../config/loader.js";
21
21
  import { isMemoryV3Live } from "../../../../config/memory-v3-gate.js";
22
22
  import { getDb, getSqliteFrom } from "../../../../persistence/db-connection.js";
23
+ import { memorySqliteOrNull } from "../memory-db.js";
23
24
  import { getWorkspaceDir } from "../paths.js";
24
25
  import { readPage } from "../v2/page-store.js";
25
26
  import { capabilityOrDiskBody } from "./capabilities.js";
@@ -46,7 +47,11 @@ interface SelectionRow {
46
47
  const SELECTION_COLUMNS = `turn, slug, source, pinned, section_ordinal, section_title`;
47
48
 
48
49
  function rowsForTurn(conversationId: string, turn: number): SelectionRow[] {
49
- return getSqliteFrom(getDb())
50
+ const raw = memorySqliteOrNull("rowsForTurn");
51
+ if (!raw) {
52
+ return [];
53
+ }
54
+ return raw
50
55
  .query(
51
56
  /*sql*/ `
52
57
  SELECT ${SELECTION_COLUMNS} FROM memory_v3_selections
@@ -59,8 +64,12 @@ function rowsForTurn(conversationId: string, turn: number): SelectionRow[] {
59
64
 
60
65
  function rowsForMessageIds(messageIds: string[]): SelectionRow[] {
61
66
  if (messageIds.length === 0) return [];
67
+ const raw = memorySqliteOrNull("rowsForMessageIds");
68
+ if (!raw) {
69
+ return [];
70
+ }
62
71
  const placeholders = messageIds.map(() => "?").join(", ");
63
- return getSqliteFrom(getDb())
72
+ return raw
64
73
  .query(
65
74
  /*sql*/ `
66
75
  SELECT ${SELECTION_COLUMNS} FROM memory_v3_selections
@@ -249,18 +258,21 @@ function isSelectionSource(source: string): source is SelectionSource {
249
258
  }
250
259
 
251
260
  export function summarizeSelections(conversationId: string): SelectionSummary {
252
- const rows = getSqliteFrom(getDb())
253
- .query(
254
- /*sql*/ `
261
+ const raw = memorySqliteOrNull("summarizeSelections");
262
+ const rows = raw
263
+ ? (raw
264
+ .query(
265
+ /*sql*/ `
255
266
  SELECT turn, slug, source FROM memory_v3_selections
256
267
  WHERE conversation_id = ?
257
268
  `,
258
- )
259
- .all(conversationId) as Array<{
260
- turn: number;
261
- slug: string;
262
- source: string;
263
- }>;
269
+ )
270
+ .all(conversationId) as Array<{
271
+ turn: number;
272
+ slug: string;
273
+ source: string;
274
+ }>)
275
+ : [];
264
276
 
265
277
  const bySource = Object.fromEntries(
266
278
  SELECTION_SOURCES.map((source) => [source, 0]),
@@ -30,9 +30,9 @@ import {
30
30
 
31
31
  import { getConfig } from "../../../../config/loader.js";
32
32
  import type { AssistantConfig } from "../../../../config/schema.js";
33
- import { getDb, getSqliteFrom } from "../../../../persistence/db-connection.js";
34
33
  import { stripCommentLines } from "../host-utils.js";
35
34
  import { getLogger } from "../logging.js";
35
+ import { memorySqliteOrNull } from "../memory-db.js";
36
36
  import { getWorkspaceDir, getWorkspacePromptPath } from "../paths.js";
37
37
  import { getPageIndex } from "../v2/page-index.js";
38
38
  import { readPage, renderPageContent } from "../v2/page-store.js";
@@ -229,15 +229,12 @@ async function initLanes(config: AssistantConfig): Promise<ShadowLanes> {
229
229
  const coreSlugs = loadCoreSet(getWorkspaceDir()).filter((slug) =>
230
230
  sectionIndex.byArticle.has(slug),
231
231
  );
232
- const hotSlugs = computeHotSet(
233
- { db: getDb() },
234
- {
235
- k: tuning.hotSetK,
236
- halfLifeMs: config.memory.v3.hotSet.halfLifeDays * DAY_MS,
237
- now: Date.now(),
238
- excludeSlugs: new Set(coreSlugs),
239
- },
240
- )
232
+ const hotSlugs = computeHotSet({
233
+ k: tuning.hotSetK,
234
+ halfLifeMs: config.memory.v3.hotSet.halfLifeDays * DAY_MS,
235
+ now: Date.now(),
236
+ excludeSlugs: new Set(coreSlugs),
237
+ })
241
238
  .map((entry) => entry.slug)
242
239
  .filter((slug) => sectionIndex.byArticle.has(slug));
243
240
 
@@ -320,18 +317,15 @@ async function initLanes(config: AssistantConfig): Promise<ShadowLanes> {
320
317
  const learned = config.memory.v3.learnedEdges;
321
318
  const learnedGraph =
322
319
  tuning.learnedEdgesCap > 0 && learned.maxPerPage > 0
323
- ? computeLearnedEdgeGraph(
324
- { db: getDb() },
325
- {
326
- halfLifeMs: learned.halfLifeDays * DAY_MS,
327
- minCount: learned.minCount,
328
- npmiFloor: learned.npmiFloor,
329
- maxPerPage: learned.maxPerPage,
330
- now: Date.now(),
331
- windowMs: LEARNED_EDGES_WINDOW_DAYS * DAY_MS,
332
- knownSlugs: new Set(sectionIndex.byArticle.keys()),
333
- },
334
- )
320
+ ? computeLearnedEdgeGraph({
321
+ halfLifeMs: learned.halfLifeDays * DAY_MS,
322
+ minCount: learned.minCount,
323
+ npmiFloor: learned.npmiFloor,
324
+ maxPerPage: learned.maxPerPage,
325
+ now: Date.now(),
326
+ windowMs: LEARNED_EDGES_WINDOW_DAYS * DAY_MS,
327
+ knownSlugs: new Set(sectionIndex.byArticle.keys()),
328
+ })
335
329
  : undefined;
336
330
  // Ensuring the dense collection is best-effort: the needle + edge lanes and
337
331
  // the core/hot prefix are in-memory and independent of Qdrant, so a Qdrant outage
@@ -530,7 +524,11 @@ export function attributeSelections(result: OrchestrateResult): SelectionRow[] {
530
524
  });
531
525
  }
532
526
 
533
- /** Write the attributed selection rows to `memory_v3_selections`. */
527
+ /**
528
+ * Write the attributed selection rows to `memory_v3_selections` over the
529
+ * dedicated memory connection. Best-effort: an unavailable memory database or
530
+ * a failed write drops the turn's log rows rather than affecting the turn.
531
+ */
534
532
  export function writeSelections(
535
533
  conversationId: string,
536
534
  turn: number,
@@ -539,30 +537,37 @@ export function writeSelections(
539
537
  if (rows.length === 0) {
540
538
  return;
541
539
  }
542
- const raw = getSqliteFrom(getDb());
543
- // PK is (conversation_id, turn, slug); OR REPLACE keeps the write
544
- // idempotent if the same turn is observed twice (e.g. a retried turn).
545
- // `message_id` is written NULL here (the assistant message does not exist at
546
- // injection time) and stamped at turn end by
547
- // `backfillMemoryV3SelectionMessageId`.
548
- const stmt = raw.query(/*sql*/ `
549
- INSERT OR REPLACE INTO memory_v3_selections (
550
- conversation_id, turn, slug, source, pinned, created_at,
551
- message_id, section_ordinal, section_title
552
- ) VALUES (?, ?, ?, ?, ?, ?, NULL, ?, ?)
553
- `);
554
- const now = Date.now();
555
- for (const row of rows) {
556
- stmt.run(
557
- conversationId,
558
- turn,
559
- row.slug,
560
- row.source,
561
- row.pinned,
562
- now,
563
- row.sectionOrdinal,
564
- row.sectionTitle,
565
- );
540
+ try {
541
+ const raw = memorySqliteOrNull("writeSelections");
542
+ if (!raw) {
543
+ return;
544
+ }
545
+ // PK is (conversation_id, turn, slug); OR REPLACE keeps the write
546
+ // idempotent if the same turn is observed twice (e.g. a retried turn).
547
+ // `message_id` is written NULL here (the assistant message does not exist
548
+ // at injection time) and stamped at turn end by
549
+ // `backfillMemoryV3SelectionMessageId`.
550
+ const stmt = raw.query(/*sql*/ `
551
+ INSERT OR REPLACE INTO memory_v3_selections (
552
+ conversation_id, turn, slug, source, pinned, created_at,
553
+ message_id, section_ordinal, section_title
554
+ ) VALUES (?, ?, ?, ?, ?, ?, NULL, ?, ?)
555
+ `);
556
+ const now = Date.now();
557
+ for (const row of rows) {
558
+ stmt.run(
559
+ conversationId,
560
+ turn,
561
+ row.slug,
562
+ row.source,
563
+ row.pinned,
564
+ now,
565
+ row.sectionOrdinal,
566
+ row.sectionTitle,
567
+ );
568
+ }
569
+ } catch (err) {
570
+ log.warn({ err }, "failed to write memory-v3 selections; continuing");
566
571
  }
567
572
  }
568
573
 
@@ -579,12 +584,23 @@ export function backfillMemoryV3SelectionMessageId(
579
584
  conversationId: string,
580
585
  assistantMessageId: string,
581
586
  ): void {
582
- getSqliteFrom(getDb())
583
- .query(
584
- /*sql*/ `UPDATE memory_v3_selections SET message_id = ?
585
- WHERE conversation_id = ? AND message_id IS NULL`,
586
- )
587
- .run(assistantMessageId, conversationId);
587
+ try {
588
+ const raw = memorySqliteOrNull("backfillMemoryV3SelectionMessageId");
589
+ if (!raw) {
590
+ return;
591
+ }
592
+ raw
593
+ .query(
594
+ /*sql*/ `UPDATE memory_v3_selections SET message_id = ?
595
+ WHERE conversation_id = ? AND message_id IS NULL`,
596
+ )
597
+ .run(assistantMessageId, conversationId);
598
+ } catch (err) {
599
+ log.warn(
600
+ { err },
601
+ "failed to backfill memory-v3 selection messageId; continuing",
602
+ );
603
+ }
588
604
  }
589
605
 
590
606
  /**
@@ -633,6 +649,7 @@ export async function observeTurn(
633
649
  prefixCards: lanes.prefixCards,
634
650
  needleK: tuning.needleK,
635
651
  denseK: tuning.denseK,
652
+ realConceptPageCount: lanes.realConceptPageCount,
636
653
  entityCap: v3.entity.cap,
637
654
  replyQueryK: tuning.replyQueryK,
638
655
  edgeSeeds: tuning.edgeSeedCount,
@@ -45,11 +45,12 @@
45
45
  * the `post-model-call` chain — later hooks see (and may override) its decision.
46
46
  */
47
47
 
48
- import type {
49
- ContentBlock,
50
- HookFunction,
51
- Message,
52
- PostModelCallContext,
48
+ import {
49
+ type ContentBlock,
50
+ type HookFunction,
51
+ INTERNAL_NUDGE_OUTPUT_SUPPRESSION,
52
+ type Message,
53
+ type PostModelCallContext,
53
54
  } from "@vellumai/plugin-api";
54
55
 
55
56
  import {
@@ -64,7 +65,9 @@ import {
64
65
  * is genuinely still running.
65
66
  */
66
67
  export const SURFACE_COMPLETION_NUDGE_TEXT =
67
- '<system_notice>You showed the user a progress surface this turn (a task_progress card or a work_result) and are about to end the turn with it still marked in_progress. If that work is finished, advance it to a terminal state now — call ui_update to set its status to "completed" (or "failed"), or ui_dismiss it — so the user is not left watching a card spin forever. Do this only if the work it represents is actually done; if it is genuinely still running, leave it. Then give your final reply.</system_notice>';
68
+ '<system_notice>You showed the user a progress surface this turn (a task_progress card or a work_result) and are about to end the turn with it still marked in_progress. If that work is finished, advance it to a terminal state now — call ui_update to set its status to "completed" (or "failed"), or ui_dismiss it — so the user is not left watching a card spin forever. Do this only if the work it represents is actually done; if it is genuinely still running, leave it. Then give your final reply.' +
69
+ INTERNAL_NUDGE_OUTPUT_SUPPRESSION +
70
+ "</system_notice>";
68
71
 
69
72
  /**
70
73
  * Surface statuses that mean the progress surface has reached a terminal state
@@ -42,7 +42,7 @@
42
42
 
43
43
  import { existsSync, readdirSync, statSync } from "node:fs";
44
44
  import { readFile } from "node:fs/promises";
45
- import { join } from "node:path";
45
+ import { basename, join } from "node:path";
46
46
  import { pathToFileURL } from "node:url";
47
47
 
48
48
  import semver from "semver";
@@ -100,15 +100,6 @@ export interface LoadExternalPluginOptions {
100
100
  readonly importTimeoutMs?: number;
101
101
  }
102
102
 
103
- /**
104
- * Strip the npm scope from a package name. `@vellumai/simple-memory` →
105
- * `simple-memory`; an unscoped name passes through unchanged.
106
- */
107
- export function stripScope(name: string): string {
108
- const match = /^@[^/]+\/(.+)$/.exec(name);
109
- return match ? match[1]! : name;
110
- }
111
-
112
103
  export function toToolNameSegment(value: string): string {
113
104
  return value.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "tool";
114
105
  }
@@ -229,7 +220,15 @@ async function buildPluginFromDir(pluginDir: string): Promise<Plugin> {
229
220
  );
230
221
  }
231
222
  const pkg: PluginPackageJson = parsed.data;
232
- const name = stripScope(pkg.name);
223
+ // A plugin's identity is its install directory name the slug the plugin
224
+ // was installed under (a marketplace slug or a GitHub path leaf). This is
225
+ // the identity every other surface uses: `plugins list`, enable/disable,
226
+ // per-conversation plugin scoping, and resident-skill `owner.id`. The
227
+ // `package.json` `name` is authored by the plugin and routinely differs
228
+ // from the slug, so keying runtime registration off it would desync the
229
+ // registry from the identity the user toggles and the scope filters match.
230
+ // The manifest is still required (schema-validated above) as the load gate.
231
+ const name = basename(pluginDir);
233
232
  const version = pkg.version && pkg.version.length > 0 ? pkg.version : "0.0.0";
234
233
 
235
234
  // Host-compat negotiation: plugins declare their plugin-api version
@@ -381,11 +380,12 @@ export async function loadExternalPlugin(
381
380
 
382
381
  /**
383
382
  * Parse a plugin's `package.json` manifest from disk. Returns the plugin
384
- * name (scope-stripped) and version, or `undefined` when the file is
385
- * missing, unparseable, or fails schema validation.
383
+ * identity (its install directory name) and version, or `undefined` when the
384
+ * `package.json` is missing, unparseable, or fails schema validation.
386
385
  *
387
386
  * Exported so the mtime cache can discover plugin identity without going
388
- * through the full `buildExternalPlugin` path.
387
+ * through the full `buildExternalPlugin` path. The identity mirrors
388
+ * {@link buildPluginFromDir}: the directory name, not `package.json` `name`.
389
389
  */
390
390
  export async function parsePluginManifest(
391
391
  pluginDir: string,
@@ -411,7 +411,7 @@ export async function parsePluginManifest(
411
411
  return undefined;
412
412
  }
413
413
  const pkg: PluginPackageJson = parsed.data;
414
- const name = stripScope(pkg.name);
414
+ const name = basename(pluginDir);
415
415
  const version = pkg.version && pkg.version.length > 0 ? pkg.version : "0.0.0";
416
416
  return { name, version };
417
417
  }
@@ -58,6 +58,7 @@ import {
58
58
  getWorkspaceHooksDir,
59
59
  getWorkspacePluginsDir,
60
60
  } from "../util/platform.js";
61
+ import { collectSourceVersions } from "./collect-source-versions.js";
61
62
  import {
62
63
  deriveToolName,
63
64
  listSurfaceDir,
@@ -308,6 +309,52 @@ async function maybeReconcileFromSentinel(): Promise<void> {
308
309
  }
309
310
  }
310
311
 
312
+ /**
313
+ * Imperatively reconcile the plugin caches against the current on-disk state
314
+ * *now* — without waiting for the resource monitor to republish the
315
+ * source-versions sentinel or for the next hook dispatch to notice it.
316
+ *
317
+ * An install / uninstall / enable / disable materializes files on disk; the
318
+ * steady-state path picks that up eventually — the monitor republishes the
319
+ * sentinel and the dispatch-path gate applies the diff on the next turn. That
320
+ * is eventually-correct but not immediate: nothing runs a newly installed
321
+ * plugin's `init` until some later turn dispatches a hook. Callers that just
322
+ * changed the plugin set on disk (the install / uninstall routes, and the
323
+ * CLI's best-effort post-install poke) call this to bring the change up
324
+ * deterministically, so a freshly installed plugin's `init` fires as part of
325
+ * the install rather than at the next daemon boot.
326
+ *
327
+ * Reuses the same collector the monitor writes into the sentinel, so the map
328
+ * applied here is identical to the monitor's next publish — which then diffs to
329
+ * a no-op. Idempotent and safe to call redundantly: `applySourceVersions` only
330
+ * redeploys directories whose fingerprint moved, and activation is guarded so a
331
+ * plugin already up is never re-initialized. Never throws — a failure is
332
+ * contained inside `applySourceVersions` and logged there.
333
+ *
334
+ * Coordinates with the sentinel gate through the shared `reconcileInFlight`
335
+ * latch: it waits out any dispatch-driven reconcile already running, then
336
+ * claims the latch for its own apply, so the two paths never overlap.
337
+ */
338
+ export async function reconcilePluginSourcesNow(): Promise<void> {
339
+ while (reconcileInFlight !== null) {
340
+ await reconcileInFlight;
341
+ }
342
+ // `applySourceVersions` contains its own failures, but the `collectSourceVersions()`
343
+ // walk that feeds it runs outside that guard — wrap the whole thing so an
344
+ // imperative reconcile never rejects into its callers (the install route must
345
+ // still return success for an install whose files already landed on disk).
346
+ reconcileInFlight = (async () => {
347
+ try {
348
+ await applySourceVersions(collectSourceVersions());
349
+ } catch (err) {
350
+ log.error({ err }, "imperative plugin reconcile failed");
351
+ }
352
+ })().finally(() => {
353
+ reconcileInFlight = null;
354
+ });
355
+ await reconcileInFlight;
356
+ }
357
+
311
358
  /**
312
359
  * Validate that a directory path from the sentinel is an allowed plugin
313
360
  * source: either under the workspace plugins directory or the standalone
@@ -25,6 +25,7 @@ import { getHookEntriesFor } from "../hooks/registry.js";
25
25
  import type { BaseHookContext } from "../hooks/types.js";
26
26
  import { type HookName, HOOKS } from "../plugin-api/constants.js";
27
27
  import { getLogger } from "../util/logger.js";
28
+ import { runInPluginContext } from "./plugin-execution-context.js";
28
29
  import type { HookEntry } from "./types.js";
29
30
 
30
31
  // ─── Hook runner ────────────────────────────────────────────────────────────
@@ -352,8 +353,15 @@ export async function runHook<TInput extends object>(
352
353
  broadcast: makeHookBroadcast({ conversationId, hookName: name, owner }),
353
354
  };
354
355
  try {
356
+ // Mark the contributing plugin as in context so host APIs the hook
357
+ // reaches (e.g. resolveCredential) can scope to it. Standalone workspace
358
+ // hooks are not plugins and establish no context.
359
+ const invokeHook =
360
+ owner.kind === "plugin"
361
+ ? () => runInPluginContext(owner.id, () => fn(draft))
362
+ : () => fn(draft);
355
363
  const result = await callWithTimeout(
356
- () => fn(draft),
364
+ invokeHook,
357
365
  HOOK_TIMEOUT_MS,
358
366
  `plugin hook '${name}' (${owner.id}) timed out after ${HOOK_TIMEOUT_MS}ms`,
359
367
  );
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Plugin execution context — tracks which plugin's code is currently running.
3
+ *
4
+ * Host APIs exposed to plugins through `@vellumai/plugin-api` sometimes need to
5
+ * know *which* plugin is calling them so they can scope their behavior to that
6
+ * plugin (e.g. {@link ../plugin-api/resolve-credential.resolveCredential} limits
7
+ * a plugin to its own credentials). A plugin's manifest name is not threaded
8
+ * through every host call, so the pipeline that invokes a plugin's hook — and
9
+ * the tool executor that runs a plugin's tool — mark the plugin as "in context"
10
+ * for the duration of that invocation via an {@link AsyncLocalStorage}. Host
11
+ * APIs read {@link getCurrentPluginName} to recover it.
12
+ *
13
+ * The store propagates across `await` boundaries, so a plugin that awaits a
14
+ * host API deep inside its hook/tool body is still seen as in context. When no
15
+ * plugin is in context (host-internal callers, the CLI, tests), the store is
16
+ * empty and scoped APIs fall back to their unscoped behavior.
17
+ */
18
+
19
+ import { AsyncLocalStorage } from "node:async_hooks";
20
+
21
+ export interface PluginExecutionContext {
22
+ /** Manifest name of the plugin whose hook or tool is currently executing. */
23
+ pluginName: string;
24
+ }
25
+
26
+ const storage = new AsyncLocalStorage<PluginExecutionContext>();
27
+
28
+ /**
29
+ * Run `fn` with `pluginName` marked as the plugin currently in context. The
30
+ * returned value (including a promise) carries the context across its async
31
+ * continuations, so callers pass the promise straight to a timeout wrapper
32
+ * without losing the binding.
33
+ */
34
+ export function runInPluginContext<T>(pluginName: string, fn: () => T): T {
35
+ return storage.run({ pluginName }, fn);
36
+ }
37
+
38
+ /**
39
+ * Name of the plugin whose hook or tool is currently executing, or `undefined`
40
+ * when no plugin is in context.
41
+ */
42
+ export function getCurrentPluginName(): string | undefined {
43
+ return storage.getStore()?.pluginName;
44
+ }
@@ -41,10 +41,10 @@ export const PRESERVED_ENTRIES = [
41
41
  ] as const;
42
42
 
43
43
  /**
44
- * A generated app build directory: `apps/<app>/dist`. This is compiled output
45
- * (the plugin source watcher builds each multi-file app's `src/` into its
46
- * sibling `dist/`), never tracked source, so every fingerprint walk excludes
47
- * it:
44
+ * Generated app build output: `apps/<app>/dist`. This is compiled output (the
45
+ * plugin source watcher builds each multi-file app's `src/` into its sibling
46
+ * `dist/`), never tracked source, so like {@link PRESERVED_ENTRIES} — every
47
+ * fingerprint walk excludes it:
48
48
  *
49
49
  * - the **live-reload** change detector (`./source-fingerprint.ts`), so the
50
50
  * watcher's own compile does not read as a source change and re-trigger
@@ -52,27 +52,24 @@ export const PRESERVED_ENTRIES = [
52
52
  * - the **install/drift** fingerprint (`../cli/lib/plugin-fingerprint.ts`), so
53
53
  * generated output is not reported as drift/added against the pinned commit.
54
54
  *
55
- * Scoped to `apps/<app>/dist` specifically a plugin's own top-level `dist/`
56
- * (if it ships built code its hooks import) is still tracked.
55
+ * A path pattern rather than a bare name so it stays scoped to `apps/<app>/dist`
56
+ * — a plugin's own top-level `dist/` (if it ships built code its hooks import)
57
+ * is still tracked. Matched against the POSIX path relative to the plugin root.
57
58
  */
58
- export function isGeneratedAppBuildDir(relDir: string): boolean {
59
- const parts = relDir.split("/");
60
- return parts.length === 3 && parts[0] === "apps" && parts[2] === "dist";
61
- }
59
+ export const GENERATED_APP_BUILD_DIR = /^apps\/[^/]+\/dist$/;
62
60
 
63
61
  /** Options controlling which entries a {@link walkPluginTree} visits. */
64
62
  export interface PluginTreeWalkOptions {
65
- /** Top-level entry names to skip (e.g. {@link PRESERVED_ENTRIES}). */
66
- readonly excludeRootEntries?: Iterable<string>;
67
- /** Directory names skipped at any depth (e.g. `node_modules`). */
68
- readonly excludeDirsAnywhere?: ReadonlySet<string>;
69
63
  /**
70
- * Skip a directory (and its whole subtree) by its POSIX path relative to the
71
- * walk root. Called for each directory before descending use for
72
- * path-scoped exclusions a bare directory name can't express (e.g. generated
73
- * `apps/<app>/dist`, via {@link isGeneratedAppBuildDir}).
64
+ * Entries to exclude. A `string` matches a top-level entry by name (e.g.
65
+ * {@link PRESERVED_ENTRIES}); a `RegExp` matches any entryat any depth —
66
+ * by its POSIX path relative to the walk root, and when it matches a
67
+ * directory the whole subtree is skipped (e.g. {@link
68
+ * GENERATED_APP_BUILD_DIR}).
74
69
  */
75
- readonly excludeDir?: (relDir: string) => boolean;
70
+ readonly excludeRootEntries?: Iterable<string | RegExp>;
71
+ /** Directory names skipped at any depth (e.g. `node_modules`). */
72
+ readonly excludeDirsAnywhere?: ReadonlySet<string>;
76
73
  /** Skip entries whose name starts with `.`, at any depth. */
77
74
  readonly excludeDotEntries?: boolean;
78
75
  /**
@@ -94,7 +91,17 @@ export function walkPluginTree(
94
91
  options: PluginTreeWalkOptions,
95
92
  visit: (rel: string, abs: string) => void,
96
93
  ): void {
97
- const excludedRoot = new Set(options.excludeRootEntries ?? []);
94
+ // Split the exclusion list: bare strings match a top-level entry name;
95
+ // RegExps match an entry's relative path at any depth.
96
+ const excludedRootNames = new Set<string>();
97
+ const excludePatterns: RegExp[] = [];
98
+ for (const entry of options.excludeRootEntries ?? []) {
99
+ if (typeof entry === "string") {
100
+ excludedRootNames.add(entry);
101
+ } else {
102
+ excludePatterns.push(entry);
103
+ }
104
+ }
98
105
 
99
106
  const walk = (relDir: string): void => {
100
107
  const absDir = relDir ? join(root, relDir) : root;
@@ -109,7 +116,7 @@ export function walkPluginTree(
109
116
  }
110
117
  for (const entry of entries) {
111
118
  const name = entry.name;
112
- if (relDir === "" && excludedRoot.has(name)) {
119
+ if (relDir === "" && excludedRootNames.has(name)) {
113
120
  continue;
114
121
  }
115
122
  if (options.excludeDotEntries === true && name.startsWith(".")) {
@@ -119,13 +126,13 @@ export function walkPluginTree(
119
126
  continue;
120
127
  }
121
128
  const rel = relDir ? `${relDir}/${name}` : name;
129
+ if (excludePatterns.some((pattern) => pattern.test(rel))) {
130
+ continue;
131
+ }
122
132
  if (entry.isDirectory()) {
123
133
  if (options.excludeDirsAnywhere?.has(name) === true) {
124
134
  continue;
125
135
  }
126
- if (options.excludeDir?.(rel) === true) {
127
- continue;
128
- }
129
136
  walk(rel);
130
137
  } else if (entry.isFile()) {
131
138
  visit(rel, join(absDir, name));
@@ -19,7 +19,7 @@
19
19
  * Exclusions: dot-entries anywhere (`.disabled`, `.git`, …), `node_modules`
20
20
  * anywhere (vendored deps change only through install flows, which recycle
21
21
  * the plugin directory), generated app build output ({@link
22
- * isGeneratedAppBuildDir}, so the watcher's own compile does not re-trigger a
22
+ * GENERATED_APP_BUILD_DIR}, so the watcher's own compile does not re-trigger a
23
23
  * pass), and — at the plugin root only — the runtime-owned
24
24
  * {@link PRESERVED_ENTRIES} (`data/`, `config.json`, `install-meta.json`),
25
25
  * none of which are importable source. Symlinked entries inside the tree are
@@ -33,7 +33,7 @@
33
33
  import { realpathSync, statSync } from "node:fs";
34
34
 
35
35
  import {
36
- isGeneratedAppBuildDir,
36
+ GENERATED_APP_BUILD_DIR,
37
37
  PRESERVED_ENTRIES,
38
38
  walkPluginTree,
39
39
  } from "./plugin-tree-walk.js";
@@ -82,9 +82,8 @@ export function snapshotPluginSource(pluginDir: string): SourceSnapshot {
82
82
  walkPluginTree(
83
83
  realRoot,
84
84
  {
85
- excludeRootEntries: PRESERVED_ENTRIES,
85
+ excludeRootEntries: [...PRESERVED_ENTRIES, GENERATED_APP_BUILD_DIR],
86
86
  excludeDirsAnywhere: EXCLUDED_DIRS_ANYWHERE,
87
- excludeDir: isGeneratedAppBuildDir,
88
87
  excludeDotEntries: true,
89
88
  bestEffort: true,
90
89
  },
@@ -85,6 +85,8 @@ export interface NormalizedOnboarding {
85
85
  occupation?: string;
86
86
  commonWork: string[];
87
87
  dailyTools: string[];
88
+ /** User-confirmed findings from onboarding research; absent when none. */
89
+ researchFindings?: string[];
88
90
  tone?: string;
89
91
  assistantName?: string;
90
92
  priorAssistants?: string[];
@@ -127,6 +129,16 @@ export function normalizeOnboardingContext(
127
129
  occupation: ctx.occupation?.trim() || undefined,
128
130
  commonWork: normalizeTasks(ctx.tasks),
129
131
  dailyTools: normalizeTools(ctx.tools),
132
+ // Findings are model-extracted from arbitrary web content and get written
133
+ // into persisted persona markdown as single bullets. Collapse ALL runs of
134
+ // whitespace (incl. newlines) so one finding can never mint extra lines —
135
+ // injected bullets, headings, or a `## ` that would corrupt the managed
136
+ // section boundary on later rewrites.
137
+ researchFindings: ctx.researchFindings?.length
138
+ ? ctx.researchFindings
139
+ .map((finding) => finding.replace(/\s+/g, " ").trim())
140
+ .filter(Boolean)
141
+ : undefined,
130
142
  tone: ctx.tone,
131
143
  assistantName: ctx.assistantName,
132
144
  googleConnected: ctx.googleConnected,
@@ -421,6 +421,14 @@ function buildOnboardingSection(normalized: NormalizedOnboarding): string {
421
421
  if (normalized.dailyTools.length > 0) {
422
422
  lines.push(`- **Daily tools:** ${normalized.dailyTools.join(", ")}`);
423
423
  }
424
+ if (normalized.researchFindings?.length) {
425
+ lines.push(
426
+ "- **Research findings** (surfaced during onboarding, confirmed by the user):",
427
+ );
428
+ for (const finding of normalized.researchFindings) {
429
+ lines.push(` - ${finding}`);
430
+ }
431
+ }
424
432
 
425
433
  lines.push("");
426
434
  return lines.join("\n");
@@ -83,7 +83,7 @@ These are enforcement rules, not advice.
83
83
 
84
84
  Identity writes (IDENTITY.md, SOUL.md), user-profile writes, journal entries: all wait until the rail produces real signal, which is Moment 1 output at the earliest. None of them delay a user-visible response. None of them happen alongside the opening turn.
85
85
 
86
- The base BOOTSTRAP task_preferences fallback is not on this rail. Your opener is the Port pitch.
86
+ Your opener is the Port pitch no other intake surface precedes it.
87
87
 
88
88
  ## Telemetry: tag your `ui_show` surfaces
89
89