@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
@@ -54,7 +54,10 @@ let requestedSttProviders: (string | undefined)[];
54
54
 
55
55
  /** A provider yields a streaming transcriber when its credential resolves. */
56
56
  function transcriberFor(providerId: string | undefined): boolean {
57
- return providerId === "vellum"
57
+ // Both managed rows ride the same platform connection, so neither has a
58
+ // credential of its own. Live voice asks for the flux row specifically,
59
+ // since managed live voice resolves to it when no family is named.
60
+ return providerId === "vellum" || providerId === "vellum-flux"
58
61
  ? managedAvailable
59
62
  : providerId !== undefined && providerKeys[providerId] !== undefined;
60
63
  }
@@ -218,8 +221,10 @@ describe("live voice on managed speech", () => {
218
221
  expect(readiness).toEqual({ status: "ready" });
219
222
 
220
223
  // The transcriber the session arms is asked for the same provider the
221
- // readiness verdict was earned on.
222
- expect(requestedSttProviders).toEqual(["vellum"]);
224
+ // readiness verdict was earned on. Live voice resolves the flux row while
225
+ // the roleless verdict above stays on the base one, which is the whole
226
+ // point of resolving per role.
227
+ expect(requestedSttProviders).toEqual(["vellum-flux"]);
223
228
  expect(readConfigFile()).toBe(configBefore);
224
229
  });
225
230
 
@@ -122,24 +122,86 @@ describe("LiveVoiceSessionManager", () => {
122
122
  const rejected = await manager.startSession(START_FRAME, second.sink);
123
123
 
124
124
  expect(accepted).toEqual({ status: "accepted", sessionId: "session-1" });
125
+ const busyFrame = {
126
+ type: "busy",
127
+ seq: 1,
128
+ activeSessionId: "session-1",
129
+ // Names where the blocking session is, so the refused client has
130
+ // somewhere to point the user.
131
+ holder: { conversationId: "conversation-123" },
132
+ } as const satisfies LiveVoiceServerFrame;
125
133
  expect(rejected).toEqual({
126
134
  status: "busy",
127
135
  activeSessionId: "session-1",
136
+ frame: busyFrame,
137
+ });
138
+ expect(second.frames).toEqual([busyFrame]);
139
+ expect(sessions).toHaveLength(1);
140
+ expect(sessions[0]?.start).toHaveBeenCalledTimes(1);
141
+ });
142
+
143
+ test("names the holder's device and the conversation it landed in", async () => {
144
+ const sessions: TestSession[] = [];
145
+ const manager = new LiveVoiceSessionManager({
146
+ createSessionId: mock(() => `session-${sessions.length + 1}`),
147
+ createSession: (context) => {
148
+ const session = createTestSession({
149
+ start: mock(async () => {
150
+ await context.sendFrame({
151
+ type: "ready",
152
+ sessionId: context.sessionId,
153
+ // The session minted its own conversation, so the start frame's
154
+ // is not the one to send anybody to.
155
+ conversationId: "conversation-minted",
156
+ });
157
+ }),
158
+ });
159
+ sessions.push(session);
160
+ return session;
161
+ },
162
+ });
163
+ const second = createSink();
164
+
165
+ await manager.startSession(
166
+ { ...START_FRAME, client: "ios" },
167
+ createSink().sink,
168
+ );
169
+ const rejected = await manager.startSession(START_FRAME, second.sink);
170
+
171
+ expect(rejected).toMatchObject({
172
+ status: "busy",
128
173
  frame: {
129
- type: "busy",
130
- seq: 1,
131
- activeSessionId: "session-1",
174
+ holder: { client: "ios", conversationId: "conversation-minted" },
132
175
  },
133
176
  });
134
- expect(second.frames).toEqual([
135
- {
136
- type: "busy",
137
- seq: 1,
138
- activeSessionId: "session-1",
177
+ });
178
+
179
+ test("says nothing about a holder that revealed nothing", async () => {
180
+ const sessions: TestSession[] = [];
181
+ const manager = new LiveVoiceSessionManager({
182
+ createSessionId: mock(() => `session-${sessions.length + 1}`),
183
+ createSession: () => {
184
+ const session = createTestSession();
185
+ sessions.push(session);
186
+ return session;
139
187
  },
140
- ]);
141
- expect(sessions).toHaveLength(1);
142
- expect(sessions[0]?.start).toHaveBeenCalledTimes(1);
188
+ });
189
+ const anonymous = {
190
+ type: "start",
191
+ audio: START_FRAME.audio,
192
+ } as const satisfies LiveVoiceClientStartFrame;
193
+ const second = createSink();
194
+
195
+ await manager.startSession(anonymous, createSink().sink);
196
+ const rejected = await manager.startSession(anonymous, second.sink);
197
+
198
+ // An empty holder would read as "we know where it is" while naming
199
+ // nothing, so the field is dropped and the client uses its vaguer copy.
200
+ expect(rejected).toEqual({
201
+ status: "busy",
202
+ activeSessionId: "session-1",
203
+ frame: { type: "busy", seq: 1, activeSessionId: "session-1" },
204
+ });
143
205
  });
144
206
 
145
207
  test("releases the active session once for repeated close events", async () => {
@@ -7,6 +7,10 @@
7
7
  * the start frame with a `credentials_unavailable` error frame carrying
8
8
  * the preflight's user message, before any transcriber is resolved, and
9
9
  * leaves the session manager free for a retry.
10
+ *
11
+ * They also pin the one exception to that rule: a client that declared
12
+ * `textInput` opens text-only when the *only* thing missing is the
13
+ * speech-to-text leg, because a session it can type into beats no session.
10
14
  */
11
15
 
12
16
  import { describe, expect, mock, test } from "bun:test";
@@ -35,6 +39,24 @@ const START_FRAME = {
35
39
  },
36
40
  } as const satisfies LiveVoiceClientStartFrame;
37
41
 
42
+ const START_FRAME_TEXT_INPUT = {
43
+ ...START_FRAME,
44
+ textInput: true,
45
+ } as const satisfies LiveVoiceClientStartFrame;
46
+
47
+ const STT_GAP = {
48
+ kind: "stt",
49
+ providerId: "deepgram",
50
+ reason: 'STT provider "deepgram" is missing credentials (Deepgram API Key)',
51
+ } as const;
52
+
53
+ const NOT_READY_STT_ONLY: LiveVoiceCredentialReadiness = {
54
+ status: "not-ready",
55
+ missing: [STT_GAP],
56
+ userMessage:
57
+ 'Live voice is unavailable because it requires an API key for the speech-to-text provider "deepgram" (Deepgram API Key).',
58
+ };
59
+
38
60
  const NOT_READY: LiveVoiceCredentialReadiness = {
39
61
  status: "not-ready",
40
62
  missing: [
@@ -60,7 +82,7 @@ class MockStreamingTranscriber implements StreamingTranscriber {
60
82
  stop(): void {}
61
83
  }
62
84
 
63
- function createContext(): {
85
+ function createContext(startFrame: LiveVoiceClientStartFrame = START_FRAME): {
64
86
  context: LiveVoiceSessionFactoryContext;
65
87
  frames: LiveVoiceServerFrame[];
66
88
  } {
@@ -71,7 +93,7 @@ function createContext(): {
71
93
  frames,
72
94
  context: {
73
95
  sessionId: "session-123",
74
- startFrame: START_FRAME,
96
+ startFrame,
75
97
  sendFrame: mock(async (payload) => {
76
98
  const frame = sequencer.next(payload);
77
99
  frames.push(frame);
@@ -128,6 +150,130 @@ describe("live-voice session credential preflight gating", () => {
128
150
  await session.close("websocket_close");
129
151
  });
130
152
 
153
+ test("ready preflight leaves audioInput off the ready frame", async () => {
154
+ // Absent means "the microphone leg is live", which is what every session
155
+ // that passes the preflight has. Only a downgraded session says otherwise.
156
+ const { context, frames } = createContext();
157
+ const session = new LiveVoiceSession(context, {
158
+ resolveTranscriber: mock(async () => new MockStreamingTranscriber()),
159
+ resolveCredentialReadiness: mock(
160
+ async (): Promise<LiveVoiceCredentialReadiness> => ({
161
+ status: "ready",
162
+ }),
163
+ ),
164
+ });
165
+
166
+ await session.start();
167
+
168
+ expect(frames[0]).toMatchObject({ type: "ready", textInput: true });
169
+ expect("audioInput" in (frames[0] ?? {})).toBe(false);
170
+
171
+ await session.close("websocket_close");
172
+ });
173
+
174
+ test("a text-input client opens text-only when only the STT leg is missing", async () => {
175
+ const { context, frames } = createContext(START_FRAME_TEXT_INPUT);
176
+ const resolveTranscriber = mock(async () => new MockStreamingTranscriber());
177
+ const session = new LiveVoiceSession(context, {
178
+ resolveTranscriber,
179
+ resolveCredentialReadiness: mock(async () => NOT_READY_STT_ONLY),
180
+ });
181
+
182
+ await session.start();
183
+
184
+ expect(frames).toHaveLength(1);
185
+ expect(frames[0]).toMatchObject({
186
+ type: "ready",
187
+ sessionId: "session-123",
188
+ conversationId: "conversation-123",
189
+ textInput: true,
190
+ audioInput: false,
191
+ });
192
+ // Nothing may arm: resolving a transcriber the preflight just said has no
193
+ // credential is what would fail the session through armUtterance.
194
+ expect(resolveTranscriber).not.toHaveBeenCalled();
195
+
196
+ await session.close("websocket_close");
197
+ });
198
+
199
+ test("a client without textInput is still rejected on a missing STT leg", async () => {
200
+ // The downgrade is only safe for a client that can take a turn some other
201
+ // way. Opening a mute, deaf session for one that cannot is worse than
202
+ // failing, because nothing in it can ever work.
203
+ const { context, frames } = createContext();
204
+ const session = new LiveVoiceSession(context, {
205
+ resolveTranscriber: mock(async () => new MockStreamingTranscriber()),
206
+ resolveCredentialReadiness: mock(async () => NOT_READY_STT_ONLY),
207
+ });
208
+
209
+ await expect(session.start()).rejects.toBeInstanceOf(
210
+ LiveVoiceSessionStartupError,
211
+ );
212
+
213
+ expect(frames).toEqual([
214
+ {
215
+ type: "error",
216
+ seq: 1,
217
+ code: "credentials_unavailable",
218
+ message: NOT_READY_STT_ONLY.userMessage,
219
+ },
220
+ ]);
221
+ });
222
+
223
+ test("a text-input client is still rejected when the TTS leg is missing", async () => {
224
+ // Text in, voice out: without the out half there is nothing to downgrade
225
+ // to, so this fails exactly as it would for any other client.
226
+ const { context, frames } = createContext(START_FRAME_TEXT_INPUT);
227
+ const session = new LiveVoiceSession(context, {
228
+ resolveTranscriber: mock(async () => new MockStreamingTranscriber()),
229
+ resolveCredentialReadiness: mock(async () => NOT_READY),
230
+ });
231
+
232
+ await expect(session.start()).rejects.toBeInstanceOf(
233
+ LiveVoiceSessionStartupError,
234
+ );
235
+
236
+ expect(frames[0]).toMatchObject({ code: "credentials_unavailable" });
237
+ });
238
+
239
+ test("a text-input client is still rejected when both legs are missing", async () => {
240
+ const { context } = createContext(START_FRAME_TEXT_INPUT);
241
+ const session = new LiveVoiceSession(context, {
242
+ resolveTranscriber: mock(async () => new MockStreamingTranscriber()),
243
+ resolveCredentialReadiness: mock(
244
+ async (): Promise<LiveVoiceCredentialReadiness> => ({
245
+ ...NOT_READY,
246
+ missing: [...NOT_READY.missing, STT_GAP],
247
+ }),
248
+ ),
249
+ });
250
+
251
+ await expect(session.start()).rejects.toBeInstanceOf(
252
+ LiveVoiceSessionStartupError,
253
+ );
254
+ });
255
+
256
+ test("a text-input client is still rejected when not-ready names no gap", async () => {
257
+ // A verdict that says not-ready and lists nothing is a resolver bug. It
258
+ // must not be read as "only STT is missing" and quietly opened half-working
259
+ // on the strength of a vacuously true check.
260
+ const { context } = createContext(START_FRAME_TEXT_INPUT);
261
+ const session = new LiveVoiceSession(context, {
262
+ resolveTranscriber: mock(async () => new MockStreamingTranscriber()),
263
+ resolveCredentialReadiness: mock(
264
+ async (): Promise<LiveVoiceCredentialReadiness> => ({
265
+ status: "not-ready",
266
+ missing: [],
267
+ userMessage: "Live voice is unavailable.",
268
+ }),
269
+ ),
270
+ });
271
+
272
+ await expect(session.start()).rejects.toBeInstanceOf(
273
+ LiveVoiceSessionStartupError,
274
+ );
275
+ });
276
+
131
277
  test("a rejected start frees the manager slot for a retry", async () => {
132
278
  const manager = new LiveVoiceSessionManager({
133
279
  createSession: (context) =>
@@ -157,6 +157,70 @@ describe("live-voice session telemetry", () => {
157
157
  });
158
158
  });
159
159
 
160
+ /**
161
+ * Run one typed turn against a session with a real turn starter, then close.
162
+ *
163
+ * `readySession` wires none, so `handleTextTurn` returns before dispatching
164
+ * and every silence assertion against it passes for the wrong reason.
165
+ */
166
+ async function endAfterTypedTurn(frame: {
167
+ text: string;
168
+ hidden?: boolean;
169
+ }): Promise<void> {
170
+ let started = false;
171
+ const session = new LiveVoiceSession(createContext(), {
172
+ resolveTranscriber: mock(async () => new MockStreamingTranscriber()),
173
+ resolveCredentialReadiness: mock(
174
+ async (): Promise<LiveVoiceCredentialReadiness> => ({
175
+ status: "ready",
176
+ }),
177
+ ),
178
+ startVoiceTurn: mock(async () => {
179
+ started = true;
180
+ return { turnId: "bridge-turn-1", abort: mock() };
181
+ }),
182
+ emitMetrics: false,
183
+ });
184
+ await session.start();
185
+ await session.handleClientFrame({ type: "text", ...frame });
186
+ for (let attempt = 0; attempt < 40 && !started; attempt += 1) {
187
+ await new Promise((resolve) => setTimeout(resolve, 5));
188
+ }
189
+ if (!started) {
190
+ throw new Error("the typed turn never reached the bridge");
191
+ }
192
+ await session.close("client_end");
193
+ }
194
+
195
+ test("the greeting that opens a session does not spend its silence reason", async () => {
196
+ // The classification answers "did anything come from the person?". A
197
+ // session that greets and hears nothing back is silent, and counting the
198
+ // greeting would retire the taxonomy for every greeted session.
199
+ await endAfterTypedTurn({
200
+ text: "this message is sent automatically",
201
+ hidden: true,
202
+ });
203
+
204
+ expect(recordLiveVoiceSessionEnded).toHaveBeenCalledWith({
205
+ sessionId: "session-123",
206
+ screen: "ended_client_end:silent_no_audio",
207
+ outcome: "completed",
208
+ });
209
+ });
210
+
211
+ test("a turn the user really took clears the silence reason", async () => {
212
+ // The other half, and what keeps the test above honest: the same path with
213
+ // an ordinary typed turn is the person taking a turn, so the session is
214
+ // not silent and carries no classification at all.
215
+ await endAfterTypedTurn({ text: "typed by hand" });
216
+
217
+ expect(recordLiveVoiceSessionEnded).toHaveBeenCalledWith({
218
+ sessionId: "session-123",
219
+ screen: "ended_client_end",
220
+ outcome: "completed",
221
+ });
222
+ });
223
+
160
224
  test("a dropped socket ends as completed, not failed", async () => {
161
225
  const session = readySession();
162
226
  await session.start();
@@ -257,6 +257,7 @@ describe("LiveVoiceSession STT", () => {
257
257
  sessionId: "session-123",
258
258
  conversationId: "conversation-123",
259
259
  turnDetection: "manual",
260
+ textInput: true,
260
261
  },
261
262
  ]);
262
263
  });
@@ -611,6 +612,7 @@ describe("LiveVoiceSession STT", () => {
611
612
  sessionId: "session-123",
612
613
  conversationId: "conversation-123",
613
614
  turnDetection: "server_vad",
615
+ textInput: true,
614
616
  },
615
617
  ]);
616
618
 
@@ -700,6 +702,7 @@ describe("LiveVoiceSession STT", () => {
700
702
  sessionId: "session-123",
701
703
  conversationId: "conversation-123",
702
704
  turnDetection: "manual",
705
+ textInput: true,
703
706
  },
704
707
  {
705
708
  type: "error",