@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091926.ebb2d62

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 (490) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/docs/activation-funnel-telemetry.md +310 -0
  4. package/examples/plugins/echo/README.md +61 -60
  5. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  6. package/examples/plugins/echo/hooks/stop.ts +16 -0
  7. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  8. package/examples/plugins/echo/package.json +1 -2
  9. package/examples/plugins/echo/src/emit.ts +19 -0
  10. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  11. package/openapi.yaml +273 -121
  12. package/package.json +2 -2
  13. package/scripts/sync-llm-catalog.ts +2 -0
  14. package/src/__tests__/activation-early-marking.test.ts +120 -0
  15. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  16. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  17. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  18. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  19. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  21. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  22. package/src/__tests__/agent-loop.test.ts +441 -96
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  24. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  25. package/src/__tests__/anthropic-provider.test.ts +23 -8
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  27. package/src/__tests__/app-control-flow.test.ts +1 -1
  28. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  29. package/src/__tests__/app-executors.test.ts +132 -0
  30. package/src/__tests__/approval-cascade.test.ts +6 -5
  31. package/src/__tests__/approval-routes-http.test.ts +4 -1
  32. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  33. package/src/__tests__/build-persisted-content.test.ts +75 -1
  34. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  35. package/src/__tests__/channel-approvals.test.ts +1 -1
  36. package/src/__tests__/compaction-circuit.test.ts +258 -0
  37. package/src/__tests__/compaction-direct.test.ts +146 -0
  38. package/src/__tests__/compaction-events.test.ts +7 -7
  39. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  40. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  41. package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
  42. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  43. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  48. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  49. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  50. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  51. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  52. package/src/__tests__/conversation-error.test.ts +15 -1
  53. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  54. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  55. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  56. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  57. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  58. package/src/__tests__/conversation-queue.test.ts +10 -10
  59. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  60. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  61. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  62. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  63. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  64. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  65. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  66. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  67. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  68. package/src/__tests__/conversation-undo.test.ts +2 -2
  69. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  70. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  71. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  72. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  73. package/src/__tests__/corrected-target.test.ts +93 -0
  74. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -4
  75. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  76. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  77. package/src/__tests__/db-acp-history.test.ts +101 -0
  78. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  79. package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
  80. package/src/__tests__/empty-response-hook.test.ts +1 -1
  81. package/src/__tests__/file-write-tool.test.ts +63 -0
  82. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  83. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  84. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  85. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  86. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  87. package/src/__tests__/heartbeat-service.test.ts +1 -0
  88. package/src/__tests__/history-repair-hook.test.ts +5 -1
  89. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  90. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  91. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  92. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  93. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  94. package/src/__tests__/injector-background-turn.test.ts +13 -23
  95. package/src/__tests__/injector-chain.test.ts +268 -44
  96. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  97. package/src/__tests__/injector-document-comments.test.ts +97 -114
  98. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  99. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  100. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  101. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  102. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  103. package/src/__tests__/llm-resolver.test.ts +73 -0
  104. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  105. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  106. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  107. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  108. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  109. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  110. package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
  111. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  112. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  113. package/src/__tests__/plugin-registry.test.ts +3 -49
  114. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  115. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  116. package/src/__tests__/plugin-types.test.ts +0 -70
  117. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  118. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  119. package/src/__tests__/published-app-updater.test.ts +138 -0
  120. package/src/__tests__/reaction-persistence.test.ts +1 -1
  121. package/src/__tests__/retry-thinking-adaptive-only.test.ts +245 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  123. package/src/__tests__/server-history-render.test.ts +39 -0
  124. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  125. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  126. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  127. package/src/__tests__/subagent-detail.test.ts +25 -7
  128. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  129. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  130. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  131. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  132. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  133. package/src/__tests__/title-generate-hook.test.ts +5 -1
  134. package/src/__tests__/tool-error-hook.test.ts +1 -1
  135. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  136. package/src/__tests__/tool-start-timestamp.test.ts +218 -0
  137. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  138. package/src/__tests__/web-fetch.test.ts +45 -0
  139. package/src/acp/__tests__/agent-process.test.ts +161 -0
  140. package/src/acp/__tests__/client-handler.test.ts +40 -0
  141. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  142. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  143. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  144. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  145. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  146. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  147. package/src/acp/agent-process.ts +61 -1
  148. package/src/acp/auto-install.test.ts +280 -0
  149. package/src/acp/auto-install.ts +232 -0
  150. package/src/acp/client-handler.ts +31 -0
  151. package/src/acp/prepare-agent-env.ts +80 -27
  152. package/src/acp/resolve-agent.test.ts +188 -28
  153. package/src/acp/resolve-agent.ts +119 -42
  154. package/src/acp/resume-hint.ts +23 -0
  155. package/src/acp/session-manager.ts +507 -73
  156. package/src/agent/compaction-circuit.ts +60 -102
  157. package/src/agent/loop.ts +403 -251
  158. package/src/api/events/tool-result.ts +9 -0
  159. package/src/api/events/tool-use-start.ts +7 -0
  160. package/src/api/responses/conversation-message.ts +72 -13
  161. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  162. package/src/approvals/guardian-request-resolvers.ts +27 -1
  163. package/src/background-wake/next-wake.ts +1 -0
  164. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  165. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  166. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  167. package/src/cli/commands/memory-v3.ts +57 -199
  168. package/src/cli/commands/plugins.ts +43 -37
  169. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  170. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  171. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  172. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  173. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  174. package/src/cli/lib/install-from-github.ts +797 -60
  175. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  176. package/src/cli/lib/plugin-details.ts +416 -0
  177. package/src/cli/lib/plugin-marketplace.ts +215 -0
  178. package/src/cli/lib/search-plugins.ts +201 -29
  179. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  180. package/src/config/acp-defaults.test.ts +10 -0
  181. package/src/config/acp-defaults.ts +9 -3
  182. package/src/config/acp-schema.ts +1 -7
  183. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  184. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  185. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  186. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  187. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  188. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  189. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  190. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  191. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  192. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  193. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  194. package/src/config/bundled-tool-registry.ts +4 -0
  195. package/src/config/call-site-defaults.ts +0 -3
  196. package/src/config/feature-flag-registry.json +10 -56
  197. package/src/config/llm-resolver.ts +39 -7
  198. package/src/config/loader.ts +5 -22
  199. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  200. package/src/config/schemas/call-site-catalog.ts +0 -21
  201. package/src/config/schemas/heartbeat.ts +9 -0
  202. package/src/config/schemas/llm.ts +17 -3
  203. package/src/config/schemas/memory-v3.ts +58 -8
  204. package/src/config/seed-inference-profiles.ts +18 -0
  205. package/src/context/compactor.ts +22 -4
  206. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  207. package/src/context/strip-injections.ts +8 -2
  208. package/src/daemon/conversation-agent-loop-handlers.ts +120 -42
  209. package/src/daemon/conversation-agent-loop.ts +308 -1297
  210. package/src/daemon/conversation-error.ts +31 -4
  211. package/src/daemon/conversation-history.ts +1 -1
  212. package/src/daemon/conversation-lifecycle.ts +11 -255
  213. package/src/daemon/conversation-media-retry.ts +19 -6
  214. package/src/daemon/conversation-messaging.ts +17 -0
  215. package/src/daemon/conversation-process.ts +22 -141
  216. package/src/daemon/conversation-queue-manager.ts +8 -0
  217. package/src/daemon/conversation-registry.ts +159 -0
  218. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  219. package/src/daemon/conversation-store.ts +9 -90
  220. package/src/daemon/conversation-surfaces.ts +165 -11
  221. package/src/daemon/conversation-workspace.ts +17 -0
  222. package/src/daemon/conversation.ts +444 -62
  223. package/src/daemon/disk-pressure-policy.ts +0 -1
  224. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  225. package/src/daemon/handlers/config-channels.ts +11 -2
  226. package/src/daemon/handlers/conversations.ts +3 -1
  227. package/src/daemon/handlers/shared.ts +9 -1
  228. package/src/daemon/handlers/skills.ts +4 -1
  229. package/src/daemon/host-app-control-proxy.ts +72 -57
  230. package/src/daemon/host-proxy-preactivation.ts +1 -3
  231. package/src/daemon/lifecycle.ts +21 -7
  232. package/src/daemon/persist-unsendable-image.ts +62 -25
  233. package/src/daemon/process-message.ts +1 -1
  234. package/src/daemon/server.ts +2 -0
  235. package/src/daemon/tool-side-effects.ts +15 -0
  236. package/src/daemon/wake-conversation-ops.ts +269 -0
  237. package/src/embedded/plugin-api.ts +2 -2
  238. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  239. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  240. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  241. package/src/heartbeat/heartbeat-service.ts +26 -0
  242. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  243. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  244. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  245. package/src/ipc/skill-routes/memory.ts +4 -2
  246. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  247. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  248. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  249. package/src/memory/activation-session-store.ts +43 -0
  250. package/src/memory/conversation-crud.ts +29 -19
  251. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  252. package/src/memory/db-init.ts +6 -0
  253. package/src/memory/embedding-backend.ts +5 -22
  254. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  255. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  256. package/src/memory/jobs-worker.ts +25 -1
  257. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  258. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  259. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  260. package/src/memory/migrations/index.ts +3 -0
  261. package/src/memory/onboarding-events-store.ts +66 -18
  262. package/src/memory/schema/acp.ts +4 -0
  263. package/src/memory/schema/infrastructure.ts +13 -0
  264. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  265. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  266. package/src/memory/v2/consolidation-job.ts +2 -63
  267. package/src/memory/v2/types.ts +5 -0
  268. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  269. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  270. package/src/notifications/access-request-copy.ts +298 -0
  271. package/src/notifications/adapters/slack.ts +3 -3
  272. package/src/notifications/adapters/telegram.ts +9 -2
  273. package/src/notifications/copy-composer.ts +49 -267
  274. package/src/notifications/decision-engine.ts +16 -35
  275. package/src/notifications/home-feed-side-effect.ts +1 -6
  276. package/src/plugin-api/constants.ts +4 -0
  277. package/src/plugin-api/index.ts +40 -11
  278. package/src/plugin-api/types.ts +108 -0
  279. package/src/plugins/defaults/compaction/compact.ts +106 -0
  280. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  281. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  282. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  283. package/src/plugins/defaults/compaction/package.json +1 -2
  284. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  285. package/src/plugins/defaults/empty-response/package.json +0 -1
  286. package/src/plugins/defaults/history-repair/package.json +0 -1
  287. package/src/plugins/defaults/index.ts +156 -26
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  290. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  291. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  292. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  293. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  297. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  298. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  299. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  300. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  301. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  304. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  306. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  307. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  308. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  309. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  310. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  311. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  312. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  313. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  314. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  315. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  316. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  317. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  318. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  319. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  321. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  322. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  323. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  324. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  325. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  326. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  328. package/src/plugins/defaults/title-generate/package.json +0 -1
  329. package/src/plugins/defaults/tool-error/package.json +0 -1
  330. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  331. package/src/plugins/external-api.ts +12 -2
  332. package/src/plugins/pipeline.ts +6 -293
  333. package/src/plugins/registry.ts +9 -37
  334. package/src/plugins/types.ts +76 -381
  335. package/src/plugins/user-loader.ts +30 -127
  336. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  337. package/src/prompts/system-prompt.ts +61 -10
  338. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  339. package/src/providers/anthropic/client.ts +9 -10
  340. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  341. package/src/providers/inference/logit-bias.ts +55 -0
  342. package/src/providers/model-catalog.ts +57 -0
  343. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  344. package/src/providers/openai/chat-completions-provider.ts +44 -1
  345. package/src/providers/retry.ts +57 -7
  346. package/src/providers/types.ts +6 -0
  347. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  348. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  349. package/src/runtime/agent-wake.ts +108 -209
  350. package/src/runtime/assistant-event-hub.ts +1 -1
  351. package/src/runtime/channel-approvals.ts +1 -1
  352. package/src/runtime/interactive-ui.ts +1 -1
  353. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  354. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  355. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  356. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  357. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  358. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  359. package/src/runtime/routes/acp-routes.test.ts +185 -32
  360. package/src/runtime/routes/acp-routes.ts +328 -30
  361. package/src/runtime/routes/app-management-routes.ts +3 -0
  362. package/src/runtime/routes/approval-routes.ts +1 -1
  363. package/src/runtime/routes/browser-routes.ts +1 -1
  364. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  365. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  366. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  367. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  368. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  369. package/src/runtime/routes/conversation-routes.ts +30 -2
  370. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  371. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  372. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  373. package/src/runtime/routes/host-cu-routes.ts +1 -1
  374. package/src/runtime/routes/identity-routes.ts +18 -3
  375. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  376. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  377. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  378. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  379. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  380. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  381. package/src/runtime/routes/playground/helpers.ts +1 -1
  382. package/src/runtime/routes/plugins-routes.ts +337 -35
  383. package/src/runtime/routes/stt-routes.ts +45 -12
  384. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  385. package/src/runtime/routes/work-items-routes.ts +2 -4
  386. package/src/runtime/routes/workspace-routes.ts +50 -15
  387. package/src/runtime/services/conversation-serializer.ts +1 -1
  388. package/src/runtime/verification-outbound-actions.ts +147 -2
  389. package/src/runtime/verification-templates.ts +29 -3
  390. package/src/services/published-app-updater.ts +30 -8
  391. package/src/signals/cancel.ts +2 -4
  392. package/src/subagent/manager.ts +21 -5
  393. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  394. package/src/telemetry/activation-funnel.ts +167 -0
  395. package/src/telemetry/types.ts +13 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  397. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  398. package/src/tools/acp/context.ts +20 -0
  399. package/src/tools/acp/list-agents.test.ts +9 -19
  400. package/src/tools/acp/list-agents.ts +3 -15
  401. package/src/tools/acp/spawn.test.ts +173 -202
  402. package/src/tools/acp/spawn.ts +37 -172
  403. package/src/tools/acp/steer.test.ts +105 -8
  404. package/src/tools/acp/steer.ts +48 -17
  405. package/src/tools/apps/executors.ts +166 -50
  406. package/src/tools/browser/browser-execution.ts +12 -2
  407. package/src/tools/filesystem/write.ts +34 -0
  408. package/src/tools/network/web-fetch.ts +65 -24
  409. package/src/tools/skills/load.ts +1 -1
  410. package/src/tools/subagent/spawn.ts +2 -4
  411. package/src/tools/ui-surface/definitions.ts +32 -5
  412. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  413. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  414. package/docs/plugins.md +0 -832
  415. package/examples/plugins/echo/register.ts +0 -143
  416. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  417. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  418. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  419. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  420. package/src/__tests__/pipeline-runner.test.ts +0 -554
  421. package/src/daemon/wake-target-adapter.ts +0 -253
  422. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  423. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  424. package/src/memory/v3/__tests__/core.test.ts +0 -39
  425. package/src/memory/v3/__tests__/health.test.ts +0 -219
  426. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  427. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  428. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  429. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  430. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  431. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  432. package/src/memory/v3/__tests__/router.test.ts +0 -332
  433. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  434. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  435. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  436. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  437. package/src/memory/v3/assign.ts +0 -268
  438. package/src/memory/v3/capabilities.ts +0 -124
  439. package/src/memory/v3/core.ts +0 -26
  440. package/src/memory/v3/health.ts +0 -0
  441. package/src/memory/v3/maintain-job.ts +0 -314
  442. package/src/memory/v3/needle.ts +0 -115
  443. package/src/memory/v3/orchestrate.ts +0 -126
  444. package/src/memory/v3/page-content.ts +0 -34
  445. package/src/memory/v3/provider-blocks.ts +0 -26
  446. package/src/memory/v3/reconcile.ts +0 -523
  447. package/src/memory/v3/router.ts +0 -190
  448. package/src/memory/v3/selection-log-store.ts +0 -84
  449. package/src/memory/v3/selector.ts +0 -226
  450. package/src/memory/v3/shadow-plugin.ts +0 -411
  451. package/src/memory/v3/snapshot.ts +0 -209
  452. package/src/memory/v3/tree.ts +0 -174
  453. package/src/memory/v3/types.ts +0 -59
  454. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  455. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  456. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  457. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  458. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  459. package/src/plugins/defaults/compaction/register.ts +0 -35
  460. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  461. package/src/plugins/defaults/empty-response/register.ts +0 -23
  462. package/src/plugins/defaults/history-repair/register.ts +0 -24
  463. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  464. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  465. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  466. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  467. package/src/plugins/defaults/title-generate/register.ts +0 -35
  468. package/src/plugins/defaults/tool-error/register.ts +0 -23
  469. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  470. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  471. package/src/proactive-artifact/decision.test.ts +0 -226
  472. package/src/proactive-artifact/decision.ts +0 -165
  473. package/src/proactive-artifact/index.ts +0 -7
  474. package/src/proactive-artifact/job.test.ts +0 -962
  475. package/src/proactive-artifact/job.ts +0 -372
  476. package/src/proactive-artifact/message-copy.ts +0 -58
  477. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  478. package/src/proactive-artifact/trigger-state.ts +0 -123
  479. package/src/util/map-limit.ts +0 -27
  480. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  481. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  482. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  483. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  484. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  485. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  486. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  487. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  488. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  489. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  490. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
@@ -34,15 +34,26 @@
34
34
 
35
35
  import { beforeEach, describe, expect, mock, test } from "bun:test";
36
36
 
37
- import { InvalidPluginNameError } from "../../../cli/lib/install-from-github.js";
37
+ import {
38
+ type InstallPluginOptions,
39
+ type InstallPluginResult,
40
+ InvalidPluginNameError,
41
+ PluginAlreadyInstalledError,
42
+ PluginNotFoundError,
43
+ PluginSourceUnavailableError,
44
+ } from "../../../cli/lib/install-from-github.js";
38
45
  import type { InstalledPluginInfo } from "../../../cli/lib/list-installed-plugins.js";
46
+ import {
47
+ type PluginDetails,
48
+ PluginDetailsNotFoundError,
49
+ type PluginDetailsOptions,
50
+ } from "../../../cli/lib/plugin-details.js";
39
51
  import type {
52
+ PluginCatalog,
40
53
  PluginSearchMatch,
41
54
  SearchPluginsDeps,
42
- SearchPluginsOptions,
43
- SearchPluginsResult,
44
55
  } from "../../../cli/lib/search-plugins.js";
45
- import { InvalidSearchPatternError } from "../../../cli/lib/search-plugins.js";
56
+ import { PluginCatalogUnavailableError } from "../../../cli/lib/search-plugins.js";
46
57
  import {
47
58
  PluginNotInstalledError,
48
59
  type UninstallPluginOptions,
@@ -57,22 +68,18 @@ mock.module("../../../cli/lib/list-installed-plugins.js", () => ({
57
68
  listInstalledPlugins: () => installedFixture,
58
69
  }));
59
70
 
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");
71
+ // Mock the catalog cache: `getCatalogSpy` records every invocation and
72
+ // returns the (unfiltered) catalog the route then filters in memory via the
73
+ // real `filterPluginCatalog`. The real `search-plugins.js` module is left
74
+ // unmocked so filtering + error classes behave exactly as in production.
75
+ const getCatalogSpy = mock(
76
+ async (_ref: string, _deps: SearchPluginsDeps): Promise<PluginCatalog> => {
77
+ throw new Error("getCatalogSpy default impl not configured");
68
78
  },
69
79
  );
70
80
 
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,
81
+ mock.module("../../../cli/lib/plugin-catalog-cache.js", () => ({
82
+ getPluginCatalog: getCatalogSpy,
76
83
  }));
77
84
 
78
85
  // Mock uninstallPlugin. The handler's error mapping is the wiring under
@@ -89,16 +96,50 @@ mock.module("../../../cli/lib/uninstall-plugin.js", () => ({
89
96
  uninstallPlugin: uninstallSpy,
90
97
  }));
91
98
 
99
+ // Mock installPlugin. As with the other libs, the handler's error mapping
100
+ // is the wiring under test; the lib's own behavior is covered separately.
101
+ const installSpy = mock(
102
+ async (_opts: InstallPluginOptions): Promise<InstallPluginResult> => {
103
+ throw new Error("installSpy default impl not configured");
104
+ },
105
+ );
106
+
92
107
  // `InvalidPluginNameError` is re-exported from uninstall-plugin.js but
93
108
  // the canonical definition lives in install-from-github.js. The
94
109
  // 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.
110
+ // `instanceof` checks inside the handler resolve to the same classes as
111
+ // the ones the spies throw. The error classes are passed through real so
112
+ // `instanceof` aligns.
97
113
  mock.module("../../../cli/lib/install-from-github.js", () => ({
114
+ DEFAULT_PLUGIN_REF: "main",
98
115
  InvalidPluginNameError,
116
+ PluginAlreadyInstalledError,
117
+ PluginNotFoundError,
118
+ PluginSourceUnavailableError,
119
+ installPlugin: installSpy,
120
+ }));
121
+
122
+ // Mock getPluginDetails: the detail handler unions disk + manifest + repo
123
+ // in the lib (covered by plugin-details.test.ts); here we isolate the
124
+ // route's error mapping and pass-through.
125
+ const detailsSpy = mock(
126
+ async (_opts: PluginDetailsOptions): Promise<PluginDetails> => {
127
+ throw new Error("detailsSpy default impl not configured");
128
+ },
129
+ );
130
+
131
+ mock.module("../../../cli/lib/plugin-details.js", () => ({
132
+ PluginDetailsNotFoundError,
133
+ getPluginDetails: detailsSpy,
99
134
  }));
100
135
 
101
- import { BadRequestError, InternalError, NotFoundError } from "../errors.js";
136
+ import {
137
+ BadRequestError,
138
+ ConflictError,
139
+ InternalError,
140
+ NotFoundError,
141
+ ServiceUnavailableError,
142
+ } from "../errors.js";
102
143
  import { ROUTES as PLUGINS_ROUTES } from "../plugins-routes.js";
103
144
  import type { RouteDefinition, RouteHandlerArgs } from "../types.js";
104
145
 
@@ -111,10 +152,12 @@ function findHandler(operationId: string): RouteDefinition["handler"] {
111
152
  const listHandler = findHandler("plugins_list");
112
153
  const searchHandler = findHandler("plugins_search");
113
154
  const uninstallHandler = findHandler("plugins_uninstall");
155
+ const getHandler = findHandler("plugins_get");
156
+ const installHandler = findHandler("plugins_install");
114
157
 
115
- function invoke(
116
- args: RouteHandlerArgs = {},
117
- ): { plugins: Array<Record<string, unknown>> } {
158
+ function invoke(args: RouteHandlerArgs = {}): {
159
+ plugins: Array<Record<string, unknown>>;
160
+ } {
118
161
  return listHandler(args) as { plugins: Array<Record<string, unknown>> };
119
162
  }
120
163
 
@@ -268,7 +311,9 @@ describe("GET /v1/plugins", () => {
268
311
 
269
312
  // matches multiple
270
313
  expect(
271
- invoke({ queryParams: { q: "o" } }).plugins.map((p) => p.id).sort(),
314
+ invoke({ queryParams: { q: "o" } })
315
+ .plugins.map((p) => p.id)
316
+ .sort(),
272
317
  ).toEqual(["calendar-sync", "todo", "weather"].sort());
273
318
 
274
319
  // no match
@@ -305,81 +350,129 @@ describe("GET /v1/plugins", () => {
305
350
  // GET /v1/plugins/search
306
351
  // ---------------------------------------------------------------------------
307
352
 
353
+ function catalog(
354
+ ref: string,
355
+ matches: readonly PluginSearchMatch[],
356
+ ): PluginCatalog {
357
+ return { ref, matches };
358
+ }
359
+
308
360
  describe("GET /v1/plugins/search", () => {
309
361
  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
- }));
362
+ getCatalogSpy.mockClear();
363
+ // Default to a happy-path empty catalog; individual tests override.
364
+ getCatalogSpy.mockImplementation(async (ref) => catalog(ref, []));
317
365
  });
318
366
 
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
- }));
367
+ test("resolves the catalog at the requested ref and filters by ?q=", async () => {
368
+ getCatalogSpy.mockImplementation(async (ref) =>
369
+ catalog(ref, [
370
+ {
371
+ name: "simple-memory",
372
+ path: "experimental/plugins/simple-memory",
373
+ source: { kind: "first-party" },
374
+ },
375
+ {
376
+ name: "caveman",
377
+ path: "github:JuliusBrussee/caveman@v1.8.2",
378
+ description: "Ultra-compressed communication mode.",
379
+ source: {
380
+ kind: "github",
381
+ repo: "JuliusBrussee/caveman",
382
+ ref: "v1.8.2",
383
+ },
384
+ },
385
+ ]),
386
+ );
328
387
 
329
388
  const result = await invokeSearch({
330
389
  queryParams: { q: "^simple", ref: "my-feature-branch" },
331
390
  });
332
391
 
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");
392
+ // The cache was consulted once at the requested ref.
393
+ expect(getCatalogSpy).toHaveBeenCalledTimes(1);
394
+ const [ref] = getCatalogSpy.mock.calls[0]!;
395
+ expect(ref).toBe("my-feature-branch");
338
396
 
397
+ // The query is applied in-memory by the real filter: `^simple` matches
398
+ // only `simple-memory`, and the source discriminator is preserved.
339
399
  expect(result).toEqual({
340
400
  query: "^simple",
341
401
  ref: "my-feature-branch",
342
402
  matches: [
343
- { name: "simple-memory", path: "experimental/plugins/simple-memory" },
344
- { name: "simple-router", path: "experimental/plugins/simple-router" },
403
+ {
404
+ name: "simple-memory",
405
+ path: "experimental/plugins/simple-memory",
406
+ source: { kind: "first-party" },
407
+ },
345
408
  ],
346
409
  });
347
410
  });
348
411
 
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();
412
+ test("missing ?q= matches all (empty-string query) at the default ref", async () => {
413
+ getCatalogSpy.mockImplementation(async (ref) =>
414
+ catalog(ref, [
415
+ {
416
+ name: "caveman",
417
+ path: "github:JuliusBrussee/caveman@v1.8.2",
418
+ source: {
419
+ kind: "github",
420
+ repo: "JuliusBrussee/caveman",
421
+ ref: "v1.8.2",
422
+ },
423
+ },
424
+ ]),
425
+ );
426
+ const result = await invokeSearch();
427
+ const [ref] = getCatalogSpy.mock.calls[0]!;
428
+ // No ref supplied → route resolves the default ref before caching.
429
+ expect(ref).toBe("main");
430
+ expect(result.query).toBe("");
431
+ expect(result.matches.map((m) => m.name)).toEqual(["caveman"]);
356
432
  });
357
433
 
358
- test("whitespace-only ?ref= is treated as missing", async () => {
434
+ test("whitespace-only ?ref= falls back to the default ref", async () => {
359
435
  await invokeSearch({ queryParams: { q: "x", ref: " " } });
360
- const [opts] = searchSpy.mock.calls[0]!;
361
- expect(opts.ref).toBeUndefined();
436
+ const [ref] = getCatalogSpy.mock.calls[0]!;
437
+ expect(ref).toBe("main");
362
438
  });
363
439
 
364
- test("supplies a bound globalThis.fetch as the lib's fetch dep", async () => {
440
+ test("supplies a bound globalThis.fetch as the catalog loader's fetch dep", async () => {
365
441
  await invokeSearch({ queryParams: { q: "memory" } });
366
- const [, deps] = searchSpy.mock.calls[0]!;
442
+ const [, deps] = getCatalogSpy.mock.calls[0]!;
367
443
  expect(typeof deps.fetch).toBe("function");
368
444
  });
369
445
 
370
- test("InvalidSearchPatternError → BadRequestError (400)", async () => {
371
- searchSpy.mockImplementation(async () => {
372
- throw new InvalidSearchPatternError("(", new SyntaxError("unbalanced"));
373
- });
374
-
446
+ test("InvalidSearchPatternError → BadRequestError (400), before any catalog load", async () => {
447
+ // GIVEN a malformed regex query and a cold cache
448
+ // WHEN the search runs
449
+ // THEN it's a deterministic 400 AND the catalog is never loaded, so a
450
+ // user typo can't waste a GitHub request (which would otherwise surface
451
+ // as 503 on a rate-limited cold cache).
375
452
  await expect(
376
453
  invokeSearch({ queryParams: { q: "(" } }),
377
454
  ).rejects.toBeInstanceOf(BadRequestError);
455
+ expect(getCatalogSpy).not.toHaveBeenCalled();
456
+ });
457
+
458
+ test("PluginCatalogUnavailableError → ServiceUnavailableError (503)", async () => {
459
+ getCatalogSpy.mockImplementation(async () => {
460
+ throw new PluginCatalogUnavailableError(
461
+ "GitHub contents listing failed for experimental/plugins @ main: HTTP 403",
462
+ 403,
463
+ );
464
+ });
465
+
466
+ // A rate-limited upstream (with no cache to fall back on) is transient
467
+ // and retryable — the route surfaces it as 503, not a misleading 500.
468
+ await expect(
469
+ invokeSearch({ queryParams: { q: "memory" } }),
470
+ ).rejects.toBeInstanceOf(ServiceUnavailableError);
378
471
  });
379
472
 
380
473
  test("unknown errors → InternalError with original message preserved", async () => {
381
- searchSpy.mockImplementation(async () => {
382
- throw new Error("GitHub contents listing failed: HTTP 502");
474
+ getCatalogSpy.mockImplementation(async () => {
475
+ throw new Error("GitHub contents listing failed: HTTP 404");
383
476
  });
384
477
 
385
478
  await expect(
@@ -394,20 +487,28 @@ describe("GET /v1/plugins/search", () => {
394
487
 
395
488
  test("re-packs readonly match array into a mutable copy", async () => {
396
489
  const frozenMatches = Object.freeze([
397
- Object.freeze({ name: "a", path: "experimental/plugins/a" }),
490
+ Object.freeze({
491
+ name: "a",
492
+ path: "experimental/plugins/a",
493
+ source: { kind: "first-party" } as const,
494
+ }),
398
495
  ]) as readonly PluginSearchMatch[];
399
- searchSpy.mockImplementation(async (opts) => ({
400
- query: opts.query,
401
- ref: opts.ref ?? "main",
402
- matches: frozenMatches,
403
- }));
496
+ getCatalogSpy.mockImplementation(async (ref) =>
497
+ catalog(ref, frozenMatches),
498
+ );
404
499
 
405
500
  const result = await invokeSearch({ queryParams: { q: "a" } });
406
501
  // The route returns a non-frozen array we can mutate without
407
502
  // touching the lib's internal cache. This matters when serializers
408
503
  // (or downstream test fixtures) reach in.
409
504
  expect(Object.isFrozen(result.matches)).toBe(false);
410
- expect(() => result.matches.push({ name: "b", path: "x" })).not.toThrow();
505
+ expect(() =>
506
+ result.matches.push({
507
+ name: "b",
508
+ path: "x",
509
+ source: { kind: "first-party" },
510
+ }),
511
+ ).not.toThrow();
411
512
  });
412
513
  });
413
514
 
@@ -510,3 +611,252 @@ describe("DELETE /v1/plugins/:name", () => {
510
611
  expect((caught as Error).message).toBe("plugin uninstall failed");
511
612
  });
512
613
  });
614
+
615
+ function pluginDetails(overrides: Partial<PluginDetails> = {}): PluginDetails {
616
+ return {
617
+ name: overrides.name ?? "caveman",
618
+ installed: overrides.installed ?? false,
619
+ description: overrides.description ?? null,
620
+ homepage: overrides.homepage ?? null,
621
+ license: overrides.license ?? null,
622
+ version: overrides.version ?? null,
623
+ source: overrides.source ?? { kind: "first-party" },
624
+ readme: overrides.readme ?? null,
625
+ ref: overrides.ref ?? "main",
626
+ };
627
+ }
628
+
629
+ async function invokeGet(args: RouteHandlerArgs = {}): Promise<PluginDetails> {
630
+ return (await getHandler(args)) as PluginDetails;
631
+ }
632
+
633
+ describe("GET /v1/plugins/:name", () => {
634
+ beforeEach(() => {
635
+ detailsSpy.mockReset();
636
+ });
637
+
638
+ test("forwards name + ref to getPluginDetails and returns the detail view", async () => {
639
+ const view = pluginDetails({
640
+ name: "caveman",
641
+ installed: true,
642
+ description: "A loud agent plugin",
643
+ homepage: "https://example.com/caveman",
644
+ license: "MIT",
645
+ version: "1.8.2",
646
+ source: { kind: "github", repo: "example-org/caveman", ref: "v1.8.2" },
647
+ readme: "# Caveman\n\nHello.",
648
+ ref: "v1.8.2",
649
+ });
650
+ detailsSpy.mockImplementation(async () => view);
651
+
652
+ const result = await invokeGet({
653
+ pathParams: { name: "caveman" },
654
+ queryParams: { ref: "v1.8.2" },
655
+ });
656
+
657
+ expect(result).toEqual(view);
658
+ expect(detailsSpy.mock.calls[0]?.[0]).toEqual({
659
+ name: "caveman",
660
+ ref: "v1.8.2",
661
+ });
662
+ });
663
+
664
+ test("omits ref (passes undefined) when the query param is absent or blank", async () => {
665
+ detailsSpy.mockImplementation(async () => pluginDetails());
666
+
667
+ await invokeGet({ pathParams: { name: "caveman" }, queryParams: {} });
668
+ expect(detailsSpy.mock.calls[0]?.[0]).toEqual({
669
+ name: "caveman",
670
+ ref: undefined,
671
+ });
672
+
673
+ await invokeGet({
674
+ pathParams: { name: "caveman" },
675
+ queryParams: { ref: " " },
676
+ });
677
+ expect(detailsSpy.mock.calls[1]?.[0]).toEqual({
678
+ name: "caveman",
679
+ ref: undefined,
680
+ });
681
+ });
682
+
683
+ test("InvalidPluginNameError → BadRequestError (400)", async () => {
684
+ detailsSpy.mockImplementation(async () => {
685
+ throw new InvalidPluginNameError("../escape");
686
+ });
687
+
688
+ await expect(
689
+ invokeGet({ pathParams: { name: "../escape" } }),
690
+ ).rejects.toBeInstanceOf(BadRequestError);
691
+ });
692
+
693
+ test("PluginDetailsNotFoundError → NotFoundError (404)", async () => {
694
+ detailsSpy.mockImplementation(async () => {
695
+ throw new PluginDetailsNotFoundError("ghost", "main");
696
+ });
697
+
698
+ await expect(
699
+ invokeGet({ pathParams: { name: "ghost" } }),
700
+ ).rejects.toBeInstanceOf(NotFoundError);
701
+ });
702
+
703
+ test("unknown errors → InternalError with original message preserved", async () => {
704
+ detailsSpy.mockImplementation(async () => {
705
+ throw new Error("ENOTFOUND api.github.com");
706
+ });
707
+
708
+ let caught: unknown;
709
+ try {
710
+ await invokeGet({ pathParams: { name: "caveman" } });
711
+ } catch (err) {
712
+ caught = err;
713
+ }
714
+ expect(caught).toBeInstanceOf(InternalError);
715
+ expect((caught as Error).message).toContain("ENOTFOUND");
716
+ });
717
+ });
718
+
719
+ async function invokeInstall(args: RouteHandlerArgs = {}): Promise<{
720
+ ok: true;
721
+ name: string;
722
+ target: string;
723
+ fileCount: number;
724
+ ref: string;
725
+ }> {
726
+ return (await installHandler(args)) as {
727
+ ok: true;
728
+ name: string;
729
+ target: string;
730
+ fileCount: number;
731
+ ref: string;
732
+ };
733
+ }
734
+
735
+ describe("POST /v1/plugins/install", () => {
736
+ beforeEach(() => {
737
+ installSpy.mockReset();
738
+ });
739
+
740
+ test("forwards name/force and shapes the result, pinning ref to the default", async () => {
741
+ installSpy.mockImplementation(async (opts) => ({
742
+ name: opts.name,
743
+ target: `/workspace/.vellum/plugins/${opts.name}`,
744
+ fileCount: 7,
745
+ ref: opts.ref ?? "main",
746
+ commit: null,
747
+ }));
748
+
749
+ const result = await invokeInstall({
750
+ body: { name: "caveman", force: true },
751
+ });
752
+
753
+ expect(result).toEqual({
754
+ ok: true,
755
+ name: "caveman",
756
+ target: "/workspace/.vellum/plugins/caveman",
757
+ fileCount: 7,
758
+ ref: "main",
759
+ });
760
+ expect(installSpy.mock.calls[0]?.[0]).toEqual({
761
+ name: "caveman",
762
+ ref: "main",
763
+ force: true,
764
+ });
765
+ });
766
+
767
+ test("ignores a caller-supplied ref and pins to the curated default", async () => {
768
+ // Security boundary: installing from an unreviewed ref (a PR branch,
769
+ // fork ref, ...) could load attacker-controlled marketplace/first-party
770
+ // code, so the HTTP route never honors a body `ref` — it always resolves
771
+ // against the curated default ref.
772
+ installSpy.mockImplementation(async (opts) => ({
773
+ name: opts.name,
774
+ target: `/workspace/.vellum/plugins/${opts.name}`,
775
+ fileCount: 7,
776
+ ref: opts.ref ?? "main",
777
+ commit: null,
778
+ }));
779
+
780
+ const result = await invokeInstall({
781
+ body: { name: "caveman", ref: "attacker-pr-branch" },
782
+ });
783
+
784
+ expect(result.ref).toBe("main");
785
+ expect(installSpy.mock.calls[0]?.[0]).toEqual({
786
+ name: "caveman",
787
+ ref: "main",
788
+ force: undefined,
789
+ });
790
+ });
791
+
792
+ test("a missing name short-circuits to BadRequestError without calling the lib", async () => {
793
+ await expect(invokeInstall({ body: {} })).rejects.toBeInstanceOf(
794
+ BadRequestError,
795
+ );
796
+ expect(installSpy).not.toHaveBeenCalled();
797
+ });
798
+
799
+ test("InvalidPluginNameError → BadRequestError (400)", async () => {
800
+ installSpy.mockImplementation(async () => {
801
+ throw new InvalidPluginNameError("../escape");
802
+ });
803
+
804
+ await expect(
805
+ invokeInstall({ body: { name: "../escape" } }),
806
+ ).rejects.toBeInstanceOf(BadRequestError);
807
+ });
808
+
809
+ test("PluginAlreadyInstalledError → ConflictError (409)", async () => {
810
+ installSpy.mockImplementation(async (opts) => {
811
+ throw new PluginAlreadyInstalledError(
812
+ opts.name,
813
+ `/workspace/.vellum/plugins/${opts.name}`,
814
+ );
815
+ });
816
+
817
+ await expect(
818
+ invokeInstall({ body: { name: "caveman" } }),
819
+ ).rejects.toBeInstanceOf(ConflictError);
820
+ });
821
+
822
+ test("PluginNotFoundError → NotFoundError (404)", async () => {
823
+ installSpy.mockImplementation(async (opts) => {
824
+ throw new PluginNotFoundError(opts.name, "main", "example-org/ghost");
825
+ });
826
+
827
+ await expect(
828
+ invokeInstall({ body: { name: "ghost" } }),
829
+ ).rejects.toBeInstanceOf(NotFoundError);
830
+ });
831
+
832
+ test("PluginSourceUnavailableError → ServiceUnavailableError (503)", async () => {
833
+ // A rate-limited or temporarily-down GitHub source is retryable: the
834
+ // route surfaces 503 so the client can back off and try again, rather
835
+ // than a misleading 500 that reads as a permanent failure.
836
+ installSpy.mockImplementation(async () => {
837
+ throw new PluginSourceUnavailableError(
838
+ "GitHub tree listing for JuliusBrussee/caveman @ v1.8.2: HTTP 403",
839
+ 403,
840
+ );
841
+ });
842
+
843
+ await expect(
844
+ invokeInstall({ body: { name: "caveman" } }),
845
+ ).rejects.toBeInstanceOf(ServiceUnavailableError);
846
+ });
847
+
848
+ test("unknown errors → InternalError with original message preserved", async () => {
849
+ installSpy.mockImplementation(async () => {
850
+ throw new Error("ECONNRESET");
851
+ });
852
+
853
+ let caught: unknown;
854
+ try {
855
+ await invokeInstall({ body: { name: "caveman" } });
856
+ } catch (err) {
857
+ caught = err;
858
+ }
859
+ expect(caught).toBeInstanceOf(InternalError);
860
+ expect((caught as Error).message).toContain("ECONNRESET");
861
+ });
862
+ });