@vellumai/assistant 0.11.3 → 0.11.4-dev.202608190019.b94dbf2

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 (596) hide show
  1. package/AGENTS.md +9 -3
  2. package/ARCHITECTURE.md +32 -10
  3. package/docs/architecture/memory.md +43 -5
  4. package/docs/architecture/security.md +96 -152
  5. package/docs/architecture/turn-actor.md +70 -0
  6. package/docs/browser-use-architecture-phase2.md +128 -56
  7. package/docs/flux-turn-detection-spike.md +248 -0
  8. package/docs/guardian-request-flow.md +35 -0
  9. package/docs/stt-provider-onboarding.md +3 -1
  10. package/knip.json +3 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  13. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  19. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  20. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  21. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  23. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
  24. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
  25. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  26. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  27. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  28. package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  29. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  30. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  31. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  32. package/openapi.yaml +289 -113
  33. package/package.json +1 -1
  34. package/scripts/voice-ttft-spike.ts +3 -3
  35. package/scripts/write-plugin-api-shim.ts +10 -0
  36. package/src/__tests__/app-compiler.test.ts +38 -3
  37. package/src/__tests__/app-control-flow.test.ts +1 -0
  38. package/src/__tests__/approval-routes-http.test.ts +2 -2
  39. package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
  40. package/src/__tests__/attachments-store.test.ts +21 -12
  41. package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
  42. package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
  43. package/src/__tests__/call-site-routing-provider.test.ts +1 -1
  44. package/src/__tests__/channel-availability-routes.test.ts +14 -1
  45. package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
  46. package/src/__tests__/channel-delivery-store.test.ts +14 -14
  47. package/src/__tests__/channel-inbound-disk-pressure.test.ts +4 -4
  48. package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
  49. package/src/__tests__/checker.test.ts +20 -314
  50. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
  51. package/src/__tests__/compaction-events.test.ts +8 -10
  52. package/src/__tests__/config-loader-backfill.test.ts +3 -3
  53. package/src/__tests__/config-schema.test.ts +25 -10
  54. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  55. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
  56. package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
  57. package/src/__tests__/conversation-agent-loop.test.ts +32 -21
  58. package/src/__tests__/conversation-attachments.test.ts +0 -1
  59. package/src/__tests__/conversation-attention-store.test.ts +63 -0
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
  61. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -0
  63. package/src/__tests__/conversation-fork-referential.test.ts +67 -0
  64. package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
  65. package/src/__tests__/conversation-load-history-repair.test.ts +209 -0
  66. package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
  67. package/src/__tests__/conversation-queue.test.ts +154 -4
  68. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  69. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
  71. package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +187 -102
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
  75. package/src/__tests__/conversation-slash-queue.test.ts +3 -0
  76. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  77. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
  78. package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
  79. package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
  80. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
  81. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
  82. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
  83. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
  85. package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
  86. package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
  87. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
  88. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
  89. package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
  90. package/src/__tests__/credential-prompt-route.test.ts +7 -10
  91. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  92. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  93. package/src/__tests__/custom-profile-ensure.test.ts +5 -1
  94. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  95. package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
  96. package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
  97. package/src/__tests__/document-append-idempotency.test.ts +233 -0
  98. package/src/__tests__/document-sync-tags.test.ts +0 -75
  99. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  100. package/src/__tests__/edit-propagation.test.ts +107 -11
  101. package/src/__tests__/file-ops-service.test.ts +163 -30
  102. package/src/__tests__/filesystem-tools.test.ts +23 -24
  103. package/src/__tests__/gateway-only-guard.test.ts +2 -5
  104. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  105. package/src/__tests__/helpers/mock-actor-context.ts +49 -0
  106. package/src/__tests__/helpers/mock-conversation.ts +13 -1
  107. package/src/__tests__/host-file-read-tool.test.ts +16 -19
  108. package/src/__tests__/http-user-message-parity.test.ts +1 -1
  109. package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
  110. package/src/__tests__/injector-chain.test.ts +63 -41
  111. package/src/__tests__/injector-disk-pressure.test.ts +11 -23
  112. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  113. package/src/__tests__/llm-context-resolution.test.ts +73 -1
  114. package/src/__tests__/llm-schema.test.ts +5 -2
  115. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
  116. package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
  117. package/src/__tests__/media-generate-image.test.ts +131 -21
  118. package/src/__tests__/memory-retrieval-hook.test.ts +100 -7
  119. package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
  120. package/src/__tests__/mtime-cache.test.ts +3 -1
  121. package/src/__tests__/non-member-access-request.test.ts +0 -20
  122. package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
  123. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  124. package/src/__tests__/plugin-api-store-credential.test.ts +261 -0
  125. package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
  126. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  127. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  128. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  129. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  130. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -4
  131. package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
  132. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
  133. package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
  134. package/src/__tests__/proxy-approval-callback.test.ts +1 -0
  135. package/src/__tests__/qdrant-manager.test.ts +14 -1
  136. package/src/__tests__/reaction-persistence.test.ts +200 -7
  137. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  138. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  139. package/src/__tests__/run-conversation-turn-persistence.test.ts +460 -105
  140. package/src/__tests__/run-due-schedules.test.ts +21 -0
  141. package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
  142. package/src/__tests__/schedule-routes.test.ts +23 -0
  143. package/src/__tests__/schedule-store.test.ts +17 -0
  144. package/src/__tests__/scoped-approval-grants.test.ts +11 -6
  145. package/src/__tests__/secret-ingress-channel.test.ts +0 -1
  146. package/src/__tests__/secret-ingress-http.test.ts +1 -1
  147. package/src/__tests__/send-endpoint-busy.test.ts +3 -3
  148. package/src/__tests__/skills.test.ts +32 -0
  149. package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
  150. package/src/__tests__/starter-task-flow.test.ts +5 -4
  151. package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
  152. package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
  153. package/src/__tests__/subagent-spawn-and-await.test.ts +18 -17
  154. package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
  155. package/src/__tests__/subagent-tools.test.ts +81 -101
  156. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
  157. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  158. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  159. package/src/__tests__/tool-executor.test.ts +68 -39
  160. package/src/__tests__/turn-events-store.test.ts +43 -0
  161. package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
  162. package/src/__tests__/ui-shape-teaching.test.ts +33 -0
  163. package/src/__tests__/ui-visual-surface.test.ts +1 -1
  164. package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
  165. package/src/__tests__/ui-work-result-surface.test.ts +1 -1
  166. package/src/__tests__/user-plugin-loader.test.ts +3 -1
  167. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  168. package/src/__tests__/visible-app-context.test.ts +16 -9
  169. package/src/__tests__/voice-config-update.test.ts +40 -0
  170. package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
  171. package/src/__tests__/voice-session-bridge.test.ts +85 -29
  172. package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
  173. package/src/__tests__/worker-plugin-surface.test.ts +77 -0
  174. package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
  175. package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
  176. package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
  177. package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
  178. package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
  179. package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
  180. package/src/acp/__tests__/auth-required.test.ts +161 -0
  181. package/src/acp/acp-claude-oauth.ts +19 -2
  182. package/src/acp/agent-process.test.ts +100 -0
  183. package/src/acp/agent-process.ts +29 -26
  184. package/src/acp/auth-required.ts +102 -0
  185. package/src/acp/session-manager.test.ts +119 -0
  186. package/src/acp/session-manager.ts +76 -3
  187. package/src/api/events/acp-auth-required.ts +55 -0
  188. package/src/api/events/host-file.ts +2 -2
  189. package/src/api/index.ts +7 -0
  190. package/src/api/surfaces.ts +12 -3
  191. package/src/apps/app-store.ts +3 -0
  192. package/src/bundler/package-resolver.ts +2 -30
  193. package/src/calls/__tests__/voice-session-bridge.test.ts +194 -11
  194. package/src/calls/__tests__/voice-triage-escalate.test.ts +102 -0
  195. package/src/calls/call-controller.ts +19 -3
  196. package/src/calls/call-setup-flow.ts +0 -1
  197. package/src/calls/media-stream-stt-session.ts +15 -0
  198. package/src/calls/voice-session-bridge.ts +115 -41
  199. package/src/calls/voice-triage-escalate.ts +105 -2
  200. package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
  201. package/src/channels/config.ts +13 -0
  202. package/src/channels/plugin-channel-declarations.ts +108 -0
  203. package/src/channels/types.ts +30 -0
  204. package/src/cli/AGENTS.md +5 -2
  205. package/src/cli/bundled-modules.ts +29 -0
  206. package/src/cli/commands/credentials.help.ts +2 -2
  207. package/src/cli/commands/db/repair.ts +4 -8
  208. package/src/cli/commands/domain.ts +6 -3
  209. package/src/cli/commands/email.ts +6 -3
  210. package/src/cli/commands/inference-providers.ts +1 -1
  211. package/src/cli/commands/keys.ts +8 -3
  212. package/src/cli/commands/mcp.help.ts +13 -4
  213. package/src/cli/commands/mcp.ts +9 -0
  214. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  215. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
  216. package/src/cli/commands/memory/index.help.ts +84 -28
  217. package/src/cli/commands/memory/index.ts +2 -0
  218. package/src/cli/commands/memory/memory-v2.ts +58 -54
  219. package/src/cli/commands/memory/memory-v3.ts +64 -0
  220. package/src/cli/commands/memory/memory-validate.ts +18 -0
  221. package/src/cli/commands/plugins.ts +85 -36
  222. package/src/cli/commands/schedules.ts +35 -1
  223. package/src/cli/commands/stt.help.ts +27 -2
  224. package/src/cli/commands/trust.ts +3 -14
  225. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
  226. package/src/cli/lib/bundled-marketplace.json +14 -1
  227. package/src/cli/lib/upgrade-plugin.ts +42 -0
  228. package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
  229. package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
  230. package/src/config/__tests__/default-provider.test.ts +6 -1
  231. package/src/config/__tests__/profile-materialization.test.ts +75 -19
  232. package/src/config/assistant-feature-flags.ts +36 -15
  233. package/src/config/balanced-model-experiment.ts +35 -0
  234. package/src/config/bundled-skills/acp/SKILL.md +6 -7
  235. package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
  236. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  237. package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
  238. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
  239. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
  240. package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
  241. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  242. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
  243. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
  244. package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
  245. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
  246. package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
  247. package/src/config/bundled-skills/subagent/SKILL.md +17 -12
  248. package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
  249. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
  250. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
  251. package/src/config/call-site-defaults.ts +11 -5
  252. package/src/config/default-profile-catalog.ts +179 -16
  253. package/src/config/default-profile-names.ts +4 -1
  254. package/src/config/default-provider-resolution.ts +4 -0
  255. package/src/config/feature-flag-registry.json +12 -12
  256. package/src/config/llm-context-resolution.ts +11 -3
  257. package/src/config/llm-resolver.ts +28 -1
  258. package/src/config/profile-materialization.ts +70 -22
  259. package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
  260. package/src/config/schemas/call-site-catalog.ts +4 -4
  261. package/src/config/schemas/live-voice.ts +57 -23
  262. package/src/config/schemas/llm.ts +59 -32
  263. package/src/config/schemas/mcp.ts +23 -0
  264. package/src/config/schemas/plugin-updates.ts +6 -2
  265. package/src/config/schemas/stt.ts +1 -0
  266. package/src/config/skills.ts +9 -2
  267. package/src/context/outbound-sanitize.ts +96 -1
  268. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
  269. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
  270. package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
  271. package/src/daemon/conversation-agent-loop.ts +31 -19
  272. package/src/daemon/conversation-messaging.ts +5 -1
  273. package/src/daemon/conversation-notifiers.ts +20 -10
  274. package/src/daemon/conversation-process.ts +20 -6
  275. package/src/daemon/conversation-runtime-assembly.ts +12 -6
  276. package/src/daemon/conversation-store.ts +14 -4
  277. package/src/daemon/conversation-surfaces.ts +75 -26
  278. package/src/daemon/conversation-tool-setup.ts +3 -6
  279. package/src/daemon/conversation.ts +154 -51
  280. package/src/daemon/doordash-steps.ts +2 -2
  281. package/src/daemon/lifecycle.ts +14 -1
  282. package/src/daemon/mcp-reload-service.ts +36 -6
  283. package/src/daemon/process-message.ts +13 -26
  284. package/src/daemon/providers-setup.ts +6 -3
  285. package/src/daemon/trust-context-types.ts +29 -0
  286. package/src/daemon/trust-context.ts +12 -7
  287. package/src/daemon/wake-conversation-ops.ts +3 -2
  288. package/src/daemon/windows-compiled-entry.ts +4 -0
  289. package/src/documents/document-store.ts +143 -240
  290. package/src/hooks/hook-loader.ts +3 -3
  291. package/src/hooks/registry.ts +103 -45
  292. package/src/hooks/types.ts +5 -0
  293. package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
  294. package/src/inbound/oauth-callback-url.ts +61 -0
  295. package/src/ipc/gateway-client.test.ts +1 -1
  296. package/src/ipc/gateway-client.ts +14 -21
  297. package/src/ipc/gateway-flag-listener.ts +17 -3
  298. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
  299. package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
  300. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +1050 -0
  301. package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
  302. package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
  303. package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
  304. package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
  305. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
  306. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
  307. package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
  308. package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
  309. package/src/live-voice/live-voice-archive.ts +2 -0
  310. package/src/live-voice/live-voice-manager.ts +16 -3
  311. package/src/live-voice/live-voice-metrics.ts +57 -32
  312. package/src/live-voice/live-voice-photo.ts +1 -2
  313. package/src/live-voice/live-voice-session.ts +598 -323
  314. package/src/live-voice/progress-narration.ts +277 -0
  315. package/src/live-voice/protocol.ts +21 -1
  316. package/src/live-voice/windows-compiled-live-voice.ts +4 -0
  317. package/src/mcp/__tests__/effective-config.test.ts +238 -0
  318. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  319. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
  320. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
  321. package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
  322. package/src/mcp/client.ts +16 -11
  323. package/src/mcp/effective-config.ts +113 -0
  324. package/src/mcp/manager.ts +11 -6
  325. package/src/mcp/mcp-auth-orchestrator.ts +13 -22
  326. package/src/mcp/mcp-oauth-provider.ts +205 -240
  327. package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
  328. package/src/monitoring/control.ts +1 -0
  329. package/src/monitoring/db-integrity-sample.ts +4 -5
  330. package/src/monitoring/plugin-auto-update.ts +128 -24
  331. package/src/notifications/AGENTS.md +2 -0
  332. package/src/notifications/approval-card-data.ts +33 -0
  333. package/src/notifications/signal.ts +1 -0
  334. package/src/permissions/AGENTS.md +16 -0
  335. package/src/permissions/checker.test.ts +75 -122
  336. package/src/permissions/checker.ts +44 -560
  337. package/src/permissions/confirmation-guardian-request.test.ts +15 -11
  338. package/src/permissions/confirmation-guardian-request.ts +2 -2
  339. package/src/permissions/prompter.ts +1 -5
  340. package/src/permissions/question-guardian-request.test.ts +14 -6
  341. package/src/permissions/question-guardian-request.ts +1 -2
  342. package/src/persistence/attachments-store.ts +8 -1
  343. package/src/persistence/bookmark-crud.ts +3 -7
  344. package/src/persistence/conversation-attention-store.ts +16 -45
  345. package/src/persistence/conversation-crud.ts +33 -4
  346. package/src/persistence/conversation-lineage.ts +9 -0
  347. package/src/persistence/conversation-queries.ts +182 -52
  348. package/src/persistence/delivery-crud.ts +150 -36
  349. package/src/persistence/embeddings/qdrant-manager.ts +84 -49
  350. package/src/persistence/external-conversation-store.ts +32 -4
  351. package/src/persistence/llm-request-log-store.ts +4 -10
  352. package/src/persistence/llm-usage-store.ts +8 -3
  353. package/src/persistence/message-reads.test.ts +197 -0
  354. package/src/persistence/message-reads.ts +211 -0
  355. package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
  356. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
  357. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
  358. package/src/persistence/real-user-turn-filter.ts +27 -3
  359. package/src/persistence/schema/documents.ts +4 -4
  360. package/src/persistence/steps.ts +9 -0
  361. package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
  362. package/src/plugin-api/constants.ts +8 -0
  363. package/src/plugin-api/conversation-turn.ts +195 -5
  364. package/src/plugin-api/index.ts +38 -6
  365. package/src/plugin-api/resolve-credential.ts +3 -2
  366. package/src/plugin-api/store-credential.ts +146 -0
  367. package/src/plugin-api/vision-support.test.ts +1 -1
  368. package/src/plugin-api/webhook-url.ts +13 -11
  369. package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
  370. package/src/plugins/defaults/main.ts +21 -7
  371. package/src/plugins/defaults/memory/AGENTS.md +15 -3
  372. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
  373. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
  374. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
  375. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
  376. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  377. package/src/plugins/defaults/memory/buffer-format.ts +165 -0
  378. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
  379. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
  380. package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
  381. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
  382. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
  383. package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
  384. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +17 -24
  385. package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
  386. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
  387. package/src/plugins/defaults/memory/indexer.ts +3 -1
  388. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
  389. package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
  390. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
  391. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  392. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
  393. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +147 -0
  394. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  395. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  396. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  397. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  398. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  399. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  400. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +195 -58
  401. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +93 -24
  402. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  403. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  404. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  405. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  406. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  407. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  408. package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
  409. package/src/plugins/defaults/memory/substrate/static-context.ts +160 -33
  410. package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
  411. package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
  412. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
  413. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
  414. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  415. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  416. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  417. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  418. package/src/plugins/defaults/memory/v3/injector.ts +8 -0
  419. package/src/plugins/defaults/memory/v3/prune.ts +2 -0
  420. package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
  421. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
  422. package/src/plugins/defaults/memory/worker-control.ts +1 -0
  423. package/src/plugins/defaults/memory/worker.ts +6 -3
  424. package/src/plugins/defaults/worker-entrypoints.ts +3 -0
  425. package/src/plugins/external-plugin-loader.ts +47 -0
  426. package/src/plugins/mcp-servers.ts +361 -0
  427. package/src/plugins/mtime-cache.ts +37 -46
  428. package/src/plugins/pipeline.ts +14 -7
  429. package/src/plugins/plugin-execution-context.ts +35 -11
  430. package/src/plugins/worker-plugin-surface.ts +33 -0
  431. package/src/prompts/templates/system-sections.ts +0 -7
  432. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  433. package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
  434. package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
  435. package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
  436. package/src/providers/__tests__/retry-callsite.test.ts +25 -2
  437. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  438. package/src/providers/anthropic/client.ts +332 -241
  439. package/src/providers/call-site-routing.ts +30 -3
  440. package/src/providers/connection-resolution.ts +194 -11
  441. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +4 -0
  442. package/src/providers/inference/adapter-factory.ts +3 -0
  443. package/src/providers/inference/auth.ts +6 -0
  444. package/src/providers/inference/connection-availability.ts +24 -2
  445. package/src/providers/inference/connections.ts +2 -0
  446. package/src/providers/model-catalog.ts +3 -3
  447. package/src/providers/model-intents.ts +28 -8
  448. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +163 -40
  449. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  450. package/src/providers/openai/chat-completions-provider.ts +133 -36
  451. package/src/providers/openai/codex-models.ts +2 -1
  452. package/src/providers/provider-send-message.ts +32 -3
  453. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
  454. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +625 -0
  455. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
  456. package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
  457. package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
  458. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +679 -0
  459. package/src/providers/speech-to-text/provider-catalog.ts +99 -8
  460. package/src/providers/speech-to-text/resolve.ts +25 -2
  461. package/src/routes/control.ts +1 -0
  462. package/src/routes/route-host-client.ts +1 -0
  463. package/src/routes/route-host-protocol.ts +7 -0
  464. package/src/routes/worker.ts +48 -15
  465. package/src/runtime/AGENTS.md +16 -17
  466. package/src/runtime/access-request-helper.ts +9 -12
  467. package/src/runtime/agent-wake.ts +18 -15
  468. package/src/runtime/effective-capabilities.test.ts +43 -0
  469. package/src/runtime/effective-capabilities.ts +27 -0
  470. package/src/runtime/guardian-reply-router.ts +6 -2
  471. package/src/runtime/pre-first-message-gate.ts +4 -0
  472. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
  473. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +522 -2
  474. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
  475. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
  476. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
  477. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
  478. package/src/runtime/routes/__tests__/plugins-routes.test.ts +132 -35
  479. package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
  480. package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
  481. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  482. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  483. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +133 -1
  484. package/src/runtime/routes/channel-availability-routes.ts +32 -14
  485. package/src/runtime/routes/channel-route-shared.ts +0 -6
  486. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
  487. package/src/runtime/routes/conversation-list-routes.ts +181 -23
  488. package/src/runtime/routes/conversation-management-routes.ts +17 -12
  489. package/src/runtime/routes/conversation-query-routes.ts +40 -27
  490. package/src/runtime/routes/conversation-routes.ts +43 -29
  491. package/src/runtime/routes/credential-prompt-routes.ts +4 -7
  492. package/src/runtime/routes/credential-routes.ts +26 -82
  493. package/src/runtime/routes/default-provider-routes.ts +15 -0
  494. package/src/runtime/routes/documents-routes.ts +3 -222
  495. package/src/runtime/routes/inbound-message-handler.ts +21 -45
  496. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
  497. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
  498. package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
  499. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
  500. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
  501. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -79
  502. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
  503. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -7
  504. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -8
  505. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -79
  506. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
  507. package/src/runtime/routes/inference-profiles-routes.ts +20 -11
  508. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
  509. package/src/runtime/routes/log-export-routes.ts +3 -0
  510. package/src/runtime/routes/mcp-auth-routes.ts +148 -57
  511. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
  512. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
  513. package/src/runtime/routes/playground/inject-failures.ts +2 -2
  514. package/src/runtime/routes/playground/reset-circuit.ts +1 -1
  515. package/src/runtime/routes/plugins-routes.ts +38 -8
  516. package/src/runtime/routes/schedule-routes.ts +94 -7
  517. package/src/runtime/routes/settings-routes.ts +6 -6
  518. package/src/runtime/routes/stt-routes.ts +31 -25
  519. package/src/runtime/routes/surface-action-routes.ts +20 -19
  520. package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
  521. package/src/runtime/routes/user-route-dispatcher.ts +69 -15
  522. package/src/runtime/routes/user-route-import.ts +108 -0
  523. package/src/runtime/routes/user-route-resolution.ts +18 -1
  524. package/src/runtime/routes/workspace-routes.ts +0 -9
  525. package/src/runtime/routes/workspace-utils.ts +3 -13
  526. package/src/runtime/services/conversation-serializer.ts +7 -2
  527. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
  528. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
  529. package/src/schedule/plugin-schedule-availability.ts +58 -0
  530. package/src/schedule/plugin-schedule-declarations.ts +23 -27
  531. package/src/schedule/plugin-schedule-reconciler.ts +12 -3
  532. package/src/schedule/schedule-store.ts +5 -1
  533. package/src/schedule/scheduler.ts +9 -4
  534. package/src/schedule/worker-control.ts +1 -0
  535. package/src/schedule/worker.ts +6 -0
  536. package/src/security/oauth2.ts +6 -22
  537. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
  538. package/src/stt/__tests__/types.test.ts +94 -0
  539. package/src/stt/daemon-batch-transcriber.ts +10 -0
  540. package/src/stt/stt-stream-session.ts +8 -4
  541. package/src/stt/types.ts +103 -0
  542. package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
  543. package/src/subagent/consult-context.ts +11 -11
  544. package/src/subagent/consult-prompt.ts +26 -35
  545. package/src/subagent/manager.ts +21 -40
  546. package/src/subagent/notify.ts +7 -1
  547. package/src/subagent/types.ts +15 -12
  548. package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
  549. package/src/tools/acp/spawn.test.ts +97 -0
  550. package/src/tools/acp/spawn.ts +38 -4
  551. package/src/tools/credentials/ref-parse.ts +35 -0
  552. package/src/tools/credentials/resolve.ts +4 -10
  553. package/src/tools/credentials/store.ts +168 -0
  554. package/src/tools/document/document-tool.ts +12 -3
  555. package/src/tools/executor.ts +53 -15
  556. package/src/tools/filesystem/read.ts +27 -10
  557. package/src/tools/host-filesystem/read.ts +27 -15
  558. package/src/tools/network/url-safety.ts +9 -16
  559. package/src/tools/permission-checker.ts +43 -52
  560. package/src/tools/registry.ts +2 -1
  561. package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
  562. package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
  563. package/src/tools/shared/filesystem/types.ts +5 -5
  564. package/src/tools/skills/scaffold-managed.ts +25 -7
  565. package/src/tools/subagent/spawn.ts +28 -88
  566. package/src/tools/tool-types.ts +26 -13
  567. package/src/tools/types.ts +6 -6
  568. package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
  569. package/src/tools/workflows/run-workflow.ts +1 -2
  570. package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
  571. package/src/tts/reasoning-tag-filter.ts +89 -0
  572. package/src/util/__tests__/worker-process-command.test.ts +37 -0
  573. package/src/util/logger.ts +16 -0
  574. package/src/util/think-tag-stream.ts +95 -0
  575. package/src/util/worker-process.ts +37 -4
  576. package/src/windows-compiled-cli.ts +32 -0
  577. package/src/windows-compiled-entry.ts +4 -0
  578. package/src/windows-compiled-logger.ts +6 -0
  579. package/src/windows-compiled-worker-entry.ts +29 -0
  580. package/src/workspace/byok-default-profile-ensure.ts +76 -24
  581. package/src/workspace/custom-profile-ensure.ts +4 -24
  582. package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
  583. package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
  584. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
  585. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
  586. package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
  587. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
  588. package/src/workspace/migrations/registry.ts +10 -0
  589. package/src/workspace/provider-commit-message-generator.ts +7 -5
  590. package/src/__tests__/document-workspace-file.test.ts +0 -467
  591. package/src/live-voice/__tests__/front-decision.test.ts +0 -645
  592. package/src/live-voice/front-decision.ts +0 -476
  593. package/src/permissions/ipc-risk-types.ts +0 -143
  594. package/src/permissions/risk-types.ts +0 -76
  595. package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
  596. package/src/subagent/consult-transcript.ts +0 -90
@@ -15,7 +15,15 @@ import { getDb } from "../persistence/db-connection.js";
15
15
  import { acpSessionHistory } from "../persistence/schema/index.js";
16
16
  import * as pendingInteractions from "../runtime/pending-interactions.js";
17
17
  import { getLogger } from "../util/logger.js";
18
+ import { markAcpConnectCardRaised } from "./acp-connect-card-state.js";
18
19
  import { AcpAgentProcess } from "./agent-process.js";
20
+ import {
21
+ ACP_AUTH_RECOVERY_GUIDANCE,
22
+ ACP_CLAUDE_AUTH_REQUIRED_CODE,
23
+ CLAUDE_ACP_COMMAND,
24
+ isAcpAuthRequired,
25
+ isClaudeAuthFailureMessage,
26
+ } from "./auth-required.js";
19
27
  import { resolveAgentWithAutoInstall } from "./auto-install.js";
20
28
  import { VellumAcpClientHandler } from "./client-handler.js";
21
29
  import { deriveFailureError } from "./failure-error.js";
@@ -26,6 +34,30 @@ import type { AcpAgentConfig, AcpSessionState } from "./types.js";
26
34
 
27
35
  const log = getLogger("acp:session-manager");
28
36
 
37
+ /**
38
+ * The `authCode` for a run that failed on its Claude credential, or undefined
39
+ * for any other failure. Adapter-gated first: the signals are Claude-specific,
40
+ * and the marker promises a repair only the Connect Claude flow can perform.
41
+ * Checks both auth-failure shapes (see `auth-required.ts`) against both the
42
+ * raw rejection and the derived failure message, since `deriveFailureError`
43
+ * may replace one with the other.
44
+ */
45
+ function claudeAuthRequiredCode(
46
+ err: unknown,
47
+ failureMessage: string,
48
+ entry: { command: string },
49
+ ): string | undefined {
50
+ if (entry.command !== CLAUDE_ACP_COMMAND) {
51
+ return undefined;
52
+ }
53
+ const rawMessage = err instanceof Error ? err.message : String(err);
54
+ return isAcpAuthRequired(err) ||
55
+ isClaudeAuthFailureMessage(rawMessage) ||
56
+ isClaudeAuthFailureMessage(failureMessage)
57
+ ? ACP_CLAUDE_AUTH_REQUIRED_CODE
58
+ : undefined;
59
+ }
60
+
29
61
  /**
30
62
  * The manager's "unknown session id" error. Thrown whenever an operation
31
63
  * references an acpSessionId with no in-memory entry (and, for resume, no
@@ -1093,6 +1125,11 @@ export class AcpSessionManager {
1093
1125
  err.message,
1094
1126
  current.process.stderrSince(stderrMark),
1095
1127
  );
1128
+ const errorCode = claudeAuthRequiredCode(
1129
+ err,
1130
+ failureMessage,
1131
+ current,
1132
+ );
1096
1133
  if (current.state.status !== "cancelled") {
1097
1134
  current.state.status = "failed";
1098
1135
  current.state.completedAt = Date.now();
@@ -1100,14 +1137,40 @@ export class AcpSessionManager {
1100
1137
  }
1101
1138
  current.currentPrompt = null;
1102
1139
  log.error(
1103
- { acpSessionId, error: err.message, failureMessage },
1140
+ { acpSessionId, error: err.message, failureMessage, errorCode },
1104
1141
  "ACP prompt failed",
1105
1142
  );
1143
+ // `acp_session_error` keeps its pre-existing shape so older packaged
1144
+ // clients still parse it; the recovery signal rides its own event
1145
+ // below (see `api/events/acp-auth-required.ts`).
1106
1146
  current.sendToVellum({
1107
1147
  type: "acp_session_error",
1108
1148
  acpSessionId,
1109
1149
  error: failureMessage,
1110
1150
  });
1151
+ // The recovery surface (event, model guidance, prompt-dedup marker)
1152
+ // is raised as a unit, and only when a spawning tool call exists to
1153
+ // anchor the card AND the user did not already stop the run. A
1154
+ // cancelled run renders no card (the client guards it), so marking
1155
+ // the never-cleared prompt-dedup registry here would suppress the
1156
+ // secure token prompt against a card that never appears.
1157
+ const recoveryAnchor =
1158
+ errorCode !== undefined && current.state.status !== "cancelled"
1159
+ ? current.parentToolUseId
1160
+ : undefined;
1161
+ if (errorCode !== undefined && recoveryAnchor !== undefined) {
1162
+ current.sendToVellum({
1163
+ type: "acp_auth_required",
1164
+ acpSessionId,
1165
+ authCode: errorCode,
1166
+ agent: current.state.agentId,
1167
+ parentToolUseId: recoveryAnchor,
1168
+ });
1169
+ // Same registry as the missing-token spawn path, so the
1170
+ // credential-prompt route redirects a redundant secure prompt at
1171
+ // the card instead of stacking a second one.
1172
+ markAcpConnectCardRaised(current.parentConversationId);
1173
+ }
1111
1174
 
1112
1175
  // Persist the terminal row before teardown clears the buffer.
1113
1176
  this.persistTerminal(acpSessionId, current);
@@ -1123,7 +1186,10 @@ export class AcpSessionManager {
1123
1186
  if (current.state.status !== "cancelled") {
1124
1187
  this.notifyParent(
1125
1188
  current,
1126
- `[ACP agent "${current.state.agentId}" failed]\n\n${failureMessage}`,
1189
+ `[ACP agent "${current.state.agentId}" failed]\n\n${failureMessage}` +
1190
+ (recoveryAnchor !== undefined
1191
+ ? `\n\n${ACP_AUTH_RECOVERY_GUIDANCE}`
1192
+ : ""),
1127
1193
  );
1128
1194
  }
1129
1195
  }
@@ -1158,9 +1224,14 @@ export class AcpSessionManager {
1158
1224
  );
1159
1225
  return;
1160
1226
  }
1227
+ // The notification turn streams to whoever is watching (the parent's sink is
1228
+ // its client hub), but it is machine-injected with no human asserted to be
1229
+ // present, so it runs non-interactive: a tool that would need approval is
1230
+ // denied rather than left waiting on a prompt nobody may answer.
1161
1231
  const enqueueResult = parentConversation.enqueueMessage({
1162
1232
  content: message,
1163
1233
  metadata: { acpNotification },
1234
+ isInteractive: false,
1164
1235
  });
1165
1236
  if (enqueueResult.queued || enqueueResult.rejected) {
1166
1237
  return;
@@ -1168,7 +1239,9 @@ export class AcpSessionManager {
1168
1239
  parentConversation
1169
1240
  .persistUserMessage({ content: message, metadata: { acpNotification } })
1170
1241
  .then(({ id: messageId }) =>
1171
- parentConversation.runAgentLoop(message, messageId),
1242
+ parentConversation.runAgentLoop(message, messageId, {
1243
+ isInteractive: false,
1244
+ }),
1172
1245
  )
1173
1246
  .catch((err) => {
1174
1247
  log.error(
@@ -0,0 +1,55 @@
1
+ /**
2
+ * `acp_auth_required` SSE event.
3
+ *
4
+ * Server to client notification that an ACP run failed because the agent
5
+ * rejected its stored credential and the client can offer a re-authentication
6
+ * affordance. Always emitted immediately AFTER the `acp_session_error` that
7
+ * carries the human-readable failure.
8
+ *
9
+ * It is a separate event rather than a field on `acp_session_error` because
10
+ * every event schema here is `.strict()` and clients fall back to an inert
11
+ * `unknown` event when parsing fails: a new FIELD makes an older packaged
12
+ * client (iOS/macOS bundle the web app) reject the whole error event and
13
+ * render the run as still active, while an unknown event TYPE is simply
14
+ * ignored. Additive signals on existing events need their own event type for
15
+ * the same reason.
16
+ *
17
+ * Canonical wire-contract source. Re-exported to external consumers via
18
+ * `@vellumai/assistant-api` (the `api/index.ts` barrel).
19
+ */
20
+
21
+ import { z } from "zod";
22
+
23
+ /**
24
+ * The code for a `claude-agent-acp` run whose Claude credential needs to be
25
+ * reconnected: the `authCode` on this event for post-spawn failures, and the
26
+ * `errorCode` on the failed `acp_spawn` tool result for pre-spawn ones.
27
+ * Single source for both sides of the wire: the daemon re-exports it via
28
+ * `acp/auth-required.ts`, the web client via
29
+ * `domains/chat/utils/acp-connect.ts`.
30
+ */
31
+ export const ACP_CLAUDE_AUTH_REQUIRED_CODE = "acp_claude_auth_required";
32
+
33
+ export const AcpAuthRequiredEventSchema = z
34
+ .object({
35
+ type: z.literal("acp_auth_required"),
36
+ acpSessionId: z.string(),
37
+ /**
38
+ * Machine-readable classification of what kind of re-authentication is
39
+ * needed, so clients branch on a stable value rather than on the failure
40
+ * text (which varies by adapter and failure mode). Currently only
41
+ * {@link ACP_CLAUDE_AUTH_REQUIRED_CODE}.
42
+ */
43
+ authCode: z.string(),
44
+ /** Agent id the run was spawned with, for display. */
45
+ agent: z.string(),
46
+ /**
47
+ * Tool-use id of the `acp_spawn` call that started this run, so the
48
+ * client anchors the inline affordance to the right transcript row.
49
+ * Absent when the run was not started by a tool call.
50
+ */
51
+ parentToolUseId: z.string().optional(),
52
+ })
53
+ .strict();
54
+
55
+ export type AcpAuthRequiredEvent = z.infer<typeof AcpAuthRequiredEventSchema>;
@@ -21,8 +21,8 @@ const HostFileReadRequestSchema = z.object({
21
21
  targetClientId: z.string().optional(),
22
22
  operation: z.literal("read"),
23
23
  path: z.string(),
24
- offset: z.number().optional(),
25
- limit: z.number().optional(),
24
+ startIndex: z.number().optional(),
25
+ maxChars: z.number().optional(),
26
26
  });
27
27
 
28
28
  const HostFileWriteRequestSchema = z.object({
package/src/api/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
 
3
+ import { AcpAuthRequiredEventSchema } from "./events/acp-auth-required.js";
3
4
  import { AcpSessionCompletedEventSchema } from "./events/acp-session-completed.js";
4
5
  import { AcpSessionErrorEventSchema } from "./events/acp-session-error.js";
5
6
  import { AcpSessionSpawnedEventSchema } from "./events/acp-session-spawned.js";
@@ -149,6 +150,11 @@ export {
149
150
  SSE_REPLAY_RING_COUNT_LIMIT,
150
151
  } from "./constants/sse-replay.js";
151
152
  export { DEFAULT_TOOL_EXECUTION_TIMEOUT_SEC } from "./constants/tool-execution.js";
153
+ export {
154
+ ACP_CLAUDE_AUTH_REQUIRED_CODE,
155
+ type AcpAuthRequiredEvent,
156
+ AcpAuthRequiredEventSchema,
157
+ } from "./events/acp-auth-required.js";
152
158
  export {
153
159
  type AcpSessionCompletedEvent,
154
160
  AcpSessionCompletedEventSchema,
@@ -895,6 +901,7 @@ export {
895
901
  * migration recipe.
896
902
  */
897
903
  export const AssistantEventSchema = z.discriminatedUnion("type", [
904
+ AcpAuthRequiredEventSchema,
898
905
  AcpSessionCompletedEventSchema,
899
906
  AcpSessionErrorEventSchema,
900
907
  AcpSessionSpawnedEventSchema,
@@ -488,6 +488,11 @@ export const WorkResultItemSchema = z.object({
488
488
  status: tolerantString(),
489
489
  tone: WorkResultToneSchema.optional().catch(undefined),
490
490
  metadata: recordArray(WorkResultMetadataSchema).optional().catch(undefined),
491
+ /**
492
+ * Makes the item row a link. Clients follow an in-app path
493
+ * (`/assistant/skills/<skillId>?tab=history`) in place and open an http(s)
494
+ * URL externally; anything else renders as an unlinked row.
495
+ */
491
496
  href: tolerantString(),
492
497
  });
493
498
  export type WorkResultItem = z.infer<typeof WorkResultItemSchema>;
@@ -591,6 +596,7 @@ export const SURFACE_TYPES = [
591
596
  "skill_card",
592
597
  "call_summary",
593
598
  "visual",
599
+ "voice_picker",
594
600
  ] as const;
595
601
 
596
602
  export const SurfaceTypeSchema = z.enum(SURFACE_TYPES);
@@ -662,9 +668,10 @@ export type SurfaceData =
662
668
  * and serves verbatim but whose shape it does not model, which is why they
663
669
  * are absent from the renderable `SurfaceData` union: `channel_setup` (a
664
670
  * side-effect command forwarded to the setup panel), `task_preferences` (a
665
- * fixed grid that reads no data), and `skill_card` / `call_summary` (cards
666
- * the daemon appends to history directly — the memory retrospective and a
667
- * call summary — and whose data shape is owned by their client renderers).
671
+ * fixed grid that reads no data), `voice_picker` (a settings card that reads
672
+ * its own config), and `skill_card` / `call_summary` (cards the daemon appends
673
+ * to history directly, the memory retrospective and a call summary, whose
674
+ * data shape is owned by their client renderers).
668
675
  */
669
676
  export interface SurfaceDataByType {
670
677
  card: CardSurfaceData;
@@ -684,6 +691,7 @@ export interface SurfaceDataByType {
684
691
  skill_card: Record<string, unknown>;
685
692
  call_summary: Record<string, unknown>;
686
693
  visual: VisualSurfaceData;
694
+ voice_picker: Record<string, unknown>;
687
695
  }
688
696
 
689
697
  /** Any surface `data` payload, including the opaque (non-renderable) types. */
@@ -717,6 +725,7 @@ export const SURFACE_DATA_SCHEMAS: {
717
725
  skill_card: z.record(z.string(), z.unknown()),
718
726
  call_summary: z.record(z.string(), z.unknown()),
719
727
  visual: VisualSurfaceDataSchema,
728
+ voice_picker: z.record(z.string(), z.unknown()),
720
729
  };
721
730
 
722
731
  /**
@@ -1422,6 +1422,9 @@ export function backfillAppConversationIds(): void {
1422
1422
  }
1423
1423
 
1424
1424
  try {
1425
+ // Unfinalized rows cannot match: their content is a { ref } pointer,
1426
+ // never inline surface JSON, so this scan sees finalized content only by
1427
+ // shape. No completeness predicate needed.
1425
1428
  const rows = rawAll<{ conversation_id: string; content: string }>(
1426
1429
  "apps:backfillConversationIds",
1427
1430
  `SELECT conversation_id, content FROM messages WHERE content LIKE '%"type":"ui_surface"%'`,
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { existsSync } from "node:fs";
9
- import { mkdir, stat } from "node:fs/promises";
9
+ import { mkdir } from "node:fs/promises";
10
10
  import { join } from "node:path";
11
11
 
12
12
  import { ensureBun } from "../util/bun-runtime.js";
@@ -24,8 +24,8 @@ export const ALLOWED_PACKAGES: readonly string[] = [
24
24
  "clsx",
25
25
  ] as const;
26
26
 
27
+ /** Bounds a runaway install; the allowlist above bounds what may be installed. */
27
28
  const INSTALL_TIMEOUT_MS = 10_000;
28
- const MAX_PACKAGE_SIZE_BYTES = 5 * 1024 * 1024; // 5 MB
29
29
 
30
30
  /** In-flight install promises keyed by package name, to deduplicate concurrent requests. */
31
31
  const inflight = new Map<string, Promise<string | null>>();
@@ -148,37 +148,9 @@ async function installPackage(
148
148
  return null;
149
149
  }
150
150
 
151
- // Enforce max size
152
- if (existsSync(pkgDir)) {
153
- const size = await dirSize(pkgDir);
154
- if (size > MAX_PACKAGE_SIZE_BYTES) {
155
- log.warn({ pkg, size }, "Package exceeds size limit, removing");
156
- const { rm } = await import("node:fs/promises");
157
- await rm(pkgDir, { recursive: true, force: true });
158
- return null;
159
- }
160
- }
161
-
162
151
  return existsSync(pkgDir) ? nodeModulesDir : null;
163
152
  } catch (err) {
164
153
  log.warn({ pkg, err }, "Package resolution failed");
165
154
  return null;
166
155
  }
167
156
  }
168
-
169
- /** Recursively sum file sizes under a directory. */
170
- async function dirSize(dir: string): Promise<number> {
171
- const { readdir } = await import("node:fs/promises");
172
- const entries = await readdir(dir, { withFileTypes: true });
173
- let total = 0;
174
- for (const entry of entries) {
175
- const full = join(dir, entry.name);
176
- if (entry.isDirectory()) {
177
- total += await dirSize(full);
178
- } else {
179
- const s = await stat(full);
180
- total += s.size;
181
- }
182
- }
183
- return total;
184
- }
@@ -72,8 +72,13 @@ mock.module("../../persistence/conversation-crud.js", () => ({
72
72
 
73
73
  import { setConfig } from "../../__tests__/helpers/set-config.js";
74
74
  import { ABORT_WATCHDOG_MS } from "../../daemon/abort-watchdog.js";
75
+ import { VOICE_ESCALATION_CONTINUATION_MESSAGE_KIND } from "../../plugin-api/constants.js";
75
76
  import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
76
- import { CALL_OPENING_MARKER } from "../voice-control-protocol.js";
77
+ import {
78
+ CALL_OPENING_MARKER,
79
+ ESCALATE_VERDICT_TOKEN,
80
+ HOLD_VERDICT_TOKEN,
81
+ } from "../voice-control-protocol.js";
77
82
  import {
78
83
  cutFrontDoorContentAtVerdict,
79
84
  preSpeechLanguageRuleFragment,
@@ -121,7 +126,7 @@ interface FakeConversation {
121
126
  metadata?: Record<string, unknown>;
122
127
  }) => Promise<{ id: string }>;
123
128
  workingDir: string;
124
- updateClient: (cb: unknown, reset?: boolean) => void;
129
+ addEventObserver: (observer: unknown) => () => void;
125
130
  handleConfirmationResponse: (
126
131
  requestId: string,
127
132
  decision: string,
@@ -187,13 +192,15 @@ function makeFakeConversation(opts: {
187
192
  opts.onPersist?.(persistCount);
188
193
  return { id: `msg-${persistCount}` };
189
194
  },
190
- // The install (reset falsy) / reset (reset true) pair marks a turn
191
- // taking ownership of the conversation vs a turn's cleanup releasing it.
192
- updateClient: (cb, reset) => {
193
- opts.events?.push(reset ? "client:reset" : "client:install");
194
- if (reset !== true) {
195
- clientCallback = cb as (msg: unknown) => Promise<void>;
196
- }
195
+ // The install / reset pair marks a turn taking ownership of the
196
+ // conversation (observer registered) vs a turn's cleanup releasing it
197
+ // (the returned disposer invoked).
198
+ addEventObserver: (observer) => {
199
+ opts.events?.push("client:install");
200
+ clientCallback = observer as (msg: unknown) => Promise<void>;
201
+ return () => {
202
+ opts.events?.push("client:reset");
203
+ };
197
204
  },
198
205
  handleConfirmationResponse: (requestId, decision) => {
199
206
  confirmationDecisions.push({ requestId, decision });
@@ -209,7 +216,7 @@ function makeFakeConversation(opts: {
209
216
  conversation,
210
217
  waitForIdleCalls,
211
218
  confirmationDecisions,
212
- /** Deliver an event the way the conversation would, to the installed handler. */
219
+ /** Deliver an event the way the conversation would, to the installed observer. */
213
220
  emitToClient: async (msg: unknown) => {
214
221
  await clientCallback?.(msg);
215
222
  },
@@ -356,6 +363,10 @@ describe("startVoiceTurn escalation-continuation persistence", () => {
356
363
  // not merely echo-suppressed live.
357
364
  const fake = makeFakeConversation({ processing: false });
358
365
  fakeConversation = fake.conversation;
366
+ let runOptions: Record<string, unknown> | undefined;
367
+ fake.conversation.runAgentLoop = async (...args: unknown[]) => {
368
+ runOptions = args[2] as Record<string, unknown>;
369
+ };
359
370
 
360
371
  await startVoiceTurn({
361
372
  ...makeTurnOptions(),
@@ -368,7 +379,11 @@ describe("startVoiceTurn escalation-continuation persistence", () => {
368
379
  expect(fake.lastPersistOpts()?.metadata).toEqual({
369
380
  voiceSessionTurn: true,
370
381
  hidden: true,
382
+ messageKind: VOICE_ESCALATION_CONTINUATION_MESSAGE_KIND,
371
383
  });
384
+ expect(runOptions?.messageKind).toBe(
385
+ VOICE_ESCALATION_CONTINUATION_MESSAGE_KIND,
386
+ );
372
387
  });
373
388
 
374
389
  test("the opener prompt is persisted un-hidden (unchanged)", async () => {
@@ -963,7 +978,7 @@ describe("startVoiceTurn prior-turn teardown barrier", () => {
963
978
  expect(events).toEqual(["persist", "client:install"]);
964
979
 
965
980
  // Turn 2 arrives during the teardown window: it must not persist or
966
- // install its client callback until turn 1's cleanup has run.
981
+ // install its event observer until turn 1's cleanup has run.
967
982
  const turn2 = startVoiceTurn(
968
983
  makeTurnOptions(undefined, "conv-teardown-order"),
969
984
  );
@@ -1701,6 +1716,174 @@ describe("cutFrontDoorContentAtVerdict", () => {
1701
1716
  });
1702
1717
  });
1703
1718
 
1719
+ /**
1720
+ * The front-door leg's raw stream is a control plane: its leading tokens are
1721
+ * the routing verdict, not speech. The hub broadcast (web / passive devices)
1722
+ * must never carry those tokens, and must still carry every word of real
1723
+ * assistant content: an over-broad filter would leave the shared transcript
1724
+ * silent or truncated, which is worse than the leak it fixes.
1725
+ */
1726
+ describe("front-door hub stream gate", () => {
1727
+ beforeEach(resetCrudLog);
1728
+
1729
+ /**
1730
+ * Conversation whose scripted agent loop announces a reserved row, streams
1731
+ * `deltas` in order, then ends the leg with `finalEvent`.
1732
+ */
1733
+ function makeStreamingConversation(
1734
+ deltas: string[],
1735
+ finalEvent:
1736
+ | "message_complete"
1737
+ | "generation_cancelled" = "message_complete",
1738
+ ): void {
1739
+ const fake = makeFakeConversation({ processing: false });
1740
+ fake.conversation.runAgentLoop = async (...args: unknown[]) => {
1741
+ const { onEvent } = args[2] as { onEvent: (msg: unknown) => void };
1742
+ onEvent({
1743
+ type: "assistant_turn_start",
1744
+ messageId: "assistant-row-1",
1745
+ conversationId: "conv-voice-bridge-test",
1746
+ });
1747
+ for (const text of deltas) {
1748
+ onEvent({
1749
+ type: "assistant_text_delta",
1750
+ text,
1751
+ messageId: "assistant-row-1",
1752
+ conversationId: "conv-voice-bridge-test",
1753
+ });
1754
+ }
1755
+ onEvent({
1756
+ type: finalEvent,
1757
+ ...(finalEvent === "message_complete"
1758
+ ? { messageId: "assistant-row-1" }
1759
+ : {}),
1760
+ conversationId: "conv-voice-bridge-test",
1761
+ });
1762
+ };
1763
+ fakeConversation = fake.conversation;
1764
+ }
1765
+
1766
+ /** The text of every `assistant_text_delta` `turn` publishes to the hub. */
1767
+ async function collectBroadcastText(
1768
+ turn: () => Promise<unknown>,
1769
+ ): Promise<string[]> {
1770
+ const texts: string[] = [];
1771
+ const subscription = assistantEventHub.subscribe({
1772
+ type: "process",
1773
+ filter: { conversationId: "conv-voice-bridge-test" },
1774
+ callback: (event) => {
1775
+ const msg = event.message as { type: string; text?: string };
1776
+ if (msg.type === "assistant_text_delta") {
1777
+ texts.push(msg.text ?? "");
1778
+ }
1779
+ },
1780
+ });
1781
+ try {
1782
+ await turn();
1783
+ // The hub publishes off a promise chain, so every broadcast costs a
1784
+ // few microtask hops before it reaches a subscriber.
1785
+ await new Promise((resolve) => setTimeout(resolve, 0));
1786
+ await flushMicrotasks();
1787
+ } finally {
1788
+ subscription.dispose();
1789
+ }
1790
+ return texts;
1791
+ }
1792
+
1793
+ test("an escalating leg broadcasts only the capped bridge, never the verdict token", async () => {
1794
+ makeStreamingConversation([
1795
+ "[1]",
1796
+ " Let me check your calendar.",
1797
+ " weak answer past the cap",
1798
+ ]);
1799
+
1800
+ const texts = await collectBroadcastText(() =>
1801
+ startVoiceTurn({ ...makeTurnOptions(), routingLeg: "front-door" }),
1802
+ );
1803
+
1804
+ expect(texts).toEqual(["Let me check your calendar."]);
1805
+ expect(texts.join("")).not.toContain(ESCALATE_VERDICT_TOKEN);
1806
+ });
1807
+
1808
+ test("a front-door answer reaches the hub in full", async () => {
1809
+ makeStreamingConversation(["It is ", "Tuesday", ", and it is sunny."]);
1810
+
1811
+ const texts = await collectBroadcastText(() =>
1812
+ startVoiceTurn({ ...makeTurnOptions(), routingLeg: "front-door" }),
1813
+ );
1814
+
1815
+ expect(texts.join("")).toBe("It is Tuesday, and it is sunny.");
1816
+ });
1817
+
1818
+ test("an answer that merely opens with a bracket is released in full", async () => {
1819
+ // "[" alone could still become the escalate token, so the gate holds it;
1820
+ // the next delta disproves the token and the whole prefix must come out.
1821
+ makeStreamingConversation(["[", "A] is the option to pick."]);
1822
+
1823
+ const texts = await collectBroadcastText(() =>
1824
+ startVoiceTurn({ ...makeTurnOptions(), routingLeg: "front-door" }),
1825
+ );
1826
+
1827
+ expect(texts.join("")).toBe("[A] is the option to pick.");
1828
+ });
1829
+
1830
+ test("a hold verdict broadcasts nothing", async () => {
1831
+ makeStreamingConversation([HOLD_VERDICT_TOKEN]);
1832
+
1833
+ const texts = await collectBroadcastText(() =>
1834
+ startVoiceTurn({
1835
+ ...makeTurnOptions(),
1836
+ routingLeg: "front-door",
1837
+ unifiedVerdict: true,
1838
+ }),
1839
+ );
1840
+
1841
+ expect(texts).toEqual([]);
1842
+ });
1843
+
1844
+ test("a leg that completes mid-bridge broadcasts what it handed off with", async () => {
1845
+ makeStreamingConversation(["[1] Let me check"]);
1846
+
1847
+ const texts = await collectBroadcastText(() =>
1848
+ startVoiceTurn({ ...makeTurnOptions(), routingLeg: "front-door" }),
1849
+ );
1850
+
1851
+ expect(texts).toEqual(["Let me check"]);
1852
+ });
1853
+
1854
+ test("a leg cancelled mid-bridge never hands off, so it broadcasts nothing", async () => {
1855
+ makeStreamingConversation(["[1] Let me check"], "generation_cancelled");
1856
+
1857
+ const texts = await collectBroadcastText(() =>
1858
+ startVoiceTurn({ ...makeTurnOptions(), routingLeg: "front-door" }),
1859
+ );
1860
+
1861
+ expect(texts).toEqual([]);
1862
+ });
1863
+
1864
+ test("the escalated continuation streams to the hub untouched", async () => {
1865
+ // The leg that produces the real answer is never gated: its deltas are
1866
+ // assistant speech from the first token.
1867
+ makeStreamingConversation(["Your next meeting ", "is at four."]);
1868
+
1869
+ const texts = await collectBroadcastText(() =>
1870
+ startVoiceTurn({ ...makeTurnOptions(), routingLeg: "escalated" }),
1871
+ );
1872
+
1873
+ expect(texts).toEqual(["Your next meeting ", "is at four."]);
1874
+ });
1875
+
1876
+ test("an un-routed voice leg streams to the hub untouched", async () => {
1877
+ makeStreamingConversation(["[1] not a verdict here"]);
1878
+
1879
+ const texts = await collectBroadcastText(() =>
1880
+ startVoiceTurn(makeTurnOptions()),
1881
+ );
1882
+
1883
+ expect(texts).toEqual(["[1] not a verdict here"]);
1884
+ });
1885
+ });
1886
+
1704
1887
  describe("transcript hygiene (teardown pass)", () => {
1705
1888
  beforeEach(resetCrudLog);
1706
1889