@vellumai/assistant 0.8.3 → 0.8.5

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 (665) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/docker-entrypoint.sh +0 -1
  3. package/docs/browser-use-architecture-phase2.md +1 -1
  4. package/knip.json +2 -1
  5. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  6. package/openapi.yaml +1492 -100
  7. package/package.json +1 -1
  8. package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
  9. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +302 -33
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
  14. package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
  15. package/src/__tests__/audit-log-rotation.test.ts +70 -16
  16. package/src/__tests__/background-workers-disk-pressure.test.ts +4 -3
  17. package/src/__tests__/btw-routes.test.ts +2 -3
  18. package/src/__tests__/call-controller.test.ts +0 -1
  19. package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
  20. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  21. package/src/__tests__/channel-guardian.test.ts +3 -3
  22. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  23. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  24. package/src/__tests__/checker.test.ts +6 -15
  25. package/src/__tests__/compaction-events.test.ts +2 -1
  26. package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
  27. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  28. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
  29. package/src/__tests__/computer-use-tools.test.ts +2 -4
  30. package/src/__tests__/config-watcher.test.ts +1 -1
  31. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  32. package/src/__tests__/context-token-estimator.test.ts +91 -1
  33. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  34. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
  35. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +55 -4
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +228 -8
  37. package/src/__tests__/conversation-agent-loop.test.ts +188 -129
  38. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
  39. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  40. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  41. package/src/__tests__/conversation-clear-safety.test.ts +25 -25
  42. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  43. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
  44. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  45. package/src/__tests__/conversation-error.test.ts +31 -0
  46. package/src/__tests__/conversation-fork-crud.test.ts +324 -0
  47. package/src/__tests__/conversation-lifecycle.test.ts +53 -12
  48. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  49. package/src/__tests__/conversation-load-history-stripped.test.ts +279 -0
  50. package/src/__tests__/conversation-pairing.test.ts +2 -2
  51. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  52. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -1
  53. package/src/__tests__/conversation-queue.test.ts +1 -1
  54. package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
  55. package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
  56. package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
  57. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  58. package/src/__tests__/conversation-skill-tools.test.ts +2 -5
  59. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  60. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  61. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  62. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  63. package/src/__tests__/conversation-store.test.ts +1 -1
  64. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  65. package/src/__tests__/conversation-sync-tags.test.ts +99 -32
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -1
  67. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  69. package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
  70. package/src/__tests__/credential-execution-tools.test.ts +6 -6
  71. package/src/__tests__/credential-security-invariants.test.ts +7 -0
  72. package/src/__tests__/credential-vault-unit.test.ts +2 -2
  73. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  74. package/src/__tests__/dm-backfill.test.ts +64 -0
  75. package/src/__tests__/dm-persistence.test.ts +33 -0
  76. package/src/__tests__/document-find-replace.test.ts +501 -0
  77. package/src/__tests__/dynamic-page-surface.test.ts +2 -2
  78. package/src/__tests__/email-html-renderer.test.ts +12 -0
  79. package/src/__tests__/first-greeting.test.ts +23 -2
  80. package/src/__tests__/gateway-flag-listener.test.ts +237 -0
  81. package/src/__tests__/gemini-provider.test.ts +78 -0
  82. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  83. package/src/__tests__/guardian-outbound-http.test.ts +7 -5
  84. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  85. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  86. package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
  87. package/src/__tests__/heartbeat-service.test.ts +4 -0
  88. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  89. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  90. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  91. package/src/__tests__/host-file-proxy.test.ts +8 -1
  92. package/src/__tests__/host-shell-tool.test.ts +1 -1
  93. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  94. package/src/__tests__/identity-routes.test.ts +57 -0
  95. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  96. package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
  97. package/src/__tests__/injector-chain.test.ts +2 -0
  98. package/src/__tests__/injector-document-comments.test.ts +378 -0
  99. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  100. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  101. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  102. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  103. package/src/__tests__/list-messages-tool-merge.test.ts +77 -17
  104. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  105. package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
  106. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
  107. package/src/__tests__/llm-resolver.test.ts +161 -9
  108. package/src/__tests__/llm-usage-store.test.ts +66 -0
  109. package/src/__tests__/log-export-routes.test.ts +99 -2
  110. package/src/__tests__/logger.test.ts +89 -0
  111. package/src/__tests__/mcp-abort-signal.test.ts +2 -2
  112. package/src/__tests__/media-generate-image.test.ts +31 -0
  113. package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
  114. package/src/__tests__/message-queue-steer.test.ts +114 -0
  115. package/src/__tests__/model-intents.test.ts +2 -4
  116. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  117. package/src/__tests__/onboarding-template-contract.test.ts +1 -1
  118. package/src/__tests__/openai-provider.test.ts +151 -0
  119. package/src/__tests__/openai-responses-provider.test.ts +118 -16
  120. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  121. package/src/__tests__/pending-interactions-resolved-event.test.ts +189 -0
  122. package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
  123. package/src/__tests__/platform.test.ts +2 -5
  124. package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
  125. package/src/__tests__/plugin-bootstrap.test.ts +2 -2
  126. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  127. package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
  128. package/src/__tests__/plugin-types.test.ts +3 -2
  129. package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
  130. package/src/__tests__/pricing.test.ts +12 -0
  131. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  132. package/src/__tests__/process-message-display-content.test.ts +21 -16
  133. package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
  134. package/src/__tests__/registry.test.ts +2 -8
  135. package/src/__tests__/require-fresh-approval.test.ts +2 -2
  136. package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
  137. package/src/__tests__/server-history-render.test.ts +83 -4
  138. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
  139. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  140. package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
  141. package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
  142. package/src/__tests__/skill-tool-factory.test.ts +1 -1
  143. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  144. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  145. package/src/__tests__/suggestion-routes.test.ts +1 -0
  146. package/src/__tests__/sync-message-contract.test.ts +59 -0
  147. package/src/__tests__/system-prompt.test.ts +161 -124
  148. package/src/__tests__/terminal-tools.test.ts +12 -2
  149. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  150. package/src/__tests__/thread-backfill.test.ts +370 -22
  151. package/src/__tests__/tool-approval-handler.test.ts +1 -5
  152. package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
  153. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
  154. package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
  155. package/src/__tests__/tool-executor.test.ts +89 -53
  156. package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
  157. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  158. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  159. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
  160. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  161. package/src/__tests__/twilio-routes.test.ts +1 -1
  162. package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
  163. package/src/__tests__/usage-routes.test.ts +3 -0
  164. package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
  165. package/src/__tests__/web-fetch.test.ts +2 -2
  166. package/src/__tests__/workspace-git-service.test.ts +94 -10
  167. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  168. package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
  169. package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
  170. package/src/acp/prepare-agent-env.ts +78 -0
  171. package/src/acp/session-manager.ts +1 -1
  172. package/src/agent/attachments.ts +1 -0
  173. package/src/agent/loop.ts +65 -20
  174. package/src/api/README.md +5 -0
  175. package/src/api/index.ts +4 -0
  176. package/src/api/package.json +10 -0
  177. package/src/background-wake/background-wake-routes.test.ts +233 -0
  178. package/src/background-wake/next-wake.test.ts +289 -0
  179. package/src/background-wake/next-wake.ts +172 -0
  180. package/src/background-wake/runtime-registry.ts +24 -0
  181. package/src/browser/operations.ts +15 -0
  182. package/src/cli/commands/__tests__/browser.test.ts +23 -5
  183. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  184. package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
  185. package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
  186. package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
  187. package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
  188. package/src/cli/commands/__tests__/memory-v2.test.ts +10 -12
  189. package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
  190. package/src/cli/commands/browser.ts +247 -0
  191. package/src/cli/commands/conversations.ts +128 -1
  192. package/src/cli/commands/domain.ts +91 -41
  193. package/src/cli/commands/inference-providers.ts +147 -1
  194. package/src/cli/commands/inference.ts +93 -40
  195. package/src/cli/commands/memory-v2-compare-render.ts +115 -0
  196. package/src/cli/commands/memory-v2.ts +483 -0
  197. package/src/cli/commands/memory-v3-render.ts +344 -0
  198. package/src/cli/commands/memory-v3.ts +316 -0
  199. package/src/cli/commands/notifications.ts +24 -2
  200. package/src/cli/program.ts +2 -0
  201. package/src/cli/utils/conversation-id.ts +17 -5
  202. package/src/config/assistant-feature-flags.ts +21 -9
  203. package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
  204. package/src/config/bundled-skills/document-editor/SKILL.md +124 -0
  205. package/src/config/bundled-skills/document-editor/TOOLS.json +258 -0
  206. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  207. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  208. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  209. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  210. package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
  211. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  212. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  213. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  214. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  215. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
  216. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
  217. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
  218. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
  219. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
  220. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  221. package/src/config/bundled-tool-registry.ts +24 -12
  222. package/src/config/call-site-defaults.ts +20 -0
  223. package/src/config/feature-flag-registry.json +115 -3
  224. package/src/config/llm-resolver.ts +16 -2
  225. package/src/config/schemas/__tests__/memory-v2.test.ts +217 -1
  226. package/src/config/schemas/call-site-catalog.ts +35 -0
  227. package/src/config/schemas/llm.ts +14 -0
  228. package/src/config/schemas/memory-v2.ts +294 -1
  229. package/src/config/schemas/memory.ts +2 -1
  230. package/src/context/compactor.ts +60 -1
  231. package/src/context/token-estimator.ts +47 -4
  232. package/src/context/window-manager.ts +25 -0
  233. package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
  234. package/src/conversations/message-consolidation.ts +404 -0
  235. package/src/credential-health/credential-health-service.ts +34 -19
  236. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
  237. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  238. package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
  239. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  240. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  241. package/src/daemon/conversation-agent-loop-handlers.ts +155 -36
  242. package/src/daemon/conversation-agent-loop.ts +307 -88
  243. package/src/daemon/conversation-error.ts +31 -1
  244. package/src/daemon/conversation-lifecycle.ts +149 -118
  245. package/src/daemon/conversation-messaging.ts +3 -0
  246. package/src/daemon/conversation-process.ts +273 -0
  247. package/src/daemon/conversation-queue-manager.ts +14 -0
  248. package/src/daemon/conversation-runtime-assembly.ts +145 -84
  249. package/src/daemon/conversation-slash.ts +37 -5
  250. package/src/daemon/conversation-surfaces.ts +45 -2
  251. package/src/daemon/conversation-tool-setup.ts +70 -3
  252. package/src/daemon/conversation-usage.ts +2 -0
  253. package/src/daemon/conversation.ts +54 -32
  254. package/src/daemon/disk-pressure-guard.ts +14 -2
  255. package/src/daemon/first-greeting.ts +10 -0
  256. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  257. package/src/daemon/handlers/config-a2a.ts +160 -0
  258. package/src/daemon/handlers/config-model.test.ts +2 -0
  259. package/src/daemon/handlers/conversations.ts +90 -3
  260. package/src/daemon/handlers/shared.ts +92 -29
  261. package/src/daemon/host-bash-proxy.ts +1 -1
  262. package/src/daemon/host-browser-proxy.ts +5 -5
  263. package/src/daemon/host-cu-proxy.ts +5 -5
  264. package/src/daemon/host-file-proxy.ts +5 -5
  265. package/src/daemon/host-proxy-base.ts +4 -4
  266. package/src/daemon/host-transfer-proxy.ts +11 -11
  267. package/src/daemon/lifecycle.ts +40 -23
  268. package/src/daemon/meet-manifest-loader.ts +1 -7
  269. package/src/daemon/message-protocol.ts +4 -0
  270. package/src/daemon/message-types/conversations.ts +14 -9
  271. package/src/daemon/message-types/document-comments.ts +50 -0
  272. package/src/daemon/message-types/home.ts +1 -13
  273. package/src/daemon/message-types/messages.ts +66 -7
  274. package/src/daemon/message-types/surfaces.ts +3 -1
  275. package/src/daemon/message-types/sync.ts +14 -0
  276. package/src/daemon/message-types/web-activity.ts +57 -0
  277. package/src/daemon/plugin-source-watcher.ts +135 -3
  278. package/src/daemon/process-message.ts +69 -12
  279. package/src/daemon/shutdown-handlers.ts +24 -5
  280. package/src/daemon/switch-inference-profile-tool.ts +52 -0
  281. package/src/daemon/tool-setup-types.ts +13 -0
  282. package/src/daemon/trust-context.ts +6 -0
  283. package/src/documents/document-comments-store.test.ts +338 -0
  284. package/src/documents/document-comments-store.ts +237 -0
  285. package/src/documents/document-store.ts +202 -0
  286. package/src/events/relationship-state-updated.ts +25 -0
  287. package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -2
  288. package/src/heartbeat/heartbeat-service.ts +1 -0
  289. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  290. package/src/home/feed-types.ts +6 -1
  291. package/src/home/home-content-refresh.ts +52 -0
  292. package/src/home/home-greeting-cache.ts +69 -0
  293. package/src/home/home-greeting.ts +85 -0
  294. package/src/home/suggested-prompts.ts +168 -9
  295. package/src/ipc/gateway-flag-listener.ts +123 -0
  296. package/src/ipc/skill-routes/registries.ts +8 -12
  297. package/src/memory/__tests__/db-async-query.test.ts +165 -0
  298. package/src/memory/__tests__/db-maintenance.test.ts +115 -0
  299. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
  300. package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
  301. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  302. package/src/memory/__tests__/memory-retrospective-job.test.ts +327 -6
  303. package/src/memory/auto-analysis-enqueue.ts +5 -1
  304. package/src/memory/conversation-crud.ts +191 -100
  305. package/src/memory/conversation-starters-cadence.ts +3 -1
  306. package/src/memory/conversation-title-service.ts +19 -3
  307. package/src/memory/db-async-query.ts +214 -0
  308. package/src/memory/db-init.ts +26 -0
  309. package/src/memory/db-maintenance.ts +30 -21
  310. package/src/memory/delivery-crud.ts +41 -0
  311. package/src/memory/delivery-status.ts +141 -15
  312. package/src/memory/external-conversation-store.ts +32 -1
  313. package/src/memory/graph/bootstrap.ts +8 -1
  314. package/src/memory/graph/capability-seed.ts +7 -3
  315. package/src/memory/graph/conversation-graph-memory.ts +100 -17
  316. package/src/memory/graph/extraction.ts +1 -5
  317. package/src/memory/graph/graph-search.ts +7 -1
  318. package/src/memory/indexer.ts +28 -18
  319. package/src/memory/job-handlers/cleanup.ts +76 -18
  320. package/src/memory/job-handlers/conversation-starters.ts +1 -4
  321. package/src/memory/jobs/embed-pkb-file.ts +6 -1
  322. package/src/memory/jobs-store.ts +14 -0
  323. package/src/memory/jobs-worker.ts +68 -15
  324. package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
  325. package/src/memory/llm-request-log-source-local.ts +7 -0
  326. package/src/memory/llm-request-log-source.ts +9 -2
  327. package/src/memory/llm-request-log-store.ts +43 -1
  328. package/src/memory/llm-usage-store.ts +24 -0
  329. package/src/memory/memory-retrospective-constants.ts +28 -0
  330. package/src/memory/memory-retrospective-enqueue.ts +11 -3
  331. package/src/memory/memory-retrospective-job.ts +413 -18
  332. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  333. package/src/memory/memory-v2-activation-log-store.ts +41 -14
  334. package/src/memory/migrations/100-core-tables.ts +1 -0
  335. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  336. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  337. package/src/memory/migrations/253-document-comments.ts +47 -0
  338. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  339. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  340. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  341. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  342. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  343. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  344. package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
  345. package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
  346. package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
  347. package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
  348. package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
  349. package/src/memory/migrations/index.ts +34 -0
  350. package/src/memory/migrations/registry.ts +58 -0
  351. package/src/memory/onboarding-events-store.ts +7 -0
  352. package/src/memory/schema/calls.ts +1 -0
  353. package/src/memory/schema/conversations.ts +3 -0
  354. package/src/memory/schema/infrastructure.ts +22 -0
  355. package/src/memory/tool-usage-store.ts +36 -8
  356. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
  357. package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
  358. package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
  359. package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
  360. package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
  361. package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
  362. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  363. package/src/memory/v2/__tests__/injection.test.ts +158 -112
  364. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  365. package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
  366. package/src/memory/v2/__tests__/router.test.ts +660 -4
  367. package/src/memory/v2/consolidation-job.ts +14 -0
  368. package/src/memory/v2/harness/compare.ts +57 -0
  369. package/src/memory/v2/harness/metrics.ts +124 -0
  370. package/src/memory/v2/harness/oracle.ts +145 -0
  371. package/src/memory/v2/harness/replay-input.ts +224 -0
  372. package/src/memory/v2/harness/retriever.ts +74 -0
  373. package/src/memory/v2/harness/router-retriever.ts +43 -0
  374. package/src/memory/v2/harness/runner.ts +106 -0
  375. package/src/memory/v2/harness/trace.ts +58 -0
  376. package/src/memory/v2/injection-events.ts +101 -0
  377. package/src/memory/v2/injection.ts +42 -25
  378. package/src/memory/v2/page-index.ts +209 -7
  379. package/src/memory/v2/page-store.ts +18 -0
  380. package/src/memory/v2/prompts/router.ts +26 -1
  381. package/src/memory/v2/qdrant.ts +14 -2
  382. package/src/memory/v2/router.ts +369 -62
  383. package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
  384. package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
  385. package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
  386. package/src/memory/v3/__tests__/edges.test.ts +563 -0
  387. package/src/memory/v3/__tests__/filter.test.ts +512 -0
  388. package/src/memory/v3/__tests__/gate.test.ts +574 -0
  389. package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
  390. package/src/memory/v3/__tests__/loop.test.ts +530 -0
  391. package/src/memory/v3/__tests__/retriever.test.ts +226 -0
  392. package/src/memory/v3/__tests__/scouts.test.ts +440 -0
  393. package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
  394. package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
  395. package/src/memory/v3/__tests__/traversal.test.ts +469 -0
  396. package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
  397. package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
  398. package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
  399. package/src/memory/v3/__tests__/validate.test.ts +245 -0
  400. package/src/memory/v3/auto-edges.ts +223 -0
  401. package/src/memory/v3/coactivation-store.ts +124 -0
  402. package/src/memory/v3/consolidation-job.ts +323 -0
  403. package/src/memory/v3/edge-learning-job.ts +160 -0
  404. package/src/memory/v3/edges.ts +249 -0
  405. package/src/memory/v3/filter.ts +281 -0
  406. package/src/memory/v3/gate.ts +334 -0
  407. package/src/memory/v3/index-composition.ts +113 -0
  408. package/src/memory/v3/llm-capture.ts +46 -0
  409. package/src/memory/v3/loop.ts +382 -0
  410. package/src/memory/v3/maintenance.ts +144 -0
  411. package/src/memory/v3/prompt-context.ts +33 -0
  412. package/src/memory/v3/prompts/consolidation.ts +458 -0
  413. package/src/memory/v3/prompts/system-prompts.ts +196 -0
  414. package/src/memory/v3/retriever.ts +33 -0
  415. package/src/memory/v3/scouts.ts +420 -0
  416. package/src/memory/v3/shadow-middleware.ts +305 -0
  417. package/src/memory/v3/traversal.ts +206 -0
  418. package/src/memory/v3/tree-index.ts +237 -0
  419. package/src/memory/v3/tree-store.ts +394 -0
  420. package/src/memory/v3/tree-walk.ts +351 -0
  421. package/src/memory/v3/types.ts +65 -0
  422. package/src/memory/v3/validate.ts +300 -0
  423. package/src/messaging/providers/index.ts +7 -1
  424. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  425. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  426. package/src/messaging/providers/slack/adapter.ts +178 -25
  427. package/src/messaging/providers/slack/api.test.ts +54 -0
  428. package/src/messaging/providers/slack/api.ts +119 -3
  429. package/src/messaging/providers/slack/client.ts +12 -0
  430. package/src/messaging/providers/slack/deep-link.ts +20 -1
  431. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  432. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  433. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  434. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  435. package/src/messaging/providers/slack/send.test.ts +77 -0
  436. package/src/messaging/providers/slack/send.ts +8 -2
  437. package/src/messaging/providers/slack/types.ts +14 -0
  438. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
  439. package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
  440. package/src/notifications/adapters/macos.ts +18 -1
  441. package/src/notifications/adapters/platform.ts +1 -1
  442. package/src/notifications/conversation-seed-composer.ts +14 -2
  443. package/src/notifications/decision-engine.ts +1 -4
  444. package/src/notifications/deferred-emit.ts +135 -0
  445. package/src/notifications/emit-signal.ts +38 -50
  446. package/src/notifications/home-feed-side-effect.ts +60 -30
  447. package/src/oauth/connect-orchestrator.ts +3 -0
  448. package/src/oauth/credential-token-resolver.ts +2 -0
  449. package/src/oauth/manual-token-connection.ts +19 -0
  450. package/src/oauth/oauth-store.ts +12 -0
  451. package/src/oauth/seed-providers.ts +22 -0
  452. package/src/permissions/prompter.ts +8 -5
  453. package/src/permissions/question-prompter.ts +5 -2
  454. package/src/permissions/secret-prompter.ts +6 -3
  455. package/src/plugin-api/index.ts +4 -0
  456. package/src/plugin-api/types.ts +7 -33
  457. package/src/plugins/defaults/index.ts +6 -0
  458. package/src/plugins/defaults/injectors.ts +100 -20
  459. package/src/plugins/external-plugin-loader.ts +5 -68
  460. package/src/plugins/types.ts +11 -16
  461. package/src/proactive-artifact/aux-message-injector.ts +17 -4
  462. package/src/prompts/__tests__/system-prompt.test.ts +46 -2
  463. package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
  464. package/src/prompts/normalize-onboarding.ts +40 -0
  465. package/src/prompts/persona-resolver.ts +36 -21
  466. package/src/prompts/sections.ts +69 -19
  467. package/src/prompts/system-prompt.ts +118 -216
  468. package/src/prompts/template-detection.ts +37 -0
  469. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  470. package/src/prompts/templates/BOOTSTRAP.md +10 -2
  471. package/src/prompts/templates/VOICE.md +3 -0
  472. package/src/prompts/templates/system-sections.ts +281 -9
  473. package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
  474. package/src/providers/__tests__/retry-callsite.test.ts +85 -5
  475. package/src/providers/anthropic/client.ts +159 -66
  476. package/src/providers/call-site-routing.ts +14 -2
  477. package/src/providers/connection-model-compat.ts +38 -0
  478. package/src/providers/connection-resolution.ts +16 -2
  479. package/src/providers/fireworks/client.ts +20 -2
  480. package/src/providers/gemini/client.ts +49 -6
  481. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  482. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  483. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  484. package/src/providers/inference/adapter-factory.ts +18 -1
  485. package/src/providers/inference/auth.ts +3 -3
  486. package/src/providers/inference/codex-token-refresh.ts +128 -0
  487. package/src/providers/inference/resolve-auth.ts +49 -6
  488. package/src/providers/minimax/client.ts +106 -0
  489. package/src/providers/model-catalog.ts +91 -1
  490. package/src/providers/model-intents.ts +1 -1
  491. package/src/providers/openai/chat-completions-provider.ts +63 -23
  492. package/src/providers/openai/codex-models.ts +18 -0
  493. package/src/providers/openai/responses-provider.ts +86 -23
  494. package/src/providers/openrouter/client.ts +5 -1
  495. package/src/providers/provider-send-message.ts +7 -1
  496. package/src/providers/retry.ts +34 -3
  497. package/src/providers/thinking-config.ts +26 -1
  498. package/src/providers/types.ts +25 -0
  499. package/src/providers/usage-tracking.ts +2 -0
  500. package/src/runtime/AGENTS.md +2 -2
  501. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  502. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  503. package/src/runtime/agent-wake.ts +152 -56
  504. package/src/runtime/assistant-event-hub.ts +76 -6
  505. package/src/runtime/auth/route-policy.ts +43 -3
  506. package/src/runtime/background-job-runner.ts +26 -0
  507. package/src/runtime/btw-sidechain.ts +0 -6
  508. package/src/runtime/channel-reply-delivery.ts +182 -47
  509. package/src/runtime/channel-retry-sweep.ts +141 -16
  510. package/src/runtime/http-types.ts +7 -6
  511. package/src/runtime/migrations/vbundle-builder.ts +10 -3
  512. package/src/runtime/pending-interactions.ts +50 -8
  513. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  514. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +161 -1
  515. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  516. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +290 -0
  517. package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
  518. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  519. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  520. package/src/runtime/routes/acp-routes.test.ts +255 -6
  521. package/src/runtime/routes/acp-routes.ts +8 -1
  522. package/src/runtime/routes/approval-routes.ts +4 -1
  523. package/src/runtime/routes/avatar-routes.ts +10 -10
  524. package/src/runtime/routes/background-wake-routes.ts +188 -0
  525. package/src/runtime/routes/browser-tabs-routes.ts +200 -0
  526. package/src/runtime/routes/btw-routes.ts +0 -6
  527. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  528. package/src/runtime/routes/content-source-routes.ts +78 -0
  529. package/src/runtime/routes/conversation-cli-routes.ts +147 -2
  530. package/src/runtime/routes/conversation-list-routes.ts +12 -4
  531. package/src/runtime/routes/conversation-management-routes.ts +77 -20
  532. package/src/runtime/routes/conversation-query-routes.ts +196 -31
  533. package/src/runtime/routes/conversation-routes.ts +472 -425
  534. package/src/runtime/routes/conversation-starter-routes.ts +6 -3
  535. package/src/runtime/routes/disk-pressure-routes.ts +1 -1
  536. package/src/runtime/routes/document-comments-routes.ts +287 -0
  537. package/src/runtime/routes/documents-routes.ts +33 -0
  538. package/src/runtime/routes/domain-routes.ts +60 -10
  539. package/src/runtime/routes/email-routes.ts +5 -2
  540. package/src/runtime/routes/events-routes.ts +54 -10
  541. package/src/runtime/routes/group-routes.ts +24 -8
  542. package/src/runtime/routes/home-feed-routes.ts +6 -3
  543. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  544. package/src/runtime/routes/host-browser-routes.ts +17 -2
  545. package/src/runtime/routes/host-cu-routes.ts +2 -2
  546. package/src/runtime/routes/identity-routes.ts +21 -0
  547. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  548. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
  549. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  550. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  551. package/src/runtime/routes/index.ts +20 -4
  552. package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
  553. package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
  554. package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
  555. package/src/runtime/routes/integrations/a2a.ts +60 -1
  556. package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
  557. package/src/runtime/routes/log-export-routes.ts +39 -0
  558. package/src/runtime/routes/memory-item-routes.ts +8 -3
  559. package/src/runtime/routes/memory-v2-routes.ts +427 -0
  560. package/src/runtime/routes/memory-v3-routes.ts +316 -0
  561. package/src/runtime/routes/migration-routes.ts +21 -24
  562. package/src/runtime/routes/notification-routes.ts +19 -2
  563. package/src/runtime/routes/plugins-routes.ts +337 -0
  564. package/src/runtime/routes/question-routes.ts +4 -1
  565. package/src/runtime/routes/rename-conversation-routes.ts +6 -2
  566. package/src/runtime/routes/sanity-routes.ts +159 -0
  567. package/src/runtime/routes/secret-routes.ts +25 -5
  568. package/src/runtime/routes/settings-routes.ts +12 -11
  569. package/src/runtime/routes/slack-channel-routes.ts +188 -0
  570. package/src/runtime/routes/workspace-routes.ts +25 -10
  571. package/src/runtime/services/conversation-serializer.ts +30 -4
  572. package/src/runtime/sync/resource-sync-events.ts +106 -38
  573. package/src/runtime/sync/sync-publisher.test.ts +49 -0
  574. package/src/runtime/sync/sync-publisher.ts +2 -1
  575. package/src/runtime/verification-outbound-actions.ts +73 -1
  576. package/src/schedule/integration-status.ts +3 -1
  577. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  578. package/src/security/oauth2-device-code.ts +307 -0
  579. package/src/security/oauth2.ts +26 -9
  580. package/src/security/secure-keys.ts +5 -0
  581. package/src/skills/catalog-install.ts +6 -2
  582. package/src/telemetry/types.ts +12 -0
  583. package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
  584. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  585. package/src/tools/acp/spawn.test.ts +119 -0
  586. package/src/tools/acp/spawn.ts +15 -2
  587. package/src/tools/apps/definitions.ts +2 -8
  588. package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
  589. package/src/tools/ask-question/ask-question-tool.ts +38 -45
  590. package/src/tools/browser/__tests__/pinned-tabs.test.ts +150 -0
  591. package/src/tools/browser/browser-execution.ts +106 -0
  592. package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
  593. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  594. package/src/tools/browser/cdp-client/__tests__/types.test.ts +4 -0
  595. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +22 -0
  596. package/src/tools/browser/cdp-client/extension-cdp-client.ts +42 -2
  597. package/src/tools/browser/cdp-client/factory.ts +171 -4
  598. package/src/tools/browser/cdp-client/local-cdp-client.ts +21 -0
  599. package/src/tools/browser/cdp-client/types.ts +101 -0
  600. package/src/tools/browser/pinned-tabs.ts +146 -0
  601. package/src/tools/computer-use/definitions.ts +22 -78
  602. package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
  603. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
  604. package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
  605. package/src/tools/credentials/vault.ts +3 -9
  606. package/src/tools/document/document-comment-tool.test.ts +379 -0
  607. package/src/tools/document/document-comment-tool.ts +156 -0
  608. package/src/tools/document/document-tool.ts +187 -2
  609. package/src/tools/execution-target.ts +21 -23
  610. package/src/tools/executor.ts +6 -1
  611. package/src/tools/filesystem/edit.ts +3 -9
  612. package/src/tools/filesystem/list.ts +3 -9
  613. package/src/tools/filesystem/read.ts +3 -9
  614. package/src/tools/filesystem/write.ts +3 -9
  615. package/src/tools/host-filesystem/edit.ts +3 -9
  616. package/src/tools/host-filesystem/read.ts +3 -9
  617. package/src/tools/host-filesystem/transfer.ts +3 -9
  618. package/src/tools/host-filesystem/write.ts +3 -9
  619. package/src/tools/host-terminal/host-shell.ts +3 -9
  620. package/src/tools/mcp/mcp-tool-factory.ts +1 -8
  621. package/src/tools/memory/register.test.ts +1 -1
  622. package/src/tools/memory/register.ts +4 -9
  623. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  624. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  625. package/src/tools/network/domain-normalize.ts +17 -0
  626. package/src/tools/network/web-fetch.ts +216 -73
  627. package/src/tools/network/web-search.ts +216 -98
  628. package/src/tools/registry.ts +7 -23
  629. package/src/tools/schema-transforms.ts +1 -1
  630. package/src/tools/skills/execute.ts +3 -9
  631. package/src/tools/skills/load.ts +3 -9
  632. package/src/tools/skills/skill-tool-factory.ts +1 -8
  633. package/src/tools/subagent/notify-parent.ts +3 -9
  634. package/src/tools/system/request-permission.ts +3 -9
  635. package/src/tools/terminal/safe-env.ts +3 -2
  636. package/src/tools/terminal/shell.ts +3 -9
  637. package/src/tools/tool-approval-handler.ts +19 -12
  638. package/src/tools/tool-defaults.ts +94 -0
  639. package/src/tools/types.ts +31 -98
  640. package/src/tools/ui-surface/definitions.ts +9 -23
  641. package/src/types/onboarding-context.ts +4 -0
  642. package/src/usage/pricing.ts +23 -0
  643. package/src/usage/types.ts +12 -0
  644. package/src/util/__tests__/favicon.test.ts +84 -0
  645. package/src/util/favicon.ts +40 -0
  646. package/src/util/logger.ts +16 -7
  647. package/src/util/platform.ts +7 -7
  648. package/src/util/sqlite3-runtime.ts +65 -0
  649. package/src/workspace/git-service.ts +75 -4
  650. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
  651. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  652. package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
  653. package/src/workspace/migrations/registry.ts +4 -0
  654. package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
  655. package/src/__tests__/message-complete-display-id.test.ts +0 -175
  656. package/src/config/bundled-skills/document/SKILL.md +0 -54
  657. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  658. package/src/daemon/seed-files.ts +0 -18
  659. package/src/prompts/cache-boundary.ts +0 -8
  660. package/src/runtime/routes/interface-routes.ts +0 -43
  661. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  662. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  663. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  664. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  665. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Verifies that every resolution path through
3
+ * `runtime/pending-interactions.ts` publishes an `interaction_resolved`
4
+ * envelope on the event hub with the right state.
5
+ *
6
+ * Each test registers an interaction directly and calls `resolve()` or
7
+ * `removeByConversation()` so we exercise the tracker in isolation
8
+ * without spinning up a Conversation, prompter, or proxy.
9
+ */
10
+
11
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
12
+
13
+ import type { ServerMessage } from "../daemon/message-protocol.js";
14
+
15
+ // Capture every broadcast emitted by the tracker. The real hub is replaced
16
+ // with a thin recorder so we can assert payloads deterministically.
17
+ const publishedMessages: ServerMessage[] = [];
18
+
19
+ mock.module("../runtime/assistant-event-hub.js", () => ({
20
+ broadcastMessage: (msg: ServerMessage) => {
21
+ publishedMessages.push(msg);
22
+ },
23
+ capabilityForMessageType: () => undefined,
24
+ assistantEventHub: {
25
+ publish: async () => {},
26
+ subscribe: () => ({ dispose: () => {}, active: true }),
27
+ },
28
+ }));
29
+
30
+ const pendingInteractions = await import("../runtime/pending-interactions.js");
31
+
32
+ beforeEach(() => {
33
+ publishedMessages.length = 0;
34
+ pendingInteractions.clear();
35
+ });
36
+
37
+ afterEach(() => {
38
+ pendingInteractions.clear();
39
+ });
40
+
41
+ function lastResolvedEvent() {
42
+ const evt = publishedMessages.find((m) => m.type === "interaction_resolved");
43
+ expect(evt).toBeDefined();
44
+ return evt as Extract<ServerMessage, { type: "interaction_resolved" }>;
45
+ }
46
+
47
+ describe("pendingInteractions.resolve emits interaction_resolved", () => {
48
+ test("default state is 'cancelled'", () => {
49
+ pendingInteractions.register("req-1", {
50
+ conversationId: "conv-1",
51
+ kind: "confirmation",
52
+ });
53
+ const returned = pendingInteractions.resolve("req-1");
54
+ expect(returned).toBeDefined();
55
+ const evt = lastResolvedEvent();
56
+ expect(evt.requestId).toBe("req-1");
57
+ expect(evt.conversationId).toBe("conv-1");
58
+ expect(evt.state).toBe("cancelled");
59
+ expect(evt.kind).toBe("confirmation");
60
+ });
61
+
62
+ test("approved state propagates", () => {
63
+ pendingInteractions.register("req-approve", {
64
+ conversationId: "conv-a",
65
+ kind: "confirmation",
66
+ });
67
+ pendingInteractions.resolve("req-approve", "approved");
68
+ expect(lastResolvedEvent().state).toBe("approved");
69
+ });
70
+
71
+ test("rejected state propagates", () => {
72
+ pendingInteractions.register("req-reject", {
73
+ conversationId: "conv-b",
74
+ kind: "confirmation",
75
+ });
76
+ pendingInteractions.resolve("req-reject", "rejected");
77
+ expect(lastResolvedEvent().state).toBe("rejected");
78
+ });
79
+
80
+ test("answered state propagates (secret response)", () => {
81
+ pendingInteractions.register("req-secret", {
82
+ conversationId: "conv-c",
83
+ kind: "secret",
84
+ });
85
+ pendingInteractions.resolve("req-secret", "answered");
86
+ const evt = lastResolvedEvent();
87
+ expect(evt.state).toBe("answered");
88
+ expect(evt.kind).toBe("secret");
89
+ });
90
+
91
+ test("superseded state propagates", () => {
92
+ pendingInteractions.register("req-super", {
93
+ conversationId: "conv-d",
94
+ kind: "confirmation",
95
+ });
96
+ pendingInteractions.resolve("req-super", "superseded");
97
+ expect(lastResolvedEvent().state).toBe("superseded");
98
+ });
99
+
100
+ test("no event is emitted when the requestId is unknown", () => {
101
+ pendingInteractions.resolve("never-registered", "approved");
102
+ expect(publishedMessages).toHaveLength(0);
103
+ });
104
+
105
+ test("a single resolve emits exactly one event", () => {
106
+ pendingInteractions.register("req-once", {
107
+ conversationId: "conv-e",
108
+ kind: "host_bash",
109
+ });
110
+ pendingInteractions.resolve("req-once", "answered");
111
+ // Second resolve is a no-op because the entry was already consumed.
112
+ pendingInteractions.resolve("req-once", "answered");
113
+ const events = publishedMessages.filter(
114
+ (m) => m.type === "interaction_resolved",
115
+ );
116
+ expect(events).toHaveLength(1);
117
+ });
118
+
119
+ test("clears the registered timer on resolve", () => {
120
+ let fired = false;
121
+ const timer = setTimeout(() => {
122
+ fired = true;
123
+ }, 10_000);
124
+ pendingInteractions.register("req-timer", {
125
+ conversationId: "conv-f",
126
+ kind: "confirmation",
127
+ timer,
128
+ });
129
+ pendingInteractions.resolve("req-timer", "approved");
130
+ clearTimeout(timer);
131
+ expect(fired).toBe(false);
132
+ });
133
+ });
134
+
135
+ describe("removeByConversation emits interaction_resolved per entry", () => {
136
+ test("emits superseded for every non-host interaction in the conversation", () => {
137
+ pendingInteractions.register("conf-1", {
138
+ conversationId: "conv-x",
139
+ kind: "confirmation",
140
+ });
141
+ pendingInteractions.register("secret-1", {
142
+ conversationId: "conv-x",
143
+ kind: "secret",
144
+ });
145
+ pendingInteractions.register("question-1", {
146
+ conversationId: "conv-x",
147
+ kind: "question",
148
+ });
149
+ pendingInteractions.register("host-bash-1", {
150
+ conversationId: "conv-x",
151
+ kind: "host_bash",
152
+ });
153
+ pendingInteractions.register("conf-other", {
154
+ conversationId: "conv-y",
155
+ kind: "confirmation",
156
+ });
157
+
158
+ pendingInteractions.removeByConversation("conv-x");
159
+
160
+ const events = publishedMessages.filter(
161
+ (m) => m.type === "interaction_resolved",
162
+ ) as Extract<ServerMessage, { type: "interaction_resolved" }>[];
163
+ expect(events).toHaveLength(3);
164
+ expect(events.every((e) => e.state === "superseded")).toBe(true);
165
+ const requestIds = new Set(events.map((e) => e.requestId));
166
+ expect(requestIds).toEqual(new Set(["conf-1", "secret-1", "question-1"]));
167
+
168
+ // host_bash entries survive auto-deny — no event for them.
169
+ expect(pendingInteractions.get("host-bash-1")).toBeDefined();
170
+ // Unrelated conversation is untouched.
171
+ expect(pendingInteractions.get("conf-other")).toBeDefined();
172
+ });
173
+
174
+ test("explicit state arg overrides the default 'superseded'", () => {
175
+ pendingInteractions.register("conf-2", {
176
+ conversationId: "conv-z",
177
+ kind: "confirmation",
178
+ });
179
+ pendingInteractions.removeByConversation("conv-z", "cancelled");
180
+ const events = publishedMessages.filter(
181
+ (m) => m.type === "interaction_resolved",
182
+ );
183
+ expect(events).toHaveLength(1);
184
+ expect(
185
+ (events[0] as Extract<ServerMessage, { type: "interaction_resolved" }>)
186
+ .state,
187
+ ).toBe("cancelled");
188
+ });
189
+ });
@@ -100,7 +100,7 @@ mock.module("../permissions/checker.js", () => ({
100
100
  mock.module("../memory/tool-usage-store.js", () => ({
101
101
  recordToolInvocation: () => {},
102
102
  getRecentInvocations: () => [],
103
- rotateToolInvocations: () => 0,
103
+ rotateToolInvocations: async () => 0,
104
104
  }));
105
105
 
106
106
  mock.module("../tools/registry.js", () => ({
@@ -111,7 +111,7 @@ mock.module("../tools/registry.js", () => ({
111
111
  description: "test tool",
112
112
  category: "shell",
113
113
  defaultRiskLevel: "medium",
114
- getDefinition: () => ({}),
114
+ input_schema: {},
115
115
  execute: async () => fakeToolResult,
116
116
  };
117
117
  },
@@ -8,8 +8,7 @@ import {
8
8
  getDataDir,
9
9
  getDbPath,
10
10
  getHistoryPath,
11
- getInterfacesDir,
12
- getLogPath,
11
+ getLogsDir,
13
12
  getPidPath,
14
13
  getSandboxRootDir,
15
14
  getSandboxWorkingDir,
@@ -65,9 +64,8 @@ describe("path characterization", () => {
65
64
 
66
65
  // Sub-paths under workspace/data
67
66
  expect(getDbPath()).toBe(join(data, "db", "assistant.db"));
68
- expect(getLogPath()).toBe(join(data, "logs", "vellum.log"));
67
+ expect(getLogsDir()).toBe(join(data, "logs"));
69
68
  expect(getHistoryPath()).toBe(join(data, "history"));
70
- expect(getInterfacesDir()).toBe(join(data, "interfaces"));
71
69
  expect(getSandboxRootDir()).toBe(join(data, "sandbox"));
72
70
  expect(getSandboxWorkingDir()).toBe(ws);
73
71
 
@@ -117,7 +115,6 @@ describe("path characterization", () => {
117
115
  expect(existsSync(join(data, "memory"))).toBe(true);
118
116
  expect(existsSync(join(data, "memory", "knowledge"))).toBe(true);
119
117
  expect(existsSync(join(data, "apps"))).toBe(true);
120
- expect(existsSync(join(data, "interfaces"))).toBe(true);
121
118
 
122
119
  rmSync(wsDir, { recursive: true, force: true });
123
120
  });
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Shape tests for the public `ToolDefinition` author-facing tool spec.
3
+ *
4
+ * These tests don't exercise runtime behavior — they assert via
5
+ * `satisfies` that representative tool literals line up with the public
6
+ * interface. If a later PR breaks a field name or signature in
7
+ * `assistant/src/plugin-api/types.ts`, this file fails to type-check and
8
+ * the regression is caught at `tsc --noEmit` / `bun test` time.
9
+ */
10
+
11
+ import { describe, expect, test } from "bun:test";
12
+
13
+ import {
14
+ RiskLevel,
15
+ type ToolContext,
16
+ type ToolDefinition,
17
+ type ToolExecutionResult,
18
+ } from "../plugin-api/index.js";
19
+
20
+ describe("ToolDefinition (public author-facing tool spec) ", () => {
21
+ test("a fully-populated literal satisfies the interface", () => {
22
+ const tool = {
23
+ description: "Greet the model in a fixed language.",
24
+ defaultRiskLevel: RiskLevel.Low,
25
+ input_schema: {
26
+ type: "object",
27
+ properties: {
28
+ language: { type: "string" },
29
+ },
30
+ required: ["language"],
31
+ additionalProperties: false,
32
+ },
33
+ async execute(
34
+ input: Record<string, unknown>,
35
+ _ctx: ToolContext,
36
+ ): Promise<ToolExecutionResult> {
37
+ return {
38
+ content: `hello, ${String(input.language)} speaker`,
39
+ isError: false,
40
+ };
41
+ },
42
+ } as const satisfies ToolDefinition;
43
+
44
+ // `as const` propagates literal types and verifies type compatibility,
45
+ // but the runtime expectations below also smoke-check the structure
46
+ // for anyone reading the test without TS folded in.
47
+ expect(typeof tool.execute).toBe("function");
48
+ expect(tool.defaultRiskLevel).toBe(RiskLevel.Low);
49
+ });
50
+
51
+ test("every field is optional — empty literal satisfies the interface", () => {
52
+ const tool: ToolDefinition = {};
53
+ expect(tool).toEqual({});
54
+ });
55
+
56
+ test("every author-facing risk level is permitted", () => {
57
+ const low: ToolDefinition = { defaultRiskLevel: RiskLevel.Low };
58
+ const medium: ToolDefinition = { defaultRiskLevel: RiskLevel.Medium };
59
+ const high: ToolDefinition = { defaultRiskLevel: RiskLevel.High };
60
+
61
+ expect(low.defaultRiskLevel).toBe(RiskLevel.Low);
62
+ expect(medium.defaultRiskLevel).toBe(RiskLevel.Medium);
63
+ expect(high.defaultRiskLevel).toBe(RiskLevel.High);
64
+ });
65
+
66
+ test("execute receives the public ToolContext", async () => {
67
+ // Type-only assertion: the execute signature uses the public
68
+ // ToolContext (narrow base). A daemon-internal field added to the
69
+ // rich ToolContext that doesn't exist on the narrow one must not be
70
+ // accessible here. We can't test this at runtime — the assertion
71
+ // lives in `tsc --noEmit` over this file.
72
+ const tool: ToolDefinition = {
73
+ async execute(_input, ctx) {
74
+ // `ctx` is the public `ToolContext`. Touch
75
+ // commonly-needed fields to make sure they're present.
76
+ const _conversationId = ctx.conversationId;
77
+ const _workingDir = ctx.workingDir;
78
+ const _signal = ctx.signal;
79
+ return { content: "ok", isError: false };
80
+ },
81
+ };
82
+ const result = await tool.execute?.(
83
+ {},
84
+ {
85
+ conversationId: "conv-abc",
86
+ workingDir: "/tmp",
87
+ signal: new AbortController().signal,
88
+ } as ToolContext,
89
+ );
90
+ expect(result?.isError).toBe(false);
91
+ });
92
+ });
@@ -375,6 +375,7 @@ describe("plugin bootstrap", () => {
375
375
  name: "gated-off-tool",
376
376
  description: "should not be registered",
377
377
  defaultRiskLevel: RiskLevel.Low,
378
+ executionTarget: "sandbox",
378
379
  input_schema: { type: "object", properties: {}, required: [] },
379
380
  execute: async () => ({ content: "nope", isError: false }),
380
381
  },
@@ -421,8 +422,7 @@ describe("plugin bootstrap", () => {
421
422
  });
422
423
 
423
424
  test("requiresFlag absent: plugin activates unconditionally", async () => {
424
- // Deliberately do not set any overrides — the resolver defaults
425
- // undeclared keys to `true`, but more importantly a plugin with no
425
+ // Deliberately do not set any overrides — a plugin with no
426
426
  // `requiresFlag` key must not consult the resolver at all.
427
427
  let initFired = false;
428
428
  const plugin = buildPlugin("no-flag", {
@@ -0,0 +1,302 @@
1
+ /**
2
+ * Tests for PluginSourceWatcher — filesystem watcher that detects plugin
3
+ * directory changes and triggers debounced reregistration.
4
+ *
5
+ * Key regression: the watcher must survive (and recover from) the Linux/Bun
6
+ * recursive-watch limitation where subdirectories created after watch starts
7
+ * are silently dropped. We test that close→reopen + rescan catches these.
8
+ */
9
+
10
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Mocks — must be set up before importing the module under test
14
+ // ---------------------------------------------------------------------------
15
+
16
+ const TEST_PLUGINS_DIR = "/tmp/test-plugins";
17
+
18
+ let capturedWatchCallback:
19
+ | ((eventType: string, filename: string | null) => void)
20
+ | null = null;
21
+ let mockWatchShouldThrow = false;
22
+ const mockWatcher = { close: mock(() => {}) };
23
+
24
+ const mockWatch = mock(
25
+ (
26
+ _path: string,
27
+ _opts: Record<string, unknown>,
28
+ callback: (eventType: string, filename: string | null) => void,
29
+ ) => {
30
+ if (mockWatchShouldThrow) throw new Error("watch failed");
31
+ capturedWatchCallback = callback;
32
+ return mockWatcher;
33
+ },
34
+ );
35
+
36
+ const mockRereadirSync = mock((_path: string): string[] => []);
37
+
38
+ let mockGetRegisteredPluginImpl: (name: string) => unknown | undefined = (
39
+ _name,
40
+ ) => undefined;
41
+ const mockGetRegisteredPlugin = mock((name: string) =>
42
+ mockGetRegisteredPluginImpl(name),
43
+ );
44
+
45
+ let mockReregisterExternalPluginImpl = mock(async (_name: string) => {});
46
+ const mockReregisterExternalPlugin = mock(async (name: string) =>
47
+ mockReregisterExternalPluginImpl(name),
48
+ );
49
+
50
+ mock.module("node:fs", () => {
51
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
52
+ const actualFs = require("node:fs");
53
+ return {
54
+ ...actualFs,
55
+ watch: mockWatch,
56
+ readdirSync: mockRereadirSync,
57
+ };
58
+ });
59
+
60
+ mock.module("../plugins/registry.js", () => ({
61
+ getRegisteredPlugin: mockGetRegisteredPlugin,
62
+ }));
63
+
64
+ mock.module("../util/platform.js", () => ({
65
+ getWorkspacePluginsDir: mock(() => TEST_PLUGINS_DIR),
66
+ }));
67
+
68
+ mock.module("../daemon/external-plugins-bootstrap.js", () => ({
69
+ reregisterExternalPlugin: mockReregisterExternalPlugin,
70
+ }));
71
+
72
+ mock.module("../util/logger.js", () => ({
73
+ getLogger: mock(() => ({
74
+ info: mock(() => {}),
75
+ warn: mock(() => {}),
76
+ })),
77
+ }));
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Import after mocks
81
+ // ---------------------------------------------------------------------------
82
+
83
+ import { PluginSourceWatcher } from "../daemon/plugin-source-watcher.js";
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // Tests
87
+ // ---------------------------------------------------------------------------
88
+
89
+ describe("PluginSourceWatcher", () => {
90
+ beforeEach(() => {
91
+ PluginSourceWatcher.resetForTests();
92
+ capturedWatchCallback = null;
93
+ mockWatchShouldThrow = false;
94
+ mockWatcher.close.mockClear();
95
+ mockWatch.mockClear();
96
+ mockRereadirSync.mockClear();
97
+ mockGetRegisteredPlugin.mockClear();
98
+ mockReregisterExternalPlugin.mockClear();
99
+ mockGetRegisteredPluginImpl = (_name: string) => undefined;
100
+ mockReregisterExternalPluginImpl = mock(async (_name: string) => {});
101
+ });
102
+
103
+ afterEach(() => {
104
+ const watcher = PluginSourceWatcher.getInstance();
105
+ watcher.stop();
106
+ });
107
+
108
+ test("start() creates a recursive watcher on the plugins directory", () => {
109
+ const watcher = PluginSourceWatcher.getInstance();
110
+ watcher.start();
111
+ expect(capturedWatchCallback).not.toBeNull();
112
+ });
113
+
114
+ test("plugin directory creation triggers rebuild", async () => {
115
+ const watcher = PluginSourceWatcher.getInstance();
116
+ watcher.start();
117
+
118
+ capturedWatchCallback!("change", "my-plugin");
119
+
120
+ // Wait for debounce
121
+ await new Promise((r) => setTimeout(r, 600));
122
+ expect(mockReregisterExternalPlugin).toHaveBeenCalledWith("my-plugin");
123
+ });
124
+
125
+ test("nested file change within plugin triggers rebuild", async () => {
126
+ const watcher = PluginSourceWatcher.getInstance();
127
+ watcher.start();
128
+
129
+ capturedWatchCallback!("change", "my-plugin/src/index.ts");
130
+
131
+ await new Promise((r) => setTimeout(r, 600));
132
+ expect(mockReregisterExternalPlugin).toHaveBeenCalledWith("my-plugin");
133
+ });
134
+
135
+ test("deeply nested file change triggers rebuild", async () => {
136
+ const watcher = PluginSourceWatcher.getInstance();
137
+ watcher.start();
138
+
139
+ capturedWatchCallback!("change", "my-plugin/src/handlers/util/helper.ts");
140
+
141
+ await new Promise((r) => setTimeout(r, 600));
142
+ expect(mockReregisterExternalPlugin).toHaveBeenCalledWith("my-plugin");
143
+ });
144
+
145
+ test("dotfiles in plugins root are ignored", async () => {
146
+ const watcher = PluginSourceWatcher.getInstance();
147
+ watcher.start();
148
+
149
+ capturedWatchCallback!("change", ".DS_Store");
150
+
151
+ await new Promise((r) => setTimeout(r, 600));
152
+ expect(mockReregisterExternalPlugin).not.toHaveBeenCalled();
153
+ });
154
+
155
+ test("null filename is ignored", async () => {
156
+ const watcher = PluginSourceWatcher.getInstance();
157
+ watcher.start();
158
+
159
+ capturedWatchCallback!("change", null);
160
+
161
+ await new Promise((r) => setTimeout(r, 600));
162
+ expect(mockReregisterExternalPlugin).not.toHaveBeenCalled();
163
+ });
164
+
165
+ test("rapid changes to same plugin are debounced into single rebuild", async () => {
166
+ const watcher = PluginSourceWatcher.getInstance();
167
+ watcher.start();
168
+
169
+ capturedWatchCallback!("change", "my-plugin/src/index.ts");
170
+ capturedWatchCallback!("change", "my-plugin/src/handlers.ts");
171
+ capturedWatchCallback!("change", "my-plugin/package.json");
172
+
173
+ await new Promise((r) => setTimeout(r, 600));
174
+ expect(mockReregisterExternalPlugin).toHaveBeenCalledTimes(1);
175
+ expect(mockReregisterExternalPlugin).toHaveBeenCalledWith("my-plugin");
176
+ });
177
+
178
+ test("changes to different plugins trigger separate rebuilds (debounced)", async () => {
179
+ const watcher = PluginSourceWatcher.getInstance();
180
+ watcher.start();
181
+
182
+ capturedWatchCallback!("change", "plugin-a/src/index.ts");
183
+ capturedWatchCallback!("change", "plugin-b/src/index.ts");
184
+
185
+ await new Promise((r) => setTimeout(r, 600));
186
+ expect(mockReregisterExternalPlugin).toHaveBeenCalledTimes(2);
187
+ expect(mockReregisterExternalPlugin).toHaveBeenNthCalledWith(1, "plugin-a");
188
+ expect(mockReregisterExternalPlugin).toHaveBeenNthCalledWith(2, "plugin-b");
189
+ });
190
+
191
+ test("stop() closes watcher and cancels pending rebuilds", () => {
192
+ const watcher = PluginSourceWatcher.getInstance();
193
+ watcher.start();
194
+
195
+ capturedWatchCallback!("change", "my-plugin/src/index.ts");
196
+ watcher.stop();
197
+
198
+ expect(mockWatcher.close).toHaveBeenCalledTimes(1);
199
+ // No rebuild should fire after stop
200
+ expect(mockReregisterExternalPlugin).not.toHaveBeenCalled();
201
+ });
202
+
203
+ test("ensureStarted() initializes watcher after start() if watch coverage was lost", () => {
204
+ const watcher = PluginSourceWatcher.getInstance();
205
+ watcher.start();
206
+ expect(capturedWatchCallback).not.toBeNull();
207
+
208
+ // Simulate lost coverage while the daemon lifecycle is still started
209
+ // (e.g. a previous watch attempt failed after startup).
210
+ (watcher as unknown as { watcher: unknown }).watcher = null;
211
+ capturedWatchCallback = null;
212
+
213
+ watcher.ensureStarted();
214
+ expect(capturedWatchCallback).not.toBeNull();
215
+ });
216
+
217
+ test("ensureStarted() is a no-op when watcher is already running", () => {
218
+ const watcher = PluginSourceWatcher.getInstance();
219
+ watcher.start();
220
+ const callCountAfterStart = mockWatch.mock.calls.length;
221
+
222
+ watcher.ensureStarted();
223
+ expect(mockWatch.mock.calls.length).toBe(callCountAfterStart);
224
+ });
225
+
226
+ test("watcher restart keeps the previous watcher active if replacement fails", async () => {
227
+ const watcher = PluginSourceWatcher.getInstance();
228
+ watcher.start();
229
+ const firstCallback = capturedWatchCallback;
230
+
231
+ mockWatchShouldThrow = true;
232
+ capturedWatchCallback!("change", "my-plugin/src/index.ts");
233
+
234
+ await new Promise((r) => setTimeout(r, 600));
235
+
236
+ expect(mockWatcher.close).not.toHaveBeenCalled();
237
+ expect((watcher as unknown as { watcher: unknown }).watcher).toBe(
238
+ mockWatcher,
239
+ );
240
+ expect(capturedWatchCallback).toBe(firstCallback);
241
+ });
242
+
243
+ test("singleton instance is shared across calls", () => {
244
+ const watcher1 = PluginSourceWatcher.getInstance();
245
+ const watcher2 = PluginSourceWatcher.getInstance();
246
+ expect(watcher1).toBe(watcher2);
247
+ });
248
+
249
+ test("resetForTests() clears the singleton", () => {
250
+ const watcher1 = PluginSourceWatcher.getInstance();
251
+ watcher1.start();
252
+
253
+ PluginSourceWatcher.resetForTests();
254
+ const watcher2 = PluginSourceWatcher.getInstance();
255
+
256
+ expect(watcher1).not.toBe(watcher2);
257
+ });
258
+
259
+ /**
260
+ * REGRESSION: When an event arrives during a close→reopen swap, rescan
261
+ * must catch any plugin not yet in the registry.
262
+ *
263
+ * Scenario:
264
+ * 1. Plugin "new-plugin" directory is created
265
+ * 2. Event fires, triggering a watcher restart (close + reopen)
266
+ * 3. Before the old watcher's callback is fully fired, another plugin
267
+ * "late-plugin" is created
268
+ * 4. The new watcher doesn't yet know about "late-plugin"
269
+ * 5. After the reopen, rescan walks the directory and finds "late-plugin"
270
+ * not in the registry, and schedules its rebuild
271
+ */
272
+ test("watcher restart + rescan catches plugins created during close→reopen", async () => {
273
+ const watcher = PluginSourceWatcher.getInstance();
274
+ watcher.start();
275
+
276
+ // Track which plugins are "registered" at each point
277
+ const registeredPlugins = new Set<string>();
278
+ mockGetRegisteredPluginImpl = (name: string) =>
279
+ registeredPlugins.has(name) ? { name } : undefined;
280
+
281
+ // Simulate multiple plugins on disk
282
+ mockRereadirSync.mockImplementation(() => [
283
+ "new-plugin",
284
+ "late-plugin",
285
+ ".DS_Store",
286
+ ]);
287
+
288
+ // Fire an event on new-plugin (this will trigger a watcher restart)
289
+ capturedWatchCallback!("change", "new-plugin/src/index.ts");
290
+
291
+ // Wait for the direct rebuild debounce, the watcher-restart debounce,
292
+ // and the rescan-triggered rebuild debounce.
293
+ await new Promise((r) => setTimeout(r, 1100));
294
+
295
+ // At this point, rescan should have run and discovered late-plugin,
296
+ // even though no direct fs.watch event was delivered for it.
297
+ const calls = mockReregisterExternalPlugin.mock.calls.map((c) => c[0]);
298
+ expect(calls).toContain("new-plugin");
299
+ expect(calls).toContain("late-plugin");
300
+ expect(calls).not.toContain(".DS_Store");
301
+ });
302
+ });