@vellumai/assistant 0.11.3 → 0.11.4-staging.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) 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 +140 -38
  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 +2 -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 +55 -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__/user-plugin-loader.test.ts +1 -1
  67. package/src/__tests__/visible-app-context.test.ts +16 -9
  68. package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
  69. package/src/__tests__/worker-plugin-surface.test.ts +77 -0
  70. package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
  71. package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
  72. package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
  73. package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
  74. package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
  75. package/src/acp/__tests__/auth-required.test.ts +161 -0
  76. package/src/acp/acp-claude-oauth.ts +19 -2
  77. package/src/acp/agent-process.test.ts +100 -0
  78. package/src/acp/agent-process.ts +29 -26
  79. package/src/acp/auth-required.ts +102 -0
  80. package/src/acp/session-manager.test.ts +119 -0
  81. package/src/acp/session-manager.ts +68 -2
  82. package/src/api/events/acp-auth-required.ts +55 -0
  83. package/src/api/index.ts +7 -0
  84. package/src/apps/app-store.ts +3 -0
  85. package/src/bundler/package-resolver.ts +2 -30
  86. package/src/calls/__tests__/voice-session-bridge.test.ts +173 -1
  87. package/src/calls/__tests__/voice-triage-escalate.test.ts +94 -0
  88. package/src/calls/call-controller.ts +9 -2
  89. package/src/calls/call-setup-flow.ts +0 -1
  90. package/src/calls/media-stream-stt-session.ts +15 -0
  91. package/src/calls/voice-session-bridge.ts +71 -16
  92. package/src/calls/voice-triage-escalate.ts +104 -2
  93. package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
  94. package/src/channels/config.ts +13 -0
  95. package/src/channels/plugin-channel-declarations.ts +108 -0
  96. package/src/channels/types.ts +30 -0
  97. package/src/cli/AGENTS.md +5 -2
  98. package/src/cli/commands/credentials.help.ts +2 -2
  99. package/src/cli/commands/inference-providers.ts +1 -1
  100. package/src/cli/commands/mcp.help.ts +13 -4
  101. package/src/cli/commands/mcp.ts +9 -0
  102. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
  103. package/src/cli/commands/memory/index.help.ts +43 -1
  104. package/src/cli/commands/memory/memory-v3.ts +64 -0
  105. package/src/cli/commands/stt.help.ts +27 -2
  106. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
  107. package/src/cli/lib/bundled-marketplace.json +13 -0
  108. package/src/cli/lib/upgrade-plugin.ts +42 -0
  109. package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
  110. package/src/config/__tests__/default-provider.test.ts +6 -1
  111. package/src/config/__tests__/profile-materialization.test.ts +75 -19
  112. package/src/config/bundled-skills/acp/SKILL.md +6 -7
  113. package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
  114. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  115. package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
  116. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  117. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
  118. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
  119. package/src/config/call-site-defaults.ts +4 -5
  120. package/src/config/default-profile-catalog.ts +82 -11
  121. package/src/config/default-profile-names.ts +4 -1
  122. package/src/config/default-provider-resolution.ts +4 -0
  123. package/src/config/llm-context-resolution.ts +11 -3
  124. package/src/config/llm-resolver.ts +28 -1
  125. package/src/config/profile-materialization.ts +70 -22
  126. package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
  127. package/src/config/schemas/call-site-catalog.ts +4 -4
  128. package/src/config/schemas/live-voice.ts +57 -23
  129. package/src/config/schemas/llm.ts +59 -32
  130. package/src/config/schemas/mcp.ts +23 -0
  131. package/src/config/schemas/plugin-updates.ts +6 -2
  132. package/src/config/schemas/stt.ts +1 -0
  133. package/src/context/outbound-sanitize.ts +96 -1
  134. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
  135. package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
  136. package/src/daemon/conversation-agent-loop.ts +17 -6
  137. package/src/daemon/conversation-messaging.ts +5 -1
  138. package/src/daemon/conversation-notifiers.ts +9 -1
  139. package/src/daemon/conversation-process.ts +9 -6
  140. package/src/daemon/conversation-runtime-assembly.ts +3 -4
  141. package/src/daemon/conversation-tool-setup.ts +1 -2
  142. package/src/daemon/conversation.ts +48 -0
  143. package/src/daemon/mcp-reload-service.ts +36 -6
  144. package/src/daemon/process-message.ts +13 -3
  145. package/src/daemon/providers-setup.ts +6 -3
  146. package/src/daemon/trust-context-types.ts +29 -0
  147. package/src/daemon/wake-conversation-ops.ts +3 -2
  148. package/src/documents/document-store.ts +138 -5
  149. package/src/hooks/hook-loader.ts +3 -3
  150. package/src/hooks/registry.ts +50 -6
  151. package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
  152. package/src/inbound/oauth-callback-url.ts +61 -0
  153. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
  154. package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
  155. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +932 -0
  156. package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
  157. package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
  158. package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
  159. package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
  160. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
  161. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
  162. package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
  163. package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
  164. package/src/live-voice/live-voice-archive.ts +2 -0
  165. package/src/live-voice/live-voice-metrics.ts +57 -32
  166. package/src/live-voice/live-voice-photo.ts +1 -2
  167. package/src/live-voice/live-voice-session.ts +535 -314
  168. package/src/live-voice/progress-narration.ts +277 -0
  169. package/src/live-voice/protocol.ts +21 -1
  170. package/src/mcp/__tests__/effective-config.test.ts +238 -0
  171. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  172. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
  173. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
  174. package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
  175. package/src/mcp/client.ts +16 -11
  176. package/src/mcp/effective-config.ts +113 -0
  177. package/src/mcp/manager.ts +11 -6
  178. package/src/mcp/mcp-auth-orchestrator.ts +13 -22
  179. package/src/mcp/mcp-oauth-provider.ts +205 -240
  180. package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
  181. package/src/monitoring/plugin-auto-update.ts +128 -24
  182. package/src/notifications/signal.ts +1 -0
  183. package/src/permissions/confirmation-guardian-request.test.ts +15 -11
  184. package/src/permissions/confirmation-guardian-request.ts +2 -2
  185. package/src/permissions/question-guardian-request.test.ts +14 -6
  186. package/src/permissions/question-guardian-request.ts +1 -2
  187. package/src/persistence/attachments-store.ts +8 -1
  188. package/src/persistence/bookmark-crud.ts +3 -7
  189. package/src/persistence/conversation-attention-store.ts +16 -45
  190. package/src/persistence/conversation-crud.ts +33 -4
  191. package/src/persistence/conversation-lineage.ts +9 -0
  192. package/src/persistence/conversation-queries.ts +108 -41
  193. package/src/persistence/delivery-crud.ts +38 -29
  194. package/src/persistence/external-conversation-store.ts +32 -4
  195. package/src/persistence/llm-request-log-store.ts +4 -10
  196. package/src/persistence/llm-usage-store.ts +8 -3
  197. package/src/persistence/message-reads.test.ts +197 -0
  198. package/src/persistence/message-reads.ts +211 -0
  199. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
  200. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
  201. package/src/persistence/real-user-turn-filter.ts +27 -3
  202. package/src/persistence/steps.ts +9 -0
  203. package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
  204. package/src/plugin-api/conversation-turn.ts +168 -5
  205. package/src/plugin-api/index.ts +21 -5
  206. package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
  207. package/src/plugins/defaults/memory/AGENTS.md +4 -0
  208. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
  209. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
  210. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
  211. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
  212. package/src/plugins/defaults/memory/buffer-format.ts +165 -0
  213. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
  214. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
  215. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
  216. package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
  217. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +8 -12
  218. package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
  219. package/src/plugins/defaults/memory/indexer.ts +3 -1
  220. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
  221. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
  222. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
  223. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +33 -0
  224. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +199 -2
  225. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +25 -18
  226. package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
  227. package/src/plugins/defaults/memory/substrate/static-context.ts +160 -4
  228. package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
  229. package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
  230. package/src/plugins/defaults/memory/v3/prune.ts +2 -0
  231. package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
  232. package/src/plugins/defaults/memory/worker.ts +6 -3
  233. package/src/plugins/external-plugin-loader.ts +47 -0
  234. package/src/plugins/mcp-servers.ts +361 -0
  235. package/src/plugins/mtime-cache.ts +23 -49
  236. package/src/plugins/worker-plugin-surface.ts +33 -0
  237. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  238. package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
  239. package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
  240. package/src/providers/__tests__/retry-callsite.test.ts +5 -2
  241. package/src/providers/call-site-routing.ts +30 -3
  242. package/src/providers/connection-resolution.ts +194 -11
  243. package/src/providers/inference/auth.ts +6 -0
  244. package/src/providers/inference/connection-availability.ts +24 -2
  245. package/src/providers/inference/connections.ts +2 -0
  246. package/src/providers/model-intents.ts +26 -6
  247. package/src/providers/openai/codex-models.ts +2 -1
  248. package/src/providers/provider-send-message.ts +32 -3
  249. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
  250. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +620 -0
  251. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
  252. package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
  253. package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
  254. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +719 -0
  255. package/src/providers/speech-to-text/provider-catalog.ts +99 -8
  256. package/src/providers/speech-to-text/resolve.ts +25 -2
  257. package/src/routes/worker.ts +17 -5
  258. package/src/runtime/access-request-helper.ts +9 -12
  259. package/src/runtime/agent-wake.ts +3 -3
  260. package/src/runtime/pre-first-message-gate.ts +4 -0
  261. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
  262. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +219 -1
  263. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
  264. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
  265. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
  266. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
  267. package/src/runtime/routes/__tests__/plugins-routes.test.ts +44 -0
  268. package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
  269. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +62 -1
  270. package/src/runtime/routes/channel-availability-routes.ts +32 -14
  271. package/src/runtime/routes/channel-route-shared.ts +0 -6
  272. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
  273. package/src/runtime/routes/conversation-list-routes.ts +112 -1
  274. package/src/runtime/routes/conversation-query-routes.ts +40 -27
  275. package/src/runtime/routes/credential-prompt-routes.ts +4 -7
  276. package/src/runtime/routes/default-provider-routes.ts +15 -0
  277. package/src/runtime/routes/inbound-message-handler.ts +17 -41
  278. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
  279. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
  280. package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
  281. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
  282. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
  283. package/src/runtime/routes/inbound-stages/edit-intercept.ts +1 -3
  284. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
  285. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -4
  286. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +0 -1
  287. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +11 -20
  288. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
  289. package/src/runtime/routes/inference-profiles-routes.ts +20 -11
  290. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
  291. package/src/runtime/routes/log-export-routes.ts +3 -0
  292. package/src/runtime/routes/mcp-auth-routes.ts +148 -57
  293. package/src/runtime/routes/plugins-routes.ts +21 -3
  294. package/src/runtime/routes/stt-routes.ts +31 -25
  295. package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
  296. package/src/runtime/routes/user-route-dispatcher.ts +39 -14
  297. package/src/runtime/routes/user-route-import.ts +108 -0
  298. package/src/schedule/worker.ts +6 -0
  299. package/src/security/oauth2.ts +6 -22
  300. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
  301. package/src/stt/__tests__/types.test.ts +94 -0
  302. package/src/stt/daemon-batch-transcriber.ts +10 -0
  303. package/src/stt/stt-stream-session.ts +8 -4
  304. package/src/stt/types.ts +103 -0
  305. package/src/subagent/manager.ts +1 -3
  306. package/src/subagent/types.ts +7 -6
  307. package/src/tools/acp/spawn.test.ts +97 -0
  308. package/src/tools/acp/spawn.ts +32 -0
  309. package/src/tools/document/document-tool.ts +12 -3
  310. package/src/tools/registry.ts +2 -1
  311. package/src/tools/workflows/run-workflow.ts +1 -2
  312. package/src/tts/__tests__/reasoning-tag-filter.test.ts +63 -0
  313. package/src/tts/reasoning-tag-filter.ts +110 -0
  314. package/src/workspace/byok-default-profile-ensure.ts +61 -18
  315. package/src/workspace/custom-profile-ensure.ts +4 -24
  316. package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
  317. package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
  318. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
  319. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
  320. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
  321. package/src/workspace/migrations/registry.ts +8 -0
  322. package/src/workspace/provider-commit-message-generator.ts +7 -5
  323. package/src/live-voice/__tests__/front-decision.test.ts +0 -645
  324. package/src/live-voice/front-decision.ts +0 -476
@@ -27,9 +27,12 @@
27
27
  * shows no route depends on it.
28
28
  *
29
29
  * Modules are lazily loaded on first request and cached by file path +
30
- * mtime. When a file changes on disk, the next request reloads it via
31
- * Bun's dynamic `import()` with a cache-busting query parameter. A request
32
- * whose file does not exist 404s — nothing is registered ahead of time.
30
+ * mtime. When a file changes on disk, the next request evicts the whole
31
+ * source tree that file belongs to (plugin root, or the workspace
32
+ * `routes/` directory) and re-imports the entry. Cache-busting only the
33
+ * entry file would re-bind it to stale helpers still in Bun's registry.
34
+ * A request whose file does not exist 404s. Nothing is registered ahead
35
+ * of time.
33
36
  */
34
37
 
35
38
  import { statSync } from "node:fs";
@@ -46,6 +49,11 @@ import {
46
49
  buildDeprecatedRouteContext,
47
50
  type UserRouteContext,
48
51
  } from "./deprecated-route-context.js";
52
+ import {
53
+ evictRouteSourceTree,
54
+ importRouteModule,
55
+ routeSourceRoot,
56
+ } from "./user-route-import.js";
49
57
  import {
50
58
  resolveHandlerFile,
51
59
  resolveRouteLocation,
@@ -134,7 +142,7 @@ export class UserRouteDispatcher {
134
142
  ? resolveHandlerFile(location.routesDir, location.subPath)
135
143
  : null;
136
144
 
137
- if (!filePath) {
145
+ if (!location || !filePath) {
138
146
  return httpError(
139
147
  "NOT_FOUND",
140
148
  `No route handler found for /x/${routePath}`,
@@ -146,7 +154,7 @@ export class UserRouteDispatcher {
146
154
  return this.dispatchViaHost(filePath, routePath, request);
147
155
  }
148
156
 
149
- const mod = await this.loadModule(filePath);
157
+ const mod = await this.loadModule(filePath, location.routesDir);
150
158
  const method = request.method as HttpMethod;
151
159
  const handler = mod.handlers[method];
152
160
 
@@ -231,11 +239,15 @@ export class UserRouteDispatcher {
231
239
  /**
232
240
  * Load a handler module, using the mtime-based cache when possible.
233
241
  *
234
- * On cache miss or stale mtime, the module is re-imported via Bun's
235
- * dynamic `import()` with a cache-busting query parameter derived
236
- * from the file's current mtime.
242
+ * On cache miss or stale mtime, every source file in the handler's tree
243
+ * is evicted first so a new entry cannot import a helper that is still
244
+ * the pre-upgrade module. Then the entry is re-imported with an mtime
245
+ * query parameter.
237
246
  */
238
- private async loadModule(filePath: string): Promise<CachedModule> {
247
+ private async loadModule(
248
+ filePath: string,
249
+ routesDir: string,
250
+ ): Promise<CachedModule> {
239
251
  const stat = statSync(filePath);
240
252
  const mtimeMs = stat.mtimeMs;
241
253
 
@@ -244,11 +256,11 @@ export class UserRouteDispatcher {
244
256
  return cached;
245
257
  }
246
258
 
247
- // Cache-bust Bun's module cache by appending mtime as a query param.
248
- const mod = (await import(`${filePath}?t=${mtimeMs}`)) as Record<
249
- string,
250
- unknown
251
- >;
259
+ const sourceRoot = routeSourceRoot(routesDir);
260
+ evictRouteSourceTree(sourceRoot);
261
+ this.dropCachedModulesUnder(sourceRoot);
262
+
263
+ const mod = await importRouteModule(filePath);
252
264
 
253
265
  const handlers: Partial<Record<HttpMethod, RouteHandler>> = {};
254
266
  for (const method of HTTP_METHODS) {
@@ -271,6 +283,19 @@ export class UserRouteDispatcher {
271
283
  return entry;
272
284
  }
273
285
 
286
+ /**
287
+ * Drop every cached handler under `sourceRoot`. Sibling routes must not
288
+ * keep closures over the helpers we just evicted.
289
+ */
290
+ private dropCachedModulesUnder(sourceRoot: string): void {
291
+ const prefix = sourceRoot.endsWith("/") ? sourceRoot : `${sourceRoot}/`;
292
+ for (const path of this.moduleCache.keys()) {
293
+ if (path === sourceRoot || path.startsWith(prefix)) {
294
+ this.moduleCache.delete(path);
295
+ }
296
+ }
297
+ }
298
+
274
299
  /**
275
300
  * Execute a handler function with a per-request timeout and error boundary.
276
301
  */
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Import a user/plugin route module so its helpers match the files on disk.
3
+ *
4
+ * Bun caches every imported helper independently. Cache-busting only the
5
+ * entry file (`import(file?t=mtime)`) re-binds it to whatever helpers are
6
+ * already in the registry. That is how a new route that imports a new export
7
+ * from `src/http.ts` 500s with "Export named 'requireNumber' not found" while
8
+ * `/frame` (still on the old http.ts surface) keeps answering 200.
9
+ *
10
+ * Hooks and tools already treat the whole plugin directory as the reload
11
+ * unit (`plugins/source-fingerprint.ts`). Routes must do the same: when the
12
+ * entry file's mtime moves, evict every source file in that tree before the
13
+ * next import, so the new entry cannot pair with a stale helper.
14
+ */
15
+
16
+ import { statSync } from "node:fs";
17
+ import { dirname, sep } from "node:path";
18
+ import { fileURLToPath, pathToFileURL } from "node:url";
19
+
20
+ import { snapshotPluginSource } from "../../plugins/source-fingerprint.js";
21
+ import { evictModule } from "../../plugins/surface-import.js";
22
+ import { getWorkspaceRoutesDir } from "../../util/platform.js";
23
+
24
+ /**
25
+ * Directory whose source files must stay mutually consistent with this
26
+ * handler. An installed or default plugin uses the plugin root (so `src/`
27
+ * helpers evict with `routes/`). Workspace routes use the workspace
28
+ * `routes/` directory.
29
+ */
30
+ export function routeSourceRoot(routesDir: string): string {
31
+ if (routesDir === getWorkspaceRoutesDir()) {
32
+ return routesDir;
33
+ }
34
+ return dirname(routesDir);
35
+ }
36
+
37
+ /**
38
+ * Infer the source root from a handler path when the caller has no
39
+ * {@link routeSourceRoot} (the route-host worker only sees `filePath`).
40
+ */
41
+ export function sourceRootForHandler(filePath: string): string {
42
+ const marker = `${sep}routes${sep}`;
43
+ const idx = filePath.lastIndexOf(marker);
44
+ if (idx !== -1) {
45
+ return filePath.slice(0, idx);
46
+ }
47
+ return dirname(filePath);
48
+ }
49
+
50
+ /**
51
+ * Drop every source module under `sourceRoot` from the runtime registry,
52
+ * including query-string aliases (`file.ts?t=mtime`) the dispatcher uses to
53
+ * cache-bust the entry file.
54
+ */
55
+ export function evictRouteSourceTree(sourceRoot: string): void {
56
+ const { evictionPaths } = snapshotPluginSource(sourceRoot);
57
+ for (const path of evictionPaths) {
58
+ evictModule(path);
59
+ }
60
+ evictRegistryAliases(sourceRoot);
61
+ }
62
+
63
+ function evictRegistryAliases(sourceRoot: string): void {
64
+ const prefix = sourceRoot.endsWith("/") ? sourceRoot : `${sourceRoot}/`;
65
+ const fileUrlPrefix = pathToFileURL(prefix).href;
66
+ for (const key of Object.keys(require.cache)) {
67
+ if (!keyBelongsToTree(key, sourceRoot, prefix, fileUrlPrefix)) {
68
+ continue;
69
+ }
70
+ delete require.cache[key];
71
+ }
72
+ }
73
+
74
+ function keyBelongsToTree(
75
+ key: string,
76
+ sourceRoot: string,
77
+ prefix: string,
78
+ fileUrlPrefix: string,
79
+ ): boolean {
80
+ const bare = key.split("?")[0] ?? key;
81
+ if (bare.includes("/node_modules/") || bare.includes("/node_modules?")) {
82
+ return false;
83
+ }
84
+ if (
85
+ bare === sourceRoot ||
86
+ bare.startsWith(prefix) ||
87
+ bare.startsWith(fileUrlPrefix)
88
+ ) {
89
+ return true;
90
+ }
91
+ if (bare.startsWith("file:")) {
92
+ try {
93
+ const asPath = fileURLToPath(bare);
94
+ return asPath === sourceRoot || asPath.startsWith(prefix);
95
+ } catch {
96
+ return false;
97
+ }
98
+ }
99
+ return false;
100
+ }
101
+
102
+ /** Dynamic-import a handler, cache-busted by the file's current mtime. */
103
+ export async function importRouteModule(
104
+ filePath: string,
105
+ ): Promise<Record<string, unknown>> {
106
+ const mtimeMs = statSync(filePath).mtimeMs;
107
+ return (await import(`${filePath}?t=${mtimeMs}`)) as Record<string, unknown>;
108
+ }
@@ -16,6 +16,7 @@ import { writeFileSync } from "node:fs";
16
16
  import { getConfig } from "../config/loader.js";
17
17
  import { rehydratePlatformCredentials } from "../config/platform-rehydration.js";
18
18
  import { resetDb } from "../persistence/db-connection.js";
19
+ import { registerWorkerPluginSurface } from "../plugins/worker-plugin-surface.js";
19
20
  import { disableStreamSeqStamping } from "../runtime/assistant-stream-state.js";
20
21
  import { initializeTools } from "../tools/registry.js";
21
22
  import { getLogger } from "../util/logger.js";
@@ -51,6 +52,11 @@ async function main(): Promise<void> {
51
52
  // rejected for both inference and background-wake requests.
52
53
  await rehydratePlatformCredentials();
53
54
 
55
+ // This process is the sole runner of schedule execution and hosts real agent
56
+ // conversations (wake, execute, and workflow modes), so it needs the default
57
+ // plugins' hooks and injectors just like the daemon.
58
+ registerWorkerPluginSurface();
59
+
54
60
  // Populate the tool registry (core built-ins + workspace tools). The daemon
55
61
  // does this at startup; this standalone process has to do it itself so
56
62
  // workflow schedules pass the core-tools readiness gate and agent-executed
@@ -273,20 +273,12 @@ async function runGatewayFlow(
273
273
  ): Promise<OAuth2FlowResult> {
274
274
  // Dynamic imports required here to avoid circular dependencies with
275
275
  // config/loader → security → oauth2 module chains.
276
- const { loadConfig } = await import("../config/loader.js");
277
- const { getOAuthCallbackUrl } =
278
- await import("../inbound/public-ingress-urls.js");
279
- const { resolveCallbackUrl } =
280
- await import("../inbound/platform-callback-registration.js");
276
+ const { resolveOauthCallbackUrl } =
277
+ await import("../inbound/oauth-callback-url.js");
281
278
  const { registerPendingCallback } =
282
279
  await import("./oauth-callback-registry.js");
283
280
 
284
- const appConfig = loadConfig();
285
- const redirectUri = await resolveCallbackUrl(
286
- () => getOAuthCallbackUrl(appConfig),
287
- "webhooks/oauth/callback",
288
- "oauth",
289
- );
281
+ const redirectUri = await resolveOauthCallbackUrl();
290
282
 
291
283
  const codePromise = new Promise<string>((resolve, reject) => {
292
284
  registerPendingCallback(state, resolve, reject);
@@ -556,20 +548,12 @@ export async function prepareOAuth2Flow(
556
548
 
557
549
  // Dynamic imports required here to avoid circular dependencies with
558
550
  // config/loader → security → oauth2 module chains.
559
- const { loadConfig } = await import("../config/loader.js");
560
- const { getOAuthCallbackUrl } =
561
- await import("../inbound/public-ingress-urls.js");
562
- const { resolveCallbackUrl } =
563
- await import("../inbound/platform-callback-registration.js");
551
+ const { resolveOauthCallbackUrl } =
552
+ await import("../inbound/oauth-callback-url.js");
564
553
  const { registerPendingCallback } =
565
554
  await import("./oauth-callback-registry.js");
566
555
 
567
- const appConfig = loadConfig();
568
- const redirectUri = await resolveCallbackUrl(
569
- () => getOAuthCallbackUrl(appConfig),
570
- "webhooks/oauth/callback",
571
- "oauth",
572
- );
556
+ const redirectUri = await resolveOauthCallbackUrl();
573
557
 
574
558
  const codeVerifier = generateCodeVerifier();
575
559
  const codeChallenge = generateCodeChallenge(codeVerifier);
@@ -1,5 +1,6 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
+ import { batchBoundaryGapReason } from "../../providers/speech-to-text/provider-catalog.js";
3
4
  import { SttError } from "../types.js";
4
5
 
5
6
  // ---------------------------------------------------------------------------
@@ -620,3 +621,24 @@ describe("vellum factory case", () => {
620
621
  expect(createDaemonBatchTranscriber(null, "deepgram")).toBeNull();
621
622
  });
622
623
  });
624
+
625
+ describe("deepgram-flux factory case", () => {
626
+ // The copy itself is pinned in provider-catalog.test.ts; what matters here
627
+ // is that the factory speaks with the catalog's voice rather than its own.
628
+ test("throws the catalog's streaming-only reason", () => {
629
+ expect(() =>
630
+ createDaemonBatchTranscriber("dg-test-key", "deepgram-flux"),
631
+ ).toThrow(batchBoundaryGapReason("deepgram-flux"));
632
+ });
633
+
634
+ test("marks the error user-facing so friendly copy does not overwrite it", () => {
635
+ try {
636
+ createDaemonBatchTranscriber("dg-test-key", "deepgram-flux");
637
+ throw new Error("expected the factory to throw");
638
+ } catch (err) {
639
+ expect(err).toBeInstanceOf(SttError);
640
+ expect((err as SttError).category).toBe("provider-error");
641
+ expect((err as SttError).userFacing).toBe(true);
642
+ }
643
+ });
644
+ });
@@ -2,10 +2,14 @@ import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import type {
4
4
  SttStreamServerClosedEvent,
5
+ SttStreamServerEagerTurnEndEvent,
5
6
  SttStreamServerErrorEvent,
6
7
  SttStreamServerEvent,
7
8
  SttStreamServerFinalEvent,
8
9
  SttStreamServerPartialEvent,
10
+ SttStreamServerTurnEndEvent,
11
+ SttStreamServerTurnResumedEvent,
12
+ SttStreamServerTurnStartEvent,
9
13
  } from "../types.js";
10
14
 
11
15
  // ---------------------------------------------------------------------------
@@ -87,3 +91,93 @@ describe("SttStreamServerEvent types", () => {
87
91
  expect(_label).toBeUndefined();
88
92
  });
89
93
  });
94
+
95
+ describe("turn-detection server events", () => {
96
+ test("turn-start event compiles and round-trips", () => {
97
+ const event: SttStreamServerTurnStartEvent = { type: "turn-start" };
98
+ const asUnion: SttStreamServerEvent = event;
99
+ expect(asUnion.type).toBe("turn-start");
100
+ });
101
+
102
+ test("eager-turn-end event carries the speculated transcript", () => {
103
+ const event: SttStreamServerEagerTurnEndEvent = {
104
+ type: "eager-turn-end",
105
+ text: "is this the end",
106
+ };
107
+ const asUnion: SttStreamServerEvent = event;
108
+ expect(asUnion.type).toBe("eager-turn-end");
109
+ expect(event.text).toBe("is this the end");
110
+ });
111
+
112
+ test("turn-resumed event compiles and round-trips", () => {
113
+ const event: SttStreamServerTurnResumedEvent = { type: "turn-resumed" };
114
+ const asUnion: SttStreamServerEvent = event;
115
+ expect(asUnion.type).toBe("turn-resumed");
116
+ });
117
+
118
+ test("turn-end event carries the committed transcript and optional confidence", () => {
119
+ const withoutConfidence: SttStreamServerTurnEndEvent = {
120
+ type: "turn-end",
121
+ text: "done speaking",
122
+ };
123
+ expect(withoutConfidence.confidence).toBeUndefined();
124
+
125
+ const event: SttStreamServerTurnEndEvent = {
126
+ type: "turn-end",
127
+ text: "done speaking",
128
+ confidence: 0.82,
129
+ };
130
+ const asUnion: SttStreamServerEvent = event;
131
+ expect(asUnion.type).toBe("turn-end");
132
+ expect(event.text).toBe("done speaking");
133
+ expect(event.confidence).toBe(0.82);
134
+ });
135
+
136
+ test("a consumer switch over every variant is exhaustive", () => {
137
+ // Fails to compile if a variant is added to SttStreamServerEvent
138
+ // without a case here, which is what forces consumers to be revisited.
139
+ const label = (event: SttStreamServerEvent): string => {
140
+ switch (event.type) {
141
+ case "partial":
142
+ case "final":
143
+ case "finalized":
144
+ case "error":
145
+ case "closed":
146
+ return "transcript";
147
+ case "turn-start":
148
+ case "eager-turn-end":
149
+ case "turn-resumed":
150
+ case "turn-end":
151
+ return "turn";
152
+ default: {
153
+ const _exhaustive: never = event;
154
+ return _exhaustive;
155
+ }
156
+ }
157
+ };
158
+
159
+ const events: SttStreamServerEvent[] = [
160
+ { type: "partial", text: "a" },
161
+ { type: "final", text: "a" },
162
+ { type: "finalized" },
163
+ { type: "error", category: "provider-error", message: "boom" },
164
+ { type: "closed" },
165
+ { type: "turn-start" },
166
+ { type: "eager-turn-end", text: "a" },
167
+ { type: "turn-resumed" },
168
+ { type: "turn-end", text: "a" },
169
+ ];
170
+
171
+ expect(events.map(label)).toEqual([
172
+ "transcript",
173
+ "transcript",
174
+ "transcript",
175
+ "transcript",
176
+ "transcript",
177
+ "turn",
178
+ "turn",
179
+ "turn",
180
+ "turn",
181
+ ]);
182
+ });
183
+ });
@@ -13,6 +13,7 @@
13
13
  * - xAI (`xai`)
14
14
  */
15
15
 
16
+ import { batchBoundaryGapReason } from "../providers/speech-to-text/provider-catalog.js";
16
17
  import type {
17
18
  BatchTranscriber,
18
19
  SttProviderId,
@@ -245,6 +246,9 @@ class VellumManagedBatchTranscriber implements BatchTranscriber {
245
246
  * language code), and the vellum managed path, whose platform proxy passes
246
247
  * it to Deepgram server-side. Whisper and Gemini auto-detect natively and
247
248
  * take no language parameter, so it is silently ignored for them.
249
+ *
250
+ * Throws an {@link SttError} for a provider with no batch endpoint at all,
251
+ * which no key can fix and `null` would understate.
248
252
  */
249
253
  export function createDaemonBatchTranscriber(
250
254
  apiKey: string | null | undefined,
@@ -268,6 +272,12 @@ export function createDaemonBatchTranscriber(
268
272
  return new GoogleGeminiBatchTranscriber(apiKey);
269
273
  case "xai":
270
274
  return new XAIBatchTranscriber(apiKey, language);
275
+ case "deepgram-flux":
276
+ // Same copy the resolver raises, so a direct factory caller and a
277
+ // config-driven one report the mismatch identically.
278
+ throw new SttError("provider-error", batchBoundaryGapReason(providerId), {
279
+ userFacing: true,
280
+ });
271
281
  default: {
272
282
  // Exhaustive check — compile error if a new SttProviderId is added
273
283
  // without a corresponding case here.
@@ -5,8 +5,11 @@
5
5
  * a client WebSocket connection and a provider-specific streaming adapter.
6
6
  * The orchestrator accepts client events (`audio`, `stop`), routes audio
7
7
  * frames to the resolved {@link StreamingTranscriber}, and emits normalized
8
- * server events (`partial`, `final`, `error`, `closed`) back over the same
9
- * WebSocket connection with per-session ordering guarantees.
8
+ * server events back over the same WebSocket connection with per-session
9
+ * ordering guarantees: transcripts (`partial`, `final`, `finalized`),
10
+ * turn boundaries from providers that detect them (`turn-start`,
11
+ * `eager-turn-end`, `turn-resumed`, `turn-end`), and termination
12
+ * (`error`, `closed`).
10
13
  *
11
14
  * Session lifecycle:
12
15
  * 1. Client opens a WebSocket to `/v1/stt/stream` with required `mimeType`
@@ -17,8 +20,9 @@
17
20
  * `resolveStreamingTranscriber()` and starts the provider session.
18
21
  * 3. The client sends `audio` frames (binary or base64-encoded JSON) and
19
22
  * a `stop` event when recording is complete.
20
- * 4. The provider emits `partial` and `final` transcript events which the
21
- * orchestrator forwards to the client as JSON frames.
23
+ * 4. The provider emits transcript events, plus turn-boundary events when it
24
+ * detects turns, and the orchestrator forwards each to the client as a
25
+ * JSON frame.
22
26
  * 5. The session closes deterministically on client disconnect, `stop`
23
27
  * event, idle timeout, or runtime shutdown.
24
28
  *
package/src/stt/types.ts CHANGED
@@ -23,6 +23,11 @@
23
23
  export type SttProviderId =
24
24
  | "openai-whisper"
25
25
  | "deepgram"
26
+ /**
27
+ * Deepgram's Flux conversational speech model. Shares the `deepgram`
28
+ * credential and is streaming-only: Flux has no batch endpoint.
29
+ */
30
+ | "deepgram-flux"
26
31
  | "google-gemini"
27
32
  | "xai"
28
33
  | "vellum";
@@ -65,6 +70,26 @@ export type ConversationStreamingMode =
65
70
  | "incremental-batch"
66
71
  | "none";
67
72
 
73
+ /**
74
+ * Turn-detection support mode.
75
+ *
76
+ * Describes whether a provider decides end-of-turn itself, in-band with the
77
+ * audio it is already transcribing, rather than leaving the boundary to the
78
+ * session's local silence timer and front-door hold verdict.
79
+ *
80
+ * - `"provider"`: the provider emits turn lifecycle events (`turn-start`,
81
+ * `turn-end`) on its transcript stream, and a live-voice session may let
82
+ * those events commit the turn.
83
+ * - `"none"`: the provider transcribes only; the local silence boundary owns
84
+ * end-of-turn.
85
+ *
86
+ * A provider declaring `"provider"` should number its turns: the staleness
87
+ * check prefers the turn index carried on the event, and degrades to the
88
+ * session's local VAD generation counter when one is absent, which understates
89
+ * the recorded latency for a turn the caller resumed and stopped again.
90
+ */
91
+ export type SttTurnDetectionMode = "provider" | "none";
92
+
68
93
  // ---------------------------------------------------------------------------
69
94
  // Boundary identifier
70
95
  // ---------------------------------------------------------------------------
@@ -245,6 +270,10 @@ export type SttStreamServerEvent =
245
270
  | SttStreamServerPartialEvent
246
271
  | SttStreamServerFinalEvent
247
272
  | SttStreamServerFinalizedEvent
273
+ | SttStreamServerTurnStartEvent
274
+ | SttStreamServerEagerTurnEndEvent
275
+ | SttStreamServerTurnResumedEvent
276
+ | SttStreamServerTurnEndEvent
248
277
  | SttStreamServerErrorEvent
249
278
  | SttStreamServerClosedEvent;
250
279
 
@@ -314,6 +343,80 @@ export interface SttStreamServerFinalizedEvent {
314
343
  readonly type: "finalized";
315
344
  }
316
345
 
346
+ // ---------------------------------------------------------------------------
347
+ // Turn-detection events
348
+ //
349
+ // Consumers that ignore these keep committing transcripts from `final`
350
+ // exactly as they do without a turn-detecting provider.
351
+ // ---------------------------------------------------------------------------
352
+
353
+ /**
354
+ * The provider's turn model detected the start of a user turn.
355
+ *
356
+ * Emitted only by providers with model-integrated turn detection; providers
357
+ * without it never emit these.
358
+ */
359
+ export interface SttStreamServerTurnStartEvent {
360
+ readonly type: "turn-start";
361
+ /**
362
+ * Provider's index for the turn being opened, monotonically increasing
363
+ * within a stream. Undefined when the provider does not number its turns.
364
+ */
365
+ readonly turnIndex?: number;
366
+ }
367
+
368
+ /**
369
+ * The provider's turn model speculates the user has finished, ahead of its
370
+ * committed end-of-turn decision. A {@link SttStreamServerTurnResumedEvent}
371
+ * retracts the speculation; a {@link SttStreamServerTurnEndEvent} confirms it.
372
+ *
373
+ * Emitted only by providers with model-integrated turn detection; providers
374
+ * without it never emit these.
375
+ */
376
+ export interface SttStreamServerEagerTurnEndEvent {
377
+ readonly type: "eager-turn-end";
378
+ /** Transcript text as of the speculated turn end. */
379
+ readonly text: string;
380
+ }
381
+
382
+ /**
383
+ * The user kept speaking after an {@link SttStreamServerEagerTurnEndEvent},
384
+ * retracting the speculated turn end.
385
+ *
386
+ * Emitted only by providers with model-integrated turn detection; providers
387
+ * without it never emit these.
388
+ */
389
+ export interface SttStreamServerTurnResumedEvent {
390
+ readonly type: "turn-resumed";
391
+ }
392
+
393
+ /**
394
+ * The provider's turn model committed an end of turn: the user has finished
395
+ * speaking and a reply can be dispatched.
396
+ *
397
+ * Emitted only by providers with model-integrated turn detection; providers
398
+ * without it never emit these.
399
+ */
400
+ export interface SttStreamServerTurnEndEvent {
401
+ readonly type: "turn-end";
402
+ /** Committed transcript text for the completed turn. */
403
+ readonly text: string;
404
+ /**
405
+ * Provider-emitted end-of-turn confidence in [0, 1]. Undefined when the
406
+ * provider does not surface a score.
407
+ */
408
+ readonly confidence?: number;
409
+ /**
410
+ * Provider's index for the turn being closed, matching the
411
+ * {@link SttStreamServerTurnStartEvent} that opened it. A consumer can
412
+ * compare it against the newest turn it has seen opened to tell an
413
+ * end-of-turn for the turn still in progress from one the provider has
414
+ * already superseded. Undefined when the provider does not number its
415
+ * turns.
416
+ */
417
+ readonly turnIndex?: number;
418
+ }
419
+
317
420
  /** An error occurred during streaming transcription. */
318
421
  export interface SttStreamServerErrorEvent {
319
422
  readonly type: "error";
@@ -705,9 +705,7 @@ export class SubagentManager {
705
705
  // Inherit the parent's *turn* trust ahead of its conversation-level slot:
706
706
  // the slot holds whichever actor sent most recently, so a spawn during one
707
707
  // actor's turn would otherwise run under another's privileges.
708
- const parentTurnTrust =
709
- parentConversation?.currentTurnTrustContext ??
710
- parentConversation?.trustContext;
708
+ const parentTurnTrust = parentConversation?.getTurnOrRestingTrust();
711
709
  if (config.trustContext) {
712
710
  conversation.setTrustContext({ ...config.trustContext });
713
711
  } else if (parentTurnTrust) {
@@ -483,12 +483,13 @@ export const SUBAGENT_ROLE_REGISTRY: Record<SubagentRole, SubagentRoleConfig> =
483
483
  skillIds: [],
484
484
  systemPromptPreamble: [
485
485
  "You are a research subagent with read-only access: search the web, read and search files, and recall memories. There is no shell, and you cannot write or edit files.",
486
- // `file_read` takes no default line limit and its results are exempt
487
- // from result-time spooling, so a full body rides every LLM call until
488
- // the turn ends. Telling the role to read whole files makes that the
489
- // default shape of a research turn, and this role now covers the
490
- // traffic three roles used to. The anti-slicing intent stays: one pass
491
- // over the range that is needed rather than many small ones.
486
+ // `file_read` returns the first DEFAULT_READ_LINE_LIMIT (2000) lines
487
+ // unless a limit is passed, with a truncation notice naming the resume
488
+ // offset, and oversized results spool to .tool-results/ like any other
489
+ // tool's (only re-reads of spooled content stay inline). So a ranged
490
+ // read is both the cheap shape and the one that avoids a spool
491
+ // round-trip. The anti-slicing intent stays: one pass over the range
492
+ // that is needed rather than many small ones.
492
493
  "Working method: use code_search to search file contents across directories, file_list to enumerate paths, and file_read to read files and logs. Prefer broad code_search queries across a directory over one-symbol-at-a-time queries, and read the range you need in one pass rather than many small slices.",
493
494
  "Send notify_parent (urgency 'important') as soon as each finding is confirmed, so progress survives interruption.",
494
495
  "Your final message is the deliverable: a compact report that answers the objective, gives the evidence behind each claim (file:line references, URLs, or quotes), and names what you could not determine. For a root-cause investigation, use the sections Symptom, Root cause, Evidence, Suggested fix, Open questions.",