@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
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Daemon-side side effects a wake performs against a live
3
+ * {@link Conversation}, used by `wakeAgentForOpportunity()` in
4
+ * `runtime/agent-wake.ts`.
5
+ *
6
+ * These are kept as standalone functions (rather than methods on
7
+ * `Conversation`) so the wake-specific concerns — event→wire translation,
8
+ * the wake ui_surface card, and the wake's one-shot tail persistence —
9
+ * stay out of the conversation class. They live in the daemon layer
10
+ * because they translate agent-loop events into client wire frames and
11
+ * touch the message store, so `runtime/agent-wake.ts` references only the
12
+ * erased `Conversation` type and never imports `message-protocol.ts`.
13
+ */
14
+
15
+ import type { AgentEvent } from "../agent/loop.js";
16
+ import {
17
+ addMessage,
18
+ getConversation,
19
+ provenanceFromTrustContext,
20
+ } from "../memory/conversation-crud.js";
21
+ import { syncMessageToDisk } from "../memory/conversation-disk-view.js";
22
+ import { backfillMessageIdOnLogs } from "../memory/llm-request-log-store.js";
23
+ import type { Message } from "../providers/types.js";
24
+ import { broadcastMessage } from "../runtime/assistant-event-hub.js";
25
+ import { getLogger } from "../util/logger.js";
26
+ import type { Conversation } from "./conversation.js";
27
+ import type { ServerMessage } from "./message-protocol.js";
28
+
29
+ const log = getLogger("wake-conversation-ops");
30
+
31
+ /**
32
+ * Translate a raw {@link AgentEvent} from the agent loop into the
33
+ * corresponding {@link ServerMessage} wire frame. The normal user-turn
34
+ * path does this via the full state-aware handler in
35
+ * `conversation-agent-loop-handlers.ts`; the wake path has no tool
36
+ * accounting, title generation, or activity-state tracking to worry
37
+ * about, so we only need the subset that produces client-visible
38
+ * frames. Events that have no client-visible wire shape (usage, error,
39
+ * preview/input-json deltas, etc.) are dropped — they produce no UI.
40
+ */
41
+ function translateAgentEventToServerMessage(
42
+ event: AgentEvent,
43
+ conversationId: string,
44
+ ): ServerMessage | null {
45
+ switch (event.type) {
46
+ case "text_delta":
47
+ return {
48
+ type: "assistant_text_delta",
49
+ text: event.text,
50
+ conversationId,
51
+ };
52
+ case "thinking_delta":
53
+ return {
54
+ type: "assistant_thinking_delta",
55
+ thinking: event.thinking,
56
+ conversationId,
57
+ };
58
+ case "tool_use":
59
+ return {
60
+ type: "tool_use_start",
61
+ toolName: event.name,
62
+ input: event.input,
63
+ conversationId,
64
+ toolUseId: event.id,
65
+ };
66
+ case "tool_use_preview_start":
67
+ return {
68
+ type: "tool_use_preview_start",
69
+ toolUseId: event.toolUseId,
70
+ toolName: event.toolName,
71
+ conversationId,
72
+ };
73
+ case "tool_output_chunk":
74
+ return {
75
+ type: "tool_output_chunk",
76
+ chunk: event.chunk,
77
+ conversationId,
78
+ toolUseId: event.toolUseId,
79
+ };
80
+ case "tool_result": {
81
+ const imageBlocks = event.contentBlocks?.filter(
82
+ (b): b is Extract<typeof b, { type: "image" }> => b.type === "image",
83
+ );
84
+ const imageDataList = imageBlocks?.length
85
+ ? imageBlocks.map((b) => b.source.data)
86
+ : undefined;
87
+ return {
88
+ type: "tool_result",
89
+ toolName: "",
90
+ result: event.content,
91
+ isError: event.isError,
92
+ diff: event.diff,
93
+ status: event.status,
94
+ conversationId,
95
+ imageData: imageDataList?.[0],
96
+ imageDataList,
97
+ toolUseId: event.toolUseId,
98
+ };
99
+ }
100
+ case "server_tool_start":
101
+ return {
102
+ type: "tool_use_start",
103
+ toolName: event.name,
104
+ input: event.input,
105
+ conversationId,
106
+ toolUseId: event.toolUseId,
107
+ };
108
+ case "server_tool_complete": {
109
+ let resultText = "";
110
+ if (Array.isArray(event.content) && event.content.length > 0) {
111
+ resultText = (event.content as unknown[])
112
+ .filter(
113
+ (r): r is { type: string; title: string; url: string } =>
114
+ typeof r === "object" &&
115
+ r != null &&
116
+ (r as { type?: string }).type === "web_search_result",
117
+ )
118
+ .map((r) => `${r.title}\n${r.url}`)
119
+ .join("\n\n");
120
+ }
121
+ return {
122
+ type: "tool_result",
123
+ toolName: "web_search",
124
+ result: resultText,
125
+ isError: event.isError,
126
+ conversationId,
127
+ toolUseId: event.toolUseId,
128
+ };
129
+ }
130
+ case "message_complete":
131
+ return {
132
+ type: "message_complete",
133
+ conversationId,
134
+ };
135
+ case "input_json_delta":
136
+ case "usage":
137
+ case "error":
138
+ case "provider_error":
139
+ case "max_tokens_reached":
140
+ case "context_compacting":
141
+ case "compaction_circuit_open":
142
+ case "compaction_circuit_closed":
143
+ case "compaction_completed":
144
+ case "history_stripped":
145
+ case "agent_loop_exit":
146
+ return null;
147
+ case "llm_call_started":
148
+ // The wake path persists its assistant tail via
149
+ // `persistWakeTailMessage` (an `addMessage`-shaped call) rather than
150
+ // the main event-handler's `reserve` → `updateContent` pipeline, so
151
+ // there is no row to reserve here. Translation returns null and the
152
+ // wake path's existing end-of-turn persist continues to mint the row.
153
+ return null;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Translate a raw agent event to its wire frame and broadcast it to
159
+ * connected clients. No-op for events with no client-visible shape.
160
+ */
161
+ export function emitWakeAgentEvent(
162
+ conversation: Conversation,
163
+ event: AgentEvent,
164
+ ): void {
165
+ const frame = translateAgentEventToServerMessage(
166
+ event,
167
+ conversation.conversationId,
168
+ );
169
+ if (!frame) return;
170
+ broadcastMessage(frame);
171
+ }
172
+
173
+ /**
174
+ * Emit the live ui_surface card announcing that a wake produced output.
175
+ * The `surfaceId` matches the `ui_surface` content block injected into
176
+ * the wake's first assistant tail message so the client renders one card,
177
+ * not two.
178
+ */
179
+ export function broadcastWakeSurface(
180
+ conversation: Conversation,
181
+ source: string,
182
+ hint: string,
183
+ surfaceId: string,
184
+ ): void {
185
+ broadcastMessage({
186
+ type: "ui_surface_show",
187
+ conversationId: conversation.conversationId,
188
+ surfaceId,
189
+ surfaceType: "card",
190
+ data: {
191
+ title: "Conversation Woke",
192
+ body: hint,
193
+ metadata: [{ label: "Source", value: source }],
194
+ },
195
+ display: "inline",
196
+ });
197
+ }
198
+
199
+ /**
200
+ * Persist a single tail message produced by a wake (assistant outputs and
201
+ * intervening tool_result user messages). Builds the channel/interface
202
+ * metadata, persists via the canonical `addMessage` path, syncs the row to
203
+ * the disk view, and backfills the message id onto this turn's LLM request
204
+ * logs so wake-produced messages match the user-turn persistence path.
205
+ */
206
+ export async function persistWakeTailMessage(
207
+ conversation: Conversation,
208
+ message: Message,
209
+ ): Promise<void> {
210
+ const turnChannelCtx = conversation.getTurnChannelContext();
211
+ const turnInterfaceCtx = conversation.getTurnInterfaceContext();
212
+ const metadata: Record<string, unknown> = {
213
+ ...provenanceFromTrustContext(conversation.trustContext),
214
+ userMessageChannel: turnChannelCtx?.userMessageChannel ?? "vellum",
215
+ assistantMessageChannel:
216
+ turnChannelCtx?.assistantMessageChannel ?? "vellum",
217
+ userMessageInterface: turnInterfaceCtx?.userMessageInterface ?? "web",
218
+ assistantMessageInterface:
219
+ turnInterfaceCtx?.assistantMessageInterface ?? "web",
220
+ };
221
+ const persisted = await addMessage(
222
+ conversation.conversationId,
223
+ message.role,
224
+ JSON.stringify(message.content),
225
+ { metadata },
226
+ );
227
+ if (message.role === "assistant") {
228
+ try {
229
+ backfillMessageIdOnLogs(conversation.conversationId, persisted.id);
230
+ } catch (err) {
231
+ log.warn(
232
+ { err, conversationId: conversation.conversationId },
233
+ "wake persist: backfill messageId on LLM logs failed (non-fatal)",
234
+ );
235
+ }
236
+ }
237
+ try {
238
+ const convRow = getConversation(conversation.conversationId);
239
+ if (convRow) {
240
+ syncMessageToDisk(
241
+ conversation.conversationId,
242
+ persisted.id,
243
+ convRow.createdAt,
244
+ );
245
+ }
246
+ } catch (err) {
247
+ log.warn(
248
+ { err, conversationId: conversation.conversationId },
249
+ "wake persist: syncMessageToDisk failed (non-fatal)",
250
+ );
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Temporarily restrict the tools visible/executable during a wake by
256
+ * reusing the conversation's subagent allowlist slot. Returns a restore
257
+ * callback that reinstates the previous allowlist so the wake can release
258
+ * the scope before any queued user turn is drained.
259
+ */
260
+ export function scopeWakeAllowedTools(
261
+ conversation: Conversation,
262
+ tools: ReadonlySet<string>,
263
+ ): () => void {
264
+ const previous = conversation.subagentAllowedTools;
265
+ conversation.setSubagentAllowedTools(new Set(tools));
266
+ return () => {
267
+ conversation.setSubagentAllowedTools(previous);
268
+ };
269
+ }
@@ -8,7 +8,7 @@
8
8
  * means plugin-api ships as part of the assistant binary's regular code
9
9
  * graph; in JIT/Docker, it loads from source.
10
10
  *
11
- * The loaded namespace is then installed on `globalThis` under a versioned
11
+ * The loaded namespace is then installed on `globalThis` under a well-known
12
12
  * symbol. The boot-time shim writer (`ensurePluginApiShim`) enumerates
13
13
  * {@link PLUGIN_API_EXPORTS} and generates a tiny ESM module at
14
14
  * `<workspaceDir>/node_modules/@vellumai/plugin-api/index.js` that
@@ -26,7 +26,7 @@
26
26
  import * as pluginApi from "../plugin-api/index.js";
27
27
 
28
28
  /** Symbol key under which the plugin-api namespace is published on globalThis. */
29
- export const PLUGIN_API_REGISTRY_KEY = Symbol.for("vellum.plugin-api.v1");
29
+ export const PLUGIN_API_REGISTRY_KEY = Symbol.for("vellum.plugin-api");
30
30
 
31
31
  // Install on globalThis once at module-load time. The shim writer reads
32
32
  // `PLUGIN_API_EXPORTS` to know which bindings to re-export; the shim's
@@ -9,6 +9,7 @@ type TestMessage = {
9
9
  content: string;
10
10
  createdAt: number;
11
11
  metadata: string | null;
12
+ clientMessageId: string | null;
12
13
  };
13
14
 
14
15
  const parentMessages: TestMessage[] = [
@@ -19,6 +20,7 @@ const parentMessages: TestMessage[] = [
19
20
  content: JSON.stringify([{ type: "text", text: "go research foo" }]),
20
21
  createdAt: 1_700_000_000_000,
21
22
  metadata: null,
23
+ clientMessageId: null,
22
24
  },
23
25
  {
24
26
  id: "msg-parent-2",
@@ -34,6 +36,7 @@ const parentMessages: TestMessage[] = [
34
36
  conversationId: "child-conv-1",
35
37
  },
36
38
  }),
39
+ clientMessageId: null,
37
40
  },
38
41
  ];
39
42
 
@@ -47,6 +50,7 @@ const childMessages: TestMessage[] = [
47
50
  ]),
48
51
  createdAt: 1_700_000_002_000,
49
52
  metadata: null,
53
+ clientMessageId: null,
50
54
  },
51
55
  {
52
56
  id: "msg-child-2",
@@ -57,6 +61,7 @@ const childMessages: TestMessage[] = [
57
61
  ]),
58
62
  createdAt: 1_700_000_003_000,
59
63
  metadata: null,
64
+ clientMessageId: null,
60
65
  },
61
66
  ];
62
67
 
@@ -85,6 +85,7 @@ const stubConfig: {
85
85
  activeHoursStart: number | null;
86
86
  activeHoursEnd: number | null;
87
87
  maxConsecutiveRuns: number | null;
88
+ maxDailyRuns: number | null;
88
89
  disposition: string;
89
90
  };
90
91
  } = {
@@ -94,6 +95,7 @@ const stubConfig: {
94
95
  activeHoursStart: null,
95
96
  activeHoursEnd: null,
96
97
  maxConsecutiveRuns: null,
98
+ maxDailyRuns: null,
97
99
  disposition: "Default disposition text.",
98
100
  },
99
101
  };
@@ -195,6 +197,7 @@ mock.module("../heartbeat-run-store.js", () => ({
195
197
  markStaleRunsAsMissed: () => 0,
196
198
  markStaleRunningAsError: () => 0,
197
199
  countCompletedHeartbeatRuns: () => 10,
200
+ countCompletedRunsToday: () => 0,
198
201
  countRecentConsecutiveRuns: () => 0,
199
202
  }));
200
203
 
@@ -24,7 +24,8 @@ export type HeartbeatSkipReason =
24
24
  | "outside_active_hours"
25
25
  | "overlap"
26
26
  | "pre_first_user_message"
27
- | "max_consecutive_runs";
27
+ | "max_consecutive_runs"
28
+ | "max_daily_runs";
28
29
 
29
30
  export interface HeartbeatRunRecord {
30
31
  id: string;
@@ -217,6 +218,27 @@ export function countCompletedHeartbeatRuns(): number {
217
218
  return row?.count ?? 0;
218
219
  }
219
220
 
221
+ /**
222
+ * Count heartbeat runs that completed with status `ok` today (local midnight).
223
+ */
224
+ export function countCompletedRunsToday(): number {
225
+ const db = getDb();
226
+ const now = new Date();
227
+ const startOfDay = new Date(
228
+ now.getFullYear(),
229
+ now.getMonth(),
230
+ now.getDate(),
231
+ ).getTime();
232
+ const row = db
233
+ .select({ count: sql<number>`count(*)` })
234
+ .from(heartbeatRuns)
235
+ .where(
236
+ sql`${heartbeatRuns.status} = 'ok' AND ${heartbeatRuns.scheduledFor} >= ${startOfDay}`,
237
+ )
238
+ .get();
239
+ return row?.count ?? 0;
240
+ }
241
+
220
242
  /**
221
243
  * Count the most recent consecutive heartbeat runs with status `ok`,
222
244
  * walking backwards from newest. Stops at the first non-`ok` status
@@ -25,6 +25,7 @@ import { stripCommentLines } from "../util/strip-comment-lines.js";
25
25
  import {
26
26
  completeHeartbeatRun,
27
27
  countCompletedHeartbeatRuns,
28
+ countCompletedRunsToday,
28
29
  countRecentConsecutiveRuns,
29
30
  insertPendingHeartbeatRun,
30
31
  markStaleRunningAsError,
@@ -187,6 +188,13 @@ export class HeartbeatService {
187
188
  );
188
189
  }
189
190
 
191
+ /** Whether the daily run cap has been reached. */
192
+ get isDailyCapReached(): boolean {
193
+ const config = getConfig().heartbeat;
194
+ if (config.maxDailyRuns == null) return false;
195
+ return countCompletedRunsToday() >= config.maxDailyRuns;
196
+ }
197
+
190
198
  async runManagedWakeIfDue(
191
199
  options: ManagedWakeHeartbeatRunOptions = {},
192
200
  ): Promise<ManagedWakeHeartbeatRunResult> {
@@ -551,6 +559,24 @@ export class HeartbeatService {
551
559
  return false;
552
560
  }
553
561
 
562
+ // Daily run cap — stop burning tokens when the daily budget is exhausted.
563
+ // Force runs bypass the cap.
564
+ if (
565
+ !force &&
566
+ config.maxDailyRuns != null &&
567
+ countCompletedRunsToday() >= config.maxDailyRuns
568
+ ) {
569
+ log.debug(
570
+ { maxDailyRuns: config.maxDailyRuns },
571
+ "Daily run cap reached, skipping",
572
+ );
573
+ if (runId) skipHeartbeatRun(runId, "max_daily_runs");
574
+ if (!this.cronMode) {
575
+ this.scheduleNextRun(config.intervalMs);
576
+ }
577
+ return false;
578
+ }
579
+
554
580
  // Overlap prevention
555
581
  if (this.activeRun) {
556
582
  log.debug("Previous heartbeat run still active, skipping");
@@ -55,7 +55,7 @@ mock.module("../../browser/operations.js", () => ({
55
55
  },
56
56
  }));
57
57
 
58
- mock.module("../../daemon/conversation-store.js", () => ({
58
+ mock.module("../../daemon/conversation-registry.js", () => ({
59
59
  findConversation: (conversationId: string) => {
60
60
  mockFindConversationCalls.push(conversationId);
61
61
  return mockConversation ?? undefined;
@@ -32,9 +32,9 @@ let mockSurfaceResult: InteractiveUiResult = {
32
32
  let mockSurfaceThrows: Error | null = null;
33
33
 
34
34
  // Re-export the real module and override only findConversation.
35
- const realStore = await import("../../daemon/conversation-store.js");
36
- mock.module("../../daemon/conversation-store.js", () => ({
37
- ...realStore,
35
+ const realRegistry = await import("../../daemon/conversation-registry.js");
36
+ mock.module("../../daemon/conversation-registry.js", () => ({
37
+ ...realRegistry,
38
38
  findConversation: (_conversationId: string) => mockConversation ?? undefined,
39
39
  }));
40
40
 
@@ -137,6 +137,21 @@ describe("host.memory.addMessage", () => {
137
137
  }),
138
138
  ).rejects.toThrow();
139
139
  });
140
+
141
+ test("rejects the non-renderable system role", async () => {
142
+ // GIVEN the messages store is UI-facing (ConversationMessage), so only
143
+ // renderable turns may be persisted via this facet
144
+ // WHEN a skill attempts to add a system row
145
+ // THEN the route rejects it instead of persisting agent-context scaffolding
146
+ await expect(
147
+ memoryAddMessageRoute.handler({
148
+ conversationId: "c",
149
+ role: "system",
150
+ content: "agent-context scaffolding",
151
+ }),
152
+ ).rejects.toThrow();
153
+ expect(addMessageSpy).not.toHaveBeenCalled();
154
+ });
140
155
  });
141
156
 
142
157
  describe("host.memory.wakeAgentForOpportunity", () => {
@@ -17,7 +17,9 @@ import type { SkillIpcRoute } from "../skill-ipc-types.js";
17
17
 
18
18
  /**
19
19
  * IPC params for `addMessage()`. `role` is constrained to the
20
- * `MessageRole` union. `metadata` is a free-form record (validated
20
+ * renderable conversation roles (`user`/`assistant`): the messages store
21
+ * is UI-facing (`ConversationMessage`), so agent-context `system` rows are
22
+ * not accepted here. `metadata` is a free-form record (validated
21
23
  * downstream by `messageMetadataSchema` with a warn-and-store fallback).
22
24
  * `skipIndexing` and `clientMessageId` mirror `AddMessageOptions`.
23
25
  *
@@ -28,7 +30,7 @@ import type { SkillIpcRoute } from "../skill-ipc-types.js";
28
30
  */
29
31
  const MemoryAddMessageParams = z.object({
30
32
  conversationId: z.string().min(1),
31
- role: z.enum(["user", "assistant", "system"]),
33
+ role: z.enum(["user", "assistant"]),
32
34
  content: z.string(),
33
35
  metadata: z.record(z.string(), z.unknown()).optional(),
34
36
  skipIndexing: z.boolean().optional(),
@@ -0,0 +1,41 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ mock.module("../../util/logger.js", () => ({
4
+ getLogger: () =>
5
+ new Proxy({} as Record<string, unknown>, {
6
+ get: () => () => {},
7
+ }),
8
+ }));
9
+
10
+ import {
11
+ isActivationSession,
12
+ markActivationSession,
13
+ } from "../activation-session-store.js";
14
+ import { getDb } from "../db-connection.js";
15
+ import { initializeDb } from "../db-init.js";
16
+ import { activationSessions } from "../schema.js";
17
+
18
+ initializeDb();
19
+
20
+ describe("activation-session-store", () => {
21
+ beforeEach(() => {
22
+ getDb().delete(activationSessions).run();
23
+ });
24
+
25
+ test("marks a conversation and reports it as an activation session", () => {
26
+ markActivationSession("c1");
27
+ expect(isActivationSession("c1")).toBe(true);
28
+ });
29
+
30
+ test("unmarked conversations are not activation sessions", () => {
31
+ expect(isActivationSession("nope")).toBe(false);
32
+ });
33
+
34
+ test("marking the same conversation twice is idempotent", () => {
35
+ markActivationSession("c1");
36
+ markActivationSession("c1");
37
+ expect(isActivationSession("c1")).toBe(true);
38
+ const rows = getDb().select().from(activationSessions).all();
39
+ expect(rows.length).toBe(1);
40
+ });
41
+ });
@@ -151,6 +151,7 @@ describe("maybeEnqueueGraphMaintenanceJobs — memory v2 consolidation", () => {
151
151
 
152
152
  test("enqueues consolidate when v2 is on and no checkpoint exists", () => {
153
153
  const config = buildConfig({ v2Enabled: true, intervalHours: 1 });
154
+ writeBuffer(15);
154
155
 
155
156
  maybeEnqueueGraphMaintenanceJobs(config, Date.now());
156
157
 
@@ -177,6 +178,7 @@ describe("maybeEnqueueGraphMaintenanceJobs — memory v2 consolidation", () => {
177
178
 
178
179
  test("enqueues consolidate again once the interval elapses", () => {
179
180
  const config = buildConfig({ v2Enabled: true, intervalHours: 1 });
181
+ writeBuffer(15);
180
182
 
181
183
  const now = Date.now();
182
184
  // Stamp checkpoint to >1h ago.
@@ -193,6 +195,7 @@ describe("maybeEnqueueGraphMaintenanceJobs — memory v2 consolidation", () => {
193
195
 
194
196
  test("respects a custom consolidation_interval_hours value", () => {
195
197
  const config = buildConfig({ v2Enabled: true, intervalHours: 6 });
198
+ writeBuffer(15);
196
199
 
197
200
  const now = Date.now();
198
201
  // 4h elapsed — under the configured 6h interval.
@@ -216,6 +219,7 @@ describe("maybeEnqueueGraphMaintenanceJobs — memory v2 consolidation", () => {
216
219
 
217
220
  test("v1 maintenance entries are suppressed when v2 is active", () => {
218
221
  const config = buildConfig({ v2Enabled: true, intervalHours: 1 });
222
+ writeBuffer(15);
219
223
 
220
224
  // No checkpoints set — every entry would be due if it were scheduled.
221
225
  deleteMemoryCheckpoint("graph_maintenance:decay:last_run");
@@ -426,3 +430,86 @@ describe("maybeEnqueueGraphMaintenanceJobs — buffer-size trigger", () => {
426
430
  expect(countPendingJobs("memory_v2_consolidate")).toBe(0);
427
431
  });
428
432
  });
433
+
434
+ describe("maybeEnqueueGraphMaintenanceJobs — min buffer lines noop", () => {
435
+ test("skips scheduled consolidation when buffer is under 10 lines", () => {
436
+ // GIVEN v2 consolidation is enabled and the interval has elapsed
437
+ const config = buildConfig({ v2Enabled: true, intervalHours: 1 });
438
+ const now = Date.now();
439
+ setMemoryCheckpoint(
440
+ CONSOLIDATE_CHECKPOINT_KEY,
441
+ String(now - 2 * 60 * 60 * 1000),
442
+ );
443
+ // AND the buffer has fewer than 10 lines
444
+ writeBuffer(5);
445
+
446
+ // WHEN the schedule runs
447
+ maybeEnqueueGraphMaintenanceJobs(config, now);
448
+
449
+ // THEN no consolidation job is enqueued
450
+ expect(countPendingJobs("memory_v2_consolidate")).toBe(0);
451
+ // AND the checkpoint advances so the skip doesn't re-fire next tick
452
+ expect(getMemoryCheckpoint(CONSOLIDATE_CHECKPOINT_KEY)).toBe(String(now));
453
+ });
454
+
455
+ test("allows scheduled consolidation when buffer has exactly 10 lines", () => {
456
+ // GIVEN v2 consolidation is enabled and the interval has elapsed
457
+ const config = buildConfig({ v2Enabled: true, intervalHours: 1 });
458
+ const now = Date.now();
459
+ setMemoryCheckpoint(
460
+ CONSOLIDATE_CHECKPOINT_KEY,
461
+ String(now - 2 * 60 * 60 * 1000),
462
+ );
463
+ // AND the buffer has exactly 10 lines
464
+ writeBuffer(10);
465
+
466
+ // WHEN the schedule runs
467
+ maybeEnqueueGraphMaintenanceJobs(config, now);
468
+
469
+ // THEN consolidation is enqueued
470
+ expect(countPendingJobs("memory_v2_consolidate")).toBe(1);
471
+ });
472
+
473
+ test("skips scheduled consolidation when buffer file is missing", () => {
474
+ // GIVEN v2 consolidation is enabled and the interval has elapsed
475
+ const config = buildConfig({ v2Enabled: true, intervalHours: 1 });
476
+ const now = Date.now();
477
+ setMemoryCheckpoint(
478
+ CONSOLIDATE_CHECKPOINT_KEY,
479
+ String(now - 2 * 60 * 60 * 1000),
480
+ );
481
+ // AND no buffer file exists (0 lines)
482
+ removeBuffer();
483
+
484
+ // WHEN the schedule runs
485
+ maybeEnqueueGraphMaintenanceJobs(config, now);
486
+
487
+ // THEN no consolidation job is enqueued
488
+ expect(countPendingJobs("memory_v2_consolidate")).toBe(0);
489
+ // AND the checkpoint advances
490
+ expect(getMemoryCheckpoint(CONSOLIDATE_CHECKPOINT_KEY)).toBe(String(now));
491
+ });
492
+
493
+ test("size-based trigger is independent of the min-lines noop", () => {
494
+ // GIVEN the time-based interval has elapsed but the buffer has fewer
495
+ // than 10 lines, while exceeding the configured maxBufferLines threshold
496
+ const config = buildConfig({
497
+ v2Enabled: true,
498
+ intervalHours: 1,
499
+ maxBufferLines: 5,
500
+ });
501
+ const now = Date.now();
502
+ setMemoryCheckpoint(
503
+ CONSOLIDATE_CHECKPOINT_KEY,
504
+ String(now - 2 * 60 * 60 * 1000),
505
+ );
506
+ writeBuffer(8);
507
+
508
+ // WHEN the schedule runs
509
+ maybeEnqueueGraphMaintenanceJobs(config, now);
510
+
511
+ // THEN consolidation is enqueued via the size trigger despite the
512
+ // time-based schedule being nooped (8 < 10 min lines, but 8 >= 5 max)
513
+ expect(countPendingJobs("memory_v2_consolidate")).toBe(1);
514
+ });
515
+ });