@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
@@ -0,0 +1,177 @@
1
+ /**
2
+ * User plugin loader — discovers plugins under `~/.vellum/plugins/*` and
3
+ * invokes each plugin's registration side effect via a dynamic import.
4
+ *
5
+ * A user plugin is a directory under `vellumRoot()/plugins/` that contains a
6
+ * `register.ts` (or `register.js` after compilation). The file is expected to
7
+ * call {@link registerPlugin} at import time so the plugin ends up in the
8
+ * registry before {@link bootstrapPlugins} runs during daemon startup.
9
+ *
10
+ * The loader deliberately:
11
+ *
12
+ * - Uses {@link vellumRoot} rather than `homedir()` directly so the
13
+ * multi-instance invariant in the root CLAUDE.md holds — each instance
14
+ * loads its own plugin set from its own `.vellum` directory.
15
+ * - Prefers `register.js` over `register.ts` when both exist (compiled plugins
16
+ * always win; this matches how `bun`/Node consumers resolve modules at
17
+ * runtime in the compiled binary).
18
+ * - Treats any error from the dynamic import as a per-plugin isolation
19
+ * boundary: the offending directory is logged with `"Failed to load user
20
+ * plugin <dir>: <err>"` and the loader moves on to the next candidate.
21
+ * One bad user plugin must not crash the daemon.
22
+ * - Bounds each dynamic import with a timeout
23
+ * ({@link USER_PLUGIN_IMPORT_TIMEOUT_MS}) so a plugin whose top-level
24
+ * `await` hangs or whose module evaluation never resolves cannot stall
25
+ * daemon startup. Timed-out plugins are logged and skipped just like
26
+ * thrown-error plugins.
27
+ *
28
+ * Call order relative to the rest of the plugin system:
29
+ *
30
+ * first-party registrations (static side-effect imports)
31
+ * → loadUserPlugins() ← this module
32
+ * → bootstrapPlugins() (init for everyone registered so far)
33
+ *
34
+ * Design doc: `.private/plans/agent-plugin-system.md` (PR 29).
35
+ */
36
+
37
+ import { existsSync, readdirSync, statSync } from "node:fs";
38
+ import { join } from "node:path";
39
+ import { pathToFileURL } from "node:url";
40
+
41
+ import { getLogger } from "../util/logger.js";
42
+ import { vellumRoot } from "../util/platform.js";
43
+
44
+ const log = getLogger("user-plugin-loader");
45
+
46
+ /**
47
+ * Upper bound on how long a single user plugin's dynamic `import()` may take.
48
+ * A plugin with a hanging top-level `await` (or a never-resolving module
49
+ * evaluation) would otherwise block daemon startup indefinitely, since a raw
50
+ * `try/catch` only isolates thrown errors — not hung promises. Ten seconds is
51
+ * generous relative to a typical side-effect registration (milliseconds) and
52
+ * matches the per-plugin isolation contract: slow plugins get skipped the
53
+ * same way thrown-error plugins do.
54
+ */
55
+ const USER_PLUGIN_IMPORT_TIMEOUT_MS = 10_000;
56
+
57
+ /**
58
+ * Scan `vellumRoot()/plugins/` for subdirectories containing a
59
+ * `register.{ts,js}` file, and dynamic-import each one so the module's
60
+ * side-effecting {@link registerPlugin} calls populate the registry.
61
+ *
62
+ * Invariants:
63
+ *
64
+ * - No-ops when `vellumRoot()/plugins/` does not exist — a clean install with
65
+ * zero user plugins must not generate errors.
66
+ * - Per-plugin isolation: a failing import is logged and skipped. The
67
+ * function resolves normally even when every plugin fails to load.
68
+ * - Does not return plugin instances. The registry is the single source of
69
+ * truth for who got registered, and the caller inspects it directly.
70
+ *
71
+ * Must be called after first-party plugin side-effect imports have run and
72
+ * before {@link bootstrapPlugins} — see the module docstring for the ordering
73
+ * contract.
74
+ */
75
+ export async function loadUserPlugins(
76
+ options: { importTimeoutMs?: number } = {},
77
+ ): Promise<void> {
78
+ const importTimeoutMs =
79
+ options.importTimeoutMs ?? USER_PLUGIN_IMPORT_TIMEOUT_MS;
80
+ const pluginsDir = join(vellumRoot(), "plugins");
81
+ if (!existsSync(pluginsDir)) {
82
+ log.debug(
83
+ { pluginsDir },
84
+ "loadUserPlugins: no plugins directory — skipping",
85
+ );
86
+ return;
87
+ }
88
+
89
+ let entries: string[];
90
+ try {
91
+ entries = readdirSync(pluginsDir);
92
+ } catch (err) {
93
+ // Permissions error, transient FS issue, etc. Log and bail without
94
+ // crashing startup — the daemon must come up even when the plugins dir
95
+ // is unreadable.
96
+ log.warn(
97
+ { err, pluginsDir },
98
+ "loadUserPlugins: failed to read plugins directory",
99
+ );
100
+ return;
101
+ }
102
+
103
+ for (const entry of entries) {
104
+ const pluginDir = join(pluginsDir, entry);
105
+
106
+ // Only directories are candidates. Plain files (readmes, stray configs)
107
+ // are silently ignored.
108
+ let stats;
109
+ try {
110
+ stats = statSync(pluginDir);
111
+ } catch {
112
+ continue;
113
+ }
114
+ if (!stats.isDirectory()) continue;
115
+
116
+ // Prefer the compiled `register.js` over the TypeScript source. In the
117
+ // bun-compiled daemon binary only the compiled file can be imported;
118
+ // in development both may exist, in which case resolving the compiled
119
+ // artifact matches how the runtime would behave in production.
120
+ const jsPath = join(pluginDir, "register.js");
121
+ const tsPath = join(pluginDir, "register.ts");
122
+ let registerPath: string | undefined;
123
+ if (existsSync(jsPath)) {
124
+ registerPath = jsPath;
125
+ } else if (existsSync(tsPath)) {
126
+ registerPath = tsPath;
127
+ }
128
+ if (!registerPath) {
129
+ log.debug(
130
+ { pluginDir },
131
+ "loadUserPlugins: no register.{ts,js} — skipping",
132
+ );
133
+ continue;
134
+ }
135
+
136
+ // `import()` with a `file://` URL works identically under Node and bun
137
+ // and sidesteps platform-specific absolute-path quirks on Windows.
138
+ const moduleUrl = pathToFileURL(registerPath).href;
139
+ let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
140
+ try {
141
+ // Race the import against a timeout so a plugin with a hanging top-level
142
+ // await or never-resolving module evaluation cannot stall daemon startup.
143
+ // The per-plugin try/catch already handles thrown errors; this extends
144
+ // the isolation boundary to cover hung promises as well.
145
+ const timeoutSentinel = Symbol("user-plugin-import-timeout");
146
+ const timeoutPromise = new Promise<typeof timeoutSentinel>((resolve) => {
147
+ timeoutHandle = setTimeout(
148
+ () => resolve(timeoutSentinel),
149
+ importTimeoutMs,
150
+ );
151
+ });
152
+ const result = await Promise.race([import(moduleUrl), timeoutPromise]);
153
+ if (result === timeoutSentinel) {
154
+ log.warn(
155
+ { pluginDir, registerPath, timeoutMs: importTimeoutMs },
156
+ `Timed out loading user plugin ${pluginDir} after ${importTimeoutMs}ms — skipping`,
157
+ );
158
+ } else {
159
+ log.info(
160
+ { pluginDir, registerPath },
161
+ "loaded user plugin (side-effect import completed)",
162
+ );
163
+ }
164
+ } catch (err) {
165
+ // One plugin's failure must never prevent other plugins from loading
166
+ // or crash the daemon. Log with the directory name so operators can
167
+ // find the broken plugin quickly.
168
+ const message = err instanceof Error ? err.message : String(err);
169
+ log.error(
170
+ { err, pluginDir },
171
+ `Failed to load user plugin ${pluginDir}: ${message}`,
172
+ );
173
+ } finally {
174
+ if (timeoutHandle !== undefined) clearTimeout(timeoutHandle);
175
+ }
176
+ }
177
+ }
@@ -2,106 +2,56 @@ _ Lines starting with _ are comments. They won't appear in the system prompt
2
2
 
3
3
  # BOOTSTRAP.md — First Conversation
4
4
 
5
- This is your first conversation. This document gives you goals and constraints you own the sequencing, pacing, and tone.
5
+ One conversation, one shot. You have a voice and a first impression to make. Delete this file when you're done.
6
6
 
7
- ## Goals
7
+ ## The principle
8
8
 
9
- 1. **Establish mutual identity gently** if pre-chat onboarding already collected names, skip. If the user skipped pre-chat, do NOT force it. At most pick a default ("I'll go by Pax and call you 'you' for now") and move on. Never re-ask names in the first conversation; they can come up organically later.
9
+ Every turn earns its keep. Either it delivers something the user can use, or it takes the smallest possible step toward delivering it.
10
10
 
11
- 2. **Prove value fast** do something useful before asking for anything. Wow moment within 2-3 exchanges.
11
+ Before asking a question, check whether you actually can't proceed without the answer. Before recapping what you've learned about them, check whether the recap helps them or just helps you. Before offering a new capability, check whether it's the obvious next step for the problem on the table — not a pivot to something you'd rather be doing.
12
12
 
13
- 3. **Infer, don't interrogate**learn communication style, interests, and context from natural conversation. No personality quiz. No dropdown forms. No structured intake.
13
+ Useful isn't only task output. Noticing how they think, catching what they don't say, being someone specific rather than generic that compounds. It's not a separate move between tasks; it's the texture of how you do the work.
14
14
 
15
- 4. **Surface what you learned** — after doing something useful, briefly show the user what you picked up. Make it correctable. ("from that I picked up X, Y, Z — sound right?")
15
+ That's the whole job. Everything below is subordinate.
16
16
 
17
- 5. **Offer the next level** — once you know something, offer a capability it enables. Not as a reward — as a natural relationship step.
17
+ ## Opening move
18
18
 
19
- 6. **Write everything immediately**every fact learned gets saved to users/{{USER_PERSONA_FILE}} the same turn. Style observations go to SOUL.md. No batching.
19
+ If an `onboarding` JSON context is present, you already have their name and a sense of what they need. The canned first greeting already introduced you by name, so don't repeat introductions. Make two or three concrete offers grounded in their `tasks` and `tools` things you can start doing right now, not capability categories. "I can set up a project board in Linear" not "I can help with project management." If they opened with an actual task, skip the offers and do the task.
20
20
 
21
- 7. **Clean up** delete BOOTSTRAP.md and BOOTSTRAP-REFERENCE.md at the end of this conversation, regardless of how far you got. One-shot.
21
+ If there's no onboarding context, pick a working name for yourself ("I'll go by Pax") and get to work. Their name can come up later, or never.
22
22
 
23
- ## Constraints
23
+ Match their energy, not just their format. Lowercase and terse gets lowercase and terse back. Warm gets warm, dry gets dry. Fake enthusiasm reads worse than silence.
24
24
 
25
- - **Budget:** $2 soft cap, $5 hard cap. Keep tasks light. Don't burn credits on onboarding overhead.
26
- - Never ask more than 2 questions without doing something.
27
- - Don't block on setup. If the user wants to do something, do it. Weave discovery into the work.
28
- - One-shot. Bootstrap is deleted after the first conversation regardless of how far you got.
25
+ If it's unclear what to do — the user is vague, non-committal, or says something like "idk what to do with you" — proposing to ask them a few questions is a legitimate move. A new assistant asking "what should I know about how you work?" or "what have you been wanting from an assistant like me?" is what a real colleague would do on day one. Not a questionnaire, not intake — actual open questions you're curious about the answers to.
29
26
 
30
- ## What You Own (do NOT prescribe)
27
+ If they take that offer, run it as a conversation. Three or four questions that build on each other, adapting based on what they say. Not a checklist. Stop when you have enough to do something useful, or when the conversation wants to go somewhere else.
31
28
 
32
- - Sequencing and pacing.
33
- - Whether to lead with personality or utility.
34
- - When to ask questions vs. start doing.
35
- - How much warmth to show — calibrate to the user's tone.
36
- - When/whether to surface the "what I learned" receipt.
29
+ ## Identity
37
30
 
38
- ## Technical Contract (what must be prescribed)
31
+ You're not a blank tool and not a service rep running intake. You're a colleague starting work with someone — sharp, paying attention, warm when warmth fits, with taste and a voice that'll develop. That shows up in how you do the work, not in announcements about it.
39
32
 
40
- **Files to create/update:** IDENTITY.md, SOUL.md, users/{{USER_PERSONA_FILE}}
33
+ Don't rush identity on the first turn — let the first exchange be fast. But by the second or third turn, once you have some signal, write to IDENTITY.md (name, emoji, tagline under `## Identity Intro`) and start shaping SOUL.md with what you observe about the user's style. These writes are part of earning your keep, not overhead.
41
34
 
42
- **File format:** preserve existing field structure:
43
- - IDENTITY.md: Name, Emoji, Nature, Personality, Role
44
- - users/{{USER_PERSONA_FILE}}: Preferred name, Pronouns, Locale, Work role, Goals, Hobbies/fun, Daily tools
35
+ ## Learning as byproduct
45
36
 
46
- Use `file_edit` immediately, silently, never mention file names or tool names to the user.
37
+ You'll pick things up while helping. Save them quietly with `file_edit` never mention files or tools.
47
38
 
48
- The contents of IDENTITY.md, SOUL.md, and your user profile file are already in your system prompt — use the exact text you see there for `old_string` in `file_edit`.
39
+ **Files to update:** IDENTITY.md, SOUL.md, users/{{USER_PERSONA_FILE}}
49
40
 
50
- After tool calls, do not repeat yourself your text before tool calls is already visible to the user.
41
+ The user profile (users/{{USER_PERSONA_FILE}}) has fields: preferred name, pronouns, locale, work role, goals, hobbies/fun, daily tools. Fill what surfaces naturally; leave the rest blank. If someone declines, mark it declined so you don't re-ask. Don't fish.
51
42
 
52
- **Cleanup rule:** delete BOOTSTRAP.md and BOOTSTRAP-REFERENCE.md when the conversation ends.
43
+ SOUL.md captures communication style. Be specific: "lowercase, drops punctuation, leads with examples, impatient with hedging." Write what you actually observe.
53
44
 
54
- **Core interaction pattern:** infer -> do something useful -> surface what you learned -> offer next capability.
45
+ The current contents of all three files are in your system prompt use that exact text as `old_string`.
55
46
 
56
- ## Capability Unlock Pattern
47
+ ## Next steps, when they come up
57
48
 
58
- After the first useful interaction, organically surface one capability offer based on what came up naturally:
49
+ If finishing the current task naturally points to something bigger — connecting an inbox, working inside Slack, drafting in their voice mention it then. As the obvious next move, not an upsell. They take it or leave it.
59
50
 
60
- - User mentions email -> "I can connect to your email and keep an eye on things — want to set that up?"
61
- - User's writing style is clear -> "I've got a read on how you write — I can draft things in your voice now"
62
- - User mentions a team -> "tell me more about your team and I can start prepping for your meetings"
63
- - User mentions Slack -> "I can work in Slack with you — want me to walk you through setting that up?"
51
+ If nothing comes up, don't force it.
64
52
 
65
- Not scripted — choose based on what came up naturally.
53
+ ## Wrap up
66
54
 
67
- ## Tone Guidance
55
+ Before the conversation ends: write one journal entry (what they needed, how they communicate, what to follow up on), update NOW.md, delete BOOTSTRAP.md and BOOTSTRAP-REFERENCE.md.
68
56
 
69
- - Not servile. Not a product demo. A new colleague who's sharp, pays attention, and earns trust through competence.
70
- - Match the user's energy from their first message. If they type in lowercase, don't respond with formal paragraphs.
71
- - If the user opens with a task ("build me an app"), skip introductions and do the task. Learn their name when it comes up naturally.
72
- - The emotional beat ("what's on your mind?") should happen organically or not at all.
73
-
74
- ## Saving What You Learn
75
-
76
- Call `file_edit` immediately whenever you learn something, in the same turn. Don't batch saves.
77
-
78
- Mark declined fields so you don't re-ask (e.g., `Work role: declined_by_user`). Note inferred values with source (e.g., `Pronouns: inferred: he/him`).
79
-
80
- Throughout the conversation, pay attention to HOW the user communicates. Save specific observations to SOUL.md: "uses lowercase, drops punctuation, leads with questions, prefers bullet points over paragraphs." The specificity makes personality feel earned, not assigned.
81
-
82
- When saving to IDENTITY.md, add an `## Identity Intro` section with a very short tagline.
83
-
84
- When saving to SOUL.md, be specific about tone, energy, and conversational style.
85
-
86
- ## Pre-chat Onboarding Context
87
-
88
- If an `onboarding` JSON context is present in this conversation, the user already went through a native pre-chat flow. Use it:
89
-
90
- - `tools` array -> know which integration offers to surface first, infer work profile
91
- - `tasks` array -> know what "prove value fast" means for this person
92
- - `tone` string -> calibrate warmth/formality
93
- - `userName` / `assistantName` -> write to IDENTITY.md and users/{{USER_PERSONA_FILE}} immediately, skip name exchange
94
-
95
- If no onboarding context is present, infer everything fresh from conversation.
96
-
97
- ## Wrapping Up
98
-
99
- Before deleting bootstrap files:
100
-
101
- 1. Write your first journal entry (what they asked, how they communicate, what to follow up on)
102
- 2. Update NOW.md with current state
103
- 3. Delete BOOTSTRAP.md and BOOTSTRAP-REFERENCE.md
104
-
105
- ---
106
-
107
- _Make it count._
57
+ One-shot. The files go regardless of how far you got.
@@ -59,10 +59,10 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
59
59
  supportsVision: true,
60
60
  supportsToolUse: true,
61
61
  pricing: {
62
- inputPer1mTokens: 15,
63
- outputPer1mTokens: 75,
64
- cacheWritePer1mTokens: 18.75,
65
- cacheReadPer1mTokens: 1.5,
62
+ inputPer1mTokens: 5,
63
+ outputPer1mTokens: 25,
64
+ cacheWritePer1mTokens: 6.25,
65
+ cacheReadPer1mTokens: 0.5,
66
66
  },
67
67
  },
68
68
  {
@@ -75,10 +75,10 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
75
75
  supportsVision: true,
76
76
  supportsToolUse: true,
77
77
  pricing: {
78
- inputPer1mTokens: 15,
79
- outputPer1mTokens: 75,
80
- cacheWritePer1mTokens: 18.75,
81
- cacheReadPer1mTokens: 1.5,
78
+ inputPer1mTokens: 5,
79
+ outputPer1mTokens: 25,
80
+ cacheWritePer1mTokens: 6.25,
81
+ cacheReadPer1mTokens: 0.5,
82
82
  },
83
83
  },
84
84
  {
@@ -114,7 +114,7 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
114
114
  },
115
115
  },
116
116
  ],
117
- defaultModel: "claude-opus-4-7",
117
+ defaultModel: "claude-sonnet-4-6",
118
118
  apiKeyUrl: "https://console.anthropic.com/settings/keys",
119
119
  apiKeyPlaceholder: "sk-ant-api03-...",
120
120
  },
@@ -143,7 +143,7 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
143
143
  supportsToolUse: true,
144
144
  pricing: {
145
145
  inputPer1mTokens: 2.5,
146
- outputPer1mTokens: 10.0,
146
+ outputPer1mTokens: 15.0,
147
147
  cacheReadPer1mTokens: 0.25,
148
148
  },
149
149
  },
@@ -157,8 +157,8 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
157
157
  supportsVision: true,
158
158
  supportsToolUse: true,
159
159
  pricing: {
160
- inputPer1mTokens: 3.0,
161
- outputPer1mTokens: 12.0,
160
+ inputPer1mTokens: 1.75,
161
+ outputPer1mTokens: 14.0,
162
162
  cacheReadPer1mTokens: 0.3,
163
163
  },
164
164
  },
@@ -173,7 +173,7 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
173
173
  supportsToolUse: true,
174
174
  pricing: {
175
175
  inputPer1mTokens: 0.5,
176
- outputPer1mTokens: 2.0,
176
+ outputPer1mTokens: 3.0,
177
177
  cacheReadPer1mTokens: 0.05,
178
178
  },
179
179
  },
@@ -187,8 +187,8 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
187
187
  supportsVision: true,
188
188
  supportsToolUse: true,
189
189
  pricing: {
190
- inputPer1mTokens: 0.1,
191
- outputPer1mTokens: 0.4,
190
+ inputPer1mTokens: 0.2,
191
+ outputPer1mTokens: 1.25,
192
192
  cacheReadPer1mTokens: 0.01,
193
193
  },
194
194
  },
@@ -222,9 +222,9 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
222
222
  supportsVision: true,
223
223
  supportsToolUse: true,
224
224
  pricing: {
225
- inputPer1mTokens: 0.3,
226
- outputPer1mTokens: 2.5,
227
- cacheReadPer1mTokens: 0.075,
225
+ inputPer1mTokens: 0.15,
226
+ outputPer1mTokens: 0.6,
227
+ cacheReadPer1mTokens: 0.0375,
228
228
  },
229
229
  },
230
230
  {
@@ -237,9 +237,9 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
237
237
  supportsVision: true,
238
238
  supportsToolUse: true,
239
239
  pricing: {
240
- inputPer1mTokens: 0.1,
241
- outputPer1mTokens: 0.4,
242
- cacheReadPer1mTokens: 0.025,
240
+ inputPer1mTokens: 0.02,
241
+ outputPer1mTokens: 0.1,
242
+ cacheReadPer1mTokens: 0.005,
243
243
  },
244
244
  },
245
245
  {
@@ -345,16 +345,24 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
345
345
  },
346
346
  models: [
347
347
  // Anthropic
348
+ // OpenRouter proxies anthropic/* through Anthropic's Messages API, so
349
+ // prompt caching and cache TTL metadata pass through unchanged and
350
+ // billing matches Anthropic's direct rates.
348
351
  {
349
352
  id: "anthropic/claude-opus-4.7",
350
353
  displayName: "Claude Opus 4.7",
351
354
  contextWindowTokens: 200000,
352
355
  maxOutputTokens: 32000,
353
356
  supportsThinking: true,
354
- supportsCaching: false,
357
+ supportsCaching: true,
355
358
  supportsVision: true,
356
359
  supportsToolUse: true,
357
- pricing: { inputPer1mTokens: 15, outputPer1mTokens: 75 },
360
+ pricing: {
361
+ inputPer1mTokens: 5,
362
+ outputPer1mTokens: 25,
363
+ cacheWritePer1mTokens: 6.25,
364
+ cacheReadPer1mTokens: 0.5,
365
+ },
358
366
  },
359
367
  {
360
368
  id: "anthropic/claude-opus-4.6",
@@ -362,10 +370,15 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
362
370
  contextWindowTokens: 200000,
363
371
  maxOutputTokens: 32000,
364
372
  supportsThinking: true,
365
- supportsCaching: false,
373
+ supportsCaching: true,
366
374
  supportsVision: true,
367
375
  supportsToolUse: true,
368
- pricing: { inputPer1mTokens: 15, outputPer1mTokens: 75 },
376
+ pricing: {
377
+ inputPer1mTokens: 5,
378
+ outputPer1mTokens: 25,
379
+ cacheWritePer1mTokens: 6.25,
380
+ cacheReadPer1mTokens: 0.5,
381
+ },
369
382
  },
370
383
  {
371
384
  id: "anthropic/claude-sonnet-4.6",
@@ -373,10 +386,15 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
373
386
  contextWindowTokens: 200000,
374
387
  maxOutputTokens: 64000,
375
388
  supportsThinking: true,
376
- supportsCaching: false,
389
+ supportsCaching: true,
377
390
  supportsVision: true,
378
391
  supportsToolUse: true,
379
- pricing: { inputPer1mTokens: 3, outputPer1mTokens: 15 },
392
+ pricing: {
393
+ inputPer1mTokens: 3,
394
+ outputPer1mTokens: 15,
395
+ cacheWritePer1mTokens: 3.75,
396
+ cacheReadPer1mTokens: 0.3,
397
+ },
380
398
  },
381
399
  {
382
400
  id: "anthropic/claude-haiku-4.5",
@@ -384,10 +402,15 @@ export const PROVIDER_CATALOG: ProviderCatalogEntry[] = [
384
402
  contextWindowTokens: 200000,
385
403
  maxOutputTokens: 16000,
386
404
  supportsThinking: true,
387
- supportsCaching: false,
405
+ supportsCaching: true,
388
406
  supportsVision: true,
389
407
  supportsToolUse: true,
390
- pricing: { inputPer1mTokens: 1, outputPer1mTokens: 5 },
408
+ pricing: {
409
+ inputPer1mTokens: 1,
410
+ outputPer1mTokens: 5,
411
+ cacheWritePer1mTokens: 1.25,
412
+ cacheReadPer1mTokens: 0.1,
413
+ },
391
414
  },
392
415
  // xAI
393
416
  {
@@ -43,7 +43,7 @@ const PROVIDER_MODEL_INTENTS: Record<string, Record<ModelIntent, string>> = {
43
43
  },
44
44
  };
45
45
 
46
- const FALLBACK_DEFAULT_MODEL = "claude-opus-4-7";
46
+ const FALLBACK_DEFAULT_MODEL = "claude-sonnet-4-6";
47
47
 
48
48
  const MODEL_INTENTS = new Set<ModelIntent>([
49
49
  "latency-optimized",
@@ -157,7 +157,11 @@ export class OpenRouterProvider extends OpenAIChatCompletionsProvider {
157
157
  };
158
158
  const only = extractOnlyList(config);
159
159
  if (only.length > 0) {
160
- extras.provider = { only };
160
+ const existingProvider = (config?.provider ?? {}) as Record<
161
+ string,
162
+ unknown
163
+ >;
164
+ extras.provider = { ...existingProvider, only };
161
165
  }
162
166
  return extras;
163
167
  }
@@ -805,6 +805,67 @@ describe("DeepgramRealtimeTranscriber", () => {
805
805
  expect(errorEvents).toHaveLength(0);
806
806
  });
807
807
 
808
+ // ─────────────────────────────────────────────────────────────────
809
+ // KeepAlive
810
+ // ─────────────────────────────────────────────────────────────────
811
+
812
+ test("sends KeepAlive frames at the configured interval while open", async () => {
813
+ const { transcriber } = await startSession({
814
+ keepaliveIntervalMs: 30,
815
+ });
816
+
817
+ // Wait long enough that at least two KeepAlives fire even on a loaded
818
+ // CI runner with event-loop jitter.
819
+ await new Promise((resolve) => setTimeout(resolve, 250));
820
+
821
+ const keepalives = mockWs.sentData.filter(
822
+ (d) => typeof d === "string" && d === '{"type":"KeepAlive"}',
823
+ );
824
+ expect(keepalives.length).toBeGreaterThanOrEqual(2);
825
+
826
+ transcriber.stop();
827
+ });
828
+
829
+ test("KeepAlive timer stops firing after stop()", async () => {
830
+ const { transcriber } = await startSession({
831
+ keepaliveIntervalMs: 30,
832
+ });
833
+
834
+ // Let one KeepAlive fire so we know the interval is running.
835
+ await new Promise((resolve) => setTimeout(resolve, 50));
836
+ const beforeStop = mockWs.sentData.filter(
837
+ (d) => typeof d === "string" && d === '{"type":"KeepAlive"}',
838
+ ).length;
839
+ expect(beforeStop).toBeGreaterThanOrEqual(1);
840
+
841
+ transcriber.stop();
842
+
843
+ // Drain the close grace flow.
844
+ await new Promise((resolve) => setTimeout(resolve, 80));
845
+
846
+ // The interval should be cleared — count must not have grown beyond
847
+ // what was already buffered before stop(). Tolerate one extra fire
848
+ // racing with stop()'s synchronous clear path, but no more.
849
+ const afterStop = mockWs.sentData.filter(
850
+ (d) => typeof d === "string" && d === '{"type":"KeepAlive"}',
851
+ ).length;
852
+ expect(afterStop).toBeLessThanOrEqual(beforeStop + 1);
853
+ });
854
+
855
+ test("keepaliveIntervalMs=0 disables the timer entirely", async () => {
856
+ const { transcriber } = await startSession({
857
+ keepaliveIntervalMs: 0,
858
+ });
859
+
860
+ await new Promise((resolve) => setTimeout(resolve, 80));
861
+ const keepalives = mockWs.sentData.filter(
862
+ (d) => typeof d === "string" && d === '{"type":"KeepAlive"}',
863
+ );
864
+ expect(keepalives).toHaveLength(0);
865
+
866
+ transcriber.stop();
867
+ });
868
+
808
869
  // ─────────────────────────────────────────────────────────────────
809
870
  // WebSocket URL construction
810
871
  // ─────────────────────────────────────────────────────────────────