@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
@@ -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;