@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,156 +0,0 @@
1
- import { beforeEach, describe, expect, mock, test } from "bun:test";
2
-
3
- import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
4
- import {
5
- CONTEXT_OVERFLOW_TOOL_NAME,
6
- requestCompressionApproval,
7
- } from "../daemon/context-overflow-approval.js";
8
- import type { PermissionPrompter } from "../permissions/prompter.js";
9
- import type { UserDecision } from "../permissions/types.js";
10
-
11
- function createMockPrompter(decision: UserDecision): PermissionPrompter {
12
- const promptFn = mock(() =>
13
- Promise.resolve({
14
- decision,
15
- selectedPattern: undefined,
16
- selectedScope: undefined,
17
- decisionContext: undefined,
18
- }),
19
- );
20
- return { prompt: promptFn } as unknown as PermissionPrompter;
21
- }
22
-
23
- describe("requestCompressionApproval", () => {
24
- beforeEach(() => {
25
- _setOverridesForTesting({});
26
- });
27
-
28
- // ── Prompt shape ──
29
-
30
- test("auto-approves without prompting under v2", async () => {
31
- _setOverridesForTesting({ "permission-controls-v2": true });
32
-
33
- const prompter = createMockPrompter("deny");
34
- const result = await requestCompressionApproval(prompter);
35
-
36
- expect(result).toEqual({ approved: true });
37
- expect(prompter.prompt).not.toHaveBeenCalled();
38
- });
39
-
40
- test("uses the reserved pseudo tool name", async () => {
41
- const prompter = createMockPrompter("allow");
42
- await requestCompressionApproval(prompter);
43
-
44
- expect(prompter.prompt).toHaveBeenCalledTimes(1);
45
- const args = (prompter.prompt as ReturnType<typeof mock>).mock.calls[0];
46
- expect(args[0]).toBe(CONTEXT_OVERFLOW_TOOL_NAME);
47
- });
48
-
49
- test("passes low risk level", async () => {
50
- const prompter = createMockPrompter("allow");
51
- await requestCompressionApproval(prompter);
52
-
53
- const args = (prompter.prompt as ReturnType<typeof mock>).mock.calls[0];
54
- // riskLevel is the 3rd argument (index 2)
55
- expect(args[2]).toBe("low");
56
- });
57
-
58
- test("provides empty allowlist and scope options", async () => {
59
- const prompter = createMockPrompter("allow");
60
- await requestCompressionApproval(prompter);
61
-
62
- const args = (prompter.prompt as ReturnType<typeof mock>).mock.calls[0];
63
- // allowlistOptions (index 3) and scopeOptions (index 4)
64
- expect(args[3]).toEqual([]);
65
- expect(args[4]).toEqual([]);
66
- });
67
-
68
- test("sets persistentDecisionsAllowed to false", async () => {
69
- const prompter = createMockPrompter("allow");
70
- await requestCompressionApproval(prompter);
71
-
72
- const args = (prompter.prompt as ReturnType<typeof mock>).mock.calls[0];
73
- // persistentDecisionsAllowed is index 8
74
- expect(args[8]).toBe(false);
75
- });
76
-
77
- test("includes a description in the input", async () => {
78
- const prompter = createMockPrompter("allow");
79
- await requestCompressionApproval(prompter);
80
-
81
- const args = (prompter.prompt as ReturnType<typeof mock>).mock.calls[0];
82
- // input is the 2nd argument (index 1)
83
- const input = args[1] as Record<string, unknown>;
84
- expect(typeof input.description).toBe("string");
85
- expect((input.description as string).length).toBeGreaterThan(0);
86
- });
87
-
88
- // ── Decision mapping ──
89
-
90
- test("maps allow decision to approved: true", async () => {
91
- const prompter = createMockPrompter("allow");
92
- const result = await requestCompressionApproval(prompter);
93
- expect(result).toEqual({ approved: true });
94
- });
95
-
96
- test("maps deny decision to approved: false", async () => {
97
- const prompter = createMockPrompter("deny");
98
- const result = await requestCompressionApproval(prompter);
99
- expect(result).toEqual({ approved: false });
100
- });
101
-
102
- test("maps always_deny decision to approved: false", async () => {
103
- const prompter = createMockPrompter("always_deny");
104
- const result = await requestCompressionApproval(prompter);
105
- expect(result).toEqual({ approved: false });
106
- });
107
-
108
- test("maps always_allow decision to approved: true", async () => {
109
- const prompter = createMockPrompter("always_allow");
110
- const result = await requestCompressionApproval(prompter);
111
- expect(result).toEqual({ approved: true });
112
- });
113
-
114
- test("maps allow_10m decision to approved: true", async () => {
115
- const prompter = createMockPrompter("allow_10m");
116
- const result = await requestCompressionApproval(prompter);
117
- expect(result).toEqual({ approved: true });
118
- });
119
-
120
- test("maps allow_conversation decision to approved: true", async () => {
121
- const prompter = createMockPrompter("allow_conversation");
122
- const result = await requestCompressionApproval(prompter);
123
- expect(result).toEqual({ approved: true });
124
- });
125
-
126
- // ── Signal forwarding ──
127
-
128
- test("forwards abort signal to prompter", async () => {
129
- const controller = new AbortController();
130
- const prompter = createMockPrompter("allow");
131
-
132
- await requestCompressionApproval(prompter, {
133
- signal: controller.signal,
134
- });
135
-
136
- const args = (prompter.prompt as ReturnType<typeof mock>).mock.calls[0];
137
- // signal is index 9
138
- expect(args[9]).toBe(controller.signal);
139
- });
140
-
141
- test("works without signal option", async () => {
142
- const prompter = createMockPrompter("allow");
143
- const result = await requestCompressionApproval(prompter);
144
- expect(result).toEqual({ approved: true });
145
-
146
- const args = (prompter.prompt as ReturnType<typeof mock>).mock.calls[0];
147
- // signal should be undefined when not provided
148
- expect(args[9]).toBeUndefined();
149
- });
150
-
151
- // ── Tool name constant ──
152
-
153
- test("CONTEXT_OVERFLOW_TOOL_NAME is context_overflow_compression", () => {
154
- expect(CONTEXT_OVERFLOW_TOOL_NAME).toBe("context_overflow_compression");
155
- });
156
- });
@@ -1,178 +0,0 @@
1
- import {
2
- chmodSync,
3
- mkdirSync,
4
- readFileSync,
5
- rmSync,
6
- writeFileSync,
7
- } from "node:fs";
8
- import { tmpdir } from "node:os";
9
- import { join } from "node:path";
10
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
11
-
12
- // Set VELLUM_WORKSPACE_DIR before importing modules that use getWorkspaceDir()
13
- const testDir = join(tmpdir(), `hooks-blocking-test-${Date.now()}`);
14
- process.env.VELLUM_WORKSPACE_DIR = testDir;
15
-
16
- import { saveHooksConfig } from "../hooks/config.js";
17
- import { HookManager, resetHookManager } from "../hooks/manager.js";
18
-
19
- function createHook(
20
- hooksDir: string,
21
- name: string,
22
- events: string[],
23
- scriptContent: string,
24
- blocking = false,
25
- ): void {
26
- const hookDir = join(hooksDir, name);
27
- mkdirSync(hookDir, { recursive: true });
28
- writeFileSync(
29
- join(hookDir, "hook.json"),
30
- JSON.stringify({
31
- name,
32
- description: `Test hook ${name}`,
33
- version: "1.0.0",
34
- events,
35
- script: "run.sh",
36
- ...(blocking ? { blocking: true } : {}),
37
- }),
38
- );
39
- const scriptPath = join(hookDir, "run.sh");
40
- writeFileSync(scriptPath, scriptContent);
41
- chmodSync(scriptPath, 0o755);
42
- }
43
-
44
- describe("Blocking Hooks", () => {
45
- let hooksDir: string;
46
-
47
- beforeEach(() => {
48
- hooksDir = join(testDir, "hooks");
49
- mkdirSync(hooksDir, { recursive: true });
50
- resetHookManager();
51
- });
52
-
53
- afterEach(() => {
54
- resetHookManager();
55
- rmSync(testDir, { recursive: true, force: true });
56
- });
57
-
58
- test("blocking hook on pre-* event cancels action on non-zero exit", async () => {
59
- createHook(
60
- hooksDir,
61
- "guard",
62
- ["pre-tool-execute"],
63
- "#!/bin/bash\nexit 1",
64
- true,
65
- );
66
- saveHooksConfig({ version: 1, hooks: { guard: { enabled: true } } });
67
-
68
- const manager = new HookManager();
69
- manager.initialize();
70
-
71
- const result = await manager.trigger("pre-tool-execute", {
72
- toolName: "bash",
73
- });
74
- expect(result.blocked).toBe(true);
75
- expect(result.blockedBy).toBe("guard");
76
- });
77
-
78
- test("blocking hook on pre-* event allows action on zero exit", async () => {
79
- createHook(
80
- hooksDir,
81
- "pass-guard",
82
- ["pre-tool-execute"],
83
- "#!/bin/bash\nexit 0",
84
- true,
85
- );
86
- saveHooksConfig({ version: 1, hooks: { "pass-guard": { enabled: true } } });
87
-
88
- const manager = new HookManager();
89
- manager.initialize();
90
-
91
- const result = await manager.trigger("pre-tool-execute", {
92
- toolName: "bash",
93
- });
94
- expect(result.blocked).toBe(false);
95
- });
96
-
97
- test("non-blocking hook does not cancel even on non-zero exit", async () => {
98
- createHook(
99
- hooksDir,
100
- "logger",
101
- ["pre-tool-execute"],
102
- "#!/bin/bash\nexit 1",
103
- false,
104
- );
105
- saveHooksConfig({ version: 1, hooks: { logger: { enabled: true } } });
106
-
107
- const manager = new HookManager();
108
- manager.initialize();
109
-
110
- const result = await manager.trigger("pre-tool-execute", {
111
- toolName: "bash",
112
- });
113
- expect(result.blocked).toBe(false);
114
- });
115
-
116
- test("blocking hook on non-pre event does not cancel", async () => {
117
- createHook(
118
- hooksDir,
119
- "post-guard",
120
- ["post-tool-execute"],
121
- "#!/bin/bash\nexit 1",
122
- true,
123
- );
124
- saveHooksConfig({ version: 1, hooks: { "post-guard": { enabled: true } } });
125
-
126
- const manager = new HookManager();
127
- manager.initialize();
128
-
129
- const result = await manager.trigger("post-tool-execute", {
130
- toolName: "bash",
131
- });
132
- expect(result.blocked).toBe(false);
133
- });
134
-
135
- test("blocking hook stops subsequent hooks from running", async () => {
136
- const outputFile = join(testDir, "after-block.txt");
137
- createHook(
138
- hooksDir,
139
- "blocker",
140
- ["pre-llm-call"],
141
- "#!/bin/bash\nexit 1",
142
- true,
143
- );
144
- createHook(
145
- hooksDir,
146
- "logger",
147
- ["pre-llm-call"],
148
- `#!/bin/bash\necho "ran" > "${outputFile}"`,
149
- false,
150
- );
151
- saveHooksConfig({
152
- version: 1,
153
- hooks: {
154
- blocker: { enabled: true },
155
- logger: { enabled: true },
156
- },
157
- });
158
-
159
- const manager = new HookManager();
160
- manager.initialize();
161
-
162
- const result = await manager.trigger("pre-llm-call", {});
163
- expect(result.blocked).toBe(true);
164
- expect(result.blockedBy).toBe("blocker");
165
-
166
- // Logger should NOT have run because blocker runs first (alphabetical order)
167
- await new Promise((r) => setTimeout(r, 100));
168
- expect(() => readFileSync(outputFile, "utf-8")).toThrow();
169
- });
170
-
171
- test("trigger returns not blocked when no hooks match", async () => {
172
- const manager = new HookManager();
173
- manager.initialize();
174
-
175
- const result = await manager.trigger("pre-tool-execute", {});
176
- expect(result.blocked).toBe(false);
177
- });
178
- });
@@ -1,182 +0,0 @@
1
- import {
2
- chmodSync,
3
- cpSync,
4
- existsSync,
5
- mkdirSync,
6
- readFileSync,
7
- rmSync,
8
- writeFileSync,
9
- } from "node:fs";
10
- import { tmpdir } from "node:os";
11
- import { join } from "node:path";
12
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
13
-
14
- import { discoverHooks } from "../hooks/discovery.js";
15
-
16
- let hooksDir: string;
17
-
18
- function makeManifest(
19
- name: string,
20
- events: string[] = ["pre-llm-call"],
21
- extra: Record<string, unknown> = {},
22
- ): string {
23
- return JSON.stringify({
24
- name,
25
- description: `Test hook: ${name}`,
26
- version: "1.0.0",
27
- events,
28
- script: "run.sh",
29
- ...extra,
30
- });
31
- }
32
-
33
- function installHook(
34
- name: string,
35
- events: string[] = ["pre-llm-call"],
36
- extra: Record<string, unknown> = {},
37
- ): void {
38
- const hookDir = join(hooksDir, name);
39
- mkdirSync(hookDir, { recursive: true });
40
- writeFileSync(join(hookDir, "hook.json"), makeManifest(name, events, extra));
41
- writeFileSync(join(hookDir, "run.sh"), "#!/bin/sh\nexit 0\n", {
42
- mode: 0o755,
43
- });
44
- }
45
-
46
- describe("hooks CLI operations", () => {
47
- beforeEach(() => {
48
- hooksDir = join(
49
- tmpdir(),
50
- `hooks-cli-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
51
- );
52
- mkdirSync(hooksDir, { recursive: true });
53
- });
54
-
55
- afterEach(() => {
56
- rmSync(hooksDir, { recursive: true, force: true });
57
- });
58
-
59
- test("discoverHooks lists installed hooks", () => {
60
- installHook("hook-a", ["pre-llm-call"]);
61
- installHook("hook-b", ["post-tool-execute", "pre-tool-execute"]);
62
-
63
- const hooks = discoverHooks(hooksDir);
64
-
65
- expect(hooks).toHaveLength(2);
66
- expect(hooks[0].name).toBe("hook-a");
67
- expect(hooks[0].manifest.events).toEqual(["pre-llm-call"]);
68
- expect(hooks[1].name).toBe("hook-b");
69
- expect(hooks[1].manifest.events).toEqual([
70
- "post-tool-execute",
71
- "pre-tool-execute",
72
- ]);
73
- });
74
-
75
- test("discoverHooks returns empty when no hooks", () => {
76
- const hooks = discoverHooks(hooksDir);
77
- expect(hooks).toHaveLength(0);
78
- });
79
-
80
- test("enable/disable toggles hook config", () => {
81
- // Write a config.json in hooksDir
82
- const configPath = join(hooksDir, "config.json");
83
- writeFileSync(
84
- configPath,
85
- JSON.stringify({ version: 1, hooks: { "my-hook": { enabled: false } } }),
86
- );
87
-
88
- // Since setHookEnabled uses getHooksDir() internally, we test the lower-level functions
89
- // Read config directly
90
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
91
- expect(config.hooks["my-hook"].enabled).toBe(false);
92
-
93
- // Simulate enable
94
- config.hooks["my-hook"].enabled = true;
95
- writeFileSync(configPath, JSON.stringify(config, null, 2));
96
-
97
- const updated = JSON.parse(readFileSync(configPath, "utf-8"));
98
- expect(updated.hooks["my-hook"].enabled).toBe(true);
99
-
100
- // Simulate disable
101
- updated.hooks["my-hook"].enabled = false;
102
- writeFileSync(configPath, JSON.stringify(updated, null, 2));
103
-
104
- const final = JSON.parse(readFileSync(configPath, "utf-8"));
105
- expect(final.hooks["my-hook"].enabled).toBe(false);
106
- });
107
-
108
- test("removeHook removes config entry", () => {
109
- const configPath = join(hooksDir, "config.json");
110
- writeFileSync(
111
- configPath,
112
- JSON.stringify({
113
- version: 1,
114
- hooks: {
115
- "keep-me": { enabled: true },
116
- "remove-me": { enabled: false },
117
- },
118
- }),
119
- );
120
-
121
- // Simulate removeHook by manipulating the config directly
122
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
123
- delete config.hooks["remove-me"];
124
- writeFileSync(configPath, JSON.stringify(config, null, 2));
125
-
126
- const updated = JSON.parse(readFileSync(configPath, "utf-8"));
127
- expect(updated.hooks["keep-me"]).toBeDefined();
128
- expect(updated.hooks["remove-me"]).toBeUndefined();
129
- });
130
-
131
- test("install copies directory and creates config entry", () => {
132
- // Create a source hook
133
- const srcDir = join(hooksDir, "_source");
134
- mkdirSync(srcDir, { recursive: true });
135
- writeFileSync(
136
- join(srcDir, "hook.json"),
137
- makeManifest("test-install", ["daemon-start"]),
138
- );
139
- writeFileSync(join(srcDir, "run.sh"), "#!/bin/sh\necho installed\n", {
140
- mode: 0o755,
141
- });
142
-
143
- // Simulate install: copy to hooks dir
144
- // cpSync and chmodSync imported at top level
145
- const targetDir = join(hooksDir, "test-install");
146
- cpSync(srcDir, targetDir, { recursive: true });
147
- chmodSync(join(targetDir, "run.sh"), 0o755);
148
-
149
- // Verify files exist
150
- expect(existsSync(join(targetDir, "hook.json"))).toBe(true);
151
- expect(existsSync(join(targetDir, "run.sh"))).toBe(true);
152
-
153
- // Verify hook is discoverable
154
- const hooks = discoverHooks(hooksDir);
155
- const installed = hooks.find((h) => h.name === "test-install");
156
- expect(installed).toBeDefined();
157
- expect(installed!.manifest.events).toEqual(["daemon-start"]);
158
- });
159
-
160
- test("remove deletes hook directory", () => {
161
- installHook("to-remove");
162
- expect(existsSync(join(hooksDir, "to-remove"))).toBe(true);
163
-
164
- // Simulate remove
165
- rmSync(join(hooksDir, "to-remove"), { recursive: true, force: true });
166
-
167
- expect(existsSync(join(hooksDir, "to-remove"))).toBe(false);
168
- const hooks = discoverHooks(hooksDir);
169
- expect(hooks.find((h) => h.name === "to-remove")).toBeUndefined();
170
- });
171
-
172
- test("list shows version and events for hooks with metadata", () => {
173
- installHook("versioned-hook", ["pre-llm-call", "post-llm-call"], {
174
- version: "2.3.1",
175
- });
176
-
177
- const hooks = discoverHooks(hooksDir);
178
- expect(hooks).toHaveLength(1);
179
- expect(hooks[0].manifest.version).toBe("2.3.1");
180
- expect(hooks[0].manifest.events).toEqual(["pre-llm-call", "post-llm-call"]);
181
- });
182
- });
@@ -1,108 +0,0 @@
1
- import {
2
- existsSync,
3
- mkdirSync,
4
- readFileSync,
5
- rmSync,
6
- writeFileSync,
7
- } from "node:fs";
8
- import { tmpdir } from "node:os";
9
- import { join } from "node:path";
10
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
11
-
12
- // Set VELLUM_WORKSPACE_DIR before importing modules that use getWorkspaceDir()
13
- const testDir = join(tmpdir(), `hooks-config-test-${Date.now()}`);
14
- process.env.VELLUM_WORKSPACE_DIR = testDir;
15
-
16
- import {
17
- ensureHookInConfig,
18
- isHookEnabled,
19
- loadHooksConfig,
20
- saveHooksConfig,
21
- setHookEnabled,
22
- } from "../hooks/config.js";
23
-
24
- describe("Hooks Config", () => {
25
- beforeEach(() => {
26
- const hooksDir = join(testDir, "hooks");
27
- mkdirSync(hooksDir, { recursive: true });
28
- });
29
-
30
- afterEach(() => {
31
- rmSync(testDir, { recursive: true, force: true });
32
- });
33
-
34
- test("loadHooksConfig returns defaults when no config file exists", () => {
35
- const config = loadHooksConfig();
36
- expect(config.version).toBe(1);
37
- expect(config.hooks).toEqual({});
38
- });
39
-
40
- test("loadHooksConfig reads existing config", () => {
41
- const configPath = join(testDir, "hooks", "config.json");
42
- writeFileSync(
43
- configPath,
44
- JSON.stringify({
45
- version: 1,
46
- hooks: { "my-hook": { enabled: true } },
47
- }),
48
- );
49
-
50
- const config = loadHooksConfig();
51
- expect(config.hooks["my-hook"].enabled).toBe(true);
52
- });
53
-
54
- test("loadHooksConfig returns defaults for invalid JSON", () => {
55
- const configPath = join(testDir, "hooks", "config.json");
56
- writeFileSync(configPath, "NOT VALID JSON {{{");
57
-
58
- const config = loadHooksConfig();
59
- expect(config.version).toBe(1);
60
- expect(config.hooks).toEqual({});
61
- });
62
-
63
- test("loadHooksConfig returns defaults for invalid structure", () => {
64
- const configPath = join(testDir, "hooks", "config.json");
65
- writeFileSync(configPath, JSON.stringify({ foo: "bar" }));
66
-
67
- const config = loadHooksConfig();
68
- expect(config.version).toBe(1);
69
- expect(config.hooks).toEqual({});
70
- });
71
-
72
- test("saveHooksConfig writes config to disk", () => {
73
- const config = { version: 1, hooks: { "test-hook": { enabled: true } } };
74
- saveHooksConfig(config);
75
-
76
- const configPath = join(testDir, "hooks", "config.json");
77
- expect(existsSync(configPath)).toBe(true);
78
- const read = JSON.parse(readFileSync(configPath, "utf-8"));
79
- expect(read.hooks["test-hook"].enabled).toBe(true);
80
- });
81
-
82
- test("isHookEnabled returns false for unknown hook", () => {
83
- expect(isHookEnabled("nonexistent")).toBe(false);
84
- });
85
-
86
- test("setHookEnabled enables a hook", () => {
87
- setHookEnabled("my-hook", true);
88
- expect(isHookEnabled("my-hook")).toBe(true);
89
- });
90
-
91
- test("setHookEnabled disables a hook", () => {
92
- setHookEnabled("my-hook", true);
93
- setHookEnabled("my-hook", false);
94
- expect(isHookEnabled("my-hook")).toBe(false);
95
- });
96
-
97
- test("ensureHookInConfig adds hook if missing", () => {
98
- ensureHookInConfig("new-hook", { enabled: false });
99
- const config = loadHooksConfig();
100
- expect(config.hooks["new-hook"]).toEqual({ enabled: false });
101
- });
102
-
103
- test("ensureHookInConfig does not overwrite existing hook", () => {
104
- setHookEnabled("existing", true);
105
- ensureHookInConfig("existing", { enabled: false });
106
- expect(isHookEnabled("existing")).toBe(true);
107
- });
108
- });