@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
@@ -36,6 +36,7 @@ import {
36
36
  handleVoiceWebhook,
37
37
  } from "../calls/twilio-routes.js";
38
38
  import { parseChannelId } from "../channels/types.js";
39
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
39
40
  import {
40
41
  getGatewayInternalBaseUrl,
41
42
  hasUngatedHttpAuthDisabled,
@@ -53,7 +54,10 @@ import {
53
54
  type SignalType,
54
55
  } from "../memory/conversation-attention-store.js";
55
56
  import {
57
+ addMessage,
56
58
  type ConversationRow,
59
+ createConversation,
60
+ deleteConversation,
57
61
  forkConversation as forkConversationInStore,
58
62
  getConversation,
59
63
  getDisplayMetaForConversations,
@@ -62,11 +66,13 @@ import { resolveConversationId } from "../memory/conversation-key-store.js";
62
66
  import {
63
67
  countConversations,
64
68
  listConversations,
69
+ listConversationsByTitlePrefix,
65
70
  listPinnedConversations,
66
71
  } from "../memory/conversation-queries.js";
67
72
  import type { ExternalConversationBinding } from "../memory/external-conversation-store.js";
68
73
  import * as externalConversationStore from "../memory/external-conversation-store.js";
69
74
  import { listGroups } from "../memory/group-crud.js";
75
+ import { enqueueMemoryJob } from "../memory/jobs-store.js";
70
76
  import { resolveStreamingTranscriber } from "../providers/speech-to-text/resolve.js";
71
77
  import {
72
78
  consumeCallback,
@@ -206,6 +212,7 @@ import {
206
212
  handlePairingStatus,
207
213
  pairingRouteDefinitions,
208
214
  } from "./routes/pairing-routes.js";
215
+ import { playgroundRouteDefinitions } from "./routes/playground/index.js";
209
216
  import { profilerRouteDefinitions } from "./routes/profiler-routes.js";
210
217
  import { recordingRouteDefinitions } from "./routes/recording-routes.js";
211
218
  import { scheduleRouteDefinitions } from "./routes/schedule-routes.js";
@@ -223,7 +230,6 @@ import { ttsRouteDefinitions } from "./routes/tts-routes.js";
223
230
  import { upgradeBroadcastRouteDefinitions } from "./routes/upgrade-broadcast-routes.js";
224
231
  import { usageRouteDefinitions } from "./routes/usage-routes.js";
225
232
  import { userRouteDefinitions } from "./routes/user-routes.js";
226
- import { watchRouteDefinitions } from "./routes/watch-routes.js";
227
233
  import { workItemRouteDefinitions } from "./routes/work-items-routes.js";
228
234
  import { workspaceCommitRouteDefinitions } from "./routes/workspace-commit-routes.js";
229
235
  import { workspaceRouteDefinitions } from "./routes/workspace-routes.js";
@@ -356,7 +362,6 @@ export class RuntimeHttpServer {
356
362
  private getSkillContext?: RuntimeHttpServerOptions["getSkillContext"];
357
363
  private conversationManagementDeps?: RuntimeHttpServerOptions["conversationManagementDeps"];
358
364
  private getModelSetContext?: RuntimeHttpServerOptions["getModelSetContext"];
359
- private getWatchDeps?: RuntimeHttpServerOptions["getWatchDeps"];
360
365
  private getRecordingDeps?: RuntimeHttpServerOptions["getRecordingDeps"];
361
366
  private getCesClient?: RuntimeHttpServerOptions["getCesClient"];
362
367
  private onProviderCredentialsChanged?: RuntimeHttpServerOptions["onProviderCredentialsChanged"];
@@ -381,7 +386,6 @@ export class RuntimeHttpServer {
381
386
  this.getSkillContext = options.getSkillContext;
382
387
  this.conversationManagementDeps = options.conversationManagementDeps;
383
388
  this.getModelSetContext = options.getModelSetContext;
384
- this.getWatchDeps = options.getWatchDeps;
385
389
  this.getRecordingDeps = options.getRecordingDeps;
386
390
  this.getCesClient = options.getCesClient;
387
391
  this.onProviderCredentialsChanged = options.onProviderCredentialsChanged;
@@ -1995,6 +1999,76 @@ export class RuntimeHttpServer {
1995
1999
  suggestionInFlight: this.suggestionInFlight,
1996
2000
  getHeartbeatService: this.getHeartbeatService,
1997
2001
  }),
2002
+ ...playgroundRouteDefinitions({
2003
+ getConversationById: async (id) => {
2004
+ // Gate on DB existence first so genuinely-missing IDs return
2005
+ // `undefined` (preserving the route handlers' 404 path) rather
2006
+ // than triggering `getOrCreateConversation`'s create branch and
2007
+ // masking the not-found case. For existing-but-not-loaded rows
2008
+ // (e.g. freshly seeded by `POST /playground/seed-conversation`),
2009
+ // hydrate the in-memory `Conversation` on demand so conv-scoped
2010
+ // playground routes work without first opening the conversation
2011
+ // in the main window.
2012
+ if (!getConversation(id)) return undefined;
2013
+ const sendDeps = this.sendMessageDeps;
2014
+ if (!sendDeps) {
2015
+ // Fall back to the in-memory active map when the daemon hasn't
2016
+ // wired the hydration-capable accessor (e.g. unit tests).
2017
+ const s = this.findConversation?.(id);
2018
+ if (!s || !("abort" in s)) return undefined;
2019
+ return s as import("../daemon/conversation.js").Conversation;
2020
+ }
2021
+ return sendDeps.getOrCreateConversation(id);
2022
+ },
2023
+ isPlaygroundEnabled: () =>
2024
+ isAssistantFeatureFlagEnabled("compaction-playground", getConfig()),
2025
+ listConversationsByTitlePrefix: (prefix) =>
2026
+ listConversationsByTitlePrefix(prefix),
2027
+ deleteConversationById: (id) => {
2028
+ // Existence check first so we can report `false` for missing rows
2029
+ // — `deleteConversation` always returns a result object even when
2030
+ // no row matched.
2031
+ if (!getConversation(id)) return false;
2032
+ // Mirror the canonical DELETE /v1/conversations/:id handler in
2033
+ // conversation-management-routes.ts: tear down the in-memory
2034
+ // Conversation first (so a running agent loop can't write to a
2035
+ // deleted row and trip FK constraints), then drop the DB row,
2036
+ // then enqueue Qdrant vector cleanup for the returned segment
2037
+ // and summary IDs. Without this, seeded-then-deleted playground
2038
+ // conversations leak vectors and zombie Conversation objects.
2039
+ if (this.findConversation?.(id)) {
2040
+ this.conversationManagementDeps?.destroyConversation(id);
2041
+ }
2042
+ const deleted = deleteConversation(id);
2043
+ for (const segId of deleted.segmentIds) {
2044
+ enqueueMemoryJob("delete_qdrant_vectors", {
2045
+ targetType: "segment",
2046
+ targetId: segId,
2047
+ });
2048
+ }
2049
+ for (const summaryId of deleted.deletedSummaryIds) {
2050
+ enqueueMemoryJob("delete_qdrant_vectors", {
2051
+ targetType: "summary",
2052
+ targetId: summaryId,
2053
+ });
2054
+ }
2055
+ return true;
2056
+ },
2057
+ createConversation: async (title) => {
2058
+ const row = createConversation({ title });
2059
+ return { id: row.id };
2060
+ },
2061
+ addMessage: async (conversationId, role, contentJson, options) => {
2062
+ const persisted = await addMessage(
2063
+ conversationId,
2064
+ role,
2065
+ contentJson,
2066
+ undefined,
2067
+ options,
2068
+ );
2069
+ return { id: persisted.id };
2070
+ },
2071
+ }),
1998
2072
  ...globalSearchRouteDefinitions(),
1999
2073
  ...approvalRouteDefinitions(),
2000
2074
  ...hostBashRouteDefinitions(),
@@ -2032,11 +2106,6 @@ export class RuntimeHttpServer {
2032
2106
  ...oauthAppsRouteDefinitions(),
2033
2107
  ...attachmentRouteDefinitions(),
2034
2108
 
2035
- ...(this.getWatchDeps
2036
- ? watchRouteDefinitions({
2037
- getWatchDeps: this.getWatchDeps,
2038
- })
2039
- : []),
2040
2109
  ...(this.getRecordingDeps
2041
2110
  ? recordingRouteDefinitions({
2042
2111
  getRecordingDeps: this.getRecordingDeps,
@@ -248,8 +248,6 @@ export interface RuntimeHttpServerOptions {
248
248
  conversationManagementDeps?: ConversationManagementDeps;
249
249
  /** Lazy factory for model config set context (conversation eviction, config reload suppression). */
250
250
  getModelSetContext?: () => import("../daemon/handlers/config-model.js").ModelSetContext;
251
- /** Provider for watch observation dependencies (watch routes). */
252
- getWatchDeps?: () => import("./routes/watch-routes.js").WatchDeps;
253
251
  /** Provider for recording dependencies (recording routes). */
254
252
  getRecordingDeps?: () => import("./routes/recording-routes.js").RecordingDeps;
255
253
  /** Accessor for the CES client, used to push API key updates to CES after hatch. */
@@ -4,7 +4,7 @@
4
4
  * A .vbundle is a gzip-compressed tar archive containing:
5
5
  * - manifest.json: metadata with schema_version, checksums, and bundle info
6
6
  * - workspace/: the entire ~/.vellum/workspace/ directory tree (DB, config,
7
- * skills, hooks, prompts, attachments, etc.) — excluding large/regenerable
7
+ * skills, prompts, attachments, etc.) — excluding large/regenerable
8
8
  * dirs (embedding-models/, data/qdrant/)
9
9
  * - trust/trust.json: trust rules (optional, lives in protected/ outside workspace)
10
10
  */
@@ -429,15 +429,6 @@ export interface BuildExportVBundleOptions {
429
429
  description?: string;
430
430
  /** Absolute path to trust.json. If provided and the file exists, it is included in the archive. */
431
431
  trustPath?: string;
432
- /**
433
- * Absolute path to the hooks directory. Previously hooks lived outside the
434
- * workspace at ~/.vellum/hooks/ and needed explicit inclusion. Now hooks
435
- * live under workspace (~/.vellum/workspace/hooks/) and are included in
436
- * the workspace walk. Only pass this for backward-compat scenarios where
437
- * hooks are still outside the workspace; otherwise omit to avoid double
438
- * export. Included in the archive under the "hooks/" prefix.
439
- */
440
- hooksDir?: string;
441
432
  /**
442
433
  * Absolute path to the workspace directory (~/.vellum/workspace/).
443
434
  * When provided and exists, the entire directory tree is walked and
@@ -479,7 +470,6 @@ export function buildExportVBundle(
479
470
  checkpoint,
480
471
  trustPath,
481
472
  workspaceDir,
482
- hooksDir,
483
473
  credentials,
484
474
  } = options;
485
475
 
@@ -515,11 +505,6 @@ export function buildExportVBundle(
515
505
  configEntry.data = new TextEncoder().encode(sanitized);
516
506
  }
517
507
 
518
- // Include hooks directory if it exists (lives at ~/.vellum/hooks/, outside workspace).
519
- if (hooksDir && existsSync(hooksDir) && lstatSync(hooksDir).isDirectory()) {
520
- files.push(...walkDirectory(hooksDir, "hooks"));
521
- }
522
-
523
508
  // Include trust rules if the file exists (lives in protected/, outside workspace).
524
509
  if (trustPath && existsSync(trustPath)) {
525
510
  const trustData = new Uint8Array(readFileSync(trustPath));
@@ -820,7 +805,6 @@ export async function streamExportVBundle(
820
805
  checkpoint,
821
806
  trustPath,
822
807
  workspaceDir,
823
- hooksDir,
824
808
  credentials,
825
809
  } = options;
826
810
 
@@ -845,11 +829,6 @@ export async function streamExportVBundle(
845
829
  );
846
830
  }
847
831
 
848
- // Include hooks directory if it exists
849
- if (hooksDir && existsSync(hooksDir) && lstatSync(hooksDir).isDirectory()) {
850
- allFileMetadata.push(...walkDirectoryForMetadata(hooksDir, "hooks"));
851
- }
852
-
853
832
  // Include trust rules if the file exists
854
833
  if (trustPath && existsSync(trustPath)) {
855
834
  const trustStat = lstatSync(trustPath);
@@ -1,40 +1,52 @@
1
1
  /**
2
- * In-memory tracker for approval prompt message timestamps.
2
+ * Persistent tracker for approval prompt message timestamps.
3
3
  *
4
- * Scopes guardian reaction approvals so only reactions on a known
5
- * approval prompt can resolve a pending request. Without this, a stray
6
- * 👍/✅ on any message in the guardian chat could approve a pending
7
- * request (since reactions are now admitted from any subscribed channel,
8
- * not just tracked bot threads).
4
+ * Scopes guardian reaction approvals so only reactions on a known approval
5
+ * prompt can resolve a pending request. Without this, a stray 👍/✅ on any
6
+ * message in the guardian chat could approve a pending request (since
7
+ * reactions are now admitted from any subscribed channel, not just tracked
8
+ * bot threads).
9
9
  *
10
- * Entries expire after `APPROVAL_PROMPT_TS_TTL_MS` (matches the guardian
11
- * approval TTL of 30 minutes, plus grace). Populated when an approval
12
- * prompt is successfully delivered; consulted before applying a guardian
13
- * reaction decision.
10
+ * Entries are stored in the `approval_prompt_ts_tracker` table (created by
11
+ * `createApprovalPromptTsTrackerTable`) so that a daemon restart between
12
+ * prompt delivery and guardian reaction does not silently invalidate
13
+ * reactions that are still within the 30-minute guardian approval TTL.
14
+ * Entries expire after `APPROVAL_PROMPT_TS_TTL_MS` (guardian approval TTL
15
+ * plus grace).
14
16
  */
17
+ import { getSqlite } from "../../memory/db-connection.js";
18
+ import { getLogger } from "../../util/logger.js";
15
19
 
16
- const APPROVAL_PROMPT_TS_TTL_MS = 35 * 60 * 1000;
17
-
18
- const tracked = new Map<string, number>();
20
+ const log = getLogger("runtime-http");
19
21
 
20
- function key(channel: string, chatId: string, ts: string): string {
21
- return `${channel}\u0000${chatId}\u0000${ts}`;
22
- }
23
-
24
- function pruneExpired(now: number): void {
25
- for (const [k, expiresAt] of tracked) {
26
- if (expiresAt <= now) tracked.delete(k);
27
- }
28
- }
22
+ const APPROVAL_PROMPT_TS_TTL_MS = 35 * 60 * 1000;
29
23
 
24
+ // Swallow errors: callers run this inside their delivery try/catch, so a
25
+ // tracker throw would be misread as a delivery failure and trigger
26
+ // fallback/retry, double-posting the guardian prompt.
30
27
  export function trackApprovalPromptTs(
31
28
  channel: string,
32
29
  chatId: string,
33
30
  ts: string,
34
31
  ): void {
35
- const now = Date.now();
36
- pruneExpired(now);
37
- tracked.set(key(channel, chatId, ts), now + APPROVAL_PROMPT_TS_TTL_MS);
32
+ try {
33
+ const now = Date.now();
34
+ const expiresAt = now + APPROVAL_PROMPT_TS_TTL_MS;
35
+ const db = getSqlite();
36
+ db.run(
37
+ /*sql*/ `DELETE FROM approval_prompt_ts_tracker WHERE expires_at <= ?`,
38
+ [now],
39
+ );
40
+ db.run(
41
+ /*sql*/ `INSERT OR REPLACE INTO approval_prompt_ts_tracker (channel, chat_id, ts, expires_at) VALUES (?, ?, ?, ?)`,
42
+ [channel, chatId, ts, expiresAt],
43
+ );
44
+ } catch (err) {
45
+ log.error(
46
+ { err, channel, chatId, ts },
47
+ "Failed to persist approval prompt ts tracker entry; continuing without tracking",
48
+ );
49
+ }
38
50
  }
39
51
 
40
52
  export function isTrackedApprovalPromptTs(
@@ -42,11 +54,19 @@ export function isTrackedApprovalPromptTs(
42
54
  chatId: string,
43
55
  ts: string,
44
56
  ): boolean {
45
- const k = key(channel, chatId, ts);
46
- const expiresAt = tracked.get(k);
47
- if (expiresAt === undefined) return false;
48
- if (expiresAt <= Date.now()) {
49
- tracked.delete(k);
57
+ const now = Date.now();
58
+ const db = getSqlite();
59
+ const row = db
60
+ .query(
61
+ /*sql*/ `SELECT expires_at FROM approval_prompt_ts_tracker WHERE channel = ? AND chat_id = ? AND ts = ?`,
62
+ )
63
+ .get(channel, chatId, ts) as { expires_at: number } | null;
64
+ if (!row) return false;
65
+ if (row.expires_at <= now) {
66
+ db.run(
67
+ /*sql*/ `DELETE FROM approval_prompt_ts_tracker WHERE channel = ? AND chat_id = ? AND ts = ?`,
68
+ [channel, chatId, ts],
69
+ );
50
70
  return false;
51
71
  }
52
72
  return true;
@@ -54,5 +74,5 @@ export function isTrackedApprovalPromptTs(
54
74
 
55
75
  /** @internal Test-only — clear all tracked entries. */
56
76
  export function _clearApprovalPromptTsTrackerForTesting(): void {
57
- tracked.clear();
77
+ getSqlite().run(/*sql*/ `DELETE FROM approval_prompt_ts_tracker`);
58
78
  }
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { z } from "zod";
12
12
 
13
+ import { emitFeedEvent } from "../../home/emit-feed-event.js";
13
14
  import { getConversationByKey } from "../../memory/conversation-key-store.js";
14
15
  import { addRule } from "../../permissions/trust-store.js";
15
16
  import type { UserDecision } from "../../permissions/types.js";
@@ -210,6 +211,22 @@ export async function handleConfirm(
210
211
  "Confirmation resolved via HTTP",
211
212
  );
212
213
 
214
+ const approved = effectiveDecision === "allow";
215
+ const toolName = interaction.confirmationDetails?.toolName ?? "unknown tool";
216
+ void emitFeedEvent({
217
+ source: "assistant",
218
+ title: `${approved ? "Approved" : "Denied"} use of ${toolName}.`,
219
+ summary: `${approved ? "Approved" : "Denied"} use of ${toolName}.`,
220
+ dedupKey: `tool-approval:${requestId}`,
221
+ urgency: approved ? undefined : "medium",
222
+ conversationId: interaction.conversationId,
223
+ }).catch((err) => {
224
+ log.warn(
225
+ { err, requestId },
226
+ "Failed to emit tool approval resolution feed event",
227
+ );
228
+ });
229
+
213
230
  // ACP permissions: resolve directly without a Conversation object.
214
231
  if (interaction.directResolve) {
215
232
  interaction.directResolve(effectiveDecision as UserDecision);
@@ -17,7 +17,7 @@
17
17
  * with 403.
18
18
  * - **Browser-origin rejection**: if an `Origin` header is present it
19
19
  * must be either empty or explicitly on the
20
- * `ALLOWED_EXTENSION_ORIGINS` allowlist. This defends against a
20
+ * `getAllowedExtensionOrigins()` allowlist. This defends against a
21
21
  * malicious web page in another tab issuing a cross-origin POST from
22
22
  * the user's browser — such a request would carry the page's origin
23
23
  * and would be rejected here even if it somehow reached loopback.
@@ -194,7 +194,9 @@ function loadAllowedExtensionOrigins(): ReadonlySet<string> {
194
194
  // 2. Local override. Silently absent is fine; malformed warns but doesn't
195
195
  // block other sources.
196
196
  try {
197
- for (const id of readIdsFromFile(LOCAL_OVERRIDE_PATH, { allowEmpty: true })) {
197
+ for (const id of readIdsFromFile(LOCAL_OVERRIDE_PATH, {
198
+ allowEmpty: true,
199
+ })) {
198
200
  merged.add(`chrome-extension://${id}/`);
199
201
  }
200
202
  } catch (err) {
@@ -240,10 +242,27 @@ function loadAllowedExtensionOrigins(): ReadonlySet<string> {
240
242
  * token. Merged from the canonical repo config at
241
243
  * `meta/browser-extension/chrome-extension-allowlist.json`, an optional
242
244
  * local override at `~/.vellum/chrome-extension-allowlist.local.json`, and
243
- * the `VELLUM_CHROME_EXTENSION_IDS` env var. Cached at module load —
244
- * allowlist edits require a daemon restart to take effect.
245
+ * the `VELLUM_CHROME_EXTENSION_IDS` env var. Computed lazily on first
246
+ * access so that importing this module (e.g. for `--version`) doesn't
247
+ * trigger filesystem reads. Cached after first call — allowlist edits
248
+ * require a daemon restart to take effect.
245
249
  */
246
- export const ALLOWED_EXTENSION_ORIGINS = loadAllowedExtensionOrigins();
250
+ let _allowedExtensionOrigins: ReadonlySet<string> | null = null;
251
+
252
+ export function getAllowedExtensionOrigins(): ReadonlySet<string> {
253
+ if (!_allowedExtensionOrigins) {
254
+ _allowedExtensionOrigins = loadAllowedExtensionOrigins();
255
+ }
256
+ return _allowedExtensionOrigins;
257
+ }
258
+
259
+ /**
260
+ * Test helper: clear the cached allowlist so the next call to
261
+ * `getAllowedExtensionOrigins()` re-reads from disk/env.
262
+ */
263
+ export function __resetAllowedExtensionOriginsForTests(): void {
264
+ _allowedExtensionOrigins = null;
265
+ }
247
266
 
248
267
  /**
249
268
  * Reset the dedicated pair-endpoint rate limiter. Exported for tests
@@ -490,8 +509,8 @@ export async function handleBrowserExtensionPair(
490
509
  // and the `/`-suffixed form against the allowlist.
491
510
  const withSlash = `${originHeader}/`;
492
511
  if (
493
- !ALLOWED_EXTENSION_ORIGINS.has(originHeader) &&
494
- !ALLOWED_EXTENSION_ORIGINS.has(withSlash)
512
+ !getAllowedExtensionOrigins().has(originHeader) &&
513
+ !getAllowedExtensionOrigins().has(withSlash)
495
514
  ) {
496
515
  auditDeny(req, peerIp, "browser_origin_not_allowlisted", {
497
516
  originHeader,
@@ -536,7 +555,7 @@ export async function handleBrowserExtensionPair(
536
555
  // check catches the failure mode where a compromised extension id
537
556
  // that doesn't match a known Vellum build still manages to reach
538
557
  // the endpoint.
539
- if (!ALLOWED_EXTENSION_ORIGINS.has(extensionOrigin)) {
558
+ if (!getAllowedExtensionOrigins().has(extensionOrigin)) {
540
559
  auditDeny(req, peerIp, "extension_origin_not_allowlisted", {
541
560
  extensionOrigin,
542
561
  });