@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
@@ -3,7 +3,6 @@ import { mkdirSync, renameSync, writeFileSync } from "node:fs";
3
3
  import { dirname } from "node:path";
4
4
 
5
5
  import { generateAvatar } from "../../media/avatar-router.js";
6
- import { mapGeminiError } from "../../media/gemini-image-service.js";
7
6
  import { getLogger } from "../../util/logger.js";
8
7
  import { getAvatarImagePath } from "../../util/platform.js";
9
8
 
@@ -69,7 +68,12 @@ export async function generateAndSaveAvatar(
69
68
  isError: false,
70
69
  };
71
70
  } catch (error) {
72
- const message = mapGeminiError(error);
71
+ // avatar-router already throws with a provider-aware, user-friendly
72
+ // message — just surface error.message directly.
73
+ const message =
74
+ error instanceof Error
75
+ ? error.message
76
+ : "An unexpected error occurred during image generation.";
73
77
  log.error({ error: message }, "Avatar generation failed");
74
78
  return {
75
79
  content: `Avatar generation failed: ${message}`,
@@ -207,6 +207,18 @@ export interface ToolContext {
207
207
  * to cdp-inspect or local Playwright.
208
208
  */
209
209
  transportInterface?: InterfaceId;
210
+ /**
211
+ * True when the host browser proxy's sender was overridden by a
212
+ * registry-routed extension connection (ChromeExtensionRegistry WebSocket).
213
+ * The CDP factory uses this to distinguish between an SSE-backed proxy
214
+ * (macOS, no extension) and an extension-backed proxy: only the latter
215
+ * should suppress desktop-auto cdp-inspect when temporarily unavailable,
216
+ * because the extension transport was explicitly expected and the
217
+ * disconnection is transient. An SSE-backed proxy that reports
218
+ * unavailable (e.g. non-interactive turn) should NOT suppress
219
+ * cdp-inspect — the proxy was never expected to service browser requests.
220
+ */
221
+ hostBrowserRegistryRouted?: boolean;
210
222
  }
211
223
 
212
224
  export interface DiffInfo {
@@ -242,6 +254,14 @@ export interface ToolExecutionResult {
242
254
  * the LLM voluntarily end its turn.
243
255
  */
244
256
  yieldToUser?: boolean;
257
+ /** Risk level from the classifier (populated during permission check). */
258
+ riskLevel?: string;
259
+ /** Human-readable reason for the risk classification. */
260
+ riskReason?: string;
261
+ /** Whether the daemon is running in a containerized (Docker) environment. */
262
+ isContainerized?: boolean;
263
+ /** Scope options ladder for the rule editor (narrowest to broadest). */
264
+ riskScopeOptions?: Array<{ pattern: string; label: string }>;
245
265
  /**
246
266
  * When present, indicates that a CES tool returned an `approval_required`
247
267
  * response. The executor uses the approval bridge to prompt the guardian,
@@ -273,6 +293,10 @@ export interface ProxyApprovalRequest {
273
293
  matchingPatterns?: string[];
274
294
  };
275
295
  sessionId: string;
296
+ /** HTTP method (plain HTTP only; undefined for HTTPS CONNECT tunnels). */
297
+ method?: string;
298
+ /** Curated non-sensitive headers (plain HTTP only). */
299
+ requestHeaders?: Record<string, string>;
276
300
  }
277
301
 
278
302
  /** Callback for proxy policy decisions requiring user confirmation. Returns true if approved. */
@@ -296,12 +320,14 @@ export interface Tool {
296
320
  defaultRiskLevel: RiskLevel;
297
321
  /** When set to 'proxy', the tool is forwarded to a connected client rather than executed locally. */
298
322
  executionMode?: "local" | "proxy";
299
- /** Whether this tool is a core built-in, provided by a skill, or from an MCP server. */
300
- origin?: "core" | "skill" | "mcp";
323
+ /** Whether this tool is a core built-in, provided by a skill, contributed by a plugin, or from an MCP server. */
324
+ origin?: "core" | "skill" | "mcp" | "plugin";
301
325
  /** If origin is 'skill', the ID of the owning skill. */
302
326
  ownerSkillId?: string;
303
327
  /** If origin is 'mcp', the ID of the owning MCP server. */
304
328
  ownerMcpServerId?: string;
329
+ /** If origin is 'plugin', the name of the owning plugin. */
330
+ ownerPluginId?: string;
305
331
  /** Content-hash of the owning skill's source at registration time. */
306
332
  ownerSkillVersionHash?: string;
307
333
  /** Whether the owning skill is bundled with the daemon (trusted first-party). */
@@ -59,8 +59,14 @@ export function normalizeAssistantId(assistantId: string): string {
59
59
  * Docker mode relocates the workspace via `VELLUM_WORKSPACE_DIR` rather
60
60
  * than `BASE_DATA_DIR`, so honoring `BASE_DATA_DIR` here does not affect
61
61
  * containerized deployments.
62
+ *
63
+ * Exported so other daemon-side consumers (e.g. the meet-join orphan reaper
64
+ * in `skills/meet-join/daemon/docker-runner.ts`) can derive a per-instance
65
+ * identifier from the same canonical root. Do not replace with ad-hoc
66
+ * `process.env.BASE_DATA_DIR` reads — this helper is the single source of
67
+ * truth for per-instance path resolution.
62
68
  */
63
- function vellumRoot(): string {
69
+ export function vellumRoot(): string {
64
70
  const baseDataDir = process.env.BASE_DATA_DIR?.trim();
65
71
  if (baseDataDir) return join(baseDataDir, ".vellum");
66
72
  return join(homedir(), ".vellum");
@@ -410,7 +416,6 @@ export function ensureDataDir(): void {
410
416
  // Workspace dirs
411
417
  workspace,
412
418
  join(workspace, "signals"),
413
- join(workspace, "hooks"),
414
419
  join(workspace, "skills"),
415
420
  join(workspace, "routes"),
416
421
  join(workspace, "embedding-models"),
@@ -23,9 +23,9 @@ const PROVIDER_PRICING: Record<string, Record<string, ModelPricing>> = {
23
23
  anthropic: {
24
24
  "claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25 },
25
25
  "claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25 },
26
- "claude-opus-4": { inputPer1M: 15, outputPer1M: 75 },
26
+ "claude-opus-4": { inputPer1M: 5, outputPer1M: 25 },
27
27
  "claude-sonnet-4": { inputPer1M: 3, outputPer1M: 15 },
28
- "claude-haiku-4": { inputPer1M: 0.8, outputPer1M: 4 },
28
+ "claude-haiku-4": { inputPer1M: 1, outputPer1M: 5 },
29
29
  },
30
30
  openai: {
31
31
  "gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15 },
@@ -51,9 +51,32 @@ const PROVIDER_PRICING: Record<string, Record<string, ModelPricing>> = {
51
51
  fireworks: {
52
52
  "accounts/fireworks/models/kimi-k2p5": {
53
53
  inputPer1M: 0.6,
54
- outputPer1M: 3.0,
54
+ outputPer1M: 2.5,
55
55
  },
56
56
  },
57
+ // Non-Anthropic OpenRouter models. Anthropic-on-OpenRouter is handled by a
58
+ // dedicated branch in resolvePricingForUsage that routes to the Anthropic
59
+ // catalog (OpenRouter bills those at Anthropic's direct rates). Rates here
60
+ // mirror the catalog metadata in model-catalog.ts so cost tracking has a
61
+ // priced value instead of falling back to 'unpriced'.
62
+ openrouter: {
63
+ "x-ai/grok-4.20-beta": { inputPer1M: 3, outputPer1M: 15 },
64
+ "x-ai/grok-4": { inputPer1M: 3, outputPer1M: 15 },
65
+ "deepseek/deepseek-r1-0528": { inputPer1M: 0.55, outputPer1M: 2.19 },
66
+ "deepseek/deepseek-chat-v3-0324": { inputPer1M: 0.27, outputPer1M: 1.1 },
67
+ "qwen/qwen3.5-plus-02-15": { inputPer1M: 0.8, outputPer1M: 2.4 },
68
+ "qwen/qwen3.5-397b-a17b": { inputPer1M: 0.9, outputPer1M: 2.7 },
69
+ "qwen/qwen3.5-flash-02-23": { inputPer1M: 0.2, outputPer1M: 0.6 },
70
+ "qwen/qwen3-coder-next": { inputPer1M: 0.5, outputPer1M: 1.5 },
71
+ "moonshotai/kimi-k2.6": { inputPer1M: 0.6, outputPer1M: 2.8 },
72
+ "moonshotai/kimi-k2.5": { inputPer1M: 0.6, outputPer1M: 2.5 },
73
+ "mistralai/mistral-medium-3": { inputPer1M: 0.4, outputPer1M: 2.0 },
74
+ "mistralai/mistral-small-2603": { inputPer1M: 0.2, outputPer1M: 0.6 },
75
+ "mistralai/devstral-2512": { inputPer1M: 0.1, outputPer1M: 0.3 },
76
+ "meta-llama/llama-4-maverick": { inputPer1M: 0.27, outputPer1M: 0.85 },
77
+ "meta-llama/llama-4-scout": { inputPer1M: 0.11, outputPer1M: 0.34 },
78
+ "amazon/nova-pro-v1": { inputPer1M: 0.8, outputPer1M: 3.2 },
79
+ },
57
80
  };
58
81
 
59
82
  /**
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
+ import { providerForImageModelPrefix } from "../../media/types.js";
4
5
  import { credentialKey } from "../../security/credential-key.js";
5
6
  import {
6
7
  getProviderKeyAsync,
@@ -110,10 +111,7 @@ export const servicesConfigMigration: WorkspaceMigration = {
110
111
  services["image-generation"] = {
111
112
  ...(existingServices["image-generation"] ?? {}),
112
113
  mode: "your-own",
113
- provider:
114
- imageGenModel.startsWith("dall-e") || imageGenModel.startsWith("gpt")
115
- ? "openai"
116
- : "gemini",
114
+ provider: providerForImageModelPrefix(imageGenModel),
117
115
  model: imageGenModel,
118
116
  };
119
117
 
@@ -43,9 +43,8 @@ export const moveHooksToWorkspaceMigration: WorkspaceMigration = {
43
43
  if (!existsSync(oldHooksDir)) return;
44
44
 
45
45
  // Move hook entries from root to workspace. The old (user) entries take
46
- // precedence over anything already at the destination (e.g. template
47
- // files written by installTemplates(), which runs before migrations).
48
- // We remove the destination first so renameSync succeeds atomically.
46
+ // precedence over anything already at the destination. We remove the
47
+ // destination first so renameSync succeeds atomically.
49
48
  try {
50
49
  const entries = readdirSync(oldHooksDir);
51
50
  for (const entry of entries) {
@@ -7,10 +7,9 @@ const GMAIL_SETTINGS_BASIC_SCOPE =
7
7
  /**
8
8
  * Backfill the `gmail.settings.basic` scope for existing Google provider rows.
9
9
  *
10
- * The scope was added to PROVIDER_SEED_DATA in #25970, but `seedProviders()`
11
- * intentionally preserves `defaultScopes` on conflict (the `onConflictDoUpdate`
12
- * in `oauth-store.ts` omits `defaultScopes` and `scopePolicy`). Any workspace
13
- * that already had a `google` provider row never picked up the new scope.
10
+ * The scope was added to PROVIDER_SEED_DATA in #25970. This migration ensures
11
+ * existing workspace rows that were created before the seed update also include
12
+ * the scope.
14
13
  *
15
14
  * This migration reads the current `defaultScopes` JSON array for the `google`
16
15
  * provider and appends the scope if it is not already present.
@@ -0,0 +1,108 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ /**
7
+ * Seed a latency-optimized default for the `conversationStarters` LLM
8
+ * call site.
9
+ *
10
+ * `conversationStarters` drives the reply-suggestion chip rendered in the
11
+ * macOS client after every assistant turn. Migration 040 seeded most
12
+ * trivial copy-generation call sites to haiku-4.5 but missed this one, so
13
+ * it falls through to `llm.default` — on workspaces where the default is
14
+ * a high-effort / extended-thinking configured model (e.g. Opus 4.x at
15
+ * `effort: "xhigh"`), every turn completion kicks off an expensive
16
+ * reasoning call that, to add insult to injury, rejects the assistant
17
+ * message prefill the suggestion generator previously relied on with an
18
+ * HTTP 400.
19
+ *
20
+ * Follows the same contract as `040-seed-latency-callsite-defaults`:
21
+ * - Skip entirely when `VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH` is set
22
+ * (platform overlay owns call-site seeds).
23
+ * - Skip when the resolved provider is not Anthropic (the seeded
24
+ * model IDs are Anthropic-shaped, so mixing with another provider
25
+ * would guarantee invalid-model errors).
26
+ * - No-op when `llm.callSites.conversationStarters` is already set.
27
+ *
28
+ * Idempotent, append-only — existing 040 entries are untouched.
29
+ */
30
+ export const seedConversationStartersCallsiteMigration: WorkspaceMigration = {
31
+ id: "046-seed-conversation-starters-callsite",
32
+ description:
33
+ "Seed latency-optimized default for conversationStarters LLM call site",
34
+ run(workspaceDir: string): void {
35
+ if (process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH) return;
36
+
37
+ const configPath = join(workspaceDir, "config.json");
38
+ const configExisted = existsSync(configPath);
39
+
40
+ let config: Record<string, unknown> = {};
41
+ if (configExisted) {
42
+ try {
43
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
44
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
45
+ config = raw as Record<string, unknown>;
46
+ } catch {
47
+ return;
48
+ }
49
+ }
50
+
51
+ const llm = readObject(config.llm) ?? {};
52
+ const defaultBlock = readObject(llm.default);
53
+
54
+ const explicitProvider = readString(defaultBlock?.provider);
55
+ if (
56
+ explicitProvider !== undefined &&
57
+ explicitProvider !== "anthropic" &&
58
+ explicitProvider !== "openrouter"
59
+ ) {
60
+ return;
61
+ }
62
+ const provider = explicitProvider ?? "anthropic";
63
+ const fastModel = resolveLatencyModel(provider);
64
+ if (fastModel === undefined) return;
65
+
66
+ const callSites = readObject(llm.callSites) ?? {};
67
+ if (readObject(callSites.conversationStarters) !== null) return;
68
+
69
+ callSites.conversationStarters = {
70
+ model: fastModel,
71
+ effort: "low",
72
+ thinking: { enabled: false },
73
+ };
74
+
75
+ llm.callSites = callSites;
76
+ config.llm = llm;
77
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
78
+ },
79
+ down(_workspaceDir: string): void {
80
+ // Forward-only: removing the seeded default would reintroduce the
81
+ // cost/latency regression and the assistant-prefill 400 that this
82
+ // migration fixes.
83
+ },
84
+ };
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // Helpers — self-contained per workspace migrations AGENTS.md
88
+ // ---------------------------------------------------------------------------
89
+
90
+ const PROVIDER_LATENCY_MODELS: Record<string, string> = {
91
+ anthropic: "claude-haiku-4-5-20251001",
92
+ openrouter: "anthropic/claude-haiku-4.5",
93
+ };
94
+
95
+ function resolveLatencyModel(provider: string): string | undefined {
96
+ return PROVIDER_LATENCY_MODELS[provider];
97
+ }
98
+
99
+ function readObject(value: unknown): Record<string, unknown> | null {
100
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
101
+ return null;
102
+ }
103
+ return value as Record<string, unknown>;
104
+ }
105
+
106
+ function readString(value: unknown): string | undefined {
107
+ return typeof value === "string" && value.length > 0 ? value : undefined;
108
+ }
@@ -0,0 +1,54 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ /**
7
+ * Remove `watchCommentary` and `watchSummary` entries from
8
+ * `llm.callSites` in existing config files. These call-sites were seeded
9
+ * by migration 040 but the screen-watch feature has been removed, so the
10
+ * keys are no longer valid members of the `LLMCallSiteEnum` and would
11
+ * trigger repeated validation warnings if left on disk.
12
+ */
13
+ export const removeWatchCallsitesMigration: WorkspaceMigration = {
14
+ id: "047-remove-watch-callsites",
15
+ description:
16
+ "Remove watchCommentary and watchSummary from llm.callSites (screen-watch removed)",
17
+ run(workspaceDir: string): void {
18
+ const configPath = join(workspaceDir, "config.json");
19
+ if (!existsSync(configPath)) return;
20
+
21
+ let config: Record<string, unknown>;
22
+ try {
23
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
24
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
25
+ config = raw as Record<string, unknown>;
26
+ } catch {
27
+ return;
28
+ }
29
+
30
+ const llm = config.llm;
31
+ if (!llm || typeof llm !== "object" || Array.isArray(llm)) return;
32
+
33
+ const callSites = (llm as Record<string, unknown>).callSites;
34
+ if (!callSites || typeof callSites !== "object" || Array.isArray(callSites))
35
+ return;
36
+
37
+ const sites = callSites as Record<string, unknown>;
38
+ let mutated = false;
39
+
40
+ for (const key of ["watchCommentary", "watchSummary"]) {
41
+ if (key in sites) {
42
+ delete sites[key];
43
+ mutated = true;
44
+ }
45
+ }
46
+
47
+ if (!mutated) return;
48
+
49
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
50
+ },
51
+ down(_workspaceDir: string): void {
52
+ // no-op — keys are obsolete
53
+ },
54
+ };
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Workspace migration 046: Remove legacy `workspace/hooks/` directory.
3
+ *
4
+ * Migration 022 moved `~/.vellum/hooks/` into `~/.vellum/workspace/hooks/`.
5
+ * With the hook system entirely removed, that directory is dead state — it is
6
+ * no longer read or written by the assistant. This migration deletes the
7
+ * directory (and everything under it) so stale hook manifests, config, and
8
+ * executables do not linger in user workspaces.
9
+ *
10
+ * Idempotent: safe to re-run after interruption. A no-op when the directory
11
+ * is already absent.
12
+ */
13
+
14
+ import { existsSync, readdirSync, rmSync, statSync } from "node:fs";
15
+ import { join } from "node:path";
16
+
17
+ import { getLogger } from "../../util/logger.js";
18
+ import type { WorkspaceMigration } from "./types.js";
19
+
20
+ const log = getLogger("workspace-migration-048-remove-workspace-hooks");
21
+
22
+ /**
23
+ * Count files under `dir` recursively. Best-effort — returns the count we
24
+ * could successfully stat, and silently skips entries that fail (e.g. a
25
+ * symlink whose target is missing, a file removed concurrently). This is
26
+ * only used for log output, so a slightly stale count is acceptable.
27
+ */
28
+ function countFilesRecursive(dir: string): number {
29
+ let count = 0;
30
+ let entries: string[];
31
+ try {
32
+ entries = readdirSync(dir);
33
+ } catch {
34
+ return 0;
35
+ }
36
+ for (const entry of entries) {
37
+ const entryPath = join(dir, entry);
38
+ try {
39
+ const s = statSync(entryPath);
40
+ if (s.isDirectory()) {
41
+ count += countFilesRecursive(entryPath);
42
+ } else {
43
+ count += 1;
44
+ }
45
+ } catch {
46
+ // best-effort
47
+ }
48
+ }
49
+ return count;
50
+ }
51
+
52
+ export const removeWorkspaceHooksMigration: WorkspaceMigration = {
53
+ id: "048-remove-workspace-hooks",
54
+ description:
55
+ "Remove legacy workspace/hooks/ directory now that the hook system is gone",
56
+
57
+ run(workspaceDir: string): void {
58
+ const hooksDir = join(workspaceDir, "hooks");
59
+ if (!existsSync(hooksDir)) return;
60
+
61
+ const fileCount = countFilesRecursive(hooksDir);
62
+ try {
63
+ rmSync(hooksDir, { recursive: true, force: true });
64
+ log.info(
65
+ { path: hooksDir, fileCount },
66
+ "Removed legacy workspace hooks directory",
67
+ );
68
+ } catch (err) {
69
+ log.warn(
70
+ { err, path: hooksDir },
71
+ "Failed to remove legacy workspace hooks directory; leaving in place",
72
+ );
73
+ }
74
+ },
75
+
76
+ down(_workspaceDir: string): void {
77
+ // Forward-only: the hook system is gone and the directory contained no
78
+ // data the assistant still consumes. Restoring an empty directory would
79
+ // just reintroduce dead state.
80
+ },
81
+ };
@@ -0,0 +1,80 @@
1
+ import {
2
+ appendFileSync,
3
+ existsSync,
4
+ readFileSync,
5
+ writeFileSync,
6
+ } from "node:fs";
7
+ import { join } from "node:path";
8
+
9
+ import { getLogger } from "../../util/logger.js";
10
+ import type { WorkspaceMigration } from "./types.js";
11
+
12
+ const log = getLogger("workspace-migration-049-release-notes-default-sonnet");
13
+
14
+ const MIGRATION_ID = "049-release-notes-default-sonnet";
15
+ const MARKER = `<!-- release-note-id:${MIGRATION_ID} -->`;
16
+
17
+ const RELEASE_NOTE = `${MARKER}
18
+ ## Default LLM is now Claude Sonnet 4.6 (main agent stays on Opus)
19
+
20
+ The schema-level default for \`llm.default.model\` is now
21
+ \`claude-sonnet-4-6\` instead of \`claude-opus-4-7\`, so background call
22
+ sites that fall through to the default now use Sonnet. If you've
23
+ already chosen a model, your persisted config takes precedence.
24
+
25
+ The main agent conversation loop remains on Opus: a companion
26
+ migration seeds \`llm.callSites.mainAgent = { model: "claude-opus-4-7" }\`
27
+ when it's unset, and the \`quality-optimized\` model intent also still
28
+ resolves to Opus.
29
+
30
+ To switch the main agent to Sonnet, clear the call-site override:
31
+
32
+ \`\`\`bash
33
+ assistant config unset llm.callSites.mainAgent
34
+ \`\`\`
35
+
36
+ To switch the overall default back to Opus, run:
37
+
38
+ \`\`\`bash
39
+ assistant config set llm.default.model claude-opus-4-7
40
+ \`\`\`
41
+ `;
42
+
43
+ export const releaseNotesDefaultSonnetMigration: WorkspaceMigration = {
44
+ id: MIGRATION_ID,
45
+ description:
46
+ "Append release notes for default LLM switch to Claude Sonnet 4.6 to UPDATES.md",
47
+
48
+ run(workspaceDir: string): void {
49
+ const updatesPath = join(workspaceDir, "UPDATES.md");
50
+
51
+ try {
52
+ if (existsSync(updatesPath)) {
53
+ const existing = readFileSync(updatesPath, "utf-8");
54
+ if (existing.includes(MARKER)) {
55
+ return;
56
+ }
57
+ const needsLeadingNewline = !existing.endsWith("\n\n");
58
+ const prefix = existing.endsWith("\n") ? "\n" : "\n\n";
59
+ appendFileSync(
60
+ updatesPath,
61
+ needsLeadingNewline ? `${prefix}${RELEASE_NOTE}` : RELEASE_NOTE,
62
+ "utf-8",
63
+ );
64
+ } else {
65
+ writeFileSync(updatesPath, RELEASE_NOTE, "utf-8");
66
+ }
67
+ log.info({ path: updatesPath }, "Appended default-Sonnet release note");
68
+ } catch (err) {
69
+ log.warn(
70
+ { err, path: updatesPath },
71
+ "Failed to append default-Sonnet release note to UPDATES.md",
72
+ );
73
+ }
74
+ },
75
+
76
+ down(_workspaceDir: string): void {
77
+ // Forward-only: UPDATES.md is a user-facing bulletin the assistant
78
+ // processes and deletes on its own.
79
+ },
80
+ };
@@ -0,0 +1,86 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ /**
7
+ * Seed `callSites.mainAgent = { model: "claude-opus-4-7" }` so the main
8
+ * agent conversation loop stays on Opus even though the schema-level
9
+ * default dropped to Sonnet. Runs in two modes, mirroring migration 040:
10
+ *
11
+ * 1. **Existing workspace** (config.json present): merge the seed into
12
+ * `llm.callSites` without overwriting a user-defined override.
13
+ * 2. **Fresh install** (config.json absent): write a minimal starter
14
+ * config with just this seed. `loadConfig()` runs after migrations
15
+ * and backfills the remaining schema defaults via `deepMergeMissing`.
16
+ *
17
+ * Applied only when:
18
+ * - the resolved provider is Anthropic (other providers own their
19
+ * own mainAgent model choice), **and**
20
+ * - `llm.default.model` is either unset or equal to the previous
21
+ * schema default `claude-opus-4-7` — a user who explicitly picked
22
+ * a different Anthropic model (e.g. Haiku) kept their own choice,
23
+ * so forcing Opus onto their mainAgent would be surprising.
24
+ */
25
+ export const seedMainAgentOpusCallsiteMigration: WorkspaceMigration = {
26
+ id: "050-seed-main-agent-opus-callsite",
27
+ description: "Seed callSites.mainAgent to claude-opus-4-7 for Anthropic",
28
+ run(workspaceDir: string): void {
29
+ // Defer to platform-provided overlays.
30
+ if (process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH) return;
31
+
32
+ const configPath = join(workspaceDir, "config.json");
33
+ const configExisted = existsSync(configPath);
34
+
35
+ let config: Record<string, unknown> = {};
36
+ if (configExisted) {
37
+ try {
38
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
39
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
40
+ config = raw as Record<string, unknown>;
41
+ } catch {
42
+ return;
43
+ }
44
+ }
45
+
46
+ const llm = readObject(config.llm) ?? {};
47
+ const defaultBlock = readObject(llm.default);
48
+
49
+ const explicitProvider = readString(defaultBlock?.provider);
50
+ if (explicitProvider !== undefined && explicitProvider !== "anthropic") {
51
+ return;
52
+ }
53
+
54
+ const explicitModel = readString(defaultBlock?.model);
55
+ if (explicitModel !== undefined && explicitModel !== "claude-opus-4-7") {
56
+ return;
57
+ }
58
+
59
+ const callSites = readObject(llm.callSites) ?? {};
60
+
61
+ if (readObject(callSites.mainAgent) !== null) return;
62
+
63
+ // maxTokens: 32000 matches Opus's maxOutputTokens (see
64
+ // model-catalog.ts); without it the site would inherit the
65
+ // default's 64000 and overshoot the model capability.
66
+ callSites.mainAgent = { model: "claude-opus-4-7", maxTokens: 32000 };
67
+ llm.callSites = callSites;
68
+ config.llm = llm;
69
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
70
+ },
71
+ down(_workspaceDir: string): void {
72
+ // Forward-only: removing the seed would silently downgrade the main
73
+ // agent loop to Sonnet on every re-run.
74
+ },
75
+ };
76
+
77
+ function readObject(value: unknown): Record<string, unknown> | null {
78
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
79
+ return null;
80
+ }
81
+ return value as Record<string, unknown>;
82
+ }
83
+
84
+ function readString(value: unknown): string | undefined {
85
+ return typeof value === "string" && value.length > 0 ? value : undefined;
86
+ }