@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091926.ebb2d62

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 (490) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/docs/activation-funnel-telemetry.md +310 -0
  4. package/examples/plugins/echo/README.md +61 -60
  5. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  6. package/examples/plugins/echo/hooks/stop.ts +16 -0
  7. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  8. package/examples/plugins/echo/package.json +1 -2
  9. package/examples/plugins/echo/src/emit.ts +19 -0
  10. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  11. package/openapi.yaml +273 -121
  12. package/package.json +2 -2
  13. package/scripts/sync-llm-catalog.ts +2 -0
  14. package/src/__tests__/activation-early-marking.test.ts +120 -0
  15. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  16. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  17. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  18. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  19. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  21. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  22. package/src/__tests__/agent-loop.test.ts +441 -96
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  24. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  25. package/src/__tests__/anthropic-provider.test.ts +23 -8
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  27. package/src/__tests__/app-control-flow.test.ts +1 -1
  28. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  29. package/src/__tests__/app-executors.test.ts +132 -0
  30. package/src/__tests__/approval-cascade.test.ts +6 -5
  31. package/src/__tests__/approval-routes-http.test.ts +4 -1
  32. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  33. package/src/__tests__/build-persisted-content.test.ts +75 -1
  34. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  35. package/src/__tests__/channel-approvals.test.ts +1 -1
  36. package/src/__tests__/compaction-circuit.test.ts +258 -0
  37. package/src/__tests__/compaction-direct.test.ts +146 -0
  38. package/src/__tests__/compaction-events.test.ts +7 -7
  39. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  40. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  41. package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
  42. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  43. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  48. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  49. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  50. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  51. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  52. package/src/__tests__/conversation-error.test.ts +15 -1
  53. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  54. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  55. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  56. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  57. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  58. package/src/__tests__/conversation-queue.test.ts +10 -10
  59. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  60. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  61. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  62. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  63. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  64. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  65. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  66. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  67. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  68. package/src/__tests__/conversation-undo.test.ts +2 -2
  69. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  70. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  71. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  72. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  73. package/src/__tests__/corrected-target.test.ts +93 -0
  74. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -4
  75. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  76. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  77. package/src/__tests__/db-acp-history.test.ts +101 -0
  78. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  79. package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
  80. package/src/__tests__/empty-response-hook.test.ts +1 -1
  81. package/src/__tests__/file-write-tool.test.ts +63 -0
  82. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  83. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  84. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  85. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  86. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  87. package/src/__tests__/heartbeat-service.test.ts +1 -0
  88. package/src/__tests__/history-repair-hook.test.ts +5 -1
  89. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  90. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  91. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  92. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  93. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  94. package/src/__tests__/injector-background-turn.test.ts +13 -23
  95. package/src/__tests__/injector-chain.test.ts +268 -44
  96. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  97. package/src/__tests__/injector-document-comments.test.ts +97 -114
  98. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  99. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  100. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  101. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  102. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  103. package/src/__tests__/llm-resolver.test.ts +73 -0
  104. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  105. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  106. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  107. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  108. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  109. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  110. package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
  111. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  112. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  113. package/src/__tests__/plugin-registry.test.ts +3 -49
  114. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  115. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  116. package/src/__tests__/plugin-types.test.ts +0 -70
  117. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  118. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  119. package/src/__tests__/published-app-updater.test.ts +138 -0
  120. package/src/__tests__/reaction-persistence.test.ts +1 -1
  121. package/src/__tests__/retry-thinking-adaptive-only.test.ts +245 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  123. package/src/__tests__/server-history-render.test.ts +39 -0
  124. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  125. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  126. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  127. package/src/__tests__/subagent-detail.test.ts +25 -7
  128. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  129. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  130. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  131. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  132. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  133. package/src/__tests__/title-generate-hook.test.ts +5 -1
  134. package/src/__tests__/tool-error-hook.test.ts +1 -1
  135. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  136. package/src/__tests__/tool-start-timestamp.test.ts +218 -0
  137. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  138. package/src/__tests__/web-fetch.test.ts +45 -0
  139. package/src/acp/__tests__/agent-process.test.ts +161 -0
  140. package/src/acp/__tests__/client-handler.test.ts +40 -0
  141. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  142. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  143. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  144. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  145. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  146. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  147. package/src/acp/agent-process.ts +61 -1
  148. package/src/acp/auto-install.test.ts +280 -0
  149. package/src/acp/auto-install.ts +232 -0
  150. package/src/acp/client-handler.ts +31 -0
  151. package/src/acp/prepare-agent-env.ts +80 -27
  152. package/src/acp/resolve-agent.test.ts +188 -28
  153. package/src/acp/resolve-agent.ts +119 -42
  154. package/src/acp/resume-hint.ts +23 -0
  155. package/src/acp/session-manager.ts +507 -73
  156. package/src/agent/compaction-circuit.ts +60 -102
  157. package/src/agent/loop.ts +403 -251
  158. package/src/api/events/tool-result.ts +9 -0
  159. package/src/api/events/tool-use-start.ts +7 -0
  160. package/src/api/responses/conversation-message.ts +72 -13
  161. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  162. package/src/approvals/guardian-request-resolvers.ts +27 -1
  163. package/src/background-wake/next-wake.ts +1 -0
  164. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  165. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  166. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  167. package/src/cli/commands/memory-v3.ts +57 -199
  168. package/src/cli/commands/plugins.ts +43 -37
  169. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  170. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  171. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  172. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  173. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  174. package/src/cli/lib/install-from-github.ts +797 -60
  175. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  176. package/src/cli/lib/plugin-details.ts +416 -0
  177. package/src/cli/lib/plugin-marketplace.ts +215 -0
  178. package/src/cli/lib/search-plugins.ts +201 -29
  179. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  180. package/src/config/acp-defaults.test.ts +10 -0
  181. package/src/config/acp-defaults.ts +9 -3
  182. package/src/config/acp-schema.ts +1 -7
  183. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  184. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  185. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  186. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  187. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  188. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  189. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  190. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  191. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  192. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  193. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  194. package/src/config/bundled-tool-registry.ts +4 -0
  195. package/src/config/call-site-defaults.ts +0 -3
  196. package/src/config/feature-flag-registry.json +10 -56
  197. package/src/config/llm-resolver.ts +39 -7
  198. package/src/config/loader.ts +5 -22
  199. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  200. package/src/config/schemas/call-site-catalog.ts +0 -21
  201. package/src/config/schemas/heartbeat.ts +9 -0
  202. package/src/config/schemas/llm.ts +17 -3
  203. package/src/config/schemas/memory-v3.ts +58 -8
  204. package/src/config/seed-inference-profiles.ts +18 -0
  205. package/src/context/compactor.ts +22 -4
  206. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  207. package/src/context/strip-injections.ts +8 -2
  208. package/src/daemon/conversation-agent-loop-handlers.ts +120 -42
  209. package/src/daemon/conversation-agent-loop.ts +308 -1297
  210. package/src/daemon/conversation-error.ts +31 -4
  211. package/src/daemon/conversation-history.ts +1 -1
  212. package/src/daemon/conversation-lifecycle.ts +11 -255
  213. package/src/daemon/conversation-media-retry.ts +19 -6
  214. package/src/daemon/conversation-messaging.ts +17 -0
  215. package/src/daemon/conversation-process.ts +22 -141
  216. package/src/daemon/conversation-queue-manager.ts +8 -0
  217. package/src/daemon/conversation-registry.ts +159 -0
  218. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  219. package/src/daemon/conversation-store.ts +9 -90
  220. package/src/daemon/conversation-surfaces.ts +165 -11
  221. package/src/daemon/conversation-workspace.ts +17 -0
  222. package/src/daemon/conversation.ts +444 -62
  223. package/src/daemon/disk-pressure-policy.ts +0 -1
  224. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  225. package/src/daemon/handlers/config-channels.ts +11 -2
  226. package/src/daemon/handlers/conversations.ts +3 -1
  227. package/src/daemon/handlers/shared.ts +9 -1
  228. package/src/daemon/handlers/skills.ts +4 -1
  229. package/src/daemon/host-app-control-proxy.ts +72 -57
  230. package/src/daemon/host-proxy-preactivation.ts +1 -3
  231. package/src/daemon/lifecycle.ts +21 -7
  232. package/src/daemon/persist-unsendable-image.ts +62 -25
  233. package/src/daemon/process-message.ts +1 -1
  234. package/src/daemon/server.ts +2 -0
  235. package/src/daemon/tool-side-effects.ts +15 -0
  236. package/src/daemon/wake-conversation-ops.ts +269 -0
  237. package/src/embedded/plugin-api.ts +2 -2
  238. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  239. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  240. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  241. package/src/heartbeat/heartbeat-service.ts +26 -0
  242. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  243. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  244. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  245. package/src/ipc/skill-routes/memory.ts +4 -2
  246. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  247. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  248. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  249. package/src/memory/activation-session-store.ts +43 -0
  250. package/src/memory/conversation-crud.ts +29 -19
  251. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  252. package/src/memory/db-init.ts +6 -0
  253. package/src/memory/embedding-backend.ts +5 -22
  254. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  255. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  256. package/src/memory/jobs-worker.ts +25 -1
  257. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  258. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  259. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  260. package/src/memory/migrations/index.ts +3 -0
  261. package/src/memory/onboarding-events-store.ts +66 -18
  262. package/src/memory/schema/acp.ts +4 -0
  263. package/src/memory/schema/infrastructure.ts +13 -0
  264. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  265. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  266. package/src/memory/v2/consolidation-job.ts +2 -63
  267. package/src/memory/v2/types.ts +5 -0
  268. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  269. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  270. package/src/notifications/access-request-copy.ts +298 -0
  271. package/src/notifications/adapters/slack.ts +3 -3
  272. package/src/notifications/adapters/telegram.ts +9 -2
  273. package/src/notifications/copy-composer.ts +49 -267
  274. package/src/notifications/decision-engine.ts +16 -35
  275. package/src/notifications/home-feed-side-effect.ts +1 -6
  276. package/src/plugin-api/constants.ts +4 -0
  277. package/src/plugin-api/index.ts +40 -11
  278. package/src/plugin-api/types.ts +108 -0
  279. package/src/plugins/defaults/compaction/compact.ts +106 -0
  280. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  281. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  282. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  283. package/src/plugins/defaults/compaction/package.json +1 -2
  284. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  285. package/src/plugins/defaults/empty-response/package.json +0 -1
  286. package/src/plugins/defaults/history-repair/package.json +0 -1
  287. package/src/plugins/defaults/index.ts +156 -26
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  290. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  291. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  292. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  293. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  297. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  298. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  299. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  300. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  301. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  304. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  306. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  307. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  308. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  309. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  310. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  311. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  312. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  313. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  314. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  315. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  316. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  317. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  318. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  319. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  321. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  322. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  323. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  324. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  325. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  326. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  328. package/src/plugins/defaults/title-generate/package.json +0 -1
  329. package/src/plugins/defaults/tool-error/package.json +0 -1
  330. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  331. package/src/plugins/external-api.ts +12 -2
  332. package/src/plugins/pipeline.ts +6 -293
  333. package/src/plugins/registry.ts +9 -37
  334. package/src/plugins/types.ts +76 -381
  335. package/src/plugins/user-loader.ts +30 -127
  336. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  337. package/src/prompts/system-prompt.ts +61 -10
  338. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  339. package/src/providers/anthropic/client.ts +9 -10
  340. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  341. package/src/providers/inference/logit-bias.ts +55 -0
  342. package/src/providers/model-catalog.ts +57 -0
  343. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  344. package/src/providers/openai/chat-completions-provider.ts +44 -1
  345. package/src/providers/retry.ts +57 -7
  346. package/src/providers/types.ts +6 -0
  347. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  348. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  349. package/src/runtime/agent-wake.ts +108 -209
  350. package/src/runtime/assistant-event-hub.ts +1 -1
  351. package/src/runtime/channel-approvals.ts +1 -1
  352. package/src/runtime/interactive-ui.ts +1 -1
  353. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  354. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  355. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  356. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  357. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  358. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  359. package/src/runtime/routes/acp-routes.test.ts +185 -32
  360. package/src/runtime/routes/acp-routes.ts +328 -30
  361. package/src/runtime/routes/app-management-routes.ts +3 -0
  362. package/src/runtime/routes/approval-routes.ts +1 -1
  363. package/src/runtime/routes/browser-routes.ts +1 -1
  364. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  365. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  366. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  367. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  368. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  369. package/src/runtime/routes/conversation-routes.ts +30 -2
  370. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  371. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  372. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  373. package/src/runtime/routes/host-cu-routes.ts +1 -1
  374. package/src/runtime/routes/identity-routes.ts +18 -3
  375. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  376. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  377. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  378. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  379. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  380. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  381. package/src/runtime/routes/playground/helpers.ts +1 -1
  382. package/src/runtime/routes/plugins-routes.ts +337 -35
  383. package/src/runtime/routes/stt-routes.ts +45 -12
  384. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  385. package/src/runtime/routes/work-items-routes.ts +2 -4
  386. package/src/runtime/routes/workspace-routes.ts +50 -15
  387. package/src/runtime/services/conversation-serializer.ts +1 -1
  388. package/src/runtime/verification-outbound-actions.ts +147 -2
  389. package/src/runtime/verification-templates.ts +29 -3
  390. package/src/services/published-app-updater.ts +30 -8
  391. package/src/signals/cancel.ts +2 -4
  392. package/src/subagent/manager.ts +21 -5
  393. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  394. package/src/telemetry/activation-funnel.ts +167 -0
  395. package/src/telemetry/types.ts +13 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  397. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  398. package/src/tools/acp/context.ts +20 -0
  399. package/src/tools/acp/list-agents.test.ts +9 -19
  400. package/src/tools/acp/list-agents.ts +3 -15
  401. package/src/tools/acp/spawn.test.ts +173 -202
  402. package/src/tools/acp/spawn.ts +37 -172
  403. package/src/tools/acp/steer.test.ts +105 -8
  404. package/src/tools/acp/steer.ts +48 -17
  405. package/src/tools/apps/executors.ts +166 -50
  406. package/src/tools/browser/browser-execution.ts +12 -2
  407. package/src/tools/filesystem/write.ts +34 -0
  408. package/src/tools/network/web-fetch.ts +65 -24
  409. package/src/tools/skills/load.ts +1 -1
  410. package/src/tools/subagent/spawn.ts +2 -4
  411. package/src/tools/ui-surface/definitions.ts +32 -5
  412. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  413. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  414. package/docs/plugins.md +0 -832
  415. package/examples/plugins/echo/register.ts +0 -143
  416. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  417. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  418. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  419. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  420. package/src/__tests__/pipeline-runner.test.ts +0 -554
  421. package/src/daemon/wake-target-adapter.ts +0 -253
  422. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  423. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  424. package/src/memory/v3/__tests__/core.test.ts +0 -39
  425. package/src/memory/v3/__tests__/health.test.ts +0 -219
  426. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  427. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  428. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  429. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  430. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  431. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  432. package/src/memory/v3/__tests__/router.test.ts +0 -332
  433. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  434. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  435. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  436. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  437. package/src/memory/v3/assign.ts +0 -268
  438. package/src/memory/v3/capabilities.ts +0 -124
  439. package/src/memory/v3/core.ts +0 -26
  440. package/src/memory/v3/health.ts +0 -0
  441. package/src/memory/v3/maintain-job.ts +0 -314
  442. package/src/memory/v3/needle.ts +0 -115
  443. package/src/memory/v3/orchestrate.ts +0 -126
  444. package/src/memory/v3/page-content.ts +0 -34
  445. package/src/memory/v3/provider-blocks.ts +0 -26
  446. package/src/memory/v3/reconcile.ts +0 -523
  447. package/src/memory/v3/router.ts +0 -190
  448. package/src/memory/v3/selection-log-store.ts +0 -84
  449. package/src/memory/v3/selector.ts +0 -226
  450. package/src/memory/v3/shadow-plugin.ts +0 -411
  451. package/src/memory/v3/snapshot.ts +0 -209
  452. package/src/memory/v3/tree.ts +0 -174
  453. package/src/memory/v3/types.ts +0 -59
  454. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  455. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  456. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  457. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  458. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  459. package/src/plugins/defaults/compaction/register.ts +0 -35
  460. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  461. package/src/plugins/defaults/empty-response/register.ts +0 -23
  462. package/src/plugins/defaults/history-repair/register.ts +0 -24
  463. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  464. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  465. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  466. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  467. package/src/plugins/defaults/title-generate/register.ts +0 -35
  468. package/src/plugins/defaults/tool-error/register.ts +0 -23
  469. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  470. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  471. package/src/proactive-artifact/decision.test.ts +0 -226
  472. package/src/proactive-artifact/decision.ts +0 -165
  473. package/src/proactive-artifact/index.ts +0 -7
  474. package/src/proactive-artifact/job.test.ts +0 -962
  475. package/src/proactive-artifact/job.ts +0 -372
  476. package/src/proactive-artifact/message-copy.ts +0 -58
  477. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  478. package/src/proactive-artifact/trigger-state.ts +0 -123
  479. package/src/util/map-limit.ts +0 -27
  480. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  481. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  482. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  483. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  484. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  485. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  486. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  487. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  488. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  489. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  490. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
@@ -1,3 +1,8 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { writeFileSync } from "node:fs";
3
+ import { unlink, writeFile } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
1
6
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
7
 
3
8
  // ---------------------------------------------------------------------------
@@ -26,6 +31,35 @@ mock.module("../../../providers/speech-to-text/resolve.js", () => ({
26
31
  },
27
32
  }));
28
33
 
34
+ // -- Spawn mock (ffmpeg/ffprobe) for the file-transcription path ------------
35
+
36
+ type SpawnResult = { exitCode: number; stdout: string; stderr: string };
37
+ let spawnOverride: ((args: string[]) => SpawnResult) | null = null;
38
+
39
+ /**
40
+ * Default spawn behavior: ffmpeg writes the requested output file so the
41
+ * downstream readFile succeeds, ffprobe reports a short duration.
42
+ */
43
+ function defaultSpawn(args: string[]): SpawnResult {
44
+ if (args[0] === "ffmpeg") {
45
+ const outputPath = args[args.length - 1];
46
+ writeFileSync(outputPath, Buffer.from("fake-wav-bytes"));
47
+ return { exitCode: 0, stdout: "", stderr: "" };
48
+ }
49
+ if (args[0] === "ffprobe") {
50
+ return { exitCode: 0, stdout: "1.0", stderr: "" };
51
+ }
52
+ return { exitCode: 0, stdout: "", stderr: "" };
53
+ }
54
+
55
+ mock.module("../../../util/spawn.js", () => ({
56
+ FFMPEG_TRANSCODE_TIMEOUT_MS: 60_000,
57
+ FFPROBE_TIMEOUT_MS: 10_000,
58
+ STT_REQUEST_TIMEOUT_MS: 300_000,
59
+ spawnWithTimeout: async (args: string[]) =>
60
+ (spawnOverride ?? defaultSpawn)(args),
61
+ }));
62
+
29
63
  // ---------------------------------------------------------------------------
30
64
  // Import under test — after mocks
31
65
  // ---------------------------------------------------------------------------
@@ -86,6 +120,7 @@ const fakeTranscriber: BatchTranscriber = {
86
120
  beforeEach(() => {
87
121
  mockTranscriber = fakeTranscriber;
88
122
  mockResolveError = null;
123
+ spawnOverride = null;
89
124
  });
90
125
 
91
126
  afterEach(() => {
@@ -366,4 +401,81 @@ describe("stt-routes", () => {
366
401
  "BAD_GATEWAY",
367
402
  );
368
403
  });
404
+
405
+ // -- File transcription: provider error categorization --------------------
406
+
407
+ async function withTempAudioFile(
408
+ run: (filePath: string) => Promise<void>,
409
+ ): Promise<void> {
410
+ const filePath = join(tmpdir(), `vellum-stt-test-${randomUUID()}.wav`);
411
+ await writeFile(filePath, Buffer.from("fake-audio"));
412
+ try {
413
+ await run(filePath);
414
+ } finally {
415
+ await unlink(filePath).catch(() => {});
416
+ }
417
+ }
418
+
419
+ test("transcribe-file maps provider auth failures (403) to 401", async () => {
420
+ // Reproduces JARVIS-994: a raw Gemini 403 denial must surface as an
421
+ // actionable 401, not a generic 502, and must not be treated as a
422
+ // daemon-level error.
423
+ mockTranscriber = {
424
+ providerId: "google-gemini",
425
+ boundaryId: "daemon-batch",
426
+ transcribe: async () => {
427
+ throw new Error(
428
+ 'Google Gemini API error (403): {"error":{"code":403,"message":"Your project has been denied access. Please contact support.","status":"PERMISSION_DENIED"}}',
429
+ );
430
+ },
431
+ };
432
+
433
+ await withTempAudioFile(async (filePath) => {
434
+ const { handler } = getRoute("stt/transcribe-file");
435
+ const err = await expectRouteError(
436
+ () => handler(makeArgs({ filePath })),
437
+ 401,
438
+ "UNAUTHORIZED",
439
+ );
440
+ expect(err.message).toContain("credentials");
441
+ });
442
+ });
443
+
444
+ test("transcribe-file maps generic provider failures to 502", async () => {
445
+ mockTranscriber = {
446
+ providerId: "openai-whisper",
447
+ boundaryId: "daemon-batch",
448
+ transcribe: async () => {
449
+ throw new Error("upstream kaboom");
450
+ },
451
+ };
452
+
453
+ await withTempAudioFile(async (filePath) => {
454
+ const { handler } = getRoute("stt/transcribe-file");
455
+ await expectRouteError(
456
+ () => handler(makeArgs({ filePath })),
457
+ 502,
458
+ "BAD_GATEWAY",
459
+ );
460
+ });
461
+ });
462
+
463
+ test("transcribe-file maps ffmpeg failures to 502 even when stderr contains a status-like number", async () => {
464
+ // Guards against ffmpeg stderr (or a user-supplied path) deciding the HTTP
465
+ // category: a conversion failure whose message contains "403" must stay a
466
+ // 502 conversion error, not become a misleading 401 auth error.
467
+ spawnOverride = (args) =>
468
+ args[0] === "ffmpeg"
469
+ ? { exitCode: 1, stdout: "", stderr: "Error opening /tmp/403/clip.wav" }
470
+ : { exitCode: 0, stdout: "1.0", stderr: "" };
471
+
472
+ await withTempAudioFile(async (filePath) => {
473
+ const { handler } = getRoute("stt/transcribe-file");
474
+ await expectRouteError(
475
+ () => handler(makeArgs({ filePath })),
476
+ 502,
477
+ "BAD_GATEWAY",
478
+ );
479
+ });
480
+ });
369
481
  });
@@ -42,7 +42,7 @@ const findBySurfaceCalls: string[] = [];
42
42
  const getOrCreateCalls: string[] = [];
43
43
  const rawGetCalls: Array<{ sql: string; params: unknown[] }> = [];
44
44
 
45
- mock.module("../../../daemon/conversation-store.js", () => ({
45
+ mock.module("../../../daemon/conversation-registry.js", () => ({
46
46
  findConversation: (id: string) => {
47
47
  findConvCalls.push(id);
48
48
  return memoryById ?? undefined;
@@ -51,6 +51,9 @@ mock.module("../../../daemon/conversation-store.js", () => ({
51
51
  findBySurfaceCalls.push(surfaceId);
52
52
  return memoryBySurface ?? undefined;
53
53
  },
54
+ }));
55
+
56
+ mock.module("../../../daemon/conversation-store.js", () => ({
54
57
  getOrCreateConversation: async (id: string) => {
55
58
  getOrCreateCalls.push(id);
56
59
  if (!rehydrated) {
@@ -183,9 +186,7 @@ describe("triggerSurfaceAction handler", () => {
183
186
  // SQL must filter the messages table by ui_surface payload pattern.
184
187
  expect(rawGetCalls[0]!.sql).toContain("FROM messages");
185
188
  expect(rawGetCalls[0]!.sql).toContain("LIKE");
186
- expect(rawGetCalls[0]!.params).toEqual([
187
- `%"surfaceId":"surf-evicted"%`,
188
- ]);
189
+ expect(rawGetCalls[0]!.params).toEqual([`%"surfaceId":"surf-evicted"%`]);
189
190
  expect(getOrCreateCalls).toEqual(["conv-from-db"]);
190
191
  expect(rehydrated.surfaceActionCalls).toEqual([
191
192
  { surfaceId: "surf-evicted", actionId: "act-3", data: undefined },
@@ -51,7 +51,7 @@ const findBySurfaceCalls: string[] = [];
51
51
  const getOrCreateCalls: string[] = [];
52
52
  const rawGetCalls: Array<{ sql: string; params: unknown[] }> = [];
53
53
 
54
- mock.module("../../../daemon/conversation-store.js", () => ({
54
+ mock.module("../../../daemon/conversation-registry.js", () => ({
55
55
  findConversation: (id: string) => {
56
56
  findConvCalls.push(id);
57
57
  return memoryById ?? undefined;
@@ -60,6 +60,9 @@ mock.module("../../../daemon/conversation-store.js", () => ({
60
60
  findBySurfaceCalls.push(surfaceId);
61
61
  return undefined;
62
62
  },
63
+ }));
64
+
65
+ mock.module("../../../daemon/conversation-store.js", () => ({
63
66
  getOrCreateConversation: async (id: string) => {
64
67
  getOrCreateCalls.push(id);
65
68
  if (!rehydrated) {
@@ -2,25 +2,28 @@
2
2
  * Tests for the ACP route handlers.
3
3
  *
4
4
  * Suites:
5
- * - POST /v1/acp/spawn — the three failure paths produced by
6
- * `resolveAcpAgent` (acp_disabled, unknown_agent, binary_not_found).
5
+ * - POST /v1/acp/spawn: resolver failure paths (unknown_agent) and the
6
+ * body-shape guard. The binary_not_found / auto-install surface is
7
+ * covered in `__tests__/acp-routes.test.ts`.
7
8
  * - POST /v1/acp/spawn (env injection) — CLAUDE_CODE_OAUTH_TOKEN is read
8
9
  * from the credential broker (policy-gated + audited) and merged into
9
10
  * `agentConfig.env` ONLY for the `claude` agent.
10
11
  * - DELETE /v1/acp/sessions?status=completed — the bulk-clear route that
11
12
  * wipes terminal-state rows (completed/failed/cancelled) from
12
- * `acp_session_history` while leaving running/initializing rows intact.
13
+ * `acp_session_history` while leaving running/initializing rows and
14
+ * rows with an in-flight resume intact.
13
15
  * - DELETE /v1/acp/sessions/:id — single-row delete: completed → 200,
14
- * running → 409, unknown id → idempotent { deleted: false }.
16
+ * running or mid-resume → 409, unknown id → idempotent
17
+ * { deleted: false }.
15
18
  *
16
19
  * The spawn tests mirror the resolver's test setup using the shared
17
20
  * `installAcpConfigStub` and `installWhichStub` helpers so the host
18
21
  * environment doesn't influence the resolver's PATH preflight.
19
22
  *
20
- * The single-id delete tests stub `getAcpSessionManager` so we can drive
21
- * the in-memory-status check without spawning real ACP child processes,
22
- * and use the real DB (initialized via the test preload's per-file
23
- * workspace) to verify the row is actually removed.
23
+ * The delete tests stub `getAcpSessionManager` so we can drive the
24
+ * in-memory-status and pending-resume checks without spawning real ACP
25
+ * child processes, and use the real DB (initialized via the test preload's
26
+ * per-file workspace) to verify the row is actually removed.
24
27
  */
25
28
 
26
29
  import {
@@ -36,6 +39,7 @@ import {
36
39
  import { installAcpConfigStub } from "../../acp/__tests__/helpers/acp-config-stub.js";
37
40
  import { installWhichStub } from "../../acp/__tests__/helpers/which-stub.js";
38
41
  import type { AcpSessionState } from "../../acp/index.js";
42
+ import * as pendingInteractions from "../pending-interactions.js";
39
43
 
40
44
  const config = await installAcpConfigStub();
41
45
  const which = installWhichStub();
@@ -48,8 +52,10 @@ afterAll(() => {
48
52
  // in-memory-status check without spawning real ACP processes, and so the
49
53
  // env-injection spawn tests can capture the `agentConfig` arg without
50
54
  // launching a real subprocess. Stored in mutable state so individual tests
51
- // can plant arbitrary states / inspect capture.
55
+ // can plant arbitrary states / inspect capture. `pendingResumeIds` models
56
+ // ids reserved by an in-flight resumeFromHistory (no SessionEntry yet).
52
57
  const inMemoryStates = new Map<string, AcpSessionState>();
58
+ const pendingResumeIds = new Set<string>();
53
59
 
54
60
  interface CapturedSpawn {
55
61
  agent: string;
@@ -70,6 +76,9 @@ mock.module("../../acp/index.js", () => ({
70
76
  if (!state) throw new Error(`ACP session "${id}" not found`);
71
77
  return state;
72
78
  },
79
+ getActiveAndPendingIds: () => [
80
+ ...new Set([...inMemoryStates.keys(), ...pendingResumeIds]),
81
+ ],
73
82
  spawn: async (
74
83
  agent: string,
75
84
  agentConfig: { env?: Record<string, string> },
@@ -162,6 +171,31 @@ mock.module("../../tools/credentials/broker.js", () => ({
162
171
  },
163
172
  }));
164
173
 
174
+ // Drive the spawn route's high-risk approval gate (ATL-822). `spawnSession`
175
+ // registers a `directResolve` confirmation in `pendingInteractions` and then
176
+ // broadcasts a `confirmation_request`. Real broadcasting needs the event hub /
177
+ // SSE wiring, so the mock auto-resolves the freshly registered interaction
178
+ // the same way `POST /v1/confirm` would (resolve + directResolve). Tests flip
179
+ // `approvalBehavior` to exercise allow / deny, and read `confirmationRequests`
180
+ // to assert the prompt's risk shape. `interaction_resolved` and other event
181
+ // types are ignored.
182
+ type ApprovalBehavior = "allow" | "deny";
183
+ let approvalBehavior: ApprovalBehavior = "allow";
184
+ const confirmationRequests: Array<Record<string, unknown>> = [];
185
+
186
+ mock.module("../../runtime/assistant-event-hub.js", () => ({
187
+ broadcastMessage: (msg: { type?: string; requestId?: string }) => {
188
+ if (msg?.type !== "confirmation_request") return;
189
+ confirmationRequests.push(msg as Record<string, unknown>);
190
+ const decision = approvalBehavior;
191
+ const interaction = pendingInteractions.resolve(
192
+ msg.requestId as string,
193
+ decision === "allow" ? "approved" : "rejected",
194
+ );
195
+ interaction?.directResolve?.(decision);
196
+ },
197
+ }));
198
+
165
199
  import { eq } from "drizzle-orm";
166
200
 
167
201
  import { getDb, getSqlite } from "../../memory/db-connection.js";
@@ -185,6 +219,8 @@ beforeEach(() => {
185
219
  capturedSpawns.length = 0;
186
220
  vaultStore.clear();
187
221
  metadataStore.clear();
222
+ approvalBehavior = "allow";
223
+ confirmationRequests.length = 0;
188
224
  });
189
225
 
190
226
  // ---------------------------------------------------------------------------
@@ -192,63 +228,108 @@ beforeEach(() => {
192
228
  // ---------------------------------------------------------------------------
193
229
 
194
230
  describe("POST /v1/acp/spawn", () => {
195
- test("throws BadRequestError when ACP is disabled", async () => {
196
- config.setConfig({ enabled: false });
231
+ test("throws BadRequestError with merged available list when agent id is unknown", async () => {
232
+ config.setConfig({
233
+ agents: {
234
+ "user-only": { command: "some-binary", args: [] },
235
+ },
236
+ });
197
237
 
198
238
  const handler = getSpawnHandler();
199
239
  await expect(
200
240
  handler({
201
241
  body: {
202
- agent: "claude",
242
+ agent: "nonexistent",
203
243
  task: "do a thing",
204
244
  conversationId: "conv-1",
205
245
  },
206
246
  }),
207
- ).rejects.toThrow("acp.enabled");
247
+ ).rejects.toThrow('Unknown agent "nonexistent"');
208
248
  });
209
249
 
210
- test("throws BadRequestError with merged available list when agent id is unknown", async () => {
211
- config.setConfig({
212
- agents: {
213
- "user-only": { command: "some-binary", args: [] },
250
+ test("body-shape guard short-circuits before the resolver runs", async () => {
251
+ const handler = getSpawnHandler();
252
+ await expect(handler({ body: { agent: "claude" } })).rejects.toThrow(
253
+ "agent, task, and conversationId are required",
254
+ );
255
+ // Guard runs before the approval gate — no prompt is surfaced.
256
+ expect(confirmationRequests).toHaveLength(0);
257
+ });
258
+ });
259
+
260
+ // ---------------------------------------------------------------------------
261
+ // POST /v1/acp/spawn — high-risk approval gate (ATL-822)
262
+ //
263
+ // Spawning an ACP agent is a host subprocess with auto-allowed
264
+ // filesystem/terminal access. The skill-tool path gets the descriptor's
265
+ // `risk: "high"` prompt via ToolExecutor; this route reaches the session
266
+ // manager directly, so it surfaces the same confirmation itself and refuses
267
+ // to spawn unless a guardian approves.
268
+ // ---------------------------------------------------------------------------
269
+
270
+ describe("POST /v1/acp/spawn — approval gate", () => {
271
+ test("surfaces a high-risk host confirmation before spawning", async () => {
272
+ seedVaultToken("test-token-abc123");
273
+
274
+ const handler = getSpawnHandler();
275
+ await handler({
276
+ body: {
277
+ agent: "claude",
278
+ task: "do a thing",
279
+ conversationId: "conv-1",
280
+ cwd: "/work/repo",
214
281
  },
215
282
  });
216
283
 
284
+ expect(confirmationRequests).toHaveLength(1);
285
+ const prompt = confirmationRequests[0];
286
+ expect(prompt.toolName).toBe("acp_spawn");
287
+ expect(prompt.riskLevel).toBe("high");
288
+ expect(prompt.executionTarget).toBe("host");
289
+ expect(prompt.conversationId).toBe("conv-1");
290
+ expect((prompt.input as { cwd?: string }).cwd).toBe("/work/repo");
291
+ // Prompt resolved "allow" → spawn proceeds.
292
+ expect(capturedSpawns).toHaveLength(1);
293
+ });
294
+
295
+ test("throws ForbiddenError and does not spawn when the guardian denies", async () => {
296
+ approvalBehavior = "deny";
297
+ seedVaultToken("test-token-abc123");
298
+
217
299
  const handler = getSpawnHandler();
218
300
  await expect(
219
301
  handler({
220
302
  body: {
221
- agent: "nonexistent",
303
+ agent: "claude",
222
304
  task: "do a thing",
223
305
  conversationId: "conv-1",
224
306
  },
225
307
  }),
226
- ).rejects.toThrow('Unknown agent "nonexistent"');
308
+ ).rejects.toThrow(/guardian approval/i);
309
+
310
+ // Denied before any host side effects: no subprocess, no pending leak.
311
+ expect(capturedSpawns).toHaveLength(0);
312
+ expect(pendingInteractions.getAll()).toHaveLength(0);
227
313
  });
228
314
 
229
- test("throws FailedDependencyError when the agent binary is missing", async () => {
230
- config.setConfig({ agents: {} });
231
- which.setWhich({});
315
+ test("denies (and does not spawn) when the client disconnects before approval", async () => {
316
+ seedVaultToken("test-token-abc123");
317
+ const controller = new AbortController();
318
+ controller.abort();
232
319
 
233
320
  const handler = getSpawnHandler();
234
321
  await expect(
235
322
  handler({
236
323
  body: {
237
- agent: "codex",
324
+ agent: "claude",
238
325
  task: "do a thing",
239
326
  conversationId: "conv-1",
240
327
  },
328
+ abortSignal: controller.signal,
241
329
  }),
242
- ).rejects.toThrow("codex-acp is not on PATH");
243
- });
244
-
245
- test("body-shape guard short-circuits before the resolver runs", async () => {
246
- config.setConfig({ enabled: false });
330
+ ).rejects.toThrow(/guardian approval/i);
247
331
 
248
- const handler = getSpawnHandler();
249
- await expect(handler({ body: { agent: "claude" } })).rejects.toThrow(
250
- "agent, task, and conversationId are required",
251
- );
332
+ expect(capturedSpawns).toHaveLength(0);
252
333
  });
253
334
  });
254
335
 
@@ -457,6 +538,8 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
457
538
  });
458
539
 
459
540
  beforeEach(() => {
541
+ inMemoryStates.clear();
542
+ pendingResumeIds.clear();
460
543
  getSqlite().run("DELETE FROM acp_session_history");
461
544
  });
462
545
 
@@ -482,6 +565,57 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
482
565
  ]);
483
566
  });
484
567
 
568
+ test("excludes terminal rows whose session is active in memory (resumed sessions)", async () => {
569
+ // A resumed session reuses its original id: the in-memory state is
570
+ // `running` while its history row still carries the old terminal
571
+ // status until the next terminal upsert. The bulk delete must not
572
+ // remove that row out from under the live session.
573
+ seedHistoryRow("row-resumed", "completed", 1000);
574
+ seedHistoryRow("row-completed", "completed", 2000);
575
+ inMemoryStates.set("row-resumed", {
576
+ id: "row-resumed",
577
+ agentId: "claude",
578
+ acpSessionId: "proto-resumed",
579
+ parentConversationId: "conv-test",
580
+ status: "running",
581
+ startedAt: 1000,
582
+ });
583
+
584
+ const handler = getBulkDeleteHandler();
585
+ const result = (await handler({
586
+ queryParams: { status: "completed" },
587
+ })) as {
588
+ deleted: number;
589
+ };
590
+ expect(result.deleted).toBe(1);
591
+
592
+ const remaining = listRows();
593
+ expect(remaining).toEqual([{ id: "row-resumed", status: "completed" }]);
594
+ });
595
+
596
+ test("excludes terminal rows whose session has a resume in flight", async () => {
597
+ // A resume that is still awaiting env preparation has no in-memory
598
+ // SessionEntry yet, but its history row (still terminal) must survive:
599
+ // deleting it mid-resume would let the later terminal upsert resurrect
600
+ // it as an orphan row.
601
+ seedHistoryRow("row-pending-resume", "completed", 1000);
602
+ seedHistoryRow("row-completed", "completed", 2000);
603
+ pendingResumeIds.add("row-pending-resume");
604
+
605
+ const handler = getBulkDeleteHandler();
606
+ const result = (await handler({
607
+ queryParams: { status: "completed" },
608
+ })) as {
609
+ deleted: number;
610
+ };
611
+ expect(result.deleted).toBe(1);
612
+
613
+ const remaining = listRows();
614
+ expect(remaining).toEqual([
615
+ { id: "row-pending-resume", status: "completed" },
616
+ ]);
617
+ });
618
+
485
619
  test("returns deleted=0 when no terminal rows are present", async () => {
486
620
  seedHistoryRow("row-running", "running", 1000);
487
621
 
@@ -559,6 +693,7 @@ describe("DELETE /v1/acp/sessions/:id", () => {
559
693
 
560
694
  beforeEach(() => {
561
695
  inMemoryStates.clear();
696
+ pendingResumeIds.clear();
562
697
  getDb().delete(acpSessionHistory).run();
563
698
  });
564
699
 
@@ -610,6 +745,24 @@ describe("DELETE /v1/acp/sessions/:id", () => {
610
745
  },
611
746
  );
612
747
 
748
+ test("returns 409 when the session has a resume in flight (not yet in memory)", async () => {
749
+ pendingResumeIds.add("sess-resuming");
750
+ insertHistoryRow({ id: "sess-resuming", status: "completed" });
751
+
752
+ const handler = getDeleteSessionHandler();
753
+ expect(() => handler({ pathParams: { id: "sess-resuming" } })).toThrow(
754
+ "resume in flight",
755
+ );
756
+
757
+ // Row untouched.
758
+ const remaining = getDb()
759
+ .select()
760
+ .from(acpSessionHistory)
761
+ .where(eq(acpSessionHistory.id, "sess-resuming"))
762
+ .all();
763
+ expect(remaining).toHaveLength(1);
764
+ });
765
+
613
766
  test("idempotent for unknown id — returns { deleted: false }", async () => {
614
767
  const handler = getDeleteSessionHandler();
615
768
  const result = (await handler({