@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091853.fbaa2ae

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (475) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/docs/activation-funnel-telemetry.md +310 -0
  4. package/examples/plugins/echo/README.md +61 -60
  5. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  6. package/examples/plugins/echo/hooks/stop.ts +16 -0
  7. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  8. package/examples/plugins/echo/package.json +1 -2
  9. package/examples/plugins/echo/src/emit.ts +19 -0
  10. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  11. package/openapi.yaml +263 -121
  12. package/package.json +2 -2
  13. package/src/__tests__/activation-early-marking.test.ts +120 -0
  14. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  15. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  16. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  17. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  18. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  19. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  20. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  21. package/src/__tests__/agent-loop.test.ts +441 -96
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  23. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  24. package/src/__tests__/anthropic-provider.test.ts +23 -8
  25. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  26. package/src/__tests__/app-control-flow.test.ts +1 -1
  27. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  28. package/src/__tests__/app-executors.test.ts +132 -0
  29. package/src/__tests__/approval-cascade.test.ts +6 -5
  30. package/src/__tests__/approval-routes-http.test.ts +4 -1
  31. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  32. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  33. package/src/__tests__/channel-approvals.test.ts +1 -1
  34. package/src/__tests__/compaction-circuit.test.ts +258 -0
  35. package/src/__tests__/compaction-direct.test.ts +146 -0
  36. package/src/__tests__/compaction-events.test.ts +7 -7
  37. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  38. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  39. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  40. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  41. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  42. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  43. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  44. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  45. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  46. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  47. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  48. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  49. package/src/__tests__/conversation-error.test.ts +15 -1
  50. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  51. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  52. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  53. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  55. package/src/__tests__/conversation-queue.test.ts +10 -10
  56. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  57. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  58. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  59. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  60. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  61. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  62. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  65. package/src/__tests__/conversation-undo.test.ts +2 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  67. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/corrected-target.test.ts +93 -0
  71. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  72. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  73. package/src/__tests__/db-acp-history.test.ts +101 -0
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +1 -1
  76. package/src/__tests__/file-write-tool.test.ts +63 -0
  77. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  78. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  79. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  80. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  82. package/src/__tests__/heartbeat-service.test.ts +1 -0
  83. package/src/__tests__/history-repair-hook.test.ts +5 -1
  84. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  85. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  86. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  87. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  88. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  89. package/src/__tests__/injector-background-turn.test.ts +13 -23
  90. package/src/__tests__/injector-chain.test.ts +268 -44
  91. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  92. package/src/__tests__/injector-document-comments.test.ts +97 -114
  93. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  94. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  95. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  96. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  97. package/src/__tests__/llm-resolver.test.ts +73 -0
  98. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  99. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  100. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  101. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  102. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  103. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  104. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  105. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  106. package/src/__tests__/plugin-registry.test.ts +3 -49
  107. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  108. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  109. package/src/__tests__/plugin-types.test.ts +0 -70
  110. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  111. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  112. package/src/__tests__/published-app-updater.test.ts +138 -0
  113. package/src/__tests__/reaction-persistence.test.ts +1 -1
  114. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  115. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  116. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  117. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  118. package/src/__tests__/subagent-detail.test.ts +25 -7
  119. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  120. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  121. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  122. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  123. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  124. package/src/__tests__/title-generate-hook.test.ts +5 -1
  125. package/src/__tests__/tool-error-hook.test.ts +1 -1
  126. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  127. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  128. package/src/__tests__/web-fetch.test.ts +45 -0
  129. package/src/acp/__tests__/agent-process.test.ts +161 -0
  130. package/src/acp/__tests__/client-handler.test.ts +40 -0
  131. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  132. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  133. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  134. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  135. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  136. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  137. package/src/acp/agent-process.ts +61 -1
  138. package/src/acp/auto-install.test.ts +280 -0
  139. package/src/acp/auto-install.ts +232 -0
  140. package/src/acp/client-handler.ts +31 -0
  141. package/src/acp/prepare-agent-env.ts +80 -27
  142. package/src/acp/resolve-agent.test.ts +188 -28
  143. package/src/acp/resolve-agent.ts +119 -42
  144. package/src/acp/resume-hint.ts +23 -0
  145. package/src/acp/session-manager.ts +507 -73
  146. package/src/agent/compaction-circuit.ts +60 -102
  147. package/src/agent/loop.ts +403 -251
  148. package/src/api/responses/conversation-message.ts +22 -2
  149. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  150. package/src/approvals/guardian-request-resolvers.ts +27 -1
  151. package/src/background-wake/next-wake.ts +1 -0
  152. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  153. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  154. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  155. package/src/cli/commands/memory-v3.ts +57 -199
  156. package/src/cli/commands/plugins.ts +43 -37
  157. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  158. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  159. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  160. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  161. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  162. package/src/cli/lib/install-from-github.ts +797 -60
  163. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  164. package/src/cli/lib/plugin-details.ts +416 -0
  165. package/src/cli/lib/plugin-marketplace.ts +215 -0
  166. package/src/cli/lib/search-plugins.ts +201 -29
  167. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  168. package/src/config/acp-defaults.test.ts +10 -0
  169. package/src/config/acp-defaults.ts +9 -3
  170. package/src/config/acp-schema.ts +1 -7
  171. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  172. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  173. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  174. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  175. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  176. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  177. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  178. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  180. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  181. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  182. package/src/config/bundled-tool-registry.ts +4 -0
  183. package/src/config/call-site-defaults.ts +0 -3
  184. package/src/config/feature-flag-registry.json +10 -16
  185. package/src/config/llm-resolver.ts +39 -7
  186. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  187. package/src/config/schemas/call-site-catalog.ts +0 -21
  188. package/src/config/schemas/heartbeat.ts +9 -0
  189. package/src/config/schemas/llm.ts +17 -3
  190. package/src/config/schemas/memory-v3.ts +58 -8
  191. package/src/config/seed-inference-profiles.ts +18 -0
  192. package/src/context/compactor.ts +22 -4
  193. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  194. package/src/context/strip-injections.ts +8 -2
  195. package/src/daemon/conversation-agent-loop-handlers.ts +18 -36
  196. package/src/daemon/conversation-agent-loop.ts +308 -1297
  197. package/src/daemon/conversation-error.ts +31 -4
  198. package/src/daemon/conversation-history.ts +1 -1
  199. package/src/daemon/conversation-lifecycle.ts +11 -255
  200. package/src/daemon/conversation-media-retry.ts +19 -6
  201. package/src/daemon/conversation-messaging.ts +17 -0
  202. package/src/daemon/conversation-process.ts +22 -141
  203. package/src/daemon/conversation-queue-manager.ts +8 -0
  204. package/src/daemon/conversation-registry.ts +159 -0
  205. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  206. package/src/daemon/conversation-store.ts +9 -90
  207. package/src/daemon/conversation-surfaces.ts +165 -11
  208. package/src/daemon/conversation-workspace.ts +17 -0
  209. package/src/daemon/conversation.ts +444 -62
  210. package/src/daemon/disk-pressure-policy.ts +0 -1
  211. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  212. package/src/daemon/handlers/config-channels.ts +11 -2
  213. package/src/daemon/handlers/conversations.ts +3 -1
  214. package/src/daemon/handlers/skills.ts +4 -1
  215. package/src/daemon/host-app-control-proxy.ts +72 -57
  216. package/src/daemon/host-proxy-preactivation.ts +1 -3
  217. package/src/daemon/lifecycle.ts +21 -7
  218. package/src/daemon/persist-unsendable-image.ts +62 -25
  219. package/src/daemon/process-message.ts +1 -1
  220. package/src/daemon/server.ts +2 -0
  221. package/src/daemon/tool-side-effects.ts +15 -0
  222. package/src/daemon/wake-conversation-ops.ts +269 -0
  223. package/src/embedded/plugin-api.ts +2 -2
  224. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  225. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  226. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  227. package/src/heartbeat/heartbeat-service.ts +26 -0
  228. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  229. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  230. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  231. package/src/ipc/skill-routes/memory.ts +4 -2
  232. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  233. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  234. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  235. package/src/memory/activation-session-store.ts +43 -0
  236. package/src/memory/conversation-crud.ts +29 -19
  237. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  238. package/src/memory/db-init.ts +6 -0
  239. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  240. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  241. package/src/memory/jobs-worker.ts +25 -1
  242. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  243. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  244. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  245. package/src/memory/migrations/index.ts +3 -0
  246. package/src/memory/onboarding-events-store.ts +66 -18
  247. package/src/memory/schema/acp.ts +4 -0
  248. package/src/memory/schema/infrastructure.ts +13 -0
  249. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  250. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  251. package/src/memory/v2/consolidation-job.ts +2 -63
  252. package/src/memory/v2/types.ts +5 -0
  253. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  254. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  255. package/src/notifications/access-request-copy.ts +298 -0
  256. package/src/notifications/adapters/slack.ts +3 -3
  257. package/src/notifications/adapters/telegram.ts +9 -2
  258. package/src/notifications/copy-composer.ts +49 -267
  259. package/src/notifications/decision-engine.ts +16 -35
  260. package/src/notifications/home-feed-side-effect.ts +1 -6
  261. package/src/plugin-api/constants.ts +4 -0
  262. package/src/plugin-api/index.ts +40 -11
  263. package/src/plugin-api/types.ts +108 -0
  264. package/src/plugins/defaults/compaction/compact.ts +106 -0
  265. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  266. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  267. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  268. package/src/plugins/defaults/compaction/package.json +1 -2
  269. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  270. package/src/plugins/defaults/empty-response/package.json +0 -1
  271. package/src/plugins/defaults/history-repair/package.json +0 -1
  272. package/src/plugins/defaults/index.ts +156 -26
  273. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  274. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  275. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  276. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  277. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  278. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  282. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  283. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  284. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  285. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  286. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  287. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  288. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  289. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  290. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  291. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  293. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  294. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  295. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  296. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  297. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  298. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  299. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  300. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  301. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  302. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  303. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  304. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  306. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  307. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  308. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  309. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  310. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  311. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  312. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  313. package/src/plugins/defaults/title-generate/package.json +0 -1
  314. package/src/plugins/defaults/tool-error/package.json +0 -1
  315. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  316. package/src/plugins/external-api.ts +12 -2
  317. package/src/plugins/pipeline.ts +6 -293
  318. package/src/plugins/registry.ts +9 -37
  319. package/src/plugins/types.ts +76 -381
  320. package/src/plugins/user-loader.ts +30 -127
  321. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  322. package/src/prompts/system-prompt.ts +61 -10
  323. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  324. package/src/providers/anthropic/client.ts +9 -10
  325. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  326. package/src/providers/inference/logit-bias.ts +55 -0
  327. package/src/providers/model-catalog.ts +34 -0
  328. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  329. package/src/providers/openai/chat-completions-provider.ts +44 -1
  330. package/src/providers/retry.ts +22 -0
  331. package/src/providers/types.ts +6 -0
  332. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  333. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  334. package/src/runtime/agent-wake.ts +108 -209
  335. package/src/runtime/assistant-event-hub.ts +1 -1
  336. package/src/runtime/channel-approvals.ts +1 -1
  337. package/src/runtime/interactive-ui.ts +1 -1
  338. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  339. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  340. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  341. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  342. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  343. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  344. package/src/runtime/routes/acp-routes.test.ts +185 -32
  345. package/src/runtime/routes/acp-routes.ts +328 -30
  346. package/src/runtime/routes/app-management-routes.ts +3 -0
  347. package/src/runtime/routes/approval-routes.ts +1 -1
  348. package/src/runtime/routes/browser-routes.ts +1 -1
  349. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  350. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  351. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  352. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  353. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  354. package/src/runtime/routes/conversation-routes.ts +30 -2
  355. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  356. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  357. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  358. package/src/runtime/routes/host-cu-routes.ts +1 -1
  359. package/src/runtime/routes/identity-routes.ts +18 -3
  360. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  361. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  362. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  363. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  364. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  365. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  366. package/src/runtime/routes/playground/helpers.ts +1 -1
  367. package/src/runtime/routes/plugins-routes.ts +337 -35
  368. package/src/runtime/routes/stt-routes.ts +45 -12
  369. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  370. package/src/runtime/routes/work-items-routes.ts +2 -4
  371. package/src/runtime/routes/workspace-routes.ts +50 -15
  372. package/src/runtime/services/conversation-serializer.ts +1 -1
  373. package/src/runtime/verification-outbound-actions.ts +147 -2
  374. package/src/runtime/verification-templates.ts +29 -3
  375. package/src/services/published-app-updater.ts +30 -8
  376. package/src/signals/cancel.ts +2 -4
  377. package/src/subagent/manager.ts +21 -5
  378. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  379. package/src/telemetry/activation-funnel.ts +167 -0
  380. package/src/telemetry/types.ts +13 -0
  381. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  382. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  383. package/src/tools/acp/context.ts +20 -0
  384. package/src/tools/acp/list-agents.test.ts +9 -19
  385. package/src/tools/acp/list-agents.ts +3 -15
  386. package/src/tools/acp/spawn.test.ts +173 -202
  387. package/src/tools/acp/spawn.ts +37 -172
  388. package/src/tools/acp/steer.test.ts +105 -8
  389. package/src/tools/acp/steer.ts +48 -17
  390. package/src/tools/apps/executors.ts +166 -50
  391. package/src/tools/browser/browser-execution.ts +12 -2
  392. package/src/tools/filesystem/write.ts +34 -0
  393. package/src/tools/network/web-fetch.ts +65 -24
  394. package/src/tools/skills/load.ts +1 -1
  395. package/src/tools/subagent/spawn.ts +2 -4
  396. package/src/tools/ui-surface/definitions.ts +32 -5
  397. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  398. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  399. package/docs/plugins.md +0 -832
  400. package/examples/plugins/echo/register.ts +0 -143
  401. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  402. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  403. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  404. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  405. package/src/__tests__/pipeline-runner.test.ts +0 -554
  406. package/src/daemon/wake-target-adapter.ts +0 -253
  407. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  408. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  409. package/src/memory/v3/__tests__/core.test.ts +0 -39
  410. package/src/memory/v3/__tests__/health.test.ts +0 -219
  411. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  412. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  413. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  414. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  415. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  416. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  417. package/src/memory/v3/__tests__/router.test.ts +0 -332
  418. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  419. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  420. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  421. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  422. package/src/memory/v3/assign.ts +0 -268
  423. package/src/memory/v3/capabilities.ts +0 -124
  424. package/src/memory/v3/core.ts +0 -26
  425. package/src/memory/v3/health.ts +0 -0
  426. package/src/memory/v3/maintain-job.ts +0 -314
  427. package/src/memory/v3/needle.ts +0 -115
  428. package/src/memory/v3/orchestrate.ts +0 -126
  429. package/src/memory/v3/page-content.ts +0 -34
  430. package/src/memory/v3/provider-blocks.ts +0 -26
  431. package/src/memory/v3/reconcile.ts +0 -523
  432. package/src/memory/v3/router.ts +0 -190
  433. package/src/memory/v3/selection-log-store.ts +0 -84
  434. package/src/memory/v3/selector.ts +0 -226
  435. package/src/memory/v3/shadow-plugin.ts +0 -411
  436. package/src/memory/v3/snapshot.ts +0 -209
  437. package/src/memory/v3/tree.ts +0 -174
  438. package/src/memory/v3/types.ts +0 -59
  439. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  440. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  441. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  442. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  443. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  444. package/src/plugins/defaults/compaction/register.ts +0 -35
  445. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  446. package/src/plugins/defaults/empty-response/register.ts +0 -23
  447. package/src/plugins/defaults/history-repair/register.ts +0 -24
  448. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  449. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  450. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  451. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  452. package/src/plugins/defaults/title-generate/register.ts +0 -35
  453. package/src/plugins/defaults/tool-error/register.ts +0 -23
  454. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  455. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  456. package/src/proactive-artifact/decision.test.ts +0 -226
  457. package/src/proactive-artifact/decision.ts +0 -165
  458. package/src/proactive-artifact/index.ts +0 -7
  459. package/src/proactive-artifact/job.test.ts +0 -962
  460. package/src/proactive-artifact/job.ts +0 -372
  461. package/src/proactive-artifact/message-copy.ts +0 -58
  462. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  463. package/src/proactive-artifact/trigger-state.ts +0 -123
  464. package/src/util/map-limit.ts +0 -27
  465. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  466. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  467. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  468. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  469. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  470. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  471. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  472. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  473. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  474. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  475. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
@@ -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({