@vellumai/assistant 0.8.4 → 0.8.5

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 (438) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/docs/browser-use-architecture-phase2.md +1 -1
  3. package/knip.json +2 -1
  4. package/openapi.yaml +809 -11
  5. package/package.json +1 -1
  6. package/src/__tests__/anthropic-provider.test.ts +34 -37
  7. package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
  8. package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
  9. package/src/__tests__/audit-log-rotation.test.ts +70 -16
  10. package/src/__tests__/background-workers-disk-pressure.test.ts +3 -3
  11. package/src/__tests__/btw-routes.test.ts +2 -3
  12. package/src/__tests__/call-controller.test.ts +0 -1
  13. package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
  14. package/src/__tests__/channel-guardian.test.ts +3 -3
  15. package/src/__tests__/checker.test.ts +6 -15
  16. package/src/__tests__/compaction-events.test.ts +1 -0
  17. package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
  18. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
  19. package/src/__tests__/computer-use-tools.test.ts +2 -4
  20. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  21. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
  22. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  23. package/src/__tests__/conversation-agent-loop-overflow.test.ts +197 -2
  24. package/src/__tests__/conversation-agent-loop.test.ts +163 -122
  25. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
  26. package/src/__tests__/conversation-clear-safety.test.ts +25 -25
  27. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
  28. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  29. package/src/__tests__/conversation-error.test.ts +31 -0
  30. package/src/__tests__/conversation-fork-crud.test.ts +178 -15
  31. package/src/__tests__/conversation-lifecycle.test.ts +52 -11
  32. package/src/__tests__/{conversation-load-cleaned-at.test.ts → conversation-load-history-stripped.test.ts} +13 -13
  33. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
  34. package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
  35. package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
  36. package/src/__tests__/conversation-skill-tools.test.ts +2 -5
  37. package/src/__tests__/conversation-store.test.ts +1 -1
  38. package/src/__tests__/conversation-sync-tags.test.ts +99 -32
  39. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  40. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  41. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  42. package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
  43. package/src/__tests__/credential-execution-tools.test.ts +6 -6
  44. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  45. package/src/__tests__/credential-vault-unit.test.ts +2 -2
  46. package/src/__tests__/dynamic-page-surface.test.ts +2 -2
  47. package/src/__tests__/email-html-renderer.test.ts +12 -0
  48. package/src/__tests__/gateway-flag-listener.test.ts +237 -0
  49. package/src/__tests__/gemini-provider.test.ts +78 -0
  50. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  51. package/src/__tests__/guardian-outbound-http.test.ts +7 -5
  52. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  53. package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
  54. package/src/__tests__/heartbeat-service.test.ts +4 -0
  55. package/src/__tests__/host-shell-tool.test.ts +1 -1
  56. package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
  57. package/src/__tests__/list-messages-tool-merge.test.ts +70 -11
  58. package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
  59. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
  60. package/src/__tests__/llm-resolver.test.ts +77 -9
  61. package/src/__tests__/llm-usage-store.test.ts +66 -0
  62. package/src/__tests__/logger.test.ts +89 -0
  63. package/src/__tests__/mcp-abort-signal.test.ts +2 -2
  64. package/src/__tests__/media-generate-image.test.ts +31 -0
  65. package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
  66. package/src/__tests__/model-intents.test.ts +2 -4
  67. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  68. package/src/__tests__/onboarding-template-contract.test.ts +1 -1
  69. package/src/__tests__/openai-provider.test.ts +46 -0
  70. package/src/__tests__/openai-responses-provider.test.ts +114 -12
  71. package/src/__tests__/pending-interactions-resolved-event.test.ts +0 -1
  72. package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
  73. package/src/__tests__/platform.test.ts +2 -2
  74. package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
  75. package/src/__tests__/plugin-bootstrap.test.ts +2 -2
  76. package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
  77. package/src/__tests__/plugin-types.test.ts +3 -2
  78. package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
  79. package/src/__tests__/pricing.test.ts +12 -0
  80. package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
  81. package/src/__tests__/registry.test.ts +2 -8
  82. package/src/__tests__/require-fresh-approval.test.ts +2 -2
  83. package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
  84. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
  85. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  86. package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
  87. package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
  88. package/src/__tests__/skill-tool-factory.test.ts +1 -1
  89. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  90. package/src/__tests__/suggestion-routes.test.ts +1 -0
  91. package/src/__tests__/sync-message-contract.test.ts +59 -0
  92. package/src/__tests__/system-prompt.test.ts +145 -131
  93. package/src/__tests__/terminal-tools.test.ts +1 -1
  94. package/src/__tests__/tool-approval-handler.test.ts +1 -5
  95. package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
  96. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
  97. package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
  98. package/src/__tests__/tool-executor.test.ts +9 -62
  99. package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
  100. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  101. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
  102. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  103. package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
  104. package/src/__tests__/usage-routes.test.ts +3 -0
  105. package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
  106. package/src/__tests__/workspace-git-service.test.ts +6 -5
  107. package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
  108. package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
  109. package/src/acp/prepare-agent-env.ts +78 -0
  110. package/src/acp/session-manager.ts +1 -1
  111. package/src/agent/loop.ts +8 -0
  112. package/src/api/README.md +5 -0
  113. package/src/api/index.ts +4 -0
  114. package/src/api/package.json +10 -0
  115. package/src/background-wake/background-wake-routes.test.ts +233 -0
  116. package/src/background-wake/runtime-registry.ts +24 -0
  117. package/src/cli/commands/__tests__/browser.test.ts +23 -5
  118. package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
  119. package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
  120. package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
  121. package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
  122. package/src/cli/commands/__tests__/memory-v2.test.ts +1 -0
  123. package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
  124. package/src/cli/commands/browser.ts +247 -0
  125. package/src/cli/commands/domain.ts +91 -41
  126. package/src/cli/commands/inference.ts +93 -40
  127. package/src/cli/commands/memory-v2-compare-render.ts +115 -0
  128. package/src/cli/commands/memory-v2.ts +176 -1
  129. package/src/cli/commands/memory-v3-render.ts +344 -0
  130. package/src/cli/commands/memory-v3.ts +316 -0
  131. package/src/cli/program.ts +2 -0
  132. package/src/config/assistant-feature-flags.ts +21 -9
  133. package/src/config/bundled-skills/document-editor/SKILL.md +11 -2
  134. package/src/config/bundled-skills/document-editor/TOOLS.json +18 -0
  135. package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
  136. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  137. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  138. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
  139. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
  140. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
  141. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
  142. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
  143. package/src/config/bundled-tool-registry.ts +2 -0
  144. package/src/config/call-site-defaults.ts +7 -6
  145. package/src/config/feature-flag-registry.json +16 -0
  146. package/src/config/schemas/__tests__/memory-v2.test.ts +213 -1
  147. package/src/config/schemas/call-site-catalog.ts +21 -7
  148. package/src/config/schemas/llm.ts +12 -1
  149. package/src/config/schemas/memory-v2.ts +246 -0
  150. package/src/config/schemas/memory.ts +2 -1
  151. package/src/context/compactor.ts +52 -0
  152. package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
  153. package/src/conversations/message-consolidation.ts +404 -0
  154. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
  155. package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
  156. package/src/daemon/conversation-agent-loop-handlers.ts +2 -13
  157. package/src/daemon/conversation-agent-loop.ts +126 -76
  158. package/src/daemon/conversation-error.ts +31 -1
  159. package/src/daemon/conversation-lifecycle.ts +27 -22
  160. package/src/daemon/conversation-runtime-assembly.ts +10 -9
  161. package/src/daemon/conversation-tool-setup.ts +63 -3
  162. package/src/daemon/conversation-usage.ts +2 -0
  163. package/src/daemon/conversation.ts +14 -29
  164. package/src/daemon/disk-pressure-guard.ts +14 -2
  165. package/src/daemon/handlers/config-model.test.ts +1 -0
  166. package/src/daemon/handlers/conversations.ts +11 -3
  167. package/src/daemon/host-browser-proxy.ts +5 -5
  168. package/src/daemon/host-cu-proxy.ts +4 -4
  169. package/src/daemon/host-file-proxy.ts +4 -4
  170. package/src/daemon/host-proxy-base.ts +4 -4
  171. package/src/daemon/host-transfer-proxy.ts +10 -10
  172. package/src/daemon/lifecycle.ts +23 -20
  173. package/src/daemon/meet-manifest-loader.ts +1 -7
  174. package/src/daemon/message-types/conversations.ts +6 -9
  175. package/src/daemon/message-types/home.ts +1 -13
  176. package/src/daemon/message-types/messages.ts +6 -14
  177. package/src/daemon/message-types/sync.ts +14 -0
  178. package/src/daemon/shutdown-handlers.ts +24 -5
  179. package/src/daemon/switch-inference-profile-tool.ts +52 -0
  180. package/src/daemon/tool-setup-types.ts +13 -0
  181. package/src/events/relationship-state-updated.ts +25 -0
  182. package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
  183. package/src/home/home-greeting.ts +0 -9
  184. package/src/home/suggested-prompts.ts +0 -9
  185. package/src/ipc/gateway-flag-listener.ts +123 -0
  186. package/src/ipc/skill-routes/registries.ts +8 -12
  187. package/src/memory/__tests__/db-async-query.test.ts +165 -0
  188. package/src/memory/__tests__/db-maintenance.test.ts +115 -0
  189. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
  190. package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
  191. package/src/memory/__tests__/memory-retrospective-job.test.ts +7 -0
  192. package/src/memory/auto-analysis-enqueue.ts +5 -1
  193. package/src/memory/conversation-crud.ts +71 -70
  194. package/src/memory/conversation-starters-cadence.ts +3 -1
  195. package/src/memory/conversation-title-service.ts +19 -3
  196. package/src/memory/db-async-query.ts +214 -0
  197. package/src/memory/db-init.ts +10 -0
  198. package/src/memory/db-maintenance.ts +30 -21
  199. package/src/memory/graph/bootstrap.ts +8 -1
  200. package/src/memory/graph/capability-seed.ts +7 -3
  201. package/src/memory/graph/conversation-graph-memory.ts +100 -17
  202. package/src/memory/graph/extraction.ts +1 -5
  203. package/src/memory/graph/graph-search.ts +7 -1
  204. package/src/memory/indexer.ts +28 -18
  205. package/src/memory/job-handlers/cleanup.ts +76 -18
  206. package/src/memory/job-handlers/conversation-starters.ts +1 -4
  207. package/src/memory/jobs/embed-pkb-file.ts +6 -1
  208. package/src/memory/jobs-store.ts +14 -0
  209. package/src/memory/jobs-worker.ts +55 -22
  210. package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
  211. package/src/memory/llm-request-log-source-local.ts +7 -0
  212. package/src/memory/llm-request-log-source.ts +9 -2
  213. package/src/memory/llm-request-log-store.ts +43 -1
  214. package/src/memory/llm-usage-store.ts +24 -0
  215. package/src/memory/memory-retrospective-enqueue.ts +8 -1
  216. package/src/memory/memory-retrospective-job.ts +5 -0
  217. package/src/memory/memory-v2-activation-log-store.ts +15 -6
  218. package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
  219. package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
  220. package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
  221. package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
  222. package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
  223. package/src/memory/migrations/index.ts +17 -0
  224. package/src/memory/migrations/registry.ts +33 -0
  225. package/src/memory/schema/conversations.ts +1 -1
  226. package/src/memory/schema/infrastructure.ts +21 -0
  227. package/src/memory/tool-usage-store.ts +36 -8
  228. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
  229. package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
  230. package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
  231. package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
  232. package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
  233. package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
  234. package/src/memory/v2/__tests__/injection.test.ts +127 -98
  235. package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
  236. package/src/memory/v2/__tests__/router.test.ts +171 -3
  237. package/src/memory/v2/harness/compare.ts +57 -0
  238. package/src/memory/v2/harness/metrics.ts +124 -0
  239. package/src/memory/v2/harness/oracle.ts +145 -0
  240. package/src/memory/v2/harness/replay-input.ts +224 -0
  241. package/src/memory/v2/harness/retriever.ts +74 -0
  242. package/src/memory/v2/harness/router-retriever.ts +43 -0
  243. package/src/memory/v2/harness/runner.ts +106 -0
  244. package/src/memory/v2/harness/trace.ts +58 -0
  245. package/src/memory/v2/injection.ts +21 -15
  246. package/src/memory/v2/prompts/router.ts +26 -1
  247. package/src/memory/v2/qdrant.ts +14 -2
  248. package/src/memory/v2/router.ts +171 -18
  249. package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
  250. package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
  251. package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
  252. package/src/memory/v3/__tests__/edges.test.ts +563 -0
  253. package/src/memory/v3/__tests__/filter.test.ts +512 -0
  254. package/src/memory/v3/__tests__/gate.test.ts +574 -0
  255. package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
  256. package/src/memory/v3/__tests__/loop.test.ts +530 -0
  257. package/src/memory/v3/__tests__/retriever.test.ts +226 -0
  258. package/src/memory/v3/__tests__/scouts.test.ts +440 -0
  259. package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
  260. package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
  261. package/src/memory/v3/__tests__/traversal.test.ts +469 -0
  262. package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
  263. package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
  264. package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
  265. package/src/memory/v3/__tests__/validate.test.ts +245 -0
  266. package/src/memory/v3/auto-edges.ts +223 -0
  267. package/src/memory/v3/coactivation-store.ts +124 -0
  268. package/src/memory/v3/consolidation-job.ts +323 -0
  269. package/src/memory/v3/edge-learning-job.ts +160 -0
  270. package/src/memory/v3/edges.ts +249 -0
  271. package/src/memory/v3/filter.ts +281 -0
  272. package/src/memory/v3/gate.ts +334 -0
  273. package/src/memory/v3/index-composition.ts +113 -0
  274. package/src/memory/v3/llm-capture.ts +46 -0
  275. package/src/memory/v3/loop.ts +382 -0
  276. package/src/memory/v3/maintenance.ts +144 -0
  277. package/src/memory/v3/prompt-context.ts +33 -0
  278. package/src/memory/v3/prompts/consolidation.ts +458 -0
  279. package/src/memory/v3/prompts/system-prompts.ts +196 -0
  280. package/src/memory/v3/retriever.ts +33 -0
  281. package/src/memory/v3/scouts.ts +420 -0
  282. package/src/memory/v3/shadow-middleware.ts +305 -0
  283. package/src/memory/v3/traversal.ts +206 -0
  284. package/src/memory/v3/tree-index.ts +237 -0
  285. package/src/memory/v3/tree-store.ts +394 -0
  286. package/src/memory/v3/tree-walk.ts +351 -0
  287. package/src/memory/v3/types.ts +65 -0
  288. package/src/memory/v3/validate.ts +300 -0
  289. package/src/notifications/adapters/macos.ts +18 -1
  290. package/src/notifications/adapters/platform.ts +1 -1
  291. package/src/notifications/decision-engine.ts +1 -4
  292. package/src/notifications/emit-signal.ts +29 -49
  293. package/src/permissions/prompter.ts +3 -3
  294. package/src/permissions/question-prompter.ts +5 -2
  295. package/src/permissions/secret-prompter.ts +2 -2
  296. package/src/plugin-api/index.ts +4 -0
  297. package/src/plugin-api/types.ts +7 -33
  298. package/src/plugins/defaults/index.ts +6 -0
  299. package/src/plugins/defaults/injectors.ts +18 -11
  300. package/src/plugins/external-plugin-loader.ts +5 -68
  301. package/src/plugins/types.ts +11 -16
  302. package/src/proactive-artifact/aux-message-injector.ts +17 -4
  303. package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
  304. package/src/prompts/persona-resolver.ts +36 -21
  305. package/src/prompts/sections.ts +39 -7
  306. package/src/prompts/system-prompt.ts +50 -185
  307. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  308. package/src/prompts/templates/system-sections.ts +230 -8
  309. package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
  310. package/src/providers/__tests__/retry-callsite.test.ts +85 -5
  311. package/src/providers/anthropic/client.ts +32 -66
  312. package/src/providers/call-site-routing.ts +14 -2
  313. package/src/providers/connection-model-compat.ts +38 -0
  314. package/src/providers/connection-resolution.ts +16 -2
  315. package/src/providers/gemini/client.ts +49 -6
  316. package/src/providers/inference/adapter-factory.ts +3 -0
  317. package/src/providers/minimax/client.ts +106 -0
  318. package/src/providers/model-catalog.ts +43 -0
  319. package/src/providers/model-intents.ts +1 -1
  320. package/src/providers/openai/chat-completions-provider.ts +6 -3
  321. package/src/providers/openai/codex-models.ts +18 -0
  322. package/src/providers/openai/responses-provider.ts +78 -21
  323. package/src/providers/provider-send-message.ts +7 -1
  324. package/src/providers/retry.ts +34 -3
  325. package/src/providers/thinking-config.ts +26 -1
  326. package/src/providers/usage-tracking.ts +2 -0
  327. package/src/runtime/AGENTS.md +2 -2
  328. package/src/runtime/agent-wake.ts +1 -0
  329. package/src/runtime/assistant-event-hub.ts +76 -6
  330. package/src/runtime/auth/route-policy.ts +36 -0
  331. package/src/runtime/btw-sidechain.ts +0 -6
  332. package/src/runtime/http-types.ts +0 -2
  333. package/src/runtime/migrations/vbundle-builder.ts +10 -3
  334. package/src/runtime/pending-interactions.ts +0 -1
  335. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +106 -0
  336. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +25 -6
  337. package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
  338. package/src/runtime/routes/acp-routes.test.ts +255 -6
  339. package/src/runtime/routes/acp-routes.ts +8 -1
  340. package/src/runtime/routes/avatar-routes.ts +10 -10
  341. package/src/runtime/routes/background-wake-routes.ts +188 -0
  342. package/src/runtime/routes/browser-tabs-routes.ts +200 -0
  343. package/src/runtime/routes/btw-routes.ts +0 -6
  344. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  345. package/src/runtime/routes/conversation-list-routes.ts +12 -4
  346. package/src/runtime/routes/conversation-management-routes.ts +77 -20
  347. package/src/runtime/routes/conversation-query-routes.ts +142 -36
  348. package/src/runtime/routes/conversation-routes.ts +252 -410
  349. package/src/runtime/routes/conversation-starter-routes.ts +6 -3
  350. package/src/runtime/routes/disk-pressure-routes.ts +1 -1
  351. package/src/runtime/routes/domain-routes.ts +60 -10
  352. package/src/runtime/routes/email-routes.ts +5 -2
  353. package/src/runtime/routes/events-routes.ts +54 -10
  354. package/src/runtime/routes/group-routes.ts +24 -8
  355. package/src/runtime/routes/host-browser-routes.ts +10 -2
  356. package/src/runtime/routes/host-cu-routes.ts +2 -2
  357. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
  358. package/src/runtime/routes/index.ts +8 -0
  359. package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
  360. package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
  361. package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
  362. package/src/runtime/routes/memory-item-routes.ts +8 -3
  363. package/src/runtime/routes/memory-v2-routes.ts +215 -5
  364. package/src/runtime/routes/memory-v3-routes.ts +316 -0
  365. package/src/runtime/routes/migration-routes.ts +21 -24
  366. package/src/runtime/routes/plugins-routes.ts +337 -0
  367. package/src/runtime/routes/rename-conversation-routes.ts +6 -2
  368. package/src/runtime/routes/secret-routes.ts +25 -5
  369. package/src/runtime/routes/settings-routes.ts +12 -11
  370. package/src/runtime/routes/slack-channel-routes.ts +5 -4
  371. package/src/runtime/routes/workspace-routes.ts +25 -10
  372. package/src/runtime/sync/resource-sync-events.ts +106 -38
  373. package/src/runtime/sync/sync-publisher.test.ts +49 -0
  374. package/src/runtime/sync/sync-publisher.ts +2 -1
  375. package/src/runtime/verification-outbound-actions.ts +73 -1
  376. package/src/telemetry/types.ts +12 -0
  377. package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
  378. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  379. package/src/tools/acp/spawn.test.ts +119 -0
  380. package/src/tools/acp/spawn.ts +15 -2
  381. package/src/tools/apps/definitions.ts +2 -8
  382. package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
  383. package/src/tools/ask-question/ask-question-tool.ts +38 -45
  384. package/src/tools/browser/__tests__/pinned-tabs.test.ts +70 -0
  385. package/src/tools/browser/browser-execution.ts +16 -3
  386. package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
  387. package/src/tools/browser/cdp-client/__tests__/types.test.ts +3 -0
  388. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +12 -0
  389. package/src/tools/browser/cdp-client/extension-cdp-client.ts +27 -1
  390. package/src/tools/browser/cdp-client/factory.ts +100 -17
  391. package/src/tools/browser/cdp-client/local-cdp-client.ts +12 -0
  392. package/src/tools/browser/cdp-client/types.ts +65 -0
  393. package/src/tools/browser/pinned-tabs.ts +96 -40
  394. package/src/tools/computer-use/definitions.ts +22 -78
  395. package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
  396. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
  397. package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
  398. package/src/tools/credentials/vault.ts +3 -9
  399. package/src/tools/document/document-tool.ts +59 -0
  400. package/src/tools/execution-target.ts +21 -23
  401. package/src/tools/executor.ts +6 -1
  402. package/src/tools/filesystem/edit.ts +3 -9
  403. package/src/tools/filesystem/list.ts +3 -9
  404. package/src/tools/filesystem/read.ts +3 -9
  405. package/src/tools/filesystem/write.ts +3 -9
  406. package/src/tools/host-filesystem/edit.ts +3 -9
  407. package/src/tools/host-filesystem/read.ts +3 -9
  408. package/src/tools/host-filesystem/transfer.ts +3 -9
  409. package/src/tools/host-filesystem/write.ts +3 -9
  410. package/src/tools/host-terminal/host-shell.ts +3 -9
  411. package/src/tools/mcp/mcp-tool-factory.ts +1 -8
  412. package/src/tools/memory/register.test.ts +1 -1
  413. package/src/tools/memory/register.ts +4 -9
  414. package/src/tools/network/web-fetch.ts +3 -9
  415. package/src/tools/network/web-search.ts +25 -32
  416. package/src/tools/registry.ts +7 -23
  417. package/src/tools/schema-transforms.ts +1 -1
  418. package/src/tools/skills/execute.ts +3 -9
  419. package/src/tools/skills/load.ts +3 -9
  420. package/src/tools/skills/skill-tool-factory.ts +1 -8
  421. package/src/tools/subagent/notify-parent.ts +3 -9
  422. package/src/tools/system/request-permission.ts +3 -9
  423. package/src/tools/terminal/shell.ts +3 -9
  424. package/src/tools/tool-defaults.ts +94 -0
  425. package/src/tools/types.ts +27 -98
  426. package/src/tools/ui-surface/definitions.ts +6 -22
  427. package/src/usage/pricing.ts +23 -0
  428. package/src/usage/types.ts +12 -0
  429. package/src/util/logger.ts +16 -7
  430. package/src/util/platform.ts +7 -2
  431. package/src/util/sqlite3-runtime.ts +65 -0
  432. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
  433. package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
  434. package/src/workspace/migrations/registry.ts +2 -0
  435. package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
  436. package/src/__tests__/message-complete-display-id.test.ts +0 -175
  437. package/src/daemon/query-complexity-router.ts +0 -75
  438. package/src/prompts/cache-boundary.ts +0 -8
@@ -0,0 +1,512 @@
1
+ /**
2
+ * Tests for the plugins route handlers in `plugins-routes.ts`.
3
+ *
4
+ * GET /v1/plugins (list):
5
+ * - Projection from `InstalledPluginInfo` → response shape (id, name,
6
+ * description, version, path; issues omitted when empty)
7
+ * - `?q=` substring filter (case-insensitive across id/name/description)
8
+ * - Trimming + empty-string fallthrough on `?q=`
9
+ * - Empty install dir → `{ plugins: [] }`
10
+ * - Issues array surfaced when present
11
+ *
12
+ * GET /v1/plugins/search (catalog search):
13
+ * - Forwards `?q=` and `?ref=` to the `searchPlugins` lib
14
+ * - Empty / missing `?q=` is passed through as the empty-regex
15
+ * ("match-all") query — the lib's documented contract
16
+ * - Wraps `InvalidSearchPatternError` into a 400 (BadRequestError)
17
+ * - Wraps unknown errors into a 500 (InternalError) with the lib's
18
+ * message preserved
19
+ * - Re-packs the lib's `readonly` match array into mutable arrays so
20
+ * downstream serializers can introspect freely
21
+ *
22
+ * DELETE /v1/plugins/:name (uninstall):
23
+ * - Forwards `pathParams.name` to the `uninstallPlugin` lib
24
+ * - Returns `{ name, target }` mirroring the lib's `UninstallPluginResult`
25
+ * - Maps `InvalidPluginNameError` → BadRequestError (400)
26
+ * - Maps `PluginNotInstalledError` → NotFoundError (404)
27
+ * - Maps unknown errors → InternalError (500) with message preserved
28
+ *
29
+ * The library functions themselves are covered by
30
+ * `assistant/src/cli/lib/__tests__/list-installed-plugins.test.ts`,
31
+ * `.../search-plugins.test.ts`, and `.../uninstall-plugin.test.ts`;
32
+ * here we mock them to isolate the route's wiring logic.
33
+ */
34
+
35
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
36
+
37
+ import { InvalidPluginNameError } from "../../../cli/lib/install-from-github.js";
38
+ import type { InstalledPluginInfo } from "../../../cli/lib/list-installed-plugins.js";
39
+ import type {
40
+ PluginSearchMatch,
41
+ SearchPluginsDeps,
42
+ SearchPluginsOptions,
43
+ SearchPluginsResult,
44
+ } from "../../../cli/lib/search-plugins.js";
45
+ import { InvalidSearchPatternError } from "../../../cli/lib/search-plugins.js";
46
+ import {
47
+ PluginNotInstalledError,
48
+ type UninstallPluginOptions,
49
+ type UninstallPluginResult,
50
+ } from "../../../cli/lib/uninstall-plugin.js";
51
+
52
+ // Mutable list returned by the mocked library function. Tests reassign
53
+ // `installedFixture` before invoking the handler.
54
+ let installedFixture: InstalledPluginInfo[] = [];
55
+
56
+ mock.module("../../../cli/lib/list-installed-plugins.js", () => ({
57
+ listInstalledPlugins: () => installedFixture,
58
+ }));
59
+
60
+ // Mock searchPlugins: `searchSpy` records every invocation; the implementation
61
+ // returns whatever `searchResult` is set to (or throws `searchError` when set).
62
+ const searchSpy = mock(
63
+ async (
64
+ _opts: SearchPluginsOptions,
65
+ _deps: SearchPluginsDeps,
66
+ ): Promise<SearchPluginsResult> => {
67
+ throw new Error("searchSpy default impl not configured");
68
+ },
69
+ );
70
+
71
+ mock.module("../../../cli/lib/search-plugins.js", () => ({
72
+ // Pass through the real error classes — the route handler checks
73
+ // `instanceof InvalidSearchPatternError`.
74
+ InvalidSearchPatternError,
75
+ searchPlugins: searchSpy,
76
+ }));
77
+
78
+ // Mock uninstallPlugin. The handler's error mapping is the wiring under
79
+ // test — the lib's own behavior is covered separately.
80
+ const uninstallSpy = mock(
81
+ (_opts: UninstallPluginOptions): UninstallPluginResult => {
82
+ throw new Error("uninstallSpy default impl not configured");
83
+ },
84
+ );
85
+
86
+ mock.module("../../../cli/lib/uninstall-plugin.js", () => ({
87
+ // Pass through error classes — the handler checks `instanceof`.
88
+ PluginNotInstalledError,
89
+ uninstallPlugin: uninstallSpy,
90
+ }));
91
+
92
+ // `InvalidPluginNameError` is re-exported from uninstall-plugin.js but
93
+ // the canonical definition lives in install-from-github.js. The
94
+ // handler imports from install-from-github.js, so mock that too so the
95
+ // `instanceof` check inside the handler resolves to the same class as
96
+ // the one the spy throws.
97
+ mock.module("../../../cli/lib/install-from-github.js", () => ({
98
+ InvalidPluginNameError,
99
+ }));
100
+
101
+ import { BadRequestError, InternalError, NotFoundError } from "../errors.js";
102
+ import { ROUTES as PLUGINS_ROUTES } from "../plugins-routes.js";
103
+ import type { RouteDefinition, RouteHandlerArgs } from "../types.js";
104
+
105
+ function findHandler(operationId: string): RouteDefinition["handler"] {
106
+ const route = PLUGINS_ROUTES.find((r) => r.operationId === operationId);
107
+ if (!route) throw new Error(`Route ${operationId} not found`);
108
+ return route.handler;
109
+ }
110
+
111
+ const listHandler = findHandler("plugins_list");
112
+ const searchHandler = findHandler("plugins_search");
113
+ const uninstallHandler = findHandler("plugins_uninstall");
114
+
115
+ function invoke(
116
+ args: RouteHandlerArgs = {},
117
+ ): { plugins: Array<Record<string, unknown>> } {
118
+ return listHandler(args) as { plugins: Array<Record<string, unknown>> };
119
+ }
120
+
121
+ async function invokeSearch(args: RouteHandlerArgs = {}): Promise<{
122
+ query: string;
123
+ ref: string;
124
+ matches: PluginSearchMatch[];
125
+ }> {
126
+ return (await searchHandler(args)) as {
127
+ query: string;
128
+ ref: string;
129
+ matches: PluginSearchMatch[];
130
+ };
131
+ }
132
+
133
+ function pluginEntry(
134
+ overrides: Partial<InstalledPluginInfo> & { name: string },
135
+ ): InstalledPluginInfo {
136
+ return {
137
+ name: overrides.name,
138
+ target: overrides.target ?? `/tmp/plugins/${overrides.name}`,
139
+ packageJson: overrides.packageJson ?? null,
140
+ issues: overrides.issues ?? [],
141
+ };
142
+ }
143
+
144
+ beforeEach(() => {
145
+ installedFixture = [];
146
+ });
147
+
148
+ describe("GET /v1/plugins", () => {
149
+ test("returns { plugins: [] } when nothing is installed", () => {
150
+ expect(invoke()).toEqual({ plugins: [] });
151
+ });
152
+
153
+ test("projects InstalledPluginInfo → response shape with all fields populated", () => {
154
+ installedFixture = [
155
+ pluginEntry({
156
+ name: "alpha",
157
+ target: "/workspace/plugins/alpha",
158
+ packageJson: {
159
+ name: "alpha",
160
+ version: "1.2.3",
161
+ description: "Alpha plugin",
162
+ },
163
+ }),
164
+ ];
165
+
166
+ const result = invoke();
167
+ expect(result.plugins).toHaveLength(1);
168
+ expect(result.plugins[0]).toEqual({
169
+ id: "alpha",
170
+ name: "alpha",
171
+ description: "Alpha plugin",
172
+ version: "1.2.3",
173
+ path: "/workspace/plugins/alpha",
174
+ });
175
+ // `issues` is omitted (not just undefined) when the entry is clean.
176
+ expect("issues" in result.plugins[0]!).toBe(false);
177
+ });
178
+
179
+ test("uses directory name for `id` and `name` even when package.json#name is scoped", () => {
180
+ installedFixture = [
181
+ pluginEntry({
182
+ name: "fancy-plugin",
183
+ packageJson: {
184
+ name: "@vendor/fancy-plugin",
185
+ version: "0.0.1",
186
+ description: undefined,
187
+ },
188
+ }),
189
+ ];
190
+
191
+ const [entry] = invoke().plugins;
192
+ expect(entry?.id).toBe("fancy-plugin");
193
+ expect(entry?.name).toBe("fancy-plugin");
194
+ });
195
+
196
+ test("nulls description and version when package.json is missing or partial", () => {
197
+ installedFixture = [
198
+ pluginEntry({ name: "no-pkg-json", packageJson: null }),
199
+ pluginEntry({
200
+ name: "partial",
201
+ packageJson: { name: "partial" }, // no version / description
202
+ }),
203
+ ];
204
+
205
+ const [missing, partial] = invoke().plugins;
206
+ expect(missing).toMatchObject({
207
+ id: "no-pkg-json",
208
+ description: null,
209
+ version: null,
210
+ });
211
+ expect(partial).toMatchObject({
212
+ id: "partial",
213
+ description: null,
214
+ version: null,
215
+ });
216
+ });
217
+
218
+ test("surfaces non-fatal issues array when present", () => {
219
+ installedFixture = [
220
+ pluginEntry({
221
+ name: "broken",
222
+ packageJson: null,
223
+ issues: ["missing package.json"],
224
+ }),
225
+ ];
226
+
227
+ const [entry] = invoke().plugins;
228
+ expect(entry?.issues).toEqual(["missing package.json"]);
229
+ });
230
+
231
+ test("?q= filters case-insensitively on id, name, and description", () => {
232
+ installedFixture = [
233
+ pluginEntry({
234
+ name: "calendar-sync",
235
+ packageJson: {
236
+ name: "calendar-sync",
237
+ version: "1.0.0",
238
+ description: "Sync events with Google Calendar",
239
+ },
240
+ }),
241
+ pluginEntry({
242
+ name: "weather",
243
+ packageJson: {
244
+ name: "weather",
245
+ version: "1.0.0",
246
+ description: "Show local conditions",
247
+ },
248
+ }),
249
+ pluginEntry({
250
+ name: "todo",
251
+ packageJson: {
252
+ name: "todo",
253
+ version: "1.0.0",
254
+ description: "Lightweight todo manager",
255
+ },
256
+ }),
257
+ ];
258
+
259
+ // id match
260
+ expect(
261
+ invoke({ queryParams: { q: "calendar" } }).plugins.map((p) => p.id),
262
+ ).toEqual(["calendar-sync"]);
263
+
264
+ // description match (case-insensitive)
265
+ expect(
266
+ invoke({ queryParams: { q: "GOOGLE" } }).plugins.map((p) => p.id),
267
+ ).toEqual(["calendar-sync"]);
268
+
269
+ // matches multiple
270
+ expect(
271
+ invoke({ queryParams: { q: "o" } }).plugins.map((p) => p.id).sort(),
272
+ ).toEqual(["calendar-sync", "todo", "weather"].sort());
273
+
274
+ // no match
275
+ expect(invoke({ queryParams: { q: "zzz" } }).plugins).toEqual([]);
276
+ });
277
+
278
+ test("?q= is trimmed; whitespace-only treated as no filter", () => {
279
+ installedFixture = [
280
+ pluginEntry({ name: "alpha" }),
281
+ pluginEntry({ name: "beta" }),
282
+ ];
283
+
284
+ expect(
285
+ invoke({ queryParams: { q: " " } }).plugins.map((p) => p.id),
286
+ ).toEqual(["alpha", "beta"]);
287
+ });
288
+
289
+ test("preserves the order returned by listInstalledPlugins", () => {
290
+ installedFixture = [
291
+ pluginEntry({ name: "alpha" }),
292
+ pluginEntry({ name: "beta" }),
293
+ pluginEntry({ name: "zeta" }),
294
+ ];
295
+
296
+ expect(invoke().plugins.map((p) => p.id)).toEqual([
297
+ "alpha",
298
+ "beta",
299
+ "zeta",
300
+ ]);
301
+ });
302
+ });
303
+
304
+ // ---------------------------------------------------------------------------
305
+ // GET /v1/plugins/search
306
+ // ---------------------------------------------------------------------------
307
+
308
+ describe("GET /v1/plugins/search", () => {
309
+ beforeEach(() => {
310
+ searchSpy.mockClear();
311
+ // Default to a happy-path resolution; individual tests override as needed.
312
+ searchSpy.mockImplementation(async (opts) => ({
313
+ query: opts.query,
314
+ ref: opts.ref ?? "main",
315
+ matches: [],
316
+ }));
317
+ });
318
+
319
+ test("forwards ?q= and ?ref= to searchPlugins; returns its result", async () => {
320
+ searchSpy.mockImplementation(async (opts) => ({
321
+ query: opts.query,
322
+ ref: opts.ref ?? "main",
323
+ matches: [
324
+ { name: "simple-memory", path: "experimental/plugins/simple-memory" },
325
+ { name: "simple-router", path: "experimental/plugins/simple-router" },
326
+ ],
327
+ }));
328
+
329
+ const result = await invokeSearch({
330
+ queryParams: { q: "^simple", ref: "my-feature-branch" },
331
+ });
332
+
333
+ // The lib received the exact query + ref we surfaced from the route.
334
+ expect(searchSpy).toHaveBeenCalledTimes(1);
335
+ const [opts] = searchSpy.mock.calls[0]!;
336
+ expect(opts.query).toBe("^simple");
337
+ expect(opts.ref).toBe("my-feature-branch");
338
+
339
+ expect(result).toEqual({
340
+ query: "^simple",
341
+ ref: "my-feature-branch",
342
+ matches: [
343
+ { name: "simple-memory", path: "experimental/plugins/simple-memory" },
344
+ { name: "simple-router", path: "experimental/plugins/simple-router" },
345
+ ],
346
+ });
347
+ });
348
+
349
+ test("missing ?q= passes empty string through (match-all per lib contract)", async () => {
350
+ await invokeSearch();
351
+ const [opts] = searchSpy.mock.calls[0]!;
352
+ expect(opts.query).toBe("");
353
+ // ref is omitted (undefined) when caller doesn't supply one — lib
354
+ // applies its DEFAULT_PLUGIN_REF fallback.
355
+ expect(opts.ref).toBeUndefined();
356
+ });
357
+
358
+ test("whitespace-only ?ref= is treated as missing", async () => {
359
+ await invokeSearch({ queryParams: { q: "x", ref: " " } });
360
+ const [opts] = searchSpy.mock.calls[0]!;
361
+ expect(opts.ref).toBeUndefined();
362
+ });
363
+
364
+ test("supplies a bound globalThis.fetch as the lib's fetch dep", async () => {
365
+ await invokeSearch({ queryParams: { q: "memory" } });
366
+ const [, deps] = searchSpy.mock.calls[0]!;
367
+ expect(typeof deps.fetch).toBe("function");
368
+ });
369
+
370
+ test("InvalidSearchPatternError → BadRequestError (400)", async () => {
371
+ searchSpy.mockImplementation(async () => {
372
+ throw new InvalidSearchPatternError("(", new SyntaxError("unbalanced"));
373
+ });
374
+
375
+ await expect(
376
+ invokeSearch({ queryParams: { q: "(" } }),
377
+ ).rejects.toBeInstanceOf(BadRequestError);
378
+ });
379
+
380
+ test("unknown errors → InternalError with original message preserved", async () => {
381
+ searchSpy.mockImplementation(async () => {
382
+ throw new Error("GitHub contents listing failed: HTTP 502");
383
+ });
384
+
385
+ await expect(
386
+ invokeSearch({ queryParams: { q: "memory" } }),
387
+ ).rejects.toMatchObject({
388
+ // The route wraps in InternalError so callers see a 500 response
389
+ // with the upstream message attached.
390
+ constructor: InternalError,
391
+ message: expect.stringContaining("GitHub contents listing failed"),
392
+ });
393
+ });
394
+
395
+ test("re-packs readonly match array into a mutable copy", async () => {
396
+ const frozenMatches = Object.freeze([
397
+ Object.freeze({ name: "a", path: "experimental/plugins/a" }),
398
+ ]) as readonly PluginSearchMatch[];
399
+ searchSpy.mockImplementation(async (opts) => ({
400
+ query: opts.query,
401
+ ref: opts.ref ?? "main",
402
+ matches: frozenMatches,
403
+ }));
404
+
405
+ const result = await invokeSearch({ queryParams: { q: "a" } });
406
+ // The route returns a non-frozen array we can mutate without
407
+ // touching the lib's internal cache. This matters when serializers
408
+ // (or downstream test fixtures) reach in.
409
+ expect(Object.isFrozen(result.matches)).toBe(false);
410
+ expect(() => result.matches.push({ name: "b", path: "x" })).not.toThrow();
411
+ });
412
+ });
413
+
414
+ // ---------------------------------------------------------------------------
415
+ // DELETE /v1/plugins/:name (uninstall)
416
+ // ---------------------------------------------------------------------------
417
+
418
+ function invokeUninstall(args: RouteHandlerArgs = {}): {
419
+ name: string;
420
+ target: string;
421
+ } {
422
+ return uninstallHandler(args) as { name: string; target: string };
423
+ }
424
+
425
+ describe("DELETE /v1/plugins/:name", () => {
426
+ beforeEach(() => {
427
+ uninstallSpy.mockReset();
428
+ });
429
+
430
+ test("forwards pathParams.name to uninstallPlugin and returns its result", () => {
431
+ uninstallSpy.mockImplementation((opts) => ({
432
+ name: opts.name,
433
+ target: `/workspace/.vellum/plugins/${opts.name}`,
434
+ }));
435
+
436
+ const result = invokeUninstall({ pathParams: { name: "simple-memory" } });
437
+
438
+ expect(uninstallSpy.mock.calls).toHaveLength(1);
439
+ expect(uninstallSpy.mock.calls[0]?.[0]).toEqual({ name: "simple-memory" });
440
+ expect(result).toEqual({
441
+ name: "simple-memory",
442
+ target: "/workspace/.vellum/plugins/simple-memory",
443
+ });
444
+ });
445
+
446
+ test("missing pathParams.name passes the empty string through to the lib", () => {
447
+ // The lib's `sanitizePluginName` is the validator of last resort —
448
+ // the route hands off the raw value without pre-trimming. The lib
449
+ // rejects empty strings, which the handler maps to 400 below.
450
+ uninstallSpy.mockImplementation(() => {
451
+ throw new InvalidPluginNameError(
452
+ 'Invalid plugin name "" — must match /^[a-z][a-z0-9-]{0,63}$/.',
453
+ );
454
+ });
455
+
456
+ expect(() => invokeUninstall({})).toThrow(BadRequestError);
457
+ expect(uninstallSpy.mock.calls[0]?.[0]).toEqual({ name: "" });
458
+ });
459
+
460
+ test("InvalidPluginNameError → BadRequestError (400)", () => {
461
+ uninstallSpy.mockImplementation(() => {
462
+ throw new InvalidPluginNameError("bad name ../escape");
463
+ });
464
+
465
+ expect(() =>
466
+ invokeUninstall({ pathParams: { name: "../escape" } }),
467
+ ).toThrow(BadRequestError);
468
+ });
469
+
470
+ test("PluginNotInstalledError → NotFoundError (404)", () => {
471
+ uninstallSpy.mockImplementation((opts) => {
472
+ throw new PluginNotInstalledError(
473
+ opts.name,
474
+ `/workspace/.vellum/plugins/${opts.name}`,
475
+ );
476
+ });
477
+
478
+ expect(() => invokeUninstall({ pathParams: { name: "ghost" } })).toThrow(
479
+ NotFoundError,
480
+ );
481
+ });
482
+
483
+ test("unknown errors → InternalError with original message preserved", () => {
484
+ uninstallSpy.mockImplementation(() => {
485
+ throw new Error("EBUSY: resource busy or locked");
486
+ });
487
+
488
+ expect(() =>
489
+ invokeUninstall({ pathParams: { name: "simple-memory" } }),
490
+ ).toThrow(InternalError);
491
+ try {
492
+ invokeUninstall({ pathParams: { name: "simple-memory" } });
493
+ } catch (err) {
494
+ expect((err as Error).message).toContain("EBUSY");
495
+ }
496
+ });
497
+
498
+ test("non-Error throws fall through to InternalError with a default message", () => {
499
+ uninstallSpy.mockImplementation(() => {
500
+ throw "boom"; // emulates a poorly-typed throwable from the lib chain
501
+ });
502
+
503
+ let caught: unknown;
504
+ try {
505
+ invokeUninstall({ pathParams: { name: "simple-memory" } });
506
+ } catch (err) {
507
+ caught = err;
508
+ }
509
+ expect(caught).toBeInstanceOf(InternalError);
510
+ expect((caught as Error).message).toBe("plugin uninstall failed");
511
+ });
512
+ });