@vellumai/assistant 0.8.10 → 0.8.11-staging.1

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 (400) hide show
  1. package/bun.lock +62 -1
  2. package/docs/workspace-tools.md +196 -0
  3. package/examples/plugins/echo/README.md +3 -3
  4. package/knip.json +1 -0
  5. package/openapi.yaml +460 -128
  6. package/package.json +2 -1
  7. package/scripts/build-plugin-api.ts +299 -0
  8. package/src/__tests__/agent-loop-callsite-precedence.test.ts +7 -0
  9. package/src/__tests__/agent-loop-compaction-events.test.ts +197 -0
  10. package/src/__tests__/agent-loop-exit-reason.test.ts +93 -96
  11. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +2 -0
  12. package/src/__tests__/agent-loop-output-hooks.test.ts +274 -1
  13. package/src/__tests__/agent-loop-override-profile.test.ts +3 -0
  14. package/src/__tests__/agent-loop-provider-error-recording.test.ts +4 -0
  15. package/src/__tests__/agent-loop-thinking.test.ts +4 -0
  16. package/src/__tests__/agent-loop.test.ts +578 -5
  17. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -1
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
  20. package/src/__tests__/btw-routes.test.ts +0 -1
  21. package/src/__tests__/build-persisted-content.test.ts +75 -1
  22. package/src/__tests__/catalog-install-normalize.test.ts +141 -0
  23. package/src/__tests__/ces-startup-timeout.test.ts +60 -0
  24. package/src/__tests__/compaction-events.test.ts +1 -0
  25. package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
  26. package/src/__tests__/context-overflow-reducer.test.ts +264 -124
  27. package/src/__tests__/context-window-manager-overflow-rung.test.ts +351 -0
  28. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  29. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -5
  30. package/src/__tests__/conversation-agent-loop-overflow.test.ts +284 -455
  31. package/src/__tests__/conversation-agent-loop.test.ts +131 -551
  32. package/src/__tests__/conversation-app-control-instantiation.test.ts +13 -0
  33. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  34. package/src/__tests__/conversation-fork-crud.test.ts +259 -0
  35. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  36. package/src/__tests__/conversation-lifecycle.test.ts +257 -1
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +38 -377
  39. package/src/__tests__/conversation-queue.test.ts +1 -39
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +119 -8
  41. package/src/__tests__/conversation-skill-tools.test.ts +491 -5
  42. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  43. package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
  44. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  45. package/src/__tests__/conversation-store.test.ts +74 -0
  46. package/src/__tests__/conversation-surfaces-app-control.test.ts +4 -1
  47. package/src/__tests__/conversation-tool-setup-attribution.test.ts +323 -0
  48. package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +34 -0
  49. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  50. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  51. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -0
  52. package/src/__tests__/corrected-target.test.ts +93 -0
  53. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -5
  54. package/src/__tests__/credential-execution-tools.test.ts +23 -11
  55. package/src/__tests__/credential-security-invariants.test.ts +6 -1
  56. package/src/__tests__/db-schedule-syntax-migration.test.ts +80 -0
  57. package/src/__tests__/device-id.test.ts +70 -1
  58. package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
  59. package/src/__tests__/empty-response-hook.test.ts +242 -66
  60. package/src/__tests__/external-plugin-loader.test.ts +0 -31
  61. package/src/__tests__/get-skill-detail-audit.test.ts +43 -1
  62. package/src/__tests__/guardian-routing-invariants.test.ts +91 -0
  63. package/src/__tests__/history-repair-hook.test.ts +228 -3
  64. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +254 -9
  66. package/src/__tests__/identity-routes.test.ts +1 -0
  67. package/src/__tests__/image-recovery-hook.test.ts +387 -0
  68. package/src/__tests__/injector-chain.test.ts +5 -4
  69. package/src/__tests__/injector-v3-suppression.test.ts +373 -47
  70. package/src/__tests__/intent-routing.test.ts +7 -0
  71. package/src/__tests__/memory-retrieval-hook.test.ts +117 -15
  72. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  73. package/src/__tests__/oauth-store.test.ts +0 -85
  74. package/src/__tests__/{context-overflow-policy.test.ts → overflow-policy.test.ts} +1 -1
  75. package/src/__tests__/parallel-tool.benchmark.test.ts +4 -0
  76. package/src/__tests__/persist-unsendable-image-downscale.test.ts +29 -9
  77. package/src/__tests__/persist-unsendable-image.test.ts +4 -4
  78. package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
  79. package/src/__tests__/plugin-bootstrap.test.ts +82 -73
  80. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  81. package/src/__tests__/plugin-types.test.ts +0 -8
  82. package/src/__tests__/provider-catalog-visibility.test.ts +1 -9
  83. package/src/__tests__/prune-old-conversations-job.test.ts +99 -0
  84. package/src/__tests__/registry.test.ts +240 -1
  85. package/src/__tests__/require-fresh-approval.test.ts +3 -0
  86. package/src/__tests__/schedule-routes.test.ts +116 -1
  87. package/src/__tests__/schedule-store.test.ts +28 -0
  88. package/src/__tests__/schedule-tools.test.ts +94 -1
  89. package/src/__tests__/server-history-render.test.ts +39 -0
  90. package/src/__tests__/skill-projection-feature-flag.test.ts +13 -0
  91. package/src/__tests__/skill-projection.benchmark.test.ts +25 -7
  92. package/src/__tests__/skills.test.ts +202 -0
  93. package/src/__tests__/slim-skill-category.test.ts +195 -0
  94. package/src/__tests__/strip-memory-injections.test.ts +33 -39
  95. package/src/__tests__/test-support/tool-invocation-seed.ts +79 -0
  96. package/src/__tests__/title-generate-hook.test.ts +9 -7
  97. package/src/__tests__/tool-audit-listener.test.ts +264 -1
  98. package/src/__tests__/tool-error-hook.test.ts +4 -3
  99. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
  100. package/src/__tests__/tool-executor-lifecycle-events.test.ts +273 -0
  101. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
  102. package/src/__tests__/tool-start-timestamp.test.ts +218 -0
  103. package/src/__tests__/tools-get-route.test.ts +202 -0
  104. package/src/__tests__/workspace-tool-loader.test.ts +319 -0
  105. package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
  106. package/src/agent/loop.ts +569 -319
  107. package/src/api/events/tool-result.ts +9 -0
  108. package/src/api/events/tool-use-start.ts +7 -0
  109. package/src/api/index.ts +10 -0
  110. package/src/api/responses/conversation-message.ts +135 -27
  111. package/src/api/responses/memory-v3-selection-log.ts +4 -4
  112. package/src/approvals/guardian-request-resolvers.ts +26 -0
  113. package/src/browser-session/backends/host-bridge.ts +29 -0
  114. package/src/browser-session/index.ts +1 -0
  115. package/src/browser-session/types.ts +5 -1
  116. package/src/cli/commands/__tests__/schedules.test.ts +62 -4
  117. package/src/cli/commands/__tests__/skills.test.ts +53 -0
  118. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  119. package/src/cli/commands/inference-providers.ts +0 -8
  120. package/src/cli/commands/plugins.ts +2 -2
  121. package/src/cli/commands/schedules.ts +27 -4
  122. package/src/cli/commands/skills.ts +187 -146
  123. package/src/cli/commands/tools.ts +106 -0
  124. package/src/cli/lib/__tests__/install-from-github.test.ts +256 -328
  125. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +6 -2
  126. package/src/cli/lib/__tests__/plugin-details.test.ts +10 -16
  127. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +2 -2
  128. package/src/cli/lib/__tests__/search-plugins.test.ts +145 -240
  129. package/src/cli/lib/install-from-github.ts +187 -117
  130. package/src/cli/lib/plugin-catalog-cache.ts +9 -9
  131. package/src/cli/lib/plugin-details.ts +38 -68
  132. package/src/cli/lib/plugin-marketplace.ts +42 -14
  133. package/src/cli/lib/search-plugins.ts +29 -129
  134. package/src/cli/program.ts +2 -0
  135. package/src/config/bundled-skills/acp/SKILL.md +1 -0
  136. package/src/config/bundled-skills/app-builder/SKILL.md +1 -0
  137. package/src/config/bundled-skills/app-control/SKILL.md +1 -0
  138. package/src/config/bundled-skills/computer-use/SKILL.md +1 -0
  139. package/src/config/bundled-skills/contacts/SKILL.md +1 -0
  140. package/src/config/bundled-skills/document-editor/SKILL.md +1 -0
  141. package/src/config/bundled-skills/followups/SKILL.md +1 -0
  142. package/src/config/bundled-skills/image-studio/SKILL.md +1 -0
  143. package/src/config/bundled-skills/media-processing/SKILL.md +1 -0
  144. package/src/config/bundled-skills/messaging/SKILL.md +1 -0
  145. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -0
  146. package/src/config/bundled-skills/playbooks/SKILL.md +1 -0
  147. package/src/config/bundled-skills/schedule/SKILL.md +1 -0
  148. package/src/config/bundled-skills/schedule/TOOLS.json +11 -3
  149. package/src/config/bundled-skills/sequences/SKILL.md +1 -0
  150. package/src/config/bundled-skills/settings/SKILL.md +1 -0
  151. package/src/config/bundled-skills/skill-management/SKILL.md +101 -1
  152. package/src/config/bundled-skills/subagent/SKILL.md +1 -0
  153. package/src/config/bundled-skills/transcribe/SKILL.md +1 -0
  154. package/src/config/env-registry.ts +23 -0
  155. package/src/config/feature-flag-registry.json +17 -81
  156. package/src/config/loader.ts +5 -22
  157. package/src/config/schema.ts +2 -0
  158. package/src/config/schemas/__tests__/compaction-logs.test.ts +56 -0
  159. package/src/config/schemas/__tests__/memory-v2.test.ts +0 -1
  160. package/src/config/schemas/__tests__/memory-v3.test.ts +61 -1
  161. package/src/config/schemas/compaction-logs.ts +79 -0
  162. package/src/config/schemas/memory-v2.ts +0 -8
  163. package/src/config/schemas/memory-v3.ts +104 -33
  164. package/src/config/seed-inference-profiles.ts +1 -1
  165. package/src/config/skills.ts +117 -47
  166. package/src/context/compactor.ts +11 -0
  167. package/src/context/strip-injections.ts +38 -4
  168. package/src/credential-execution/feature-gates.ts +0 -21
  169. package/src/credential-execution/startup-timeout.ts +32 -4
  170. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +18 -0
  171. package/src/daemon/conversation-agent-loop-handlers.ts +140 -91
  172. package/src/daemon/conversation-agent-loop.ts +123 -658
  173. package/src/daemon/conversation-error.ts +6 -33
  174. package/src/daemon/conversation-lifecycle.ts +1 -1
  175. package/src/daemon/conversation-runtime-assembly.ts +183 -22
  176. package/src/daemon/conversation-skill-tools.ts +137 -8
  177. package/src/daemon/conversation-slash.ts +0 -14
  178. package/src/daemon/conversation-store.ts +2 -19
  179. package/src/daemon/conversation-tool-setup.ts +87 -1
  180. package/src/daemon/conversation.ts +119 -50
  181. package/src/daemon/external-plugins-bootstrap.ts +36 -95
  182. package/src/daemon/handlers/config-channels.ts +11 -2
  183. package/src/daemon/handlers/shared.ts +9 -1
  184. package/src/daemon/handlers/skills.ts +10 -4
  185. package/src/daemon/host-app-control-proxy.ts +72 -57
  186. package/src/daemon/host-browser-proxy.ts +117 -22
  187. package/src/daemon/lifecycle.ts +34 -5
  188. package/src/daemon/message-protocol.ts +0 -7
  189. package/src/daemon/message-types/schedules.ts +1 -0
  190. package/src/daemon/message-types/skills.ts +17 -0
  191. package/src/daemon/providers-setup.ts +3 -0
  192. package/src/daemon/server.ts +3 -3
  193. package/src/daemon/tool-setup-types.ts +9 -3
  194. package/src/daemon/trust-context.ts +23 -0
  195. package/src/daemon/workspace-tools-watcher.ts +324 -0
  196. package/src/events/tool-audit-listener.ts +78 -16
  197. package/src/events/tool-metrics-listener.ts +2 -5
  198. package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +227 -0
  199. package/src/memory/__tests__/conversation-queries.test.ts +176 -0
  200. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +20 -32
  201. package/src/memory/compaction-log-writer-clickhouse.ts +418 -0
  202. package/src/memory/conversation-crud.ts +134 -3
  203. package/src/memory/conversation-queries.ts +64 -7
  204. package/src/memory/db-init.ts +14 -0
  205. package/src/memory/embedding-backend.test.ts +130 -1
  206. package/src/memory/embedding-backend.ts +79 -106
  207. package/src/memory/embedding-gemini.ts +5 -0
  208. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +12 -0
  209. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +19 -0
  210. package/src/memory/graph/conversation-graph-memory.ts +36 -25
  211. package/src/memory/graph/tool-handlers.ts +3 -0
  212. package/src/memory/job-handlers/cleanup.ts +3 -1
  213. package/src/memory/jobs-store.ts +0 -28
  214. package/src/memory/jobs-worker.ts +10 -22
  215. package/src/memory/memory-marker.ts +29 -0
  216. package/src/memory/memory-retrospective-startup-cleanup.ts +1 -1
  217. package/src/memory/migrations/268-add-memory-v3-selections.ts +6 -0
  218. package/src/memory/migrations/270-schedule-description.ts +36 -0
  219. package/src/memory/migrations/275-tool-invocations-add-skill-id.test.ts +81 -0
  220. package/src/memory/migrations/275-tool-invocations-add-skill-id.ts +20 -0
  221. package/src/memory/migrations/276-tool-invocations-created-at-id-index.test.ts +68 -0
  222. package/src/memory/migrations/276-tool-invocations-created-at-id-index.ts +20 -0
  223. package/src/memory/migrations/277-add-memory-v3-ever-injected.ts +29 -0
  224. package/src/memory/migrations/278-tool-invocations-telemetry-columns.test.ts +96 -0
  225. package/src/memory/migrations/278-tool-invocations-telemetry-columns.ts +39 -0
  226. package/src/memory/migrations/279-create-skill-loaded-events.test.ts +84 -0
  227. package/src/memory/migrations/279-create-skill-loaded-events.ts +26 -0
  228. package/src/memory/migrations/280-conversations-surfaced-at.test.ts +88 -0
  229. package/src/memory/migrations/280-conversations-surfaced-at.ts +24 -0
  230. package/src/memory/migrations/index.ts +10 -0
  231. package/src/memory/migrations/registry.ts +8 -0
  232. package/src/memory/schema/conversations.ts +16 -0
  233. package/src/memory/schema/infrastructure.ts +26 -0
  234. package/src/memory/skill-loaded-events-store.test.ts +160 -0
  235. package/src/memory/skill-loaded-events-store.ts +95 -0
  236. package/src/memory/tool-executed-events-store.test.ts +219 -0
  237. package/src/memory/tool-executed-events-store.ts +102 -0
  238. package/src/memory/tool-usage-store.ts +15 -3
  239. package/src/memory/v2/__tests__/consolidation-job.test.ts +117 -12
  240. package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +189 -0
  241. package/src/memory/v2/__tests__/injected-block-slugs.test.ts +90 -0
  242. package/src/memory/v2/__tests__/page-store.test.ts +33 -0
  243. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +88 -15
  244. package/src/memory/v2/activation-store.ts +50 -1
  245. package/src/memory/v2/consolidation-job.ts +113 -29
  246. package/src/memory/v2/injected-block-slugs.ts +79 -0
  247. package/src/memory/v2/injection.ts +6 -1
  248. package/src/memory/v2/prompts/consolidation.ts +414 -13
  249. package/src/memory/v2/router.ts +2 -28
  250. package/src/memory/v2/static-context.ts +1 -1
  251. package/src/memory/v2/types.ts +16 -0
  252. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  253. package/src/notifications/access-request-copy.ts +298 -0
  254. package/src/notifications/adapters/slack.ts +3 -3
  255. package/src/notifications/adapters/telegram.ts +2 -1
  256. package/src/notifications/copy-composer.ts +49 -267
  257. package/src/notifications/decision-engine.ts +16 -35
  258. package/src/notifications/home-feed-side-effect.ts +1 -6
  259. package/src/oauth/oauth-store.ts +0 -9
  260. package/src/permissions/checker.test.ts +83 -1
  261. package/src/permissions/checker.ts +25 -2
  262. package/src/permissions/gateway-threshold-reader.test.ts +182 -0
  263. package/src/permissions/gateway-threshold-reader.ts +80 -0
  264. package/src/platform/client.ts +1 -3
  265. package/src/platform/feature-gate.ts +3 -12
  266. package/src/plugin-api/constants.ts +4 -2
  267. package/src/plugin-api/index.ts +63 -11
  268. package/src/plugin-api/types.ts +236 -71
  269. package/src/plugins/defaults/compaction/compact.ts +66 -2
  270. package/src/plugins/defaults/compaction/context-overflow-reducer.ts +240 -32
  271. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  272. package/src/{daemon/context-overflow-policy.ts → plugins/defaults/compaction/overflow-policy.ts} +1 -1
  273. package/src/plugins/defaults/compaction/window-manager.ts +303 -1
  274. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +173 -0
  275. package/src/plugins/defaults/empty-response/hooks/stop.ts +11 -115
  276. package/src/plugins/defaults/empty-response/nudge-state-store.ts +46 -0
  277. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +50 -0
  278. package/src/plugins/defaults/history-repair/hooks/stop.ts +22 -0
  279. package/src/plugins/defaults/history-repair/repair-state-store.ts +51 -0
  280. package/src/plugins/defaults/history-repair/terminal.ts +39 -2
  281. package/src/plugins/defaults/image-recovery/detect.ts +25 -0
  282. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +73 -0
  283. package/src/plugins/defaults/image-recovery/hooks/stop.ts +22 -0
  284. package/src/plugins/defaults/image-recovery/image-recovery-state-store.ts +48 -0
  285. package/src/plugins/defaults/image-recovery/package.json +14 -0
  286. package/src/{daemon/persist-unsendable-image.ts → plugins/defaults/image-recovery/recover.ts} +67 -14
  287. package/src/plugins/defaults/index.ts +71 -5
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +76 -112
  289. package/src/plugins/defaults/memory-retrieval/hooks/{user-prompt-submit-temp.ts → user-prompt-submit.ts} +83 -74
  290. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +14 -8
  291. package/src/plugins/defaults/memory-retrieval/injectors.ts +2 -18
  292. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  293. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1157 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +683 -0
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +161 -140
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +160 -0
  297. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +335 -316
  298. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +145 -53
  299. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +38 -1
  300. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +18 -8
  301. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +112 -71
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +192 -51
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +4 -16
  304. package/src/plugins/defaults/memory-v3-shadow/card.test.ts +173 -0
  305. package/src/plugins/defaults/memory-v3-shadow/card.ts +116 -0
  306. package/src/plugins/defaults/memory-v3-shadow/core-set.test.ts +104 -0
  307. package/src/plugins/defaults/memory-v3-shadow/core-set.ts +59 -0
  308. package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.test.ts +305 -0
  309. package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.ts +278 -0
  310. package/src/plugins/defaults/memory-v3-shadow/hot-set.test.ts +138 -0
  311. package/src/plugins/defaults/memory-v3-shadow/hot-set.ts +85 -0
  312. package/src/plugins/defaults/memory-v3-shadow/injector.ts +331 -24
  313. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +119 -13
  314. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +169 -114
  315. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +47 -16
  316. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +144 -66
  317. package/src/plugins/defaults/memory-v3-shadow/prune.test.ts +758 -0
  318. package/src/plugins/defaults/memory-v3-shadow/prune.ts +471 -0
  319. package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +68 -16
  320. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +19 -12
  321. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +96 -43
  322. package/src/plugins/defaults/memory-v3-shadow/types.ts +34 -17
  323. package/src/plugins/defaults/title-generate/hooks/stop.ts +9 -11
  324. package/src/plugins/pipeline.ts +8 -5
  325. package/src/plugins/types.ts +5 -51
  326. package/src/providers/cache-control.ts +26 -0
  327. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -2
  328. package/src/providers/model-catalog.ts +13 -1
  329. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +147 -0
  330. package/src/providers/openai/chat-completions-provider.ts +46 -0
  331. package/src/providers/openai/responses-provider.ts +45 -0
  332. package/src/providers/registry.ts +0 -8
  333. package/src/runtime/__tests__/agent-wake.test.ts +0 -1
  334. package/src/runtime/agent-wake.ts +13 -0
  335. package/src/runtime/routes/__tests__/acp-routes.test.ts +151 -0
  336. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +12 -50
  337. package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +322 -0
  338. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +0 -62
  339. package/src/runtime/routes/__tests__/plugins-routes.test.ts +31 -11
  340. package/src/runtime/routes/acp-routes.test.ts +106 -0
  341. package/src/runtime/routes/acp-routes.ts +248 -2
  342. package/src/runtime/routes/browser-tabs-routes.ts +1 -1
  343. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  344. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +0 -14
  345. package/src/runtime/routes/consolidation-routes.ts +6 -82
  346. package/src/runtime/routes/conversation-list-routes.ts +6 -0
  347. package/src/runtime/routes/conversation-management-routes.ts +71 -0
  348. package/src/runtime/routes/identity-routes.ts +8 -0
  349. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  350. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -45
  351. package/src/runtime/routes/plugins-routes.ts +34 -45
  352. package/src/runtime/routes/schedule-routes.ts +43 -5
  353. package/src/runtime/routes/settings-routes.ts +140 -15
  354. package/src/runtime/routes/skills-routes.ts +18 -6
  355. package/src/runtime/services/__tests__/conversation-serializer.test.ts +140 -0
  356. package/src/runtime/services/conversation-serializer.ts +38 -1
  357. package/src/runtime/verification-outbound-actions.ts +147 -2
  358. package/src/runtime/verification-templates.ts +29 -3
  359. package/src/schedule/schedule-store.ts +19 -0
  360. package/src/skills/catalog-install.ts +77 -13
  361. package/src/tasks/task-scheduler.ts +1 -0
  362. package/src/telemetry/types.ts +66 -1
  363. package/src/telemetry/usage-telemetry-reporter.test.ts +542 -13
  364. package/src/telemetry/usage-telemetry-reporter.ts +213 -20
  365. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +49 -2
  366. package/src/tools/browser/__tests__/browser-status.test.ts +29 -5
  367. package/src/tools/browser/browser-execution.ts +27 -9
  368. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +380 -4
  369. package/src/tools/browser/cdp-client/__tests__/host-bridge-cdp-client.test.ts +107 -0
  370. package/src/tools/browser/cdp-client/__tests__/types.test.ts +6 -1
  371. package/src/tools/browser/cdp-client/factory.ts +217 -17
  372. package/src/tools/browser/cdp-client/host-bridge-cdp-client.ts +67 -0
  373. package/src/tools/browser/cdp-client/types.ts +22 -2
  374. package/src/tools/credential-execution/make-authenticated-request.ts +2 -1
  375. package/src/tools/credential-execution/manage-secure-command-tool.ts +169 -164
  376. package/src/tools/credential-execution/run-authenticated-command.ts +2 -1
  377. package/src/tools/executor.ts +39 -7
  378. package/src/tools/registry.ts +387 -5
  379. package/src/tools/schedule/create.ts +16 -0
  380. package/src/tools/schedule/list.ts +12 -4
  381. package/src/tools/schedule/update.ts +12 -0
  382. package/src/tools/skills/load.ts +11 -6
  383. package/src/tools/terminal/safe-env.ts +2 -0
  384. package/src/tools/types.ts +65 -9
  385. package/src/tools/workspace-tools/loader.ts +673 -0
  386. package/src/usage/attribution.ts +28 -0
  387. package/src/util/device-id.ts +17 -3
  388. package/src/util/platform.ts +16 -0
  389. package/tsconfig.plugin-api.json +13 -0
  390. package/src/__tests__/plugin-external-api.test.ts +0 -68
  391. package/src/__tests__/plugin-skill-contribution.test.ts +0 -355
  392. package/src/daemon/message-types/browser.ts +0 -10
  393. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  394. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +0 -36
  395. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +0 -37
  396. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +0 -106
  397. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +0 -44
  398. package/src/plugins/defaults/memory-v3-shadow/working-set.ts +0 -91
  399. package/src/plugins/external-api.ts +0 -114
  400. package/src/plugins/plugin-skill-contributions.ts +0 -292
@@ -1,24 +1,19 @@
1
1
  /**
2
2
  * Install a plugin by name from the canonical GitHub source.
3
3
  *
4
- * A name resolves to one of two sources, materialized into
5
- * `<workspacePluginsDir>/<name>/` so the daemon discovers it on next start:
6
- * 1. A whitelisted external ecosystem plugin, when the name matches an entry
7
- * in the curated `experimental/plugins/marketplace.json` manifest. The
8
- * pinned `owner/repo[/path]@ref` (see {@link ./plugin-marketplace}) is
9
- * fetched with a shallow `git` clone at that ref — one network operation
10
- * regardless of repo size, immune to GitHub's unauthenticated API
11
- * rate-limit, and recording the exact resolved commit for provenance.
12
- * When we curate an adapter stub for the plugin (an
13
- * `experimental/plugins/<name>/` directory in this repo with a
14
- * `scripts.postinstall` command), the stub is overlaid onto the clone and
15
- * its postinstall runs to translate a foreign-ecosystem layout into the
16
- * shape Vellum's loader runs (see {@link applyAdapterStub}).
17
- * 2. Otherwise the first-party convention
18
- * `vellum-ai/vellum-assistant/experimental/plugins/<name>/` at the
19
- * configured ref, fetched via the GitHub Contents API (a small handful of
20
- * in-repo files — cloning the whole monorepo to install one would be
21
- * wasteful).
4
+ * A name resolves to a whitelisted external ecosystem plugin — an entry in the
5
+ * curated `plugins/marketplace.json` manifest. The pinned
6
+ * `owner/repo[/path]@ref` (see {@link ./plugin-marketplace}) is fetched with a
7
+ * shallow `git` clone at that ref — one network operation regardless of repo
8
+ * size, immune to GitHub's unauthenticated API rate-limit, and recording the
9
+ * exact resolved commit for provenance — and materialized into
10
+ * `<workspacePluginsDir>/<name>/` so the daemon discovers it on next start.
11
+ *
12
+ * When we curate an adapter stub for the plugin (a `plugins/<name>/` directory
13
+ * in this repo with a `scripts.postinstall` command), the stub is overlaid
14
+ * onto the clone and its postinstall runs to translate a foreign-ecosystem
15
+ * layout into the shape Vellum's loader runs (see {@link applyAdapterStub}). A
16
+ * name with no marketplace entry is a not-found.
22
17
  *
23
18
  * Designed for direct programmatic use. The CLI command
24
19
  * `assistant plugins install <name>` is a thin wrapper that supplies
@@ -47,6 +42,8 @@ import { ensureBun } from "../../util/bun-runtime.js";
47
42
  import { getWorkspacePluginsDir } from "../../util/platform.js";
48
43
  import {
49
44
  fetchMarketplaceEntries,
45
+ MarketplaceFetchError,
46
+ type ResolvedPluginSource,
50
47
  resolveMarketplaceSource,
51
48
  } from "./plugin-marketplace.js";
52
49
 
@@ -54,7 +51,7 @@ const execFileAsync = promisify(execFile);
54
51
 
55
52
  const PLUGIN_SOURCE_OWNER = "vellum-ai";
56
53
  const PLUGIN_SOURCE_REPO = "vellum-assistant";
57
- const PLUGIN_SOURCE_PATH_PREFIX = "experimental/plugins";
54
+ const PLUGIN_SOURCE_PATH_PREFIX = "plugins";
58
55
  /** Default git ref to fetch from when callers don't override. */
59
56
  export const DEFAULT_PLUGIN_REF = "main";
60
57
 
@@ -130,7 +127,7 @@ export interface InstallPluginResult {
130
127
  readonly target: string;
131
128
  readonly fileCount: number;
132
129
  readonly ref: string;
133
- /** Resolved commit SHA for git-cloned external sources; null for first-party (no clone). */
130
+ /** Resolved commit SHA the external source was cloned at; null when it could not be read. */
134
131
  readonly commit: string | null;
135
132
  }
136
133
 
@@ -214,8 +211,6 @@ function isTransientUpstreamStatus(res: Response): boolean {
214
211
 
215
212
  /** Resolved GitHub coordinates a plugin name is fetched from. */
216
213
  interface PluginFetchSource {
217
- /** Whether the plugin lives in our monorepo or an external whitelisted repo. */
218
- readonly kind: "first-party" | "external";
219
214
  readonly owner: string;
220
215
  readonly repo: string;
221
216
  /** Repo-relative directory holding the plugin root; `""` = repo root. */
@@ -230,54 +225,42 @@ function sourceLabel(source: PluginFetchSource): string {
230
225
  : `${source.owner}/${source.repo}`;
231
226
  }
232
227
 
233
- /** First-party `experimental/plugins/<name>` coordinates at a given ref. */
234
- function firstPartySource(name: string, ref: string): PluginFetchSource {
235
- return {
236
- kind: "first-party",
237
- owner: PLUGIN_SOURCE_OWNER,
238
- repo: PLUGIN_SOURCE_REPO,
239
- rootPath: `${PLUGIN_SOURCE_PATH_PREFIX}/${name}`,
240
- ref,
241
- };
242
- }
243
-
244
228
  /**
245
- * Resolve a plugin name to concrete GitHub coordinates.
229
+ * Resolve a plugin name to the concrete GitHub coordinates of its pinned
230
+ * marketplace entry, or `null` when no entry claims the name.
246
231
  *
247
- * A name claimed by the curated marketplace resolves to its pinned external
248
- * repo; any other name resolves to the first-party `experimental/plugins/<name>`
249
- * convention. The marketplace is external-only by construction — a same-named
250
- * `experimental/plugins/<name>` directory is the plugin's optional *adapter
251
- * stub* (a curated `package.json` + postinstall script overlaid onto the clone
252
- * to translate it into Vellum's shape; see {@link applyAdapterStub}), not a
253
- * standalone first-party plugin. So letting the marketplace win the name is
254
- * what makes the stub apply to the external clone, and the search catalog
255
- * surfaces the same name as external — install and search stay in agreement.
232
+ * The marketplace is external-only by construction — a same-named
233
+ * `plugins/<name>` directory is the plugin's optional *adapter stub* (a curated
234
+ * `package.json` + postinstall script overlaid onto the clone to translate it
235
+ * into Vellum's shape; see {@link applyAdapterStub}), not a standalone plugin.
256
236
  *
257
- * A missing or malformed manifest degrades to first-party resolution — the
258
- * whitelist is supplementary and must never block installing a first-party
259
- * plugin. An external name then surfaces a clear not-found error downstream.
237
+ * A transient marketplace failure (rate-limit / 5xx) surfaces as a retryable
238
+ * {@link PluginSourceUnavailableError}; a malformed manifest propagates as a
239
+ * hard error, since the marketplace is the source of truth for what is
240
+ * installable.
260
241
  */
261
242
  async function resolvePluginSource(
262
243
  name: string,
263
244
  marketplaceRef: string,
264
245
  fetchFn: FetchLike,
265
- ): Promise<PluginFetchSource> {
266
- let resolved = null;
246
+ ): Promise<PluginFetchSource | null> {
247
+ let resolved: ResolvedPluginSource | null;
267
248
  try {
268
249
  const entries = await fetchMarketplaceEntries(
269
250
  { fetch: fetchFn },
270
251
  { ref: marketplaceRef },
271
252
  );
272
253
  resolved = resolveMarketplaceSource(name, entries);
273
- } catch {
274
- // Degrade to first-party resolution below.
254
+ } catch (err) {
255
+ if (err instanceof MarketplaceFetchError && err.transient) {
256
+ throw new PluginSourceUnavailableError(err.message, err.status ?? 503);
257
+ }
258
+ throw err;
275
259
  }
276
260
 
277
- if (!resolved) return firstPartySource(name, marketplaceRef);
261
+ if (!resolved) return null;
278
262
 
279
263
  return {
280
- kind: "external",
281
264
  owner: resolved.owner,
282
265
  repo: resolved.repo,
283
266
  rootPath: resolved.path,
@@ -288,7 +271,7 @@ async function resolvePluginSource(
288
271
  /**
289
272
  * Reject plugin names that could escape the canonical source path or the
290
273
  * install target. The source convention is a flat namespace under
291
- * `experimental/plugins/`, so a legitimate name is a single path segment
274
+ * `plugins/`, so a legitimate name is a single path segment
292
275
  * built from kebab-case alphanumerics.
293
276
  *
294
277
  * Exported so callers (e.g. the CLI input prompt) can validate up front
@@ -344,6 +327,13 @@ export async function installPlugin(
344
327
  const force = opts.force ?? false;
345
328
 
346
329
  const source = await resolvePluginSource(name, marketplaceRef, deps.fetch);
330
+ if (!source) {
331
+ throw new PluginNotFoundError(
332
+ name,
333
+ marketplaceRef,
334
+ "plugins/marketplace.json",
335
+ );
336
+ }
347
337
  const ref = source.ref;
348
338
 
349
339
  const pluginsDir = deps.workspacePluginsDir ?? getWorkspacePluginsDir();
@@ -353,10 +343,19 @@ export async function installPlugin(
353
343
  throw new PluginAlreadyInstalledError(name, target);
354
344
  }
355
345
 
356
- // Stage into a sibling temp dir so an in-progress install never destroys
357
- // the currently installed version. `process.pid` keeps concurrent installs
358
- // of the same plugin from clobbering each other's staging.
359
- const stagingDir = `${target}.installing.${process.pid}`;
346
+ // Stage *outside* the served `plugins/` directory. The daemon watches that
347
+ // directory and its startup loader enumerates it, so a staging dir living
348
+ // inside it is observed mid-install — before the adapter overlay runs, an
349
+ // external clone still carries its upstream `package.json` (wrong name, no
350
+ // plugin-api peer dep), which the loader rejects with spurious name-mismatch
351
+ // and missing-peer-dependency warnings. Staging in a sibling directory keeps
352
+ // the half-built tree invisible until the final swap. The root is on the
353
+ // same filesystem as the target, so that swap stays an atomic rename.
354
+ // `process.pid` keeps concurrent installs of the same plugin from clobbering
355
+ // each other's staging.
356
+ const stagingRoot = join(dirname(pluginsDir), ".plugins-staging");
357
+ mkdirSync(stagingRoot, { recursive: true });
358
+ const stagingDir = join(stagingRoot, `${name}.installing.${process.pid}`);
360
359
  if (existsSync(stagingDir)) {
361
360
  rmSync(stagingDir, { recursive: true, force: true });
362
361
  }
@@ -365,52 +364,20 @@ export async function installPlugin(
365
364
  let fileCount: number;
366
365
  let commit: string | null = null;
367
366
  try {
368
- if (source.kind === "external") {
369
- const cloned = await copyExternalViaGit(
370
- source,
371
- stagingDir,
372
- deps.runGit ?? defaultGitRunner,
373
- );
374
- fileCount = cloned.fileCount;
375
- commit = cloned.commit;
376
- // An external clone is often a foreign-ecosystem plugin (e.g. a Claude
377
- // Code plugin) that the Vellum loader can't run as-is. When we curate an
378
- // adapter stub for it, overlay the stub and run its transform so the
379
- // materialized tree is a valid Vellum plugin. Raw clones (no stub) are
380
- // left untouched.
381
- if (fileCount > 0) {
382
- await applyAdapterStub(name, marketplaceRef, stagingDir, deps);
383
- }
384
- } else {
385
- fileCount = await copyDir(
386
- source.owner,
387
- source.repo,
388
- source.rootPath,
389
- ref,
390
- stagingDir,
391
- deps.fetch,
392
- );
393
- // We only land in the first-party branch for this name when the
394
- // marketplace lookup returned no claim. A *healthy* marketplace that
395
- // claims the name routes to the external+adapter branch above; reaching
396
- // here for a directory that is actually an adapter stub (declares a
397
- // `scripts.postinstall`) therefore means the marketplace failed to load
398
- // (rate-limit / 5xx / malformed) and we degraded past it. A stub has no
399
- // hooks/tools of its own — it only transforms an external clone — so
400
- // installing it alone would materialize a non-functional plugin. Fail
401
- // loudly and retryably instead of silently shipping a broken plugin.
402
- // Genuine first-party plugins (no postinstall) install normally.
403
- if (
404
- fileCount > 0 &&
405
- resolvePostinstallScript(name, stagingDir) !== null
406
- ) {
407
- throw new PluginPostinstallError(
408
- name,
409
- "resolved to a first-party adapter stub, but its marketplace entry " +
410
- "could not be read to locate the external source it adapts — the " +
411
- "marketplace lookup likely failed transiently. Retry the install.",
412
- );
413
- }
367
+ const cloned = await copyExternalViaGit(
368
+ source,
369
+ stagingDir,
370
+ deps.runGit ?? defaultGitRunner,
371
+ );
372
+ fileCount = cloned.fileCount;
373
+ commit = cloned.commit;
374
+ // An external clone is often a foreign-ecosystem plugin (e.g. a Claude
375
+ // Code plugin) that the Vellum loader can't run as-is. When we curate an
376
+ // adapter stub for it, overlay the stub and run its transform so the
377
+ // materialized tree is a valid Vellum plugin. Raw clones (no stub) are
378
+ // left untouched.
379
+ if (fileCount > 0) {
380
+ await applyAdapterStub(name, marketplaceRef, stagingDir, deps);
414
381
  }
415
382
  } catch (err) {
416
383
  rmSync(stagingDir, { recursive: true, force: true });
@@ -431,6 +398,9 @@ export async function installPlugin(
431
398
  // Atomic-ish swap: rmSync + renameSync. On POSIX the rename itself is
432
399
  // atomic, so the only window where the target is absent is between the
433
400
  // rm and the rename — and at that point the staging dir is fully populated.
401
+ // Ensure the served `plugins/` directory exists: staging now lives outside
402
+ // it, so the target's parent is no longer created as a side effect.
403
+ mkdirSync(pluginsDir, { recursive: true });
434
404
  if (existsSync(target)) {
435
405
  rmSync(target, { recursive: true, force: true });
436
406
  }
@@ -534,14 +504,16 @@ const POSTINSTALL_TIMEOUT_MS = 60_000;
534
504
  * Overlay our curated adapter stub onto a freshly cloned external plugin and
535
505
  * run its postinstall transform, returning whether a transform ran.
536
506
  *
537
- * The stub lives at `experimental/plugins/<name>/` in our own repo and carries
507
+ * The stub lives at `plugins/<name>/` in our own repo and carries
538
508
  * a `package.json` (with a `scripts.postinstall` adapter command) plus the
539
509
  * adapter script it names. We fetch it via the Contents API — a couple of
540
- * small files, well within the rate limit — and copy it over the clone, which
541
- * deliberately overwrites the clone's `package.json` so the postinstall we run
542
- * is ours, never the upstream repo's lifecycle script. Absent a stub (the
543
- * common case for a plugin already in Vellum shape), nothing is overlaid and
544
- * the clone is installed as-is.
510
+ * small files, well within the rate limit — and copy it over the clone so the
511
+ * postinstall we run is ours, never the upstream repo's lifecycle script. The
512
+ * overlaid stub `package.json` exists only to name that adapter; the installed
513
+ * plugin's manifest is rebuilt from the upstream `package.json` afterwards (see
514
+ * {@link normalizeInstalledManifest}). Absent a stub (the common case for a
515
+ * plugin already in Vellum shape), nothing is overlaid and the clone is
516
+ * installed as-is.
545
517
  *
546
518
  * On any adapter failure the error propagates so {@link installPlugin} rolls
547
519
  * back staging — better to fail loudly than ship a half-transformed plugin.
@@ -552,6 +524,11 @@ async function applyAdapterStub(
552
524
  stagingDir: string,
553
525
  deps: InstallPluginDeps,
554
526
  ): Promise<boolean> {
527
+ // Capture the cloned upstream manifest before the stub overlay replaces it,
528
+ // so the installed plugin can preserve it verbatim except for the two fields
529
+ // the Vellum loader requires (name + plugin-api peer dep).
530
+ const upstreamPkg = readPackageJson(join(stagingDir, "package.json"));
531
+
555
532
  const stubFileCount = await copyDir(
556
533
  PLUGIN_SOURCE_OWNER,
557
534
  PLUGIN_SOURCE_REPO,
@@ -571,9 +548,89 @@ async function applyAdapterStub(
571
548
  } catch (err) {
572
549
  throw new PluginPostinstallError(name, subprocessErrorText(err));
573
550
  }
551
+
552
+ normalizeInstalledManifest(name, stagingDir, upstreamPkg);
574
553
  return true;
575
554
  }
576
555
 
556
+ /**
557
+ * Default `@vellumai/plugin-api` peer-dependency range stamped onto an adapted
558
+ * plugin that doesn't already declare one.
559
+ */
560
+ const PLUGIN_API_PEER_RANGE = ">=0.8.0";
561
+
562
+ type PackageManifest = Record<string, unknown>;
563
+
564
+ /** Parse the `package.json` at `path`, or null if it's absent or unparseable. */
565
+ function readPackageJson(path: string): PackageManifest | null {
566
+ if (!existsSync(path)) return null;
567
+ try {
568
+ const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
569
+ return typeof parsed === "object" &&
570
+ parsed !== null &&
571
+ !Array.isArray(parsed)
572
+ ? (parsed as PackageManifest)
573
+ : null;
574
+ } catch {
575
+ return null;
576
+ }
577
+ }
578
+
579
+ /**
580
+ * Write the installed plugin's final `package.json` after the adapter has run.
581
+ *
582
+ * A curated adapter stub deliberately overlays its own `package.json` onto the
583
+ * clone so the installer can find and run the stub's `scripts.postinstall`.
584
+ * That stub is install-time machinery, not the plugin's manifest, so once the
585
+ * adapter has run we rebuild the manifest from the upstream `package.json`
586
+ * captured before the overlay — preserving its `version`, `description`,
587
+ * `license`, and every other field — and mutate only what the Vellum loader
588
+ * requires: `name` must equal the install directory, and `@vellumai/plugin-api`
589
+ * must be declared as a peer dependency. The spent `postinstall` script is
590
+ * dropped so the installed plugin carries no install-time machinery.
591
+ *
592
+ * When the upstream repo shipped no `package.json`, the overlaid stub is the
593
+ * only manifest available, so it becomes the base instead.
594
+ */
595
+ function normalizeInstalledManifest(
596
+ name: string,
597
+ stagingDir: string,
598
+ upstreamPkg: PackageManifest | null,
599
+ ): void {
600
+ const manifestPath = join(stagingDir, "package.json");
601
+ const base = upstreamPkg ?? readPackageJson(manifestPath) ?? {};
602
+
603
+ const peer =
604
+ typeof base.peerDependencies === "object" && base.peerDependencies !== null
605
+ ? (base.peerDependencies as Record<string, unknown>)
606
+ : {};
607
+ const existingRange = peer["@vellumai/plugin-api"];
608
+
609
+ const manifest: PackageManifest = {
610
+ ...base,
611
+ name,
612
+ peerDependencies: {
613
+ ...peer,
614
+ "@vellumai/plugin-api":
615
+ typeof existingRange === "string"
616
+ ? existingRange
617
+ : PLUGIN_API_PEER_RANGE,
618
+ },
619
+ };
620
+
621
+ if (typeof manifest.scripts === "object" && manifest.scripts !== null) {
622
+ const scripts = { ...(manifest.scripts as Record<string, unknown>) };
623
+ delete scripts.postinstall;
624
+ if (Object.keys(scripts).length === 0) {
625
+ delete manifest.scripts;
626
+ } else {
627
+ manifest.scripts = scripts;
628
+ }
629
+ }
630
+
631
+ writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
632
+ }
633
+
577
634
  /**
578
635
  * Resolve the absolute path of the adapter script named by the (overlaid stub)
579
636
  * `package.json`'s `scripts.postinstall`, or `null` when there is no stub
@@ -695,7 +752,8 @@ function pluginPostinstallEnv(bun: string): NodeJS.ProcessEnv {
695
752
 
696
753
  /**
697
754
  * Recursively copy regular files from `srcRoot` into `destDir`, skipping the
698
- * top-level `.git` directory and any symlinks. Returns the file count.
755
+ * top-level `.git` directory, a top-level `bunfig.toml` (see below), and any
756
+ * symlinks. Returns the file count.
699
757
  */
700
758
  function copyTreeSkippingGit(srcRoot: string, destDir: string): number {
701
759
  let count = 0;
@@ -705,6 +763,17 @@ function copyTreeSkippingGit(srcRoot: string, destDir: string): number {
705
763
  // Drop git metadata and symlinks: the loader follows neither, and a
706
764
  // symlink could otherwise point outside the staging tree.
707
765
  if (relDir === "" && entry.name === ".git") continue;
766
+ // Drop a top-level `bunfig.toml`. The adapter postinstall runs `bun` with
767
+ // its cwd at the staged root, and Bun auto-loads `$cwd/bunfig.toml` as
768
+ // project config — including a `preload` list it executes before the
769
+ // entry point. An upstream config would therefore run arbitrary code
770
+ // ahead of the curated adapter, defeating the command/env guards. Bun
771
+ // reads only the cwd's file (it neither walks up nor descends), so
772
+ // dropping it at the root closes the vector; a Vellum plugin never
773
+ // consumes `bunfig.toml`. Match case-insensitively because the macOS
774
+ // install target's filesystem is case-insensitive, where Bun would still
775
+ // open a clone-supplied `BUNFIG.TOML`.
776
+ if (relDir === "" && entry.name.toLowerCase() === "bunfig.toml") continue;
708
777
  if (entry.isSymbolicLink()) continue;
709
778
 
710
779
  const rel = relDir ? join(relDir, entry.name) : entry.name;
@@ -799,7 +868,7 @@ function writeInstallManifest(
799
868
  const manifest = {
800
869
  name,
801
870
  source: {
802
- kind: source.kind,
871
+ kind: "github",
803
872
  owner: source.owner,
804
873
  repo: source.repo,
805
874
  path: source.rootPath || undefined,
@@ -815,13 +884,14 @@ function writeInstallManifest(
815
884
  }
816
885
 
817
886
  /**
818
- * Recursively copy a first-party plugin directory via the GitHub Contents API.
887
+ * Recursively copy a curated adapter stub directory via the GitHub Contents API.
819
888
  *
820
- * First-party plugins live in our own monorepo as a small handful of files, so
821
- * the per-directory walk stays well within the unauthenticated rate limit —
822
- * and avoids cloning the entire repository just to install one plugin. Returns
823
- * the number of files written; zero means the directory doesn't exist at this
824
- * ref, which the caller maps to a not-found error.
889
+ * An adapter stub lives in our own monorepo at `plugins/<name>/` as a small
890
+ * handful of files (a `package.json` + postinstall script), so the
891
+ * per-directory walk stays well within the unauthenticated rate limit — and
892
+ * avoids cloning the entire repository just to overlay it. Returns the number
893
+ * of files written; zero means no stub exists at this ref, in which case the
894
+ * external clone is installed as-is.
825
895
  */
826
896
  async function copyDir(
827
897
  owner: string,
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * In-memory cache for the installable plugin catalog.
3
3
  *
4
- * The catalog (`loadPluginCatalog`) is built from two unauthenticated GitHub
5
- * Contents API calls — the first-party `experimental/plugins/` directory
6
- * listing and the `marketplace.json` manifest. GitHub's unauthenticated rate
7
- * limit is 60 requests/hour per IP, so a long-lived daemon that hit GitHub on
8
- * every catalog search (on web mount, on every keystroke, across reloads and
9
- * multiple clients) would exhaust that budget and start getting HTTP 403s.
4
+ * The catalog (`loadPluginCatalog`) is built from a single unauthenticated
5
+ * GitHub Contents API call — the `marketplace.json` manifest. GitHub's
6
+ * unauthenticated rate limit is 60 requests/hour per IP, so a long-lived
7
+ * daemon that hit GitHub on every catalog search (on web mount, on every
8
+ * keystroke, across reloads and multiple clients) would exhaust that budget
9
+ * and start getting HTTP 403s.
10
10
  *
11
11
  * Because the GitHub responses are **query-independent** — `searchPlugins`
12
12
  * applies the regex filter in memory — one catalog load serves any number of
@@ -44,9 +44,9 @@ const cache = new Map<string, CacheEntry>();
44
44
  * transient upstream error ({@link PluginCatalogUnavailableError} — rate
45
45
  * limiting or a 5xx) and a cached catalog exists, the stale catalog is served
46
46
  * (and its TTL window is reset so a sustained outage doesn't re-enter the
47
- * failing fetch on every call). A hard error (e.g. the canonical plugins
48
- * prefix returning 404, or a deleted ref) always propagates — serving stale
49
- * there would silently mask a real misconfiguration. With no cache to fall
47
+ * failing fetch on every call). A hard error (e.g. a malformed manifest, or a
48
+ * deleted ref) always propagates — serving stale there would silently mask a
49
+ * real misconfiguration. With no cache to fall
50
50
  * back on, the underlying error propagates regardless so the caller can
51
51
  * surface it (e.g. the route maps a rate-limit failure to 503).
52
52
  */
@@ -6,18 +6,19 @@
6
6
  * most authoritative for each field:
7
7
  * 1. The locally installed copy under `<workspacePluginsDir>/<name>/`, when
8
8
  * present — its `package.json` and `README.md` are read straight off disk.
9
- * 2. The curated `experimental/plugins/marketplace.json` entry, for external
9
+ * 2. The curated `plugins/marketplace.json` entry, for external
10
10
  * ecosystem plugins (description / homepage / license / pinned source).
11
- * 3. The plugin's own repository at the pinned ref (first-party
12
- * `experimental/plugins/<name>/` or the external `owner/repo[/path]`),
11
+ * 3. The plugin's own external repository at the pinned `owner/repo[/path]`,
13
12
  * fetched via the GitHub Contents API for the README and any
14
13
  * `package.json` fields the manifest doesn't carry.
15
14
  *
16
- * Name-collision precedence matches {@link ./search-plugins} and
17
- * {@link ./install-from-github}: a marketplace entry owns its name, so the
15
+ * The `source` field is the marketplace entry's pinned origin when one claims
16
+ * the name, otherwise `null` — an installed copy with no catalog entry has no
17
+ * advertised origin. Name-collision precedence matches {@link ./search-plugins}
18
+ * and {@link ./install-from-github}: a marketplace entry owns its name, so the
18
19
  * detail page advertises the external source the catalog and installer use. A
19
- * same-named `experimental/plugins/<name>/` directory is that plugin's adapter
20
- * stub, not a standalone first-party plugin, so it does not override the claim.
20
+ * same-named `plugins/<name>/` directory is that plugin's adapter stub, not a
21
+ * standalone plugin, so it does not override the claim.
21
22
  *
22
23
  * Designed for direct programmatic use with an injected `fetch`, mirroring the
23
24
  * sibling plugin libraries.
@@ -38,10 +39,6 @@ import {
38
39
  } from "./plugin-marketplace.js";
39
40
  import type { PluginMatchSource } from "./search-plugins.js";
40
41
 
41
- const PLUGIN_SOURCE_OWNER = "vellum-ai";
42
- const PLUGIN_SOURCE_REPO = "vellum-assistant";
43
- const PLUGIN_SOURCE_PATH_PREFIX = "experimental/plugins";
44
-
45
42
  /** Recognised README filenames, matched case-insensitively against a listing. */
46
43
  const README_RE = /^readme(\.md|\.markdown)?$/i;
47
44
 
@@ -91,8 +88,11 @@ export interface PluginDetails {
91
88
  readonly license: string | null;
92
89
  /** Resolved version (installed copy first, then repo `package.json`); `null` when unknown. */
93
90
  readonly version: string | null;
94
- /** Discriminated origin, mirroring the catalog's {@link PluginMatchSource}. */
95
- readonly source: PluginMatchSource;
91
+ /**
92
+ * Pinned origin, mirroring the catalog's {@link PluginMatchSource}; `null`
93
+ * when an installed copy has no marketplace entry to advertise an origin.
94
+ */
95
+ readonly source: PluginMatchSource | null;
96
96
  /** README markdown, or `null` when the plugin ships none. */
97
97
  readonly readme: string | null;
98
98
  /** Git ref the catalog metadata / README were resolved at. */
@@ -114,8 +114,8 @@ export class PluginDetailsNotFoundError extends Error {
114
114
  * Resolve the detail view for {@link opts.name}.
115
115
  *
116
116
  * Throws {@link PluginDetailsNotFoundError} when the name is neither installed
117
- * locally nor present in the catalog (first-party directory or marketplace
118
- * entry). Network failures while enriching from GitHub degrade to the fields
117
+ * locally nor present in the marketplace catalog. Network failures while
118
+ * enriching from GitHub degrade to the fields
119
119
  * already known from disk / the manifest rather than failing the whole view —
120
120
  * a detail page that renders metadata without a README beats a hard error.
121
121
  */
@@ -132,40 +132,24 @@ export async function getPluginDetails(
132
132
 
133
133
  const marketplaceEntry = await findMarketplaceEntry(name, ref, fetchFn);
134
134
 
135
- // A marketplace entry owns its name (the same-named in-repo directory, if
136
- // any, is its adapter stub) — so only probe the first-party directory when
137
- // the name is unclaimed, which also spares a GitHub request in the common
138
- // external case.
139
- const firstPartyEntries =
140
- marketplaceEntry === null
141
- ? await listDirSafe(
142
- PLUGIN_SOURCE_OWNER,
143
- PLUGIN_SOURCE_REPO,
144
- `${PLUGIN_SOURCE_PATH_PREFIX}/${name}`,
145
- ref,
146
- fetchFn,
147
- )
148
- : null;
149
- const firstPartyExists = firstPartyEntries !== null;
150
-
151
- if (!local.installed && !firstPartyExists && !marketplaceEntry) {
135
+ if (!local.installed && !marketplaceEntry) {
152
136
  throw new PluginDetailsNotFoundError(name, ref);
153
137
  }
154
138
 
155
- const useExternal = marketplaceEntry !== null;
156
-
157
- const source: PluginMatchSource = useExternal
139
+ const source: PluginMatchSource | null = marketplaceEntry
158
140
  ? {
159
141
  kind: "github",
160
- repo: marketplaceEntry!.source.repo,
161
- ref: marketplaceEntry!.source.ref,
162
- ...(marketplaceEntry!.source.path
163
- ? { path: marketplaceEntry!.source.path }
142
+ repo: marketplaceEntry.source.repo,
143
+ ref: marketplaceEntry.source.ref,
144
+ ...(marketplaceEntry.source.path
145
+ ? { path: marketplaceEntry.source.path }
164
146
  : {}),
165
147
  }
166
- : { kind: "first-party" };
148
+ : null;
167
149
 
168
- const remote = await readRemotePlugin(source, firstPartyEntries, fetchFn);
150
+ const remote = source
151
+ ? await readRemotePlugin(source, fetchFn)
152
+ : { manifest: emptyManifest(), readme: null };
169
153
 
170
154
  const readme = local.readme ?? remote.readme;
171
155
 
@@ -244,39 +228,25 @@ interface RemotePlugin {
244
228
  }
245
229
 
246
230
  /**
247
- * Fetch README + `package.json` from the plugin's repository.
231
+ * Fetch README + `package.json` from the plugin's external repository.
248
232
  *
249
- * For a first-party plugin the listing is the in-repo directory the caller
250
- * already fetched at the catalog ref (passed through to avoid a duplicate
251
- * request). For an external plugin we list its `owner/repo[/path]` directory
252
- * fresh at the plugin's own pinned `source.ref`.
233
+ * Lists the plugin's `owner/repo[/path]` directory fresh at its own pinned
234
+ * `source.ref` — the detail page must describe the same artifact the installer
235
+ * resolves from `source.ref`, even when that differs from the catalog ref
236
+ * (`main`).
253
237
  */
254
238
  async function readRemotePlugin(
255
239
  source: PluginMatchSource,
256
- firstPartyEntries: readonly GitHubContentEntry[] | null,
257
240
  fetchFn: FetchLike,
258
241
  ): Promise<RemotePlugin> {
259
- let owner: string;
260
- let repo: string;
261
- let entries: readonly GitHubContentEntry[] | null;
262
-
263
- if (source.kind === "github") {
264
- [owner, repo] = source.repo.split("/", 2) as [string, string];
265
- // Read the external repo at its pinned ref, not the assistant catalog ref —
266
- // the detail page must describe the same artifact the installer resolves
267
- // from `source.ref`, even when that differs from the catalog ref (`main`).
268
- entries = await listDirSafe(
269
- owner,
270
- repo,
271
- source.path ?? "",
272
- source.ref,
273
- fetchFn,
274
- );
275
- } else {
276
- owner = PLUGIN_SOURCE_OWNER;
277
- repo = PLUGIN_SOURCE_REPO;
278
- entries = firstPartyEntries;
279
- }
242
+ const [owner, repo] = source.repo.split("/", 2) as [string, string];
243
+ const entries = await listDirSafe(
244
+ owner,
245
+ repo,
246
+ source.path ?? "",
247
+ source.ref,
248
+ fetchFn,
249
+ );
280
250
 
281
251
  if (!entries) {
282
252
  return { manifest: emptyManifest(), readme: null };