@vellumai/assistant 0.12.1-staging.1 → 0.12.1

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 (404) hide show
  1. package/AGENTS.md +0 -4
  2. package/ARCHITECTURE.md +12 -11
  3. package/docs/architecture/turn-actor.md +8 -15
  4. package/openapi.yaml +1 -291
  5. package/package.json +1 -1
  6. package/src/__tests__/always-loaded-tools-guard.test.ts +10 -18
  7. package/src/__tests__/app-compiler.test.ts +0 -6
  8. package/src/__tests__/app-control-tool-schemas.test.ts +2 -5
  9. package/src/__tests__/app-dir-path-guard.test.ts +3 -1
  10. package/src/__tests__/app-source-watcher.test.ts +218 -0
  11. package/src/{calls/__tests__ → __tests__}/call-controller.test.ts +82 -503
  12. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +2 -5
  13. package/src/__tests__/computer-use-tools.test.ts +5 -17
  14. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  15. package/src/__tests__/config-schema.test.ts +29 -28
  16. package/src/__tests__/config-watcher-skill-reseed.test.ts +359 -0
  17. package/src/__tests__/config-watcher.test.ts +8 -23
  18. package/src/__tests__/connection-model-compat.test.ts +2 -2
  19. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  20. package/src/__tests__/conversation-pairing.test.ts +0 -26
  21. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +10 -4
  22. package/src/__tests__/conversation-queue.test.ts +207 -48
  23. package/src/__tests__/conversation-retry-discard.test.ts +1 -54
  24. package/src/__tests__/conversation-skill-tools.test.ts +0 -27
  25. package/src/__tests__/conversation-surfaces-app-control.test.ts +3 -8
  26. package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -67
  27. package/src/__tests__/conversation-title-service.test.ts +10 -14
  28. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +5 -3
  29. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -51
  30. package/src/__tests__/credential-record-retire.test.ts +1 -171
  31. package/src/__tests__/credential-routes.test.ts +3 -212
  32. package/src/__tests__/credential-security-invariants.test.ts +1 -2
  33. package/src/__tests__/cu-unified-flow.test.ts +2 -8
  34. package/src/__tests__/drain-kick-guard.test.ts +2 -0
  35. package/src/__tests__/drain-requeue-on-contention.test.ts +6 -0
  36. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  37. package/src/__tests__/host-bash-proxy.test.ts +45 -26
  38. package/src/__tests__/host-browser-proxy.test.ts +17 -6
  39. package/src/__tests__/host-file-proxy-targeted.test.ts +0 -23
  40. package/src/__tests__/host-file-proxy.test.ts +23 -11
  41. package/src/__tests__/host-transfer-proxy-targeted.test.ts +9 -30
  42. package/src/__tests__/llm-catalog-parity.test.ts +0 -25
  43. package/src/__tests__/mcp-auth-routes.test.ts +9 -6
  44. package/src/__tests__/mcp-config-secret-boundary.test.ts +136 -78
  45. package/src/__tests__/mcp-health-check.test.ts +11 -8
  46. package/src/__tests__/mcp-list-plugin-servers.test.ts +10 -8
  47. package/src/{calls/__tests__ → __tests__}/media-stream-audio-transcode.test.ts +1 -1
  48. package/src/{calls/__tests__ → __tests__}/media-stream-output.test.ts +8 -37
  49. package/src/{calls/__tests__ → __tests__}/media-stream-parser.test.ts +1 -1
  50. package/src/{calls/__tests__ → __tests__}/media-stream-server-integration.test.ts +57 -150
  51. package/src/{calls/__tests__ → __tests__}/media-stream-stt-session.test.ts +5 -5
  52. package/src/{calls/__tests__ → __tests__}/media-turn-detector.test.ts +1 -1
  53. package/src/__tests__/messages-after-tiebreaker.test.ts +0 -91
  54. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  55. package/src/__tests__/messaging-send-tool.test.ts +548 -355
  56. package/src/__tests__/model-intents.test.ts +0 -3
  57. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  58. package/src/__tests__/platform-client-verify-credential.test.ts +0 -2
  59. package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -13
  60. package/src/__tests__/script-proxy-certs.test.ts +0 -35
  61. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  62. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  63. package/src/__tests__/speaker-identification.test.ts +56 -0
  64. package/src/__tests__/subagent-tool-gate-mode.test.ts +3 -19
  65. package/src/__tests__/subagent-tools.test.ts +2 -3
  66. package/src/__tests__/telephony-tts-guard.test.ts +3 -38
  67. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +0 -4
  68. package/src/{calls/__tests__/voice-session-bridge-turn-flow.test.ts → __tests__/voice-session-bridge.test.ts} +12 -20
  69. package/src/__tests__/watch-retro-tool-availability.test.ts +17 -8
  70. package/src/__tests__/workspace-git-service.test.ts +0 -26
  71. package/src/acp/__tests__/agent-process.test.ts +4 -258
  72. package/src/acp/__tests__/auth-required.test.ts +0 -96
  73. package/src/acp/__tests__/client-handler.test.ts +2 -90
  74. package/src/acp/__tests__/session-manager-persistence.test.ts +1 -5
  75. package/src/acp/__tests__/session-manager-resume.test.ts +6 -270
  76. package/src/acp/__tests__/session-manager-usage.test.ts +2 -4
  77. package/src/acp/__tests__/session-manager.test.ts +4 -895
  78. package/src/acp/agent-process.test.ts +1 -4
  79. package/src/acp/agent-process.ts +9 -94
  80. package/src/acp/auth-required.ts +1 -50
  81. package/src/acp/auto-install.test.ts +7 -9
  82. package/src/acp/client-handler.ts +3 -16
  83. package/src/acp/resolve-agent.test.ts +6 -220
  84. package/src/acp/resolve-agent.ts +23 -88
  85. package/src/acp/session-manager.test.ts +3 -196
  86. package/src/acp/session-manager.ts +18 -441
  87. package/src/acp/types.ts +5 -50
  88. package/src/api/events/app-files-changed.ts +3 -4
  89. package/src/api/index.ts +0 -6
  90. package/src/apps/app-store.ts +1 -1
  91. package/src/bundler/app-compiler.ts +0 -15
  92. package/src/calls/__tests__/voice-control-protocol.test.ts +25 -58
  93. package/src/calls/__tests__/voice-session-bridge.test.ts +1 -37
  94. package/src/calls/__tests__/voice-triage-escalate.test.ts +0 -105
  95. package/src/calls/call-constants.ts +4 -24
  96. package/src/calls/call-controller.ts +199 -499
  97. package/src/calls/call-transport.ts +0 -16
  98. package/src/calls/media-stream-output.ts +0 -32
  99. package/src/calls/media-stream-server.ts +30 -82
  100. package/src/calls/media-stream-stt-session.ts +0 -15
  101. package/src/calls/resolve-call-tts-provider.ts +2 -33
  102. package/src/calls/speaker-identification.ts +233 -0
  103. package/src/calls/tts-call-strategy.ts +5 -9
  104. package/src/calls/types.ts +0 -2
  105. package/src/calls/voice-control-protocol.ts +21 -53
  106. package/src/calls/voice-session-bridge.ts +46 -59
  107. package/src/calls/voice-triage-escalate.ts +56 -175
  108. package/src/cli/commands/apps.help.ts +13 -67
  109. package/src/cli/commands/apps.ts +0 -183
  110. package/src/cli/commands/channels/index.help.ts +2 -70
  111. package/src/cli/commands/channels/index.ts +3 -16
  112. package/src/cli/commands/credentials.help.ts +2 -6
  113. package/src/cli/commands/credentials.ts +2 -2
  114. package/src/cli/commands/mcp.help.ts +8 -8
  115. package/src/cli/commands/mcp.ts +1 -1
  116. package/src/cli/commands/roadmap.help.ts +1 -5
  117. package/src/cli/commands/roadmap.ts +1 -1
  118. package/src/cli/lib/bundled-marketplace.json +90 -103
  119. package/src/config/__tests__/default-profile-catalog.test.ts +0 -22
  120. package/src/config/__tests__/memory-retrospective-schema.test.ts +1 -15
  121. package/src/config/__tests__/platform-rehydration.test.ts +0 -93
  122. package/src/config/acp-defaults.test.ts +3 -13
  123. package/src/config/acp-defaults.ts +6 -23
  124. package/src/config/acp-schema.ts +2 -30
  125. package/src/config/bundled-skills/acp/SKILL.md +14 -22
  126. package/src/config/bundled-skills/acp/TOOLS.json +1 -5
  127. package/src/config/bundled-skills/app-builder/SKILL.md +9 -12
  128. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  129. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  130. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +139 -67
  131. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  132. package/src/config/bundled-skills/screen-annotation/SKILL.md +1 -8
  133. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +1 -4
  134. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  135. package/src/config/default-profile-catalog.ts +1 -1
  136. package/src/config/env.ts +2 -2
  137. package/src/config/feature-flag-registry.json +12 -8
  138. package/src/config/memory-v3-gate.test.ts +0 -31
  139. package/src/config/memory-v3-gate.ts +16 -20
  140. package/src/config/platform-rehydration.ts +21 -75
  141. package/src/config/schema.ts +2 -2
  142. package/src/config/schemas/__tests__/live-voice.test.ts +158 -8
  143. package/src/config/schemas/calls.ts +28 -1
  144. package/src/config/schemas/live-voice.ts +173 -1
  145. package/src/config/schemas/mcp.ts +1 -1
  146. package/src/config/schemas/memory-retrospective.ts +0 -10
  147. package/src/config/schemas/skills.ts +14 -0
  148. package/src/credential-execution/process-manager.test.ts +19 -18
  149. package/src/daemon/__tests__/conversation-tool-setup.test.ts +121 -66
  150. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +1 -4
  151. package/src/daemon/app-source-watcher.ts +218 -0
  152. package/src/daemon/config-watcher.ts +194 -13
  153. package/src/daemon/conversation-process.ts +64 -35
  154. package/src/daemon/conversation-skill-tools.ts +31 -13
  155. package/src/daemon/conversation-store.ts +1 -1
  156. package/src/daemon/conversation-surfaces.ts +8 -10
  157. package/src/daemon/conversation-tool-setup.ts +43 -37
  158. package/src/daemon/conversation.ts +3 -0
  159. package/src/daemon/host-bash-proxy.ts +12 -8
  160. package/src/daemon/host-browser-proxy.ts +1 -15
  161. package/src/daemon/host-file-proxy.ts +10 -8
  162. package/src/daemon/host-transfer-proxy.ts +18 -14
  163. package/src/daemon/lifecycle.ts +5 -0
  164. package/src/daemon/mcp-reload-service.ts +5 -5
  165. package/src/daemon/message-types/sync.ts +0 -1
  166. package/src/daemon/providers-setup.ts +1 -1
  167. package/src/daemon/shutdown-handlers.ts +9 -1
  168. package/src/daemon/skill-memory-refresh.ts +0 -64
  169. package/src/daemon/tool-side-effects.ts +18 -7
  170. package/src/hooks/types.ts +0 -33
  171. package/src/inbound/platform-callback-registration.ts +5 -4
  172. package/src/live-voice/__tests__/live-voice-metrics.test.ts +3 -0
  173. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  174. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  175. package/src/{calls → live-voice}/__tests__/progress-narration.test.ts +4 -4
  176. package/src/{calls → live-voice}/__tests__/progress-phrases.test.ts +1 -1
  177. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +69 -13
  178. package/src/live-voice/live-voice-session.ts +693 -213
  179. package/src/{calls → live-voice}/progress-narration.ts +2 -2
  180. package/src/live-voice/protocol.ts +2 -0
  181. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  182. package/src/mcp/effective-config.ts +3 -3
  183. package/src/mcp/mcp-header-store.ts +45 -29
  184. package/src/mcp/startup.ts +4 -8
  185. package/src/messaging/provider.ts +3 -10
  186. package/src/messaging/providers/callback-routing.ts +0 -18
  187. package/src/messaging/providers/channel-transport.ts +0 -56
  188. package/src/messaging/providers/discord/transport.ts +0 -17
  189. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +17 -6
  190. package/src/messaging/providers/slack/adapter.ts +44 -7
  191. package/src/messaging/providers/slack/transport.ts +0 -16
  192. package/src/messaging/providers/telegram-bot/adapter.ts +48 -1
  193. package/src/messaging/providers/telegram-bot/client.ts +26 -0
  194. package/src/messaging/providers/telegram-bot/transport.ts +0 -20
  195. package/src/messaging/providers/whatsapp/adapter.ts +48 -1
  196. package/src/messaging/providers/whatsapp/client.ts +29 -0
  197. package/src/messaging/providers/whatsapp/transport.ts +0 -18
  198. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +0 -8
  199. package/src/notifications/__tests__/assistant-reply-producer.test.ts +0 -55
  200. package/src/notifications/__tests__/home-feed-side-effect.test.ts +3 -153
  201. package/src/notifications/assistant-reply-producer.ts +3 -8
  202. package/src/notifications/conversation-pairing.ts +16 -36
  203. package/src/notifications/delivered-post-record.ts +1 -11
  204. package/src/notifications/home-feed-side-effect.ts +7 -55
  205. package/src/outbound-proxy/certs.ts +5 -3
  206. package/src/permissions/checker.test.ts +6 -11
  207. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  208. package/src/persistence/conversation-crud.ts +63 -71
  209. package/src/persistence/conversation-lineage.ts +4 -12
  210. package/src/persistence/conversation-title-service.ts +26 -9
  211. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  212. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  213. package/src/persistence/message-reads.ts +0 -30
  214. package/src/persistence/migrations/157-invite-contact-id.ts +6 -18
  215. package/src/persistence/schema/memory-core.ts +0 -5
  216. package/src/platform/client.test.ts +0 -2
  217. package/src/platform/client.ts +10 -2
  218. package/src/plugin-api/constants.ts +0 -2
  219. package/src/plugin-api/index.ts +0 -1
  220. package/src/plugin-api/types.ts +0 -2
  221. package/src/plugins/defaults/index.ts +0 -2
  222. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +0 -46
  223. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +5 -9
  224. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +10 -20
  225. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +0 -219
  226. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +0 -31
  227. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +0 -63
  228. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +0 -4
  229. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -4
  230. package/src/plugins/defaults/memory/hooks/init.ts +0 -10
  231. package/src/plugins/defaults/memory/hooks/shutdown.ts +0 -2
  232. package/src/plugins/defaults/memory/jobs-worker.ts +0 -13
  233. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +60 -34
  234. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +5 -3
  235. package/src/plugins/defaults/memory/memory-retrospective-job.ts +3 -12
  236. package/src/plugins/defaults/memory/memory-retrospective-state.ts +71 -312
  237. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +1 -2
  238. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +3 -5
  239. package/src/plugins/defaults/memory/startup.ts +1 -6
  240. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +9 -7
  241. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  242. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +0 -89
  243. package/src/plugins/defaults/memory/worker.ts +0 -6
  244. package/src/plugins/mcp-servers.ts +75 -15
  245. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  246. package/src/providers/gemini/client.ts +23 -9
  247. package/src/providers/model-catalog.ts +0 -34
  248. package/src/providers/model-intents.ts +5 -10
  249. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +18 -146
  250. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +1 -83
  251. package/src/providers/openai/chat-completions-provider.ts +78 -192
  252. package/src/providers/openai/codex-models.ts +4 -0
  253. package/src/runtime/AGENTS.md +0 -2
  254. package/src/runtime/auth/same-actor.ts +3 -38
  255. package/src/runtime/http-server.ts +151 -160
  256. package/src/runtime/routes/__tests__/acp-routes.test.ts +4 -159
  257. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +1 -108
  258. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +12 -16
  259. package/src/runtime/routes/acp-routes.test.ts +0 -15
  260. package/src/runtime/routes/acp-routes.ts +9 -72
  261. package/src/runtime/routes/app-management-routes.ts +0 -73
  262. package/src/runtime/routes/app-routes.ts +1 -1
  263. package/src/runtime/routes/conversation-list-routes.ts +3 -14
  264. package/src/runtime/routes/conversation-query-routes.ts +0 -40
  265. package/src/runtime/routes/credential-routes.ts +10 -19
  266. package/src/runtime/routes/diagnostics-routes.ts +3 -55
  267. package/src/runtime/routes/index.ts +0 -4
  268. package/src/runtime/routes/log-export-routes.ts +0 -2
  269. package/src/runtime/routes/mcp-auth-routes.ts +47 -39
  270. package/src/runtime/routes/migration-routes.ts +21 -8
  271. package/src/runtime/routes/platform-routes.ts +15 -18
  272. package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -28
  273. package/src/runtime/services/conversation-serializer.ts +3 -16
  274. package/src/runtime/sync/resource-sync-events.ts +1 -2
  275. package/src/schedule/worker.ts +1 -1
  276. package/src/security/ces-rpc-record-backend.ts +0 -7
  277. package/src/subagent/consult-context.ts +25 -12
  278. package/src/tools/acp/list-agents.test.ts +1 -3
  279. package/src/tools/acp/spawn.test.ts +11 -146
  280. package/src/tools/acp/spawn.ts +3 -21
  281. package/src/tools/acp/status.ts +3 -40
  282. package/src/tools/app-control/skill-proxy-bridge.ts +1 -2
  283. package/src/tools/capability-offer.test.ts +2 -22
  284. package/src/tools/capability-offer.ts +1 -16
  285. package/src/tools/computer-use/definitions.ts +2 -4
  286. package/src/tools/computer-use/skill-proxy-bridge.ts +1 -2
  287. package/src/tools/credentials/metadata-store.ts +0 -100
  288. package/src/tools/host-filesystem/edit.test.ts +1 -2
  289. package/src/tools/host-filesystem/edit.ts +2 -2
  290. package/src/tools/host-filesystem/read.test.ts +1 -2
  291. package/src/tools/host-filesystem/read.ts +2 -2
  292. package/src/tools/host-filesystem/transfer.test.ts +1 -2
  293. package/src/tools/host-filesystem/transfer.ts +2 -2
  294. package/src/tools/host-filesystem/write.test.ts +1 -2
  295. package/src/tools/host-filesystem/write.ts +2 -2
  296. package/src/tools/host-terminal/host-shell.test.ts +0 -16
  297. package/src/tools/host-terminal/host-shell.ts +20 -4
  298. package/src/tools/policy-context.ts +7 -6
  299. package/src/tools/subagent/spawn.ts +8 -5
  300. package/src/tools/watch/watch-retro-report.ts +8 -0
  301. package/src/util/__tests__/language-subtag.test.ts +1 -23
  302. package/src/util/fs-watcher-error.ts +36 -0
  303. package/src/util/language-subtag.ts +0 -16
  304. package/src/util/platform.ts +0 -8
  305. package/src/workspace/git-service.ts +54 -5
  306. package/src/workspace/migrations/registry.ts +0 -6
  307. package/scripts/smoke-desktop-window-theme.ts +0 -186
  308. package/src/__tests__/git-porcelain.test.ts +0 -47
  309. package/src/__tests__/helpers/set-workspace-mcp.ts +0 -11
  310. package/src/__tests__/message-deleted-hook-dispatch.test.ts +0 -119
  311. package/src/__tests__/resolve-app.test.ts +0 -135
  312. package/src/__tests__/source-fingerprint.test.ts +0 -184
  313. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +0 -336
  314. package/src/acp/__tests__/helpers/acp-model-option.ts +0 -51
  315. package/src/acp/model-config.test.ts +0 -166
  316. package/src/acp/model-config.ts +0 -138
  317. package/src/api/events/acp-session-model-update.test.ts +0 -88
  318. package/src/api/events/acp-session-model-update.ts +0 -46
  319. package/src/apps/resolve-app.ts +0 -81
  320. package/src/apps/source-fingerprint.ts +0 -266
  321. package/src/calls/__tests__/barge-in-guard.test.ts +0 -81
  322. package/src/calls/__tests__/voice-leg-coordinator.test.ts +0 -229
  323. package/src/calls/__tests__/voice-progress-cadence.test.ts +0 -274
  324. package/src/calls/barge-in-guard.ts +0 -118
  325. package/src/calls/voice-leg-coordinator.ts +0 -189
  326. package/src/calls/voice-progress-cadence.ts +0 -423
  327. package/src/cli/commands/__tests__/apps.test.ts +0 -245
  328. package/src/cli/commands/__tests__/roadmap.help.test.ts +0 -19
  329. package/src/cli/commands/channels/__tests__/send.test.ts +0 -218
  330. package/src/cli/commands/channels/send.ts +0 -128
  331. package/src/config/__tests__/platform-identity.test.ts +0 -117
  332. package/src/config/acp-agent-write.ts +0 -43
  333. package/src/config/acp-schema.test.ts +0 -81
  334. package/src/config/platform-identity.ts +0 -144
  335. package/src/config/schemas/__tests__/voice.test.ts +0 -148
  336. package/src/config/schemas/voice.ts +0 -150
  337. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +0 -133
  338. package/src/daemon/app-change-notify.ts +0 -45
  339. package/src/desktop/__tests__/fake-desktop.ts +0 -119
  340. package/src/desktop/desktop-chrome-policy.test.ts +0 -71
  341. package/src/desktop/desktop-chrome-policy.ts +0 -42
  342. package/src/desktop/desktop-dependencies.test.ts +0 -102
  343. package/src/desktop/desktop-dependencies.ts +0 -306
  344. package/src/desktop/desktop-feature.test.ts +0 -18
  345. package/src/desktop/desktop-feature.ts +0 -19
  346. package/src/desktop/desktop-panel-config.test.ts +0 -135
  347. package/src/desktop/desktop-panel-config.ts +0 -149
  348. package/src/desktop/desktop-session-manager.test.ts +0 -627
  349. package/src/desktop/desktop-session-manager.ts +0 -814
  350. package/src/desktop/desktop-stream-bridge.test.ts +0 -395
  351. package/src/desktop/desktop-stream-bridge.ts +0 -214
  352. package/src/desktop/desktop-wallpaper.test.ts +0 -191
  353. package/src/desktop/desktop-wallpaper.ts +0 -113
  354. package/src/desktop/desktop-window-theme.ts +0 -170
  355. package/src/i18n/AGENTS.md +0 -22
  356. package/src/i18n/__tests__/i18n.test.ts +0 -108
  357. package/src/i18n/index.ts +0 -16
  358. package/src/i18n/locales.ts +0 -93
  359. package/src/i18n/messages.ts +0 -61
  360. package/src/i18n/title.ts +0 -25
  361. package/src/mcp/__tests__/workspace-mcp-config.test.ts +0 -135
  362. package/src/mcp/spec-schema.ts +0 -111
  363. package/src/mcp/workspace-mcp-config.ts +0 -188
  364. package/src/messaging/providers/__tests__/proactive-address.test.ts +0 -95
  365. package/src/persistence/conversation-title-placeholders.ts +0 -23
  366. package/src/persistence/message-cursor.ts +0 -78
  367. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +0 -149
  368. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +0 -118
  369. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +0 -83
  370. package/src/plugins/defaults/memory/hooks/message-deleted.ts +0 -21
  371. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +0 -126
  372. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +0 -79
  373. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +0 -28
  374. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +0 -9
  375. package/src/providers/gemini-thought-signature.test.ts +0 -63
  376. package/src/providers/gemini-thought-signature.ts +0 -44
  377. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +0 -613
  378. package/src/providers/openai/__tests__/google-thought-signature.test.ts +0 -235
  379. package/src/providers/openai/google-thought-signature.ts +0 -320
  380. package/src/runtime/__tests__/channel-send.test.ts +0 -443
  381. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +0 -89
  382. package/src/runtime/__tests__/live-voice-upgrade.test.ts +0 -58
  383. package/src/runtime/__tests__/runtime-ws-test-utils.ts +0 -72
  384. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +0 -59
  385. package/src/runtime/channel-send.ts +0 -316
  386. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +0 -205
  387. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +0 -127
  388. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
  389. package/src/runtime/routes/channel-send-routes.ts +0 -85
  390. package/src/runtime/routes/desktop-setup-routes.test.ts +0 -74
  391. package/src/runtime/routes/desktop-setup-routes.ts +0 -35
  392. package/src/security/__tests__/ces-http-record-backend.test.ts +0 -137
  393. package/src/security/ces-http-record-backend.ts +0 -120
  394. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +0 -60
  395. package/src/skills/workspace-skill-md-signature.ts +0 -50
  396. package/src/tools/acp/status.test.ts +0 -94
  397. package/src/util/ca-bundle.ts +0 -13
  398. package/src/workspace/git-porcelain.ts +0 -66
  399. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +0 -258
  400. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +0 -78
  401. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +0 -214
  402. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +0 -102
  403. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +0 -199
  404. /package/src/{calls → live-voice}/progress-phrases.ts +0 -0
@@ -8,17 +8,17 @@ import {
8
8
  test,
9
9
  } from "bun:test";
10
10
 
11
- mock.module("../../config/env.js", () => ({ isHttpAuthDisabled: () => true }));
11
+ mock.module("../config/env.js", () => ({ isHttpAuthDisabled: () => true }));
12
12
 
13
13
  // Guardian requests/deliveries are created through the gateway client; serve
14
14
  // that surface from the in-memory sim so dispatch never opens IPC.
15
15
  import {
16
16
  bridgeState,
17
17
  gatewayGuardianRequestsStoreBridge,
18
- } from "../../__tests__/helpers/gateway-guardian-requests-store-bridge.js";
18
+ } from "./helpers/gateway-guardian-requests-store-bridge.js";
19
19
 
20
20
  mock.module(
21
- "../../channels/gateway-guardian-requests.js",
21
+ "../channels/gateway-guardian-requests.js",
22
22
  () => gatewayGuardianRequestsStoreBridge,
23
23
  );
24
24
 
@@ -36,7 +36,7 @@ mock.module(
36
36
  let mockResolvableProviderKeys: ((service: string) => string | null) | null =
37
37
  null;
38
38
 
39
- mock.module("../../security/secure-keys.js", () => ({
39
+ mock.module("../security/secure-keys.js", () => ({
40
40
  getSecureKeyAsync: async () => null,
41
41
  getSecureKey: () => null,
42
42
  getProviderKeyAsync: async (service: string) =>
@@ -45,19 +45,7 @@ mock.module("../../security/secure-keys.js", () => ({
45
45
  : "test-key",
46
46
  }));
47
47
 
48
- // Managed-speech defaulting reaches the platform client and the credential
49
- // store. The controller tests drive the bridge mock on a microtask budget, so
50
- // the configured providers stand as the effective ones here.
51
- mock.module("../../config/managed-speech-defaults.js", () => ({
52
- resolveEffectiveSpeechProviders: async (config?: {
53
- services: { stt: { provider: string }; tts: { provider: string } };
54
- }) => {
55
- const services = (config ?? loadConfig()).services;
56
- return { stt: services.stt.provider, tts: services.tts.provider };
57
- },
58
- }));
59
-
60
- mock.module("../../security/credential-key.js", () => ({
48
+ mock.module("../security/credential-key.js", () => ({
61
49
  credentialKey: (...args: string[]) => args.join("/"),
62
50
  }));
63
51
 
@@ -68,10 +56,7 @@ let mockSilenceTimeoutMs = 30_000;
68
56
  let mockEndCallListenWindowMs = 0;
69
57
  let mockEndCallDrainMaxWaitMs = 15_000;
70
58
 
71
- import * as realCallConstants from "../call-constants.js";
72
-
73
- mock.module("../call-constants.js", () => ({
74
- ...realCallConstants,
59
+ mock.module("../calls/call-constants.js", () => ({
75
60
  getMaxCallDurationMs: () => 12 * 60 * 1000,
76
61
  getUserConsultationTimeoutMs: () => mockConsultationTimeoutMs,
77
62
  getSilenceTimeoutMs: () => mockSilenceTimeoutMs,
@@ -125,7 +110,7 @@ let mockStartVoiceTurn: Mock<any>;
125
110
 
126
111
  // ── Notification pipeline mock (prevent async handle leaks from fire-and-forget dispatches) ──
127
112
 
128
- mock.module("../../notifications/emit-signal.js", () => ({
113
+ mock.module("../notifications/emit-signal.js", () => ({
129
114
  emitNotificationSignal: async () => ({
130
115
  signalId: "mock-signal",
131
116
  deduplicated: false,
@@ -137,7 +122,7 @@ mock.module("../../notifications/emit-signal.js", () => ({
137
122
 
138
123
  // Guardian principalId is resolved from the gateway binding reader. Mirror the
139
124
  // vellum binding seeded by resetTables so guardian dispatch can resolve it.
140
- mock.module("../../contacts/guardian-delivery-reader.js", () => ({
125
+ mock.module("../contacts/guardian-delivery-reader.js", () => ({
141
126
  getGuardianDelivery: async () => [
142
127
  {
143
128
  channelType: "vellum",
@@ -154,7 +139,7 @@ mock.module("../../contacts/guardian-delivery-reader.js", () => ({
154
139
  anyGuardian: (list: unknown[]) => list[0],
155
140
  }));
156
141
 
157
- mock.module("../voice-session-bridge.js", () => {
142
+ mock.module("../calls/voice-session-bridge.js", () => {
158
143
  mockStartVoiceTurn = mock(createMockVoiceTurn(["Hello", " there"]));
159
144
  return {
160
145
  startVoiceTurn: (...args: unknown[]) => mockStartVoiceTurn(...args),
@@ -170,8 +155,8 @@ mock.module("../voice-session-bridge.js", () => {
170
155
  import {
171
156
  _resetTtsProviderOverridesForTests,
172
157
  _setTtsProviderForTests,
173
- } from "../../tts/provider-catalog.js";
174
- import type { TtsProvider } from "../../tts/types.js";
158
+ } from "../tts/provider-catalog.js";
159
+ import type { TtsProvider } from "../tts/types.js";
175
160
 
176
161
  function registerTestTtsProviders(): void {
177
162
  _resetTtsProviderOverridesForTests();
@@ -221,33 +206,26 @@ registerTestTtsProviders();
221
206
 
222
207
  // ── Import source modules after all mocks are registered ────────────
223
208
 
224
- import { resetDbForTesting } from "../../__tests__/db-test-helpers.js";
225
- import { createGuardianBinding } from "../../__tests__/helpers/create-guardian-binding.js";
226
- import { setConfig } from "../../__tests__/helpers/set-config.js";
227
- import { loadConfig } from "../../config/loader.js";
228
- import type { VoiceProgressConfig } from "../../config/schemas/voice.js";
229
- import { getMessages } from "../../persistence/conversation-crud.js";
230
- import { getDb } from "../../persistence/db-connection.js";
231
- import { initializeDb } from "../../persistence/db-init.js";
232
- import { resetTestTables } from "../../persistence/raw-query.js";
233
- import { conversations } from "../../persistence/schema/index.js";
234
- import { CallController } from "../call-controller.js";
235
- import { getCallController } from "../call-state.js";
209
+ import { CallController } from "../calls/call-controller.js";
210
+ import { getCallController } from "../calls/call-state.js";
236
211
  import {
237
212
  createCallSession,
238
213
  getCallEvents,
239
214
  getCallSession,
240
215
  getPendingQuestion,
241
216
  updateCallSession,
242
- } from "../call-store.js";
243
- import type { CallTransport } from "../call-transport.js";
244
- import type { VoiceProgressNarrator } from "../progress-narration.js";
245
- import { resolveCallTtsProvider } from "../resolve-call-tts-provider.js";
246
- import {
247
- ESCALATION_CONTINUATION_CONTENT,
248
- FALLBACK_ESCALATION_BRIDGE,
249
- FALLBACK_ESCALATION_BRIDGE_BY_LANGUAGE,
250
- } from "../voice-triage-escalate.js";
217
+ } from "../calls/call-store.js";
218
+ import type { CallTransport } from "../calls/call-transport.js";
219
+ import { resolveCallTtsProvider } from "../calls/resolve-call-tts-provider.js";
220
+ import { loadConfig } from "../config/loader.js";
221
+ import { getMessages } from "../persistence/conversation-crud.js";
222
+ import { getDb } from "../persistence/db-connection.js";
223
+ import { initializeDb } from "../persistence/db-init.js";
224
+ import { resetTestTables } from "../persistence/raw-query.js";
225
+ import { conversations } from "../persistence/schema/index.js";
226
+ import { resetDbForTesting } from "./db-test-helpers.js";
227
+ import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
228
+ import { setConfig } from "./helpers/set-config.js";
251
229
 
252
230
  // Disable memory so persisted call messages skip background indexing, and
253
231
  // seed the ingress base URL used for synthesized-audio play URLs.
@@ -267,8 +245,6 @@ afterAll(() => {
267
245
 
268
246
  interface MockTransport extends CallTransport {
269
247
  sentTokens: Array<{ token: string; last: boolean }>;
270
- /** Tokens sent with the system-copy flag (no caller-language hint). */
271
- systemCopyTokens: string[];
272
248
  sentPlayUrls: string[];
273
249
  endCalled: boolean;
274
250
  endReason: string | undefined;
@@ -278,7 +254,6 @@ interface MockTransport extends CallTransport {
278
254
  function createMockTransport(): MockTransport {
279
255
  const state = {
280
256
  sentTokens: [] as Array<{ token: string; last: boolean }>,
281
- systemCopyTokens: [] as string[],
282
257
  sentPlayUrls: [] as string[],
283
258
  _endCalled: false,
284
259
  _endReason: undefined as string | undefined,
@@ -289,9 +264,6 @@ function createMockTransport(): MockTransport {
289
264
  get sentTokens() {
290
265
  return state.sentTokens;
291
266
  },
292
- get systemCopyTokens() {
293
- return state.systemCopyTokens;
294
- },
295
267
  get sentPlayUrls() {
296
268
  return state.sentPlayUrls;
297
269
  },
@@ -304,15 +276,8 @@ function createMockTransport(): MockTransport {
304
276
  get cancelPendingSpeechCount() {
305
277
  return state._cancelPendingSpeechCount;
306
278
  },
307
- sendTextToken(
308
- token: string,
309
- last: boolean,
310
- opts?: { systemCopy?: boolean },
311
- ) {
279
+ sendTextToken(token: string, last: boolean) {
312
280
  state.sentTokens.push({ token, last });
313
- if (opts?.systemCopy === true) {
314
- state.systemCopyTokens.push(token);
315
- }
316
281
  },
317
282
  sendPlayUrl(url: string) {
318
283
  state.sentPlayUrls.push(url);
@@ -407,16 +372,13 @@ function setupController(
407
372
  task?: string,
408
373
  opts?: {
409
374
  assistantId?: string;
410
- trustContext?: import("../../daemon/trust-context-types.js").TrustContext;
375
+ trustContext?: import("../daemon/trust-context-types.js").TrustContext;
411
376
  /** Simulate the media-stream transport's PCM requirement. */
412
377
  requiresPcmAudio?: boolean;
413
378
  /** Simulate a transport that gates teardown on playback drain. */
414
379
  awaitPlaybackDrained?: () => Promise<void>;
415
380
  /** Synthesis-language resolver, as the media-stream server wires it. */
416
381
  resolveSynthesisLanguage?: () => string | undefined;
417
- /** Progress narration seam; an explicit null keeps the call silent. */
418
- progressNarrator?: VoiceProgressNarrator | null;
419
- progressConfig?: VoiceProgressConfig;
420
382
  },
421
383
  ) {
422
384
  ensureConversation("conv-ctrl-test");
@@ -441,11 +403,6 @@ function setupController(
441
403
  assistantId: opts?.assistantId,
442
404
  trustContext: opts?.trustContext,
443
405
  resolveSynthesisLanguage: opts?.resolveSynthesisLanguage,
444
- // Tests that do not exercise narration keep the default narrator out of
445
- // the way: a null narrator never generates or speaks.
446
- progressNarrator:
447
- opts?.progressNarrator === undefined ? null : opts.progressNarrator,
448
- progressConfig: opts?.progressConfig,
449
406
  });
450
407
  return { session, relay: transport, controller };
451
408
  }
@@ -549,6 +506,35 @@ describe("call-controller", () => {
549
506
  controller.destroy();
550
507
  });
551
508
 
509
+ test("handleCallerUtterance: includes speaker context in voice turn content", async () => {
510
+ mockStartVoiceTurn.mockImplementation(
511
+ async (opts: {
512
+ content: string;
513
+ onTextDelta: (t: string) => void;
514
+ onComplete: () => void;
515
+ }) => {
516
+ expect(opts.content).toContain(
517
+ '[SPEAKER id="speaker-1" label="Aaron" source="provider" confidence="0.91"]',
518
+ );
519
+ expect(opts.content).toContain("Can you summarize this meeting?");
520
+ opts.onTextDelta("Sure, here is a summary.");
521
+ opts.onComplete();
522
+ return { turnId: "run-1", abort: () => {} };
523
+ },
524
+ );
525
+
526
+ const { controller } = setupController();
527
+
528
+ await controller.handleCallerUtterance("Can you summarize this meeting?", {
529
+ speakerId: "speaker-1",
530
+ speakerLabel: "Aaron",
531
+ speakerConfidence: 0.91,
532
+ source: "provider",
533
+ });
534
+
535
+ controller.destroy();
536
+ });
537
+
552
538
  test("startInitialGreeting: sends CALL_OPENING content and strips control marker from speech", async () => {
553
539
  let turnCount = 0;
554
540
  mockStartVoiceTurn.mockImplementation(
@@ -2301,7 +2287,7 @@ describe("call-controller", () => {
2301
2287
 
2302
2288
  // Compute expected digest independently using the same utility
2303
2289
  const { computeToolApprovalDigest } =
2304
- await import("../../security/tool-approval-digest.js");
2290
+ await import("../security/tool-approval-digest.js");
2305
2291
  const expectedDigest = computeToolApprovalDigest("send_email", {
2306
2292
  subject: "Hello",
2307
2293
  to: "bob@example.com",
@@ -3101,7 +3087,7 @@ describe("call-controller", () => {
3101
3087
  controller.destroy();
3102
3088
  });
3103
3089
 
3104
- test("synthesized provider: stays 'processing' during synthesis latency until the play URL is sent", async () => {
3090
+ test("synthesized provider: stays 'processing' during synthesis latency, so barge-in cannot abort an inaudible turn", async () => {
3105
3091
  const cfg = loadConfig();
3106
3092
  cfg.services.tts.provider = "fish-audio";
3107
3093
  cfg.services.tts.providers["fish-audio"].referenceId = "fish-ref-123";
@@ -3142,11 +3128,11 @@ describe("call-controller", () => {
3142
3128
  await new Promise((r) => setTimeout(r, 20));
3143
3129
 
3144
3130
  // No audio has reached the caller yet (play URL not sent), so the controller
3145
- // must stay in `processing`: the state tells the transport nothing is
3146
- // audible, and only sustained caller speech (the transport's guard) may
3147
- // cut the inaudible turn off. See JARVIS-1232.
3131
+ // must stay in `processing` — a barge-in here would abort a turn the caller
3132
+ // cannot hear. See JARVIS-1232.
3148
3133
  expect(relay.sentPlayUrls.length).toBe(0);
3149
3134
  expect(controller.getState()).toBe("processing");
3135
+ expect(controller.handleBargeIn()).toBe(false);
3150
3136
 
3151
3137
  // Release audio → play URL sent → turn finishes and returns to idle.
3152
3138
  releaseChunk?.();
@@ -3418,7 +3404,7 @@ describe("call-controller", () => {
3418
3404
  function registerFishAudioSegmentRecorder(opts?: {
3419
3405
  onSynthesizeStream?: (
3420
3406
  text: string,
3421
- request: import("../../tts/types.js").TtsSynthesisRequest,
3407
+ request: import("../tts/types.js").TtsSynthesisRequest,
3422
3408
  onChunk: (chunk: Buffer) => void,
3423
3409
  ) => Promise<void>;
3424
3410
  }): { synthesizedTexts: string[] } {
@@ -4234,415 +4220,6 @@ describe("call-controller", () => {
4234
4220
  });
4235
4221
  });
4236
4222
 
4237
- // ── Triage-and-escalate routing ─────────────────────────────────────
4238
-
4239
- describe("triage-and-escalate routing", () => {
4240
- interface LegOpts {
4241
- content: string;
4242
- routingLeg?: string;
4243
- spokenEscalationBridge?: string;
4244
- launchedAtMs?: number;
4245
- voiceTelemetry?: { sessionId: string; entry?: string };
4246
- callbacks?: {
4247
- persisted_user_message_id?: (id: string) => void;
4248
- persisted_assistant_message_id?: (id: string) => void;
4249
- tool_use_start?: (
4250
- toolName: string,
4251
- detail?: { toolUseId?: string },
4252
- ) => void;
4253
- tool_result?: (event: {
4254
- toolName: string;
4255
- toolUseId?: string;
4256
- isError?: boolean;
4257
- resultPreview: string;
4258
- }) => void;
4259
- };
4260
- onTextDelta: (t: string) => void;
4261
- onComplete: () => void;
4262
- signal?: AbortSignal;
4263
- }
4264
-
4265
- /**
4266
- * Script the bridge one leg per call: the front-door leg's tokens first,
4267
- * then the escalated leg's. Records every call's options and which legs
4268
- * the controller aborted.
4269
- */
4270
- function scriptLegs(
4271
- legs: string[][],
4272
- beforeComplete?: (opts: LegOpts, index: number) => void,
4273
- ) {
4274
- const calls: LegOpts[] = [];
4275
- const aborted: number[] = [];
4276
- mockStartVoiceTurn.mockImplementation(async (opts: LegOpts) => {
4277
- const index = calls.length;
4278
- calls.push(opts);
4279
- for (const token of legs[index] ?? []) {
4280
- opts.onTextDelta(token);
4281
- }
4282
- beforeComplete?.(opts, index);
4283
- opts.onComplete();
4284
- return { turnId: `run-${index}`, abort: () => aborted.push(index) };
4285
- });
4286
- return { calls, aborted };
4287
- }
4288
-
4289
- function spokenText(relay: MockTransport): string {
4290
- return relay.sentTokens
4291
- .filter((t) => !t.last)
4292
- .map((t) => t.token)
4293
- .join("");
4294
- }
4295
-
4296
- function assistantSpokePayload(sessionId: string): Record<string, unknown> {
4297
- const spoke = getCallEvents(sessionId).filter(
4298
- (e) => e.eventType === "assistant_spoke",
4299
- );
4300
- expect(spoke.length).toBe(1);
4301
- return JSON.parse(spoke[0].payloadJson) as Record<string, unknown>;
4302
- }
4303
-
4304
- test("a front-door answer runs one leg and is spoken verbatim", async () => {
4305
- const { calls, aborted } = scriptLegs([["Hello", " there."]]);
4306
- const { session, relay, controller } = setupController();
4307
-
4308
- await controller.handleCallerUtterance("Hi");
4309
-
4310
- expect(calls.length).toBe(1);
4311
- expect(calls[0].routingLeg).toBe("front-door");
4312
- expect(calls[0].content).toBe("Hi");
4313
- expect(calls[0].spokenEscalationBridge).toBeUndefined();
4314
- expect(aborted).toEqual([]);
4315
- expect(spokenText(relay)).toContain("Hello there.");
4316
- expect(assistantSpokePayload(session.id).text).toBe("Hello there.");
4317
-
4318
- controller.destroy();
4319
- });
4320
-
4321
- test("an escalate verdict speaks the holding phrase, aborts the front-door leg, and continues on the escalated leg", async () => {
4322
- const { calls, aborted } = scriptLegs([
4323
- // The verdict token may arrive split across deltas; the leading
4324
- // "[" is held until it classifies.
4325
- ["[", "1] Let me check", " that.", " Ignored past the cap."],
4326
- ["The answer is 42."],
4327
- ]);
4328
- const { session, relay, controller } = setupController();
4329
-
4330
- await controller.handleCallerUtterance("What is six times seven?");
4331
-
4332
- expect(calls.length).toBe(2);
4333
- expect(calls[0].routingLeg).toBe("front-door");
4334
- expect(calls[1].routingLeg).toBe("escalated");
4335
- expect(calls[1].content).toBe(ESCALATION_CONTINUATION_CONTENT);
4336
- expect(calls[1].spokenEscalationBridge).toBe("Let me check that.");
4337
- expect(aborted).toEqual([0]);
4338
-
4339
- const spoken = spokenText(relay);
4340
- expect(spoken).toContain("Let me check that.");
4341
- expect(spoken).toContain("The answer is 42.");
4342
- expect(spoken.indexOf("Let me check that.")).toBeLessThan(
4343
- spoken.indexOf("The answer is 42."),
4344
- );
4345
- expect(spoken).not.toContain("[1]");
4346
- expect(spoken).not.toContain("Ignored past the cap.");
4347
- expect(assistantSpokePayload(session.id).text).toBe(
4348
- "Let me check that. The answer is 42.",
4349
- );
4350
- // One end-of-turn signal for the whole turn, after the escalated leg.
4351
- expect(relay.sentTokens.filter((t) => t.last).length).toBe(1);
4352
-
4353
- controller.destroy();
4354
- });
4355
-
4356
- test("a bare escalate verdict speaks the canned bridge, audio-only", async () => {
4357
- const { calls } = scriptLegs([["[1]"], ["Forty-two."]]);
4358
- const { session, relay, controller } = setupController();
4359
-
4360
- await controller.handleCallerUtterance("What is six times seven?");
4361
-
4362
- expect(calls.length).toBe(2);
4363
- expect(calls[1].spokenEscalationBridge).toBe(FALLBACK_ESCALATION_BRIDGE);
4364
- expect(spokenText(relay)).toContain(FALLBACK_ESCALATION_BRIDGE);
4365
- // The canned bridge is not the model's speech: the turn's text (and the
4366
- // transcript row the bridge keeps) carry only the escalated answer.
4367
- expect(assistantSpokePayload(session.id).text).toBe("Forty-two.");
4368
-
4369
- controller.destroy();
4370
- });
4371
-
4372
- test("a leg cancelled mid-bridge never spawns the escalated leg", async () => {
4373
- const calls: LegOpts[] = [];
4374
- let releaseLeg: () => void = () => {};
4375
- mockStartVoiceTurn.mockImplementation(async (opts: LegOpts) => {
4376
- calls.push(opts);
4377
- opts.onTextDelta("[1] Let me");
4378
- await new Promise<void>((resolve) => {
4379
- releaseLeg = resolve;
4380
- opts.signal?.addEventListener("abort", () => resolve());
4381
- });
4382
- // The bridge reports a cancelled leg's completion too.
4383
- opts.onComplete();
4384
- return { turnId: "run-cancelled", abort: () => releaseLeg() };
4385
- });
4386
- const { relay, controller } = setupController();
4387
-
4388
- const turnPromise = controller.handleCallerUtterance("Hi");
4389
- for (let i = 0; i < 10; i++) {
4390
- await Promise.resolve();
4391
- }
4392
- expect(calls.length).toBe(1);
4393
-
4394
- controller.handleInterrupt();
4395
- await turnPromise;
4396
-
4397
- expect(calls.length).toBe(1);
4398
- expect(spokenText(relay)).toBe("");
4399
-
4400
- controller.destroy();
4401
- });
4402
-
4403
- test("passes the launch time, phone telemetry, and callbacks to the bridge", async () => {
4404
- const before = Date.now();
4405
- const { calls } = scriptLegs([["Done."]], (opts) => {
4406
- opts.callbacks?.persisted_user_message_id?.("user-row-1");
4407
- opts.callbacks?.tool_use_start?.("web_search", { toolUseId: "tu-1" });
4408
- opts.callbacks?.tool_result?.({
4409
- toolName: "web_search",
4410
- toolUseId: "tu-1",
4411
- isError: false,
4412
- resultPreview: "three results",
4413
- });
4414
- opts.callbacks?.persisted_assistant_message_id?.("assistant-row-1");
4415
- });
4416
- const { session, controller } = setupController();
4417
-
4418
- await controller.handleCallerUtterance("Look that up");
4419
-
4420
- expect(calls.length).toBe(1);
4421
- expect(calls[0].launchedAtMs).toBeGreaterThanOrEqual(before);
4422
- expect(calls[0].launchedAtMs).toBeLessThanOrEqual(Date.now());
4423
- expect(calls[0].voiceTelemetry).toEqual({
4424
- sessionId: session.id,
4425
- entry: "phone_inbound",
4426
- });
4427
-
4428
- const events = getCallEvents(session.id);
4429
- const started = events.filter((e) => e.eventType === "tool_use_started");
4430
- expect(started.length).toBe(1);
4431
- expect(JSON.parse(started[0].payloadJson)).toEqual({
4432
- toolName: "web_search",
4433
- toolUseId: "tu-1",
4434
- });
4435
- const completed = events.filter(
4436
- (e) => e.eventType === "tool_use_completed",
4437
- );
4438
- expect(completed.length).toBe(1);
4439
- expect(JSON.parse(completed[0].payloadJson)).toEqual({
4440
- toolName: "web_search",
4441
- toolUseId: "tu-1",
4442
- isError: false,
4443
- resultPreview: "three results",
4444
- });
4445
- expect(assistantSpokePayload(session.id)).toEqual({
4446
- text: "Done.",
4447
- userMessageId: "user-row-1",
4448
- assistantMessageId: "assistant-row-1",
4449
- });
4450
-
4451
- controller.destroy();
4452
- });
4453
-
4454
- test("the escalated leg's hidden continuation row is not the caller's utterance", async () => {
4455
- const { calls } = scriptLegs(
4456
- [["[1] One moment."], ["Here you go."]],
4457
- (opts, index) => {
4458
- opts.callbacks?.persisted_user_message_id?.(`user-row-${index}`);
4459
- opts.callbacks?.persisted_assistant_message_id?.(
4460
- `assistant-row-${index}`,
4461
- );
4462
- },
4463
- );
4464
- const { session, controller } = setupController();
4465
-
4466
- await controller.handleCallerUtterance("Hi");
4467
-
4468
- expect(calls.length).toBe(2);
4469
- expect(assistantSpokePayload(session.id)).toEqual({
4470
- text: "One moment. Here you go.",
4471
- userMessageId: "user-row-0",
4472
- assistantMessageId: "assistant-row-1",
4473
- });
4474
-
4475
- controller.destroy();
4476
- });
4477
-
4478
- test("native route: a canned bridge in a language the table lacks goes out as system copy", async () => {
4479
- scriptLegs([["[1]"], ["Forty-two."]]);
4480
- const { relay, controller } = setupController(undefined, {
4481
- resolveSynthesisLanguage: () => "ko",
4482
- });
4483
-
4484
- await controller.handleCallerUtterance("What is six times seven?");
4485
-
4486
- // The English fallback must not ride the caller's Korean hint.
4487
- expect(relay.systemCopyTokens).toEqual([
4488
- `${FALLBACK_ESCALATION_BRIDGE} `,
4489
- ]);
4490
- expect(spokenText(relay)).toContain("Forty-two.");
4491
-
4492
- controller.destroy();
4493
- });
4494
-
4495
- test("native route: a localized canned bridge rides the caller's language like model text", async () => {
4496
- scriptLegs([["[1]"], ["Cuarenta y dos."]]);
4497
- const { relay, controller } = setupController(undefined, {
4498
- resolveSynthesisLanguage: () => "es",
4499
- });
4500
-
4501
- await controller.handleCallerUtterance("¿Cuánto es seis por siete?");
4502
-
4503
- expect(relay.systemCopyTokens).toEqual([]);
4504
- expect(spokenText(relay)).toContain(
4505
- FALLBACK_ESCALATION_BRIDGE_BY_LANGUAGE.es,
4506
- );
4507
-
4508
- controller.destroy();
4509
- });
4510
-
4511
- test("synthesized route: the canned bridge carries its own language hint, the answer the caller's", async () => {
4512
- const requests: Array<{ text: string; language: string | undefined }> =
4513
- [];
4514
- registerFishAudioSegmentRecorder({
4515
- onSynthesizeStream: async (text, request) => {
4516
- requests.push({ text, language: request.language });
4517
- },
4518
- });
4519
- scriptLegs([["[1]"], ["Forty-two."]]);
4520
- const { controller } = setupController(undefined, {
4521
- resolveSynthesisLanguage: () => "ko",
4522
- });
4523
-
4524
- await controller.handleCallerUtterance("What is six times seven?");
4525
-
4526
- expect(requests).toEqual([
4527
- { text: FALLBACK_ESCALATION_BRIDGE, language: "en" },
4528
- { text: "Forty-two.", language: "ko" },
4529
- ]);
4530
-
4531
- controller.destroy();
4532
- });
4533
-
4534
- test("progress narration speaks into the escalated leg's tool loop, audio-only", async () => {
4535
- const narrationConfig: VoiceProgressConfig = {
4536
- enabled: true,
4537
- opsThreshold: 1,
4538
- idleIntervalMs: 60_000,
4539
- maxSilenceMs: 60_000,
4540
- longOpMs: 15_000,
4541
- minGapMs: 10,
4542
- generationTimeoutMs: 1_500,
4543
- };
4544
- const narrator: VoiceProgressNarrator = {
4545
- generateProgressText: async (input) => {
4546
- expect(input.transcriptSoFar).toBe("What is on my calendar?");
4547
- expect(input.currentOp?.toolName).toBe("calendar_list");
4548
- return "Pulling up your calendar now.";
4549
- },
4550
- };
4551
- const { session, relay, controller } = setupController(undefined, {
4552
- progressNarrator: narrator,
4553
- progressConfig: narrationConfig,
4554
- });
4555
- const calls: LegOpts[] = [];
4556
- mockStartVoiceTurn.mockImplementation(async (opts: LegOpts) => {
4557
- const index = calls.length;
4558
- calls.push(opts);
4559
- if (index === 0) {
4560
- opts.onTextDelta("[1] One moment.");
4561
- opts.onComplete();
4562
- return { turnId: "run-front", abort: () => {} };
4563
- }
4564
- // The escalated leg runs a tool; narration must land while it runs.
4565
- // The bridge just spoken is a floor holder, so the tool starts after
4566
- // the (test-short) minimum gap.
4567
- await new Promise((r) => setTimeout(r, 25));
4568
- opts.callbacks?.tool_use_start?.("calendar_list", { toolUseId: "c1" });
4569
- await pollUntil(() =>
4570
- relay.sentTokens.some((t) =>
4571
- t.token.includes("Pulling up your calendar now."),
4572
- ),
4573
- );
4574
- opts.callbacks?.tool_result?.({
4575
- toolName: "calendar_list",
4576
- toolUseId: "c1",
4577
- resultPreview: "two events",
4578
- });
4579
- opts.onTextDelta("You have two events today.");
4580
- opts.onComplete();
4581
- return { turnId: "run-escalated", abort: () => {} };
4582
- });
4583
-
4584
- await controller.handleCallerUtterance("What is on my calendar?");
4585
-
4586
- const spoken = spokenText(relay);
4587
- expect(spoken.indexOf("One moment.")).toBeLessThan(
4588
- spoken.indexOf("Pulling up your calendar now."),
4589
- );
4590
- expect(spoken.indexOf("Pulling up your calendar now.")).toBeLessThan(
4591
- spoken.indexOf("You have two events today."),
4592
- );
4593
- // Narration is audio-only: the turn's recorded text is the model's.
4594
- expect(assistantSpokePayload(session.id).text).toBe(
4595
- "One moment. You have two events today.",
4596
- );
4597
-
4598
- controller.destroy();
4599
- });
4600
-
4601
- test("no narration without a narrator", async () => {
4602
- const { relay, controller } = setupController(undefined, {
4603
- progressNarrator: null,
4604
- progressConfig: {
4605
- enabled: true,
4606
- opsThreshold: 1,
4607
- idleIntervalMs: 60_000,
4608
- maxSilenceMs: 60_000,
4609
- longOpMs: 15_000,
4610
- minGapMs: 10,
4611
- generationTimeoutMs: 1_500,
4612
- },
4613
- });
4614
- scriptLegs([["Done."]], (opts) => {
4615
- opts.callbacks?.tool_use_start?.("web_search", { toolUseId: "t1" });
4616
- opts.callbacks?.tool_result?.({
4617
- toolName: "web_search",
4618
- toolUseId: "t1",
4619
- resultPreview: "ok",
4620
- });
4621
- });
4622
-
4623
- await controller.handleCallerUtterance("Look it up");
4624
-
4625
- expect(spokenText(relay)).toBe("Done.");
4626
-
4627
- controller.destroy();
4628
- });
4629
-
4630
- test("an outbound call reports the outbound entry", async () => {
4631
- const { calls } = scriptLegs([["Hello, this is Ava."]]);
4632
- const { session, controller } = setupController("Book a table for two");
4633
-
4634
- await controller.startInitialGreeting();
4635
-
4636
- expect(calls.length).toBe(1);
4637
- expect(calls[0].voiceTelemetry).toEqual({
4638
- sessionId: session.id,
4639
- entry: "phone_outbound",
4640
- });
4641
-
4642
- controller.destroy();
4643
- });
4644
- });
4645
-
4646
4223
  // ── handleBargeIn ───────────────────────────────────────────────────
4647
4224
 
4648
4225
  describe("handleBargeIn", () => {
@@ -4665,11 +4242,10 @@ describe("call-controller", () => {
4665
4242
  controller.destroy();
4666
4243
  });
4667
4244
 
4668
- test("handleBargeIn accepts a barge-in while still processing and aborts the silent turn", async () => {
4245
+ test("handleBargeIn returns false and does not abort while still processing (no output yet)", async () => {
4669
4246
  // Simulate a turn stuck waiting for the processing lock: no tokens
4670
- // emitted, no completion. The controller stays in `processing`, and a
4671
- // sustained caller barge-in (the transport's guard has already
4672
- // vouched for it) cuts the thinking turn off before it speaks.
4247
+ // emitted, no completion. The controller must stay in `processing` and
4248
+ // NOT flip to `speaking`, so barge-in can't abort a silent turn.
4673
4249
  mockStartVoiceTurn.mockImplementation(
4674
4250
  async (opts: {
4675
4251
  onTextDelta: (t: string) => void;
@@ -4694,17 +4270,18 @@ describe("call-controller", () => {
4694
4270
 
4695
4271
  const onAccepted = mock(() => {});
4696
4272
  const bargeResult = controller.handleBargeIn(onAccepted);
4697
- expect(bargeResult).toBe(true);
4698
- expect(onAccepted).toHaveBeenCalledTimes(1);
4699
- // The turn is aborted; nothing was spoken, so no end-of-turn token.
4700
- expect(controller.getState()).toBe("idle");
4273
+ expect(bargeResult).toBe(false);
4274
+ expect(onAccepted).not.toHaveBeenCalled();
4275
+ // Still processing (not aborted), and no interrupt/end-of-turn token sent.
4276
+ expect(controller.getState()).toBe("processing");
4701
4277
  const endTokens = relay.sentTokens.filter(
4702
4278
  (t) => t.last === true && t.token === "",
4703
4279
  );
4704
4280
  expect(endTokens.length).toBe(0);
4705
4281
 
4706
- await turnPromise.catch(() => {});
4282
+ // Cleanup: abort the pending turn
4707
4283
  controller.destroy();
4284
+ await turnPromise.catch(() => {});
4708
4285
  });
4709
4286
 
4710
4287
  test("stays in processing until first token, then flips to speaking and barge-in is accepted", async () => {
@@ -4746,7 +4323,9 @@ describe("call-controller", () => {
4746
4323
  await Promise.resolve();
4747
4324
  }
4748
4325
 
4749
- // Before any token: processing (no audio out yet).
4326
+ // Before any token: processing, barge-in ignored (turn not aborted).
4327
+ expect(controller.getState()).toBe("processing");
4328
+ expect(controller.handleBargeIn()).toBe(false);
4750
4329
  expect(controller.getState()).toBe("processing");
4751
4330
 
4752
4331
  // Release the first token → controller flips to speaking.
@@ -4764,7 +4343,7 @@ describe("call-controller", () => {
4764
4343
  await turnPromise.catch(() => {});
4765
4344
  });
4766
4345
 
4767
- test("buffering transport (media-stream): streamed tokens do not flip to speaking; a barge-in still cuts the thinking turn off", async () => {
4346
+ test("buffering transport (media-stream): streamed tokens do not flip to speaking; barge-in is rejected and the turn still delivers", async () => {
4768
4347
  // Simulates the media-stream transport: sendTextToken(.., false)
4769
4348
  // only buffers; audio starts later. The controller must stay in
4770
4349
  // `processing` until the transport's audio-start signal fires, so
@@ -4808,17 +4387,17 @@ describe("call-controller", () => {
4808
4387
  expect(controller.getState()).toBe("processing");
4809
4388
  expect(audioStartCallback).not.toBeNull();
4810
4389
 
4811
- // Sustained caller speech mid-generation: the buffered, unspoken turn
4812
- // is cut off like a spoken one would be, and its text is discarded.
4813
- expect(controller.handleBargeIn()).toBe(true);
4814
- expect(controller.getState()).toBe("idle");
4390
+ // VAD speech-start mid-generation → barge-in rejected, turn intact.
4391
+ expect(controller.handleBargeIn()).toBe(false);
4392
+ expect(controller.getState()).toBe("processing");
4815
4393
 
4816
- await turnPromise.catch(() => {});
4817
- // No end-of-turn token: nothing had been spoken.
4394
+ // The turn completes and delivers its end-of-turn signal.
4395
+ releaseTurn();
4396
+ await turnPromise;
4818
4397
  const endTokens = relay.sentTokens.filter(
4819
4398
  (t) => t.last === true && t.token === "",
4820
4399
  );
4821
- expect(endTokens.length).toBe(0);
4400
+ expect(endTokens.length).toBe(1);
4822
4401
 
4823
4402
  controller.destroy();
4824
4403
  });