@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
@@ -1,6 +1,9 @@
1
1
  import { z } from "zod";
2
2
 
3
- import { PROVIDERS_REQUIRING_BASE_URL_AND_MODELS } from "../../providers/inference/auth.js";
3
+ import {
4
+ PROVIDERS_REQUIRING_BASE_URL_AND_MODELS,
5
+ ROUTING_IDENTITY_PROVIDERS,
6
+ } from "../../providers/inference/auth.js";
4
7
  import { PROVIDER_CATALOG } from "../../providers/model-catalog.js";
5
8
  import { isCodexSubscriptionModel } from "../../providers/openai/codex-models.js";
6
9
  import {
@@ -25,32 +28,54 @@ import {
25
28
  // Provider enum
26
29
  // ---------------------------------------------------------------------------
27
30
 
28
- export const LLMProvider = z
29
- .enum([
30
- "anthropic",
31
- "openai",
32
- "gemini",
33
- "ollama",
34
- "fireworks",
35
- "openrouter",
36
- "vercel-ai-gateway",
37
- "openai-compatible",
38
- "minimax",
39
- "atlascloud",
40
- "together",
41
- "litellm",
42
- "baseten",
43
- "poolside",
44
- // Routing identities rather than adapters: "vellum" = the platform-managed
45
- // route (upstream derived from the model at dispatch), "chatgpt" = the
46
- // subscription route to OpenAI. Neither has a PROVIDER_CATALOG entry;
47
- // dispatch substitutes the real upstream before any adapter lookup.
48
- "vellum",
49
- "chatgpt",
50
- ])
51
- .meta({ id: "LLMProvider" });
31
+ /**
32
+ * The provider values the write surfaces accept today: adapter-backed
33
+ * catalog providers plus the two routing identities. The schema itself is
34
+ * an open string so a stored provider outside this list parses instead of
35
+ * stripping its profile; dispatch resolves such a label as a connection
36
+ * entry name and fails with an explainable resolution error when no row
37
+ * carries it. Write-time membership is enforced at the profiles route and
38
+ * the `commitConfigWrite` choke point (`unknownLlmProviderIssue`), which
39
+ * is where entry names unlock when the entries model enables them.
40
+ */
41
+ export const KNOWN_LLM_PROVIDERS = [
42
+ "anthropic",
43
+ "openai",
44
+ "gemini",
45
+ "ollama",
46
+ "fireworks",
47
+ "openrouter",
48
+ "vercel-ai-gateway",
49
+ "openai-compatible",
50
+ "minimax",
51
+ "atlascloud",
52
+ "together",
53
+ "litellm",
54
+ "baseten",
55
+ "poolside",
56
+ // Routing identities rather than adapters: "vellum" = the platform-managed
57
+ // route (upstream derived from the model at dispatch), "chatgpt" = the
58
+ // subscription route to OpenAI. Neither has a PROVIDER_CATALOG entry;
59
+ // dispatch substitutes the real upstream before any adapter lookup.
60
+ "vellum",
61
+ "chatgpt",
62
+ ] as const;
63
+
64
+ export const LLMProvider = z.string().min(1).meta({ id: "LLMProvider" });
52
65
  export type LLMProvider = z.infer<typeof LLMProvider>;
53
66
 
67
+ /**
68
+ * Write-surface membership check for a provider value. Returns a message
69
+ * when the value is outside {@link KNOWN_LLM_PROVIDERS}, null when it is
70
+ * allowed. Pure and sync so the profiles route and the config-write choke
71
+ * point share one rule.
72
+ */
73
+ export function unknownLlmProviderIssue(provider: string): string | null {
74
+ return (KNOWN_LLM_PROVIDERS as readonly string[]).includes(provider)
75
+ ? null
76
+ : `Invalid provider "${provider}". Valid providers: ${KNOWN_LLM_PROVIDERS.join(", ")}.`;
77
+ }
78
+
54
79
  /**
55
80
  * Providers that can back `llm.defaultProvider`: the named columns of the
56
81
  * default-profile matrix plus every API-key catalog provider whose personal
@@ -70,10 +95,10 @@ export const DEFAULT_PROVIDER_CHOICES: readonly LLMProvider[] = [
70
95
  entry.defaultModel !== "",
71
96
  )
72
97
  .map((entry) => entry.id)
73
- // A catalog provider absent from `LLMProvider` cannot be referenced by
74
- // any profile, so it cannot back the defaults either.
98
+ // A catalog provider outside the known provider set cannot be
99
+ // referenced by any profile, so it cannot back the defaults either.
75
100
  .filter((id): id is LLMProvider =>
76
- (LLMProvider.options as readonly string[]).includes(id),
101
+ (KNOWN_LLM_PROVIDERS as readonly string[]).includes(id),
77
102
  ),
78
103
  ]),
79
104
  ];
@@ -84,13 +109,15 @@ export function isDefaultProviderChoice(value: string): value is LLMProvider {
84
109
 
85
110
  /**
86
111
  * Default-provider choices whose profiles materialize from the shared BYOK
87
- * templates: every choice except the `vellum` routing identity, whose
88
- * defaults are pinned managed models.
112
+ * templates: every choice except the routing identities (`vellum`,
113
+ * `chatgpt`), whose defaults are pinned models on code-owned columns.
89
114
  */
90
115
  export function isByokDefaultProviderChoice(
91
116
  value: string,
92
117
  ): value is LLMProvider {
93
- return value !== "vellum" && isDefaultProviderChoice(value);
118
+ return (
119
+ !ROUTING_IDENTITY_PROVIDERS.has(value) && isDefaultProviderChoice(value)
120
+ );
94
121
  }
95
122
 
96
123
  const DefaultProviderEnum = z.enum(
@@ -188,7 +215,7 @@ export const LLMCallSiteEnum = z.enum([
188
215
  "skillCategoryInference",
189
216
  "inference",
190
217
  "vision",
191
- "voiceFrontDecision",
218
+ "voiceProgressNarration",
192
219
  "voiceFrontDoor",
193
220
  "trustRuleSuggestion",
194
221
  "homeGreeting",
@@ -109,3 +109,26 @@ export const McpConfigSchema = z
109
109
  export type McpTransport = z.infer<typeof McpTransportSchema>;
110
110
  export type McpServerConfig = z.infer<typeof McpServerConfigSchema>;
111
111
  export type McpConfig = z.infer<typeof McpConfigSchema>;
112
+
113
+ /**
114
+ * Who declared a server: the workspace `config.json`, which the user owns,
115
+ * or a plugin's `mcp.json`, which its author owns.
116
+ *
117
+ * Deliberately not a schema field. It is resolved from where the entry was
118
+ * read, never parsed from a file, so nothing on disk can claim to be
119
+ * workspace-owned. What it gates is credential access: only a workspace
120
+ * server resolves `mcp:<serverId>:*` from the credential store, because a
121
+ * plugin controls both its server key and its URL and would otherwise
122
+ * receive a workspace credential at an endpoint it chose.
123
+ */
124
+ export type McpServerSource = "workspace" | "plugin";
125
+
126
+ /** A server config with its origin resolved. */
127
+ export interface ResolvedMcpServerConfig extends McpServerConfig {
128
+ readonly source: McpServerSource;
129
+ }
130
+
131
+ /** The MCP config the daemon runs: both origins, every server attributed. */
132
+ export interface ResolvedMcpConfig extends Omit<McpConfig, "servers"> {
133
+ readonly servers: Record<string, ResolvedMcpServerConfig>;
134
+ }
@@ -5,11 +5,15 @@ import { z } from "zod";
5
5
  *
6
6
  * The resource monitor process runs a periodic sweep (see
7
7
  * `monitoring/plugin-auto-update.ts`) that moves every installed, enabled
8
- * plugin to its source's current revision. The sweep is off by default:
8
+ * marketplace plugin to the catalog's current pin. The sweep is off by default:
9
9
  * `mode: "manual"` means nothing upgrades unless a human runs
10
10
  * `assistant plugins upgrade <name>` (or the equivalent route), which is the
11
11
  * behavior every workspace had before this block existed. Setting
12
12
  * `mode: "auto"` opts the workspace into the hourly sweep.
13
+ *
14
+ * Opting in never covers plugins installed straight from a GitHub URL. Those
15
+ * track a mutable ref rather than a reviewed pin, so the sweep leaves them to
16
+ * an explicit `assistant plugins upgrade`.
13
17
  */
14
18
  export const PluginUpdatesConfigSchema = z
15
19
  .object({
@@ -19,7 +23,7 @@ export const PluginUpdatesConfigSchema = z
19
23
  })
20
24
  .default("manual")
21
25
  .describe(
22
- 'How installed plugins move to newer revisions. "manual" (default) never upgrades on its own — upgrades happen only when a human asks for one. "auto" lets the resource monitor upgrade every installed, enabled plugin on the `checkIntervalMs` cadence.',
26
+ 'How installed plugins move to newer revisions. "manual" (default) never upgrades on its own: upgrades happen only when a human asks for one. "auto" lets the resource monitor upgrade every installed, enabled marketplace plugin on the `checkIntervalMs` cadence. Plugins installed directly from a GitHub URL are never upgraded automatically, since they track a mutable ref instead of a reviewed pin.',
23
27
  ),
24
28
  strategy: z
25
29
  // Deliberately narrower than the full `PluginUpgradeStrategy` union: the
@@ -6,6 +6,7 @@ import { z } from "zod";
6
6
  */
7
7
  export const VALID_STT_PROVIDERS = [
8
8
  "deepgram",
9
+ "deepgram-flux",
9
10
  "google-gemini",
10
11
  "openai-whisper",
11
12
  "xai",
@@ -102,6 +102,96 @@ export function compactAxTreeHistory(messages: Message[]): Message[] {
102
102
  });
103
103
  }
104
104
 
105
+ /**
106
+ * Full-wrapper matcher for the `<channel_capabilities>` block. Both ends are
107
+ * required so a user message that merely opens with the tag (someone pasting
108
+ * prompt markup into chat) is never mistaken for an injected block, the same
109
+ * discipline `stripUserTextBlocksByPrefix` uses in `strip-injections.ts`.
110
+ */
111
+ const CHANNEL_CAPABILITIES_OPEN = "<channel_capabilities>\n";
112
+ const CHANNEL_CAPABILITIES_CLOSE = "\n</channel_capabilities>";
113
+
114
+ /** Whether a text block is a complete `<channel_capabilities>…` block. */
115
+ function isChannelCapabilitiesBlock(text: string): boolean {
116
+ return (
117
+ text.startsWith(CHANNEL_CAPABILITIES_OPEN) &&
118
+ text.endsWith(CHANNEL_CAPABILITIES_CLOSE)
119
+ );
120
+ }
121
+
122
+ /**
123
+ * Drop `<channel_capabilities>` blocks that repeat the last one still standing,
124
+ * so a conversation carries one copy per distinct set of capabilities instead
125
+ * of one per turn.
126
+ *
127
+ * Runtime injection prepends this block to the turn-starting user message and
128
+ * then freezes it into history: live, because injection splices into the
129
+ * conversation's own message array, and across restarts, because `loadFromDb`
130
+ * rehydrates it from message metadata. Nothing ever removes it, so an N-turn
131
+ * channel conversation ships N copies. Unlike `<turn_context>`, which at least
132
+ * carries a fresh timestamp, this block is a pure function of the channel
133
+ * (`buildChannelCapabilityBlock`): every copy after the first is byte-identical
134
+ * to the one above it and tells the model nothing it has not already read.
135
+ *
136
+ * ## Why "same as the last retained copy" and not "not the current turn"
137
+ *
138
+ * Prompt caching only pays off when the bytes a provider marked in turn N are
139
+ * still at the same position in turn N+1, an invariant this repo enforces end
140
+ * to end in `prompt-cache-cross-turn-stability.test.ts`. Any rule of the form
141
+ * "keep it on the current turn, collapse it above" necessarily re-renders one
142
+ * message every turn (the turn that just ended), which is a full cache miss on
143
+ * the whole message prefix, every turn, for every conversation.
144
+ *
145
+ * Comparing against the last RETAINED copy avoids that entirely: whether a
146
+ * given occurrence survives depends only on the messages above it, never on
147
+ * how many turns come later. A message's rendering is therefore fixed the
148
+ * moment it is written, and the prefix stays byte-stable as the conversation
149
+ * grows. Capabilities that genuinely change mid-conversation (the same
150
+ * conversation resumed from a different client) still differ from the retained
151
+ * copy, so the new block is kept and the model sees the change.
152
+ *
153
+ * The cost is placement: the surviving copy sits at the top of the conversation
154
+ * rather than next to the newest message. It is the same bytes either way.
155
+ *
156
+ * Idempotent, like every other transform in this module: re-running it over an
157
+ * already-deduplicated history retains exactly the same occurrences.
158
+ */
159
+ export function dedupeChannelCapabilityBlocks(history: Message[]): Message[] {
160
+ let lastRetained: string | null = null;
161
+ let changed = false;
162
+
163
+ const next = history.map((message) => {
164
+ if (message.role !== "user") {
165
+ return message;
166
+ }
167
+
168
+ let messageChanged = false;
169
+ const content = message.content.filter((block) => {
170
+ if (block.type !== "text" || !isChannelCapabilitiesBlock(block.text)) {
171
+ return true;
172
+ }
173
+ if (block.text === lastRetained) {
174
+ messageChanged = true;
175
+ return false;
176
+ }
177
+ lastRetained = block.text;
178
+ return true;
179
+ });
180
+
181
+ // A user row is never only injections in practice, but guard anyway: an
182
+ // empty content array is not a message any provider will accept, and
183
+ // dropping the row would break tool_use/tool_result pairing and the
184
+ // row-to-history index mapping `summarizeUpToMessage` relies on.
185
+ if (!messageChanged || content.length === 0) {
186
+ return message;
187
+ }
188
+ changed = true;
189
+ return { ...message, content };
190
+ });
191
+
192
+ return changed ? next : history;
193
+ }
194
+
105
195
  /**
106
196
  * Index of the last user message carrying `tool_result` blocks — the
107
197
  * "current turn" boundary {@link stripOldMediaBlocks} keeps intact while
@@ -194,6 +284,9 @@ function stripOldMediaBlocks(history: Message[]): Message[] {
194
284
  * `web_search_tool_result` blocks to text summaries; Anthropic's opaque
195
285
  * `encrypted_content` tokens expire / are route-scoped, and replaying a stale
196
286
  * one is rejected with `Invalid encrypted_content in search_result block`.
287
+ * - {@link dedupeChannelCapabilityBlocks} keeps one `<channel_capabilities>`
288
+ * block per distinct set of capabilities instead of the one-per-turn history
289
+ * accumulates; every repeat is byte-identical to the copy above it.
197
290
  *
198
291
  * Transforms the outbound copy only — the durable history keeps the rich
199
292
  * originals and each send re-derives the sanitized projection (every transform
@@ -206,5 +299,7 @@ function stripOldMediaBlocks(history: Message[]): Message[] {
206
299
  export function preModelCallSanitize(history: Message[]): Message[] {
207
300
  const mediaStripped = stripOldMediaBlocks(history);
208
301
  const axCompacted = compactAxTreeHistory(mediaStripped);
209
- return stripHistoricalWebSearchResults(axCompacted).messages;
302
+ const webSearchStripped =
303
+ stripHistoricalWebSearchResults(axCompacted).messages;
304
+ return dedupeChannelCapabilityBlocks(webSearchStripped);
210
305
  }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The plugin source reconcile drives MCP reload.
3
+ *
4
+ * Without this, installing a plugin leaves its `mcp.json` servers
5
+ * unconnected and its tools unregistered until an explicit MCP reload, a
6
+ * config edit, or a restart — and, worse in the other direction, an
7
+ * uninstalled or disabled plugin's connected client and registered tools
8
+ * stay callable. The reconcile is the seam every install / uninstall /
9
+ * upgrade / enable / disable already funnels through.
10
+ *
11
+ * The guard is equally load-bearing: a reconcile also fires for plugin
12
+ * edits that touch no `mcp.json`, and reloading on those would tear down
13
+ * every healthy workspace connection to rebuild it for nothing.
14
+ */
15
+
16
+ import { beforeEach, describe, expect, jest, mock, test } from "bun:test";
17
+
18
+ let pluginServersChanged = true;
19
+
20
+ const managerStop = jest.fn(async () => {});
21
+ const managerStart = jest.fn(async () => []);
22
+
23
+ mock.module("../../mcp/effective-config.js", () => ({
24
+ buildEffectiveMcpConfig: () => ({
25
+ servers: {
26
+ unabyss: {
27
+ transport: { type: "streamable-http", url: "https://mcp.example/x" },
28
+ enabled: true,
29
+ defaultRiskLevel: "low",
30
+ maxTools: 20,
31
+ source: "plugin",
32
+ },
33
+ },
34
+ globalMaxTools: 50,
35
+ }),
36
+ pluginMcpServersChangedSinceLastBuild: () => pluginServersChanged,
37
+ }));
38
+
39
+ mock.module("../../mcp/manager.js", () => ({
40
+ getMcpServerManager: () => ({ stop: managerStop, start: managerStart }),
41
+ }));
42
+
43
+ mock.module("../../mcp/mcp-header-store.js", () => ({
44
+ migrateLegacyMcpHeaders: async () => {},
45
+ }));
46
+
47
+ mock.module("../../config/loader.js", () => ({
48
+ getConfig: () => ({ mcp: { servers: {}, globalMaxTools: 50 } }),
49
+ invalidateConfigCache: () => {},
50
+ }));
51
+
52
+ mock.module("../../tools/registry.js", () => ({
53
+ registerMcpTools: () => [],
54
+ unregisterAllMcpTools: () => {},
55
+ }));
56
+
57
+ const { reconcilePluginMcpServers } = await import("../mcp-reload-service.js");
58
+
59
+ describe("reconcilePluginMcpServers", () => {
60
+ beforeEach(() => {
61
+ managerStop.mockClear();
62
+ managerStart.mockClear();
63
+ });
64
+
65
+ test("reloads when the plugin-declared set changed", async () => {
66
+ pluginServersChanged = true;
67
+
68
+ await reconcilePluginMcpServers();
69
+
70
+ expect(managerStop).toHaveBeenCalledTimes(1);
71
+ expect(managerStart).toHaveBeenCalledTimes(1);
72
+ });
73
+
74
+ test("does nothing when no plugin changed the set", async () => {
75
+ pluginServersChanged = false;
76
+
77
+ await reconcilePluginMcpServers();
78
+
79
+ expect(managerStop).not.toHaveBeenCalled();
80
+ expect(managerStart).not.toHaveBeenCalled();
81
+ });
82
+ });
@@ -143,6 +143,7 @@ import type {
143
143
  WebSearchResultItem,
144
144
  } from "./message-types/web-activity.js";
145
145
  import { referenceMediaBlocksForPersist } from "./persist-media-references.js";
146
+ import { turnOrRestingTrust } from "./trust-context-types.js";
146
147
  import type { TurnLatencyTracker } from "./turn-latency-tracker.js";
147
148
 
148
149
  const log = getLogger("agent-loop-handlers");
@@ -1330,7 +1331,7 @@ function buildAssistantChannelMetadata(
1330
1331
  deps: EventHandlerDeps,
1331
1332
  ): Record<string, unknown> {
1332
1333
  const metadata: Record<string, unknown> = {
1333
- ...provenanceFromTrustContext(deps.ctx.trustContext),
1334
+ ...provenanceFromTrustContext(turnOrRestingTrust(deps.ctx)),
1334
1335
  userMessageChannel: deps.turnChannelContext.userMessageChannel,
1335
1336
  assistantMessageChannel: deps.turnChannelContext.assistantMessageChannel,
1336
1337
  userMessageInterface: deps.turnInterfaceContext.userMessageInterface,
@@ -1340,15 +1341,19 @@ function buildAssistantChannelMetadata(
1340
1341
  };
1341
1342
 
1342
1343
  if (deps.turnChannelContext.assistantMessageChannel === "slack") {
1343
- const channelId = deps.ctx.trustContext?.requesterChatId;
1344
+ // The whole envelope resolves from one turn-local snapshot: the actor the
1345
+ // provenance above names is the actor whose channel and thread the row
1346
+ // routes to. Reading the live slot here instead would let a concurrent
1347
+ // inbound repoint the reply mid-turn, and could stamp one actor's class
1348
+ // beside another actor's routing identity.
1349
+ const rowTrust = turnOrRestingTrust(deps.ctx);
1350
+ const channelId = rowTrust?.requesterChatId;
1344
1351
  if (channelId) {
1345
- // Resolve the reply thread from this turn's own inbound thread id,
1346
- // captured turn-locally on the trust context at ingress (the same field
1347
- // guardian-approval cards read). This is deliberately not the shared
1348
- // conversation binding: on a legacy flat→thread aliased Slack
1349
- // conversation a concurrent inbound can rewrite the binding's
1350
- // externalThreadId mid-turn, whereas the trust context is per-turn.
1351
- const turnThreadTs = deps.ctx.trustContext?.sourceThreadId;
1352
+ // This turn's own inbound thread id (the same field guardian-approval
1353
+ // cards read). Deliberately not the shared conversation binding: on a
1354
+ // legacy flat→thread aliased Slack conversation a concurrent inbound
1355
+ // can rewrite the binding's externalThreadId mid-turn.
1356
+ const turnThreadTs = rowTrust?.sourceThreadId;
1352
1357
  const threadTs = isSlackTs(turnThreadTs) ? turnThreadTs : undefined;
1353
1358
  const timestampTimezone = resolveAssistantReplyTimestampTimezone(
1354
1359
  deps.ctx,
@@ -1896,7 +1901,7 @@ function buildToolResultMetadata(
1896
1901
  deps: EventHandlerDeps,
1897
1902
  ): Record<string, unknown> {
1898
1903
  return {
1899
- ...provenanceFromTrustContext(deps.ctx.trustContext),
1904
+ ...provenanceFromTrustContext(turnOrRestingTrust(deps.ctx)),
1900
1905
  userMessageChannel: deps.turnChannelContext.userMessageChannel,
1901
1906
  assistantMessageChannel: deps.turnChannelContext.assistantMessageChannel,
1902
1907
  userMessageInterface: deps.turnInterfaceContext.userMessageInterface,
@@ -61,7 +61,11 @@ import { HOOKS } from "../plugin-api/constants.js";
61
61
  import type { ConversationGraphMemory } from "../plugins/defaults/memory/graph/conversation-graph-memory.js";
62
62
  import { enqueueMemoryRetrospectiveOnCompaction } from "../plugins/defaults/memory/memory-retrospective-enqueue.js";
63
63
  import { runHook } from "../plugins/pipeline.js";
64
- import { isManagedConnectionRoute } from "../providers/connection-resolution.js";
64
+ import {
65
+ dispatchProviderResolvable,
66
+ isManagedConnectionRoute,
67
+ resolveEntryConnectionName,
68
+ } from "../providers/connection-resolution.js";
65
69
  import {
66
70
  ConnectionResolutionError,
67
71
  resolveRoutingIdentity,
@@ -130,6 +134,7 @@ import {
130
134
  } from "./inflight-turn-registry.js";
131
135
  import type { UsageStats } from "./message-protocol.js";
132
136
  import type { TrustContext } from "./trust-context-types.js";
137
+ import { turnOrRestingTrust } from "./trust-context-types.js";
133
138
  import { resolveTurnCallSite } from "./turn-call-site.js";
134
139
  import { runWithLatencySubSpans } from "./turn-latency-sub-spans.js";
135
140
  import {
@@ -491,10 +496,13 @@ export async function runAgentLoopImpl(
491
496
  const turnErrorAttribution = (): ConversationErrorAttribution => {
492
497
  try {
493
498
  const overrideProfile = readCurrentOverrideProfile();
499
+ // Mirrors dispatch's selection (including the resolvable-provider
500
+ // healing) so attribution names the profile that actually ran.
494
501
  const resolveOpts = {
495
502
  overrideProfile,
496
503
  forceOverrideProfile,
497
504
  selectionSeed: ctx.conversationId,
505
+ isResolvableProvider: dispatchProviderResolvable,
498
506
  };
499
507
  const { config: resolved, profileName } =
500
508
  resolveCallSiteConfigWithProfile(turnCallSite, config.llm, resolveOpts);
@@ -509,6 +517,10 @@ export async function runAgentLoopImpl(
509
517
  }
510
518
  connectionName = error.connectionName;
511
519
  }
520
+ // An entry-name provider IS the connection name, matching the
521
+ // dispatch-side translation.
522
+ connectionName ??=
523
+ resolveEntryConnectionName(resolved.provider) ?? undefined;
512
524
  // Managed-ness comes from the connection row, matching what dispatch
513
525
  // decides. The profile's own provider can't stand in: a concrete
514
526
  // provider tweak over a managed winner keeps the managed connection
@@ -1283,8 +1295,7 @@ export async function runAgentLoopImpl(
1283
1295
  // context, then the fallback — matching the trust the runtime injection
1284
1296
  // assembly resolves for the same turn. The loop's other turn-identity
1285
1297
  // fields self-resolve from its own conversation id.
1286
- const loopTrust =
1287
- ctx.currentTurnTrustContext ?? ctx.trustContext ?? FALLBACK_TURN_TRUST;
1298
+ const loopTrust = ctx.getTurnOrRestingTrust() ?? FALLBACK_TURN_TRUST;
1288
1299
 
1289
1300
  /**
1290
1301
  * Shared closure: runs the agent loop with the wrapper's turn context and
@@ -1404,7 +1415,7 @@ export async function runAgentLoopImpl(
1404
1415
  // row here rather than writing a duplicate.
1405
1416
  if (state.pendingToolResults.size > 0) {
1406
1417
  const toolResultMetadata = {
1407
- ...provenanceFromTrustContext(ctx.trustContext),
1418
+ ...provenanceFromTrustContext(turnOrRestingTrust(ctx)),
1408
1419
  userMessageChannel: capturedTurnChannelContext.userMessageChannel,
1409
1420
  assistantMessageChannel:
1410
1421
  capturedTurnChannelContext.assistantMessageChannel,
@@ -1431,7 +1442,7 @@ export async function runAgentLoopImpl(
1431
1442
  budgetYieldClassification.userMessage,
1432
1443
  );
1433
1444
  const yieldNoticeMetadata = {
1434
- ...provenanceFromTrustContext(ctx.trustContext),
1445
+ ...provenanceFromTrustContext(turnOrRestingTrust(ctx)),
1435
1446
  userMessageChannel: capturedTurnChannelContext.userMessageChannel,
1436
1447
  assistantMessageChannel:
1437
1448
  capturedTurnChannelContext.assistantMessageChannel,
@@ -1565,7 +1576,7 @@ export async function runAgentLoopImpl(
1565
1576
  state.lastAssistantMessageId = undefined;
1566
1577
  } else {
1567
1578
  const errChannelMeta = {
1568
- ...provenanceFromTrustContext(ctx.trustContext),
1579
+ ...provenanceFromTrustContext(turnOrRestingTrust(ctx)),
1569
1580
  userMessageChannel: capturedTurnChannelContext.userMessageChannel,
1570
1581
  assistantMessageChannel:
1571
1582
  capturedTurnChannelContext.assistantMessageChannel,
@@ -68,6 +68,7 @@ import type { SlackInboundMessageMetadata } from "./handlers/shared.js";
68
68
  import type { UserMessageAttachment } from "./message-protocol.js";
69
69
  import type { ConversationTransportMetadata } from "./message-types/conversations.js";
70
70
  import type { TrustContext } from "./trust-context-types.js";
71
+ import { restingTrust } from "./trust-context-types.js";
71
72
 
72
73
  const log = getLogger("conversation-messaging");
73
74
 
@@ -863,7 +864,10 @@ export async function persistQueuedMessageBody(
863
864
  const turnIfCtx =
864
865
  extractTurnInterfaceContext(metadata) ?? ctx.getTurnInterfaceContext();
865
866
  const provenance = provenanceFromTrustContext(
866
- options.trustContext ?? ctx.trustContext,
867
+ // Callers that own a turn pass the sender's trust; the fallback serves
868
+ // ingress paths that persist before any per-turn stamp exists, where
869
+ // the slot their own resolution just wrote is the right actor.
870
+ options.trustContext ?? restingTrust(ctx),
867
871
  );
868
872
  const imageSourcePaths = extractImageSourcePaths(attachments);
869
873
 
@@ -25,6 +25,7 @@ import {
25
25
  } from "../persistence/conversation-crud.js";
26
26
  import type { Message } from "../providers/types.js";
27
27
  import type { TrustContext } from "./trust-context-types.js";
28
+ import { restingTrust } from "./trust-context-types.js";
28
29
 
29
30
  /**
30
31
  * Subset of Conversation state that notifier callbacks need to read at
@@ -58,7 +59,14 @@ export function registerConversationNotifiers(
58
59
  JSON.stringify([{ type: "text", text: questionText }]),
59
60
  {
60
61
  metadata: {
61
- ...provenanceFromTrustContext(ctx.trustContext),
62
+ ...provenanceFromTrustContext(
63
+ // This callback fires after the voice turn has settled: the
64
+ // per-turn field may still hold that turn's actor (nothing
65
+ // clears it at release), while voice cleanup has restored the
66
+ // conversation's resting trust. The owner is the right actor
67
+ // for a row persisted outside any turn.
68
+ restingTrust(ctx),
69
+ ),
62
70
  userMessageChannel: "phone",
63
71
  assistantMessageChannel: "phone",
64
72
  userMessageInterface: "phone",