@vellumai/assistant 0.8.1 → 0.8.2

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 (506) hide show
  1. package/ARCHITECTURE.md +2 -7
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +5 -0
  5. package/docker-init-apt-root.sh +94 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +325 -3
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  21. package/src/__tests__/anthropic-provider.test.ts +45 -0
  22. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  23. package/src/__tests__/app-executors.test.ts +220 -4
  24. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  25. package/src/__tests__/bundled-asset.test.ts +6 -6
  26. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  27. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  28. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  29. package/src/__tests__/clawhub.test.ts +75 -16
  30. package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
  31. package/src/__tests__/config-schema.test.ts +21 -0
  32. package/src/__tests__/config-set-route.test.ts +80 -0
  33. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  34. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  35. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  36. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  37. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  38. package/src/__tests__/context-token-estimator.test.ts +1 -0
  39. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  42. package/src/__tests__/conversation-agent-loop.test.ts +2 -0
  43. package/src/__tests__/conversation-error.test.ts +42 -3
  44. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  45. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  46. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  47. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  48. package/src/__tests__/conversation-pairing.test.ts +54 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  51. package/src/__tests__/conversation-queue.test.ts +4 -1
  52. package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
  53. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  54. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  55. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  56. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  59. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  60. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  61. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  62. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  63. package/src/__tests__/dm-backfill.test.ts +121 -10
  64. package/src/__tests__/document-tool-security.test.ts +258 -0
  65. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  66. package/src/__tests__/edit-propagation.test.ts +33 -0
  67. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  68. package/src/__tests__/external-plugin-loader.test.ts +60 -36
  69. package/src/__tests__/filing-service.test.ts +140 -0
  70. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  71. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  72. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  73. package/src/__tests__/helpers/wait-for.ts +21 -0
  74. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  75. package/src/__tests__/history-repair.test.ts +73 -0
  76. package/src/__tests__/host-app-control-proxy.test.ts +266 -10
  77. package/src/__tests__/image-credentials.test.ts +1 -1
  78. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  79. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  80. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  81. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  82. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  83. package/src/__tests__/injector-chain.test.ts +10 -8
  84. package/src/__tests__/install-skill-routing.test.ts +155 -37
  85. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
  86. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  87. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  88. package/src/__tests__/llm-catalog-parity.test.ts +55 -13
  89. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
  90. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  91. package/src/__tests__/llm-usage-store.test.ts +114 -0
  92. package/src/__tests__/managed-profile-guard.test.ts +31 -29
  93. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  94. package/src/__tests__/managed-store.test.ts +84 -192
  95. package/src/__tests__/media-generate-image.test.ts +1 -1
  96. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  97. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  98. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  99. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  100. package/src/__tests__/openai-provider.test.ts +24 -0
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  102. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  103. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  104. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
  105. package/src/__tests__/platform.test.ts +2 -0
  106. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  107. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  108. package/src/__tests__/plugin-external-api.test.ts +68 -0
  109. package/src/__tests__/plugin-registry.test.ts +0 -77
  110. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  111. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  112. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  113. package/src/__tests__/plugin-types.test.ts +3 -13
  114. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  115. package/src/__tests__/process-message-display-content.test.ts +421 -0
  116. package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
  117. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  118. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
  119. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  120. package/src/__tests__/schedule-routes.test.ts +50 -3
  121. package/src/__tests__/schedule-store.test.ts +94 -0
  122. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  123. package/src/__tests__/schema-transforms.test.ts +20 -0
  124. package/src/__tests__/search-skills-unified.test.ts +0 -5
  125. package/src/__tests__/server-history-render.test.ts +43 -0
  126. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  127. package/src/__tests__/skill-load-tool.test.ts +27 -89
  128. package/src/__tests__/skill-memory.test.ts +23 -3
  129. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  130. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  131. package/src/__tests__/skills-install-extract.test.ts +49 -38
  132. package/src/__tests__/skills-install-staging.test.ts +159 -0
  133. package/src/__tests__/skills-uninstall.test.ts +9 -41
  134. package/src/__tests__/skills.test.ts +51 -58
  135. package/src/__tests__/slack-channel-config.test.ts +9 -0
  136. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  137. package/src/__tests__/system-prompt.test.ts +737 -63
  138. package/src/__tests__/terminal-tools.test.ts +28 -1
  139. package/src/__tests__/thread-backfill.test.ts +557 -27
  140. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  141. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  142. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  143. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  145. package/src/__tests__/tool-executor.test.ts +16 -4
  146. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  147. package/src/__tests__/turn-events-store.test.ts +256 -0
  148. package/src/__tests__/twilio-routes.test.ts +4 -0
  149. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  150. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  151. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  152. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  153. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  154. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  155. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  156. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  157. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  158. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  159. package/src/acp/resolve-agent.ts +1 -1
  160. package/src/agent/image-optimize.ts +13 -5
  161. package/src/calls/voice-session-bridge.ts +61 -42
  162. package/src/channels/types.ts +108 -0
  163. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  164. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  165. package/src/cli/commands/__tests__/schedules.test.ts +491 -0
  166. package/src/cli/commands/changelog.ts +106 -42
  167. package/src/cli/commands/conversations.ts +102 -17
  168. package/src/cli/commands/default-action.ts +10 -53
  169. package/src/cli/commands/notifications.ts +329 -317
  170. package/src/cli/commands/plugins.ts +185 -0
  171. package/src/cli/commands/schedules.ts +391 -0
  172. package/src/cli/commands/telemetry.ts +40 -0
  173. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  174. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  177. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  178. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  179. package/src/cli/lib/cli-colors.ts +12 -0
  180. package/src/cli/lib/confirm-prompt.ts +79 -0
  181. package/src/cli/lib/install-from-github.ts +304 -0
  182. package/src/cli/lib/list-installed-plugins.ts +137 -0
  183. package/src/cli/lib/uninstall-plugin.ts +82 -0
  184. package/src/cli/lib/unknown-command.ts +111 -0
  185. package/src/cli/program.ts +38 -2
  186. package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
  187. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  188. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  189. package/src/config/bundled-skills/document/SKILL.md +23 -3
  190. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  191. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  192. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  193. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  194. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  195. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  196. package/src/config/bundled-tool-registry.ts +6 -0
  197. package/src/config/feature-flag-registry.json +41 -1
  198. package/src/config/loader.ts +64 -38
  199. package/src/config/schema.ts +7 -10
  200. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  201. package/src/config/schemas/channels.ts +8 -0
  202. package/src/config/schemas/compaction.ts +28 -0
  203. package/src/config/schemas/heartbeat.ts +9 -0
  204. package/src/config/schemas/llm-request-logs.ts +31 -7
  205. package/src/config/schemas/llm.ts +3 -0
  206. package/src/config/schemas/memory-retrieval.ts +18 -0
  207. package/src/config/schemas/tools.ts +14 -0
  208. package/src/config/skills.ts +3 -96
  209. package/src/context/compactor.ts +1047 -0
  210. package/src/context/token-estimator.ts +2 -2
  211. package/src/context/window-manager.ts +197 -1520
  212. package/src/credential-execution/managed-catalog.ts +37 -0
  213. package/src/credential-health/credential-health-service.ts +280 -19
  214. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
  215. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  216. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  217. package/src/daemon/approval-generators.ts +8 -6
  218. package/src/daemon/config-watcher.ts +94 -31
  219. package/src/daemon/conversation-agent-loop.ts +169 -9
  220. package/src/daemon/conversation-error.ts +171 -37
  221. package/src/daemon/conversation-lifecycle.ts +53 -40
  222. package/src/daemon/conversation-messaging.ts +25 -6
  223. package/src/daemon/conversation-process.ts +49 -12
  224. package/src/daemon/conversation-runtime-assembly.ts +16 -1
  225. package/src/daemon/conversation-slash.ts +12 -5
  226. package/src/daemon/conversation-store.ts +11 -4
  227. package/src/daemon/conversation-tool-setup.ts +39 -7
  228. package/src/daemon/conversation.ts +33 -1
  229. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  230. package/src/daemon/first-greeting.ts +22 -2
  231. package/src/daemon/handlers/config-model.ts +6 -5
  232. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  233. package/src/daemon/handlers/shared.ts +14 -5
  234. package/src/daemon/handlers/skills.ts +111 -108
  235. package/src/daemon/history-repair.ts +28 -1
  236. package/src/daemon/host-app-control-proxy.ts +98 -23
  237. package/src/daemon/lifecycle.ts +45 -35
  238. package/src/daemon/meet-host-supervisor.ts +5 -4
  239. package/src/daemon/memory-v2-startup.ts +49 -0
  240. package/src/daemon/message-protocol.ts +1 -0
  241. package/src/daemon/message-types/conversations.ts +25 -0
  242. package/src/daemon/message-types/messages.ts +61 -0
  243. package/src/daemon/message-types/subagents.ts +1 -0
  244. package/src/daemon/message-types/sync.ts +1 -0
  245. package/src/daemon/pkb-reminder-builder.test.ts +1 -1
  246. package/src/daemon/pkb-reminder-builder.ts +1 -1
  247. package/src/daemon/plugin-source-watcher.ts +146 -0
  248. package/src/daemon/process-message.ts +21 -3
  249. package/src/daemon/server.ts +11 -2
  250. package/src/daemon/skill-memory-refresh.ts +29 -0
  251. package/src/documents/document-store.ts +221 -3
  252. package/src/embedded/plugin-api.ts +40 -0
  253. package/src/filing/filing-service.ts +39 -0
  254. package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
  255. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  256. package/src/heartbeat/heartbeat-service.ts +41 -0
  257. package/src/home/__tests__/feed-types.test.ts +40 -0
  258. package/src/home/feed-types.ts +22 -0
  259. package/src/home/post-connect-feed.ts +1 -0
  260. package/src/index.ts +18 -1
  261. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  262. package/src/mcp/client.ts +20 -4
  263. package/src/media/image-credentials.ts +3 -3
  264. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  265. package/src/memory/__tests__/conversation-queries.test.ts +263 -0
  266. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  267. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  268. package/src/memory/__tests__/message-content.test.ts +35 -0
  269. package/src/memory/bookmark-crud.ts +42 -10
  270. package/src/memory/context-search/sources/conversations.ts +62 -2
  271. package/src/memory/context-search/sources/workspace.ts +4 -0
  272. package/src/memory/conversation-crud.ts +63 -19
  273. package/src/memory/conversation-queries.ts +110 -10
  274. package/src/memory/db-init.ts +6 -0
  275. package/src/memory/delivery-crud.ts +152 -5
  276. package/src/memory/embedding-backend.ts +4 -4
  277. package/src/memory/external-conversation-store.ts +66 -5
  278. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
  279. package/src/memory/graph/conversation-graph-memory.ts +31 -15
  280. package/src/memory/graph/tools.ts +3 -3
  281. package/src/memory/indexer.ts +34 -29
  282. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  283. package/src/memory/jobs/embed-concept-page.ts +20 -11
  284. package/src/memory/jobs-worker.ts +6 -1
  285. package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
  286. package/src/memory/llm-request-log-source.ts +19 -52
  287. package/src/memory/llm-usage-store.ts +125 -5
  288. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  289. package/src/memory/message-content.ts +1 -1
  290. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  291. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  292. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  293. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  294. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  295. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  296. package/src/memory/migrations/index.ts +6 -0
  297. package/src/memory/migrations/registry.ts +8 -0
  298. package/src/memory/onboarding-events-store.ts +106 -0
  299. package/src/memory/schema/bookmarks.ts +0 -2
  300. package/src/memory/schema/calls.ts +1 -0
  301. package/src/memory/schema/inference.ts +1 -3
  302. package/src/memory/schema/infrastructure.ts +12 -0
  303. package/src/memory/turn-events-store.ts +127 -2
  304. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  305. package/src/memory/v2/__tests__/injection.test.ts +98 -8
  306. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  307. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  308. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  309. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  310. package/src/memory/v2/__tests__/router.test.ts +15 -0
  311. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  312. package/src/memory/v2/injection.ts +32 -6
  313. package/src/memory/v2/migration.ts +49 -19
  314. package/src/memory/v2/page-index.ts +35 -5
  315. package/src/memory/v2/prompts/router.ts +11 -8
  316. package/src/memory/v2/prompts/sweep.ts +2 -2
  317. package/src/memory/v2/qdrant.ts +135 -7
  318. package/src/memory/v2/router.ts +9 -8
  319. package/src/memory/v2/skill-store.ts +120 -35
  320. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  321. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  322. package/src/messaging/providers/slack/adapter.ts +43 -5
  323. package/src/messaging/providers/slack/client.ts +27 -0
  324. package/src/messaging/providers/slack/deep-link.ts +65 -0
  325. package/src/messaging/providers/slack/download.ts +104 -0
  326. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  327. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  328. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  329. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  330. package/src/messaging/providers/slack/types.ts +20 -1
  331. package/src/notifications/conversation-pairing.ts +2 -1
  332. package/src/notifications/decision-engine.ts +2 -1
  333. package/src/notifications/emit-signal.ts +20 -1
  334. package/src/notifications/home-feed-side-effect.ts +54 -0
  335. package/src/notifications/signal.ts +3 -1
  336. package/src/oauth/connection-resolver.ts +8 -4
  337. package/src/oauth/platform-connection.ts +6 -2
  338. package/src/oauth/seed-providers.ts +10 -1
  339. package/src/permissions/checker.ts +2 -0
  340. package/src/permissions/ipc-risk-types.ts +1 -0
  341. package/src/permissions/question-prompter.test.ts +416 -0
  342. package/src/permissions/question-prompter.ts +294 -0
  343. package/src/platform/client.test.ts +1 -1
  344. package/src/platform/client.ts +1 -1
  345. package/src/plugin-api/constants.ts +26 -0
  346. package/src/plugin-api/index.ts +34 -1
  347. package/src/plugin-api/types.ts +104 -22
  348. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  349. package/src/plugins/defaults/compaction.ts +0 -4
  350. package/src/plugins/defaults/empty-response.ts +0 -2
  351. package/src/plugins/defaults/history-repair.ts +0 -2
  352. package/src/plugins/defaults/injectors.ts +36 -3
  353. package/src/plugins/defaults/llm-call.ts +0 -2
  354. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  355. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  356. package/src/plugins/defaults/persistence.ts +0 -2
  357. package/src/plugins/defaults/title-generate.ts +0 -5
  358. package/src/plugins/defaults/token-estimate.ts +0 -2
  359. package/src/plugins/defaults/tool-error.ts +0 -7
  360. package/src/plugins/defaults/tool-execute.ts +0 -2
  361. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  362. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  363. package/src/plugins/external-api.ts +104 -0
  364. package/src/plugins/external-plugin-loader.ts +105 -32
  365. package/src/plugins/feature-gate.ts +22 -0
  366. package/src/plugins/pipeline.ts +37 -0
  367. package/src/plugins/registry.ts +48 -80
  368. package/src/plugins/types.ts +31 -26
  369. package/src/plugins/user-loader.ts +21 -2
  370. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  371. package/src/proactive-artifact/job.test.ts +37 -5
  372. package/src/prompts/__tests__/system-prompt.test.ts +12 -0
  373. package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
  374. package/src/prompts/normalize-onboarding.ts +27 -0
  375. package/src/prompts/sections.ts +302 -0
  376. package/src/prompts/system-prompt.ts +63 -166
  377. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  378. package/src/prompts/templates/system-sections.ts +173 -0
  379. package/src/providers/__tests__/inference.test.ts +22 -7
  380. package/src/providers/anthropic/client.ts +28 -28
  381. package/src/providers/connection-resolution.ts +7 -0
  382. package/src/providers/inference/adapter-factory.ts +41 -4
  383. package/src/providers/inference/connections.ts +74 -29
  384. package/src/providers/inference/resolve-auth.ts +12 -4
  385. package/src/providers/model-catalog.ts +294 -12
  386. package/src/providers/openai/chat-completions-provider.ts +10 -2
  387. package/src/providers/openrouter/client.ts +7 -0
  388. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
  389. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  390. package/src/providers/provider-availability.ts +17 -2
  391. package/src/providers/provider-catalog-visibility.ts +36 -0
  392. package/src/providers/registry.ts +22 -14
  393. package/src/providers/retry.ts +47 -1
  394. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  395. package/src/runtime/agent-wake.ts +42 -14
  396. package/src/runtime/auth/route-policy.ts +8 -1
  397. package/src/runtime/btw-sidechain.ts +2 -0
  398. package/src/runtime/http-types.ts +19 -0
  399. package/src/runtime/migrations/origin-mode.ts +1 -1
  400. package/src/runtime/pending-interactions.ts +1 -0
  401. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  402. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  403. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
  404. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  405. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  406. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  407. package/src/runtime/routes/acp-routes.ts +5 -3
  408. package/src/runtime/routes/auth-routes.ts +1 -1
  409. package/src/runtime/routes/bookmark-routes.ts +5 -3
  410. package/src/runtime/routes/btw-routes.ts +5 -1
  411. package/src/runtime/routes/channel-availability-routes.ts +121 -0
  412. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  413. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  414. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  415. package/src/runtime/routes/conversation-query-routes.ts +40 -35
  416. package/src/runtime/routes/conversation-routes.ts +90 -11
  417. package/src/runtime/routes/documents-routes.ts +25 -86
  418. package/src/runtime/routes/group-routes.ts +5 -0
  419. package/src/runtime/routes/inbound-conversation.ts +28 -8
  420. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  421. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  422. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  423. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  424. package/src/runtime/routes/index.ts +6 -0
  425. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  426. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  427. package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
  428. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  429. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  430. package/src/runtime/routes/integrations/twilio.ts +6 -13
  431. package/src/runtime/routes/notification-routes.ts +1 -1
  432. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  433. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  434. package/src/runtime/routes/question-routes.ts +259 -0
  435. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  436. package/src/runtime/routes/schedule-routes.ts +4 -7
  437. package/src/runtime/routes/subagents-routes.ts +57 -18
  438. package/src/runtime/routes/telemetry-routes.ts +27 -0
  439. package/src/runtime/routes/tts-routes.ts +27 -2
  440. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  441. package/src/runtime/routes/workspace-routes.ts +28 -0
  442. package/src/runtime/services/conversation-serializer.ts +39 -7
  443. package/src/runtime/sync/resource-sync-events.ts +93 -1
  444. package/src/schedule/schedule-store.ts +27 -2
  445. package/src/schedule/scheduler.ts +9 -1
  446. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  447. package/src/security/untrusted-content.ts +93 -8
  448. package/src/skills/catalog-files.ts +1 -1
  449. package/src/skills/catalog-install.ts +233 -116
  450. package/src/skills/clawhub.ts +70 -13
  451. package/src/skills/managed-store.ts +4 -119
  452. package/src/skills/skillssh-registry.ts +27 -48
  453. package/src/subagent/manager.ts +15 -7
  454. package/src/telemetry/types.ts +113 -1
  455. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  456. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  457. package/src/tools/apps/executors.ts +58 -7
  458. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  459. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  460. package/src/tools/browser/browser-execution.ts +15 -11
  461. package/src/tools/computer-use/definitions.ts +3 -3
  462. package/src/tools/credentials/vault.ts +1 -1
  463. package/src/tools/document/document-tool.ts +124 -1
  464. package/src/tools/filesystem/edit.ts +1 -1
  465. package/src/tools/filesystem/list.ts +1 -1
  466. package/src/tools/filesystem/read.ts +1 -1
  467. package/src/tools/filesystem/write.ts +5 -2
  468. package/src/tools/host-filesystem/transfer.ts +1 -1
  469. package/src/tools/host-terminal/host-shell.ts +1 -1
  470. package/src/tools/permission-checker.ts +1 -1
  471. package/src/tools/registry.ts +17 -7
  472. package/src/tools/schedule/create.ts +2 -2
  473. package/src/tools/schema-transforms.ts +7 -2
  474. package/src/tools/side-effects.ts +1 -0
  475. package/src/tools/skills/delete-managed.ts +4 -4
  476. package/src/tools/skills/execute.ts +1 -1
  477. package/src/tools/skills/scaffold-managed.ts +3 -2
  478. package/src/tools/subagent/notify-parent.ts +1 -1
  479. package/src/tools/system/request-permission.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +60 -1
  481. package/src/tools/tool-manifest.ts +2 -0
  482. package/src/tools/types.ts +72 -21
  483. package/src/tools/ui-surface/definitions.ts +6 -5
  484. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  485. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  486. package/src/types/onboarding-context.ts +2 -0
  487. package/src/util/errors.ts +17 -0
  488. package/src/util/platform.ts +10 -0
  489. package/src/watcher/__tests__/engine.test.ts +22 -0
  490. package/src/watcher/engine.ts +6 -2
  491. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  492. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  493. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  494. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  495. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  496. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  497. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  498. package/src/workspace/migrations/registry.ts +8 -0
  499. package/src/workspace/migrations/runner.ts +39 -9
  500. package/src/workspace/migrations/types.ts +4 -0
  501. package/examples/plugins/echo/bun.lock +0 -25
  502. package/src/__tests__/context-window-manager.test.ts +0 -2481
  503. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  504. package/src/context/prompts/compact.md +0 -26
  505. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  506. /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
@@ -11,8 +11,11 @@
11
11
  *
12
12
  * <pluginDir>/
13
13
  * package.json ← manifest.name comes from `name`
14
- * (npm scope stripped); manifest.requires
15
- * comes from `vellum.requires` if present
14
+ * (npm scope stripped);
15
+ * peerDependencies["@vellumai/plugin-api"]
16
+ * semver range is checked against the
17
+ * running assistant version and rejects
18
+ * the plugin if unsatisfied
16
19
  * hooks/
17
20
  * <name>.ts ← default export → plugin.hooks[<name>]
18
21
  * (today the runtime invokes "init" at
@@ -40,8 +43,10 @@ import { readFile } from "node:fs/promises";
40
43
  import { join } from "node:path";
41
44
  import { pathToFileURL } from "node:url";
42
45
 
46
+ import semver from "semver";
43
47
  import { z } from "zod";
44
48
 
49
+ import assistantPkg from "../../package.json" with { type: "json" };
45
50
  import { getLogger } from "../util/logger.js";
46
51
  import { registerPlugin } from "./registry.js";
47
52
  import type {
@@ -52,6 +57,8 @@ import type {
52
57
  PluginToolRegistration,
53
58
  } from "./types.js";
54
59
 
60
+ const PLUGIN_API_PEER_DEP = "@vellumai/plugin-api";
61
+
55
62
  const log = getLogger("external-plugin-loader");
56
63
 
57
64
  /** Default upper bound on how long a single plugin load may take. */
@@ -61,9 +68,11 @@ const DEFAULT_IMPORT_TIMEOUT_MS = 10_000;
61
68
  * Zod schema for the subset of `package.json` the external loader reads.
62
69
  *
63
70
  * - `name` is the only required field; everything else is best-effort.
64
- * - `vellum.requires` is forwarded to {@link PluginManifest.requires}
65
- * verbatim (no merge with defaults) empty objects propagate so the
66
- * registry can reject under-specified plugins.
71
+ * - `peerDependencies["@vellumai/plugin-api"]` is the canonical host-compat
72
+ * declaration. If present, the loader checks `semver.satisfies(host, range)`
73
+ * against the running assistant version and rejects the plugin on
74
+ * mismatch. If absent, the plugin loads without a host-compat claim
75
+ * (with a warning).
67
76
  * - Unknown fields pass through (`passthrough`) so the loader does not
68
77
  * destructively reshape the file when the rest of the npm ecosystem
69
78
  * writes to it.
@@ -72,12 +81,7 @@ const PluginPackageJsonSchema = z
72
81
  .object({
73
82
  name: z.string().min(1, "package.json `name` must be a non-empty string"),
74
83
  version: z.string().optional(),
75
- vellum: z
76
- .object({
77
- requires: z.record(z.string(), z.string()).optional(),
78
- })
79
- .passthrough()
80
- .optional(),
84
+ peerDependencies: z.record(z.string(), z.string()).optional(),
81
85
  })
82
86
  .passthrough();
83
87
 
@@ -213,12 +217,49 @@ async function buildPluginFromDir(pluginDir: string): Promise<Plugin> {
213
217
  const name = stripScope(pkg.name);
214
218
  const version = pkg.version && pkg.version.length > 0 ? pkg.version : "0.0.0";
215
219
 
216
- // Default `requires` keeps the existing v1 negotiation working for
217
- // plugins that have not yet opted into `vellum.requires`. Plugins that
218
- // set `vellum.requires` get exactly what they declared no merge.
219
- const requires = pkg.vellum?.requires ?? { pluginRuntime: "v1" };
220
+ // Host-compat negotiation: plugins declare their plugin-api version
221
+ // range via standard `peerDependencies["@vellumai/plugin-api"]`. We
222
+ // inspect the range and report unparseable / unsatisfied cases via
223
+ // `log.error` but still load the plugin the plugin-installation
224
+ // flow is in flux and a strict gate here would block experimentation
225
+ // for the customers driving the install UX. Once the install path
226
+ // settles, the two `log.error` branches below should harden into
227
+ // throws so a stale plugin can't silently run against a mismatched
228
+ // host.
229
+ //
230
+ // If the peerDep is absent, the plugin loads without a host-compat
231
+ // claim; we log a warning so the omission is visible at boot.
232
+ const range = pkg.peerDependencies?.[PLUGIN_API_PEER_DEP];
233
+ if (range !== undefined) {
234
+ if (!semver.validRange(range)) {
235
+ log.error(
236
+ { pluginDir, plugin: name, peerDep: PLUGIN_API_PEER_DEP, range },
237
+ `external plugin ${name}: peerDependencies["${PLUGIN_API_PEER_DEP}"] is not a valid semver range — loading anyway`,
238
+ );
239
+ } else if (
240
+ !semver.satisfies(assistantPkg.version, range, {
241
+ includePrerelease: true,
242
+ })
243
+ ) {
244
+ log.error(
245
+ {
246
+ pluginDir,
247
+ plugin: name,
248
+ peerDep: PLUGIN_API_PEER_DEP,
249
+ range,
250
+ assistantVersion: assistantPkg.version,
251
+ },
252
+ `external plugin ${name}: peerDependencies["${PLUGIN_API_PEER_DEP}"] requires "${range}" but assistant is ${assistantPkg.version} — loading anyway`,
253
+ );
254
+ }
255
+ } else {
256
+ log.warn(
257
+ { pluginDir, plugin: name, peerDep: PLUGIN_API_PEER_DEP },
258
+ "external plugin missing plugin-api peerDependency — loading without host-compat claim",
259
+ );
260
+ }
220
261
 
221
- const manifest: PluginManifest = { name, version, requires };
262
+ const manifest: PluginManifest = { name, version };
222
263
  const plugin: Plugin = { manifest };
223
264
 
224
265
  const hooks = await loadHooks(pluginDir, name);
@@ -244,16 +285,23 @@ async function buildPluginFromDir(pluginDir: string): Promise<Plugin> {
244
285
  }
245
286
 
246
287
  /**
247
- * Load the external plugin at `pluginDir` and register it.
288
+ * Build a {@link Plugin} from `pluginDir` with the same timeout +
289
+ * per-plugin isolation contract as {@link loadExternalPlugin}, but
290
+ * without registering it. The plugin-source watcher consumes this so it
291
+ * can decide between first-time registration (init once, then publish) and
292
+ * hot-reload (replace + skip init) based on what's already in the registry.
293
+ *
294
+ * Returns `undefined` on timeout, build failure, or abandoned surface
295
+ * import. Never throws — failures are logged with directory attribution.
248
296
  */
249
- export async function loadExternalPlugin(
297
+ export async function buildExternalPlugin(
250
298
  pluginDir: string,
251
299
  opts: LoadExternalPluginOptions = {},
252
- ): Promise<void> {
300
+ ): Promise<Plugin | undefined> {
253
301
  const timeoutMs = opts.importTimeoutMs ?? DEFAULT_IMPORT_TIMEOUT_MS;
254
302
  let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
255
303
  try {
256
- const timeoutSentinel = Symbol("external-plugin-load-timeout");
304
+ const timeoutSentinel = Symbol("external-plugin-build-timeout");
257
305
  const buildPromise = buildPluginFromDir(pluginDir);
258
306
  const timeoutPromise = new Promise<typeof timeoutSentinel>((resolve) => {
259
307
  timeoutHandle = setTimeout(() => resolve(timeoutSentinel), timeoutMs);
@@ -262,33 +310,58 @@ export async function loadExternalPlugin(
262
310
  if (result === timeoutSentinel) {
263
311
  // Abandoned build — surface imports may still be running. Attach a
264
312
  // terminal `.catch` so a late rejection does not surface as an
265
- // unhandled-rejection crash. The closed-registration latch in
266
- // `registry.ts` rejects any late `registerPlugin()` call from a
267
- // surface module that finishes evaluating after this loader has
268
- // moved on.
313
+ // unhandled-rejection crash. Callers who feed the returned plugin
314
+ // into `registerPlugin` rely on the closed-registration latch
315
+ // (registry.ts) to reject any stale late-arriving registration.
269
316
  buildPromise.catch(() => {
270
317
  /* swallow — see comment above */
271
318
  });
272
319
  log.warn(
273
320
  { pluginDir, timeoutMs },
274
- `Timed out loading external plugin ${pluginDir} after ${timeoutMs}ms — skipping`,
321
+ `Timed out building external plugin ${pluginDir} after ${timeoutMs}ms — skipping`,
275
322
  );
276
- return;
323
+ return undefined;
277
324
  }
278
- registerPlugin(result);
279
- log.info(
280
- { pluginDir, name: result.manifest.name },
281
- "loaded external plugin",
282
- );
325
+ return result;
283
326
  } catch (err) {
284
327
  // Per-plugin isolation: one bad external plugin must not crash the
285
328
  // daemon. Surface the failure with attribution and move on.
286
329
  const message = err instanceof Error ? err.message : String(err);
287
330
  log.error(
288
331
  { err, pluginDir },
289
- `Failed to load external plugin ${pluginDir}: ${message}`,
332
+ `Failed to build external plugin ${pluginDir}: ${message}`,
290
333
  );
334
+ return undefined;
291
335
  } finally {
292
336
  if (timeoutHandle !== undefined) clearTimeout(timeoutHandle);
293
337
  }
294
338
  }
339
+
340
+ /**
341
+ * Load the external plugin at `pluginDir` and register it. Thin wrapper
342
+ * over {@link buildExternalPlugin} that calls `registerPlugin` on the
343
+ * built plugin, preserving the existing `loadUserPlugins` call shape.
344
+ */
345
+ export async function loadExternalPlugin(
346
+ pluginDir: string,
347
+ opts: LoadExternalPluginOptions = {},
348
+ ): Promise<void> {
349
+ const plugin = await buildExternalPlugin(pluginDir, opts);
350
+ if (plugin === undefined) {
351
+ // buildExternalPlugin already logged the failure with attribution.
352
+ return;
353
+ }
354
+ try {
355
+ registerPlugin(plugin);
356
+ log.info(
357
+ { pluginDir, name: plugin.manifest.name },
358
+ "loaded external plugin",
359
+ );
360
+ } catch (err) {
361
+ const message = err instanceof Error ? err.message : String(err);
362
+ log.error(
363
+ { err, pluginDir, plugin: plugin.manifest.name },
364
+ `Failed to register external plugin ${pluginDir}: ${message}`,
365
+ );
366
+ }
367
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * External-plugin feature gate.
3
+ *
4
+ * Single source of truth for whether the experimental external-plugin
5
+ * surface is enabled. The flag gates both the `assistant plugins` CLI
6
+ * command tree and (in future) the declarative external-plugin loader
7
+ * pathway in the daemon.
8
+ *
9
+ * The flag key uses the simple kebab-case format and is declared in
10
+ * `meta/feature-flags/feature-flag-registry.json`.
11
+ */
12
+
13
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
14
+ import type { AssistantConfig } from "../config/schema.js";
15
+
16
+ /** Gate key for the external-plugin surface. */
17
+ const EXTERNAL_PLUGINS_FLAG_KEY = "external-plugins" as const;
18
+
19
+ /** Whether the external-plugin surface is enabled. */
20
+ export function isExternalPluginsEnabled(config: AssistantConfig): boolean {
21
+ return isAssistantFeatureFlagEnabled(EXTERNAL_PLUGINS_FLAG_KEY, config);
22
+ }
@@ -26,7 +26,9 @@
26
26
 
27
27
  import type { Logger } from "pino";
28
28
 
29
+ import type { HookName } from "../plugin-api/constants.js";
29
30
  import { getLogger } from "../util/logger.js";
31
+ import { getHooksFor } from "./registry.js";
30
32
  import {
31
33
  type Middleware,
32
34
  type PipelineName,
@@ -314,3 +316,38 @@ export async function runPipeline<A, R>(
314
316
  logger.info(record, "plugin.pipeline");
315
317
  }
316
318
  }
319
+
320
+ // ─── Hook runner ────────────────────────────────────────────────────────────
321
+
322
+ /**
323
+ * Execute a hook chain: walk every registered plugin's hook for `name` in
324
+ * registration order, threading `initialCtx` through each. Hooks may either
325
+ * mutate the context in place (returning `void`) or return a new context
326
+ * that replaces the threaded value for subsequent hooks. The final context
327
+ * after the chain settles is returned.
328
+ *
329
+ * `runHook` is the hook-side counterpart to {@link runPipeline}:
330
+ * `runPipeline` composes middleware around a terminal handler for stateful
331
+ * request/response pipelines (memory retrieval, history repair, etc.);
332
+ * `runHook` walks ordered hook functions for declarative chain-style
333
+ * context transformations (`user-prompt-submit` today).
334
+ *
335
+ * @param name The hook identifier — pick one from {@link HOOKS}.
336
+ * @param initialCtx Context the first hook receives.
337
+ * @returns The final context after the chain settles. Same reference as
338
+ * `initialCtx` when no plugin registers `name`, and when every
339
+ * chained hook returns `void` (mutation-in-place style).
340
+ */
341
+ export async function runHook<TCtx>(
342
+ name: HookName,
343
+ initialCtx: TCtx,
344
+ ): Promise<TCtx> {
345
+ let active = initialCtx;
346
+ for (const hook of getHooksFor<TCtx>(name)) {
347
+ const result = await hook(active);
348
+ if (result !== undefined) {
349
+ active = result;
350
+ }
351
+ }
352
+ return active;
353
+ }
@@ -1,12 +1,13 @@
1
1
  /**
2
- * Plugin registry with manifest validation and capability-based API versioning.
2
+ * Plugin registry with manifest validation.
3
3
  *
4
- * Plugins declare the assistant capabilities they need via
5
- * `manifest.requires` (a `{ capability: version }` map). The registry checks
6
- * each entry against {@link ASSISTANT_API_VERSIONS} the canonical table of
7
- * capability supported-version-list pairs the assistant exposes and
8
- * refuses to register plugins that ask for a version the assistant does not
9
- * support.
4
+ * Host-compat negotiation lives in the plugin's `package.json`
5
+ * `peerDependencies["@vellumai/plugin-api"]` semver range the
6
+ * external-plugin loader checks it against the assistant version at
7
+ * load time. This module owns the rest of the manifest validation
8
+ * contract: name/version presence, duplicate-name detection, and the
9
+ * closed-registration latch that protects `bootstrapPlugins()` from
10
+ * late-arriving registrations.
10
11
  *
11
12
  * Registration is order-preserving: {@link getRegisteredPlugins},
12
13
  * {@link getMiddlewaresFor}, and (secondarily) {@link getInjectors} all reflect
@@ -26,49 +27,9 @@ import {
26
27
  type PipelineName,
27
28
  type Plugin,
28
29
  PluginExecutionError,
30
+ type PluginHookFn,
29
31
  } from "./types.js";
30
32
 
31
- /**
32
- * Capability table declaring which plugin-facing API versions the assistant
33
- * runtime exposes. Each capability maps to the list of supported semver-lite
34
- * tags (currently a single `"v1"` per capability).
35
- *
36
- * New capabilities must be added here AND in their corresponding pipeline /
37
- * runtime module so plugins can negotiate against them. Removing a version
38
- * tag is a breaking change — all consumers in the plugin ecosystem relying on
39
- * it will fail to register until they update their `requires` map.
40
- *
41
- * The `pluginRuntime` capability is the base runtime API every plugin must
42
- * negotiate for; the remaining entries mirror {@link PipelineName} and the
43
- * top-level context APIs plugins most commonly consume.
44
- */
45
- export const ASSISTANT_API_VERSIONS: Record<string, string[]> = {
46
- // Runtime APIs every plugin interacts with at some level. `memoryApi` is the
47
- // broader memory-subsystem capability (distinct from the `memoryRetrieval`
48
- // pipeline, which gets its own `memoryRetrievalApi` entry below).
49
- pluginRuntime: ["v1"],
50
- memoryApi: ["v1"],
51
- compactionApi: ["v1"],
52
- persistenceApi: ["v1"],
53
-
54
- // Per-pipeline APIs. One entry for every slot in {@link PipelineName} that
55
- // isn't already covered by the runtime-APIs block above (`compaction` and
56
- // `persistence` live there because plugins commonly interact with them
57
- // outside a pipeline middleware context).
58
- turnApi: ["v1"],
59
- llmCallApi: ["v1"],
60
- toolExecuteApi: ["v1"],
61
- memoryRetrievalApi: ["v1"],
62
- historyRepairApi: ["v1"],
63
- tokenEstimateApi: ["v1"],
64
- overflowReduceApi: ["v1"],
65
- titleGenerateApi: ["v1"],
66
- toolResultTruncateApi: ["v1"],
67
- emptyResponseApi: ["v1"],
68
- toolErrorApi: ["v1"],
69
- circuitBreakerApi: ["v1"],
70
- };
71
-
72
33
  // ─── Internal state ──────────────────────────────────────────────────────────
73
34
 
74
35
  /**
@@ -96,11 +57,13 @@ let registrationClosed = false;
96
57
  /**
97
58
  * Validate and register a plugin. Throws {@link PluginExecutionError} if:
98
59
  *
99
- * - `manifest`, `manifest.name`, `manifest.version`, or `manifest.requires`
100
- * are missing.
60
+ * - `manifest`, `manifest.name`, or `manifest.version` are missing.
101
61
  * - a plugin with the same name is already registered.
102
- * - any entry in `manifest.requires` names an unknown capability or a version
103
- * the assistant does not expose.
62
+ * - registration has been closed by {@link closeRegistration}.
63
+ *
64
+ * Host-compat is checked upstream by the external-plugin loader against
65
+ * the plugin's `peerDependencies["@vellumai/plugin-api"]` semver range —
66
+ * the registry does not re-validate it here.
104
67
  *
105
68
  * On success the plugin is appended to the registry in the order this
106
69
  * function is called. This function does NOT invoke `plugin.init()` — that
@@ -144,13 +107,6 @@ export function registerPlugin(plugin: Plugin): void {
144
107
  name,
145
108
  );
146
109
  }
147
- if (!manifest.requires || typeof manifest.requires !== "object") {
148
- throw new PluginExecutionError(
149
- `plugin ${name} manifest.requires is required`,
150
- name,
151
- );
152
- }
153
-
154
110
  // Duplicate-name check — plugins must be uniquely addressable in logs,
155
111
  // storage paths, and error messages. Runs BEFORE the closed-registration
156
112
  // check so `registerDefaultPlugins()` (which replays every default even
@@ -174,27 +130,6 @@ export function registerPlugin(plugin: Plugin): void {
174
130
  );
175
131
  }
176
132
 
177
- // Capability negotiation. Every plugin must negotiate against
178
- // `pluginRuntime`; we enforce that by requiring an entry to exist rather
179
- // than special-casing it here, so the per-entry mismatch error is uniform.
180
- if (!("pluginRuntime" in manifest.requires)) {
181
- throw new PluginExecutionError(
182
- `plugin ${name} must declare requires.pluginRuntime (e.g. "v1")`,
183
- name,
184
- );
185
- }
186
-
187
- for (const [api, requiredVersion] of Object.entries(manifest.requires)) {
188
- const supported = ASSISTANT_API_VERSIONS[api];
189
- if (!supported || !supported.includes(requiredVersion)) {
190
- const exposed = supported ? supported.join(", ") : "(none)";
191
- throw new PluginExecutionError(
192
- `plugin ${name} requires ${api}@${requiredVersion}, assistant exposes ${exposed}`,
193
- name,
194
- );
195
- }
196
- }
197
-
198
133
  registeredPlugins.set(name, plugin);
199
134
  }
200
135
 
@@ -230,6 +165,31 @@ export function getMiddlewaresFor<P extends PipelineName>(
230
165
  return out;
231
166
  }
232
167
 
168
+ /**
169
+ * Collect every registered plugin's hook for the given name, in
170
+ * registration order. Plugins that don't declare a hook for `name` are
171
+ * skipped. Used by the daemon to invoke chain-style hooks like
172
+ * `user-prompt-submit` where each plugin's hook may transform a shared
173
+ * context.
174
+ *
175
+ * The `TCtx` generic mirrors {@link PluginHookFn}'s — callers parameterize
176
+ * over the concrete context type their hook receives. Hooks that mutate
177
+ * the context in place return `void`; hooks that return a new context
178
+ * replace the threaded value for the next hook in the chain.
179
+ */
180
+ export function getHooksFor<TCtx = unknown>(
181
+ name: string,
182
+ ): PluginHookFn<TCtx>[] {
183
+ const out: PluginHookFn<TCtx>[] = [];
184
+ for (const plugin of registeredPlugins.values()) {
185
+ const hook = plugin.hooks?.[name];
186
+ if (hook) {
187
+ out.push(hook as PluginHookFn<TCtx>);
188
+ }
189
+ }
190
+ return out;
191
+ }
192
+
233
193
  /**
234
194
  * Flatten every registered plugin's `injectors` array and sort the result by
235
195
  * `order` ascending. Two injectors with the same `order` retain their relative
@@ -275,6 +235,14 @@ export function unregisterPlugin(name: string): void {
275
235
  registeredPlugins.delete(name);
276
236
  }
277
237
 
238
+ export function getRegisteredPlugin(name: string): Plugin | undefined {
239
+ return registeredPlugins.get(name);
240
+ }
241
+
242
+ export function setRegisteredPlugin(plugin: Plugin): void {
243
+ registeredPlugins.set(plugin.manifest.name, plugin);
244
+ }
245
+
278
246
  // ─── Test hooks ──────────────────────────────────────────────────────────────
279
247
 
280
248
  /**
@@ -42,7 +42,11 @@ import type {
42
42
  ToolDefinition,
43
43
  } from "../providers/types.js";
44
44
  import type { SkillRoute } from "../runtime/skill-route-registry.js";
45
- import type { Tool, ToolContext, ToolExecutionResult } from "../tools/types.js";
45
+ import type {
46
+ PluginTool,
47
+ ToolContext,
48
+ ToolExecutionResult,
49
+ } from "../tools/types.js";
46
50
  import { AssistantError, ErrorCode } from "../util/errors.js";
47
51
 
48
52
  // ─── Manifest ────────────────────────────────────────────────────────────────
@@ -60,22 +64,13 @@ import { AssistantError, ErrorCode } from "../util/errors.js";
60
64
  export interface PluginManifest {
61
65
  /** Unique plugin identifier (kebab-case). Duplicate names fail registration. */
62
66
  name: string;
63
- /** Plugin version (semver). Informational — the registry compares
64
- * capability versions via `requires`, not this field. */
65
- version: string;
66
67
  /**
67
- * Capabilities this plugin exposes to other plugins.
68
- *
69
- * **Reserved for future cross-plugin composition not currently consumed
70
- * by any runtime code.** The field is declared so future cross-plugin work
71
- * can land without a manifest version bump, but today nothing reads it and
72
- * plugins must not depend on it for capability discovery. See
73
- * `assistant/docs/plugins.md` (Cross-plugin communication) for the
74
- * rationale.
68
+ * Plugin version (semver). Informational. Host-compat negotiation lives
69
+ * in the plugin's `package.json` `peerDependencies["@vellumai/plugin-api"]`
70
+ * range checked by the external-plugin loader against the assistant's
71
+ * own version at load time.
75
72
  */
76
- provides?: Record<string, string>;
77
- /** Capabilities this plugin needs from the assistant runtime. */
78
- requires: Record<string, string>;
73
+ version: string;
79
74
  /** Credential keys the plugin needs resolved before `init()` runs. */
80
75
  requiresCredential?: string[];
81
76
  /**
@@ -844,6 +839,17 @@ export interface TurnInjectionInputs {
844
839
  * knows no human is present to answer clarification questions.
845
840
  */
846
841
  readonly isNonInteractive?: boolean;
842
+ /**
843
+ * Active documents open in this conversation — surfaced by the
844
+ * `active-documents` injector so the assistant can target existing docs
845
+ * with `document_update` instead of creating duplicates.
846
+ */
847
+ readonly activeDocuments?: ReadonlyArray<{
848
+ surfaceId: string;
849
+ title: string;
850
+ wordCount: number;
851
+ updatedAt: number;
852
+ }> | null;
847
853
  }
848
854
 
849
855
  export interface DiskPressureInjectionContext {
@@ -995,15 +1001,16 @@ export interface Injector {
995
1001
  // catalog-discoverable skills today.
996
1002
 
997
1003
  /**
998
- * Tool registration contributed by a plugin. Uses the canonical {@link Tool}
999
- * interface from the tool registry the bootstrap stamps `origin: "plugin"`
1000
- * and `ownerPluginId: <plugin.name>` before handing the batch to
1001
- * `registerPluginTools`, which keeps plugin ref-counts and conflict detection
1002
- * in a namespace disjoint from real skills. Plugin authors supply the
1003
- * functional fields (`name`, `description`, `getDefinition`, `execute`, etc.)
1004
- * and leave the ownership metadata to the bootstrap to set authoritatively.
1004
+ * Tool registration contributed by a plugin. Uses the narrow
1005
+ * {@link PluginTool} shapeplugin authors declare functional fields
1006
+ * (`name`, `description`, `input_schema`, `execute`, etc.) and leave category
1007
+ * / ownership metadata to the bootstrap, which stamps `category: "plugin"`,
1008
+ * `origin: "plugin"`, and `ownerPluginId: <plugin.name>` before handing the
1009
+ * batch to `registerPluginTools`. The registration boundary synthesizes
1010
+ * `getDefinition()` from `{name, description, input_schema}` so the canonical
1011
+ * {@link Tool} interface used by the internal registry stays unchanged.
1005
1012
  */
1006
- export type PluginToolRegistration = Tool;
1013
+ export type PluginToolRegistration = PluginTool;
1007
1014
  /**
1008
1015
  * HTTP route registration contributed by a plugin. Plugins express routes as
1009
1016
  * {@link SkillRoute} values — the same shape the skill-route registry
@@ -1075,9 +1082,7 @@ export interface PluginSkillRegistration {
1075
1082
  * Unknown keys are populated by the loader for forward compatibility
1076
1083
  * but are not invoked by today's runtime.
1077
1084
  */
1078
- export type PluginHookFn<TCtx = unknown> = (
1079
- ctx: TCtx,
1080
- ) => Promise<TCtx | void>;
1085
+ export type PluginHookFn<TCtx = unknown> = (ctx: TCtx) => Promise<TCtx | void>;
1081
1086
 
1082
1087
  /**
1083
1088
  * Map of lifecycle hooks contributed by a plugin. Keys match file
@@ -49,7 +49,8 @@ import { join } from "node:path";
49
49
  import { pathToFileURL } from "node:url";
50
50
 
51
51
  import { getLogger } from "../util/logger.js";
52
- import { getWorkspaceDir } from "../util/platform.js";
52
+ import { getWorkspacePluginsDir } from "../util/platform.js";
53
+ import { ensurePluginApiShim } from "./ensure-plugin-api-shim.js";
53
54
  import { loadExternalPlugin } from "./external-plugin-loader.js";
54
55
  import { closeRegistration } from "./registry.js";
55
56
 
@@ -93,7 +94,25 @@ export async function loadUserPlugins(
93
94
  ): Promise<void> {
94
95
  const importTimeoutMs = options.importTimeoutMs ?? USER_PLUGIN_IMPORT_TIMEOUT_MS;
95
96
 
96
- const pluginsDir = join(getWorkspaceDir(), "plugins");
97
+ // Materialize the workspace-level `@vellumai/plugin-api` shim *before*
98
+ // we dynamic-import any user plugins. The shim file must exist on disk
99
+ // before the first plugin's `import "@vellumai/plugin-api"` is parsed.
100
+ //
101
+ // Wrapped in try/catch because per `AGENTS.md` the daemon must never
102
+ // block startup. A shim-write failure (ENOSPC, read-only workspace,
103
+ // perms) is logged and we continue — plugins that try to import the
104
+ // public specifier will fail individually inside the per-plugin import
105
+ // loop below, which is already isolated.
106
+ try {
107
+ await ensurePluginApiShim();
108
+ } catch (err) {
109
+ log.warn(
110
+ { err },
111
+ "loadUserPlugins: plugin-api shim materialization failed — continuing with degraded plugin support",
112
+ );
113
+ }
114
+
115
+ const pluginsDir = getWorkspacePluginsDir();
97
116
 
98
117
  if (!existsSync(pluginsDir)) {
99
118
  // The clean-install case. Closing the registration window keeps the
@@ -8,6 +8,10 @@
8
8
 
9
9
  import { createAssistantMessage } from "../agent/message-types.js";
10
10
  import { findConversation } from "../daemon/conversation-store.js";
11
+ import {
12
+ conversationMessagesSyncTag,
13
+ SYNC_TAGS,
14
+ } from "../daemon/message-types/sync.js";
11
15
  import { addMessage } from "../memory/conversation-crud.js";
12
16
  import type { BroadcastFn } from "../notifications/adapters/macos.js";
13
17
  import { getLogger } from "../util/logger.js";
@@ -71,4 +75,11 @@ export async function injectAuxAssistantMessage(params: {
71
75
  type: "conversation_list_invalidated",
72
76
  reason: "reordered",
73
77
  });
78
+ params.broadcastMessage({
79
+ type: "sync_changed",
80
+ tags: [
81
+ SYNC_TAGS.conversationsList,
82
+ conversationMessagesSyncTag(params.conversationId),
83
+ ],
84
+ });
74
85
  }