@vellumai/assistant 0.8.7 → 0.8.8-dev.202606052332.17fc8ea

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 (570) hide show
  1. package/Dockerfile +20 -4
  2. package/bun.lock +2 -2
  3. package/docker-entrypoint.sh +4 -2
  4. package/docker-init-apt-root.sh +3 -1
  5. package/docker-kata-apt-env.sh +3 -1
  6. package/docker-kata-runtime-family.sh +12 -0
  7. package/docs/architecture/memory.md +1 -1
  8. package/examples/plugins/echo/README.md +61 -66
  9. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  10. package/examples/plugins/echo/hooks/stop.ts +16 -0
  11. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  12. package/examples/plugins/echo/package.json +1 -2
  13. package/examples/plugins/echo/src/emit.ts +19 -0
  14. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
  15. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  16. package/openapi.yaml +3378 -335
  17. package/package.json +2 -2
  18. package/scripts/generate-openapi.ts +68 -41
  19. package/src/__tests__/agent-loop-exit-reason.test.ts +35 -93
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
  21. package/src/__tests__/agent-loop.test.ts +37 -87
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  23. package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
  24. package/src/__tests__/annotate-risk-options.test.ts +2 -3
  25. package/src/__tests__/anthropic-provider.test.ts +95 -2
  26. package/src/__tests__/app-control-flow.test.ts +1 -1
  27. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  28. package/src/__tests__/approval-routes-http.test.ts +4 -1
  29. package/src/__tests__/assistant-event-hub.test.ts +25 -0
  30. package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
  31. package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
  32. package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
  33. package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
  34. package/src/__tests__/btw-routes.test.ts +62 -3
  35. package/src/__tests__/build-persisted-content.test.ts +184 -0
  36. package/src/__tests__/catalog-files.test.ts +1 -1
  37. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  38. package/src/__tests__/channel-approvals.test.ts +1 -1
  39. package/src/__tests__/clawhub-files.test.ts +1 -1
  40. package/src/__tests__/compaction-circuit.test.ts +258 -0
  41. package/src/__tests__/compaction-direct.test.ts +132 -0
  42. package/src/__tests__/compaction.benchmark.test.ts +0 -30
  43. package/src/__tests__/config-watcher.test.ts +1 -1
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -5
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -7
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +316 -1143
  48. package/src/__tests__/conversation-agent-loop.test.ts +638 -1655
  49. package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
  50. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  51. package/src/__tests__/conversation-history-web-search.test.ts +11 -1
  52. package/src/__tests__/conversation-pairing.test.ts +4 -31
  53. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -10
  55. package/src/__tests__/conversation-queue.test.ts +2 -0
  56. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
  57. package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
  58. package/src/__tests__/conversation-runtime-assembly.test.ts +310 -300
  59. package/src/__tests__/conversation-runtime-workspace.test.ts +105 -45
  60. package/src/__tests__/conversation-slash-commands.test.ts +8 -42
  61. package/src/__tests__/conversation-slash-queue.test.ts +6 -1
  62. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  63. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
  64. package/src/__tests__/conversation-sync-tags.test.ts +27 -15
  65. package/src/__tests__/conversation-title-service.test.ts +135 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +17 -16
  67. package/src/__tests__/conversation-workspace-injection.test.ts +67 -2
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +7 -6
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/cross-provider-web-search.test.ts +214 -1
  71. package/src/__tests__/db-acp-history.test.ts +101 -0
  72. package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
  73. package/src/__tests__/dm-persistence.test.ts +5 -1
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +304 -0
  76. package/src/__tests__/feature-flag-test-helpers.ts +2 -2
  77. package/src/__tests__/file-write-tool.test.ts +63 -0
  78. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  79. package/src/__tests__/gemini-image-service.test.ts +13 -0
  80. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  81. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  82. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  83. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  84. package/src/__tests__/heartbeat-service.test.ts +1 -0
  85. package/src/__tests__/helpers/mock-provider.ts +110 -0
  86. package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
  87. package/src/__tests__/history-repair-hook.test.ts +1 -0
  88. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  89. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  90. package/src/__tests__/identity-intro-cache.test.ts +12 -100
  91. package/src/__tests__/identity-routes.test.ts +248 -7
  92. package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
  93. package/src/__tests__/injector-background-turn.test.ts +3 -9
  94. package/src/__tests__/injector-chain.test.ts +139 -275
  95. package/src/__tests__/injector-disk-pressure.test.ts +75 -41
  96. package/src/__tests__/injector-document-comments.test.ts +3 -3
  97. package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
  98. package/src/__tests__/injector-v3-suppression.test.ts +31 -37
  99. package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
  100. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  101. package/src/__tests__/list-messages-page-latest.test.ts +60 -0
  102. package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
  103. package/src/__tests__/llm-usage-store.test.ts +223 -1
  104. package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
  105. package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
  106. package/src/__tests__/native-web-search.test.ts +191 -0
  107. package/src/__tests__/onboarding-template-contract.test.ts +2 -0
  108. package/src/__tests__/openai-image-service.test.ts +17 -0
  109. package/src/__tests__/openai-provider.test.ts +31 -1
  110. package/src/__tests__/{overflow-reduce-pipeline.test.ts → overflow-reduction-loop.test.ts} +64 -284
  111. package/src/__tests__/persist-unsendable-image.test.ts +215 -0
  112. package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
  113. package/src/__tests__/pkb-autoinject.test.ts +2 -5
  114. package/src/__tests__/plugin-api-shim.test.ts +3 -6
  115. package/src/__tests__/plugin-bootstrap.test.ts +14 -40
  116. package/src/__tests__/plugin-registry.test.ts +3 -76
  117. package/src/__tests__/plugin-types.test.ts +0 -193
  118. package/src/__tests__/process-message-display-content.test.ts +6 -2
  119. package/src/__tests__/reaction-persistence.test.ts +1 -1
  120. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
  121. package/src/__tests__/resolve-trust-class.test.ts +4 -4
  122. package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
  123. package/src/__tests__/schedule-routes.test.ts +603 -2
  124. package/src/__tests__/schedule-store.test.ts +41 -0
  125. package/src/__tests__/schedule-tools.test.ts +35 -0
  126. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  127. package/src/__tests__/server-history-render.test.ts +314 -1
  128. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  129. package/src/__tests__/skillssh-files.test.ts +1 -1
  130. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  131. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  132. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  133. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  134. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  135. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  136. package/src/__tests__/system-prompt.test.ts +20 -0
  137. package/src/__tests__/task-scheduler.test.ts +162 -1
  138. package/src/__tests__/terminal-tools.test.ts +6 -1
  139. package/src/__tests__/title-generate-hook.test.ts +319 -0
  140. package/src/__tests__/tool-error-hook.test.ts +278 -0
  141. package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
  142. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  143. package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
  144. package/src/__tests__/tool-result-truncation.test.ts +0 -2
  145. package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
  146. package/src/__tests__/ui-work-result-surface.test.ts +159 -0
  147. package/src/__tests__/usage-routes.test.ts +285 -1
  148. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  149. package/src/__tests__/voice-session-bridge.test.ts +6 -3
  150. package/src/__tests__/web-search-backend-failure.test.ts +166 -0
  151. package/src/acp/__tests__/agent-process.test.ts +161 -0
  152. package/src/acp/__tests__/client-handler.test.ts +40 -0
  153. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  154. package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
  155. package/src/acp/__tests__/prepare-agent-env.test.ts +137 -0
  156. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  157. package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
  158. package/src/acp/agent-process.ts +61 -1
  159. package/src/acp/auto-install.test.ts +196 -0
  160. package/src/acp/auto-install.ts +177 -0
  161. package/src/acp/client-handler.ts +31 -0
  162. package/src/acp/feature-gate.test.ts +48 -0
  163. package/src/acp/feature-gate.ts +34 -0
  164. package/src/acp/prepare-agent-env.ts +83 -29
  165. package/src/acp/resolve-agent.test.ts +320 -7
  166. package/src/acp/resolve-agent.ts +182 -18
  167. package/src/acp/resume-hint.ts +25 -0
  168. package/src/acp/session-manager.ts +495 -73
  169. package/src/acp/types.ts +8 -0
  170. package/src/agent/compaction-circuit.ts +60 -102
  171. package/src/agent/loop.ts +362 -485
  172. package/src/api/events/assistant-thinking-delta.ts +33 -0
  173. package/src/api/events/tool-output-chunk.ts +45 -0
  174. package/src/api/events/tool-use-preview-start.ts +32 -0
  175. package/src/api/events/trace-event.ts +69 -0
  176. package/src/api/index.ts +48 -13
  177. package/src/api/responses/conversation-message.ts +374 -0
  178. package/src/approvals/guardian-request-resolvers.ts +1 -1
  179. package/src/avatar/__tests__/avatar-store.test.ts +34 -29
  180. package/src/background-wake/next-wake.ts +1 -0
  181. package/src/cli/commands/__tests__/notifications.test.ts +58 -14
  182. package/src/cli/commands/notifications.ts +112 -60
  183. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  184. package/src/config/acp-defaults.test.ts +10 -0
  185. package/src/config/acp-defaults.ts +6 -0
  186. package/src/config/assistant-feature-flags.ts +22 -11
  187. package/src/config/bundled-skills/acp/SKILL.md +83 -31
  188. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  189. package/src/config/bundled-skills/app-builder/SKILL.md +224 -398
  190. package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
  191. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  192. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  193. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  194. package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
  195. package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
  196. package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
  197. package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
  198. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  199. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  200. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  201. package/src/config/bundled-skills/messaging/SKILL.md +0 -7
  202. package/src/config/bundled-tool-registry.ts +2 -0
  203. package/src/config/feature-flag-cache.ts +3 -3
  204. package/src/config/feature-flag-registry.json +48 -7
  205. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
  207. package/src/config/schemas/heartbeat.ts +9 -0
  208. package/src/config/schemas/llm.ts +1 -0
  209. package/src/config/schemas/memory-v2.ts +8 -0
  210. package/src/config/schemas/memory-v3.ts +8 -0
  211. package/src/config/schemas/platform.ts +8 -0
  212. package/src/config/seed-inference-profiles.ts +2 -2
  213. package/src/config/skills.ts +13 -0
  214. package/src/context/compactor.ts +1 -1
  215. package/src/context/strip-injections.ts +128 -0
  216. package/src/context/token-estimator.ts +23 -0
  217. package/src/context/tool-result-truncation.ts +0 -23
  218. package/src/context/window-manager.ts +5 -7
  219. package/src/credential-execution/executable-discovery.ts +16 -0
  220. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
  221. package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
  222. package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
  223. package/src/daemon/assistant-attachments.ts +1 -1
  224. package/src/daemon/config-watcher.ts +2 -2
  225. package/src/daemon/context-overflow-reducer.ts +0 -1
  226. package/src/daemon/conversation-agent-loop-handlers.ts +594 -153
  227. package/src/daemon/conversation-agent-loop.ts +301 -997
  228. package/src/daemon/conversation-history.ts +5 -4
  229. package/src/daemon/conversation-lifecycle.ts +3 -4
  230. package/src/daemon/conversation-messaging.ts +7 -6
  231. package/src/daemon/conversation-process.ts +11 -16
  232. package/src/daemon/conversation-registry.ts +159 -0
  233. package/src/daemon/conversation-runtime-assembly.ts +218 -398
  234. package/src/daemon/conversation-slash.ts +6 -25
  235. package/src/daemon/conversation-store.ts +9 -90
  236. package/src/daemon/conversation-surfaces.ts +222 -4
  237. package/src/daemon/conversation-tool-setup.ts +2 -29
  238. package/src/daemon/conversation-workspace.ts +17 -0
  239. package/src/daemon/conversation.ts +32 -20
  240. package/src/daemon/external-plugins-bootstrap.ts +17 -18
  241. package/src/daemon/handlers/config-a2a.ts +51 -36
  242. package/src/daemon/handlers/config-slack-channel.ts +20 -14
  243. package/src/daemon/handlers/config-telegram.ts +16 -2
  244. package/src/daemon/handlers/conversations.ts +3 -1
  245. package/src/daemon/handlers/shared.ts +156 -84
  246. package/src/daemon/handlers/skills.ts +42 -10
  247. package/src/daemon/lifecycle.ts +25 -0
  248. package/src/daemon/message-types/apps.ts +1 -29
  249. package/src/daemon/message-types/messages.ts +9 -57
  250. package/src/daemon/message-types/skills.ts +2 -0
  251. package/src/daemon/message-types/surfaces.ts +136 -3
  252. package/src/daemon/now-scratchpad.ts +21 -0
  253. package/src/daemon/orphan-reaper.test.ts +210 -0
  254. package/src/daemon/orphan-reaper.ts +240 -0
  255. package/src/daemon/overflow-reduction-loop.ts +230 -0
  256. package/src/daemon/persist-unsendable-image.ts +117 -0
  257. package/src/daemon/process-message.ts +1 -3
  258. package/src/daemon/server.ts +2 -0
  259. package/src/daemon/trace-emitter.ts +6 -4
  260. package/src/daemon/trust-context.ts +19 -0
  261. package/src/daemon/wake-target-adapter.ts +3 -1
  262. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  263. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  264. package/src/heartbeat/heartbeat-service.ts +26 -0
  265. package/src/home/home-greeting-cache.ts +24 -1
  266. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  267. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  268. package/src/ipc/gateway-client.test.ts +2 -2
  269. package/src/ipc/gateway-client.ts +3 -3
  270. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  271. package/src/ipc/skill-routes/memory.ts +4 -2
  272. package/src/media/gemini-image-service.ts +15 -0
  273. package/src/media/openai-image-service.ts +14 -0
  274. package/src/media/types.ts +34 -0
  275. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
  276. package/src/memory/auth-fallback-events-store.ts +94 -0
  277. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  278. package/src/memory/conversation-title-service.ts +65 -41
  279. package/src/memory/db-init.ts +6 -0
  280. package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
  281. package/src/memory/graph/conversation-graph-memory.ts +65 -0
  282. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  283. package/src/memory/jobs-store.ts +33 -0
  284. package/src/memory/jobs-worker.ts +32 -5
  285. package/src/memory/llm-usage-store.ts +224 -50
  286. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
  287. package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
  288. package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
  289. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  290. package/src/memory/migrations/index.ts +3 -0
  291. package/src/memory/pkb/autoinject.ts +61 -0
  292. package/src/memory/pkb/context.ts +50 -0
  293. package/src/memory/pkb/types.ts +14 -0
  294. package/src/memory/schedule-attribution-sql.ts +104 -0
  295. package/src/memory/schema/acp.ts +4 -0
  296. package/src/memory/schema/infrastructure.ts +16 -0
  297. package/src/memory/usage-grouped-buckets.ts +6 -1
  298. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -4
  299. package/src/memory/v2/consolidation-job.ts +14 -5
  300. package/src/notifications/conversation-pairing.ts +8 -15
  301. package/src/notifications/decision-engine.ts +6 -3
  302. package/src/notifications/home-feed-side-effect.ts +12 -1
  303. package/src/permissions/prompter.ts +4 -0
  304. package/src/plugin-api/constants.ts +4 -0
  305. package/src/plugin-api/index.ts +7 -5
  306. package/src/plugin-api/types.ts +151 -1
  307. package/src/plugins/defaults/compaction/compact.ts +59 -0
  308. package/src/plugins/defaults/compaction/package.json +1 -1
  309. package/src/plugins/defaults/compaction/register.ts +8 -19
  310. package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
  311. package/src/plugins/defaults/empty-response/register.ts +8 -13
  312. package/src/plugins/defaults/index.ts +2 -18
  313. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +95 -0
  314. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
  315. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
  316. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +288 -81
  317. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  318. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +16 -0
  319. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +4 -4
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  321. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +48 -12
  322. package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +13 -0
  323. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  324. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  325. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +104 -32
  326. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +96 -30
  328. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +34 -16
  329. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +5 -5
  330. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +2 -2
  331. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  332. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  333. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  334. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  335. package/src/plugins/defaults/memory-v3-shadow/llm-retry.ts +32 -0
  336. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +8 -8
  337. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +26 -14
  338. package/src/plugins/defaults/{llm-call → memory-v3-shadow}/package.json +2 -2
  339. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  340. package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +26 -0
  341. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +3 -3
  342. package/src/plugins/defaults/memory-v3-shadow/register.ts +26 -0
  343. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  344. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +51 -45
  345. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  346. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +61 -46
  347. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +69 -99
  348. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  349. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +8 -0
  350. package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
  351. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
  352. package/src/plugins/defaults/title-generate/package.json +1 -1
  353. package/src/plugins/defaults/title-generate/register.ts +18 -18
  354. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
  355. package/src/plugins/defaults/tool-error/package.json +1 -1
  356. package/src/plugins/defaults/tool-error/register.ts +9 -21
  357. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
  358. package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
  359. package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
  360. package/src/plugins/external-api.ts +2 -2
  361. package/src/plugins/pipeline.ts +6 -305
  362. package/src/plugins/registry.ts +10 -55
  363. package/src/plugins/types.ts +62 -797
  364. package/src/plugins/user-loader.ts +30 -127
  365. package/src/proactive-artifact/aux-message-injector.ts +4 -4
  366. package/src/proactive-artifact/job.test.ts +8 -13
  367. package/src/prompts/__tests__/system-prompt.test.ts +42 -0
  368. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +64 -0
  369. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  370. package/src/prompts/templates/system-sections.ts +15 -0
  371. package/src/providers/anthropic/client.ts +37 -29
  372. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
  373. package/src/providers/openai/chat-completions-provider.ts +44 -0
  374. package/src/providers/openrouter/client.ts +1 -0
  375. package/src/providers/placeholder-sentinels.ts +35 -0
  376. package/src/runtime/__tests__/agent-wake.test.ts +10 -6
  377. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  378. package/src/runtime/agent-wake.ts +2 -5
  379. package/src/runtime/assistant-event-hub.ts +37 -7
  380. package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
  381. package/src/runtime/channel-approvals.ts +1 -1
  382. package/src/runtime/http-router.ts +16 -21
  383. package/src/runtime/http-types.ts +16 -70
  384. package/src/runtime/interactive-ui.ts +1 -1
  385. package/src/runtime/pending-interactions.ts +1 -0
  386. package/src/runtime/routes/__tests__/acp-routes.test.ts +283 -55
  387. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
  388. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  389. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
  390. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
  391. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  392. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  393. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  394. package/src/runtime/routes/acp-routes.test.ts +89 -25
  395. package/src/runtime/routes/acp-routes.ts +81 -29
  396. package/src/runtime/routes/app-management-routes.ts +6 -117
  397. package/src/runtime/routes/app-routes.ts +13 -15
  398. package/src/runtime/routes/approval-routes.ts +1 -1
  399. package/src/runtime/routes/attachment-routes.ts +26 -15
  400. package/src/runtime/routes/avatar-routes.ts +26 -0
  401. package/src/runtime/routes/browser-routes.ts +1 -1
  402. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  403. package/src/runtime/routes/btw-routes.ts +29 -23
  404. package/src/runtime/routes/consolidation-routes.ts +120 -20
  405. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  406. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  407. package/src/runtime/routes/conversation-query-routes.ts +3 -1
  408. package/src/runtime/routes/conversation-routes.ts +372 -185
  409. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  410. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  411. package/src/runtime/routes/documents-routes.ts +4 -0
  412. package/src/runtime/routes/domain-routes.ts +51 -37
  413. package/src/runtime/routes/epoch-millis-range.ts +34 -0
  414. package/src/runtime/routes/events-routes.ts +28 -34
  415. package/src/runtime/routes/gateway-log-routes.ts +26 -4
  416. package/src/runtime/routes/heartbeat-routes.ts +32 -12
  417. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  418. package/src/runtime/routes/host-cu-routes.ts +1 -1
  419. package/src/runtime/routes/identity-intro-cache.ts +11 -34
  420. package/src/runtime/routes/identity-routes.ts +224 -18
  421. package/src/runtime/routes/image-generation-routes.ts +40 -2
  422. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  423. package/src/runtime/routes/index.ts +2 -0
  424. package/src/runtime/routes/integrations/a2a.ts +12 -10
  425. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
  426. package/src/runtime/routes/integrations/slack/channel.ts +4 -0
  427. package/src/runtime/routes/integrations/slack/share.ts +27 -6
  428. package/src/runtime/routes/integrations/telegram.ts +6 -0
  429. package/src/runtime/routes/integrations/twilio.ts +42 -0
  430. package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
  431. package/src/runtime/routes/log-export-routes.ts +8 -0
  432. package/src/runtime/routes/memory-v2-routes.ts +15 -8
  433. package/src/runtime/routes/memory-v3-routes.ts +66 -34
  434. package/src/runtime/routes/oauth-apps.ts +66 -12
  435. package/src/runtime/routes/oauth-providers.ts +44 -5
  436. package/src/runtime/routes/platform-routes.ts +81 -5
  437. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
  438. package/src/runtime/routes/playground/force-compact.ts +1 -1
  439. package/src/runtime/routes/playground/helpers.ts +1 -1
  440. package/src/runtime/routes/rename-conversation-routes.ts +5 -0
  441. package/src/runtime/routes/schedule-routes.ts +152 -42
  442. package/src/runtime/routes/secret-routes.ts +14 -2
  443. package/src/runtime/routes/skills-routes.ts +43 -14
  444. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  445. package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
  446. package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
  447. package/src/runtime/routes/trust-rules-routes.ts +26 -2
  448. package/src/runtime/routes/tts-routes.ts +35 -0
  449. package/src/runtime/routes/types.ts +66 -8
  450. package/src/runtime/routes/usage-routes.ts +47 -39
  451. package/src/runtime/routes/webhook-routes.ts +41 -2
  452. package/src/runtime/routes/work-items-routes.ts +2 -4
  453. package/src/runtime/routes/workspace-routes.ts +4 -0
  454. package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
  455. package/src/runtime/services/analyze-conversation.ts +2 -2
  456. package/src/runtime/services/conversation-serializer.ts +1 -1
  457. package/src/schedule/schedule-store.ts +20 -1
  458. package/src/schedule/schedule-usage-store.ts +83 -0
  459. package/src/schedule/scheduler.ts +12 -5
  460. package/src/signals/cancel.ts +2 -4
  461. package/src/skills/catalog-files.ts +2 -2
  462. package/src/skills/catalog-install.ts +3 -0
  463. package/src/skills/categories-cache.ts +118 -0
  464. package/src/skills/clawhub-files.ts +1 -2
  465. package/src/skills/skillssh-files.ts +1 -2
  466. package/src/subagent/manager.ts +17 -5
  467. package/src/telemetry/types.ts +29 -1
  468. package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
  469. package/src/telemetry/usage-telemetry-reporter.ts +57 -2
  470. package/src/tools/acp/context.ts +20 -0
  471. package/src/tools/acp/list-agents.test.ts +7 -1
  472. package/src/tools/acp/spawn.test.ts +158 -55
  473. package/src/tools/acp/spawn.ts +47 -72
  474. package/src/tools/acp/steer.test.ts +105 -8
  475. package/src/tools/acp/steer.ts +48 -17
  476. package/src/tools/apps/executors.ts +13 -8
  477. package/src/tools/executor.ts +1 -53
  478. package/src/tools/filesystem/write.ts +34 -0
  479. package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
  480. package/src/tools/network/__tests__/web-search.test.ts +11 -3
  481. package/src/tools/network/web-search-error.test.ts +248 -0
  482. package/src/tools/network/web-search-error.ts +267 -0
  483. package/src/tools/network/web-search.ts +207 -48
  484. package/src/tools/schedule/create.ts +2 -0
  485. package/src/tools/subagent/spawn.ts +2 -4
  486. package/src/tools/terminal/safe-env.ts +10 -1
  487. package/src/tools/ui-surface/definitions.ts +34 -5
  488. package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
  489. package/src/tts/provider-catalog.ts +76 -1
  490. package/src/util/mutex.ts +47 -0
  491. package/src/workspace/git-service.ts +1 -42
  492. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  493. package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
  494. package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
  495. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +117 -0
  496. package/src/workspace/migrations/registry.ts +6 -0
  497. package/docs/plugins.md +0 -836
  498. package/examples/plugins/echo/register.ts +0 -184
  499. package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
  500. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  501. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  502. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  503. package/src/__tests__/empty-response-pipeline.test.ts +0 -423
  504. package/src/__tests__/llm-call-pipeline.test.ts +0 -287
  505. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
  506. package/src/__tests__/persistence-pipeline.test.ts +0 -503
  507. package/src/__tests__/pipeline-runner.test.ts +0 -564
  508. package/src/__tests__/title-generate-pipeline.test.ts +0 -211
  509. package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
  510. package/src/__tests__/tool-error-pipeline.test.ts +0 -241
  511. package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
  512. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
  513. package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
  514. package/src/gallery/default-gallery.ts +0 -1359
  515. package/src/gallery/gallery-manifest.ts +0 -28
  516. package/src/home/feature-gate.ts +0 -22
  517. package/src/memory/v3/provider-blocks.ts +0 -16
  518. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  519. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  520. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  521. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  522. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  523. package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
  524. package/src/plugins/defaults/empty-response/terminal.ts +0 -106
  525. package/src/plugins/defaults/injectors/package.json +0 -15
  526. package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
  527. package/src/plugins/defaults/llm-call/register.ts +0 -45
  528. package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
  529. package/src/plugins/defaults/memory-retrieval/package.json +0 -15
  530. package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
  531. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  532. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  533. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  534. package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
  535. package/src/plugins/defaults/persistence/package.json +0 -15
  536. package/src/plugins/defaults/persistence/register.ts +0 -38
  537. package/src/plugins/defaults/persistence/terminal.ts +0 -83
  538. package/src/plugins/defaults/title-generate/terminal.ts +0 -31
  539. package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
  540. package/src/plugins/defaults/token-estimate/package.json +0 -15
  541. package/src/plugins/defaults/token-estimate/register.ts +0 -34
  542. package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
  543. package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
  544. package/src/plugins/defaults/tool-error/terminal.ts +0 -47
  545. package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
  546. package/src/plugins/defaults/tool-execute/package.json +0 -15
  547. package/src/plugins/defaults/tool-execute/register.ts +0 -49
  548. package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
  549. package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
  550. package/src/skills/category-inference.ts +0 -111
  551. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  552. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  553. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  554. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  555. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  556. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  557. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  558. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +0 -0
  559. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  560. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  561. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  562. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  563. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  564. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  565. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  566. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  567. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  568. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  569. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  570. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
@@ -1,13 +1,23 @@
1
1
  /**
2
- * Tests for `GET /v1/acp/sessions`.
2
+ * Tests for ACP route handlers.
3
3
  *
4
- * The handler merges in-memory `AcpSessionManager.getStatus()` output with
5
- * persisted `acp_session_history` rows, deduping by id (in-memory wins),
6
- * filtering by `?conversationId`, sorting newest-first, and truncating to
7
- * `?limit` (default 50, max 500).
4
+ * `GET /v1/acp/sessions`: the handler merges in-memory
5
+ * `AcpSessionManager.getStatus()` output with persisted
6
+ * `acp_session_history` rows, deduping by id (in-memory wins), filtering by
7
+ * `?conversationId`, sorting newest-first, and truncating to `?limit`
8
+ * (default 50, max 500).
9
+ *
10
+ * `POST /v1/acp/spawn`: when the adapter binary is missing, the handler
11
+ * silently auto-installs allowlisted adapter packages before failing with
12
+ * the install hint. `execFile` is stubbed via the shared
13
+ * `installExecFileStub` helper so tests can script `npm i -g` outcomes.
8
14
  */
9
15
 
10
- import { beforeEach, describe, expect, mock, test } from "bun:test";
16
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
17
+
18
+ import { installAcpConfigStub } from "../../../acp/__tests__/helpers/acp-config-stub.js";
19
+ import { installExecFileStub } from "../../../acp/__tests__/helpers/exec-file-stub.js";
20
+ import { installWhichStub } from "../../../acp/__tests__/helpers/which-stub.js";
11
21
 
12
22
  mock.module("../../../util/logger.js", () => ({
13
23
  getLogger: () =>
@@ -16,6 +26,12 @@ mock.module("../../../util/logger.js", () => ({
16
26
  }),
17
27
  }));
18
28
 
29
+ const {
30
+ execScripts,
31
+ execFileMock,
32
+ reset: resetExecFileStub,
33
+ } = installExecFileStub();
34
+
19
35
  // ---------------------------------------------------------------------------
20
36
  // Stub the ACP session manager so tests control the in-memory side without
21
37
  // spawning real child processes. The route handler imports
@@ -37,64 +53,61 @@ interface FakeSessionState {
37
53
 
38
54
  let fakeInMemorySessions: FakeSessionState[] = [];
39
55
 
56
+ const spawnMock = mock(async () => ({
57
+ acpSessionId: "acp-route-session",
58
+ protocolSessionId: "proto-route-session",
59
+ }));
60
+
61
+ const defaultSteerOrResumeImpl = async (
62
+ _id: string,
63
+ _instruction: string,
64
+ ): Promise<{ resumed: boolean }> => ({ resumed: false });
65
+ let steerOrResumeImpl: (
66
+ id: string,
67
+ instruction: string,
68
+ ) => Promise<{ resumed: boolean }> = defaultSteerOrResumeImpl;
69
+ const steerOrResumeMock = mock(
70
+ (id: string, instruction: string, _send: unknown) =>
71
+ steerOrResumeImpl(id, instruction),
72
+ );
73
+
40
74
  mock.module("../../../acp/index.js", () => ({
41
75
  getAcpSessionManager: () => ({
42
76
  getStatus: () => fakeInMemorySessions,
77
+ spawn: spawnMock,
78
+ steerOrResume: steerOrResumeMock,
43
79
  }),
44
80
  }));
45
81
 
46
- import { getSqlite } from "../../../memory/db-connection.js";
82
+ // Identity env-prep: the credential-broker plumbing it wraps is exercised in
83
+ // its own suite; spawn tests here only care about the resolve/install flow.
84
+ mock.module("../../../acp/prepare-agent-env.js", () => ({
85
+ prepareAgentEnv: async (agentConfig: unknown) => agentConfig,
86
+ }));
87
+
88
+ const config = await installAcpConfigStub();
89
+ const which = installWhichStub();
90
+
91
+ import {
92
+ clearHistory,
93
+ insertHistoryRow,
94
+ } from "../../../acp/__tests__/helpers/acp-history-db.js";
95
+ import {
96
+ AcpResumeError,
97
+ AcpSessionNotFoundError,
98
+ } from "../../../acp/session-manager.js";
47
99
  import { initializeDb } from "../../../memory/db-init.js";
48
- import { ROUTES } from "../acp-routes.js";
100
+ import { FailedDependencyError, NotFoundError } from "../errors.js";
49
101
 
50
- initializeDb();
102
+ const { ROUTES } = await import("../acp-routes.js");
103
+ const { _resetAdapterInstallCacheForTests } =
104
+ await import("../../../acp/auto-install.js");
51
105
 
52
- function clearHistory(): void {
53
- getSqlite().run("DELETE FROM acp_session_history");
54
- }
106
+ initializeDb();
55
107
 
56
- function insertHistoryRow(row: {
57
- id: string;
58
- agentId: string;
59
- acpSessionId: string;
60
- parentConversationId: string;
61
- startedAt: number;
62
- completedAt?: number | null;
63
- status: string;
64
- stopReason?: string | null;
65
- error?: string | null;
66
- eventLogJson?: string;
67
- }): void {
68
- getSqlite()
69
- .query(
70
- /*sql*/ `
71
- INSERT INTO acp_session_history (
72
- id,
73
- agent_id,
74
- acp_session_id,
75
- parent_conversation_id,
76
- started_at,
77
- completed_at,
78
- status,
79
- stop_reason,
80
- error,
81
- event_log_json
82
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
83
- `,
84
- )
85
- .run(
86
- row.id,
87
- row.agentId,
88
- row.acpSessionId,
89
- row.parentConversationId,
90
- row.startedAt,
91
- row.completedAt ?? null,
92
- row.status,
93
- row.stopReason ?? null,
94
- row.error ?? null,
95
- row.eventLogJson ?? "[]",
96
- );
97
- }
108
+ afterAll(() => {
109
+ which.restore();
110
+ });
98
111
 
99
112
  function getSessionsHandler() {
100
113
  const route = ROUTES.find(
@@ -122,6 +135,13 @@ interface ResponseShape {
122
135
  beforeEach(() => {
123
136
  fakeInMemorySessions = [];
124
137
  clearHistory();
138
+ resetExecFileStub();
139
+ spawnMock.mockClear();
140
+ steerOrResumeMock.mockClear();
141
+ steerOrResumeImpl = defaultSteerOrResumeImpl;
142
+ _resetAdapterInstallCacheForTests();
143
+ config.setConfig({});
144
+ which.setWhich((cmd) => `/usr/local/bin/${cmd}`);
125
145
  });
126
146
 
127
147
  describe("GET /v1/acp/sessions — merged in-memory + history", () => {
@@ -306,7 +326,9 @@ describe("GET /v1/acp/sessions — merged in-memory + history", () => {
306
326
  });
307
327
 
308
328
  const handler = getSessionsHandler();
309
- const body = (await handler({ queryParams: { limit: "2" } })) as ResponseShape;
329
+ const body = (await handler({
330
+ queryParams: { limit: "2" },
331
+ })) as ResponseShape;
310
332
  expect(body.sessions).toHaveLength(2);
311
333
  expect(body.sessions.map((s) => s.id)).toEqual(["live-newest", "hist-mid"]);
312
334
  });
@@ -392,3 +414,209 @@ describe("GET /v1/acp/sessions — merged in-memory + history", () => {
392
414
  expect(body.sessions).toHaveLength(3);
393
415
  });
394
416
  });
417
+
418
+ // ---------------------------------------------------------------------------
419
+ // POST /v1/acp/spawn: auto-install on missing adapter binary
420
+ // ---------------------------------------------------------------------------
421
+
422
+ function getSpawnHandler() {
423
+ const route = ROUTES.find(
424
+ (r) => r.endpoint === "acp/spawn" && r.method === "POST",
425
+ );
426
+ if (!route) throw new Error("acp/spawn POST route not found");
427
+ return route.handler;
428
+ }
429
+
430
+ const SPAWN_BODY = {
431
+ agent: "claude",
432
+ task: "do something",
433
+ conversationId: "conv-1",
434
+ };
435
+
436
+ describe("POST /v1/acp/spawn: auto-install on missing binary", () => {
437
+ test("binary missing + bun present: spawn proceeds via bunx without npm", async () => {
438
+ which.setWhich({ bun: "/usr/local/bin/bun" });
439
+
440
+ const handler = getSpawnHandler();
441
+ const body = (await handler({ body: SPAWN_BODY })) as Record<
442
+ string,
443
+ unknown
444
+ >;
445
+
446
+ expect(body).toEqual({
447
+ acpSessionId: "acp-route-session",
448
+ protocolSessionId: "proto-route-session",
449
+ agent: "claude",
450
+ });
451
+ expect(execFileMock).not.toHaveBeenCalled();
452
+ expect(spawnMock).toHaveBeenCalledTimes(1);
453
+ const agentConfigArg = (spawnMock.mock.calls[0] as unknown[])[1] as {
454
+ command: string;
455
+ args: string[];
456
+ adapterCommand?: string;
457
+ };
458
+ expect(agentConfigArg.command).toBe("bun");
459
+ expect(agentConfigArg.args).toEqual([
460
+ "x",
461
+ "--bun",
462
+ "@agentclientprotocol/claude-agent-acp",
463
+ ]);
464
+ expect(agentConfigArg.adapterCommand).toBe("claude-agent-acp");
465
+ });
466
+
467
+ test("known command: installs the mapped package and spawn proceeds", async () => {
468
+ // Binary appears on PATH only after `npm i -g` runs, simulating a
469
+ // successful global install.
470
+ let binaryOnPath = false;
471
+ which.setWhich((cmd) => (binaryOnPath ? `/usr/local/bin/${cmd}` : null));
472
+ execScripts.set("npm i", {
473
+ stdout: "",
474
+ onCall: () => {
475
+ binaryOnPath = true;
476
+ },
477
+ });
478
+
479
+ const handler = getSpawnHandler();
480
+ const body = (await handler({ body: SPAWN_BODY })) as Record<
481
+ string,
482
+ unknown
483
+ >;
484
+
485
+ expect(body).toEqual({
486
+ acpSessionId: "acp-route-session",
487
+ protocolSessionId: "proto-route-session",
488
+ agent: "claude",
489
+ });
490
+ expect(spawnMock).toHaveBeenCalledTimes(1);
491
+ expect(execFileMock).toHaveBeenCalledTimes(1);
492
+ expect(execFileMock.mock.calls[0][1]).toEqual([
493
+ "i",
494
+ "-g",
495
+ "@agentclientprotocol/claude-agent-acp",
496
+ ]);
497
+ });
498
+
499
+ test("npm failure: FailedDependencyError carries hint and failure reason", async () => {
500
+ which.setWhich({});
501
+ execScripts.set("npm i", {
502
+ error: new Error("EACCES: permission denied"),
503
+ });
504
+
505
+ const handler = getSpawnHandler();
506
+ const promise = handler({ body: SPAWN_BODY });
507
+ await expect(promise).rejects.toBeInstanceOf(FailedDependencyError);
508
+ await expect(promise).rejects.toThrow(
509
+ /claude-agent-acp is not on PATH.*npm i -g @agentclientprotocol\/claude-agent-acp.*auto-install failed.*EACCES/,
510
+ );
511
+ expect(spawnMock).not.toHaveBeenCalled();
512
+ });
513
+
514
+ test("unknown command: plain hint maps to FailedDependencyError", async () => {
515
+ // The allowlist itself (no npm invocation for unmapped commands) is
516
+ // pinned in auto-install.test.ts and spawn.test.ts; this asserts only
517
+ // the route's transport mapping of the plain-hint failure.
518
+ config.setConfig({
519
+ agents: { custom: { command: "custom-bin", args: [] } },
520
+ });
521
+ which.setWhich({});
522
+
523
+ const handler = getSpawnHandler();
524
+ const promise = handler({ body: { ...SPAWN_BODY, agent: "custom" } });
525
+ await expect(promise).rejects.toBeInstanceOf(FailedDependencyError);
526
+ await expect(promise).rejects.toThrow(
527
+ "custom-bin is not on PATH. Install 'custom-bin' and ensure it is on PATH.",
528
+ );
529
+ });
530
+ });
531
+
532
+ // ---------------------------------------------------------------------------
533
+ // POST /v1/acp/:id/steer: transparent resume of sessions not in memory
534
+ // ---------------------------------------------------------------------------
535
+
536
+ function getSteerHandler() {
537
+ const route = ROUTES.find(
538
+ (r) => r.endpoint === "acp/:id/steer" && r.method === "POST",
539
+ );
540
+ if (!route) throw new Error("acp/:id/steer POST route not found");
541
+ return route.handler;
542
+ }
543
+
544
+ describe("POST /v1/acp/:id/steer: resume fallback", () => {
545
+ test("in-memory session steers without a resume", async () => {
546
+ const handler = getSteerHandler();
547
+ const body = await handler({
548
+ pathParams: { id: "live-1" },
549
+ body: { instruction: "redirect" },
550
+ });
551
+
552
+ expect(body).toEqual({ acpSessionId: "live-1", steered: true });
553
+ expect(steerOrResumeMock).toHaveBeenCalledTimes(1);
554
+ expect(steerOrResumeMock.mock.calls[0][0]).toBe("live-1");
555
+ expect(steerOrResumeMock.mock.calls[0][1]).toBe("redirect");
556
+ expect(typeof steerOrResumeMock.mock.calls[0][2]).toBe("function");
557
+ });
558
+
559
+ test("resumed session reports the resumed flag", async () => {
560
+ steerOrResumeImpl = async () => ({ resumed: true });
561
+
562
+ const handler = getSteerHandler();
563
+ const body = await handler({
564
+ pathParams: { id: "gone-1" },
565
+ body: { instruction: "keep going" },
566
+ });
567
+
568
+ expect(body).toEqual({
569
+ acpSessionId: "gone-1",
570
+ steered: true,
571
+ resumed: true,
572
+ });
573
+ });
574
+
575
+ test("typed not-found (no session, no history row) maps to NotFoundError", async () => {
576
+ steerOrResumeImpl = async (id) => {
577
+ throw new AcpSessionNotFoundError(id);
578
+ };
579
+
580
+ const handler = getSteerHandler();
581
+ const promise = handler({
582
+ pathParams: { id: "missing-1" },
583
+ body: { instruction: "go" },
584
+ });
585
+ await expect(promise).rejects.toBeInstanceOf(NotFoundError);
586
+ });
587
+
588
+ test("resume failure surfaces as FailedDependencyError with the actionable hint", async () => {
589
+ steerOrResumeImpl = async (id) => {
590
+ throw new AcpResumeError(
591
+ new Error(
592
+ `ACP session "${id}" was recorded before resume support ` +
593
+ `(no working directory persisted) and cannot be resumed. ` +
594
+ `Spawn a new session instead.`,
595
+ ),
596
+ );
597
+ };
598
+
599
+ const handler = getSteerHandler();
600
+ const promise = handler({
601
+ pathParams: { id: "legacy-1" },
602
+ body: { instruction: "go" },
603
+ });
604
+ await expect(promise).rejects.toBeInstanceOf(FailedDependencyError);
605
+ await expect(promise).rejects.toThrow(/recorded before resume support/);
606
+ });
607
+
608
+ test("plain steer errors map to NotFoundError", async () => {
609
+ steerOrResumeImpl = async (id) => {
610
+ throw new Error(
611
+ `ACP session "${id}" is not running (status: initializing)`,
612
+ );
613
+ };
614
+
615
+ const handler = getSteerHandler();
616
+ const promise = handler({
617
+ pathParams: { id: "init-1" },
618
+ body: { instruction: "go" },
619
+ });
620
+ await expect(promise).rejects.toBeInstanceOf(NotFoundError);
621
+ });
622
+ });
@@ -24,7 +24,10 @@
24
24
  * once assistant output exists.
25
25
  */
26
26
 
27
- import { beforeEach, describe, expect, mock, test } from "bun:test";
27
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
28
+ import { tmpdir } from "node:os";
29
+ import { join } from "node:path";
30
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
28
31
 
29
32
  mock.module("../../../util/logger.js", () => ({
30
33
  getLogger: () =>
@@ -33,19 +36,28 @@ mock.module("../../../util/logger.js", () => ({
33
36
  }),
34
37
  }));
35
38
 
39
+ import { invalidateConfigCache } from "../../../config/loader.js";
36
40
  import { createConversation } from "../../../memory/conversation-crud.js";
37
41
  import { getDb } from "../../../memory/db-connection.js";
38
42
  import { initializeDb } from "../../../memory/db-init.js";
39
- import { rawRun } from "../../../memory/raw-query.js";
43
+ import { enqueueMemoryJob } from "../../../memory/jobs-store.js";
44
+ import { recordUsageEvent } from "../../../memory/llm-usage-store.js";
45
+ import { rawAll, rawRun } from "../../../memory/raw-query.js";
40
46
  import { ROUTES } from "../consolidation-routes.js";
41
47
  import type { RouteDefinition } from "../types.js";
42
48
 
43
49
  initializeDb();
44
50
 
51
+ let workspaceDir: string;
52
+ let origWorkspaceDir: string | undefined;
53
+ let configPath: string;
54
+
45
55
  function resetTables(): void {
46
56
  const db = getDb();
57
+ db.run(`DELETE FROM llm_usage_events`);
47
58
  db.run(`DELETE FROM messages`);
48
59
  db.run(`DELETE FROM conversations`);
60
+ db.run(`DELETE FROM memory_jobs`);
49
61
  }
50
62
 
51
63
  function findHandler(operationId: string): RouteDefinition["handler"] {
@@ -69,6 +81,59 @@ function insertMessage(
69
81
  );
70
82
  }
71
83
 
84
+ function recordUsageCostAt(
85
+ conversationId: string,
86
+ requestId: string,
87
+ createdAt: number,
88
+ estimatedCostUsd: number,
89
+ ): void {
90
+ const event = recordUsageEvent(
91
+ {
92
+ conversationId,
93
+ runId: null,
94
+ requestId,
95
+ actor: "main_agent",
96
+ callSite: "mainAgent",
97
+ inferenceProfile: "balanced",
98
+ provider: "anthropic",
99
+ model: "claude-sonnet-4-20250514",
100
+ inputTokens: 100,
101
+ outputTokens: 50,
102
+ cacheCreationInputTokens: 0,
103
+ cacheReadInputTokens: 0,
104
+ rawUsage: null,
105
+ },
106
+ { estimatedCostUsd, pricingStatus: "priced" },
107
+ );
108
+ rawRun(
109
+ "UPDATE llm_usage_events SET created_at = ? WHERE id = ?",
110
+ createdAt,
111
+ event.id,
112
+ );
113
+ }
114
+
115
+ function readConfig(): Record<string, unknown> {
116
+ return JSON.parse(readFileSync(configPath, "utf-8"));
117
+ }
118
+
119
+ function readMemoryJobRows(): Array<{
120
+ id: string;
121
+ status: string;
122
+ lastError: string | null;
123
+ payload: string;
124
+ }> {
125
+ return rawAll<{
126
+ id: string;
127
+ status: string;
128
+ lastError: string | null;
129
+ payload: string;
130
+ }>(`
131
+ SELECT id, status, last_error AS lastError, payload
132
+ FROM memory_jobs
133
+ ORDER BY id
134
+ `);
135
+ }
136
+
72
137
  interface RunRecord {
73
138
  id: string;
74
139
  scheduledFor: number;
@@ -79,6 +144,7 @@ interface RunRecord {
79
144
  skipReason: string | null;
80
145
  error: string | null;
81
146
  conversationId: string | null;
147
+ estimatedCostUsd: number;
82
148
  createdAt: number;
83
149
  }
84
150
 
@@ -88,9 +154,24 @@ interface ListRunsResponse {
88
154
 
89
155
  describe("listConsolidationRuns handler", () => {
90
156
  beforeEach(() => {
157
+ workspaceDir = mkdtempSync(join(tmpdir(), "vellum-consolidation-routes-"));
158
+ origWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
159
+ process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
160
+ configPath = join(workspaceDir, "config.json");
161
+ invalidateConfigCache();
91
162
  resetTables();
92
163
  });
93
164
 
165
+ afterEach(() => {
166
+ if (origWorkspaceDir === undefined) {
167
+ delete process.env.VELLUM_WORKSPACE_DIR;
168
+ } else {
169
+ process.env.VELLUM_WORKSPACE_DIR = origWorkspaceDir;
170
+ }
171
+ invalidateConfigCache();
172
+ rmSync(workspaceDir, { recursive: true, force: true });
173
+ });
174
+
94
175
  test("returns only conversations sourced from memory_v2_consolidation", async () => {
95
176
  createConversation({ title: "c1", source: "memory_v2_consolidation" });
96
177
  createConversation({ title: "h1", source: "heartbeat" });
@@ -138,6 +219,47 @@ describe("listConsolidationRuns handler", () => {
138
219
  expect(run.createdAt).toBe(1000);
139
220
  });
140
221
 
222
+ test("exposes estimatedCostUsd from the conversation total when available", async () => {
223
+ const conv = createConversation({
224
+ title: "c1",
225
+ source: "memory_v2_consolidation",
226
+ });
227
+ rawRun(
228
+ "UPDATE conversations SET created_at = ?, total_estimated_cost = ? WHERE id = ?",
229
+ 1000,
230
+ 0.42,
231
+ conv.id,
232
+ );
233
+ insertMessage(conv.id, "assistant", 2000);
234
+ recordUsageCostAt(conv.id, "consolidation-fallback-cost", 1500, 0.99);
235
+
236
+ const handler = findHandler("listConsolidationRuns");
237
+ const result = (await handler({})) as ListRunsResponse;
238
+
239
+ expect(result.runs[0]!.estimatedCostUsd).toBeCloseTo(0.42);
240
+ });
241
+
242
+ test("falls back to conversation-window usage cost when the total is empty", async () => {
243
+ const conv = createConversation({
244
+ title: "c1",
245
+ source: "memory_v2_consolidation",
246
+ });
247
+ rawRun(
248
+ "UPDATE conversations SET created_at = ? WHERE id = ?",
249
+ 1000,
250
+ conv.id,
251
+ );
252
+ insertMessage(conv.id, "assistant", 2000);
253
+ recordUsageCostAt(conv.id, "consolidation-before", 999, 0.4);
254
+ recordUsageCostAt(conv.id, "consolidation-inside", 1500, 0.07);
255
+ recordUsageCostAt(conv.id, "consolidation-after", 2001, 0.5);
256
+
257
+ const handler = findHandler("listConsolidationRuns");
258
+ const result = (await handler({})) as ListRunsResponse;
259
+
260
+ expect(result.runs[0]!.estimatedCostUsd).toBeCloseTo(0.07);
261
+ });
262
+
141
263
  test("synthesizes status='running' when conversation has no assistant message", async () => {
142
264
  createConversation({ title: "c1", source: "memory_v2_consolidation" });
143
265
 
@@ -256,3 +378,144 @@ describe("listConsolidationRuns handler", () => {
256
378
  expect(bad.runs).toHaveLength(5);
257
379
  });
258
380
  });
381
+
382
+ describe("updateConsolidationConfig handler", () => {
383
+ beforeEach(() => {
384
+ workspaceDir = mkdtempSync(join(tmpdir(), "vellum-consolidation-routes-"));
385
+ origWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
386
+ process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
387
+ configPath = join(workspaceDir, "config.json");
388
+ invalidateConfigCache();
389
+ resetTables();
390
+ });
391
+
392
+ afterEach(() => {
393
+ if (origWorkspaceDir === undefined) {
394
+ delete process.env.VELLUM_WORKSPACE_DIR;
395
+ } else {
396
+ process.env.VELLUM_WORKSPACE_DIR = origWorkspaceDir;
397
+ }
398
+ invalidateConfigCache();
399
+ rmSync(workspaceDir, { recursive: true, force: true });
400
+ });
401
+
402
+ test("persists only the consolidation enabled override without disabling memory v2", async () => {
403
+ writeFileSync(
404
+ configPath,
405
+ JSON.stringify(
406
+ {
407
+ memory: {
408
+ v2: {
409
+ enabled: true,
410
+ consolidation_interval_hours: 4,
411
+ },
412
+ },
413
+ },
414
+ null,
415
+ 2,
416
+ ) + "\n",
417
+ );
418
+
419
+ const handler = findHandler("updateConsolidationConfig");
420
+ const result = (await handler({ body: { enabled: false } })) as {
421
+ available: boolean;
422
+ enabled: boolean;
423
+ intervalMs: number;
424
+ nextRunAt: number | null;
425
+ success: boolean;
426
+ };
427
+
428
+ expect(readConfig()).toEqual({
429
+ memory: {
430
+ v2: {
431
+ enabled: true,
432
+ consolidation_interval_hours: 4,
433
+ consolidation_enabled: false,
434
+ },
435
+ },
436
+ });
437
+ expect(result).toMatchObject({
438
+ available: true,
439
+ enabled: false,
440
+ intervalMs: 4 * 60 * 60 * 1000,
441
+ nextRunAt: null,
442
+ success: true,
443
+ });
444
+ });
445
+
446
+ test("disabling automatic consolidation cancels pending automatic jobs but preserves manual run-now jobs", async () => {
447
+ writeFileSync(
448
+ configPath,
449
+ JSON.stringify(
450
+ {
451
+ memory: {
452
+ v2: {
453
+ enabled: true,
454
+ consolidation_enabled: true,
455
+ },
456
+ },
457
+ },
458
+ null,
459
+ 2,
460
+ ) + "\n",
461
+ );
462
+ const automaticJobId = enqueueMemoryJob("memory_v2_consolidate", {
463
+ trigger: "automatic",
464
+ });
465
+ const legacyJobId = enqueueMemoryJob("memory_v2_consolidate", {});
466
+ const manualJobId = enqueueMemoryJob("memory_v2_consolidate", {
467
+ trigger: "manual",
468
+ });
469
+
470
+ const handler = findHandler("updateConsolidationConfig");
471
+ await handler({ body: { enabled: false } });
472
+
473
+ const rowsById = new Map(
474
+ readMemoryJobRows().map((row) => [row.id, row] as const),
475
+ );
476
+ expect(rowsById.get(automaticJobId)).toMatchObject({
477
+ status: "failed",
478
+ lastError: "automatic_consolidation_disabled",
479
+ });
480
+ expect(rowsById.get(legacyJobId)).toMatchObject({
481
+ status: "failed",
482
+ lastError: "automatic_consolidation_disabled",
483
+ });
484
+ expect(rowsById.get(manualJobId)).toMatchObject({
485
+ status: "pending",
486
+ lastError: null,
487
+ payload: JSON.stringify({ trigger: "manual" }),
488
+ });
489
+ });
490
+
491
+ test("run-now remains available when automatic consolidation is disabled", async () => {
492
+ writeFileSync(
493
+ configPath,
494
+ JSON.stringify(
495
+ {
496
+ memory: {
497
+ v2: {
498
+ enabled: true,
499
+ consolidation_enabled: false,
500
+ },
501
+ },
502
+ },
503
+ null,
504
+ 2,
505
+ ) + "\n",
506
+ );
507
+
508
+ const handler = findHandler("runConsolidationNow");
509
+ const result = (await handler({})) as {
510
+ success: boolean;
511
+ ran: boolean;
512
+ jobId: string | null;
513
+ };
514
+
515
+ expect(result.success).toBe(true);
516
+ expect(result.ran).toBe(true);
517
+ expect(result.jobId).toBeString();
518
+ const row = readMemoryJobRows().find((job) => job.id === result.jobId);
519
+ expect(row?.payload).toBe(JSON.stringify({ trigger: "manual" }));
520
+ });
521
+ });