@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
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import {
4
4
  LiveVoiceConfigSchema,
5
+ LiveVoiceFluxConfigSchema,
5
6
  LiveVoiceFrontModelConfigSchema,
6
7
  LiveVoiceVadConfigSchema,
7
8
  VALID_LIVE_VOICE_MODES,
@@ -21,11 +22,18 @@ const FRONT_MODEL_DEFAULTS = {
21
22
  endpointDecisionTimeoutMs: 1200,
22
23
  endpointExtensionMs: 1500,
23
24
  endpointMaxExtensions: 2,
24
- ackFirstDeltaTimeoutMs: 2500,
25
- ackGenerationTimeoutMs: 600,
26
25
  progress: PROGRESS_DEFAULTS,
27
26
  };
28
27
 
28
+ // `eagerEotThreshold` is deliberately absent: it has no default, and leaving it
29
+ // unset is what keeps Deepgram from emitting speculative turn events.
30
+ const FLUX_DEFAULTS = {
31
+ turnEnd: { enabled: false },
32
+ model: "flux-general-en",
33
+ eotThreshold: 0.7,
34
+ eotTimeoutMs: 5_000,
35
+ };
36
+
29
37
  describe("LiveVoiceVadConfigSchema", () => {
30
38
  test("empty object parses to defaults", () => {
31
39
  const parsed = LiveVoiceVadConfigSchema.parse({});
@@ -124,8 +132,15 @@ describe("LiveVoiceFrontModelConfigSchema", () => {
124
132
  expect(parsed.endpointMaxExtensions).toBe(0);
125
133
  // Unspecified fields still get defaults
126
134
  expect(parsed.endpointExtensionMs).toBe(1500);
127
- expect(parsed.ackFirstDeltaTimeoutMs).toBe(2500);
128
- expect(parsed.ackGenerationTimeoutMs).toBe(600);
135
+ });
136
+
137
+ test("strips retired generated-ack settings", () => {
138
+ const parsed = LiveVoiceFrontModelConfigSchema.parse({
139
+ ackFirstDeltaTimeoutMs: 2500,
140
+ ackGenerationTimeoutMs: 600,
141
+ });
142
+ expect(parsed).not.toHaveProperty("ackFirstDeltaTimeoutMs");
143
+ expect(parsed).not.toHaveProperty("ackGenerationTimeoutMs");
129
144
  });
130
145
 
131
146
  test("rejects non-positive endpointDecisionTimeoutMs", () => {
@@ -213,6 +228,87 @@ describe("LiveVoiceFrontModelConfigSchema", () => {
213
228
  });
214
229
  });
215
230
 
231
+ describe("LiveVoiceFluxConfigSchema", () => {
232
+ test("empty object parses to defaults, with turn-end off", () => {
233
+ expect(LiveVoiceFluxConfigSchema.parse({})).toEqual(FLUX_DEFAULTS);
234
+ });
235
+
236
+ test("an unset eagerEotThreshold is absent, not zero", () => {
237
+ expect("eagerEotThreshold" in LiveVoiceFluxConfigSchema.parse({})).toBe(
238
+ false,
239
+ );
240
+ });
241
+
242
+ test("accepts overrides", () => {
243
+ const parsed = LiveVoiceFluxConfigSchema.parse({
244
+ turnEnd: { enabled: true },
245
+ model: "flux-general-multi",
246
+ eotThreshold: 0.85,
247
+ eagerEotThreshold: 0.45,
248
+ eotTimeoutMs: 12_000,
249
+ });
250
+ expect(parsed.turnEnd.enabled).toBe(true);
251
+ expect(parsed.model).toBe("flux-general-multi");
252
+ expect(parsed.eotThreshold).toBe(0.85);
253
+ expect(parsed.eagerEotThreshold).toBe(0.45);
254
+ expect(parsed.eotTimeoutMs).toBe(12_000);
255
+ });
256
+
257
+ test("partial overrides merge with defaults", () => {
258
+ const parsed = LiveVoiceFluxConfigSchema.parse({ eotThreshold: 0.6 });
259
+ expect(parsed.eotThreshold).toBe(0.6);
260
+ expect(parsed.turnEnd.enabled).toBe(false);
261
+ expect(parsed.model).toBe("flux-general-en");
262
+ expect(parsed.eotTimeoutMs).toBe(5_000);
263
+ });
264
+
265
+ test("rejects an eotThreshold outside 0.5..0.9", () => {
266
+ const above = LiveVoiceFluxConfigSchema.safeParse({ eotThreshold: 0.95 });
267
+ expect(above.success).toBe(false);
268
+ expect(above.error?.issues.map((i) => i.message)).toEqual([
269
+ "liveVoice.flux.eotThreshold must be <= 0.9",
270
+ ]);
271
+
272
+ const below = LiveVoiceFluxConfigSchema.safeParse({ eotThreshold: 0.4 });
273
+ expect(below.success).toBe(false);
274
+ expect(below.error?.issues.map((i) => i.message)).toEqual([
275
+ "liveVoice.flux.eotThreshold must be >= 0.5",
276
+ ]);
277
+ });
278
+
279
+ test("rejects an eagerEotThreshold outside 0.3..0.9", () => {
280
+ expect(
281
+ LiveVoiceFluxConfigSchema.safeParse({ eagerEotThreshold: 0.2 }).success,
282
+ ).toBe(false);
283
+ expect(
284
+ LiveVoiceFluxConfigSchema.safeParse({ eagerEotThreshold: 0.95 }).success,
285
+ ).toBe(false);
286
+ });
287
+
288
+ test("rejects an eotTimeoutMs outside 500..60000, and non-integers", () => {
289
+ expect(
290
+ LiveVoiceFluxConfigSchema.safeParse({ eotTimeoutMs: 499 }).success,
291
+ ).toBe(false);
292
+ expect(
293
+ LiveVoiceFluxConfigSchema.safeParse({ eotTimeoutMs: 60_001 }).success,
294
+ ).toBe(false);
295
+ expect(
296
+ LiveVoiceFluxConfigSchema.safeParse({ eotTimeoutMs: 1_500.5 }).success,
297
+ ).toBe(false);
298
+ });
299
+
300
+ test("rejects a non-boolean turnEnd.enabled", () => {
301
+ const result = LiveVoiceFluxConfigSchema.safeParse({
302
+ turnEnd: { enabled: "yes" },
303
+ });
304
+ expect(result.success).toBe(false);
305
+ const msgs = result.error?.issues.map((i) => i.message) ?? [];
306
+ expect(msgs.some((m) => m.includes("liveVoice.flux.turnEnd.enabled"))).toBe(
307
+ true,
308
+ );
309
+ });
310
+ });
311
+
216
312
  describe("LiveVoiceConfigSchema", () => {
217
313
  test("empty object parses to defaults", () => {
218
314
  const parsed = LiveVoiceConfigSchema.parse({});
@@ -228,6 +324,9 @@ describe("LiveVoiceConfigSchema", () => {
228
324
  echoDrainSlackMs: 300,
229
325
  },
230
326
  frontModel: FRONT_MODEL_DEFAULTS,
327
+ // Off by default: Flux turn detection is opt-in, so the front-door hold
328
+ // verdict keeps committing turns until it is enabled.
329
+ flux: FLUX_DEFAULTS,
231
330
  maxSessionDurationSeconds: 1800,
232
331
  // Off by default: voice turns carry only their transcript, no audio
233
332
  // artifacts on the conversation messages (JARVIS-1283).
@@ -255,6 +354,7 @@ describe("LiveVoiceConfigSchema", () => {
255
354
  mode: "ptt",
256
355
  vad: { silenceThresholdMs: 900 },
257
356
  frontModel: { endpointDecisionTimeoutMs: 300 },
357
+ flux: { turnEnd: { enabled: true } },
258
358
  maxSessionDurationSeconds: 600,
259
359
  });
260
360
  expect(parsed.mode).toBe("ptt");
@@ -265,6 +365,9 @@ describe("LiveVoiceConfigSchema", () => {
265
365
  // Partial frontModel overrides merge with defaults
266
366
  expect(parsed.frontModel.endpointDecisionTimeoutMs).toBe(300);
267
367
  expect(parsed.frontModel.endpointExtensionMs).toBe(1500);
368
+ // Partial flux overrides merge with defaults
369
+ expect(parsed.flux.turnEnd.enabled).toBe(true);
370
+ expect(parsed.flux.eotThreshold).toBe(0.7);
268
371
  expect(parsed.maxSessionDurationSeconds).toBe(600);
269
372
  });
270
373
 
@@ -289,11 +289,11 @@ const CATALOG_RECORD: CatalogRecord = {
289
289
  "Captions images via a vision-capable profile for text-only model fallback.",
290
290
  domain: "skills",
291
291
  },
292
- voiceFrontDecision: {
293
- id: "voiceFrontDecision",
294
- displayName: "Voice Front Decision",
292
+ voiceProgressNarration: {
293
+ id: "voiceProgressNarration",
294
+ displayName: "Voice Progress Narration",
295
295
  description:
296
- "Fast turn-taking and presence decisions during live voice (semantic endpointing, ack generation).",
296
+ "Phrases short spoken progress updates during long-running live voice turns.",
297
297
  domain: "agentLoop",
298
298
  },
299
299
  voiceFrontDoor: {
@@ -207,34 +207,64 @@ export const LiveVoiceFrontModelConfigSchema = z
207
207
  )
208
208
  .default(2)
209
209
  .describe("Cap on consecutive 'hold' extensions per utterance"),
210
- ackFirstDeltaTimeoutMs: z
211
- .number({
212
- error: "liveVoice.frontModel.ackFirstDeltaTimeoutMs must be a number",
213
- })
214
- .int("liveVoice.frontModel.ackFirstDeltaTimeoutMs must be an integer")
215
- .positive(
216
- "liveVoice.frontModel.ackFirstDeltaTimeoutMs must be a positive integer",
217
- )
218
- .default(2500)
219
- .describe(
220
- "Keyword-delay budget (ms): a spoken ack fires if no first assistant delta has arrived by then",
221
- ),
222
- ackGenerationTimeoutMs: z
223
- .number({
224
- error: "liveVoice.frontModel.ackGenerationTimeoutMs must be a number",
225
- })
226
- .int("liveVoice.frontModel.ackGenerationTimeoutMs must be an integer")
227
- .positive(
228
- "liveVoice.frontModel.ackGenerationTimeoutMs must be a positive integer",
229
- )
230
- .default(600)
231
- .describe("Budget (ms) for LLM-generated ack text"),
232
210
  progress: LiveVoiceProgressConfigSchema.default(
233
211
  LiveVoiceProgressConfigSchema.parse({}),
234
212
  ),
235
213
  })
236
214
  .describe(
237
- "Front-model presence layer tuning for live voice sessions (semantic endpointing + spoken acks + progress narration)",
215
+ "Voice front-door endpointing and long-turn progress narration tuning",
216
+ );
217
+
218
+ const LiveVoiceFluxTurnEndConfigSchema = z
219
+ .object({
220
+ enabled: z
221
+ .boolean({ error: "liveVoice.flux.turnEnd.enabled must be a boolean" })
222
+ .default(false)
223
+ .describe(
224
+ "Commit the live-voice turn on Flux's EndOfTurn instead of the front-door [0] hold verdict. Requires services.stt.provider to be deepgram-flux; ignored otherwise.",
225
+ ),
226
+ })
227
+ .describe(
228
+ "Which signal commits a live voice turn when Deepgram Flux is the STT provider",
229
+ );
230
+
231
+ export const LiveVoiceFluxConfigSchema = z
232
+ .object({
233
+ turnEnd: LiveVoiceFluxTurnEndConfigSchema.default(
234
+ LiveVoiceFluxTurnEndConfigSchema.parse({}),
235
+ ),
236
+ model: z
237
+ .string({ error: "liveVoice.flux.model must be a string" })
238
+ .default("flux-general-en")
239
+ .describe("Deepgram Flux model requested when opening the STT stream"),
240
+ eotThreshold: z
241
+ .number({ error: "liveVoice.flux.eotThreshold must be a number" })
242
+ .min(0.5, "liveVoice.flux.eotThreshold must be >= 0.5")
243
+ .max(0.9, "liveVoice.flux.eotThreshold must be <= 0.9")
244
+ .default(0.7)
245
+ .describe(
246
+ "End-of-turn confidence Flux must reach before it emits EndOfTurn. Lower values commit sooner and cut speakers off more often; higher values wait longer and add end-of-turn latency.",
247
+ ),
248
+ eagerEotThreshold: z
249
+ .number({ error: "liveVoice.flux.eagerEotThreshold must be a number" })
250
+ .min(0.3, "liveVoice.flux.eagerEotThreshold must be >= 0.3")
251
+ .max(0.9, "liveVoice.flux.eagerEotThreshold must be <= 0.9")
252
+ .optional()
253
+ .describe(
254
+ "Confidence at which Flux starts speculating that the turn has ended. Leaving it unset disables Deepgram's EagerEndOfTurn / TurnResumed events; enabling it raises LLM calls 50-70% because speculative turns that resume are thrown away.",
255
+ ),
256
+ eotTimeoutMs: z
257
+ .number({ error: "liveVoice.flux.eotTimeoutMs must be a number" })
258
+ .int("liveVoice.flux.eotTimeoutMs must be an integer")
259
+ .min(500, "liveVoice.flux.eotTimeoutMs must be >= 500")
260
+ .max(60_000, "liveVoice.flux.eotTimeoutMs must be <= 60000")
261
+ .default(5_000)
262
+ .describe(
263
+ "Silence (ms) after which Flux force-ends the turn even though its end-of-turn confidence never reached eotThreshold",
264
+ ),
265
+ })
266
+ .describe(
267
+ "Deepgram Flux turn-detection tuning for live voice sessions (model-integrated end-of-turn)",
238
268
  );
239
269
 
240
270
  export const LiveVoiceConfigSchema = z
@@ -251,6 +281,9 @@ export const LiveVoiceConfigSchema = z
251
281
  frontModel: LiveVoiceFrontModelConfigSchema.default(
252
282
  LiveVoiceFrontModelConfigSchema.parse({}),
253
283
  ),
284
+ flux: LiveVoiceFluxConfigSchema.default(
285
+ LiveVoiceFluxConfigSchema.parse({}),
286
+ ),
254
287
  maxSessionDurationSeconds: z
255
288
  .number({
256
289
  error: "liveVoice.maxSessionDurationSeconds must be a number",
@@ -280,3 +313,4 @@ export type LiveVoiceFrontModelConfig = z.infer<
280
313
  export type LiveVoiceProgressConfig = z.infer<
281
314
  typeof LiveVoiceProgressConfigSchema
282
315
  >;
316
+ export type LiveVoiceFluxConfig = z.infer<typeof LiveVoiceFluxConfigSchema>;
@@ -1,6 +1,9 @@
1
1
  import { z } from "zod";
2
2
 
3
- import { PROVIDERS_REQUIRING_BASE_URL_AND_MODELS } from "../../providers/inference/auth.js";
3
+ import {
4
+ PROVIDERS_REQUIRING_BASE_URL_AND_MODELS,
5
+ ROUTING_IDENTITY_PROVIDERS,
6
+ } from "../../providers/inference/auth.js";
4
7
  import { PROVIDER_CATALOG } from "../../providers/model-catalog.js";
5
8
  import { isCodexSubscriptionModel } from "../../providers/openai/codex-models.js";
6
9
  import {
@@ -25,32 +28,54 @@ import {
25
28
  // Provider enum
26
29
  // ---------------------------------------------------------------------------
27
30
 
28
- export const LLMProvider = z
29
- .enum([
30
- "anthropic",
31
- "openai",
32
- "gemini",
33
- "ollama",
34
- "fireworks",
35
- "openrouter",
36
- "vercel-ai-gateway",
37
- "openai-compatible",
38
- "minimax",
39
- "atlascloud",
40
- "together",
41
- "litellm",
42
- "baseten",
43
- "poolside",
44
- // Routing identities rather than adapters: "vellum" = the platform-managed
45
- // route (upstream derived from the model at dispatch), "chatgpt" = the
46
- // subscription route to OpenAI. Neither has a PROVIDER_CATALOG entry;
47
- // dispatch substitutes the real upstream before any adapter lookup.
48
- "vellum",
49
- "chatgpt",
50
- ])
51
- .meta({ id: "LLMProvider" });
31
+ /**
32
+ * The provider values the write surfaces accept today: adapter-backed
33
+ * catalog providers plus the two routing identities. The schema itself is
34
+ * an open string so a stored provider outside this list parses instead of
35
+ * stripping its profile; dispatch resolves such a label as a connection
36
+ * entry name and fails with an explainable resolution error when no row
37
+ * carries it. Write-time membership is enforced at the profiles route and
38
+ * the `commitConfigWrite` choke point (`unknownLlmProviderIssue`), which
39
+ * is where entry names unlock when the entries model enables them.
40
+ */
41
+ export const KNOWN_LLM_PROVIDERS = [
42
+ "anthropic",
43
+ "openai",
44
+ "gemini",
45
+ "ollama",
46
+ "fireworks",
47
+ "openrouter",
48
+ "vercel-ai-gateway",
49
+ "openai-compatible",
50
+ "minimax",
51
+ "atlascloud",
52
+ "together",
53
+ "litellm",
54
+ "baseten",
55
+ "poolside",
56
+ // Routing identities rather than adapters: "vellum" = the platform-managed
57
+ // route (upstream derived from the model at dispatch), "chatgpt" = the
58
+ // subscription route to OpenAI. Neither has a PROVIDER_CATALOG entry;
59
+ // dispatch substitutes the real upstream before any adapter lookup.
60
+ "vellum",
61
+ "chatgpt",
62
+ ] as const;
63
+
64
+ export const LLMProvider = z.string().min(1).meta({ id: "LLMProvider" });
52
65
  export type LLMProvider = z.infer<typeof LLMProvider>;
53
66
 
67
+ /**
68
+ * Write-surface membership check for a provider value. Returns a message
69
+ * when the value is outside {@link KNOWN_LLM_PROVIDERS}, null when it is
70
+ * allowed. Pure and sync so the profiles route and the config-write choke
71
+ * point share one rule.
72
+ */
73
+ export function unknownLlmProviderIssue(provider: string): string | null {
74
+ return (KNOWN_LLM_PROVIDERS as readonly string[]).includes(provider)
75
+ ? null
76
+ : `Invalid provider "${provider}". Valid providers: ${KNOWN_LLM_PROVIDERS.join(", ")}.`;
77
+ }
78
+
54
79
  /**
55
80
  * Providers that can back `llm.defaultProvider`: the named columns of the
56
81
  * default-profile matrix plus every API-key catalog provider whose personal
@@ -70,10 +95,10 @@ export const DEFAULT_PROVIDER_CHOICES: readonly LLMProvider[] = [
70
95
  entry.defaultModel !== "",
71
96
  )
72
97
  .map((entry) => entry.id)
73
- // A catalog provider absent from `LLMProvider` cannot be referenced by
74
- // any profile, so it cannot back the defaults either.
98
+ // A catalog provider outside the known provider set cannot be
99
+ // referenced by any profile, so it cannot back the defaults either.
75
100
  .filter((id): id is LLMProvider =>
76
- (LLMProvider.options as readonly string[]).includes(id),
101
+ (KNOWN_LLM_PROVIDERS as readonly string[]).includes(id),
77
102
  ),
78
103
  ]),
79
104
  ];
@@ -84,13 +109,15 @@ export function isDefaultProviderChoice(value: string): value is LLMProvider {
84
109
 
85
110
  /**
86
111
  * Default-provider choices whose profiles materialize from the shared BYOK
87
- * templates: every choice except the `vellum` routing identity, whose
88
- * defaults are pinned managed models.
112
+ * templates: every choice except the routing identities (`vellum`,
113
+ * `chatgpt`), whose defaults are pinned models on code-owned columns.
89
114
  */
90
115
  export function isByokDefaultProviderChoice(
91
116
  value: string,
92
117
  ): value is LLMProvider {
93
- return value !== "vellum" && isDefaultProviderChoice(value);
118
+ return (
119
+ !ROUTING_IDENTITY_PROVIDERS.has(value) && isDefaultProviderChoice(value)
120
+ );
94
121
  }
95
122
 
96
123
  const DefaultProviderEnum = z.enum(
@@ -188,7 +215,7 @@ export const LLMCallSiteEnum = z.enum([
188
215
  "skillCategoryInference",
189
216
  "inference",
190
217
  "vision",
191
- "voiceFrontDecision",
218
+ "voiceProgressNarration",
192
219
  "voiceFrontDoor",
193
220
  "trustRuleSuggestion",
194
221
  "homeGreeting",
@@ -109,3 +109,26 @@ export const McpConfigSchema = z
109
109
  export type McpTransport = z.infer<typeof McpTransportSchema>;
110
110
  export type McpServerConfig = z.infer<typeof McpServerConfigSchema>;
111
111
  export type McpConfig = z.infer<typeof McpConfigSchema>;
112
+
113
+ /**
114
+ * Who declared a server: the workspace `config.json`, which the user owns,
115
+ * or a plugin's `mcp.json`, which its author owns.
116
+ *
117
+ * Deliberately not a schema field. It is resolved from where the entry was
118
+ * read, never parsed from a file, so nothing on disk can claim to be
119
+ * workspace-owned. What it gates is credential access: only a workspace
120
+ * server resolves `mcp:<serverId>:*` from the credential store, because a
121
+ * plugin controls both its server key and its URL and would otherwise
122
+ * receive a workspace credential at an endpoint it chose.
123
+ */
124
+ export type McpServerSource = "workspace" | "plugin";
125
+
126
+ /** A server config with its origin resolved. */
127
+ export interface ResolvedMcpServerConfig extends McpServerConfig {
128
+ readonly source: McpServerSource;
129
+ }
130
+
131
+ /** The MCP config the daemon runs: both origins, every server attributed. */
132
+ export interface ResolvedMcpConfig extends Omit<McpConfig, "servers"> {
133
+ readonly servers: Record<string, ResolvedMcpServerConfig>;
134
+ }
@@ -5,11 +5,15 @@ import { z } from "zod";
5
5
  *
6
6
  * The resource monitor process runs a periodic sweep (see
7
7
  * `monitoring/plugin-auto-update.ts`) that moves every installed, enabled
8
- * plugin to its source's current revision. The sweep is off by default:
8
+ * marketplace plugin to the catalog's current pin. The sweep is off by default:
9
9
  * `mode: "manual"` means nothing upgrades unless a human runs
10
10
  * `assistant plugins upgrade <name>` (or the equivalent route), which is the
11
11
  * behavior every workspace had before this block existed. Setting
12
12
  * `mode: "auto"` opts the workspace into the hourly sweep.
13
+ *
14
+ * Opting in never covers plugins installed straight from a GitHub URL. Those
15
+ * track a mutable ref rather than a reviewed pin, so the sweep leaves them to
16
+ * an explicit `assistant plugins upgrade`.
13
17
  */
14
18
  export const PluginUpdatesConfigSchema = z
15
19
  .object({
@@ -19,7 +23,7 @@ export const PluginUpdatesConfigSchema = z
19
23
  })
20
24
  .default("manual")
21
25
  .describe(
22
- 'How installed plugins move to newer revisions. "manual" (default) never upgrades on its own — upgrades happen only when a human asks for one. "auto" lets the resource monitor upgrade every installed, enabled plugin on the `checkIntervalMs` cadence.',
26
+ 'How installed plugins move to newer revisions. "manual" (default) never upgrades on its own: upgrades happen only when a human asks for one. "auto" lets the resource monitor upgrade every installed, enabled marketplace plugin on the `checkIntervalMs` cadence. Plugins installed directly from a GitHub URL are never upgraded automatically, since they track a mutable ref instead of a reviewed pin.',
23
27
  ),
24
28
  strategy: z
25
29
  // Deliberately narrower than the full `PluginUpgradeStrategy` union: the
@@ -6,6 +6,7 @@ import { z } from "zod";
6
6
  */
7
7
  export const VALID_STT_PROVIDERS = [
8
8
  "deepgram",
9
+ "deepgram-flux",
9
10
  "google-gemini",
10
11
  "openai-whisper",
11
12
  "xai",
@@ -104,11 +104,18 @@ export interface SkillSummary {
104
104
  owner?: OwnerInfo;
105
105
  /** Parsed tool manifest metadata, if the skill has a valid TOOLS.json. */
106
106
  toolManifest?: SkillToolManifestMeta;
107
- /** IDs of child skills that this skill includes (metadata-only, not auto-activated). */
107
+ /**
108
+ * IDs of child skills this skill composes. `skill_load` inlines each child's
109
+ * body and projects its tools when the parent loads (tools/skills/load.ts);
110
+ * they play no part in which skills get selected for a turn.
111
+ */
108
112
  includes?: string[];
109
113
  /** Feature flag ID declared in frontmatter. Only skills with this field are subject to feature flag gating. */
110
114
  featureFlag?: string;
111
- /** Compact routing cues projected into <available_skills> XML to guide skill selection. */
115
+ /**
116
+ * Intent phrases rendered into the skill's capability card as "Use when: ..."
117
+ * (memory substrate/skill-content.ts), which is the text skill routing scores.
118
+ */
112
119
  activationHints?: string[];
113
120
  /** Conditions under which this skill should NOT be loaded. */
114
121
  avoidWhen?: string[];
@@ -102,6 +102,96 @@ export function compactAxTreeHistory(messages: Message[]): Message[] {
102
102
  });
103
103
  }
104
104
 
105
+ /**
106
+ * Full-wrapper matcher for the `<channel_capabilities>` block. Both ends are
107
+ * required so a user message that merely opens with the tag (someone pasting
108
+ * prompt markup into chat) is never mistaken for an injected block, the same
109
+ * discipline `stripUserTextBlocksByPrefix` uses in `strip-injections.ts`.
110
+ */
111
+ const CHANNEL_CAPABILITIES_OPEN = "<channel_capabilities>\n";
112
+ const CHANNEL_CAPABILITIES_CLOSE = "\n</channel_capabilities>";
113
+
114
+ /** Whether a text block is a complete `<channel_capabilities>…` block. */
115
+ function isChannelCapabilitiesBlock(text: string): boolean {
116
+ return (
117
+ text.startsWith(CHANNEL_CAPABILITIES_OPEN) &&
118
+ text.endsWith(CHANNEL_CAPABILITIES_CLOSE)
119
+ );
120
+ }
121
+
122
+ /**
123
+ * Drop `<channel_capabilities>` blocks that repeat the last one still standing,
124
+ * so a conversation carries one copy per distinct set of capabilities instead
125
+ * of one per turn.
126
+ *
127
+ * Runtime injection prepends this block to the turn-starting user message and
128
+ * then freezes it into history: live, because injection splices into the
129
+ * conversation's own message array, and across restarts, because `loadFromDb`
130
+ * rehydrates it from message metadata. Nothing ever removes it, so an N-turn
131
+ * channel conversation ships N copies. Unlike `<turn_context>`, which at least
132
+ * carries a fresh timestamp, this block is a pure function of the channel
133
+ * (`buildChannelCapabilityBlock`): every copy after the first is byte-identical
134
+ * to the one above it and tells the model nothing it has not already read.
135
+ *
136
+ * ## Why "same as the last retained copy" and not "not the current turn"
137
+ *
138
+ * Prompt caching only pays off when the bytes a provider marked in turn N are
139
+ * still at the same position in turn N+1, an invariant this repo enforces end
140
+ * to end in `prompt-cache-cross-turn-stability.test.ts`. Any rule of the form
141
+ * "keep it on the current turn, collapse it above" necessarily re-renders one
142
+ * message every turn (the turn that just ended), which is a full cache miss on
143
+ * the whole message prefix, every turn, for every conversation.
144
+ *
145
+ * Comparing against the last RETAINED copy avoids that entirely: whether a
146
+ * given occurrence survives depends only on the messages above it, never on
147
+ * how many turns come later. A message's rendering is therefore fixed the
148
+ * moment it is written, and the prefix stays byte-stable as the conversation
149
+ * grows. Capabilities that genuinely change mid-conversation (the same
150
+ * conversation resumed from a different client) still differ from the retained
151
+ * copy, so the new block is kept and the model sees the change.
152
+ *
153
+ * The cost is placement: the surviving copy sits at the top of the conversation
154
+ * rather than next to the newest message. It is the same bytes either way.
155
+ *
156
+ * Idempotent, like every other transform in this module: re-running it over an
157
+ * already-deduplicated history retains exactly the same occurrences.
158
+ */
159
+ export function dedupeChannelCapabilityBlocks(history: Message[]): Message[] {
160
+ let lastRetained: string | null = null;
161
+ let changed = false;
162
+
163
+ const next = history.map((message) => {
164
+ if (message.role !== "user") {
165
+ return message;
166
+ }
167
+
168
+ let messageChanged = false;
169
+ const content = message.content.filter((block) => {
170
+ if (block.type !== "text" || !isChannelCapabilitiesBlock(block.text)) {
171
+ return true;
172
+ }
173
+ if (block.text === lastRetained) {
174
+ messageChanged = true;
175
+ return false;
176
+ }
177
+ lastRetained = block.text;
178
+ return true;
179
+ });
180
+
181
+ // A user row is never only injections in practice, but guard anyway: an
182
+ // empty content array is not a message any provider will accept, and
183
+ // dropping the row would break tool_use/tool_result pairing and the
184
+ // row-to-history index mapping `summarizeUpToMessage` relies on.
185
+ if (!messageChanged || content.length === 0) {
186
+ return message;
187
+ }
188
+ changed = true;
189
+ return { ...message, content };
190
+ });
191
+
192
+ return changed ? next : history;
193
+ }
194
+
105
195
  /**
106
196
  * Index of the last user message carrying `tool_result` blocks — the
107
197
  * "current turn" boundary {@link stripOldMediaBlocks} keeps intact while
@@ -194,6 +284,9 @@ function stripOldMediaBlocks(history: Message[]): Message[] {
194
284
  * `web_search_tool_result` blocks to text summaries; Anthropic's opaque
195
285
  * `encrypted_content` tokens expire / are route-scoped, and replaying a stale
196
286
  * one is rejected with `Invalid encrypted_content in search_result block`.
287
+ * - {@link dedupeChannelCapabilityBlocks} keeps one `<channel_capabilities>`
288
+ * block per distinct set of capabilities instead of the one-per-turn history
289
+ * accumulates; every repeat is byte-identical to the copy above it.
197
290
  *
198
291
  * Transforms the outbound copy only — the durable history keeps the rich
199
292
  * originals and each send re-derives the sanitized projection (every transform
@@ -206,5 +299,7 @@ function stripOldMediaBlocks(history: Message[]): Message[] {
206
299
  export function preModelCallSanitize(history: Message[]): Message[] {
207
300
  const mediaStripped = stripOldMediaBlocks(history);
208
301
  const axCompacted = compactAxTreeHistory(mediaStripped);
209
- return stripHistoricalWebSearchResults(axCompacted).messages;
302
+ const webSearchStripped =
303
+ stripHistoricalWebSearchResults(axCompacted).messages;
304
+ return dedupeChannelCapabilityBlocks(webSearchStripped);
210
305
  }
@@ -158,7 +158,7 @@ function makeContext(overrides?: Partial<Conversation>): HarnessContext {
158
158
 
159
159
  const base = asConversation({
160
160
  conversationId: "origin-conv-id",
161
- sendToClient: (msg) => sent.push(msg),
161
+ emit: (msg) => sent.push(msg),
162
162
  pendingSurfaceActions: new Map<string, { surfaceType: SurfaceType }>(),
163
163
  lastSurfaceAction: new Map<
164
164
  string,