@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,100 +0,0 @@
1
- import { writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
-
4
- import { ensureDir, readTextFileSync } from "../util/fs.js";
5
- import { getLogger } from "../util/logger.js";
6
- import { getWorkspaceHooksDir } from "../util/platform.js";
7
- import type { HookConfig, HookConfigEntry, HookManifest } from "./types.js";
8
-
9
- const log = getLogger("hooks-config");
10
-
11
- const HOOKS_CONFIG_VERSION = 1;
12
-
13
- function getConfigPath(): string {
14
- return join(getWorkspaceHooksDir(), "config.json");
15
- }
16
-
17
- export function loadHooksConfig(): HookConfig {
18
- const configPath = getConfigPath();
19
- const raw = readTextFileSync(configPath);
20
- if (raw == null) {
21
- return { version: HOOKS_CONFIG_VERSION, hooks: {} };
22
- }
23
-
24
- try {
25
- const parsed = JSON.parse(raw) as HookConfig;
26
- if (
27
- typeof parsed.version !== "number" ||
28
- typeof parsed.hooks !== "object" ||
29
- parsed.hooks == null
30
- ) {
31
- log.warn({ configPath }, "Invalid hooks config, using defaults");
32
- return { version: HOOKS_CONFIG_VERSION, hooks: {} };
33
- }
34
- return parsed;
35
- } catch (err) {
36
- log.warn(
37
- { err, configPath },
38
- "Failed to read hooks config, using defaults",
39
- );
40
- return { version: HOOKS_CONFIG_VERSION, hooks: {} };
41
- }
42
- }
43
-
44
- export function saveHooksConfig(config: HookConfig): void {
45
- const configPath = getConfigPath();
46
- ensureDir(dirname(configPath));
47
- writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
48
- }
49
-
50
- export function isHookEnabled(hookName: string): boolean {
51
- const config = loadHooksConfig();
52
- return config.hooks[hookName]?.enabled ?? false;
53
- }
54
-
55
- export function setHookEnabled(hookName: string, enabled: boolean): void {
56
- const config = loadHooksConfig();
57
- config.hooks[hookName] = { ...config.hooks[hookName], enabled };
58
- saveHooksConfig(config);
59
- }
60
-
61
- export function ensureHookInConfig(
62
- hookName: string,
63
- entry: HookConfigEntry,
64
- ): void {
65
- const config = loadHooksConfig();
66
- if (hookName in config.hooks) return;
67
- config.hooks[hookName] = entry;
68
- saveHooksConfig(config);
69
- }
70
-
71
- export function removeHook(hookName: string): void {
72
- const config = loadHooksConfig();
73
- delete config.hooks[hookName];
74
- saveHooksConfig(config);
75
- }
76
-
77
- /**
78
- * Get merged settings for a hook. Manifest defaults are used as the base,
79
- * then user overrides from config.json are applied on top.
80
- */
81
- export function getHookSettings(
82
- hookName: string,
83
- manifest: HookManifest,
84
- ): Record<string, unknown> {
85
- // Start with defaults from manifest schema
86
- const defaults: Record<string, unknown> = {};
87
- if (manifest.settingsSchema) {
88
- for (const [key, schema] of Object.entries(manifest.settingsSchema)) {
89
- if (schema.default !== undefined) {
90
- defaults[key] = schema.default;
91
- }
92
- }
93
- }
94
-
95
- // Merge user overrides from config
96
- const config = loadHooksConfig();
97
- const userSettings = config.hooks[hookName]?.settings ?? {};
98
-
99
- return { ...defaults, ...userSettings };
100
- }
@@ -1,135 +0,0 @@
1
- import { type Dirent, readdirSync, readFileSync } from "node:fs";
2
- import { join, relative, resolve } from "node:path";
3
-
4
- import { pathExists } from "../util/fs.js";
5
- import { getLogger } from "../util/logger.js";
6
- import { getWorkspaceHooksDir } from "../util/platform.js";
7
- import { loadHooksConfig } from "./config.js";
8
- import type { DiscoveredHook, HookManifest } from "./types.js";
9
-
10
- const log = getLogger("hooks-discovery");
11
-
12
- const VALID_EVENTS = new Set<string>([
13
- "daemon-start",
14
- "daemon-stop",
15
- "conversation-start",
16
- "conversation-end",
17
- // Legacy aliases — existing user hooks may still reference the old names.
18
- "session-start",
19
- "session-end",
20
- "pre-llm-call",
21
- "post-llm-call",
22
- "pre-tool-execute",
23
- "post-tool-execute",
24
- "permission-request",
25
- "permission-resolve",
26
- "pre-message",
27
- "post-message",
28
- "on-error",
29
- ]);
30
-
31
- /**
32
- * Validates the core manifest fields required for discovery.
33
- * `description` and `version` are optional here so that legacy hook manifests
34
- * (created before those fields were added) continue to be discovered.
35
- */
36
- export function isValidManifest(manifest: unknown): manifest is HookManifest {
37
- if (typeof manifest !== "object" || manifest == null) return false;
38
- const m = manifest as Record<string, unknown>;
39
- if (typeof m.name !== "string" || !m.name) return false;
40
- if (typeof m.script !== "string" || !m.script) return false;
41
- if (!Array.isArray(m.events) || m.events.length === 0) return false;
42
- for (const e of m.events) {
43
- if (typeof e !== "string" || !VALID_EVENTS.has(e)) return false;
44
- }
45
- // Optional fields: allow if present but must be strings
46
- if (m.description !== undefined && typeof m.description !== "string")
47
- return false;
48
- if (m.version !== undefined && typeof m.version !== "string") return false;
49
- return true;
50
- }
51
-
52
- /**
53
- * Stricter validation for installing new hooks.
54
- * Requires `description` and `version` in addition to the core fields.
55
- */
56
- export function isValidInstallManifest(
57
- manifest: unknown,
58
- ): manifest is HookManifest & { description: string; version: string } {
59
- if (!isValidManifest(manifest)) return false;
60
- if (typeof manifest.description !== "string" || !manifest.description)
61
- return false;
62
- if (typeof manifest.version !== "string" || !manifest.version) return false;
63
- return true;
64
- }
65
-
66
- export function discoverHooks(hooksDir?: string): DiscoveredHook[] {
67
- const dir = hooksDir ?? getWorkspaceHooksDir();
68
- if (!pathExists(dir)) return [];
69
-
70
- const config = loadHooksConfig();
71
- const hooks: DiscoveredHook[] = [];
72
-
73
- let entries: Dirent[];
74
- try {
75
- entries = readdirSync(dir, { withFileTypes: true }) as Dirent[];
76
- } catch (err) {
77
- log.warn({ err, dir }, "Failed to read hooks directory");
78
- return [];
79
- }
80
-
81
- for (const entry of entries) {
82
- if (!entry.isDirectory()) continue;
83
-
84
- const hookDir = join(dir, entry.name);
85
- const manifestPath = join(hookDir, "hook.json");
86
- if (!pathExists(manifestPath)) continue;
87
-
88
- let manifest: unknown;
89
- try {
90
- manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
91
- } catch (err) {
92
- log.warn({ err, hookDir }, "Failed to parse hook manifest");
93
- continue;
94
- }
95
-
96
- if (!isValidManifest(manifest)) {
97
- log.warn({ hookDir }, "Invalid hook manifest, skipping");
98
- continue;
99
- }
100
-
101
- const scriptPath = resolve(hookDir, manifest.script);
102
- const rel = relative(hookDir, scriptPath);
103
- // Normalize backslashes so Windows-style traversal (e.g. `..\\..\\evil.js`) is
104
- // also caught. This project targets macOS, but we check for defense in depth.
105
- const normalizedRel = rel.replaceAll("\\", "/");
106
- if (
107
- normalizedRel.startsWith("../") ||
108
- normalizedRel === ".." ||
109
- resolve(hookDir, rel) !== scriptPath
110
- ) {
111
- log.warn(
112
- { hookDir, script: manifest.script },
113
- "Hook script path traversal detected, skipping",
114
- );
115
- continue;
116
- }
117
- if (!pathExists(scriptPath)) {
118
- log.warn(
119
- { hookDir, script: manifest.script },
120
- "Hook script not found, skipping",
121
- );
122
- continue;
123
- }
124
-
125
- hooks.push({
126
- name: entry.name,
127
- dir: hookDir,
128
- manifest,
129
- scriptPath,
130
- enabled: config.hooks[entry.name]?.enabled ?? false,
131
- });
132
- }
133
-
134
- return hooks.sort((a, b) => a.name.localeCompare(b.name));
135
- }
@@ -1,179 +0,0 @@
1
- import { type FSWatcher, watch } from "node:fs";
2
-
3
- import { getIsContainerized } from "../config/env-registry.js";
4
- import { Debouncer } from "../util/debounce.js";
5
- import { pathExists } from "../util/fs.js";
6
- import { getLogger } from "../util/logger.js";
7
- import { getWorkspaceHooksDir } from "../util/platform.js";
8
- import { discoverHooks } from "./discovery.js";
9
- import { runHookScript } from "./runner.js";
10
- import type {
11
- DiscoveredHook,
12
- HookEventData,
13
- HookEventName,
14
- HookTriggerResult,
15
- } from "./types.js";
16
-
17
- const log = getLogger("hooks-manager");
18
-
19
- /**
20
- * Legacy event name aliases so existing user hooks that reference the old
21
- * session-based names still fire when the corresponding conversation event
22
- * is triggered.
23
- */
24
- const LEGACY_EVENT_ALIASES: Partial<Record<HookEventName, HookEventName[]>> = {
25
- "conversation-start": ["session-start"],
26
- "conversation-end": ["session-end"],
27
- };
28
-
29
- export class HookManager {
30
- private hooks: DiscoveredHook[] = [];
31
- private eventIndex = new Map<HookEventName, DiscoveredHook[]>();
32
- private watcher: FSWatcher | null = null;
33
- private readonly debouncer = new Debouncer(500);
34
-
35
- initialize(): void {
36
- if (getIsContainerized()) {
37
- log.info("Hooks disabled in containerized mode");
38
- return;
39
- }
40
- this.hooks = discoverHooks();
41
- this.buildEventIndex();
42
- const enabled = this.hooks.filter((h) => h.enabled).length;
43
- if (this.hooks.length > 0) {
44
- log.info({ enabled, total: this.hooks.length }, "Hooks discovered");
45
- }
46
- }
47
-
48
- private buildEventIndex(): void {
49
- this.eventIndex.clear();
50
- for (const hook of this.hooks) {
51
- if (!hook.enabled) continue;
52
- for (const event of hook.manifest.events) {
53
- const list = this.eventIndex.get(event) ?? [];
54
- list.push(hook);
55
- this.eventIndex.set(event, list);
56
- }
57
- }
58
- // Sort alphabetically by name for deterministic ordering
59
- for (const [, list] of this.eventIndex) {
60
- list.sort((a, b) => a.name.localeCompare(b.name));
61
- }
62
- }
63
-
64
- async trigger(
65
- event: HookEventName,
66
- data: Record<string, unknown>,
67
- ): Promise<HookTriggerResult> {
68
- // Collect hooks registered under the canonical event name and any
69
- // legacy aliases (e.g. session-start -> conversation-start).
70
- const primaryHooks = this.eventIndex.get(event) ?? [];
71
- const legacyAliases = LEGACY_EVENT_ALIASES[event] ?? [];
72
- const aliasHooks = legacyAliases.flatMap(
73
- (alias) => this.eventIndex.get(alias) ?? [],
74
- );
75
- // Deduplicate in case a hook subscribes to both old and new names.
76
- const seen = new Set<string>();
77
- const hooks: DiscoveredHook[] = [];
78
- for (const h of [...primaryHooks, ...aliasHooks]) {
79
- if (!seen.has(h.name)) {
80
- seen.add(h.name);
81
- hooks.push(h);
82
- }
83
- }
84
- if (hooks.length === 0) return { blocked: false };
85
-
86
- const isPreEvent = event.startsWith("pre-");
87
- const eventData: HookEventData = { ...data, event };
88
-
89
- for (const hook of hooks) {
90
- try {
91
- const result = await runHookScript(hook, eventData);
92
- if (result.exitCode != null && result.exitCode !== 0) {
93
- // Blocking hooks on pre-* events cancel the action
94
- if (isPreEvent && hook.manifest.blocking) {
95
- log.info(
96
- { hook: hook.name, event, exitCode: result.exitCode },
97
- "Blocking hook rejected action",
98
- );
99
- return { blocked: true, blockedBy: hook.name };
100
- }
101
- log.warn(
102
- { hook: hook.name, event, exitCode: result.exitCode },
103
- "Hook exited with non-zero code",
104
- );
105
- }
106
- } catch (err) {
107
- log.warn({ err, hook: hook.name, event }, "Hook execution failed");
108
- }
109
- }
110
-
111
- return { blocked: false };
112
- }
113
-
114
- reload(): void {
115
- if (getIsContainerized()) return;
116
- this.hooks = discoverHooks();
117
- this.buildEventIndex();
118
- const enabled = this.hooks.filter((h) => h.enabled).length;
119
- log.info({ enabled, total: this.hooks.length }, "Hooks reloaded");
120
- }
121
-
122
- watch(): void {
123
- if (getIsContainerized()) return;
124
- const hooksDir = getWorkspaceHooksDir();
125
- if (!pathExists(hooksDir)) return;
126
-
127
- this.stopWatching();
128
-
129
- try {
130
- this.watcher = watch(
131
- hooksDir,
132
- { recursive: true },
133
- (_eventType, filename) => {
134
- this.debouncer.schedule(() => {
135
- log.info(
136
- { filename: String(filename ?? "") },
137
- "Hooks directory changed, reloading",
138
- );
139
- this.reload();
140
- });
141
- },
142
- );
143
- log.info({ dir: hooksDir }, "Watching hooks directory for changes");
144
- } catch (err) {
145
- log.warn(
146
- { err, dir: hooksDir },
147
- "Failed to watch hooks directory. Hot-reload will be unavailable.",
148
- );
149
- }
150
- }
151
-
152
- stopWatching(): void {
153
- this.debouncer.cancel();
154
- if (this.watcher) {
155
- this.watcher.close();
156
- this.watcher = null;
157
- }
158
- }
159
-
160
- getDiscoveredHooks(): DiscoveredHook[] {
161
- return [...this.hooks];
162
- }
163
- }
164
-
165
- let instance: HookManager | null = null;
166
-
167
- export function getHookManager(): HookManager {
168
- if (!instance) {
169
- instance = new HookManager();
170
- instance.initialize();
171
- }
172
- return instance;
173
- }
174
-
175
- /** Reset the singleton (for testing) */
176
- export function resetHookManager(): void {
177
- instance?.stopWatching();
178
- instance = null;
179
- }
@@ -1,117 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { homedir } from "node:os";
3
- import { extname, join } from "node:path";
4
-
5
- import { ensureBun } from "../util/bun-runtime.js";
6
- import { getWorkspaceDir } from "../util/platform.js";
7
- import { getHookSettings } from "./config.js";
8
- import type { DiscoveredHook, HookEventData } from "./types.js";
9
-
10
- async function getSpawnArgs(
11
- scriptPath: string,
12
- ): Promise<{ command: string; args: string[] }> {
13
- const ext = extname(scriptPath);
14
- if (ext === ".ts") {
15
- const bunPath = await ensureBun();
16
- return { command: bunPath, args: ["run", scriptPath] };
17
- }
18
- return { command: scriptPath, args: [] };
19
- }
20
-
21
- export interface HookRunResult {
22
- exitCode: number | null;
23
- stdout: string;
24
- stderr: string;
25
- }
26
-
27
- export async function runHookScript(
28
- hook: DiscoveredHook,
29
- eventData: HookEventData,
30
- options?: { timeoutMs?: number },
31
- ): Promise<HookRunResult> {
32
- const timeoutMs = options?.timeoutMs ?? 5000;
33
-
34
- let spawnResult: { command: string; args: string[] };
35
- try {
36
- spawnResult = await getSpawnArgs(hook.scriptPath);
37
- } catch (err) {
38
- return { exitCode: null, stdout: "", stderr: (err as Error).message };
39
- }
40
- const { command, args } = spawnResult;
41
-
42
- return new Promise<HookRunResult>((resolve) => {
43
- const child = spawn(command, args, {
44
- cwd: hook.dir,
45
- env: {
46
- ...process.env,
47
- VELLUM_HOOK_EVENT: eventData.event,
48
- VELLUM_HOOK_NAME: hook.name,
49
- // @deprecated — usage of VELLUM_ROOT_DIR by hook scripts is deprecated.
50
- // Removing this requires an LLM-based migration or declarative migration
51
- // file to update existing user-authored hooks to use VELLUM_WORKSPACE_DIR.
52
- //
53
- // VELLUM_ROOT_DIR is kept at the legacy `~/.vellum` value even when
54
- // vellumRoot() resolves per-instance via BASE_DATA_DIR. User hook
55
- // scripts written against this env var expected the legacy path;
56
- // changing it would be a silent contract break. Hooks that need the
57
- // per-instance root should read BASE_DATA_DIR themselves or use the
58
- // new env vars the environment-layout plan adds.
59
- VELLUM_ROOT_DIR: join(homedir(), ".vellum"),
60
- VELLUM_WORKSPACE_DIR: getWorkspaceDir(),
61
- VELLUM_HOOK_SETTINGS: JSON.stringify(
62
- getHookSettings(hook.name, hook.manifest),
63
- ),
64
- },
65
- stdio: ["pipe", "pipe", "pipe"],
66
- });
67
-
68
- let stdout = "";
69
- let stderr = "";
70
- let settled = false;
71
-
72
- child.stdout.on("data", (chunk: Buffer) => {
73
- stdout += chunk.toString();
74
- });
75
- child.stderr.on("data", (chunk: Buffer) => {
76
- stderr += chunk.toString();
77
- });
78
-
79
- const timer = setTimeout(() => {
80
- if (settled) return;
81
- settled = true;
82
- child.kill("SIGTERM");
83
- // Give the process a short grace period to exit after SIGTERM, then SIGKILL
84
- const killTimer = setTimeout(() => {
85
- child.kill("SIGKILL");
86
- }, 2000);
87
- child.once("close", () => {
88
- clearTimeout(killTimer);
89
- resolve({
90
- exitCode: null,
91
- stdout,
92
- stderr: stderr + "\nHook timed out",
93
- });
94
- });
95
- }, timeoutMs);
96
-
97
- child.on("close", (code) => {
98
- if (settled) return;
99
- settled = true;
100
- clearTimeout(timer);
101
- resolve({ exitCode: code, stdout, stderr });
102
- });
103
-
104
- child.on("error", (err) => {
105
- if (settled) return;
106
- settled = true;
107
- clearTimeout(timer);
108
- resolve({ exitCode: null, stdout, stderr: stderr + "\n" + err.message });
109
- });
110
-
111
- // Suppress unhandled EPIPE errors if the child exits before we finish writing
112
- child.stdin.on("error", () => {});
113
- // Write event data to stdin and close
114
- child.stdin.write(JSON.stringify(eventData));
115
- child.stdin.end();
116
- });
117
- }
@@ -1,77 +0,0 @@
1
- import {
2
- chmodSync,
3
- cpSync,
4
- type Dirent,
5
- readdirSync,
6
- readFileSync,
7
- rmSync,
8
- } from "node:fs";
9
- import { join } from "node:path";
10
-
11
- import { resolveBundledDir } from "../util/bundled-asset.js";
12
- import { pathExists } from "../util/fs.js";
13
- import { getLogger } from "../util/logger.js";
14
- import { getWorkspaceHooksDir } from "../util/platform.js";
15
- import { ensureHookInConfig } from "./config.js";
16
-
17
- const log = getLogger("hooks-templates");
18
-
19
- /**
20
- * Install bundled hook templates into the user's hooks directory.
21
- * Templates are copied from `assistant/hook-templates/` to `~/.vellum/hooks/`.
22
- * - Never overwrites existing hooks (user modifications are preserved).
23
- * - Newly installed hooks are disabled by default.
24
- */
25
- export function installTemplates(): void {
26
- const templatesDir = resolveBundledDir(
27
- import.meta.dirname ?? __dirname,
28
- "../../hook-templates",
29
- "hook-templates",
30
- );
31
- if (!pathExists(templatesDir)) return;
32
-
33
- const hooksDir = getWorkspaceHooksDir();
34
- const entries = readdirSync(templatesDir, {
35
- withFileTypes: true,
36
- }) as Dirent[];
37
-
38
- for (const entry of entries) {
39
- if (!entry.isDirectory()) continue;
40
-
41
- const targetDir = join(hooksDir, entry.name);
42
- if (pathExists(targetDir)) continue; // Never overwrite user hooks
43
-
44
- try {
45
- // Copy template directory
46
- cpSync(join(templatesDir, entry.name), targetDir, { recursive: true });
47
-
48
- // Make script executable
49
- const manifestPath = join(targetDir, "hook.json");
50
- if (pathExists(manifestPath)) {
51
- const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
52
- if (manifest.script) {
53
- chmodSync(join(targetDir, manifest.script), 0o755);
54
- }
55
- }
56
-
57
- // Add to config (disabled by default)
58
- ensureHookInConfig(entry.name, { enabled: false });
59
-
60
- log.info(
61
- { hook: entry.name },
62
- "Installed hook template (disabled by default)",
63
- );
64
- } catch (err) {
65
- // Clean up partially-copied directory so the next restart can retry
66
- try {
67
- rmSync(targetDir, { recursive: true, force: true });
68
- } catch (e) {
69
- log.debug(
70
- { err: e },
71
- "Cleanup of partial hook template directory failed",
72
- );
73
- }
74
- log.warn({ err, hook: entry.name }, "Failed to install hook template");
75
- }
76
- }
77
- }
@@ -1,75 +0,0 @@
1
- export type HookEventName =
2
- // Daemon lifecycle
3
- | "daemon-start"
4
- | "daemon-stop"
5
- // Conversation lifecycle
6
- | "conversation-start"
7
- | "conversation-end"
8
- // Legacy aliases (backwards compat for existing user hooks)
9
- | "session-start"
10
- | "session-end"
11
- // LLM call lifecycle
12
- | "pre-llm-call"
13
- | "post-llm-call"
14
- // Tool execution lifecycle
15
- | "pre-tool-execute"
16
- | "post-tool-execute"
17
- // Permission lifecycle
18
- | "permission-request"
19
- | "permission-resolve"
20
- // Message lifecycle
21
- | "pre-message"
22
- | "post-message"
23
- // Error
24
- | "on-error";
25
-
26
- export interface HookSettingsSchemaEntry {
27
- type: string;
28
- default?: unknown;
29
- description?: string;
30
- }
31
-
32
- export interface HookManifest {
33
- name: string;
34
- description?: string;
35
- version?: string;
36
- events: HookEventName[];
37
- script: string;
38
- /** When true, non-zero exit from this hook cancels pre-* actions. Default false. */
39
- blocking?: boolean;
40
- /** Schema for per-hook settings with defaults and descriptions. */
41
- settingsSchema?: Record<string, HookSettingsSchemaEntry>;
42
- }
43
-
44
- export interface HookConfigEntry {
45
- enabled: boolean;
46
- /** Per-hook user settings that override manifest defaults. */
47
- settings?: Record<string, unknown>;
48
- }
49
-
50
- export interface HookConfig {
51
- version: number;
52
- hooks: Record<string, HookConfigEntry>;
53
- }
54
-
55
- export interface DiscoveredHook {
56
- name: string;
57
- /** Absolute path to hook directory */
58
- dir: string;
59
- manifest: HookManifest;
60
- /** Absolute path to the script */
61
- scriptPath: string;
62
- enabled: boolean;
63
- }
64
-
65
- export interface HookEventData {
66
- event: HookEventName;
67
- [key: string]: unknown;
68
- }
69
-
70
- export interface HookTriggerResult {
71
- /** True if a blocking hook rejected the action (non-zero exit). */
72
- blocked: boolean;
73
- /** Name of the hook that blocked, if any. */
74
- blockedBy?: string;
75
- }