@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
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Tests for the `voice_picker` ui_show surface's "never blocks a turn"
3
+ * invariant.
4
+ *
5
+ * The card has no terminal action: it settles when the user picks a voice,
6
+ * with no button to click. `actions` and `await_action` are generic ui_show
7
+ * params though, so the model can attach them to any surface, and the web
8
+ * client latches `awaiting_user_input` on the presence of actions alone with
9
+ * nothing to clear it. The daemon resolver therefore strips both, and a
10
+ * pending picker never holds the one-interactive-surface lock.
11
+ */
12
+
13
+ import { describe, expect, test } from "bun:test";
14
+
15
+ import { UISurfaceShowEventSchema } from "../api/events/ui-surface-show.js";
16
+ import type { AssistantEvent } from "../api/index.js";
17
+ import type { Conversation } from "../daemon/conversation.js";
18
+ import {
19
+ createSurfaceMutex,
20
+ surfaceProxyResolver,
21
+ } from "../daemon/conversation-surfaces.js";
22
+ import type { SurfaceType } from "../daemon/message-protocol.js";
23
+ import { INTERACTIVE_SURFACE_TYPES } from "../daemon/message-protocol.js";
24
+ import { asConversation } from "./helpers/mock-conversation.js";
25
+
26
+ function makeContext(sent: AssistantEvent[] = []): Conversation {
27
+ return asConversation({
28
+ conversationId: "session-1",
29
+ emit: (msg) => sent.push(msg),
30
+ pendingSurfaceActions: new Map<string, { surfaceType: SurfaceType }>(),
31
+ lastSurfaceAction: new Map<
32
+ string,
33
+ { actionId: string; data?: Record<string, unknown> }
34
+ >(),
35
+ surfaceState: new Map(),
36
+ surfaceUndoStacks: new Map<string, string[]>(),
37
+ accumulatedSurfaceState: new Map<string, Record<string, unknown>>(),
38
+ surfaceActionRequestIds: new Set<string>(),
39
+ currentTurnSurfaces: [],
40
+ isProcessing: () => false,
41
+ enqueueMessage: () => ({ queued: false, requestId: "req-1" }),
42
+ getQueueDepth: () => 0,
43
+ processMessage: async () => "ok",
44
+ withSurface: createSurfaceMutex(),
45
+ });
46
+ }
47
+
48
+ function showEvent(sent: AssistantEvent[]) {
49
+ return UISurfaceShowEventSchema.parse(
50
+ sent.find((msg) => msg.type === "ui_surface_show"),
51
+ );
52
+ }
53
+
54
+ describe("voice_picker never blocks a turn", () => {
55
+ test("shows with an empty payload and awaits nothing", async () => {
56
+ const sent: AssistantEvent[] = [];
57
+ const ctx = makeContext(sent);
58
+
59
+ const result = await surfaceProxyResolver(ctx, "ui_show", {
60
+ surface_type: "voice_picker",
61
+ data: {},
62
+ });
63
+
64
+ expect(result.isError).toBe(false);
65
+ expect(result.yieldToUser).toBeUndefined();
66
+
67
+ const parsed = showEvent(sent);
68
+ expect(parsed.surfaceType).toBe("voice_picker");
69
+ expect(parsed.actions).toBeUndefined();
70
+ expect(ctx.pendingSurfaceActions.has(parsed.surfaceId)).toBe(false);
71
+ });
72
+
73
+ test("strips actions the model attached", async () => {
74
+ const sent: AssistantEvent[] = [];
75
+ const ctx = makeContext(sent);
76
+
77
+ const result = await surfaceProxyResolver(ctx, "ui_show", {
78
+ surface_type: "voice_picker",
79
+ data: {},
80
+ actions: [{ id: "done", label: "Done", style: "primary" }],
81
+ });
82
+
83
+ expect(result.isError).toBe(false);
84
+ expect(result.yieldToUser).toBeUndefined();
85
+
86
+ const parsed = showEvent(sent);
87
+ expect(parsed.actions).toBeUndefined();
88
+ // The persisted copy must match the emitted one, or a history reseed would
89
+ // resurrect the button the client blocks on.
90
+ expect(ctx.currentTurnSurfaces[0]?.actions).toBeUndefined();
91
+ expect(ctx.pendingSurfaceActions.has(parsed.surfaceId)).toBe(false);
92
+ });
93
+
94
+ test("ignores an explicit await_action", async () => {
95
+ const sent: AssistantEvent[] = [];
96
+ const ctx = makeContext(sent);
97
+
98
+ const result = await surfaceProxyResolver(ctx, "ui_show", {
99
+ surface_type: "voice_picker",
100
+ data: {},
101
+ await_action: true,
102
+ });
103
+
104
+ expect(result.isError).toBe(false);
105
+ expect(result.yieldToUser).toBeUndefined();
106
+ expect(result.content).not.toContain("awaiting_user_action");
107
+ expect(ctx.pendingSurfaceActions.size).toBe(0);
108
+ });
109
+
110
+ test("a pending picker does not hold the one-interactive-surface lock", async () => {
111
+ const ctx = makeContext();
112
+ ctx.pendingSurfaceActions.set("surface-picker", {
113
+ surfaceType: "voice_picker",
114
+ });
115
+
116
+ const result = await surfaceProxyResolver(ctx, "ui_show", {
117
+ surface_type: "choice",
118
+ data: { options: [{ id: "a", title: "Option A" }] },
119
+ });
120
+
121
+ expect(result.isError).toBe(false);
122
+ expect(result.content).not.toContain("already awaiting user input");
123
+ });
124
+
125
+ test("voice_picker is not an interactive surface type", () => {
126
+ expect(INTERACTIVE_SURFACE_TYPES).not.toContain("voice_picker");
127
+ });
128
+ });
@@ -19,7 +19,7 @@ import { asConversation } from "./helpers/mock-conversation.js";
19
19
  function makeContext(sent: AssistantEvent[] = []): Conversation {
20
20
  return asConversation({
21
21
  conversationId: "session-1",
22
- sendToClient: (msg) => sent.push(msg),
22
+ emit: (msg) => sent.push(msg),
23
23
  pendingSurfaceActions: new Map<string, { surfaceType: SurfaceType }>(),
24
24
  lastSurfaceAction: new Map<
25
25
  string,
@@ -23,11 +23,12 @@ import { tmpdir } from "node:os";
23
23
  import { join } from "node:path";
24
24
  import { beforeEach, describe, expect, test } from "bun:test";
25
25
 
26
+ import { getUserHooksFor } from "../hooks/registry.js";
26
27
  import {
27
28
  getCachedUserTools,
28
- getUserHooksFor,
29
29
  resetPluginCacheForTests,
30
30
  } from "../plugins/mtime-cache.js";
31
+ import { resetPluginRegistryForTests } from "../plugins/registry.js";
31
32
  import { loadUserPlugins } from "../plugins/user-loader.js";
32
33
 
33
34
  // Isolate every run under its own tempdir so parallel test files (and
@@ -71,6 +72,7 @@ function clearPluginsDir(): void {
71
72
  describe("user plugin loader", () => {
72
73
  beforeEach(() => {
73
74
  resetPluginCacheForTests();
75
+ resetPluginRegistryForTests();
74
76
  clearPluginsDir();
75
77
  });
76
78
 
@@ -18,9 +18,7 @@ mock.module("../permissions/checker.js", () => ({
18
18
  isDynamicSkillLoadInvocation: () => false,
19
19
  classifyRisk: async () => ({ level: "low" }),
20
20
  check: async () => ({ decision: "allow", reason: "allowed" }),
21
- generateAllowlistOptions: () => [],
22
21
  generateScopeOptions: () => [],
23
- getCachedAssessment: () => undefined,
24
22
  }));
25
23
 
26
24
  mock.module("../persistence/conversation-crud.js", () => ({
@@ -25,6 +25,7 @@ import {
25
25
  import { registerDefaultPluginInjectors } from "../plugins/defaults/index.js";
26
26
  import type { Message } from "../providers/types.js";
27
27
  import { getWorkspacePluginsDir } from "../util/platform.js";
28
+ import { asConversation } from "./helpers/mock-conversation.js";
28
29
 
29
30
  // The injector chain is registered by the daemon bootstrap in production; do
30
31
  // the same here so `applyRuntimeInjections` walks a non-empty chain.
@@ -151,15 +152,21 @@ describe("visible_app injection", () => {
151
152
  ];
152
153
 
153
154
  function seedConversation(currentTurnVisibleAppId: string | undefined): void {
154
- setConversation(CONVERSATION_ID, {
155
- conversationId: CONVERSATION_ID,
156
- workingDir: "/sandbox",
157
- workspaceTopLevelContext: "",
158
- workspaceTopLevelDirty: false,
159
- surfaceState: new Map(),
160
- currentTurnVisibleAppId,
161
- currentTurnTemporalSnapshot: { clientTimezone: null },
162
- } as never);
155
+ setConversation(
156
+ CONVERSATION_ID,
157
+ asConversation({
158
+ conversationId: CONVERSATION_ID,
159
+ workingDir: "/sandbox",
160
+ workspaceTopLevelContext: "",
161
+ workspaceTopLevelDirty: false,
162
+ surfaceState: new Map(),
163
+ currentTurnVisibleAppId,
164
+ currentTurnTemporalSnapshot: {
165
+ clientTimezone: null,
166
+ timeSinceLastMessage: null,
167
+ },
168
+ }),
169
+ );
163
170
  }
164
171
 
165
172
  afterEach(() => {
@@ -276,6 +276,46 @@ describe("voice_config_update — tts_voice_id (managed/vellum active)", () => {
276
276
  expect(result.content).not.toContain("broadcast");
277
277
  });
278
278
 
279
+ test("hints the inline picker for the next managed voice change", async () => {
280
+ makeVellumActive();
281
+
282
+ const result = await run(
283
+ { setting: "tts_voice_id", value: "aura-2-zeus-en" },
284
+ makeContext(),
285
+ );
286
+
287
+ expect(result.isError).toBe(false);
288
+ expect(result.content).toStartWith("Vellum managed voice updated to");
289
+ expect(result.content).toContain(
290
+ 'ui_show { surface_type: "voice_picker", data: {} }',
291
+ );
292
+ });
293
+
294
+ test("does not hint the picker when the voice is not managed", async () => {
295
+ writeConfig({ services: { tts: { provider: "elevenlabs" } } });
296
+ invalidateConfigCache();
297
+
298
+ const result = await run(
299
+ { setting: "tts_voice_id", value: "abc123" },
300
+ makeContext(),
301
+ );
302
+
303
+ expect(result.isError).toBe(false);
304
+ expect(result.content).not.toContain("voice_picker");
305
+ });
306
+
307
+ test("does not hint the picker for a non-voice setting", async () => {
308
+ makeVellumActive();
309
+
310
+ const result = await run(
311
+ { setting: "conversation_timeout", value: 30 },
312
+ makeContext(),
313
+ );
314
+
315
+ expect(result.isError).toBe(false);
316
+ expect(result.content).not.toContain("voice_picker");
317
+ });
318
+
279
319
  test("rejects a value with characters invalid for any voice id", async () => {
280
320
  makeVellumActive();
281
321
 
@@ -89,7 +89,8 @@ const CALL_SESSION_ID = "call-session-voice-grant-test";
89
89
 
90
90
  /**
91
91
  * Create a mock session that, when runAgentLoop is called, emits a
92
- * confirmation_request through the updateClient callback before completing.
92
+ * confirmation_request through the registered event observer before
93
+ * completing.
93
94
  */
94
95
  function createMockSession(opts?: {
95
96
  confirmationRequestId?: string;
@@ -118,8 +119,9 @@ function createMockSession(opts?: {
118
119
  currentRequestId: requestId,
119
120
  abort: () => {},
120
121
  persistUserMessage: async () => ({ id: "msg-1", deduplicated: false }),
121
- updateClient: (cb: (msg: AssistantEvent) => void, _reset?: boolean) => {
122
+ addEventObserver: (cb: (msg: AssistantEvent) => void) => {
122
123
  clientCallback = cb;
124
+ return () => {};
123
125
  },
124
126
  handleConfirmationResponse: (
125
127
  reqId: string,
@@ -138,7 +140,7 @@ function createMockSession(opts?: {
138
140
  _messageId: string,
139
141
  options?: { onEvent?: (msg: AssistantEvent) => void },
140
142
  ) => {
141
- // Emit a confirmation_request through the client callback
143
+ // Emit a confirmation_request through the event observer
142
144
  if (clientCallback) {
143
145
  clientCallback({
144
146
  type: "confirmation_request",
@@ -61,7 +61,7 @@ function makeStreamingSession(events: AssistantEvent[]): Conversation {
61
61
  setTurnChannelContext: () => {},
62
62
  setTurnInterfaceContext: () => {},
63
63
  setVoiceCallControlPrompt: () => {},
64
- updateClient: () => {},
64
+ addEventObserver: () => () => {},
65
65
  ensureActorScopedHistory: async () => {},
66
66
  runAgentLoop: async (
67
67
  _content: string,
@@ -116,7 +116,7 @@ function makePersistingStreamingSession(
116
116
  turnInterfaceContext = ctx;
117
117
  },
118
118
  setVoiceCallControlPrompt: () => {},
119
- updateClient: () => {},
119
+ addEventObserver: () => () => {},
120
120
  ensureActorScopedHistory: async () => {},
121
121
  runAgentLoop: async (
122
122
  _content: string,
@@ -254,7 +254,7 @@ describe("voice-session-bridge", () => {
254
254
  setTurnChannelContext: () => {},
255
255
  setTurnInterfaceContext: () => {},
256
256
  setVoiceCallControlPrompt: () => {},
257
- updateClient: () => {},
257
+ addEventObserver: () => () => {},
258
258
  ensureActorScopedHistory: async () => {},
259
259
  runAgentLoop: async () => {
260
260
  await new Promise((r) => setTimeout(r, 200));
@@ -321,6 +321,51 @@ describe("voice-session-bridge", () => {
321
321
  expect(capturedOptions?.callSite).toBe("callAgent");
322
322
  });
323
323
 
324
+ test("startVoiceTurn declares the turn interactive so approval prompts are raised for its observer", async () => {
325
+ // A caller is on the line. If the turn ran non-interactive the permission
326
+ // checker would deny an approval-gated tool before any prompt existed, and
327
+ // the bridge's approval observer (auto-resolve for a non-guardian caller, a
328
+ // real card for the guardian) would never get to decide. Presence is per
329
+ // turn, so the bridge has to say so.
330
+ const conversation = createConversation("voice bridge interactive test");
331
+ const events: AssistantEvent[] = [
332
+ { type: "message_complete", conversationId: conversation.id },
333
+ ];
334
+
335
+ let capturedOptions: Record<string, unknown> | undefined;
336
+ const session = {
337
+ ...makeStreamingSession(events),
338
+ runAgentLoop: async (
339
+ _content: string,
340
+ _messageId: string,
341
+ options?: Record<string, unknown>,
342
+ ) => {
343
+ capturedOptions = options;
344
+ const onEvent =
345
+ (options as { onEvent?: (msg: AssistantEvent) => void })?.onEvent ??
346
+ (() => {});
347
+ for (const event of events) {
348
+ onEvent(event);
349
+ }
350
+ },
351
+ } as unknown as Conversation;
352
+
353
+ injectDeps(() => session);
354
+
355
+ await startVoiceTurn({
356
+ conversationId: conversation.id,
357
+ content: "Hello",
358
+ isInbound: true,
359
+ onTextDelta: () => {},
360
+ onComplete: () => {},
361
+ onError: () => {},
362
+ });
363
+
364
+ await new Promise((r) => setTimeout(r, 50));
365
+
366
+ expect(capturedOptions?.isInteractive).toBe(true);
367
+ });
368
+
324
369
  test("external AbortSignal triggers turn abort", async () => {
325
370
  const conversation = createConversation("voice bridge signal test");
326
371
  let abortCalled = false;
@@ -339,7 +384,7 @@ describe("voice-session-bridge", () => {
339
384
  setTurnChannelContext: () => {},
340
385
  setTurnInterfaceContext: () => {},
341
386
  setVoiceCallControlPrompt: () => {},
342
- updateClient: () => {},
387
+ addEventObserver: () => () => {},
343
388
  ensureActorScopedHistory: async () => {},
344
389
  runAgentLoop: async () => {
345
390
  await new Promise((r) => setTimeout(r, 200));
@@ -704,13 +749,14 @@ describe("voice-session-bridge", () => {
704
749
  setTurnChannelContext: () => {},
705
750
  setTurnInterfaceContext: () => {},
706
751
  setVoiceCallControlPrompt: () => {},
707
- updateClient: (handler: (msg: AssistantEvent) => void) => {
752
+ addEventObserver: (handler: (msg: AssistantEvent) => void) => {
708
753
  clientHandler = handler;
754
+ return () => {};
709
755
  },
710
756
  ensureActorScopedHistory: async () => {},
711
757
  runAgentLoop: async () => {
712
- // Simulate the prompter emitting a confirmation_request via the
713
- // updateClient callback (this is how the real prompter works).
758
+ // Simulate the conversation emitting a confirmation_request to its
759
+ // event observer (this is how the real conversation fans out).
714
760
  clientHandler({
715
761
  type: "confirmation_request",
716
762
  requestId: "req-voice-1",
@@ -796,8 +842,9 @@ describe("voice-session-bridge", () => {
796
842
  setTurnChannelContext: () => {},
797
843
  setTurnInterfaceContext: () => {},
798
844
  setVoiceCallControlPrompt: () => {},
799
- updateClient: (handler: (msg: AssistantEvent) => void) => {
845
+ addEventObserver: (handler: (msg: AssistantEvent) => void) => {
800
846
  clientHandler = handler;
847
+ return () => {};
801
848
  },
802
849
  ensureActorScopedHistory: async () => {},
803
850
  runAgentLoop: async () => {
@@ -879,8 +926,9 @@ describe("voice-session-bridge", () => {
879
926
  setTurnChannelContext: () => {},
880
927
  setTurnInterfaceContext: () => {},
881
928
  setVoiceCallControlPrompt: () => {},
882
- updateClient: (handler: (msg: AssistantEvent) => void) => {
929
+ addEventObserver: (handler: (msg: AssistantEvent) => void) => {
883
930
  clientHandler = handler;
931
+ return () => {};
884
932
  },
885
933
  ensureActorScopedHistory: async () => {},
886
934
  runAgentLoop: async () => {
@@ -946,8 +994,9 @@ describe("voice-session-bridge", () => {
946
994
  setTurnChannelContext: () => {},
947
995
  setTurnInterfaceContext: () => {},
948
996
  setVoiceCallControlPrompt: () => {},
949
- updateClient: (handler: (msg: AssistantEvent) => void) => {
997
+ addEventObserver: (handler: (msg: AssistantEvent) => void) => {
950
998
  clientHandler = handler;
999
+ return () => {};
951
1000
  },
952
1001
  ensureActorScopedHistory: async () => {},
953
1002
  runAgentLoop: async () => {
@@ -1009,8 +1058,9 @@ describe("voice-session-bridge", () => {
1009
1058
  setTurnChannelContext: () => {},
1010
1059
  setTurnInterfaceContext: () => {},
1011
1060
  setVoiceCallControlPrompt: () => {},
1012
- updateClient: (handler: (msg: AssistantEvent) => void) => {
1061
+ addEventObserver: (handler: (msg: AssistantEvent) => void) => {
1013
1062
  clientHandler = handler;
1063
+ return () => {};
1014
1064
  },
1015
1065
  ensureActorScopedHistory: async () => {},
1016
1066
  runAgentLoop: async () => {
@@ -1061,13 +1111,20 @@ describe("voice-session-bridge", () => {
1061
1111
  // broadcast in voice-session-bridge.ts's confirmation_request branch. The
1062
1112
  // fake's handleConfirmationResponse mirrors production's synchronous
1063
1113
  // `interaction_resolved` broadcast so the wire order is observable through
1064
- // the event hub, which serializes publishes in call order.
1114
+ // the event hub, which serializes publishes in call order. Its `emit`
1115
+ // mirrors a real conversation's: the sink (the hub) delivers first, then
1116
+ // observers run, so the bridge's resolution lands after the request.
1065
1117
  function makeConfirmationOrderingSession(
1066
1118
  conversationId: string,
1067
1119
  requestId: string,
1068
1120
  ): Conversation {
1069
1121
  let clientHandler: (msg: AssistantEvent) => void = () => {};
1122
+ const emit = (msg: AssistantEvent) => {
1123
+ broadcastMessage(msg);
1124
+ clientHandler(msg);
1125
+ };
1070
1126
  return {
1127
+ emit,
1071
1128
  isProcessing: () => false,
1072
1129
  persistUserMessage: async () => ({
1073
1130
  id: "test-msg-id",
@@ -1080,12 +1137,13 @@ describe("voice-session-bridge", () => {
1080
1137
  setTurnChannelContext: () => {},
1081
1138
  setTurnInterfaceContext: () => {},
1082
1139
  setVoiceCallControlPrompt: () => {},
1083
- updateClient: (handler: (msg: AssistantEvent) => void) => {
1140
+ addEventObserver: (handler: (msg: AssistantEvent) => void) => {
1084
1141
  clientHandler = handler;
1142
+ return () => {};
1085
1143
  },
1086
1144
  ensureActorScopedHistory: async () => {},
1087
1145
  runAgentLoop: async () => {
1088
- clientHandler({
1146
+ emit({
1089
1147
  type: "confirmation_request",
1090
1148
  requestId,
1091
1149
  toolName: "host_bash",
@@ -1222,8 +1280,9 @@ describe("voice-session-bridge", () => {
1222
1280
  setTurnChannelContext: () => {},
1223
1281
  setTurnInterfaceContext: () => {},
1224
1282
  setVoiceCallControlPrompt: () => {},
1225
- updateClient: (handler: (msg: AssistantEvent) => void) => {
1283
+ addEventObserver: (handler: (msg: AssistantEvent) => void) => {
1226
1284
  clientHandler = handler;
1285
+ return () => {};
1227
1286
  },
1228
1287
  ensureActorScopedHistory: async () => {},
1229
1288
  runAgentLoop: async () => {
@@ -1290,7 +1349,7 @@ describe("voice-session-bridge", () => {
1290
1349
  setTurnChannelContext: () => {},
1291
1350
  setTurnInterfaceContext: () => {},
1292
1351
  setVoiceCallControlPrompt: () => {},
1293
- updateClient: () => {},
1352
+ addEventObserver: () => () => {},
1294
1353
  ensureActorScopedHistory: async () => {},
1295
1354
  runAgentLoop: async () => {},
1296
1355
  handleConfirmationResponse: () => {},
@@ -1349,7 +1408,7 @@ describe("voice-session-bridge", () => {
1349
1408
  setTurnChannelContext: recordLast("setTurnChannelContext"),
1350
1409
  setTurnInterfaceContext: recordLast("setTurnInterfaceContext"),
1351
1410
  setVoiceCallControlPrompt: recordLast("setVoiceCallControlPrompt"),
1352
- updateClient: () => {},
1411
+ addEventObserver: () => () => {},
1353
1412
  ensureActorScopedHistory: async () => {},
1354
1413
  runAgentLoop: async () => {},
1355
1414
  handleConfirmationResponse: () => {},
@@ -1391,15 +1450,12 @@ describe("voice-session-bridge", () => {
1391
1450
  expect(session.forcePromptSideEffects).toBe(false);
1392
1451
  });
1393
1452
 
1394
- test("cleanup on early persistUserMessage throw does not detach prior client sender", async () => {
1453
+ test("cleanup on early persistUserMessage throw does not detach a prior turn's observer", async () => {
1395
1454
  const conversation = createConversation(
1396
1455
  "voice bridge sender detach guard test",
1397
1456
  );
1398
1457
 
1399
- const updateClientCalls: Array<{
1400
- callback: unknown;
1401
- replace: boolean | undefined;
1402
- }> = [];
1458
+ const addEventObserverCalls: unknown[] = [];
1403
1459
 
1404
1460
  const session = {
1405
1461
  isProcessing: () => false,
@@ -1415,8 +1471,9 @@ describe("voice-session-bridge", () => {
1415
1471
  setTurnChannelContext: () => {},
1416
1472
  setTurnInterfaceContext: () => {},
1417
1473
  setVoiceCallControlPrompt: () => {},
1418
- updateClient: (callback: unknown, replace?: boolean) => {
1419
- updateClientCalls.push({ callback, replace });
1474
+ addEventObserver: (observer: unknown) => {
1475
+ addEventObserverCalls.push(observer);
1476
+ return () => {};
1420
1477
  },
1421
1478
  ensureActorScopedHistory: async () => {},
1422
1479
  runAgentLoop: async () => {},
@@ -1451,10 +1508,9 @@ describe("voice-session-bridge", () => {
1451
1508
  expect(caught?.message).toBe(
1452
1509
  "persist failed before bridge installed callback",
1453
1510
  );
1454
- // The bridge never reached its `conversation.updateClient(...)` install
1455
- // site, so cleanup must not touch updateClient — otherwise it would
1456
- // detach a sender installed by a prior turn on the same conversation.
1457
- expect(updateClientCalls).toEqual([]);
1511
+ // The bridge never reached its `conversation.addEventObserver(...)`
1512
+ // install site, so no observer was registered for cleanup to detach.
1513
+ expect(addEventObserverCalls).toEqual([]);
1458
1514
  });
1459
1515
 
1460
1516
  test("pre-aborted signal triggers immediate abort", async () => {
@@ -1475,7 +1531,7 @@ describe("voice-session-bridge", () => {
1475
1531
  setTurnChannelContext: () => {},
1476
1532
  setTurnInterfaceContext: () => {},
1477
1533
  setVoiceCallControlPrompt: () => {},
1478
- updateClient: () => {},
1534
+ addEventObserver: () => () => {},
1479
1535
  ensureActorScopedHistory: async () => {},
1480
1536
  runAgentLoop: async () => {
1481
1537
  await new Promise((r) => setTimeout(r, 200));
@@ -18,10 +18,18 @@ import { Glob } from "bun";
18
18
  * - start the PID-file identity guard — the PID file is a worker's sole
19
19
  * tracking handle, so a worker the file stops naming can never be
20
20
  * stopped externally and must evict itself.
21
+ *
22
+ * The subset that hosts real agent conversations
23
+ * ({@link CONVERSATION_RUNNING_ENTRYPOINTS}) must additionally register the
24
+ * default plugins' hooks and injectors, since hook dispatch is process-global
25
+ * and a worker that skips it runs conversations with no plugin behavior at all
26
+ * (no image captioning or vision-rejection recovery, no tool-result
27
+ * truncation, no runtime injections).
21
28
  */
22
29
 
23
30
  const DISABLE_CALL = "disableStreamSeqStamping()";
24
31
  const PID_GUARD_CALL = "startWorkerPidFileGuard(";
32
+ const PLUGIN_SURFACE_CALL = "registerWorkerPluginSurface(";
25
33
 
26
34
  /** Shape of a shutdown that schedules `process.exit` instead of calling it. */
27
35
  const DEFERRED_EXIT = ".finally(() => process.exit";
@@ -44,6 +52,16 @@ const WORK_START_MARKERS: Record<string, string> = {
44
52
  "routes/worker.ts": 'server.on("connection"',
45
53
  };
46
54
 
55
+ /**
56
+ * The entrypoints whose work wakes real agent conversations. `monitoring` (a
57
+ * resource sampler) and `routes` (an HTTP route host) run no conversations, so
58
+ * they carry no plugin surface.
59
+ */
60
+ const CONVERSATION_RUNNING_ENTRYPOINTS: ReadonlySet<string> = new Set([
61
+ "schedule/worker.ts",
62
+ "plugins/defaults/memory/worker.ts",
63
+ ]);
64
+
47
65
  function findWorkerEntrypoints(): string[] {
48
66
  const srcRoot = join(process.cwd(), "src");
49
67
  const glob = new Glob("**/worker.ts");
@@ -137,4 +155,40 @@ describe("worker entrypoint guards", () => {
137
155
  "worker superseded at startup before it runs orphaned work.",
138
156
  ).toEqual([]);
139
157
  });
158
+
159
+ test("the conversation-running entrypoints are all discovered workers", () => {
160
+ const discovered = new Set(findWorkerEntrypoints());
161
+ const unknown = [...CONVERSATION_RUNNING_ENTRYPOINTS].filter(
162
+ (file) => !discovered.has(file),
163
+ );
164
+ expect(
165
+ unknown,
166
+ "CONVERSATION_RUNNING_ENTRYPOINTS names an entrypoint the glob does " +
167
+ "not find — the worker moved or was renamed.",
168
+ ).toEqual([]);
169
+ });
170
+
171
+ test("every conversation-running entrypoint registers the default plugin surface before starting work", () => {
172
+ const offenders = [...CONVERSATION_RUNNING_ENTRYPOINTS]
173
+ .sort()
174
+ .filter((file) => {
175
+ const source = readFileSync(join(process.cwd(), "src", file), "utf8");
176
+ const marker = WORK_START_MARKERS[file];
177
+ if (marker == null) {
178
+ return true;
179
+ }
180
+ const registerAt = source.indexOf(PLUGIN_SURFACE_CALL);
181
+ const workAt = source.indexOf(marker);
182
+ return registerAt < 0 || workAt < 0 || registerAt > workAt;
183
+ });
184
+ expect(
185
+ offenders,
186
+ `A worker that wakes agent conversations must call ` +
187
+ `${PLUGIN_SURFACE_CALL}) before its work-start call (see ` +
188
+ "WORK_START_MARKERS). Hook dispatch is process-global, so without it " +
189
+ "the conversations this process runs get no default plugin behavior: " +
190
+ "no vision-rejection recovery, no tool-result truncation, no runtime " +
191
+ "injections.",
192
+ ).toEqual([]);
193
+ });
140
194
  });