@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,401 @@
1
+ /**
2
+ * Tests for the `memoryRetrieval` plugin pipeline (PR 20).
3
+ *
4
+ * Covers the default terminal behavior, timeout handling, and custom-plugin
5
+ * substitution. Uses `mock.module` to stub the workspace PKB/NOW readers
6
+ * so the test doesn't touch the developer's real `~/.vellum`. The memory
7
+ * graph handle is a hand-rolled fake passed as a dependency — the default
8
+ * retriever only needs `prepareMemory`.
9
+ */
10
+
11
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
12
+
13
+ // Stub PKB/NOW readers BEFORE importing the module under test so the
14
+ // bindings resolve through the mock.
15
+ const readPkbContextMock = mock((): string | null => "pkb-default");
16
+ const readNowContextMock = mock((): string | null => "now-default");
17
+ mock.module("../daemon/conversation-runtime-assembly.js", () => ({
18
+ readPkbContext: readPkbContextMock,
19
+ readNowScratchpad: readNowContextMock,
20
+ }));
21
+
22
+ import type { AssistantConfig } from "../config/schema.js";
23
+ import type { TrustContext } from "../daemon/conversation-runtime-assembly.js";
24
+ import type { ServerMessage } from "../daemon/message-protocol.js";
25
+ import type { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
26
+ import {
27
+ asDefaultGraphPayload,
28
+ DEFAULT_MEMORY_GRAPH_KIND,
29
+ type DefaultMemoryRetrievalDeps,
30
+ defaultMemoryRetrievalPlugin,
31
+ runDefaultMemoryRetrieval,
32
+ } from "../plugins/defaults/memory-retrieval.js";
33
+ import { DEFAULT_TIMEOUTS, runPipeline } from "../plugins/pipeline.js";
34
+ import {
35
+ getMiddlewaresFor,
36
+ registerPlugin,
37
+ resetPluginRegistryForTests,
38
+ } from "../plugins/registry.js";
39
+ import {
40
+ type MemoryArgs,
41
+ type MemoryResult,
42
+ type Middleware,
43
+ type Plugin,
44
+ PluginTimeoutError,
45
+ type TurnContext,
46
+ } from "../plugins/types.js";
47
+ import type { Message } from "../providers/types.js";
48
+
49
+ const trust: TrustContext = {
50
+ sourceChannel: "vellum",
51
+ trustClass: "guardian",
52
+ };
53
+
54
+ function makeTurnCtx(overrides: Partial<TurnContext> = {}): TurnContext {
55
+ return {
56
+ requestId: "req-test",
57
+ conversationId: "conv-test",
58
+ turnIndex: 0,
59
+ trust,
60
+ ...overrides,
61
+ };
62
+ }
63
+
64
+ function makeMemoryArgs(overrides: Partial<MemoryArgs> = {}): MemoryArgs {
65
+ return {
66
+ conversationId: "conv-test",
67
+ trustContext: trust,
68
+ turnIndex: 0,
69
+ ...overrides,
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Fake graph-memory whose `prepareMemory` returns a canonical result. The
75
+ * default retriever threads this return value through
76
+ * `MemoryResult.memoryGraphBlocks[0].result`, so tests can assert the block
77
+ * shape by comparing the embedded object identity.
78
+ */
79
+ function makeFakeGraphMemory(overrides?: {
80
+ messages?: Message[];
81
+ injectedTokens?: number;
82
+ injectedBlockText?: string | null;
83
+ }): {
84
+ memory: ConversationGraphMemory;
85
+ prepareMemoryMock: ReturnType<typeof mock>;
86
+ } {
87
+ const returnValue = {
88
+ runMessages: overrides?.messages ?? [],
89
+ injectedTokens: overrides?.injectedTokens ?? 0,
90
+ latencyMs: 0,
91
+ mode: "none" as const,
92
+ injectedBlockText:
93
+ overrides?.injectedBlockText === undefined
94
+ ? null
95
+ : overrides.injectedBlockText,
96
+ metrics: null,
97
+ };
98
+ const prepareMemoryMock = mock(async () => returnValue);
99
+ const memory = {
100
+ prepareMemory: prepareMemoryMock,
101
+ } as unknown as ConversationGraphMemory;
102
+ return { memory, prepareMemoryMock };
103
+ }
104
+
105
+ function makeDeps(
106
+ overrides: Partial<DefaultMemoryRetrievalDeps> = {},
107
+ ): DefaultMemoryRetrievalDeps {
108
+ const { memory } = makeFakeGraphMemory();
109
+ return {
110
+ messages: [],
111
+ graphMemory: memory,
112
+ config: {} as AssistantConfig,
113
+ onEvent: () => {},
114
+ isTrustedActor: true,
115
+ ...overrides,
116
+ };
117
+ }
118
+
119
+ beforeEach(() => {
120
+ resetPluginRegistryForTests();
121
+ readPkbContextMock.mockReset();
122
+ readNowContextMock.mockReset();
123
+ readPkbContextMock.mockImplementation(() => "pkb-default");
124
+ readNowContextMock.mockImplementation(() => "now-default");
125
+ });
126
+
127
+ describe("runDefaultMemoryRetrieval", () => {
128
+ test("returns PKB, NOW, and a single graph block when the actor is trusted", async () => {
129
+ const { memory, prepareMemoryMock } = makeFakeGraphMemory();
130
+ const deps = makeDeps({ graphMemory: memory, isTrustedActor: true });
131
+
132
+ const result = await runDefaultMemoryRetrieval(makeMemoryArgs(), deps);
133
+
134
+ expect(result.pkbContent).toBe("pkb-default");
135
+ expect(result.nowContent).toBe("now-default");
136
+ expect(result.memoryGraphBlocks).toHaveLength(1);
137
+ expect(prepareMemoryMock).toHaveBeenCalledTimes(1);
138
+
139
+ const payload = asDefaultGraphPayload(result.memoryGraphBlocks);
140
+ expect(payload).not.toBeNull();
141
+ expect(payload?.kind).toBe(DEFAULT_MEMORY_GRAPH_KIND);
142
+ // The default retriever forwards the graph-memory return value
143
+ // verbatim under `payload.result` — consumers in the agent loop
144
+ // rely on that identity.
145
+ expect(payload?.result.mode).toBe("none");
146
+ });
147
+
148
+ test("skips graph retrieval for untrusted actors", async () => {
149
+ const { memory, prepareMemoryMock } = makeFakeGraphMemory();
150
+ const deps = makeDeps({ graphMemory: memory, isTrustedActor: false });
151
+
152
+ const result = await runDefaultMemoryRetrieval(makeMemoryArgs(), deps);
153
+
154
+ expect(prepareMemoryMock).not.toHaveBeenCalled();
155
+ expect(result.memoryGraphBlocks).toEqual([]);
156
+ expect(result.pkbContent).toBe("pkb-default");
157
+ expect(result.nowContent).toBe("now-default");
158
+ });
159
+
160
+ test("passes through null PKB and NOW when the files are absent", async () => {
161
+ readPkbContextMock.mockImplementation(() => null);
162
+ readNowContextMock.mockImplementation(() => null);
163
+ const deps = makeDeps();
164
+
165
+ const result = await runDefaultMemoryRetrieval(makeMemoryArgs(), deps);
166
+
167
+ expect(result.pkbContent).toBeNull();
168
+ expect(result.nowContent).toBeNull();
169
+ });
170
+ });
171
+
172
+ describe("asDefaultGraphPayload", () => {
173
+ test("returns null when the blocks array is empty", () => {
174
+ expect(asDefaultGraphPayload([])).toBeNull();
175
+ });
176
+
177
+ test("returns null when the first block lacks the default discriminator", () => {
178
+ expect(asDefaultGraphPayload([{ kind: "custom" }])).toBeNull();
179
+ expect(asDefaultGraphPayload([{}])).toBeNull();
180
+ expect(asDefaultGraphPayload([null])).toBeNull();
181
+ });
182
+
183
+ test("narrows blocks whose first entry carries the default discriminator", () => {
184
+ const payload = {
185
+ kind: DEFAULT_MEMORY_GRAPH_KIND,
186
+ result: { mode: "per-turn" } as never,
187
+ };
188
+ expect(asDefaultGraphPayload([payload])).toBe(payload);
189
+ });
190
+ });
191
+
192
+ describe("memoryRetrieval pipeline — default vs custom plugin", () => {
193
+ test("default (no plugins registered) matches current retrieval exactly", async () => {
194
+ const deps = makeDeps();
195
+ const args = makeMemoryArgs();
196
+ const terminalDirect = await runDefaultMemoryRetrieval(args, deps);
197
+
198
+ // With an empty registry the pipeline runs the terminal directly. Use a
199
+ // fresh graph handle so `prepareMemory` call counts don't leak across
200
+ // the two invocations.
201
+ const deps2 = makeDeps();
202
+ const terminalViaPipeline = await runPipeline(
203
+ "memoryRetrieval",
204
+ getMiddlewaresFor("memoryRetrieval"),
205
+ (innerArgs: MemoryArgs) => runDefaultMemoryRetrieval(innerArgs, deps2),
206
+ args,
207
+ makeTurnCtx(),
208
+ DEFAULT_TIMEOUTS.memoryRetrieval,
209
+ );
210
+
211
+ expect(terminalViaPipeline.pkbContent).toBe(terminalDirect.pkbContent);
212
+ expect(terminalViaPipeline.nowContent).toBe(terminalDirect.nowContent);
213
+ expect(terminalViaPipeline.memoryGraphBlocks).toHaveLength(
214
+ terminalDirect.memoryGraphBlocks.length,
215
+ );
216
+ });
217
+
218
+ test("with the default plugin registered, pipeline still produces default output", async () => {
219
+ registerPlugin(defaultMemoryRetrievalPlugin);
220
+ const deps = makeDeps();
221
+ const args = makeMemoryArgs();
222
+
223
+ const result = await runPipeline(
224
+ "memoryRetrieval",
225
+ getMiddlewaresFor("memoryRetrieval"),
226
+ (innerArgs: MemoryArgs) => runDefaultMemoryRetrieval(innerArgs, deps),
227
+ args,
228
+ makeTurnCtx(),
229
+ DEFAULT_TIMEOUTS.memoryRetrieval,
230
+ );
231
+
232
+ expect(result.pkbContent).toBe("pkb-default");
233
+ expect(result.nowContent).toBe("now-default");
234
+ expect(result.memoryGraphBlocks).toHaveLength(1);
235
+ expect(asDefaultGraphPayload(result.memoryGraphBlocks)).not.toBeNull();
236
+ });
237
+
238
+ test("custom plugin can replace all three sources via short-circuit", async () => {
239
+ const customBlock = { kind: "custom.source", text: "replacement" };
240
+ const customMiddleware: Middleware<MemoryArgs, MemoryResult> =
241
+ async function customRetriever() {
242
+ // Skip `next` entirely — the terminal never runs.
243
+ return {
244
+ pkbContent: "pkb-custom",
245
+ nowContent: "now-custom",
246
+ memoryGraphBlocks: [customBlock],
247
+ };
248
+ };
249
+
250
+ const customPlugin: Plugin = {
251
+ manifest: {
252
+ name: "custom-memory-retrieval",
253
+ version: "0.0.1",
254
+ requires: { pluginRuntime: "v1", memoryApi: "v1" },
255
+ },
256
+ middleware: { memoryRetrieval: customMiddleware },
257
+ };
258
+ registerPlugin(customPlugin);
259
+
260
+ const deps = makeDeps();
261
+ const args = makeMemoryArgs();
262
+
263
+ const result = await runPipeline(
264
+ "memoryRetrieval",
265
+ getMiddlewaresFor("memoryRetrieval"),
266
+ (innerArgs: MemoryArgs) => runDefaultMemoryRetrieval(innerArgs, deps),
267
+ args,
268
+ makeTurnCtx(),
269
+ DEFAULT_TIMEOUTS.memoryRetrieval,
270
+ );
271
+
272
+ expect(result.pkbContent).toBe("pkb-custom");
273
+ expect(result.nowContent).toBe("now-custom");
274
+ expect(result.memoryGraphBlocks).toEqual([customBlock]);
275
+ // The terminal never ran, so the stubbed readers were NOT invoked.
276
+ expect(readPkbContextMock).not.toHaveBeenCalled();
277
+ expect(readNowContextMock).not.toHaveBeenCalled();
278
+ // And `asDefaultGraphPayload` must return null because the custom
279
+ // plugin supplied a block without the default discriminator — this is
280
+ // what drives the agent-loop escape hatch.
281
+ expect(asDefaultGraphPayload(result.memoryGraphBlocks)).toBeNull();
282
+ });
283
+
284
+ test("timeout: terminal that hangs past the budget fails with PluginTimeoutError", async () => {
285
+ // Hang-prone middleware that never resolves; the runner arms a 5s timer
286
+ // by default, but the test overrides to a much smaller budget to keep
287
+ // the suite fast.
288
+ const hanging: Middleware<MemoryArgs, MemoryResult> =
289
+ async function hangingRetriever(_args, _next) {
290
+ return new Promise<MemoryResult>(() => {
291
+ // Never resolves — simulates a retriever that blocks on I/O.
292
+ });
293
+ };
294
+
295
+ const plugin: Plugin = {
296
+ manifest: {
297
+ name: "hanging-memory-plugin",
298
+ version: "0.0.1",
299
+ requires: { pluginRuntime: "v1", memoryApi: "v1" },
300
+ },
301
+ middleware: { memoryRetrieval: hanging },
302
+ };
303
+ registerPlugin(plugin);
304
+
305
+ const deps = makeDeps();
306
+ const args = makeMemoryArgs();
307
+
308
+ let caught: unknown;
309
+ try {
310
+ await runPipeline(
311
+ "memoryRetrieval",
312
+ getMiddlewaresFor("memoryRetrieval"),
313
+ (innerArgs: MemoryArgs) => runDefaultMemoryRetrieval(innerArgs, deps),
314
+ args,
315
+ makeTurnCtx(),
316
+ 30, // tiny budget — real production path uses 5_000ms
317
+ );
318
+ } catch (err) {
319
+ caught = err;
320
+ }
321
+
322
+ expect(caught).toBeInstanceOf(PluginTimeoutError);
323
+ const timeoutErr = caught as PluginTimeoutError;
324
+ expect(timeoutErr.pipeline).toBe("memoryRetrieval");
325
+ // The runner records whatever `ctx.pluginName` is set to when the
326
+ // timer fires. The default pipeline doesn't bind a plugin name, so
327
+ // the attribution is undefined — still fail the turn cleanly.
328
+ expect(timeoutErr.message).toContain("memoryRetrieval");
329
+ });
330
+
331
+ test("pipeline timeout aborts the signal threaded into prepareMemory", async () => {
332
+ // Regression for the cancellation gap: before `MemoryArgs.signal` was
333
+ // swapped by the pipeline's abort-linker, a pipeline timeout rejected
334
+ // the race but left `prepareMemory` running — mutating graph state
335
+ // after the turn had already errored. This test wires a long-running
336
+ // `prepareMemory` fake, arms a tight budget, and asserts the signal
337
+ // the terminal actually received reports `aborted === true` once the
338
+ // timer fires.
339
+ let capturedSignal: AbortSignal | undefined;
340
+ const hangingPrepare = mock(
341
+ (
342
+ _msgs: Message[],
343
+ _cfg: AssistantConfig,
344
+ signal: AbortSignal,
345
+ _onEvent: (msg: ServerMessage) => void,
346
+ ) => {
347
+ capturedSignal = signal;
348
+ return new Promise((_resolve, reject) => {
349
+ signal.addEventListener("abort", () => reject(new Error("aborted")));
350
+ });
351
+ },
352
+ );
353
+ const graphMemory = {
354
+ prepareMemory: hangingPrepare,
355
+ } as unknown as ConversationGraphMemory;
356
+ const deps = makeDeps({ graphMemory });
357
+ const outerController = new AbortController();
358
+ const args = makeMemoryArgs({ signal: outerController.signal });
359
+
360
+ let caught: unknown;
361
+ try {
362
+ await runPipeline(
363
+ "memoryRetrieval",
364
+ getMiddlewaresFor("memoryRetrieval"),
365
+ (innerArgs: MemoryArgs) => runDefaultMemoryRetrieval(innerArgs, deps),
366
+ args,
367
+ makeTurnCtx(),
368
+ 30,
369
+ );
370
+ } catch (err) {
371
+ caught = err;
372
+ }
373
+
374
+ expect(caught).toBeInstanceOf(PluginTimeoutError);
375
+ expect(capturedSignal).toBeDefined();
376
+ // The signal the terminal observed must be the pipeline's linked
377
+ // signal (not the caller's bare signal), and it must be aborted so
378
+ // `prepareMemory` stops work instead of running to completion after
379
+ // the race has already rejected.
380
+ expect(capturedSignal).not.toBe(outerController.signal);
381
+ expect(capturedSignal!.aborted).toBe(true);
382
+ });
383
+
384
+ test("onEvent is invoked by the default retriever's terminal path", async () => {
385
+ const received: ServerMessage[] = [];
386
+ const { memory } = makeFakeGraphMemory();
387
+ const deps = makeDeps({
388
+ graphMemory: memory,
389
+ onEvent: (msg) => received.push(msg),
390
+ isTrustedActor: true,
391
+ });
392
+
393
+ await runDefaultMemoryRetrieval(makeMemoryArgs(), deps);
394
+
395
+ // The fake graph doesn't emit events, but the event sink must be
396
+ // forwarded intact so the real retriever can use it. Verify by
397
+ // reaching into the mock assertion above (prepareMemoryMock called
398
+ // with `onEvent`).
399
+ expect(received).toEqual([]);
400
+ });
401
+ });
@@ -35,6 +35,7 @@ mock.module("../memory/qdrant-client.js", () => ({
35
35
  deletePoints: async () => {},
36
36
  }),
37
37
  initQdrantClient: () => {},
38
+ resolveQdrantUrl: () => "http://127.0.0.1:6333",
38
39
  }));
39
40
 
40
41
  import { DEFAULT_CONFIG } from "../config/defaults.js";
@@ -11,11 +11,11 @@
11
11
  * - Invalid GCS URL: a real https://evil.com URL is rejected with the
12
12
  * redacted `Invalid URL: host` message; the raw URL is not echoed back
13
13
  * in the response body.
14
- * - Memory ceiling: a 100 MB fixture streams through with peak RSS delta
15
- * bounded at ~128 MB. The ceiling is wider than the direct
16
- * `streamCommitImport` ceiling because the URL handler stacks a Node
17
- * HTTP response body + gunzip state + tar-stream state on top of the
18
- * importer's per-entry working set.
14
+ *
15
+ * Memory-ceiling coverage lives at the streaming-importer layer see
16
+ * `vbundle-streaming-importer.test.ts` ("streamCommitImport memory
17
+ * ceiling"). The URL handler adds only fixed HTTP/framing overhead on top
18
+ * of that pipeline, not bundle-size-proportional allocation.
19
19
  *
20
20
  * The raw-bytes ingress path is exercised by a separate test file,
21
21
  * `migration-import-commit-http.test.ts`.
@@ -414,69 +414,6 @@ describe("handleMigrationImport — JSON {url} body", () => {
414
414
  });
415
415
  });
416
416
 
417
- // ---------------------------------------------------------------------------
418
- // Memory ceiling — streams a 100 MB fixture through and caps RSS.
419
- // ---------------------------------------------------------------------------
420
-
421
- function writeLargeFixtureToDisk(archivePath: string): void {
422
- const CHUNK = 25 * 1024 * 1024;
423
- const files = [0, 1, 2, 3].map((i) => ({
424
- path: `workspace/big-${i}.bin`,
425
- data: new Uint8Array(CHUNK).fill(0x41 + i),
426
- }));
427
- const { archive } = buildVBundle({ files });
428
- writeFileSync(archivePath, archive);
429
- }
430
-
431
- describe("handleMigrationImport — URL body memory ceiling", () => {
432
- test("100 MB fixture streams in without pushing RSS past ~128 MB over baseline", async () => {
433
- const archivePath = join(testParent, "fixture-large.vbundle");
434
- writeLargeFixtureToDisk(archivePath);
435
-
436
- const fixture = await startFixtureServer((_req, res) => {
437
- res.writeHead(200, { "Content-Type": "application/octet-stream" });
438
- createReadStream(archivePath).pipe(res);
439
- });
440
-
441
- try {
442
- // Force a GC opportunity before measuring baseline so stale fixture
443
- // buffers don't count against the importer's budget. Bun exposes
444
- // `Bun.gc` but not process.gc; gate on whether it exists.
445
- const maybeBunGc = (
446
- globalThis as { Bun?: { gc?: (sync?: boolean) => void } }
447
- ).Bun?.gc;
448
- if (typeof maybeBunGc === "function") maybeBunGc(true);
449
-
450
- const baselineRss = process.memoryUsage().rss;
451
-
452
- const req = new Request("http://localhost/v1/migrations/import", {
453
- method: "POST",
454
- headers: { "Content-Type": "application/json" },
455
- body: JSON.stringify({ url: makeFakeSignedUrl(fixture.port) }),
456
- });
457
-
458
- const res = await handleMigrationImport(req);
459
- const body = (await res.json()) as ImportCommitResponse;
460
-
461
- const peakRss = process.memoryUsage().rss;
462
-
463
- expect(res.status).toBe(200);
464
- expect(body.success).toBe(true);
465
-
466
- // 128 MB ceiling: the URL handler pipes the HTTP response body
467
- // through gunzip + tar-stream on top of the streaming importer's
468
- // per-entry working set. The extra framing state is bigger than
469
- // raw-stream importing (which fits in ~64 MB), so 128 MB keeps
470
- // enough headroom to detect full-bundle buffering without
471
- // flapping on normal streaming-mode overhead.
472
- const delta = peakRss - baselineRss;
473
- expect(delta).toBeLessThan(128 * 1024 * 1024);
474
- } finally {
475
- await fixture.close();
476
- }
477
- }, 90_000);
478
- });
479
-
480
417
  // ---------------------------------------------------------------------------
481
418
  // Gap A regression: no-swap success path must not append a stale
482
419
  // "newer migration" warning sourced from the live DB. The warning would
@@ -31,9 +31,11 @@ describe("model intents", () => {
31
31
  });
32
32
 
33
33
  test("falls back to provider default for unknown providers", () => {
34
- expect(getProviderDefaultModel("unknown-provider")).toBe("claude-opus-4-7");
34
+ expect(getProviderDefaultModel("unknown-provider")).toBe(
35
+ "claude-sonnet-4-6",
36
+ );
35
37
  expect(resolveModelIntent("unknown-provider", "quality-optimized")).toBe(
36
- "claude-opus-4-7",
38
+ "claude-sonnet-4-6",
37
39
  );
38
40
  });
39
41
  });
@@ -593,7 +593,7 @@ describe("notification broadcaster", () => {
593
593
  broadcaster.setOnConversationCreated((info) => createdCalls.push(info));
594
594
 
595
595
  const signal = makeSignal({
596
- sourceEventName: "schedule.complete",
596
+ sourceEventName: "schedule.notify",
597
597
  conversationMetadata: {
598
598
  groupId: "system:scheduled",
599
599
  source: "schedule",
@@ -607,7 +607,7 @@ describe("notification broadcaster", () => {
607
607
  expect(createdCalls).toHaveLength(1);
608
608
  expect(createdCalls[0].groupId).toBe("system:scheduled");
609
609
  expect(createdCalls[0].source).toBe("schedule");
610
- expect(createdCalls[0].sourceEventName).toBe("schedule.complete");
610
+ expect(createdCalls[0].sourceEventName).toBe("schedule.notify");
611
611
  });
612
612
 
613
613
  test("onConversationCreated omits groupId and source when conversationMetadata is absent", async () => {
@@ -632,7 +632,7 @@ describe("notification broadcaster", () => {
632
632
  const dispatchCalls: ConversationCreatedInfo[] = [];
633
633
 
634
634
  const signal = makeSignal({
635
- sourceEventName: "schedule.complete",
635
+ sourceEventName: "schedule.notify",
636
636
  conversationMetadata: {
637
637
  groupId: "system:scheduled",
638
638
  source: "schedule",
@@ -147,17 +147,6 @@ describe("notification decision strategy", () => {
147
147
  expect(copy.telegram!.deliveryText).toBe("Take out the trash");
148
148
  });
149
149
 
150
- test("schedule.complete template uses name from payload", () => {
151
- const signal = makeSignal({
152
- sourceEventName: "schedule.complete",
153
- contextPayload: { name: "Daily backup" },
154
- });
155
-
156
- const copy = composeFallbackCopy(signal, channels);
157
- expect(copy.vellum).toBeDefined();
158
- expect(copy.vellum!.body).toContain("Daily backup");
159
- });
160
-
161
150
  test("unknown event name produces generic copy with urgency prefix", () => {
162
151
  const signal = makeSignal({
163
152
  sourceEventName: "some_novel.event",
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Regression test: recurring `schedule.notify` firings must not be
3
+ * deduplicated against prior firings of the same schedule.
4
+ *
5
+ * The scheduler supplies a unique per-firing dedupeKey
6
+ * (`schedule:notify:<id>:<timestamp>`) so `updateEventDedupeKey` is never
7
+ * called for schedule signals and `checkDedupe` never finds a matching
8
+ * row when the LLM decision engine generates a stable key like
9
+ * `schedule:notify:<id>`.
10
+ */
11
+
12
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
13
+
14
+ mock.module("../util/logger.js", () => ({
15
+ getLogger: () =>
16
+ new Proxy({} as Record<string, unknown>, {
17
+ get: () => () => {},
18
+ }),
19
+ truncateForLog: (value: string) => value,
20
+ }));
21
+
22
+ import { getDb, initializeDb } from "../memory/db.js";
23
+ import { notificationEvents } from "../memory/schema.js";
24
+ import { runDeterministicChecks } from "../notifications/deterministic-checks.js";
25
+ import { createEvent } from "../notifications/events-store.js";
26
+ import type { NotificationSignal } from "../notifications/signal.js";
27
+ import type { NotificationDecision } from "../notifications/types.js";
28
+
29
+ initializeDb();
30
+
31
+ beforeEach(() => {
32
+ getDb().delete(notificationEvents).run();
33
+ });
34
+
35
+ function makeSignal(
36
+ overrides?: Partial<NotificationSignal>,
37
+ ): NotificationSignal {
38
+ return {
39
+ signalId: `sig-${crypto.randomUUID()}`,
40
+ createdAt: Date.now(),
41
+ sourceChannel: "scheduler",
42
+ sourceContextId: "schedule-123",
43
+ sourceEventName: "schedule.notify",
44
+ contextPayload: { scheduleId: "schedule-123", label: "Drink water" },
45
+ attentionHints: {
46
+ requiresAction: true,
47
+ urgency: "high",
48
+ isAsyncBackground: false,
49
+ visibleInSourceNow: false,
50
+ },
51
+ ...overrides,
52
+ };
53
+ }
54
+
55
+ function makeDecision(
56
+ overrides?: Partial<NotificationDecision>,
57
+ ): NotificationDecision {
58
+ return {
59
+ shouldNotify: true,
60
+ selectedChannels: ["vellum"],
61
+ reasoningSummary: "Schedule reminder",
62
+ renderedCopy: {
63
+ vellum: { title: "Reminder", body: "Time to drink water" },
64
+ },
65
+ dedupeKey: "schedule:notify:schedule-123",
66
+ confidence: 0.9,
67
+ fallbackUsed: false,
68
+ ...overrides,
69
+ };
70
+ }
71
+
72
+ describe("recurring schedule.notify dedup", () => {
73
+ test("notify mode with timestamped producer keys is not blocked", async () => {
74
+ const stableKey = "schedule:notify:schedule-123";
75
+ const firstId = crypto.randomUUID();
76
+ const secondId = crypto.randomUUID();
77
+
78
+ const firstSignal = makeSignal({ signalId: firstId });
79
+ createEvent({
80
+ id: firstSignal.signalId,
81
+ sourceEventName: "schedule.notify",
82
+ sourceChannel: "scheduler",
83
+ sourceContextId: "schedule-123",
84
+ attentionHints: firstSignal.attentionHints,
85
+ payload: firstSignal.contextPayload,
86
+ dedupeKey: `schedule:notify:schedule-123:${Date.now() - 60_000}`,
87
+ });
88
+
89
+ const secondSignal = makeSignal({ signalId: secondId });
90
+ createEvent({
91
+ id: secondSignal.signalId,
92
+ sourceEventName: "schedule.notify",
93
+ sourceChannel: "scheduler",
94
+ sourceContextId: "schedule-123",
95
+ attentionHints: secondSignal.attentionHints,
96
+ payload: secondSignal.contextPayload,
97
+ dedupeKey: `schedule:notify:schedule-123:${Date.now()}`,
98
+ });
99
+
100
+ const decision = makeDecision({ dedupeKey: stableKey });
101
+
102
+ const result = await runDeterministicChecks(secondSignal, decision, {
103
+ connectedChannels: ["vellum"],
104
+ });
105
+
106
+ expect(result.passed).toBe(true);
107
+ });
108
+ });
@@ -56,7 +56,7 @@ mock.module("../oauth/oauth-store.js", () => ({
56
56
  userinfoUrl: null,
57
57
  baseUrl: null,
58
58
  defaultScopes: "[]",
59
- scopePolicy: "[]",
59
+ availableScopes: null,
60
60
  scopeSeparator: null,
61
61
  authorizeParams: null,
62
62
  pingUrl: null,