@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
@@ -23,13 +23,35 @@ import type { ServerMessage } from "../../daemon/message-protocol.js";
23
23
  import type { AcpSessionUpdate } from "../../daemon/message-types/acp.js";
24
24
  import { getSqlite } from "../../memory/db-connection.js";
25
25
  import { initializeDb } from "../../memory/db-init.js";
26
- import {
27
- clearHistory,
28
- insertHistoryRow,
29
- readHistoryRow,
30
- } from "./helpers/acp-history-db.js";
31
26
  initializeDb();
32
27
 
28
+ function clearHistory() {
29
+ getSqlite().run("DELETE FROM acp_session_history");
30
+ }
31
+
32
+ interface HistoryRow {
33
+ id: string;
34
+ agent_id: string;
35
+ acp_session_id: string;
36
+ parent_conversation_id: string;
37
+ started_at: number;
38
+ completed_at: number | null;
39
+ status: string;
40
+ stop_reason: string | null;
41
+ error: string | null;
42
+ event_log_json: string;
43
+ }
44
+
45
+ function readHistoryRow(id: string): HistoryRow | null {
46
+ return getSqlite()
47
+ .query(
48
+ `SELECT id, agent_id, acp_session_id, parent_conversation_id,
49
+ started_at, completed_at, status, stop_reason, error, event_log_json
50
+ FROM acp_session_history WHERE id = ?`,
51
+ )
52
+ .get(id) as HistoryRow | null;
53
+ }
54
+
33
55
  /**
34
56
  * Builds a manager with a fake session pre-injected and returns the handles
35
57
  * needed to drive terminal transitions in tests.
@@ -39,7 +61,6 @@ function buildSessionWithFakeProcess(opts: {
39
61
  agentId: string;
40
62
  protocolSessionId: string;
41
63
  parentConversationId: string;
42
- cwd?: string;
43
64
  }): {
44
65
  manager: AcpSessionManager;
45
66
  resolvePrompt: (v: { stopReason: string }) => void;
@@ -105,7 +126,7 @@ function buildSessionWithFakeProcess(opts: {
105
126
  sendToVellum: wrappedSend,
106
127
  currentPrompt: null as Promise<unknown> | null,
107
128
  parentConversationId: opts.parentConversationId,
108
- cwd: opts.cwd ?? "/tmp",
129
+ cwd: "/tmp",
109
130
  command: "claude-agent-acp",
110
131
  };
111
132
  sessions.set(opts.id, entry);
@@ -324,94 +345,6 @@ describe("AcpSessionManager — terminal persistence", () => {
324
345
  expect(row!.event_log_json.length).toBeLessThan(256 * 1024 + 1024);
325
346
  });
326
347
 
327
- test("persists the spawn cwd on terminal transition", async () => {
328
- const id = "session-cwd-1";
329
- const handles = buildSessionWithFakeProcess({
330
- id,
331
- agentId: "agent-cwd",
332
- protocolSessionId: "proto-cwd",
333
- parentConversationId: "conv-cwd",
334
- cwd: "/Users/me/projects/widget",
335
- });
336
-
337
- handles.resolvePrompt({ stopReason: "end_turn" });
338
- await Promise.resolve();
339
- await Promise.resolve();
340
-
341
- const row = readHistoryRow(id);
342
- expect(row).not.toBeNull();
343
- expect(row!.cwd).toBe("/Users/me/projects/widget");
344
- });
345
-
346
- test("legacy rows without a cwd read back as null", () => {
347
- insertHistoryRow({
348
- id: "legacy-row-1",
349
- agentId: "agent-legacy",
350
- acpSessionId: "proto-legacy",
351
- parentConversationId: "conv-legacy",
352
- startedAt: 1000,
353
- completedAt: null,
354
- status: "completed",
355
- stopReason: null,
356
- cwd: null,
357
- });
358
-
359
- const row = readHistoryRow("legacy-row-1");
360
- expect(row).not.toBeNull();
361
- expect(row!.cwd).toBeNull();
362
- });
363
-
364
- test("upserts over a pre-existing row with the same id (resumed runs)", async () => {
365
- const id = "session-upsert-1";
366
- // Simulate the row left behind by the original run that a resumed run
367
- // reuses the id of.
368
- insertHistoryRow({
369
- id,
370
- agentId: "agent-up",
371
- acpSessionId: "proto-up",
372
- parentConversationId: "conv-up",
373
- startedAt: 1000,
374
- completedAt: 2000,
375
- status: "cancelled",
376
- stopReason: "daemon_restarted",
377
- eventLogJson: '[{"old":true}]',
378
- cwd: "/old/cwd",
379
- });
380
-
381
- const handles = buildSessionWithFakeProcess({
382
- id,
383
- agentId: "agent-up",
384
- protocolSessionId: "proto-up",
385
- parentConversationId: "conv-up",
386
- cwd: "/new/cwd",
387
- });
388
-
389
- handles.emitUpdate({
390
- type: "acp_session_update",
391
- acpSessionId: id,
392
- updateType: "agent_message_chunk",
393
- content: "from the resumed run",
394
- });
395
-
396
- handles.resolvePrompt({ stopReason: "end_turn" });
397
- await Promise.resolve();
398
- await Promise.resolve();
399
-
400
- const count = getSqlite()
401
- .query(`SELECT COUNT(*) AS n FROM acp_session_history WHERE id = ?`)
402
- .get(id) as { n: number };
403
- expect(count.n).toBe(1);
404
-
405
- const row = readHistoryRow(id);
406
- expect(row).not.toBeNull();
407
- expect(row!.status).toBe("completed");
408
- expect(row!.stop_reason).toBe("end_turn");
409
- expect(row!.cwd).toBe("/new/cwd");
410
- const log = JSON.parse(row!.event_log_json) as AcpSessionUpdate[];
411
- expect(log).toHaveLength(1);
412
- expect(log[0]).toMatchObject({ content: "from the resumed run" });
413
- });
414
-
415
348
  test("persists empty event log when no updates were emitted", async () => {
416
349
  const id = "session-empty-1";
417
350
  const handles = buildSessionWithFakeProcess({
@@ -34,7 +34,6 @@ export type AcpClientFactory = (agent: Agent) => Client;
34
34
  export class AcpAgentProcess {
35
35
  private proc: ChildProcess | null = null;
36
36
  private connection: acp.ClientSideConnection | null = null;
37
- private initializeResponse: InitializeResponse | null = null;
38
37
 
39
38
  constructor(
40
39
  public readonly agentId: string,
@@ -100,7 +99,7 @@ export class AcpAgentProcess {
100
99
 
101
100
  log.info({ agentId: this.agentId }, "Initializing ACP connection");
102
101
 
103
- const response = await this.connection.initialize({
102
+ return this.connection.initialize({
104
103
  protocolVersion: acp.PROTOCOL_VERSION,
105
104
  clientInfo: { name: "vellum", version: "1.0.0" },
106
105
  clientCapabilities: {
@@ -108,28 +107,6 @@ export class AcpAgentProcess {
108
107
  terminal: true,
109
108
  },
110
109
  });
111
-
112
- this.initializeResponse = response;
113
- return response;
114
- }
115
-
116
- /**
117
- * Whether the agent advertised support for `session/load` at initialize.
118
- * Returns false before initialize() resolves.
119
- */
120
- get supportsLoadSession(): boolean {
121
- return this.initializeResponse?.agentCapabilities?.loadSession === true;
122
- }
123
-
124
- /**
125
- * Whether the agent advertised support for `session/resume` at initialize.
126
- * Returns false before initialize() resolves.
127
- */
128
- get supportsSessionResume(): boolean {
129
- return (
130
- this.initializeResponse?.agentCapabilities?.sessionCapabilities?.resume !=
131
- null
132
- );
133
110
  }
134
111
 
135
112
  /**
@@ -151,41 +128,6 @@ export class AcpAgentProcess {
151
128
  return result.sessionId;
152
129
  }
153
130
 
154
- /**
155
- * Loads a previously persisted ACP session via `session/load`.
156
- *
157
- * Per the ACP spec, the agent replays the session's conversation history
158
- * as `session/update` notifications before the load response resolves;
159
- * callers should suppress forwarding of those replayed updates (see
160
- * VellumAcpClientHandler.beginReplaySuppression).
161
- */
162
- async loadSession(sessionId: string, cwd: string): Promise<void> {
163
- if (!this.connection) {
164
- throw new Error(`ACP agent "${this.agentId}" is not spawned`);
165
- }
166
-
167
- log.info({ agentId: this.agentId, sessionId, cwd }, "Loading ACP session");
168
-
169
- await this.connection.loadSession({ sessionId, cwd, mcpServers: [] });
170
- }
171
-
172
- /**
173
- * Resumes a previously persisted ACP session via `session/resume`.
174
- *
175
- * Unlike `session/load`, resume performs no history replay, so it is
176
- * preferred when the agent advertises the capability
177
- * (see supportsSessionResume).
178
- */
179
- async resumeSession(sessionId: string, cwd: string): Promise<void> {
180
- if (!this.connection) {
181
- throw new Error(`ACP agent "${this.agentId}" is not spawned`);
182
- }
183
-
184
- log.info({ agentId: this.agentId, sessionId, cwd }, "Resuming ACP session");
185
-
186
- await this.connection.resumeSession({ sessionId, cwd, mcpServers: [] });
187
- }
188
-
189
131
  /**
190
132
  * Sends a prompt to an existing ACP session.
191
133
  * Returns the prompt response (includes stopReason).
@@ -233,7 +175,6 @@ export class AcpAgentProcess {
233
175
  this.proc = null;
234
176
  }
235
177
  this.connection = null;
236
- this.initializeResponse = null;
237
178
  }
238
179
 
239
180
  /**
@@ -264,6 +205,5 @@ export class AcpAgentProcess {
264
205
 
265
206
  this.proc = null;
266
207
  this.connection = null;
267
- this.initializeResponse = null;
268
208
  }
269
209
  }
@@ -51,7 +51,6 @@ interface TerminalState {
51
51
  export class VellumAcpClientHandler implements Client {
52
52
  private terminals = new Map<string, TerminalState>();
53
53
  private accumulatedText = "";
54
- private suppressForwarding = false;
55
54
  /** Tracks pending ACP permission requestIds for cleanup on session close. */
56
55
  readonly pendingRequestIds = new Set<string>();
57
56
 
@@ -66,38 +65,8 @@ export class VellumAcpClientHandler implements Client {
66
65
  private readonly parentConversationId: string,
67
66
  ) {}
68
67
 
69
- /**
70
- * Begins suppressing session updates from being forwarded to Vellum.
71
- *
72
- * Per the ACP spec, `session/load` replays the entire conversation history
73
- * as `session/update` notifications before the load response resolves. The
74
- * parent conversation already received those events during the original
75
- * run, so re-forwarding them would duplicate them into the conversation and
76
- * the ring buffer. Callers wrap `loadSession` in
77
- * beginReplaySuppression()/endReplaySuppression() to drop the replay.
78
- * `session/resume` performs no replay, which is why it is preferred when
79
- * the agent supports it.
80
- */
81
- beginReplaySuppression(): void {
82
- this.suppressForwarding = true;
83
- }
84
-
85
- /** Ends replay suppression; subsequent updates flow normally. */
86
- endReplaySuppression(): void {
87
- this.suppressForwarding = false;
88
- }
89
-
90
68
  async sessionUpdate(params: SessionNotification): Promise<void> {
91
69
  const update = params.update;
92
-
93
- if (this.suppressForwarding) {
94
- log.debug(
95
- { acpSessionId: this.acpSessionId, updateType: update.sessionUpdate },
96
- "Dropping replayed session update during suppression",
97
- );
98
- return;
99
- }
100
-
101
70
  log.debug(
102
71
  { acpSessionId: this.acpSessionId, updateType: update.sessionUpdate },
103
72
  "ACP session update received",
@@ -20,91 +20,56 @@
20
20
  * owns the plaintext read boundary.
21
21
  */
22
22
 
23
+ import { basename } from "node:path";
24
+
23
25
  import { FailedDependencyError } from "../runtime/routes/errors.js";
24
26
  import { credentialBroker } from "../tools/credentials/broker.js";
25
27
  import {
26
28
  getCredentialMetadata,
27
29
  upsertCredentialMetadata,
28
30
  } from "../tools/credentials/metadata-store.js";
29
- import { getLogger } from "../util/logger.js";
30
- import { adapterCommandOf } from "./resolve-agent.js";
31
31
  import type { AcpAgentConfig } from "./types.js";
32
32
 
33
- const log = getLogger("acp:prepare-agent-env");
34
-
35
33
  const ACP_SPAWN_TOOL = "acp_spawn";
36
- const ACP_SERVICE = "acp";
37
34
 
38
35
  /**
39
- * Ensure an `acp/<field>` credential has metadata that allows the
40
- * `acp_spawn` tool to read it, but only for legacy/unmanaged cases:
36
+ * Ensure the `acp/claude_oauth_token` credential has metadata that allows
37
+ * the `acp_spawn` tool to read it, but only for legacy/unmanaged cases:
41
38
  *
42
- * - No metadata at all: create with `allowedTools: ["acp_spawn"]`.
43
- * - Metadata exists with an empty `allowedTools`: default provisioning
39
+ * - No metadata at all create with `allowedTools: ["acp_spawn"]`.
40
+ * - Metadata exists with an empty `allowedTools` default provisioning
44
41
  * path (user ran `credentials set` without `--allowed-tools`), add it.
45
- * - Metadata exists with a non-empty `allowedTools`: explicit policy set
46
- * by the user/admin. Respect it even if `acp_spawn` is absent; the
47
- * broker will deny the read and the caller decides whether that's fatal.
42
+ * - Metadata exists with a non-empty `allowedTools` explicit policy set
43
+ * by the user/admin. Respect it even if `acp_spawn` is absent the
44
+ * broker will deny the read and the preflight will throw.
48
45
  */
49
- function ensureAcpCredentialPolicy(
50
- field: string,
51
- usageDescription: string,
52
- ): void {
53
- const meta = getCredentialMetadata(ACP_SERVICE, field);
46
+ function ensureAcpTokenPolicy(): void {
47
+ const meta = getCredentialMetadata("acp", "claude_oauth_token");
54
48
  if (!meta) {
55
- upsertCredentialMetadata(ACP_SERVICE, field, {
49
+ upsertCredentialMetadata("acp", "claude_oauth_token", {
56
50
  allowedTools: [ACP_SPAWN_TOOL],
57
- usageDescription,
51
+ usageDescription:
52
+ "Claude OAuth token for ACP agent authentication",
58
53
  });
59
54
  return;
60
55
  }
61
56
  const tools = meta.allowedTools ?? [];
62
57
  if (tools.length === 0) {
63
- upsertCredentialMetadata(ACP_SERVICE, field, {
58
+ upsertCredentialMetadata("acp", "claude_oauth_token", {
64
59
  allowedTools: [ACP_SPAWN_TOOL],
65
60
  });
66
61
  }
67
62
  }
68
63
 
69
- /**
70
- * Read an `acp/<field>` credential through the broker and inject it into
71
- * `env` under `envVar`. Returns the broker's failure reason when the value
72
- * was not injected (missing credential, denied policy, no stored value),
73
- * or undefined on success. Never throws: `serverUse` signals every failure
74
- * mode, including a simply-absent credential, as `{ success: false,
75
- * reason }`, so callers choose whether a miss is fatal.
76
- */
77
- async function injectCredential(
78
- env: Record<string, string>,
79
- field: string,
80
- envVar: string,
81
- usageDescription: string,
82
- ): Promise<string | undefined> {
83
- ensureAcpCredentialPolicy(field, usageDescription);
84
- const result = await credentialBroker.serverUse<void>({
85
- service: ACP_SERVICE,
86
- field,
87
- toolName: ACP_SPAWN_TOOL,
88
- execute: async (value) => {
89
- env[envVar] = value;
90
- },
91
- });
92
- return result.success ? undefined : result.reason;
93
- }
94
-
95
64
  /**
96
65
  * Returns a NEW config with any required credentials merged into `env`.
97
66
  * Does NOT mutate the input. Throws `FailedDependencyError` if a required
98
67
  * credential is missing from both the user-supplied env override and the
99
68
  * secure store.
100
69
  *
101
- * Gating is keyed off the canonical adapter identity (`adapterCommand` set
102
- * by the resolver, falling back to the command basename for plain configs),
103
- * not the user-facing agent id. A custom `acp.agents.my-claude = { command:
104
- * "claude-agent-acp", ... }` alias still gets the env it needs, and so does
105
- * the bunx-rewritten claude adapter (whose `command` is "bun"). Without
106
- * the adapterCommand gate, bunx-resolved spawns would start with no auth
107
- * and die as zombies on the first prompt.
70
+ * Gating is keyed off the resolved agent COMMAND (basename), not the
71
+ * user-facing agent id, so a custom `acp.agents.my-claude = { command:
72
+ * "claude-agent-acp", ... }` alias still gets the env it needs.
108
73
  *
109
74
  * For `claude-agent-acp` the only required env var is
110
75
  * `CLAUDE_CODE_OAUTH_TOKEN`. Two provisioning routes converge on it, with
@@ -119,11 +84,6 @@ async function injectCredential(
119
84
  * before spawning. The "fail-fast" throw is symmetric with the existing
120
85
  * `binary_not_found` preflight in `resolveAcpAgent` and strictly better
121
86
  * than a `warn` + zombie subprocess 10 seconds later.
122
- *
123
- * For `gemini` the env var is `GEMINI_API_KEY`, provisioned the same two
124
- * ways (config.json override wins, vault field `gemini_api_key` second),
125
- * but it is OPTIONAL: the Gemini CLI supports its own OAuth login, so a
126
- * vault miss proceeds without the key instead of failing the spawn.
127
87
  */
128
88
  export async function prepareAgentEnv(
129
89
  agentConfig: AcpAgentConfig,
@@ -132,16 +92,19 @@ export async function prepareAgentEnv(
132
92
  // agent reference. The local `env` binding sidesteps TS narrowing
133
93
  // limitations on the optional `AcpAgentConfig.env` field.
134
94
  const env: Record<string, string> = { ...(agentConfig.env ?? {}) };
135
- const adapterCommand = adapterCommandOf(agentConfig);
95
+ const commandBasename = basename(agentConfig.command);
136
96
 
137
- if (adapterCommand === "claude-agent-acp") {
97
+ if (commandBasename === "claude-agent-acp") {
138
98
  if (!env.CLAUDE_CODE_OAUTH_TOKEN) {
139
- await injectCredential(
140
- env,
141
- "claude_oauth_token",
142
- "CLAUDE_CODE_OAUTH_TOKEN",
143
- "Claude OAuth token for ACP agent authentication",
144
- );
99
+ ensureAcpTokenPolicy();
100
+ await credentialBroker.serverUse<void>({
101
+ service: "acp",
102
+ field: "claude_oauth_token",
103
+ toolName: ACP_SPAWN_TOOL,
104
+ execute: async (token) => {
105
+ env.CLAUDE_CODE_OAUTH_TOKEN = token;
106
+ },
107
+ });
145
108
  }
146
109
  if (!env.CLAUDE_CODE_OAUTH_TOKEN) {
147
110
  throw new FailedDependencyError(
@@ -150,23 +113,6 @@ export async function prepareAgentEnv(
150
113
  "(or set it under acp.agents.<id>.env in config.json).",
151
114
  );
152
115
  }
153
- } else if (adapterCommand === "gemini") {
154
- if (!env.GEMINI_API_KEY) {
155
- const missReason = await injectCredential(
156
- env,
157
- "gemini_api_key",
158
- "GEMINI_API_KEY",
159
- "Gemini API key for ACP agent authentication",
160
- );
161
- if (missReason !== undefined) {
162
- // Optional credential: Gemini CLI can authenticate via its own
163
- // OAuth login, so a vault miss must not fail the spawn.
164
- log.debug(
165
- { reason: missReason },
166
- "Gemini API key unavailable from the vault; spawning without GEMINI_API_KEY",
167
- );
168
- }
169
- }
170
116
  }
171
117
 
172
118
  return { ...agentConfig, env };