@vellumai/assistant 0.6.2 → 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 (895) hide show
  1. package/ARCHITECTURE.md +273 -10
  2. package/Dockerfile +2 -3
  3. package/bun.lock +41 -49
  4. package/bunfig.toml +3 -0
  5. package/docs/architecture/memory.md +1 -1
  6. package/docs/backup-troubleshooting.md +52 -0
  7. package/docs/browser-use-architecture-phase2.md +174 -0
  8. package/docs/stt-provider-onboarding.md +120 -0
  9. package/knip.json +12 -2
  10. package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
  11. package/node_modules/@vellumai/ces-contracts/package.json +3 -3
  12. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  13. package/openapi.yaml +1111 -86
  14. package/package.json +40 -42
  15. package/scripts/generate-openapi.ts +0 -2
  16. package/scripts/test.sh +73 -18
  17. package/src/__tests__/acp-session.test.ts +43 -0
  18. package/src/__tests__/agent-image-optimize.test.ts +28 -0
  19. package/src/__tests__/agent-loop.test.ts +123 -0
  20. package/src/__tests__/anthropic-provider.test.ts +263 -10
  21. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  22. package/src/__tests__/app-executors.test.ts +1 -0
  23. package/src/__tests__/app-source-watcher.test.ts +37 -11
  24. package/src/__tests__/approval-routes-http.test.ts +178 -1
  25. package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
  26. package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
  27. package/src/__tests__/browser-fill-credential.test.ts +240 -94
  28. package/src/__tests__/browser-manager.test.ts +40 -27
  29. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  30. package/src/__tests__/browser-skill-endstate.test.ts +31 -7
  31. package/src/__tests__/btw-routes.test.ts +7 -0
  32. package/src/__tests__/call-controller.test.ts +581 -20
  33. package/src/__tests__/catalog-files.test.ts +1000 -0
  34. package/src/__tests__/channel-approvals.test.ts +53 -0
  35. package/src/__tests__/channel-invite-transport.test.ts +2 -2
  36. package/src/__tests__/channel-readiness-routes.test.ts +16 -20
  37. package/src/__tests__/channel-readiness-service.test.ts +12 -7
  38. package/src/__tests__/checker.test.ts +157 -10
  39. package/src/__tests__/clawhub-files.test.ts +347 -0
  40. package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
  41. package/src/__tests__/config-analysis.test.ts +100 -0
  42. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  43. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  44. package/src/__tests__/config-schema.test.ts +1248 -224
  45. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
  46. package/src/__tests__/config-watcher.test.ts +43 -8
  47. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  48. package/src/__tests__/contact-store-user-file.test.ts +512 -0
  49. package/src/__tests__/contacts-write.test.ts +197 -0
  50. package/src/__tests__/context-overflow-approval.test.ts +16 -1
  51. package/src/__tests__/context-window-manager.test.ts +88 -0
  52. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  53. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -1
  54. package/src/__tests__/conversation-agent-loop.test.ts +99 -3
  55. package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
  56. package/src/__tests__/conversation-attachments.test.ts +80 -4
  57. package/src/__tests__/conversation-confirmation-signals.test.ts +290 -0
  58. package/src/__tests__/conversation-error.test.ts +70 -0
  59. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  60. package/src/__tests__/conversation-history-web-search.test.ts +12 -4
  61. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  62. package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
  63. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  64. package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
  65. package/src/__tests__/conversation-list-source.test.ts +145 -0
  66. package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
  67. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  68. package/src/__tests__/conversation-queue.test.ts +946 -62
  69. package/src/__tests__/conversation-routes-disk-view.test.ts +275 -0
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  72. package/src/__tests__/conversation-runtime-assembly.test.ts +324 -46
  73. package/src/__tests__/conversation-skill-tools.test.ts +7 -4
  74. package/src/__tests__/conversation-slash-commands.test.ts +33 -0
  75. package/src/__tests__/conversation-slash-queue.test.ts +89 -18
  76. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  77. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  78. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  79. package/src/__tests__/conversation-store.test.ts +195 -0
  80. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
  81. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  82. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  83. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  84. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
  85. package/src/__tests__/credential-health-service.test.ts +352 -0
  86. package/src/__tests__/credential-security-invariants.test.ts +6 -3
  87. package/src/__tests__/credential-vault-unit.test.ts +383 -7
  88. package/src/__tests__/credential-vault.test.ts +152 -13
  89. package/src/__tests__/credentials-cli.test.ts +42 -18
  90. package/src/__tests__/cross-provider-web-search.test.ts +146 -35
  91. package/src/__tests__/date-context.test.ts +4 -4
  92. package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
  93. package/src/__tests__/device-id.test.ts +112 -0
  94. package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
  95. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
  96. package/src/__tests__/email-html-renderer.test.ts +71 -0
  97. package/src/__tests__/email-invite-adapter.test.ts +36 -32
  98. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  99. package/src/__tests__/emit-event-signal.test.ts +71 -0
  100. package/src/__tests__/extension-id-sync-guard.test.ts +222 -0
  101. package/src/__tests__/fixtures/mock-chrome-extension.ts +386 -0
  102. package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
  103. package/src/__tests__/gateway-only-guard.test.ts +2 -0
  104. package/src/__tests__/gemini-provider.test.ts +66 -2
  105. package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
  106. package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
  107. package/src/__tests__/gmail-archive-gate.test.ts +246 -0
  108. package/src/__tests__/gmail-preferences.test.ts +117 -0
  109. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  110. package/src/__tests__/headless-browser-interactions.test.ts +738 -359
  111. package/src/__tests__/headless-browser-mode.test.ts +614 -0
  112. package/src/__tests__/headless-browser-navigate.test.ts +528 -49
  113. package/src/__tests__/headless-browser-read-tools.test.ts +274 -100
  114. package/src/__tests__/headless-browser-snapshot.test.ts +250 -77
  115. package/src/__tests__/heartbeat-service.test.ts +70 -17
  116. package/src/__tests__/home-state-routes.test.ts +162 -0
  117. package/src/__tests__/host-bash-proxy.test.ts +145 -1
  118. package/src/__tests__/host-browser-e2e-cloud.test.ts +596 -0
  119. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  120. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  121. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  122. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  123. package/src/__tests__/host-browser-routes.test.ts +198 -0
  124. package/src/__tests__/host-browser-ws-events-e2e.test.ts +423 -0
  125. package/src/__tests__/host-cu-proxy.test.ts +166 -1
  126. package/src/__tests__/host-file-proxy.test.ts +185 -1
  127. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  128. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  129. package/src/__tests__/host-shell-tool.test.ts +1 -11
  130. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  131. package/src/__tests__/identity-intro-cache.test.ts +40 -10
  132. package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
  133. package/src/__tests__/integration-status.test.ts +6 -7
  134. package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
  135. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  136. package/src/__tests__/llm-context-normalization.test.ts +488 -0
  137. package/src/__tests__/llm-context-route-provider.test.ts +86 -5
  138. package/src/__tests__/llm-usage-store.test.ts +363 -0
  139. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  140. package/src/__tests__/mcp-health-check.test.ts +10 -3
  141. package/src/__tests__/media-stream-output.test.ts +555 -0
  142. package/src/__tests__/media-stream-parser.test.ts +374 -0
  143. package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
  144. package/src/__tests__/media-stream-stt-session.test.ts +588 -0
  145. package/src/__tests__/media-turn-detector.test.ts +440 -0
  146. package/src/__tests__/message-queue.test.ts +125 -0
  147. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  148. package/src/__tests__/migration-export-http.test.ts +67 -8
  149. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  150. package/src/__tests__/migration-import-commit-http.test.ts +109 -7
  151. package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
  152. package/src/__tests__/migration-validate-http.test.ts +3 -3
  153. package/src/__tests__/mock-gateway-ipc.ts +151 -0
  154. package/src/__tests__/model-intents.test.ts +2 -2
  155. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  156. package/src/__tests__/oauth-apps-routes.test.ts +18 -12
  157. package/src/__tests__/oauth-cli.test.ts +709 -60
  158. package/src/__tests__/oauth-connect-orchestrator.test.ts +118 -24
  159. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  160. package/src/__tests__/oauth-provider-serializer.test.ts +147 -10
  161. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  162. package/src/__tests__/oauth-providers-routes.test.ts +52 -14
  163. package/src/__tests__/oauth-store.test.ts +1465 -176
  164. package/src/__tests__/oauth2-gateway-transport.test.ts +460 -26
  165. package/src/__tests__/onboarding-template-contract.test.ts +81 -70
  166. package/src/__tests__/openai-provider.test.ts +178 -2
  167. package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
  168. package/src/__tests__/openai-responses-provider.test.ts +1105 -0
  169. package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
  170. package/src/__tests__/outlook-categories.test.ts +1 -1
  171. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  172. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  173. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  174. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  175. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  176. package/src/__tests__/outlook-trash.test.ts +1 -1
  177. package/src/__tests__/outlook-unsubscribe.test.ts +32 -3
  178. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  179. package/src/__tests__/permission-mode.test.ts +28 -56
  180. package/src/__tests__/persona-resolver.test.ts +251 -0
  181. package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
  182. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  183. package/src/__tests__/platform.test.ts +92 -1
  184. package/src/__tests__/post-turn-tool-result-truncation.test.ts +343 -0
  185. package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
  186. package/src/__tests__/pricing.test.ts +174 -0
  187. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  188. package/src/__tests__/qdrant-manager.test.ts +29 -8
  189. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
  190. package/src/__tests__/relationship-state-contract.test.ts +175 -0
  191. package/src/__tests__/relay-server.test.ts +423 -5
  192. package/src/__tests__/require-fresh-approval.test.ts +40 -1
  193. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  194. package/src/__tests__/schedule-routes.test.ts +162 -0
  195. package/src/__tests__/search-skills-unified.test.ts +118 -0
  196. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  197. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  198. package/src/__tests__/secret-scanner-executor.test.ts +4 -0
  199. package/src/__tests__/secure-keys.test.ts +107 -0
  200. package/src/__tests__/send-endpoint-busy.test.ts +8 -1
  201. package/src/__tests__/sequence-store.test.ts +1 -1
  202. package/src/__tests__/server-history-render.test.ts +49 -0
  203. package/src/__tests__/set-permission-mode.test.ts +13 -250
  204. package/src/__tests__/settings-routes.test.ts +201 -0
  205. package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
  206. package/src/__tests__/skills-file-content-endpoint.test.ts +801 -0
  207. package/src/__tests__/skills-files-catalog-fallback.test.ts +738 -0
  208. package/src/__tests__/skills.test.ts +5 -2
  209. package/src/__tests__/skillssh-files.test.ts +446 -0
  210. package/src/__tests__/slack-block-formatting.test.ts +110 -0
  211. package/src/__tests__/slack-channel-config.test.ts +576 -16
  212. package/src/__tests__/stt-catalog-parity.test.ts +282 -0
  213. package/src/__tests__/stt-stream-session.test.ts +535 -0
  214. package/src/__tests__/subagent-detail.test.ts +44 -2
  215. package/src/__tests__/subagent-disposal.test.ts +1 -0
  216. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  217. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  218. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  219. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  220. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  221. package/src/__tests__/subagent-tools.test.ts +1 -0
  222. package/src/__tests__/subagent-types.test.ts +1 -0
  223. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  224. package/src/__tests__/system-prompt.test.ts +184 -27
  225. package/src/__tests__/task-scheduler.test.ts +32 -6
  226. package/src/__tests__/telegram-config.test.ts +10 -13
  227. package/src/__tests__/telephony-stt-routing.test.ts +329 -0
  228. package/src/__tests__/terminal-tools.test.ts +25 -5
  229. package/src/__tests__/test-preload.ts +18 -0
  230. package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
  231. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  232. package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
  233. package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
  234. package/src/__tests__/tool-executor.test.ts +33 -24
  235. package/src/__tests__/tool-result-truncation.test.ts +36 -0
  236. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  237. package/src/__tests__/top-level-renderer.test.ts +73 -1
  238. package/src/__tests__/transport-hints-queue.test.ts +14 -29
  239. package/src/__tests__/trust-store.test.ts +7 -1
  240. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
  241. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  242. package/src/__tests__/tts-catalog-parity.test.ts +345 -0
  243. package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
  244. package/src/__tests__/twilio-routes.test.ts +376 -0
  245. package/src/__tests__/unicode.test.ts +293 -0
  246. package/src/__tests__/update-bulletin-format.test.ts +59 -0
  247. package/src/__tests__/update-bulletin.test.ts +206 -5
  248. package/src/__tests__/usage-routes.test.ts +25 -4
  249. package/src/__tests__/user-reference.test.ts +46 -61
  250. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  251. package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
  252. package/src/__tests__/voice-config-update.test.ts +403 -0
  253. package/src/__tests__/voice-quality.test.ts +434 -19
  254. package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
  255. package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
  256. package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
  257. package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
  258. package/src/__tests__/workspace-migration-meets.test.ts +244 -0
  259. package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
  260. package/src/__tests__/workspace-policy.test.ts +2 -0
  261. package/src/acp/client-handler.ts +30 -4
  262. package/src/agent/image-optimize.ts +24 -12
  263. package/src/agent/loop.ts +55 -9
  264. package/src/approvals/guardian-request-resolvers.ts +21 -15
  265. package/src/backup/__tests__/backup-key.test.ts +152 -0
  266. package/src/backup/__tests__/backup-worker.test.ts +767 -0
  267. package/src/backup/__tests__/list-snapshots.test.ts +87 -0
  268. package/src/backup/__tests__/local-writer.test.ts +218 -0
  269. package/src/backup/__tests__/offsite-writer.test.ts +641 -0
  270. package/src/backup/__tests__/paths.test.ts +300 -0
  271. package/src/backup/__tests__/restore.test.ts +498 -0
  272. package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
  273. package/src/backup/__tests__/stream-crypt.test.ts +228 -0
  274. package/src/backup/backup-key.ts +137 -0
  275. package/src/backup/backup-worker.ts +459 -0
  276. package/src/backup/list-snapshots.ts +147 -0
  277. package/src/backup/local-writer.ts +133 -0
  278. package/src/backup/offsite-writer.ts +222 -0
  279. package/src/backup/paths.ts +226 -0
  280. package/src/backup/restore.ts +322 -0
  281. package/src/backup/snapshot-lock.ts +431 -0
  282. package/src/backup/stream-crypt.ts +263 -0
  283. package/src/browser-session/__tests__/manager.test.ts +297 -0
  284. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  285. package/src/browser-session/backends/extension.ts +26 -0
  286. package/src/browser-session/backends/local.ts +24 -0
  287. package/src/browser-session/events.ts +164 -0
  288. package/src/browser-session/index.ts +27 -0
  289. package/src/browser-session/manager.ts +159 -0
  290. package/src/browser-session/types.ts +28 -0
  291. package/src/bundler/package-resolver.ts +4 -0
  292. package/src/calls/audio-store.ts +11 -5
  293. package/src/calls/call-controller.ts +226 -71
  294. package/src/calls/call-domain.ts +9 -0
  295. package/src/calls/call-speech-output.ts +190 -0
  296. package/src/calls/call-transport.ts +77 -0
  297. package/src/calls/media-stream-audio-transcode.ts +173 -0
  298. package/src/calls/media-stream-output.ts +660 -0
  299. package/src/calls/media-stream-parser.ts +300 -0
  300. package/src/calls/media-stream-protocol.ts +166 -0
  301. package/src/calls/media-stream-server.ts +592 -0
  302. package/src/calls/media-stream-stt-session.ts +460 -0
  303. package/src/calls/media-turn-detector.ts +230 -0
  304. package/src/calls/relay-server.ts +90 -75
  305. package/src/calls/resolve-call-tts-provider.ts +136 -0
  306. package/src/calls/telephony-stt-routing.ts +145 -0
  307. package/src/calls/tts-call-strategy.ts +161 -0
  308. package/src/calls/tts-text-sanitizer.ts +32 -16
  309. package/src/calls/twilio-routes.ts +281 -17
  310. package/src/calls/voice-quality.ts +78 -35
  311. package/src/calls/voice-session-bridge.ts +8 -1
  312. package/src/channels/__tests__/types.test.ts +134 -0
  313. package/src/channels/types.ts +69 -3
  314. package/src/cli/__tests__/run-assistant-command.ts +11 -1
  315. package/src/cli/commands/__tests__/backup.test.ts +1165 -0
  316. package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
  317. package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
  318. package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
  319. package/src/cli/commands/__tests__/email-download.test.ts +16 -1
  320. package/src/cli/commands/__tests__/email-list.test.ts +22 -4
  321. package/src/cli/commands/__tests__/email-register.test.ts +4 -4
  322. package/src/cli/commands/__tests__/email-send.test.ts +37 -4
  323. package/src/cli/commands/__tests__/email-status.test.ts +5 -1
  324. package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
  325. package/src/cli/commands/backup.ts +993 -0
  326. package/src/cli/commands/conversations.ts +77 -0
  327. package/src/cli/commands/credentials.ts +3 -4
  328. package/src/cli/commands/domain.ts +210 -0
  329. package/src/cli/commands/email.ts +273 -16
  330. package/src/cli/commands/mcp.ts +16 -4
  331. package/src/cli/commands/oauth/__tests__/connect.test.ts +56 -44
  332. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  333. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  334. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  335. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +32 -33
  336. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +330 -0
  337. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +117 -12
  338. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  339. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  340. package/src/cli/commands/oauth/apps.ts +7 -4
  341. package/src/cli/commands/oauth/connect.ts +6 -3
  342. package/src/cli/commands/oauth/disconnect.ts +1 -1
  343. package/src/cli/commands/oauth/mode.ts +12 -3
  344. package/src/cli/commands/oauth/providers.ts +215 -36
  345. package/src/cli/commands/oauth/shared.ts +7 -6
  346. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +254 -0
  347. package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
  348. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  349. package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
  350. package/src/cli/commands/platform/index.ts +107 -10
  351. package/src/cli/commands/usage.ts +10 -9
  352. package/src/cli/lib/daemon-credential-client.ts +4 -0
  353. package/src/cli/program.ts +30 -4
  354. package/src/config/__tests__/backup-schema.test.ts +134 -0
  355. package/src/config/assistant-feature-flags.ts +61 -62
  356. package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
  357. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +141 -0
  358. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  359. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  360. package/src/config/bundled-skills/browser/SKILL.md +30 -5
  361. package/src/config/bundled-skills/browser/TOOLS.json +123 -0
  362. package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
  363. package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
  364. package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
  365. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
  366. package/src/config/bundled-skills/contacts/SKILL.md +5 -2
  367. package/src/config/bundled-skills/document/SKILL.md +4 -0
  368. package/src/config/bundled-skills/gmail/SKILL.md +54 -8
  369. package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
  370. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
  371. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
  372. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
  373. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
  374. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
  375. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
  376. package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
  377. package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
  378. package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
  379. package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
  380. package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
  381. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
  382. package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
  383. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
  384. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  385. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  386. package/src/config/bundled-skills/outlook/SKILL.md +9 -2
  387. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
  388. package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
  389. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
  390. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  391. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  392. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
  393. package/src/config/bundled-skills/slack/SKILL.md +1 -0
  394. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  395. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  396. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  397. package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
  398. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
  399. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
  400. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
  401. package/src/config/bundled-tool-registry.ts +8 -0
  402. package/src/config/env-registry.ts +38 -0
  403. package/src/config/env.ts +49 -4
  404. package/src/config/feature-flag-registry.json +85 -14
  405. package/src/config/loader.ts +82 -13
  406. package/src/config/sanitize-for-transfer.ts +47 -0
  407. package/src/config/schema.ts +81 -15
  408. package/src/config/schemas/__tests__/stt.test.ts +43 -0
  409. package/src/config/schemas/analysis.ts +51 -0
  410. package/src/config/schemas/backup.ts +72 -0
  411. package/src/config/schemas/calls.ts +1 -26
  412. package/src/config/schemas/elevenlabs.ts +0 -59
  413. package/src/config/schemas/filing.ts +47 -7
  414. package/src/config/schemas/heartbeat.ts +27 -5
  415. package/src/config/schemas/host-browser.ts +112 -0
  416. package/src/config/schemas/inference.ts +1 -1
  417. package/src/config/schemas/memory-lifecycle.ts +14 -2
  418. package/src/config/schemas/memory-retrieval.ts +103 -0
  419. package/src/config/schemas/security.ts +0 -6
  420. package/src/config/schemas/services.ts +52 -0
  421. package/src/config/schemas/stt.ts +59 -0
  422. package/src/config/schemas/tts.ts +230 -0
  423. package/src/config/schemas/updates.ts +14 -0
  424. package/src/config/skills.ts +4 -0
  425. package/src/config/types.ts +4 -1
  426. package/src/contacts/contact-store.ts +56 -11
  427. package/src/contacts/contacts-write.ts +38 -1
  428. package/src/context/post-turn-tool-result-truncation.ts +177 -0
  429. package/src/context/tool-result-truncation.ts +2 -1
  430. package/src/context/window-manager.ts +61 -10
  431. package/src/credential-execution/approval-bridge.ts +49 -15
  432. package/src/credential-execution/executable-discovery.ts +12 -2
  433. package/src/credential-execution/process-manager.ts +33 -2
  434. package/src/credential-health/credential-health-service.ts +366 -0
  435. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
  436. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
  437. package/src/daemon/__tests__/conversation-tool-setup.test.ts +195 -0
  438. package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
  439. package/src/daemon/app-source-watcher.ts +35 -0
  440. package/src/daemon/config-watcher.ts +99 -5
  441. package/src/daemon/context-overflow-approval.ts +5 -0
  442. package/src/daemon/conversation-agent-loop-handlers.ts +23 -2
  443. package/src/daemon/conversation-agent-loop.ts +153 -42
  444. package/src/daemon/conversation-attachments.ts +40 -0
  445. package/src/daemon/conversation-error.ts +11 -0
  446. package/src/daemon/conversation-history.ts +40 -6
  447. package/src/daemon/conversation-launch.ts +220 -0
  448. package/src/daemon/conversation-lifecycle.ts +59 -9
  449. package/src/daemon/conversation-messaging.ts +37 -3
  450. package/src/daemon/conversation-notifiers.ts +5 -0
  451. package/src/daemon/conversation-process.ts +622 -13
  452. package/src/daemon/conversation-queue-manager.ts +24 -0
  453. package/src/daemon/conversation-runtime-assembly.ts +128 -36
  454. package/src/daemon/conversation-slash.ts +36 -0
  455. package/src/daemon/conversation-surfaces.ts +131 -40
  456. package/src/daemon/conversation-tool-setup.ts +99 -8
  457. package/src/daemon/conversation-usage.ts +7 -4
  458. package/src/daemon/conversation-workspace.ts +12 -0
  459. package/src/daemon/conversation.ts +292 -16
  460. package/src/daemon/date-context.ts +10 -10
  461. package/src/daemon/first-greeting.ts +3 -2
  462. package/src/daemon/handlers/config-slack-channel.ts +269 -94
  463. package/src/daemon/handlers/conversations.ts +13 -141
  464. package/src/daemon/handlers/shared.ts +80 -0
  465. package/src/daemon/handlers/skills.ts +483 -44
  466. package/src/daemon/host-bash-proxy.ts +48 -13
  467. package/src/daemon/host-browser-proxy.ts +192 -0
  468. package/src/daemon/host-cu-proxy.ts +36 -11
  469. package/src/daemon/host-file-proxy.ts +57 -9
  470. package/src/daemon/lifecycle.ts +179 -28
  471. package/src/daemon/message-protocol.ts +13 -0
  472. package/src/daemon/message-types/conversations.ts +89 -14
  473. package/src/daemon/message-types/home.ts +40 -0
  474. package/src/daemon/message-types/host-browser.ts +100 -0
  475. package/src/daemon/message-types/meet.ts +143 -0
  476. package/src/daemon/message-types/messages.ts +19 -5
  477. package/src/daemon/message-types/schedules.ts +34 -2
  478. package/src/daemon/message-types/skills.ts +26 -0
  479. package/src/daemon/message-types/subagents.ts +2 -0
  480. package/src/daemon/message-types/surfaces.ts +2 -0
  481. package/src/daemon/server.ts +439 -14
  482. package/src/daemon/shutdown-handlers.ts +32 -4
  483. package/src/daemon/shutdown-registry.ts +40 -0
  484. package/src/daemon/tool-side-effects.ts +15 -0
  485. package/src/daemon/transport-hints.ts +5 -24
  486. package/src/email/html-renderer.ts +76 -0
  487. package/src/heartbeat/heartbeat-service.ts +93 -7
  488. package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
  489. package/src/home/__tests__/emit-feed-event.test.ts +169 -0
  490. package/src/home/__tests__/feed-scheduler.test.ts +194 -0
  491. package/src/home/__tests__/feed-types.test.ts +275 -0
  492. package/src/home/__tests__/feed-writer.test.ts +688 -0
  493. package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
  494. package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
  495. package/src/home/__tests__/progress-formula.test.ts +213 -0
  496. package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
  497. package/src/home/__tests__/rollup-producer.test.ts +398 -0
  498. package/src/home/assistant-feed-authoring.ts +124 -0
  499. package/src/home/emit-feed-event.ts +158 -0
  500. package/src/home/feed-scheduler.ts +247 -0
  501. package/src/home/feed-types.ts +181 -0
  502. package/src/home/feed-writer.ts +469 -0
  503. package/src/home/platform-gmail-digest.ts +163 -0
  504. package/src/home/progress-formula.ts +86 -0
  505. package/src/home/relationship-state-writer.ts +824 -0
  506. package/src/home/relationship-state.ts +143 -0
  507. package/src/home/rollup-producer.ts +384 -0
  508. package/src/hooks/runner.ts +7 -0
  509. package/src/inbound/platform-callback-registration.ts +30 -20
  510. package/src/inbound/public-ingress-urls.ts +12 -0
  511. package/src/instrument.ts +1 -1
  512. package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
  513. package/src/ipc/cli-client.ts +151 -0
  514. package/src/ipc/cli-server.ts +234 -0
  515. package/src/ipc/gateway-client.ts +180 -0
  516. package/src/ipc/routes/index.ts +5 -0
  517. package/src/ipc/routes/wake-conversation.ts +19 -0
  518. package/src/mcp/client.ts +59 -24
  519. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
  520. package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
  521. package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
  522. package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
  523. package/src/memory/app-store.ts +31 -1
  524. package/src/memory/attachments-store.ts +70 -0
  525. package/src/memory/auto-analysis-enqueue.ts +127 -0
  526. package/src/memory/auto-analysis-guard.ts +27 -0
  527. package/src/memory/cleanup-schedule-state.ts +37 -0
  528. package/src/memory/conversation-analyze-job.ts +73 -0
  529. package/src/memory/conversation-crud.ts +122 -0
  530. package/src/memory/conversation-disk-view.ts +7 -0
  531. package/src/memory/conversation-group-migration.ts +34 -2
  532. package/src/memory/conversation-queries.ts +6 -5
  533. package/src/memory/conversation-starters-cadence.ts +76 -0
  534. package/src/memory/conversation-title-service.ts +5 -2
  535. package/src/memory/db-init.ts +18 -0
  536. package/src/memory/db-maintenance.ts +108 -0
  537. package/src/memory/db.ts +1 -0
  538. package/src/memory/embedding-backend.test.ts +75 -0
  539. package/src/memory/embedding-backend.ts +131 -5
  540. package/src/memory/embedding-gemini.test.ts +54 -0
  541. package/src/memory/embedding-gemini.ts +20 -9
  542. package/src/memory/embedding-local.ts +176 -17
  543. package/src/memory/graph/consolidation.ts +10 -23
  544. package/src/memory/graph/conversation-graph-memory.ts +15 -0
  545. package/src/memory/graph/extraction-job.ts +15 -0
  546. package/src/memory/graph/extraction.test.ts +23 -0
  547. package/src/memory/graph/extraction.ts +8 -0
  548. package/src/memory/graph/retriever.ts +67 -40
  549. package/src/memory/graph/scoring.test.ts +186 -0
  550. package/src/memory/graph/scoring.ts +31 -1
  551. package/src/memory/graph/store.test.ts +7 -3
  552. package/src/memory/graph/store.ts +47 -12
  553. package/src/memory/graph/tools.ts +1 -1
  554. package/src/memory/group-crud.ts +6 -1
  555. package/src/memory/indexer.ts +95 -16
  556. package/src/memory/job-handlers/cleanup.ts +11 -8
  557. package/src/memory/job-handlers/conversation-starters.ts +16 -10
  558. package/src/memory/jobs-store.ts +64 -4
  559. package/src/memory/jobs-worker.ts +22 -9
  560. package/src/memory/llm-usage-store.ts +137 -60
  561. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  562. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  563. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  564. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  565. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  566. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  567. package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
  568. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
  569. package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
  570. package/src/memory/migrations/index.ts +12 -0
  571. package/src/memory/migrations/registry.ts +16 -0
  572. package/src/memory/qdrant-manager.ts +43 -16
  573. package/src/memory/schema/conversations.ts +3 -0
  574. package/src/memory/schema/oauth.ts +21 -13
  575. package/src/memory/usage-buckets.ts +396 -0
  576. package/src/messaging/providers/gmail/client.ts +57 -6
  577. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
  578. package/src/messaging/providers/slack/adapter.ts +143 -38
  579. package/src/messaging/providers/slack/client.ts +16 -0
  580. package/src/messaging/providers/slack/types.ts +4 -0
  581. package/src/notifications/decision-engine.ts +3 -3
  582. package/src/notifications/signal.ts +5 -0
  583. package/src/oauth/AGENTS.md +76 -0
  584. package/src/oauth/__tests__/identity-verifier.test.ts +25 -19
  585. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  586. package/src/oauth/byo-connection.test.ts +26 -9
  587. package/src/oauth/byo-connection.ts +10 -8
  588. package/src/oauth/connect-orchestrator.ts +25 -21
  589. package/src/oauth/connect-types.ts +3 -3
  590. package/src/oauth/connection-resolver.test.ts +17 -4
  591. package/src/oauth/connection-resolver.ts +22 -18
  592. package/src/oauth/connection.ts +3 -1
  593. package/src/oauth/manual-token-connection.ts +13 -13
  594. package/src/oauth/oauth-store.ts +223 -100
  595. package/src/oauth/platform-connection.test.ts +101 -3
  596. package/src/oauth/platform-connection.ts +56 -35
  597. package/src/oauth/provider-serializer.ts +31 -5
  598. package/src/oauth/revoke.ts +76 -0
  599. package/src/oauth/seed-providers.ts +133 -87
  600. package/src/oauth/token-persistence.ts +1 -1
  601. package/src/permissions/checker.ts +16 -6
  602. package/src/permissions/defaults.ts +49 -1
  603. package/src/permissions/permission-mode.ts +4 -11
  604. package/src/permissions/prompter.ts +13 -1
  605. package/src/permissions/trust-store.ts +3 -3
  606. package/src/permissions/v2-consent-policy.ts +87 -0
  607. package/src/permissions/workspace-policy.ts +3 -0
  608. package/src/platform/client.test.ts +10 -0
  609. package/src/platform/sync-identity.ts +129 -0
  610. package/src/prompts/persona-resolver.ts +126 -2
  611. package/src/prompts/system-prompt.ts +76 -38
  612. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  613. package/src/prompts/templates/BOOTSTRAP.md +59 -105
  614. package/src/prompts/templates/SOUL.md +3 -1
  615. package/src/prompts/templates/UPDATES.md +12 -0
  616. package/src/prompts/templates/channels/slack.md +20 -0
  617. package/src/prompts/update-bulletin-format.ts +26 -9
  618. package/src/prompts/update-bulletin.ts +34 -23
  619. package/src/prompts/user-reference.ts +20 -17
  620. package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
  621. package/src/providers/anthropic/client.ts +157 -60
  622. package/src/providers/fireworks/client.ts +2 -2
  623. package/src/providers/gemini/client.ts +9 -1
  624. package/src/providers/model-catalog.ts +6 -0
  625. package/src/providers/model-intents.ts +4 -4
  626. package/src/providers/ollama/client.ts +2 -2
  627. package/src/providers/openai/chat-completions-provider.ts +474 -0
  628. package/src/providers/openai/client.ts +25 -440
  629. package/src/providers/openai/responses-provider.ts +502 -0
  630. package/src/providers/openrouter/client.ts +101 -4
  631. package/src/providers/provider-secret-catalog.ts +139 -0
  632. package/src/providers/registry.ts +2 -2
  633. package/src/providers/retry.ts +14 -3
  634. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
  635. package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
  636. package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
  637. package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
  638. package/src/providers/speech-to-text/deepgram.test.ts +332 -0
  639. package/src/providers/speech-to-text/deepgram.ts +115 -0
  640. package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
  641. package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
  642. package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
  643. package/src/providers/speech-to-text/google-gemini.ts +101 -0
  644. package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
  645. package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
  646. package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
  647. package/src/providers/speech-to-text/openai-whisper.ts +63 -33
  648. package/src/providers/speech-to-text/provider-catalog.ts +306 -0
  649. package/src/providers/speech-to-text/resolve.ts +386 -6
  650. package/src/providers/types.ts +10 -1
  651. package/src/runtime/AGENTS.md +65 -0
  652. package/src/runtime/__tests__/agent-wake.test.ts +831 -0
  653. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  654. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  655. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  656. package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
  657. package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
  658. package/src/runtime/agent-wake.ts +512 -0
  659. package/src/runtime/assistant-event-hub.ts +2 -2
  660. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  661. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  662. package/src/runtime/auth/__tests__/route-policy.test.ts +48 -0
  663. package/src/runtime/auth/middleware.ts +98 -0
  664. package/src/runtime/auth/route-policy.ts +33 -9
  665. package/src/runtime/auth/token-service.ts +56 -1
  666. package/src/runtime/btw-sidechain.ts +2 -0
  667. package/src/runtime/capability-tokens.ts +414 -0
  668. package/src/runtime/channel-approvals.ts +18 -5
  669. package/src/runtime/channel-invite-transport.ts +1 -1
  670. package/src/runtime/channel-invite-transports/email.ts +14 -6
  671. package/src/runtime/channel-readiness-service.ts +12 -22
  672. package/src/runtime/chrome-extension-registry.ts +368 -0
  673. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  674. package/src/runtime/guardian-decision-types.ts +7 -0
  675. package/src/runtime/http-server.ts +815 -75
  676. package/src/runtime/http-types.ts +6 -2
  677. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  678. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  679. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +198 -0
  680. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
  681. package/src/runtime/migrations/migration-transport.ts +7 -0
  682. package/src/runtime/migrations/migration-wizard.ts +23 -2
  683. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  684. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  685. package/src/runtime/migrations/vbundle-import-analyzer.ts +96 -1
  686. package/src/runtime/migrations/vbundle-importer.ts +89 -5
  687. package/src/runtime/pending-interactions.ts +18 -13
  688. package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
  689. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
  690. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
  691. package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
  692. package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
  693. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
  694. package/src/runtime/routes/app-management-routes.ts +12 -18
  695. package/src/runtime/routes/approval-routes.ts +90 -16
  696. package/src/runtime/routes/attachment-routes.test.ts +9 -3
  697. package/src/runtime/routes/attachment-routes.ts +216 -17
  698. package/src/runtime/routes/backup-routes.ts +519 -0
  699. package/src/runtime/routes/browser-extension-pair-routes.ts +556 -0
  700. package/src/runtime/routes/btw-routes.ts +8 -6
  701. package/src/runtime/routes/contact-routes.test.ts +298 -0
  702. package/src/runtime/routes/contact-routes.ts +132 -5
  703. package/src/runtime/routes/conversation-analysis-routes.ts +22 -141
  704. package/src/runtime/routes/conversation-management-routes.ts +223 -0
  705. package/src/runtime/routes/conversation-routes.ts +598 -103
  706. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  707. package/src/runtime/routes/filing-routes.ts +93 -0
  708. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  709. package/src/runtime/routes/home-feed-routes.ts +334 -0
  710. package/src/runtime/routes/home-state-routes.ts +138 -0
  711. package/src/runtime/routes/host-browser-routes.ts +268 -0
  712. package/src/runtime/routes/host-file-routes.ts +9 -1
  713. package/src/runtime/routes/identity-intro-cache.ts +7 -3
  714. package/src/runtime/routes/identity-routes.ts +262 -33
  715. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
  716. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
  717. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
  718. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
  719. package/src/runtime/routes/integrations/slack/channel.ts +11 -3
  720. package/src/runtime/routes/integrations/slack/share.ts +45 -7
  721. package/src/runtime/routes/llm-context-normalization.ts +303 -0
  722. package/src/runtime/routes/log-export-routes.ts +42 -22
  723. package/src/runtime/routes/memory-item-routes.test.ts +3 -2
  724. package/src/runtime/routes/memory-item-routes.ts +1 -7
  725. package/src/runtime/routes/migration-routes.ts +122 -2
  726. package/src/runtime/routes/oauth-apps.ts +15 -17
  727. package/src/runtime/routes/oauth-providers.ts +4 -0
  728. package/src/runtime/routes/schedule-routes.ts +24 -11
  729. package/src/runtime/routes/settings-routes.ts +31 -102
  730. package/src/runtime/routes/skills-routes.ts +128 -9
  731. package/src/runtime/routes/stt-routes.ts +233 -0
  732. package/src/runtime/routes/subagents-routes.ts +14 -10
  733. package/src/runtime/routes/surface-action-routes.ts +41 -2
  734. package/src/runtime/routes/tts-routes.ts +108 -24
  735. package/src/runtime/routes/usage-routes.ts +38 -9
  736. package/src/runtime/routes/user-route-dispatcher.ts +50 -5
  737. package/src/runtime/routes/user-routes.ts +13 -1
  738. package/src/runtime/routes/work-items-routes.ts +8 -1
  739. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  740. package/src/runtime/routes/workspace-routes.ts +8 -1
  741. package/src/runtime/routes/workspace-utils.ts +2 -0
  742. package/src/runtime/runtime-mode.ts +33 -0
  743. package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
  744. package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
  745. package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
  746. package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
  747. package/src/runtime/services/analyze-conversation.ts +344 -0
  748. package/src/runtime/services/analyze-deps-singleton.ts +32 -0
  749. package/src/runtime/services/auto-analysis-prompt.ts +55 -0
  750. package/src/runtime/skill-route-registry.ts +49 -0
  751. package/src/runtime/slack-block-formatting.ts +437 -10
  752. package/src/schedule/scheduler.ts +57 -5
  753. package/src/security/ces-credential-client.ts +20 -0
  754. package/src/security/ces-rpc-credential-backend.ts +17 -0
  755. package/src/security/credential-backend.ts +5 -0
  756. package/src/security/oauth2.ts +68 -29
  757. package/src/security/secure-keys.ts +143 -27
  758. package/src/security/token-manager.ts +31 -10
  759. package/src/sequence/engine.ts +23 -0
  760. package/src/sequence/types.ts +1 -1
  761. package/src/skills/catalog-files.ts +554 -0
  762. package/src/skills/category-inference.ts +122 -0
  763. package/src/skills/clawhub-files.ts +213 -0
  764. package/src/skills/clawhub.ts +84 -23
  765. package/src/skills/skill-file-provider.ts +40 -0
  766. package/src/skills/skillssh-files.ts +395 -0
  767. package/src/skills/skillssh-registry.ts +4 -4
  768. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
  769. package/src/stt/__tests__/types.test.ts +89 -0
  770. package/src/stt/daemon-batch-transcriber.ts +195 -0
  771. package/src/stt/stt-stream-session.ts +499 -0
  772. package/src/stt/types.ts +330 -0
  773. package/src/stt/wav-encoder.test.ts +373 -0
  774. package/src/stt/wav-encoder.ts +175 -0
  775. package/src/subagent/manager.ts +169 -40
  776. package/src/subagent/types.ts +19 -0
  777. package/src/tools/apps/executors.ts +11 -2
  778. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  779. package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
  780. package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
  781. package/src/tools/browser/auth-detector.ts +43 -12
  782. package/src/tools/browser/browser-execution.ts +1787 -342
  783. package/src/tools/browser/browser-manager.ts +81 -12
  784. package/src/tools/browser/browser-mode-constants.ts +12 -0
  785. package/src/tools/browser/browser-mode.ts +92 -0
  786. package/src/tools/browser/browser-status-constants.ts +33 -0
  787. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  788. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  789. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +1263 -0
  790. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +359 -0
  791. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1993 -0
  792. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  793. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  794. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  795. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  796. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  797. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  798. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +1007 -0
  799. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  800. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +744 -0
  801. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  802. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +868 -0
  803. package/src/tools/browser/cdp-client/errors.ts +49 -0
  804. package/src/tools/browser/cdp-client/extension-cdp-client.ts +148 -0
  805. package/src/tools/browser/cdp-client/factory.ts +914 -0
  806. package/src/tools/browser/cdp-client/index.ts +28 -0
  807. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  808. package/src/tools/browser/cdp-client/types.ts +120 -0
  809. package/src/tools/credentials/vault.ts +35 -6
  810. package/src/tools/filesystem/edit.ts +1 -1
  811. package/src/tools/filesystem/list.ts +1 -1
  812. package/src/tools/filesystem/read.ts +1 -1
  813. package/src/tools/filesystem/write.ts +2 -1
  814. package/src/tools/host-filesystem/edit.ts +1 -1
  815. package/src/tools/host-filesystem/read.ts +12 -15
  816. package/src/tools/host-filesystem/write.ts +1 -1
  817. package/src/tools/host-terminal/host-shell.ts +21 -16
  818. package/src/tools/network/web-fetch.ts +5 -2
  819. package/src/tools/network/web-search.ts +5 -2
  820. package/src/tools/permission-checker.ts +77 -82
  821. package/src/tools/registry.ts +0 -2
  822. package/src/tools/secret-detection-handler.ts +34 -0
  823. package/src/tools/shared/filesystem/image-read.ts +61 -40
  824. package/src/tools/shared/shell-output.ts +3 -1
  825. package/src/tools/side-effects.ts +2 -0
  826. package/src/tools/skills/sandbox-runner.ts +3 -2
  827. package/src/tools/subagent/spawn.ts +47 -3
  828. package/src/tools/subagent/status.ts +2 -0
  829. package/src/tools/system/register.ts +2 -16
  830. package/src/tools/terminal/safe-env.ts +15 -0
  831. package/src/tools/terminal/shell.ts +36 -20
  832. package/src/tools/tool-approval-handler.ts +48 -2
  833. package/src/tools/tool-manifest.ts +21 -0
  834. package/src/tools/types.ts +19 -0
  835. package/src/tools/ui-surface/definitions.ts +6 -1
  836. package/src/tts/__tests__/provider-adapters.test.ts +834 -0
  837. package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
  838. package/src/tts/__tests__/provider-catalog.test.ts +183 -0
  839. package/src/tts/__tests__/provider-registry.test.ts +90 -0
  840. package/src/tts/provider-catalog.ts +201 -0
  841. package/src/tts/provider-registry.ts +73 -0
  842. package/src/tts/providers/deepgram-provider.ts +219 -0
  843. package/src/tts/providers/elevenlabs-provider.ts +211 -0
  844. package/src/tts/providers/fish-audio-provider.ts +183 -0
  845. package/src/tts/providers/index.ts +42 -0
  846. package/src/tts/providers/register-builtins.ts +130 -0
  847. package/src/tts/synthesize-text.ts +110 -0
  848. package/src/tts/tts-config-resolver.ts +78 -0
  849. package/src/tts/types.ts +153 -0
  850. package/src/types/onboarding-context.ts +7 -0
  851. package/src/util/abort-reasons.ts +58 -0
  852. package/src/util/device-id.ts +32 -16
  853. package/src/util/errors.ts +9 -1
  854. package/src/util/platform.ts +63 -24
  855. package/src/util/pricing.ts +66 -3
  856. package/src/util/spawn.ts +1 -1
  857. package/src/util/truncate.ts +4 -2
  858. package/src/util/unicode.ts +201 -0
  859. package/src/version.ts +19 -24
  860. package/src/watcher/engine.ts +23 -0
  861. package/src/watcher/watcher-store.ts +31 -0
  862. package/src/workspace/migrations/003-seed-device-id.ts +9 -3
  863. package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
  864. package/src/workspace/migrations/029-seed-pkb.ts +1 -1
  865. package/src/workspace/migrations/031-drop-user-md.ts +317 -0
  866. package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
  867. package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
  868. package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
  869. package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
  870. package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
  871. package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
  872. package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
  873. package/src/workspace/migrations/registry.ts +16 -0
  874. package/src/workspace/top-level-renderer.ts +31 -1
  875. package/src/workspace/turn-commit.ts +31 -0
  876. package/src/__tests__/chrome-cdp.test.ts +0 -419
  877. package/src/__tests__/email-cli.test.ts +0 -297
  878. package/src/__tests__/email-service-config-fallback.test.ts +0 -102
  879. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  880. package/src/__tests__/permission-mode-store.test.ts +0 -277
  881. package/src/browser-extension-relay/protocol.ts +0 -63
  882. package/src/browser-extension-relay/server.ts +0 -203
  883. package/src/cli/commands/browser-relay.ts +0 -536
  884. package/src/config/schemas/sandbox.ts +0 -14
  885. package/src/email/guardrails.ts +0 -221
  886. package/src/email/provider.ts +0 -117
  887. package/src/email/providers/agentmail.ts +0 -361
  888. package/src/email/providers/index.ts +0 -65
  889. package/src/email/service.ts +0 -384
  890. package/src/email/types.ts +0 -126
  891. package/src/permissions/permission-mode-store.ts +0 -180
  892. package/src/prompts/templates/USER.md +0 -13
  893. package/src/providers/speech-to-text/types.ts +0 -17
  894. package/src/tools/browser/chrome-cdp.ts +0 -239
  895. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -0,0 +1,980 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+
3
+ import type { SttStreamServerEvent } from "../../stt/types.js";
4
+ import { DeepgramRealtimeTranscriber } from "./deepgram-realtime.js";
5
+
6
+ const TEST_API_KEY = "dg-test-key-for-streaming";
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Mock WebSocket
10
+ // ---------------------------------------------------------------------------
11
+
12
+ type WsEventType = "open" | "close" | "error" | "message";
13
+ type WsListener = (...args: unknown[]) => void;
14
+
15
+ /**
16
+ * Minimal mock WebSocket that simulates the Deepgram live endpoint.
17
+ * Tests drive behavior by calling helper methods (e.g. `simulateOpen`,
18
+ * `simulateMessage`).
19
+ */
20
+ class MockWebSocket {
21
+ readyState = 0; // CONNECTING
22
+ bufferedAmount = 0;
23
+
24
+ /** All data sent via `.send()`. */
25
+ sentData: (string | Uint8Array)[] = [];
26
+
27
+ /** Whether `.close()` was called. */
28
+ closeCalled = false;
29
+ closeCode?: number;
30
+ closeReason?: string;
31
+
32
+ private listeners = new Map<WsEventType, WsListener[]>();
33
+
34
+ addEventListener(type: WsEventType, listener: WsListener): void {
35
+ const list = this.listeners.get(type) ?? [];
36
+ list.push(listener);
37
+ this.listeners.set(type, list);
38
+ }
39
+
40
+ removeEventListener(type: string, listener: unknown): void {
41
+ const list = this.listeners.get(type as WsEventType);
42
+ if (!list) return;
43
+ const idx = list.indexOf(listener as WsListener);
44
+ if (idx !== -1) list.splice(idx, 1);
45
+ }
46
+
47
+ send(data: string | Uint8Array): void {
48
+ if (this.readyState !== 1) {
49
+ throw new Error("WebSocket is not open");
50
+ }
51
+ this.sentData.push(data);
52
+ }
53
+
54
+ close(code?: number, reason?: string): void {
55
+ this.closeCalled = true;
56
+ this.closeCode = code;
57
+ this.closeReason = reason;
58
+ this.readyState = 3; // CLOSED
59
+ }
60
+
61
+ // ── Test helpers ──────────────────────────────────────────────────
62
+
63
+ simulateOpen(): void {
64
+ this.readyState = 1; // OPEN
65
+ for (const l of this.listeners.get("open") ?? []) l();
66
+ }
67
+
68
+ simulateMessage(data: string): void {
69
+ for (const l of this.listeners.get("message") ?? []) l({ data });
70
+ }
71
+
72
+ simulateClose(code = 1000, reason = ""): void {
73
+ this.readyState = 3;
74
+ for (const l of this.listeners.get("close") ?? []) l({ code, reason });
75
+ }
76
+
77
+ simulateError(err: unknown): void {
78
+ for (const l of this.listeners.get("error") ?? []) l(err);
79
+ }
80
+ }
81
+
82
+ // ---------------------------------------------------------------------------
83
+ // Helpers
84
+ // ---------------------------------------------------------------------------
85
+
86
+ /** Build a Deepgram streaming "Results" JSON frame. */
87
+ function resultsFrame(
88
+ transcript: string,
89
+ options: {
90
+ is_final?: boolean;
91
+ speech_final?: boolean;
92
+ words?: { word: string; speaker?: number }[];
93
+ } = {},
94
+ ): string {
95
+ return JSON.stringify({
96
+ type: "Results",
97
+ channel_index: [0, 1],
98
+ duration: 1.5,
99
+ start: 0,
100
+ is_final: options.is_final ?? false,
101
+ speech_final: options.speech_final ?? false,
102
+ channel: {
103
+ alternatives: [
104
+ {
105
+ transcript,
106
+ confidence: 0.95,
107
+ ...(options.words ? { words: options.words } : {}),
108
+ },
109
+ ],
110
+ },
111
+ });
112
+ }
113
+
114
+ /** Build a Deepgram "UtteranceEnd" frame. */
115
+ function utteranceEndFrame(): string {
116
+ return JSON.stringify({ type: "UtteranceEnd" });
117
+ }
118
+
119
+ /** Build a Deepgram "Metadata" frame. */
120
+ function metadataFrame(): string {
121
+ return JSON.stringify({
122
+ type: "Metadata",
123
+ request_id: "test-request-id",
124
+ model_info: { name: "nova-2" },
125
+ });
126
+ }
127
+
128
+ /** Collect all events emitted during a test. */
129
+ function createEventCollector(): {
130
+ events: SttStreamServerEvent[];
131
+ onEvent: (event: SttStreamServerEvent) => void;
132
+ } {
133
+ const events: SttStreamServerEvent[] = [];
134
+ return {
135
+ events,
136
+ onEvent: (event: SttStreamServerEvent) => events.push(event),
137
+ };
138
+ }
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Test suite
142
+ // ---------------------------------------------------------------------------
143
+
144
+ describe("DeepgramRealtimeTranscriber", () => {
145
+ let mockWs: MockWebSocket;
146
+ let originalWebSocket: unknown;
147
+
148
+ beforeEach(() => {
149
+ mockWs = new MockWebSocket();
150
+ originalWebSocket = (globalThis as Record<string, unknown>).WebSocket;
151
+
152
+ // Replace global WebSocket with a factory that returns our mock.
153
+ (globalThis as Record<string, unknown>).WebSocket = class {
154
+ constructor(
155
+ _url: string,
156
+ _options?: { headers?: Record<string, string> },
157
+ ) {
158
+ // Immediately schedule the mock's open event for the next microtask
159
+ // so start() can attach its handlers first.
160
+ return mockWs;
161
+ }
162
+ };
163
+ });
164
+
165
+ afterEach(() => {
166
+ (globalThis as Record<string, unknown>).WebSocket = originalWebSocket;
167
+ });
168
+
169
+ // ── Helper: start a session ────────────────────────────────────────
170
+
171
+ async function startSession(
172
+ options?: ConstructorParameters<typeof DeepgramRealtimeTranscriber>[1],
173
+ ): Promise<{
174
+ transcriber: DeepgramRealtimeTranscriber;
175
+ events: SttStreamServerEvent[];
176
+ }> {
177
+ const transcriber = new DeepgramRealtimeTranscriber(TEST_API_KEY, {
178
+ inactivityTimeoutMs: 60_000, // long timeout to avoid test flakes
179
+ ...options,
180
+ });
181
+ const { events, onEvent } = createEventCollector();
182
+
183
+ const startPromise = transcriber.start(onEvent);
184
+ // Simulate the WebSocket opening after start() attaches handlers.
185
+ mockWs.simulateOpen();
186
+ await startPromise;
187
+
188
+ return { transcriber, events };
189
+ }
190
+
191
+ // ─────────────────────────────────────────────────────────────────
192
+ // Connection lifecycle
193
+ // ─────────────────────────────────────────────────────────────────
194
+
195
+ test("start() opens WebSocket and resolves on open", async () => {
196
+ const transcriber = new DeepgramRealtimeTranscriber(TEST_API_KEY);
197
+ const { onEvent } = createEventCollector();
198
+
199
+ const startPromise = transcriber.start(onEvent);
200
+ mockWs.simulateOpen();
201
+ await startPromise;
202
+
203
+ // The mock WebSocket should have been created (readyState was set to OPEN).
204
+ expect(mockWs.readyState).toBe(1);
205
+ });
206
+
207
+ test("start() rejects on connect timeout", async () => {
208
+ const transcriber = new DeepgramRealtimeTranscriber(TEST_API_KEY, {
209
+ connectTimeoutMs: 50,
210
+ });
211
+ const { onEvent } = createEventCollector();
212
+
213
+ // Never simulate open — let the timeout fire.
214
+ await expect(transcriber.start(onEvent)).rejects.toThrow(
215
+ "Deepgram realtime connect timeout",
216
+ );
217
+ });
218
+
219
+ test("start() rejects on WebSocket error during connect", async () => {
220
+ const transcriber = new DeepgramRealtimeTranscriber(TEST_API_KEY);
221
+ const { onEvent } = createEventCollector();
222
+
223
+ const startPromise = transcriber.start(onEvent);
224
+ mockWs.simulateError(new Error("Connection refused"));
225
+
226
+ await expect(startPromise).rejects.toThrow("connect error");
227
+ });
228
+
229
+ test("start() rejects on WebSocket close before open", async () => {
230
+ const transcriber = new DeepgramRealtimeTranscriber(TEST_API_KEY);
231
+ const { onEvent } = createEventCollector();
232
+
233
+ const startPromise = transcriber.start(onEvent);
234
+ mockWs.simulateClose(1006, "abnormal");
235
+
236
+ await expect(startPromise).rejects.toThrow("closed before open");
237
+ });
238
+
239
+ test("start() throws if called twice", async () => {
240
+ const { transcriber } = await startSession();
241
+
242
+ await expect(transcriber.start(() => {})).rejects.toThrow(
243
+ "start() called twice",
244
+ );
245
+ });
246
+
247
+ // ─────────────────────────────────────────────────────────────────
248
+ // Partial (interim) transcript events
249
+ // ─────────────────────────────────────────────────────────────────
250
+
251
+ test("emits partial event for interim results (is_final=false)", async () => {
252
+ const { events } = await startSession();
253
+
254
+ mockWs.simulateMessage(resultsFrame("hello wor", { is_final: false }));
255
+
256
+ expect(events).toHaveLength(1);
257
+ expect(events[0]).toEqual({
258
+ type: "partial",
259
+ text: "hello wor",
260
+ confidence: 0.95,
261
+ });
262
+ });
263
+
264
+ test("trims whitespace from partial transcript text", async () => {
265
+ const { events } = await startSession();
266
+
267
+ mockWs.simulateMessage(resultsFrame(" hello ", { is_final: false }));
268
+
269
+ expect(events[0]).toEqual({
270
+ type: "partial",
271
+ text: "hello",
272
+ confidence: 0.95,
273
+ });
274
+ });
275
+
276
+ test("does not emit partials when interimResults is disabled", async () => {
277
+ const { events } = await startSession({ interimResults: false });
278
+
279
+ mockWs.simulateMessage(resultsFrame("hello", { is_final: false }));
280
+
281
+ expect(events).toHaveLength(0);
282
+ });
283
+
284
+ // ─────────────────────────────────────────────────────────────────
285
+ // Final transcript events
286
+ // ─────────────────────────────────────────────────────────────────
287
+
288
+ test("emits final event for committed results (is_final=true)", async () => {
289
+ const { events } = await startSession();
290
+
291
+ mockWs.simulateMessage(
292
+ resultsFrame("hello world", { is_final: true, speech_final: true }),
293
+ );
294
+
295
+ expect(events).toHaveLength(1);
296
+ expect(events[0]).toEqual({
297
+ type: "final",
298
+ text: "hello world",
299
+ confidence: 0.95,
300
+ });
301
+ });
302
+
303
+ test("emits final with empty text for silence segments", async () => {
304
+ const { events } = await startSession();
305
+
306
+ mockWs.simulateMessage(resultsFrame("", { is_final: true }));
307
+
308
+ expect(events).toHaveLength(1);
309
+ expect(events[0]).toEqual({
310
+ type: "final",
311
+ text: "",
312
+ confidence: 0.95,
313
+ });
314
+ });
315
+
316
+ test("handles missing transcript field gracefully", async () => {
317
+ const { events } = await startSession();
318
+
319
+ const frame = JSON.stringify({
320
+ type: "Results",
321
+ is_final: true,
322
+ channel: { alternatives: [{ confidence: 0.5 }] },
323
+ });
324
+ mockWs.simulateMessage(frame);
325
+
326
+ expect(events).toHaveLength(1);
327
+ expect(events[0]).toEqual({
328
+ type: "final",
329
+ text: "",
330
+ confidence: 0.5,
331
+ });
332
+ });
333
+
334
+ test("handles missing channel field gracefully", async () => {
335
+ const { events } = await startSession();
336
+
337
+ const frame = JSON.stringify({
338
+ type: "Results",
339
+ is_final: true,
340
+ });
341
+ mockWs.simulateMessage(frame);
342
+
343
+ expect(events).toHaveLength(1);
344
+ expect(events[0]).toEqual({ type: "final", text: "" });
345
+ });
346
+
347
+ // ─────────────────────────────────────────────────────────────────
348
+ // Diarization: speakerLabel extraction
349
+ // ─────────────────────────────────────────────────────────────────
350
+
351
+ // Fixture A: diarize disabled (default) — baseline shape unchanged.
352
+ test("omits speakerLabel when diarization is disabled", async () => {
353
+ const { events } = await startSession();
354
+
355
+ mockWs.simulateMessage(resultsFrame("hello world", { is_final: true }));
356
+
357
+ expect(events).toHaveLength(1);
358
+ expect(events[0]).toEqual({
359
+ type: "final",
360
+ text: "hello world",
361
+ confidence: 0.95,
362
+ });
363
+ // `in` check: the key must not exist at all, not just be undefined.
364
+ expect("speakerLabel" in events[0]).toBe(false);
365
+ });
366
+
367
+ // Fixture B: single-speaker segment with diarize on.
368
+ test("emits speakerLabel '0' for a single-speaker segment", async () => {
369
+ const { events } = await startSession({ diarize: true });
370
+
371
+ mockWs.simulateMessage(
372
+ resultsFrame("hello world", {
373
+ is_final: true,
374
+ words: [
375
+ { word: "hello", speaker: 0 },
376
+ { word: "world", speaker: 0 },
377
+ ],
378
+ }),
379
+ );
380
+
381
+ expect(events).toHaveLength(1);
382
+ expect(events[0]).toEqual({
383
+ type: "final",
384
+ text: "hello world",
385
+ speakerLabel: "0",
386
+ confidence: 0.95,
387
+ });
388
+ });
389
+
390
+ // Fixture C: two speakers with one dominant — mode wins.
391
+ test("emits speakerLabel for the dominant speaker in a two-speaker segment", async () => {
392
+ const { events } = await startSession({ diarize: true });
393
+
394
+ // Speaker 1 says three words, speaker 0 says one — speaker 1 is the mode.
395
+ mockWs.simulateMessage(
396
+ resultsFrame("yes exactly right here", {
397
+ is_final: true,
398
+ words: [
399
+ { word: "yes", speaker: 0 },
400
+ { word: "exactly", speaker: 1 },
401
+ { word: "right", speaker: 1 },
402
+ { word: "here", speaker: 1 },
403
+ ],
404
+ }),
405
+ );
406
+
407
+ expect(events).toHaveLength(1);
408
+ expect(events[0]).toEqual({
409
+ type: "final",
410
+ text: "yes exactly right here",
411
+ speakerLabel: "1",
412
+ confidence: 0.95,
413
+ });
414
+ });
415
+
416
+ // Fixture D: tied segment — first-word speaker wins.
417
+ test("breaks ties by picking the first word's speaker", async () => {
418
+ const { events } = await startSession({ diarize: true });
419
+
420
+ // 2 words for each speaker — tie. First word is speaker 2, so 2 wins.
421
+ mockWs.simulateMessage(
422
+ resultsFrame("alpha beta gamma delta", {
423
+ is_final: true,
424
+ words: [
425
+ { word: "alpha", speaker: 2 },
426
+ { word: "beta", speaker: 3 },
427
+ { word: "gamma", speaker: 2 },
428
+ { word: "delta", speaker: 3 },
429
+ ],
430
+ }),
431
+ );
432
+
433
+ expect(events).toHaveLength(1);
434
+ expect(events[0]).toEqual({
435
+ type: "final",
436
+ text: "alpha beta gamma delta",
437
+ speakerLabel: "2",
438
+ confidence: 0.95,
439
+ });
440
+ });
441
+
442
+ // Also verify partials carry the label.
443
+ test("emits speakerLabel on partial events when diarization is enabled", async () => {
444
+ const { events } = await startSession({ diarize: true });
445
+
446
+ mockWs.simulateMessage(
447
+ resultsFrame("hel", {
448
+ is_final: false,
449
+ words: [{ word: "hel", speaker: 0 }],
450
+ }),
451
+ );
452
+
453
+ expect(events).toHaveLength(1);
454
+ expect(events[0]).toEqual({
455
+ type: "partial",
456
+ text: "hel",
457
+ speakerLabel: "0",
458
+ confidence: 0.95,
459
+ });
460
+ });
461
+
462
+ // Diarize on, but the provider response carries no per-word speakers —
463
+ // speakerLabel must stay undefined/absent.
464
+ test("omits speakerLabel when words have no speaker field", async () => {
465
+ const { events } = await startSession({ diarize: true });
466
+
467
+ mockWs.simulateMessage(
468
+ resultsFrame("no speakers here", {
469
+ is_final: true,
470
+ words: [{ word: "no" }, { word: "speakers" }, { word: "here" }],
471
+ }),
472
+ );
473
+
474
+ expect(events).toHaveLength(1);
475
+ expect(events[0]).toEqual({
476
+ type: "final",
477
+ text: "no speakers here",
478
+ confidence: 0.95,
479
+ });
480
+ expect("speakerLabel" in events[0]).toBe(false);
481
+ });
482
+
483
+ test("forwards diarize=true to the Deepgram WebSocket URL", async () => {
484
+ let capturedUrl: string | undefined;
485
+ const origWs = (globalThis as Record<string, unknown>).WebSocket;
486
+ (globalThis as Record<string, unknown>).WebSocket = class {
487
+ constructor(url: string) {
488
+ capturedUrl = url;
489
+ return mockWs;
490
+ }
491
+ };
492
+
493
+ const transcriber = new DeepgramRealtimeTranscriber(TEST_API_KEY, {
494
+ diarize: true,
495
+ });
496
+ const { onEvent } = createEventCollector();
497
+ const startPromise = transcriber.start(onEvent);
498
+ mockWs.simulateOpen();
499
+ await startPromise;
500
+
501
+ const url = new URL(capturedUrl!);
502
+ expect(url.searchParams.get("diarize")).toBe("true");
503
+
504
+ (globalThis as Record<string, unknown>).WebSocket = origWs;
505
+ });
506
+
507
+ test("omits diarize param when diarization is disabled (default)", async () => {
508
+ let capturedUrl: string | undefined;
509
+ const origWs = (globalThis as Record<string, unknown>).WebSocket;
510
+ (globalThis as Record<string, unknown>).WebSocket = class {
511
+ constructor(url: string) {
512
+ capturedUrl = url;
513
+ return mockWs;
514
+ }
515
+ };
516
+
517
+ const transcriber = new DeepgramRealtimeTranscriber(TEST_API_KEY);
518
+ const { onEvent } = createEventCollector();
519
+ const startPromise = transcriber.start(onEvent);
520
+ mockWs.simulateOpen();
521
+ await startPromise;
522
+
523
+ const url = new URL(capturedUrl!);
524
+ expect(url.searchParams.get("diarize")).toBeNull();
525
+
526
+ (globalThis as Record<string, unknown>).WebSocket = origWs;
527
+ });
528
+
529
+ // ─────────────────────────────────────────────────────────────────
530
+ // Multi-event sequence
531
+ // ─────────────────────────────────────────────────────────────────
532
+
533
+ test("emits partial then final for a complete utterance", async () => {
534
+ const { events } = await startSession();
535
+
536
+ mockWs.simulateMessage(resultsFrame("hel", { is_final: false }));
537
+ mockWs.simulateMessage(resultsFrame("hello", { is_final: false }));
538
+ mockWs.simulateMessage(
539
+ resultsFrame("hello world", { is_final: true, speech_final: true }),
540
+ );
541
+
542
+ expect(events).toHaveLength(3);
543
+ expect(events[0]).toEqual({
544
+ type: "partial",
545
+ text: "hel",
546
+ confidence: 0.95,
547
+ });
548
+ expect(events[1]).toEqual({
549
+ type: "partial",
550
+ text: "hello",
551
+ confidence: 0.95,
552
+ });
553
+ expect(events[2]).toEqual({
554
+ type: "final",
555
+ text: "hello world",
556
+ confidence: 0.95,
557
+ });
558
+ });
559
+
560
+ // ─────────────────────────────────────────────────────────────────
561
+ // Non-transcript frames
562
+ // ─────────────────────────────────────────────────────────────────
563
+
564
+ test("ignores UtteranceEnd frames (no event emitted)", async () => {
565
+ const { events } = await startSession();
566
+
567
+ mockWs.simulateMessage(utteranceEndFrame());
568
+
569
+ expect(events).toHaveLength(0);
570
+ });
571
+
572
+ test("ignores Metadata frames (no event emitted)", async () => {
573
+ const { events } = await startSession();
574
+
575
+ mockWs.simulateMessage(metadataFrame());
576
+
577
+ expect(events).toHaveLength(0);
578
+ });
579
+
580
+ test("ignores non-JSON messages", async () => {
581
+ const { events } = await startSession();
582
+
583
+ mockWs.simulateMessage("not json at all");
584
+
585
+ expect(events).toHaveLength(0);
586
+ });
587
+
588
+ // ─────────────────────────────────────────────────────────────────
589
+ // Audio sending and backpressure
590
+ // ─────────────────────────────────────────────────────────────────
591
+
592
+ test("sendAudio forwards raw bytes to WebSocket", async () => {
593
+ const { transcriber } = await startSession();
594
+
595
+ const audio = Buffer.from("raw-pcm-data");
596
+ transcriber.sendAudio(audio, "audio/pcm");
597
+
598
+ expect(mockWs.sentData).toHaveLength(1);
599
+ const sent = mockWs.sentData[0];
600
+ expect(sent).toBeInstanceOf(Uint8Array);
601
+ expect(Buffer.from(sent as Uint8Array).toString()).toBe("raw-pcm-data");
602
+ });
603
+
604
+ test("sendAudio drops frames when backpressure is high", async () => {
605
+ const { transcriber } = await startSession();
606
+
607
+ // Simulate high backpressure.
608
+ mockWs.bufferedAmount = 2 * 1024 * 1024; // 2 MiB > 1 MiB threshold
609
+
610
+ transcriber.sendAudio(Buffer.from("dropped"), "audio/pcm");
611
+
612
+ expect(mockWs.sentData).toHaveLength(0);
613
+ });
614
+
615
+ test("sendAudio is no-op after stop()", async () => {
616
+ const { transcriber } = await startSession();
617
+
618
+ transcriber.stop();
619
+ transcriber.sendAudio(Buffer.from("ignored"), "audio/pcm");
620
+
621
+ // Only the CloseStream message should have been sent, not the audio.
622
+ const textMessages = mockWs.sentData.filter((d) => typeof d === "string");
623
+ expect(textMessages).toHaveLength(1);
624
+ expect(JSON.parse(textMessages[0] as string)).toEqual({
625
+ type: "CloseStream",
626
+ });
627
+ });
628
+
629
+ // ─────────────────────────────────────────────────────────────────
630
+ // Stop lifecycle
631
+ // ─────────────────────────────────────────────────────────────────
632
+
633
+ test("stop() sends CloseStream message", async () => {
634
+ const { transcriber } = await startSession();
635
+
636
+ transcriber.stop();
637
+
638
+ const textMessages = mockWs.sentData.filter((d) => typeof d === "string");
639
+ expect(textMessages).toHaveLength(1);
640
+ expect(JSON.parse(textMessages[0] as string)).toEqual({
641
+ type: "CloseStream",
642
+ });
643
+ });
644
+
645
+ test("stop() emits closed event when provider closes normally", async () => {
646
+ const { transcriber, events } = await startSession();
647
+
648
+ transcriber.stop();
649
+ mockWs.simulateClose(1000, "normal");
650
+
651
+ const closedEvents = events.filter((e) => e.type === "closed");
652
+ expect(closedEvents).toHaveLength(1);
653
+ });
654
+
655
+ test("stop() emits closed after grace timeout if provider does not close", async () => {
656
+ // Use a short inactivity timeout and override the close grace to be short.
657
+ const { events } = await startSession({
658
+ inactivityTimeoutMs: 60_000,
659
+ });
660
+
661
+ // We need to access the adapter internally to verify the grace timer
662
+ // fires. Since we can't easily override CLOSE_GRACE_MS, we just verify
663
+ // that stop() + normal close produces the right events.
664
+ // (The grace timer is 5s by default, too long for a unit test, so we
665
+ // test the normal close path instead.)
666
+
667
+ // Send some data first, then stop
668
+ mockWs.simulateMessage(resultsFrame("test", { is_final: true }));
669
+
670
+ // Trigger provider close after stop
671
+ const transcriber = new DeepgramRealtimeTranscriber(TEST_API_KEY, {
672
+ inactivityTimeoutMs: 60_000,
673
+ });
674
+ const { events: events2, onEvent } = createEventCollector();
675
+ const startPromise = transcriber.start(onEvent);
676
+ mockWs = new MockWebSocket();
677
+ // Re-mock the WebSocket for this transcriber — we can't easily because
678
+ // the first one was already created. Instead, verify the normal path.
679
+ expect(events.filter((e) => e.type === "final")).toHaveLength(1);
680
+
681
+ // Cleanup
682
+ try {
683
+ startPromise.catch(() => {});
684
+ } catch {
685
+ // ignore
686
+ }
687
+ void events2;
688
+ });
689
+
690
+ test("stop() is idempotent (calling twice does not throw)", async () => {
691
+ const { transcriber, events } = await startSession();
692
+
693
+ transcriber.stop();
694
+ mockWs.simulateClose(1000, "");
695
+ transcriber.stop(); // Second call should be a no-op.
696
+
697
+ const closedEvents = events.filter((e) => e.type === "closed");
698
+ expect(closedEvents).toHaveLength(1);
699
+ });
700
+
701
+ // ─────────────────────────────────────────────────────────────────
702
+ // Error handling
703
+ // ─────────────────────────────────────────────────────────────────
704
+
705
+ test("unexpected close emits error + closed events", async () => {
706
+ const { events } = await startSession();
707
+
708
+ mockWs.simulateClose(1006, "abnormal closure");
709
+
710
+ const errorEvents = events.filter((e) => e.type === "error");
711
+ const closedEvents = events.filter((e) => e.type === "closed");
712
+ expect(errorEvents).toHaveLength(1);
713
+ expect(closedEvents).toHaveLength(1);
714
+
715
+ const err = errorEvents[0] as {
716
+ type: "error";
717
+ category: string;
718
+ message: string;
719
+ };
720
+ expect(err.category).toBe("provider-error");
721
+ expect(err.message).toContain("1006");
722
+ });
723
+
724
+ test("auth error close code maps to auth category", async () => {
725
+ const { events } = await startSession();
726
+
727
+ mockWs.simulateClose(1008, "policy violation");
728
+
729
+ const errorEvents = events.filter((e) => e.type === "error");
730
+ expect(errorEvents).toHaveLength(1);
731
+ const err = errorEvents[0] as { type: "error"; category: string };
732
+ expect(err.category).toBe("auth");
733
+ });
734
+
735
+ test("rate limit close code 1013 maps to rate-limit category", async () => {
736
+ const { events } = await startSession();
737
+
738
+ mockWs.simulateClose(1013, "try again later");
739
+
740
+ const errorEvents = events.filter((e) => e.type === "error");
741
+ expect(errorEvents).toHaveLength(1);
742
+ const err = errorEvents[0] as { type: "error"; category: string };
743
+ expect(err.category).toBe("rate-limit");
744
+ });
745
+
746
+ test("WebSocket error event emits error + closed events", async () => {
747
+ const { events } = await startSession();
748
+
749
+ mockWs.simulateError(new Error("network failure"));
750
+
751
+ const errorEvents = events.filter((e) => e.type === "error");
752
+ const closedEvents = events.filter((e) => e.type === "closed");
753
+ expect(errorEvents).toHaveLength(1);
754
+ expect(closedEvents).toHaveLength(1);
755
+
756
+ const err = errorEvents[0] as {
757
+ type: "error";
758
+ category: string;
759
+ message: string;
760
+ };
761
+ expect(err.category).toBe("provider-error");
762
+ expect(err.message).toContain("network failure");
763
+ });
764
+
765
+ // ─────────────────────────────────────────────────────────────────
766
+ // Inactivity timeout
767
+ // ─────────────────────────────────────────────────────────────────
768
+
769
+ test("inactivity timeout emits error + closed events", async () => {
770
+ const { events } = await startSession({
771
+ inactivityTimeoutMs: 50, // very short for testing
772
+ });
773
+
774
+ // Wait for the inactivity timeout to fire.
775
+ await new Promise((resolve) => setTimeout(resolve, 100));
776
+
777
+ const errorEvents = events.filter((e) => e.type === "error");
778
+ const closedEvents = events.filter((e) => e.type === "closed");
779
+ expect(errorEvents).toHaveLength(1);
780
+ expect(closedEvents).toHaveLength(1);
781
+
782
+ const err = errorEvents[0] as {
783
+ type: "error";
784
+ category: string;
785
+ message: string;
786
+ };
787
+ expect(err.category).toBe("timeout");
788
+ expect(err.message).toContain("inactivity");
789
+ });
790
+
791
+ test("inactivity timer resets on incoming messages", async () => {
792
+ const { events } = await startSession({
793
+ inactivityTimeoutMs: 100,
794
+ });
795
+
796
+ // Send a message before the timeout fires — should reset the timer.
797
+ await new Promise((resolve) => setTimeout(resolve, 60));
798
+ mockWs.simulateMessage(resultsFrame("hello", { is_final: false }));
799
+
800
+ // Wait another period — less than a full timeout from the last message.
801
+ await new Promise((resolve) => setTimeout(resolve, 60));
802
+
803
+ // Should not have timed out yet (timer was reset by the message).
804
+ const errorEvents = events.filter((e) => e.type === "error");
805
+ expect(errorEvents).toHaveLength(0);
806
+ });
807
+
808
+ // ─────────────────────────────────────────────────────────────────
809
+ // WebSocket URL construction
810
+ // ─────────────────────────────────────────────────────────────────
811
+
812
+ test("builds correct WebSocket URL with default params", async () => {
813
+ let capturedUrl: string | undefined;
814
+ let capturedOptions: { headers?: Record<string, string> } | undefined;
815
+ const origWs = (globalThis as Record<string, unknown>).WebSocket;
816
+ (globalThis as Record<string, unknown>).WebSocket = class {
817
+ constructor(url: string, options?: { headers?: Record<string, string> }) {
818
+ capturedUrl = url;
819
+ capturedOptions = options;
820
+ return mockWs;
821
+ }
822
+ };
823
+
824
+ const transcriber = new DeepgramRealtimeTranscriber(TEST_API_KEY);
825
+ const { onEvent } = createEventCollector();
826
+ const startPromise = transcriber.start(onEvent);
827
+ mockWs.simulateOpen();
828
+ await startPromise;
829
+
830
+ expect(capturedUrl).toBeDefined();
831
+ const url = new URL(capturedUrl!);
832
+ expect(url.protocol).toBe("wss:");
833
+ expect(url.hostname).toBe("api.deepgram.com");
834
+ expect(url.pathname).toBe("/v1/listen");
835
+ expect(url.searchParams.get("model")).toBe("nova-2");
836
+ expect(url.searchParams.get("token")).toBeNull();
837
+ expect(url.searchParams.get("smart_format")).toBe("true");
838
+ expect(url.searchParams.get("interim_results")).toBe("true");
839
+ expect(url.searchParams.get("punctuate")).toBe("true");
840
+ expect(url.searchParams.get("encoding")).toBe("linear16");
841
+ expect(url.searchParams.get("sample_rate")).toBe("16000");
842
+ expect(url.searchParams.get("channels")).toBe("1");
843
+ expect(capturedOptions?.headers?.Authorization).toBe(
844
+ `Token ${TEST_API_KEY}`,
845
+ );
846
+
847
+ (globalThis as Record<string, unknown>).WebSocket = origWs;
848
+ });
849
+
850
+ test("includes language param when specified", async () => {
851
+ let capturedUrl: string | undefined;
852
+ const origWs = (globalThis as Record<string, unknown>).WebSocket;
853
+ (globalThis as Record<string, unknown>).WebSocket = class {
854
+ constructor(url: string) {
855
+ capturedUrl = url;
856
+ return mockWs;
857
+ }
858
+ };
859
+
860
+ const transcriber = new DeepgramRealtimeTranscriber(TEST_API_KEY, {
861
+ language: "es",
862
+ });
863
+ const { onEvent } = createEventCollector();
864
+ const startPromise = transcriber.start(onEvent);
865
+ mockWs.simulateOpen();
866
+ await startPromise;
867
+
868
+ const url = new URL(capturedUrl!);
869
+ expect(url.searchParams.get("language")).toBe("es");
870
+
871
+ (globalThis as Record<string, unknown>).WebSocket = origWs;
872
+ });
873
+
874
+ test("includes utterance_end_ms when specified", async () => {
875
+ let capturedUrl: string | undefined;
876
+ const origWs = (globalThis as Record<string, unknown>).WebSocket;
877
+ (globalThis as Record<string, unknown>).WebSocket = class {
878
+ constructor(url: string) {
879
+ capturedUrl = url;
880
+ return mockWs;
881
+ }
882
+ };
883
+
884
+ const transcriber = new DeepgramRealtimeTranscriber(TEST_API_KEY, {
885
+ utteranceEndMs: 1000,
886
+ });
887
+ const { onEvent } = createEventCollector();
888
+ const startPromise = transcriber.start(onEvent);
889
+ mockWs.simulateOpen();
890
+ await startPromise;
891
+
892
+ const url = new URL(capturedUrl!);
893
+ expect(url.searchParams.get("utterance_end_ms")).toBe("1000");
894
+
895
+ (globalThis as Record<string, unknown>).WebSocket = origWs;
896
+ });
897
+
898
+ test("uses custom base URL when specified", async () => {
899
+ let capturedUrl: string | undefined;
900
+ const origWs = (globalThis as Record<string, unknown>).WebSocket;
901
+ (globalThis as Record<string, unknown>).WebSocket = class {
902
+ constructor(url: string) {
903
+ capturedUrl = url;
904
+ return mockWs;
905
+ }
906
+ };
907
+
908
+ const transcriber = new DeepgramRealtimeTranscriber(TEST_API_KEY, {
909
+ baseUrl: "wss://custom-deepgram.example.com/",
910
+ });
911
+ const { onEvent } = createEventCollector();
912
+ const startPromise = transcriber.start(onEvent);
913
+ mockWs.simulateOpen();
914
+ await startPromise;
915
+
916
+ expect(capturedUrl).toContain(
917
+ "wss://custom-deepgram.example.com/v1/listen",
918
+ );
919
+
920
+ (globalThis as Record<string, unknown>).WebSocket = origWs;
921
+ });
922
+
923
+ // Top-level `speaker` on the alternative is a separate Deepgram response
924
+ // shape that some API versions use when a chunk is dominated by one voice.
925
+ // The word-level path is covered in the Diarization section above; this
926
+ // test guarantees we pick up the shorter form as well.
927
+ test("emits speakerLabel from top-level alternative.speaker when diarize is enabled", async () => {
928
+ const { events } = await startSession({ diarize: true });
929
+
930
+ const frame = JSON.stringify({
931
+ type: "Results",
932
+ is_final: true,
933
+ channel: {
934
+ alternatives: [{ transcript: "hi", confidence: 0.9, speaker: 2 }],
935
+ },
936
+ });
937
+ mockWs.simulateMessage(frame);
938
+
939
+ expect(events).toHaveLength(1);
940
+ const event = events[0] as {
941
+ type: string;
942
+ text: string;
943
+ speakerLabel?: string;
944
+ confidence?: number;
945
+ };
946
+ expect(event.type).toBe("final");
947
+ expect(event.text).toBe("hi");
948
+ expect(event.speakerLabel).toBe("2");
949
+ expect(event.confidence).toBe(0.9);
950
+ });
951
+
952
+ // ─────────────────────────────────────────────────────────────────
953
+ // Provider identity
954
+ // ─────────────────────────────────────────────────────────────────
955
+
956
+ test("reports correct providerId and boundaryId", () => {
957
+ const transcriber = new DeepgramRealtimeTranscriber(TEST_API_KEY);
958
+ expect(transcriber.providerId).toBe("deepgram");
959
+ expect(transcriber.boundaryId).toBe("daemon-streaming");
960
+ });
961
+
962
+ // ─────────────────────────────────────────────────────────────────
963
+ // No session leak after close
964
+ // ─────────────────────────────────────────────────────────────────
965
+
966
+ test("no events emitted after closed event", async () => {
967
+ const { events } = await startSession();
968
+
969
+ // Force an error close.
970
+ mockWs.simulateError(new Error("boom"));
971
+
972
+ const countAfterClose = events.length;
973
+
974
+ // Try sending more messages — should be ignored.
975
+ mockWs.simulateMessage(resultsFrame("late", { is_final: true }));
976
+ mockWs.simulateClose(1000, "");
977
+
978
+ expect(events.length).toBe(countAfterClose);
979
+ });
980
+ });