@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,13 +3,10 @@ import { describe, expect, test } from "bun:test";
3
3
  import {
4
4
  calculateMaxToolResultChars,
5
5
  HARD_MAX_TOOL_RESULT_CHARS,
6
- isOversizedToolResult,
7
6
  MIN_KEEP_CHARS,
8
- truncateOversizedToolResults,
9
7
  truncateToolResultText,
10
8
  TRUNCATION_SUFFIX,
11
9
  } from "../context/tool-result-truncation.js";
12
- import type { ContentBlock, ToolResultContent } from "../providers/types.js";
13
10
 
14
11
  function hasOrphanedSurrogate(str: string): boolean {
15
12
  for (let i = 0; i < str.length; i++) {
@@ -25,22 +22,6 @@ function hasOrphanedSurrogate(str: string): boolean {
25
22
  return false;
26
23
  }
27
24
 
28
- // ---------------------------------------------------------------------------
29
- // Helpers
30
- // ---------------------------------------------------------------------------
31
-
32
- function makeToolResult(content: string): ToolResultContent {
33
- return {
34
- type: "tool_result",
35
- tool_use_id: "test-id",
36
- content,
37
- };
38
- }
39
-
40
- function makeTextBlock(text: string): ContentBlock {
41
- return { type: "text", text };
42
- }
43
-
44
25
  // ---------------------------------------------------------------------------
45
26
  // truncateToolResultText
46
27
  // ---------------------------------------------------------------------------
@@ -161,94 +142,3 @@ describe("calculateMaxToolResultChars", () => {
161
142
  expect(result).toBe(216_000);
162
143
  });
163
144
  });
164
-
165
- // ---------------------------------------------------------------------------
166
- // isOversizedToolResult
167
- // ---------------------------------------------------------------------------
168
-
169
- describe("isOversizedToolResult", () => {
170
- test("returns false for small tool results", () => {
171
- const block = makeToolResult("small content");
172
- expect(isOversizedToolResult(block, 180_000)).toBe(false);
173
- });
174
-
175
- test("returns true for oversized tool results", () => {
176
- const block = makeToolResult("x".repeat(500_000));
177
- expect(isOversizedToolResult(block, 180_000)).toBe(true);
178
- });
179
-
180
- test("returns false for non-tool-result blocks (cast safely)", () => {
181
- const textBlock = makeTextBlock("hello") as unknown as ToolResultContent;
182
- // A text block has no `.content` string of meaningful length, so it
183
- // should not be considered oversized. We cast to exercise the function
184
- // safely even with unexpected input.
185
- expect(isOversizedToolResult(textBlock, 180_000)).toBe(false);
186
- });
187
- });
188
-
189
- // ---------------------------------------------------------------------------
190
- // truncateOversizedToolResults
191
- // ---------------------------------------------------------------------------
192
-
193
- describe("truncateOversizedToolResults", () => {
194
- const contextWindow = 180_000; // maxChars = 216_000
195
-
196
- test("returns unchanged blocks when nothing is oversized", () => {
197
- const blocks: ContentBlock[] = [
198
- makeTextBlock("hello"),
199
- makeToolResult("short"),
200
- ];
201
- const { blocks: result, truncatedCount } = truncateOversizedToolResults(
202
- blocks,
203
- contextWindow,
204
- );
205
- expect(truncatedCount).toBe(0);
206
- expect(result).toEqual(blocks);
207
- });
208
-
209
- test("truncates oversized tool results", () => {
210
- const big = makeToolResult("y".repeat(500_000));
211
- const { blocks: result, truncatedCount } = truncateOversizedToolResults(
212
- [big],
213
- contextWindow,
214
- );
215
- expect(truncatedCount).toBe(1);
216
- const truncated = result[0] as ToolResultContent;
217
- expect(truncated.content.length).toBeLessThan(500_000);
218
- expect(truncated.content).toContain(TRUNCATION_SUFFIX);
219
- });
220
-
221
- test("preserves non-tool-result blocks unchanged", () => {
222
- const text = makeTextBlock("keep me");
223
- const big = makeToolResult("z".repeat(500_000));
224
- const { blocks: result } = truncateOversizedToolResults(
225
- [text, big],
226
- contextWindow,
227
- );
228
- expect(result[0]).toBe(text); // same reference
229
- });
230
-
231
- test("reports correct truncatedCount", () => {
232
- const small = makeToolResult("ok");
233
- const big = makeToolResult("a".repeat(500_000));
234
- const { truncatedCount } = truncateOversizedToolResults(
235
- [small, big],
236
- contextWindow,
237
- );
238
- expect(truncatedCount).toBe(1);
239
- });
240
-
241
- test("handles multiple oversized results", () => {
242
- const big1 = makeToolResult("a".repeat(500_000));
243
- const big2 = makeToolResult("b".repeat(500_000));
244
- const { blocks: result, truncatedCount } = truncateOversizedToolResults(
245
- [big1, big2],
246
- contextWindow,
247
- );
248
- expect(truncatedCount).toBe(2);
249
- for (const b of result) {
250
- const tr = b as ToolResultContent;
251
- expect(tr.content).toContain(TRUNCATION_SUFFIX);
252
- }
253
- });
254
- });
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Tests for the user plugin loader (PR 29).
3
+ *
4
+ * Redirects `vellumRoot()` into a per-test temp directory via `BASE_DATA_DIR`
5
+ * (the canonical multi-instance override read by `util/platform.ts`) so
6
+ * `loadUserPlugins()` walks an isolated tree that we populate on demand.
7
+ *
8
+ * Covers:
9
+ * - A plugin whose `register.ts` calls `registerPlugin()` at import time
10
+ * ends up in the registry after `loadUserPlugins()` resolves.
11
+ * - A plugin whose `register.ts` throws during import is logged + skipped;
12
+ * other plugins in the same directory still load.
13
+ * - A missing `vellumRoot()/plugins/` directory is a no-op (zero installed
14
+ * user plugins is the default shape of a fresh daemon).
15
+ */
16
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
17
+ import { tmpdir } from "node:os";
18
+ import { join } from "node:path";
19
+ import { beforeEach, describe, expect, test } from "bun:test";
20
+
21
+ import {
22
+ getRegisteredPlugins,
23
+ resetPluginRegistryForTests,
24
+ } from "../plugins/registry.js";
25
+ import { loadUserPlugins } from "../plugins/user-loader.js";
26
+
27
+ // Isolate every run under its own tempdir so parallel test files (and
28
+ // repeated runs of this file) cannot collide on `~/.vellum/plugins/`.
29
+ // Each describe-scope gets a fresh subdirectory.
30
+ const TEST_INSTANCE_DIR = join(
31
+ tmpdir(),
32
+ `vellum-user-plugin-loader-test-${process.pid}-${Date.now()}`,
33
+ );
34
+ process.env.BASE_DATA_DIR = TEST_INSTANCE_DIR;
35
+
36
+ /** The plugins directory the loader will walk. */
37
+ const PLUGINS_DIR = join(TEST_INSTANCE_DIR, ".vellum", "plugins");
38
+
39
+ /**
40
+ * Write a plugin directory with a `register.ts` (TypeScript source, so bun
41
+ * can import it at test time without a build step) that executes the given
42
+ * body. The body has access to `registerPlugin` via a relative import back
43
+ * into the repo's registry module.
44
+ *
45
+ * `relativeRegistryImport` points from the synthetic plugin file at
46
+ * `<TEST_INSTANCE_DIR>/.vellum/plugins/<name>/register.ts` to the real
47
+ * registry source at `<repo>/assistant/src/plugins/registry.ts`. Using a
48
+ * relative path (rather than a project-root alias) keeps the test hermetic
49
+ * and matches how an on-disk user plugin would actually import the
50
+ * registry's public API in a real install.
51
+ */
52
+ function writePlugin(name: string, body: string): void {
53
+ const pluginDir = join(PLUGINS_DIR, name);
54
+ mkdirSync(pluginDir, { recursive: true });
55
+ // Resolve the absolute path to the registry module so the synthetic
56
+ // register.ts can import it. bun happily resolves `.ts` files at runtime
57
+ // when the test suite itself is running in source mode.
58
+ const registryPath = join(import.meta.dir, "..", "plugins", "registry.ts");
59
+ const registerSource = `
60
+ import { registerPlugin } from ${JSON.stringify(registryPath)};
61
+ ${body}
62
+ `;
63
+ writeFileSync(join(pluginDir, "register.ts"), registerSource);
64
+ }
65
+
66
+ function clearPluginsDir(): void {
67
+ rmSync(TEST_INSTANCE_DIR, { recursive: true, force: true });
68
+ }
69
+
70
+ describe("user plugin loader", () => {
71
+ beforeEach(() => {
72
+ resetPluginRegistryForTests();
73
+ clearPluginsDir();
74
+ });
75
+
76
+ test("loads a valid plugin whose register.ts calls registerPlugin()", async () => {
77
+ writePlugin(
78
+ "my-plugin",
79
+ `
80
+ registerPlugin({
81
+ manifest: {
82
+ name: "my-plugin",
83
+ version: "0.0.1",
84
+ requires: { pluginRuntime: "v1" },
85
+ },
86
+ });
87
+ `,
88
+ );
89
+
90
+ await loadUserPlugins();
91
+
92
+ const registered = getRegisteredPlugins();
93
+ expect(registered).toHaveLength(1);
94
+ expect(registered[0]?.manifest.name).toBe("my-plugin");
95
+ });
96
+
97
+ test("per-plugin failure is isolated: other plugins still load", async () => {
98
+ // Plugin A throws at import time. The loader must log and move on so
99
+ // Plugin B still ends up registered — one bad user plugin cannot brick
100
+ // the entire user-plugin surface or crash the daemon.
101
+ writePlugin(
102
+ "broken-plugin",
103
+ `
104
+ throw new Error("boom at import time");
105
+ `,
106
+ );
107
+ writePlugin(
108
+ "good-plugin",
109
+ `
110
+ registerPlugin({
111
+ manifest: {
112
+ name: "good-plugin",
113
+ version: "0.0.1",
114
+ requires: { pluginRuntime: "v1" },
115
+ },
116
+ });
117
+ `,
118
+ );
119
+
120
+ await loadUserPlugins();
121
+
122
+ const registered = getRegisteredPlugins();
123
+ const names = registered.map((p) => p.manifest.name);
124
+ // Order is not guaranteed (filesystem-dependent) — assert membership.
125
+ expect(names).toContain("good-plugin");
126
+ expect(names).not.toContain("broken-plugin");
127
+ });
128
+
129
+ test("missing plugins/ directory is a no-op", async () => {
130
+ // clearPluginsDir() in beforeEach has already removed TEST_INSTANCE_DIR
131
+ // entirely, so vellumRoot()/plugins/ does not exist. The loader must
132
+ // complete without throwing and without registering anything.
133
+ await loadUserPlugins();
134
+ expect(getRegisteredPlugins()).toHaveLength(0);
135
+ });
136
+
137
+ test("plugin with hanging top-level await is timed out and skipped", async () => {
138
+ // A plugin whose module evaluation never resolves (hanging top-level
139
+ // await) would otherwise block daemon startup indefinitely. The loader
140
+ // must bound the import with a timeout so the hang is isolated the same
141
+ // way a thrown error would be. A neighboring well-behaved plugin must
142
+ // still load.
143
+ writePlugin(
144
+ "hanging-plugin",
145
+ `
146
+ await new Promise(() => {
147
+ // Intentionally never resolves — simulates a plugin whose top-level
148
+ // await blocks forever.
149
+ });
150
+ registerPlugin({
151
+ manifest: {
152
+ name: "hanging-plugin",
153
+ version: "0.0.1",
154
+ requires: { pluginRuntime: "v1" },
155
+ },
156
+ });
157
+ `,
158
+ );
159
+ writePlugin(
160
+ "healthy-plugin",
161
+ `
162
+ registerPlugin({
163
+ manifest: {
164
+ name: "healthy-plugin",
165
+ version: "0.0.1",
166
+ requires: { pluginRuntime: "v1" },
167
+ },
168
+ });
169
+ `,
170
+ );
171
+
172
+ // Use a short test-only timeout so the suite does not wait the full
173
+ // production 10s for the hung-plugin path.
174
+ await loadUserPlugins({ importTimeoutMs: 250 });
175
+
176
+ const names = getRegisteredPlugins().map((p) => p.manifest.name);
177
+ expect(names).toContain("healthy-plugin");
178
+ expect(names).not.toContain("hanging-plugin");
179
+ });
180
+
181
+ test("subdirectory without register.{ts,js} is silently skipped", async () => {
182
+ // Populate a directory that looks like a plugin but lacks a register
183
+ // file. The loader must skip it without throwing.
184
+ const stubDir = join(PLUGINS_DIR, "not-a-plugin");
185
+ mkdirSync(stubDir, { recursive: true });
186
+ writeFileSync(join(stubDir, "README.md"), "# not actually a plugin\n");
187
+
188
+ await loadUserPlugins();
189
+ expect(getRegisteredPlugins()).toHaveLength(0);
190
+ });
191
+ });
@@ -0,0 +1,185 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
11
+
12
+ import { seedConversationStartersCallsiteMigration } from "../workspace/migrations/046-seed-conversation-starters-callsite.js";
13
+
14
+ let workspaceDir: string;
15
+
16
+ function freshWorkspace(): void {
17
+ workspaceDir = join(
18
+ tmpdir(),
19
+ `vellum-migration-046-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
20
+ );
21
+ mkdirSync(workspaceDir, { recursive: true });
22
+ }
23
+
24
+ function writeConfig(data: Record<string, unknown>): void {
25
+ writeFileSync(
26
+ join(workspaceDir, "config.json"),
27
+ JSON.stringify(data, null, 2) + "\n",
28
+ );
29
+ }
30
+
31
+ function readConfig(): Record<string, unknown> {
32
+ return JSON.parse(readFileSync(join(workspaceDir, "config.json"), "utf-8"));
33
+ }
34
+
35
+ function configPath(): string {
36
+ return join(workspaceDir, "config.json");
37
+ }
38
+
39
+ beforeEach(() => {
40
+ freshWorkspace();
41
+ delete process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH;
42
+ });
43
+
44
+ afterEach(() => {
45
+ delete process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH;
46
+ if (existsSync(workspaceDir)) {
47
+ rmSync(workspaceDir, { recursive: true, force: true });
48
+ }
49
+ });
50
+
51
+ describe("046-seed-conversation-starters-callsite migration", () => {
52
+ test("has correct migration id", () => {
53
+ expect(seedConversationStartersCallsiteMigration.id).toBe(
54
+ "046-seed-conversation-starters-callsite",
55
+ );
56
+ });
57
+
58
+ test("seeds haiku callsite entry on Anthropic workspace without override", () => {
59
+ writeConfig({
60
+ llm: {
61
+ default: { provider: "anthropic", model: "claude-opus-4-7" },
62
+ callSites: {
63
+ interactionClassifier: { model: "claude-haiku-4-5-20251001" },
64
+ },
65
+ },
66
+ });
67
+
68
+ seedConversationStartersCallsiteMigration.run(workspaceDir);
69
+
70
+ const config = readConfig() as {
71
+ llm: { callSites: Record<string, Record<string, unknown>> };
72
+ };
73
+ expect(config.llm.callSites.conversationStarters).toEqual({
74
+ model: "claude-haiku-4-5-20251001",
75
+ effort: "low",
76
+ thinking: { enabled: false },
77
+ });
78
+ // Does not clobber unrelated entries.
79
+ expect(config.llm.callSites.interactionClassifier).toEqual({
80
+ model: "claude-haiku-4-5-20251001",
81
+ });
82
+ });
83
+
84
+ test("seeds openrouter-shaped model ID on openrouter workspace", () => {
85
+ writeConfig({
86
+ llm: {
87
+ default: { provider: "openrouter", model: "anthropic/claude-opus-4.7" },
88
+ },
89
+ });
90
+
91
+ seedConversationStartersCallsiteMigration.run(workspaceDir);
92
+
93
+ const config = readConfig() as {
94
+ llm: { callSites: Record<string, Record<string, unknown>> };
95
+ };
96
+ expect(config.llm.callSites.conversationStarters).toEqual({
97
+ model: "anthropic/claude-haiku-4.5",
98
+ effort: "low",
99
+ thinking: { enabled: false },
100
+ });
101
+ });
102
+
103
+ test("skips when conversationStarters already has an explicit override", () => {
104
+ writeConfig({
105
+ llm: {
106
+ default: { provider: "anthropic" },
107
+ callSites: {
108
+ conversationStarters: {
109
+ model: "claude-opus-4-7",
110
+ effort: "high",
111
+ },
112
+ },
113
+ },
114
+ });
115
+
116
+ seedConversationStartersCallsiteMigration.run(workspaceDir);
117
+
118
+ const config = readConfig() as {
119
+ llm: { callSites: Record<string, Record<string, unknown>> };
120
+ };
121
+ expect(config.llm.callSites.conversationStarters).toEqual({
122
+ model: "claude-opus-4-7",
123
+ effort: "high",
124
+ });
125
+ });
126
+
127
+ test("skips entirely when non-Anthropic, non-OpenRouter provider is configured", () => {
128
+ writeConfig({
129
+ llm: {
130
+ default: { provider: "openai", model: "gpt-5.4" },
131
+ },
132
+ });
133
+
134
+ seedConversationStartersCallsiteMigration.run(workspaceDir);
135
+
136
+ const config = readConfig() as {
137
+ llm: { callSites?: Record<string, unknown> };
138
+ };
139
+ // No callSites object created at all.
140
+ expect(config.llm.callSites).toBeUndefined();
141
+ });
142
+
143
+ test("skips when VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH is set", () => {
144
+ process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH = "/tmp/overlay.json";
145
+ writeConfig({
146
+ llm: { default: { provider: "anthropic" } },
147
+ });
148
+
149
+ seedConversationStartersCallsiteMigration.run(workspaceDir);
150
+
151
+ const config = readConfig() as {
152
+ llm: { callSites?: Record<string, unknown> };
153
+ };
154
+ expect(config.llm.callSites).toBeUndefined();
155
+ });
156
+
157
+ test("runs on fresh install (no config.json) and writes starter config", () => {
158
+ expect(existsSync(configPath())).toBe(false);
159
+
160
+ seedConversationStartersCallsiteMigration.run(workspaceDir);
161
+
162
+ expect(existsSync(configPath())).toBe(true);
163
+ const config = readConfig() as {
164
+ llm: { callSites: Record<string, Record<string, unknown>> };
165
+ };
166
+ expect(config.llm.callSites.conversationStarters).toEqual({
167
+ model: "claude-haiku-4-5-20251001",
168
+ effort: "low",
169
+ thinking: { enabled: false },
170
+ });
171
+ });
172
+
173
+ test("is idempotent — a second run is a no-op", () => {
174
+ writeConfig({
175
+ llm: { default: { provider: "anthropic" } },
176
+ });
177
+
178
+ seedConversationStartersCallsiteMigration.run(workspaceDir);
179
+ const afterFirst = readFileSync(configPath(), "utf-8");
180
+ seedConversationStartersCallsiteMigration.run(workspaceDir);
181
+ const afterSecond = readFileSync(configPath(), "utf-8");
182
+
183
+ expect(afterSecond).toBe(afterFirst);
184
+ });
185
+ });
@@ -0,0 +1,100 @@
1
+ import {
2
+ existsSync,
3
+ mkdtempSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import {
11
+ afterAll,
12
+ afterEach,
13
+ beforeAll,
14
+ beforeEach,
15
+ describe,
16
+ expect,
17
+ test,
18
+ } from "bun:test";
19
+
20
+ import { releaseNotesDefaultSonnetMigration } from "../workspace/migrations/049-release-notes-default-sonnet.js";
21
+
22
+ const MIGRATION_ID = "049-release-notes-default-sonnet";
23
+ const MARKER = `<!-- release-note-id:${MIGRATION_ID} -->`;
24
+
25
+ let testRoot: string;
26
+ let workspaceDir: string;
27
+
28
+ beforeAll(() => {
29
+ testRoot = mkdtempSync(join(tmpdir(), "migration-047-test-"));
30
+ });
31
+
32
+ afterAll(() => {
33
+ rmSync(testRoot, { recursive: true, force: true });
34
+ });
35
+
36
+ beforeEach(() => {
37
+ workspaceDir = mkdtempSync(join(testRoot, "ws-"));
38
+ });
39
+
40
+ afterEach(() => {
41
+ rmSync(workspaceDir, { recursive: true, force: true });
42
+ });
43
+
44
+ function updatesPath(): string {
45
+ return join(workspaceDir, "UPDATES.md");
46
+ }
47
+
48
+ describe("workspace migration 047-release-notes-default-sonnet", () => {
49
+ test("has the correct id", () => {
50
+ expect(releaseNotesDefaultSonnetMigration.id).toBe(MIGRATION_ID);
51
+ });
52
+
53
+ test("creates UPDATES.md with marker and key copy when file is absent", () => {
54
+ expect(existsSync(updatesPath())).toBe(false);
55
+
56
+ releaseNotesDefaultSonnetMigration.run(workspaceDir);
57
+
58
+ const content = readFileSync(updatesPath(), "utf-8");
59
+ expect(content).toContain(MARKER);
60
+ expect(content).toContain("Claude Sonnet 4.6");
61
+ expect(content).toContain("main agent");
62
+ expect(content).toContain("claude-opus-4-7");
63
+ expect(content.startsWith(MARKER)).toBe(true);
64
+ });
65
+
66
+ test("is a no-op when marker is already present", () => {
67
+ const seeded = `## Prior\n\n${MARKER}\nSomething.\n`;
68
+ writeFileSync(updatesPath(), seeded, "utf-8");
69
+
70
+ releaseNotesDefaultSonnetMigration.run(workspaceDir);
71
+ const first = readFileSync(updatesPath(), "utf-8");
72
+ expect(first).toBe(seeded);
73
+
74
+ releaseNotesDefaultSonnetMigration.run(workspaceDir);
75
+ const second = readFileSync(updatesPath(), "utf-8");
76
+ expect(second).toBe(seeded);
77
+ expect(second.split(MARKER).length - 1).toBe(1);
78
+ });
79
+
80
+ test("appends to existing UPDATES.md when marker is absent", () => {
81
+ const prior = "## Earlier note\n\nBody.\n";
82
+ writeFileSync(updatesPath(), prior, "utf-8");
83
+
84
+ releaseNotesDefaultSonnetMigration.run(workspaceDir);
85
+
86
+ const content = readFileSync(updatesPath(), "utf-8");
87
+ expect(content.startsWith(prior)).toBe(true);
88
+ expect(content).toContain(MARKER);
89
+ expect(content).not.toContain("\n\n\n");
90
+ });
91
+
92
+ test("down() is a no-op", () => {
93
+ writeFileSync(updatesPath(), `${MARKER}\nBody.\n`, "utf-8");
94
+ const before = readFileSync(updatesPath(), "utf-8");
95
+
96
+ releaseNotesDefaultSonnetMigration.down(workspaceDir);
97
+
98
+ expect(readFileSync(updatesPath(), "utf-8")).toBe(before);
99
+ });
100
+ });