@vellumai/assistant 0.8.7-dev.202606052232.2ddc989 → 0.8.8

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 (262) hide show
  1. package/bun.lock +2 -2
  2. package/docs/plugins.md +832 -0
  3. package/examples/plugins/echo/README.md +60 -61
  4. package/examples/plugins/echo/package.json +2 -1
  5. package/examples/plugins/echo/register.ts +143 -0
  6. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +6 -7
  7. package/openapi.yaml +5 -15
  8. package/package.json +2 -2
  9. package/src/__tests__/agent-loop-exit-reason.test.ts +56 -3
  10. package/src/__tests__/anthropic-provider.test.ts +1 -1
  11. package/src/__tests__/app-control-flow.test.ts +1 -1
  12. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  13. package/src/__tests__/approval-routes-http.test.ts +1 -4
  14. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  15. package/src/__tests__/channel-approvals.test.ts +1 -1
  16. package/src/__tests__/circuit-breaker-pipeline.test.ts +405 -0
  17. package/src/__tests__/compaction-pipeline.test.ts +210 -0
  18. package/src/__tests__/compaction-timeout-recovery.test.ts +251 -0
  19. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +3 -0
  20. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +3 -0
  21. package/src/__tests__/conversation-agent-loop-overflow.test.ts +7 -3
  22. package/src/__tests__/conversation-agent-loop.test.ts +39 -42
  23. package/src/__tests__/conversation-clean-command.test.ts +2 -5
  24. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -4
  25. package/src/__tests__/conversation-runtime-assembly.test.ts +71 -140
  26. package/src/__tests__/conversation-runtime-workspace.test.ts +27 -108
  27. package/src/__tests__/conversation-starter-routes.test.ts +6 -14
  28. package/src/__tests__/conversation-workspace-cache-state.test.ts +16 -17
  29. package/src/__tests__/conversation-workspace-injection.test.ts +1 -61
  30. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -7
  31. package/src/__tests__/db-acp-history.test.ts +0 -101
  32. package/src/__tests__/dynamic-page-surface.test.ts +0 -31
  33. package/src/__tests__/file-write-tool.test.ts +0 -63
  34. package/src/__tests__/gateway-only-guard.test.ts +2 -12
  35. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  36. package/src/__tests__/guardian-routing-invariants.test.ts +4 -2
  37. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  38. package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -1
  39. package/src/__tests__/heartbeat-service.test.ts +0 -1
  40. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  41. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  42. package/src/__tests__/injector-background-turn.test.ts +1 -1
  43. package/src/__tests__/injector-chain.test.ts +6 -34
  44. package/src/__tests__/injector-disk-pressure.test.ts +34 -77
  45. package/src/__tests__/injector-document-comments.test.ts +1 -1
  46. package/src/__tests__/list-messages-hidden-metadata.test.ts +0 -38
  47. package/src/__tests__/memory-v2-static-injector.test.ts +1 -1
  48. package/src/__tests__/{overflow-reduction-loop.test.ts → overflow-reduce-pipeline.test.ts} +284 -64
  49. package/src/__tests__/pipeline-runner.test.ts +554 -0
  50. package/src/__tests__/plugin-api-shim.test.ts +6 -3
  51. package/src/__tests__/plugin-bootstrap.test.ts +23 -12
  52. package/src/__tests__/plugin-registry.test.ts +49 -3
  53. package/src/__tests__/plugin-types.test.ts +70 -0
  54. package/src/__tests__/reaction-persistence.test.ts +1 -1
  55. package/src/__tests__/send-endpoint-busy.test.ts +1 -4
  56. package/src/__tests__/skill-feature-flags-integration.test.ts +0 -33
  57. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  58. package/src/__tests__/subagent-fork-notifications.test.ts +3 -1
  59. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  60. package/src/__tests__/subagent-manager-notify.test.ts +3 -1
  61. package/src/__tests__/subagent-notify-parent.test.ts +3 -1
  62. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  63. package/src/__tests__/user-plugin-loader.test.ts +286 -54
  64. package/src/acp/__tests__/client-handler.test.ts +0 -40
  65. package/src/acp/__tests__/prepare-agent-env.test.ts +0 -137
  66. package/src/acp/__tests__/session-manager-persistence.test.ts +28 -95
  67. package/src/acp/agent-process.ts +1 -61
  68. package/src/acp/client-handler.ts +0 -31
  69. package/src/acp/prepare-agent-env.ts +29 -83
  70. package/src/acp/resolve-agent.test.ts +7 -320
  71. package/src/acp/resolve-agent.ts +18 -182
  72. package/src/acp/session-manager.ts +73 -495
  73. package/src/acp/types.ts +0 -8
  74. package/src/agent/compaction-circuit.ts +102 -60
  75. package/src/agent/loop.ts +59 -32
  76. package/src/api/responses/conversation-message.ts +1 -7
  77. package/src/approvals/guardian-request-resolvers.ts +1 -1
  78. package/src/background-wake/next-wake.ts +0 -1
  79. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  80. package/src/config/acp-defaults.test.ts +0 -10
  81. package/src/config/acp-defaults.ts +0 -6
  82. package/src/config/bundled-skills/acp/SKILL.md +31 -83
  83. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  84. package/src/config/bundled-skills/app-builder/SKILL.md +381 -224
  85. package/src/config/bundled-skills/app-builder/TOOLS.json +0 -29
  86. package/src/config/bundled-skills/document-editor/SKILL.md +23 -28
  87. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  88. package/src/config/bundled-tool-registry.ts +0 -2
  89. package/src/config/feature-flag-registry.json +5 -14
  90. package/src/config/schemas/heartbeat.ts +0 -9
  91. package/src/context/strip-injections.ts +2 -8
  92. package/src/context/window-manager.ts +1 -2
  93. package/src/daemon/conversation-agent-loop-handlers.ts +11 -0
  94. package/src/daemon/conversation-agent-loop.ts +279 -62
  95. package/src/daemon/conversation-runtime-assembly.ts +69 -106
  96. package/src/daemon/conversation-store.ts +90 -9
  97. package/src/daemon/conversation-workspace.ts +0 -17
  98. package/src/daemon/conversation.ts +6 -0
  99. package/src/daemon/external-plugins-bootstrap.ts +11 -11
  100. package/src/daemon/handlers/conversations.ts +1 -3
  101. package/src/daemon/handlers/skills.ts +1 -4
  102. package/src/daemon/lifecycle.ts +0 -21
  103. package/src/daemon/server.ts +0 -2
  104. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -3
  105. package/src/heartbeat/heartbeat-run-store.ts +1 -23
  106. package/src/heartbeat/heartbeat-service.ts +0 -26
  107. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  108. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  109. package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -15
  110. package/src/ipc/skill-routes/memory.ts +2 -4
  111. package/src/memory/conversation-starter-checkpoints.ts +0 -1
  112. package/src/memory/db-init.ts +0 -2
  113. package/src/memory/job-handlers/conversation-starters.ts +2 -13
  114. package/src/memory/jobs-worker.ts +1 -1
  115. package/src/memory/migrations/index.ts +0 -1
  116. package/src/memory/schema/acp.ts +0 -4
  117. package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
  118. package/src/memory/v2/consolidation-job.ts +4 -13
  119. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/assign.test.ts +4 -4
  120. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/live-integration.test.ts +4 -4
  121. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/maintain-job.test.ts +5 -5
  122. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/orchestrate.test.ts +3 -3
  123. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/reconcile.test.ts +2 -2
  124. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/render-injection.test.ts +1 -1
  125. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/router.test.ts +3 -3
  126. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selection-log-store.test.ts +8 -8
  127. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selector.test.ts +3 -3
  128. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/shadow-plugin.test.ts +12 -12
  129. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/assign.ts +5 -5
  130. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/capabilities.ts +2 -2
  131. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/maintain-job.ts +8 -8
  132. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/page-content.ts +2 -2
  133. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/provider-blocks.ts +1 -1
  134. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/reconcile.ts +3 -3
  135. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/render-injection.ts +1 -1
  136. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/router.ts +3 -3
  137. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selection-log-store.ts +4 -4
  138. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selector.ts +4 -4
  139. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/shadow-plugin.ts +90 -28
  140. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/tree.ts +1 -1
  141. package/src/plugin-api/index.ts +5 -0
  142. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +93 -0
  143. package/src/plugins/defaults/{memory-v3-shadow → circuit-breaker}/package.json +2 -2
  144. package/src/plugins/defaults/circuit-breaker/register.ts +39 -0
  145. package/src/plugins/defaults/compaction/middlewares/compaction.ts +25 -0
  146. package/src/plugins/defaults/compaction/package.json +1 -1
  147. package/src/plugins/defaults/compaction/register.ts +19 -8
  148. package/src/plugins/defaults/compaction/terminal.ts +73 -0
  149. package/src/plugins/defaults/index.ts +5 -3
  150. package/src/plugins/defaults/{memory-retrieval/injectors.ts → injectors/register.ts} +7 -45
  151. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +7 -11
  152. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  153. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +126 -0
  154. package/src/plugins/defaults/overflow-reduce/package.json +15 -0
  155. package/src/plugins/defaults/overflow-reduce/register.ts +42 -0
  156. package/src/plugins/external-api.ts +2 -2
  157. package/src/plugins/pipeline.ts +293 -6
  158. package/src/plugins/registry.ts +37 -9
  159. package/src/plugins/types.ts +336 -32
  160. package/src/plugins/user-loader.ts +127 -30
  161. package/src/proactive-artifact/aux-message-injector.ts +1 -1
  162. package/src/proactive-artifact/job.test.ts +1 -1
  163. package/src/prompts/__tests__/system-prompt.test.ts +0 -6
  164. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +2 -4
  165. package/src/runtime/__tests__/agent-wake.test.ts +5 -5
  166. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  167. package/src/runtime/agent-wake.ts +3 -0
  168. package/src/runtime/assistant-event-hub.ts +1 -1
  169. package/src/runtime/channel-approvals.ts +1 -1
  170. package/src/runtime/interactive-ui.ts +1 -1
  171. package/src/runtime/routes/__tests__/acp-routes.test.ts +55 -283
  172. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  173. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +4 -5
  174. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -4
  175. package/src/runtime/routes/acp-routes.test.ts +25 -89
  176. package/src/runtime/routes/acp-routes.ts +29 -81
  177. package/src/runtime/routes/approval-routes.ts +1 -1
  178. package/src/runtime/routes/browser-routes.ts +1 -1
  179. package/src/runtime/routes/browser-tabs-routes.ts +10 -6
  180. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  181. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  182. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  183. package/src/runtime/routes/conversation-routes.ts +2 -15
  184. package/src/runtime/routes/conversation-starter-routes.ts +7 -13
  185. package/src/runtime/routes/conversations-import-routes.ts +7 -24
  186. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  187. package/src/runtime/routes/host-cu-routes.ts +1 -1
  188. package/src/runtime/routes/identity-routes.ts +3 -18
  189. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  190. package/src/runtime/routes/memory-v3-routes.ts +6 -16
  191. package/src/runtime/routes/playground/helpers.ts +1 -1
  192. package/src/runtime/routes/surface-conversation-resolver.ts +3 -4
  193. package/src/runtime/routes/work-items-routes.ts +4 -2
  194. package/src/runtime/services/conversation-serializer.ts +1 -1
  195. package/src/signals/cancel.ts +4 -2
  196. package/src/subagent/manager.ts +5 -17
  197. package/src/tools/acp/list-agents.test.ts +1 -7
  198. package/src/tools/acp/spawn.test.ts +55 -158
  199. package/src/tools/acp/spawn.ts +72 -47
  200. package/src/tools/acp/steer.test.ts +8 -105
  201. package/src/tools/acp/steer.ts +17 -48
  202. package/src/tools/apps/executors.ts +8 -13
  203. package/src/tools/filesystem/write.ts +0 -34
  204. package/src/tools/subagent/spawn.ts +4 -2
  205. package/src/tools/ui-surface/definitions.ts +4 -25
  206. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +5 -4
  207. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +45 -69
  208. package/examples/plugins/echo/hooks/post-tool-use.ts +0 -18
  209. package/examples/plugins/echo/hooks/stop.ts +0 -16
  210. package/examples/plugins/echo/hooks/user-prompt-submit.ts +0 -18
  211. package/examples/plugins/echo/src/emit.ts +0 -19
  212. package/src/__tests__/compaction-circuit.test.ts +0 -258
  213. package/src/__tests__/compaction-direct.test.ts +0 -132
  214. package/src/__tests__/conversations-import-system-filter.test.ts +0 -101
  215. package/src/acp/__tests__/agent-process.test.ts +0 -161
  216. package/src/acp/__tests__/helpers/acp-history-db.ts +0 -82
  217. package/src/acp/__tests__/helpers/exec-file-stub.ts +0 -101
  218. package/src/acp/__tests__/session-manager-resume.test.ts +0 -736
  219. package/src/acp/auto-install.test.ts +0 -196
  220. package/src/acp/auto-install.ts +0 -177
  221. package/src/acp/feature-gate.test.ts +0 -48
  222. package/src/acp/feature-gate.ts +0 -34
  223. package/src/acp/resume-hint.ts +0 -25
  224. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +0 -48
  225. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +0 -57
  226. package/src/config/bundled-skills/app-builder/references/SLIDES.md +0 -38
  227. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -62
  228. package/src/daemon/conversation-registry.ts +0 -159
  229. package/src/daemon/overflow-reduction-loop.ts +0 -230
  230. package/src/memory/migrations/272-acp-session-history-cwd.ts +0 -36
  231. package/src/plugins/defaults/compaction/compact.ts +0 -59
  232. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +0 -14
  233. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +0 -19
  234. package/src/plugins/defaults/memory-v3-shadow/injector.ts +0 -75
  235. package/src/plugins/defaults/memory-v3-shadow/register.ts +0 -26
  236. package/src/tools/acp/context.ts +0 -20
  237. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/capabilities.test.ts +0 -0
  238. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/core.test.ts +0 -0
  239. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/eval-turns.json +0 -0
  240. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/live-turns.json +0 -0
  241. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/health.test.ts +0 -0
  242. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/needle.test.ts +0 -0
  243. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/provider-blocks.test.ts +0 -0
  244. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/snapshot.test.ts +0 -0
  245. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/tree.test.ts +0 -0
  246. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/types.test.ts +0 -0
  247. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-eviction.test.ts +0 -0
  248. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-skeleton.test.ts +0 -0
  249. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/core.ts +0 -0
  250. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/README.md +0 -0
  251. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/assignments.json +0 -0
  252. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/core.json +0 -0
  253. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-x.md +0 -0
  254. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-y.md +0 -0
  255. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-b/topic-z.md +0 -0
  256. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/health.ts +0 -0
  257. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/llm-retry.ts +0 -0
  258. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/needle.ts +0 -0
  259. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/orchestrate.ts +0 -0
  260. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/snapshot.ts +0 -0
  261. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/types.ts +0 -0
  262. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/working-set.ts +0 -0
@@ -1,59 +1,43 @@
1
1
  # Echo plugin
2
2
 
3
- Minimal example plugin. Observes the assistant's turn-lifecycle hooks and logs
4
- one JSON line per hook invocation to `stderr`:
3
+ Minimal example plugin. Observes every assistant pipeline and logs one JSON
4
+ line per invocation to `stderr`:
5
5
 
6
6
  ```json
7
- {
8
- "plugin": "echo",
9
- "hook": "post-tool-use",
10
- "conversationId": "conv_abc123"
11
- }
7
+ {"plugin":"echo","pipeline":"compaction","durationMs":1590,"outcome":"success"}
8
+ {"plugin":"echo","pipeline":"overflowReduce","durationMs":42,"outcome":"success"}
12
9
  ```
13
10
 
14
11
  Use this as a starting point for writing your own plugin, or as a quick way
15
- to eyeball which hooks fire during a conversation.
12
+ to eyeball which pipelines fire during a conversation and how long they
13
+ take.
16
14
 
17
- For the full plugin authoring guide — manifest shape, every contribution
18
- surface, hook patterns, and conventions — see
19
- [`experimental/plugins/README.md`](../../../../experimental/plugins/README.md).
20
- [`simple-memory`](../../../../experimental/plugins/simple-memory/) is the
21
- canonical reference implementation that exercises every wired surface.
15
+ For the full plugin authoring guide, see
16
+ [`assistant/docs/plugins.md`](../../../docs/plugins.md).
22
17
 
23
18
  ## What it does
24
19
 
25
- - Contributes one observer hook per turn-lifecycle event:
26
- `user-prompt-submit`, `post-tool-use`, and `stop`.
27
- - Each hook emits one line to `stderr` and returns `void`, so the threaded
28
- context flows through unchanged.
29
- - Never modifies the turn's messages, tool results, or stop decision. It is
30
- purely observational safe to stack alongside any other plugin.
31
-
32
- ## Directory layout
33
-
34
- The assistant discovers a plugin by its `package.json` manifest and builds the
35
- `Plugin` from the interface directories — `hooks/<name>.ts` files whose default
36
- export is the hook function. Files under `src/` are internal helpers and are
37
- not walked by the loader.
38
-
39
- ```
40
- echo/
41
- ├── package.json # Manifest (name + @vellumai/plugin-api range)
42
- ├── README.md
43
- ├── hooks/
44
- │ ├── user-prompt-submit.ts # default export = hook function
45
- │ ├── post-tool-use.ts
46
- │ └── stop.ts
47
- └── src/
48
- └── emit.ts # shared stderr emitter (not a surface)
49
- ```
20
+ - Registers one observer middleware per slot in
21
+ `PipelineMiddlewareMap` — `compaction`, `overflowReduce`, and
22
+ `circuitBreaker`.
23
+ - Each middleware calls `next(args)` to pass the request through unchanged,
24
+ measures wall-clock duration, and emits one line to `stderr` whether the
25
+ downstream succeeded or threw.
26
+ - Never modifies arguments, never rewrites results, never swallows errors.
27
+ It is purely observational — safe to stack alongside any other plugin.
50
28
 
51
29
  ## Install locally
52
30
 
53
31
  The assistant scans `<workspaceDir>/plugins/*` (e.g.
54
- `~/.vellum/workspace/plugins/`) for subdirectories containing a `package.json`
55
- and loads each one during assistant startup. Dropping (or symlinking) this
56
- directory in place is enough to enable it.
32
+ `~/.vellum/workspace/plugins/`) for subdirectories containing a
33
+ `register.{ts,js}` file and dynamic-imports each one during assistant
34
+ startup. Dropping (or symlinking) this directory in place is enough to
35
+ enable it.
36
+
37
+ The plugin reads `registerPlugin` from `globalThis.__vellumPluginRuntime`,
38
+ which the daemon attaches before scanning plugins. This works against both
39
+ the `bun --compile`-bundled daemon binary AND a daemon running from
40
+ source — no special install procedure required either way.
57
41
 
58
42
  ### Option 1 — symlink from the repo (simplest in-repo dev)
59
43
 
@@ -70,10 +54,23 @@ pick up changes.
70
54
  ### Option 2 — standalone copy
71
55
 
72
56
  A plain `cp -R` of this directory into `~/.vellum/workspace/plugins/echo/`
73
- works as-is. The hooks import their types from the public `@vellumai/plugin-api`
74
- specifier, which the daemon materializes as a workspace-level shim before it
75
- loads any plugin so the copied directory resolves it without any path
76
- rewriting, in or out of a vellum-assistant checkout.
57
+ works for the runtime imports (which go through the global bridge), but
58
+ the `import type` lines at the top of `register.ts` still resolve into
59
+ the in-repo assistant source tree. If your standalone copy lives outside
60
+ a vellum-assistant checkout, rewrite those `import type` paths to point
61
+ at an absolute path inside any checkout — they're erased at compile time
62
+ and have no module-identity effect at runtime:
63
+
64
+ ```ts
65
+ // before (repo-local):
66
+ import type { VellumPluginRuntime } from "../../../src/plugins/external-api.js";
67
+ import type { Plugin } from "../../../src/plugins/types.js";
68
+ // after (standalone, edit to your checkout path):
69
+ import type { VellumPluginRuntime } from "/path/to/vellum-assistant/assistant/src/plugins/external-api.js";
70
+ import type { Plugin } from "/path/to/vellum-assistant/assistant/src/plugins/types.js";
71
+ ```
72
+
73
+ No runtime-import rewriting is needed — the bridge already handles that.
77
74
 
78
75
  ### Restart the assistant
79
76
 
@@ -87,23 +84,24 @@ vellum restart
87
84
  ## Verify it works
88
85
 
89
86
  With the plugin installed and the assistant restarted, send any message
90
- that exercises a turn — a conversation reply, a tool call and tail the
91
- assistant's stderr log:
87
+ that exercises a pipeline — a conversation turn, a tool call, a title
88
+ generation — and tail the assistant's stderr log:
92
89
 
93
90
  ```bash
94
91
  tail -f ~/.vellum/daemon.log
95
92
  ```
96
93
 
97
- You should see one line per hook invocation, similar to:
94
+ You should see one line per pipeline invocation, similar to:
98
95
 
99
96
  ```json
100
- {
101
- "plugin": "echo",
102
- "hook": "post-tool-use",
103
- "conversationId": "conv_abc123"
104
- }
97
+ {"plugin":"echo","pipeline":"compaction","durationMs":1590,"outcome":"success"}
98
+ {"plugin":"echo","pipeline":"overflowReduce","durationMs":42,"outcome":"success"}
105
99
  ```
106
100
 
101
+ If a pipeline throws (for example, a tool that errors out), you'll see a
102
+ line with `"outcome":"error"` — the plugin rethrows after logging so the
103
+ original error still propagates.
104
+
107
105
  ## Uninstall
108
106
 
109
107
  Remove the symlink (or the copied directory) and restart the assistant:
@@ -115,13 +113,14 @@ vellum restart
115
113
 
116
114
  ## Next steps
117
115
 
118
- - Read [`experimental/plugins/README.md`](../../../../experimental/plugins/README.md)
119
- for the full plugin authoring guide: manifest shape, every contribution
120
- surface, hook patterns (observe / transform), tool contributions, and
121
- conventions.
116
+ - Read [`assistant/docs/plugins.md`](../../../docs/plugins.md) for the full
117
+ plugin authoring guide: manifest shape, middleware patterns
118
+ (observe / transform / short-circuit / veto), strict-fail semantics, the
119
+ per-pipeline timeout table, credential and config access, and
120
+ troubleshooting.
122
121
  - Look at the first-party default plugins under
123
- `assistant/src/plugins/defaults/` for examples of hooks that transform a
124
- turn rather than just observing it.
122
+ `assistant/src/plugins/defaults/` for examples of non-observational
123
+ middleware.
125
124
  - Build your own plugin by copying this directory, renaming the manifest
126
- `name`, and replacing the observer hooks with ones that do whatever you
127
- need.
125
+ `name`, and replacing the observer with a middleware that does whatever
126
+ you need.
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@vellumai/plugin-echo-example",
3
3
  "version": "0.1.0",
4
- "description": "Example plugin that observes every assistant turn-lifecycle hook and logs one structured line per invocation to stderr. Meant as an authoring reference — not shipped with the assistant runtime.",
4
+ "description": "Example plugin that observes every assistant pipeline and logs one structured line per invocation to stderr. Meant as an authoring reference — not shipped with the assistant runtime.",
5
5
  "private": true,
6
6
  "license": "MIT",
7
7
  "type": "module",
8
+ "main": "./register.ts",
8
9
  "engines": {
9
10
  "node": ">=20.12.0"
10
11
  },
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Echo plugin — observes every assistant pipeline and logs one structured
3
+ * line per invocation to stderr.
4
+ *
5
+ * Bundled in the repository as an authoring reference. To try it locally,
6
+ * symlink (or copy) this directory into `<workspaceDir>/plugins/echo/` and
7
+ * restart the assistant. See `README.md` in this directory for the install
8
+ * recipe and `assistant/docs/plugins.md` for general plugin authoring docs.
9
+ *
10
+ * ## Runtime bridge
11
+ *
12
+ * The plugin reads `registerPlugin` from `globalThis.__vellumPluginRuntime`,
13
+ * a stable handle the daemon attaches at startup. This lets the same plugin
14
+ * file work whether the daemon is running from source (relative or absolute
15
+ * imports would resolve to the daemon's modules) or as a `bun --compile`
16
+ * binary (where absolute imports would load a disjoint disk copy with a
17
+ * separate registry instance). The bridge is documented in
18
+ * `assistant/src/plugins/external-api.ts`.
19
+ *
20
+ * Type imports below still come from the in-repo source tree. Types are
21
+ * erased at runtime, so they don't affect module identity — but they only
22
+ * resolve while this file lives inside the vellum-assistant checkout. For a
23
+ * standalone-copy install, rewrite the `import type` paths to absolute paths
24
+ * inside a checkout (or vendor only the types you need).
25
+ *
26
+ * ## Design
27
+ *
28
+ * - Registers an observer middleware on every slot of `PipelineMiddlewareMap`.
29
+ * - Each middleware records a start timestamp, calls `next(args)`, and on
30
+ * return — whether successful or not — emits one JSON line on `stderr` with
31
+ * `{ plugin, pipeline, durationMs, outcome }`. A `try { return await next(); }
32
+ * catch { outcome = "error"; rethrow; } finally { log(); }` pattern keeps the
33
+ * observation strictly non-interfering: the plugin never swallows errors
34
+ * and never rewrites arguments or results.
35
+ * - Middleware is declared as async functions with stable names so the
36
+ * pipeline runner's `chain` log field attributes them correctly.
37
+ *
38
+ * The file exports no named symbols at module level — it only runs
39
+ * `registerPlugin(echoPlugin)` as an import-time side effect, matching the
40
+ * user-plugin-loader contract (see `assistant/src/plugins/user-loader.ts`).
41
+ */
42
+
43
+ import type { VellumPluginRuntime } from "../../../src/plugins/external-api.js";
44
+ import type {
45
+ CircuitBreakerArgs,
46
+ CircuitBreakerResult,
47
+ CompactionArgs,
48
+ CompactionResult,
49
+ OverflowReduceArgs,
50
+ OverflowReduceResult,
51
+ Plugin,
52
+ } from "../../../src/plugins/types.js";
53
+
54
+ const runtime = (globalThis as { __vellumPluginRuntime?: VellumPluginRuntime })
55
+ .__vellumPluginRuntime;
56
+ if (!runtime || runtime.version !== 1) {
57
+ throw new Error(
58
+ "echo plugin: globalThis.__vellumPluginRuntime is missing or has an unexpected version — install a recent assistant build",
59
+ );
60
+ }
61
+ const { registerPlugin } = runtime;
62
+
63
+ const PLUGIN_NAME = "echo";
64
+
65
+ /**
66
+ * One line written to stderr per pipeline invocation. Kept intentionally
67
+ * compact — pino-style JSON so operators can pipe the assistant's stderr
68
+ * through `jq` without reshaping.
69
+ */
70
+ function emit(
71
+ pipelineName: string,
72
+ startMs: number,
73
+ outcome: "success" | "error",
74
+ ): void {
75
+ const durationMs = Math.round(performance.now() - startMs);
76
+ const record = {
77
+ plugin: PLUGIN_NAME,
78
+ pipeline: pipelineName,
79
+ durationMs,
80
+ outcome,
81
+ };
82
+ process.stderr.write(`${JSON.stringify(record)}\n`);
83
+ }
84
+
85
+ /**
86
+ * Factory for a pipeline-agnostic observer middleware. The returned function
87
+ * carries a `name` so `runPipeline`'s `chain` log field attributes this
88
+ * plugin's frame correctly. Error paths rethrow — the plugin is purely
89
+ * observational and must never change the turn's outcome.
90
+ */
91
+ function makeObserver<A, R>(
92
+ pipelineName: string,
93
+ ): (args: A, next: (args: A) => Promise<R>, _ctx: unknown) => Promise<R> {
94
+ const fn = async function echoObserver(
95
+ args: A,
96
+ next: (args: A) => Promise<R>,
97
+ _ctx: unknown,
98
+ ): Promise<R> {
99
+ const start = performance.now();
100
+ let outcome: "success" | "error" = "success";
101
+ try {
102
+ return await next(args);
103
+ } catch (err) {
104
+ outcome = "error";
105
+ throw err;
106
+ } finally {
107
+ emit(pipelineName, start, outcome);
108
+ }
109
+ };
110
+ return fn;
111
+ }
112
+
113
+ /**
114
+ * The echo plugin. Declares one middleware per slot in
115
+ * `PipelineMiddlewareMap` — all thin observers produced by `makeObserver`.
116
+ *
117
+ * Manifest:
118
+ * - Host-compat range lives in `package.json` under
119
+ * `peerDependencies["@vellumai/plugin-api"]`. The external-plugin loader
120
+ * validates it against the running assistant version via
121
+ * `semver.satisfies()` before this file is even imported.
122
+ * - No `requiresCredential` or `requiresFlag` — the plugin needs no external
123
+ * state and runs unconditionally.
124
+ */
125
+ const echoPlugin: Plugin = {
126
+ manifest: {
127
+ name: PLUGIN_NAME,
128
+ version: "0.1.0",
129
+ },
130
+ middleware: {
131
+ compaction: makeObserver<CompactionArgs, CompactionResult>("compaction"),
132
+ overflowReduce: makeObserver<OverflowReduceArgs, OverflowReduceResult>(
133
+ "overflowReduce",
134
+ ),
135
+ circuitBreaker: makeObserver<CircuitBreakerArgs, CircuitBreakerResult>(
136
+ "circuitBreaker",
137
+ ),
138
+ },
139
+ };
140
+
141
+ // Side-effect registration — the user-plugin loader dynamic-imports this
142
+ // file and expects the registry to pick up the plugin during that import.
143
+ registerPlugin(echoPlugin);
@@ -194,12 +194,8 @@ export interface ProvidersFacet {
194
194
  // Memory
195
195
  // ---------------------------------------------------------------------------
196
196
 
197
- /**
198
- * Valid message roles for `memory.addMessage`. The messages store is
199
- * UI-facing (`ConversationMessage`), so only renderable turns are accepted —
200
- * agent-context `system` rows are not persisted via this facet.
201
- */
202
- export type MessageRole = "user" | "assistant";
197
+ /** Valid message roles for `memory.addMessage`. */
198
+ export type MessageRole = "user" | "assistant" | "system";
203
199
 
204
200
  /**
205
201
  * Callable signature for `memory.addMessage`. Mirrors the daemon's
@@ -251,7 +247,10 @@ export interface Subscription {
251
247
  export interface EventsFacet {
252
248
  publish(event: AssistantEvent): Promise<void>;
253
249
  subscribe(filter: Filter, cb: AssistantEventCallback): Subscription;
254
- buildEvent(message: ServerMessage, conversationId?: string): AssistantEvent;
250
+ buildEvent(
251
+ message: ServerMessage,
252
+ conversationId?: string,
253
+ ): AssistantEvent;
255
254
  }
256
255
 
257
256
  // ---------------------------------------------------------------------------
package/openapi.yaml CHANGED
@@ -3,7 +3,7 @@
3
3
  openapi: 3.1.0
4
4
  info:
5
5
  title: Vellum Assistant API
6
- version: 0.8.7
6
+ version: 0.8.8
7
7
  description: Auto-generated OpenAPI specification for the Vellum Assistant runtime HTTP server.
8
8
  servers:
9
9
  - url: http://127.0.0.1:7821
@@ -95,9 +95,7 @@ paths:
95
95
  post:
96
96
  operationId: acp_by_id_steer_post
97
97
  summary: Steer ACP session
98
- description:
99
- Send a steering instruction to an ACP session. Sessions no longer in memory (completed, or lost to a daemon
100
- restart) are transparently resumed from persisted history first, when the agent supports ACP session loading.
98
+ description: Send a steering instruction to an active ACP session.
101
99
  tags:
102
100
  - acp
103
101
  responses:
@@ -112,9 +110,6 @@ paths:
112
110
  type: string
113
111
  steered:
114
112
  type: boolean
115
- resumed:
116
- description: True when the session was resumed from persisted history before steering.
117
- type: boolean
118
113
  required:
119
114
  - acpSessionId
120
115
  - steered
@@ -141,9 +136,7 @@ paths:
141
136
  delete:
142
137
  operationId: acp_sessions_delete
143
138
  summary: Bulk-clear terminal ACP sessions
144
- description:
145
- Remove every terminal-state row (completed/failed/cancelled) from the persisted acp_session_history table.
146
- Rows whose session is currently active in memory (e.g. resumed) or has a resume in flight are excluded.
139
+ description: Remove every terminal-state row (completed/failed/cancelled) from the persisted acp_session_history table.
147
140
  tags:
148
141
  - acp
149
142
  responses:
@@ -245,8 +238,8 @@ paths:
245
238
  operationId: acp_sessions_by_id_delete
246
239
  summary: Delete ACP session from history
247
240
  description:
248
- Remove a persisted ACP session row. Rejects with 409 when the session is still active in memory or has a
249
- resume in flight; idempotent for unknown ids.
241
+ Remove a persisted ACP session row. Rejects with 409 when the session is still active in memory; idempotent
242
+ for unknown ids.
250
243
  tags:
251
244
  - acp
252
245
  responses:
@@ -16029,9 +16022,6 @@ paths:
16029
16022
  type: string
16030
16023
  role:
16031
16024
  type: string
16032
- enum:
16033
- - user
16034
- - assistant
16035
16025
  content:
16036
16026
  type: string
16037
16027
  timestamp:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.8.7-dev.202606052232.2ddc989",
3
+ "version": "0.8.8",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -33,7 +33,7 @@
33
33
  "prepack": "node ../scripts/prepack-bundled-deps.mjs"
34
34
  },
35
35
  "dependencies": {
36
- "@agentclientprotocol/sdk": "0.25.0",
36
+ "@agentclientprotocol/sdk": "0.16.1",
37
37
  "@anthropic-ai/sdk": "0.78.0",
38
38
  "@google/genai": "1.45.0",
39
39
  "@modelcontextprotocol/sdk": "1.27.1",
@@ -13,7 +13,7 @@
13
13
  * Sites not exercised here (`aborted_via_error`) require deeper provider
14
14
  * fakery and are best covered by integration tests.
15
15
  */
16
- import { describe, expect, test } from "bun:test";
16
+ import { describe, expect, spyOn, test } from "bun:test";
17
17
 
18
18
  import type {
19
19
  AgentEvent,
@@ -23,6 +23,7 @@ import type {
23
23
  } from "../agent/loop.js";
24
24
  import { AgentLoop, isMaxTokensStopReason } from "../agent/loop.js";
25
25
  import type { TurnContext } from "../plugins/types.js";
26
+ import { PluginTimeoutError } from "../plugins/types.js";
26
27
  import type {
27
28
  Message,
28
29
  Provider,
@@ -101,8 +102,8 @@ const userMessage: Message = {
101
102
  };
102
103
 
103
104
  // A turn context whose `contextWindowManager.maybeCompact` returns a canned
104
- // result, so the loop's compaction call runs without the real orchestrator
105
- // machinery.
105
+ // result, so the loop's native compaction pipeline runs without the real
106
+ // orchestrator machinery.
106
107
  function fakeCompactionTurnContext(result: {
107
108
  compacted: boolean;
108
109
  exhausted: boolean;
@@ -118,6 +119,22 @@ function fakeCompactionTurnContext(result: {
118
119
  } as unknown as TurnContext;
119
120
  }
120
121
 
122
+ // A turn context whose compaction call times out, exercising the loop's
123
+ // PluginTimeoutError handling.
124
+ function timeoutCompactionTurnContext(): TurnContext {
125
+ return {
126
+ requestId: "req-compact",
127
+ conversationId: "conv-compact",
128
+ turnIndex: 0,
129
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
130
+ contextWindowManager: {
131
+ maybeCompact: async () => {
132
+ throw new PluginTimeoutError("compaction", "default-compaction", 1);
133
+ },
134
+ },
135
+ } as unknown as TurnContext;
136
+ }
137
+
121
138
  function lastExitEvent(
122
139
  events: AgentEvent[],
123
140
  ): Extract<AgentEvent, { type: "agent_loop_exit" }> | undefined {
@@ -399,6 +416,42 @@ describe("AgentLoop exit-reason instrumentation", () => {
399
416
  expect(result.exitReason).not.toBe("budget");
400
417
  });
401
418
 
419
+ test("yields 'budget' when inline compaction times out", async () => {
420
+ const { provider } = createMockProvider([
421
+ toolUseResponse("t1", "read_file", { path: "/a.txt" }),
422
+ textResponse("never reached"),
423
+ ]);
424
+ const toolExecutor = async () => ({ content: "ok", isError: false });
425
+ const loop = new AgentLoop(provider, "system", {
426
+ tools: dummyTools,
427
+ toolExecutor: toolExecutor,
428
+ });
429
+
430
+ const compaction: MidLoopCompaction = {
431
+ postCompactionHook: async () => {
432
+ throw new Error("postCompactionHook must not run after a timeout");
433
+ },
434
+ };
435
+ const recordOutcomeSpy = spyOn(loop.compactionCircuit, "recordOutcome");
436
+
437
+ // WHEN the compaction pipeline throws a PluginTimeoutError
438
+ const result = await loop.run([userMessage], () => {}, {
439
+ resolveContextWindow: () => ({
440
+ maxInputTokens: 10,
441
+ overflowRecovery: { enabled: true, safetyMarginRatio: 0 },
442
+ }),
443
+ compaction,
444
+ turnContext: timeoutCompactionTurnContext(),
445
+ });
446
+
447
+ // THEN the loop records the timeout as a compaction failure against its
448
+ // own circuit breaker, and yields for budget so the orchestrator can
449
+ // escalate.
450
+ expect(recordOutcomeSpy).toHaveBeenCalledTimes(1);
451
+ expect(recordOutcomeSpy.mock.calls[0]?.[1]).toBe(true);
452
+ expect(result.exitReason).toBe("budget");
453
+ });
454
+
402
455
  test("yields 'budget' when inline compaction reports exhausted", async () => {
403
456
  const { provider } = createMockProvider([
404
457
  toolUseResponse("t1", "read_file", { path: "/a.txt" }),
@@ -102,7 +102,7 @@ mock.module("@anthropic-ai/sdk", () => ({
102
102
  }));
103
103
 
104
104
  // Import after mocking
105
- import { cachedTextBlock } from "../plugins/defaults/memory-v3-shadow/provider-blocks.js";
105
+ import { cachedTextBlock } from "../memory/v3/provider-blocks.js";
106
106
  import { AnthropicProvider } from "../providers/anthropic/client.js";
107
107
  import {
108
108
  isPlaceholderSentinelText,
@@ -103,7 +103,7 @@ const { ROUTES } = await import("../runtime/routes/host-app-control-routes.js");
103
103
  const { surfaceProxyResolver } =
104
104
  await import("../daemon/conversation-surfaces.js");
105
105
  const { setConversation, clearConversations } =
106
- await import("../daemon/conversation-registry.js");
106
+ await import("../daemon/conversation-store.js");
107
107
  type SurfaceConversationContext =
108
108
  import("../daemon/conversation-surfaces.js").SurfaceConversationContext;
109
109
 
@@ -19,7 +19,6 @@ const ALLOWLIST = new Set([
19
19
  "assistant/src/memory/app-store.ts", // defines getAppsDir
20
20
  "assistant/src/memory/app-git-service.ts", // uses getAppsDir for git repo root, not per-app paths
21
21
  "assistant/src/daemon/app-source-watcher.ts", // uses getAppsDir for recursive fs.watch root, not per-app paths
22
- "assistant/src/tools/filesystem/write.ts", // uses getAppsDir as an exemption root for the artifact-HTML guard, not per-app paths
23
22
  ]);
24
23
 
25
24
  function isTestFile(filePath: string): boolean {
@@ -119,15 +119,12 @@ mock.module("../permissions/trust-store.js", () => ({
119
119
  // ---------------------------------------------------------------------------
120
120
  let _conversationFactory: (() => Conversation) | undefined;
121
121
 
122
- mock.module("../daemon/conversation-registry.js", () => ({
122
+ mock.module("../daemon/conversation-store.js", () => ({
123
123
  findConversation: () => {
124
124
  // Return the current test session for any conversation ID lookup.
125
125
  if (!_conversationFactory) return undefined;
126
126
  return _conversationFactory();
127
127
  },
128
- }));
129
-
130
- mock.module("../daemon/conversation-store.js", () => ({
131
128
  getOrCreateConversation: async () => {
132
129
  if (!_conversationFactory)
133
130
  throw new Error("_conversationFactory not set in test");
@@ -16,7 +16,7 @@ mock.module("../util/logger.js", () => ({
16
16
  }));
17
17
 
18
18
  const _conversationMocks = new Map<string, unknown>();
19
- mock.module("../daemon/conversation-registry.js", () => ({
19
+ mock.module("../daemon/conversation-store.js", () => ({
20
20
  findConversation: (id: string) => _conversationMocks.get(id),
21
21
  }));
22
22
 
@@ -11,7 +11,7 @@ mock.module("../util/logger.js", () => ({
11
11
 
12
12
  // Map conversationId → mock session so findConversation returns the right mock.
13
13
  const conversationMocks = new Map<string, unknown>();
14
- mock.module("../daemon/conversation-registry.js", () => ({
14
+ mock.module("../daemon/conversation-store.js", () => ({
15
15
  findConversation: (id: string) => conversationMocks.get(id),
16
16
  }));
17
17