@vellumai/assistant 0.6.3 → 0.6.4

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 (667) hide show
  1. package/ARCHITECTURE.md +273 -10
  2. package/Dockerfile +2 -3
  3. package/bun.lock +5 -13
  4. package/docs/backup-troubleshooting.md +52 -0
  5. package/docs/browser-use-architecture-phase2.md +174 -0
  6. package/docs/stt-provider-onboarding.md +120 -0
  7. package/knip.json +12 -2
  8. package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
  9. package/node_modules/@vellumai/ces-contracts/package.json +3 -3
  10. package/openapi.yaml +982 -72
  11. package/package.json +4 -6
  12. package/scripts/generate-openapi.ts +0 -1
  13. package/scripts/test.sh +73 -18
  14. package/src/__tests__/agent-image-optimize.test.ts +28 -0
  15. package/src/__tests__/agent-loop.test.ts +123 -0
  16. package/src/__tests__/anthropic-provider.test.ts +263 -10
  17. package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
  18. package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
  19. package/src/__tests__/browser-fill-credential.test.ts +11 -0
  20. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  21. package/src/__tests__/browser-skill-endstate.test.ts +31 -7
  22. package/src/__tests__/btw-routes.test.ts +7 -0
  23. package/src/__tests__/call-controller.test.ts +581 -20
  24. package/src/__tests__/catalog-files.test.ts +138 -0
  25. package/src/__tests__/channel-invite-transport.test.ts +2 -2
  26. package/src/__tests__/channel-readiness-routes.test.ts +16 -20
  27. package/src/__tests__/channel-readiness-service.test.ts +12 -7
  28. package/src/__tests__/checker.test.ts +157 -10
  29. package/src/__tests__/clawhub-files.test.ts +347 -0
  30. package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
  31. package/src/__tests__/config-analysis.test.ts +100 -0
  32. package/src/__tests__/config-schema.test.ts +1013 -66
  33. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
  34. package/src/__tests__/config-watcher.test.ts +43 -8
  35. package/src/__tests__/contact-store-user-file.test.ts +512 -0
  36. package/src/__tests__/contacts-write.test.ts +197 -0
  37. package/src/__tests__/context-window-manager.test.ts +88 -0
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  40. package/src/__tests__/conversation-agent-loop.test.ts +98 -2
  41. package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
  42. package/src/__tests__/conversation-error.test.ts +70 -0
  43. package/src/__tests__/conversation-history-web-search.test.ts +11 -4
  44. package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
  45. package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
  46. package/src/__tests__/conversation-list-source.test.ts +145 -0
  47. package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
  48. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  49. package/src/__tests__/conversation-queue.test.ts +901 -60
  50. package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
  51. package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
  52. package/src/__tests__/conversation-skill-tools.test.ts +7 -4
  53. package/src/__tests__/conversation-slash-commands.test.ts +33 -0
  54. package/src/__tests__/conversation-slash-queue.test.ts +89 -18
  55. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  56. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  59. package/src/__tests__/credential-health-service.test.ts +352 -0
  60. package/src/__tests__/credential-security-invariants.test.ts +5 -3
  61. package/src/__tests__/credential-vault-unit.test.ts +379 -3
  62. package/src/__tests__/credentials-cli.test.ts +40 -16
  63. package/src/__tests__/cross-provider-web-search.test.ts +146 -35
  64. package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
  65. package/src/__tests__/device-id.test.ts +112 -0
  66. package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
  67. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
  68. package/src/__tests__/email-html-renderer.test.ts +71 -0
  69. package/src/__tests__/email-invite-adapter.test.ts +36 -32
  70. package/src/__tests__/emit-event-signal.test.ts +71 -0
  71. package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
  72. package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
  73. package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
  74. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  75. package/src/__tests__/gemini-provider.test.ts +64 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
  77. package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
  78. package/src/__tests__/gmail-archive-gate.test.ts +246 -0
  79. package/src/__tests__/gmail-preferences.test.ts +117 -0
  80. package/src/__tests__/headless-browser-interactions.test.ts +43 -0
  81. package/src/__tests__/headless-browser-mode.test.ts +614 -0
  82. package/src/__tests__/headless-browser-navigate.test.ts +142 -5
  83. package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
  84. package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
  85. package/src/__tests__/heartbeat-service.test.ts +70 -17
  86. package/src/__tests__/home-state-routes.test.ts +162 -0
  87. package/src/__tests__/host-bash-proxy.test.ts +0 -5
  88. package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
  89. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
  90. package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
  91. package/src/__tests__/host-cu-proxy.test.ts +0 -5
  92. package/src/__tests__/identity-intro-cache.test.ts +40 -10
  93. package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
  94. package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
  95. package/src/__tests__/llm-context-normalization.test.ts +488 -0
  96. package/src/__tests__/llm-context-route-provider.test.ts +86 -5
  97. package/src/__tests__/llm-usage-store.test.ts +363 -0
  98. package/src/__tests__/media-stream-output.test.ts +555 -0
  99. package/src/__tests__/media-stream-parser.test.ts +374 -0
  100. package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
  101. package/src/__tests__/media-stream-stt-session.test.ts +588 -0
  102. package/src/__tests__/media-turn-detector.test.ts +440 -0
  103. package/src/__tests__/message-queue.test.ts +125 -0
  104. package/src/__tests__/migration-export-http.test.ts +6 -6
  105. package/src/__tests__/migration-import-commit-http.test.ts +8 -6
  106. package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
  107. package/src/__tests__/migration-validate-http.test.ts +3 -3
  108. package/src/__tests__/mock-gateway-ipc.ts +151 -0
  109. package/src/__tests__/model-intents.test.ts +2 -2
  110. package/src/__tests__/oauth-apps-routes.test.ts +1 -0
  111. package/src/__tests__/oauth-cli.test.ts +2 -0
  112. package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
  113. package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
  114. package/src/__tests__/oauth-providers-routes.test.ts +2 -0
  115. package/src/__tests__/oauth-store.test.ts +85 -0
  116. package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
  117. package/src/__tests__/onboarding-template-contract.test.ts +6 -13
  118. package/src/__tests__/openai-provider.test.ts +176 -0
  119. package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
  120. package/src/__tests__/openai-responses-provider.test.ts +1105 -0
  121. package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
  122. package/src/__tests__/outlook-unsubscribe.test.ts +31 -2
  123. package/src/__tests__/persona-resolver.test.ts +251 -0
  124. package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
  125. package/src/__tests__/platform.test.ts +92 -1
  126. package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
  127. package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
  128. package/src/__tests__/pricing.test.ts +174 -0
  129. package/src/__tests__/qdrant-manager.test.ts +29 -8
  130. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
  131. package/src/__tests__/relationship-state-contract.test.ts +175 -0
  132. package/src/__tests__/relay-server.test.ts +423 -5
  133. package/src/__tests__/search-skills-unified.test.ts +118 -0
  134. package/src/__tests__/secret-scanner-executor.test.ts +4 -0
  135. package/src/__tests__/secure-keys.test.ts +107 -0
  136. package/src/__tests__/send-endpoint-busy.test.ts +5 -1
  137. package/src/__tests__/sequence-store.test.ts +1 -1
  138. package/src/__tests__/server-history-render.test.ts +49 -0
  139. package/src/__tests__/settings-routes.test.ts +201 -0
  140. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  141. package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
  142. package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
  143. package/src/__tests__/skills.test.ts +5 -2
  144. package/src/__tests__/skillssh-files.test.ts +446 -0
  145. package/src/__tests__/slack-block-formatting.test.ts +110 -0
  146. package/src/__tests__/slack-channel-config.test.ts +564 -1
  147. package/src/__tests__/stt-catalog-parity.test.ts +282 -0
  148. package/src/__tests__/stt-stream-session.test.ts +535 -0
  149. package/src/__tests__/system-prompt.test.ts +112 -26
  150. package/src/__tests__/telephony-stt-routing.test.ts +329 -0
  151. package/src/__tests__/terminal-tools.test.ts +18 -7
  152. package/src/__tests__/test-preload.ts +18 -0
  153. package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
  154. package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
  155. package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
  156. package/src/__tests__/tool-executor.test.ts +33 -24
  157. package/src/__tests__/tool-result-truncation.test.ts +36 -0
  158. package/src/__tests__/trust-store.test.ts +7 -1
  159. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
  160. package/src/__tests__/tts-catalog-parity.test.ts +345 -0
  161. package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
  162. package/src/__tests__/twilio-routes.test.ts +376 -0
  163. package/src/__tests__/unicode.test.ts +293 -0
  164. package/src/__tests__/update-bulletin-format.test.ts +59 -0
  165. package/src/__tests__/update-bulletin.test.ts +206 -5
  166. package/src/__tests__/usage-routes.test.ts +25 -4
  167. package/src/__tests__/user-reference.test.ts +46 -61
  168. package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
  169. package/src/__tests__/voice-config-update.test.ts +403 -0
  170. package/src/__tests__/voice-quality.test.ts +434 -19
  171. package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
  172. package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
  173. package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
  174. package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
  175. package/src/__tests__/workspace-migration-meets.test.ts +244 -0
  176. package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
  177. package/src/__tests__/workspace-policy.test.ts +2 -0
  178. package/src/agent/image-optimize.ts +24 -12
  179. package/src/agent/loop.ts +43 -3
  180. package/src/backup/__tests__/backup-key.test.ts +152 -0
  181. package/src/backup/__tests__/backup-worker.test.ts +767 -0
  182. package/src/backup/__tests__/list-snapshots.test.ts +87 -0
  183. package/src/backup/__tests__/local-writer.test.ts +218 -0
  184. package/src/backup/__tests__/offsite-writer.test.ts +641 -0
  185. package/src/backup/__tests__/paths.test.ts +300 -0
  186. package/src/backup/__tests__/restore.test.ts +498 -0
  187. package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
  188. package/src/backup/__tests__/stream-crypt.test.ts +228 -0
  189. package/src/backup/backup-key.ts +137 -0
  190. package/src/backup/backup-worker.ts +459 -0
  191. package/src/backup/list-snapshots.ts +147 -0
  192. package/src/backup/local-writer.ts +133 -0
  193. package/src/backup/offsite-writer.ts +222 -0
  194. package/src/backup/paths.ts +226 -0
  195. package/src/backup/restore.ts +322 -0
  196. package/src/backup/snapshot-lock.ts +431 -0
  197. package/src/backup/stream-crypt.ts +263 -0
  198. package/src/bundler/package-resolver.ts +4 -0
  199. package/src/calls/audio-store.ts +11 -5
  200. package/src/calls/call-controller.ts +226 -71
  201. package/src/calls/call-domain.ts +9 -0
  202. package/src/calls/call-speech-output.ts +190 -0
  203. package/src/calls/call-transport.ts +77 -0
  204. package/src/calls/media-stream-audio-transcode.ts +173 -0
  205. package/src/calls/media-stream-output.ts +660 -0
  206. package/src/calls/media-stream-parser.ts +300 -0
  207. package/src/calls/media-stream-protocol.ts +166 -0
  208. package/src/calls/media-stream-server.ts +592 -0
  209. package/src/calls/media-stream-stt-session.ts +460 -0
  210. package/src/calls/media-turn-detector.ts +230 -0
  211. package/src/calls/relay-server.ts +90 -75
  212. package/src/calls/resolve-call-tts-provider.ts +136 -0
  213. package/src/calls/telephony-stt-routing.ts +145 -0
  214. package/src/calls/tts-call-strategy.ts +161 -0
  215. package/src/calls/tts-text-sanitizer.ts +32 -16
  216. package/src/calls/twilio-routes.ts +281 -17
  217. package/src/calls/voice-quality.ts +78 -35
  218. package/src/calls/voice-session-bridge.ts +8 -1
  219. package/src/channels/types.ts +16 -0
  220. package/src/cli/__tests__/run-assistant-command.ts +11 -1
  221. package/src/cli/commands/__tests__/backup.test.ts +1165 -0
  222. package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
  223. package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
  224. package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
  225. package/src/cli/commands/__tests__/email-download.test.ts +16 -1
  226. package/src/cli/commands/__tests__/email-list.test.ts +22 -4
  227. package/src/cli/commands/__tests__/email-register.test.ts +4 -4
  228. package/src/cli/commands/__tests__/email-send.test.ts +37 -4
  229. package/src/cli/commands/__tests__/email-status.test.ts +5 -1
  230. package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
  231. package/src/cli/commands/backup.ts +993 -0
  232. package/src/cli/commands/conversations.ts +77 -0
  233. package/src/cli/commands/credentials.ts +0 -1
  234. package/src/cli/commands/domain.ts +210 -0
  235. package/src/cli/commands/email.ts +255 -3
  236. package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
  237. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
  238. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
  239. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
  240. package/src/cli/commands/oauth/mode.ts +12 -3
  241. package/src/cli/commands/oauth/providers.ts +15 -0
  242. package/src/cli/commands/oauth/shared.ts +2 -1
  243. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
  244. package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
  245. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  246. package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
  247. package/src/cli/program.ts +30 -4
  248. package/src/config/__tests__/backup-schema.test.ts +134 -0
  249. package/src/config/assistant-feature-flags.ts +61 -62
  250. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
  251. package/src/config/bundled-skills/browser/SKILL.md +30 -5
  252. package/src/config/bundled-skills/browser/TOOLS.json +123 -0
  253. package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
  254. package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
  255. package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
  256. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
  257. package/src/config/bundled-skills/contacts/SKILL.md +2 -2
  258. package/src/config/bundled-skills/gmail/SKILL.md +53 -7
  259. package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
  260. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
  261. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
  262. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
  263. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
  264. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
  265. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
  266. package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
  267. package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
  268. package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
  269. package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
  270. package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
  271. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
  272. package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
  273. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
  274. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  275. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  276. package/src/config/bundled-skills/outlook/SKILL.md +2 -2
  277. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
  278. package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
  279. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
  280. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  281. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  282. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
  283. package/src/config/bundled-skills/slack/SKILL.md +1 -0
  284. package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
  285. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
  286. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
  287. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
  288. package/src/config/bundled-tool-registry.ts +8 -0
  289. package/src/config/env-registry.ts +24 -0
  290. package/src/config/env.ts +34 -10
  291. package/src/config/feature-flag-registry.json +46 -14
  292. package/src/config/loader.ts +26 -12
  293. package/src/config/schema.ts +35 -10
  294. package/src/config/schemas/__tests__/stt.test.ts +43 -0
  295. package/src/config/schemas/analysis.ts +51 -0
  296. package/src/config/schemas/backup.ts +72 -0
  297. package/src/config/schemas/calls.ts +1 -26
  298. package/src/config/schemas/elevenlabs.ts +0 -59
  299. package/src/config/schemas/filing.ts +47 -7
  300. package/src/config/schemas/heartbeat.ts +27 -5
  301. package/src/config/schemas/host-browser.ts +47 -1
  302. package/src/config/schemas/inference.ts +1 -1
  303. package/src/config/schemas/memory-lifecycle.ts +14 -2
  304. package/src/config/schemas/services.ts +44 -0
  305. package/src/config/schemas/stt.ts +59 -0
  306. package/src/config/schemas/tts.ts +230 -0
  307. package/src/config/schemas/updates.ts +14 -0
  308. package/src/config/skills.ts +4 -0
  309. package/src/config/types.ts +4 -0
  310. package/src/contacts/contact-store.ts +56 -11
  311. package/src/contacts/contacts-write.ts +38 -1
  312. package/src/context/post-turn-tool-result-truncation.ts +3 -2
  313. package/src/context/tool-result-truncation.ts +2 -1
  314. package/src/context/window-manager.ts +45 -12
  315. package/src/credential-execution/executable-discovery.ts +12 -2
  316. package/src/credential-execution/process-manager.ts +33 -2
  317. package/src/credential-health/credential-health-service.ts +366 -0
  318. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
  319. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
  320. package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
  321. package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
  322. package/src/daemon/config-watcher.ts +99 -5
  323. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  324. package/src/daemon/conversation-agent-loop.ts +101 -24
  325. package/src/daemon/conversation-error.ts +11 -0
  326. package/src/daemon/conversation-history.ts +40 -6
  327. package/src/daemon/conversation-launch.ts +220 -0
  328. package/src/daemon/conversation-lifecycle.ts +59 -9
  329. package/src/daemon/conversation-messaging.ts +37 -3
  330. package/src/daemon/conversation-notifiers.ts +5 -0
  331. package/src/daemon/conversation-process.ts +581 -19
  332. package/src/daemon/conversation-queue-manager.ts +24 -0
  333. package/src/daemon/conversation-runtime-assembly.ts +11 -1
  334. package/src/daemon/conversation-slash.ts +36 -0
  335. package/src/daemon/conversation-surfaces.ts +94 -4
  336. package/src/daemon/conversation-tool-setup.ts +25 -0
  337. package/src/daemon/conversation-usage.ts +7 -4
  338. package/src/daemon/conversation.ts +86 -28
  339. package/src/daemon/handlers/config-slack-channel.ts +269 -94
  340. package/src/daemon/handlers/conversations.ts +4 -1
  341. package/src/daemon/handlers/shared.ts +22 -0
  342. package/src/daemon/handlers/skills.ts +321 -77
  343. package/src/daemon/host-browser-proxy.ts +2 -1
  344. package/src/daemon/lifecycle.ts +122 -25
  345. package/src/daemon/message-protocol.ts +6 -0
  346. package/src/daemon/message-types/conversations.ts +34 -1
  347. package/src/daemon/message-types/home.ts +40 -0
  348. package/src/daemon/message-types/meet.ts +143 -0
  349. package/src/daemon/message-types/messages.ts +14 -0
  350. package/src/daemon/message-types/schedules.ts +34 -2
  351. package/src/daemon/message-types/skills.ts +16 -0
  352. package/src/daemon/message-types/surfaces.ts +2 -0
  353. package/src/daemon/server.ts +347 -2
  354. package/src/daemon/shutdown-handlers.ts +32 -4
  355. package/src/daemon/shutdown-registry.ts +40 -0
  356. package/src/daemon/tool-side-effects.ts +9 -0
  357. package/src/email/html-renderer.ts +76 -0
  358. package/src/heartbeat/heartbeat-service.ts +93 -7
  359. package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
  360. package/src/home/__tests__/emit-feed-event.test.ts +169 -0
  361. package/src/home/__tests__/feed-scheduler.test.ts +194 -0
  362. package/src/home/__tests__/feed-types.test.ts +275 -0
  363. package/src/home/__tests__/feed-writer.test.ts +688 -0
  364. package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
  365. package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
  366. package/src/home/__tests__/progress-formula.test.ts +213 -0
  367. package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
  368. package/src/home/__tests__/rollup-producer.test.ts +398 -0
  369. package/src/home/assistant-feed-authoring.ts +124 -0
  370. package/src/home/emit-feed-event.ts +158 -0
  371. package/src/home/feed-scheduler.ts +247 -0
  372. package/src/home/feed-types.ts +181 -0
  373. package/src/home/feed-writer.ts +469 -0
  374. package/src/home/platform-gmail-digest.ts +163 -0
  375. package/src/home/progress-formula.ts +86 -0
  376. package/src/home/relationship-state-writer.ts +824 -0
  377. package/src/home/relationship-state.ts +143 -0
  378. package/src/home/rollup-producer.ts +384 -0
  379. package/src/hooks/runner.ts +7 -0
  380. package/src/inbound/platform-callback-registration.ts +12 -3
  381. package/src/inbound/public-ingress-urls.ts +12 -0
  382. package/src/instrument.ts +1 -1
  383. package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
  384. package/src/ipc/cli-client.ts +151 -0
  385. package/src/ipc/cli-server.ts +234 -0
  386. package/src/ipc/gateway-client.ts +180 -0
  387. package/src/ipc/routes/index.ts +5 -0
  388. package/src/ipc/routes/wake-conversation.ts +19 -0
  389. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
  390. package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
  391. package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
  392. package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
  393. package/src/memory/app-store.ts +1 -1
  394. package/src/memory/attachments-store.ts +70 -0
  395. package/src/memory/auto-analysis-enqueue.ts +127 -0
  396. package/src/memory/auto-analysis-guard.ts +27 -0
  397. package/src/memory/cleanup-schedule-state.ts +37 -0
  398. package/src/memory/conversation-analyze-job.ts +73 -0
  399. package/src/memory/conversation-crud.ts +99 -0
  400. package/src/memory/conversation-disk-view.ts +7 -0
  401. package/src/memory/conversation-group-migration.ts +34 -2
  402. package/src/memory/conversation-queries.ts +6 -5
  403. package/src/memory/db-init.ts +6 -0
  404. package/src/memory/db-maintenance.ts +108 -0
  405. package/src/memory/db.ts +1 -0
  406. package/src/memory/graph/conversation-graph-memory.ts +15 -0
  407. package/src/memory/graph/extraction.test.ts +23 -0
  408. package/src/memory/graph/extraction.ts +8 -0
  409. package/src/memory/graph/retriever.ts +27 -18
  410. package/src/memory/graph/scoring.test.ts +186 -0
  411. package/src/memory/graph/scoring.ts +31 -1
  412. package/src/memory/graph/tools.ts +1 -1
  413. package/src/memory/group-crud.ts +6 -1
  414. package/src/memory/indexer.ts +95 -16
  415. package/src/memory/job-handlers/cleanup.ts +11 -8
  416. package/src/memory/job-handlers/conversation-starters.ts +16 -10
  417. package/src/memory/jobs-store.ts +64 -4
  418. package/src/memory/jobs-worker.ts +22 -9
  419. package/src/memory/llm-usage-store.ts +92 -56
  420. package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
  421. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
  422. package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
  423. package/src/memory/migrations/index.ts +6 -0
  424. package/src/memory/migrations/registry.ts +8 -0
  425. package/src/memory/qdrant-manager.ts +43 -16
  426. package/src/memory/schema/conversations.ts +2 -0
  427. package/src/memory/schema/oauth.ts +3 -0
  428. package/src/memory/usage-buckets.ts +396 -0
  429. package/src/messaging/providers/gmail/client.ts +57 -6
  430. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
  431. package/src/messaging/providers/slack/adapter.ts +143 -38
  432. package/src/messaging/providers/slack/client.ts +16 -0
  433. package/src/messaging/providers/slack/types.ts +4 -0
  434. package/src/notifications/decision-engine.ts +3 -3
  435. package/src/notifications/signal.ts +5 -0
  436. package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
  437. package/src/oauth/byo-connection.test.ts +18 -1
  438. package/src/oauth/byo-connection.ts +3 -1
  439. package/src/oauth/connect-orchestrator.ts +2 -0
  440. package/src/oauth/connection-resolver.ts +6 -2
  441. package/src/oauth/connection.ts +2 -0
  442. package/src/oauth/oauth-store.ts +9 -0
  443. package/src/oauth/platform-connection.test.ts +98 -0
  444. package/src/oauth/platform-connection.ts +52 -31
  445. package/src/oauth/seed-providers.ts +7 -0
  446. package/src/permissions/checker.ts +16 -6
  447. package/src/permissions/defaults.ts +49 -1
  448. package/src/permissions/trust-store.ts +3 -3
  449. package/src/permissions/workspace-policy.ts +3 -0
  450. package/src/platform/client.test.ts +10 -0
  451. package/src/platform/sync-identity.ts +129 -0
  452. package/src/prompts/persona-resolver.ts +126 -2
  453. package/src/prompts/system-prompt.ts +59 -18
  454. package/src/prompts/templates/BOOTSTRAP.md +5 -5
  455. package/src/prompts/templates/SOUL.md +3 -1
  456. package/src/prompts/templates/UPDATES.md +12 -0
  457. package/src/prompts/templates/channels/slack.md +20 -0
  458. package/src/prompts/update-bulletin-format.ts +26 -9
  459. package/src/prompts/update-bulletin.ts +34 -23
  460. package/src/prompts/user-reference.ts +20 -17
  461. package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
  462. package/src/providers/anthropic/client.ts +157 -61
  463. package/src/providers/fireworks/client.ts +2 -2
  464. package/src/providers/gemini/client.ts +9 -1
  465. package/src/providers/model-catalog.ts +6 -0
  466. package/src/providers/model-intents.ts +4 -4
  467. package/src/providers/ollama/client.ts +2 -2
  468. package/src/providers/openai/chat-completions-provider.ts +474 -0
  469. package/src/providers/openai/client.ts +25 -440
  470. package/src/providers/openai/responses-provider.ts +502 -0
  471. package/src/providers/openrouter/client.ts +101 -4
  472. package/src/providers/provider-secret-catalog.ts +139 -0
  473. package/src/providers/registry.ts +2 -2
  474. package/src/providers/retry.ts +14 -3
  475. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
  476. package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
  477. package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
  478. package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
  479. package/src/providers/speech-to-text/deepgram.test.ts +332 -0
  480. package/src/providers/speech-to-text/deepgram.ts +115 -0
  481. package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
  482. package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
  483. package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
  484. package/src/providers/speech-to-text/google-gemini.ts +101 -0
  485. package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
  486. package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
  487. package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
  488. package/src/providers/speech-to-text/openai-whisper.ts +63 -33
  489. package/src/providers/speech-to-text/provider-catalog.ts +306 -0
  490. package/src/providers/speech-to-text/resolve.ts +386 -6
  491. package/src/providers/types.ts +9 -0
  492. package/src/runtime/AGENTS.md +43 -1
  493. package/src/runtime/__tests__/agent-wake.test.ts +831 -0
  494. package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
  495. package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
  496. package/src/runtime/agent-wake.ts +512 -0
  497. package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
  498. package/src/runtime/auth/route-policy.ts +30 -5
  499. package/src/runtime/auth/token-service.ts +56 -1
  500. package/src/runtime/btw-sidechain.ts +2 -0
  501. package/src/runtime/capability-tokens.ts +10 -10
  502. package/src/runtime/channel-invite-transport.ts +1 -1
  503. package/src/runtime/channel-invite-transports/email.ts +14 -6
  504. package/src/runtime/channel-readiness-service.ts +12 -22
  505. package/src/runtime/chrome-extension-registry.ts +38 -2
  506. package/src/runtime/http-server.ts +395 -10
  507. package/src/runtime/http-types.ts +6 -2
  508. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
  509. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
  510. package/src/runtime/migrations/migration-transport.ts +1 -0
  511. package/src/runtime/migrations/migration-wizard.ts +1 -0
  512. package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
  513. package/src/runtime/migrations/vbundle-importer.ts +34 -0
  514. package/src/runtime/pending-interactions.ts +0 -11
  515. package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
  516. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
  517. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
  518. package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
  519. package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
  520. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
  521. package/src/runtime/routes/app-management-routes.ts +12 -18
  522. package/src/runtime/routes/attachment-routes.test.ts +9 -3
  523. package/src/runtime/routes/attachment-routes.ts +216 -17
  524. package/src/runtime/routes/backup-routes.ts +519 -0
  525. package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
  526. package/src/runtime/routes/btw-routes.ts +8 -6
  527. package/src/runtime/routes/contact-routes.test.ts +298 -0
  528. package/src/runtime/routes/contact-routes.ts +132 -5
  529. package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
  530. package/src/runtime/routes/conversation-management-routes.ts +115 -0
  531. package/src/runtime/routes/conversation-routes.ts +367 -146
  532. package/src/runtime/routes/filing-routes.ts +93 -0
  533. package/src/runtime/routes/home-feed-routes.ts +334 -0
  534. package/src/runtime/routes/home-state-routes.ts +138 -0
  535. package/src/runtime/routes/host-browser-routes.ts +3 -14
  536. package/src/runtime/routes/identity-intro-cache.ts +7 -3
  537. package/src/runtime/routes/identity-routes.ts +3 -17
  538. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
  539. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
  540. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
  541. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
  542. package/src/runtime/routes/integrations/slack/channel.ts +11 -3
  543. package/src/runtime/routes/integrations/slack/share.ts +45 -7
  544. package/src/runtime/routes/llm-context-normalization.ts +303 -0
  545. package/src/runtime/routes/memory-item-routes.test.ts +3 -2
  546. package/src/runtime/routes/migration-routes.ts +40 -5
  547. package/src/runtime/routes/settings-routes.ts +22 -5
  548. package/src/runtime/routes/skills-routes.ts +76 -7
  549. package/src/runtime/routes/stt-routes.ts +233 -0
  550. package/src/runtime/routes/surface-action-routes.ts +41 -2
  551. package/src/runtime/routes/tts-routes.ts +108 -24
  552. package/src/runtime/routes/usage-routes.ts +30 -2
  553. package/src/runtime/routes/user-route-dispatcher.ts +50 -5
  554. package/src/runtime/routes/user-routes.ts +13 -1
  555. package/src/runtime/routes/work-items-routes.ts +8 -1
  556. package/src/runtime/runtime-mode.ts +33 -0
  557. package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
  558. package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
  559. package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
  560. package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
  561. package/src/runtime/services/analyze-conversation.ts +344 -0
  562. package/src/runtime/services/analyze-deps-singleton.ts +32 -0
  563. package/src/runtime/services/auto-analysis-prompt.ts +55 -0
  564. package/src/runtime/skill-route-registry.ts +49 -0
  565. package/src/runtime/slack-block-formatting.ts +437 -10
  566. package/src/schedule/scheduler.ts +50 -0
  567. package/src/security/oauth2.ts +26 -4
  568. package/src/security/secure-keys.ts +25 -2
  569. package/src/security/token-manager.ts +8 -0
  570. package/src/sequence/engine.ts +23 -0
  571. package/src/sequence/types.ts +1 -1
  572. package/src/skills/catalog-files.ts +64 -2
  573. package/src/skills/category-inference.ts +122 -0
  574. package/src/skills/clawhub-files.ts +213 -0
  575. package/src/skills/clawhub.ts +84 -23
  576. package/src/skills/skill-file-provider.ts +40 -0
  577. package/src/skills/skillssh-files.ts +395 -0
  578. package/src/skills/skillssh-registry.ts +4 -4
  579. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
  580. package/src/stt/__tests__/types.test.ts +89 -0
  581. package/src/stt/daemon-batch-transcriber.ts +195 -0
  582. package/src/stt/stt-stream-session.ts +499 -0
  583. package/src/stt/types.ts +330 -0
  584. package/src/stt/wav-encoder.test.ts +373 -0
  585. package/src/stt/wav-encoder.ts +175 -0
  586. package/src/subagent/manager.ts +38 -14
  587. package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
  588. package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
  589. package/src/tools/browser/browser-execution.ts +1163 -23
  590. package/src/tools/browser/browser-manager.ts +45 -0
  591. package/src/tools/browser/browser-mode-constants.ts +12 -0
  592. package/src/tools/browser/browser-mode.ts +92 -0
  593. package/src/tools/browser/browser-status-constants.ts +33 -0
  594. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
  595. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
  596. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
  597. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
  598. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
  599. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
  600. package/src/tools/browser/cdp-client/errors.ts +15 -0
  601. package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
  602. package/src/tools/browser/cdp-client/factory.ts +797 -87
  603. package/src/tools/browser/cdp-client/index.ts +16 -2
  604. package/src/tools/browser/cdp-client/types.ts +68 -0
  605. package/src/tools/credentials/vault.ts +35 -6
  606. package/src/tools/network/web-fetch.ts +5 -2
  607. package/src/tools/network/web-search.ts +5 -2
  608. package/src/tools/shared/shell-output.ts +3 -1
  609. package/src/tools/side-effects.ts +2 -0
  610. package/src/tools/skills/sandbox-runner.ts +3 -2
  611. package/src/tools/terminal/safe-env.ts +10 -2
  612. package/src/tools/terminal/shell.ts +15 -4
  613. package/src/tools/tool-manifest.ts +21 -0
  614. package/src/tools/types.ts +17 -0
  615. package/src/tools/ui-surface/definitions.ts +6 -1
  616. package/src/tts/__tests__/provider-adapters.test.ts +834 -0
  617. package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
  618. package/src/tts/__tests__/provider-catalog.test.ts +183 -0
  619. package/src/tts/__tests__/provider-registry.test.ts +90 -0
  620. package/src/tts/provider-catalog.ts +201 -0
  621. package/src/tts/provider-registry.ts +73 -0
  622. package/src/tts/providers/deepgram-provider.ts +219 -0
  623. package/src/tts/providers/elevenlabs-provider.ts +211 -0
  624. package/src/tts/providers/fish-audio-provider.ts +183 -0
  625. package/src/tts/providers/index.ts +42 -0
  626. package/src/tts/providers/register-builtins.ts +130 -0
  627. package/src/tts/synthesize-text.ts +110 -0
  628. package/src/tts/tts-config-resolver.ts +78 -0
  629. package/src/tts/types.ts +153 -0
  630. package/src/types/onboarding-context.ts +7 -0
  631. package/src/util/abort-reasons.ts +58 -0
  632. package/src/util/device-id.ts +32 -16
  633. package/src/util/errors.ts +9 -1
  634. package/src/util/platform.ts +54 -10
  635. package/src/util/pricing.ts +66 -3
  636. package/src/util/spawn.ts +1 -1
  637. package/src/util/truncate.ts +4 -2
  638. package/src/util/unicode.ts +201 -0
  639. package/src/version.ts +19 -24
  640. package/src/watcher/engine.ts +23 -0
  641. package/src/watcher/watcher-store.ts +31 -0
  642. package/src/workspace/migrations/003-seed-device-id.ts +9 -3
  643. package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
  644. package/src/workspace/migrations/029-seed-pkb.ts +1 -1
  645. package/src/workspace/migrations/031-drop-user-md.ts +317 -0
  646. package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
  647. package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
  648. package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
  649. package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
  650. package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
  651. package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
  652. package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
  653. package/src/workspace/migrations/registry.ts +16 -0
  654. package/src/workspace/top-level-renderer.ts +13 -1
  655. package/src/workspace/turn-commit.ts +31 -0
  656. package/src/__tests__/email-cli.test.ts +0 -297
  657. package/src/__tests__/email-service-config-fallback.test.ts +0 -102
  658. package/src/cli/commands/browser-relay.ts +0 -466
  659. package/src/email/guardrails.ts +0 -221
  660. package/src/email/provider.ts +0 -117
  661. package/src/email/providers/agentmail.ts +0 -361
  662. package/src/email/providers/index.ts +0 -65
  663. package/src/email/service.ts +0 -384
  664. package/src/email/types.ts +0 -126
  665. package/src/prompts/templates/USER.md +0 -13
  666. package/src/providers/speech-to-text/types.ts +0 -17
  667. package/src/runtime/routes/browser-cdp-routes.ts +0 -229
@@ -0,0 +1,71 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { markdownToEmailHtml } from "../email/html-renderer.js";
4
+
5
+ describe("markdownToEmailHtml", () => {
6
+ test("wraps plain text in email template", () => {
7
+ const result = markdownToEmailHtml("Hello world");
8
+ expect(result).toContain("<!DOCTYPE html>");
9
+ expect(result).toContain("<p>Hello world</p>");
10
+ expect(result).toContain("max-width:600px");
11
+ });
12
+
13
+ test("converts markdown bold to <strong>", () => {
14
+ const result = markdownToEmailHtml("This is **bold** text");
15
+ expect(result).toContain("<strong>bold</strong>");
16
+ });
17
+
18
+ test("converts markdown links", () => {
19
+ const result = markdownToEmailHtml("Visit [Vellum](https://vellum.ai)");
20
+ expect(result).toContain('<a href="https://vellum.ai">Vellum</a>');
21
+ });
22
+
23
+ test("converts markdown lists", () => {
24
+ const result = markdownToEmailHtml("- item one\n- item two\n- item three");
25
+ expect(result).toContain("<li>item one</li>");
26
+ expect(result).toContain("<li>item two</li>");
27
+ expect(result).toContain("<ul>");
28
+ });
29
+
30
+ test("converts markdown code blocks", () => {
31
+ const result = markdownToEmailHtml("```\nconsole.log('hi')\n```");
32
+ expect(result).toContain("<code>");
33
+ expect(result).toContain("console.log(");
34
+ });
35
+
36
+ test("converts line breaks (GFM breaks mode)", () => {
37
+ const result = markdownToEmailHtml("line one\nline two");
38
+ expect(result).toContain("<br>");
39
+ });
40
+
41
+ test("returns raw HTML input as-is", () => {
42
+ const rawHtml = "<div>Already HTML</div>";
43
+ const result = markdownToEmailHtml(rawHtml);
44
+ expect(result).toBe(rawHtml);
45
+ });
46
+
47
+ test("handles empty string", () => {
48
+ const result = markdownToEmailHtml("");
49
+ expect(result).toBe("");
50
+ });
51
+
52
+ test("handles multiline markdown email", () => {
53
+ const md = `Hi there,
54
+
55
+ Thanks for reaching out! Here's what I found:
56
+
57
+ 1. **First point** — some details
58
+ 2. **Second point** — more details
59
+
60
+ Let me know if you need anything else.
61
+
62
+ Best,
63
+ Assistant`;
64
+
65
+ const result = markdownToEmailHtml(md);
66
+ expect(result).toContain("<!DOCTYPE html>");
67
+ expect(result).toContain("<strong>First point</strong>");
68
+ expect(result).toContain("<ol>");
69
+ expect(result).toContain("<li>");
70
+ });
71
+ });
@@ -1,51 +1,66 @@
1
1
  /**
2
2
  * Tests for the email invite adapter.
3
3
  *
4
- * Verifies that the email adapter resolves the assistant's real inbox
5
- * address when one is configured and falls back to `undefined` (triggering
6
- * generic invite instructions) when no inbox exists.
4
+ * Verifies that the email adapter resolves the assistant's email address
5
+ * from workspace config and falls back to `undefined` when no address
6
+ * is configured.
7
7
  */
8
8
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
9
9
 
10
- import { resolveAdapterHandle } from "../runtime/channel-invite-transport.js";
11
- import { emailInviteAdapter } from "../runtime/channel-invite-transports/email.js";
12
-
13
10
  // ---------------------------------------------------------------------------
14
- // Mock the EmailService singleton
11
+ // Mock the config loader
15
12
  // ---------------------------------------------------------------------------
16
13
 
17
- let mockPrimaryAddress: string | undefined;
18
-
19
- mock.module("../email/service.js", () => ({
20
- getEmailService: () => ({
21
- getPrimaryInboxAddress: async () => mockPrimaryAddress,
22
- }),
23
- // Re-export other symbols that callers might need
24
- EmailService: class {},
14
+ let mockConfig: Record<string, unknown> = {};
15
+
16
+ mock.module("../config/loader.js", () => ({
17
+ loadRawConfig: () => mockConfig,
18
+ getNestedValue: (obj: Record<string, unknown>, path: string) => {
19
+ const keys = path.split(".");
20
+ let current: unknown = obj;
21
+ for (const key of keys) {
22
+ if (current == null || typeof current !== "object") return undefined;
23
+ current = (current as Record<string, unknown>)[key];
24
+ }
25
+ return current;
26
+ },
27
+ getConfig: () => ({}),
28
+ saveRawConfig: () => {},
29
+ setNestedValue: () => {},
25
30
  }));
26
31
 
32
+ import { resolveAdapterHandle } from "../runtime/channel-invite-transport.js";
33
+ import { emailInviteAdapter } from "../runtime/channel-invite-transports/email.js";
34
+
27
35
  // ---------------------------------------------------------------------------
28
36
  // Tests
29
37
  // ---------------------------------------------------------------------------
30
38
 
31
39
  describe("emailInviteAdapter", () => {
32
40
  beforeEach(() => {
33
- mockPrimaryAddress = undefined;
41
+ mockConfig = {};
34
42
  });
35
43
 
36
44
  afterEach(() => {
37
- mockPrimaryAddress = undefined;
45
+ mockConfig = {};
38
46
  });
39
47
 
40
48
  test("returns configured email address via resolveChannelHandleAsync", async () => {
41
- mockPrimaryAddress = "hello@mycompany.agentmail.to";
49
+ mockConfig = { email: { address: "hello@vellum.me" } };
50
+
51
+ const handle = await resolveAdapterHandle(emailInviteAdapter);
52
+ expect(handle).toBe("hello@vellum.me");
53
+ });
54
+
55
+ test("returns undefined when no address is configured", async () => {
56
+ mockConfig = {};
42
57
 
43
58
  const handle = await resolveAdapterHandle(emailInviteAdapter);
44
- expect(handle).toBe("hello@mycompany.agentmail.to");
59
+ expect(handle).toBeUndefined();
45
60
  });
46
61
 
47
- test("returns undefined when no inbox is configured", async () => {
48
- mockPrimaryAddress = undefined;
62
+ test("returns undefined when email.address is empty string", async () => {
63
+ mockConfig = { email: { address: "" } };
49
64
 
50
65
  const handle = await resolveAdapterHandle(emailInviteAdapter);
51
66
  expect(handle).toBeUndefined();
@@ -56,7 +71,6 @@ describe("emailInviteAdapter", () => {
56
71
  });
57
72
 
58
73
  test("does not define sync resolveChannelHandle", () => {
59
- // The email adapter uses the async path exclusively
60
74
  expect(emailInviteAdapter.resolveChannelHandle).toBeUndefined();
61
75
  });
62
76
 
@@ -64,14 +78,4 @@ describe("emailInviteAdapter", () => {
64
78
  expect(emailInviteAdapter.buildShareLink).toBeUndefined();
65
79
  expect(emailInviteAdapter.extractInboundToken).toBeUndefined();
66
80
  });
67
-
68
- test("returns config fallback address when provider has no inboxes", async () => {
69
- // Simulates the config fallback: provider returns no inboxes, but
70
- // email.address is set in workspace config. The service's
71
- // getPrimaryInboxAddress() should return the configured address.
72
- mockPrimaryAddress = "configured@example.com";
73
-
74
- const handle = await resolveAdapterHandle(emailInviteAdapter);
75
- expect(handle).toBe("configured@example.com");
76
- });
77
81
  });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Behavioral test for the generic CLI→daemon event bridge.
3
+ *
4
+ * Callers write a JSON-encoded {@link ServerMessage} to
5
+ * `<signalsDir>/emit-event`. {@link handleEmitEventSignal} reads that
6
+ * payload and republishes it through the {@link assistantEventHub} so
7
+ * SSE subscribers receive it.
8
+ */
9
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { afterEach, describe, expect, test } from "bun:test";
12
+
13
+ import type { ServerMessage } from "../daemon/message-protocol.js";
14
+ import type { AssistantEvent } from "../runtime/assistant-event.js";
15
+ import { assistantEventHub } from "../runtime/assistant-event-hub.js";
16
+ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
17
+ import { handleEmitEventSignal } from "../signals/emit-event.js";
18
+ import { getSignalsDir } from "../util/platform.js";
19
+
20
+ function signalPath(): string {
21
+ return join(getSignalsDir(), "emit-event");
22
+ }
23
+
24
+ const subscriptions: Array<{ dispose(): void }> = [];
25
+
26
+ afterEach(() => {
27
+ for (const sub of subscriptions.splice(0)) {
28
+ sub.dispose();
29
+ }
30
+ const path = signalPath();
31
+ if (existsSync(path)) {
32
+ rmSync(path, { force: true });
33
+ }
34
+ });
35
+
36
+ describe("handleEmitEventSignal", () => {
37
+ test("reads a ServerMessage from the signal file and publishes it to the event hub", async () => {
38
+ mkdirSync(getSignalsDir(), { recursive: true });
39
+
40
+ const payload: ServerMessage = { type: "tasks_changed" };
41
+
42
+ writeFileSync(signalPath(), JSON.stringify(payload), "utf-8");
43
+
44
+ const received: AssistantEvent[] = [];
45
+ let resolveDelivered: (() => void) | null = null;
46
+ const delivered = new Promise<void>((resolve) => {
47
+ resolveDelivered = resolve;
48
+ });
49
+
50
+ subscriptions.push(
51
+ assistantEventHub.subscribe(
52
+ { assistantId: DAEMON_INTERNAL_ASSISTANT_ID },
53
+ (event) => {
54
+ received.push(event);
55
+ resolveDelivered?.();
56
+ },
57
+ ),
58
+ );
59
+
60
+ handleEmitEventSignal();
61
+
62
+ await delivered;
63
+
64
+ expect(received).toHaveLength(1);
65
+ const event = received[0];
66
+ expect(event.assistantId).toBe(DAEMON_INTERNAL_ASSISTANT_ID);
67
+ expect(event.message).toEqual(payload);
68
+ expect(typeof event.id).toBe("string");
69
+ expect(typeof event.emittedAt).toBe("string");
70
+ });
71
+ });
@@ -11,7 +11,8 @@
11
11
  * (not duplicated across runtime/tests/docs).
12
12
  */
13
13
 
14
- import { readdirSync, readFileSync, statSync } from "node:fs";
14
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
15
+ import { homedir } from "node:os";
15
16
  import { join, resolve } from "node:path";
16
17
  import { describe, expect, test } from "bun:test";
17
18
 
@@ -21,8 +22,14 @@ const repoRoot = resolve(__dirname, "..", "..", "..");
21
22
  const CANONICAL_CONFIG_REL_PATH =
22
23
  "meta/browser-extension/chrome-extension-allowlist.json";
23
24
  const CANONICAL_CONFIG_ABS_PATH = join(repoRoot, CANONICAL_CONFIG_REL_PATH);
25
+ const LOCAL_OVERRIDE_PATH = join(
26
+ homedir(),
27
+ ".vellum",
28
+ "chrome-extension-allowlist.local.json",
29
+ );
24
30
 
25
31
  const EXTENSION_ID_REGEX = /^[a-p]{32}$/;
32
+ const PLACEHOLDER_ID_REGEX = /^TODO_[A-Z0-9_]+$/;
26
33
 
27
34
  type AllowlistConfig = {
28
35
  version: number;
@@ -48,19 +55,32 @@ function parseCanonicalConfig(): AllowlistConfig {
48
55
  }
49
56
 
50
57
  const seen = new Set<string>();
58
+ const validIds: string[] = [];
51
59
  for (const id of parsed.allowedExtensionIds) {
52
- if (typeof id !== "string" || !EXTENSION_ID_REGEX.test(id)) {
60
+ if (typeof id !== "string") {
53
61
  throw new Error(`Invalid canonical extension id: ${String(id)}`);
54
62
  }
55
63
  if (seen.has(id)) {
56
64
  throw new Error(`Duplicate canonical extension id: ${id}`);
57
65
  }
58
66
  seen.add(id);
67
+
68
+ if (EXTENSION_ID_REGEX.test(id)) {
69
+ validIds.push(id);
70
+ } else if (!PLACEHOLDER_ID_REGEX.test(id)) {
71
+ throw new Error(`Invalid canonical extension id: ${id}`);
72
+ }
73
+ }
74
+
75
+ if (validIds.length === 0) {
76
+ throw new Error(
77
+ "Invalid canonical config: allowedExtensionIds must contain at least one real extension id",
78
+ );
59
79
  }
60
80
 
61
81
  return {
62
82
  version: parsed.version as number,
63
- allowedExtensionIds: parsed.allowedExtensionIds as string[],
83
+ allowedExtensionIds: validIds,
64
84
  };
65
85
  }
66
86
 
@@ -97,8 +117,18 @@ function listTextFilesRecursively(root: string): string[] {
97
117
  const out: string[] = [];
98
118
 
99
119
  function walk(dir: string): void {
100
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
120
+ let entries;
121
+ try {
122
+ entries = readdirSync(dir, { withFileTypes: true });
123
+ } catch (err) {
124
+ // Directory may have been removed by a concurrent test; skip it.
125
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") return;
126
+ throw err;
127
+ }
128
+ for (const entry of entries) {
101
129
  if (entry.name.startsWith(".DS_Store")) continue;
130
+ // Skip temp fixtures created by parallel tests (e.g. .test-starter-bundle-<pid>).
131
+ if (entry.name.startsWith(".test-")) continue;
102
132
  const absPath = join(dir, entry.name);
103
133
  if (entry.isDirectory()) {
104
134
  if (ignoredDirs.has(entry.name)) continue;
@@ -111,7 +141,13 @@ function listTextFilesRecursively(root: string): string[] {
111
141
  if (!allowedExtensions.has(ext)) continue;
112
142
 
113
143
  // Skip large files to keep this guard lightweight.
114
- const size = statSync(absPath).size;
144
+ let size: number;
145
+ try {
146
+ size = statSync(absPath).size;
147
+ } catch (err) {
148
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") continue;
149
+ throw err;
150
+ }
115
151
  if (size > 1_000_000) continue;
116
152
  out.push(absPath);
117
153
  }
@@ -128,12 +164,36 @@ describe("Chrome extension allowlist guard", () => {
128
164
  expect(config.allowedExtensionIds.length).toBeGreaterThan(0);
129
165
  });
130
166
 
131
- test("assistant runtime allowlist mirrors canonical config", () => {
167
+ test("assistant runtime allowlist contains every canonical origin", () => {
168
+ // The runtime set is the union of canonical + local override
169
+ // (~/.vellum/chrome-extension-allowlist.local.json) + env var. A dev
170
+ // machine may have extras; we only assert the canonical IDs are present.
171
+ const config = parseCanonicalConfig();
172
+ for (const id of config.allowedExtensionIds) {
173
+ const origin = `chrome-extension://${id}/`;
174
+ expect(ALLOWED_EXTENSION_ORIGINS.has(origin)).toBe(true);
175
+ }
176
+ });
177
+
178
+ test("assistant runtime allowlist exactly mirrors canonical when no override sources are active", () => {
179
+ // Exact-equality invariant: when neither the local override file nor
180
+ // the env-var override is active, the runtime set must equal the
181
+ // canonical set — nothing extra, nothing missing. A dev machine with
182
+ // an unpacked-extension override will skip this deterministic check
183
+ // and rely on the subset assertion above.
184
+ if (existsSync(LOCAL_OVERRIDE_PATH)) return;
185
+ if (
186
+ process.env.VELLUM_CHROME_EXTENSION_IDS ||
187
+ process.env.VELLUM_CHROME_EXTENSION_ID
188
+ ) {
189
+ return;
190
+ }
191
+
132
192
  const config = parseCanonicalConfig();
133
193
  const expectedOrigins = new Set(
134
194
  config.allowedExtensionIds.map((id) => `chrome-extension://${id}/`),
135
195
  );
136
- expect(ALLOWED_EXTENSION_ORIGINS).toEqual(expectedOrigins);
196
+ expect(new Set(ALLOWED_EXTENSION_ORIGINS)).toEqual(expectedOrigins);
137
197
  });
138
198
 
139
199
  test("concrete extension IDs appear only in canonical config", () => {
@@ -144,7 +204,14 @@ describe("Chrome extension allowlist guard", () => {
144
204
  const matches: string[] = [];
145
205
  for (const absPath of allFiles) {
146
206
  const relPath = absPath.replace(`${repoRoot}/`, "");
147
- const content = readFileSync(absPath, "utf8");
207
+ let content: string;
208
+ try {
209
+ content = readFileSync(absPath, "utf8");
210
+ } catch (err) {
211
+ // File may have been removed by a concurrent test between listing and reading.
212
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") continue;
213
+ throw err;
214
+ }
148
215
  if (content.includes(extensionId)) {
149
216
  matches.push(relPath);
150
217
  }
@@ -124,6 +124,12 @@ export interface MockChromeExtension {
124
124
  * command.
125
125
  */
126
126
  sendSessionInvalidated(event: { targetId?: string; reason?: string }): void;
127
+ /**
128
+ * Send an arbitrary pre-serialized JSON string over the active
129
+ * WebSocket. Used by tests that need to send frame types not covered
130
+ * by the fixture's typed helpers (e.g. keepalive frames).
131
+ */
132
+ sendRaw(json: string): void;
127
133
  }
128
134
 
129
135
  // ── Defaults ────────────────────────────────────────────────────────
@@ -371,5 +377,10 @@ export function createMockChromeExtension(
371
377
  }),
372
378
  );
373
379
  },
380
+ sendRaw(json: string) {
381
+ const sock = ws;
382
+ if (!sock || sock.readyState !== WebSocket.OPEN) return;
383
+ sock.send(json);
384
+ },
374
385
  };
375
386
  }
@@ -68,6 +68,9 @@ mock.module("../config/loader.js", () => ({
68
68
  twilio: {
69
69
  phoneNumber: "+15550001111",
70
70
  },
71
+ services: {
72
+ stt: { provider: "deepgram" },
73
+ },
71
74
  }),
72
75
  invalidateConfigCache: () => {},
73
76
  }));
@@ -90,7 +93,9 @@ mock.module("../calls/twilio-provider.js", () => ({
90
93
  },
91
94
  }));
92
95
 
93
- mock.module("../security/secure-keys.js", () => ({}));
96
+ mock.module("../security/secure-keys.js", () => ({
97
+ getProviderKeyAsync: () => Promise.resolve(null),
98
+ }));
94
99
 
95
100
  // NOTE: Do NOT mock '../inbound/public-ingress-urls.js' here.
96
101
  // Those are pure functions that derive URLs from the config object returned by
@@ -455,6 +460,86 @@ describe("gateway-only ingress enforcement", () => {
455
460
  });
456
461
  });
457
462
 
463
+ // ── Media-stream WebSocket upgrade ─────────────────────────────────
464
+
465
+ describe("media-stream WebSocket upgrade", () => {
466
+ test("blocks non-private-network origin", async () => {
467
+ // The peer address (127.0.0.1) passes the private network check,
468
+ // but the external Origin header triggers the secondary defense-in-depth block.
469
+ const res = await fetch(
470
+ `http://127.0.0.1:${port}/v1/calls/media-stream?callSessionId=sess-123`,
471
+ {
472
+ headers: {
473
+ Upgrade: "websocket",
474
+ Connection: "Upgrade",
475
+ Origin: "https://external.example.com",
476
+ "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
477
+ "Sec-WebSocket-Version": "13",
478
+ },
479
+ },
480
+ );
481
+ expect(res.status).toBe(403);
482
+ const body = (await res.json()) as {
483
+ error: { code: string; message: string };
484
+ };
485
+ expect(body.error.code).toBe("FORBIDDEN");
486
+ expect(body.error.message).toContain(
487
+ "Direct media-stream access disabled",
488
+ );
489
+ });
490
+
491
+ test("allows request with no origin header (private network peer)", async () => {
492
+ // Without an origin header, isPrivateNetworkOrigin returns true.
493
+ // The peer address (127.0.0.1) passes the private network peer check.
494
+ const res = await fetch(
495
+ `http://127.0.0.1:${port}/v1/calls/media-stream?callSessionId=sess-123`,
496
+ {
497
+ headers: {
498
+ Upgrade: "websocket",
499
+ Connection: "Upgrade",
500
+ "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
501
+ "Sec-WebSocket-Version": "13",
502
+ },
503
+ },
504
+ );
505
+ // Should NOT be 403 — WebSocket upgrade may or may not succeed
506
+ // depending on test environment, but the gateway guard should pass.
507
+ expect(res.status).not.toBe(403);
508
+ });
509
+
510
+ test("allows localhost origin from loopback peer", async () => {
511
+ const res = await fetch(
512
+ `http://127.0.0.1:${port}/v1/calls/media-stream?callSessionId=sess-123`,
513
+ {
514
+ headers: {
515
+ Upgrade: "websocket",
516
+ Connection: "Upgrade",
517
+ Origin: "http://127.0.0.1:3000",
518
+ "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
519
+ "Sec-WebSocket-Version": "13",
520
+ },
521
+ },
522
+ );
523
+ // Should NOT be 403
524
+ expect(res.status).not.toBe(403);
525
+ });
526
+
527
+ test("returns 400 when callSessionId is missing", async () => {
528
+ const res = await fetch(
529
+ `http://127.0.0.1:${port}/v1/calls/media-stream`,
530
+ {
531
+ headers: {
532
+ Upgrade: "websocket",
533
+ Connection: "Upgrade",
534
+ "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
535
+ "Sec-WebSocket-Version": "13",
536
+ },
537
+ },
538
+ );
539
+ expect(res.status).toBe(400);
540
+ });
541
+ });
542
+
458
543
  // ── isPrivateAddress unit tests ─────────────────────────────────────
459
544
 
460
545
  describe("isPrivateAddress", () => {
@@ -686,6 +771,126 @@ describe("gateway-only ingress enforcement", () => {
686
771
  });
687
772
  });
688
773
 
774
+ // ── STT stream WebSocket upgrade ────────────────────────────────────
775
+
776
+ describe("STT stream WebSocket upgrade", () => {
777
+ test("blocks non-private-network origin", async () => {
778
+ const res = await fetch(
779
+ `http://127.0.0.1:${port}/v1/stt/stream?provider=deepgram&mimeType=audio/webm`,
780
+ {
781
+ headers: {
782
+ Upgrade: "websocket",
783
+ Connection: "Upgrade",
784
+ Origin: "https://external.example.com",
785
+ "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
786
+ "Sec-WebSocket-Version": "13",
787
+ },
788
+ },
789
+ );
790
+ expect(res.status).toBe(403);
791
+ const body = (await res.json()) as {
792
+ error: { code: string; message: string };
793
+ };
794
+ expect(body.error.code).toBe("FORBIDDEN");
795
+ expect(body.error.message).toContain("Direct STT stream access disabled");
796
+ });
797
+
798
+ test("rejects upgrade without a token", async () => {
799
+ const res = await fetch(
800
+ `http://127.0.0.1:${port}/v1/stt/stream?provider=deepgram&mimeType=audio/webm`,
801
+ {
802
+ headers: {
803
+ Upgrade: "websocket",
804
+ Connection: "Upgrade",
805
+ "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
806
+ "Sec-WebSocket-Version": "13",
807
+ },
808
+ },
809
+ );
810
+ expect(res.status).toBe(401);
811
+ });
812
+
813
+ test("rejects upgrade with actor JWT (requires gateway service token)", async () => {
814
+ const res = await fetch(
815
+ `http://127.0.0.1:${port}/v1/stt/stream?token=${TEST_JWT}&provider=deepgram&mimeType=audio/webm`,
816
+ {
817
+ headers: {
818
+ Upgrade: "websocket",
819
+ Connection: "Upgrade",
820
+ "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
821
+ "Sec-WebSocket-Version": "13",
822
+ },
823
+ },
824
+ );
825
+ // Actor JWTs should be rejected — only gateway service tokens are allowed.
826
+ expect(res.status).toBe(401);
827
+ });
828
+
829
+ test("accepts upgrade with gateway service token from private network", async () => {
830
+ const res = await fetch(
831
+ `http://127.0.0.1:${port}/v1/stt/stream?token=${GATEWAY_JWT}&provider=deepgram&mimeType=audio/webm`,
832
+ {
833
+ headers: {
834
+ Upgrade: "websocket",
835
+ Connection: "Upgrade",
836
+ "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
837
+ "Sec-WebSocket-Version": "13",
838
+ },
839
+ },
840
+ );
841
+ // Should NOT be 403 or 401 — WebSocket upgrade may or may not succeed
842
+ // depending on test environment, but the auth and network guards should pass.
843
+ expect(res.status).not.toBe(403);
844
+ expect(res.status).not.toBe(401);
845
+ });
846
+
847
+ test("succeeds when provider is omitted (config-authoritative)", async () => {
848
+ const res = await fetch(
849
+ `http://127.0.0.1:${port}/v1/stt/stream?token=${GATEWAY_JWT}&mimeType=audio/webm`,
850
+ {
851
+ headers: {
852
+ Upgrade: "websocket",
853
+ Connection: "Upgrade",
854
+ "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
855
+ "Sec-WebSocket-Version": "13",
856
+ },
857
+ },
858
+ );
859
+ // Should NOT be 400 — provider is optional.
860
+ expect(res.status).not.toBe(400);
861
+ });
862
+
863
+ test("returns 400 when mimeType is missing", async () => {
864
+ const res = await fetch(
865
+ `http://127.0.0.1:${port}/v1/stt/stream?token=${GATEWAY_JWT}&provider=deepgram`,
866
+ {
867
+ headers: {
868
+ Upgrade: "websocket",
869
+ Connection: "Upgrade",
870
+ "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
871
+ "Sec-WebSocket-Version": "13",
872
+ },
873
+ },
874
+ );
875
+ expect(res.status).toBe(400);
876
+ });
877
+
878
+ test("returns 400 when both provider and mimeType are missing", async () => {
879
+ const res = await fetch(
880
+ `http://127.0.0.1:${port}/v1/stt/stream?token=${GATEWAY_JWT}`,
881
+ {
882
+ headers: {
883
+ Upgrade: "websocket",
884
+ Connection: "Upgrade",
885
+ "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
886
+ "Sec-WebSocket-Version": "13",
887
+ },
888
+ },
889
+ );
890
+ expect(res.status).toBe(400);
891
+ });
892
+ });
893
+
689
894
  // ── Startup warning for non-loopback host ──────────────────────────
690
895
 
691
896
  describe("startup guard — non-loopback host", () => {
@@ -23,7 +23,6 @@ const ALLOWLIST = new Set([
23
23
 
24
24
  // --- Intentional local daemon-control paths ---
25
25
  "assistant/src/cli/commands/conversations.ts", // CLI wipe talks to runtime directly
26
- "assistant/src/cli/commands/browser-relay.ts", // CLI shim talks to /v1/browser-cdp on the local daemon
27
26
  "clients/shared/Network/DaemonClient.swift",
28
27
  "clients/shared/App/Auth/PlatformOAuthService.swift", // comment explaining runtimeUrl vs platformUrl
29
28
  "clients/macos/vellum-assistant/App/AppDelegate.swift",