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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (596) hide show
  1. package/AGENTS.md +9 -3
  2. package/ARCHITECTURE.md +32 -10
  3. package/docs/architecture/memory.md +43 -5
  4. package/docs/architecture/security.md +96 -152
  5. package/docs/architecture/turn-actor.md +70 -0
  6. package/docs/browser-use-architecture-phase2.md +128 -56
  7. package/docs/flux-turn-detection-spike.md +248 -0
  8. package/docs/guardian-request-flow.md +35 -0
  9. package/docs/stt-provider-onboarding.md +3 -1
  10. package/knip.json +3 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  13. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  19. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  20. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  21. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  23. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
  24. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
  25. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  26. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  27. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  28. package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  29. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  30. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  31. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  32. package/openapi.yaml +289 -113
  33. package/package.json +1 -1
  34. package/scripts/voice-ttft-spike.ts +3 -3
  35. package/scripts/write-plugin-api-shim.ts +10 -0
  36. package/src/__tests__/app-compiler.test.ts +38 -3
  37. package/src/__tests__/app-control-flow.test.ts +1 -0
  38. package/src/__tests__/approval-routes-http.test.ts +2 -2
  39. package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
  40. package/src/__tests__/attachments-store.test.ts +21 -12
  41. package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
  42. package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
  43. package/src/__tests__/call-site-routing-provider.test.ts +1 -1
  44. package/src/__tests__/channel-availability-routes.test.ts +14 -1
  45. package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
  46. package/src/__tests__/channel-delivery-store.test.ts +14 -14
  47. package/src/__tests__/channel-inbound-disk-pressure.test.ts +4 -4
  48. package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
  49. package/src/__tests__/checker.test.ts +20 -314
  50. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
  51. package/src/__tests__/compaction-events.test.ts +8 -10
  52. package/src/__tests__/config-loader-backfill.test.ts +3 -3
  53. package/src/__tests__/config-schema.test.ts +25 -10
  54. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  55. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
  56. package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
  57. package/src/__tests__/conversation-agent-loop.test.ts +32 -21
  58. package/src/__tests__/conversation-attachments.test.ts +0 -1
  59. package/src/__tests__/conversation-attention-store.test.ts +63 -0
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
  61. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -0
  63. package/src/__tests__/conversation-fork-referential.test.ts +67 -0
  64. package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
  65. package/src/__tests__/conversation-load-history-repair.test.ts +209 -0
  66. package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
  67. package/src/__tests__/conversation-queue.test.ts +154 -4
  68. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  69. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
  71. package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +187 -102
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
  75. package/src/__tests__/conversation-slash-queue.test.ts +3 -0
  76. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  77. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
  78. package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
  79. package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
  80. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
  81. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
  82. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
  83. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
  85. package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
  86. package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
  87. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
  88. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
  89. package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
  90. package/src/__tests__/credential-prompt-route.test.ts +7 -10
  91. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  92. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  93. package/src/__tests__/custom-profile-ensure.test.ts +5 -1
  94. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  95. package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
  96. package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
  97. package/src/__tests__/document-append-idempotency.test.ts +233 -0
  98. package/src/__tests__/document-sync-tags.test.ts +0 -75
  99. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  100. package/src/__tests__/edit-propagation.test.ts +107 -11
  101. package/src/__tests__/file-ops-service.test.ts +163 -30
  102. package/src/__tests__/filesystem-tools.test.ts +23 -24
  103. package/src/__tests__/gateway-only-guard.test.ts +2 -5
  104. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  105. package/src/__tests__/helpers/mock-actor-context.ts +49 -0
  106. package/src/__tests__/helpers/mock-conversation.ts +13 -1
  107. package/src/__tests__/host-file-read-tool.test.ts +16 -19
  108. package/src/__tests__/http-user-message-parity.test.ts +1 -1
  109. package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
  110. package/src/__tests__/injector-chain.test.ts +63 -41
  111. package/src/__tests__/injector-disk-pressure.test.ts +11 -23
  112. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  113. package/src/__tests__/llm-context-resolution.test.ts +73 -1
  114. package/src/__tests__/llm-schema.test.ts +5 -2
  115. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
  116. package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
  117. package/src/__tests__/media-generate-image.test.ts +131 -21
  118. package/src/__tests__/memory-retrieval-hook.test.ts +100 -7
  119. package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
  120. package/src/__tests__/mtime-cache.test.ts +3 -1
  121. package/src/__tests__/non-member-access-request.test.ts +0 -20
  122. package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
  123. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  124. package/src/__tests__/plugin-api-store-credential.test.ts +261 -0
  125. package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
  126. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  127. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  128. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  129. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  130. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -4
  131. package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
  132. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
  133. package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
  134. package/src/__tests__/proxy-approval-callback.test.ts +1 -0
  135. package/src/__tests__/qdrant-manager.test.ts +14 -1
  136. package/src/__tests__/reaction-persistence.test.ts +200 -7
  137. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  138. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  139. package/src/__tests__/run-conversation-turn-persistence.test.ts +460 -105
  140. package/src/__tests__/run-due-schedules.test.ts +21 -0
  141. package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
  142. package/src/__tests__/schedule-routes.test.ts +23 -0
  143. package/src/__tests__/schedule-store.test.ts +17 -0
  144. package/src/__tests__/scoped-approval-grants.test.ts +11 -6
  145. package/src/__tests__/secret-ingress-channel.test.ts +0 -1
  146. package/src/__tests__/secret-ingress-http.test.ts +1 -1
  147. package/src/__tests__/send-endpoint-busy.test.ts +3 -3
  148. package/src/__tests__/skills.test.ts +32 -0
  149. package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
  150. package/src/__tests__/starter-task-flow.test.ts +5 -4
  151. package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
  152. package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
  153. package/src/__tests__/subagent-spawn-and-await.test.ts +18 -17
  154. package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
  155. package/src/__tests__/subagent-tools.test.ts +81 -101
  156. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
  157. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  158. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  159. package/src/__tests__/tool-executor.test.ts +68 -39
  160. package/src/__tests__/turn-events-store.test.ts +43 -0
  161. package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
  162. package/src/__tests__/ui-shape-teaching.test.ts +33 -0
  163. package/src/__tests__/ui-visual-surface.test.ts +1 -1
  164. package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
  165. package/src/__tests__/ui-work-result-surface.test.ts +1 -1
  166. package/src/__tests__/user-plugin-loader.test.ts +3 -1
  167. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  168. package/src/__tests__/visible-app-context.test.ts +16 -9
  169. package/src/__tests__/voice-config-update.test.ts +40 -0
  170. package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
  171. package/src/__tests__/voice-session-bridge.test.ts +85 -29
  172. package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
  173. package/src/__tests__/worker-plugin-surface.test.ts +77 -0
  174. package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
  175. package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
  176. package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
  177. package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
  178. package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
  179. package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
  180. package/src/acp/__tests__/auth-required.test.ts +161 -0
  181. package/src/acp/acp-claude-oauth.ts +19 -2
  182. package/src/acp/agent-process.test.ts +100 -0
  183. package/src/acp/agent-process.ts +29 -26
  184. package/src/acp/auth-required.ts +102 -0
  185. package/src/acp/session-manager.test.ts +119 -0
  186. package/src/acp/session-manager.ts +76 -3
  187. package/src/api/events/acp-auth-required.ts +55 -0
  188. package/src/api/events/host-file.ts +2 -2
  189. package/src/api/index.ts +7 -0
  190. package/src/api/surfaces.ts +12 -3
  191. package/src/apps/app-store.ts +3 -0
  192. package/src/bundler/package-resolver.ts +2 -30
  193. package/src/calls/__tests__/voice-session-bridge.test.ts +194 -11
  194. package/src/calls/__tests__/voice-triage-escalate.test.ts +102 -0
  195. package/src/calls/call-controller.ts +19 -3
  196. package/src/calls/call-setup-flow.ts +0 -1
  197. package/src/calls/media-stream-stt-session.ts +15 -0
  198. package/src/calls/voice-session-bridge.ts +115 -41
  199. package/src/calls/voice-triage-escalate.ts +105 -2
  200. package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
  201. package/src/channels/config.ts +13 -0
  202. package/src/channels/plugin-channel-declarations.ts +108 -0
  203. package/src/channels/types.ts +30 -0
  204. package/src/cli/AGENTS.md +5 -2
  205. package/src/cli/bundled-modules.ts +29 -0
  206. package/src/cli/commands/credentials.help.ts +2 -2
  207. package/src/cli/commands/db/repair.ts +4 -8
  208. package/src/cli/commands/domain.ts +6 -3
  209. package/src/cli/commands/email.ts +6 -3
  210. package/src/cli/commands/inference-providers.ts +1 -1
  211. package/src/cli/commands/keys.ts +8 -3
  212. package/src/cli/commands/mcp.help.ts +13 -4
  213. package/src/cli/commands/mcp.ts +9 -0
  214. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  215. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
  216. package/src/cli/commands/memory/index.help.ts +84 -28
  217. package/src/cli/commands/memory/index.ts +2 -0
  218. package/src/cli/commands/memory/memory-v2.ts +58 -54
  219. package/src/cli/commands/memory/memory-v3.ts +64 -0
  220. package/src/cli/commands/memory/memory-validate.ts +18 -0
  221. package/src/cli/commands/plugins.ts +85 -36
  222. package/src/cli/commands/schedules.ts +35 -1
  223. package/src/cli/commands/stt.help.ts +27 -2
  224. package/src/cli/commands/trust.ts +3 -14
  225. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
  226. package/src/cli/lib/bundled-marketplace.json +14 -1
  227. package/src/cli/lib/upgrade-plugin.ts +42 -0
  228. package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
  229. package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
  230. package/src/config/__tests__/default-provider.test.ts +6 -1
  231. package/src/config/__tests__/profile-materialization.test.ts +75 -19
  232. package/src/config/assistant-feature-flags.ts +36 -15
  233. package/src/config/balanced-model-experiment.ts +35 -0
  234. package/src/config/bundled-skills/acp/SKILL.md +6 -7
  235. package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
  236. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  237. package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
  238. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
  239. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
  240. package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
  241. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  242. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
  243. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
  244. package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
  245. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
  246. package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
  247. package/src/config/bundled-skills/subagent/SKILL.md +17 -12
  248. package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
  249. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
  250. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
  251. package/src/config/call-site-defaults.ts +11 -5
  252. package/src/config/default-profile-catalog.ts +179 -16
  253. package/src/config/default-profile-names.ts +4 -1
  254. package/src/config/default-provider-resolution.ts +4 -0
  255. package/src/config/feature-flag-registry.json +12 -12
  256. package/src/config/llm-context-resolution.ts +11 -3
  257. package/src/config/llm-resolver.ts +28 -1
  258. package/src/config/profile-materialization.ts +70 -22
  259. package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
  260. package/src/config/schemas/call-site-catalog.ts +4 -4
  261. package/src/config/schemas/live-voice.ts +57 -23
  262. package/src/config/schemas/llm.ts +59 -32
  263. package/src/config/schemas/mcp.ts +23 -0
  264. package/src/config/schemas/plugin-updates.ts +6 -2
  265. package/src/config/schemas/stt.ts +1 -0
  266. package/src/config/skills.ts +9 -2
  267. package/src/context/outbound-sanitize.ts +96 -1
  268. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
  269. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
  270. package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
  271. package/src/daemon/conversation-agent-loop.ts +31 -19
  272. package/src/daemon/conversation-messaging.ts +5 -1
  273. package/src/daemon/conversation-notifiers.ts +20 -10
  274. package/src/daemon/conversation-process.ts +20 -6
  275. package/src/daemon/conversation-runtime-assembly.ts +12 -6
  276. package/src/daemon/conversation-store.ts +14 -4
  277. package/src/daemon/conversation-surfaces.ts +75 -26
  278. package/src/daemon/conversation-tool-setup.ts +3 -6
  279. package/src/daemon/conversation.ts +154 -51
  280. package/src/daemon/doordash-steps.ts +2 -2
  281. package/src/daemon/lifecycle.ts +14 -1
  282. package/src/daemon/mcp-reload-service.ts +36 -6
  283. package/src/daemon/process-message.ts +13 -26
  284. package/src/daemon/providers-setup.ts +6 -3
  285. package/src/daemon/trust-context-types.ts +29 -0
  286. package/src/daemon/trust-context.ts +12 -7
  287. package/src/daemon/wake-conversation-ops.ts +3 -2
  288. package/src/daemon/windows-compiled-entry.ts +4 -0
  289. package/src/documents/document-store.ts +143 -240
  290. package/src/hooks/hook-loader.ts +3 -3
  291. package/src/hooks/registry.ts +103 -45
  292. package/src/hooks/types.ts +5 -0
  293. package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
  294. package/src/inbound/oauth-callback-url.ts +61 -0
  295. package/src/ipc/gateway-client.test.ts +1 -1
  296. package/src/ipc/gateway-client.ts +14 -21
  297. package/src/ipc/gateway-flag-listener.ts +17 -3
  298. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
  299. package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
  300. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +1050 -0
  301. package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
  302. package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
  303. package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
  304. package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
  305. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
  306. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
  307. package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
  308. package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
  309. package/src/live-voice/live-voice-archive.ts +2 -0
  310. package/src/live-voice/live-voice-manager.ts +16 -3
  311. package/src/live-voice/live-voice-metrics.ts +57 -32
  312. package/src/live-voice/live-voice-photo.ts +1 -2
  313. package/src/live-voice/live-voice-session.ts +598 -323
  314. package/src/live-voice/progress-narration.ts +277 -0
  315. package/src/live-voice/protocol.ts +21 -1
  316. package/src/live-voice/windows-compiled-live-voice.ts +4 -0
  317. package/src/mcp/__tests__/effective-config.test.ts +238 -0
  318. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  319. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
  320. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
  321. package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
  322. package/src/mcp/client.ts +16 -11
  323. package/src/mcp/effective-config.ts +113 -0
  324. package/src/mcp/manager.ts +11 -6
  325. package/src/mcp/mcp-auth-orchestrator.ts +13 -22
  326. package/src/mcp/mcp-oauth-provider.ts +205 -240
  327. package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
  328. package/src/monitoring/control.ts +1 -0
  329. package/src/monitoring/db-integrity-sample.ts +4 -5
  330. package/src/monitoring/plugin-auto-update.ts +128 -24
  331. package/src/notifications/AGENTS.md +2 -0
  332. package/src/notifications/approval-card-data.ts +33 -0
  333. package/src/notifications/signal.ts +1 -0
  334. package/src/permissions/AGENTS.md +16 -0
  335. package/src/permissions/checker.test.ts +75 -122
  336. package/src/permissions/checker.ts +44 -560
  337. package/src/permissions/confirmation-guardian-request.test.ts +15 -11
  338. package/src/permissions/confirmation-guardian-request.ts +2 -2
  339. package/src/permissions/prompter.ts +1 -5
  340. package/src/permissions/question-guardian-request.test.ts +14 -6
  341. package/src/permissions/question-guardian-request.ts +1 -2
  342. package/src/persistence/attachments-store.ts +8 -1
  343. package/src/persistence/bookmark-crud.ts +3 -7
  344. package/src/persistence/conversation-attention-store.ts +16 -45
  345. package/src/persistence/conversation-crud.ts +33 -4
  346. package/src/persistence/conversation-lineage.ts +9 -0
  347. package/src/persistence/conversation-queries.ts +182 -52
  348. package/src/persistence/delivery-crud.ts +150 -36
  349. package/src/persistence/embeddings/qdrant-manager.ts +84 -49
  350. package/src/persistence/external-conversation-store.ts +32 -4
  351. package/src/persistence/llm-request-log-store.ts +4 -10
  352. package/src/persistence/llm-usage-store.ts +8 -3
  353. package/src/persistence/message-reads.test.ts +197 -0
  354. package/src/persistence/message-reads.ts +211 -0
  355. package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
  356. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
  357. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
  358. package/src/persistence/real-user-turn-filter.ts +27 -3
  359. package/src/persistence/schema/documents.ts +4 -4
  360. package/src/persistence/steps.ts +9 -0
  361. package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
  362. package/src/plugin-api/constants.ts +8 -0
  363. package/src/plugin-api/conversation-turn.ts +195 -5
  364. package/src/plugin-api/index.ts +38 -6
  365. package/src/plugin-api/resolve-credential.ts +3 -2
  366. package/src/plugin-api/store-credential.ts +146 -0
  367. package/src/plugin-api/vision-support.test.ts +1 -1
  368. package/src/plugin-api/webhook-url.ts +13 -11
  369. package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
  370. package/src/plugins/defaults/main.ts +21 -7
  371. package/src/plugins/defaults/memory/AGENTS.md +15 -3
  372. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
  373. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
  374. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
  375. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
  376. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  377. package/src/plugins/defaults/memory/buffer-format.ts +165 -0
  378. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
  379. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
  380. package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
  381. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
  382. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
  383. package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
  384. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +17 -24
  385. package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
  386. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
  387. package/src/plugins/defaults/memory/indexer.ts +3 -1
  388. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
  389. package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
  390. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
  391. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  392. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
  393. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +147 -0
  394. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  395. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  396. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  397. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  398. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  399. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  400. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +195 -58
  401. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +93 -24
  402. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  403. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  404. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  405. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  406. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  407. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  408. package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
  409. package/src/plugins/defaults/memory/substrate/static-context.ts +160 -33
  410. package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
  411. package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
  412. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
  413. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
  414. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  415. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  416. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  417. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  418. package/src/plugins/defaults/memory/v3/injector.ts +8 -0
  419. package/src/plugins/defaults/memory/v3/prune.ts +2 -0
  420. package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
  421. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
  422. package/src/plugins/defaults/memory/worker-control.ts +1 -0
  423. package/src/plugins/defaults/memory/worker.ts +6 -3
  424. package/src/plugins/defaults/worker-entrypoints.ts +3 -0
  425. package/src/plugins/external-plugin-loader.ts +47 -0
  426. package/src/plugins/mcp-servers.ts +361 -0
  427. package/src/plugins/mtime-cache.ts +37 -46
  428. package/src/plugins/pipeline.ts +14 -7
  429. package/src/plugins/plugin-execution-context.ts +35 -11
  430. package/src/plugins/worker-plugin-surface.ts +33 -0
  431. package/src/prompts/templates/system-sections.ts +0 -7
  432. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  433. package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
  434. package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
  435. package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
  436. package/src/providers/__tests__/retry-callsite.test.ts +25 -2
  437. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  438. package/src/providers/anthropic/client.ts +332 -241
  439. package/src/providers/call-site-routing.ts +30 -3
  440. package/src/providers/connection-resolution.ts +194 -11
  441. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +4 -0
  442. package/src/providers/inference/adapter-factory.ts +3 -0
  443. package/src/providers/inference/auth.ts +6 -0
  444. package/src/providers/inference/connection-availability.ts +24 -2
  445. package/src/providers/inference/connections.ts +2 -0
  446. package/src/providers/model-catalog.ts +3 -3
  447. package/src/providers/model-intents.ts +28 -8
  448. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +163 -40
  449. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  450. package/src/providers/openai/chat-completions-provider.ts +133 -36
  451. package/src/providers/openai/codex-models.ts +2 -1
  452. package/src/providers/provider-send-message.ts +32 -3
  453. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
  454. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +625 -0
  455. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
  456. package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
  457. package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
  458. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +679 -0
  459. package/src/providers/speech-to-text/provider-catalog.ts +99 -8
  460. package/src/providers/speech-to-text/resolve.ts +25 -2
  461. package/src/routes/control.ts +1 -0
  462. package/src/routes/route-host-client.ts +1 -0
  463. package/src/routes/route-host-protocol.ts +7 -0
  464. package/src/routes/worker.ts +48 -15
  465. package/src/runtime/AGENTS.md +16 -17
  466. package/src/runtime/access-request-helper.ts +9 -12
  467. package/src/runtime/agent-wake.ts +18 -15
  468. package/src/runtime/effective-capabilities.test.ts +43 -0
  469. package/src/runtime/effective-capabilities.ts +27 -0
  470. package/src/runtime/guardian-reply-router.ts +6 -2
  471. package/src/runtime/pre-first-message-gate.ts +4 -0
  472. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
  473. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +522 -2
  474. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
  475. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
  476. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
  477. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
  478. package/src/runtime/routes/__tests__/plugins-routes.test.ts +132 -35
  479. package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
  480. package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
  481. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  482. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  483. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +133 -1
  484. package/src/runtime/routes/channel-availability-routes.ts +32 -14
  485. package/src/runtime/routes/channel-route-shared.ts +0 -6
  486. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
  487. package/src/runtime/routes/conversation-list-routes.ts +181 -23
  488. package/src/runtime/routes/conversation-management-routes.ts +17 -12
  489. package/src/runtime/routes/conversation-query-routes.ts +40 -27
  490. package/src/runtime/routes/conversation-routes.ts +43 -29
  491. package/src/runtime/routes/credential-prompt-routes.ts +4 -7
  492. package/src/runtime/routes/credential-routes.ts +26 -82
  493. package/src/runtime/routes/default-provider-routes.ts +15 -0
  494. package/src/runtime/routes/documents-routes.ts +3 -222
  495. package/src/runtime/routes/inbound-message-handler.ts +21 -45
  496. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
  497. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
  498. package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
  499. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
  500. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
  501. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -79
  502. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
  503. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -7
  504. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -8
  505. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -79
  506. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
  507. package/src/runtime/routes/inference-profiles-routes.ts +20 -11
  508. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
  509. package/src/runtime/routes/log-export-routes.ts +3 -0
  510. package/src/runtime/routes/mcp-auth-routes.ts +148 -57
  511. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
  512. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
  513. package/src/runtime/routes/playground/inject-failures.ts +2 -2
  514. package/src/runtime/routes/playground/reset-circuit.ts +1 -1
  515. package/src/runtime/routes/plugins-routes.ts +38 -8
  516. package/src/runtime/routes/schedule-routes.ts +94 -7
  517. package/src/runtime/routes/settings-routes.ts +6 -6
  518. package/src/runtime/routes/stt-routes.ts +31 -25
  519. package/src/runtime/routes/surface-action-routes.ts +20 -19
  520. package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
  521. package/src/runtime/routes/user-route-dispatcher.ts +69 -15
  522. package/src/runtime/routes/user-route-import.ts +108 -0
  523. package/src/runtime/routes/user-route-resolution.ts +18 -1
  524. package/src/runtime/routes/workspace-routes.ts +0 -9
  525. package/src/runtime/routes/workspace-utils.ts +3 -13
  526. package/src/runtime/services/conversation-serializer.ts +7 -2
  527. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
  528. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
  529. package/src/schedule/plugin-schedule-availability.ts +58 -0
  530. package/src/schedule/plugin-schedule-declarations.ts +23 -27
  531. package/src/schedule/plugin-schedule-reconciler.ts +12 -3
  532. package/src/schedule/schedule-store.ts +5 -1
  533. package/src/schedule/scheduler.ts +9 -4
  534. package/src/schedule/worker-control.ts +1 -0
  535. package/src/schedule/worker.ts +6 -0
  536. package/src/security/oauth2.ts +6 -22
  537. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
  538. package/src/stt/__tests__/types.test.ts +94 -0
  539. package/src/stt/daemon-batch-transcriber.ts +10 -0
  540. package/src/stt/stt-stream-session.ts +8 -4
  541. package/src/stt/types.ts +103 -0
  542. package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
  543. package/src/subagent/consult-context.ts +11 -11
  544. package/src/subagent/consult-prompt.ts +26 -35
  545. package/src/subagent/manager.ts +21 -40
  546. package/src/subagent/notify.ts +7 -1
  547. package/src/subagent/types.ts +15 -12
  548. package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
  549. package/src/tools/acp/spawn.test.ts +97 -0
  550. package/src/tools/acp/spawn.ts +38 -4
  551. package/src/tools/credentials/ref-parse.ts +35 -0
  552. package/src/tools/credentials/resolve.ts +4 -10
  553. package/src/tools/credentials/store.ts +168 -0
  554. package/src/tools/document/document-tool.ts +12 -3
  555. package/src/tools/executor.ts +53 -15
  556. package/src/tools/filesystem/read.ts +27 -10
  557. package/src/tools/host-filesystem/read.ts +27 -15
  558. package/src/tools/network/url-safety.ts +9 -16
  559. package/src/tools/permission-checker.ts +43 -52
  560. package/src/tools/registry.ts +2 -1
  561. package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
  562. package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
  563. package/src/tools/shared/filesystem/types.ts +5 -5
  564. package/src/tools/skills/scaffold-managed.ts +25 -7
  565. package/src/tools/subagent/spawn.ts +28 -88
  566. package/src/tools/tool-types.ts +26 -13
  567. package/src/tools/types.ts +6 -6
  568. package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
  569. package/src/tools/workflows/run-workflow.ts +1 -2
  570. package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
  571. package/src/tts/reasoning-tag-filter.ts +89 -0
  572. package/src/util/__tests__/worker-process-command.test.ts +37 -0
  573. package/src/util/logger.ts +16 -0
  574. package/src/util/think-tag-stream.ts +95 -0
  575. package/src/util/worker-process.ts +37 -4
  576. package/src/windows-compiled-cli.ts +32 -0
  577. package/src/windows-compiled-entry.ts +4 -0
  578. package/src/windows-compiled-logger.ts +6 -0
  579. package/src/windows-compiled-worker-entry.ts +29 -0
  580. package/src/workspace/byok-default-profile-ensure.ts +76 -24
  581. package/src/workspace/custom-profile-ensure.ts +4 -24
  582. package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
  583. package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
  584. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
  585. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
  586. package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
  587. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
  588. package/src/workspace/migrations/registry.ts +10 -0
  589. package/src/workspace/provider-commit-message-generator.ts +7 -5
  590. package/src/__tests__/document-workspace-file.test.ts +0 -467
  591. package/src/live-voice/__tests__/front-decision.test.ts +0 -645
  592. package/src/live-voice/front-decision.ts +0 -476
  593. package/src/permissions/ipc-risk-types.ts +0 -143
  594. package/src/permissions/risk-types.ts +0 -76
  595. package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
  596. package/src/subagent/consult-transcript.ts +0 -90
@@ -14,6 +14,7 @@ import type {
14
14
  ConversationStreamingMode,
15
15
  SttBoundaryId,
16
16
  SttProviderId,
17
+ SttTurnDetectionMode,
17
18
  TelephonySttMode,
18
19
  } from "../../stt/types.js";
19
20
  import { baseLanguageSubtag } from "../../util/language-subtag.js";
@@ -87,6 +88,14 @@ interface SttProviderEntry {
87
88
  */
88
89
  readonly conversationStreamingMode: ConversationStreamingMode;
89
90
 
91
+ /**
92
+ * Whether the provider decides end-of-turn itself, in-band with the audio
93
+ * it transcribes, or leaves the boundary to the session's local silence
94
+ * timer. A live-voice session reads this to decide whether to arm its
95
+ * provider turn-end path; it never names a provider directly.
96
+ */
97
+ readonly turnDetection: SttTurnDetectionMode;
98
+
90
99
  /**
91
100
  * Whether the provider can attribute transcribed speech to distinct
92
101
  * speakers (speaker diarization). When `true`, callers may opt in to
@@ -105,8 +114,9 @@ interface SttProviderEntry {
105
114
  *
106
115
  * - `"manual"`: the provider accepts an explicit language parameter and
107
116
  * defaults to English when omitted; a language picker is meaningful.
108
- * - `"auto"`: the provider detects language natively and accepts no
109
- * language parameter; a picker would be a no-op, so clients must hide it.
117
+ * - `"auto"`: the provider takes no language parameter, either because it
118
+ * detects the language natively or because its model is monolingual; a
119
+ * picker would be a no-op, so clients must hide it.
110
120
  */
111
121
  readonly languageSelection: "manual" | "auto";
112
122
 
@@ -118,6 +128,17 @@ interface SttProviderEntry {
118
128
  // Catalog data
119
129
  // ---------------------------------------------------------------------------
120
130
 
131
+ /**
132
+ * Shared by every provider that authenticates against a Deepgram account.
133
+ * The key is the same one, so the instructions must not drift apart.
134
+ */
135
+ const DEEPGRAM_CREDENTIALS_GUIDE: SttCredentialsGuide = {
136
+ description:
137
+ "Sign in to the Deepgram console, navigate to API Keys, and create a new key.",
138
+ url: "https://console.deepgram.com/",
139
+ linkLabel: "Open Deepgram Console",
140
+ };
141
+
121
142
  /**
122
143
  * Provider catalog entries, keyed by provider ID.
123
144
  *
@@ -147,14 +168,41 @@ const CATALOG: ReadonlyMap<SttProviderId, SttProviderEntry> = new Map<
147
168
  ]),
148
169
  telephonyMode: "realtime-ws",
149
170
  conversationStreamingMode: "realtime-ws",
171
+ turnDetection: "none",
150
172
  supportsDiarization: true,
151
173
  languageSelection: "manual",
152
- credentialsGuide: {
153
- description:
154
- "Sign in to the Deepgram console, navigate to API Keys, and create a new key.",
155
- url: "https://console.deepgram.com/",
156
- linkLabel: "Open Deepgram Console",
157
- },
174
+ credentialsGuide: DEEPGRAM_CREDENTIALS_GUIDE,
175
+ },
176
+ ],
177
+ [
178
+ "deepgram-flux",
179
+ {
180
+ id: "deepgram-flux",
181
+ displayName: "Deepgram Flux",
182
+ subtitle:
183
+ "Conversational speech-to-text with model-native turn detection. Uses your Deepgram API key.",
184
+ setupMode: "api-key",
185
+ setupHint:
186
+ "Enter your Deepgram API key. Flux shares the same key as Deepgram.",
187
+ // Shared with the `deepgram` provider: Flux is a model on the same
188
+ // account, not a separate credential.
189
+ credentialProvider: "deepgram",
190
+ // Streaming only: Flux has no batch endpoint.
191
+ supportedBoundaries: new Set<SttBoundaryId>(["daemon-streaming"]),
192
+ // Telephony is out of scope for the spike. Nothing reroutes a call to
193
+ // another provider, so a Flux-configured assistant does not transcribe
194
+ // calls at all.
195
+ telephonyMode: "none",
196
+ conversationStreamingMode: "realtime-ws",
197
+ // Flux emits turn lifecycle events on its transcript stream and numbers
198
+ // them, so a live-voice session may let them commit the turn.
199
+ turnDetection: "provider",
200
+ supportsDiarization: false,
201
+ // "no picker", not native detection: the Flux model is English-only and
202
+ // takes no language parameter, so audio in another language transcribes
203
+ // as English rather than being detected.
204
+ languageSelection: "auto",
205
+ credentialsGuide: DEEPGRAM_CREDENTIALS_GUIDE,
158
206
  },
159
207
  ],
160
208
  [
@@ -174,6 +222,7 @@ const CATALOG: ReadonlyMap<SttProviderId, SttProviderEntry> = new Map<
174
222
  ]),
175
223
  telephonyMode: "batch-only",
176
224
  conversationStreamingMode: "realtime-ws",
225
+ turnDetection: "none",
177
226
  supportsDiarization: false,
178
227
  languageSelection: "auto",
179
228
  credentialsGuide: {
@@ -200,6 +249,7 @@ const CATALOG: ReadonlyMap<SttProviderId, SttProviderEntry> = new Map<
200
249
  ]),
201
250
  telephonyMode: "batch-only",
202
251
  conversationStreamingMode: "incremental-batch",
252
+ turnDetection: "none",
203
253
  supportsDiarization: false,
204
254
  languageSelection: "auto",
205
255
  credentialsGuide: {
@@ -226,6 +276,7 @@ const CATALOG: ReadonlyMap<SttProviderId, SttProviderEntry> = new Map<
226
276
  ]),
227
277
  telephonyMode: "realtime-ws",
228
278
  conversationStreamingMode: "realtime-ws",
279
+ turnDetection: "none",
229
280
  supportsDiarization: false,
230
281
  // The relay dials Deepgram nova-3 server-side, which takes an explicit
231
282
  // language parameter.
@@ -248,6 +299,7 @@ const CATALOG: ReadonlyMap<SttProviderId, SttProviderEntry> = new Map<
248
299
  ]),
249
300
  telephonyMode: "batch-only",
250
301
  conversationStreamingMode: "realtime-ws",
302
+ turnDetection: "none",
251
303
  supportsDiarization: true,
252
304
  languageSelection: "manual",
253
305
  credentialsGuide: {
@@ -342,6 +394,32 @@ export function supportsBoundary(
342
394
  return CATALOG.get(id)?.supportedBoundaries.has(boundary) ?? false;
343
395
  }
344
396
 
397
+ /**
398
+ * Message explaining why a provider cannot serve the `daemon-batch` boundary.
399
+ *
400
+ * Streaming-only providers are selected through the same
401
+ * `services.stt.provider` key as batch-capable ones, so a batch caller that
402
+ * reports only "nothing is configured" points the operator at the wrong
403
+ * problem. This names the provider and, where one exists, the batch-capable
404
+ * provider on the same credential, so acting on it needs no catalog reading.
405
+ */
406
+ export function batchBoundaryGapReason(id: SttProviderId): string {
407
+ const entry = CATALOG.get(id);
408
+ const label = entry?.displayName ?? id;
409
+ const alternative = entry
410
+ ? [...CATALOG.values()].find(
411
+ (candidate) =>
412
+ candidate.id !== entry.id &&
413
+ candidate.credentialProvider === entry.credentialProvider &&
414
+ candidate.supportedBoundaries.has("daemon-batch"),
415
+ )
416
+ : undefined;
417
+ const remedy = alternative
418
+ ? `Batch transcription requires the ${alternative.id} provider: set services.stt.provider to "${alternative.id}".`
419
+ : "Set services.stt.provider to a provider that supports batch transcription.";
420
+ return `${label} is streaming-only. ${remedy}`;
421
+ }
422
+
345
423
  /**
346
424
  * Check whether a provider supports speaker diarization.
347
425
  *
@@ -353,6 +431,19 @@ export function supportsDiarization(id: SttProviderId): boolean {
353
431
  return CATALOG.get(id)?.supportsDiarization ?? false;
354
432
  }
355
433
 
434
+ /**
435
+ * Check whether a provider decides end-of-turn itself.
436
+ *
437
+ * Returns `false` for unknown provider IDs, which keeps an unrecognized
438
+ * provider on the local silence boundary rather than waiting for turn events
439
+ * that will never arrive. A live-voice session reads this instead of naming a
440
+ * provider, so a new turn-detecting provider is a catalog entry rather than a
441
+ * session change.
442
+ */
443
+ export function supportsProviderTurnDetection(id: SttProviderId): boolean {
444
+ return CATALOG.get(id)?.turnDetection === "provider";
445
+ }
446
+
356
447
  /**
357
448
  * Return all canonical provider IDs in deterministic (insertion) order.
358
449
  */
@@ -6,8 +6,10 @@ import type {
6
6
  StreamingTranscriber,
7
7
  SttProviderId,
8
8
  } from "../../stt/types.js";
9
+ import { SttError } from "../../stt/types.js";
9
10
  import { getLogger } from "../../util/logger.js";
10
11
  import {
12
+ batchBoundaryGapReason,
11
13
  getCredentialProvider,
12
14
  getProviderEntry,
13
15
  supportsBoundary,
@@ -88,8 +90,13 @@ export function effectiveSttLanguage(
88
90
  *
89
91
  * Returns `null` when:
90
92
  * - The configured provider is not in the catalog.
91
- * - The configured provider doesn't support the `daemon-batch` boundary.
92
93
  * - No credentials are configured for the resolved provider.
94
+ *
95
+ * Throws an {@link SttError} when the configured provider is in the catalog
96
+ * but has no `daemon-batch` boundary. That is a configuration mismatch the
97
+ * resolver can name, and returning `null` for it would reach the user as the
98
+ * "no speech-to-text provider is configured" copy every caller pairs with
99
+ * `null`, which is the opposite of what happened.
93
100
  */
94
101
  export async function resolveBatchTranscriber(): Promise<BatchTranscriber | null> {
95
102
  // Snapshot the stt config once, before any await, so a concurrent config
@@ -111,7 +118,9 @@ export async function resolveBatchTranscriber(): Promise<BatchTranscriber | null
111
118
 
112
119
  // Verify the provider supports the daemon-batch boundary.
113
120
  if (!supportsBoundary(provider as SttProviderId, "daemon-batch")) {
114
- return null;
121
+ const reason = batchBoundaryGapReason(provider as SttProviderId);
122
+ log.warn({ providerId: provider }, reason);
123
+ throw new SttError("provider-error", reason, { userFacing: true });
115
124
  }
116
125
 
117
126
  if (provider === "vellum") {
@@ -632,6 +641,20 @@ async function createStreamingTranscriber(
632
641
  : {}),
633
642
  });
634
643
  }
644
+ case "deepgram-flux": {
645
+ // Flux is streaming-only and dials Deepgram's /v2/listen conversational
646
+ // endpoint. Turn-detection tuning comes from `liveVoice.flux`, which
647
+ // the adapter reads itself, so only the transport-level sample rate is
648
+ // passed here. No language is forwarded: the spike pins the
649
+ // English-only `flux-general-en`, and `language_hint` means nothing to
650
+ // a monolingual model. Diarization is off in the catalog, so a
651
+ // `"required"` caller never reaches this case.
652
+ const { DeepgramFluxRealtimeTranscriber } =
653
+ await import("./deepgram-flux-realtime.js");
654
+ return new DeepgramFluxRealtimeTranscriber(apiKey, {
655
+ sampleRate: options.sampleRate,
656
+ });
657
+ }
635
658
  default: {
636
659
  const _exhaustive: never = providerId;
637
660
  return null;
@@ -68,6 +68,7 @@ export async function spawnRouteHostWorkerProcess(
68
68
  return await spawnWorkerProcess({
69
69
  pidPath: routeHostPidPath(),
70
70
  entry: new URL("./worker.ts", import.meta.url),
71
+ packagedEntry: "routes",
71
72
  workerLabel: "Route host",
72
73
  options: opts,
73
74
  });
@@ -149,6 +149,7 @@ export class RouteHostClient {
149
149
  ({ pid } = await spawnWorkerProcess({
150
150
  pidPath: this.pidPath,
151
151
  entry: this.workerEntryUrl,
152
+ packagedEntry: "routes",
152
153
  workerLabel: "Route host",
153
154
  // Owned by the daemon (appears in its process tree, torn down with it);
154
155
  // kill it if it hangs during startup so a failed spawn leaves nothing.
@@ -33,6 +33,13 @@ export interface RouteInvokeParams {
33
33
  readonly url: string;
34
34
  /** Request header entries as `[name, value]` pairs (preserves duplicates). */
35
35
  readonly headers: ReadonlyArray<readonly [string, string]>;
36
+ /**
37
+ * Manifest name of the plugin whose namespace the route falls in, absent for
38
+ * a workspace route. The host marks it as the plugin in context for the
39
+ * handler's execution, so plugin-scoped host APIs behave the same whether the
40
+ * handler runs in the host or in-thread on the daemon.
41
+ */
42
+ readonly pluginName?: string;
36
43
  }
37
44
 
38
45
  /**
@@ -24,7 +24,13 @@ import { createServer, type Server, type Socket } from "node:net";
24
24
  import type { IpcEnvelope } from "@vellumai/ipc-server-utils";
25
25
  import { IpcFrameReader, writeMessage } from "@vellumai/ipc-server-utils";
26
26
 
27
+ import { runInPluginContext } from "../plugins/plugin-execution-context.js";
27
28
  import { disableStreamSeqStamping } from "../runtime/assistant-stream-state.js";
29
+ import {
30
+ evictRouteSourceTree,
31
+ importRouteModule,
32
+ sourceRootForHandler,
33
+ } from "../runtime/routes/user-route-import.js";
28
34
  import { getLogger } from "../util/logger.js";
29
35
  import {
30
36
  ensureProcDir,
@@ -43,6 +49,9 @@ import {
43
49
 
44
50
  const log = getLogger("route-host");
45
51
 
52
+ /** Last entry-file mtime imported in this process, keyed by handler path. */
53
+ const lastHandlerMtime = new Map<string, number>();
54
+
46
55
  const socketPath = getProcSocketPath(ROUTE_HOST_PROC_NAME);
47
56
  const pidPath = getProcPidPath(ROUTE_HOST_PROC_NAME);
48
57
 
@@ -110,30 +119,54 @@ async function handleInvoke(
110
119
  params: RouteInvokeParams,
111
120
  body: Uint8Array | undefined,
112
121
  ): Promise<void> {
113
- // Each worker has its own module cache; cache-bust per mtime so an edited
114
- // handler is re-imported.
115
- const mod = (await import(
116
- `${params.filePath}?t=${params.mtimeMs}`
117
- )) as Record<string, unknown>;
118
-
119
- const handler = mod[params.method];
120
- if (typeof handler !== "function") {
121
- const allowed = HTTP_METHODS.filter((m) => typeof mod[m] === "function");
122
+ // Each worker has its own module cache. When the entry file's mtime
123
+ // moves, evict the whole source tree so a new handler cannot bind to a
124
+ // stale helper, then re-import.
125
+ const lastMtime = lastHandlerMtime.get(params.filePath);
126
+ if (lastMtime !== params.mtimeMs) {
127
+ evictRouteSourceTree(sourceRootForHandler(params.filePath));
128
+ lastHandlerMtime.set(params.filePath, params.mtimeMs);
129
+ }
130
+
131
+ // A plugin's own routes execute as that plugin, matching the daemon's
132
+ // in-thread path, so plugin-scoped host APIs a handler reaches
133
+ // (`resolveCredential`, `indexDocument`) scope to the owning plugin rather
134
+ // than falling through to their unscoped branch. The context covers the
135
+ // import as well as the call: a route module can reach a scoped API at
136
+ // evaluation time, and it is imported once per mtime.
137
+ const serve = async (): Promise<
138
+ { ok: true; response: Response } | { ok: false; allowed: string[] }
139
+ > => {
140
+ const mod = await importRouteModule(params.filePath);
141
+ const handler = mod[params.method];
142
+ if (typeof handler !== "function") {
143
+ return {
144
+ ok: false,
145
+ allowed: HTTP_METHODS.filter((m) => typeof mod[m] === "function"),
146
+ };
147
+ }
148
+ const request = reconstructRequest(params, body);
149
+ const response = (await (handler as (req: Request) => unknown)(
150
+ request,
151
+ )) as Response;
152
+ return { ok: true, response };
153
+ };
154
+ const outcome = await (params.pluginName
155
+ ? runInPluginContext(params.pluginName, serve)
156
+ : serve());
157
+
158
+ if (!outcome.ok) {
122
159
  replyResult(
123
160
  socket,
124
161
  id,
125
162
  405,
126
- allowed.length ? [["allow", allowed.join(", ")]] : [],
163
+ outcome.allowed.length ? [["allow", outcome.allowed.join(", ")]] : [],
127
164
  null,
128
165
  );
129
166
  return;
130
167
  }
131
168
 
132
- const request = reconstructRequest(params, body);
133
- const response = (await (handler as (req: Request) => unknown)(
134
- request,
135
- )) as Response;
136
-
169
+ const { response } = outcome;
137
170
  const buffer = new Uint8Array(await response.arrayBuffer());
138
171
  const headers: [string, string][] = [];
139
172
  response.headers.forEach((value, name) => {
@@ -93,39 +93,38 @@ Host app-control allows the assistant to proxy app-control actions (target a spe
93
93
  - `POST /v1/host-app-control-result` — `{ requestId, state, pngBase64?, windowBounds?, executionResult?, executionError? }`. `state` is one of `"running" | "missing" | "minimized"`.
94
94
  - **Tracking**: Uses the same `pending-interactions` tracker as the other host proxy types, with `kind: "host_app_control"`. The route handler is in `host-app-control-routes.ts` and forwards the payload to the owning conversation's `hostAppControlProxy.resolve()`. Late delivery is tolerated — the route returns 200 even when no pending interaction matches (e.g. the conversation was disposed before the client reported back).
95
95
 
96
- ### `chrome-extension` interface (Phase 2)
96
+ ### `chrome-extension` interface
97
97
 
98
98
  The `chrome-extension` interface in `INTERFACE_IDS` is a non-interactive transport that supports only the `host_browser` capability — it does NOT support `host_bash`, `host_file`, or `host_cu`. This is encoded in `supportsHostProxy(id, capability)`: passing a capability argument returns `true` for `chrome-extension` only when the capability is `host_browser`; the no-arg form returns `false` for `chrome-extension` (so legacy desktop-only call sites that assume full-desktop proxy availability continue to gate correctly).
99
99
 
100
- For **self-hosted** deployments, `host_browser_request` frames are routed through the `ChromeExtensionRegistry` singleton (`runtime/chrome-extension-registry.ts`), which tracks active chrome-extension WebSocket connections keyed by `(guardianId, clientInstanceId)`. The registry is populated on WebSocket `open` and drained on `close` inside `http-server.ts`'s `/v1/browser-relay` handlers — see the `wsType === "browser-relay"` branches. For **cloud/platform-hosted** deployments, the chrome extension connects via SSE (`GET /v1/events` with `X-Vellum-Interface-Id: chrome-extension`) and `host_browser_request` frames travel through `assistantEventHub` to the SSE stream. The extension POSTs results back to `POST /v1/host-browser-result`. Transport selection is handled by `HostBrowserProxy`, which publishes events to the `assistantEventHub` with `targetCapability: "host_browser"` — the hub delivers to whichever subscriber (chrome-extension or macOS client) has the `host_browser` capability. For macOS, `host_browser_request` frames travel through `assistantEventHub` (SSE) by default; when the guardian also has an active extension connection, the registry-routed WebSocket sender takes precedence.
100
+ The extension reaches the daemon over the same two doors every client uses. Self-hosted, it pairs once through the gateway's `POST /v1/pair` (`gateway/src/http/routes/pair.ts`: loopback-only, rate-limited, mints an `actor_client_v1` JWT for the `X-Vellum-Interface-Id: chrome-extension` caller); cloud deployments issue the guardian-bound JWT through the gateway's WorkOS-backed flow. It then subscribes to `GET /v1/events` with `X-Vellum-Interface-Id: chrome-extension`, which registers it on `assistantEventHub` with the single `host_browser` capability, and it answers work by POSTing to `POST /v1/host-browser-result`.
101
101
 
102
- A single guardian may have multiple parallel extension installs connected at once (two Chrome profiles, two desktops sharing a sync identity). Each install generates a stable `clientInstanceId` on first run, persists it in `chrome.storage.local`, and sends it on every WebSocket handshake as a query param (`clientInstanceId=...`) or header (`x-client-instance-id`). The registry keys inner entries by that id so sibling installs don't evict each other on register/unregister. The default `send(guardianId, msg)` path routes to whichever instance has the most recent activity (`lastActiveAt`); `sendToInstance(guardianId, clientInstanceId, msg)` pins a specific install. Older extension builds that omit the id get a connection-scoped `legacy:<connectionId>` fallback key so they degrade gracefully to single-instance semantics.
102
+ `HostBrowserProxy` (`daemon/host-browser-proxy.ts`, a lazily-created singleton) is the only sender: it publishes `host_browser_request` frames to the hub with `targetCapability: "host_browser"` and an explicit target client, choosing that client at send time from the hub's live roster (`resolveTargetClient`). Both the extension and the macOS desktop bridge register `host_browser`, so ordering is method-aware rather than pure recency: `Vellum.*` pseudo-methods (tabs, attach, detach) go only to a chrome-extension client, the only transport that implements them; raw CDP methods prefer a chrome-extension client over the macOS bridge, most recently active first within each group; an explicit `targetClientId` pins one client; and when a `sourceActorPrincipalId` is supplied only that actor's clients are eligible. Several extension installs for one guardian are simply several `host_browser` clients on the roster. A dispatch that lands during a brief MV3 service-worker reconnect waits up to `EXTENSION_RECONNECT_GRACE_MS` before failing.
103
103
 
104
- `Conversation.hostBrowserSenderOverride` is the integration point between the turn layer and the proxy. When any turn enters the routes layer and the guardian has an active extension connection in the `ChromeExtensionRegistry`, `conversation-routes.ts` resolves the registry entry and sets the override to a sender that writes to that WebSocket. This applies to chrome-extension turns (where the registry is the only transport) and macOS turns (where the extension connection lets browser tools route through the user's real Chrome session instead of cdp-inspect/local). `Conversation.restoreBrowserProxyAvailability()` re-threads the override on queue drain — without this, the drain path would clobber the registry-routed sender with the default `sendToClient` (pointed at the SSE hub) and `host_browser_request` frames would stop reaching the extension mid-queue.
104
+ Nothing in the turn layer wires the proxy: it reads the roster on every send, and the conversation's own event sink is fixed for its life (see `assistant/AGENTS.md`, "Conversation event delivery and turn presence"), so a queued or drained turn reaches the extension exactly like a live one.
105
105
 
106
- Capability token bootstrap for self-hosted deployments is handled by the gateway (`gateway/src/http/routes/browser-extension-pair.ts`) which mints a guardian-bound HMAC capability token. The daemon delegates token verification to the gateway via IPC (`verify_capability_token`) — it must never read secrets from `GATEWAY_SECURITY_DIR` or any other gateway-owned directory. Cloud deployments issue guardian-bound JWTs via the gateway's WorkOS-backed flow.
107
-
108
- See `docs/browser-use-architecture-phase2.md` for the full wire diagram and component inventory.
106
+ See `docs/browser-use-architecture-phase2.md` for the backend scenarios and the manual QA checklist.
109
107
 
110
108
  ### Canonical browser backend precedence (macOS)
111
109
 
112
110
  On macOS-originated turns, the CDP factory (`tools/browser/cdp-client/factory.ts`) evaluates three browser backends in strict priority order. Each candidate is tried lazily; if the first command fails with a transport-level error, the factory falls over to the next candidate. CDP protocol errors (the browser understood the command but rejected it) do NOT trigger failover.
113
111
 
114
- | Priority | Backend | Condition | Transport |
115
- | -------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
116
- | 1 | **Extension / host proxy** | `hostBrowserProxy` present AND `isAvailable()` returns `true`. On macOS, the proxy is always provisioned. On other interfaces, requires an active hub subscriber with `host_browser` capability | WS via `ChromeExtensionRegistry` (self-hosted), SSE via `assistantEventHub` with `targetCapability: "host_browser"` (cloud extension or macOS) |
117
- | 2 | **cdp-inspect** | (a) `hostBrowser.cdpInspect.enabled` is `true` in config, OR (b) `transportInterface === "macos"` AND `desktopAuto.enabled` is `true` (default) AND the cooldown from a prior failure is not active | Direct CDP WebSocket to `localhost:9222` |
118
- | 3 | **Local** | Always present as the final fallback | In-process Playwright CDP via `browserManager` |
112
+ | Priority | Backend | Condition | Transport |
113
+ | -------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
114
+ | 1 | **Extension / host proxy** | Two candidates from the always-present `HostBrowserProxy` singleton: `extension` when `hasExtensionClient(actor)` finds a chrome-extension client on the hub; otherwise `host-bridge` when `isAvailable(actor)` finds any `host_browser` client (the macOS desktop bridge) and that actor's host-bridge cooldown is not active | SSE via `assistantEventHub` with `targetCapability: "host_browser"`, to the chosen client |
115
+ | 2 | **cdp-inspect** | (a) `hostBrowser.cdpInspect.enabled` is `true` in config, OR (b) `transportInterface === "macos"` AND `desktopAuto.enabled` is `true` (default) AND the cooldown from a prior failure is not active | Direct CDP WebSocket to `localhost:9222` |
116
+ | 3 | **Local** | Always present as the final fallback | In-process Playwright CDP via `browserManager` |
119
117
 
120
118
  **Transport selection for the extension/host-proxy backend:**
121
119
 
122
- The "extension" backend label is a misnomer inherited from the original Phase 2 design where only the Chrome Extension provided host-browser access. In the current architecture, two transports can power this backend:
120
+ The "extension" backend label predates the macOS bridge; two SSE transports power it, both through the same `HostBrowserProxy` → `ExtensionCdpClient` pipeline:
121
+
122
+ - **Chrome extension**: `HostBrowserProxy.send()` publishes to `assistantEventHub` with `targetCapability: "host_browser"`, targeting the chrome-extension client `resolveTargetClient` chose (see the interface section above); the extension executes the CDP command via `chrome.debugger` and POSTs the result to `/v1/host-browser-result`.
123
+ - **macOS SSE bridge**: when no chrome-extension client is eligible, the same publish targets the macOS subscriber (which registers every host-proxy capability); the desktop client executes the command against the local Chrome and POSTs the result the same way.
123
124
 
124
- - **Extension WebSocket** (self-hosted): When the `ChromeExtensionRegistry` has an active entry for the guardian, the registry-routed sender delivers frames over the `/v1/browser-relay` WebSocket to the Chrome extension, which executes CDP commands via `chrome.debugger`.
125
- - **Extension SSE** (cloud/platform): When no WebSocket entry exists, `HostBrowserProxy.send()` publishes to `assistantEventHub` with `targetCapability: "host_browser"`. The hub delivers the event to the chrome-extension subscriber (which registered with that capability via SSE). The extension POSTs results back to `/v1/host-browser-result`. This path is used for any `canServiceSseBrowser()` interface (`web`, `chrome-extension`, `macos`).
126
- - **macOS SSE bridge**: When the macOS desktop client is connected but no extension is present, the same hub publish with `targetCapability: "host_browser"` delivers to the macOS subscriber (which has all host-proxy capabilities). The desktop client executes CDP commands against the local Chrome and POSTs results back to `/v1/host-browser-result`.
125
+ In the CDP factory the bridge is the internal `"host-bridge"` candidate kind (`InternalBrowserMode`, never a caller-pinnable `browser_mode`). `browser_status` labels the extension path `details.transport: "extension-ws"`; the label predates the SSE transport and is kept as-is.
127
126
 
128
- All three transports use the same `HostBrowserProxy` → `ExtensionCdpClient` pipeline. The `browser_status` output distinguishes the transport via the `details.transport` field: `"extension-ws"` or `"macos-sse"`.
127
+ **Host-bridge cooldown:** a `host-bridge` transport failure records a per-actor cooldown (`recordHostBridgeCooldown`, keyed by `sourceActorPrincipalId`, `__default__` when unresolved) for the same `desktopAuto.cooldownMs` window; while it is active the factory skips the bridge candidate (log `CDP factory: host-bridge skipped (cooldown active)`) and the turn drops straight to cdp-inspect/local. Per-actor because on a multi-actor cloud daemon the bridge reaches a different desktop per actor, so one actor's missing debug port must not suppress another's only route to their Chrome. Never applies to the `extension` candidate.
129
128
 
130
129
  **Fallback criteria for cdp-inspect (desktop-auto):**
131
130
 
@@ -29,6 +29,7 @@ import { buildVellumCardAffinity } from "../notifications/vellum-card-affinity.j
29
29
  import { IntegrityError } from "../util/errors.js";
30
30
  import { getLogger } from "../util/logger.js";
31
31
  import { resolveAnchoredGuardian } from "./anchored-guardian.js";
32
+ import { DAEMON_INTERNAL_ASSISTANT_ID } from "./assistant-scope.js";
32
33
  import { CHALLENGE_TTL_MS } from "./channel-verification-service.js";
33
34
  import {
34
35
  type AccessRequestTrigger,
@@ -46,7 +47,6 @@ const log = getLogger("access-request-helper");
46
47
  export type { AccessRequestTrigger } from "./introduction-policy.js";
47
48
 
48
49
  export interface AccessRequestParams {
49
- canonicalAssistantId: string;
50
50
  sourceChannel: ChannelId;
51
51
  conversationExternalId: string;
52
52
  actorExternalId?: string;
@@ -93,15 +93,18 @@ export type AccessRequestResult =
93
93
  // ---------------------------------------------------------------------------
94
94
 
95
95
  /**
96
- * Assistant-scoped conversation id for an actor's access requests. Stable key
97
- * that dedupes pending prompts for the same (assistant, channel, actor).
96
+ * Conversation id for an actor's access requests. Stable key that dedupes
97
+ * pending prompts for the same (channel, actor).
98
+ *
99
+ * The `self` segment is part of a persisted id format: guardian request rows
100
+ * carry these ids, so the shape is fixed even though the daemon scopes all its
101
+ * storage to the one assistant (`DAEMON_INTERNAL_ASSISTANT_ID`).
98
102
  */
99
103
  export function accessRequestConversationId(
100
- canonicalAssistantId: string,
101
104
  sourceChannel: string,
102
105
  actorExternalId: string,
103
106
  ): string {
104
- return `access-req-${canonicalAssistantId}-${sourceChannel}-${actorExternalId}`;
107
+ return `access-req-${DAEMON_INTERNAL_ASSISTANT_ID}-${sourceChannel}-${actorExternalId}`;
105
108
  }
106
109
 
107
110
  /**
@@ -126,7 +129,6 @@ export function accessRequestConversationId(
126
129
  * fail-closed.
127
130
  */
128
131
  export async function isApprovalHandshakeInProgress(params: {
129
- canonicalAssistantId: string;
130
132
  sourceChannel: string;
131
133
  actorExternalId: string;
132
134
  }): Promise<boolean> {
@@ -134,7 +136,6 @@ export async function isApprovalHandshakeInProgress(params: {
134
136
  return false;
135
137
  }
136
138
  const conversationId = accessRequestConversationId(
137
- params.canonicalAssistantId,
138
139
  params.sourceChannel,
139
140
  params.actorExternalId,
140
141
  );
@@ -171,7 +172,6 @@ export async function notifyGuardianOfAccessRequest(
171
172
  params: AccessRequestParams,
172
173
  ): Promise<AccessRequestResult> {
173
174
  const {
174
- canonicalAssistantId,
175
175
  sourceChannel,
176
176
  conversationExternalId,
177
177
  actorExternalId,
@@ -219,7 +219,6 @@ export async function notifyGuardianOfAccessRequest(
219
219
  // from assistant A could be returned for assistant B, allowing the caller
220
220
  // to piggyback on A's guardian approval.
221
221
  const conversationId = accessRequestConversationId(
222
- canonicalAssistantId,
223
222
  sourceChannel,
224
223
  actorExternalId,
225
224
  );
@@ -270,7 +269,6 @@ export async function notifyGuardianOfAccessRequest(
270
269
  // re-prompting is allowed again.
271
270
  if (
272
271
  await isApprovalHandshakeInProgress({
273
- canonicalAssistantId,
274
272
  sourceChannel,
275
273
  actorExternalId,
276
274
  })
@@ -283,7 +281,7 @@ export async function notifyGuardianOfAccessRequest(
283
281
  }
284
282
 
285
283
  const senderIdentifier = actorDisplayName || actorUsername || actorExternalId;
286
- const requestId = `access-req-${canonicalAssistantId}-${sourceChannel}-${actorExternalId}-${Date.now()}`;
284
+ const requestId = `access-req-${DAEMON_INTERNAL_ASSISTANT_ID}-${sourceChannel}-${actorExternalId}-${Date.now()}`;
287
285
 
288
286
  // Access requests are decisionable: without a bound principal nobody could
289
287
  // ever decide them (mirrors the gateway create's integrity guard).
@@ -461,7 +459,6 @@ export async function maybeNotifyGuardianOfAdmittedContact(
461
459
  }
462
460
 
463
461
  const conversationId = accessRequestConversationId(
464
- params.canonicalAssistantId,
465
462
  params.sourceChannel,
466
463
  params.actorExternalId,
467
464
  );
@@ -360,7 +360,7 @@ export interface WakeOptions {
360
360
  * the conversation belongs to. Used by fork-based memory retrospectives to
361
361
  * render the SOURCE conversation's persona sections — both for review
362
362
  * quality and for byte-parity with the source's cached system-prompt
363
- * prefix. May also pin `hasNoClient` for the prompt build (see
363
+ * prefix. May also pin presence for the prompt build (see
364
364
  * {@link SystemPromptPersonaOverride}). Prompt-build selection only; trust
365
365
  * class and approval semantics are governed solely by `trustContext`.
366
366
  */
@@ -400,14 +400,14 @@ export interface WakeOptions {
400
400
  */
401
401
  cronRunId?: string;
402
402
  /**
403
- * Run the woken turn clientless: pin `hasNoClient = true` for the duration of
404
- * the agent-loop run (restored after). Wakes bypass the orchestrator's
405
- * turn-start interactivity setup, so a wake on a conversation with no client
406
- * attached otherwise derives `isInteractive: true` (the default
407
- * `hasNoClient = false`). Pinning it makes `conversation-tool-setup` derive
408
- * `isInteractive: false`, which `policy-context` maps to `background`
409
- * (guardian) / `headless` (unknown) — so a side-effecting tool that would
410
- * prompt is denied instead of stalling on a client that isn't there.
403
+ * Run the woken turn clientless: pin the per-turn presence
404
+ * (`currentTurnIsNonInteractive = true`, which `hasNoClient` reads) for the
405
+ * duration of the agent-loop run (restored after). Wakes bypass the
406
+ * orchestrator's turn-start interactivity setup, so the pin is what makes
407
+ * `conversation-tool-setup` derive `isInteractive: false`, which
408
+ * `policy-context` maps to `background` (guardian) / `headless` (unknown),
409
+ * so a side-effecting tool that would prompt is denied instead of stalling
410
+ * on a client that isn't there.
411
411
  */
412
412
  clientless?: boolean;
413
413
  }
@@ -718,9 +718,9 @@ export async function wakeAgentForOpportunity(
718
718
  return runWakeSingleFlight(conversationId, async () => {
719
719
  // Snapshot the conversation's resting trust before the resolver runs, so
720
720
  // it can be restored after. The resolver leaves the wake's trust on the
721
- // conversation, and a following no-trust wake would otherwise read it via
722
- // tool setup's `currentTurnTrustContext ?? trustContext` fallback. Null
723
- // when the conversation isn't resident yet (a fresh hydrate or a fork).
721
+ // conversation, and a following no-trust wake would otherwise pick it up
722
+ // from the resting trust its consumers fall back to. Null when the
723
+ // conversation isn't resident yet (a fresh hydrate or a fork).
724
724
  let priorPersistentTrust: TrustContext | null = null;
725
725
  if (opts.trustContext) {
726
726
  const { findConversation } =
@@ -1454,7 +1454,8 @@ export async function wakeAgentForOpportunity(
1454
1454
  const priorCallSite = conversation.currentCallSite;
1455
1455
  const priorTurnOverrideProfile = conversation.currentTurnOverrideProfile;
1456
1456
  const priorTurnCronRunId = conversation.currentTurnCronRunId;
1457
- const priorHasNoClient = conversation.hasNoClient;
1457
+ const priorTurnIsNonInteractive =
1458
+ conversation.currentTurnIsNonInteractive;
1458
1459
  const priorTurnTrust = conversation.currentTurnTrustContext;
1459
1460
  conversation.currentCallSite = callSite;
1460
1461
  conversation.currentTurnOverrideProfile = overrideProfile;
@@ -1462,7 +1463,9 @@ export async function wakeAgentForOpportunity(
1462
1463
  // delegates work to subagents whose usage must attribute to that firing.
1463
1464
  conversation.currentTurnCronRunId = opts.cronRunId ?? null;
1464
1465
  if (opts.clientless) {
1465
- conversation.hasNoClient = true;
1466
+ // Presence is per-turn state; a clientless wake declares no human is
1467
+ // present for the duration of its dispatch.
1468
+ conversation.currentTurnIsNonInteractive = true;
1466
1469
  }
1467
1470
  // Per-turn guardian elevation for the wake's tools, set after the pre-run
1468
1471
  // reads so a pre-run failure can't leak it; restored in the finally.
@@ -1550,7 +1553,7 @@ export async function wakeAgentForOpportunity(
1550
1553
  conversation.currentCallSite = priorCallSite;
1551
1554
  conversation.currentTurnOverrideProfile = priorTurnOverrideProfile;
1552
1555
  conversation.currentTurnCronRunId = priorTurnCronRunId;
1553
- conversation.hasNoClient = priorHasNoClient;
1556
+ conversation.currentTurnIsNonInteractive = priorTurnIsNonInteractive;
1554
1557
  conversation.currentTurnTrustContext = priorTurnTrust;
1555
1558
  }
1556
1559