@vellumai/assistant 0.12.1-dev.202609141913.5e16607 → 0.12.1-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 (439) 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/docs/guardian-request-flow.md +0 -16
  5. package/openapi.yaml +49 -291
  6. package/package.json +1 -1
  7. package/src/__tests__/always-loaded-tools-guard.test.ts +10 -18
  8. package/src/__tests__/app-compiler.test.ts +0 -6
  9. package/src/__tests__/app-control-tool-schemas.test.ts +2 -5
  10. package/src/__tests__/app-dir-path-guard.test.ts +3 -1
  11. package/src/__tests__/app-source-watcher.test.ts +218 -0
  12. package/src/{calls/__tests__ → __tests__}/call-controller.test.ts +82 -503
  13. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +2 -5
  14. package/src/__tests__/computer-use-tools.test.ts +5 -17
  15. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  16. package/src/__tests__/config-schema.test.ts +29 -28
  17. package/src/__tests__/config-watcher-skill-reseed.test.ts +359 -0
  18. package/src/__tests__/config-watcher.test.ts +8 -23
  19. package/src/__tests__/connection-model-compat.test.ts +2 -2
  20. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  21. package/src/__tests__/conversation-pairing.test.ts +0 -26
  22. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +10 -4
  23. package/src/__tests__/conversation-queue.test.ts +207 -48
  24. package/src/__tests__/conversation-retry-discard.test.ts +1 -54
  25. package/src/__tests__/conversation-skill-tools.test.ts +0 -27
  26. package/src/__tests__/conversation-surfaces-app-control.test.ts +3 -8
  27. package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -67
  28. package/src/__tests__/conversation-title-service.test.ts +10 -14
  29. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +5 -3
  30. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -51
  31. package/src/__tests__/credential-record-retire.test.ts +1 -171
  32. package/src/__tests__/credential-routes.test.ts +3 -212
  33. package/src/__tests__/credential-security-invariants.test.ts +2 -4
  34. package/src/__tests__/cu-unified-flow.test.ts +2 -8
  35. package/src/__tests__/document-sync-tags.test.ts +0 -25
  36. package/src/__tests__/drain-kick-guard.test.ts +2 -0
  37. package/src/__tests__/drain-requeue-on-contention.test.ts +6 -0
  38. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  39. package/src/__tests__/host-bash-proxy.test.ts +45 -26
  40. package/src/__tests__/host-browser-proxy.test.ts +17 -6
  41. package/src/__tests__/host-file-proxy-targeted.test.ts +0 -23
  42. package/src/__tests__/host-file-proxy.test.ts +23 -11
  43. package/src/__tests__/host-transfer-proxy-targeted.test.ts +9 -30
  44. package/src/__tests__/llm-catalog-parity.test.ts +0 -25
  45. package/src/__tests__/mcp-auth-routes.test.ts +9 -6
  46. package/src/__tests__/mcp-config-secret-boundary.test.ts +136 -78
  47. package/src/__tests__/mcp-health-check.test.ts +11 -8
  48. package/src/__tests__/mcp-list-plugin-servers.test.ts +10 -8
  49. package/src/{calls/__tests__ → __tests__}/media-stream-audio-transcode.test.ts +1 -1
  50. package/src/{calls/__tests__ → __tests__}/media-stream-output.test.ts +8 -37
  51. package/src/{calls/__tests__ → __tests__}/media-stream-parser.test.ts +1 -1
  52. package/src/{calls/__tests__ → __tests__}/media-stream-server-integration.test.ts +57 -150
  53. package/src/{calls/__tests__ → __tests__}/media-stream-stt-session.test.ts +5 -5
  54. package/src/{calls/__tests__ → __tests__}/media-turn-detector.test.ts +1 -1
  55. package/src/__tests__/messages-after-tiebreaker.test.ts +0 -91
  56. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  57. package/src/__tests__/messaging-send-tool.test.ts +548 -355
  58. package/src/__tests__/model-intents.test.ts +0 -3
  59. package/src/__tests__/notification-vellum-adapter.test.ts +2 -85
  60. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  61. package/src/__tests__/platform-callback-registration.test.ts +16 -8
  62. package/src/__tests__/platform-client-verify-credential.test.ts +0 -2
  63. package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -13
  64. package/src/__tests__/script-proxy-certs.test.ts +0 -35
  65. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  66. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  67. package/src/__tests__/speaker-identification.test.ts +56 -0
  68. package/src/__tests__/subagent-tool-gate-mode.test.ts +3 -19
  69. package/src/__tests__/subagent-tools.test.ts +2 -3
  70. package/src/__tests__/telephony-tts-guard.test.ts +3 -38
  71. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +0 -4
  72. package/src/{calls/__tests__/voice-session-bridge-turn-flow.test.ts → __tests__/voice-session-bridge.test.ts} +12 -20
  73. package/src/__tests__/watch-retro-tool-availability.test.ts +17 -8
  74. package/src/__tests__/workspace-git-service.test.ts +0 -26
  75. package/src/acp/__tests__/agent-process.test.ts +4 -258
  76. package/src/acp/__tests__/auth-required.test.ts +0 -96
  77. package/src/acp/__tests__/client-handler.test.ts +2 -90
  78. package/src/acp/__tests__/session-manager-persistence.test.ts +1 -5
  79. package/src/acp/__tests__/session-manager-resume.test.ts +6 -270
  80. package/src/acp/__tests__/session-manager-usage.test.ts +2 -4
  81. package/src/acp/__tests__/session-manager.test.ts +4 -895
  82. package/src/acp/agent-process.test.ts +1 -4
  83. package/src/acp/agent-process.ts +9 -94
  84. package/src/acp/auth-required.ts +1 -50
  85. package/src/acp/auto-install.test.ts +7 -9
  86. package/src/acp/client-handler.ts +3 -16
  87. package/src/acp/resolve-agent.test.ts +6 -220
  88. package/src/acp/resolve-agent.ts +23 -88
  89. package/src/acp/session-manager.test.ts +3 -196
  90. package/src/acp/session-manager.ts +18 -441
  91. package/src/acp/types.ts +5 -50
  92. package/src/api/events/app-files-changed.ts +3 -4
  93. package/src/api/events/notification-intent.ts +0 -2
  94. package/src/api/index.ts +0 -6
  95. package/src/apps/app-store.ts +1 -1
  96. package/src/bundler/app-compiler.ts +0 -15
  97. package/src/calls/__tests__/voice-control-protocol.test.ts +25 -58
  98. package/src/calls/__tests__/voice-session-bridge.test.ts +1 -37
  99. package/src/calls/__tests__/voice-triage-escalate.test.ts +0 -105
  100. package/src/calls/call-constants.ts +4 -24
  101. package/src/calls/call-controller.ts +199 -499
  102. package/src/calls/call-transport.ts +0 -16
  103. package/src/calls/media-stream-output.ts +0 -32
  104. package/src/calls/media-stream-server.ts +30 -82
  105. package/src/calls/media-stream-stt-session.ts +0 -15
  106. package/src/calls/resolve-call-tts-provider.ts +2 -33
  107. package/src/calls/speaker-identification.ts +233 -0
  108. package/src/calls/tts-call-strategy.ts +5 -9
  109. package/src/calls/types.ts +0 -2
  110. package/src/calls/voice-control-protocol.ts +21 -53
  111. package/src/calls/voice-session-bridge.ts +46 -59
  112. package/src/calls/voice-triage-escalate.ts +56 -175
  113. package/src/cli/commands/apps.help.ts +13 -67
  114. package/src/cli/commands/apps.ts +0 -183
  115. package/src/cli/commands/bash.help.ts +13 -8
  116. package/src/cli/commands/bash.ts +25 -7
  117. package/src/cli/commands/channels/index.help.ts +2 -70
  118. package/src/cli/commands/channels/index.ts +3 -16
  119. package/src/cli/commands/credentials.help.ts +2 -6
  120. package/src/cli/commands/credentials.ts +2 -2
  121. package/src/cli/commands/mcp.help.ts +8 -8
  122. package/src/cli/commands/mcp.ts +1 -1
  123. package/src/cli/commands/roadmap.help.ts +1 -5
  124. package/src/cli/commands/roadmap.ts +1 -1
  125. package/src/cli/lib/bundled-marketplace.json +90 -103
  126. package/src/config/__tests__/default-profile-catalog.test.ts +0 -22
  127. package/src/config/__tests__/memory-retrospective-schema.test.ts +1 -15
  128. package/src/config/__tests__/platform-rehydration.test.ts +9 -99
  129. package/src/config/acp-defaults.test.ts +3 -13
  130. package/src/config/acp-defaults.ts +6 -23
  131. package/src/config/acp-schema.ts +2 -30
  132. package/src/config/bundled-skills/acp/SKILL.md +14 -22
  133. package/src/config/bundled-skills/acp/TOOLS.json +1 -5
  134. package/src/config/bundled-skills/app-builder/SKILL.md +9 -12
  135. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  136. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  137. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  138. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +139 -67
  139. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  140. package/src/config/bundled-skills/schedule/SKILL.md +4 -3
  141. package/src/config/bundled-skills/screen-annotation/SKILL.md +1 -8
  142. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +1 -4
  143. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  144. package/src/config/default-profile-catalog.ts +1 -1
  145. package/src/config/env.ts +2 -2
  146. package/src/config/feature-flag-registry.json +12 -24
  147. package/src/config/loader.ts +0 -1
  148. package/src/config/memory-v3-gate.test.ts +0 -31
  149. package/src/config/memory-v3-gate.ts +16 -20
  150. package/src/config/platform-rehydration.ts +27 -11
  151. package/src/config/schema.ts +2 -2
  152. package/src/config/schemas/__tests__/live-voice.test.ts +158 -8
  153. package/src/config/schemas/calls.ts +28 -1
  154. package/src/config/schemas/live-voice.ts +173 -1
  155. package/src/config/schemas/mcp.ts +1 -1
  156. package/src/config/schemas/memory-retrospective.ts +0 -10
  157. package/src/config/schemas/services.ts +0 -8
  158. package/src/config/schemas/skills.ts +14 -0
  159. package/src/credential-execution/process-manager.test.ts +19 -18
  160. package/src/daemon/__tests__/conversation-tool-setup.test.ts +121 -66
  161. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +1 -4
  162. package/src/daemon/app-source-watcher.ts +218 -0
  163. package/src/daemon/config-watcher.ts +194 -13
  164. package/src/daemon/conversation-process.ts +64 -35
  165. package/src/daemon/conversation-skill-tools.ts +31 -13
  166. package/src/daemon/conversation-store.ts +1 -1
  167. package/src/daemon/conversation-surfaces.ts +8 -10
  168. package/src/daemon/conversation-tool-setup.ts +43 -37
  169. package/src/daemon/conversation.ts +3 -0
  170. package/src/daemon/host-bash-proxy.ts +12 -8
  171. package/src/daemon/host-browser-proxy.ts +1 -15
  172. package/src/daemon/host-file-proxy.ts +10 -8
  173. package/src/daemon/host-transfer-proxy.ts +18 -14
  174. package/src/daemon/lifecycle.ts +5 -0
  175. package/src/daemon/mcp-reload-service.ts +5 -5
  176. package/src/daemon/message-types/sync.ts +0 -1
  177. package/src/daemon/orphan-reaper.ts +1 -1
  178. package/src/daemon/providers-setup.ts +1 -1
  179. package/src/daemon/shutdown-handlers.ts +9 -1
  180. package/src/daemon/skill-memory-refresh.ts +0 -64
  181. package/src/daemon/tool-side-effects.ts +18 -7
  182. package/src/home/__tests__/feed-writer.test.ts +0 -22
  183. package/src/home/feed-writer.ts +2 -11
  184. package/src/hooks/types.ts +0 -33
  185. package/src/inbound/platform-callback-registration.ts +5 -4
  186. package/src/live-voice/__tests__/live-voice-metrics.test.ts +3 -0
  187. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  188. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  189. package/src/{calls → live-voice}/__tests__/progress-narration.test.ts +4 -4
  190. package/src/{calls → live-voice}/__tests__/progress-phrases.test.ts +1 -1
  191. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +69 -13
  192. package/src/live-voice/live-voice-session.ts +693 -213
  193. package/src/{calls → live-voice}/progress-narration.ts +2 -2
  194. package/src/live-voice/protocol.ts +2 -0
  195. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  196. package/src/mcp/effective-config.ts +3 -3
  197. package/src/mcp/mcp-header-store.ts +45 -29
  198. package/src/mcp/startup.ts +4 -8
  199. package/src/messaging/provider.ts +3 -10
  200. package/src/messaging/providers/callback-routing.ts +0 -18
  201. package/src/messaging/providers/channel-transport.ts +0 -56
  202. package/src/messaging/providers/discord/transport.ts +0 -17
  203. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +17 -6
  204. package/src/messaging/providers/slack/adapter.ts +44 -7
  205. package/src/messaging/providers/slack/transport.ts +0 -16
  206. package/src/messaging/providers/telegram-bot/adapter.ts +48 -1
  207. package/src/messaging/providers/telegram-bot/client.ts +26 -0
  208. package/src/messaging/providers/telegram-bot/transport.ts +0 -20
  209. package/src/messaging/providers/whatsapp/adapter.ts +48 -1
  210. package/src/messaging/providers/whatsapp/client.ts +29 -0
  211. package/src/messaging/providers/whatsapp/transport.ts +0 -18
  212. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +0 -8
  213. package/src/notifications/__tests__/assistant-reply-producer.test.ts +0 -55
  214. package/src/notifications/__tests__/guardian-feed-projection.test.ts +2 -158
  215. package/src/notifications/__tests__/home-feed-side-effect.test.ts +3 -153
  216. package/src/notifications/__tests__/schedule-result-producer.test.ts +3 -99
  217. package/src/notifications/adapters/macos.ts +0 -3
  218. package/src/notifications/assistant-reply-producer.ts +3 -8
  219. package/src/notifications/conversation-pairing.ts +16 -36
  220. package/src/notifications/delivered-post-record.ts +1 -11
  221. package/src/notifications/guardian-feed-projection.ts +5 -125
  222. package/src/notifications/home-feed-side-effect.ts +7 -55
  223. package/src/notifications/schedule-result-producer.ts +22 -66
  224. package/src/oauth/__tests__/seed-providers-managed.test.ts +0 -54
  225. package/src/oauth/byo-connection.test.ts +0 -45
  226. package/src/oauth/byo-connection.ts +6 -36
  227. package/src/oauth/connection-resolver.test.ts +0 -73
  228. package/src/oauth/connection-resolver.ts +2 -95
  229. package/src/oauth/seed-providers.ts +0 -165
  230. package/src/outbound-proxy/certs.ts +5 -3
  231. package/src/permissions/checker.test.ts +6 -11
  232. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  233. package/src/persistence/conversation-crud.ts +63 -71
  234. package/src/persistence/conversation-lineage.ts +4 -12
  235. package/src/persistence/conversation-title-service.ts +26 -9
  236. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  237. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  238. package/src/persistence/message-reads.ts +0 -30
  239. package/src/persistence/migrations/157-invite-contact-id.ts +6 -18
  240. package/src/persistence/schema/memory-core.ts +0 -5
  241. package/src/platform/client.test.ts +2 -4
  242. package/src/platform/client.ts +10 -2
  243. package/src/plugin-api/constants.ts +0 -2
  244. package/src/plugin-api/index.ts +0 -1
  245. package/src/plugin-api/types.ts +0 -2
  246. package/src/plugins/defaults/index.ts +0 -2
  247. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +0 -46
  248. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +5 -9
  249. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +10 -20
  250. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +0 -219
  251. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +0 -31
  252. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +0 -63
  253. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +0 -4
  254. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -4
  255. package/src/plugins/defaults/memory/hooks/init.ts +0 -10
  256. package/src/plugins/defaults/memory/hooks/shutdown.ts +0 -2
  257. package/src/plugins/defaults/memory/jobs-worker.ts +0 -13
  258. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +60 -34
  259. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +5 -3
  260. package/src/plugins/defaults/memory/memory-retrospective-job.ts +3 -12
  261. package/src/plugins/defaults/memory/memory-retrospective-state.ts +71 -312
  262. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +1 -2
  263. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +3 -5
  264. package/src/plugins/defaults/memory/startup.ts +1 -6
  265. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +9 -7
  266. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  267. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +0 -89
  268. package/src/plugins/defaults/memory/worker.ts +0 -6
  269. package/src/plugins/mcp-servers.ts +75 -15
  270. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  271. package/src/providers/gemini/client.ts +23 -9
  272. package/src/providers/inference/auth.ts +3 -2
  273. package/src/providers/inference/connection-availability.ts +8 -27
  274. package/src/providers/inference/resolve-auth.ts +4 -22
  275. package/src/providers/model-catalog.ts +0 -34
  276. package/src/providers/model-intents.ts +5 -10
  277. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +18 -146
  278. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +1 -83
  279. package/src/providers/openai/chat-completions-provider.ts +78 -192
  280. package/src/providers/openai/codex-models.ts +4 -0
  281. package/src/runtime/AGENTS.md +0 -2
  282. package/src/runtime/auth/same-actor.ts +3 -38
  283. package/src/runtime/http-server.ts +151 -160
  284. package/src/runtime/routes/__tests__/acp-routes.test.ts +4 -159
  285. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +1 -108
  286. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  287. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +12 -16
  288. package/src/runtime/routes/acp-routes.test.ts +0 -15
  289. package/src/runtime/routes/acp-routes.ts +9 -72
  290. package/src/runtime/routes/app-management-routes.ts +0 -73
  291. package/src/runtime/routes/app-routes.ts +1 -1
  292. package/src/runtime/routes/conversation-list-routes.ts +3 -14
  293. package/src/runtime/routes/conversation-query-routes.ts +0 -40
  294. package/src/runtime/routes/credential-routes.ts +10 -19
  295. package/src/runtime/routes/debug-bash-routes.ts +173 -0
  296. package/src/runtime/routes/diagnostics-routes.ts +3 -55
  297. package/src/runtime/routes/index.ts +2 -4
  298. package/src/runtime/routes/log-export-routes.ts +0 -2
  299. package/src/runtime/routes/mcp-auth-routes.ts +47 -39
  300. package/src/runtime/routes/migration-routes.ts +20 -6
  301. package/src/runtime/routes/platform-routes.ts +15 -18
  302. package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -28
  303. package/src/runtime/services/conversation-serializer.ts +3 -16
  304. package/src/runtime/sync/resource-sync-events.ts +1 -2
  305. package/src/schedule/worker.ts +1 -1
  306. package/src/security/ces-rpc-record-backend.ts +0 -7
  307. package/src/subagent/consult-context.ts +25 -12
  308. package/src/tools/acp/list-agents.test.ts +1 -3
  309. package/src/tools/acp/spawn.test.ts +11 -146
  310. package/src/tools/acp/spawn.ts +3 -21
  311. package/src/tools/acp/status.ts +3 -40
  312. package/src/tools/app-control/skill-proxy-bridge.ts +1 -2
  313. package/src/tools/capability-offer.test.ts +2 -22
  314. package/src/tools/capability-offer.ts +1 -16
  315. package/src/tools/computer-use/definitions.ts +2 -4
  316. package/src/tools/computer-use/skill-proxy-bridge.ts +1 -2
  317. package/src/tools/credentials/metadata-store.ts +0 -100
  318. package/src/tools/host-filesystem/edit.test.ts +1 -2
  319. package/src/tools/host-filesystem/edit.ts +2 -2
  320. package/src/tools/host-filesystem/read.test.ts +1 -2
  321. package/src/tools/host-filesystem/read.ts +2 -2
  322. package/src/tools/host-filesystem/transfer.test.ts +1 -2
  323. package/src/tools/host-filesystem/transfer.ts +2 -2
  324. package/src/tools/host-filesystem/write.test.ts +1 -2
  325. package/src/tools/host-filesystem/write.ts +2 -2
  326. package/src/tools/host-terminal/host-shell.test.ts +0 -16
  327. package/src/tools/host-terminal/host-shell.ts +20 -4
  328. package/src/tools/policy-context.ts +7 -6
  329. package/src/tools/subagent/spawn.ts +8 -5
  330. package/src/tools/watch/watch-retro-report.ts +8 -0
  331. package/src/util/__tests__/language-subtag.test.ts +1 -23
  332. package/src/util/fs-watcher-error.ts +36 -0
  333. package/src/util/language-subtag.ts +0 -16
  334. package/src/util/platform.ts +0 -8
  335. package/src/workspace/git-service.ts +54 -5
  336. package/src/workspace/migrations/registry.ts +0 -6
  337. package/scripts/smoke-desktop-window-theme.ts +0 -186
  338. package/src/__tests__/git-porcelain.test.ts +0 -47
  339. package/src/__tests__/helpers/set-workspace-mcp.ts +0 -11
  340. package/src/__tests__/message-deleted-hook-dispatch.test.ts +0 -119
  341. package/src/__tests__/resolve-app.test.ts +0 -135
  342. package/src/__tests__/service-account-token.test.ts +0 -247
  343. package/src/__tests__/source-fingerprint.test.ts +0 -184
  344. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +0 -336
  345. package/src/acp/__tests__/helpers/acp-model-option.ts +0 -51
  346. package/src/acp/model-config.test.ts +0 -166
  347. package/src/acp/model-config.ts +0 -138
  348. package/src/api/events/acp-session-model-update.test.ts +0 -88
  349. package/src/api/events/acp-session-model-update.ts +0 -46
  350. package/src/apps/resolve-app.ts +0 -81
  351. package/src/apps/source-fingerprint.ts +0 -266
  352. package/src/calls/__tests__/barge-in-guard.test.ts +0 -81
  353. package/src/calls/__tests__/voice-leg-coordinator.test.ts +0 -229
  354. package/src/calls/__tests__/voice-progress-cadence.test.ts +0 -274
  355. package/src/calls/barge-in-guard.ts +0 -118
  356. package/src/calls/voice-leg-coordinator.ts +0 -189
  357. package/src/calls/voice-progress-cadence.ts +0 -423
  358. package/src/cli/commands/__tests__/apps.test.ts +0 -245
  359. package/src/cli/commands/__tests__/bash.test.ts +0 -53
  360. package/src/cli/commands/__tests__/roadmap.help.test.ts +0 -19
  361. package/src/cli/commands/channels/__tests__/send.test.ts +0 -218
  362. package/src/cli/commands/channels/send.ts +0 -128
  363. package/src/config/__tests__/platform-identity.test.ts +0 -255
  364. package/src/config/acp-agent-write.ts +0 -43
  365. package/src/config/acp-schema.test.ts +0 -81
  366. package/src/config/platform-identity.ts +0 -201
  367. package/src/config/schemas/__tests__/voice.test.ts +0 -148
  368. package/src/config/schemas/voice.ts +0 -150
  369. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +0 -133
  370. package/src/daemon/app-change-notify.ts +0 -45
  371. package/src/desktop/__tests__/fake-desktop.ts +0 -119
  372. package/src/desktop/desktop-chrome-policy.test.ts +0 -71
  373. package/src/desktop/desktop-chrome-policy.ts +0 -42
  374. package/src/desktop/desktop-dependencies.test.ts +0 -102
  375. package/src/desktop/desktop-dependencies.ts +0 -306
  376. package/src/desktop/desktop-feature.test.ts +0 -18
  377. package/src/desktop/desktop-feature.ts +0 -19
  378. package/src/desktop/desktop-panel-config.test.ts +0 -135
  379. package/src/desktop/desktop-panel-config.ts +0 -149
  380. package/src/desktop/desktop-session-manager.test.ts +0 -627
  381. package/src/desktop/desktop-session-manager.ts +0 -814
  382. package/src/desktop/desktop-stream-bridge.test.ts +0 -395
  383. package/src/desktop/desktop-stream-bridge.ts +0 -214
  384. package/src/desktop/desktop-wallpaper.test.ts +0 -191
  385. package/src/desktop/desktop-wallpaper.ts +0 -113
  386. package/src/desktop/desktop-window-theme.ts +0 -170
  387. package/src/i18n/AGENTS.md +0 -22
  388. package/src/i18n/__tests__/i18n.test.ts +0 -108
  389. package/src/i18n/index.ts +0 -16
  390. package/src/i18n/locales.ts +0 -93
  391. package/src/i18n/messages.ts +0 -61
  392. package/src/i18n/title.ts +0 -25
  393. package/src/mcp/__tests__/workspace-mcp-config.test.ts +0 -135
  394. package/src/mcp/spec-schema.ts +0 -111
  395. package/src/mcp/workspace-mcp-config.ts +0 -188
  396. package/src/messaging/providers/__tests__/proactive-address.test.ts +0 -95
  397. package/src/persistence/conversation-title-placeholders.ts +0 -23
  398. package/src/persistence/message-cursor.ts +0 -78
  399. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +0 -149
  400. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +0 -118
  401. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +0 -83
  402. package/src/plugins/defaults/memory/hooks/message-deleted.ts +0 -21
  403. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +0 -126
  404. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +0 -79
  405. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +0 -28
  406. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +0 -9
  407. package/src/providers/gemini-thought-signature.test.ts +0 -63
  408. package/src/providers/gemini-thought-signature.ts +0 -44
  409. package/src/providers/inference/service-account-token.ts +0 -248
  410. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +0 -613
  411. package/src/providers/openai/__tests__/google-thought-signature.test.ts +0 -235
  412. package/src/providers/openai/google-thought-signature.ts +0 -320
  413. package/src/runtime/__tests__/channel-send.test.ts +0 -443
  414. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +0 -89
  415. package/src/runtime/__tests__/live-voice-upgrade.test.ts +0 -58
  416. package/src/runtime/__tests__/runtime-ws-test-utils.ts +0 -72
  417. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +0 -59
  418. package/src/runtime/channel-send.ts +0 -316
  419. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +0 -205
  420. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +0 -127
  421. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
  422. package/src/runtime/routes/channel-send-routes.ts +0 -85
  423. package/src/runtime/routes/desktop-setup-routes.test.ts +0 -74
  424. package/src/runtime/routes/desktop-setup-routes.ts +0 -35
  425. package/src/security/__tests__/ces-http-record-backend.test.ts +0 -137
  426. package/src/security/ces-http-record-backend.ts +0 -120
  427. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +0 -60
  428. package/src/skills/workspace-skill-md-signature.ts +0 -50
  429. package/src/tools/acp/status.test.ts +0 -94
  430. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +0 -57
  431. package/src/tools/terminal/sanitized-bash.ts +0 -105
  432. package/src/util/ca-bundle.ts +0 -13
  433. package/src/workspace/git-porcelain.ts +0 -66
  434. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +0 -258
  435. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +0 -78
  436. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +0 -214
  437. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +0 -102
  438. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +0 -199
  439. /package/src/{calls → live-voice}/progress-phrases.ts +0 -0
@@ -1,189 +0,0 @@
1
- /**
2
- * The front-door leg coordinator, shared by the live-voice session and the
3
- * phone call controller.
4
- *
5
- * A triaged voice turn opens on a fast, toolless front-door leg whose
6
- * leading tokens carry its verdict (see `voice-triage-escalate.ts`). The
7
- * coordinator reads that leg's raw stream through the verdict machine and
8
- * sequences what follows: an answer is released as speech, a hold discards
9
- * a speculative turn, and an escalate verdict hands the turn off to the
10
- * escalated leg with a spoken bridge. The hand-off order is the part that
11
- * must not drift between drivers: pause narration, abort the front-door
12
- * leg, resolve and speak the bridge, mark it as the floor holder, start the
13
- * escalated leg, re-arm narration. A leg that was cancelled never hands off.
14
- *
15
- * Everything transport-specific comes in through {@link FrontDoorLegHost}:
16
- * how answer text and the bridge are spoken, how a leg is started or
17
- * aborted, and the speculative hold and commit that only live voice has.
18
- */
19
-
20
- import type { ProgressCadence } from "./voice-progress-cadence.js";
21
- import {
22
- createFrontDoorVerdictMachine,
23
- ESCALATION_CONTINUATION_CONTENT,
24
- resolveSpokenEscalationBridge,
25
- } from "./voice-triage-escalate.js";
26
-
27
- /** The phrase spoken across the hand-off, as {@link resolveSpokenEscalationBridge} settles it. */
28
- export type SpokenEscalationBridge = ReturnType<
29
- typeof resolveSpokenEscalationBridge
30
- >;
31
-
32
- /** The leg that answers for real after the front-door leg handed off. */
33
- export interface EscalatedLeg {
34
- content: string;
35
- routingLeg: "escalated";
36
- /**
37
- * The bridge the listener heard, quoted to the escalated continuation
38
- * rule so the quality model does not re-announce it.
39
- */
40
- spokenEscalationBridge: string;
41
- }
42
-
43
- /**
44
- * The escalated leg for a hand-off whose bridge was `spokenBridge`. The
45
- * bridge pins this leg to the conversation's own profile, so no profile
46
- * override rides along.
47
- */
48
- export function escalatedLegFor(spokenBridge: string): EscalatedLeg {
49
- return {
50
- content: ESCALATION_CONTINUATION_CONTENT,
51
- routingLeg: "escalated",
52
- spokenEscalationBridge: spokenBridge,
53
- };
54
- }
55
-
56
- /** The driver's half of a front-door leg. */
57
- export interface FrontDoorLegHost {
58
- /**
59
- * Whether the turn is still live: not cancelled, not finalized. A dead
60
- * turn must not spawn an escalated leg.
61
- */
62
- isLive(): boolean;
63
- /** The turn's spoken language, for the canned bridge. */
64
- language(): string | undefined;
65
- /**
66
- * Progress narration to pause across the hand-off: the bridge holds the
67
- * floor, and audible-silence gating keeps narration quiet until it has
68
- * played on the escalated leg.
69
- */
70
- progress?: Pick<ProgressCadence, "clear" | "noteFloorHolder" | "arm">;
71
- /**
72
- * Speculative legs only: the model judged the speaker mid-thought, so the
73
- * leg is discarded and listening continues.
74
- */
75
- onHold?(): void;
76
- /**
77
- * Speculative legs only: commit the turn before acting on an answer or
78
- * escalate verdict. Returns false when the turn is gone, which stops the
79
- * verdict from being acted on.
80
- */
81
- commit?(): boolean;
82
- /**
83
- * Answer text released by the verdict machine, in stream order. On the
84
- * transition it includes the leading text held while the verdict was
85
- * pending. The driver runs its own control-marker holdback over it.
86
- */
87
- onAnswerText(text: string): void;
88
- /**
89
- * Abort the front-door leg so a model that keeps generating past the
90
- * bridge cap adds no latency before the escalated leg starts.
91
- */
92
- abortLeg(): void;
93
- /**
94
- * Speak the bridge so the strong-model call has no dead air. The model's
95
- * own bridge is real assistant speech; the canned fallback is audio-only,
96
- * matching the row the bridge's transcript hygiene deletes for it.
97
- */
98
- speakBridge(bridge: SpokenEscalationBridge): void;
99
- /** Start the escalated leg on the same turn. */
100
- startEscalatedLeg(leg: EscalatedLeg): void;
101
- }
102
-
103
- export interface FrontDoorLegCoordinator {
104
- /** Whether this leg handed the turn off to the escalated leg. */
105
- readonly handedOff: boolean;
106
- /** Feed one raw delta of the front-door leg's stream. */
107
- push(text: string): void;
108
- /**
109
- * The leg completed normally (not cancelled). A leg that stopped
110
- * mid-bridge (a bare escalate verdict, or a holding phrase with no
111
- * sentence terminator) hands off now with what arrived; the canned
112
- * fallback covers an empty bridge. Returns whether the leg handed off.
113
- */
114
- complete(): boolean;
115
- }
116
-
117
- export function createFrontDoorLegCoordinator(options: {
118
- /**
119
- * Whether the leg's decision rule teaches the hold token: true only for
120
- * speculative legs, whose leading tokens are interpreted.
121
- */
122
- holdEnabled: boolean;
123
- host: FrontDoorLegHost;
124
- }): FrontDoorLegCoordinator {
125
- const { host } = options;
126
- const verdict = createFrontDoorVerdictMachine(options.holdEnabled);
127
- let handedOff = false;
128
-
129
- const handOff = (cappedBridge: string): void => {
130
- if (handedOff || !host.isLive()) {
131
- return;
132
- }
133
- handedOff = true;
134
- host.progress?.clear();
135
- host.abortLeg();
136
- const bridge = resolveSpokenEscalationBridge(cappedBridge, host.language());
137
- host.speakBridge(bridge);
138
- // The bridge is the turn's spoken acknowledgement: narration keeps its
139
- // minimum gap from it rather than following it back to back.
140
- host.progress?.noteFloorHolder();
141
- host.startEscalatedLeg(escalatedLegFor(bridge.spokenBridge));
142
- // The escalated leg runs the slowest work in the system, and the bridge
143
- // only covers its first couple of seconds: the dead air narration
144
- // exists for.
145
- host.progress?.arm();
146
- };
147
-
148
- return {
149
- get handedOff() {
150
- return handedOff;
151
- },
152
- push(text) {
153
- if (handedOff) {
154
- return;
155
- }
156
- const step = verdict.push(text);
157
- if (step.kind === "hold") {
158
- host.onHold?.();
159
- return;
160
- }
161
- if (
162
- (step.kind === "escalate" || step.kind === "answer") &&
163
- host.commit !== undefined &&
164
- !host.commit()
165
- ) {
166
- return;
167
- }
168
- if (step.kind === "answer") {
169
- host.onAnswerText(step.text);
170
- return;
171
- }
172
- if (
173
- (step.kind === "escalate" || step.kind === "bridge") &&
174
- step.bridge !== null
175
- ) {
176
- handOff(step.bridge);
177
- }
178
- },
179
- complete() {
180
- if (!handedOff && host.isLive()) {
181
- const step = verdict.finish();
182
- if (step.kind === "bridge") {
183
- handOff(step.bridge);
184
- }
185
- }
186
- return handedOff;
187
- },
188
- };
189
- }
@@ -1,423 +0,0 @@
1
- /**
2
- * Progress narration cadence for one voice turn, shared by the live-voice
3
- * session and the phone call controller.
4
- *
5
- * A voice turn that runs tools is silent while they run. The cadence speaks
6
- * short audio-only progress updates into that dead air: a generated
7
- * sentence about what the turn is doing (see `progress-narration.ts`), or a
8
- * static fallback phrase when generation yields nothing. It follows the
9
- * turn's tool activity rather than the clock: an "ops" burst, a long
10
- * operation completing, or a full interval of audible silence with news
11
- * since the last update, with `maxSilenceMs` as the heartbeat ceiling.
12
- *
13
- * The cadence owns the per-turn state (the tool-activity log, epochs,
14
- * spacing, the self-re-arming idle timer). Everything transport-specific
15
- * comes in through {@link ProgressCadenceHost}: whether the turn's audio is
16
- * idle, when it last reached the listener, and how to speak a phrase.
17
- */
18
-
19
- import type { VoiceProgressConfig } from "../config/schemas/voice.js";
20
- import { fixedPhraseLanguage } from "../util/language-subtag.js";
21
- import { getLogger } from "../util/logger.js";
22
- import type {
23
- VoiceProgressNarrator,
24
- VoiceProgressTextInput,
25
- } from "./progress-narration.js";
26
- import {
27
- pickProgressPhrase,
28
- PROGRESS_FALLBACK_PHRASES_BY_LANGUAGE,
29
- } from "./progress-phrases.js";
30
-
31
- const log = getLogger("voice-progress-cadence");
32
-
33
- /**
34
- * One tool operation observed on a turn, fed by the bridge's structured tool
35
- * callbacks. `completedAtMs`/`isError`/`resultPreview` land with tool_result.
36
- */
37
- export interface TurnProgressOp {
38
- toolName: string;
39
- toolUseId?: string;
40
- startedAtMs: number;
41
- completedAtMs?: number;
42
- isError?: boolean;
43
- resultPreview?: string;
44
- }
45
-
46
- /** What prompted a narration attempt. */
47
- export type ProgressNarrationTrigger = "ops" | "idle" | "op_complete";
48
-
49
- /** A tool completion as the bridge reports it. */
50
- export interface ProgressToolResult {
51
- toolName: string;
52
- toolUseId?: string;
53
- isError?: boolean;
54
- resultPreview?: string;
55
- }
56
-
57
- /**
58
- * Newest incomplete op, optionally restricted to a tool name (the
59
- * tool_result fallback match when no toolUseId correlates).
60
- */
61
- export function findLastIncompleteOp(
62
- ops: readonly TurnProgressOp[],
63
- toolName?: string,
64
- ): TurnProgressOp | undefined {
65
- for (let i = ops.length - 1; i >= 0; i -= 1) {
66
- const op = ops[i];
67
- if (
68
- op.completedAtMs === undefined &&
69
- (toolName === undefined || op.toolName === toolName)
70
- ) {
71
- return op;
72
- }
73
- }
74
- return undefined;
75
- }
76
-
77
- /**
78
- * The transport-specific half of progress narration, supplied by the driver
79
- * that owns the turn's audio.
80
- */
81
- export interface ProgressCadenceHost {
82
- /** Identifies the turn in logs. */
83
- turnId: string;
84
- /** Dispatch timestamp of the turn (`Date.now()` at launch). */
85
- launchedAtMs: number;
86
- /** Aborts an in-flight narration generation with the turn. */
87
- signal: AbortSignal;
88
- /**
89
- * Whether the turn may still be narrated: it is the live turn, it has not
90
- * completed, and it is not waiting on a decision (a turn waiting on an
91
- * approval says so once and is quiet after that).
92
- */
93
- canNarrate(): boolean;
94
- /**
95
- * No assistant audio is buffered, queued, in flight, or (estimatedly)
96
- * still playing to the listener.
97
- */
98
- isAudioIdle(): boolean;
99
- /**
100
- * Wall-clock estimate of when audio already handed to the transport
101
- * finishes reaching the listener, 0 when nothing is playing. Together with
102
- * the cadence's own last-emitted stamp this anchors the dead-air countdown
103
- * to when the listener last heard something.
104
- */
105
- playbackTailUntilMs(): number;
106
- /** The caller's words this turn answers, for the narrator. */
107
- transcriptSoFar(): string;
108
- /** The turn's spoken language, when known. */
109
- language(): string | undefined;
110
- /**
111
- * Bumped on every assistant text delta. A narration generated while the
112
- * model was already speaking is stale and is dropped.
113
- */
114
- deltaEpoch(): number;
115
- /**
116
- * Speak one sanitized phrase, audio-only. `language` is the TTS hint the
117
- * phrase must carry ("en" for a static fallback the localized table lacks
118
- * in the turn's language); undefined rides the turn's language. Returns
119
- * false when nothing was spoken.
120
- */
121
- speak(phrase: string, language: string | undefined): boolean;
122
- /** Called after a narration was actually enqueued (metrics). */
123
- onNarrationSpoken?(): void;
124
- }
125
-
126
- export interface ProgressCadenceOptions {
127
- config: VoiceProgressConfig;
128
- /** Null disables narration for the turn (no TTS, or narration off). */
129
- narrator: VoiceProgressNarrator | null;
130
- host: ProgressCadenceHost;
131
- /**
132
- * Rotates the static fallback phrase across turns so a session with
133
- * repeated dead air does not repeat one sentence.
134
- */
135
- nextFallbackPhraseIndex: () => number;
136
- }
137
-
138
- export interface ProgressCadence {
139
- /** Tool operations this turn, in start order. */
140
- readonly ops: readonly TurnProgressOp[];
141
- /** Narrations actually spoken this turn. */
142
- readonly updatesSpoken: number;
143
- /** A tool started: counts toward the "ops" trigger and is news for idle. */
144
- toolStarted(toolName: string, toolUseId?: string): void;
145
- /**
146
- * A tool finished. A long operation completing narrates on its own beat;
147
- * a quick one only counts as news.
148
- */
149
- toolFinished(event: ProgressToolResult): void;
150
- /** The turn's audio last finished emitting now: restarts the dead-air clock. */
151
- noteAudioSettled(): void;
152
- /**
153
- * Something else held the floor now (a spoken approval line, a bridge):
154
- * narration keeps `minGapMs` from it.
155
- */
156
- noteFloorHolder(): void;
157
- /**
158
- * Arm (or re-arm) the dead-air timer. A no-op when narration is off for
159
- * the turn.
160
- */
161
- arm(): void;
162
- /** Stop the dead-air timer; the turn ended, was cancelled, or handed off. */
163
- clear(): void;
164
- }
165
-
166
- export function createProgressCadence(
167
- options: ProgressCadenceOptions,
168
- ): ProgressCadence {
169
- const { config, narrator, host, nextFallbackPhraseIndex } = options;
170
- const ops: TurnProgressOp[] = [];
171
- // Ops accumulated toward the next ops-triggered narration. Counted once
172
- // per op, on start (not completion), so a burst of slow tools still trips
173
- // the threshold while they run.
174
- let opsSinceNarration = 0;
175
- // Bumped by every observable change to the turn's tool activity. The idle
176
- // trigger compares it against `narratedEpoch` so a tick with nothing new
177
- // to report stays silent.
178
- let stateEpoch = 0;
179
- let narratedEpoch = 0;
180
- let updatesSpoken = 0;
181
- // When the last spoken floor-holder enqueued; gates `minGapMs`.
182
- let lastFloorHolderAtMs: number | null = null;
183
- // When the turn's audio last finished emitting (turn launch until anything
184
- // speaks). Idle time is measured from when the listener last heard
185
- // something, not from when the turn started.
186
- let lastAudibleAtMs = Date.now();
187
- let idleTimer: ReturnType<typeof setTimeout> | null = null;
188
- // A narration generation is awaiting the narrator; serializes narrations.
189
- let narrationInFlight = false;
190
-
191
- const enabled = config.enabled && narrator !== null;
192
-
193
- // When the turn's current audible silence began: the latest of the last
194
- // emitted segment, the estimated playback end, and the last floor holder.
195
- const silenceSinceMs = (): number =>
196
- Math.max(
197
- lastAudibleAtMs,
198
- host.playbackTailUntilMs(),
199
- lastFloorHolderAtMs ?? 0,
200
- );
201
-
202
- const canNarrate = (): boolean => host.canNarrate() && host.isAudioIdle();
203
-
204
- const withinMinGap = (): boolean =>
205
- lastFloorHolderAtMs !== null &&
206
- Date.now() - lastFloorHolderAtMs < config.minGapMs;
207
-
208
- // The idle tick has something worth saying when the turn's tool activity
209
- // has moved since the last narration described it, or when the silence has
210
- // run past `maxSilenceMs`, the heartbeat ceiling that proves the assistant
211
- // is still alive on a turn with no observable activity at all.
212
- const idleHasSomethingToSay = (): boolean => {
213
- if (stateEpoch !== narratedEpoch) {
214
- return true;
215
- }
216
- const silentForMs = Date.now() - silenceSinceMs();
217
- if (silentForMs >= config.maxSilenceMs) {
218
- return true;
219
- }
220
- log.debug(
221
- { turnId: host.turnId, silentForMs },
222
- "Voice progress narration held: nothing new since the last update",
223
- );
224
- return false;
225
- };
226
-
227
- const clearTimer = (): void => {
228
- if (idleTimer !== null) {
229
- clearTimeout(idleTimer);
230
- idleTimer = null;
231
- }
232
- };
233
-
234
- // Arms the dead-air timer. The countdown measures audible silence, not
235
- // time since launch, so it covers mid-turn silences for the whole turn. On
236
- // expiry with audio still pending, or with the silence not yet a full
237
- // interval old, it re-arms for the remainder; only a full interval of
238
- // audible silence reaches the gatekeeper. The interval is a polling
239
- // cadence, not a speaking cadence: most ticks find nothing new and stay
240
- // quiet.
241
- const armTimer = (delayMs?: number): void => {
242
- clearTimer();
243
- idleTimer = setTimeout(() => {
244
- idleTimer = null;
245
- if (!host.canNarrate()) {
246
- return;
247
- }
248
- if (!host.isAudioIdle()) {
249
- armTimer();
250
- return;
251
- }
252
- const remaining = silenceSinceMs() + config.idleIntervalMs - Date.now();
253
- if (remaining > 0) {
254
- armTimer(remaining);
255
- return;
256
- }
257
- maybeNarrate("idle");
258
- armTimer();
259
- }, delayMs ?? config.idleIntervalMs);
260
- };
261
-
262
- // Generate and speak one audio-only progress narration. On a null result,
263
- // idle narration falls back to a static phrase while activity-triggered
264
- // narration stays silent.
265
- const speakUpdate = async (
266
- activeNarrator: VoiceProgressNarrator,
267
- trigger: ProgressNarrationTrigger,
268
- ): Promise<void> => {
269
- narrationInFlight = true;
270
- const deltaEpochAtLaunch = host.deltaEpoch();
271
- // The activity this update describes. Tool events that land
272
- // mid-generation are news the generated text cannot carry, so they must
273
- // leave the idle trigger armed rather than count as already narrated.
274
- const stateEpochAtLaunch = stateEpoch;
275
- try {
276
- const now = Date.now();
277
- const currentOp = findLastIncompleteOp(ops);
278
- const language = host.language();
279
- const input: VoiceProgressTextInput = {
280
- transcriptSoFar: host.transcriptSoFar(),
281
- completedOps: ops
282
- .filter(
283
- (op): op is TurnProgressOp & { completedAtMs: number } =>
284
- op.completedAtMs !== undefined,
285
- )
286
- // `ops` is in start order; the narrator wants completion order,
287
- // which differs when parallel tools finish out of order.
288
- .sort((a, b) => a.completedAtMs - b.completedAtMs)
289
- .map((op) => ({
290
- toolName: op.toolName,
291
- ...(op.isError !== undefined ? { isError: op.isError } : {}),
292
- ...(op.resultPreview !== undefined
293
- ? { resultPreview: op.resultPreview }
294
- : {}),
295
- })),
296
- currentOp: currentOp
297
- ? {
298
- toolName: currentOp.toolName,
299
- elapsedMs: now - currentOp.startedAtMs,
300
- }
301
- : null,
302
- turnElapsedMs: now - host.launchedAtMs,
303
- updateIndex: updatesSpoken + 1,
304
- ...(language !== undefined ? { languageHint: language } : {}),
305
- };
306
- const generated = await activeNarrator
307
- .generateProgressText(input, host.signal)
308
- // The narrator contract never rejects. Keep test stubs fail-soft too.
309
- .catch(() => null);
310
- // Re-check liveness and staleness after the provider call.
311
- if (
312
- !canNarrate() ||
313
- host.deltaEpoch() !== deltaEpochAtLaunch ||
314
- withinMinGap()
315
- ) {
316
- return;
317
- }
318
- let raw = generated;
319
- // Generated text is in the turn's language; only the static fallback
320
- // comes from a localized table and may need the "en" override.
321
- let fillerLanguage: string | undefined;
322
- if (raw === null) {
323
- if (trigger !== "idle") {
324
- return;
325
- }
326
- raw = pickProgressPhrase(nextFallbackPhraseIndex(), language);
327
- fillerLanguage = fixedPhraseLanguage(
328
- PROGRESS_FALLBACK_PHRASES_BY_LANGUAGE,
329
- language,
330
- );
331
- }
332
- if (!host.speak(raw, fillerLanguage)) {
333
- return;
334
- }
335
- lastFloorHolderAtMs = Date.now();
336
- opsSinceNarration = 0;
337
- narratedEpoch = stateEpochAtLaunch;
338
- updatesSpoken += 1;
339
- host.onNarrationSpoken?.();
340
- // Restart the dead-air countdown from this narration.
341
- armTimer();
342
- } finally {
343
- narrationInFlight = false;
344
- }
345
- };
346
-
347
- // Gatekeeper. Speaks only while the turn is audibly silent, with one
348
- // generation at a time and the configured spacing.
349
- const maybeNarrate = (trigger: ProgressNarrationTrigger): void => {
350
- if (
351
- !enabled ||
352
- narrator === null ||
353
- !canNarrate() ||
354
- narrationInFlight ||
355
- withinMinGap() ||
356
- (trigger === "ops" && opsSinceNarration < config.opsThreshold) ||
357
- (trigger === "idle" && !idleHasSomethingToSay())
358
- ) {
359
- return;
360
- }
361
- void speakUpdate(narrator, trigger);
362
- };
363
-
364
- return {
365
- get ops() {
366
- return ops;
367
- },
368
- get updatesSpoken() {
369
- return updatesSpoken;
370
- },
371
- toolStarted(toolName, toolUseId) {
372
- ops.push({
373
- toolName,
374
- ...(toolUseId !== undefined ? { toolUseId } : {}),
375
- startedAtMs: Date.now(),
376
- });
377
- opsSinceNarration += 1;
378
- stateEpoch += 1;
379
- maybeNarrate("ops");
380
- },
381
- toolFinished(event) {
382
- // Match by toolUseId when present; otherwise the last-started
383
- // incomplete op with the same name (parallel same-name ops resolve
384
- // newest-first).
385
- const op =
386
- (event.toolUseId !== undefined
387
- ? ops.find((o) => o.toolUseId === event.toolUseId)
388
- : undefined) ?? findLastIncompleteOp(ops, event.toolName);
389
- // A long-running op finishing is the beat the listener has been
390
- // waiting through: it narrates immediately rather than waiting for
391
- // `opsThreshold` more ops, which on a one-slow-tool turn never arrive.
392
- // Short ops stay on the ops trigger.
393
- let trigger: ProgressNarrationTrigger = "ops";
394
- if (op) {
395
- op.completedAtMs = Date.now();
396
- if (event.isError !== undefined) {
397
- op.isError = event.isError;
398
- }
399
- if (event.resultPreview !== undefined) {
400
- op.resultPreview = event.resultPreview;
401
- }
402
- if (op.completedAtMs - op.startedAtMs >= config.longOpMs) {
403
- trigger = "op_complete";
404
- }
405
- }
406
- stateEpoch += 1;
407
- maybeNarrate(trigger);
408
- },
409
- noteAudioSettled() {
410
- lastAudibleAtMs = Date.now();
411
- },
412
- noteFloorHolder() {
413
- lastFloorHolderAtMs = Date.now();
414
- },
415
- arm() {
416
- if (!enabled) {
417
- return;
418
- }
419
- armTimer();
420
- },
421
- clear: clearTimer,
422
- };
423
- }