@vellumai/assistant 0.11.6 → 0.11.7-staging.2

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 (478) hide show
  1. package/ARCHITECTURE.md +31 -19
  2. package/Dockerfile +1 -0
  3. package/docs/architecture/security.md +3 -3
  4. package/docs/flux-turn-detection-spike.md +8 -7
  5. package/docs/runbook-trusted-contacts.md +14 -0
  6. package/docs/stt-provider-onboarding.md +8 -4
  7. package/knip.json +1 -0
  8. package/node_modules/@vellumai/avatar-manifest/package.json +18 -0
  9. package/node_modules/@vellumai/avatar-manifest/src/__tests__/manifest.test.ts +173 -0
  10. package/node_modules/@vellumai/avatar-manifest/src/__tests__/read.test.ts +114 -0
  11. package/node_modules/@vellumai/avatar-manifest/src/index.ts +29 -0
  12. package/node_modules/@vellumai/avatar-manifest/src/layout.ts +18 -0
  13. package/node_modules/@vellumai/avatar-manifest/src/manifest.ts +156 -0
  14. package/node_modules/@vellumai/avatar-manifest/src/read.ts +109 -0
  15. package/node_modules/@vellumai/avatar-manifest/tsconfig.json +20 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  17. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  18. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  19. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  20. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  21. package/node_modules/@vellumai/ces-client/src/http-credentials.ts +161 -0
  22. package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
  23. package/node_modules/@vellumai/environments/package.json +2 -1
  24. package/node_modules/@vellumai/environments/src/shell.test.ts +56 -0
  25. package/node_modules/@vellumai/environments/src/shell.ts +65 -0
  26. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  27. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  28. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  29. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  30. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  31. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +32 -0
  32. package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -0
  33. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +82 -0
  34. package/node_modules/@vellumai/gateway-client/src/__tests__/plugin-admission-denied-contract.test.ts +56 -0
  35. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +18 -0
  36. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +5 -11
  37. package/node_modules/@vellumai/gateway-client/src/channel-permission-contract.ts +3 -1
  38. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +9 -0
  39. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +4 -0
  40. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +75 -0
  41. package/node_modules/@vellumai/gateway-client/src/index.ts +17 -0
  42. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +8 -0
  43. package/node_modules/@vellumai/gateway-client/src/plugin-admission-denied-contract.ts +47 -0
  44. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +11 -0
  45. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  46. package/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  47. package/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  48. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  49. package/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  50. package/openapi.yaml +419 -55
  51. package/package.json +4 -1
  52. package/scripts/sync-llm-catalog.ts +6 -0
  53. package/src/__tests__/approval-card-edit-inbound-handoff.test.ts +199 -0
  54. package/src/__tests__/approval-card-edit.test.ts +153 -0
  55. package/src/__tests__/attachments-store-heic-normalize.test.ts +1 -1
  56. package/src/__tests__/available-skills.test.ts +54 -0
  57. package/src/__tests__/avatar-identity-sync.test.ts +5 -1
  58. package/src/__tests__/catalog-install-normalize.test.ts +9 -1
  59. package/src/__tests__/channel-policy.test.ts +1 -3
  60. package/src/__tests__/channel-readiness-discord.test.ts +9 -70
  61. package/src/__tests__/compactor-call-site-logging.test.ts +61 -0
  62. package/src/__tests__/config-loader-backfill.test.ts +69 -0
  63. package/src/__tests__/config-schema.test.ts +2 -3
  64. package/src/__tests__/conversation-agent-loop.test.ts +238 -0
  65. package/src/__tests__/conversation-attachments.test.ts +0 -1
  66. package/src/__tests__/conversation-error.test.ts +44 -0
  67. package/src/__tests__/conversation-evictor.test.ts +19 -0
  68. package/src/__tests__/conversation-fork-referential.test.ts +73 -63
  69. package/src/__tests__/conversation-fork-retrospective.test.ts +99 -74
  70. package/src/__tests__/conversation-runtime-assembly.test.ts +23 -0
  71. package/src/__tests__/conversation-slash.test.ts +9 -0
  72. package/src/__tests__/conversation-title-service.test.ts +97 -0
  73. package/src/__tests__/conversation-tool-setup-attribution.test.ts +16 -0
  74. package/src/__tests__/credential-broker-server-use.test.ts +0 -218
  75. package/src/__tests__/credential-broker.test.ts +43 -420
  76. package/src/__tests__/credential-record-write-through.test.ts +78 -0
  77. package/src/__tests__/credential-routes.test.ts +17 -1
  78. package/src/__tests__/credential-security-invariants.test.ts +8 -9
  79. package/src/__tests__/default-profile-catalog-fallback.test.ts +289 -0
  80. package/src/__tests__/delete-propagation.test.ts +92 -2
  81. package/src/__tests__/discord-callback-round-trip.test.ts +93 -0
  82. package/src/__tests__/discord-channel-config.test.ts +237 -0
  83. package/src/__tests__/edit-propagation.test.ts +42 -2
  84. package/src/__tests__/evict-conversations-for-reload.test.ts +51 -1
  85. package/src/__tests__/fallback-breaker.test.ts +1335 -0
  86. package/src/__tests__/gateway-threshold-reader-mock.ts +6 -0
  87. package/src/__tests__/guardian-card-withdrawal.test.ts +30 -0
  88. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -2
  89. package/src/__tests__/image-conversion.test.ts +38 -4
  90. package/src/__tests__/install-skill-routing.test.ts +25 -0
  91. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  92. package/src/__tests__/llm-resolver.test.ts +74 -0
  93. package/src/__tests__/llm-schema-fallback-profile.test.ts +359 -0
  94. package/src/__tests__/managed-fallback-dispatch.test.ts +430 -0
  95. package/src/__tests__/managed-profile-guard.test.ts +28 -0
  96. package/src/__tests__/managed-speech-defaults.test.ts +196 -1
  97. package/src/__tests__/mcp-cli.test.ts +23 -1
  98. package/src/__tests__/mcp-tool-annotations-risk.test.ts +43 -34
  99. package/src/__tests__/media-stream-stt-session.test.ts +2 -0
  100. package/src/__tests__/notification-decision-fallback.test.ts +106 -0
  101. package/src/__tests__/notification-discord-adapter.test.ts +162 -0
  102. package/src/__tests__/oauth-connect-orchestrator.test.ts +95 -0
  103. package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -8
  104. package/src/__tests__/persist-unsendable-image.test.ts +3 -3
  105. package/src/__tests__/platform.test.ts +19 -1
  106. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  107. package/src/__tests__/provider-catalog-visibility.test.ts +17 -0
  108. package/src/__tests__/provider-platform-proxy-integration.test.ts +8 -1
  109. package/src/__tests__/provider-usage-tracking.test.ts +56 -0
  110. package/src/__tests__/require-fresh-approval.test.ts +5 -2
  111. package/src/__tests__/retry-fallback-escalation.test.ts +1093 -0
  112. package/src/__tests__/schedule-retry.test.ts +183 -4
  113. package/src/__tests__/scheduler-reuse-conversation.test.ts +6 -6
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +55 -0
  115. package/src/__tests__/skill-load-tool.test.ts +34 -0
  116. package/src/__tests__/skill-tool-factory.test.ts +6 -1
  117. package/src/__tests__/skill-tool-manifest.test.ts +20 -0
  118. package/src/__tests__/skills.test.ts +25 -0
  119. package/src/__tests__/subagent-manager-notify.test.ts +38 -0
  120. package/src/__tests__/terminal-tools.test.ts +2 -0
  121. package/src/__tests__/tool-approval-handler.test.ts +146 -2
  122. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +108 -2
  123. package/src/__tests__/tool-audit.test.ts +111 -40
  124. package/src/__tests__/tool-executor-lifecycle-events.test.ts +228 -18
  125. package/src/__tests__/ui-shape-teaching.test.ts +1 -1
  126. package/src/__tests__/verification-control-plane-policy.test.ts +6 -2
  127. package/src/__tests__/voice-config-update.test.ts +43 -0
  128. package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +480 -0
  129. package/src/__tests__/workspace-migration-148-strip-unsupported-fallback-profiles.test.ts +167 -0
  130. package/src/__tests__/workspace-migration-149-repoint-backup-profile-selections.test.ts +144 -0
  131. package/src/agent/loop.ts +31 -2
  132. package/src/approvals/guardian-card-withdrawal.ts +22 -0
  133. package/src/avatar/__tests__/ensure-raster.test.ts +291 -0
  134. package/src/avatar/avatar-manifest.ts +48 -100
  135. package/src/avatar/avatar-store.ts +7 -7
  136. package/src/avatar/ensure-raster.ts +146 -0
  137. package/src/avatar/resvg-lazy.test.ts +0 -1
  138. package/src/avatar/traits-png-sync.ts +9 -10
  139. package/src/background-wake/wake-intent-hooks.test.ts +0 -1
  140. package/src/backup/__tests__/paths.test.ts +6 -2
  141. package/src/backup/paths.ts +6 -4
  142. package/src/bundler/app-compiler.ts +2 -2
  143. package/src/bundler/compiler-tools.test.ts +36 -0
  144. package/src/bundler/compiler-tools.ts +54 -18
  145. package/src/calls/__tests__/telephony-synthesis-language.test.ts +81 -0
  146. package/src/calls/__tests__/voice-session-bridge.test.ts +57 -0
  147. package/src/calls/media-stream-stt-session.ts +2 -1
  148. package/src/calls/telephony-synthesis-language.ts +13 -5
  149. package/src/calls/voice-session-bridge.ts +19 -4
  150. package/src/channels/config.ts +23 -12
  151. package/src/cli/AGENTS.md +3 -1
  152. package/src/cli/commands/backup.help.ts +10 -13
  153. package/src/cli/commands/contacts.help.ts +16 -4
  154. package/src/cli/commands/contacts.ts +5 -1
  155. package/src/cli/commands/credentials.help.ts +1 -1
  156. package/src/cli/commands/inference-providers.ts +18 -53
  157. package/src/cli/commands/mcp.help.ts +6 -5
  158. package/src/cli/commands/mcp.ts +1 -1
  159. package/src/cli/commands/memory/memory-retrospective.ts +1 -0
  160. package/src/cli/commands/stt.help.ts +2 -2
  161. package/src/config/__tests__/backup-schema.test.ts +1 -1
  162. package/src/config/__tests__/default-profile-catalog.test.ts +83 -2
  163. package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +1 -1
  164. package/src/config/__tests__/memory-retrospective-schema.test.ts +12 -15
  165. package/src/config/bundled-skills/computer-use/SKILL.md +6 -6
  166. package/src/config/bundled-skills/computer-use/TOOLS.json +9 -6
  167. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +3 -1
  168. package/src/config/bundled-skills/media-processing/services/preprocess.ts +1 -1
  169. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  170. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +16 -15
  171. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -1
  172. package/src/config/bundled-skills/settings/TOOLS.json +11 -5
  173. package/src/config/bundled-skills/settings/tools/open-system-settings.test.ts +85 -0
  174. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +33 -8
  175. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +54 -3
  176. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +1 -1
  177. package/src/config/default-profile-catalog.ts +195 -10
  178. package/src/config/default-profile-names.ts +41 -0
  179. package/src/config/feature-flag-registry.json +34 -2
  180. package/src/config/inference-profile-validation.ts +11 -2
  181. package/src/config/llm-resolver.ts +25 -0
  182. package/src/config/loader.ts +3 -0
  183. package/src/config/managed-speech-defaults.ts +160 -10
  184. package/src/config/schema.ts +2 -0
  185. package/src/config/schemas/__tests__/live-voice.test.ts +7 -6
  186. package/src/config/schemas/__tests__/stt.test.ts +94 -0
  187. package/src/config/schemas/backup.ts +1 -1
  188. package/src/config/schemas/channels.ts +32 -0
  189. package/src/config/schemas/live-voice.ts +13 -4
  190. package/src/config/schemas/llm.ts +263 -15
  191. package/src/config/schemas/mcp.ts +10 -2
  192. package/src/config/schemas/memory-retrospective.ts +1 -19
  193. package/src/config/schemas/services.ts +24 -0
  194. package/src/config/schemas/stt.ts +128 -5
  195. package/src/config/seed-inference-profiles.ts +32 -3
  196. package/src/config/skill-state.ts +5 -0
  197. package/src/config/skills.ts +21 -0
  198. package/src/contacts/notify-contacts-changed.ts +5 -2
  199. package/src/context/compactor.ts +74 -1
  200. package/src/daemon/__tests__/conversation-tool-setup.test.ts +138 -1
  201. package/src/daemon/config-watcher.ts +2 -1
  202. package/src/daemon/conversation-agent-loop-handlers.ts +27 -0
  203. package/src/daemon/conversation-agent-loop.ts +86 -10
  204. package/src/daemon/conversation-error.ts +25 -0
  205. package/src/daemon/conversation-evictor.ts +2 -4
  206. package/src/daemon/conversation-runtime-assembly.ts +19 -7
  207. package/src/daemon/conversation-skill-tools.ts +5 -1
  208. package/src/daemon/conversation-slash.ts +2 -2
  209. package/src/daemon/conversation-store.ts +8 -1
  210. package/src/daemon/conversation-tool-setup.ts +37 -0
  211. package/src/daemon/conversation.ts +0 -4
  212. package/src/daemon/handlers/channel-config-result.ts +30 -0
  213. package/src/daemon/handlers/config-discord-channel.ts +301 -0
  214. package/src/daemon/handlers/config-ingress.ts +9 -0
  215. package/src/daemon/handlers/config-slack-channel.ts +13 -15
  216. package/src/daemon/handlers/config-telegram.ts +2 -5
  217. package/src/daemon/handlers/conversations.ts +0 -2
  218. package/src/daemon/handlers/skills.ts +25 -7
  219. package/src/daemon/lifecycle.ts +3 -1
  220. package/src/daemon/message-types/messages.ts +0 -2
  221. package/src/daemon/tool-setup-types.ts +5 -3
  222. package/src/daemon/trust-context-types.test.ts +33 -0
  223. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  224. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +58 -4
  225. package/src/live-voice/__tests__/live-voice-managed-speech-fallback.test.ts +8 -3
  226. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +73 -11
  227. package/src/live-voice/__tests__/live-voice-session-preflight.test.ts +148 -2
  228. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +64 -0
  229. package/src/live-voice/__tests__/live-voice-stt.test.ts +3 -0
  230. package/src/live-voice/__tests__/live-voice-text-turn.test.ts +391 -0
  231. package/src/live-voice/__tests__/protocol.test.ts +224 -0
  232. package/src/live-voice/live-voice-credential-preflight.ts +5 -1
  233. package/src/live-voice/live-voice-session-manager.ts +39 -2
  234. package/src/live-voice/live-voice-session.ts +224 -9
  235. package/src/live-voice/protocol.ts +187 -1
  236. package/src/messaging/providers/discord/api.ts +2 -1
  237. package/src/messaging/providers/discord/withdraw.ts +62 -0
  238. package/src/messaging/read-provider-metadata.ts +32 -0
  239. package/src/monitoring/__tests__/file-descriptors.test.ts +32 -0
  240. package/src/monitoring/__tests__/process-memory.test.ts +34 -1
  241. package/src/monitoring/db-integrity-sample.ts +1 -0
  242. package/src/monitoring/file-descriptors.ts +39 -6
  243. package/src/monitoring/process-memory.ts +37 -5
  244. package/src/monitoring/resource-sampler.ts +25 -8
  245. package/src/notifications/__tests__/activity-failed-dedupe.test.ts +161 -0
  246. package/src/notifications/__tests__/background-failure-signal.test.ts +107 -0
  247. package/src/notifications/__tests__/connected-channels.test.ts +12 -0
  248. package/src/notifications/__tests__/copy-composer.test.ts +83 -0
  249. package/src/notifications/__tests__/destination-resolver.test.ts +38 -0
  250. package/src/notifications/activity-failed-dedupe.ts +95 -0
  251. package/src/notifications/adapters/discord.ts +132 -0
  252. package/src/notifications/adapters/shared.ts +16 -0
  253. package/src/notifications/adapters/slack.ts +6 -1
  254. package/src/notifications/adapters/telegram.ts +5 -8
  255. package/src/notifications/approval-card-data.ts +94 -1
  256. package/src/notifications/background-failure-signal.ts +116 -0
  257. package/src/notifications/broadcaster.ts +3 -9
  258. package/src/notifications/copy-composer.ts +41 -8
  259. package/src/notifications/decision-engine.ts +55 -0
  260. package/src/notifications/destination-resolver.ts +45 -5
  261. package/src/notifications/emit-signal.ts +19 -5
  262. package/src/notifications/guardian-question-mode.ts +57 -0
  263. package/src/notifications/types.ts +2 -8
  264. package/src/oauth/__tests__/seed-providers-managed.test.ts +18 -0
  265. package/src/oauth/connect-orchestrator.ts +16 -0
  266. package/src/oauth/seed-providers.ts +262 -0
  267. package/src/permissions/checker.test.ts +21 -0
  268. package/src/permissions/checker.ts +6 -0
  269. package/src/permissions/gateway-threshold-reader.test.ts +184 -0
  270. package/src/permissions/gateway-threshold-reader.ts +144 -10
  271. package/src/permissions/prompter.ts +0 -6
  272. package/src/permissions/types.ts +5 -4
  273. package/src/persistence/__tests__/bulk-write-gate.test.ts +0 -38
  274. package/src/persistence/bulk-write-gate.ts +9 -10
  275. package/src/persistence/conversation-crud.ts +88 -134
  276. package/src/persistence/conversation-title-service.ts +43 -29
  277. package/src/persistence/embeddings/embedding-local.ts +14 -85
  278. package/src/persistence/embeddings/embedding-runtime-manager.ts +1 -0
  279. package/src/persistence/lifecycle-events-store.test.ts +50 -0
  280. package/src/persistence/lifecycle-events-store.ts +75 -5
  281. package/src/platform/platform-patch-queue.test.ts +368 -0
  282. package/src/platform/platform-patch-queue.ts +190 -0
  283. package/src/platform/sync-avatar.test.ts +527 -0
  284. package/src/platform/sync-avatar.ts +209 -0
  285. package/src/platform/sync-identity.test.ts +75 -0
  286. package/src/platform/sync-identity.ts +17 -110
  287. package/src/plugin-api/model-profiles.test.ts +2 -2
  288. package/src/plugin-api/model-profiles.ts +7 -7
  289. package/src/plugin-api/transcription-session.ts +1 -1
  290. package/src/plugins/defaults/compaction/window-manager.ts +4 -0
  291. package/src/plugins/defaults/image-recovery/recover.ts +2 -2
  292. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +133 -34
  293. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +21 -31
  294. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +13 -9
  295. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +31 -4
  296. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +0 -11
  297. package/src/plugins/defaults/memory/memory-retrospective-fork-boundary.ts +27 -13
  298. package/src/plugins/defaults/memory/memory-retrospective-job.ts +183 -113
  299. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +13 -13
  300. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +2 -2
  301. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +1 -1
  302. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +4 -2
  303. package/src/plugins/defaults/memory/substrate/sweep-job.ts +9 -42
  304. package/src/plugins/defaults/memory/v2/rerank-local.ts +1 -0
  305. package/src/plugins/defaults/memory/worker.ts +7 -0
  306. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  307. package/src/providers/__tests__/retry-callsite.test.ts +55 -0
  308. package/src/providers/__tests__/vellum-connection-routing.test.ts +21 -3
  309. package/src/providers/connection-resolution.ts +2 -17
  310. package/src/providers/fallback-breaker.ts +389 -0
  311. package/src/providers/inference/__tests__/adapter-factory-opencode.test.ts +92 -0
  312. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +16 -0
  313. package/src/providers/inference/__tests__/base-url-security.test.ts +12 -0
  314. package/src/providers/inference/adapter-factory.ts +246 -3
  315. package/src/providers/inference/auth.ts +15 -14
  316. package/src/providers/inference/chatgpt-subscription-auth.ts +70 -0
  317. package/src/providers/inference/chatgpt-subscription-credentials.ts +84 -0
  318. package/src/providers/inference/resolve-auth.ts +1 -1
  319. package/src/providers/media-resolve.ts +2 -3
  320. package/src/providers/model-catalog.ts +100 -0
  321. package/src/providers/openai/__tests__/api-error-normalization.test.ts +22 -0
  322. package/src/providers/openai/__tests__/chat-template-flatten.test.ts +198 -0
  323. package/src/providers/openai/api-error-normalization.ts +13 -0
  324. package/src/providers/openai/chat-completions-provider.ts +84 -0
  325. package/src/providers/opencode/client.test.ts +150 -0
  326. package/src/providers/opencode/client.ts +61 -0
  327. package/src/providers/platform-proxy/constants.ts +5 -0
  328. package/src/providers/provider-resolvability.ts +20 -0
  329. package/src/providers/provider-secret-catalog.ts +3 -2
  330. package/src/providers/retry.ts +956 -68
  331. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +42 -0
  332. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +136 -0
  333. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +120 -0
  334. package/src/providers/speech-to-text/__tests__/resolve.test.ts +316 -12
  335. package/src/providers/speech-to-text/deepgram-flux-frames.ts +83 -3
  336. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +132 -4
  337. package/src/providers/speech-to-text/provider-catalog.ts +191 -3
  338. package/src/providers/speech-to-text/resolve.ts +132 -24
  339. package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +296 -0
  340. package/src/providers/types.ts +39 -0
  341. package/src/providers/usage-tracking.ts +12 -1
  342. package/src/providers/vellum/client.ts +29 -0
  343. package/src/providers/vellum-model-routing.test.ts +2 -0
  344. package/src/providers/vellum-model-routing.ts +3 -3
  345. package/src/runtime/AGENTS.md +1 -2
  346. package/src/runtime/__tests__/agent-wake.test.ts +105 -3
  347. package/src/runtime/__tests__/background-job-runner.test.ts +89 -3
  348. package/src/runtime/agent-wake.ts +44 -5
  349. package/src/runtime/background-job-runner.ts +68 -50
  350. package/src/runtime/channel-readiness-service.ts +0 -39
  351. package/src/runtime/http-server.ts +12 -4
  352. package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +18 -18
  353. package/src/runtime/routes/__tests__/backup-routes.test.ts +9 -5
  354. package/src/runtime/routes/__tests__/chatgpt-subscription-auth-routes.test.ts +372 -0
  355. package/src/runtime/routes/__tests__/contact-routes.test.ts +7 -0
  356. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +96 -0
  357. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +37 -0
  358. package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +2 -1
  359. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +87 -0
  360. package/src/runtime/routes/acp-claude-auth-routes.ts +25 -54
  361. package/src/runtime/routes/approval-routes.ts +0 -12
  362. package/src/runtime/routes/avatar-routes.ts +17 -30
  363. package/src/runtime/routes/backup-routes.ts +2 -2
  364. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +296 -113
  365. package/src/runtime/routes/contact-prompt-routes.ts +65 -6
  366. package/src/runtime/routes/contact-routes.ts +20 -15
  367. package/src/runtime/routes/conversation-query-routes.ts +98 -10
  368. package/src/runtime/routes/conversation-routes.ts +2 -2
  369. package/src/runtime/routes/credential-routes.ts +2 -1
  370. package/src/runtime/routes/guardian-approval-interception.ts +12 -9
  371. package/src/runtime/routes/inbound-message-handler.ts +47 -48
  372. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +18 -13
  373. package/src/runtime/routes/inbound-stages/admission-policy.ts +2 -2
  374. package/src/runtime/routes/inbound-stages/edit-intercept.ts +20 -2
  375. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +9 -8
  376. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -1
  377. package/src/runtime/routes/index.ts +2 -0
  378. package/src/runtime/routes/inference-profile-session-handler.ts +2 -2
  379. package/src/runtime/routes/inference-profiles-routes.ts +12 -7
  380. package/src/runtime/routes/inference-provider-connection-routes.ts +2 -2
  381. package/src/runtime/routes/inference-send-routes.ts +2 -2
  382. package/src/runtime/routes/ingress-status-routes.ts +2 -2
  383. package/src/runtime/routes/integrations/discord.ts +119 -0
  384. package/src/runtime/routes/llm-call-sites-routes.ts +2 -2
  385. package/src/runtime/routes/mcp-auth-routes.ts +12 -6
  386. package/src/runtime/routes/migration-routes.ts +3 -1
  387. package/src/runtime/routes/oauth-pending-flows.ts +136 -0
  388. package/src/runtime/routes/secret-routes.ts +7 -0
  389. package/src/runtime/routes/stt-routes.ts +38 -6
  390. package/src/runtime/routes/wake-conversation-routes.ts +8 -1
  391. package/src/runtime/routes/watch-routes.ts +9 -5
  392. package/src/runtime/sync/resource-sync-events.ts +2 -0
  393. package/src/runtime/trust-verdict-consumer.ts +3 -3
  394. package/src/schedule/__tests__/worker-feature-flags.test.ts +58 -0
  395. package/src/schedule/retry-policy.ts +8 -8
  396. package/src/schedule/run-script.ts +14 -23
  397. package/src/schedule/schedule-recovery.ts +0 -1
  398. package/src/schedule/scheduler.ts +89 -51
  399. package/src/schedule/worker.ts +57 -0
  400. package/src/security/__tests__/openai-device-auth.test.ts +355 -0
  401. package/src/security/ces-rpc-record-backend.ts +123 -0
  402. package/src/security/oauth2.ts +7 -0
  403. package/src/security/openai-device-auth.ts +364 -0
  404. package/src/security/secure-keys.ts +15 -0
  405. package/src/skills/available-skills.ts +10 -1
  406. package/src/skills/catalog-install.ts +19 -1
  407. package/src/skills/inline-command-runner.ts +6 -2
  408. package/src/skills/platform-compatibility.test.ts +49 -0
  409. package/src/skills/platform-compatibility.ts +65 -0
  410. package/src/skills/tool-manifest.ts +20 -0
  411. package/src/stt/__tests__/roles.test.ts +152 -0
  412. package/src/stt/daemon-batch-transcriber.ts +4 -1
  413. package/src/stt/roles.ts +180 -0
  414. package/src/stt/stt-stream-session.ts +3 -2
  415. package/src/stt/types.ts +33 -3
  416. package/src/subagent/manager.ts +12 -0
  417. package/src/telemetry/__tests__/live-voice-funnel.test.ts +34 -0
  418. package/src/telemetry/live-voice-funnel.ts +12 -2
  419. package/src/telemetry/telemetry-wire.generated.ts +6 -0
  420. package/src/telemetry/tool-audit.ts +83 -30
  421. package/src/telemetry/tool-executed-events-store.test.ts +0 -1
  422. package/src/telemetry/turn-outcome.ts +75 -4
  423. package/src/tools/client-os.ts +115 -0
  424. package/src/tools/computer-use/definitions.ts +11 -7
  425. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -2
  426. package/src/tools/credentials/broker-types.ts +0 -71
  427. package/src/tools/credentials/broker.ts +6 -201
  428. package/src/tools/credentials/metadata-store.ts +65 -16
  429. package/src/tools/credentials/store.ts +2 -0
  430. package/src/tools/executor.ts +2 -8
  431. package/src/tools/host-shell.test.ts +75 -0
  432. package/src/tools/host-shell.ts +24 -0
  433. package/src/tools/host-terminal/host-shell.ts +15 -44
  434. package/src/tools/mcp/mcp-tool-factory.ts +44 -9
  435. package/src/tools/permission-checker.ts +73 -21
  436. package/src/tools/policy-context.ts +1 -0
  437. package/src/tools/shared/filesystem/image-read.ts +1 -1
  438. package/src/tools/skills/find-similar-skills.ts +10 -2
  439. package/src/tools/skills/load.ts +41 -6
  440. package/src/tools/skills/sandbox-runner.ts +8 -16
  441. package/src/tools/skills/skill-tool-factory.ts +1 -0
  442. package/src/tools/terminal/__tests__/safe-env.test.ts +64 -1
  443. package/src/tools/terminal/safe-env.ts +39 -9
  444. package/src/tools/terminal/shell.ts +12 -31
  445. package/src/tools/tool-approval-handler.ts +64 -10
  446. package/src/tools/tool-types.ts +10 -0
  447. package/src/tools/types.ts +19 -3
  448. package/src/tools/ui-surface/surface-shape-docs.ts +4 -3
  449. package/src/tools/workflows/manage-workflows.ts +2 -2
  450. package/src/tools/workflows/run-workflow.test.ts +2 -1
  451. package/src/util/__tests__/file-use.test.ts +61 -0
  452. package/src/util/__tests__/process-table.test.ts +66 -0
  453. package/src/util/__tests__/process-tree.test.ts +46 -0
  454. package/src/util/errors.ts +1 -0
  455. package/src/util/file-use.ts +91 -0
  456. package/src/util/host-process.test.ts +17 -0
  457. package/src/util/host-process.ts +75 -0
  458. package/src/util/image-conversion.ts +39 -7
  459. package/src/util/platform.ts +32 -13
  460. package/src/util/process-table.ts +249 -0
  461. package/src/util/process-tree.ts +85 -129
  462. package/src/util/provider-error-patterns.ts +20 -0
  463. package/src/util/spawn.ts +9 -2
  464. package/src/watch/__tests__/watch-retro.test.ts +48 -1
  465. package/src/watch/watch-retro.ts +46 -4
  466. package/src/workspace/git-service.ts +4 -14
  467. package/src/workspace/migrations/147-rename-colliding-backup-profile-names.ts +360 -0
  468. package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +117 -0
  469. package/src/workspace/migrations/149-repoint-backup-profile-selections.ts +204 -0
  470. package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +139 -0
  471. package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +160 -0
  472. package/src/workspace/migrations/registry.ts +8 -0
  473. package/src/__tests__/permission-types.test.ts +0 -17
  474. package/src/channels/gateway-discord-admission.ts +0 -32
  475. package/src/persistence/fork-message-copy.ts +0 -239
  476. package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +0 -221
  477. package/src/security/__tests__/oauth2-device-code.test.ts +0 -431
  478. package/src/security/oauth2-device-code.ts +0 -312
@@ -13,11 +13,13 @@
13
13
  import type {
14
14
  ConversationStreamingMode,
15
15
  SttBoundaryId,
16
+ SttModelFamily,
16
17
  SttProviderId,
17
18
  SttTurnDetectionMode,
18
19
  TelephonySttMode,
19
20
  } from "../../stt/types.js";
20
21
  import { baseLanguageSubtag } from "../../util/language-subtag.js";
22
+ import { FLUX_MULTILINGUAL_SUBTAGS } from "./deepgram-flux-frames.js";
21
23
 
22
24
  // ---------------------------------------------------------------------------
23
25
  // Client display metadata
@@ -122,6 +124,23 @@ interface SttProviderEntry {
122
124
 
123
125
  /** Guide for obtaining API credentials from this provider. */
124
126
  readonly credentialsGuide?: SttCredentialsGuide;
127
+
128
+ /**
129
+ * Set when this row is a model family of another provider rather than a
130
+ * provider in its own right: the id a user selects, plus the
131
+ * `services.stt.providers.<id>.model` value that reaches this row.
132
+ *
133
+ * Rows carrying this are not separately selectable. They share the parent's
134
+ * credential and appear nowhere in a provider picker.
135
+ */
136
+ readonly variantOf?: SttProviderId;
137
+ readonly variantModel?: SttModelFamily;
138
+
139
+ /**
140
+ * The family this row runs when no `model` is set. Present only on rows that
141
+ * have variants, so the default is nameable in config and in errors.
142
+ */
143
+ readonly baseModelFamily?: SttModelFamily;
125
144
  }
126
145
 
127
146
  // ---------------------------------------------------------------------------
@@ -156,6 +175,7 @@ const CATALOG: ReadonlyMap<SttProviderId, SttProviderEntry> = new Map<
156
175
  "deepgram",
157
176
  {
158
177
  id: "deepgram",
178
+ baseModelFamily: "nova-3",
159
179
  displayName: "Deepgram",
160
180
  subtitle:
161
181
  "Fast, real-time speech-to-text with streaming support. Requires a Deepgram API key.",
@@ -178,6 +198,8 @@ const CATALOG: ReadonlyMap<SttProviderId, SttProviderEntry> = new Map<
178
198
  "deepgram-flux",
179
199
  {
180
200
  id: "deepgram-flux",
201
+ variantOf: "deepgram",
202
+ variantModel: "flux",
181
203
  displayName: "Deepgram Flux",
182
204
  subtitle:
183
205
  "Conversational speech-to-text with model-native turn detection. Uses your Deepgram API key.",
@@ -198,9 +220,11 @@ const CATALOG: ReadonlyMap<SttProviderId, SttProviderEntry> = new Map<
198
220
  // them, so a live-voice session may let them commit the turn.
199
221
  turnDetection: "provider",
200
222
  supportsDiarization: false,
201
- // "no picker", not native detection: the Flux model is English-only and
202
- // takes no language parameter, so audio in another language transcribes
203
- // as English rather than being detected.
223
+ // "no picker", not native detection: this entry pins the English Flux
224
+ // model and sends no language parameter, so audio in another language
225
+ // transcribes as English. Flux itself has a multilingual model; the
226
+ // managed `vellum-flux` entry reaches it, and a BYOK picker would need
227
+ // the model to follow the selection.
204
228
  languageSelection: "auto",
205
229
  credentialsGuide: DEEPGRAM_CREDENTIALS_GUIDE,
206
230
  },
@@ -264,6 +288,7 @@ const CATALOG: ReadonlyMap<SttProviderId, SttProviderEntry> = new Map<
264
288
  "vellum",
265
289
  {
266
290
  id: "vellum",
291
+ baseModelFamily: "nova-3",
267
292
  displayName: "Vellum",
268
293
  subtitle:
269
294
  "Speech-to-text through your Vellum account — billed to Vellum credits, no separate API key needed.",
@@ -283,6 +308,39 @@ const CATALOG: ReadonlyMap<SttProviderId, SttProviderEntry> = new Map<
283
308
  languageSelection: "manual",
284
309
  },
285
310
  ],
311
+ [
312
+ "vellum-flux",
313
+ {
314
+ id: "vellum-flux",
315
+ variantOf: "vellum",
316
+ variantModel: "flux",
317
+ displayName: "Vellum (Flux)",
318
+ subtitle:
319
+ "Conversational speech-to-text with model-native turn detection, through your Vellum account. No separate API key needed.",
320
+ setupMode: "connection",
321
+ setupHint: "Connect your Vellum account to enable managed transcription.",
322
+ // Shared with `vellum`: the platform connection is the credential, and
323
+ // Flux is a different endpoint on the same relay, not a second account.
324
+ credentialProvider: "vellum",
325
+ // Streaming only, same as the BYOK Flux entry: Flux has no batch
326
+ // endpoint, and the relay exposes none.
327
+ supportedBoundaries: new Set<SttBoundaryId>(["daemon-streaming"]),
328
+ // The relay maps `Finalize` onto Flux's `CloseStream`, which ends the
329
+ // session rather than flushing it, and telephony's utterance-boundary
330
+ // finals are a nova-3 concept. Calls stay on `vellum`.
331
+ telephonyMode: "none",
332
+ conversationStreamingMode: "realtime-ws",
333
+ // The relay is dialed with `contract=flux`, so Flux's turn lifecycle
334
+ // events arrive intact and a live-voice session may let them commit
335
+ // the turn. Without that opt-in the relay translates them away and
336
+ // this would be false.
337
+ turnDetection: "provider",
338
+ supportsDiarization: false,
339
+ // The relay selects `flux-general-en` or `flux-general-multi` from the
340
+ // language it is sent, so unlike BYOK Flux the picker is meaningful.
341
+ languageSelection: "manual",
342
+ },
343
+ ],
286
344
  [
287
345
  "xai",
288
346
  {
@@ -440,6 +498,136 @@ export function supportsDiarization(id: SttProviderId): boolean {
440
498
  * provider, so a new turn-detecting provider is a catalog entry rather than a
441
499
  * session change.
442
500
  */
501
+ /**
502
+ * Whether a provider authenticates with the Vellum platform connection rather
503
+ * than a stored API key.
504
+ *
505
+ * Derived from the catalog rather than a hand-kept id list: every check that
506
+ * gates managed speech reads this, so adding a managed provider cannot leave
507
+ * one of them behind on a stale literal.
508
+ */
509
+ export function isManagedSttProvider(id: SttProviderId): boolean {
510
+ return CATALOG.get(id)?.credentialProvider === "vellum";
511
+ }
512
+
513
+ /**
514
+ * The catalog row a configured provider plus model family resolves to.
515
+ *
516
+ * Falls back to the provider's own row when it offers no matching variant, so
517
+ * a model setting a provider does not implement is inert rather than fatal.
518
+ * The parameter is structural to keep the catalog free of config imports.
519
+ */
520
+ export function resolveSttCatalogKey(stt: {
521
+ readonly provider: string;
522
+ readonly providers?:
523
+ | Record<string, { readonly model?: unknown } | undefined>
524
+ | undefined;
525
+ }): SttProviderId {
526
+ const provider = stt.provider as SttProviderId;
527
+ return sttCatalogKeyFor(provider, stt.providers?.[provider]?.model);
528
+ }
529
+
530
+ /**
531
+ * The catalog row a provider and model family pair resolves to.
532
+ *
533
+ * Split out from {@link resolveSttCatalogKey} because a selection does not
534
+ * always come from the top-level config block: a per-consumer role names its
535
+ * own provider and family, and has to reach the same row that pair would
536
+ * reach anywhere else.
537
+ */
538
+ export function sttCatalogKeyFor(
539
+ provider: SttProviderId,
540
+ model: unknown,
541
+ ): SttProviderId {
542
+ if (typeof model !== "string") {
543
+ return provider;
544
+ }
545
+ for (const entry of CATALOG.values()) {
546
+ if (entry.variantOf === provider && entry.variantModel === model) {
547
+ return entry.id;
548
+ }
549
+ }
550
+ return provider;
551
+ }
552
+
553
+ /**
554
+ * The model families a provider offers, as `services.stt.providers.<id>.model`
555
+ * values. A provider with a single family returns an empty list, and setting
556
+ * `model` on it is a configuration error rather than a silent no-op.
557
+ */
558
+ export function listProviderModelFamilies(
559
+ id: SttProviderId,
560
+ ): readonly SttModelFamily[] {
561
+ const base = CATALOG.get(id);
562
+ if (!base || base.variantOf !== undefined) {
563
+ return [];
564
+ }
565
+ const families = [...CATALOG.values()]
566
+ .filter((entry) => entry.variantOf === id && entry.variantModel)
567
+ .map((entry) => entry.variantModel as SttModelFamily);
568
+ return families.length > 0
569
+ ? [base.baseModelFamily ?? "nova-3", ...families]
570
+ : [];
571
+ }
572
+
573
+ /**
574
+ * The config that selects a catalog row: the provider id a user sets, plus
575
+ * the model family when the row is a variant.
576
+ *
577
+ * The inverse of {@link resolveSttCatalogKey}. Anything persisting a resolved
578
+ * provider has to write these two fields rather than the key, which is not a
579
+ * valid `services.stt.provider` value for a variant row.
580
+ */
581
+ export function sttConfigForCatalogKey(id: SttProviderId): {
582
+ provider: SttProviderId;
583
+ model?: SttModelFamily;
584
+ } {
585
+ const entry = CATALOG.get(id);
586
+ if (!entry?.variantOf || !entry.variantModel) {
587
+ return { provider: id };
588
+ }
589
+ return { provider: entry.variantOf, model: entry.variantModel };
590
+ }
591
+
592
+ /**
593
+ * The family a provider runs when no `model` is set, for callers that must
594
+ * write an explicit value rather than leave a stale one in place. Providers
595
+ * with a single family have no name to write and return undefined.
596
+ */
597
+ /**
598
+ * The spoken languages a provider's turn-detecting model family serves, or
599
+ * undefined when it has no such family.
600
+ *
601
+ * A family that owns the turn boundary does not necessarily cover the
602
+ * provider's whole language roster, and dialing it outside that set fails
603
+ * rather than degrades. Reported from here so the answer has one home.
604
+ */
605
+ export function turnDetectionLanguagesFor(
606
+ id: SttProviderId,
607
+ ): readonly string[] | undefined {
608
+ const variant = [...CATALOG.values()].find(
609
+ (entry) => entry.variantOf === id && entry.turnDetection === "provider",
610
+ );
611
+ return variant ? [...FLUX_MULTILINGUAL_SUBTAGS].sort() : undefined;
612
+ }
613
+
614
+ export function baseModelFamilyFor(
615
+ id: SttProviderId,
616
+ ): SttModelFamily | undefined {
617
+ const entry = CATALOG.get(id);
618
+ return entry?.variantOf === undefined ? entry?.baseModelFamily : undefined;
619
+ }
620
+
621
+ /**
622
+ * Provider ids a user may select. Excludes model-family rows, which are
623
+ * reached through `services.stt.providers.<id>.model` instead.
624
+ */
625
+ export function listSelectableProviderIds(): readonly SttProviderId[] {
626
+ return [...CATALOG.values()]
627
+ .filter((entry) => entry.variantOf === undefined)
628
+ .map((entry) => entry.id);
629
+ }
630
+
443
631
  export function supportsProviderTurnDetection(id: SttProviderId): boolean {
444
632
  return CATALOG.get(id)?.turnDetection === "provider";
445
633
  }
@@ -1,6 +1,7 @@
1
1
  import { getConfig } from "../../config/loader.js";
2
2
  import { getProviderKeyAsync } from "../../security/secure-keys.js";
3
3
  import { createDaemonBatchTranscriber } from "../../stt/daemon-batch-transcriber.js";
4
+ import { sttCatalogKeyForRole, type SttRole } from "../../stt/roles.js";
4
5
  import type {
5
6
  BatchTranscriber,
6
7
  StreamingTranscriber,
@@ -12,6 +13,8 @@ import {
12
13
  batchBoundaryGapReason,
13
14
  getCredentialProvider,
14
15
  getProviderEntry,
16
+ isManagedSttProvider,
17
+ resolveSttCatalogKey,
15
18
  supportsBoundary,
16
19
  supportsDiarization,
17
20
  } from "./provider-catalog.js";
@@ -35,6 +38,10 @@ const log = getLogger("stt-resolver");
35
38
  const MULTILINGUAL_DEFAULT_PROVIDERS: ReadonlySet<SttProviderId> = new Set([
36
39
  "deepgram",
37
40
  "vellum",
41
+ // Managed Flux resolves an absent language to `flux-general-en` relay-side,
42
+ // so unset is an English pin here too. With "multi" the relay selects
43
+ // `flux-general-multi` and lets Flux detect and code-switch.
44
+ "vellum-flux",
38
45
  ] as SttProviderId[]);
39
46
 
40
47
  /**
@@ -76,6 +83,29 @@ export function effectiveSttLanguage(
76
83
  : undefined;
77
84
  }
78
85
 
86
+ /**
87
+ * Record which provider a role actually dialed, when that differs from the
88
+ * global setting.
89
+ *
90
+ * `assistant config get` reports what was set, not what resolved, and managed
91
+ * defaulting already substitutes silently. A role override adds a second way
92
+ * for the two to diverge, so the divergence is logged rather than left to be
93
+ * inferred from a provider's behaviour.
94
+ */
95
+ function logRoleSelection(
96
+ role: SttRole | undefined,
97
+ resolved: SttProviderId,
98
+ base: SttProviderId,
99
+ ): void {
100
+ if (role === undefined || resolved === base) {
101
+ return;
102
+ }
103
+ log.info(
104
+ { role, providerId: resolved, baseProviderId: base },
105
+ "STT role override selected a provider other than services.stt.provider",
106
+ );
107
+ }
108
+
79
109
  // ---------------------------------------------------------------------------
80
110
  // Batch transcriber resolver (existing public API — unchanged contract)
81
111
  // ---------------------------------------------------------------------------
@@ -98,11 +128,14 @@ export function effectiveSttLanguage(
98
128
  * "no speech-to-text provider is configured" copy every caller pairs with
99
129
  * `null`, which is the opposite of what happened.
100
130
  */
101
- export async function resolveBatchTranscriber(): Promise<BatchTranscriber | null> {
131
+ export async function resolveBatchTranscriber(
132
+ options: { role?: SttRole } = {},
133
+ ): Promise<BatchTranscriber | null> {
102
134
  // Snapshot the stt config once, before any await, so a concurrent config
103
135
  // change cannot pair one setting's old value with another's new value.
104
136
  const stt = getConfig().services.stt;
105
- const provider = stt.provider;
137
+ const provider = sttCatalogKeyForRole(stt, options.role);
138
+ logRoleSelection(options.role, provider, resolveSttCatalogKey(stt));
106
139
  const language = effectiveSttLanguage(
107
140
  provider as SttProviderId,
108
141
  stt.language,
@@ -177,22 +210,27 @@ export type TelephonySttCapability =
177
210
  };
178
211
 
179
212
  /**
180
- * Validate whether the configured `services.stt` provider is eligible for
181
- * future real-time telephony call ingestion.
213
+ * Validate whether the STT provider serving telephony is eligible for
214
+ * real-time call ingestion.
182
215
  *
183
- * This resolver does **not** create a live transcriber — it only validates
216
+ * This resolver does **not** create a live transcriber: it only validates
184
217
  * that the configuration, catalog entry, and credentials are all in order.
185
- * The actual wiring is deferred to a future media-stream call adapter PR.
218
+ *
219
+ * The provider is the telephony role's, which is the one a call actually
220
+ * dials. Reading the global provider instead would answer about a provider
221
+ * no call uses: a credentialed role selection would be turned away for the
222
+ * global's missing key, and the inverse would pass preflight and fail at the
223
+ * dial.
186
224
  *
187
225
  * Callers can branch on the discriminated `status` field:
188
- * - `"supported"` — the provider is telephony-eligible and credentials exist.
189
- * - `"unsupported"` — the provider exists but has `telephonyMode: "none"`.
190
- * - `"unconfigured"` — the provider is unknown or missing from the catalog.
191
- * - `"missing-credentials"` — the provider is eligible but has no API key.
226
+ * - `"supported"`: the provider is telephony-eligible and credentials exist.
227
+ * - `"unsupported"`: the provider exists but has `telephonyMode: "none"`.
228
+ * - `"unconfigured"`: the provider is unknown or missing from the catalog.
229
+ * - `"missing-credentials"`: the provider is eligible but has no API key.
192
230
  */
193
231
  export async function resolveTelephonySttCapability(): Promise<TelephonySttCapability> {
194
232
  const config = getConfig();
195
- const provider = config.services.stt.provider;
233
+ const provider = sttCatalogKeyForRole(config.services.stt, "telephony");
196
234
 
197
235
  const entry = getProviderEntry(provider as SttProviderId);
198
236
  if (!entry) {
@@ -281,7 +319,7 @@ export type ConversationStreamingSttCapability =
281
319
  */
282
320
  export async function resolveConversationStreamingSttCapability(): Promise<ConversationStreamingSttCapability> {
283
321
  const config = getConfig();
284
- const provider = config.services.stt.provider;
322
+ const provider = resolveSttCatalogKey(config.services.stt);
285
323
 
286
324
  const entry = getProviderEntry(provider as SttProviderId);
287
325
  if (!entry) {
@@ -347,6 +385,13 @@ export interface ResolveStreamingTranscriberOptions {
347
385
  * readiness check approved.
348
386
  */
349
387
  providerId?: SttProviderId;
388
+ /**
389
+ * Consumer asking for the transcriber. Selects that role's
390
+ * `services.stt.roles` override; omitted falls back to the global
391
+ * `services.stt.provider`. Ignored when `providerId` is given, since that
392
+ * caller already resolved the provider itself.
393
+ */
394
+ role?: SttRole;
350
395
  /**
351
396
  * Speaker diarization preference. Default: `"off"`.
352
397
  *
@@ -409,7 +454,11 @@ export async function resolveStreamingTranscriber(
409
454
  // change cannot pair one setting's old value with another's new value
410
455
  // (e.g. the old provider with the new language).
411
456
  const stt = getConfig().services.stt;
412
- const provider = options.providerId ?? (stt.provider as SttProviderId);
457
+ const provider =
458
+ options.providerId ?? sttCatalogKeyForRole(stt, options.role);
459
+ if (options.providerId === undefined) {
460
+ logRoleSelection(options.role, provider, resolveSttCatalogKey(stt));
461
+ }
413
462
  // Config-level language applies to every streaming caller (live voice,
414
463
  // dictation, telephony) unless one overrides it for a single session, so
415
464
  // the setting lands in one place rather than at each call site. An unset
@@ -466,11 +515,14 @@ export async function resolveStreamingTranscriber(
466
515
  (diarizePreference === "preferred" || diarizePreference === "required") &&
467
516
  providerSupportsDiarization;
468
517
 
469
- const apiKey =
470
- provider === "vellum"
471
- ? null
472
- : await getProviderKeyAsync(credentialProviderName);
473
- if (provider === "vellum") {
518
+ // Both managed entries authenticate with the platform connection, not a
519
+ // stored API key: there is no key to fetch, and readiness is whether the
520
+ // connection resolves.
521
+ const managed = isManagedSttProvider(provider);
522
+ const apiKey = managed
523
+ ? null
524
+ : await getProviderKeyAsync(credentialProviderName);
525
+ if (managed) {
474
526
  if (!(await sttProviderKeyResolves("vellum"))) {
475
527
  return null;
476
528
  }
@@ -641,18 +693,74 @@ async function createStreamingTranscriber(
641
693
  : {}),
642
694
  });
643
695
  }
696
+ case "vellum-flux": {
697
+ // Managed Flux: same relay as `vellum`, but the STT v2 endpoint with
698
+ // `contract=flux` so the turn events arrive intact instead of being
699
+ // translated into the released v1 Deepgram dialect. Gated on the local
700
+ // platform connection for the same reason as `vellum` above.
701
+ const { vellumManagedSpeechAvailable } =
702
+ await import("./vellum-managed.js");
703
+ if (!(await vellumManagedSpeechAvailable())) {
704
+ return null;
705
+ }
706
+ // The relay maps the language onto a Flux model and refuses the dial
707
+ // when there is none, so a language outside Flux's roster fails as a
708
+ // relay param error the caller cannot explain. Refuse here instead, on
709
+ // the same rule the BYOK twin above applies, so the reason is named
710
+ // where it can be read.
711
+ const { fluxModelForLanguage: managedFluxModelForLanguage } =
712
+ await import("./deepgram-flux-frames.js");
713
+ if (managedFluxModelForLanguage(options.language) === null) {
714
+ log.warn(
715
+ { providerId, language: options.language },
716
+ "Managed Flux has no model for the configured language; refusing rather than transcribing it as another",
717
+ );
718
+ return null;
719
+ }
720
+ const { resolveSpeechRelayConnection } =
721
+ await import("./vellum-speech-relay-connection.js");
722
+ const connection = await resolveSpeechRelayConnection();
723
+ if (!connection) {
724
+ return null;
725
+ }
726
+ const { VellumManagedFluxRealtimeTranscriber } =
727
+ await import("./vellum-managed-flux-realtime.js");
728
+ return new VellumManagedFluxRealtimeTranscriber(connection, {
729
+ sampleRate: options.sampleRate,
730
+ // The relay picks `flux-general-en` or `flux-general-multi` from
731
+ // this, so it is the only lever over the Flux model from here.
732
+ // `utteranceBoundaryFinals` is a nova-3 concept and the catalog
733
+ // keeps this provider off telephony, so it never arrives.
734
+ ...(options.language ? { language: options.language } : {}),
735
+ });
736
+ }
644
737
  case "deepgram-flux": {
645
738
  // Flux is streaming-only and dials Deepgram's /v2/listen conversational
646
- // endpoint. Turn-detection tuning comes from `liveVoice.flux`, which
647
- // the adapter reads itself, so only the transport-level sample rate is
648
- // passed here. No language is forwarded: the spike pins the
649
- // English-only `flux-general-en`, and `language_hint` means nothing to
650
- // a monolingual model. Diarization is off in the catalog, so a
651
- // `"required"` caller never reaches this case.
739
+ // endpoint. Turn-detection tuning comes from `liveVoice.flux`, which the
740
+ // adapter reads itself. The language picks the model, so a language Flux
741
+ // has no model for resolves to nothing rather than being transcribed by
742
+ // the English one, which returns fluent-looking nonsense the transcript
743
+ // gives no sign of. Diarization is off in the catalog, so a `"required"`
744
+ // caller never reaches this case.
745
+ const { fluxModelForLanguage } =
746
+ await import("./deepgram-flux-frames.js");
747
+ // A pinned model is the operator saying which one to run, so the
748
+ // language check only guards the derived case.
749
+ if (
750
+ getConfig().liveVoice.flux.model === undefined &&
751
+ fluxModelForLanguage(options.language) === null
752
+ ) {
753
+ log.warn(
754
+ { providerId, language: options.language },
755
+ "Deepgram Flux has no model for the configured language; refusing rather than transcribing it as another",
756
+ );
757
+ return null;
758
+ }
652
759
  const { DeepgramFluxRealtimeTranscriber } =
653
760
  await import("./deepgram-flux-realtime.js");
654
761
  return new DeepgramFluxRealtimeTranscriber(apiKey, {
655
762
  sampleRate: options.sampleRate,
763
+ ...(options.language ? { language: options.language } : {}),
656
764
  });
657
765
  }
658
766
  default: {