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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/bun.lock +2 -2
  2. package/docs/plugins.md +832 -0
  3. package/examples/plugins/echo/README.md +60 -61
  4. package/examples/plugins/echo/package.json +2 -1
  5. package/examples/plugins/echo/register.ts +143 -0
  6. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +6 -7
  7. package/openapi.yaml +5 -15
  8. package/package.json +2 -2
  9. package/src/__tests__/agent-loop-exit-reason.test.ts +56 -3
  10. package/src/__tests__/anthropic-provider.test.ts +1 -1
  11. package/src/__tests__/app-control-flow.test.ts +1 -1
  12. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  13. package/src/__tests__/approval-routes-http.test.ts +1 -4
  14. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  15. package/src/__tests__/channel-approvals.test.ts +1 -1
  16. package/src/__tests__/circuit-breaker-pipeline.test.ts +405 -0
  17. package/src/__tests__/compaction-pipeline.test.ts +210 -0
  18. package/src/__tests__/compaction-timeout-recovery.test.ts +251 -0
  19. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +3 -0
  20. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +3 -0
  21. package/src/__tests__/conversation-agent-loop-overflow.test.ts +7 -3
  22. package/src/__tests__/conversation-agent-loop.test.ts +39 -42
  23. package/src/__tests__/conversation-clean-command.test.ts +2 -5
  24. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -4
  25. package/src/__tests__/conversation-runtime-assembly.test.ts +71 -140
  26. package/src/__tests__/conversation-runtime-workspace.test.ts +27 -108
  27. package/src/__tests__/conversation-starter-routes.test.ts +6 -14
  28. package/src/__tests__/conversation-workspace-cache-state.test.ts +16 -17
  29. package/src/__tests__/conversation-workspace-injection.test.ts +1 -61
  30. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -7
  31. package/src/__tests__/db-acp-history.test.ts +0 -101
  32. package/src/__tests__/dynamic-page-surface.test.ts +0 -31
  33. package/src/__tests__/file-write-tool.test.ts +0 -63
  34. package/src/__tests__/gateway-only-guard.test.ts +2 -12
  35. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  36. package/src/__tests__/guardian-routing-invariants.test.ts +4 -2
  37. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  38. package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -1
  39. package/src/__tests__/heartbeat-service.test.ts +0 -1
  40. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  41. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  42. package/src/__tests__/injector-background-turn.test.ts +1 -1
  43. package/src/__tests__/injector-chain.test.ts +6 -34
  44. package/src/__tests__/injector-disk-pressure.test.ts +34 -77
  45. package/src/__tests__/injector-document-comments.test.ts +1 -1
  46. package/src/__tests__/list-messages-hidden-metadata.test.ts +0 -38
  47. package/src/__tests__/memory-v2-static-injector.test.ts +1 -1
  48. package/src/__tests__/{overflow-reduction-loop.test.ts → overflow-reduce-pipeline.test.ts} +284 -64
  49. package/src/__tests__/pipeline-runner.test.ts +554 -0
  50. package/src/__tests__/plugin-api-shim.test.ts +6 -3
  51. package/src/__tests__/plugin-bootstrap.test.ts +23 -12
  52. package/src/__tests__/plugin-registry.test.ts +49 -3
  53. package/src/__tests__/plugin-types.test.ts +70 -0
  54. package/src/__tests__/reaction-persistence.test.ts +1 -1
  55. package/src/__tests__/send-endpoint-busy.test.ts +1 -4
  56. package/src/__tests__/skill-feature-flags-integration.test.ts +0 -33
  57. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  58. package/src/__tests__/subagent-fork-notifications.test.ts +3 -1
  59. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  60. package/src/__tests__/subagent-manager-notify.test.ts +3 -1
  61. package/src/__tests__/subagent-notify-parent.test.ts +3 -1
  62. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  63. package/src/__tests__/user-plugin-loader.test.ts +286 -54
  64. package/src/acp/__tests__/client-handler.test.ts +0 -40
  65. package/src/acp/__tests__/prepare-agent-env.test.ts +0 -137
  66. package/src/acp/__tests__/session-manager-persistence.test.ts +28 -95
  67. package/src/acp/agent-process.ts +1 -61
  68. package/src/acp/client-handler.ts +0 -31
  69. package/src/acp/prepare-agent-env.ts +29 -83
  70. package/src/acp/resolve-agent.test.ts +7 -320
  71. package/src/acp/resolve-agent.ts +18 -182
  72. package/src/acp/session-manager.ts +73 -495
  73. package/src/acp/types.ts +0 -8
  74. package/src/agent/compaction-circuit.ts +102 -60
  75. package/src/agent/loop.ts +59 -32
  76. package/src/api/responses/conversation-message.ts +1 -7
  77. package/src/approvals/guardian-request-resolvers.ts +1 -1
  78. package/src/background-wake/next-wake.ts +0 -1
  79. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  80. package/src/config/acp-defaults.test.ts +0 -10
  81. package/src/config/acp-defaults.ts +0 -6
  82. package/src/config/bundled-skills/acp/SKILL.md +31 -83
  83. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  84. package/src/config/bundled-skills/app-builder/SKILL.md +381 -224
  85. package/src/config/bundled-skills/app-builder/TOOLS.json +0 -29
  86. package/src/config/bundled-skills/document-editor/SKILL.md +23 -28
  87. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  88. package/src/config/bundled-tool-registry.ts +0 -2
  89. package/src/config/feature-flag-registry.json +5 -14
  90. package/src/config/schemas/heartbeat.ts +0 -9
  91. package/src/context/strip-injections.ts +2 -8
  92. package/src/context/window-manager.ts +1 -2
  93. package/src/daemon/conversation-agent-loop-handlers.ts +11 -0
  94. package/src/daemon/conversation-agent-loop.ts +279 -62
  95. package/src/daemon/conversation-runtime-assembly.ts +69 -106
  96. package/src/daemon/conversation-store.ts +90 -9
  97. package/src/daemon/conversation-workspace.ts +0 -17
  98. package/src/daemon/conversation.ts +6 -0
  99. package/src/daemon/external-plugins-bootstrap.ts +11 -11
  100. package/src/daemon/handlers/conversations.ts +1 -3
  101. package/src/daemon/handlers/skills.ts +1 -4
  102. package/src/daemon/lifecycle.ts +0 -21
  103. package/src/daemon/server.ts +0 -2
  104. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -3
  105. package/src/heartbeat/heartbeat-run-store.ts +1 -23
  106. package/src/heartbeat/heartbeat-service.ts +0 -26
  107. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  108. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  109. package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -15
  110. package/src/ipc/skill-routes/memory.ts +2 -4
  111. package/src/memory/conversation-starter-checkpoints.ts +0 -1
  112. package/src/memory/db-init.ts +0 -2
  113. package/src/memory/job-handlers/conversation-starters.ts +2 -13
  114. package/src/memory/jobs-worker.ts +1 -1
  115. package/src/memory/migrations/index.ts +0 -1
  116. package/src/memory/schema/acp.ts +0 -4
  117. package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
  118. package/src/memory/v2/consolidation-job.ts +4 -13
  119. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/assign.test.ts +4 -4
  120. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/live-integration.test.ts +4 -4
  121. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/maintain-job.test.ts +5 -5
  122. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/orchestrate.test.ts +3 -3
  123. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/reconcile.test.ts +2 -2
  124. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/render-injection.test.ts +1 -1
  125. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/router.test.ts +3 -3
  126. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selection-log-store.test.ts +8 -8
  127. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/selector.test.ts +3 -3
  128. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/shadow-plugin.test.ts +12 -12
  129. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/assign.ts +5 -5
  130. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/capabilities.ts +2 -2
  131. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/maintain-job.ts +8 -8
  132. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/page-content.ts +2 -2
  133. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/provider-blocks.ts +1 -1
  134. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/reconcile.ts +3 -3
  135. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/render-injection.ts +1 -1
  136. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/router.ts +3 -3
  137. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selection-log-store.ts +4 -4
  138. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/selector.ts +4 -4
  139. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/shadow-plugin.ts +90 -28
  140. package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/tree.ts +1 -1
  141. package/src/plugin-api/index.ts +5 -0
  142. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +93 -0
  143. package/src/plugins/defaults/{memory-v3-shadow → circuit-breaker}/package.json +2 -2
  144. package/src/plugins/defaults/circuit-breaker/register.ts +39 -0
  145. package/src/plugins/defaults/compaction/middlewares/compaction.ts +25 -0
  146. package/src/plugins/defaults/compaction/package.json +1 -1
  147. package/src/plugins/defaults/compaction/register.ts +19 -8
  148. package/src/plugins/defaults/compaction/terminal.ts +73 -0
  149. package/src/plugins/defaults/index.ts +5 -3
  150. package/src/plugins/defaults/{memory-retrieval/injectors.ts → injectors/register.ts} +7 -45
  151. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +7 -11
  152. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  153. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +126 -0
  154. package/src/plugins/defaults/overflow-reduce/package.json +15 -0
  155. package/src/plugins/defaults/overflow-reduce/register.ts +42 -0
  156. package/src/plugins/external-api.ts +2 -2
  157. package/src/plugins/pipeline.ts +293 -6
  158. package/src/plugins/registry.ts +37 -9
  159. package/src/plugins/types.ts +336 -32
  160. package/src/plugins/user-loader.ts +127 -30
  161. package/src/proactive-artifact/aux-message-injector.ts +1 -1
  162. package/src/proactive-artifact/job.test.ts +1 -1
  163. package/src/prompts/__tests__/system-prompt.test.ts +0 -6
  164. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +2 -4
  165. package/src/runtime/__tests__/agent-wake.test.ts +5 -5
  166. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  167. package/src/runtime/agent-wake.ts +3 -0
  168. package/src/runtime/assistant-event-hub.ts +1 -1
  169. package/src/runtime/channel-approvals.ts +1 -1
  170. package/src/runtime/interactive-ui.ts +1 -1
  171. package/src/runtime/routes/__tests__/acp-routes.test.ts +55 -283
  172. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  173. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +4 -5
  174. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -4
  175. package/src/runtime/routes/acp-routes.test.ts +25 -89
  176. package/src/runtime/routes/acp-routes.ts +29 -81
  177. package/src/runtime/routes/approval-routes.ts +1 -1
  178. package/src/runtime/routes/browser-routes.ts +1 -1
  179. package/src/runtime/routes/browser-tabs-routes.ts +10 -6
  180. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  181. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  182. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  183. package/src/runtime/routes/conversation-routes.ts +2 -15
  184. package/src/runtime/routes/conversation-starter-routes.ts +7 -13
  185. package/src/runtime/routes/conversations-import-routes.ts +7 -24
  186. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  187. package/src/runtime/routes/host-cu-routes.ts +1 -1
  188. package/src/runtime/routes/identity-routes.ts +3 -18
  189. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  190. package/src/runtime/routes/memory-v3-routes.ts +6 -16
  191. package/src/runtime/routes/playground/helpers.ts +1 -1
  192. package/src/runtime/routes/surface-conversation-resolver.ts +3 -4
  193. package/src/runtime/routes/work-items-routes.ts +4 -2
  194. package/src/runtime/services/conversation-serializer.ts +1 -1
  195. package/src/signals/cancel.ts +4 -2
  196. package/src/subagent/manager.ts +5 -17
  197. package/src/tools/acp/list-agents.test.ts +1 -7
  198. package/src/tools/acp/spawn.test.ts +55 -158
  199. package/src/tools/acp/spawn.ts +72 -47
  200. package/src/tools/acp/steer.test.ts +8 -105
  201. package/src/tools/acp/steer.ts +17 -48
  202. package/src/tools/apps/executors.ts +8 -13
  203. package/src/tools/filesystem/write.ts +0 -34
  204. package/src/tools/subagent/spawn.ts +4 -2
  205. package/src/tools/ui-surface/definitions.ts +4 -25
  206. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +5 -4
  207. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +45 -69
  208. package/examples/plugins/echo/hooks/post-tool-use.ts +0 -18
  209. package/examples/plugins/echo/hooks/stop.ts +0 -16
  210. package/examples/plugins/echo/hooks/user-prompt-submit.ts +0 -18
  211. package/examples/plugins/echo/src/emit.ts +0 -19
  212. package/src/__tests__/compaction-circuit.test.ts +0 -258
  213. package/src/__tests__/compaction-direct.test.ts +0 -132
  214. package/src/__tests__/conversations-import-system-filter.test.ts +0 -101
  215. package/src/acp/__tests__/agent-process.test.ts +0 -161
  216. package/src/acp/__tests__/helpers/acp-history-db.ts +0 -82
  217. package/src/acp/__tests__/helpers/exec-file-stub.ts +0 -101
  218. package/src/acp/__tests__/session-manager-resume.test.ts +0 -736
  219. package/src/acp/auto-install.test.ts +0 -196
  220. package/src/acp/auto-install.ts +0 -177
  221. package/src/acp/feature-gate.test.ts +0 -48
  222. package/src/acp/feature-gate.ts +0 -34
  223. package/src/acp/resume-hint.ts +0 -25
  224. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +0 -48
  225. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +0 -57
  226. package/src/config/bundled-skills/app-builder/references/SLIDES.md +0 -38
  227. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -62
  228. package/src/daemon/conversation-registry.ts +0 -159
  229. package/src/daemon/overflow-reduction-loop.ts +0 -230
  230. package/src/memory/migrations/272-acp-session-history-cwd.ts +0 -36
  231. package/src/plugins/defaults/compaction/compact.ts +0 -59
  232. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +0 -14
  233. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +0 -19
  234. package/src/plugins/defaults/memory-v3-shadow/injector.ts +0 -75
  235. package/src/plugins/defaults/memory-v3-shadow/register.ts +0 -26
  236. package/src/tools/acp/context.ts +0 -20
  237. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/capabilities.test.ts +0 -0
  238. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/core.test.ts +0 -0
  239. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/eval-turns.json +0 -0
  240. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/fixtures/live-turns.json +0 -0
  241. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/health.test.ts +0 -0
  242. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/needle.test.ts +0 -0
  243. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/provider-blocks.test.ts +0 -0
  244. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/snapshot.test.ts +0 -0
  245. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/tree.test.ts +0 -0
  246. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/types.test.ts +0 -0
  247. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-eviction.test.ts +0 -0
  248. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/__tests__/working-set-skeleton.test.ts +0 -0
  249. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/core.ts +0 -0
  250. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/README.md +0 -0
  251. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/assignments.json +0 -0
  252. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/core.json +0 -0
  253. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-x.md +0 -0
  254. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-y.md +0 -0
  255. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/data/leaves/domain-b/topic-z.md +0 -0
  256. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/health.ts +0 -0
  257. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/llm-retry.ts +0 -0
  258. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/needle.ts +0 -0
  259. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/orchestrate.ts +0 -0
  260. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/snapshot.ts +0 -0
  261. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/types.ts +0 -0
  262. /package/src/{plugins/defaults/memory-v3-shadow → memory/v3}/working-set.ts +0 -0
@@ -1,23 +1,13 @@
1
1
  /**
2
- * Tests for ACP route handlers.
2
+ * Tests for `GET /v1/acp/sessions`.
3
3
  *
4
- * `GET /v1/acp/sessions`: the handler merges in-memory
5
- * `AcpSessionManager.getStatus()` output with persisted
6
- * `acp_session_history` rows, deduping by id (in-memory wins), filtering by
7
- * `?conversationId`, sorting newest-first, and truncating to `?limit`
8
- * (default 50, max 500).
9
- *
10
- * `POST /v1/acp/spawn`: when the adapter binary is missing, the handler
11
- * silently auto-installs allowlisted adapter packages before failing with
12
- * the install hint. `execFile` is stubbed via the shared
13
- * `installExecFileStub` helper so tests can script `npm i -g` outcomes.
4
+ * The handler merges in-memory `AcpSessionManager.getStatus()` output with
5
+ * persisted `acp_session_history` rows, deduping by id (in-memory wins),
6
+ * filtering by `?conversationId`, sorting newest-first, and truncating to
7
+ * `?limit` (default 50, max 500).
14
8
  */
15
9
 
16
- import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
17
-
18
- import { installAcpConfigStub } from "../../../acp/__tests__/helpers/acp-config-stub.js";
19
- import { installExecFileStub } from "../../../acp/__tests__/helpers/exec-file-stub.js";
20
- import { installWhichStub } from "../../../acp/__tests__/helpers/which-stub.js";
10
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
21
11
 
22
12
  mock.module("../../../util/logger.js", () => ({
23
13
  getLogger: () =>
@@ -26,12 +16,6 @@ mock.module("../../../util/logger.js", () => ({
26
16
  }),
27
17
  }));
28
18
 
29
- const {
30
- execScripts,
31
- execFileMock,
32
- reset: resetExecFileStub,
33
- } = installExecFileStub();
34
-
35
19
  // ---------------------------------------------------------------------------
36
20
  // Stub the ACP session manager so tests control the in-memory side without
37
21
  // spawning real child processes. The route handler imports
@@ -53,61 +37,64 @@ interface FakeSessionState {
53
37
 
54
38
  let fakeInMemorySessions: FakeSessionState[] = [];
55
39
 
56
- const spawnMock = mock(async () => ({
57
- acpSessionId: "acp-route-session",
58
- protocolSessionId: "proto-route-session",
59
- }));
60
-
61
- const defaultSteerOrResumeImpl = async (
62
- _id: string,
63
- _instruction: string,
64
- ): Promise<{ resumed: boolean }> => ({ resumed: false });
65
- let steerOrResumeImpl: (
66
- id: string,
67
- instruction: string,
68
- ) => Promise<{ resumed: boolean }> = defaultSteerOrResumeImpl;
69
- const steerOrResumeMock = mock(
70
- (id: string, instruction: string, _send: unknown) =>
71
- steerOrResumeImpl(id, instruction),
72
- );
73
-
74
40
  mock.module("../../../acp/index.js", () => ({
75
41
  getAcpSessionManager: () => ({
76
42
  getStatus: () => fakeInMemorySessions,
77
- spawn: spawnMock,
78
- steerOrResume: steerOrResumeMock,
79
43
  }),
80
44
  }));
81
45
 
82
- // Identity env-prep: the credential-broker plumbing it wraps is exercised in
83
- // its own suite; spawn tests here only care about the resolve/install flow.
84
- mock.module("../../../acp/prepare-agent-env.js", () => ({
85
- prepareAgentEnv: async (agentConfig: unknown) => agentConfig,
86
- }));
87
-
88
- const config = await installAcpConfigStub();
89
- const which = installWhichStub();
90
-
91
- import {
92
- clearHistory,
93
- insertHistoryRow,
94
- } from "../../../acp/__tests__/helpers/acp-history-db.js";
95
- import {
96
- AcpResumeError,
97
- AcpSessionNotFoundError,
98
- } from "../../../acp/session-manager.js";
46
+ import { getSqlite } from "../../../memory/db-connection.js";
99
47
  import { initializeDb } from "../../../memory/db-init.js";
100
- import { FailedDependencyError, NotFoundError } from "../errors.js";
101
-
102
- const { ROUTES } = await import("../acp-routes.js");
103
- const { _resetAdapterInstallCacheForTests } =
104
- await import("../../../acp/auto-install.js");
48
+ import { ROUTES } from "../acp-routes.js";
105
49
 
106
50
  initializeDb();
107
51
 
108
- afterAll(() => {
109
- which.restore();
110
- });
52
+ function clearHistory(): void {
53
+ getSqlite().run("DELETE FROM acp_session_history");
54
+ }
55
+
56
+ function insertHistoryRow(row: {
57
+ id: string;
58
+ agentId: string;
59
+ acpSessionId: string;
60
+ parentConversationId: string;
61
+ startedAt: number;
62
+ completedAt?: number | null;
63
+ status: string;
64
+ stopReason?: string | null;
65
+ error?: string | null;
66
+ eventLogJson?: string;
67
+ }): void {
68
+ getSqlite()
69
+ .query(
70
+ /*sql*/ `
71
+ INSERT INTO acp_session_history (
72
+ id,
73
+ agent_id,
74
+ acp_session_id,
75
+ parent_conversation_id,
76
+ started_at,
77
+ completed_at,
78
+ status,
79
+ stop_reason,
80
+ error,
81
+ event_log_json
82
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
83
+ `,
84
+ )
85
+ .run(
86
+ row.id,
87
+ row.agentId,
88
+ row.acpSessionId,
89
+ row.parentConversationId,
90
+ row.startedAt,
91
+ row.completedAt ?? null,
92
+ row.status,
93
+ row.stopReason ?? null,
94
+ row.error ?? null,
95
+ row.eventLogJson ?? "[]",
96
+ );
97
+ }
111
98
 
112
99
  function getSessionsHandler() {
113
100
  const route = ROUTES.find(
@@ -135,13 +122,6 @@ interface ResponseShape {
135
122
  beforeEach(() => {
136
123
  fakeInMemorySessions = [];
137
124
  clearHistory();
138
- resetExecFileStub();
139
- spawnMock.mockClear();
140
- steerOrResumeMock.mockClear();
141
- steerOrResumeImpl = defaultSteerOrResumeImpl;
142
- _resetAdapterInstallCacheForTests();
143
- config.setConfig({});
144
- which.setWhich((cmd) => `/usr/local/bin/${cmd}`);
145
125
  });
146
126
 
147
127
  describe("GET /v1/acp/sessions — merged in-memory + history", () => {
@@ -326,9 +306,7 @@ describe("GET /v1/acp/sessions — merged in-memory + history", () => {
326
306
  });
327
307
 
328
308
  const handler = getSessionsHandler();
329
- const body = (await handler({
330
- queryParams: { limit: "2" },
331
- })) as ResponseShape;
309
+ const body = (await handler({ queryParams: { limit: "2" } })) as ResponseShape;
332
310
  expect(body.sessions).toHaveLength(2);
333
311
  expect(body.sessions.map((s) => s.id)).toEqual(["live-newest", "hist-mid"]);
334
312
  });
@@ -414,209 +392,3 @@ describe("GET /v1/acp/sessions — merged in-memory + history", () => {
414
392
  expect(body.sessions).toHaveLength(3);
415
393
  });
416
394
  });
417
-
418
- // ---------------------------------------------------------------------------
419
- // POST /v1/acp/spawn: auto-install on missing adapter binary
420
- // ---------------------------------------------------------------------------
421
-
422
- function getSpawnHandler() {
423
- const route = ROUTES.find(
424
- (r) => r.endpoint === "acp/spawn" && r.method === "POST",
425
- );
426
- if (!route) throw new Error("acp/spawn POST route not found");
427
- return route.handler;
428
- }
429
-
430
- const SPAWN_BODY = {
431
- agent: "claude",
432
- task: "do something",
433
- conversationId: "conv-1",
434
- };
435
-
436
- describe("POST /v1/acp/spawn: auto-install on missing binary", () => {
437
- test("binary missing + bun present: spawn proceeds via bunx without npm", async () => {
438
- which.setWhich({ bun: "/usr/local/bin/bun" });
439
-
440
- const handler = getSpawnHandler();
441
- const body = (await handler({ body: SPAWN_BODY })) as Record<
442
- string,
443
- unknown
444
- >;
445
-
446
- expect(body).toEqual({
447
- acpSessionId: "acp-route-session",
448
- protocolSessionId: "proto-route-session",
449
- agent: "claude",
450
- });
451
- expect(execFileMock).not.toHaveBeenCalled();
452
- expect(spawnMock).toHaveBeenCalledTimes(1);
453
- const agentConfigArg = (spawnMock.mock.calls[0] as unknown[])[1] as {
454
- command: string;
455
- args: string[];
456
- adapterCommand?: string;
457
- };
458
- expect(agentConfigArg.command).toBe("bun");
459
- expect(agentConfigArg.args).toEqual([
460
- "x",
461
- "--bun",
462
- "@agentclientprotocol/claude-agent-acp",
463
- ]);
464
- expect(agentConfigArg.adapterCommand).toBe("claude-agent-acp");
465
- });
466
-
467
- test("known command: installs the mapped package and spawn proceeds", async () => {
468
- // Binary appears on PATH only after `npm i -g` runs, simulating a
469
- // successful global install.
470
- let binaryOnPath = false;
471
- which.setWhich((cmd) => (binaryOnPath ? `/usr/local/bin/${cmd}` : null));
472
- execScripts.set("npm i", {
473
- stdout: "",
474
- onCall: () => {
475
- binaryOnPath = true;
476
- },
477
- });
478
-
479
- const handler = getSpawnHandler();
480
- const body = (await handler({ body: SPAWN_BODY })) as Record<
481
- string,
482
- unknown
483
- >;
484
-
485
- expect(body).toEqual({
486
- acpSessionId: "acp-route-session",
487
- protocolSessionId: "proto-route-session",
488
- agent: "claude",
489
- });
490
- expect(spawnMock).toHaveBeenCalledTimes(1);
491
- expect(execFileMock).toHaveBeenCalledTimes(1);
492
- expect(execFileMock.mock.calls[0][1]).toEqual([
493
- "i",
494
- "-g",
495
- "@agentclientprotocol/claude-agent-acp",
496
- ]);
497
- });
498
-
499
- test("npm failure: FailedDependencyError carries hint and failure reason", async () => {
500
- which.setWhich({});
501
- execScripts.set("npm i", {
502
- error: new Error("EACCES: permission denied"),
503
- });
504
-
505
- const handler = getSpawnHandler();
506
- const promise = handler({ body: SPAWN_BODY });
507
- await expect(promise).rejects.toBeInstanceOf(FailedDependencyError);
508
- await expect(promise).rejects.toThrow(
509
- /claude-agent-acp is not on PATH.*npm i -g @agentclientprotocol\/claude-agent-acp.*auto-install failed.*EACCES/,
510
- );
511
- expect(spawnMock).not.toHaveBeenCalled();
512
- });
513
-
514
- test("unknown command: plain hint maps to FailedDependencyError", async () => {
515
- // The allowlist itself (no npm invocation for unmapped commands) is
516
- // pinned in auto-install.test.ts and spawn.test.ts; this asserts only
517
- // the route's transport mapping of the plain-hint failure.
518
- config.setConfig({
519
- agents: { custom: { command: "custom-bin", args: [] } },
520
- });
521
- which.setWhich({});
522
-
523
- const handler = getSpawnHandler();
524
- const promise = handler({ body: { ...SPAWN_BODY, agent: "custom" } });
525
- await expect(promise).rejects.toBeInstanceOf(FailedDependencyError);
526
- await expect(promise).rejects.toThrow(
527
- "custom-bin is not on PATH. Install 'custom-bin' and ensure it is on PATH.",
528
- );
529
- });
530
- });
531
-
532
- // ---------------------------------------------------------------------------
533
- // POST /v1/acp/:id/steer: transparent resume of sessions not in memory
534
- // ---------------------------------------------------------------------------
535
-
536
- function getSteerHandler() {
537
- const route = ROUTES.find(
538
- (r) => r.endpoint === "acp/:id/steer" && r.method === "POST",
539
- );
540
- if (!route) throw new Error("acp/:id/steer POST route not found");
541
- return route.handler;
542
- }
543
-
544
- describe("POST /v1/acp/:id/steer: resume fallback", () => {
545
- test("in-memory session steers without a resume", async () => {
546
- const handler = getSteerHandler();
547
- const body = await handler({
548
- pathParams: { id: "live-1" },
549
- body: { instruction: "redirect" },
550
- });
551
-
552
- expect(body).toEqual({ acpSessionId: "live-1", steered: true });
553
- expect(steerOrResumeMock).toHaveBeenCalledTimes(1);
554
- expect(steerOrResumeMock.mock.calls[0][0]).toBe("live-1");
555
- expect(steerOrResumeMock.mock.calls[0][1]).toBe("redirect");
556
- expect(typeof steerOrResumeMock.mock.calls[0][2]).toBe("function");
557
- });
558
-
559
- test("resumed session reports the resumed flag", async () => {
560
- steerOrResumeImpl = async () => ({ resumed: true });
561
-
562
- const handler = getSteerHandler();
563
- const body = await handler({
564
- pathParams: { id: "gone-1" },
565
- body: { instruction: "keep going" },
566
- });
567
-
568
- expect(body).toEqual({
569
- acpSessionId: "gone-1",
570
- steered: true,
571
- resumed: true,
572
- });
573
- });
574
-
575
- test("typed not-found (no session, no history row) maps to NotFoundError", async () => {
576
- steerOrResumeImpl = async (id) => {
577
- throw new AcpSessionNotFoundError(id);
578
- };
579
-
580
- const handler = getSteerHandler();
581
- const promise = handler({
582
- pathParams: { id: "missing-1" },
583
- body: { instruction: "go" },
584
- });
585
- await expect(promise).rejects.toBeInstanceOf(NotFoundError);
586
- });
587
-
588
- test("resume failure surfaces as FailedDependencyError with the actionable hint", async () => {
589
- steerOrResumeImpl = async (id) => {
590
- throw new AcpResumeError(
591
- new Error(
592
- `ACP session "${id}" was recorded before resume support ` +
593
- `(no working directory persisted) and cannot be resumed. ` +
594
- `Spawn a new session instead.`,
595
- ),
596
- );
597
- };
598
-
599
- const handler = getSteerHandler();
600
- const promise = handler({
601
- pathParams: { id: "legacy-1" },
602
- body: { instruction: "go" },
603
- });
604
- await expect(promise).rejects.toBeInstanceOf(FailedDependencyError);
605
- await expect(promise).rejects.toThrow(/recorded before resume support/);
606
- });
607
-
608
- test("plain steer errors map to NotFoundError", async () => {
609
- steerOrResumeImpl = async (id) => {
610
- throw new Error(
611
- `ACP session "${id}" is not running (status: initializing)`,
612
- );
613
- };
614
-
615
- const handler = getSteerHandler();
616
- const promise = handler({
617
- pathParams: { id: "init-1" },
618
- body: { instruction: "go" },
619
- });
620
- await expect(promise).rejects.toBeInstanceOf(NotFoundError);
621
- });
622
- });
@@ -23,7 +23,7 @@ mock.module("../../assistant-event-hub.js", () => ({
23
23
  broadcastMessage: () => {},
24
24
  }));
25
25
 
26
- import { findConversation } from "../../../daemon/conversation-registry.js";
26
+ import { findConversation } from "../../../daemon/conversation-store.js";
27
27
  import { createConversation } from "../../../memory/conversation-crud.js";
28
28
  import { getDb } from "../../../memory/db-connection.js";
29
29
  import { initializeDb } from "../../../memory/db-init.js";
@@ -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-registry.js", () => ({
45
+ mock.module("../../../daemon/conversation-store.js", () => ({
46
46
  findConversation: (id: string) => {
47
47
  findConvCalls.push(id);
48
48
  return memoryById ?? undefined;
@@ -51,9 +51,6 @@ mock.module("../../../daemon/conversation-registry.js", () => ({
51
51
  findBySurfaceCalls.push(surfaceId);
52
52
  return memoryBySurface ?? undefined;
53
53
  },
54
- }));
55
-
56
- mock.module("../../../daemon/conversation-store.js", () => ({
57
54
  getOrCreateConversation: async (id: string) => {
58
55
  getOrCreateCalls.push(id);
59
56
  if (!rehydrated) {
@@ -186,7 +183,9 @@ describe("triggerSurfaceAction handler", () => {
186
183
  // SQL must filter the messages table by ui_surface payload pattern.
187
184
  expect(rawGetCalls[0]!.sql).toContain("FROM messages");
188
185
  expect(rawGetCalls[0]!.sql).toContain("LIKE");
189
- expect(rawGetCalls[0]!.params).toEqual([`%"surfaceId":"surf-evicted"%`]);
186
+ expect(rawGetCalls[0]!.params).toEqual([
187
+ `%"surfaceId":"surf-evicted"%`,
188
+ ]);
190
189
  expect(getOrCreateCalls).toEqual(["conv-from-db"]);
191
190
  expect(rehydrated.surfaceActionCalls).toEqual([
192
191
  { 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-registry.js", () => ({
54
+ mock.module("../../../daemon/conversation-store.js", () => ({
55
55
  findConversation: (id: string) => {
56
56
  findConvCalls.push(id);
57
57
  return memoryById ?? undefined;
@@ -60,9 +60,6 @@ mock.module("../../../daemon/conversation-registry.js", () => ({
60
60
  findBySurfaceCalls.push(surfaceId);
61
61
  return undefined;
62
62
  },
63
- }));
64
-
65
- mock.module("../../../daemon/conversation-store.js", () => ({
66
63
  getOrCreateConversation: async (id: string) => {
67
64
  getOrCreateCalls.push(id);
68
65
  if (!rehydrated) {
@@ -2,28 +2,25 @@
2
2
  * Tests for the ACP route handlers.
3
3
  *
4
4
  * Suites:
5
- * - POST /v1/acp/spawn: resolver failure paths (acp_disabled,
6
- * unknown_agent) and the body-shape guard. The binary_not_found /
7
- * auto-install surface is covered in `__tests__/acp-routes.test.ts`.
5
+ * - POST /v1/acp/spawn the three failure paths produced by
6
+ * `resolveAcpAgent` (acp_disabled, unknown_agent, binary_not_found).
8
7
  * - POST /v1/acp/spawn (env injection) — CLAUDE_CODE_OAUTH_TOKEN is read
9
8
  * from the credential broker (policy-gated + audited) and merged into
10
9
  * `agentConfig.env` ONLY for the `claude` agent.
11
10
  * - DELETE /v1/acp/sessions?status=completed — the bulk-clear route that
12
11
  * wipes terminal-state rows (completed/failed/cancelled) from
13
- * `acp_session_history` while leaving running/initializing rows and
14
- * rows with an in-flight resume intact.
12
+ * `acp_session_history` while leaving running/initializing rows intact.
15
13
  * - DELETE /v1/acp/sessions/:id — single-row delete: completed → 200,
16
- * running or mid-resume → 409, unknown id → idempotent
17
- * { deleted: false }.
14
+ * running → 409, unknown id → idempotent { deleted: false }.
18
15
  *
19
16
  * The spawn tests mirror the resolver's test setup using the shared
20
17
  * `installAcpConfigStub` and `installWhichStub` helpers so the host
21
18
  * environment doesn't influence the resolver's PATH preflight.
22
19
  *
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.
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.
27
24
  */
28
25
 
29
26
  import {
@@ -51,10 +48,8 @@ afterAll(() => {
51
48
  // in-memory-status check without spawning real ACP processes, and so the
52
49
  // env-injection spawn tests can capture the `agentConfig` arg without
53
50
  // launching a real subprocess. Stored in mutable state so individual tests
54
- // can plant arbitrary states / inspect capture. `pendingResumeIds` models
55
- // ids reserved by an in-flight resumeFromHistory (no SessionEntry yet).
51
+ // can plant arbitrary states / inspect capture.
56
52
  const inMemoryStates = new Map<string, AcpSessionState>();
57
- const pendingResumeIds = new Set<string>();
58
53
 
59
54
  interface CapturedSpawn {
60
55
  agent: string;
@@ -75,9 +70,6 @@ mock.module("../../acp/index.js", () => ({
75
70
  if (!state) throw new Error(`ACP session "${id}" not found`);
76
71
  return state;
77
72
  },
78
- getActiveAndPendingIds: () => [
79
- ...new Set([...inMemoryStates.keys(), ...pendingResumeIds]),
80
- ],
81
73
  spawn: async (
82
74
  agent: string,
83
75
  agentConfig: { env?: Record<string, string> },
@@ -234,6 +226,22 @@ describe("POST /v1/acp/spawn", () => {
234
226
  ).rejects.toThrow('Unknown agent "nonexistent"');
235
227
  });
236
228
 
229
+ test("throws FailedDependencyError when the agent binary is missing", async () => {
230
+ config.setConfig({ agents: {} });
231
+ which.setWhich({});
232
+
233
+ const handler = getSpawnHandler();
234
+ await expect(
235
+ handler({
236
+ body: {
237
+ agent: "codex",
238
+ task: "do a thing",
239
+ conversationId: "conv-1",
240
+ },
241
+ }),
242
+ ).rejects.toThrow("codex-acp is not on PATH");
243
+ });
244
+
237
245
  test("body-shape guard short-circuits before the resolver runs", async () => {
238
246
  config.setConfig({ enabled: false });
239
247
 
@@ -449,8 +457,6 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
449
457
  });
450
458
 
451
459
  beforeEach(() => {
452
- inMemoryStates.clear();
453
- pendingResumeIds.clear();
454
460
  getSqlite().run("DELETE FROM acp_session_history");
455
461
  });
456
462
 
@@ -476,57 +482,6 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
476
482
  ]);
477
483
  });
478
484
 
479
- test("excludes terminal rows whose session is active in memory (resumed sessions)", async () => {
480
- // A resumed session reuses its original id: the in-memory state is
481
- // `running` while its history row still carries the old terminal
482
- // status until the next terminal upsert. The bulk delete must not
483
- // remove that row out from under the live session.
484
- seedHistoryRow("row-resumed", "completed", 1000);
485
- seedHistoryRow("row-completed", "completed", 2000);
486
- inMemoryStates.set("row-resumed", {
487
- id: "row-resumed",
488
- agentId: "claude",
489
- acpSessionId: "proto-resumed",
490
- parentConversationId: "conv-test",
491
- status: "running",
492
- startedAt: 1000,
493
- });
494
-
495
- const handler = getBulkDeleteHandler();
496
- const result = (await handler({
497
- queryParams: { status: "completed" },
498
- })) as {
499
- deleted: number;
500
- };
501
- expect(result.deleted).toBe(1);
502
-
503
- const remaining = listRows();
504
- expect(remaining).toEqual([{ id: "row-resumed", status: "completed" }]);
505
- });
506
-
507
- test("excludes terminal rows whose session has a resume in flight", async () => {
508
- // A resume that is still awaiting env preparation has no in-memory
509
- // SessionEntry yet, but its history row (still terminal) must survive:
510
- // deleting it mid-resume would let the later terminal upsert resurrect
511
- // it as an orphan row.
512
- seedHistoryRow("row-pending-resume", "completed", 1000);
513
- seedHistoryRow("row-completed", "completed", 2000);
514
- pendingResumeIds.add("row-pending-resume");
515
-
516
- const handler = getBulkDeleteHandler();
517
- const result = (await handler({
518
- queryParams: { status: "completed" },
519
- })) as {
520
- deleted: number;
521
- };
522
- expect(result.deleted).toBe(1);
523
-
524
- const remaining = listRows();
525
- expect(remaining).toEqual([
526
- { id: "row-pending-resume", status: "completed" },
527
- ]);
528
- });
529
-
530
485
  test("returns deleted=0 when no terminal rows are present", async () => {
531
486
  seedHistoryRow("row-running", "running", 1000);
532
487
 
@@ -604,7 +559,6 @@ describe("DELETE /v1/acp/sessions/:id", () => {
604
559
 
605
560
  beforeEach(() => {
606
561
  inMemoryStates.clear();
607
- pendingResumeIds.clear();
608
562
  getDb().delete(acpSessionHistory).run();
609
563
  });
610
564
 
@@ -656,24 +610,6 @@ describe("DELETE /v1/acp/sessions/:id", () => {
656
610
  },
657
611
  );
658
612
 
659
- test("returns 409 when the session has a resume in flight (not yet in memory)", async () => {
660
- pendingResumeIds.add("sess-resuming");
661
- insertHistoryRow({ id: "sess-resuming", status: "completed" });
662
-
663
- const handler = getDeleteSessionHandler();
664
- expect(() => handler({ pathParams: { id: "sess-resuming" } })).toThrow(
665
- "resume in flight",
666
- );
667
-
668
- // Row untouched.
669
- const remaining = getDb()
670
- .select()
671
- .from(acpSessionHistory)
672
- .where(eq(acpSessionHistory.id, "sess-resuming"))
673
- .all();
674
- expect(remaining).toHaveLength(1);
675
- });
676
-
677
613
  test("idempotent for unknown id — returns { deleted: false }", async () => {
678
614
  const handler = getDeleteSessionHandler();
679
615
  const result = (await handler({