@vellumai/assistant 0.6.5 → 0.6.6

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 (443) hide show
  1. package/AGENTS.md +9 -1
  2. package/ARCHITECTURE.md +15 -17
  3. package/Dockerfile +6 -4
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +283 -0
  5. package/docs/architecture/integrations.md +32 -39
  6. package/docs/architecture/memory.md +25 -30
  7. package/docs/architecture/security.md +7 -6
  8. package/docs/browser-use-architecture-phase2.md +63 -20
  9. package/docs/plugins.md +761 -0
  10. package/examples/plugins/echo/README.md +132 -0
  11. package/examples/plugins/echo/package.json +17 -0
  12. package/examples/plugins/echo/register.ts +187 -0
  13. package/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
  14. package/openapi.yaml +212 -68
  15. package/package.json +1 -1
  16. package/src/__tests__/app-compiler.test.ts +57 -0
  17. package/src/__tests__/approval-cascade.test.ts +7 -2
  18. package/src/__tests__/auto-analysis-end-to-end.test.ts +1 -0
  19. package/src/__tests__/avatar-generator.test.ts +4 -2
  20. package/src/__tests__/bundled-asset.test.ts +6 -6
  21. package/src/__tests__/catalog-cache.test.ts +69 -0
  22. package/src/__tests__/checker.test.ts +459 -171
  23. package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
  24. package/src/__tests__/compaction-events.test.ts +501 -0
  25. package/src/__tests__/compaction-pipeline.test.ts +210 -0
  26. package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
  27. package/src/__tests__/compaction-timeout-recovery.test.ts +262 -0
  28. package/src/__tests__/config-model-image-provider.test.ts +110 -0
  29. package/src/__tests__/config-schema.test.ts +22 -9
  30. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
  31. package/src/__tests__/contacts-tools.test.ts +26 -0
  32. package/src/__tests__/context-overflow-policy.test.ts +7 -7
  33. package/src/__tests__/context-window-manager.test.ts +355 -4
  34. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  35. package/src/__tests__/conversation-agent-loop-overflow.test.ts +26 -30
  36. package/src/__tests__/conversation-agent-loop.test.ts +30 -141
  37. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -1
  38. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  39. package/src/__tests__/conversation-init.benchmark.test.ts +2 -16
  40. package/src/__tests__/conversation-pairing.test.ts +174 -10
  41. package/src/__tests__/conversation-pre-run-repair.test.ts +4 -1
  42. package/src/__tests__/conversation-process-callsite.test.ts +3 -0
  43. package/src/__tests__/conversation-provider-retry-repair.test.ts +16 -7
  44. package/src/__tests__/conversation-queue.test.ts +29 -14
  45. package/src/__tests__/conversation-routes-disk-view.test.ts +7 -6
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +155 -110
  47. package/src/__tests__/conversation-runtime-workspace.test.ts +23 -38
  48. package/src/__tests__/conversation-seed-composer.test.ts +2 -2
  49. package/src/__tests__/conversation-slash-queue.test.ts +7 -2
  50. package/src/__tests__/conversation-slash-unknown.test.ts +25 -2
  51. package/src/__tests__/conversation-speed-override.test.ts +6 -1
  52. package/src/__tests__/conversation-title-service.test.ts +116 -0
  53. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +41 -2
  54. package/src/__tests__/conversation-usage.test.ts +1 -1
  55. package/src/__tests__/conversation-workspace-cache-state.test.ts +4 -1
  56. package/src/__tests__/conversation-workspace-injection.test.ts +3 -0
  57. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -1
  58. package/src/__tests__/credential-health-service.test.ts +78 -9
  59. package/src/__tests__/credential-security-invariants.test.ts +2 -2
  60. package/src/__tests__/db-schedule-syntax-migration.test.ts +1 -0
  61. package/src/__tests__/empty-response-pipeline.test.ts +305 -0
  62. package/src/__tests__/extension-id-sync-guard.test.ts +3 -3
  63. package/src/__tests__/first-greeting.test.ts +247 -5
  64. package/src/__tests__/headless-browser-mode.test.ts +57 -0
  65. package/src/__tests__/history-repair-pipeline.test.ts +399 -0
  66. package/src/__tests__/host-browser-e2e-cloud.test.ts +307 -0
  67. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +3 -3
  68. package/src/__tests__/host-proxy-interface.test.ts +36 -2
  69. package/src/__tests__/image-credentials.test.ts +137 -0
  70. package/src/__tests__/image-service-dispatcher.test.ts +186 -0
  71. package/src/__tests__/injector-chain.test.ts +526 -0
  72. package/src/__tests__/intent-routing.test.ts +0 -26
  73. package/src/__tests__/llm-call-pipeline.test.ts +285 -0
  74. package/src/__tests__/llm-schema.test.ts +1 -1
  75. package/src/__tests__/media-generate-image.test.ts +119 -13
  76. package/src/__tests__/memory-retrieval-pipeline.test.ts +401 -0
  77. package/src/__tests__/memory-upsert-concurrency.test.ts +1 -0
  78. package/src/__tests__/migration-import-from-url.test.ts +5 -68
  79. package/src/__tests__/model-intents.test.ts +4 -2
  80. package/src/__tests__/notification-broadcaster.test.ts +3 -3
  81. package/src/__tests__/notification-decision-strategy.test.ts +0 -11
  82. package/src/__tests__/notification-schedule-notify-dedup.test.ts +108 -0
  83. package/src/__tests__/oauth-apps-routes.test.ts +1 -1
  84. package/src/__tests__/oauth-cli.test.ts +14 -12
  85. package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
  86. package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
  87. package/src/__tests__/oauth-provider-visibility.test.ts +3 -5
  88. package/src/__tests__/oauth-providers-routes.test.ts +3 -2
  89. package/src/__tests__/oauth-store.test.ts +41 -76
  90. package/src/__tests__/onboarding-template-contract.test.ts +16 -64
  91. package/src/__tests__/openai-image-service.test.ts +368 -0
  92. package/src/__tests__/overflow-reduce-pipeline.test.ts +676 -0
  93. package/src/__tests__/permission-checker-host-gate.test.ts +0 -24
  94. package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
  95. package/src/__tests__/persistence-pipeline.test.ts +377 -0
  96. package/src/__tests__/pipeline-runner.test.ts +565 -0
  97. package/src/__tests__/platform.test.ts +5 -2
  98. package/src/__tests__/plugin-bootstrap.test.ts +483 -0
  99. package/src/__tests__/plugin-registry.test.ts +273 -0
  100. package/src/__tests__/plugin-route-contribution.test.ts +288 -0
  101. package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
  102. package/src/__tests__/plugin-tool-contribution.test.ts +286 -0
  103. package/src/__tests__/plugin-types.test.ts +320 -0
  104. package/src/__tests__/pricing.test.ts +44 -12
  105. package/src/__tests__/proxy-approval-callback.test.ts +69 -8
  106. package/src/__tests__/reaction-persistence.test.ts +1 -0
  107. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
  108. package/src/__tests__/registry.test.ts +0 -2
  109. package/src/__tests__/schedule-routes.test.ts +131 -1
  110. package/src/__tests__/scheduler-recurrence.test.ts +14 -70
  111. package/src/__tests__/scheduler-reuse-conversation.test.ts +10 -50
  112. package/src/__tests__/secret-detection-handler.test.ts +0 -10
  113. package/src/__tests__/shell-identity.test.ts +0 -134
  114. package/src/__tests__/suggestion-routes.test.ts +103 -4
  115. package/src/__tests__/task-memory-cleanup.test.ts +1 -0
  116. package/src/__tests__/task-scheduler.test.ts +3 -15
  117. package/src/__tests__/test-preload.ts +11 -0
  118. package/src/__tests__/title-generate-pipeline.test.ts +224 -0
  119. package/src/__tests__/token-estimate-pipeline.test.ts +431 -0
  120. package/src/__tests__/tool-error-pipeline.test.ts +244 -0
  121. package/src/__tests__/tool-execute-pipeline.test.ts +431 -0
  122. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -6
  123. package/src/__tests__/tool-executor-shell-integration.test.ts +7 -10
  124. package/src/__tests__/tool-executor.test.ts +141 -0
  125. package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
  126. package/src/__tests__/tool-result-truncation.test.ts +0 -110
  127. package/src/__tests__/user-plugin-loader.test.ts +191 -0
  128. package/src/__tests__/workspace-migration-046-seed-conversation-starters-callsite.test.ts +185 -0
  129. package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
  130. package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
  131. package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
  132. package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
  133. package/src/__tests__/workspace-policy.test.ts +21 -3
  134. package/src/agent/loop.ts +340 -102
  135. package/src/approvals/__tests__/guardian-feed-event.test.ts +304 -0
  136. package/src/approvals/guardian-request-resolvers.ts +80 -0
  137. package/src/backup/__tests__/backup-worker.test.ts +2 -13
  138. package/src/backup/backup-worker.ts +3 -15
  139. package/src/bundler/app-compiler.ts +84 -1
  140. package/src/calls/call-state.ts +2 -2
  141. package/src/channels/__tests__/types.test.ts +3 -3
  142. package/src/channels/types.ts +6 -4
  143. package/src/cli/__tests__/notifications.test.ts +87 -211
  144. package/src/cli/commands/__tests__/backup.test.ts +1 -1
  145. package/src/cli/commands/__tests__/image-generation.test.ts +255 -35
  146. package/src/cli/commands/__tests__/inference-send.test.ts +12 -0
  147. package/src/cli/commands/__tests__/tts-synthesize.test.ts +12 -0
  148. package/src/cli/commands/backup.ts +2 -2
  149. package/src/cli/commands/clients.ts +138 -0
  150. package/src/cli/commands/completions.ts +2 -9
  151. package/src/cli/commands/conversations.ts +55 -7
  152. package/src/cli/commands/image-generation.ts +33 -34
  153. package/src/cli/commands/notifications.ts +68 -103
  154. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
  155. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
  156. package/src/cli/commands/oauth/connect.ts +2 -2
  157. package/src/cli/commands/oauth/providers.ts +176 -8
  158. package/src/cli/commands/oauth/status.ts +46 -36
  159. package/src/cli/commands/skills.ts +3 -4
  160. package/src/cli/program.ts +25 -29
  161. package/src/config/__tests__/backup-schema.test.ts +7 -2
  162. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  163. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
  164. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
  165. package/src/config/bundled-skills/contacts/tools/contact-search.ts +28 -51
  166. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +22 -40
  167. package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
  168. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
  169. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
  170. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  171. package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
  172. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +12 -0
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +58 -0
  174. package/src/config/bundled-skills/schedule/SKILL.md +8 -3
  175. package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
  176. package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
  177. package/src/config/bundled-tool-registry.ts +0 -15
  178. package/src/config/feature-flag-registry.json +17 -1
  179. package/src/config/schema.ts +19 -0
  180. package/src/config/schemas/backup.ts +1 -1
  181. package/src/config/schemas/conversations.ts +16 -0
  182. package/src/config/schemas/llm.ts +2 -3
  183. package/src/config/schemas/security.ts +6 -6
  184. package/src/config/schemas/tts.ts +11 -0
  185. package/src/config/skill-state.ts +6 -2
  186. package/src/config/skills.ts +94 -5
  187. package/src/context/__tests__/compact-prompt.test.ts +27 -9
  188. package/src/context/prompts/compact.md +26 -12
  189. package/src/context/tool-result-truncation.ts +3 -63
  190. package/src/context/window-manager.ts +190 -16
  191. package/src/credential-health/credential-health-service.ts +19 -6
  192. package/src/daemon/__tests__/conversation-feed-event.test.ts +317 -0
  193. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
  194. package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
  195. package/src/daemon/config-watcher.ts +0 -2
  196. package/src/daemon/context-overflow-policy.ts +4 -13
  197. package/src/daemon/conversation-agent-loop-handlers.ts +83 -22
  198. package/src/daemon/conversation-agent-loop.ts +984 -683
  199. package/src/daemon/conversation-history.ts +10 -19
  200. package/src/daemon/conversation-lifecycle.ts +37 -19
  201. package/src/daemon/conversation-notifiers.ts +2 -110
  202. package/src/daemon/conversation-process.ts +14 -7
  203. package/src/daemon/conversation-runtime-assembly.ts +532 -411
  204. package/src/daemon/conversation-tool-setup.ts +41 -4
  205. package/src/daemon/conversation.ts +80 -35
  206. package/src/daemon/external-plugins-bootstrap.ts +478 -0
  207. package/src/daemon/first-greeting.ts +191 -14
  208. package/src/daemon/handlers/config-model.ts +11 -0
  209. package/src/daemon/handlers/skills.ts +5 -1
  210. package/src/daemon/lifecycle.ts +33 -68
  211. package/src/daemon/message-types/computer-use.ts +2 -34
  212. package/src/daemon/message-types/conversations.ts +49 -0
  213. package/src/daemon/message-types/messages.ts +12 -0
  214. package/src/daemon/server.ts +5 -3
  215. package/src/daemon/shutdown-handlers.ts +2 -12
  216. package/src/daemon/tool-side-effects.ts +14 -56
  217. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +160 -0
  218. package/src/heartbeat/heartbeat-service.ts +24 -1
  219. package/src/home/__tests__/feed-population-integration.test.ts +312 -0
  220. package/src/home/emit-feed-event.ts +7 -0
  221. package/src/home/feed-types.ts +41 -2
  222. package/src/home/rewrite-command-preview.ts +66 -0
  223. package/src/ipc/__tests__/socket-path.test.ts +11 -50
  224. package/src/ipc/cli-client.ts +1 -1
  225. package/src/ipc/cli-server.ts +3 -3
  226. package/src/ipc/gateway-client.ts +4 -1
  227. package/src/ipc/routes/browser-context.ts +2 -0
  228. package/src/ipc/routes/browser.ts +1 -0
  229. package/src/ipc/routes/get-contact.ts +16 -0
  230. package/src/ipc/routes/index.ts +14 -0
  231. package/src/ipc/routes/list-clients.ts +31 -0
  232. package/src/ipc/routes/merge-contacts.ts +17 -0
  233. package/src/ipc/routes/notification.ts +133 -0
  234. package/src/ipc/routes/rename-conversation.ts +59 -0
  235. package/src/ipc/routes/search-contacts.ts +19 -0
  236. package/src/ipc/routes/upsert-contact.ts +25 -0
  237. package/src/ipc/socket-path.ts +14 -38
  238. package/src/media/app-icon-generator.ts +23 -46
  239. package/src/media/avatar-router.ts +26 -41
  240. package/src/media/gemini-image-service.ts +8 -41
  241. package/src/media/image-credentials.ts +73 -0
  242. package/src/media/image-service.ts +85 -0
  243. package/src/media/openai-image-service.ts +131 -0
  244. package/src/media/types.ts +46 -0
  245. package/src/memory/conversation-crud.ts +48 -18
  246. package/src/memory/conversation-queries.ts +57 -4
  247. package/src/memory/conversation-title-service.ts +25 -0
  248. package/src/memory/db-init.ts +8 -0
  249. package/src/memory/embedding-gemini.test.ts +41 -2
  250. package/src/memory/embedding-gemini.ts +6 -1
  251. package/src/memory/graph/bootstrap.test.ts +282 -0
  252. package/src/memory/graph/bootstrap.ts +8 -5
  253. package/src/memory/graph/extraction.ts +10 -2
  254. package/src/memory/graph/graph-search.test.ts +1 -0
  255. package/src/memory/graph/inspect.ts +2 -2
  256. package/src/memory/graph/retriever.ts +10 -3
  257. package/src/memory/migrations/041-approval-prompt-ts-tracker.ts +26 -0
  258. package/src/memory/migrations/149-oauth-tables.ts +1 -0
  259. package/src/memory/migrations/223-schedule-script-column.ts +11 -0
  260. package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
  261. package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
  262. package/src/memory/migrations/index.ts +4 -0
  263. package/src/memory/pkb/pkb-index.test.ts +1 -0
  264. package/src/memory/pkb/pkb-reconcile.test.ts +1 -0
  265. package/src/memory/pkb/pkb-search.test.ts +65 -4
  266. package/src/memory/pkb/pkb-search.ts +40 -18
  267. package/src/memory/qdrant-client.test.ts +60 -0
  268. package/src/memory/qdrant-client.ts +25 -0
  269. package/src/memory/schema/infrastructure.ts +1 -0
  270. package/src/memory/schema/oauth.ts +4 -1
  271. package/src/messaging/providers/slack/render-transcript.test.ts +77 -29
  272. package/src/messaging/providers/slack/render-transcript.ts +58 -0
  273. package/src/notifications/conversation-pairing.ts +78 -19
  274. package/src/notifications/copy-composer.ts +0 -5
  275. package/src/notifications/emit-signal.ts +1 -1
  276. package/src/notifications/signal.ts +1 -2
  277. package/src/oauth/AGENTS.md +1 -1
  278. package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
  279. package/src/oauth/connect-orchestrator.ts +8 -34
  280. package/src/oauth/connect-types.ts +6 -10
  281. package/src/oauth/manual-token-connection.ts +23 -0
  282. package/src/oauth/oauth-store.ts +30 -14
  283. package/src/oauth/provider-serializer.ts +6 -1
  284. package/src/oauth/seed-providers.ts +56 -108
  285. package/src/outbound-proxy/http-forwarder.ts +9 -0
  286. package/src/permissions/approval-policy.test.ts +293 -18
  287. package/src/permissions/approval-policy.ts +110 -58
  288. package/src/permissions/arg-parser.test.ts +161 -0
  289. package/src/permissions/arg-parser.ts +141 -0
  290. package/src/permissions/bash-risk-classifier.test.ts +414 -2
  291. package/src/permissions/bash-risk-classifier.ts +303 -60
  292. package/src/permissions/checker.ts +157 -29
  293. package/src/permissions/command-registry.test.ts +239 -0
  294. package/src/permissions/command-registry.ts +234 -54
  295. package/src/permissions/defaults.ts +5 -4
  296. package/src/permissions/gateway-threshold-reader.ts +196 -0
  297. package/src/permissions/prompter.ts +4 -0
  298. package/src/permissions/risk-types.ts +61 -4
  299. package/src/permissions/schedule-risk-classifier.test.ts +129 -0
  300. package/src/permissions/schedule-risk-classifier.ts +85 -0
  301. package/src/permissions/shell-identity.ts +2 -42
  302. package/src/permissions/types.ts +2 -0
  303. package/src/permissions/workspace-policy.ts +8 -3
  304. package/src/plugins/defaults/circuit-breaker.ts +146 -0
  305. package/src/plugins/defaults/compaction.ts +145 -0
  306. package/src/plugins/defaults/empty-response.ts +126 -0
  307. package/src/plugins/defaults/history-repair.ts +85 -0
  308. package/src/plugins/defaults/index.ts +116 -0
  309. package/src/plugins/defaults/injectors.ts +491 -0
  310. package/src/plugins/defaults/llm-call.ts +82 -0
  311. package/src/plugins/defaults/memory-retrieval.ts +226 -0
  312. package/src/plugins/defaults/overflow-reduce.ts +181 -0
  313. package/src/plugins/defaults/persistence.ts +129 -0
  314. package/src/plugins/defaults/title-generate.ts +95 -0
  315. package/src/plugins/defaults/token-estimate.ts +104 -0
  316. package/src/plugins/defaults/tool-error.ts +126 -0
  317. package/src/plugins/defaults/tool-execute.ts +89 -0
  318. package/src/plugins/defaults/tool-result-truncate.ts +88 -0
  319. package/src/plugins/pipeline.ts +316 -0
  320. package/src/plugins/plugin-skill-contributions.ts +292 -0
  321. package/src/plugins/registry.ts +241 -0
  322. package/src/plugins/types.ts +1134 -0
  323. package/src/plugins/user-loader.ts +177 -0
  324. package/src/prompts/templates/BOOTSTRAP.md +27 -77
  325. package/src/providers/model-catalog.ts +52 -29
  326. package/src/providers/model-intents.ts +1 -1
  327. package/src/providers/openrouter/client.ts +5 -1
  328. package/src/providers/speech-to-text/deepgram-realtime.test.ts +61 -0
  329. package/src/providers/speech-to-text/deepgram-realtime.ts +57 -0
  330. package/src/providers/speech-to-text/xai-realtime.test.ts +72 -4
  331. package/src/providers/speech-to-text/xai-realtime.ts +39 -14
  332. package/src/runtime/AGENTS.md +25 -16
  333. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +3 -3
  334. package/src/runtime/__tests__/client-registry.test.ts +293 -0
  335. package/src/runtime/client-registry.ts +261 -0
  336. package/src/runtime/http-server.ts +77 -8
  337. package/src/runtime/http-types.ts +0 -2
  338. package/src/runtime/migrations/vbundle-builder.ts +1 -22
  339. package/src/runtime/routes/approval-prompt-ts-tracker.ts +51 -31
  340. package/src/runtime/routes/approval-routes.ts +17 -0
  341. package/src/runtime/routes/browser-extension-pair-routes.ts +27 -8
  342. package/src/runtime/routes/conversation-routes.ts +223 -116
  343. package/src/runtime/routes/inbound-message-handler.ts +88 -13
  344. package/src/runtime/routes/memory-item-routes.test.ts +1 -0
  345. package/src/runtime/routes/migration-routes.ts +0 -3
  346. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +284 -0
  347. package/src/runtime/routes/playground/__tests__/guard.test.ts +80 -0
  348. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +294 -0
  349. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +271 -0
  350. package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +202 -0
  351. package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +309 -0
  352. package/src/runtime/routes/playground/__tests__/state.test.ts +224 -0
  353. package/src/runtime/routes/playground/conversation-not-found.ts +29 -0
  354. package/src/runtime/routes/playground/deps.ts +56 -0
  355. package/src/runtime/routes/playground/force-compact.ts +73 -0
  356. package/src/runtime/routes/playground/guard.ts +37 -0
  357. package/src/runtime/routes/playground/index.ts +28 -0
  358. package/src/runtime/routes/playground/inject-failures.ts +159 -0
  359. package/src/runtime/routes/playground/reset-circuit.ts +115 -0
  360. package/src/runtime/routes/playground/seed-conversation.ts +139 -0
  361. package/src/runtime/routes/playground/seeded-conversations.ts +78 -0
  362. package/src/runtime/routes/playground/state.ts +78 -0
  363. package/src/runtime/routes/schedule-routes.ts +89 -8
  364. package/src/runtime/skill-route-registry.ts +75 -15
  365. package/src/schedule/run-script.ts +68 -0
  366. package/src/schedule/schedule-store.ts +7 -1
  367. package/src/schedule/scheduler.ts +48 -8
  368. package/src/skills/catalog-cache.ts +12 -5
  369. package/src/tools/browser/__tests__/browser-status.test.ts +189 -0
  370. package/src/tools/browser/browser-execution.ts +88 -19
  371. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
  372. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
  373. package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
  374. package/src/tools/browser/cdp-client/factory.ts +15 -4
  375. package/src/tools/executor.ts +126 -74
  376. package/src/tools/network/script-proxy/session-manager.ts +37 -1
  377. package/src/tools/permission-checker.ts +98 -49
  378. package/src/tools/policy-context.ts +4 -0
  379. package/src/tools/registry.ts +140 -3
  380. package/src/tools/schedule/create.ts +23 -8
  381. package/src/tools/schedule/update.ts +3 -1
  382. package/src/tools/secret-detection-handler.ts +0 -51
  383. package/src/tools/system/avatar-generator.ts +6 -2
  384. package/src/tools/types.ts +28 -2
  385. package/src/util/platform.ts +7 -2
  386. package/src/util/pricing.ts +26 -3
  387. package/src/workspace/migrations/006-services-config.ts +2 -4
  388. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
  389. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +3 -4
  390. package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +108 -0
  391. package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
  392. package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
  393. package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
  394. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
  395. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
  396. package/src/workspace/migrations/registry.ts +12 -0
  397. package/tsconfig.json +1 -1
  398. package/hook-templates/debug-prompt-logger/hook.json +0 -7
  399. package/hook-templates/debug-prompt-logger/run.sh +0 -66
  400. package/src/__tests__/compaction-circuit-breaker.test.ts +0 -336
  401. package/src/__tests__/context-overflow-approval.test.ts +0 -156
  402. package/src/__tests__/hooks-blocking.test.ts +0 -178
  403. package/src/__tests__/hooks-cli.test.ts +0 -182
  404. package/src/__tests__/hooks-config.test.ts +0 -108
  405. package/src/__tests__/hooks-discovery.test.ts +0 -211
  406. package/src/__tests__/hooks-integration.test.ts +0 -196
  407. package/src/__tests__/hooks-manager.test.ts +0 -226
  408. package/src/__tests__/hooks-runner.test.ts +0 -175
  409. package/src/__tests__/hooks-settings.test.ts +0 -160
  410. package/src/__tests__/hooks-templates.test.ts +0 -169
  411. package/src/__tests__/hooks-ts-runner.test.ts +0 -170
  412. package/src/__tests__/hooks-watch.test.ts +0 -112
  413. package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
  414. package/src/__tests__/oauth-scope-policy.test.ts +0 -180
  415. package/src/__tests__/send-notification-tool.test.ts +0 -83
  416. package/src/cli/commands/shotgun.ts +0 -266
  417. package/src/config/bundled-skills/conversations/SKILL.md +0 -20
  418. package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
  419. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -88
  420. package/src/config/bundled-skills/heartbeat/SKILL.md +0 -43
  421. package/src/config/bundled-skills/notifications/SKILL.md +0 -40
  422. package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
  423. package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
  424. package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
  425. package/src/config/bundled-skills/screen-watch/SKILL.md +0 -27
  426. package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
  427. package/src/config/bundled-skills/screen-watch/tools/start-screen-watch.ts +0 -12
  428. package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
  429. package/src/daemon/context-overflow-approval.ts +0 -52
  430. package/src/daemon/watch-handler.ts +0 -399
  431. package/src/hooks/cli.ts +0 -253
  432. package/src/hooks/config.ts +0 -100
  433. package/src/hooks/discovery.ts +0 -135
  434. package/src/hooks/manager.ts +0 -179
  435. package/src/hooks/runner.ts +0 -117
  436. package/src/hooks/templates.ts +0 -77
  437. package/src/hooks/types.ts +0 -75
  438. package/src/oauth/scope-policy.ts +0 -89
  439. package/src/runtime/gateway-internal-client.ts +0 -94
  440. package/src/runtime/routes/watch-routes.ts +0 -156
  441. package/src/signals/shotgun.ts +0 -203
  442. package/src/tools/watch/screen-watch.ts +0 -144
  443. package/src/tools/watch/watch-state.ts +0 -142
@@ -0,0 +1,483 @@
1
+ /**
2
+ * Tests for plugin bootstrap (PR 14).
3
+ *
4
+ * Covers:
5
+ * - A noop `init()` fires with a valid `PluginInitContext` that exposes every
6
+ * documented field.
7
+ * - `requiresCredential` entries are resolved through the credential store
8
+ * helper and arrive in `ctx.credentials`.
9
+ * - Version-mismatch registration fails with an error that names the plugin
10
+ * (the registry enforces this at `registerPlugin` time, so bootstrap never
11
+ * sees the malformed plugin).
12
+ * - Shutdown hook walks plugins in reverse registration order.
13
+ *
14
+ * Uses `mock.module` to stub `security/secure-keys.js` so credential
15
+ * resolution doesn't hit the real backend. `resetPluginRegistryForTests()`
16
+ * isolates registry state between cases.
17
+ */
18
+
19
+ import { existsSync } from "node:fs";
20
+ import { rm } from "node:fs/promises";
21
+ import { tmpdir } from "node:os";
22
+ import { join } from "node:path";
23
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
24
+
25
+ // Mock credential store before importing the bootstrap module so the
26
+ // module-under-test captures the stubbed binding.
27
+ const getSecureKeyAsyncMock = mock(
28
+ async (_account: string): Promise<string | undefined> => undefined,
29
+ );
30
+ mock.module("../security/secure-keys.js", () => ({
31
+ getSecureKeyAsync: getSecureKeyAsyncMock,
32
+ }));
33
+
34
+ import {
35
+ _setOverridesForTesting,
36
+ clearFeatureFlagOverridesCache,
37
+ } from "../config/assistant-feature-flags.js";
38
+ import type { AssistantConfig } from "../config/schema.js";
39
+ import {
40
+ bootstrapPlugins,
41
+ type DaemonContext,
42
+ } from "../daemon/external-plugins-bootstrap.js";
43
+ import { runShutdownHooks } from "../daemon/shutdown-registry.js";
44
+ import { RiskLevel } from "../permissions/types.js";
45
+ import {
46
+ ASSISTANT_API_VERSIONS,
47
+ registerPlugin,
48
+ resetPluginRegistryForTests,
49
+ } from "../plugins/registry.js";
50
+ import {
51
+ type Plugin,
52
+ PluginExecutionError,
53
+ type PluginInitContext,
54
+ } from "../plugins/types.js";
55
+
56
+ // Redirect plugin storage directory creation into a per-process temp tree so
57
+ // the test doesn't touch the developer's real ~/.vellum.
58
+ const TEST_INSTANCE_DIR = join(
59
+ tmpdir(),
60
+ `vellum-plugin-bootstrap-test-${process.pid}`,
61
+ );
62
+ process.env.BASE_DATA_DIR = TEST_INSTANCE_DIR;
63
+
64
+ const fakeConfig = {} as unknown as AssistantConfig;
65
+ const fakeCtx: DaemonContext = {
66
+ config: fakeConfig,
67
+ assistantVersion: "9.9.9-test",
68
+ };
69
+
70
+ function buildPlugin(
71
+ name: string,
72
+ extras: Partial<Omit<Plugin, "manifest">> = {},
73
+ options: {
74
+ requires?: Record<string, string>;
75
+ requiresCredential?: string[];
76
+ requiresFlag?: string[];
77
+ } = {},
78
+ ): Plugin {
79
+ return {
80
+ manifest: {
81
+ name,
82
+ version: "0.0.1",
83
+ requires: options.requires ?? { pluginRuntime: "v1" },
84
+ ...(options.requiresCredential
85
+ ? { requiresCredential: options.requiresCredential }
86
+ : {}),
87
+ ...(options.requiresFlag ? { requiresFlag: options.requiresFlag } : {}),
88
+ },
89
+ ...extras,
90
+ };
91
+ }
92
+
93
+ describe("plugin bootstrap", () => {
94
+ beforeEach(async () => {
95
+ resetPluginRegistryForTests();
96
+ getSecureKeyAsyncMock.mockReset();
97
+ getSecureKeyAsyncMock.mockImplementation(async () => undefined);
98
+ // Reset feature-flag cache so tests start from a known state. Individual
99
+ // tests that exercise `requiresFlag` use `_setOverridesForTesting(...)`
100
+ // to install their own overrides.
101
+ clearFeatureFlagOverridesCache();
102
+ // Clean storage directory between runs so nothing leaks across cases.
103
+ await rm(TEST_INSTANCE_DIR, { recursive: true, force: true });
104
+ });
105
+
106
+ test("noop plugin: init fires with a fully-populated PluginInitContext", async () => {
107
+ let received: PluginInitContext | undefined;
108
+ const plugin: Plugin = buildPlugin("alpha", {
109
+ async init(ctx) {
110
+ received = ctx;
111
+ },
112
+ });
113
+ registerPlugin(plugin);
114
+
115
+ await bootstrapPlugins(fakeCtx);
116
+
117
+ expect(received).toBeDefined();
118
+ const ctx = received!;
119
+
120
+ // Every documented field must be present on the context passed to init.
121
+ expect(ctx.config).toBeUndefined(); // no `plugins.alpha` block in fake config
122
+ expect(ctx.credentials).toEqual({});
123
+ expect(ctx.logger).toBeDefined();
124
+ expect(typeof (ctx.logger as { info: unknown }).info).toBe("function");
125
+ // Storage dir lives under vellumRoot()/plugins-data/<name> and must have
126
+ // been created on disk by bootstrap.
127
+ expect(ctx.pluginStorageDir).toBe(
128
+ join(TEST_INSTANCE_DIR, ".vellum", "plugins-data", "alpha"),
129
+ );
130
+ expect(existsSync(ctx.pluginStorageDir)).toBe(true);
131
+ expect(ctx.assistantVersion).toBe("9.9.9-test");
132
+ // apiVersions must surface the canonical capability table from the
133
+ // registry so plugins can negotiate at runtime.
134
+ expect(ctx.apiVersions).toBe(ASSISTANT_API_VERSIONS);
135
+ expect(ctx.apiVersions.pluginRuntime).toEqual(["v1"]);
136
+ });
137
+
138
+ test("credential resolution: init receives the resolved value under credentials[key]", async () => {
139
+ getSecureKeyAsyncMock.mockImplementation(async (account: string) => {
140
+ if (account === "some-key") return "super-secret-value";
141
+ return undefined;
142
+ });
143
+
144
+ let received: PluginInitContext | undefined;
145
+ const plugin = buildPlugin(
146
+ "credentialed",
147
+ {
148
+ async init(ctx) {
149
+ received = ctx;
150
+ },
151
+ },
152
+ { requiresCredential: ["some-key"] },
153
+ );
154
+ registerPlugin(plugin);
155
+
156
+ await bootstrapPlugins(fakeCtx);
157
+
158
+ expect(getSecureKeyAsyncMock).toHaveBeenCalledTimes(1);
159
+ expect(getSecureKeyAsyncMock).toHaveBeenCalledWith("some-key");
160
+ expect(received?.credentials).toEqual({ "some-key": "super-secret-value" });
161
+ });
162
+
163
+ test("credential resolution: missing credential fails bootstrap with the plugin named", async () => {
164
+ getSecureKeyAsyncMock.mockImplementation(async () => undefined);
165
+
166
+ registerPlugin(
167
+ buildPlugin(
168
+ "missing-cred",
169
+ { async init() {} },
170
+ { requiresCredential: ["absent-key"] },
171
+ ),
172
+ );
173
+
174
+ let caught: unknown;
175
+ try {
176
+ await bootstrapPlugins(fakeCtx);
177
+ } catch (err) {
178
+ caught = err;
179
+ }
180
+ expect(caught).toBeInstanceOf(PluginExecutionError);
181
+ const msg = (caught as PluginExecutionError).message;
182
+ expect(msg).toContain("missing-cred");
183
+ expect(msg).toContain("absent-key");
184
+ });
185
+
186
+ test("version mismatch: registration surfaces a clear error naming the plugin", () => {
187
+ // The assistant only exposes pluginRuntime@v1 — asking for v99 must fail
188
+ // registration with the plugin name in the message. The error is raised
189
+ // at registerPlugin() rather than bootstrap, because the registry is the
190
+ // single authoritative point of capability validation.
191
+ const plugin = buildPlugin(
192
+ "from-the-future",
193
+ {},
194
+ { requires: { pluginRuntime: "v99" } },
195
+ );
196
+
197
+ let caught: unknown;
198
+ try {
199
+ registerPlugin(plugin);
200
+ } catch (err) {
201
+ caught = err;
202
+ }
203
+ expect(caught).toBeInstanceOf(PluginExecutionError);
204
+ const msg = (caught as PluginExecutionError).message;
205
+ expect(msg).toContain("from-the-future");
206
+ expect(msg).toContain("pluginRuntime");
207
+ expect(msg).toContain("v99");
208
+ expect((caught as PluginExecutionError).pluginName).toBe("from-the-future");
209
+ });
210
+
211
+ test("plugin init throw: bootstrap throws a PluginExecutionError naming the plugin", async () => {
212
+ registerPlugin(
213
+ buildPlugin("broken", {
214
+ async init() {
215
+ throw new Error("kaboom");
216
+ },
217
+ }),
218
+ );
219
+
220
+ let caught: unknown;
221
+ try {
222
+ await bootstrapPlugins(fakeCtx);
223
+ } catch (err) {
224
+ caught = err;
225
+ }
226
+ expect(caught).toBeInstanceOf(PluginExecutionError);
227
+ const msg = (caught as PluginExecutionError).message;
228
+ expect(msg).toContain("broken");
229
+ expect(msg).toContain("kaboom");
230
+ });
231
+
232
+ test("partial-init failure: earlier plugins' onShutdown runs in reverse before the error propagates", async () => {
233
+ // If plugin N throws during init, every plugin 1..N-1 that already made
234
+ // it through its full init+contribution phase must have onShutdown()
235
+ // invoked in reverse registration order before bootstrap re-throws.
236
+ // Without this, earlier plugins leak live tools/routes/skills because
237
+ // the shutdown hook is only registered once the entire loop completes.
238
+ const callOrder: string[] = [];
239
+ registerPlugin(
240
+ buildPlugin("survivor-a", {
241
+ async init() {},
242
+ async onShutdown() {
243
+ callOrder.push("survivor-a");
244
+ },
245
+ }),
246
+ );
247
+ registerPlugin(
248
+ buildPlugin("survivor-b", {
249
+ async init() {},
250
+ async onShutdown() {
251
+ callOrder.push("survivor-b");
252
+ },
253
+ }),
254
+ );
255
+ registerPlugin(
256
+ buildPlugin("failing", {
257
+ async init() {
258
+ throw new Error("mid-bootstrap failure");
259
+ },
260
+ async onShutdown() {
261
+ // Never called — this plugin never completes init, so it was never
262
+ // added to the active list that teardown walks.
263
+ callOrder.push("failing");
264
+ },
265
+ }),
266
+ );
267
+
268
+ let caught: unknown;
269
+ try {
270
+ await bootstrapPlugins(fakeCtx);
271
+ } catch (err) {
272
+ caught = err;
273
+ }
274
+ expect(caught).toBeInstanceOf(PluginExecutionError);
275
+
276
+ // Reverse order: survivor-b registered after survivor-a, so it tears
277
+ // down first; "failing" never entered the active list.
278
+ expect(callOrder).toEqual(["survivor-b", "survivor-a"]);
279
+ });
280
+
281
+ test("shutdown order: onShutdown fires in reverse registration order", async () => {
282
+ const callOrder: string[] = [];
283
+ registerPlugin(
284
+ buildPlugin("first-registered", {
285
+ async onShutdown() {
286
+ callOrder.push("first-registered");
287
+ },
288
+ }),
289
+ );
290
+ registerPlugin(
291
+ buildPlugin("second-registered", {
292
+ async onShutdown() {
293
+ callOrder.push("second-registered");
294
+ },
295
+ }),
296
+ );
297
+
298
+ await bootstrapPlugins(fakeCtx);
299
+ await runShutdownHooks("test-shutdown");
300
+
301
+ // The last plugin to register must shut down first; the first to register
302
+ // shuts down last. Symmetric tear-down around registration order is the
303
+ // whole point of the reverse walk.
304
+ expect(callOrder).toEqual(["second-registered", "first-registered"]);
305
+ });
306
+
307
+ test("empty registry: bootstrap seeds the first-party defaults without throwing", async () => {
308
+ // The bootstrap path calls `registerDefaultPlugins` at the top, so even
309
+ // when the test-reset registry starts empty the bootstrap emerges with
310
+ // the canonical defaults installed (compaction circuit breaker,
311
+ // tool-result truncate, etc.). Just assert bootstrap completes without
312
+ // throwing — the surface of defaults is verified in each pipeline's own
313
+ // dedicated test file.
314
+ await bootstrapPlugins(fakeCtx);
315
+ });
316
+
317
+ // ── requiresFlag gating (G2.2) ──────────────────────────────────────────
318
+ //
319
+ // Plugins that declare `manifest.requiresFlag: [key1, ...]` must only
320
+ // activate when ALL listed flag keys resolve to `true` at bootstrap.
321
+ // "Skipping" a plugin means:
322
+ // - init() is not invoked,
323
+ // - tools/routes/skills are not registered,
324
+ // - no shutdown hook entry is installed (nothing to tear down later).
325
+ // Plugins without `requiresFlag` are unaffected.
326
+ //
327
+ // Uses `_setOverridesForTesting` to control the resolver deterministically
328
+ // — no disk writes, no gateway IPC, no reliance on registry defaults.
329
+
330
+ test("requiresFlag enabled: plugin inits normally", async () => {
331
+ _setOverridesForTesting({ "plugin-gated-enabled": true });
332
+
333
+ let initFired = false;
334
+ const plugin = buildPlugin(
335
+ "gated-on",
336
+ {
337
+ async init() {
338
+ initFired = true;
339
+ },
340
+ },
341
+ { requiresFlag: ["plugin-gated-enabled"] },
342
+ );
343
+ registerPlugin(plugin);
344
+
345
+ await bootstrapPlugins(fakeCtx);
346
+
347
+ expect(initFired).toBe(true);
348
+ });
349
+
350
+ test("requiresFlag disabled: init does not fire and no tools/routes/skills are registered", async () => {
351
+ _setOverridesForTesting({ "plugin-gated-disabled": false });
352
+
353
+ let initFired = false;
354
+ // Attach tool/route/skill contributions alongside init. If gating works,
355
+ // none of them should land in their respective registries.
356
+ const plugin = buildPlugin(
357
+ "gated-off",
358
+ {
359
+ async init() {
360
+ initFired = true;
361
+ },
362
+ tools: [
363
+ {
364
+ name: "gated-off-tool",
365
+ description: "should not be registered",
366
+ category: "plugin-test",
367
+ defaultRiskLevel: RiskLevel.Low,
368
+ getDefinition: () => ({
369
+ name: "gated-off-tool",
370
+ description: "should not be registered",
371
+ input_schema: { type: "object", properties: {}, required: [] },
372
+ }),
373
+ execute: async () => ({ content: "nope", isError: false }),
374
+ },
375
+ ],
376
+ routes: [
377
+ {
378
+ // Unique pattern so we don't collide with any other test's route.
379
+ pattern: /^\/_plugin\/gated-off\/status$/,
380
+ methods: ["GET"],
381
+ handler: async () => new Response("ok"),
382
+ },
383
+ ],
384
+ skills: [
385
+ {
386
+ id: "gated-off/skill",
387
+ name: "gated-off-skill",
388
+ description: "should not be catalogued",
389
+ body: "# unused",
390
+ },
391
+ ],
392
+ },
393
+ { requiresFlag: ["plugin-gated-disabled"] },
394
+ );
395
+ registerPlugin(plugin);
396
+
397
+ // Grab tool / route / skill introspection helpers lazily so the import
398
+ // side effect happens after `mock.module` has taken effect.
399
+ const { getTool } = await import("../tools/registry.js");
400
+ const { getPluginSkillRefCount } =
401
+ await import("../plugins/plugin-skill-contributions.js");
402
+ const { matchSkillRoute } =
403
+ await import("../runtime/skill-route-registry.js");
404
+
405
+ await bootstrapPlugins(fakeCtx);
406
+
407
+ // init must not have fired.
408
+ expect(initFired).toBe(false);
409
+ // No tool contributed.
410
+ expect(getTool("gated-off-tool")).toBeUndefined();
411
+ // No route wired up — `matchSkillRoute` returns null when nothing matches.
412
+ expect(matchSkillRoute("/_plugin/gated-off/status", "GET")).toBeNull();
413
+ // No skill catalogued under this plugin's name — ref count stays 0.
414
+ expect(getPluginSkillRefCount("gated-off")).toBe(0);
415
+ });
416
+
417
+ test("requiresFlag absent: plugin activates unconditionally", async () => {
418
+ // Deliberately do not set any overrides — the resolver defaults
419
+ // undeclared keys to `true`, but more importantly a plugin with no
420
+ // `requiresFlag` key must not consult the resolver at all.
421
+ let initFired = false;
422
+ const plugin = buildPlugin("no-flag", {
423
+ async init() {
424
+ initFired = true;
425
+ },
426
+ });
427
+ registerPlugin(plugin);
428
+
429
+ await bootstrapPlugins(fakeCtx);
430
+
431
+ expect(initFired).toBe(true);
432
+ });
433
+
434
+ test("requiresFlag: one disabled flag out of several skips the plugin", async () => {
435
+ // When ANY listed flag is disabled, the plugin is skipped wholesale —
436
+ // this prevents sneaky partial activation on AND semantics.
437
+ _setOverridesForTesting({
438
+ "plugin-multi-a": true,
439
+ "plugin-multi-b": false,
440
+ });
441
+
442
+ let initFired = false;
443
+ const plugin = buildPlugin(
444
+ "multi-flag",
445
+ {
446
+ async init() {
447
+ initFired = true;
448
+ },
449
+ },
450
+ { requiresFlag: ["plugin-multi-a", "plugin-multi-b"] },
451
+ );
452
+ registerPlugin(plugin);
453
+
454
+ await bootstrapPlugins(fakeCtx);
455
+
456
+ expect(initFired).toBe(false);
457
+ });
458
+
459
+ test("requiresFlag disabled: no shutdown hook entry installed for the skipped plugin", async () => {
460
+ _setOverridesForTesting({ "plugin-shutdown-flag": false });
461
+
462
+ let shutdownFired = false;
463
+ const plugin = buildPlugin(
464
+ "shutdown-skipped",
465
+ {
466
+ async init() {},
467
+ async onShutdown() {
468
+ shutdownFired = true;
469
+ },
470
+ },
471
+ { requiresFlag: ["plugin-shutdown-flag"] },
472
+ );
473
+ registerPlugin(plugin);
474
+
475
+ await bootstrapPlugins(fakeCtx);
476
+ await runShutdownHooks("test-shutdown");
477
+
478
+ // The shutdown hook is a single registered callback that walks a
479
+ // snapshot taken at bootstrap. A skipped plugin should never appear in
480
+ // that snapshot, so its `onShutdown` must never fire.
481
+ expect(shutdownFired).toBe(false);
482
+ });
483
+ });