@vellumai/assistant 0.11.3 → 0.11.4-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 (341) hide show
  1. package/ARCHITECTURE.md +11 -6
  2. package/docs/architecture/memory.md +11 -0
  3. package/docs/architecture/turn-actor.md +70 -0
  4. package/docs/flux-turn-detection-spike.md +243 -0
  5. package/docs/stt-provider-onboarding.md +3 -1
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  8. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
  9. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  10. package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  11. package/openapi.yaml +139 -37
  12. package/package.json +1 -1
  13. package/scripts/voice-ttft-spike.ts +3 -3
  14. package/src/__tests__/app-compiler.test.ts +38 -3
  15. package/src/__tests__/attachments-store.test.ts +21 -12
  16. package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
  17. package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
  18. package/src/__tests__/call-site-routing-provider.test.ts +1 -1
  19. package/src/__tests__/channel-availability-routes.test.ts +14 -1
  20. package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
  21. package/src/__tests__/channel-delivery-store.test.ts +14 -14
  22. package/src/__tests__/config-loader-backfill.test.ts +3 -3
  23. package/src/__tests__/config-schema.test.ts +25 -10
  24. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
  25. package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
  26. package/src/__tests__/conversation-agent-loop.test.ts +28 -20
  27. package/src/__tests__/conversation-attention-store.test.ts +63 -0
  28. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
  29. package/src/__tests__/conversation-fork-crud.test.ts +69 -0
  30. package/src/__tests__/conversation-fork-referential.test.ts +67 -0
  31. package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
  32. package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
  33. package/src/__tests__/conversation-queue.test.ts +177 -4
  34. package/src/__tests__/conversation-runtime-assembly.test.ts +134 -102
  35. package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
  36. package/src/__tests__/credential-prompt-route.test.ts +7 -10
  37. package/src/__tests__/custom-profile-ensure.test.ts +5 -1
  38. package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
  39. package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
  40. package/src/__tests__/document-append-idempotency.test.ts +233 -0
  41. package/src/__tests__/edit-propagation.test.ts +0 -7
  42. package/src/__tests__/helpers/mock-actor-context.ts +49 -0
  43. package/src/__tests__/helpers/mock-conversation.ts +13 -1
  44. package/src/__tests__/injector-chain.test.ts +63 -41
  45. package/src/__tests__/injector-disk-pressure.test.ts +11 -23
  46. package/src/__tests__/llm-context-resolution.test.ts +73 -1
  47. package/src/__tests__/llm-schema.test.ts +5 -2
  48. package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
  49. package/src/__tests__/memory-retrieval-hook.test.ts +6 -5
  50. package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
  51. package/src/__tests__/mtime-cache.test.ts +1 -1
  52. package/src/__tests__/non-member-access-request.test.ts +0 -20
  53. package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
  54. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -0
  55. package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
  56. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
  57. package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
  58. package/src/__tests__/run-conversation-turn-persistence.test.ts +434 -105
  59. package/src/__tests__/scoped-approval-grants.test.ts +11 -6
  60. package/src/__tests__/secret-ingress-channel.test.ts +0 -1
  61. package/src/__tests__/skills.test.ts +32 -0
  62. package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
  63. package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
  64. package/src/__tests__/subagent-spawn-and-await.test.ts +14 -10
  65. package/src/__tests__/turn-events-store.test.ts +43 -0
  66. package/src/__tests__/ui-shape-teaching.test.ts +33 -0
  67. package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
  68. package/src/__tests__/user-plugin-loader.test.ts +1 -1
  69. package/src/__tests__/visible-app-context.test.ts +16 -9
  70. package/src/__tests__/voice-config-update.test.ts +40 -0
  71. package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
  72. package/src/__tests__/worker-plugin-surface.test.ts +77 -0
  73. package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
  74. package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
  75. package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
  76. package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
  77. package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
  78. package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
  79. package/src/acp/__tests__/auth-required.test.ts +161 -0
  80. package/src/acp/acp-claude-oauth.ts +19 -2
  81. package/src/acp/agent-process.test.ts +100 -0
  82. package/src/acp/agent-process.ts +29 -26
  83. package/src/acp/auth-required.ts +102 -0
  84. package/src/acp/session-manager.test.ts +119 -0
  85. package/src/acp/session-manager.ts +68 -2
  86. package/src/api/events/acp-auth-required.ts +55 -0
  87. package/src/api/index.ts +7 -0
  88. package/src/api/surfaces.ts +7 -3
  89. package/src/apps/app-store.ts +3 -0
  90. package/src/bundler/package-resolver.ts +2 -30
  91. package/src/calls/__tests__/voice-session-bridge.test.ts +173 -1
  92. package/src/calls/__tests__/voice-triage-escalate.test.ts +94 -0
  93. package/src/calls/call-controller.ts +19 -3
  94. package/src/calls/call-setup-flow.ts +0 -1
  95. package/src/calls/media-stream-stt-session.ts +15 -0
  96. package/src/calls/voice-session-bridge.ts +71 -16
  97. package/src/calls/voice-triage-escalate.ts +104 -2
  98. package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
  99. package/src/channels/config.ts +13 -0
  100. package/src/channels/plugin-channel-declarations.ts +108 -0
  101. package/src/channels/types.ts +30 -0
  102. package/src/cli/AGENTS.md +5 -2
  103. package/src/cli/commands/credentials.help.ts +2 -2
  104. package/src/cli/commands/inference-providers.ts +1 -1
  105. package/src/cli/commands/mcp.help.ts +13 -4
  106. package/src/cli/commands/mcp.ts +9 -0
  107. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
  108. package/src/cli/commands/memory/index.help.ts +43 -1
  109. package/src/cli/commands/memory/memory-v3.ts +64 -0
  110. package/src/cli/commands/stt.help.ts +27 -2
  111. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
  112. package/src/cli/lib/bundled-marketplace.json +13 -0
  113. package/src/cli/lib/upgrade-plugin.ts +42 -0
  114. package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
  115. package/src/config/__tests__/default-provider.test.ts +6 -1
  116. package/src/config/__tests__/profile-materialization.test.ts +75 -19
  117. package/src/config/bundled-skills/acp/SKILL.md +6 -7
  118. package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
  119. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  120. package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
  121. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  122. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
  123. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
  124. package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
  125. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
  126. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
  127. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
  128. package/src/config/call-site-defaults.ts +4 -5
  129. package/src/config/default-profile-catalog.ts +83 -12
  130. package/src/config/default-profile-names.ts +4 -1
  131. package/src/config/default-provider-resolution.ts +4 -0
  132. package/src/config/llm-context-resolution.ts +11 -3
  133. package/src/config/llm-resolver.ts +28 -1
  134. package/src/config/profile-materialization.ts +70 -22
  135. package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
  136. package/src/config/schemas/call-site-catalog.ts +4 -4
  137. package/src/config/schemas/live-voice.ts +57 -23
  138. package/src/config/schemas/llm.ts +59 -32
  139. package/src/config/schemas/mcp.ts +23 -0
  140. package/src/config/schemas/plugin-updates.ts +6 -2
  141. package/src/config/schemas/stt.ts +1 -0
  142. package/src/context/outbound-sanitize.ts +96 -1
  143. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
  144. package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
  145. package/src/daemon/conversation-agent-loop.ts +17 -6
  146. package/src/daemon/conversation-messaging.ts +5 -1
  147. package/src/daemon/conversation-notifiers.ts +9 -1
  148. package/src/daemon/conversation-process.ts +36 -6
  149. package/src/daemon/conversation-runtime-assembly.ts +3 -4
  150. package/src/daemon/conversation-surfaces.ts +27 -5
  151. package/src/daemon/conversation-tool-setup.ts +1 -2
  152. package/src/daemon/conversation.ts +48 -0
  153. package/src/daemon/interactive-turn-sender.ts +59 -0
  154. package/src/daemon/mcp-reload-service.ts +36 -6
  155. package/src/daemon/process-message.ts +24 -24
  156. package/src/daemon/providers-setup.ts +6 -3
  157. package/src/daemon/trust-context-types.ts +29 -0
  158. package/src/daemon/wake-conversation-ops.ts +3 -2
  159. package/src/documents/document-store.ts +138 -5
  160. package/src/hooks/hook-loader.ts +3 -3
  161. package/src/hooks/registry.ts +50 -6
  162. package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
  163. package/src/inbound/oauth-callback-url.ts +61 -0
  164. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
  165. package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
  166. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +932 -0
  167. package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
  168. package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
  169. package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
  170. package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
  171. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
  172. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
  173. package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
  174. package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
  175. package/src/live-voice/live-voice-archive.ts +2 -0
  176. package/src/live-voice/live-voice-metrics.ts +57 -32
  177. package/src/live-voice/live-voice-photo.ts +1 -2
  178. package/src/live-voice/live-voice-session.ts +535 -314
  179. package/src/live-voice/progress-narration.ts +277 -0
  180. package/src/live-voice/protocol.ts +21 -1
  181. package/src/mcp/__tests__/effective-config.test.ts +238 -0
  182. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  183. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
  184. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
  185. package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
  186. package/src/mcp/client.ts +16 -11
  187. package/src/mcp/effective-config.ts +113 -0
  188. package/src/mcp/manager.ts +11 -6
  189. package/src/mcp/mcp-auth-orchestrator.ts +13 -22
  190. package/src/mcp/mcp-oauth-provider.ts +205 -240
  191. package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
  192. package/src/monitoring/plugin-auto-update.ts +128 -24
  193. package/src/notifications/signal.ts +1 -0
  194. package/src/permissions/confirmation-guardian-request.test.ts +15 -11
  195. package/src/permissions/confirmation-guardian-request.ts +2 -2
  196. package/src/permissions/question-guardian-request.test.ts +14 -6
  197. package/src/permissions/question-guardian-request.ts +1 -2
  198. package/src/persistence/attachments-store.ts +8 -1
  199. package/src/persistence/bookmark-crud.ts +3 -7
  200. package/src/persistence/conversation-attention-store.ts +16 -45
  201. package/src/persistence/conversation-crud.ts +33 -4
  202. package/src/persistence/conversation-lineage.ts +9 -0
  203. package/src/persistence/conversation-queries.ts +108 -41
  204. package/src/persistence/delivery-crud.ts +38 -29
  205. package/src/persistence/external-conversation-store.ts +32 -4
  206. package/src/persistence/llm-request-log-store.ts +4 -10
  207. package/src/persistence/llm-usage-store.ts +8 -3
  208. package/src/persistence/message-reads.test.ts +197 -0
  209. package/src/persistence/message-reads.ts +211 -0
  210. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
  211. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
  212. package/src/persistence/real-user-turn-filter.ts +27 -3
  213. package/src/persistence/steps.ts +9 -0
  214. package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
  215. package/src/plugin-api/conversation-turn.ts +168 -5
  216. package/src/plugin-api/index.ts +21 -5
  217. package/src/plugin-api/vision-support.test.ts +1 -1
  218. package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
  219. package/src/plugins/defaults/memory/AGENTS.md +4 -0
  220. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
  221. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
  222. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
  223. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
  224. package/src/plugins/defaults/memory/buffer-format.ts +165 -0
  225. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
  226. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
  227. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
  228. package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
  229. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +8 -12
  230. package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
  231. package/src/plugins/defaults/memory/indexer.ts +3 -1
  232. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
  233. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
  234. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
  235. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +33 -0
  236. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +199 -2
  237. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +25 -18
  238. package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
  239. package/src/plugins/defaults/memory/substrate/static-context.ts +160 -4
  240. package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
  241. package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
  242. package/src/plugins/defaults/memory/v3/prune.ts +2 -0
  243. package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
  244. package/src/plugins/defaults/memory/worker.ts +6 -3
  245. package/src/plugins/external-plugin-loader.ts +47 -0
  246. package/src/plugins/mcp-servers.ts +361 -0
  247. package/src/plugins/mtime-cache.ts +23 -49
  248. package/src/plugins/worker-plugin-surface.ts +33 -0
  249. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  250. package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
  251. package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
  252. package/src/providers/__tests__/retry-callsite.test.ts +5 -2
  253. package/src/providers/call-site-routing.ts +30 -3
  254. package/src/providers/connection-resolution.ts +194 -11
  255. package/src/providers/inference/auth.ts +6 -0
  256. package/src/providers/inference/connection-availability.ts +24 -2
  257. package/src/providers/inference/connections.ts +2 -0
  258. package/src/providers/model-catalog.ts +3 -3
  259. package/src/providers/model-intents.ts +28 -8
  260. package/src/providers/openai/chat-completions-provider.ts +5 -6
  261. package/src/providers/openai/codex-models.ts +2 -1
  262. package/src/providers/provider-send-message.ts +32 -3
  263. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
  264. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +620 -0
  265. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
  266. package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
  267. package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
  268. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +719 -0
  269. package/src/providers/speech-to-text/provider-catalog.ts +99 -8
  270. package/src/providers/speech-to-text/resolve.ts +25 -2
  271. package/src/routes/worker.ts +17 -5
  272. package/src/runtime/access-request-helper.ts +9 -12
  273. package/src/runtime/agent-wake.ts +3 -3
  274. package/src/runtime/pre-first-message-gate.ts +4 -0
  275. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
  276. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +219 -1
  277. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
  278. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
  279. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
  280. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
  281. package/src/runtime/routes/__tests__/plugins-routes.test.ts +44 -0
  282. package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
  283. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +62 -1
  284. package/src/runtime/routes/channel-availability-routes.ts +32 -14
  285. package/src/runtime/routes/channel-route-shared.ts +0 -6
  286. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
  287. package/src/runtime/routes/conversation-list-routes.ts +112 -1
  288. package/src/runtime/routes/conversation-query-routes.ts +40 -27
  289. package/src/runtime/routes/credential-prompt-routes.ts +4 -7
  290. package/src/runtime/routes/default-provider-routes.ts +15 -0
  291. package/src/runtime/routes/inbound-message-handler.ts +17 -41
  292. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
  293. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
  294. package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
  295. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
  296. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
  297. package/src/runtime/routes/inbound-stages/edit-intercept.ts +1 -3
  298. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
  299. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -4
  300. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +0 -1
  301. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +11 -20
  302. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
  303. package/src/runtime/routes/inference-profiles-routes.ts +20 -11
  304. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
  305. package/src/runtime/routes/log-export-routes.ts +3 -0
  306. package/src/runtime/routes/mcp-auth-routes.ts +148 -57
  307. package/src/runtime/routes/plugins-routes.ts +21 -3
  308. package/src/runtime/routes/stt-routes.ts +31 -25
  309. package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
  310. package/src/runtime/routes/user-route-dispatcher.ts +39 -14
  311. package/src/runtime/routes/user-route-import.ts +108 -0
  312. package/src/schedule/worker.ts +6 -0
  313. package/src/security/oauth2.ts +6 -22
  314. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
  315. package/src/stt/__tests__/types.test.ts +94 -0
  316. package/src/stt/daemon-batch-transcriber.ts +10 -0
  317. package/src/stt/stt-stream-session.ts +8 -4
  318. package/src/stt/types.ts +103 -0
  319. package/src/subagent/manager.ts +1 -3
  320. package/src/subagent/types.ts +7 -6
  321. package/src/tools/acp/spawn.test.ts +97 -0
  322. package/src/tools/acp/spawn.ts +32 -0
  323. package/src/tools/document/document-tool.ts +12 -3
  324. package/src/tools/registry.ts +2 -1
  325. package/src/tools/ui-surface/surface-shape-docs.ts +11 -0
  326. package/src/tools/workflows/run-workflow.ts +1 -2
  327. package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
  328. package/src/tts/reasoning-tag-filter.ts +89 -0
  329. package/src/util/think-tag-stream.ts +95 -0
  330. package/src/workspace/byok-default-profile-ensure.ts +76 -24
  331. package/src/workspace/custom-profile-ensure.ts +4 -24
  332. package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
  333. package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
  334. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
  335. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
  336. package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
  337. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
  338. package/src/workspace/migrations/registry.ts +10 -0
  339. package/src/workspace/provider-commit-message-generator.ts +7 -5
  340. package/src/live-voice/__tests__/front-decision.test.ts +0 -645
  341. package/src/live-voice/front-decision.ts +0 -476
@@ -147,6 +147,11 @@ const { _resetAdapterInstallCacheForTests } =
147
147
  await import("../../acp/auto-install.js");
148
148
  const { ACP_CLAUDE_OAUTH_MISSING_CODE } =
149
149
  await import("../../acp/prepare-agent-env.js");
150
+ const { ACP_CLAUDE_AUTH_REQUIRED_CODE, AcpAuthRequiredError } =
151
+ await import("../../acp/auth-required.js");
152
+ const { hasAcpConnectCardRaised } =
153
+ await import("../../acp/acp-connect-card-state.js");
154
+ const { AcpAgentProcess } = await import("../../acp/agent-process.js");
150
155
 
151
156
  // ---------------------------------------------------------------------------
152
157
  // Helpers
@@ -547,3 +552,95 @@ describe("executeAcpSpawn — CLAUDE_CODE_OAUTH_TOKEN injection", () => {
547
552
  expect(result.errorCode).toBe(ACP_CLAUDE_OAUTH_MISSING_CODE);
548
553
  });
549
554
  });
555
+
556
+ // ---------------------------------------------------------------------------
557
+ // Pre-spawn Claude auth rejection
558
+ // ---------------------------------------------------------------------------
559
+
560
+ describe("pre-spawn Claude auth rejection", () => {
561
+ test("raises the recovery surface: errorCode, card guidance, registry mark", async () => {
562
+ // The adapter can reject session creation with auth_required (a stored
563
+ // credential the server refuses). The error thrown here is produced by
564
+ // the REAL conversion seam, not hand-built: a wire-shaped JSON-RPC
565
+ // auth_required rejection driven through AcpAgentProcess.createSession's
566
+ // actual retry path, so this test fails if that seam stops yielding the
567
+ // typed error the tool boundary maps to the recovery surface.
568
+ const proc = new AcpAgentProcess(
569
+ "claude",
570
+ { command: "claude-agent-acp", args: [] },
571
+ (() => ({})) as never,
572
+ );
573
+ (proc as unknown as { connection: unknown }).connection = {
574
+ newSession: () =>
575
+ Promise.reject({ code: -32000, message: "Authentication required" }),
576
+ };
577
+ (proc as unknown as { initializeResponse: unknown }).initializeResponse = {
578
+ authMethods: [
579
+ {
580
+ id: "claude-ai-login",
581
+ name: "Claude Subscription",
582
+ type: "terminal",
583
+ },
584
+ ],
585
+ };
586
+ (proc as unknown as { spawnedEnv: unknown }).spawnedEnv = {};
587
+ const realSeamError: unknown = await proc.createSession("/tmp").then(
588
+ () => null,
589
+ (e: unknown) => e,
590
+ );
591
+ expect(realSeamError).toBeInstanceOf(AcpAuthRequiredError);
592
+
593
+ spawnMock.mockImplementationOnce(async () => {
594
+ throw realSeamError;
595
+ });
596
+
597
+ const context = { ...makeContext(), conversationId: "conv-prespawn-auth" };
598
+ const result = await executeAcpSpawn(
599
+ { agent: "claude", task: "do something" },
600
+ context,
601
+ );
602
+
603
+ expect(result.isError).toBe(true);
604
+ expect(result.errorCode).toBe(ACP_CLAUDE_AUTH_REQUIRED_CODE);
605
+ expect(result.content).toContain("Connect Claude Code");
606
+ expect(hasAcpConnectCardRaised("conv-prespawn-auth")).toBe(true);
607
+ });
608
+
609
+ test("a raw wire auth_required object (unwrapped initialize path) gets the same surface", async () => {
610
+ // initialize() cannot ride withAuthRetry (its response advertises the
611
+ // methods the retry selects from), so its rejection arrives as the plain
612
+ // JSON-RPC object rather than the typed error. The gate must recognize
613
+ // both shapes or this path silently flattens to a generic failure.
614
+ spawnMock.mockImplementationOnce(async () => {
615
+ throw { code: -32000, message: "Authentication required" };
616
+ });
617
+
618
+ const context = { ...makeContext(), conversationId: "conv-prespawn-raw" };
619
+ const result = await executeAcpSpawn(
620
+ { agent: "claude", task: "do something" },
621
+ context,
622
+ );
623
+
624
+ expect(result.isError).toBe(true);
625
+ expect(result.errorCode).toBe(ACP_CLAUDE_AUTH_REQUIRED_CODE);
626
+ expect(result.content).toContain("Connect Claude Code");
627
+ expect(hasAcpConnectCardRaised("conv-prespawn-raw")).toBe(true);
628
+ });
629
+
630
+ test("a non-auth spawn failure keeps the plain flattened result", async () => {
631
+ spawnMock.mockImplementationOnce(async () => {
632
+ throw new Error("adapter exploded");
633
+ });
634
+
635
+ const context = { ...makeContext(), conversationId: "conv-prespawn-boom" };
636
+ const result = await executeAcpSpawn(
637
+ { agent: "claude", task: "do something" },
638
+ context,
639
+ );
640
+
641
+ expect(result.isError).toBe(true);
642
+ expect(result.errorCode).toBeUndefined();
643
+ expect(result.content).toContain("adapter exploded");
644
+ expect(hasAcpConnectCardRaised("conv-prespawn-boom")).toBe(false);
645
+ });
646
+ });
@@ -3,6 +3,12 @@ import { basename } from "node:path";
3
3
  import { z } from "zod";
4
4
 
5
5
  import { markAcpConnectCardRaised } from "../../acp/acp-connect-card-state.js";
6
+ import {
7
+ ACP_AUTH_RECOVERY_GUIDANCE,
8
+ ACP_CLAUDE_AUTH_REQUIRED_CODE,
9
+ CLAUDE_ACP_COMMAND,
10
+ isAcpAuthRequired,
11
+ } from "../../acp/auth-required.js";
6
12
  import { resolveAgentWithAutoInstall } from "../../acp/auto-install.js";
7
13
  import { getAcpSessionManager } from "../../acp/index.js";
8
14
  import {
@@ -147,6 +153,32 @@ export async function executeAcpSpawn(
147
153
 
148
154
  return { content: payload, isError: false };
149
155
  } catch (err) {
156
+ // A pre-spawn rejection of the stored Claude credential (the adapter
157
+ // raises auth_required during session creation) gets the same recovery
158
+ // surface as the missing-token preflight: the errorCode raises the inline
159
+ // Connect card off this failed tool call, the registry mark dedups a
160
+ // redundant secure prompt, and the guidance keeps the model pointed at
161
+ // the card. Adapter-gated: the card repairs Claude credentials only.
162
+ // `isAcpAuthRequired` rather than an instanceof: it also recognizes the
163
+ // raw JSON-RPC auth_required object, which is how a rejection reaches
164
+ // this catch from the one protocol call `withAuthRetry` cannot wrap
165
+ // (`initialize`, whose response is what advertises the auth methods the
166
+ // retry would need).
167
+ if (
168
+ isAcpAuthRequired(err) &&
169
+ basename(agentConfig.command) === CLAUDE_ACP_COMMAND
170
+ ) {
171
+ const message =
172
+ err instanceof Error
173
+ ? err.message
174
+ : String((err as { message?: unknown }).message ?? err);
175
+ markAcpConnectCardRaised(context.conversationId);
176
+ return {
177
+ content: `${message}\n\n${ACP_AUTH_RECOVERY_GUIDANCE}`,
178
+ isError: true,
179
+ errorCode: ACP_CLAUDE_AUTH_REQUIRED_CODE,
180
+ };
181
+ }
150
182
  const msg =
151
183
  err instanceof Error
152
184
  ? err.message
@@ -426,13 +426,22 @@ export function executeDocumentUpdate(
426
426
  };
427
427
  }
428
428
 
429
+ // The store drops append content that merely restates the document's tail,
430
+ // so the client renders what actually landed rather than the submission.
431
+ const applied = result.appliedMarkdown;
432
+ const message = result.duplicateLeadingContentSkipped
433
+ ? applied.length === 0
434
+ ? "Nothing appended: the content is already at the end of the document"
435
+ : "Document content updated (leading content already at the end of the document was skipped)"
436
+ : "Document content updated";
437
+
429
438
  // Send document_editor_update message to update the built-in RTE
430
439
  if (context.sendToClient) {
431
440
  context.sendToClient({
432
441
  type: "document_editor_update",
433
442
  conversationId: context.conversationId,
434
443
  surfaceId,
435
- markdown: content,
444
+ markdown: applied,
436
445
  mode,
437
446
  });
438
447
 
@@ -441,7 +450,7 @@ export function executeDocumentUpdate(
441
450
  success: true,
442
451
  surface_id: surfaceId,
443
452
  mode,
444
- message: "Document content updated",
453
+ message,
445
454
  }),
446
455
  isError: false,
447
456
  };
@@ -458,7 +467,7 @@ export function executeDocumentUpdate(
458
467
  success: true,
459
468
  surface_id: surfaceId,
460
469
  mode,
461
- message: "Document content updated (no client connected to render it)",
470
+ message: `${message} (no client connected to render it)`,
462
471
  }),
463
472
  isError: false,
464
473
  };
@@ -431,7 +431,8 @@ export function unregisterPluginTools(pluginName: string): void {
431
431
  * {@link import("../plugins/mtime-cache.js").getActiveUserPluginTools} and diffs
432
432
  * the result into the registry: plugins that vanished are unregistered, new
433
433
  * plugins register, and a plugin whose per-tool source mtimes moved is
434
- * re-registered. Mirrors how hooks resolve through `getUserHookEntriesFor`.
434
+ * re-registered. Mirrors how hooks resolve through `getUserHookEntriesFor`
435
+ * in `hooks/registry.ts`.
435
436
  *
436
437
  * This is a pure read of the plugin cache — it does NOT reconcile the cache
437
438
  * against the source-versions sentinel, so it never activates a plugin or runs
@@ -211,6 +211,17 @@ export const SURFACE_SHAPE_DOCS: Record<string, SurfaceShapeDoc> = {
211
211
  ? null
212
212
  : '`data.channel` must be one of "slack", "telegram", "phone"',
213
213
  },
214
+ voice_picker: {
215
+ // The steering lives in `purpose` because that is the only part of a cold
216
+ // type the model ever sees: `shape` ships solely inside a teaching error,
217
+ // and an empty payload has no missing-content state to raise one. The
218
+ // surface exists to replace prose voice tours, so losing the directive
219
+ // would leave nothing but a renderer nobody invokes.
220
+ purpose:
221
+ "inline picker for the voice the assistant speaks in; show it when the user asks to change, hear, or pick a voice rather than describing voices in prose or sending them to Settings; attach no actions and never set await_action; during a live voice call answer in speech instead, the room already carries its own picker",
222
+ shape:
223
+ "{}: no payload, the card reads the current voice and the catalog itself. Selecting a voice applies on the assistant's next spoken turn",
224
+ },
214
225
  };
215
226
 
216
227
  /** Model-facing surface_type enum, derived so docs and schema cannot drift. */
@@ -55,8 +55,7 @@ export const runWorkflowInputSchema = z.looseObject({
55
55
  */
56
56
  function resolveTrustContext(context: ToolContext): TrustContext {
57
57
  const conversation = findConversation(context.conversationId);
58
- const fromConversation =
59
- conversation?.currentTurnTrustContext ?? conversation?.trustContext;
58
+ const fromConversation = conversation?.getTurnOrRestingTrust();
60
59
  if (fromConversation) {
61
60
  return fromConversation;
62
61
  }
@@ -0,0 +1,78 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { createReasoningTagFilter } from "../reasoning-tag-filter.js";
4
+
5
+ function run(chunks: string[]): string {
6
+ const filter = createReasoningTagFilter();
7
+ let out = "";
8
+ for (const chunk of chunks) {
9
+ out += filter.push(chunk);
10
+ }
11
+ out += filter.flush();
12
+ return out;
13
+ }
14
+
15
+ describe("ReasoningTagFilter", () => {
16
+ test("passes plain text through unchanged", () => {
17
+ expect(run(["Hello ", "there."])).toBe("Hello there.");
18
+ });
19
+
20
+ test("drops a complete think span within one delta", () => {
21
+ expect(run(["<think>hm, let me reason</think>Answer."])).toBe("Answer.");
22
+ });
23
+
24
+ test("drops a reasoning span crossing many deltas", () => {
25
+ expect(
26
+ run(["<thi", "nk>step one", " step two", "</th", "ink>Spoken reply"]),
27
+ ).toBe("Spoken reply");
28
+ });
29
+
30
+ test("drops thinking-tag variant and is case-insensitive", () => {
31
+ expect(run(["<Thinking>inner</Thinking>ok"])).toBe("ok");
32
+ });
33
+
34
+ test("keeps text before and after the span", () => {
35
+ expect(run(["Sure. <think>secret</think> Done."])).toBe("Sure. Done.");
36
+ });
37
+
38
+ test("handles multiple spans in one turn", () => {
39
+ expect(run(["a<think>x</think>b", "<think>y", "z</think>c"])).toBe("abc");
40
+ });
41
+
42
+ test("an unclosed span at end of stream stays dropped", () => {
43
+ expect(run(["ok<think>never closes"])).toBe("ok");
44
+ });
45
+
46
+ test("a dangling partial open tag is released as literal text at flush", () => {
47
+ expect(run(["price <th"])).toBe("price <th");
48
+ });
49
+
50
+ test("a false partial that disambiguates to text is released", () => {
51
+ expect(run(["a <th", "ree-step plan"])).toBe("a <three-step plan");
52
+ });
53
+
54
+ test("does not touch angle brackets that are not think tags", () => {
55
+ expect(run(["use <b>bold</b> and x < y"])).toBe(
56
+ "use <b>bold</b> and x < y",
57
+ );
58
+ });
59
+
60
+ test("holds back nothing across an empty stream", () => {
61
+ expect(run([""])).toBe("");
62
+ });
63
+
64
+ test("does not shift indexes for characters whose lowercase form is longer", () => {
65
+ // U+0130 (dotted capital I) lowers to two code units; positional
66
+ // scanning must never lowercase the haystack wholesale.
67
+ expect(run(["\u0130stanbul rocks. <think>hidden</think> Done."])).toBe(
68
+ "\u0130stanbul rocks. Done.",
69
+ );
70
+ expect(run(["\u0130\u0130\u0130<THINK>x</THINK>ok"])).toBe(
71
+ "\u0130\u0130\u0130ok",
72
+ );
73
+ });
74
+
75
+ test("prefers the longer tag when both match at one index", () => {
76
+ expect(run(["a<thinking>x</thinking>b"])).toBe("ab");
77
+ });
78
+ });
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Streaming reasoning-tag filter for the TTS pipelines.
3
+ *
4
+ * Some OpenAI-compatible reasoning models emit their chain of thought inline
5
+ * in the content stream as `<think>...</think>` (or `<thinking>...</thinking>`)
6
+ * spans. When a profile has not opted into the chat-completions provider's
7
+ * `parseThinkTags` (which re-routes those spans to `thinking_delta`), the raw
8
+ * reasoning rides `text_delta`, and anything downstream that speaks text
9
+ * deltas would read the model's inner monologue aloud and delay real speech
10
+ * by the full length of the reasoning.
11
+ *
12
+ * This filter is the TTS-boundary guard: voice output must never speak
13
+ * reasoning regardless of profile configuration. It is stateful because a
14
+ * tag or a reasoning span routinely crosses delta boundaries; feed every
15
+ * delta through {@link ReasoningTagFilter.push} and call
16
+ * {@link ReasoningTagFilter.flush} when the turn's stream ends.
17
+ *
18
+ * Display paths are deliberately untouched: `assistant_text_delta` frames
19
+ * keep the raw text, exactly like the markdown handling in
20
+ * `calls/tts-text-sanitizer.ts`.
21
+ *
22
+ * Tag scanning lives in `util/think-tag-stream.ts`, shared with the
23
+ * chat-completions provider's think-tag router, and is positionally exact
24
+ * on the original string (no full-string lowercasing, which shifts indexes
25
+ * for characters like the dotted capital I).
26
+ */
27
+
28
+ import { indexOfTag, partialTagSuffix } from "../util/think-tag-stream.js";
29
+
30
+ const OPEN_TAGS = ["<think>", "<thinking>"] as const;
31
+ const CLOSE_TAGS = ["</think>", "</thinking>"] as const;
32
+
33
+ export class ReasoningTagFilter {
34
+ private insideReasoning = false;
35
+ private pending = "";
36
+
37
+ /**
38
+ * Feed one streamed delta; returns the speakable text it releases.
39
+ * Reasoning spans are dropped; a partial tag at the end of the buffer is
40
+ * held back until the next delta disambiguates it.
41
+ */
42
+ push(text: string): string {
43
+ this.pending += text;
44
+ let out = "";
45
+ for (;;) {
46
+ if (this.insideReasoning) {
47
+ const close = indexOfTag(this.pending, CLOSE_TAGS, true);
48
+ if (close) {
49
+ this.pending = this.pending.slice(close.index + close.tag.length);
50
+ this.insideReasoning = false;
51
+ continue;
52
+ }
53
+ // Everything buffered is reasoning except a possible partial close
54
+ // tag at the end; drop the reasoning, keep the partial.
55
+ const partial = partialTagSuffix(this.pending, CLOSE_TAGS, true);
56
+ this.pending = partial > 0 ? this.pending.slice(-partial) : "";
57
+ return out;
58
+ }
59
+ const open = indexOfTag(this.pending, OPEN_TAGS, true);
60
+ if (open) {
61
+ out += this.pending.slice(0, open.index);
62
+ this.pending = this.pending.slice(open.index + open.tag.length);
63
+ this.insideReasoning = true;
64
+ continue;
65
+ }
66
+ const partial = partialTagSuffix(this.pending, OPEN_TAGS, true);
67
+ const safeLength = this.pending.length - partial;
68
+ out += this.pending.slice(0, safeLength);
69
+ this.pending = partial > 0 ? this.pending.slice(safeLength) : "";
70
+ return out;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * End of stream: release any held-back partial. Outside a reasoning span a
76
+ * dangling "<thin" was ordinary text after all; inside one, the span never
77
+ * closed and its content stays dropped.
78
+ */
79
+ flush(): string {
80
+ const rest = this.insideReasoning ? "" : this.pending;
81
+ this.pending = "";
82
+ this.insideReasoning = false;
83
+ return rest;
84
+ }
85
+ }
86
+
87
+ export function createReasoningTagFilter(): ReasoningTagFilter {
88
+ return new ReasoningTagFilter();
89
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Shared primitives for streaming think-tag scanning.
3
+ *
4
+ * Two consumers parse inline `<think>` spans out of streamed model content
5
+ * and must not drift apart (single source of truth):
6
+ *
7
+ * - `providers/openai/chat-completions-provider.ts` ROUTES the spans to
8
+ * `thinking_delta` events (content preserved, case-sensitive `<think>`
9
+ * only, matching the models that emit it).
10
+ * - `tts/reasoning-tag-filter.ts` DROPS the spans from the spoken stream
11
+ * (case-insensitive, `<think>` and `<thinking>` variants).
12
+ *
13
+ * The state machines stay separate because they do different things with
14
+ * the reasoning; what they share is the tag scanning below. All scanning is
15
+ * positionally exact on the ORIGINAL string: case-insensitive mode folds
16
+ * A-Z per character code and never calls `toLowerCase()` on the haystack,
17
+ * because Unicode lowercasing can change string length (e.g. dotted capital
18
+ * I, U+0130, lowers to two code units) and shift every subsequent index.
19
+ * Tags must be given in lowercase ASCII.
20
+ */
21
+
22
+ function foldedCharCode(code: number, caseInsensitive: boolean): number {
23
+ return caseInsensitive && code >= 65 && code <= 90 ? code + 32 : code;
24
+ }
25
+
26
+ function tagMatchesAt(
27
+ haystack: string,
28
+ start: number,
29
+ tag: string,
30
+ caseInsensitive: boolean,
31
+ ): boolean {
32
+ if (start + tag.length > haystack.length) {
33
+ return false;
34
+ }
35
+ for (let k = 0; k < tag.length; k += 1) {
36
+ if (
37
+ foldedCharCode(haystack.charCodeAt(start + k), caseInsensitive) !==
38
+ tag.charCodeAt(k)
39
+ ) {
40
+ return false;
41
+ }
42
+ }
43
+ return true;
44
+ }
45
+
46
+ /**
47
+ * First occurrence of any tag in `haystack`, by original-string index.
48
+ * When several tags match at the same index the longest wins, so
49
+ * `<thinking>` is not consumed as `<think>` plus stray text.
50
+ */
51
+ export function indexOfTag(
52
+ haystack: string,
53
+ tags: readonly string[],
54
+ caseInsensitive: boolean,
55
+ ): { index: number; tag: string } | null {
56
+ for (let i = 0; i < haystack.length; i += 1) {
57
+ let bestTag: string | null = null;
58
+ for (const tag of tags) {
59
+ if (tagMatchesAt(haystack, i, tag, caseInsensitive)) {
60
+ if (bestTag === null || tag.length > bestTag.length) {
61
+ bestTag = tag;
62
+ }
63
+ }
64
+ }
65
+ if (bestTag !== null) {
66
+ return { index: i, tag: bestTag };
67
+ }
68
+ }
69
+ return null;
70
+ }
71
+
72
+ /**
73
+ * Length of the longest suffix of `text` that is a proper prefix of any
74
+ * listed tag. Streaming scanners hold that many characters back so a tag
75
+ * split across deltas is not emitted before it can be recognized.
76
+ */
77
+ export function partialTagSuffix(
78
+ text: string,
79
+ tags: readonly string[],
80
+ caseInsensitive: boolean,
81
+ ): number {
82
+ const longest = Math.max(...tags.map((tag) => tag.length)) - 1;
83
+ for (let len = Math.min(text.length, longest); len > 0; len -= 1) {
84
+ const start = text.length - len;
85
+ for (const tag of tags) {
86
+ if (
87
+ len < tag.length &&
88
+ tagMatchesAt(text, start, tag.slice(0, len), caseInsensitive)
89
+ ) {
90
+ return len;
91
+ }
92
+ }
93
+ }
94
+ return 0;
95
+ }