@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
@@ -485,6 +485,7 @@ function winnerConfigFragment(entry: ProfileEntry): Mergeable {
485
485
  description: _description,
486
486
  status: _status,
487
487
  mix: _mix,
488
+ fallbackProfile: _fallbackProfile,
488
489
  ...config
489
490
  } = entry;
490
491
  return config as Mergeable;
@@ -642,6 +643,30 @@ export function resolveEffectiveProfileKey(
642
643
  return selectWinningProfile(callSite, llm, opts).profileName ?? undefined;
643
644
  }
644
645
 
646
+ /**
647
+ * Returns the winning profile key only when it identifies a single provider
648
+ * route: one profile whose own provider/model fragment serves every call it
649
+ * wins. A mix winner returns `undefined` on purpose. Its arm is picked per
650
+ * conversation from a seed (or `Math.random()` unseeded), so a caller
651
+ * recomputing after the fact cannot know which arm actually ran, and arms can
652
+ * span providers, so the outer mix key would put one label on what are
653
+ * several provider routes. Use this where attribution must not claim a
654
+ * broader scope than the route that ran (e.g. failure-notification dedupe);
655
+ * `undefined` means "no single route can be named", and callers fall back to
656
+ * their narrower key.
657
+ */
658
+ export function resolveSingleRouteProfileKey(
659
+ callSite: LLMCallSite,
660
+ llm: z.infer<typeof LLMSchema>,
661
+ opts: ResolveCallSiteOpts = {},
662
+ ): string | undefined {
663
+ const name = selectWinningProfile(callSite, llm, opts).profileName;
664
+ if (name == null) {
665
+ return undefined;
666
+ }
667
+ return providerAwareEntry(llm, name)?.mix == null ? name : undefined;
668
+ }
669
+
645
670
  /**
646
671
  * Stable non-null identity for profileless configs. Callers should prefer real
647
672
  * profile keys first; when no named profile applies, the resolved model id is
@@ -179,6 +179,9 @@ export function getDeploymentContextDefaults(): Record<string, unknown> {
179
179
  "todoist-oauth": managed,
180
180
  "discord-oauth": managed,
181
181
  "hubspot-oauth": managed,
182
+ "monday-oauth": managed,
183
+ "eventbrite-oauth": managed,
184
+ "calendly-oauth": managed,
182
185
  },
183
186
  };
184
187
  }
@@ -18,8 +18,23 @@
18
18
 
19
19
  import { ttsSecretResolves } from "../calls/telephony-tts-capability.js";
20
20
  import { managedSpeechAvailable } from "../platform/managed-speech.js";
21
- import { getProviderEntry } from "../providers/speech-to-text/provider-catalog.js";
21
+ import { fluxModelForLanguage } from "../providers/speech-to-text/deepgram-flux-frames.js";
22
+ import {
23
+ baseModelFamilyFor,
24
+ getProviderEntry,
25
+ isManagedSttProvider,
26
+ resolveSttCatalogKey,
27
+ sttConfigForCatalogKey,
28
+ supportsProviderTurnDetection,
29
+ } from "../providers/speech-to-text/provider-catalog.js";
22
30
  import { sttProviderKeyResolves } from "../providers/speech-to-text/resolve.js";
31
+ import {
32
+ STT_ROLES,
33
+ sttCatalogKeyForRole,
34
+ type SttRole,
35
+ sttRoleCapabilityGap,
36
+ sttSelectionForRole,
37
+ } from "../stt/roles.js";
23
38
  import type { SttProviderId } from "../stt/types.js";
24
39
  import { getCatalogProvider } from "../tts/provider-catalog.js";
25
40
  import type { TtsProviderId } from "../tts/types.js";
@@ -60,6 +75,31 @@ async function ttsByokCredentialsResolve(provider: string): Promise<boolean> {
60
75
  return true;
61
76
  }
62
77
 
78
+ /**
79
+ * The managed provider that stands in for a configured one whose credential
80
+ * did not resolve.
81
+ *
82
+ * Managed speech has two STT entries, and they are not interchangeable:
83
+ * `vellum-flux` decides end-of-turn itself, `vellum` leaves it to the
84
+ * session's silence timer. Substituting the plain one for a provider that had
85
+ * provider turn detection would keep live voice working while quietly
86
+ * changing how it takes turns, which reads as "Flux is no better" rather than
87
+ * as a missing credential. Match the capability instead.
88
+ */
89
+ function managedStandInFor(
90
+ configured: SttProviderId,
91
+ language: string | undefined,
92
+ ): SttProviderId {
93
+ if (!supportsProviderTurnDetection(configured)) {
94
+ return "vellum";
95
+ }
96
+ // Flux has a model for ten languages. Outside them the relay rejects the
97
+ // dial outright, so standing in with it would hand the speaker a mic that
98
+ // does not work at all rather than one that detects turns less well. The
99
+ // language is the one the resolver will actually send.
100
+ return fluxModelForLanguage(language) === null ? "vellum" : "vellum-flux";
101
+ }
102
+
63
103
  /** The speech providers a runtime path uses, after managed-speech defaulting. */
64
104
  export interface EffectiveSpeechProviders {
65
105
  stt: SttProviderId;
@@ -70,19 +110,23 @@ export interface EffectiveSpeechProviders {
70
110
  * Resolve the speech providers the runtime actually uses.
71
111
  *
72
112
  * A configured service whose BYOK credential does not resolve is reported as
73
- * `"vellum"` while managed speech is available; every other service keeps its
74
- * configured provider. Read-only callers that hold no `settings.write`
113
+ * its managed stand-in while managed speech is available (see
114
+ * {@link managedStandInFor}, which preserves provider turn detection); every
115
+ * other service keeps its configured provider. Read-only: callers that hold no `settings.write`
75
116
  * scope (the live-voice WebSocket transport) resolve the same verdict the
76
117
  * preflight route does without persisting anything.
77
118
  *
78
119
  * `config` selects the configuration to read the configured providers from,
79
- * for callers already holding one (defaults to the loaded config).
120
+ * for callers already holding one (defaults to the loaded config). `role`
121
+ * selects which consumer's STT override applies; omitting it reads the global
122
+ * provider.
80
123
  */
81
124
  export async function resolveEffectiveSpeechProviders(
82
125
  config?: AssistantConfig,
126
+ options: { role?: SttRole } = {},
83
127
  ): Promise<EffectiveSpeechProviders> {
84
128
  const services = (config ?? getConfig()).services;
85
- const configuredStt = services.stt.provider as SttProviderId;
129
+ const configuredStt = sttCatalogKeyForRole(services.stt, options.role);
86
130
  const configuredTts = services.tts.provider as TtsProviderId;
87
131
 
88
132
  if (!(await managedSpeechAvailable())) {
@@ -90,9 +134,9 @@ export async function resolveEffectiveSpeechProviders(
90
134
  }
91
135
 
92
136
  const stt =
93
- configuredStt !== "vellum" &&
137
+ !isManagedSttProvider(configuredStt) &&
94
138
  !(await sttByokCredentialResolves(configuredStt))
95
- ? "vellum"
139
+ ? managedStandInFor(configuredStt, services.stt.language)
96
140
  : configuredStt;
97
141
 
98
142
  const tts =
@@ -101,7 +145,85 @@ export async function resolveEffectiveSpeechProviders(
101
145
  ? "vellum"
102
146
  : configuredTts;
103
147
 
104
- return { stt, tts };
148
+ return {
149
+ stt: managedLiveVoiceModelFamily(stt, options.role, services.stt),
150
+ tts,
151
+ };
152
+ }
153
+
154
+ /**
155
+ * Managed live voice runs Flux.
156
+ *
157
+ * Turn detection is the reason to reach for Flux at all, and managed users
158
+ * cannot ask for it: the provider picker offers no model family, so `vellum`
159
+ * is as specific as they can be. Deciding it here rather than persisting a
160
+ * `services.stt.roles.liveVoice` entry keeps managed opinionated without
161
+ * writing config on anyone's behalf, and reaches installs already on `vellum`
162
+ * that a substitution rule never sees.
163
+ *
164
+ * Two things still win over it. A family the user named is honoured, because
165
+ * `services.stt.providers.vellum.model` accepts `nova-3` and quietly ignoring
166
+ * a valid setting is the silent substitution roles exist to prevent. And a
167
+ * language outside Flux's roster stays on nova-3, since the relay refuses the
168
+ * dial rather than degrading (see `managedStandInFor`).
169
+ *
170
+ * Live voice only: Flux streams and nothing else, so every other consumer
171
+ * would lose its transcriber.
172
+ */
173
+ function managedLiveVoiceModelFamily(
174
+ resolved: SttProviderId,
175
+ role: SttRole | undefined,
176
+ stt: AssistantConfig["services"]["stt"],
177
+ ): SttProviderId {
178
+ if (role !== "liveVoice" || resolved !== "vellum") {
179
+ return resolved;
180
+ }
181
+ if (sttSelectionForRole(stt, role).model !== undefined) {
182
+ return resolved;
183
+ }
184
+ return fluxModelForLanguage(stt.language) === null ? "vellum" : "vellum-flux";
185
+ }
186
+
187
+ /**
188
+ * Whether a provider can serve every consumer, or only the one that chose it.
189
+ *
190
+ * `vellum` covers every boundary, so substituting it is safe everywhere. A
191
+ * narrowing row like `vellum-flux` streams and nothing else, and the roles it
192
+ * fails are exactly the consumers that must not be moved onto it.
193
+ */
194
+ function servesEveryRole(provider: SttProviderId): boolean {
195
+ const selection = sttConfigForCatalogKey(provider);
196
+ return STT_ROLES.every(
197
+ (role) => sttRoleCapabilityGap(role, selection) === null,
198
+ );
199
+ }
200
+
201
+ /**
202
+ * The writes that put `selection` in force as the global provider.
203
+ *
204
+ * The family is always written, including the base one. Substituting away
205
+ * from a variant leaves its `model` behind otherwise, and the next load
206
+ * resolves straight back to the variant this substitution just rejected: the
207
+ * provider would read as plain vellum while running Flux, with batch and
208
+ * telephony quietly unavailable. A provider with no families has no name to
209
+ * write, and no variant to have left a stale value behind either.
210
+ */
211
+ function globalSttUpdates(selection: {
212
+ provider: string;
213
+ model?: string | undefined;
214
+ }): { path: string; provider: string }[] {
215
+ const updates = [
216
+ { path: "services.stt.provider", provider: selection.provider },
217
+ ];
218
+ const family =
219
+ selection.model ?? baseModelFamilyFor(selection.provider as SttProviderId);
220
+ if (family !== undefined) {
221
+ updates.push({
222
+ path: `services.stt.providers.${selection.provider}.model`,
223
+ provider: family,
224
+ });
225
+ }
226
+ return updates;
105
227
  }
106
228
 
107
229
  /**
@@ -119,8 +241,36 @@ export async function maybeDefaultSpeechToManaged(): Promise<void> {
119
241
  const effective = await resolveEffectiveSpeechProviders();
120
242
 
121
243
  const updates: { path: string; provider: string }[] = [];
122
- if (effective.stt !== services.stt.provider) {
123
- updates.push({ path: "services.stt.provider", provider: effective.stt });
244
+ if (effective.stt !== resolveSttCatalogKey(services.stt)) {
245
+ // A variant row is not a valid services.stt.provider value, so persist
246
+ // the pair that selects it. Writing the key itself would put an id the
247
+ // schema rejects on disk, and an unparseable services block is how the
248
+ // loader's salvage ladder ends up resetting the whole section.
249
+ const standIn = sttConfigForCatalogKey(effective.stt);
250
+ if (servesEveryRole(effective.stt)) {
251
+ updates.push(...globalSttUpdates(standIn));
252
+ } else {
253
+ // A stand-in that cannot serve every consumer belongs to the one that
254
+ // chose it. Live voice is that consumer: provider turn detection is
255
+ // the only reason managedStandInFor reaches for a narrow row, and
256
+ // writing that row globally would take batch transcription and
257
+ // telephony from every other consumer in a single write.
258
+ updates.push({
259
+ path: "services.stt.roles.liveVoice.provider",
260
+ provider: standIn.provider,
261
+ });
262
+ if (standIn.model !== undefined) {
263
+ updates.push({
264
+ path: "services.stt.roles.liveVoice.model",
265
+ provider: standIn.model,
266
+ });
267
+ }
268
+ // The other consumers still need a provider that answers, and the
269
+ // narrow stand-in's own base is the managed row that serves them.
270
+ if (servesEveryRole(standIn.provider as SttProviderId)) {
271
+ updates.push(...globalSttUpdates({ provider: standIn.provider }));
272
+ }
273
+ }
124
274
  }
125
275
  if (effective.tts !== services.tts.provider) {
126
276
  updates.push({ path: "services.tts.provider", provider: effective.tts });
@@ -20,6 +20,7 @@ import { BackupConfigSchema } from "./schemas/backup.js";
20
20
  import { CallsConfigSchema } from "./schemas/calls.js";
21
21
  import {
22
22
  A2AConfigSchema,
23
+ DiscordConfigSchema,
23
24
  SlackConfigSchema,
24
25
  TelegramConfigSchema,
25
26
  TwilioConfigSchema,
@@ -120,6 +121,7 @@ export const AssistantConfigSchema = z.object({
120
121
  whatsapp: WhatsAppConfigSchema.default(WhatsAppConfigSchema.parse({})),
121
122
  telegram: TelegramConfigSchema.default(TelegramConfigSchema.parse({})),
122
123
  slack: SlackConfigSchema.default(SlackConfigSchema.parse({})),
124
+ discord: DiscordConfigSchema.default(DiscordConfigSchema.parse({})),
123
125
  a2a: A2AConfigSchema.default(A2AConfigSchema.parse({})),
124
126
  ingress: IngressConfigSchema,
125
127
  platform: PlatformConfigSchema.default(PlatformConfigSchema.parse({})),
@@ -26,10 +26,11 @@ const FRONT_MODEL_DEFAULTS = {
26
26
  };
27
27
 
28
28
  // `eagerEotThreshold` is deliberately absent: it has no default, and leaving it
29
- // unset is what keeps Deepgram from emitting speculative turn events.
29
+ // unset is what keeps Deepgram from emitting speculative turn events. `model`
30
+ // is absent for a related reason: unset means the spoken language selects the
31
+ // model, and pinning one overrides that.
30
32
  const FLUX_DEFAULTS = {
31
- turnEnd: { enabled: false },
32
- model: "flux-general-en",
33
+ turnEnd: { enabled: true },
33
34
  eotThreshold: 0.7,
34
35
  eotTimeoutMs: 5_000,
35
36
  };
@@ -229,7 +230,7 @@ describe("LiveVoiceFrontModelConfigSchema", () => {
229
230
  });
230
231
 
231
232
  describe("LiveVoiceFluxConfigSchema", () => {
232
- test("empty object parses to defaults, with turn-end off", () => {
233
+ test("empty object parses to defaults, with turn-end on", () => {
233
234
  expect(LiveVoiceFluxConfigSchema.parse({})).toEqual(FLUX_DEFAULTS);
234
235
  });
235
236
 
@@ -257,8 +258,8 @@ describe("LiveVoiceFluxConfigSchema", () => {
257
258
  test("partial overrides merge with defaults", () => {
258
259
  const parsed = LiveVoiceFluxConfigSchema.parse({ eotThreshold: 0.6 });
259
260
  expect(parsed.eotThreshold).toBe(0.6);
260
- expect(parsed.turnEnd.enabled).toBe(false);
261
- expect(parsed.model).toBe("flux-general-en");
261
+ expect(parsed.turnEnd.enabled).toBe(true);
262
+ expect(parsed.model).toBeUndefined();
262
263
  expect(parsed.eotTimeoutMs).toBe(5_000);
263
264
  });
264
265
 
@@ -122,6 +122,100 @@ describe("managed provider", () => {
122
122
  provider: "vellum",
123
123
  language: "multi",
124
124
  providers: {},
125
+ roles: {},
125
126
  });
126
127
  });
127
128
  });
129
+
130
+ describe("services.stt.roles", () => {
131
+ test("defaults to empty so an untouched config is unchanged", () => {
132
+ expect(SttServiceSchema.parse({ provider: "deepgram" }).roles).toEqual({});
133
+ });
134
+
135
+ test("accepts a role whose selection covers what it needs", () => {
136
+ const parsed = SttServiceSchema.parse({
137
+ provider: "deepgram",
138
+ roles: { liveVoice: { provider: "deepgram", model: "flux" } },
139
+ });
140
+ expect(parsed.roles).toEqual({
141
+ liveVoice: { provider: "deepgram", model: "flux" },
142
+ });
143
+ });
144
+
145
+ test("a role can select a different family than the global default", () => {
146
+ // The point of the split: live voice on flux while everything else keeps
147
+ // the batch-capable family.
148
+ const parsed = SttServiceSchema.parse({
149
+ provider: "deepgram",
150
+ roles: { liveVoice: { provider: "deepgram", model: "flux" } },
151
+ });
152
+ expect(parsed.provider).toBe("deepgram");
153
+ expect(parsed.roles.liveVoice?.model).toBe("flux");
154
+ });
155
+
156
+ test("rejects a streaming-only family for a role that batches", () => {
157
+ // The whole point of the split: Flux has no batch endpoint, so it can
158
+ // serve live voice and cannot serve file transcription.
159
+ for (const role of ["batch", "dictation", "telephony"]) {
160
+ const result = SttServiceSchema.safeParse({
161
+ provider: "deepgram",
162
+ roles: { [role]: { provider: "deepgram", model: "flux" } },
163
+ });
164
+ expect(result.success).toBe(false);
165
+ expect(result.error?.issues[0]?.message).toContain(
166
+ `services.stt.roles.${role} cannot be "deepgram" running flux`,
167
+ );
168
+ }
169
+ });
170
+
171
+ test("names the first requirement a provider misses", () => {
172
+ // Telephony needs both boundaries AND call ingestion. Flux is missing a
173
+ // boundary, so that is what the message says: the reported reason is the
174
+ // one the user has to act on, not a generic incompatibility.
175
+ const result = SttServiceSchema.safeParse({
176
+ provider: "deepgram",
177
+ roles: { telephony: { provider: "deepgram", model: "flux" } },
178
+ });
179
+ expect(result.success).toBe(false);
180
+ expect(result.error?.issues[0]?.message).toContain(
181
+ "supports no daemon-batch transcription, which telephony needs",
182
+ );
183
+ });
184
+
185
+ test("rejects an unknown role key", () => {
186
+ expect(
187
+ SttServiceSchema.safeParse({
188
+ provider: "deepgram",
189
+ roles: { transcription: { provider: "deepgram" } },
190
+ }).success,
191
+ ).toBe(false);
192
+ });
193
+
194
+ test("normalizes provider aliases inside a role", () => {
195
+ expect(
196
+ SttServiceSchema.parse({
197
+ provider: "deepgram",
198
+ roles: { batch: { provider: "whisper" } },
199
+ }).roles,
200
+ ).toEqual({ batch: { provider: "openai-whisper" } });
201
+ });
202
+ });
203
+
204
+ describe("services.stt.roles model validation", () => {
205
+ test("rejects a family the role's provider cannot serve", () => {
206
+ const result = SttServiceSchema.safeParse({
207
+ provider: "deepgram",
208
+ roles: { liveVoice: { provider: "openai-whisper", model: "flux" } },
209
+ });
210
+ expect(result.success).toBe(false);
211
+ expect(result.error?.issues[0]?.message).toContain("single model");
212
+ });
213
+
214
+ test("a role with no model runs the provider's default family", () => {
215
+ const parsed = SttServiceSchema.parse({
216
+ provider: "deepgram",
217
+ roles: { batch: { provider: "deepgram" } },
218
+ });
219
+ expect(parsed.roles.batch).toEqual({ provider: "deepgram" });
220
+ });
221
+ });
@@ -29,7 +29,7 @@ export const BackupOffsiteConfigSchema = z
29
29
  .nullable()
30
30
  .default(null)
31
31
  .describe(
32
- "Offsite destinations. null means use the default iCloud Drive destination with encryption on; an explicit array (including []) overrides the default.",
32
+ "Offsite destinations. null uses encrypted iCloud Drive on macOS and no implicit destination on Windows or Linux; an explicit array (including []) overrides the default.",
33
33
  ),
34
34
  })
35
35
  .describe("Offsite backup configuration");
@@ -121,6 +121,38 @@ export const A2AConfigSchema = z
121
121
  })
122
122
  .describe("Agent-to-Agent protocol channel configuration");
123
123
 
124
+ /**
125
+ * Discord channel settings.
126
+ *
127
+ * All three are read back from Discord when a bot token validates and written
128
+ * here so a connected panel can name the bot and build an install link
129
+ * without another round trip. The bot user and the application are separate
130
+ * ids: they coincide today, and a link built on that assumption would break
131
+ * silently if they stopped.
132
+ */
133
+ export const DiscordConfigSchema = z
134
+ .object({
135
+ botUserId: z
136
+ .string({ error: "discord.botUserId must be a string" })
137
+ .default("")
138
+ .describe("Discord bot user ID"),
139
+ botUsername: z
140
+ .string({ error: "discord.botUsername must be a string" })
141
+ .default("")
142
+ .describe("Discord bot display name"),
143
+ applicationId: z
144
+ .string({ error: "discord.applicationId must be a string" })
145
+ .default("")
146
+ .describe("Discord application ID the bot belongs to"),
147
+ inviteUrl: z
148
+ .string({ error: "discord.inviteUrl must be a string" })
149
+ .default("")
150
+ .describe(
151
+ "Install link for the bot's application, computed when a token validates",
152
+ ),
153
+ })
154
+ .describe("Discord channel configuration");
155
+
124
156
  export const SlackConfigSchema = z
125
157
  .object({
126
158
  deliverAuthBypass: z
@@ -219,9 +219,16 @@ const LiveVoiceFluxTurnEndConfigSchema = z
219
219
  .object({
220
220
  enabled: z
221
221
  .boolean({ error: "liveVoice.flux.turnEnd.enabled must be a boolean" })
222
- .default(false)
222
+ // On by default because it is the only reason to select Flux, and
223
+ // because off is not a neutral setting on this provider: managed Flux
224
+ // exposes no `finalizeUtterance` (Flux commits turns itself), so the
225
+ // session's persistence check falls through and it opens a socket per
226
+ // utterance, putting a full Deepgram handshake on every turn. Ignored
227
+ // by any provider that does not own its turn boundary, so the default
228
+ // reaches only the sessions it is about.
229
+ .default(true)
223
230
  .describe(
224
- "Commit the live-voice turn on Flux's EndOfTurn instead of the front-door [0] hold verdict. Requires services.stt.provider to be deepgram-flux; ignored otherwise.",
231
+ "Commit the live-voice turn on Flux's EndOfTurn instead of the front-door [0] hold verdict. Requires the active STT provider to be running the flux model family (services.stt.providers.<provider>.model); ignored otherwise.",
225
232
  ),
226
233
  })
227
234
  .describe(
@@ -235,8 +242,10 @@ export const LiveVoiceFluxConfigSchema = z
235
242
  ),
236
243
  model: z
237
244
  .string({ error: "liveVoice.flux.model must be a string" })
238
- .default("flux-general-en")
239
- .describe("Deepgram Flux model requested when opening the STT stream"),
245
+ .optional()
246
+ .describe(
247
+ "Deepgram Flux model to pin when opening the STT stream. Unset (the default) selects the model from services.stt.language: English and unset use the English model, everything else uses the multilingual one",
248
+ ),
240
249
  eotThreshold: z
241
250
  .number({ error: "liveVoice.flux.eotThreshold must be a number" })
242
251
  .min(0.5, "liveVoice.flux.eotThreshold must be >= 0.5")