@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091853.fbaa2ae

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 (475) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/docs/activation-funnel-telemetry.md +310 -0
  4. package/examples/plugins/echo/README.md +61 -60
  5. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  6. package/examples/plugins/echo/hooks/stop.ts +16 -0
  7. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  8. package/examples/plugins/echo/package.json +1 -2
  9. package/examples/plugins/echo/src/emit.ts +19 -0
  10. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  11. package/openapi.yaml +263 -121
  12. package/package.json +2 -2
  13. package/src/__tests__/activation-early-marking.test.ts +120 -0
  14. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  15. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  16. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  17. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  18. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  19. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  20. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  21. package/src/__tests__/agent-loop.test.ts +441 -96
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  23. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  24. package/src/__tests__/anthropic-provider.test.ts +23 -8
  25. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  26. package/src/__tests__/app-control-flow.test.ts +1 -1
  27. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  28. package/src/__tests__/app-executors.test.ts +132 -0
  29. package/src/__tests__/approval-cascade.test.ts +6 -5
  30. package/src/__tests__/approval-routes-http.test.ts +4 -1
  31. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  32. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  33. package/src/__tests__/channel-approvals.test.ts +1 -1
  34. package/src/__tests__/compaction-circuit.test.ts +258 -0
  35. package/src/__tests__/compaction-direct.test.ts +146 -0
  36. package/src/__tests__/compaction-events.test.ts +7 -7
  37. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  38. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  39. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  40. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  41. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  42. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  43. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  44. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  45. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  46. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  47. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  48. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  49. package/src/__tests__/conversation-error.test.ts +15 -1
  50. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  51. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  52. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  53. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  55. package/src/__tests__/conversation-queue.test.ts +10 -10
  56. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  57. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  58. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  59. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  60. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  61. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  62. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  65. package/src/__tests__/conversation-undo.test.ts +2 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  67. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/corrected-target.test.ts +93 -0
  71. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  72. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  73. package/src/__tests__/db-acp-history.test.ts +101 -0
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +1 -1
  76. package/src/__tests__/file-write-tool.test.ts +63 -0
  77. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  78. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  79. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  80. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  82. package/src/__tests__/heartbeat-service.test.ts +1 -0
  83. package/src/__tests__/history-repair-hook.test.ts +5 -1
  84. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  85. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  86. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  87. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  88. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  89. package/src/__tests__/injector-background-turn.test.ts +13 -23
  90. package/src/__tests__/injector-chain.test.ts +268 -44
  91. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  92. package/src/__tests__/injector-document-comments.test.ts +97 -114
  93. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  94. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  95. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  96. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  97. package/src/__tests__/llm-resolver.test.ts +73 -0
  98. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  99. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  100. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  101. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  102. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  103. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  104. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  105. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  106. package/src/__tests__/plugin-registry.test.ts +3 -49
  107. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  108. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  109. package/src/__tests__/plugin-types.test.ts +0 -70
  110. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  111. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  112. package/src/__tests__/published-app-updater.test.ts +138 -0
  113. package/src/__tests__/reaction-persistence.test.ts +1 -1
  114. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  115. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  116. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  117. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  118. package/src/__tests__/subagent-detail.test.ts +25 -7
  119. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  120. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  121. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  122. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  123. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  124. package/src/__tests__/title-generate-hook.test.ts +5 -1
  125. package/src/__tests__/tool-error-hook.test.ts +1 -1
  126. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  127. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  128. package/src/__tests__/web-fetch.test.ts +45 -0
  129. package/src/acp/__tests__/agent-process.test.ts +161 -0
  130. package/src/acp/__tests__/client-handler.test.ts +40 -0
  131. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  132. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  133. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  134. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  135. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  136. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  137. package/src/acp/agent-process.ts +61 -1
  138. package/src/acp/auto-install.test.ts +280 -0
  139. package/src/acp/auto-install.ts +232 -0
  140. package/src/acp/client-handler.ts +31 -0
  141. package/src/acp/prepare-agent-env.ts +80 -27
  142. package/src/acp/resolve-agent.test.ts +188 -28
  143. package/src/acp/resolve-agent.ts +119 -42
  144. package/src/acp/resume-hint.ts +23 -0
  145. package/src/acp/session-manager.ts +507 -73
  146. package/src/agent/compaction-circuit.ts +60 -102
  147. package/src/agent/loop.ts +403 -251
  148. package/src/api/responses/conversation-message.ts +22 -2
  149. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  150. package/src/approvals/guardian-request-resolvers.ts +27 -1
  151. package/src/background-wake/next-wake.ts +1 -0
  152. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  153. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  154. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  155. package/src/cli/commands/memory-v3.ts +57 -199
  156. package/src/cli/commands/plugins.ts +43 -37
  157. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  158. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  159. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  160. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  161. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  162. package/src/cli/lib/install-from-github.ts +797 -60
  163. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  164. package/src/cli/lib/plugin-details.ts +416 -0
  165. package/src/cli/lib/plugin-marketplace.ts +215 -0
  166. package/src/cli/lib/search-plugins.ts +201 -29
  167. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  168. package/src/config/acp-defaults.test.ts +10 -0
  169. package/src/config/acp-defaults.ts +9 -3
  170. package/src/config/acp-schema.ts +1 -7
  171. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  172. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  173. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  174. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  175. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  176. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  177. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  178. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  180. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  181. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  182. package/src/config/bundled-tool-registry.ts +4 -0
  183. package/src/config/call-site-defaults.ts +0 -3
  184. package/src/config/feature-flag-registry.json +10 -16
  185. package/src/config/llm-resolver.ts +39 -7
  186. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  187. package/src/config/schemas/call-site-catalog.ts +0 -21
  188. package/src/config/schemas/heartbeat.ts +9 -0
  189. package/src/config/schemas/llm.ts +17 -3
  190. package/src/config/schemas/memory-v3.ts +58 -8
  191. package/src/config/seed-inference-profiles.ts +18 -0
  192. package/src/context/compactor.ts +22 -4
  193. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  194. package/src/context/strip-injections.ts +8 -2
  195. package/src/daemon/conversation-agent-loop-handlers.ts +18 -36
  196. package/src/daemon/conversation-agent-loop.ts +308 -1297
  197. package/src/daemon/conversation-error.ts +31 -4
  198. package/src/daemon/conversation-history.ts +1 -1
  199. package/src/daemon/conversation-lifecycle.ts +11 -255
  200. package/src/daemon/conversation-media-retry.ts +19 -6
  201. package/src/daemon/conversation-messaging.ts +17 -0
  202. package/src/daemon/conversation-process.ts +22 -141
  203. package/src/daemon/conversation-queue-manager.ts +8 -0
  204. package/src/daemon/conversation-registry.ts +159 -0
  205. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  206. package/src/daemon/conversation-store.ts +9 -90
  207. package/src/daemon/conversation-surfaces.ts +165 -11
  208. package/src/daemon/conversation-workspace.ts +17 -0
  209. package/src/daemon/conversation.ts +444 -62
  210. package/src/daemon/disk-pressure-policy.ts +0 -1
  211. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  212. package/src/daemon/handlers/config-channels.ts +11 -2
  213. package/src/daemon/handlers/conversations.ts +3 -1
  214. package/src/daemon/handlers/skills.ts +4 -1
  215. package/src/daemon/host-app-control-proxy.ts +72 -57
  216. package/src/daemon/host-proxy-preactivation.ts +1 -3
  217. package/src/daemon/lifecycle.ts +21 -7
  218. package/src/daemon/persist-unsendable-image.ts +62 -25
  219. package/src/daemon/process-message.ts +1 -1
  220. package/src/daemon/server.ts +2 -0
  221. package/src/daemon/tool-side-effects.ts +15 -0
  222. package/src/daemon/wake-conversation-ops.ts +269 -0
  223. package/src/embedded/plugin-api.ts +2 -2
  224. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  225. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  226. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  227. package/src/heartbeat/heartbeat-service.ts +26 -0
  228. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  229. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  230. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  231. package/src/ipc/skill-routes/memory.ts +4 -2
  232. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  233. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  234. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  235. package/src/memory/activation-session-store.ts +43 -0
  236. package/src/memory/conversation-crud.ts +29 -19
  237. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  238. package/src/memory/db-init.ts +6 -0
  239. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  240. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  241. package/src/memory/jobs-worker.ts +25 -1
  242. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  243. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  244. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  245. package/src/memory/migrations/index.ts +3 -0
  246. package/src/memory/onboarding-events-store.ts +66 -18
  247. package/src/memory/schema/acp.ts +4 -0
  248. package/src/memory/schema/infrastructure.ts +13 -0
  249. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  250. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  251. package/src/memory/v2/consolidation-job.ts +2 -63
  252. package/src/memory/v2/types.ts +5 -0
  253. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  254. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  255. package/src/notifications/access-request-copy.ts +298 -0
  256. package/src/notifications/adapters/slack.ts +3 -3
  257. package/src/notifications/adapters/telegram.ts +9 -2
  258. package/src/notifications/copy-composer.ts +49 -267
  259. package/src/notifications/decision-engine.ts +16 -35
  260. package/src/notifications/home-feed-side-effect.ts +1 -6
  261. package/src/plugin-api/constants.ts +4 -0
  262. package/src/plugin-api/index.ts +40 -11
  263. package/src/plugin-api/types.ts +108 -0
  264. package/src/plugins/defaults/compaction/compact.ts +106 -0
  265. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  266. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  267. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  268. package/src/plugins/defaults/compaction/package.json +1 -2
  269. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  270. package/src/plugins/defaults/empty-response/package.json +0 -1
  271. package/src/plugins/defaults/history-repair/package.json +0 -1
  272. package/src/plugins/defaults/index.ts +156 -26
  273. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  274. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  275. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  276. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  277. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  278. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  282. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  283. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  284. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  285. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  286. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  287. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  288. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  289. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  290. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  291. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  293. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  294. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  295. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  296. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  297. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  298. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  299. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  300. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  301. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  302. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  303. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  304. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  306. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  307. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  308. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  309. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  310. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  311. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  312. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  313. package/src/plugins/defaults/title-generate/package.json +0 -1
  314. package/src/plugins/defaults/tool-error/package.json +0 -1
  315. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  316. package/src/plugins/external-api.ts +12 -2
  317. package/src/plugins/pipeline.ts +6 -293
  318. package/src/plugins/registry.ts +9 -37
  319. package/src/plugins/types.ts +76 -381
  320. package/src/plugins/user-loader.ts +30 -127
  321. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  322. package/src/prompts/system-prompt.ts +61 -10
  323. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  324. package/src/providers/anthropic/client.ts +9 -10
  325. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  326. package/src/providers/inference/logit-bias.ts +55 -0
  327. package/src/providers/model-catalog.ts +34 -0
  328. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  329. package/src/providers/openai/chat-completions-provider.ts +44 -1
  330. package/src/providers/retry.ts +22 -0
  331. package/src/providers/types.ts +6 -0
  332. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  333. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  334. package/src/runtime/agent-wake.ts +108 -209
  335. package/src/runtime/assistant-event-hub.ts +1 -1
  336. package/src/runtime/channel-approvals.ts +1 -1
  337. package/src/runtime/interactive-ui.ts +1 -1
  338. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  339. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  340. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  341. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  342. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  343. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  344. package/src/runtime/routes/acp-routes.test.ts +185 -32
  345. package/src/runtime/routes/acp-routes.ts +328 -30
  346. package/src/runtime/routes/app-management-routes.ts +3 -0
  347. package/src/runtime/routes/approval-routes.ts +1 -1
  348. package/src/runtime/routes/browser-routes.ts +1 -1
  349. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  350. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  351. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  352. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  353. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  354. package/src/runtime/routes/conversation-routes.ts +30 -2
  355. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  356. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  357. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  358. package/src/runtime/routes/host-cu-routes.ts +1 -1
  359. package/src/runtime/routes/identity-routes.ts +18 -3
  360. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  361. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  362. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  363. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  364. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  365. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  366. package/src/runtime/routes/playground/helpers.ts +1 -1
  367. package/src/runtime/routes/plugins-routes.ts +337 -35
  368. package/src/runtime/routes/stt-routes.ts +45 -12
  369. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  370. package/src/runtime/routes/work-items-routes.ts +2 -4
  371. package/src/runtime/routes/workspace-routes.ts +50 -15
  372. package/src/runtime/services/conversation-serializer.ts +1 -1
  373. package/src/runtime/verification-outbound-actions.ts +147 -2
  374. package/src/runtime/verification-templates.ts +29 -3
  375. package/src/services/published-app-updater.ts +30 -8
  376. package/src/signals/cancel.ts +2 -4
  377. package/src/subagent/manager.ts +21 -5
  378. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  379. package/src/telemetry/activation-funnel.ts +167 -0
  380. package/src/telemetry/types.ts +13 -0
  381. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  382. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  383. package/src/tools/acp/context.ts +20 -0
  384. package/src/tools/acp/list-agents.test.ts +9 -19
  385. package/src/tools/acp/list-agents.ts +3 -15
  386. package/src/tools/acp/spawn.test.ts +173 -202
  387. package/src/tools/acp/spawn.ts +37 -172
  388. package/src/tools/acp/steer.test.ts +105 -8
  389. package/src/tools/acp/steer.ts +48 -17
  390. package/src/tools/apps/executors.ts +166 -50
  391. package/src/tools/browser/browser-execution.ts +12 -2
  392. package/src/tools/filesystem/write.ts +34 -0
  393. package/src/tools/network/web-fetch.ts +65 -24
  394. package/src/tools/skills/load.ts +1 -1
  395. package/src/tools/subagent/spawn.ts +2 -4
  396. package/src/tools/ui-surface/definitions.ts +32 -5
  397. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  398. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  399. package/docs/plugins.md +0 -832
  400. package/examples/plugins/echo/register.ts +0 -143
  401. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  402. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  403. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  404. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  405. package/src/__tests__/pipeline-runner.test.ts +0 -554
  406. package/src/daemon/wake-target-adapter.ts +0 -253
  407. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  408. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  409. package/src/memory/v3/__tests__/core.test.ts +0 -39
  410. package/src/memory/v3/__tests__/health.test.ts +0 -219
  411. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  412. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  413. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  414. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  415. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  416. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  417. package/src/memory/v3/__tests__/router.test.ts +0 -332
  418. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  419. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  420. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  421. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  422. package/src/memory/v3/assign.ts +0 -268
  423. package/src/memory/v3/capabilities.ts +0 -124
  424. package/src/memory/v3/core.ts +0 -26
  425. package/src/memory/v3/health.ts +0 -0
  426. package/src/memory/v3/maintain-job.ts +0 -314
  427. package/src/memory/v3/needle.ts +0 -115
  428. package/src/memory/v3/orchestrate.ts +0 -126
  429. package/src/memory/v3/page-content.ts +0 -34
  430. package/src/memory/v3/provider-blocks.ts +0 -26
  431. package/src/memory/v3/reconcile.ts +0 -523
  432. package/src/memory/v3/router.ts +0 -190
  433. package/src/memory/v3/selection-log-store.ts +0 -84
  434. package/src/memory/v3/selector.ts +0 -226
  435. package/src/memory/v3/shadow-plugin.ts +0 -411
  436. package/src/memory/v3/snapshot.ts +0 -209
  437. package/src/memory/v3/tree.ts +0 -174
  438. package/src/memory/v3/types.ts +0 -59
  439. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  440. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  441. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  442. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  443. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  444. package/src/plugins/defaults/compaction/register.ts +0 -35
  445. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  446. package/src/plugins/defaults/empty-response/register.ts +0 -23
  447. package/src/plugins/defaults/history-repair/register.ts +0 -24
  448. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  449. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  450. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  451. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  452. package/src/plugins/defaults/title-generate/register.ts +0 -35
  453. package/src/plugins/defaults/tool-error/register.ts +0 -23
  454. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  455. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  456. package/src/proactive-artifact/decision.test.ts +0 -226
  457. package/src/proactive-artifact/decision.ts +0 -165
  458. package/src/proactive-artifact/index.ts +0 -7
  459. package/src/proactive-artifact/job.test.ts +0 -962
  460. package/src/proactive-artifact/job.ts +0 -372
  461. package/src/proactive-artifact/message-copy.ts +0 -58
  462. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  463. package/src/proactive-artifact/trigger-state.ts +0 -123
  464. package/src/util/map-limit.ts +0 -27
  465. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  466. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  467. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  468. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  469. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  470. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  471. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  472. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  473. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  474. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  475. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
@@ -46,14 +46,24 @@ const workspaceTreeEntrySchema = z.object({
46
46
 
47
47
  type TreeEntry = z.infer<typeof workspaceTreeEntrySchema>;
48
48
 
49
- // Total number of filesystem entries we're willing to traverse when computing
50
- // recursive directory sizes for a single tree listing. The budget is shared
51
- // across every directory in the response so a giant `repos/` checkout cannot
52
- // starve the request — once exhausted, remaining directories report `size:
53
- // null` (caller renders an unknown-size placeholder). 50k is generous for
54
- // typical workspaces (sounds, db, logs) while bailing fast on multi-repo
55
- // trees.
56
- const DIR_SIZE_TOTAL_ENTRY_BUDGET = 50_000;
49
+ // Recursive directory sizing walks the filesystem synchronously, so it is
50
+ // bounded by two caps that work together:
51
+ //
52
+ // - `DIR_SIZE_PER_DIR_ENTRY_BUDGET` caps a single directory. No one giant
53
+ // subtree (e.g. a `node_modules/` or `repos/` checkout) can consume the whole
54
+ // listing's budget and starve its siblings; only the oversized directory
55
+ // itself reports `size: null` (the caller renders an unknown-size
56
+ // placeholder) while every other folder still shows an accurate size.
57
+ // - `DIR_SIZE_TOTAL_ENTRY_BUDGET` caps the listing as a whole, so a workspace
58
+ // with many large sibling directories still can't block the daemon event loop
59
+ // for an unbounded amount of time. Once it is exhausted, the remaining
60
+ // directories report `size: null`.
61
+ //
62
+ // Each directory is allotted `min(per-dir cap, remaining listing budget)`
63
+ // entries, and whatever it actually consumes is deducted from the listing
64
+ // budget.
65
+ const DIR_SIZE_PER_DIR_ENTRY_BUDGET = 250_000;
66
+ const DIR_SIZE_TOTAL_ENTRY_BUDGET = 1_000_000;
57
67
 
58
68
  interface DirSizeBudget {
59
69
  remaining: number;
@@ -61,13 +71,13 @@ interface DirSizeBudget {
61
71
 
62
72
  /**
63
73
  * Recursively sum the byte size of every regular file under `absPath`,
64
- * sharing a single entry budget across all directories in the same listing.
74
+ * traversing up to `budget.remaining` filesystem entries.
65
75
  *
66
76
  * Returns:
67
77
  * - the total size in bytes when the entire subtree was traversed within
68
78
  * budget
69
- * - `null` if the shared budget was exhausted before completion (the caller
70
- * surfaces that as an unknown size in the UI)
79
+ * - `null` if the budget was exhausted before completion (the caller surfaces
80
+ * that as an unknown size in the UI)
71
81
  *
72
82
  * Symlinks are not followed. We rely on `withFileTypes` so we never `stat`
73
83
  * directories purely to discover their type.
@@ -108,6 +118,27 @@ function computeDirSize(absPath: string, budget: DirSizeBudget): number | null {
108
118
  return total;
109
119
  }
110
120
 
121
+ /**
122
+ * Compute a directory's recursive size while drawing from a shared listing
123
+ * budget. The directory is allotted at most `DIR_SIZE_PER_DIR_ENTRY_BUDGET`
124
+ * entries — and never more than the listing has left — and whatever it consumes
125
+ * is deducted from `listingBudget` so the next sibling sees the reduced
126
+ * remainder. Returns `null` (unknown size) when the allotment is exhausted.
127
+ */
128
+ function computeDirSizeWithinListing(
129
+ absPath: string,
130
+ listingBudget: DirSizeBudget,
131
+ ): number | null {
132
+ const allotment = Math.min(
133
+ DIR_SIZE_PER_DIR_ENTRY_BUDGET,
134
+ listingBudget.remaining,
135
+ );
136
+ const dirBudget: DirSizeBudget = { remaining: allotment };
137
+ const size = computeDirSize(absPath, dirBudget);
138
+ listingBudget.remaining -= allotment - dirBudget.remaining;
139
+ return size;
140
+ }
141
+
111
142
  const SOUNDS_WORKSPACE_PATH = "data/sounds";
112
143
 
113
144
  function normaliseWorkspacePathForSync(path: string): string {
@@ -153,9 +184,11 @@ function handleWorkspaceTree({ queryParams }: RouteHandlerArgs) {
153
184
  const dirents = readdirSync(resolved, { withFileTypes: true });
154
185
  const workspaceDir = getWorkspaceDir();
155
186
 
156
- // Shared budget across every directory in this listing. When opted out,
157
- // we never construct one, so directories report `size: null` as before.
158
- const dirSizeBudget: DirSizeBudget | undefined = includeDirSizes
187
+ // Listing-wide budget, only constructed when sizes are requested. Each
188
+ // directory draws a per-directory allotment from it so one oversized
189
+ // subtree can't starve its siblings, while the shared remainder still caps
190
+ // total synchronous traversal for the whole listing.
191
+ const listingBudget: DirSizeBudget | undefined = includeDirSizes
159
192
  ? { remaining: DIR_SIZE_TOTAL_ENTRY_BUDGET }
160
193
  : undefined;
161
194
 
@@ -176,7 +209,9 @@ function handleWorkspaceTree({ queryParams }: RouteHandlerArgs) {
176
209
  const relativePath = fullPath.slice(workspaceDir.length + 1);
177
210
 
178
211
  const dirSize =
179
- isDir && dirSizeBudget ? computeDirSize(fullPath, dirSizeBudget) : null;
212
+ isDir && listingBudget
213
+ ? computeDirSizeWithinListing(fullPath, listingBudget)
214
+ : null;
180
215
 
181
216
  entries.push({
182
217
  name: entry.name,
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { parseChannelId } from "../../channels/types.js";
11
11
  import { getConfig } from "../../config/loader.js";
12
- import { findConversation } from "../../daemon/conversation-store.js";
12
+ import { findConversation } from "../../daemon/conversation-registry.js";
13
13
  import { normalizeConversationType } from "../../daemon/message-types/shared.js";
14
14
  import {
15
15
  type AttentionState,
@@ -2,7 +2,7 @@
2
2
  * Shared outbound verification action logic.
3
3
  *
4
4
  * These pure functions encapsulate the business logic for starting, resending,
5
- * and cancelling outbound verification flows (Telegram, voice, Slack).
5
+ * and cancelling outbound verification flows (Telegram, voice, Slack, email).
6
6
  * They return transport-agnostic result objects and are consumed by both the
7
7
  * message handler (config-channels.ts) and the HTTP route layer (channel-verification-routes.ts).
8
8
  */
@@ -26,6 +26,7 @@ import {
26
26
  updateSessionStatus,
27
27
  } from "./channel-verification-service.js";
28
28
  import {
29
+ composeVerificationEmail,
29
30
  composeVerificationSlack,
30
31
  composeVerificationTelegram,
31
32
  GUARDIAN_VERIFY_TEMPLATE_KEYS,
@@ -52,6 +53,8 @@ export const DESTINATION_RATE_WINDOW_MS = 3_600_000;
52
53
  /** Session TTL in seconds (matches challenge TTL of 10 minutes). */
53
54
  const SESSION_TTL_SECONDS = 600;
54
55
 
56
+ const EMAIL_VERIFICATION_SUBJECT = "Vellum Assistant Guardian Verification";
57
+
55
58
  // ---------------------------------------------------------------------------
56
59
  // Telegram destination classification
57
60
  // ---------------------------------------------------------------------------
@@ -122,6 +125,8 @@ interface OutboundActionResult {
122
125
  * reach the gateway. The daemon HTTP route handler calls
123
126
  * deliverVerificationSlack() after receiving this payload. */
124
127
  _pendingSlackDm?: { userId: string; text: string; assistantId: string };
128
+ /** Internal: email delivery payload for the caller to dispatch (same pattern as Slack). */
129
+ _pendingEmail?: { to: string; text: string; subject: string; assistantId: string };
125
130
  }
126
131
 
127
132
  // ---------------------------------------------------------------------------
@@ -512,6 +517,80 @@ export function deliverVerificationSlack(
512
517
  deliverVerificationSlackAsync(userId, text, assistantId);
513
518
  }
514
519
 
520
+ // ---------------------------------------------------------------------------
521
+ // Email delivery helper
522
+ // ---------------------------------------------------------------------------
523
+
524
+ /**
525
+ * Deliver a verification email via the platform email send API.
526
+ * Fire-and-forget wrapper for use in the daemon process (HTTP route handlers).
527
+ */
528
+ export function deliverVerificationEmail(
529
+ to: string,
530
+ text: string,
531
+ subject: string,
532
+ _assistantId: string,
533
+ ): void {
534
+ (async () => {
535
+ try {
536
+ const { VellumPlatformClient } = await import(
537
+ "../platform/client.js"
538
+ );
539
+ const client = await VellumPlatformClient.create();
540
+ if (!client?.platformAssistantId) {
541
+ log.error("Cannot deliver verification email: platform client not configured");
542
+ return;
543
+ }
544
+
545
+ const listResponse = await client.fetch(
546
+ `/v1/assistants/${client.platformAssistantId}/email-addresses/`,
547
+ );
548
+ if (!listResponse.ok) {
549
+ log.error({ status: listResponse.status }, "Failed to list email addresses for verification");
550
+ return;
551
+ }
552
+ const listData = (await listResponse.json()) as {
553
+ results: { address: string }[];
554
+ };
555
+ const addresses = listData.results ?? [];
556
+ if (addresses.length === 0) {
557
+ log.error("No email address registered — cannot deliver verification email");
558
+ return;
559
+ }
560
+ const fromAddress = addresses[0].address;
561
+
562
+ const { markdownToEmailHtml } = await import(
563
+ "../email/html-renderer.js"
564
+ );
565
+ const html = markdownToEmailHtml(text);
566
+
567
+ const response = await client.fetch("/v1/runtime-proxy/email/send/", {
568
+ method: "POST",
569
+ headers: { "Content-Type": "application/json" },
570
+ body: JSON.stringify({
571
+ to: [to],
572
+ from_address: fromAddress,
573
+ text,
574
+ subject,
575
+ html,
576
+ }),
577
+ });
578
+
579
+ if (response.ok) {
580
+ log.info({ to }, "Verification email delivered");
581
+ } else {
582
+ const respBody = await response.json().catch(() => ({}));
583
+ log.error(
584
+ { to, status: response.status, respBody },
585
+ "Failed to deliver verification email",
586
+ );
587
+ }
588
+ } catch (err) {
589
+ log.error({ err, to }, "Failed to deliver verification email");
590
+ }
591
+ })();
592
+ }
593
+
515
594
  function startOutboundSlack(
516
595
  destination: string | undefined,
517
596
  assistantId: string,
@@ -644,13 +723,35 @@ function startOutboundEmail(
644
723
  verificationPurpose: "guardian",
645
724
  });
646
725
 
726
+ const emailBody = composeVerificationEmail(
727
+ GUARDIAN_VERIFY_TEMPLATE_KEYS.EMAIL_CHALLENGE_REQUEST,
728
+ {
729
+ code: sessionResult.secret,
730
+ expiresInMinutes: Math.floor(SESSION_TTL_SECONDS / 60),
731
+ },
732
+ );
733
+
734
+ const now = Date.now();
735
+ const nextResendAt = now + RESEND_COOLDOWN_MS;
736
+ const sendCount = 1;
737
+
738
+ updateSessionDelivery(sessionResult.sessionId, now, sendCount, nextResendAt);
739
+
647
740
  return {
648
741
  success: true,
649
742
  verificationSessionId: sessionResult.sessionId,
650
743
  secret: sessionResult.secret,
651
744
  expiresAt: sessionResult.expiresAt,
745
+ nextResendAt,
746
+ sendCount,
652
747
  channel,
653
748
  originConversationId,
749
+ _pendingEmail: {
750
+ to: normalizedEmail,
751
+ text: emailBody,
752
+ subject: EMAIL_VERIFICATION_SUBJECT,
753
+ assistantId,
754
+ },
654
755
  };
655
756
  }
656
757
 
@@ -851,12 +952,56 @@ export function resendOutbound(
851
952
  originConversationId,
852
953
  _pendingSlackDm: { userId: destination, text: slackBody, assistantId },
853
954
  };
955
+ } else if (channel === "email") {
956
+ const newSession = createOutboundSession({
957
+ channel,
958
+ expectedExternalUserId: destination,
959
+ expectedChatId: destination,
960
+ identityBindingStatus: "bound",
961
+ destinationAddress: destination,
962
+ verificationPurpose: "guardian",
963
+ });
964
+
965
+ const emailBody = composeVerificationEmail(
966
+ GUARDIAN_VERIFY_TEMPLATE_KEYS.EMAIL_RESEND,
967
+ {
968
+ code: newSession.secret,
969
+ expiresInMinutes: Math.floor(SESSION_TTL_SECONDS / 60),
970
+ },
971
+ );
972
+
973
+ const now = Date.now();
974
+ const newSendCount = currentSendCount + 1;
975
+ const nextResendAt = now + RESEND_COOLDOWN_MS;
976
+
977
+ updateSessionDelivery(
978
+ newSession.sessionId,
979
+ now,
980
+ newSendCount,
981
+ nextResendAt,
982
+ );
983
+
984
+ return {
985
+ success: true,
986
+ verificationSessionId: newSession.sessionId,
987
+ secret: newSession.secret,
988
+ nextResendAt,
989
+ sendCount: newSendCount,
990
+ channel,
991
+ originConversationId,
992
+ _pendingEmail: {
993
+ to: destination,
994
+ text: emailBody,
995
+ subject: EMAIL_VERIFICATION_SUBJECT,
996
+ assistantId,
997
+ },
998
+ };
854
999
  }
855
1000
 
856
1001
  return {
857
1002
  success: false,
858
1003
  error: "unsupported_channel",
859
- message: `Resend is only supported for Telegram, phone, and Slack. Got: ${channel}`,
1004
+ message: `Resend is only supported for Telegram, phone, Slack, and email. Got: ${channel}`,
860
1005
  channel,
861
1006
  };
862
1007
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Template-only copy for outbound guardian verification messages (Telegram, Slack, and voice).
2
+ * Template-only copy for outbound guardian verification messages (Telegram, Slack, email, and voice).
3
3
  *
4
4
  * All outbound verification messages are composed from these templates
5
5
  * to prevent free-form caller/user text injection. Only typed variables
@@ -26,6 +26,10 @@ export const GUARDIAN_VERIFY_TEMPLATE_KEYS = {
26
26
  "guardian_verify.slack.trusted_contact_challenge",
27
27
  /** Resend Slack DM verification for inbound trusted contact (includes the code). */
28
28
  SLACK_TRUSTED_CONTACT_RESEND: "guardian_verify.slack.trusted_contact_resend",
29
+ /** Initial outbound email verification prompt (includes the code). */
30
+ EMAIL_CHALLENGE_REQUEST: "guardian_verify.email.challenge_request",
31
+ /** Resend email verification prompt (includes the code). */
32
+ EMAIL_RESEND: "guardian_verify.email.resend",
29
33
  /** Outbound voice call intro prompt: asks guardian to enter verification code via keypad. */
30
34
  VOICE_CALL_INTRO: "guardian_verify.voice.call_intro",
31
35
  /** Voice retry prompt after an incorrect code entry. */
@@ -42,7 +46,7 @@ export const GUARDIAN_VERIFY_TEMPLATE_KEYS = {
42
46
  CHANNEL_BOOTSTRAP_BOUND: "guardian_verify.channel.bootstrap_bound",
43
47
  } as const;
44
48
 
45
- /** Template keys for Telegram/Slack text-based verification messages. */
49
+ /** Template keys for Telegram/Slack/email text-based verification messages. */
46
50
  type TextVerifyTemplateKey =
47
51
  | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.ALREADY_VERIFIED
48
52
  | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.TELEGRAM_CHALLENGE_REQUEST
@@ -50,7 +54,9 @@ type TextVerifyTemplateKey =
50
54
  | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.SLACK_CHALLENGE_REQUEST
51
55
  | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.SLACK_RESEND
52
56
  | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.SLACK_TRUSTED_CONTACT_CHALLENGE
53
- | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.SLACK_TRUSTED_CONTACT_RESEND;
57
+ | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.SLACK_TRUSTED_CONTACT_RESEND
58
+ | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.EMAIL_CHALLENGE_REQUEST
59
+ | typeof GUARDIAN_VERIFY_TEMPLATE_KEYS.EMAIL_RESEND;
54
60
 
55
61
  /** Template keys for deterministic channel verification reply messages. */
56
62
  type ChannelVerifyReplyTemplateKey =
@@ -115,6 +121,14 @@ const templates: Record<
115
121
  [GUARDIAN_VERIFY_TEMPLATE_KEYS.SLACK_TRUSTED_CONTACT_RESEND]: (vars) => {
116
122
  return `Vellum assistant verification: your code is ${vars.code}. Reply with this code to verify your identity. It expires in ${vars.expiresInMinutes} minutes. (resent)`;
117
123
  },
124
+
125
+ [GUARDIAN_VERIFY_TEMPLATE_KEYS.EMAIL_CHALLENGE_REQUEST]: (vars) => {
126
+ return `Vellum assistant guardian verification requested.\n\nYour verification code is: ${vars.code}\n\nReply to this email with only the 6-digit code above to complete verification. This code expires in ${vars.expiresInMinutes} minutes.`;
127
+ },
128
+
129
+ [GUARDIAN_VERIFY_TEMPLATE_KEYS.EMAIL_RESEND]: (vars) => {
130
+ return `Vellum assistant guardian verification requested.\n\nYour new verification code is: ${vars.code}\n\nReply to this email with only the 6-digit code above to complete verification. This code expires in ${vars.expiresInMinutes} minutes. (resent)`;
131
+ },
118
132
  };
119
133
 
120
134
  /**
@@ -129,6 +143,18 @@ export function composeVerificationSlack(
129
143
  return composer(vars);
130
144
  }
131
145
 
146
+ /**
147
+ * Compose an outbound verification email from a template key and typed variables.
148
+ * Returns plain string content suitable for email delivery.
149
+ */
150
+ export function composeVerificationEmail(
151
+ templateKey: TextVerifyTemplateKey,
152
+ vars: GuardianVerifyTemplateVars,
153
+ ): string {
154
+ const composer = templates[templateKey];
155
+ return composer(vars);
156
+ }
157
+
132
158
  /**
133
159
  * Compose an outbound verification Telegram message from a template key and typed variables.
134
160
  * Returns plain string content suitable for Telegram delivery.
@@ -3,8 +3,15 @@
3
3
  */
4
4
 
5
5
  import { createHash } from "node:crypto";
6
+ import { existsSync } from "node:fs";
7
+ import { join } from "node:path";
6
8
 
7
- import { getApp } from "../memory/app-store.js";
9
+ import {
10
+ getApp,
11
+ getAppDirPath,
12
+ isMultifileApp,
13
+ resolveEffectiveAppHtml,
14
+ } from "../memory/app-store.js";
8
15
  import {
9
16
  getActivePublishedPageByAppId,
10
17
  updatePublishedPage,
@@ -23,17 +30,32 @@ export async function updatePublishedAppDeployment(
23
30
  const publishedPage = getActivePublishedPageByAppId(appId);
24
31
  if (!publishedPage) return;
25
32
 
26
- // 2. Load the app to get current HTML
33
+ // 2. Load the app and resolve its deployable HTML. For multifile apps the
34
+ // real content lives in dist/index.html (compiled from src/), not in
35
+ // htmlDefinition (which is "" for them) — using htmlDefinition directly
36
+ // would deploy a blank page.
27
37
  const app = getApp(appId);
28
- if (!app || !app.htmlDefinition) {
29
- log.warn({ appId }, "Published app not found or has no HTML");
38
+ if (!app) {
39
+ log.warn({ appId }, "Published app not found");
30
40
  return;
31
41
  }
32
42
 
43
+ // Skip rather than deploy the compile-failure fallback when a multifile
44
+ // app has no compiled output (e.g. a concurrent compile failed); a later
45
+ // successful compile re-triggers this path.
46
+ if (
47
+ isMultifileApp(app) &&
48
+ !existsSync(join(getAppDirPath(app.id), "dist", "index.html"))
49
+ ) {
50
+ log.warn({ appId }, "Skipping auto-redeploy: compiled output missing");
51
+ return;
52
+ }
53
+
54
+ const html = resolveEffectiveAppHtml(app);
55
+ if (!html) return;
56
+
33
57
  // 3. Hash the current HTML and check if it changed
34
- const newHash = createHash("sha256")
35
- .update(app.htmlDefinition)
36
- .digest("hex");
58
+ const newHash = createHash("sha256").update(html).digest("hex");
37
59
  if (newHash === publishedPage.htmlHash) return; // No change
38
60
 
39
61
  // 4. Get Vercel token — don't prompt, just skip if unavailable
@@ -46,7 +68,7 @@ export async function updatePublishedAppDeployment(
46
68
  execute: async (token) => {
47
69
  // 5. Deploy updated HTML using the same project slug
48
70
  const result = await deployHtmlToVercel({
49
- html: app.htmlDefinition,
71
+ html,
50
72
  name: slug,
51
73
  token,
52
74
  });
@@ -11,10 +11,8 @@ import { readFileSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
 
13
13
  import { getIsContainerized } from "../config/env-registry.js";
14
- import {
15
- findConversation,
16
- touchConversation,
17
- } from "../daemon/conversation-store.js";
14
+ import { findConversation } from "../daemon/conversation-registry.js";
15
+ import { touchConversation } from "../daemon/conversation-store.js";
18
16
  import { getSubagentManager } from "../subagent/index.js";
19
17
  import { createAbortReason } from "../util/abort-reasons.js";
20
18
  import { getLogger } from "../util/logger.js";
@@ -13,7 +13,11 @@ import { v4 as uuid } from "uuid";
13
13
  import { resolveCallSiteConfig } from "../config/llm-resolver.js";
14
14
  import { getConfig } from "../config/loader.js";
15
15
  import { Conversation } from "../daemon/conversation.js";
16
- import { findConversation } from "../daemon/conversation-store.js";
16
+ import {
17
+ findConversation,
18
+ removeSubagentConversation,
19
+ setSubagentConversation,
20
+ } from "../daemon/conversation-registry.js";
17
21
  import type { ServerMessage } from "../daemon/message-protocol.js";
18
22
  import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
19
23
  import { wrapWithCallSiteRouting } from "../providers/call-site-routing.js";
@@ -294,6 +298,10 @@ export class SubagentManager {
294
298
  // This ensures interactive prompts (host attachment reads) fail fast.
295
299
  conversation.updateClient(wrappedSendToClient, true);
296
300
  conversation.setIsSubagent(true);
301
+ // Subagents are created as background conversations (see the
302
+ // `bootstrapConversation` call above) and never call `loadFromDb`, so cache
303
+ // the type on the live conversation directly for the runtime-assembly path.
304
+ conversation.conversationType = "background";
297
305
 
298
306
  // Subagents execute as background child conversations, but their tool
299
307
  // permissions must still be scoped to the actor that spawned them. Without
@@ -334,6 +342,10 @@ export class SubagentManager {
334
342
 
335
343
  managed.conversation = conversation;
336
344
  this.subagents.set(subagentId, managed);
345
+ // Index the live conversation so the per-conversation injectors (workspace
346
+ // context, disk-pressure warning) can resolve it by id; subagents are not
347
+ // in the eviction-managed conversation store.
348
+ setSubagentConversation(conversationRecord.id, conversation);
337
349
  const labelKey = `${config.parentConversationId}:${config.label.toLowerCase().trim()}`;
338
350
  if (this.labelIndex.has(labelKey)) {
339
351
  log.warn(
@@ -695,7 +707,9 @@ export class SubagentManager {
695
707
  */
696
708
  private releaseConversation(managed: ManagedSubagent): void {
697
709
  if (!managed.conversation) return;
698
- managed.conversation.dispose();
710
+ const conversation = managed.conversation;
711
+ removeSubagentConversation(conversation.conversationId, conversation);
712
+ conversation.dispose();
699
713
  managed.conversation = null;
700
714
  managed.retainedUntil = Date.now() + TERMINAL_RETENTION_MS;
701
715
  this.ensureSweepRunning();
@@ -716,16 +730,18 @@ export class SubagentManager {
716
730
  if (!managed) return;
717
731
 
718
732
  if (managed.conversation) {
733
+ const conversation = managed.conversation;
719
734
  if (!TERMINAL_STATUSES.has(managed.state.status)) {
720
- managed.conversation.abort(
735
+ conversation.abort(
721
736
  createAbortReason(
722
737
  "subagent_aborted",
723
738
  "SubagentManager.dispose",
724
- managed.conversation.conversationId,
739
+ conversation.conversationId,
725
740
  ),
726
741
  );
727
742
  }
728
- managed.conversation.dispose();
743
+ removeSubagentConversation(conversation.conversationId, conversation);
744
+ conversation.dispose();
729
745
  managed.conversation = null;
730
746
  }
731
747
  this.subagents.delete(subagentId);
@@ -0,0 +1,95 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ ACTIVATION_MOMENT_PARAMS,
5
+ ACTIVATION_STEPS,
6
+ activationStepIndex,
7
+ activationStepNameForMomentParam,
8
+ buildActivationDaemonEventId,
9
+ isActivationMomentParam,
10
+ isActivationStepName,
11
+ } from "../activation-funnel.js";
12
+
13
+ const ALL_STEP_NAMES = Object.values(ACTIVATION_STEPS).map((s) => s.stepName);
14
+
15
+ describe("activation funnel vocabulary", () => {
16
+ test("step indices are 1-5 and unique", () => {
17
+ const indices = Object.values(ACTIVATION_STEPS).map((s) => s.stepIndex);
18
+ expect([...indices].sort((a, b) => a - b)).toEqual([1, 2, 3, 4, 5]);
19
+ expect(new Set(indices).size).toBe(indices.length);
20
+ });
21
+
22
+ test("activationStepIndex resolves index from name", () => {
23
+ for (const { stepName, stepIndex } of Object.values(ACTIVATION_STEPS)) {
24
+ expect(activationStepIndex(stepName)).toBe(stepIndex);
25
+ }
26
+ });
27
+
28
+ test("isActivationStepName accepts all five step names", () => {
29
+ expect(ALL_STEP_NAMES).toHaveLength(5);
30
+ for (const stepName of ALL_STEP_NAMES) {
31
+ expect(isActivationStepName(stepName)).toBe(true);
32
+ }
33
+ });
34
+
35
+ test("isActivationStepName rejects unknown names", () => {
36
+ expect(isActivationStepName("bogus")).toBe(false);
37
+ });
38
+
39
+ test("buildActivationDaemonEventId is deterministic", () => {
40
+ expect(
41
+ buildActivationDaemonEventId("conv-abc", "activation_moment_1_complete"),
42
+ ).toBe("activation_v1_2026_06:conv-abc:activation_moment_1_complete");
43
+ });
44
+ });
45
+
46
+ describe("activation moment param (model-facing tag)", () => {
47
+ test("ACTIVATION_MOMENT_PARAMS lists the five tokens in step order", () => {
48
+ expect(ACTIVATION_MOMENT_PARAMS).toEqual([
49
+ "moment_1",
50
+ "moment_2",
51
+ "moment_3",
52
+ "first_wow_executed",
53
+ "first_wow_interacted",
54
+ ]);
55
+ });
56
+
57
+ test("isActivationMomentParam accepts all five tokens", () => {
58
+ for (const param of ACTIVATION_MOMENT_PARAMS) {
59
+ expect(isActivationMomentParam(param)).toBe(true);
60
+ }
61
+ });
62
+
63
+ test("isActivationMomentParam rejects unknown tokens (incl. wire step names)", () => {
64
+ expect(isActivationMomentParam("bogus")).toBe(false);
65
+ // The wire step name is NOT a valid model-facing token.
66
+ expect(isActivationMomentParam("activation_moment_1_complete")).toBe(false);
67
+ expect(isActivationMomentParam("moment_4")).toBe(false);
68
+ });
69
+
70
+ test("activationStepNameForMomentParam maps each token to its step name", () => {
71
+ expect(activationStepNameForMomentParam("moment_1")).toBe(
72
+ ACTIVATION_STEPS.moment1.stepName,
73
+ );
74
+ expect(activationStepNameForMomentParam("moment_2")).toBe(
75
+ ACTIVATION_STEPS.moment2.stepName,
76
+ );
77
+ expect(activationStepNameForMomentParam("moment_3")).toBe(
78
+ ACTIVATION_STEPS.moment3.stepName,
79
+ );
80
+ expect(activationStepNameForMomentParam("first_wow_executed")).toBe(
81
+ ACTIVATION_STEPS.firstWowExecuted.stepName,
82
+ );
83
+ expect(activationStepNameForMomentParam("first_wow_interacted")).toBe(
84
+ ACTIVATION_STEPS.firstWowInteracted.stepName,
85
+ );
86
+ });
87
+
88
+ test("every moment param maps to a valid wire step name", () => {
89
+ for (const param of ACTIVATION_MOMENT_PARAMS) {
90
+ expect(
91
+ isActivationStepName(activationStepNameForMomentParam(param)),
92
+ ).toBe(true);
93
+ }
94
+ });
95
+ });