@vellumai/assistant 0.8.1 → 0.8.3

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 (630) hide show
  1. package/ARCHITECTURE.md +13 -19
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +17 -0
  5. package/docker-init-apt-root.sh +167 -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 +642 -5
  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-loop-exit-reason.test.ts +272 -0
  21. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  23. package/src/__tests__/anthropic-provider.test.ts +45 -0
  24. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  25. package/src/__tests__/app-executors.test.ts +220 -4
  26. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  27. package/src/__tests__/bundled-asset.test.ts +6 -6
  28. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  29. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  30. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  31. package/src/__tests__/clawhub.test.ts +75 -16
  32. package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
  33. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  34. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  35. package/src/__tests__/config-schema.test.ts +21 -0
  36. package/src/__tests__/config-set-route.test.ts +80 -0
  37. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  38. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  39. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  40. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  41. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  42. package/src/__tests__/context-token-estimator.test.ts +31 -65
  43. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  46. package/src/__tests__/conversation-agent-loop.test.ts +59 -1
  47. package/src/__tests__/conversation-error.test.ts +42 -3
  48. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  49. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  50. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  51. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  52. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  53. package/src/__tests__/conversation-pairing.test.ts +54 -0
  54. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  55. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  56. package/src/__tests__/conversation-queue.test.ts +4 -1
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
  58. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  59. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  60. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  61. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  64. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  65. package/src/__tests__/date-context.test.ts +45 -0
  66. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  67. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  68. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  69. package/src/__tests__/dm-backfill.test.ts +121 -10
  70. package/src/__tests__/document-tool-security.test.ts +258 -0
  71. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  72. package/src/__tests__/edit-propagation.test.ts +33 -0
  73. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  74. package/src/__tests__/external-plugin-loader.test.ts +151 -55
  75. package/src/__tests__/filing-service.test.ts +140 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  77. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  78. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  79. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  80. package/src/__tests__/heartbeat-service.test.ts +24 -164
  81. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  82. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  83. package/src/__tests__/helpers/wait-for.ts +21 -0
  84. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  85. package/src/__tests__/history-repair.test.ts +73 -0
  86. package/src/__tests__/host-app-control-proxy.test.ts +507 -10
  87. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  88. package/src/__tests__/image-credentials.test.ts +1 -1
  89. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  90. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  91. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  92. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  93. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  94. package/src/__tests__/injector-background-turn.test.ts +153 -0
  95. package/src/__tests__/injector-chain.test.ts +15 -8
  96. package/src/__tests__/install-skill-routing.test.ts +155 -37
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
  98. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  99. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  100. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  101. package/src/__tests__/llm-catalog-parity.test.ts +58 -13
  102. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  103. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  104. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
  105. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  106. package/src/__tests__/llm-resolver.test.ts +255 -2
  107. package/src/__tests__/llm-usage-store.test.ts +114 -0
  108. package/src/__tests__/managed-profile-guard.test.ts +41 -29
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  110. package/src/__tests__/managed-store.test.ts +84 -192
  111. package/src/__tests__/media-generate-image.test.ts +1 -1
  112. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  113. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  114. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  115. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  116. package/src/__tests__/notification-deep-link.test.ts +15 -0
  117. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  118. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  119. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  120. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  121. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  122. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  123. package/src/__tests__/openai-provider.test.ts +242 -3
  124. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  125. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  126. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  127. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  128. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  129. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
  130. package/src/__tests__/platform.test.ts +2 -0
  131. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  132. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  133. package/src/__tests__/plugin-external-api.test.ts +68 -0
  134. package/src/__tests__/plugin-registry.test.ts +0 -77
  135. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  136. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  137. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  138. package/src/__tests__/plugin-types.test.ts +3 -13
  139. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  140. package/src/__tests__/process-message-display-content.test.ts +421 -0
  141. package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  143. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
  144. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  145. package/src/__tests__/schedule-routes.test.ts +50 -3
  146. package/src/__tests__/schedule-store.test.ts +94 -0
  147. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  148. package/src/__tests__/schema-transforms.test.ts +20 -0
  149. package/src/__tests__/search-skills-unified.test.ts +0 -5
  150. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
  151. package/src/__tests__/server-history-render.test.ts +43 -0
  152. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  153. package/src/__tests__/skill-load-tool.test.ts +27 -89
  154. package/src/__tests__/skill-memory.test.ts +23 -3
  155. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  156. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  157. package/src/__tests__/skills-install-extract.test.ts +49 -38
  158. package/src/__tests__/skills-install-staging.test.ts +159 -0
  159. package/src/__tests__/skills-uninstall.test.ts +9 -41
  160. package/src/__tests__/skills.test.ts +51 -58
  161. package/src/__tests__/slack-channel-config.test.ts +9 -0
  162. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  163. package/src/__tests__/system-prompt.test.ts +670 -63
  164. package/src/__tests__/terminal-tools.test.ts +28 -1
  165. package/src/__tests__/thread-backfill.test.ts +557 -27
  166. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  167. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  168. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  169. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  170. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  171. package/src/__tests__/tool-executor.test.ts +16 -4
  172. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  173. package/src/__tests__/turn-events-store.test.ts +256 -0
  174. package/src/__tests__/twilio-routes.test.ts +4 -0
  175. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  176. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  177. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  178. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  179. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  180. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  181. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  182. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  183. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  184. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  185. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  186. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  187. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  188. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  189. package/src/a2a/__tests__/task-store.test.ts +246 -0
  190. package/src/a2a/agent-card.ts +58 -0
  191. package/src/a2a/feature-gate.ts +8 -0
  192. package/src/a2a/protocol-constants.ts +21 -0
  193. package/src/a2a/protocol-errors.ts +50 -0
  194. package/src/a2a/protocol-types.ts +162 -0
  195. package/src/a2a/task-store.ts +168 -0
  196. package/src/acp/resolve-agent.ts +1 -1
  197. package/src/agent/image-optimize.ts +13 -5
  198. package/src/agent/loop.ts +167 -18
  199. package/src/calls/voice-session-bridge.ts +61 -42
  200. package/src/channels/config.ts +9 -0
  201. package/src/channels/types.ts +122 -0
  202. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  203. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  204. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  205. package/src/cli/commands/__tests__/schedules.test.ts +960 -0
  206. package/src/cli/commands/changelog.ts +106 -42
  207. package/src/cli/commands/conversations.ts +102 -17
  208. package/src/cli/commands/default-action.ts +10 -53
  209. package/src/cli/commands/notifications.ts +388 -346
  210. package/src/cli/commands/plugins.ts +252 -0
  211. package/src/cli/commands/schedules.ts +683 -0
  212. package/src/cli/commands/telemetry.ts +40 -0
  213. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  214. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  215. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  216. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  217. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  218. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  219. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  220. package/src/cli/lib/cli-colors.ts +12 -0
  221. package/src/cli/lib/confirm-prompt.ts +79 -0
  222. package/src/cli/lib/install-from-github.ts +303 -0
  223. package/src/cli/lib/list-installed-plugins.ts +137 -0
  224. package/src/cli/lib/search-plugins.ts +163 -0
  225. package/src/cli/lib/uninstall-plugin.ts +82 -0
  226. package/src/cli/lib/unknown-command.ts +111 -0
  227. package/src/cli/program.ts +52 -2
  228. package/src/config/assistant-feature-flags.ts +24 -54
  229. package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
  230. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  231. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  232. package/src/config/bundled-skills/document/SKILL.md +23 -3
  233. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  234. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  235. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  236. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  237. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  238. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  239. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  240. package/src/config/bundled-tool-registry.ts +6 -0
  241. package/src/config/call-site-defaults.ts +105 -0
  242. package/src/config/feature-flag-registry.json +41 -9
  243. package/src/config/llm-resolver.ts +52 -1
  244. package/src/config/loader.ts +64 -38
  245. package/src/config/schema.ts +9 -10
  246. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  247. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  248. package/src/config/schemas/channels.ts +17 -0
  249. package/src/config/schemas/compaction.ts +28 -0
  250. package/src/config/schemas/conversations.ts +10 -0
  251. package/src/config/schemas/heartbeat.ts +23 -0
  252. package/src/config/schemas/llm-request-logs.ts +31 -7
  253. package/src/config/schemas/llm.ts +1 -0
  254. package/src/config/schemas/memory-retrieval.ts +18 -0
  255. package/src/config/schemas/memory-retrospective.ts +1 -1
  256. package/src/config/schemas/memory-v2.ts +4 -4
  257. package/src/config/schemas/memory.ts +3 -1
  258. package/src/config/schemas/tools.ts +14 -0
  259. package/src/config/seed-inference-profiles.ts +99 -29
  260. package/src/config/skills.ts +3 -96
  261. package/src/context/compactor.ts +1107 -0
  262. package/src/context/token-estimator.ts +34 -36
  263. package/src/context/window-manager.ts +197 -1520
  264. package/src/credential-execution/managed-catalog.ts +37 -0
  265. package/src/credential-health/credential-health-service.ts +280 -19
  266. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
  267. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  268. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  269. package/src/daemon/approval-generators.ts +8 -6
  270. package/src/daemon/config-watcher.ts +94 -31
  271. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  272. package/src/daemon/conversation-agent-loop.ts +198 -11
  273. package/src/daemon/conversation-error.ts +171 -37
  274. package/src/daemon/conversation-lifecycle.ts +53 -40
  275. package/src/daemon/conversation-messaging.ts +25 -6
  276. package/src/daemon/conversation-process.ts +49 -12
  277. package/src/daemon/conversation-runtime-assembly.ts +25 -1
  278. package/src/daemon/conversation-slash.ts +12 -5
  279. package/src/daemon/conversation-store.ts +11 -4
  280. package/src/daemon/conversation-tool-setup.ts +39 -7
  281. package/src/daemon/conversation.ts +33 -8
  282. package/src/daemon/date-context.ts +40 -0
  283. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  284. package/src/daemon/first-greeting.ts +22 -2
  285. package/src/daemon/guardian-action-generators.ts +1 -125
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  287. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  288. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  289. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  290. package/src/daemon/handlers/config-a2a.ts +289 -0
  291. package/src/daemon/handlers/config-model.ts +6 -5
  292. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  293. package/src/daemon/handlers/conversations.ts +1 -0
  294. package/src/daemon/handlers/shared.ts +14 -5
  295. package/src/daemon/handlers/skills.ts +111 -108
  296. package/src/daemon/history-repair.ts +28 -1
  297. package/src/daemon/host-app-control-proxy.ts +153 -27
  298. package/src/daemon/host-proxy-preactivation.ts +85 -18
  299. package/src/daemon/lifecycle.ts +89 -91
  300. package/src/daemon/meet-host-supervisor.ts +5 -4
  301. package/src/daemon/memory-v2-startup.ts +85 -0
  302. package/src/daemon/message-protocol.ts +1 -0
  303. package/src/daemon/message-types/conversations.ts +25 -0
  304. package/src/daemon/message-types/messages.ts +61 -0
  305. package/src/daemon/message-types/notifications.ts +21 -0
  306. package/src/daemon/message-types/subagents.ts +1 -0
  307. package/src/daemon/message-types/sync.ts +1 -0
  308. package/src/daemon/pkb-reminder-builder.test.ts +11 -54
  309. package/src/daemon/pkb-reminder-builder.ts +5 -20
  310. package/src/daemon/plugin-source-watcher.ts +146 -0
  311. package/src/daemon/process-message.ts +24 -3
  312. package/src/daemon/server.ts +11 -2
  313. package/src/daemon/skill-memory-refresh.ts +33 -0
  314. package/src/daemon/wake-target-adapter.ts +2 -0
  315. package/src/documents/document-store.ts +221 -3
  316. package/src/embedded/plugin-api.ts +40 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  318. package/src/export/transcript-formatter.ts +54 -20
  319. package/src/filing/filing-service.ts +39 -0
  320. package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
  321. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  322. package/src/heartbeat/heartbeat-service.ts +73 -189
  323. package/src/home/__tests__/feed-types.test.ts +80 -0
  324. package/src/home/feed-types.ts +36 -2
  325. package/src/home/post-connect-feed.ts +1 -0
  326. package/src/index.ts +18 -1
  327. package/src/ipc/cli-client.ts +147 -45
  328. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  329. package/src/mcp/client.ts +20 -4
  330. package/src/media/image-credentials.ts +3 -3
  331. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  332. package/src/memory/__tests__/conversation-queries.test.ts +483 -0
  333. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  334. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  335. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  336. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  337. package/src/memory/__tests__/message-content.test.ts +35 -0
  338. package/src/memory/bookmark-crud.ts +42 -10
  339. package/src/memory/context-search/sources/conversations.ts +62 -2
  340. package/src/memory/context-search/sources/workspace.ts +4 -0
  341. package/src/memory/conversation-crud.ts +63 -19
  342. package/src/memory/conversation-queries.ts +197 -11
  343. package/src/memory/conversation-title-service.ts +26 -4
  344. package/src/memory/db-init.ts +12 -0
  345. package/src/memory/delivery-crud.ts +152 -5
  346. package/src/memory/embedding-backend.ts +4 -4
  347. package/src/memory/external-conversation-store.ts +66 -5
  348. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
  349. package/src/memory/graph/conversation-graph-memory.ts +49 -21
  350. package/src/memory/graph/tools.ts +9 -40
  351. package/src/memory/indexer.ts +34 -29
  352. package/src/memory/invite-store.ts +53 -0
  353. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  354. package/src/memory/jobs/embed-concept-page.ts +20 -11
  355. package/src/memory/jobs-worker.ts +6 -1
  356. package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
  357. package/src/memory/llm-request-log-source.ts +19 -52
  358. package/src/memory/llm-request-log-store.ts +92 -1
  359. package/src/memory/llm-usage-store.ts +125 -5
  360. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  361. package/src/memory/memory-retrospective-job.ts +33 -6
  362. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  363. package/src/memory/message-content.ts +1 -1
  364. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  365. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  366. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  367. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  368. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  369. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  370. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  371. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  372. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  373. package/src/memory/migrations/index.ts +9 -0
  374. package/src/memory/migrations/registry.ts +16 -0
  375. package/src/memory/onboarding-events-store.ts +106 -0
  376. package/src/memory/schema/a2a.ts +15 -0
  377. package/src/memory/schema/bookmarks.ts +0 -2
  378. package/src/memory/schema/calls.ts +1 -0
  379. package/src/memory/schema/index.ts +1 -0
  380. package/src/memory/schema/inference.ts +3 -3
  381. package/src/memory/schema/infrastructure.ts +13 -0
  382. package/src/memory/turn-events-store.ts +127 -2
  383. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  384. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  385. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  386. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  387. package/src/memory/v2/__tests__/injection.test.ts +288 -11
  388. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  389. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  390. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  391. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  392. package/src/memory/v2/__tests__/router.test.ts +15 -0
  393. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  394. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  395. package/src/memory/v2/activation-store.ts +14 -16
  396. package/src/memory/v2/cli-command-content.ts +19 -0
  397. package/src/memory/v2/cli-command-store.ts +304 -0
  398. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  399. package/src/memory/v2/injection.ts +81 -26
  400. package/src/memory/v2/migration.ts +49 -19
  401. package/src/memory/v2/page-index.ts +63 -8
  402. package/src/memory/v2/prompts/router.ts +11 -8
  403. package/src/memory/v2/prompts/sweep.ts +2 -2
  404. package/src/memory/v2/qdrant.ts +135 -7
  405. package/src/memory/v2/router.ts +9 -8
  406. package/src/memory/v2/skill-store.ts +120 -35
  407. package/src/memory/v2/static-context.ts +4 -4
  408. package/src/memory/v2/types.ts +23 -0
  409. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  410. package/src/messaging/providers/a2a/deliver.ts +156 -0
  411. package/src/messaging/providers/gmail/client.ts +9 -2
  412. package/src/messaging/providers/index.ts +11 -2
  413. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  414. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  415. package/src/messaging/providers/slack/adapter.ts +43 -5
  416. package/src/messaging/providers/slack/client.ts +27 -0
  417. package/src/messaging/providers/slack/deep-link.ts +65 -0
  418. package/src/messaging/providers/slack/download.ts +104 -0
  419. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  420. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  421. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  422. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  423. package/src/messaging/providers/slack/types.ts +20 -1
  424. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  425. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  426. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  427. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  428. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  429. package/src/notifications/adapters/macos.ts +12 -2
  430. package/src/notifications/broadcaster.ts +29 -4
  431. package/src/notifications/conversation-pairing.ts +2 -1
  432. package/src/notifications/copy-composer.ts +17 -64
  433. package/src/notifications/decision-engine.ts +113 -45
  434. package/src/notifications/deterministic-checks.ts +96 -0
  435. package/src/notifications/emit-signal.ts +21 -1
  436. package/src/notifications/home-feed-side-effect.ts +138 -5
  437. package/src/notifications/signal.ts +3 -5
  438. package/src/notifications/types.ts +8 -0
  439. package/src/oauth/connection-resolver.ts +8 -4
  440. package/src/oauth/platform-connection.test.ts +43 -3
  441. package/src/oauth/platform-connection.ts +19 -6
  442. package/src/oauth/seed-providers.ts +10 -1
  443. package/src/permissions/checker.ts +2 -0
  444. package/src/permissions/ipc-risk-types.ts +1 -0
  445. package/src/permissions/question-prompter.test.ts +416 -0
  446. package/src/permissions/question-prompter.ts +294 -0
  447. package/src/platform/client.test.ts +1 -1
  448. package/src/platform/client.ts +1 -1
  449. package/src/plugin-api/constants.ts +26 -0
  450. package/src/plugin-api/index.ts +34 -1
  451. package/src/plugin-api/types.ts +104 -22
  452. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  453. package/src/plugins/defaults/compaction.ts +0 -4
  454. package/src/plugins/defaults/empty-response.ts +0 -2
  455. package/src/plugins/defaults/history-repair.ts +0 -2
  456. package/src/plugins/defaults/injectors.ts +74 -22
  457. package/src/plugins/defaults/llm-call.ts +0 -2
  458. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  459. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  460. package/src/plugins/defaults/persistence.ts +0 -2
  461. package/src/plugins/defaults/title-generate.ts +0 -5
  462. package/src/plugins/defaults/token-estimate.ts +0 -2
  463. package/src/plugins/defaults/tool-error.ts +0 -7
  464. package/src/plugins/defaults/tool-execute.ts +0 -2
  465. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  466. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  467. package/src/plugins/external-api.ts +104 -0
  468. package/src/plugins/external-plugin-loader.ts +187 -42
  469. package/src/plugins/feature-gate.ts +22 -0
  470. package/src/plugins/pipeline.ts +37 -0
  471. package/src/plugins/registry.ts +48 -80
  472. package/src/plugins/types.ts +40 -26
  473. package/src/plugins/user-loader.ts +21 -2
  474. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  475. package/src/proactive-artifact/job.test.ts +37 -5
  476. package/src/prompts/__tests__/system-prompt.test.ts +10 -43
  477. package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
  478. package/src/prompts/normalize-onboarding.ts +27 -0
  479. package/src/prompts/sections.ts +302 -0
  480. package/src/prompts/system-prompt.ts +63 -174
  481. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  482. package/src/prompts/templates/system-sections.ts +164 -0
  483. package/src/providers/__tests__/inference.test.ts +24 -7
  484. package/src/providers/anthropic/client.ts +28 -28
  485. package/src/providers/call-site-routing.ts +24 -6
  486. package/src/providers/connection-resolution.ts +68 -11
  487. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  488. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  489. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  490. package/src/providers/inference/adapter-factory.ts +32 -6
  491. package/src/providers/inference/auth.ts +12 -0
  492. package/src/providers/inference/backfill.ts +14 -1
  493. package/src/providers/inference/connections.ts +159 -34
  494. package/src/providers/inference/resolve-auth.ts +14 -4
  495. package/src/providers/model-catalog.ts +249 -12
  496. package/src/providers/model-intents.ts +3 -3
  497. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  498. package/src/providers/openai/chat-completions-provider.ts +169 -8
  499. package/src/providers/openrouter/client.ts +49 -4
  500. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
  501. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  502. package/src/providers/provider-availability.ts +17 -2
  503. package/src/providers/provider-catalog-visibility.ts +38 -0
  504. package/src/providers/provider-send-message.ts +27 -12
  505. package/src/providers/registry.ts +52 -15
  506. package/src/providers/retry.ts +47 -1
  507. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  508. package/src/runtime/agent-wake.ts +103 -15
  509. package/src/runtime/auth/route-policy.ts +21 -1
  510. package/src/runtime/btw-sidechain.ts +2 -0
  511. package/src/runtime/http-server.ts +7 -16
  512. package/src/runtime/http-types.ts +19 -47
  513. package/src/runtime/migrations/origin-mode.ts +1 -1
  514. package/src/runtime/pending-interactions.ts +1 -0
  515. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  516. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  517. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  518. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
  519. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  520. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  521. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  522. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  523. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  524. package/src/runtime/routes/acp-routes.ts +5 -3
  525. package/src/runtime/routes/auth-routes.ts +1 -1
  526. package/src/runtime/routes/bookmark-routes.ts +5 -3
  527. package/src/runtime/routes/btw-routes.ts +5 -1
  528. package/src/runtime/routes/channel-availability-routes.ts +126 -0
  529. package/src/runtime/routes/consolidation-routes.ts +100 -0
  530. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  531. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  532. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  533. package/src/runtime/routes/conversation-query-routes.ts +99 -35
  534. package/src/runtime/routes/conversation-routes.ts +97 -11
  535. package/src/runtime/routes/documents-routes.ts +25 -86
  536. package/src/runtime/routes/group-routes.ts +5 -0
  537. package/src/runtime/routes/inbound-conversation.ts +28 -8
  538. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  539. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  540. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  541. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  542. package/src/runtime/routes/index.ts +8 -0
  543. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  544. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  545. package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
  546. package/src/runtime/routes/integrations/a2a.ts +235 -0
  547. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  548. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  549. package/src/runtime/routes/integrations/twilio.ts +6 -13
  550. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  551. package/src/runtime/routes/notification-routes.ts +1 -1
  552. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  553. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  554. package/src/runtime/routes/question-routes.ts +259 -0
  555. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  556. package/src/runtime/routes/schedule-routes.ts +4 -7
  557. package/src/runtime/routes/subagents-routes.ts +98 -18
  558. package/src/runtime/routes/telemetry-routes.ts +27 -0
  559. package/src/runtime/routes/tts-routes.ts +27 -2
  560. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  561. package/src/runtime/routes/workspace-routes.ts +28 -0
  562. package/src/runtime/services/conversation-serializer.ts +39 -7
  563. package/src/runtime/sync/resource-sync-events.ts +93 -1
  564. package/src/schedule/schedule-store.ts +27 -2
  565. package/src/schedule/scheduler.ts +9 -1
  566. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  567. package/src/security/untrusted-content.ts +93 -8
  568. package/src/skills/catalog-files.ts +1 -1
  569. package/src/skills/catalog-install.ts +233 -116
  570. package/src/skills/clawhub.ts +70 -13
  571. package/src/skills/managed-store.ts +4 -119
  572. package/src/skills/skillssh-registry.ts +27 -48
  573. package/src/subagent/manager.ts +17 -7
  574. package/src/telemetry/types.ts +113 -1
  575. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  576. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  577. package/src/tools/apps/executors.ts +58 -7
  578. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  579. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  580. package/src/tools/browser/browser-execution.ts +15 -11
  581. package/src/tools/computer-use/definitions.ts +3 -3
  582. package/src/tools/credentials/vault.ts +1 -1
  583. package/src/tools/document/document-tool.ts +124 -1
  584. package/src/tools/filesystem/edit.ts +1 -1
  585. package/src/tools/filesystem/list.ts +1 -1
  586. package/src/tools/filesystem/read.ts +1 -1
  587. package/src/tools/filesystem/write.ts +5 -2
  588. package/src/tools/host-filesystem/transfer.ts +1 -1
  589. package/src/tools/host-terminal/host-shell.ts +1 -1
  590. package/src/tools/memory/register.ts +1 -9
  591. package/src/tools/permission-checker.ts +1 -1
  592. package/src/tools/registry.ts +17 -7
  593. package/src/tools/schedule/create.ts +2 -2
  594. package/src/tools/schema-transforms.ts +7 -2
  595. package/src/tools/side-effects.ts +1 -0
  596. package/src/tools/skills/delete-managed.ts +4 -4
  597. package/src/tools/skills/execute.ts +1 -1
  598. package/src/tools/skills/scaffold-managed.ts +3 -2
  599. package/src/tools/subagent/notify-parent.ts +1 -1
  600. package/src/tools/system/request-permission.ts +2 -2
  601. package/src/tools/terminal/safe-env.ts +60 -1
  602. package/src/tools/tool-manifest.ts +2 -0
  603. package/src/tools/types.ts +107 -21
  604. package/src/tools/ui-surface/definitions.ts +6 -5
  605. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  606. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  607. package/src/types/onboarding-context.ts +2 -0
  608. package/src/util/errors.ts +17 -0
  609. package/src/util/platform.ts +10 -0
  610. package/src/watcher/__tests__/engine.test.ts +22 -0
  611. package/src/watcher/engine.ts +6 -2
  612. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  613. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  614. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  615. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  616. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  617. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  618. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  619. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  620. package/src/workspace/migrations/registry.ts +10 -0
  621. package/src/workspace/migrations/runner.ts +39 -9
  622. package/src/workspace/migrations/types.ts +4 -0
  623. package/examples/plugins/echo/bun.lock +0 -25
  624. package/src/__tests__/context-window-manager.test.ts +0 -2481
  625. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  626. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  627. package/src/context/prompts/compact.md +0 -26
  628. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  629. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  630. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -0,0 +1,104 @@
1
+ /**
2
+ * External plugin runtime — `globalThis.__vellumPluginRuntime` bridge.
3
+ *
4
+ * Workspace-local plugins (`<workspaceDir>/plugins/*`) get dynamic-imported by
5
+ * {@link loadUserPlugins}. They need to call `registerPlugin()` from their
6
+ * module body and they typically also want to read secrets, subscribe to
7
+ * runtime events, etc.
8
+ *
9
+ * Importing those symbols by absolute path
10
+ * (`/abs/path/to/assistant/src/plugins/registry.js`) works when the daemon is
11
+ * running from source — both the daemon and the dynamic-imported plugin
12
+ * resolve the same on-disk file and share module identity. It DOES NOT work
13
+ * when the daemon is a `bun --compile` binary: the daemon's modules are baked
14
+ * into the executable, and any absolute-path import re-loads a fresh disk
15
+ * copy. `registerPlugin()` then writes into a disjoint registry instance and
16
+ * the daemon never sees the plugin.
17
+ *
18
+ * The fix is to expose a single, stable handle on `globalThis` that plugins
19
+ * read at module-load time. The daemon's bundled modules attach themselves
20
+ * here once at startup; plugins consume the same instance regardless of how
21
+ * the daemon was built.
22
+ *
23
+ * Plugins use the bridge like this:
24
+ *
25
+ * const runtime = (globalThis as { __vellumPluginRuntime?: ... })
26
+ * .__vellumPluginRuntime;
27
+ * if (!runtime || runtime.version !== 1) throw new Error("...");
28
+ * const { registerPlugin, assistantEventHub, getSecureKeyAsync } = runtime;
29
+ *
30
+ * Type-only imports (`import type { Plugin } from "..."`) remain free to use
31
+ * absolute paths or workspace-local copies — the TypeScript compiler erases
32
+ * them and they have no module-identity effect at runtime.
33
+ *
34
+ * See `assistant/docs/plugins.md` for the full authoring contract and
35
+ * `assistant/examples/plugins/echo/register.ts` for a worked example.
36
+ */
37
+
38
+ import { assistantEventHub } from "../runtime/assistant-event-hub.js";
39
+ import { getSecureKeyAsync } from "../security/secure-keys.js";
40
+ import { registerPlugin } from "./registry.js";
41
+
42
+ /**
43
+ * The handle plugins read from `globalThis.__vellumPluginRuntime`.
44
+ *
45
+ * `version` is the contract version. Plugins should assert against the
46
+ * version they were authored against and refuse to register if they see a
47
+ * different one — bumping it is a breaking change to the plugin surface.
48
+ *
49
+ * The field set is intentionally small: the most-commonly-needed symbols
50
+ * across both static (module-load-time) and dynamic (init-time) plugin
51
+ * lifecycle. Plugins that need richer runtime state can still receive it
52
+ * through {@link PluginInitContext} during `init()`.
53
+ */
54
+ export interface VellumPluginRuntime {
55
+ readonly version: 1;
56
+ readonly registerPlugin: typeof registerPlugin;
57
+ readonly assistantEventHub: typeof assistantEventHub;
58
+ readonly getSecureKeyAsync: typeof getSecureKeyAsync;
59
+ }
60
+
61
+ /** Stable globalThis key. Don't rename — plugins reference it by string. */
62
+ const RUNTIME_GLOBAL_KEY = "__vellumPluginRuntime" as const;
63
+
64
+ interface GlobalWithRuntime {
65
+ [RUNTIME_GLOBAL_KEY]?: VellumPluginRuntime;
66
+ }
67
+
68
+ /**
69
+ * Install the plugin runtime bridge on `globalThis`. Idempotent — repeat
70
+ * calls are no-ops, so it's safe to invoke from tests that also touch the
71
+ * lifecycle path.
72
+ *
73
+ * Must be called BEFORE {@link loadUserPlugins} runs, otherwise plugins that
74
+ * touch `globalThis.__vellumPluginRuntime` in their module body will throw.
75
+ */
76
+ export function installPluginRuntime(): void {
77
+ const g = globalThis as GlobalWithRuntime;
78
+ if (g[RUNTIME_GLOBAL_KEY]) return;
79
+ g[RUNTIME_GLOBAL_KEY] = {
80
+ version: 1,
81
+ registerPlugin,
82
+ assistantEventHub,
83
+ getSecureKeyAsync,
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Read the installed runtime. Returns `undefined` if {@link installPluginRuntime}
89
+ * hasn't been called yet — plugins should treat this as a fatal error.
90
+ *
91
+ * Exposed mainly for tests and for the rare in-process consumer that wants
92
+ * to read the bridge from the same module graph that installed it.
93
+ */
94
+ export function getPluginRuntime(): VellumPluginRuntime | undefined {
95
+ return (globalThis as GlobalWithRuntime)[RUNTIME_GLOBAL_KEY];
96
+ }
97
+
98
+ /**
99
+ * Tear down the runtime handle. Test-only — production code never uninstalls
100
+ * because the runtime lifetime is the daemon lifetime.
101
+ */
102
+ export function uninstallPluginRuntimeForTests(): void {
103
+ delete (globalThis as GlobalWithRuntime)[RUNTIME_GLOBAL_KEY];
104
+ }
@@ -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
@@ -20,7 +23,9 @@
20
23
  * other filenames sit in the map for
21
24
  * forward compatibility)
22
25
  * tools/
23
- * *.ts ← each file's default export → plugin.tools[]
26
+ * *.ts ← each default export → plugin.tools[];
27
+ * runtime name derives from the filename
28
+ * basename
24
29
  * src/ ← internal helpers, ignored by the loader
25
30
  *
26
31
  * Per-surface, `.js` is preferred over `.ts` (compiled-binary semantics).
@@ -40,8 +45,16 @@ import { readFile } from "node:fs/promises";
40
45
  import { join } from "node:path";
41
46
  import { pathToFileURL } from "node:url";
42
47
 
48
+ import semver from "semver";
43
49
  import { z } from "zod";
44
50
 
51
+ import assistantPkg from "../../package.json" with { type: "json" };
52
+ import type {
53
+ LoadedPluginTool,
54
+ PluginTool,
55
+ RiskLevel,
56
+ ToolExecutionResult,
57
+ } from "../tools/types.js";
45
58
  import { getLogger } from "../util/logger.js";
46
59
  import { registerPlugin } from "./registry.js";
47
60
  import type {
@@ -52,6 +65,8 @@ import type {
52
65
  PluginToolRegistration,
53
66
  } from "./types.js";
54
67
 
68
+ const PLUGIN_API_PEER_DEP = "@vellumai/plugin-api";
69
+
55
70
  const log = getLogger("external-plugin-loader");
56
71
 
57
72
  /** Default upper bound on how long a single plugin load may take. */
@@ -61,9 +76,11 @@ const DEFAULT_IMPORT_TIMEOUT_MS = 10_000;
61
76
  * Zod schema for the subset of `package.json` the external loader reads.
62
77
  *
63
78
  * - `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.
79
+ * - `peerDependencies["@vellumai/plugin-api"]` is the canonical host-compat
80
+ * declaration. If present, the loader checks `semver.satisfies(host, range)`
81
+ * against the running assistant version and rejects the plugin on
82
+ * mismatch. If absent, the plugin loads without a host-compat claim
83
+ * (with a warning).
67
84
  * - Unknown fields pass through (`passthrough`) so the loader does not
68
85
  * destructively reshape the file when the rest of the npm ecosystem
69
86
  * writes to it.
@@ -72,12 +89,7 @@ const PluginPackageJsonSchema = z
72
89
  .object({
73
90
  name: z.string().min(1, "package.json `name` must be a non-empty string"),
74
91
  version: z.string().optional(),
75
- vellum: z
76
- .object({
77
- requires: z.record(z.string(), z.string()).optional(),
78
- })
79
- .passthrough()
80
- .optional(),
92
+ peerDependencies: z.record(z.string(), z.string()).optional(),
81
93
  })
82
94
  .passthrough();
83
95
 
@@ -102,6 +114,72 @@ function stripScope(name: string): string {
102
114
  return match ? match[1]! : name;
103
115
  }
104
116
 
117
+ function toToolNameSegment(value: string): string {
118
+ return value.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "tool";
119
+ }
120
+
121
+ function deriveToolName(toolFileBaseName: string): string {
122
+ return toToolNameSegment(toolFileBaseName);
123
+ }
124
+
125
+ /**
126
+ * Defaults applied by {@link applyPluginToolDefaults} when a plugin tool
127
+ * omits one of the normally-required fields. Exported as a constant so
128
+ * tests and callers can reference the same source of truth.
129
+ *
130
+ * The default `execute` returns an error result so the model sees a clear
131
+ * "this tool isn't wired up" signal at call time. The plugin still loads
132
+ * cleanly — broken individual tools must never block daemon boot.
133
+ */
134
+ export const PLUGIN_TOOL_DEFAULTS = Object.freeze({
135
+ description: "",
136
+ defaultRiskLevel: "medium" as RiskLevel,
137
+ input_schema: Object.freeze({
138
+ type: "object",
139
+ properties: {},
140
+ additionalProperties: false,
141
+ }) as object,
142
+ });
143
+
144
+ /**
145
+ * Fill the four normally-required {@link PluginTool} fields with documented
146
+ * defaults when the author omitted them. Returns a {@link LoadedPluginTool}
147
+ * that is safe to register.
148
+ */
149
+ function applyPluginToolDefaults(
150
+ tool: PluginTool,
151
+ name: string,
152
+ ): LoadedPluginTool {
153
+ const description =
154
+ typeof tool.description === "string"
155
+ ? tool.description
156
+ : PLUGIN_TOOL_DEFAULTS.description;
157
+ const defaultRiskLevel =
158
+ typeof tool.defaultRiskLevel === "string"
159
+ ? tool.defaultRiskLevel
160
+ : PLUGIN_TOOL_DEFAULTS.defaultRiskLevel;
161
+ const input_schema =
162
+ tool.input_schema !== null &&
163
+ typeof tool.input_schema === "object"
164
+ ? tool.input_schema
165
+ : PLUGIN_TOOL_DEFAULTS.input_schema;
166
+ const execute =
167
+ typeof tool.execute === "function"
168
+ ? tool.execute
169
+ : async (): Promise<ToolExecutionResult> => ({
170
+ content: `plugin tool ${name} has no execute implementation`,
171
+ isError: true,
172
+ });
173
+ return {
174
+ ...tool,
175
+ name,
176
+ description,
177
+ defaultRiskLevel,
178
+ input_schema,
179
+ execute,
180
+ };
181
+ }
182
+
105
183
  /**
106
184
  * Dynamic-import `absolutePath` and return its default export. Throws when
107
185
  * the module has no default export — callers attribute the error.
@@ -213,30 +291,65 @@ async function buildPluginFromDir(pluginDir: string): Promise<Plugin> {
213
291
  const name = stripScope(pkg.name);
214
292
  const version = pkg.version && pkg.version.length > 0 ? pkg.version : "0.0.0";
215
293
 
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" };
294
+ // Host-compat negotiation: plugins declare their plugin-api version
295
+ // range via standard `peerDependencies["@vellumai/plugin-api"]`. We
296
+ // inspect the range and report unparseable / unsatisfied cases via
297
+ // `log.error` but still load the plugin the plugin-installation
298
+ // flow is in flux and a strict gate here would block experimentation
299
+ // for the customers driving the install UX. Once the install path
300
+ // settles, the two `log.error` branches below should harden into
301
+ // throws so a stale plugin can't silently run against a mismatched
302
+ // host.
303
+ //
304
+ // If the peerDep is absent, the plugin loads without a host-compat
305
+ // claim; we log a warning so the omission is visible at boot.
306
+ const range = pkg.peerDependencies?.[PLUGIN_API_PEER_DEP];
307
+ if (range !== undefined) {
308
+ if (!semver.validRange(range)) {
309
+ log.error(
310
+ { pluginDir, plugin: name, peerDep: PLUGIN_API_PEER_DEP, range },
311
+ `external plugin ${name}: peerDependencies["${PLUGIN_API_PEER_DEP}"] is not a valid semver range — loading anyway`,
312
+ );
313
+ } else if (
314
+ !semver.satisfies(assistantPkg.version, range, {
315
+ includePrerelease: true,
316
+ })
317
+ ) {
318
+ log.error(
319
+ {
320
+ pluginDir,
321
+ plugin: name,
322
+ peerDep: PLUGIN_API_PEER_DEP,
323
+ range,
324
+ assistantVersion: assistantPkg.version,
325
+ },
326
+ `external plugin ${name}: peerDependencies["${PLUGIN_API_PEER_DEP}"] requires "${range}" but assistant is ${assistantPkg.version} — loading anyway`,
327
+ );
328
+ }
329
+ } else {
330
+ log.warn(
331
+ { pluginDir, plugin: name, peerDep: PLUGIN_API_PEER_DEP },
332
+ "external plugin missing plugin-api peerDependency — loading without host-compat claim",
333
+ );
334
+ }
220
335
 
221
- const manifest: PluginManifest = { name, version, requires };
336
+ const manifest: PluginManifest = { name, version };
222
337
  const plugin: Plugin = { manifest };
223
338
 
224
339
  const hooks = await loadHooks(pluginDir, name);
225
340
  if (hooks !== undefined) plugin.hooks = hooks;
226
341
 
227
342
  const tools: PluginToolRegistration[] = [];
228
- for (const { path: toolPath } of listSurfaceDir(join(pluginDir, "tools"))) {
229
- const tool = await importDefault<PluginToolRegistration>(toolPath);
230
- if (
231
- tool === null ||
232
- typeof tool !== "object" ||
233
- typeof (tool as { name?: unknown }).name !== "string"
234
- ) {
343
+ for (const { name: toolName, path: toolPath } of listSurfaceDir(
344
+ join(pluginDir, "tools"),
345
+ )) {
346
+ const tool = await importDefault<PluginTool>(toolPath);
347
+ if (tool === null || typeof tool !== "object") {
235
348
  throw new Error(
236
- `external plugin ${name}: ${toolPath} default export must be a Tool object with a string "name"`,
349
+ `external plugin ${name}: ${toolPath} default export must be an object`,
237
350
  );
238
351
  }
239
- tools.push(tool);
352
+ tools.push(applyPluginToolDefaults(tool, deriveToolName(toolName)));
240
353
  }
241
354
  if (tools.length > 0) plugin.tools = tools;
242
355
 
@@ -244,16 +357,23 @@ async function buildPluginFromDir(pluginDir: string): Promise<Plugin> {
244
357
  }
245
358
 
246
359
  /**
247
- * Load the external plugin at `pluginDir` and register it.
360
+ * Build a {@link Plugin} from `pluginDir` with the same timeout +
361
+ * per-plugin isolation contract as {@link loadExternalPlugin}, but
362
+ * without registering it. The plugin-source watcher consumes this so it
363
+ * can decide between first-time registration (init once, then publish) and
364
+ * hot-reload (replace + skip init) based on what's already in the registry.
365
+ *
366
+ * Returns `undefined` on timeout, build failure, or abandoned surface
367
+ * import. Never throws — failures are logged with directory attribution.
248
368
  */
249
- export async function loadExternalPlugin(
369
+ export async function buildExternalPlugin(
250
370
  pluginDir: string,
251
371
  opts: LoadExternalPluginOptions = {},
252
- ): Promise<void> {
372
+ ): Promise<Plugin | undefined> {
253
373
  const timeoutMs = opts.importTimeoutMs ?? DEFAULT_IMPORT_TIMEOUT_MS;
254
374
  let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
255
375
  try {
256
- const timeoutSentinel = Symbol("external-plugin-load-timeout");
376
+ const timeoutSentinel = Symbol("external-plugin-build-timeout");
257
377
  const buildPromise = buildPluginFromDir(pluginDir);
258
378
  const timeoutPromise = new Promise<typeof timeoutSentinel>((resolve) => {
259
379
  timeoutHandle = setTimeout(() => resolve(timeoutSentinel), timeoutMs);
@@ -262,33 +382,58 @@ export async function loadExternalPlugin(
262
382
  if (result === timeoutSentinel) {
263
383
  // Abandoned build — surface imports may still be running. Attach a
264
384
  // 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.
385
+ // unhandled-rejection crash. Callers who feed the returned plugin
386
+ // into `registerPlugin` rely on the closed-registration latch
387
+ // (registry.ts) to reject any stale late-arriving registration.
269
388
  buildPromise.catch(() => {
270
389
  /* swallow — see comment above */
271
390
  });
272
391
  log.warn(
273
392
  { pluginDir, timeoutMs },
274
- `Timed out loading external plugin ${pluginDir} after ${timeoutMs}ms — skipping`,
393
+ `Timed out building external plugin ${pluginDir} after ${timeoutMs}ms — skipping`,
275
394
  );
276
- return;
395
+ return undefined;
277
396
  }
278
- registerPlugin(result);
279
- log.info(
280
- { pluginDir, name: result.manifest.name },
281
- "loaded external plugin",
282
- );
397
+ return result;
283
398
  } catch (err) {
284
399
  // Per-plugin isolation: one bad external plugin must not crash the
285
400
  // daemon. Surface the failure with attribution and move on.
286
401
  const message = err instanceof Error ? err.message : String(err);
287
402
  log.error(
288
403
  { err, pluginDir },
289
- `Failed to load external plugin ${pluginDir}: ${message}`,
404
+ `Failed to build external plugin ${pluginDir}: ${message}`,
290
405
  );
406
+ return undefined;
291
407
  } finally {
292
408
  if (timeoutHandle !== undefined) clearTimeout(timeoutHandle);
293
409
  }
294
410
  }
411
+
412
+ /**
413
+ * Load the external plugin at `pluginDir` and register it. Thin wrapper
414
+ * over {@link buildExternalPlugin} that calls `registerPlugin` on the
415
+ * built plugin, preserving the existing `loadUserPlugins` call shape.
416
+ */
417
+ export async function loadExternalPlugin(
418
+ pluginDir: string,
419
+ opts: LoadExternalPluginOptions = {},
420
+ ): Promise<void> {
421
+ const plugin = await buildExternalPlugin(pluginDir, opts);
422
+ if (plugin === undefined) {
423
+ // buildExternalPlugin already logged the failure with attribution.
424
+ return;
425
+ }
426
+ try {
427
+ registerPlugin(plugin);
428
+ log.info(
429
+ { pluginDir, name: plugin.manifest.name },
430
+ "loaded external plugin",
431
+ );
432
+ } catch (err) {
433
+ const message = err instanceof Error ? err.message : String(err);
434
+ log.error(
435
+ { err, pluginDir, plugin: plugin.manifest.name },
436
+ `Failed to register external plugin ${pluginDir}: ${message}`,
437
+ );
438
+ }
439
+ }
@@ -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
  /**