@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
@@ -1,112 +0,0 @@
1
- import { mkdirSync, rmSync, writeFileSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
5
-
6
- import { HookManager } from "../hooks/manager.js";
7
-
8
- let hooksDir: string;
9
-
10
- function makeManifest(
11
- name: string,
12
- events: string[] = ["pre-llm-call"],
13
- ): string {
14
- return JSON.stringify({
15
- name,
16
- description: `Test hook: ${name}`,
17
- version: "1.0.0",
18
- events,
19
- script: "run.sh",
20
- });
21
- }
22
-
23
- function installHook(name: string, events: string[] = ["pre-llm-call"]): void {
24
- const hookDir = join(hooksDir, name);
25
- mkdirSync(hookDir, { recursive: true });
26
- writeFileSync(join(hookDir, "hook.json"), makeManifest(name, events));
27
- writeFileSync(join(hookDir, "run.sh"), "#!/bin/sh\nexit 0\n", {
28
- mode: 0o755,
29
- });
30
- }
31
-
32
- function writeConfig(hooks: Record<string, { enabled: boolean }>): void {
33
- writeFileSync(
34
- join(hooksDir, "config.json"),
35
- JSON.stringify({ version: 1, hooks }),
36
- );
37
- }
38
-
39
- describe("hooks watch mode", () => {
40
- let manager: HookManager;
41
-
42
- beforeEach(() => {
43
- hooksDir = join(
44
- tmpdir(),
45
- `hooks-watch-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
46
- );
47
- mkdirSync(hooksDir, { recursive: true });
48
- });
49
-
50
- afterEach(() => {
51
- if (manager) manager.stopWatching();
52
- rmSync(hooksDir, { recursive: true, force: true });
53
- });
54
-
55
- test("reload() re-discovers hooks and rebuilds event index", () => {
56
- installHook("hook-a", ["pre-llm-call"]);
57
- writeConfig({ "hook-a": { enabled: true } });
58
-
59
- manager = new HookManager();
60
- // Initialize by discovering hooks from our temp dir
61
- // We use a workaround since HookManager uses getHooksDir() internally
62
- // Instead, test the reload behavior by checking the public API
63
- const initialHooks = manager.getDiscoveredHooks();
64
- expect(initialHooks).toHaveLength(0); // Not initialized yet
65
-
66
- manager.initialize();
67
- // After initialize, hooks depend on getHooksDir() which points elsewhere
68
- // So we test reload conceptually: calling reload should not throw
69
- expect(() => manager.reload()).not.toThrow();
70
- });
71
-
72
- test("stopWatching() cleans up watcher and timer", () => {
73
- manager = new HookManager();
74
- // stopWatching before watch should not throw
75
- expect(() => manager.stopWatching()).not.toThrow();
76
-
77
- // watch on a non-existent dir should not throw
78
- manager.watch();
79
- expect(() => manager.stopWatching()).not.toThrow();
80
- });
81
-
82
- test("watch() on non-existent directory does not throw", () => {
83
- manager = new HookManager();
84
- // The default hooks dir may not exist in test; should be safe
85
- expect(() => manager.watch()).not.toThrow();
86
- manager.stopWatching();
87
- });
88
-
89
- test("reload() updates enabled hooks count", () => {
90
- manager = new HookManager();
91
- manager.initialize();
92
-
93
- // First reload
94
- const hooks1 = manager.getDiscoveredHooks();
95
-
96
- // Reload should work without error
97
- manager.reload();
98
- const hooks2 = manager.getDiscoveredHooks();
99
-
100
- // Both should return the same result (consistent state)
101
- expect(hooks1.length).toBe(hooks2.length);
102
- });
103
-
104
- test("multiple stopWatching() calls are idempotent", () => {
105
- manager = new HookManager();
106
- manager.watch();
107
- manager.stopWatching();
108
- manager.stopWatching();
109
- manager.stopWatching();
110
- // No errors thrown
111
- });
112
- });
@@ -1,213 +0,0 @@
1
- /**
2
- * Regression test: recurring schedule notifications must not be
3
- * deduplicated against prior firings of the same schedule.
4
- *
5
- * Before the fix, `schedule.complete` signals were emitted without a
6
- * producer dedupeKey. The LLM decision engine would generate a stable
7
- * key (e.g. `schedule:complete:<id>`) and `updateEventDedupeKey` would
8
- * write it back to the event row. On the next firing, `checkDedupe`
9
- * found the first row's stable key within the 1-hour window and
10
- * silently blocked the notification.
11
- *
12
- * The fix: always supply a unique per-firing dedupeKey from the
13
- * producer so `updateEventDedupeKey` is never called for schedule
14
- * signals, and `checkDedupe` never finds a matching row.
15
- */
16
-
17
- import { beforeEach, describe, expect, mock, test } from "bun:test";
18
-
19
- mock.module("../util/logger.js", () => ({
20
- getLogger: () =>
21
- new Proxy({} as Record<string, unknown>, {
22
- get: () => () => {},
23
- }),
24
- truncateForLog: (value: string) => value,
25
- }));
26
-
27
- import { getDb, initializeDb } from "../memory/db.js";
28
- import { notificationEvents } from "../memory/schema.js";
29
- import { runDeterministicChecks } from "../notifications/deterministic-checks.js";
30
- import {
31
- createEvent,
32
- updateEventDedupeKey,
33
- } from "../notifications/events-store.js";
34
- import type { NotificationSignal } from "../notifications/signal.js";
35
- import type { NotificationDecision } from "../notifications/types.js";
36
-
37
- initializeDb();
38
-
39
- beforeEach(() => {
40
- // Clear notification events between tests for isolation
41
- getDb().delete(notificationEvents).run();
42
- });
43
-
44
- function makeSignal(
45
- overrides?: Partial<NotificationSignal>,
46
- ): NotificationSignal {
47
- return {
48
- signalId: `sig-${crypto.randomUUID()}`,
49
- createdAt: Date.now(),
50
- sourceChannel: "scheduler",
51
- sourceContextId: "schedule-123",
52
- sourceEventName: "schedule.complete",
53
- contextPayload: { scheduleId: "schedule-123", name: "Drink water" },
54
- attentionHints: {
55
- requiresAction: false,
56
- urgency: "medium",
57
- isAsyncBackground: true,
58
- visibleInSourceNow: false,
59
- },
60
- ...overrides,
61
- };
62
- }
63
-
64
- function makeDecision(
65
- overrides?: Partial<NotificationDecision>,
66
- ): NotificationDecision {
67
- return {
68
- shouldNotify: true,
69
- selectedChannels: ["vellum"],
70
- reasoningSummary: "Schedule completed",
71
- renderedCopy: {
72
- vellum: { title: "Reminder", body: "Time to drink water" },
73
- },
74
- dedupeKey: "schedule:complete:schedule-123",
75
- confidence: 0.9,
76
- fallbackUsed: false,
77
- ...overrides,
78
- };
79
- }
80
-
81
- describe("recurring schedule notification dedup", () => {
82
- test("second firing is blocked when LLM stable key is written to first event row (the bug)", async () => {
83
- // Simulate the BROKEN behavior: producer sends no dedupeKey,
84
- // LLM generates a stable key, and updateEventDedupeKey writes it
85
- // to the first event row.
86
-
87
- const stableKey = "schedule:complete:schedule-123";
88
- const firstId = crypto.randomUUID();
89
- const secondId = crypto.randomUUID();
90
-
91
- // First firing: create event with null dedupeKey, then backfill with LLM key
92
- const firstSignal = makeSignal({ signalId: firstId });
93
- createEvent({
94
- id: firstSignal.signalId,
95
- sourceEventName: "schedule.complete",
96
- sourceChannel: "scheduler",
97
- sourceContextId: "schedule-123",
98
- attentionHints: firstSignal.attentionHints,
99
- payload: firstSignal.contextPayload,
100
- // No dedupeKey — this is the bug scenario
101
- });
102
- // LLM decision generates a stable key, pipeline writes it back
103
- updateEventDedupeKey(firstSignal.signalId, stableKey);
104
-
105
- // Second firing: new event, same schedule
106
- const secondSignal = makeSignal({ signalId: secondId });
107
- createEvent({
108
- id: secondSignal.signalId,
109
- sourceEventName: "schedule.complete",
110
- sourceChannel: "scheduler",
111
- sourceContextId: "schedule-123",
112
- attentionHints: secondSignal.attentionHints,
113
- payload: secondSignal.contextPayload,
114
- });
115
-
116
- // LLM generates the same stable key for the second firing
117
- const decision = makeDecision({ dedupeKey: stableKey });
118
-
119
- const result = await runDeterministicChecks(secondSignal, decision, {
120
- connectedChannels: ["vellum"],
121
- });
122
-
123
- // The second firing is BLOCKED — this is the bug
124
- expect(result.passed).toBe(false);
125
- expect(result.reason).toContain("Dedupe");
126
- });
127
-
128
- test("second firing passes when producer supplies unique per-firing dedupeKey (the fix)", async () => {
129
- const stableKey = "schedule:complete:schedule-123";
130
- const firstId = crypto.randomUUID();
131
- const secondId = crypto.randomUUID();
132
-
133
- // First firing: producer supplies a timestamped dedupeKey
134
- const firstSignal = makeSignal({ signalId: firstId });
135
- createEvent({
136
- id: firstSignal.signalId,
137
- sourceEventName: "schedule.complete",
138
- sourceChannel: "scheduler",
139
- sourceContextId: "schedule-123",
140
- attentionHints: firstSignal.attentionHints,
141
- payload: firstSignal.contextPayload,
142
- dedupeKey: `schedule:complete:schedule-123:${Date.now() - 60_000}`,
143
- });
144
- // updateEventDedupeKey is NOT called because params.dedupeKey is truthy
145
-
146
- // Second firing: new event with its own unique timestamped key
147
- const secondSignal = makeSignal({ signalId: secondId });
148
- createEvent({
149
- id: secondSignal.signalId,
150
- sourceEventName: "schedule.complete",
151
- sourceChannel: "scheduler",
152
- sourceContextId: "schedule-123",
153
- attentionHints: secondSignal.attentionHints,
154
- payload: secondSignal.contextPayload,
155
- dedupeKey: `schedule:complete:schedule-123:${Date.now()}`,
156
- });
157
-
158
- // LLM still generates a stable key — but no row in the DB has it
159
- const decision = makeDecision({ dedupeKey: stableKey });
160
-
161
- const result = await runDeterministicChecks(secondSignal, decision, {
162
- connectedChannels: ["vellum"],
163
- });
164
-
165
- // The second firing PASSES — the fix works
166
- expect(result.passed).toBe(true);
167
- });
168
-
169
- test("notify mode with timestamped producer keys is not blocked", async () => {
170
- const stableKey = "schedule:notify:schedule-123";
171
- const firstId = crypto.randomUUID();
172
- const secondId = crypto.randomUUID();
173
-
174
- // First firing
175
- const firstSignal = makeSignal({
176
- signalId: firstId,
177
- sourceEventName: "schedule.notify",
178
- });
179
- createEvent({
180
- id: firstSignal.signalId,
181
- sourceEventName: "schedule.notify",
182
- sourceChannel: "scheduler",
183
- sourceContextId: "schedule-123",
184
- attentionHints: firstSignal.attentionHints,
185
- payload: firstSignal.contextPayload,
186
- dedupeKey: `schedule:notify:schedule-123:${Date.now() - 60_000}`,
187
- });
188
-
189
- // Second firing
190
- const secondSignal = makeSignal({
191
- signalId: secondId,
192
- sourceEventName: "schedule.notify",
193
- });
194
- createEvent({
195
- id: secondSignal.signalId,
196
- sourceEventName: "schedule.notify",
197
- sourceChannel: "scheduler",
198
- sourceContextId: "schedule-123",
199
- attentionHints: secondSignal.attentionHints,
200
- payload: secondSignal.contextPayload,
201
- dedupeKey: `schedule:notify:schedule-123:${Date.now()}`,
202
- });
203
-
204
- // LLM generates stable key — no matching row
205
- const decision = makeDecision({ dedupeKey: stableKey });
206
-
207
- const result = await runDeterministicChecks(secondSignal, decision, {
208
- connectedChannels: ["vellum"],
209
- });
210
-
211
- expect(result.passed).toBe(true);
212
- });
213
- });
@@ -1,180 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
-
3
- import type { ScopeResolverInput } from "../oauth/scope-policy.js";
4
- import { resolveScopes } from "../oauth/scope-policy.js";
5
-
6
- // ---------------------------------------------------------------------------
7
- // Helpers
8
- // ---------------------------------------------------------------------------
9
-
10
- /** Build a minimal scope resolver input for testing scope resolution. */
11
- function makeProfile(
12
- overrides: Partial<ScopeResolverInput> = {},
13
- ): ScopeResolverInput {
14
- return {
15
- service: "test-service",
16
- defaultScopes: ["read", "write"],
17
- scopePolicy: {
18
- allowAdditionalScopes: false,
19
- allowedOptionalScopes: [],
20
- forbiddenScopes: [],
21
- },
22
- ...overrides,
23
- };
24
- }
25
-
26
- // ---------------------------------------------------------------------------
27
- // Tests
28
- // ---------------------------------------------------------------------------
29
-
30
- describe("resolveScopes", () => {
31
- it("returns default scopes when no requestedScopes are provided", () => {
32
- const profile = makeProfile({ defaultScopes: ["read", "write"] });
33
- const result = resolveScopes(profile);
34
- expect(result).toEqual({ ok: true, scopes: ["read", "write"] });
35
- });
36
-
37
- it("returns default scopes when requestedScopes is undefined", () => {
38
- const profile = makeProfile({ defaultScopes: ["read", "write"] });
39
- const result = resolveScopes(profile, undefined);
40
- expect(result).toEqual({ ok: true, scopes: ["read", "write"] });
41
- });
42
-
43
- it("returns default scopes when requestedScopes is an empty array", () => {
44
- const profile = makeProfile({ defaultScopes: ["read", "write"] });
45
- const result = resolveScopes(profile, []);
46
- expect(result).toEqual({ ok: true, scopes: ["read", "write"] });
47
- });
48
-
49
- it("rejects a forbidden scope with a clear error message", () => {
50
- const profile = makeProfile({
51
- scopePolicy: {
52
- allowAdditionalScopes: true,
53
- allowedOptionalScopes: ["admin"],
54
- forbiddenScopes: ["delete"],
55
- },
56
- });
57
- const result = resolveScopes(profile, ["delete"]);
58
- expect(result.ok).toBe(false);
59
- if (!result.ok) {
60
- expect(result.error).toBe("Scope 'delete' is forbidden for test-service");
61
- }
62
- });
63
-
64
- it("rejects additional scopes when allowAdditionalScopes is false", () => {
65
- const profile = makeProfile({
66
- defaultScopes: ["read", "write"],
67
- scopePolicy: {
68
- allowAdditionalScopes: false,
69
- allowedOptionalScopes: [],
70
- forbiddenScopes: [],
71
- },
72
- });
73
- const result = resolveScopes(profile, ["admin"]);
74
- expect(result.ok).toBe(false);
75
- if (!result.ok) {
76
- expect(result.error).toContain(
77
- "Additional scopes are not allowed for test-service",
78
- );
79
- expect(result.allowedScopes).toEqual(["read", "write"]);
80
- }
81
- });
82
-
83
- it("rejects a scope not in allowedOptionalScopes even when allowAdditionalScopes is true", () => {
84
- const profile = makeProfile({
85
- defaultScopes: ["read"],
86
- scopePolicy: {
87
- allowAdditionalScopes: true,
88
- allowedOptionalScopes: ["write"],
89
- forbiddenScopes: [],
90
- },
91
- });
92
- const result = resolveScopes(profile, ["admin"]);
93
- expect(result.ok).toBe(false);
94
- if (!result.ok) {
95
- expect(result.error).toBe(
96
- "Scope 'admin' is not in the allowed optional scopes for test-service",
97
- );
98
- expect(result.allowedScopes).toEqual(["read", "write"]);
99
- }
100
- });
101
-
102
- it("accepts an additional scope when it is in allowedOptionalScopes and allowAdditionalScopes is true", () => {
103
- const profile = makeProfile({
104
- defaultScopes: ["read"],
105
- scopePolicy: {
106
- allowAdditionalScopes: true,
107
- allowedOptionalScopes: ["write", "admin"],
108
- forbiddenScopes: [],
109
- },
110
- });
111
- const result = resolveScopes(profile, ["write"]);
112
- expect(result.ok).toBe(true);
113
- if (result.ok) {
114
- expect(result.scopes).toEqual(["read", "write"]);
115
- }
116
- });
117
-
118
- it("deduplicates scopes in the result", () => {
119
- const profile = makeProfile({
120
- defaultScopes: ["read", "write"],
121
- scopePolicy: {
122
- allowAdditionalScopes: true,
123
- allowedOptionalScopes: ["admin"],
124
- forbiddenScopes: [],
125
- },
126
- });
127
- // Request duplicates of existing defaults and a new scope twice
128
- const result = resolveScopes(profile, ["read", "write", "admin", "admin"]);
129
- expect(result.ok).toBe(true);
130
- if (result.ok) {
131
- expect(result.scopes).toEqual(["read", "write", "admin"]);
132
- }
133
- });
134
-
135
- it("keeps requested scopes that are already in defaults without error", () => {
136
- const profile = makeProfile({
137
- defaultScopes: ["read", "write"],
138
- scopePolicy: {
139
- allowAdditionalScopes: false,
140
- allowedOptionalScopes: [],
141
- forbiddenScopes: [],
142
- },
143
- });
144
- // Requesting only default scopes should succeed even when
145
- // allowAdditionalScopes is false
146
- const result = resolveScopes(profile, ["read", "write"]);
147
- expect(result.ok).toBe(true);
148
- if (result.ok) {
149
- expect(result.scopes).toEqual(["read", "write"]);
150
- }
151
- });
152
-
153
- it("checks forbidden scopes before allowAdditionalScopes policy", () => {
154
- const profile = makeProfile({
155
- defaultScopes: ["read"],
156
- scopePolicy: {
157
- allowAdditionalScopes: true,
158
- allowedOptionalScopes: [],
159
- forbiddenScopes: ["destroy"],
160
- },
161
- });
162
- const result = resolveScopes(profile, ["destroy"]);
163
- expect(result.ok).toBe(false);
164
- if (!result.ok) {
165
- // Should be the forbidden error, not the "not in optional scopes" error
166
- expect(result.error).toContain("forbidden");
167
- }
168
- });
169
-
170
- it("returns a defensive copy of defaultScopes (not the same array reference)", () => {
171
- const defaults = ["read", "write"];
172
- const profile = makeProfile({ defaultScopes: defaults });
173
- const result = resolveScopes(profile);
174
- expect(result.ok).toBe(true);
175
- if (result.ok) {
176
- expect(result.scopes).toEqual(defaults);
177
- expect(result.scopes).not.toBe(defaults);
178
- }
179
- });
180
- });
@@ -1,83 +0,0 @@
1
- import { beforeEach, describe, expect, mock, test } from "bun:test";
2
-
3
- const emitNotificationSignalMock = mock(async (_params: unknown) => {});
4
-
5
- mock.module("../notifications/emit-signal.js", () => ({
6
- emitNotificationSignal: (params: unknown) =>
7
- emitNotificationSignalMock(params),
8
- }));
9
-
10
- import { run } from "../config/bundled-skills/notifications/tools/send-notification.js";
11
-
12
- describe("send-notification tool", () => {
13
- beforeEach(() => {
14
- emitNotificationSignalMock.mockClear();
15
- });
16
-
17
- test("emits a notification signal with normalized routing context", async () => {
18
- const result = await run(
19
- {
20
- message: "Your verification code is 123456",
21
- title: "Verification code",
22
- urgency: "high",
23
- conversation_id: "conv-override",
24
- requires_action: true,
25
- preferred_channels: ["vellum"],
26
- deep_link_metadata: { conversationId: "conv-deeplink" },
27
- dedupe_key: "voice-code-123456",
28
- },
29
- {
30
- workingDir: "/tmp",
31
- conversationId: "conv-1",
32
- assistantId: "ast-alpha",
33
- trustClass: "guardian" as const,
34
- },
35
- );
36
-
37
- expect(result.isError).toBe(false);
38
- expect(emitNotificationSignalMock).toHaveBeenCalledTimes(1);
39
- expect(emitNotificationSignalMock).toHaveBeenCalledWith({
40
- sourceEventName: "user.send_notification",
41
- sourceChannel: "assistant_tool",
42
- sourceContextId: "conv-override",
43
- attentionHints: {
44
- requiresAction: true,
45
- urgency: "high",
46
- deadlineAt: undefined,
47
- isAsyncBackground: false,
48
- visibleInSourceNow: false,
49
- },
50
- contextPayload: {
51
- requestedMessage: "Your verification code is 123456",
52
- requestedByTool: "send_notification",
53
- requestedByContextId: "conv-1",
54
- requestedTitle: "Verification code",
55
- requestedByConversationId: "conv-override",
56
- preferredChannels: ["vellum"],
57
- deepLinkMetadata: { conversationId: "conv-deeplink" },
58
- },
59
- dedupeKey: "voice-code-123456",
60
- throwOnError: true,
61
- });
62
- });
63
-
64
- test("returns an error when the notification pipeline throws", async () => {
65
- emitNotificationSignalMock.mockImplementationOnce(async () => {
66
- throw new Error("database unavailable");
67
- });
68
-
69
- const result = await run(
70
- { message: "test notification" },
71
- {
72
- workingDir: "/tmp",
73
- conversationId: "conv-1",
74
- assistantId: "ast-alpha",
75
- trustClass: "guardian" as const,
76
- },
77
- );
78
-
79
- expect(result.isError).toBe(true);
80
- expect(result.content).toContain("database unavailable");
81
- expect(emitNotificationSignalMock).toHaveBeenCalledTimes(1);
82
- });
83
- });