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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (570) hide show
  1. package/Dockerfile +20 -4
  2. package/bun.lock +2 -2
  3. package/docker-entrypoint.sh +4 -2
  4. package/docker-init-apt-root.sh +3 -1
  5. package/docker-kata-apt-env.sh +3 -1
  6. package/docker-kata-runtime-family.sh +12 -0
  7. package/docs/architecture/memory.md +1 -1
  8. package/examples/plugins/echo/README.md +61 -66
  9. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  10. package/examples/plugins/echo/hooks/stop.ts +16 -0
  11. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  12. package/examples/plugins/echo/package.json +1 -2
  13. package/examples/plugins/echo/src/emit.ts +19 -0
  14. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
  15. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  16. package/openapi.yaml +3378 -335
  17. package/package.json +2 -2
  18. package/scripts/generate-openapi.ts +68 -41
  19. package/src/__tests__/agent-loop-exit-reason.test.ts +35 -93
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
  21. package/src/__tests__/agent-loop.test.ts +37 -87
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  23. package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
  24. package/src/__tests__/annotate-risk-options.test.ts +2 -3
  25. package/src/__tests__/anthropic-provider.test.ts +95 -2
  26. package/src/__tests__/app-control-flow.test.ts +1 -1
  27. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  28. package/src/__tests__/approval-routes-http.test.ts +4 -1
  29. package/src/__tests__/assistant-event-hub.test.ts +25 -0
  30. package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
  31. package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
  32. package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
  33. package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
  34. package/src/__tests__/btw-routes.test.ts +62 -3
  35. package/src/__tests__/build-persisted-content.test.ts +184 -0
  36. package/src/__tests__/catalog-files.test.ts +1 -1
  37. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  38. package/src/__tests__/channel-approvals.test.ts +1 -1
  39. package/src/__tests__/clawhub-files.test.ts +1 -1
  40. package/src/__tests__/compaction-circuit.test.ts +258 -0
  41. package/src/__tests__/compaction-direct.test.ts +132 -0
  42. package/src/__tests__/compaction.benchmark.test.ts +0 -30
  43. package/src/__tests__/config-watcher.test.ts +1 -1
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -5
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -7
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +316 -1143
  48. package/src/__tests__/conversation-agent-loop.test.ts +638 -1655
  49. package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
  50. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  51. package/src/__tests__/conversation-history-web-search.test.ts +11 -1
  52. package/src/__tests__/conversation-pairing.test.ts +4 -31
  53. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -10
  55. package/src/__tests__/conversation-queue.test.ts +2 -0
  56. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
  57. package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
  58. package/src/__tests__/conversation-runtime-assembly.test.ts +310 -300
  59. package/src/__tests__/conversation-runtime-workspace.test.ts +105 -45
  60. package/src/__tests__/conversation-slash-commands.test.ts +8 -42
  61. package/src/__tests__/conversation-slash-queue.test.ts +6 -1
  62. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  63. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
  64. package/src/__tests__/conversation-sync-tags.test.ts +27 -15
  65. package/src/__tests__/conversation-title-service.test.ts +135 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +17 -16
  67. package/src/__tests__/conversation-workspace-injection.test.ts +67 -2
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +7 -6
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/cross-provider-web-search.test.ts +214 -1
  71. package/src/__tests__/db-acp-history.test.ts +101 -0
  72. package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
  73. package/src/__tests__/dm-persistence.test.ts +5 -1
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +304 -0
  76. package/src/__tests__/feature-flag-test-helpers.ts +2 -2
  77. package/src/__tests__/file-write-tool.test.ts +63 -0
  78. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  79. package/src/__tests__/gemini-image-service.test.ts +13 -0
  80. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  81. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  82. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  83. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  84. package/src/__tests__/heartbeat-service.test.ts +1 -0
  85. package/src/__tests__/helpers/mock-provider.ts +110 -0
  86. package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
  87. package/src/__tests__/history-repair-hook.test.ts +1 -0
  88. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  89. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  90. package/src/__tests__/identity-intro-cache.test.ts +12 -100
  91. package/src/__tests__/identity-routes.test.ts +248 -7
  92. package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
  93. package/src/__tests__/injector-background-turn.test.ts +3 -9
  94. package/src/__tests__/injector-chain.test.ts +139 -275
  95. package/src/__tests__/injector-disk-pressure.test.ts +75 -41
  96. package/src/__tests__/injector-document-comments.test.ts +3 -3
  97. package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
  98. package/src/__tests__/injector-v3-suppression.test.ts +31 -37
  99. package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
  100. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  101. package/src/__tests__/list-messages-page-latest.test.ts +60 -0
  102. package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
  103. package/src/__tests__/llm-usage-store.test.ts +223 -1
  104. package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
  105. package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
  106. package/src/__tests__/native-web-search.test.ts +191 -0
  107. package/src/__tests__/onboarding-template-contract.test.ts +2 -0
  108. package/src/__tests__/openai-image-service.test.ts +17 -0
  109. package/src/__tests__/openai-provider.test.ts +31 -1
  110. package/src/__tests__/{overflow-reduce-pipeline.test.ts → overflow-reduction-loop.test.ts} +64 -284
  111. package/src/__tests__/persist-unsendable-image.test.ts +215 -0
  112. package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
  113. package/src/__tests__/pkb-autoinject.test.ts +2 -5
  114. package/src/__tests__/plugin-api-shim.test.ts +3 -6
  115. package/src/__tests__/plugin-bootstrap.test.ts +14 -40
  116. package/src/__tests__/plugin-registry.test.ts +3 -76
  117. package/src/__tests__/plugin-types.test.ts +0 -193
  118. package/src/__tests__/process-message-display-content.test.ts +6 -2
  119. package/src/__tests__/reaction-persistence.test.ts +1 -1
  120. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
  121. package/src/__tests__/resolve-trust-class.test.ts +4 -4
  122. package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
  123. package/src/__tests__/schedule-routes.test.ts +603 -2
  124. package/src/__tests__/schedule-store.test.ts +41 -0
  125. package/src/__tests__/schedule-tools.test.ts +35 -0
  126. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  127. package/src/__tests__/server-history-render.test.ts +314 -1
  128. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  129. package/src/__tests__/skillssh-files.test.ts +1 -1
  130. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  131. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  132. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  133. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  134. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  135. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  136. package/src/__tests__/system-prompt.test.ts +20 -0
  137. package/src/__tests__/task-scheduler.test.ts +162 -1
  138. package/src/__tests__/terminal-tools.test.ts +6 -1
  139. package/src/__tests__/title-generate-hook.test.ts +319 -0
  140. package/src/__tests__/tool-error-hook.test.ts +278 -0
  141. package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
  142. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  143. package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
  144. package/src/__tests__/tool-result-truncation.test.ts +0 -2
  145. package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
  146. package/src/__tests__/ui-work-result-surface.test.ts +159 -0
  147. package/src/__tests__/usage-routes.test.ts +285 -1
  148. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  149. package/src/__tests__/voice-session-bridge.test.ts +6 -3
  150. package/src/__tests__/web-search-backend-failure.test.ts +166 -0
  151. package/src/acp/__tests__/agent-process.test.ts +161 -0
  152. package/src/acp/__tests__/client-handler.test.ts +40 -0
  153. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  154. package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
  155. package/src/acp/__tests__/prepare-agent-env.test.ts +137 -0
  156. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  157. package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
  158. package/src/acp/agent-process.ts +61 -1
  159. package/src/acp/auto-install.test.ts +196 -0
  160. package/src/acp/auto-install.ts +177 -0
  161. package/src/acp/client-handler.ts +31 -0
  162. package/src/acp/feature-gate.test.ts +48 -0
  163. package/src/acp/feature-gate.ts +34 -0
  164. package/src/acp/prepare-agent-env.ts +83 -29
  165. package/src/acp/resolve-agent.test.ts +320 -7
  166. package/src/acp/resolve-agent.ts +182 -18
  167. package/src/acp/resume-hint.ts +25 -0
  168. package/src/acp/session-manager.ts +495 -73
  169. package/src/acp/types.ts +8 -0
  170. package/src/agent/compaction-circuit.ts +60 -102
  171. package/src/agent/loop.ts +362 -485
  172. package/src/api/events/assistant-thinking-delta.ts +33 -0
  173. package/src/api/events/tool-output-chunk.ts +45 -0
  174. package/src/api/events/tool-use-preview-start.ts +32 -0
  175. package/src/api/events/trace-event.ts +69 -0
  176. package/src/api/index.ts +48 -13
  177. package/src/api/responses/conversation-message.ts +374 -0
  178. package/src/approvals/guardian-request-resolvers.ts +1 -1
  179. package/src/avatar/__tests__/avatar-store.test.ts +34 -29
  180. package/src/background-wake/next-wake.ts +1 -0
  181. package/src/cli/commands/__tests__/notifications.test.ts +58 -14
  182. package/src/cli/commands/notifications.ts +112 -60
  183. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  184. package/src/config/acp-defaults.test.ts +10 -0
  185. package/src/config/acp-defaults.ts +6 -0
  186. package/src/config/assistant-feature-flags.ts +22 -11
  187. package/src/config/bundled-skills/acp/SKILL.md +83 -31
  188. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  189. package/src/config/bundled-skills/app-builder/SKILL.md +224 -398
  190. package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
  191. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  192. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  193. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  194. package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
  195. package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
  196. package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
  197. package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
  198. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  199. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  200. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  201. package/src/config/bundled-skills/messaging/SKILL.md +0 -7
  202. package/src/config/bundled-tool-registry.ts +2 -0
  203. package/src/config/feature-flag-cache.ts +3 -3
  204. package/src/config/feature-flag-registry.json +48 -7
  205. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
  207. package/src/config/schemas/heartbeat.ts +9 -0
  208. package/src/config/schemas/llm.ts +1 -0
  209. package/src/config/schemas/memory-v2.ts +8 -0
  210. package/src/config/schemas/memory-v3.ts +8 -0
  211. package/src/config/schemas/platform.ts +8 -0
  212. package/src/config/seed-inference-profiles.ts +2 -2
  213. package/src/config/skills.ts +13 -0
  214. package/src/context/compactor.ts +1 -1
  215. package/src/context/strip-injections.ts +128 -0
  216. package/src/context/token-estimator.ts +23 -0
  217. package/src/context/tool-result-truncation.ts +0 -23
  218. package/src/context/window-manager.ts +5 -7
  219. package/src/credential-execution/executable-discovery.ts +16 -0
  220. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
  221. package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
  222. package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
  223. package/src/daemon/assistant-attachments.ts +1 -1
  224. package/src/daemon/config-watcher.ts +2 -2
  225. package/src/daemon/context-overflow-reducer.ts +0 -1
  226. package/src/daemon/conversation-agent-loop-handlers.ts +594 -153
  227. package/src/daemon/conversation-agent-loop.ts +301 -997
  228. package/src/daemon/conversation-history.ts +5 -4
  229. package/src/daemon/conversation-lifecycle.ts +3 -4
  230. package/src/daemon/conversation-messaging.ts +7 -6
  231. package/src/daemon/conversation-process.ts +11 -16
  232. package/src/daemon/conversation-registry.ts +159 -0
  233. package/src/daemon/conversation-runtime-assembly.ts +218 -398
  234. package/src/daemon/conversation-slash.ts +6 -25
  235. package/src/daemon/conversation-store.ts +9 -90
  236. package/src/daemon/conversation-surfaces.ts +222 -4
  237. package/src/daemon/conversation-tool-setup.ts +2 -29
  238. package/src/daemon/conversation-workspace.ts +17 -0
  239. package/src/daemon/conversation.ts +32 -20
  240. package/src/daemon/external-plugins-bootstrap.ts +17 -18
  241. package/src/daemon/handlers/config-a2a.ts +51 -36
  242. package/src/daemon/handlers/config-slack-channel.ts +20 -14
  243. package/src/daemon/handlers/config-telegram.ts +16 -2
  244. package/src/daemon/handlers/conversations.ts +3 -1
  245. package/src/daemon/handlers/shared.ts +156 -84
  246. package/src/daemon/handlers/skills.ts +42 -10
  247. package/src/daemon/lifecycle.ts +25 -0
  248. package/src/daemon/message-types/apps.ts +1 -29
  249. package/src/daemon/message-types/messages.ts +9 -57
  250. package/src/daemon/message-types/skills.ts +2 -0
  251. package/src/daemon/message-types/surfaces.ts +136 -3
  252. package/src/daemon/now-scratchpad.ts +21 -0
  253. package/src/daemon/orphan-reaper.test.ts +210 -0
  254. package/src/daemon/orphan-reaper.ts +240 -0
  255. package/src/daemon/overflow-reduction-loop.ts +230 -0
  256. package/src/daemon/persist-unsendable-image.ts +117 -0
  257. package/src/daemon/process-message.ts +1 -3
  258. package/src/daemon/server.ts +2 -0
  259. package/src/daemon/trace-emitter.ts +6 -4
  260. package/src/daemon/trust-context.ts +19 -0
  261. package/src/daemon/wake-target-adapter.ts +3 -1
  262. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  263. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  264. package/src/heartbeat/heartbeat-service.ts +26 -0
  265. package/src/home/home-greeting-cache.ts +24 -1
  266. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  267. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  268. package/src/ipc/gateway-client.test.ts +2 -2
  269. package/src/ipc/gateway-client.ts +3 -3
  270. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  271. package/src/ipc/skill-routes/memory.ts +4 -2
  272. package/src/media/gemini-image-service.ts +15 -0
  273. package/src/media/openai-image-service.ts +14 -0
  274. package/src/media/types.ts +34 -0
  275. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
  276. package/src/memory/auth-fallback-events-store.ts +94 -0
  277. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  278. package/src/memory/conversation-title-service.ts +65 -41
  279. package/src/memory/db-init.ts +6 -0
  280. package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
  281. package/src/memory/graph/conversation-graph-memory.ts +65 -0
  282. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  283. package/src/memory/jobs-store.ts +33 -0
  284. package/src/memory/jobs-worker.ts +32 -5
  285. package/src/memory/llm-usage-store.ts +224 -50
  286. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
  287. package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
  288. package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
  289. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  290. package/src/memory/migrations/index.ts +3 -0
  291. package/src/memory/pkb/autoinject.ts +61 -0
  292. package/src/memory/pkb/context.ts +50 -0
  293. package/src/memory/pkb/types.ts +14 -0
  294. package/src/memory/schedule-attribution-sql.ts +104 -0
  295. package/src/memory/schema/acp.ts +4 -0
  296. package/src/memory/schema/infrastructure.ts +16 -0
  297. package/src/memory/usage-grouped-buckets.ts +6 -1
  298. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -4
  299. package/src/memory/v2/consolidation-job.ts +14 -5
  300. package/src/notifications/conversation-pairing.ts +8 -15
  301. package/src/notifications/decision-engine.ts +6 -3
  302. package/src/notifications/home-feed-side-effect.ts +12 -1
  303. package/src/permissions/prompter.ts +4 -0
  304. package/src/plugin-api/constants.ts +4 -0
  305. package/src/plugin-api/index.ts +7 -5
  306. package/src/plugin-api/types.ts +151 -1
  307. package/src/plugins/defaults/compaction/compact.ts +59 -0
  308. package/src/plugins/defaults/compaction/package.json +1 -1
  309. package/src/plugins/defaults/compaction/register.ts +8 -19
  310. package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
  311. package/src/plugins/defaults/empty-response/register.ts +8 -13
  312. package/src/plugins/defaults/index.ts +2 -18
  313. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +95 -0
  314. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
  315. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
  316. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +288 -81
  317. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  318. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +16 -0
  319. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +4 -4
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  321. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +48 -12
  322. package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +13 -0
  323. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  324. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  325. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +104 -32
  326. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +96 -30
  328. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +34 -16
  329. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +5 -5
  330. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +2 -2
  331. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  332. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  333. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  334. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  335. package/src/plugins/defaults/memory-v3-shadow/llm-retry.ts +32 -0
  336. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +8 -8
  337. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +26 -14
  338. package/src/plugins/defaults/{llm-call → memory-v3-shadow}/package.json +2 -2
  339. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  340. package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +26 -0
  341. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +3 -3
  342. package/src/plugins/defaults/memory-v3-shadow/register.ts +26 -0
  343. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  344. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +51 -45
  345. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  346. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +61 -46
  347. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +69 -99
  348. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  349. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +8 -0
  350. package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
  351. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
  352. package/src/plugins/defaults/title-generate/package.json +1 -1
  353. package/src/plugins/defaults/title-generate/register.ts +18 -18
  354. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
  355. package/src/plugins/defaults/tool-error/package.json +1 -1
  356. package/src/plugins/defaults/tool-error/register.ts +9 -21
  357. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
  358. package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
  359. package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
  360. package/src/plugins/external-api.ts +2 -2
  361. package/src/plugins/pipeline.ts +6 -305
  362. package/src/plugins/registry.ts +10 -55
  363. package/src/plugins/types.ts +62 -797
  364. package/src/plugins/user-loader.ts +30 -127
  365. package/src/proactive-artifact/aux-message-injector.ts +4 -4
  366. package/src/proactive-artifact/job.test.ts +8 -13
  367. package/src/prompts/__tests__/system-prompt.test.ts +42 -0
  368. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +64 -0
  369. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  370. package/src/prompts/templates/system-sections.ts +15 -0
  371. package/src/providers/anthropic/client.ts +37 -29
  372. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
  373. package/src/providers/openai/chat-completions-provider.ts +44 -0
  374. package/src/providers/openrouter/client.ts +1 -0
  375. package/src/providers/placeholder-sentinels.ts +35 -0
  376. package/src/runtime/__tests__/agent-wake.test.ts +10 -6
  377. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  378. package/src/runtime/agent-wake.ts +2 -5
  379. package/src/runtime/assistant-event-hub.ts +37 -7
  380. package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
  381. package/src/runtime/channel-approvals.ts +1 -1
  382. package/src/runtime/http-router.ts +16 -21
  383. package/src/runtime/http-types.ts +16 -70
  384. package/src/runtime/interactive-ui.ts +1 -1
  385. package/src/runtime/pending-interactions.ts +1 -0
  386. package/src/runtime/routes/__tests__/acp-routes.test.ts +283 -55
  387. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
  388. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  389. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
  390. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
  391. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  392. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  393. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  394. package/src/runtime/routes/acp-routes.test.ts +89 -25
  395. package/src/runtime/routes/acp-routes.ts +81 -29
  396. package/src/runtime/routes/app-management-routes.ts +6 -117
  397. package/src/runtime/routes/app-routes.ts +13 -15
  398. package/src/runtime/routes/approval-routes.ts +1 -1
  399. package/src/runtime/routes/attachment-routes.ts +26 -15
  400. package/src/runtime/routes/avatar-routes.ts +26 -0
  401. package/src/runtime/routes/browser-routes.ts +1 -1
  402. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  403. package/src/runtime/routes/btw-routes.ts +29 -23
  404. package/src/runtime/routes/consolidation-routes.ts +120 -20
  405. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  406. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  407. package/src/runtime/routes/conversation-query-routes.ts +3 -1
  408. package/src/runtime/routes/conversation-routes.ts +372 -185
  409. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  410. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  411. package/src/runtime/routes/documents-routes.ts +4 -0
  412. package/src/runtime/routes/domain-routes.ts +51 -37
  413. package/src/runtime/routes/epoch-millis-range.ts +34 -0
  414. package/src/runtime/routes/events-routes.ts +28 -34
  415. package/src/runtime/routes/gateway-log-routes.ts +26 -4
  416. package/src/runtime/routes/heartbeat-routes.ts +32 -12
  417. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  418. package/src/runtime/routes/host-cu-routes.ts +1 -1
  419. package/src/runtime/routes/identity-intro-cache.ts +11 -34
  420. package/src/runtime/routes/identity-routes.ts +224 -18
  421. package/src/runtime/routes/image-generation-routes.ts +40 -2
  422. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  423. package/src/runtime/routes/index.ts +2 -0
  424. package/src/runtime/routes/integrations/a2a.ts +12 -10
  425. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
  426. package/src/runtime/routes/integrations/slack/channel.ts +4 -0
  427. package/src/runtime/routes/integrations/slack/share.ts +27 -6
  428. package/src/runtime/routes/integrations/telegram.ts +6 -0
  429. package/src/runtime/routes/integrations/twilio.ts +42 -0
  430. package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
  431. package/src/runtime/routes/log-export-routes.ts +8 -0
  432. package/src/runtime/routes/memory-v2-routes.ts +15 -8
  433. package/src/runtime/routes/memory-v3-routes.ts +66 -34
  434. package/src/runtime/routes/oauth-apps.ts +66 -12
  435. package/src/runtime/routes/oauth-providers.ts +44 -5
  436. package/src/runtime/routes/platform-routes.ts +81 -5
  437. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
  438. package/src/runtime/routes/playground/force-compact.ts +1 -1
  439. package/src/runtime/routes/playground/helpers.ts +1 -1
  440. package/src/runtime/routes/rename-conversation-routes.ts +5 -0
  441. package/src/runtime/routes/schedule-routes.ts +152 -42
  442. package/src/runtime/routes/secret-routes.ts +14 -2
  443. package/src/runtime/routes/skills-routes.ts +43 -14
  444. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  445. package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
  446. package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
  447. package/src/runtime/routes/trust-rules-routes.ts +26 -2
  448. package/src/runtime/routes/tts-routes.ts +35 -0
  449. package/src/runtime/routes/types.ts +66 -8
  450. package/src/runtime/routes/usage-routes.ts +47 -39
  451. package/src/runtime/routes/webhook-routes.ts +41 -2
  452. package/src/runtime/routes/work-items-routes.ts +2 -4
  453. package/src/runtime/routes/workspace-routes.ts +4 -0
  454. package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
  455. package/src/runtime/services/analyze-conversation.ts +2 -2
  456. package/src/runtime/services/conversation-serializer.ts +1 -1
  457. package/src/schedule/schedule-store.ts +20 -1
  458. package/src/schedule/schedule-usage-store.ts +83 -0
  459. package/src/schedule/scheduler.ts +12 -5
  460. package/src/signals/cancel.ts +2 -4
  461. package/src/skills/catalog-files.ts +2 -2
  462. package/src/skills/catalog-install.ts +3 -0
  463. package/src/skills/categories-cache.ts +118 -0
  464. package/src/skills/clawhub-files.ts +1 -2
  465. package/src/skills/skillssh-files.ts +1 -2
  466. package/src/subagent/manager.ts +17 -5
  467. package/src/telemetry/types.ts +29 -1
  468. package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
  469. package/src/telemetry/usage-telemetry-reporter.ts +57 -2
  470. package/src/tools/acp/context.ts +20 -0
  471. package/src/tools/acp/list-agents.test.ts +7 -1
  472. package/src/tools/acp/spawn.test.ts +158 -55
  473. package/src/tools/acp/spawn.ts +47 -72
  474. package/src/tools/acp/steer.test.ts +105 -8
  475. package/src/tools/acp/steer.ts +48 -17
  476. package/src/tools/apps/executors.ts +13 -8
  477. package/src/tools/executor.ts +1 -53
  478. package/src/tools/filesystem/write.ts +34 -0
  479. package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
  480. package/src/tools/network/__tests__/web-search.test.ts +11 -3
  481. package/src/tools/network/web-search-error.test.ts +248 -0
  482. package/src/tools/network/web-search-error.ts +267 -0
  483. package/src/tools/network/web-search.ts +207 -48
  484. package/src/tools/schedule/create.ts +2 -0
  485. package/src/tools/subagent/spawn.ts +2 -4
  486. package/src/tools/terminal/safe-env.ts +10 -1
  487. package/src/tools/ui-surface/definitions.ts +34 -5
  488. package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
  489. package/src/tts/provider-catalog.ts +76 -1
  490. package/src/util/mutex.ts +47 -0
  491. package/src/workspace/git-service.ts +1 -42
  492. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  493. package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
  494. package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
  495. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +117 -0
  496. package/src/workspace/migrations/registry.ts +6 -0
  497. package/docs/plugins.md +0 -836
  498. package/examples/plugins/echo/register.ts +0 -184
  499. package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
  500. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  501. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  502. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  503. package/src/__tests__/empty-response-pipeline.test.ts +0 -423
  504. package/src/__tests__/llm-call-pipeline.test.ts +0 -287
  505. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
  506. package/src/__tests__/persistence-pipeline.test.ts +0 -503
  507. package/src/__tests__/pipeline-runner.test.ts +0 -564
  508. package/src/__tests__/title-generate-pipeline.test.ts +0 -211
  509. package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
  510. package/src/__tests__/tool-error-pipeline.test.ts +0 -241
  511. package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
  512. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
  513. package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
  514. package/src/gallery/default-gallery.ts +0 -1359
  515. package/src/gallery/gallery-manifest.ts +0 -28
  516. package/src/home/feature-gate.ts +0 -22
  517. package/src/memory/v3/provider-blocks.ts +0 -16
  518. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  519. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  520. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  521. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  522. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  523. package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
  524. package/src/plugins/defaults/empty-response/terminal.ts +0 -106
  525. package/src/plugins/defaults/injectors/package.json +0 -15
  526. package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
  527. package/src/plugins/defaults/llm-call/register.ts +0 -45
  528. package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
  529. package/src/plugins/defaults/memory-retrieval/package.json +0 -15
  530. package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
  531. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  532. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  533. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  534. package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
  535. package/src/plugins/defaults/persistence/package.json +0 -15
  536. package/src/plugins/defaults/persistence/register.ts +0 -38
  537. package/src/plugins/defaults/persistence/terminal.ts +0 -83
  538. package/src/plugins/defaults/title-generate/terminal.ts +0 -31
  539. package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
  540. package/src/plugins/defaults/token-estimate/package.json +0 -15
  541. package/src/plugins/defaults/token-estimate/register.ts +0 -34
  542. package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
  543. package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
  544. package/src/plugins/defaults/tool-error/terminal.ts +0 -47
  545. package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
  546. package/src/plugins/defaults/tool-execute/package.json +0 -15
  547. package/src/plugins/defaults/tool-execute/register.ts +0 -49
  548. package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
  549. package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
  550. package/src/skills/category-inference.ts +0 -111
  551. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  552. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  553. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  554. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  555. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  556. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  557. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  558. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +0 -0
  559. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  560. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  561. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  562. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  563. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  564. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  565. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  566. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  567. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  568. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  569. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  570. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
@@ -23,7 +23,7 @@ mock.module("../../assistant-event-hub.js", () => ({
23
23
  broadcastMessage: () => {},
24
24
  }));
25
25
 
26
- import { findConversation } from "../../../daemon/conversation-store.js";
26
+ import { findConversation } from "../../../daemon/conversation-registry.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";
@@ -175,7 +175,10 @@ function seedRequestLog(
175
175
  .run();
176
176
  }
177
177
 
178
- function seedConversationKey(conversationKey: string, conversationId: string): void {
178
+ function seedConversationKey(
179
+ conversationKey: string,
180
+ conversationId: string,
181
+ ): void {
179
182
  getDb()
180
183
  .insert(conversationKeys)
181
184
  .values({
@@ -776,6 +779,33 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
776
779
  });
777
780
  });
778
781
 
782
+ test("saves a profile using the minimax provider (regression #32404)", async () => {
783
+ // minimax is exposed as a first-class provider in the catalog, so saving
784
+ // a profile bound to it must pass ProfileEntry validation rather than 400.
785
+ const result = await replaceProfileRoute.handler({
786
+ pathParams: { name: "custom" },
787
+ body: {
788
+ provider: "minimax",
789
+ model: "MiniMax-M2.7",
790
+ },
791
+ });
792
+
793
+ expect(result).toEqual({ ok: true });
794
+ const savedProfile = (
795
+ savedRawConfig?.llm as {
796
+ profiles: Record<string, Record<string, unknown>>;
797
+ }
798
+ ).profiles.custom;
799
+
800
+ expect(savedProfile.provider).toBe("minimax");
801
+ expect(savedProfile.model).toBe("MiniMax-M2.7");
802
+ expect(savedProfile.provider_connection).toBe("minimax-personal");
803
+
804
+ const conn = getConnection(getDb(), "minimax-personal");
805
+ expect(conn).not.toBeNull();
806
+ expect(conn!.provider).toBe("minimax");
807
+ });
808
+
779
809
  describe("managed profile guard", () => {
780
810
  beforeEach(() => {
781
811
  // Seed a managed profile alongside the existing custom one.
@@ -155,7 +155,11 @@ describe("memory_v2_ema_scores route registration", () => {
155
155
  expect(route!.tags).toContain("memory");
156
156
  // Schema rejects unknown keys so accidental request-body fields fail
157
157
  // loudly during route adapter parsing rather than silently propagating.
158
- expect(() => route!.requestBody!.parse({ extra: true })).toThrow();
159
- expect(() => route!.requestBody!.parse({})).not.toThrow();
158
+ const requestBody = route!.requestBody;
159
+ if (!requestBody || !("parse" in requestBody)) {
160
+ throw new Error("expected a JSON (Zod) request-body schema");
161
+ }
162
+ expect(() => requestBody.parse({ extra: true })).toThrow();
163
+ expect(() => requestBody.parse({})).not.toThrow();
160
164
  });
161
165
  });
@@ -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) {
@@ -164,8 +164,12 @@ afterEach(() => {
164
164
  // ---------------------------------------------------------------------------
165
165
 
166
166
  describe("tts-routes", () => {
167
- test("exports route definitions for messages/:messageId/tts, tts/synthesize, and tts/synthesize-cli", () => {
168
- expect(ROUTES).toHaveLength(3);
167
+ test("exports route definitions for tts/providers, messages/:messageId/tts, tts/synthesize, and tts/synthesize-cli", () => {
168
+ expect(ROUTES).toHaveLength(4);
169
+
170
+ const providers = getRoute("tts/providers");
171
+ expect(providers.method).toBe("GET");
172
+ expect(providers.policy?.requiredScopes).toContain("settings.read");
169
173
 
170
174
  const msgTts = getRoute("messages/:messageId/tts");
171
175
  expect(msgTts.method).toBe("POST");
@@ -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 (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`.
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 {
@@ -48,8 +51,10 @@ afterAll(() => {
48
51
  // in-memory-status check without spawning real ACP processes, and so the
49
52
  // env-injection spawn tests can capture the `agentConfig` arg without
50
53
  // launching a real subprocess. Stored in mutable state so individual tests
51
- // can plant arbitrary states / inspect capture.
54
+ // can plant arbitrary states / inspect capture. `pendingResumeIds` models
55
+ // ids reserved by an in-flight resumeFromHistory (no SessionEntry yet).
52
56
  const inMemoryStates = new Map<string, AcpSessionState>();
57
+ const pendingResumeIds = new Set<string>();
53
58
 
54
59
  interface CapturedSpawn {
55
60
  agent: string;
@@ -70,6 +75,9 @@ mock.module("../../acp/index.js", () => ({
70
75
  if (!state) throw new Error(`ACP session "${id}" not found`);
71
76
  return state;
72
77
  },
78
+ getActiveAndPendingIds: () => [
79
+ ...new Set([...inMemoryStates.keys(), ...pendingResumeIds]),
80
+ ],
73
81
  spawn: async (
74
82
  agent: string,
75
83
  agentConfig: { env?: Record<string, string> },
@@ -226,22 +234,6 @@ describe("POST /v1/acp/spawn", () => {
226
234
  ).rejects.toThrow('Unknown agent "nonexistent"');
227
235
  });
228
236
 
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
-
245
237
  test("body-shape guard short-circuits before the resolver runs", async () => {
246
238
  config.setConfig({ enabled: false });
247
239
 
@@ -457,6 +449,8 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
457
449
  });
458
450
 
459
451
  beforeEach(() => {
452
+ inMemoryStates.clear();
453
+ pendingResumeIds.clear();
460
454
  getSqlite().run("DELETE FROM acp_session_history");
461
455
  });
462
456
 
@@ -482,6 +476,57 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
482
476
  ]);
483
477
  });
484
478
 
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
+
485
530
  test("returns deleted=0 when no terminal rows are present", async () => {
486
531
  seedHistoryRow("row-running", "running", 1000);
487
532
 
@@ -559,6 +604,7 @@ describe("DELETE /v1/acp/sessions/:id", () => {
559
604
 
560
605
  beforeEach(() => {
561
606
  inMemoryStates.clear();
607
+ pendingResumeIds.clear();
562
608
  getDb().delete(acpSessionHistory).run();
563
609
  });
564
610
 
@@ -610,6 +656,24 @@ describe("DELETE /v1/acp/sessions/:id", () => {
610
656
  },
611
657
  );
612
658
 
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
+
613
677
  test("idempotent for unknown id — returns { deleted: false }", async () => {
614
678
  const handler = getDeleteSessionHandler();
615
679
  const result = (await handler({
@@ -4,12 +4,14 @@
4
4
  * Exposes spawn, steer, cancel, close, sessions, and permission operations
5
5
  * over HTTP and IPC.
6
6
  */
7
- import { desc, eq, inArray } from "drizzle-orm";
7
+ import { and, desc, eq, inArray, notInArray } from "drizzle-orm";
8
8
  import { z } from "zod";
9
9
 
10
+ import { resolveAgentWithAutoInstall } from "../../acp/auto-install.js";
10
11
  import { getAcpSessionManager } from "../../acp/index.js";
11
12
  import { prepareAgentEnv } from "../../acp/prepare-agent-env.js";
12
- import { resolveAcpAgent } from "../../acp/resolve-agent.js";
13
+ import { formatResolveFailure } from "../../acp/resolve-agent.js";
14
+ import { AcpResumeError } from "../../acp/session-manager.js";
13
15
  import type { AcpSessionState } from "../../acp/types.js";
14
16
  import { getDb } from "../../memory/db-connection.js";
15
17
  import { rawChanges } from "../../memory/raw-query.js";
@@ -61,26 +63,19 @@ async function spawnSession({ body }: RouteHandlerArgs) {
61
63
  throw new BadRequestError("agent, task, and conversationId are required");
62
64
  }
63
65
 
64
- const resolved = resolveAcpAgent(agent);
66
+ // Resolve the agent, silently auto-installing a missing allowlisted
67
+ // adapter binary (see acp/auto-install.ts). Shared with the skill-tool
68
+ // path in tools/acp/spawn.ts; only the transport mapping differs.
69
+ const { resolved, failureMessage } = await resolveAgentWithAutoInstall(agent);
70
+ if (failureMessage) {
71
+ throw new FailedDependencyError(failureMessage);
72
+ }
65
73
  if (!resolved.ok) {
66
- switch (resolved.reason) {
67
- case "acp_disabled":
68
- throw new BadRequestError(resolved.hint);
69
- case "unknown_agent":
70
- throw new BadRequestError(
71
- `Unknown agent "${agent}". Available: ${resolved.available.join(", ")}.`,
72
- );
73
- case "binary_not_found":
74
- throw new FailedDependencyError(
75
- `${resolved.command} is not on PATH. ${resolved.hint}`,
76
- );
77
- default: {
78
- const _exhaustive: never = resolved;
79
- throw new Error(
80
- `Unexpected acp resolver reason: ${(_exhaustive as { reason: string }).reason}`,
81
- );
82
- }
74
+ const message = formatResolveFailure(agent, resolved);
75
+ if (resolved.reason === "binary_not_found") {
76
+ throw new FailedDependencyError(message);
83
77
  }
78
+ throw new BadRequestError(message);
84
79
  }
85
80
 
86
81
  // Inject required env vars and preflight via the shared helper. See
@@ -116,13 +111,31 @@ async function steerSession({ pathParams, body }: RouteHandlerArgs) {
116
111
  throw new BadRequestError("instruction is required");
117
112
  }
118
113
 
114
+ // Sessions no longer in memory (completed, or lost to a daemon restart)
115
+ // are transparently resumed from persisted history with the instruction
116
+ // fired in the same call, mirroring the acp_steer skill tool.
117
+ // broadcastMessage plays the sender role spawnSession gives it, so
118
+ // connected clients render the session.
119
119
  const manager = getAcpSessionManager();
120
120
  try {
121
- await manager.steer(id, instruction);
122
- } catch {
121
+ const { resumed } = await manager.steerOrResume(
122
+ id,
123
+ instruction,
124
+ broadcastMessage,
125
+ );
126
+ return resumed
127
+ ? { acpSessionId: id, steered: true, resumed: true }
128
+ : { acpSessionId: id, steered: true };
129
+ } catch (err) {
130
+ // Resume errors carry the actionable hint (legacy row without cwd,
131
+ // agent capability missing, resolver failures, ...).
132
+ if (err instanceof AcpResumeError) {
133
+ throw new FailedDependencyError(err.message);
134
+ }
135
+ // Unknown ids (no in-memory session, no history row) and plain steer
136
+ // failures both map to 404, as before.
123
137
  throw new NotFoundError("ACP session not found");
124
138
  }
125
- return { acpSessionId: id, steered: true };
126
139
  }
127
140
 
128
141
  async function cancelSession({ pathParams }: RouteHandlerArgs) {
@@ -161,9 +174,25 @@ function bulkDeleteSessions({ queryParams }: RouteHandlerArgs) {
161
174
  "status query param is required and must be 'completed'",
162
175
  );
163
176
  }
177
+ // Exclude sessions currently active in memory AND ids with a resume in
178
+ // flight (reserved but not yet registered): a resumed session reuses its
179
+ // original id, and its history row keeps the old terminal status until
180
+ // the next terminal upsert - a status-only delete would remove the row
181
+ // out from under the live (or resuming) session, and the later terminal
182
+ // upsert would resurrect it. Mirrors the 409 guard on the single-id
183
+ // delete route.
184
+ const activeIds = getAcpSessionManager().getActiveAndPendingIds();
185
+ const terminalFilter = inArray(
186
+ acpSessionHistory.status,
187
+ TERMINAL_SESSION_STATUSES,
188
+ );
164
189
  getDb()
165
190
  .delete(acpSessionHistory)
166
- .where(inArray(acpSessionHistory.status, TERMINAL_SESSION_STATUSES))
191
+ .where(
192
+ activeIds.length > 0
193
+ ? and(terminalFilter, notInArray(acpSessionHistory.id, activeIds))
194
+ : terminalFilter,
195
+ )
167
196
  .run();
168
197
  const deleted = rawChanges();
169
198
  log.info({ deleted }, "Bulk-cleared terminal ACP session history");
@@ -172,9 +201,10 @@ function bulkDeleteSessions({ queryParams }: RouteHandlerArgs) {
172
201
 
173
202
  function deleteSession({ pathParams }: RouteHandlerArgs) {
174
203
  const id = pathParams?.id as string;
204
+ const manager = getAcpSessionManager();
175
205
 
176
206
  try {
177
- const state = getAcpSessionManager().getStatus(id);
207
+ const state = manager.getStatus(id);
178
208
  if (
179
209
  !Array.isArray(state) &&
180
210
  (state.status === "running" || state.status === "initializing")
@@ -185,7 +215,16 @@ function deleteSession({ pathParams }: RouteHandlerArgs) {
185
215
  }
186
216
  } catch (err) {
187
217
  if (err instanceof ConflictError) throw err;
188
- // Not in memory fall through to the (idempotent) DB delete.
218
+ // Not registered in memory, but a resume may be in flight (id reserved
219
+ // while awaiting env preparation). Its history row must survive until
220
+ // the resume lands - the later terminal upsert would resurrect a
221
+ // deleted row.
222
+ if (manager.getActiveAndPendingIds().includes(id)) {
223
+ throw new ConflictError(
224
+ `ACP session "${id}" has a resume in flight. Wait for it to finish before deleting.`,
225
+ );
226
+ }
227
+ // Otherwise fall through to the (idempotent) DB delete.
189
228
  }
190
229
 
191
230
  getDb().delete(acpSessionHistory).where(eq(acpSessionHistory.id, id)).run();
@@ -233,7 +272,11 @@ export const ROUTES: RouteDefinition[] = [
233
272
  },
234
273
  handler: steerSession,
235
274
  summary: "Steer ACP session",
236
- description: "Send a steering instruction to an active ACP session.",
275
+ description:
276
+ "Send a steering instruction to an ACP session. Sessions no longer " +
277
+ "in memory (completed, or lost to a daemon restart) are " +
278
+ "transparently resumed from persisted history first, when the agent " +
279
+ "supports ACP session loading.",
237
280
  tags: ["acp"],
238
281
  requestBody: z.object({
239
282
  instruction: z.string(),
@@ -241,6 +284,12 @@ export const ROUTES: RouteDefinition[] = [
241
284
  responseBody: z.object({
242
285
  acpSessionId: z.string(),
243
286
  steered: z.boolean(),
287
+ resumed: z
288
+ .boolean()
289
+ .optional()
290
+ .describe(
291
+ "True when the session was resumed from persisted history before steering.",
292
+ ),
244
293
  }),
245
294
  },
246
295
  {
@@ -324,7 +373,9 @@ export const ROUTES: RouteDefinition[] = [
324
373
  summary: "Bulk-clear terminal ACP sessions",
325
374
  description:
326
375
  "Remove every terminal-state row (completed/failed/cancelled) from " +
327
- "the persisted acp_session_history table.",
376
+ "the persisted acp_session_history table. Rows whose session is " +
377
+ "currently active in memory (e.g. resumed) or has a resume in " +
378
+ "flight are excluded.",
328
379
  tags: ["acp"],
329
380
  queryParams: [
330
381
  {
@@ -350,7 +401,8 @@ export const ROUTES: RouteDefinition[] = [
350
401
  summary: "Delete ACP session from history",
351
402
  description:
352
403
  "Remove a persisted ACP session row. Rejects with 409 when the " +
353
- "session is still active in memory; idempotent for unknown ids.",
404
+ "session is still active in memory or has a resume in flight; " +
405
+ "idempotent for unknown ids.",
354
406
  tags: ["acp"],
355
407
  responseBody: z.object({
356
408
  deleted: z.boolean(),
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Route handlers for app CRUD, bundling, sharing, versioning,
3
- * gallery, and signing operations.
3
+ * and signing operations.
4
4
  */
5
5
  import { randomBytes } from "node:crypto";
6
6
  import {
@@ -12,7 +12,7 @@ import {
12
12
  } from "node:fs";
13
13
  import { stat, unlink } from "node:fs/promises";
14
14
  import { homedir, tmpdir } from "node:os";
15
- import { dirname, join } from "node:path";
15
+ import { join } from "node:path";
16
16
 
17
17
  import { z } from "zod";
18
18
 
@@ -22,7 +22,6 @@ import { scanBundle } from "../../bundler/bundle-scanner.js";
22
22
  import type { SignatureJson } from "../../bundler/bundle-signer.js";
23
23
  import { verifyBundleSignature } from "../../bundler/signature-verifier.js";
24
24
  import { compareSemver } from "../../daemon/handlers/shared.js";
25
- import { defaultGallery } from "../../gallery/default-gallery.js";
26
25
  import {
27
26
  getAppDiff,
28
27
  getAppHistory,
@@ -272,47 +271,6 @@ function forkSharedApp(
272
271
  return { success: true, appId: newApp.id, name: newApp.name };
273
272
  }
274
273
 
275
- async function installGalleryApp(
276
- galleryAppId: string,
277
- ): Promise<
278
- | { success: true; appId: string; name: string }
279
- | { success: false; error: string }
280
- > {
281
- const galleryApp = defaultGallery.apps.find((a) => a.id === galleryAppId);
282
- if (!galleryApp) {
283
- return {
284
- success: false,
285
- error: `Gallery app not found: ${galleryAppId}`,
286
- };
287
- }
288
-
289
- const app = createApp({
290
- name: galleryApp.name,
291
- description: galleryApp.description,
292
- schemaJson: galleryApp.schemaJson,
293
- htmlDefinition: galleryApp.htmlDefinition,
294
- formatVersion: galleryApp.formatVersion,
295
- });
296
-
297
- if (galleryApp.formatVersion === 2 && galleryApp.sourceFiles) {
298
- const appDir = getAppDirPath(app.id);
299
- for (const [relPath, content] of Object.entries(galleryApp.sourceFiles)) {
300
- const fullPath = join(appDir, relPath);
301
- mkdirSync(dirname(fullPath), { recursive: true });
302
- writeFileSync(fullPath, content, "utf-8");
303
- }
304
- const result = await compileApp(appDir);
305
- if (!result.ok) {
306
- log.warn(
307
- { appId: app.id, errors: result.errors },
308
- "Gallery app compilation had errors; falling back to htmlDefinition",
309
- );
310
- }
311
- }
312
-
313
- return { success: true, appId: app.id, name: app.name };
314
- }
315
-
316
274
  async function openBundle(filePath: string): Promise<Record<string, unknown>> {
317
275
  const fileStat = await stat(filePath);
318
276
  const bundleSizeBytes = fileStat.size;
@@ -594,22 +552,6 @@ function handleForkSharedApp({ body, headers }: RouteHandlerArgs) {
594
552
  return result;
595
553
  }
596
554
 
597
- async function handleInstallGalleryApp({ body, headers }: RouteHandlerArgs) {
598
- if (!body?.galleryAppId) {
599
- throw new BadRequestError("galleryAppId is required");
600
- }
601
- const result = await installGalleryApp(body.galleryAppId as string);
602
- if (!result.success) {
603
- throw new BadRequestError(result.error);
604
- }
605
- publishAppsChanged(getOriginClientId(headers));
606
- return result;
607
- }
608
-
609
- function handleListGallery() {
610
- return { gallery: defaultGallery };
611
- }
612
-
613
555
  function handleSignBundle({ body }: RouteHandlerArgs) {
614
556
  if (!body?.payload) {
615
557
  throw new BadRequestError("payload is required");
@@ -900,62 +842,6 @@ export const ROUTES: RouteDefinition[] = [
900
842
  name: z.string(),
901
843
  }),
902
844
  },
903
- {
904
- operationId: "apps_gallery_install",
905
- endpoint: "apps/gallery/install",
906
- method: "POST",
907
- policy: {
908
- requiredScopes: ["settings.write"],
909
- allowedPrincipalTypes: ACTOR_PRINCIPALS,
910
- },
911
- handler: handleInstallGalleryApp,
912
- summary: "Install a gallery app",
913
- description: "Install an app from the built-in gallery by its ID.",
914
- tags: ["apps"],
915
- requestBody: z.object({ galleryAppId: z.string() }),
916
- responseBody: z.object({
917
- success: z.literal(true),
918
- appId: z.string(),
919
- name: z.string(),
920
- }),
921
- },
922
- {
923
- operationId: "apps_gallery_list",
924
- endpoint: "apps/gallery",
925
- method: "GET",
926
- policy: {
927
- requiredScopes: ["settings.read"],
928
- allowedPrincipalTypes: ACTOR_PRINCIPALS,
929
- },
930
- handler: handleListGallery,
931
- summary: "List gallery apps",
932
- description: "Return the built-in app gallery catalog.",
933
- tags: ["apps"],
934
- responseBody: z.object({
935
- gallery: z.object({
936
- version: z.number(),
937
- updatedAt: z.string(),
938
- categories: z.array(
939
- z.object({
940
- id: z.string(),
941
- name: z.string(),
942
- icon: z.string(),
943
- }),
944
- ),
945
- apps: z.array(
946
- z.object({
947
- id: z.string(),
948
- name: z.string(),
949
- description: z.string(),
950
- icon: z.string(),
951
- category: z.string(),
952
- version: z.string(),
953
- featured: z.boolean().optional(),
954
- }),
955
- ),
956
- }),
957
- }),
958
- },
959
845
  {
960
846
  operationId: "apps_import_bundle",
961
847
  endpoint: "apps/import-bundle",
@@ -969,7 +855,10 @@ export const ROUTES: RouteDefinition[] = [
969
855
  description:
970
856
  "Upload, validate, and install a .vbundle archive as a new local app.",
971
857
  tags: ["apps"],
972
- rawBody: true,
858
+ requestBody: {
859
+ contentType: "application/octet-stream",
860
+ schema: { type: "string", format: "binary" },
861
+ },
973
862
  responseBody: z.object({
974
863
  success: z.boolean(),
975
864
  appId: z.string(),