@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091853.fbaa2ae

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 (475) 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 +263 -121
  12. package/package.json +2 -2
  13. package/src/__tests__/activation-early-marking.test.ts +120 -0
  14. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  15. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  16. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  17. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  18. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  19. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  20. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  21. package/src/__tests__/agent-loop.test.ts +441 -96
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  23. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  24. package/src/__tests__/anthropic-provider.test.ts +23 -8
  25. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  26. package/src/__tests__/app-control-flow.test.ts +1 -1
  27. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  28. package/src/__tests__/app-executors.test.ts +132 -0
  29. package/src/__tests__/approval-cascade.test.ts +6 -5
  30. package/src/__tests__/approval-routes-http.test.ts +4 -1
  31. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  32. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  33. package/src/__tests__/channel-approvals.test.ts +1 -1
  34. package/src/__tests__/compaction-circuit.test.ts +258 -0
  35. package/src/__tests__/compaction-direct.test.ts +146 -0
  36. package/src/__tests__/compaction-events.test.ts +7 -7
  37. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  38. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  39. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  40. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  41. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  42. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  43. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  44. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  45. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  46. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  47. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  48. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  49. package/src/__tests__/conversation-error.test.ts +15 -1
  50. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  51. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  52. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  53. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  55. package/src/__tests__/conversation-queue.test.ts +10 -10
  56. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  57. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  58. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  59. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  60. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  61. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  62. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  65. package/src/__tests__/conversation-undo.test.ts +2 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  67. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/corrected-target.test.ts +93 -0
  71. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  72. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  73. package/src/__tests__/db-acp-history.test.ts +101 -0
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +1 -1
  76. package/src/__tests__/file-write-tool.test.ts +63 -0
  77. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  78. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  79. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  80. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  82. package/src/__tests__/heartbeat-service.test.ts +1 -0
  83. package/src/__tests__/history-repair-hook.test.ts +5 -1
  84. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  85. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  86. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  87. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  88. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  89. package/src/__tests__/injector-background-turn.test.ts +13 -23
  90. package/src/__tests__/injector-chain.test.ts +268 -44
  91. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  92. package/src/__tests__/injector-document-comments.test.ts +97 -114
  93. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  94. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  95. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  96. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  97. package/src/__tests__/llm-resolver.test.ts +73 -0
  98. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  99. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  100. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  101. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  102. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  103. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  104. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  105. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  106. package/src/__tests__/plugin-registry.test.ts +3 -49
  107. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  108. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  109. package/src/__tests__/plugin-types.test.ts +0 -70
  110. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  111. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  112. package/src/__tests__/published-app-updater.test.ts +138 -0
  113. package/src/__tests__/reaction-persistence.test.ts +1 -1
  114. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  115. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  116. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  117. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  118. package/src/__tests__/subagent-detail.test.ts +25 -7
  119. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  120. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  121. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  122. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  123. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  124. package/src/__tests__/title-generate-hook.test.ts +5 -1
  125. package/src/__tests__/tool-error-hook.test.ts +1 -1
  126. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  127. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  128. package/src/__tests__/web-fetch.test.ts +45 -0
  129. package/src/acp/__tests__/agent-process.test.ts +161 -0
  130. package/src/acp/__tests__/client-handler.test.ts +40 -0
  131. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  132. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  133. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  134. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  135. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  136. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  137. package/src/acp/agent-process.ts +61 -1
  138. package/src/acp/auto-install.test.ts +280 -0
  139. package/src/acp/auto-install.ts +232 -0
  140. package/src/acp/client-handler.ts +31 -0
  141. package/src/acp/prepare-agent-env.ts +80 -27
  142. package/src/acp/resolve-agent.test.ts +188 -28
  143. package/src/acp/resolve-agent.ts +119 -42
  144. package/src/acp/resume-hint.ts +23 -0
  145. package/src/acp/session-manager.ts +507 -73
  146. package/src/agent/compaction-circuit.ts +60 -102
  147. package/src/agent/loop.ts +403 -251
  148. package/src/api/responses/conversation-message.ts +22 -2
  149. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  150. package/src/approvals/guardian-request-resolvers.ts +27 -1
  151. package/src/background-wake/next-wake.ts +1 -0
  152. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  153. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  154. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  155. package/src/cli/commands/memory-v3.ts +57 -199
  156. package/src/cli/commands/plugins.ts +43 -37
  157. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  158. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  159. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  160. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  161. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  162. package/src/cli/lib/install-from-github.ts +797 -60
  163. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  164. package/src/cli/lib/plugin-details.ts +416 -0
  165. package/src/cli/lib/plugin-marketplace.ts +215 -0
  166. package/src/cli/lib/search-plugins.ts +201 -29
  167. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  168. package/src/config/acp-defaults.test.ts +10 -0
  169. package/src/config/acp-defaults.ts +9 -3
  170. package/src/config/acp-schema.ts +1 -7
  171. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  172. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  173. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  174. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  175. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  176. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  177. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  178. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  180. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  181. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  182. package/src/config/bundled-tool-registry.ts +4 -0
  183. package/src/config/call-site-defaults.ts +0 -3
  184. package/src/config/feature-flag-registry.json +10 -16
  185. package/src/config/llm-resolver.ts +39 -7
  186. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  187. package/src/config/schemas/call-site-catalog.ts +0 -21
  188. package/src/config/schemas/heartbeat.ts +9 -0
  189. package/src/config/schemas/llm.ts +17 -3
  190. package/src/config/schemas/memory-v3.ts +58 -8
  191. package/src/config/seed-inference-profiles.ts +18 -0
  192. package/src/context/compactor.ts +22 -4
  193. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  194. package/src/context/strip-injections.ts +8 -2
  195. package/src/daemon/conversation-agent-loop-handlers.ts +18 -36
  196. package/src/daemon/conversation-agent-loop.ts +308 -1297
  197. package/src/daemon/conversation-error.ts +31 -4
  198. package/src/daemon/conversation-history.ts +1 -1
  199. package/src/daemon/conversation-lifecycle.ts +11 -255
  200. package/src/daemon/conversation-media-retry.ts +19 -6
  201. package/src/daemon/conversation-messaging.ts +17 -0
  202. package/src/daemon/conversation-process.ts +22 -141
  203. package/src/daemon/conversation-queue-manager.ts +8 -0
  204. package/src/daemon/conversation-registry.ts +159 -0
  205. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  206. package/src/daemon/conversation-store.ts +9 -90
  207. package/src/daemon/conversation-surfaces.ts +165 -11
  208. package/src/daemon/conversation-workspace.ts +17 -0
  209. package/src/daemon/conversation.ts +444 -62
  210. package/src/daemon/disk-pressure-policy.ts +0 -1
  211. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  212. package/src/daemon/handlers/config-channels.ts +11 -2
  213. package/src/daemon/handlers/conversations.ts +3 -1
  214. package/src/daemon/handlers/skills.ts +4 -1
  215. package/src/daemon/host-app-control-proxy.ts +72 -57
  216. package/src/daemon/host-proxy-preactivation.ts +1 -3
  217. package/src/daemon/lifecycle.ts +21 -7
  218. package/src/daemon/persist-unsendable-image.ts +62 -25
  219. package/src/daemon/process-message.ts +1 -1
  220. package/src/daemon/server.ts +2 -0
  221. package/src/daemon/tool-side-effects.ts +15 -0
  222. package/src/daemon/wake-conversation-ops.ts +269 -0
  223. package/src/embedded/plugin-api.ts +2 -2
  224. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  225. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  226. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  227. package/src/heartbeat/heartbeat-service.ts +26 -0
  228. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  229. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  230. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  231. package/src/ipc/skill-routes/memory.ts +4 -2
  232. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  233. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  234. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  235. package/src/memory/activation-session-store.ts +43 -0
  236. package/src/memory/conversation-crud.ts +29 -19
  237. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  238. package/src/memory/db-init.ts +6 -0
  239. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  240. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  241. package/src/memory/jobs-worker.ts +25 -1
  242. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  243. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  244. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  245. package/src/memory/migrations/index.ts +3 -0
  246. package/src/memory/onboarding-events-store.ts +66 -18
  247. package/src/memory/schema/acp.ts +4 -0
  248. package/src/memory/schema/infrastructure.ts +13 -0
  249. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  250. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  251. package/src/memory/v2/consolidation-job.ts +2 -63
  252. package/src/memory/v2/types.ts +5 -0
  253. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  254. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  255. package/src/notifications/access-request-copy.ts +298 -0
  256. package/src/notifications/adapters/slack.ts +3 -3
  257. package/src/notifications/adapters/telegram.ts +9 -2
  258. package/src/notifications/copy-composer.ts +49 -267
  259. package/src/notifications/decision-engine.ts +16 -35
  260. package/src/notifications/home-feed-side-effect.ts +1 -6
  261. package/src/plugin-api/constants.ts +4 -0
  262. package/src/plugin-api/index.ts +40 -11
  263. package/src/plugin-api/types.ts +108 -0
  264. package/src/plugins/defaults/compaction/compact.ts +106 -0
  265. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  266. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  267. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  268. package/src/plugins/defaults/compaction/package.json +1 -2
  269. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  270. package/src/plugins/defaults/empty-response/package.json +0 -1
  271. package/src/plugins/defaults/history-repair/package.json +0 -1
  272. package/src/plugins/defaults/index.ts +156 -26
  273. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  274. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  275. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  276. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  277. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  278. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  282. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  283. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  284. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  285. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  286. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  287. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  288. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  289. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  290. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  291. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  293. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  294. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  295. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  296. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  297. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  298. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  299. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  300. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  301. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  302. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  303. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  304. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  306. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  307. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  308. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  309. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  310. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  311. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  312. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  313. package/src/plugins/defaults/title-generate/package.json +0 -1
  314. package/src/plugins/defaults/tool-error/package.json +0 -1
  315. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  316. package/src/plugins/external-api.ts +12 -2
  317. package/src/plugins/pipeline.ts +6 -293
  318. package/src/plugins/registry.ts +9 -37
  319. package/src/plugins/types.ts +76 -381
  320. package/src/plugins/user-loader.ts +30 -127
  321. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  322. package/src/prompts/system-prompt.ts +61 -10
  323. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  324. package/src/providers/anthropic/client.ts +9 -10
  325. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  326. package/src/providers/inference/logit-bias.ts +55 -0
  327. package/src/providers/model-catalog.ts +34 -0
  328. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  329. package/src/providers/openai/chat-completions-provider.ts +44 -1
  330. package/src/providers/retry.ts +22 -0
  331. package/src/providers/types.ts +6 -0
  332. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  333. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  334. package/src/runtime/agent-wake.ts +108 -209
  335. package/src/runtime/assistant-event-hub.ts +1 -1
  336. package/src/runtime/channel-approvals.ts +1 -1
  337. package/src/runtime/interactive-ui.ts +1 -1
  338. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  339. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  340. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  341. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  342. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  343. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  344. package/src/runtime/routes/acp-routes.test.ts +185 -32
  345. package/src/runtime/routes/acp-routes.ts +328 -30
  346. package/src/runtime/routes/app-management-routes.ts +3 -0
  347. package/src/runtime/routes/approval-routes.ts +1 -1
  348. package/src/runtime/routes/browser-routes.ts +1 -1
  349. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  350. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  351. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  352. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  353. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  354. package/src/runtime/routes/conversation-routes.ts +30 -2
  355. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  356. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  357. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  358. package/src/runtime/routes/host-cu-routes.ts +1 -1
  359. package/src/runtime/routes/identity-routes.ts +18 -3
  360. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  361. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  362. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  363. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  364. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  365. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  366. package/src/runtime/routes/playground/helpers.ts +1 -1
  367. package/src/runtime/routes/plugins-routes.ts +337 -35
  368. package/src/runtime/routes/stt-routes.ts +45 -12
  369. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  370. package/src/runtime/routes/work-items-routes.ts +2 -4
  371. package/src/runtime/routes/workspace-routes.ts +50 -15
  372. package/src/runtime/services/conversation-serializer.ts +1 -1
  373. package/src/runtime/verification-outbound-actions.ts +147 -2
  374. package/src/runtime/verification-templates.ts +29 -3
  375. package/src/services/published-app-updater.ts +30 -8
  376. package/src/signals/cancel.ts +2 -4
  377. package/src/subagent/manager.ts +21 -5
  378. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  379. package/src/telemetry/activation-funnel.ts +167 -0
  380. package/src/telemetry/types.ts +13 -0
  381. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  382. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  383. package/src/tools/acp/context.ts +20 -0
  384. package/src/tools/acp/list-agents.test.ts +9 -19
  385. package/src/tools/acp/list-agents.ts +3 -15
  386. package/src/tools/acp/spawn.test.ts +173 -202
  387. package/src/tools/acp/spawn.ts +37 -172
  388. package/src/tools/acp/steer.test.ts +105 -8
  389. package/src/tools/acp/steer.ts +48 -17
  390. package/src/tools/apps/executors.ts +166 -50
  391. package/src/tools/browser/browser-execution.ts +12 -2
  392. package/src/tools/filesystem/write.ts +34 -0
  393. package/src/tools/network/web-fetch.ts +65 -24
  394. package/src/tools/skills/load.ts +1 -1
  395. package/src/tools/subagent/spawn.ts +2 -4
  396. package/src/tools/ui-surface/definitions.ts +32 -5
  397. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  398. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  399. package/docs/plugins.md +0 -832
  400. package/examples/plugins/echo/register.ts +0 -143
  401. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  402. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  403. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  404. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  405. package/src/__tests__/pipeline-runner.test.ts +0 -554
  406. package/src/daemon/wake-target-adapter.ts +0 -253
  407. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  408. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  409. package/src/memory/v3/__tests__/core.test.ts +0 -39
  410. package/src/memory/v3/__tests__/health.test.ts +0 -219
  411. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  412. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  413. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  414. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  415. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  416. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  417. package/src/memory/v3/__tests__/router.test.ts +0 -332
  418. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  419. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  420. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  421. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  422. package/src/memory/v3/assign.ts +0 -268
  423. package/src/memory/v3/capabilities.ts +0 -124
  424. package/src/memory/v3/core.ts +0 -26
  425. package/src/memory/v3/health.ts +0 -0
  426. package/src/memory/v3/maintain-job.ts +0 -314
  427. package/src/memory/v3/needle.ts +0 -115
  428. package/src/memory/v3/orchestrate.ts +0 -126
  429. package/src/memory/v3/page-content.ts +0 -34
  430. package/src/memory/v3/provider-blocks.ts +0 -26
  431. package/src/memory/v3/reconcile.ts +0 -523
  432. package/src/memory/v3/router.ts +0 -190
  433. package/src/memory/v3/selection-log-store.ts +0 -84
  434. package/src/memory/v3/selector.ts +0 -226
  435. package/src/memory/v3/shadow-plugin.ts +0 -411
  436. package/src/memory/v3/snapshot.ts +0 -209
  437. package/src/memory/v3/tree.ts +0 -174
  438. package/src/memory/v3/types.ts +0 -59
  439. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  440. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  441. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  442. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  443. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  444. package/src/plugins/defaults/compaction/register.ts +0 -35
  445. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  446. package/src/plugins/defaults/empty-response/register.ts +0 -23
  447. package/src/plugins/defaults/history-repair/register.ts +0 -24
  448. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  449. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  450. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  451. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  452. package/src/plugins/defaults/title-generate/register.ts +0 -35
  453. package/src/plugins/defaults/tool-error/register.ts +0 -23
  454. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  455. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  456. package/src/proactive-artifact/decision.test.ts +0 -226
  457. package/src/proactive-artifact/decision.ts +0 -165
  458. package/src/proactive-artifact/index.ts +0 -7
  459. package/src/proactive-artifact/job.test.ts +0 -962
  460. package/src/proactive-artifact/job.ts +0 -372
  461. package/src/proactive-artifact/message-copy.ts +0 -58
  462. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  463. package/src/proactive-artifact/trigger-state.ts +0 -123
  464. package/src/util/map-limit.ts +0 -27
  465. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  466. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  467. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  468. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  469. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  470. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  471. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  472. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  473. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  474. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  475. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Tests for {@link fetchMarketplaceEntries} and {@link resolveMarketplaceSource}.
3
+ *
4
+ * Network is replaced with an in-memory fixture passed via the `fetch`
5
+ * dependency — no globals are monkey-patched.
6
+ */
7
+
8
+ import { describe, expect, test } from "bun:test";
9
+
10
+ import type { FetchLike } from "../install-from-github.js";
11
+ import {
12
+ fetchMarketplaceEntries,
13
+ type MarketplaceEntry,
14
+ MarketplaceFetchError,
15
+ resolveMarketplaceSource,
16
+ } from "../plugin-marketplace.js";
17
+
18
+ const MANIFEST_URL_PREFIX =
19
+ "https://api.github.com/repos/vellum-ai/vellum-assistant/contents/experimental/plugins/marketplace.json";
20
+
21
+ // External marketplace refs must be full commit SHAs (immutable). Fixtures use
22
+ // realistic 40-char hex object names rather than tags/branches.
23
+ const CAVEMAN_SHA = "63a91ecadbf4c4719a4602a5abb00883f9966034";
24
+ const NESTED_SHA = "0123456789abcdef0123456789abcdef01234567";
25
+
26
+ /** Serve `body` (any value) as the raw manifest file at the manifest URL. */
27
+ function manifestFetch(body: unknown, status = 200, raw?: string): FetchLike {
28
+ return (async (input: RequestInfo | URL) => {
29
+ const url = typeof input === "string" ? input : input.toString();
30
+ if (!url.startsWith(MANIFEST_URL_PREFIX)) {
31
+ return new Response("unexpected url: " + url, { status: 500 });
32
+ }
33
+ if (status !== 200) {
34
+ return new Response("error", { status });
35
+ }
36
+ return new Response(raw ?? JSON.stringify(body), { status: 200 });
37
+ }) as FetchLike;
38
+ }
39
+
40
+ const VALID_MANIFEST = {
41
+ name: "vellum-assistant",
42
+ owner: { name: "Vellum" },
43
+ plugins: [
44
+ {
45
+ name: "caveman",
46
+ source: {
47
+ source: "github",
48
+ repo: "JuliusBrussee/caveman",
49
+ ref: CAVEMAN_SHA,
50
+ },
51
+ description: "Ultra-compressed communication mode.",
52
+ category: "productivity",
53
+ },
54
+ {
55
+ name: "nested",
56
+ source: {
57
+ source: "github",
58
+ repo: "acme/monorepo",
59
+ path: "packages/nested",
60
+ ref: NESTED_SHA,
61
+ },
62
+ },
63
+ ],
64
+ };
65
+
66
+ describe("fetchMarketplaceEntries", () => {
67
+ test("parses a valid manifest and returns its plugin entries", async () => {
68
+ // GIVEN a canonical repo serving a valid marketplace manifest
69
+ const fetch = manifestFetch(VALID_MANIFEST);
70
+
71
+ // WHEN we fetch the marketplace entries at a ref
72
+ const entries = await fetchMarketplaceEntries({ fetch }, { ref: "main" });
73
+
74
+ // THEN both whitelisted plugins are returned with their pinned sources
75
+ expect(entries.map((e) => e.name)).toEqual(["caveman", "nested"]);
76
+ expect(entries[0]!.source).toEqual({
77
+ source: "github",
78
+ repo: "JuliusBrussee/caveman",
79
+ ref: CAVEMAN_SHA,
80
+ });
81
+ expect(entries[1]!.source.path).toBe("packages/nested");
82
+ });
83
+
84
+ test("forwards the ref to the GitHub Contents API", async () => {
85
+ // GIVEN a fixture that records the ref query parameter
86
+ let seenRef: string | undefined;
87
+ const fetch: FetchLike = (async (input: RequestInfo | URL) => {
88
+ const url = typeof input === "string" ? input : input.toString();
89
+ seenRef = /[?&]ref=([^&]+)/.exec(url)?.[1];
90
+ return new Response(JSON.stringify(VALID_MANIFEST), { status: 200 });
91
+ }) as FetchLike;
92
+
93
+ // WHEN we fetch at an explicit ref
94
+ await fetchMarketplaceEntries({ fetch }, { ref: "feat-branch" });
95
+
96
+ // THEN the ref reaches GitHub
97
+ expect(seenRef).toBe("feat-branch");
98
+ });
99
+
100
+ test("treats a missing manifest (404) as an empty whitelist", async () => {
101
+ // GIVEN no manifest exists at this ref
102
+ const fetch = manifestFetch(null, 404);
103
+
104
+ // WHEN we fetch the entries
105
+ const entries = await fetchMarketplaceEntries({ fetch }, { ref: "main" });
106
+
107
+ // THEN the absence is a normal empty result, not an error
108
+ expect(entries).toEqual([]);
109
+ });
110
+
111
+ test("throws MarketplaceFetchError on non-404 HTTP failure", async () => {
112
+ // GIVEN GitHub returns a server error
113
+ const fetch = manifestFetch(null, 503);
114
+
115
+ // WHEN / THEN the failure surfaces as a typed error
116
+ await expect(
117
+ fetchMarketplaceEntries({ fetch }, { ref: "main" }),
118
+ ).rejects.toBeInstanceOf(MarketplaceFetchError);
119
+ });
120
+
121
+ test("throws MarketplaceFetchError on non-JSON body", async () => {
122
+ // GIVEN the manifest body is not valid JSON
123
+ const fetch = manifestFetch(undefined, 200, "{ not json");
124
+
125
+ // WHEN / THEN parsing fails loudly
126
+ await expect(
127
+ fetchMarketplaceEntries({ fetch }, { ref: "main" }),
128
+ ).rejects.toBeInstanceOf(MarketplaceFetchError);
129
+ });
130
+
131
+ test("rejects a mutable tag or branch ref (must be a full commit SHA)", async () => {
132
+ // GIVEN an entry whose source pins a mutable tag rather than a commit SHA
133
+ const fetch = manifestFetch({
134
+ name: "x",
135
+ plugins: [
136
+ {
137
+ name: "caveman",
138
+ source: {
139
+ source: "github",
140
+ repo: "JuliusBrussee/caveman",
141
+ ref: "v1.8.2",
142
+ },
143
+ },
144
+ ],
145
+ });
146
+
147
+ // WHEN / THEN the mutable ref is rejected: a retag could repoint it at
148
+ // attacker code the daemon later dynamically imports.
149
+ await expect(
150
+ fetchMarketplaceEntries({ fetch }, { ref: "main" }),
151
+ ).rejects.toBeInstanceOf(MarketplaceFetchError);
152
+ });
153
+
154
+ test("rejects an abbreviated commit SHA", async () => {
155
+ // GIVEN an entry whose source uses a short (ambiguous, non-pinning) SHA
156
+ const fetch = manifestFetch({
157
+ name: "x",
158
+ plugins: [
159
+ {
160
+ name: "caveman",
161
+ source: {
162
+ source: "github",
163
+ repo: "JuliusBrussee/caveman",
164
+ ref: "63a91ec",
165
+ },
166
+ },
167
+ ],
168
+ });
169
+
170
+ // WHEN / THEN only a full object name pins the install immutably
171
+ await expect(
172
+ fetchMarketplaceEntries({ fetch }, { ref: "main" }),
173
+ ).rejects.toBeInstanceOf(MarketplaceFetchError);
174
+ });
175
+
176
+ test("rejects a manifest entry missing a pinned ref", async () => {
177
+ // GIVEN an entry whose source omits the required ref
178
+ const fetch = manifestFetch({
179
+ name: "x",
180
+ plugins: [
181
+ { name: "floating", source: { source: "github", repo: "a/b" } },
182
+ ],
183
+ });
184
+
185
+ // WHEN / THEN schema validation rejects the unpinned source
186
+ await expect(
187
+ fetchMarketplaceEntries({ fetch }, { ref: "main" }),
188
+ ).rejects.toBeInstanceOf(MarketplaceFetchError);
189
+ });
190
+
191
+ test("rejects a path that escapes the repo root", async () => {
192
+ // GIVEN an entry whose path contains a parent-segment escape
193
+ const fetch = manifestFetch({
194
+ name: "x",
195
+ plugins: [
196
+ {
197
+ name: "escape",
198
+ source: {
199
+ source: "github",
200
+ repo: "a/b",
201
+ path: "../../etc",
202
+ ref: "main",
203
+ },
204
+ },
205
+ ],
206
+ });
207
+
208
+ // WHEN / THEN the unsafe path is rejected
209
+ await expect(
210
+ fetchMarketplaceEntries({ fetch }, { ref: "main" }),
211
+ ).rejects.toBeInstanceOf(MarketplaceFetchError);
212
+ });
213
+ });
214
+
215
+ describe("resolveMarketplaceSource", () => {
216
+ const entries: readonly MarketplaceEntry[] = [
217
+ {
218
+ name: "caveman",
219
+ source: {
220
+ source: "github",
221
+ repo: "JuliusBrussee/caveman",
222
+ ref: CAVEMAN_SHA,
223
+ },
224
+ },
225
+ {
226
+ name: "nested",
227
+ source: {
228
+ source: "github",
229
+ repo: "acme/monorepo",
230
+ path: "packages/nested",
231
+ ref: NESTED_SHA,
232
+ },
233
+ },
234
+ ];
235
+
236
+ test("resolves a repo-root plugin to owner/repo with empty path", () => {
237
+ // GIVEN whitelisted entries
238
+ // WHEN resolving a name whose source has no sub-path
239
+ const resolved = resolveMarketplaceSource("caveman", entries);
240
+
241
+ // THEN the owner/repo split out and path defaults to repo root
242
+ expect(resolved).toEqual({
243
+ owner: "JuliusBrussee",
244
+ repo: "caveman",
245
+ path: "",
246
+ ref: CAVEMAN_SHA,
247
+ });
248
+ });
249
+
250
+ test("resolves a sub-path plugin to its directory", () => {
251
+ // GIVEN whitelisted entries
252
+ // WHEN resolving a name whose source pins a sub-path
253
+ const resolved = resolveMarketplaceSource("nested", entries);
254
+
255
+ // THEN the sub-path is preserved
256
+ expect(resolved).toEqual({
257
+ owner: "acme",
258
+ repo: "monorepo",
259
+ path: "packages/nested",
260
+ ref: NESTED_SHA,
261
+ });
262
+ });
263
+
264
+ test("returns null for a name not in the whitelist", () => {
265
+ // GIVEN whitelisted entries
266
+ // WHEN resolving an unknown name
267
+ // THEN no source is returned (caller falls back to first-party)
268
+ expect(resolveMarketplaceSource("unknown", entries)).toBeNull();
269
+ });
270
+ });
@@ -11,6 +11,7 @@ import { describe, expect, test } from "bun:test";
11
11
  import {
12
12
  type FetchLike,
13
13
  InvalidSearchPatternError,
14
+ PluginCatalogUnavailableError,
14
15
  searchPlugins,
15
16
  } from "../search-plugins.js";
16
17
 
@@ -180,44 +181,73 @@ describe("searchPlugins", () => {
180
181
  expect(result.matches.map((m) => m.name)).toEqual(["simple-memory"]);
181
182
  });
182
183
 
183
- test("HTTP 5xx from GitHub propagates with the status code", async () => {
184
- await expect(
185
- searchPlugins(
186
- { query: "memory" },
187
- {
188
- fetch: (async () =>
189
- new Response("upstream broken", { status: 503 })) as FetchLike,
190
- },
191
- ),
192
- ).rejects.toThrow(/HTTP 503/);
184
+ test("HTTP 5xx is a transient PluginCatalogUnavailableError", async () => {
185
+ // GIVEN GitHub returns a 5xx (upstream outage)
186
+ // WHEN we search
187
+ // THEN the failure is classified transient so the caller can serve a
188
+ // stale cache and the route can map it to 503 (not a misleading 500).
189
+ const err = await searchPlugins(
190
+ { query: "memory" },
191
+ {
192
+ fetch: (async () =>
193
+ new Response("upstream broken", { status: 503 })) as FetchLike,
194
+ },
195
+ ).catch((e: unknown) => e);
196
+ expect(err).toBeInstanceOf(PluginCatalogUnavailableError);
197
+ expect((err as PluginCatalogUnavailableError).status).toBe(503);
193
198
  });
194
199
 
195
- test("HTTP 403 (rate-limited / forbidden) surfaces as an error", async () => {
196
- await expect(
197
- searchPlugins(
198
- { query: "memory" },
199
- {
200
- fetch: (async () =>
201
- new Response("rate limit exceeded", { status: 403 })) as FetchLike,
202
- },
203
- ),
204
- ).rejects.toThrow(/HTTP 403/);
200
+ test("rate-limited 403 (x-ratelimit-remaining: 0) is transient", async () => {
201
+ // GIVEN GitHub returns 403 with the rate-limit budget exhausted
202
+ // WHEN we search
203
+ // THEN it's transient — this is exactly the unauthenticated 60 req/hr
204
+ // exhaustion that empties the catalog, and it must surface as 503.
205
+ const err = await searchPlugins(
206
+ { query: "memory" },
207
+ {
208
+ fetch: (async () =>
209
+ new Response("rate limit exceeded", {
210
+ status: 403,
211
+ headers: { "x-ratelimit-remaining": "0" },
212
+ })) as FetchLike,
213
+ },
214
+ ).catch((e: unknown) => e);
215
+ expect(err).toBeInstanceOf(PluginCatalogUnavailableError);
216
+ expect((err as PluginCatalogUnavailableError).status).toBe(403);
205
217
  });
206
218
 
207
- test("404 on the plugins prefix surfaces as an error (not silently empty)", async () => {
219
+ test("a 403 without the rate-limit signal is a hard error", async () => {
220
+ // GIVEN a bare 403 (genuine permissions problem, not rate limiting)
221
+ // WHEN we search
222
+ // THEN it is NOT transient — serving a stale catalog would mask a real
223
+ // misconfiguration, so it propagates as a plain error.
224
+ const err = await searchPlugins(
225
+ { query: "memory" },
226
+ {
227
+ fetch: (async () =>
228
+ new Response("forbidden", { status: 403 })) as FetchLike,
229
+ },
230
+ ).catch((e: unknown) => e);
231
+ expect(err).toBeInstanceOf(Error);
232
+ expect(err).not.toBeInstanceOf(PluginCatalogUnavailableError);
233
+ expect((err as Error).message).toMatch(/HTTP 403/);
234
+ });
235
+
236
+ test("404 on the plugins prefix is a hard error (not silently empty)", async () => {
208
237
  // Distinct from `installPlugin`, where 404 on a specific plugin name is
209
238
  // normal "not found". For the search, 404 on the prefix means the
210
- // canonical source path itself is gone — that's an upstream problem
211
- // worth surfacing, not a clean empty result.
212
- await expect(
213
- searchPlugins(
214
- { query: "memory" },
215
- {
216
- fetch: (async () =>
217
- new Response("not found", { status: 404 })) as FetchLike,
218
- },
219
- ),
220
- ).rejects.toThrow(/HTTP 404/);
239
+ // canonical source path itself is gone — that's a real misconfiguration
240
+ // worth surfacing, not a clean empty result and not a transient outage.
241
+ const err = await searchPlugins(
242
+ { query: "memory" },
243
+ {
244
+ fetch: (async () =>
245
+ new Response("not found", { status: 404 })) as FetchLike,
246
+ },
247
+ ).catch((e: unknown) => e);
248
+ expect(err).toBeInstanceOf(Error);
249
+ expect(err).not.toBeInstanceOf(PluginCatalogUnavailableError);
250
+ expect((err as Error).message).toMatch(/HTTP 404/);
221
251
  });
222
252
 
223
253
  test("returns matches sorted by name", async () => {
@@ -238,6 +268,177 @@ describe("searchPlugins", () => {
238
268
  ]);
239
269
  });
240
270
 
271
+ test("merges whitelisted marketplace entries with first-party dirs", async () => {
272
+ // GIVEN the canonical repo serves both a first-party plugin listing and a
273
+ // marketplace manifest whitelisting an external plugin
274
+ const manifest = {
275
+ name: "vellum-assistant",
276
+ plugins: [
277
+ {
278
+ name: "caveman",
279
+ source: {
280
+ source: "github",
281
+ repo: "JuliusBrussee/caveman",
282
+ ref: "63a91ecadbf4c4719a4602a5abb00883f9966034",
283
+ },
284
+ description: "Ultra-compressed communication mode.",
285
+ },
286
+ ],
287
+ };
288
+ const fetch: FetchLike = (async (input: RequestInfo | URL) => {
289
+ const url = typeof input === "string" ? input : input.toString();
290
+ if (url.includes("marketplace.json")) {
291
+ return new Response(JSON.stringify(manifest), { status: 200 });
292
+ }
293
+ return new Response(
294
+ JSON.stringify([
295
+ {
296
+ name: "simple-memory",
297
+ path: "experimental/plugins/simple-memory",
298
+ type: "dir",
299
+ size: 0,
300
+ download_url: null,
301
+ },
302
+ ]),
303
+ { status: 200, headers: { "content-type": "application/json" } },
304
+ );
305
+ }) as FetchLike;
306
+
307
+ // WHEN we search the catalog with a match-all query
308
+ const result = await searchPlugins({ query: "" }, { fetch });
309
+
310
+ // THEN both sources appear, sorted by name, each tagged with its origin
311
+ expect(result.matches).toEqual([
312
+ {
313
+ name: "caveman",
314
+ path: "github:JuliusBrussee/caveman@63a91ecadbf4c4719a4602a5abb00883f9966034",
315
+ description: "Ultra-compressed communication mode.",
316
+ source: {
317
+ kind: "github",
318
+ repo: "JuliusBrussee/caveman",
319
+ ref: "63a91ecadbf4c4719a4602a5abb00883f9966034",
320
+ },
321
+ },
322
+ {
323
+ name: "simple-memory",
324
+ path: "experimental/plugins/simple-memory",
325
+ source: { kind: "first-party" },
326
+ },
327
+ ]);
328
+ });
329
+
330
+ test("filters marketplace entries by the query too", async () => {
331
+ // GIVEN a marketplace whitelisting an external plugin and no first-party dirs
332
+ const manifest = {
333
+ name: "vellum-assistant",
334
+ plugins: [
335
+ {
336
+ name: "caveman",
337
+ source: {
338
+ source: "github",
339
+ repo: "JuliusBrussee/caveman",
340
+ ref: "63a91ecadbf4c4719a4602a5abb00883f9966034",
341
+ },
342
+ },
343
+ ],
344
+ };
345
+ const fetch: FetchLike = (async (input: RequestInfo | URL) => {
346
+ const url = typeof input === "string" ? input : input.toString();
347
+ if (url.includes("marketplace.json")) {
348
+ return new Response(JSON.stringify(manifest), { status: 200 });
349
+ }
350
+ return new Response(JSON.stringify([]), { status: 200 });
351
+ }) as FetchLike;
352
+
353
+ // WHEN the query matches no plugin name
354
+ const result = await searchPlugins({ query: "memory" }, { fetch });
355
+
356
+ // THEN the non-matching marketplace entry is excluded
357
+ expect(result.matches).toEqual([]);
358
+ });
359
+
360
+ test("a marketplace entry owns a name shared by an in-repo stub dir", async () => {
361
+ // GIVEN a marketplace entry named "caveman"
362
+ const manifest = {
363
+ name: "vellum-assistant",
364
+ plugins: [
365
+ {
366
+ name: "caveman",
367
+ source: {
368
+ source: "github",
369
+ repo: "JuliusBrussee/caveman",
370
+ ref: "63a91ecadbf4c4719a4602a5abb00883f9966034",
371
+ },
372
+ },
373
+ ],
374
+ };
375
+ // AND a same-named in-repo directory, which is caveman's adapter stub
376
+ // rather than a standalone first-party plugin
377
+ const fetch: FetchLike = (async (input: RequestInfo | URL) => {
378
+ const url = typeof input === "string" ? input : input.toString();
379
+ if (url.includes("marketplace.json")) {
380
+ return new Response(JSON.stringify(manifest), { status: 200 });
381
+ }
382
+ return new Response(
383
+ JSON.stringify([
384
+ {
385
+ name: "caveman",
386
+ path: "experimental/plugins/caveman",
387
+ type: "dir",
388
+ size: 0,
389
+ download_url: null,
390
+ },
391
+ ]),
392
+ { status: 200 },
393
+ );
394
+ }) as FetchLike;
395
+
396
+ // WHEN we search
397
+ const result = await searchPlugins({ query: "caveman" }, { fetch });
398
+
399
+ // THEN the name surfaces once, as the external marketplace entry — the
400
+ // stub dir is its adapter overlay, not a competing first-party listing
401
+ expect(result.matches).toEqual([
402
+ {
403
+ name: "caveman",
404
+ path: "github:JuliusBrussee/caveman@63a91ecadbf4c4719a4602a5abb00883f9966034",
405
+ source: {
406
+ kind: "github",
407
+ repo: "JuliusBrussee/caveman",
408
+ ref: "63a91ecadbf4c4719a4602a5abb00883f9966034",
409
+ },
410
+ },
411
+ ]);
412
+ });
413
+
414
+ test("a broken marketplace manifest degrades to the first-party listing", async () => {
415
+ // GIVEN the manifest is malformed but the first-party listing is healthy
416
+ const fetch: FetchLike = (async (input: RequestInfo | URL) => {
417
+ const url = typeof input === "string" ? input : input.toString();
418
+ if (url.includes("marketplace.json")) {
419
+ return new Response("{ not json", { status: 200 });
420
+ }
421
+ return new Response(
422
+ JSON.stringify([
423
+ {
424
+ name: "simple-memory",
425
+ path: "experimental/plugins/simple-memory",
426
+ type: "dir",
427
+ size: 0,
428
+ download_url: null,
429
+ },
430
+ ]),
431
+ { status: 200 },
432
+ );
433
+ }) as FetchLike;
434
+
435
+ // WHEN we search
436
+ const result = await searchPlugins({ query: "" }, { fetch });
437
+
438
+ // THEN the core catalog is unaffected by the broken whitelist
439
+ expect(result.matches.map((m) => m.name)).toEqual(["simple-memory"]);
440
+ });
441
+
241
442
  test("sends no Authorization header (canonical source is a public repo)", async () => {
242
443
  let seenAuth: string | undefined;
243
444
  let seenUserAgent: string | undefined;