@vellumai/assistant 0.8.8 → 0.8.9-staging.2

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 (360) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/examples/plugins/echo/README.md +61 -60
  4. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  5. package/examples/plugins/echo/hooks/stop.ts +16 -0
  6. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  7. package/examples/plugins/echo/package.json +1 -2
  8. package/examples/plugins/echo/src/emit.ts +19 -0
  9. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  10. package/openapi.yaml +235 -6
  11. package/package.json +2 -2
  12. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  13. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  14. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  15. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  16. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  17. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  18. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  19. package/src/__tests__/agent-loop.test.ts +441 -96
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  21. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  22. package/src/__tests__/anthropic-provider.test.ts +1 -1
  23. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  24. package/src/__tests__/app-control-flow.test.ts +1 -1
  25. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  26. package/src/__tests__/app-executors.test.ts +132 -0
  27. package/src/__tests__/approval-cascade.test.ts +5 -4
  28. package/src/__tests__/approval-routes-http.test.ts +4 -1
  29. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  30. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  31. package/src/__tests__/channel-approvals.test.ts +1 -1
  32. package/src/__tests__/compaction-circuit.test.ts +258 -0
  33. package/src/__tests__/compaction-direct.test.ts +132 -0
  34. package/src/__tests__/compaction-events.test.ts +5 -5
  35. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  36. package/src/__tests__/context-overflow-reducer.test.ts +1 -1
  37. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
  38. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  39. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  40. package/src/__tests__/conversation-agent-loop-overflow.test.ts +142 -218
  41. package/src/__tests__/conversation-agent-loop.test.ts +297 -586
  42. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  43. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
  44. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  45. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  46. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
  47. package/src/__tests__/conversation-process-callsite.test.ts +14 -14
  48. package/src/__tests__/conversation-provider-retry-repair.test.ts +70 -65
  49. package/src/__tests__/conversation-queue.test.ts +9 -9
  50. package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
  51. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  52. package/src/__tests__/conversation-slash-queue.test.ts +6 -4
  53. package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
  54. package/src/__tests__/conversation-speed-override.test.ts +10 -9
  55. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  56. package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
  57. package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
  59. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  60. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  61. package/src/__tests__/db-acp-history.test.ts +101 -0
  62. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  63. package/src/__tests__/empty-response-hook.test.ts +1 -1
  64. package/src/__tests__/file-write-tool.test.ts +63 -0
  65. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  66. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  67. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  68. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  69. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  70. package/src/__tests__/heartbeat-service.test.ts +1 -0
  71. package/src/__tests__/history-repair-hook.test.ts +1 -1
  72. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  73. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  74. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  75. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  76. package/src/__tests__/injector-background-turn.test.ts +13 -23
  77. package/src/__tests__/injector-chain.test.ts +268 -44
  78. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  79. package/src/__tests__/injector-document-comments.test.ts +97 -114
  80. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  81. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  82. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  83. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  84. package/src/__tests__/memory-retrieval-hook.test.ts +131 -26
  85. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  86. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  87. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  88. package/src/__tests__/plugin-bootstrap.test.ts +12 -23
  89. package/src/__tests__/plugin-registry.test.ts +3 -49
  90. package/src/__tests__/plugin-types.test.ts +0 -70
  91. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  92. package/src/__tests__/reaction-persistence.test.ts +1 -1
  93. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  94. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  95. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  96. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  97. package/src/__tests__/subagent-detail.test.ts +25 -7
  98. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  99. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  100. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  101. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  102. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  103. package/src/__tests__/title-generate-hook.test.ts +1 -1
  104. package/src/__tests__/tool-error-hook.test.ts +1 -1
  105. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  106. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  107. package/src/acp/__tests__/agent-process.test.ts +161 -0
  108. package/src/acp/__tests__/client-handler.test.ts +40 -0
  109. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  110. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  111. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  112. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  113. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  114. package/src/acp/agent-process.ts +61 -1
  115. package/src/acp/auto-install.test.ts +280 -0
  116. package/src/acp/auto-install.ts +232 -0
  117. package/src/acp/client-handler.ts +31 -0
  118. package/src/acp/feature-gate.test.ts +48 -0
  119. package/src/acp/feature-gate.ts +34 -0
  120. package/src/acp/prepare-agent-env.ts +80 -27
  121. package/src/acp/resolve-agent.test.ts +225 -9
  122. package/src/acp/resolve-agent.ts +122 -17
  123. package/src/acp/resume-hint.ts +23 -0
  124. package/src/acp/session-manager.ts +507 -73
  125. package/src/agent/compaction-circuit.ts +60 -102
  126. package/src/agent/loop.ts +414 -248
  127. package/src/api/responses/conversation-message.ts +14 -1
  128. package/src/approvals/guardian-request-resolvers.ts +1 -1
  129. package/src/background-wake/next-wake.ts +1 -0
  130. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  131. package/src/cli/commands/plugins.ts +43 -37
  132. package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
  133. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  134. package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
  135. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
  136. package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
  137. package/src/cli/lib/install-from-github.ts +464 -55
  138. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  139. package/src/cli/lib/plugin-details.ts +409 -0
  140. package/src/cli/lib/plugin-marketplace.ts +197 -0
  141. package/src/cli/lib/search-plugins.ts +195 -29
  142. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  143. package/src/config/acp-defaults.test.ts +10 -0
  144. package/src/config/acp-defaults.ts +6 -0
  145. package/src/config/bundled-skills/acp/SKILL.md +85 -33
  146. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  147. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  148. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  149. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  150. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  151. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  152. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  153. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  154. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  155. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  156. package/src/config/bundled-tool-registry.ts +4 -0
  157. package/src/config/call-site-defaults.ts +0 -2
  158. package/src/config/feature-flag-registry.json +15 -6
  159. package/src/config/schemas/call-site-catalog.ts +0 -14
  160. package/src/config/schemas/heartbeat.ts +9 -0
  161. package/src/config/schemas/llm.ts +0 -2
  162. package/src/context/compactor.ts +22 -4
  163. package/src/context/strip-injections.ts +8 -2
  164. package/src/context/window-manager.ts +27 -13
  165. package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
  166. package/src/daemon/conversation-agent-loop.ts +175 -1055
  167. package/src/daemon/conversation-lifecycle.ts +11 -255
  168. package/src/daemon/conversation-process.ts +8 -136
  169. package/src/daemon/conversation-registry.ts +159 -0
  170. package/src/daemon/conversation-runtime-assembly.ts +293 -392
  171. package/src/daemon/conversation-store.ts +9 -90
  172. package/src/daemon/conversation-surfaces.ts +24 -8
  173. package/src/daemon/conversation-workspace.ts +17 -0
  174. package/src/daemon/conversation.ts +404 -57
  175. package/src/daemon/disk-pressure-policy.ts +0 -1
  176. package/src/daemon/external-plugins-bootstrap.ts +14 -19
  177. package/src/daemon/handlers/conversations.ts +3 -1
  178. package/src/daemon/handlers/skills.ts +4 -1
  179. package/src/daemon/host-proxy-preactivation.ts +1 -3
  180. package/src/daemon/lifecycle.ts +21 -7
  181. package/src/daemon/server.ts +2 -0
  182. package/src/daemon/wake-conversation-ops.ts +269 -0
  183. package/src/embedded/plugin-api.ts +2 -2
  184. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  185. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  186. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  187. package/src/heartbeat/heartbeat-service.ts +26 -0
  188. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  189. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  190. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  191. package/src/ipc/skill-routes/memory.ts +4 -2
  192. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  193. package/src/memory/conversation-crud.ts +29 -19
  194. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  195. package/src/memory/db-init.ts +2 -0
  196. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  197. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  198. package/src/memory/jobs-worker.ts +25 -1
  199. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  200. package/src/memory/migrations/index.ts +1 -0
  201. package/src/memory/schema/acp.ts +4 -0
  202. package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
  203. package/src/memory/v2/consolidation-job.ts +13 -4
  204. package/src/plugin-api/constants.ts +4 -0
  205. package/src/plugin-api/index.ts +6 -5
  206. package/src/plugin-api/types.ts +75 -0
  207. package/src/plugins/defaults/compaction/compact.ts +59 -0
  208. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +7 -7
  209. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  210. package/src/plugins/defaults/compaction/package.json +1 -2
  211. package/src/plugins/defaults/empty-response/package.json +0 -1
  212. package/src/plugins/defaults/history-repair/package.json +0 -1
  213. package/src/plugins/defaults/index.ts +135 -26
  214. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  215. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +184 -74
  216. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  217. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  218. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  219. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  220. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +9 -6
  221. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  222. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +8 -5
  223. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  224. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  225. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +10 -5
  226. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  227. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +5 -5
  228. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
  229. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
  230. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
  231. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
  232. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  233. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  234. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  235. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
  236. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
  237. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  238. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  239. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
  240. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
  241. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  242. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +5 -5
  243. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  244. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +7 -7
  245. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
  246. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  247. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -1
  248. package/src/plugins/defaults/title-generate/package.json +0 -1
  249. package/src/plugins/defaults/tool-error/package.json +0 -1
  250. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  251. package/src/plugins/pipeline.ts +6 -293
  252. package/src/plugins/registry.ts +9 -37
  253. package/src/plugins/types.ts +76 -381
  254. package/src/plugins/user-loader.ts +30 -127
  255. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  256. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
  257. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  258. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  259. package/src/runtime/agent-wake.ts +108 -209
  260. package/src/runtime/assistant-event-hub.ts +1 -1
  261. package/src/runtime/channel-approvals.ts +1 -1
  262. package/src/runtime/interactive-ui.ts +1 -1
  263. package/src/runtime/routes/__tests__/acp-routes.test.ts +315 -55
  264. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  265. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  266. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  267. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  268. package/src/runtime/routes/acp-routes.test.ts +89 -25
  269. package/src/runtime/routes/acp-routes.ts +81 -29
  270. package/src/runtime/routes/approval-routes.ts +1 -1
  271. package/src/runtime/routes/browser-routes.ts +1 -1
  272. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  273. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  274. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  275. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  276. package/src/runtime/routes/conversation-routes.ts +28 -2
  277. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  278. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  279. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  280. package/src/runtime/routes/host-cu-routes.ts +1 -1
  281. package/src/runtime/routes/identity-routes.ts +18 -3
  282. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  283. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  284. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  285. package/src/runtime/routes/memory-v3-routes.ts +16 -6
  286. package/src/runtime/routes/playground/helpers.ts +1 -1
  287. package/src/runtime/routes/plugins-routes.ts +337 -35
  288. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  289. package/src/runtime/routes/work-items-routes.ts +2 -4
  290. package/src/runtime/services/conversation-serializer.ts +1 -1
  291. package/src/signals/cancel.ts +2 -4
  292. package/src/subagent/manager.ts +21 -5
  293. package/src/tools/acp/context.ts +20 -0
  294. package/src/tools/acp/list-agents.test.ts +8 -2
  295. package/src/tools/acp/spawn.test.ts +176 -195
  296. package/src/tools/acp/spawn.ts +37 -172
  297. package/src/tools/acp/steer.test.ts +105 -8
  298. package/src/tools/acp/steer.ts +48 -17
  299. package/src/tools/apps/executors.ts +166 -50
  300. package/src/tools/filesystem/write.ts +34 -0
  301. package/src/tools/subagent/spawn.ts +2 -4
  302. package/src/tools/ui-surface/definitions.ts +25 -5
  303. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  304. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  305. package/docs/plugins.md +0 -832
  306. package/examples/plugins/echo/register.ts +0 -143
  307. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  308. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  309. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  310. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  311. package/src/__tests__/pipeline-runner.test.ts +0 -554
  312. package/src/__tests__/plugin-external-api.test.ts +0 -68
  313. package/src/daemon/wake-target-adapter.ts +0 -253
  314. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  315. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  316. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  317. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  318. package/src/plugins/defaults/compaction/register.ts +0 -35
  319. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  320. package/src/plugins/defaults/empty-response/register.ts +0 -23
  321. package/src/plugins/defaults/history-repair/register.ts +0 -24
  322. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  323. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  324. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  325. package/src/plugins/defaults/title-generate/register.ts +0 -35
  326. package/src/plugins/defaults/tool-error/register.ts +0 -23
  327. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  328. package/src/plugins/external-api.ts +0 -104
  329. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  330. package/src/proactive-artifact/decision.test.ts +0 -226
  331. package/src/proactive-artifact/decision.ts +0 -165
  332. package/src/proactive-artifact/index.ts +0 -7
  333. package/src/proactive-artifact/job.test.ts +0 -962
  334. package/src/proactive-artifact/job.ts +0 -372
  335. package/src/proactive-artifact/message-copy.ts +0 -58
  336. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  337. package/src/proactive-artifact/trigger-state.ts +0 -123
  338. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  339. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  340. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  341. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  342. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +0 -0
  343. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  344. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/provider-blocks.test.ts +0 -0
  345. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  346. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  347. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  348. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  349. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  350. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  351. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  352. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  353. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  354. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  355. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  356. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  357. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
  358. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  359. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  360. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
@@ -2,21 +2,22 @@
2
2
  * Plugin bootstrap — runs every registered plugin's `init()` hook once during
3
3
  * daemon startup.
4
4
  *
5
- * Plugins register themselves via side-effect imports elsewhere in the boot
6
- * sequence (first-party) or at runtime (hot-reload). By the time
7
- * {@link bootstrapPlugins} runs, the registry has been fully populated for
8
- * this boot cycle. This function:
5
+ * The registry is populated before this runs: first-party defaults are
6
+ * registered explicitly (step 1 below), user plugins are registered by the
7
+ * user-plugin loader from the workspace `plugins/` directory, and hot-reload
8
+ * registers at runtime. By the time {@link bootstrapPlugins} runs, the
9
+ * registry has been fully populated for this boot cycle. This function:
9
10
  *
10
11
  * 1. Registers the canonical first-party default plugins via
11
- * {@link registerDefaultPlugins} (one per pipeline). Registration is
12
- * idempotent so repeat calls (e.g. during integration tests) do not throw.
12
+ * {@link registerDefaultPlugins}. Registration is idempotent so repeat
13
+ * calls (e.g. during integration tests) do not throw.
13
14
  * 2. Walks {@link getRegisteredPlugins} in registration order.
14
15
  * 3. For each plugin, consults `manifest.requiresFlag` against
15
16
  * {@link isAssistantFeatureFlagEnabled}. If any listed flag is disabled,
16
17
  * the plugin is skipped wholesale — no `init()`, no tool/route/skill
17
18
  * contributions, no entry in the shutdown hook, and the plugin is also
18
- * dropped from the registry via {@link unregisterPlugin} so its middleware
19
- * stops participating in pipeline runs. This is the primary mechanism for
19
+ * dropped from the registry via {@link unregisterPlugin} so none of its
20
+ * hooks participate in the turn lifecycle. This is the primary mechanism for
20
21
  * shipping experimental plugins behind a feature flag.
21
22
  * 4. Resolves the plugin's `manifest.requiresCredential` entries via the
22
23
  * credential store helper ({@link getSecureKeyAsync}). In Docker mode
@@ -64,7 +65,6 @@ import { getConfig } from "../config/loader.js";
64
65
  import type { AssistantConfig } from "../config/schema.js";
65
66
  import { HOOKS } from "../plugin-api/constants.js";
66
67
  import { registerDefaultPlugins } from "../plugins/defaults/index.js";
67
- import { installPluginRuntime } from "../plugins/external-api.js";
68
68
  import { buildExternalPlugin } from "../plugins/external-plugin-loader.js";
69
69
  import {
70
70
  registerPluginSkills,
@@ -186,21 +186,17 @@ function getDisabledPluginFlag(
186
186
  * Bring the plugin layer up during daemon startup. Runs the full sequence in
187
187
  * the one order the rest of the system depends on:
188
188
  *
189
- * 1. Install the `globalThis.__vellumPluginRuntime` bridge so plugins can touch
190
- * it from their module body (see `plugins/external-api.ts` — compiled-binary
191
- * module identity).
192
- * 2. Register the first-party defaults so their middleware composes innermost,
189
+ * 1. Register the first-party defaults so their middleware composes innermost,
193
190
  * ahead of any user plugins.
194
- * 3. Load user plugins from `<workspaceDir>/plugins/*`. A failing user plugin is
191
+ * 2. Load user plugins from `<workspaceDir>/plugins/*`. A failing user plugin is
195
192
  * logged and skipped; `loadUserPlugins()` closes the registration window
196
193
  * when it returns, so the defaults must already be registered by then.
197
- * 4. Run every registered plugin's `init()` via {@link bootstrapPlugins}.
194
+ * 3. Run every registered plugin's `init()` via {@link bootstrapPlugins}.
198
195
  *
199
196
  * Plugin bootstrap is wrapped so a failing plugin cannot block daemon startup —
200
197
  * the daemon comes up with degraded plugin functionality instead.
201
198
  */
202
199
  export async function initializePlugins(): Promise<void> {
203
- installPluginRuntime();
204
200
  registerDefaultPlugins();
205
201
  await loadUserPlugins();
206
202
  try {
@@ -261,9 +257,8 @@ export async function bootstrapPlugins(): Promise<void> {
261
257
  // Tear down a plugin's contributions AND remove it from the registry. The
262
258
  // two steps always move together on the bootstrap failure path: the former
263
259
  // clears tools/routes/skills (so they stop appearing to the model/HTTP
264
- // server), the latter drops the plugin's entry from the Map (so
265
- // `getMiddlewaresFor` doesn't re-enter an uninitialized plugin on the next
266
- // pipeline invocation).
260
+ // server), the latter drops the plugin's entry from the Map (so the registry
261
+ // doesn't expose an uninitialized plugin's hooks).
267
262
  // Shutdown context is identical for every plugin in this boot — construct
268
263
  // once and reuse across the bootstrap-failure rollback and the normal
269
264
  // shutdown hook below. Only `assistantVersion` is exposed today; future
@@ -8,9 +8,11 @@ import { createAbortReason } from "../../util/abort-reasons.js";
8
8
  import { truncate } from "../../util/truncate.js";
9
9
  import { regenerate } from "../conversation-history.js";
10
10
  import {
11
- clearAllActiveConversations,
12
11
  conversationEntries,
13
12
  findConversation,
13
+ } from "../conversation-registry.js";
14
+ import {
15
+ clearAllActiveConversations,
14
16
  getOrCreateConversation,
15
17
  touchConversation,
16
18
  } from "../conversation-store.js";
@@ -31,7 +31,10 @@ import {
31
31
  isTextMimeType as isTextMime,
32
32
  MAX_INLINE_TEXT_SIZE,
33
33
  } from "../../runtime/routes/workspace-utils.js";
34
- import { getCachedCatalogSync, getCatalog } from "../../skills/catalog-cache.js";
34
+ import {
35
+ getCachedCatalogSync,
36
+ getCatalog,
37
+ } from "../../skills/catalog-cache.js";
35
38
  import type { SkillFileEntry } from "../../skills/catalog-files.js";
36
39
  import {
37
40
  catalogSkillToSlim,
@@ -34,9 +34,7 @@ import { getLogger } from "../util/logger.js";
34
34
  const log = getLogger("host-proxy-preactivation");
35
35
 
36
36
  /**
37
- * Subset of Conversation/ProcessConversationContext that
38
- * `preactivateHostProxySkills` needs. Both `Conversation` and
39
- * `ProcessConversationContext` satisfy this structurally.
37
+ * Subset of `Conversation` that `preactivateHostProxySkills` needs.
40
38
  */
41
39
  export interface HostProxyPreactivationTarget {
42
40
  readonly conversationId: string;
@@ -60,7 +60,6 @@ import { sweepConceptPageFrontmatter } from "../memory/v2/frontmatter-sweep.js";
60
60
  import { emitNotificationSignal } from "../notifications/emit-signal.js";
61
61
  import { backfillManualTokenConnections } from "../oauth/manual-token-connection.js";
62
62
  import { seedOAuthProviders } from "../oauth/seed-providers.js";
63
- import { backfillGuardIfNeeded } from "../proactive-artifact/index.js";
64
63
  import { ensurePromptFiles } from "../prompts/system-prompt.js";
65
64
  import { runProviderConnectionsBackfill } from "../providers/inference/backfill.js";
66
65
  import { resolveManagedProxyContext } from "../providers/platform-proxy/context.js";
@@ -98,6 +97,7 @@ import {
98
97
  updateWorkItem,
99
98
  } from "../work-items/work-item-store.js";
100
99
  import { WorkspaceHeartbeatService } from "../workspace/heartbeat-service.js";
100
+ import { repairAdaptiveThinkingOnManagedProfiles } from "../workspace/migrations/097-enable-adaptive-thinking-managed-profiles.js";
101
101
  import { WORKSPACE_MIGRATIONS } from "../workspace/migrations/registry.js";
102
102
  import { runWorkspaceMigrations } from "../workspace/migrations/runner.js";
103
103
  import {
@@ -589,6 +589,26 @@ export async function runDaemon(): Promise<void> {
589
589
  );
590
590
  }
591
591
 
592
+ // Re-run the adaptive thinking repair after overlay merge + profile seeding.
593
+ // Workspace migration 097 enables adaptive thinking on managed profiles, but
594
+ // it runs before mergeDefaultWorkspaceConfig() which can overwrite the fix
595
+ // with overlay profiles that have thinking disabled or absent. On-platform
596
+ // instances where the overlay supplies "balanced" / "quality-optimized"
597
+ // profiles without thinking enabled would be stuck permanently because the
598
+ // migration is already checkpointed as completed. This idempotent repair
599
+ // ensures thinking is enabled regardless of overlay ordering.
600
+ if (defaultConfigMerge.hadOverlay) {
601
+ try {
602
+ repairAdaptiveThinkingOnManagedProfiles(getWorkspaceDir());
603
+ log.info("Post-overlay adaptive thinking repair complete");
604
+ } catch (err) {
605
+ log.warn(
606
+ { err },
607
+ "Post-overlay adaptive thinking repair failed — continuing startup",
608
+ );
609
+ }
610
+ }
611
+
592
612
  log.info("Daemon startup: loading config");
593
613
  const config = loadConfig();
594
614
 
@@ -1276,12 +1296,6 @@ export async function runDaemon(): Promise<void> {
1276
1296
  "Heartbeat service configured",
1277
1297
  );
1278
1298
 
1279
- try {
1280
- backfillGuardIfNeeded();
1281
- } catch (err) {
1282
- log.warn({ err }, "Proactive artifact backfill failed");
1283
- }
1284
-
1285
1299
  // Filing yields to the memory v2 consolidation job when v2 is enabled —
1286
1300
  // both serve the same role (periodic background memory processing) and
1287
1301
  // running both is redundant. The consolidation job runs through the
@@ -39,6 +39,8 @@ import {
39
39
  conversationEntries,
40
40
  deleteConversation,
41
41
  getConversationMap,
42
+ } from "./conversation-registry.js";
43
+ import {
42
44
  getOrCreateConversation as getOrCreateActiveConversation,
43
45
  initConversationLifecycle,
44
46
  setCesClientPromise,
@@ -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", () => {