@vellumai/assistant 0.11.3 → 0.11.4-staging.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/ARCHITECTURE.md +11 -6
  2. package/docs/architecture/memory.md +11 -0
  3. package/docs/architecture/turn-actor.md +70 -0
  4. package/docs/flux-turn-detection-spike.md +243 -0
  5. package/docs/stt-provider-onboarding.md +3 -1
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  8. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
  9. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  10. package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  11. package/openapi.yaml +140 -38
  12. package/package.json +1 -1
  13. package/scripts/voice-ttft-spike.ts +3 -3
  14. package/src/__tests__/app-compiler.test.ts +38 -3
  15. package/src/__tests__/attachments-store.test.ts +21 -12
  16. package/src/__tests__/byok-default-profile-ensure.test.ts +2 -0
  17. package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
  18. package/src/__tests__/call-site-routing-provider.test.ts +1 -1
  19. package/src/__tests__/channel-availability-routes.test.ts +14 -1
  20. package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
  21. package/src/__tests__/channel-delivery-store.test.ts +14 -14
  22. package/src/__tests__/config-loader-backfill.test.ts +3 -3
  23. package/src/__tests__/config-schema.test.ts +25 -10
  24. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
  25. package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
  26. package/src/__tests__/conversation-agent-loop.test.ts +28 -20
  27. package/src/__tests__/conversation-attention-store.test.ts +63 -0
  28. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
  29. package/src/__tests__/conversation-fork-crud.test.ts +69 -0
  30. package/src/__tests__/conversation-fork-referential.test.ts +67 -0
  31. package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
  32. package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
  33. package/src/__tests__/conversation-queue.test.ts +55 -4
  34. package/src/__tests__/conversation-runtime-assembly.test.ts +134 -102
  35. package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
  36. package/src/__tests__/credential-prompt-route.test.ts +7 -10
  37. package/src/__tests__/custom-profile-ensure.test.ts +5 -1
  38. package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
  39. package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
  40. package/src/__tests__/document-append-idempotency.test.ts +233 -0
  41. package/src/__tests__/edit-propagation.test.ts +0 -7
  42. package/src/__tests__/helpers/mock-actor-context.ts +49 -0
  43. package/src/__tests__/helpers/mock-conversation.ts +13 -1
  44. package/src/__tests__/injector-chain.test.ts +63 -41
  45. package/src/__tests__/injector-disk-pressure.test.ts +11 -23
  46. package/src/__tests__/llm-context-resolution.test.ts +73 -1
  47. package/src/__tests__/llm-schema.test.ts +5 -2
  48. package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
  49. package/src/__tests__/memory-retrieval-hook.test.ts +6 -5
  50. package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
  51. package/src/__tests__/mtime-cache.test.ts +1 -1
  52. package/src/__tests__/non-member-access-request.test.ts +0 -20
  53. package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
  54. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -0
  55. package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
  56. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
  57. package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
  58. package/src/__tests__/run-conversation-turn-persistence.test.ts +434 -105
  59. package/src/__tests__/scoped-approval-grants.test.ts +11 -6
  60. package/src/__tests__/secret-ingress-channel.test.ts +0 -1
  61. package/src/__tests__/skills.test.ts +32 -0
  62. package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
  63. package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
  64. package/src/__tests__/subagent-spawn-and-await.test.ts +14 -10
  65. package/src/__tests__/turn-events-store.test.ts +43 -0
  66. package/src/__tests__/user-plugin-loader.test.ts +1 -1
  67. package/src/__tests__/visible-app-context.test.ts +16 -9
  68. package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
  69. package/src/__tests__/worker-plugin-surface.test.ts +77 -0
  70. package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
  71. package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
  72. package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
  73. package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
  74. package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
  75. package/src/acp/__tests__/auth-required.test.ts +161 -0
  76. package/src/acp/acp-claude-oauth.ts +19 -2
  77. package/src/acp/agent-process.test.ts +100 -0
  78. package/src/acp/agent-process.ts +29 -26
  79. package/src/acp/auth-required.ts +102 -0
  80. package/src/acp/session-manager.test.ts +119 -0
  81. package/src/acp/session-manager.ts +68 -2
  82. package/src/api/events/acp-auth-required.ts +55 -0
  83. package/src/api/index.ts +7 -0
  84. package/src/apps/app-store.ts +3 -0
  85. package/src/bundler/package-resolver.ts +2 -30
  86. package/src/calls/__tests__/voice-session-bridge.test.ts +173 -1
  87. package/src/calls/__tests__/voice-triage-escalate.test.ts +94 -0
  88. package/src/calls/call-controller.ts +9 -2
  89. package/src/calls/call-setup-flow.ts +0 -1
  90. package/src/calls/media-stream-stt-session.ts +15 -0
  91. package/src/calls/voice-session-bridge.ts +71 -16
  92. package/src/calls/voice-triage-escalate.ts +104 -2
  93. package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
  94. package/src/channels/config.ts +13 -0
  95. package/src/channels/plugin-channel-declarations.ts +108 -0
  96. package/src/channels/types.ts +30 -0
  97. package/src/cli/AGENTS.md +5 -2
  98. package/src/cli/commands/credentials.help.ts +2 -2
  99. package/src/cli/commands/inference-providers.ts +1 -1
  100. package/src/cli/commands/mcp.help.ts +13 -4
  101. package/src/cli/commands/mcp.ts +9 -0
  102. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
  103. package/src/cli/commands/memory/index.help.ts +43 -1
  104. package/src/cli/commands/memory/memory-v3.ts +64 -0
  105. package/src/cli/commands/stt.help.ts +27 -2
  106. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
  107. package/src/cli/lib/bundled-marketplace.json +13 -0
  108. package/src/cli/lib/upgrade-plugin.ts +42 -0
  109. package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
  110. package/src/config/__tests__/default-provider.test.ts +6 -1
  111. package/src/config/__tests__/profile-materialization.test.ts +75 -19
  112. package/src/config/bundled-skills/acp/SKILL.md +6 -7
  113. package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
  114. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  115. package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
  116. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  117. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
  118. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
  119. package/src/config/call-site-defaults.ts +4 -5
  120. package/src/config/default-profile-catalog.ts +82 -11
  121. package/src/config/default-profile-names.ts +4 -1
  122. package/src/config/default-provider-resolution.ts +4 -0
  123. package/src/config/llm-context-resolution.ts +11 -3
  124. package/src/config/llm-resolver.ts +28 -1
  125. package/src/config/profile-materialization.ts +70 -22
  126. package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
  127. package/src/config/schemas/call-site-catalog.ts +4 -4
  128. package/src/config/schemas/live-voice.ts +57 -23
  129. package/src/config/schemas/llm.ts +59 -32
  130. package/src/config/schemas/mcp.ts +23 -0
  131. package/src/config/schemas/plugin-updates.ts +6 -2
  132. package/src/config/schemas/stt.ts +1 -0
  133. package/src/context/outbound-sanitize.ts +96 -1
  134. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
  135. package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
  136. package/src/daemon/conversation-agent-loop.ts +17 -6
  137. package/src/daemon/conversation-messaging.ts +5 -1
  138. package/src/daemon/conversation-notifiers.ts +9 -1
  139. package/src/daemon/conversation-process.ts +9 -6
  140. package/src/daemon/conversation-runtime-assembly.ts +3 -4
  141. package/src/daemon/conversation-tool-setup.ts +1 -2
  142. package/src/daemon/conversation.ts +48 -0
  143. package/src/daemon/mcp-reload-service.ts +36 -6
  144. package/src/daemon/process-message.ts +13 -3
  145. package/src/daemon/providers-setup.ts +6 -3
  146. package/src/daemon/trust-context-types.ts +29 -0
  147. package/src/daemon/wake-conversation-ops.ts +3 -2
  148. package/src/documents/document-store.ts +138 -5
  149. package/src/hooks/hook-loader.ts +3 -3
  150. package/src/hooks/registry.ts +50 -6
  151. package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
  152. package/src/inbound/oauth-callback-url.ts +61 -0
  153. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
  154. package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
  155. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +932 -0
  156. package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
  157. package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
  158. package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
  159. package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
  160. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
  161. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
  162. package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
  163. package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
  164. package/src/live-voice/live-voice-archive.ts +2 -0
  165. package/src/live-voice/live-voice-metrics.ts +57 -32
  166. package/src/live-voice/live-voice-photo.ts +1 -2
  167. package/src/live-voice/live-voice-session.ts +535 -314
  168. package/src/live-voice/progress-narration.ts +277 -0
  169. package/src/live-voice/protocol.ts +21 -1
  170. package/src/mcp/__tests__/effective-config.test.ts +238 -0
  171. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  172. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
  173. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
  174. package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
  175. package/src/mcp/client.ts +16 -11
  176. package/src/mcp/effective-config.ts +113 -0
  177. package/src/mcp/manager.ts +11 -6
  178. package/src/mcp/mcp-auth-orchestrator.ts +13 -22
  179. package/src/mcp/mcp-oauth-provider.ts +205 -240
  180. package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
  181. package/src/monitoring/plugin-auto-update.ts +128 -24
  182. package/src/notifications/signal.ts +1 -0
  183. package/src/permissions/confirmation-guardian-request.test.ts +15 -11
  184. package/src/permissions/confirmation-guardian-request.ts +2 -2
  185. package/src/permissions/question-guardian-request.test.ts +14 -6
  186. package/src/permissions/question-guardian-request.ts +1 -2
  187. package/src/persistence/attachments-store.ts +8 -1
  188. package/src/persistence/bookmark-crud.ts +3 -7
  189. package/src/persistence/conversation-attention-store.ts +16 -45
  190. package/src/persistence/conversation-crud.ts +33 -4
  191. package/src/persistence/conversation-lineage.ts +9 -0
  192. package/src/persistence/conversation-queries.ts +108 -41
  193. package/src/persistence/delivery-crud.ts +38 -29
  194. package/src/persistence/external-conversation-store.ts +32 -4
  195. package/src/persistence/llm-request-log-store.ts +4 -10
  196. package/src/persistence/llm-usage-store.ts +8 -3
  197. package/src/persistence/message-reads.test.ts +197 -0
  198. package/src/persistence/message-reads.ts +211 -0
  199. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
  200. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
  201. package/src/persistence/real-user-turn-filter.ts +27 -3
  202. package/src/persistence/steps.ts +9 -0
  203. package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
  204. package/src/plugin-api/conversation-turn.ts +168 -5
  205. package/src/plugin-api/index.ts +21 -5
  206. package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
  207. package/src/plugins/defaults/memory/AGENTS.md +4 -0
  208. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
  209. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
  210. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
  211. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
  212. package/src/plugins/defaults/memory/buffer-format.ts +165 -0
  213. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
  214. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
  215. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
  216. package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
  217. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +8 -12
  218. package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
  219. package/src/plugins/defaults/memory/indexer.ts +3 -1
  220. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
  221. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
  222. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
  223. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +33 -0
  224. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +199 -2
  225. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +25 -18
  226. package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
  227. package/src/plugins/defaults/memory/substrate/static-context.ts +160 -4
  228. package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
  229. package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
  230. package/src/plugins/defaults/memory/v3/prune.ts +2 -0
  231. package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
  232. package/src/plugins/defaults/memory/worker.ts +6 -3
  233. package/src/plugins/external-plugin-loader.ts +47 -0
  234. package/src/plugins/mcp-servers.ts +361 -0
  235. package/src/plugins/mtime-cache.ts +23 -49
  236. package/src/plugins/worker-plugin-surface.ts +33 -0
  237. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  238. package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
  239. package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
  240. package/src/providers/__tests__/retry-callsite.test.ts +5 -2
  241. package/src/providers/call-site-routing.ts +30 -3
  242. package/src/providers/connection-resolution.ts +194 -11
  243. package/src/providers/inference/auth.ts +6 -0
  244. package/src/providers/inference/connection-availability.ts +24 -2
  245. package/src/providers/inference/connections.ts +2 -0
  246. package/src/providers/model-intents.ts +26 -6
  247. package/src/providers/openai/codex-models.ts +2 -1
  248. package/src/providers/provider-send-message.ts +32 -3
  249. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
  250. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +620 -0
  251. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
  252. package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
  253. package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
  254. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +719 -0
  255. package/src/providers/speech-to-text/provider-catalog.ts +99 -8
  256. package/src/providers/speech-to-text/resolve.ts +25 -2
  257. package/src/routes/worker.ts +17 -5
  258. package/src/runtime/access-request-helper.ts +9 -12
  259. package/src/runtime/agent-wake.ts +3 -3
  260. package/src/runtime/pre-first-message-gate.ts +4 -0
  261. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
  262. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +219 -1
  263. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
  264. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
  265. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
  266. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
  267. package/src/runtime/routes/__tests__/plugins-routes.test.ts +44 -0
  268. package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
  269. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +62 -1
  270. package/src/runtime/routes/channel-availability-routes.ts +32 -14
  271. package/src/runtime/routes/channel-route-shared.ts +0 -6
  272. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
  273. package/src/runtime/routes/conversation-list-routes.ts +112 -1
  274. package/src/runtime/routes/conversation-query-routes.ts +40 -27
  275. package/src/runtime/routes/credential-prompt-routes.ts +4 -7
  276. package/src/runtime/routes/default-provider-routes.ts +15 -0
  277. package/src/runtime/routes/inbound-message-handler.ts +17 -41
  278. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
  279. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
  280. package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
  281. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
  282. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
  283. package/src/runtime/routes/inbound-stages/edit-intercept.ts +1 -3
  284. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
  285. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -4
  286. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +0 -1
  287. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +11 -20
  288. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
  289. package/src/runtime/routes/inference-profiles-routes.ts +20 -11
  290. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
  291. package/src/runtime/routes/log-export-routes.ts +3 -0
  292. package/src/runtime/routes/mcp-auth-routes.ts +148 -57
  293. package/src/runtime/routes/plugins-routes.ts +21 -3
  294. package/src/runtime/routes/stt-routes.ts +31 -25
  295. package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
  296. package/src/runtime/routes/user-route-dispatcher.ts +39 -14
  297. package/src/runtime/routes/user-route-import.ts +108 -0
  298. package/src/schedule/worker.ts +6 -0
  299. package/src/security/oauth2.ts +6 -22
  300. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
  301. package/src/stt/__tests__/types.test.ts +94 -0
  302. package/src/stt/daemon-batch-transcriber.ts +10 -0
  303. package/src/stt/stt-stream-session.ts +8 -4
  304. package/src/stt/types.ts +103 -0
  305. package/src/subagent/manager.ts +1 -3
  306. package/src/subagent/types.ts +7 -6
  307. package/src/tools/acp/spawn.test.ts +97 -0
  308. package/src/tools/acp/spawn.ts +32 -0
  309. package/src/tools/document/document-tool.ts +12 -3
  310. package/src/tools/registry.ts +2 -1
  311. package/src/tools/workflows/run-workflow.ts +1 -2
  312. package/src/tts/__tests__/reasoning-tag-filter.test.ts +63 -0
  313. package/src/tts/reasoning-tag-filter.ts +110 -0
  314. package/src/workspace/byok-default-profile-ensure.ts +61 -18
  315. package/src/workspace/custom-profile-ensure.ts +4 -24
  316. package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
  317. package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
  318. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
  319. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
  320. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
  321. package/src/workspace/migrations/registry.ts +8 -0
  322. package/src/workspace/provider-commit-message-generator.ts +7 -5
  323. package/src/live-voice/__tests__/front-decision.test.ts +0 -645
  324. package/src/live-voice/front-decision.ts +0 -476
@@ -0,0 +1,361 @@
1
+ /**
2
+ * Plugin-contributed MCP servers.
3
+ *
4
+ * A plugin declares MCP servers in a root `mcp.json`, per the Agent
5
+ * Plugins 1.0.0 specification:
6
+ *
7
+ * {
8
+ * "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
9
+ * "mcpServers": {
10
+ * "unabyss": { "type": "streamable-http", "url": "https://mcp.unabyss.com" }
11
+ * }
12
+ * }
13
+ *
14
+ * This module reads those files and projects each entry onto the
15
+ * assistant's own `McpServerConfig` shape, so a plugin-declared server can
16
+ * flow through the same surfaces as one configured in the workspace
17
+ * `config.json`.
18
+ *
19
+ * Failure isolation follows the spec: an invalid top-level `mcp.json`
20
+ * disables MCP for that plugin only, and an invalid individual server
21
+ * disables only that entry. Neither ever throws, because a malformed file
22
+ * in one plugin must not remove another plugin's servers from a listing.
23
+ *
24
+ * Authentication is deliberately absent. Agent Plugins 1.0.0 defines no
25
+ * portable OAuth or credential-reference fields; authentication is
26
+ * client-managed, and any `headers` in the file are literal package data.
27
+ * A plugin therefore cannot ship a credential, and the assistant's own
28
+ * credential store stays the only place secrets live.
29
+ *
30
+ * Consumers must not resolve a plugin server's id against the
31
+ * `mcp:<serverId>:*` credential namespace. Those keys belong to
32
+ * workspace-configured servers, and a plugin controls both its server key
33
+ * and its URL, so honoring them for a plugin-declared server would send a
34
+ * workspace credential to an endpoint the plugin chose. Every config built
35
+ * here carries `source: "plugin"`, which is what `McpClient` reads to
36
+ * decide, so the rule travels with the server rather than with the caller.
37
+ */
38
+
39
+ import { existsSync, readFileSync } from "node:fs";
40
+ import { join } from "node:path";
41
+
42
+ import { z } from "zod";
43
+
44
+ import {
45
+ type AllPluginInfo,
46
+ listAllPlugins,
47
+ type ListInstalledPluginsOptions,
48
+ } from "../cli/lib/list-installed-plugins.js";
49
+ import type {
50
+ McpTransport,
51
+ ResolvedMcpServerConfig,
52
+ } from "../config/schemas/mcp.js";
53
+ import { getLogger } from "../util/logger.js";
54
+
55
+ const log = getLogger("plugin-mcp-servers");
56
+
57
+ /** Filename a plugin declares its MCP servers in, at the plugin root. */
58
+ export const PLUGIN_MCP_MANIFEST = "mcp.json";
59
+
60
+ /**
61
+ * Risk level assigned to a plugin-declared server. `mcp.json` has no risk
62
+ * field, since the spec defines none, so a host default applies.
63
+ *
64
+ * `low` — so a plugin's tools run without prompting under the default
65
+ * auto-approve threshold — because the review happens earlier: the
66
+ * marketplace catalog (`plugins/marketplace.json`) is a curated whitelist
67
+ * of SHA-pinned entries, and installing a plugin is itself the user's
68
+ * decision to run the code it ships. Gating every call afterwards prompts
69
+ * on the tools the user installed the plugin to get.
70
+ *
71
+ * The gap this leaves is deliberate and known: a plugin installed
72
+ * off-marketplace straight from a GitHub URL gets the same default, and
73
+ * nothing recorded at install time distinguishes the two afterwards. A
74
+ * provenance signal is what would let this default be curation-gated
75
+ * rather than blanket. The user can still override per server — see the
76
+ * `defaultRiskLevel` field on a workspace `config.json` entry, which
77
+ * outranks a plugin's declaration of the same id.
78
+ */
79
+ const PLUGIN_SERVER_DEFAULT_RISK = "low" as const;
80
+
81
+ /** Matches the `maxTools` default in `McpServerConfigSchema`. */
82
+ const PLUGIN_SERVER_DEFAULT_MAX_TOOLS = 20;
83
+
84
+ // ---------------------------------------------------------------------------
85
+ // Wire schema (Agent Plugins 1.0.0)
86
+ // ---------------------------------------------------------------------------
87
+
88
+ const PluginStdioServerSchema = z.object({
89
+ type: z.literal("stdio"),
90
+ command: z.string().min(1),
91
+ args: z.array(z.string()).optional(),
92
+ env: z.record(z.string(), z.string()).optional(),
93
+ cwd: z.string().optional(),
94
+ });
95
+
96
+ const PluginHttpServerSchema = z.object({
97
+ type: z.enum(["streamable-http", "sse"]),
98
+ url: z.string().min(1),
99
+ headers: z.record(z.string(), z.string()).optional(),
100
+ });
101
+
102
+ const PluginMcpServerSchema = z.discriminatedUnion("type", [
103
+ PluginStdioServerSchema,
104
+ PluginHttpServerSchema,
105
+ ]);
106
+
107
+ const PluginMcpManifestSchema = z.object({
108
+ mcpServers: z.record(z.string(), z.unknown()),
109
+ });
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Public shape
113
+ // ---------------------------------------------------------------------------
114
+
115
+ /** One MCP server declared by one plugin. */
116
+ export interface PluginMcpServer {
117
+ /** Server id in the assistant's namespace. See {@link buildServerId}. */
118
+ readonly id: string;
119
+ /** Directory name of the plugin that declared it. */
120
+ readonly pluginName: string;
121
+ /** Key this server appears under in the plugin's `mcp.json`. */
122
+ readonly serverKey: string;
123
+ /** Projected onto the assistant's own server-config shape. */
124
+ readonly config: ResolvedMcpServerConfig;
125
+ }
126
+
127
+ /** A plugin's `mcp.json` that could not be used, in whole or in part. */
128
+ export interface PluginMcpIssue {
129
+ readonly pluginName: string;
130
+ /** Absent when the whole manifest is unusable rather than one entry. */
131
+ readonly serverKey?: string;
132
+ readonly message: string;
133
+ }
134
+
135
+ export interface PluginMcpServersResult {
136
+ readonly servers: readonly PluginMcpServer[];
137
+ readonly issues: readonly PluginMcpIssue[];
138
+ }
139
+
140
+ /**
141
+ * Server id for a plugin-declared server.
142
+ *
143
+ * Plugin servers share one namespace with workspace servers and with each
144
+ * other, so the plugin name is the qualifier. The redundant case is
145
+ * collapsed: a plugin named `unabyss` whose server key is also `unabyss`
146
+ * yields `unabyss`, not `unabyss__unabyss`, which matters because the id
147
+ * is embedded in every tool name the server contributes
148
+ * (`mcp__<id>__<tool>`).
149
+ */
150
+ export function buildServerId(pluginName: string, serverKey: string): string {
151
+ return pluginName === serverKey ? pluginName : `${pluginName}__${serverKey}`;
152
+ }
153
+
154
+ /**
155
+ * Expand the two path variables the Agent Plugins spec defines for stdio
156
+ * servers. They interpolate textually in `args`, `env` values, and `cwd`
157
+ * only, never in `command`, a URL, or a header, so a manifest cannot use
158
+ * them to build the executable path itself.
159
+ */
160
+ export function interpolatePluginPaths(
161
+ value: string,
162
+ pluginRoot: string,
163
+ pluginData: string,
164
+ ): string {
165
+ return value
166
+ .replaceAll("${PLUGIN_ROOT}", pluginRoot)
167
+ .replaceAll("${PLUGIN_DATA}", pluginData);
168
+ }
169
+
170
+ /**
171
+ * Whether a directory is a plugin the runtime would actually load.
172
+ *
173
+ * `listAllPlugins` is an inventory of directories and reports a malformed
174
+ * entry rather than dropping it, so it happily returns a directory with no
175
+ * usable `package.json`. The runtime loader rejects those in
176
+ * `parsePluginManifest`: the manifest must parse and carry a non-empty
177
+ * `name`. Applying the same gate here keeps `mcp.json` from being honored
178
+ * for a directory that will never load as a plugin.
179
+ */
180
+ export function hasLoadableManifest(plugin: AllPluginInfo): boolean {
181
+ const name = plugin.packageJson?.name;
182
+ return typeof name === "string" && name.length > 0;
183
+ }
184
+
185
+ // ---------------------------------------------------------------------------
186
+ // Reading
187
+ // ---------------------------------------------------------------------------
188
+
189
+ /**
190
+ * Read every installed plugin's `mcp.json` and return the servers they
191
+ * declare, plus anything that could not be read.
192
+ *
193
+ * A disabled plugin is skipped entirely: `.disabled` means the plugin
194
+ * contributes no hooks and no tools, and an MCP server it declares is no
195
+ * different.
196
+ */
197
+ export function readPluginMcpServers(
198
+ opts: ListInstalledPluginsOptions = {},
199
+ ): PluginMcpServersResult {
200
+ const servers: PluginMcpServer[] = [];
201
+ const issues: PluginMcpIssue[] = [];
202
+ const seen = new Map<string, string>();
203
+
204
+ let plugins: readonly AllPluginInfo[];
205
+ try {
206
+ plugins = listAllPlugins(opts);
207
+ } catch (err) {
208
+ // The plugins directory being unreadable is not a reason to fail a
209
+ // listing that also has workspace-configured servers in it.
210
+ log.warn({ err }, "Could not enumerate plugins for MCP declarations");
211
+ return { servers: [], issues: [] };
212
+ }
213
+
214
+ for (const plugin of plugins) {
215
+ if (plugin.disabled) {
216
+ continue;
217
+ }
218
+ const manifestPath = join(plugin.target, PLUGIN_MCP_MANIFEST);
219
+ if (!existsSync(manifestPath)) {
220
+ continue;
221
+ }
222
+
223
+ if (!hasLoadableManifest(plugin)) {
224
+ issues.push({
225
+ pluginName: plugin.name,
226
+ message: `${PLUGIN_MCP_MANIFEST} ignored: package.json is missing, unparseable, or has no name, so the runtime will not load this directory as a plugin`,
227
+ });
228
+ continue;
229
+ }
230
+
231
+ const parsed = parseManifest(manifestPath);
232
+ if ("error" in parsed) {
233
+ issues.push({ pluginName: plugin.name, message: parsed.error });
234
+ continue;
235
+ }
236
+
237
+ for (const [serverKey, raw] of Object.entries(parsed.mcpServers)) {
238
+ if (serverKey.trim().length === 0) {
239
+ issues.push({
240
+ pluginName: plugin.name,
241
+ message: "server key must be a non-empty string; skipping",
242
+ });
243
+ continue;
244
+ }
245
+
246
+ const entry = PluginMcpServerSchema.safeParse(raw);
247
+ if (!entry.success) {
248
+ issues.push({
249
+ pluginName: plugin.name,
250
+ serverKey,
251
+ message: `invalid server entry: ${entry.error.issues
252
+ .map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`)
253
+ .join("; ")}`,
254
+ });
255
+ continue;
256
+ }
257
+
258
+ const id = buildServerId(plugin.name, serverKey);
259
+ const previous = seen.get(id);
260
+ if (previous) {
261
+ // Two plugins claiming one id would silently shadow each other, and
262
+ // the loser's tools would just be missing. Report both instead.
263
+ issues.push({
264
+ pluginName: plugin.name,
265
+ serverKey,
266
+ message: `server id "${id}" is already declared by plugin "${previous}"; skipping`,
267
+ });
268
+ continue;
269
+ }
270
+ seen.set(id, plugin.name);
271
+
272
+ // The spec allows `cwd` on a stdio server; the assistant's transport
273
+ // has no such field. Dropping it silently would leave a server that
274
+ // resolves its relative paths against the wrong directory and fails
275
+ // for no visible reason, so say so.
276
+ if (entry.data.type === "stdio" && entry.data.cwd) {
277
+ issues.push({
278
+ pluginName: plugin.name,
279
+ serverKey,
280
+ message: `"cwd" is not supported by this host and was ignored; the server runs in the daemon's working directory`,
281
+ });
282
+ }
283
+
284
+ servers.push({
285
+ id,
286
+ pluginName: plugin.name,
287
+ serverKey,
288
+ config: {
289
+ transport: projectTransport(entry.data, plugin.target),
290
+ enabled: true,
291
+ defaultRiskLevel: PLUGIN_SERVER_DEFAULT_RISK,
292
+ maxTools: PLUGIN_SERVER_DEFAULT_MAX_TOOLS,
293
+ source: "plugin",
294
+ },
295
+ });
296
+ }
297
+ }
298
+
299
+ return { servers, issues };
300
+ }
301
+
302
+ function parseManifest(
303
+ path: string,
304
+ ): { mcpServers: Record<string, unknown> } | { error: string } {
305
+ let raw: string;
306
+ try {
307
+ raw = readFileSync(path, "utf8");
308
+ } catch (err) {
309
+ return {
310
+ error: `${PLUGIN_MCP_MANIFEST} unreadable: ${err instanceof Error ? err.message : String(err)}`,
311
+ };
312
+ }
313
+
314
+ let json: unknown;
315
+ try {
316
+ json = JSON.parse(raw);
317
+ } catch (err) {
318
+ return {
319
+ error: `${PLUGIN_MCP_MANIFEST} invalid JSON: ${err instanceof Error ? err.message : String(err)}`,
320
+ };
321
+ }
322
+
323
+ const manifest = PluginMcpManifestSchema.safeParse(json);
324
+ if (!manifest.success) {
325
+ return {
326
+ error: `${PLUGIN_MCP_MANIFEST} is missing a valid "mcpServers" object`,
327
+ };
328
+ }
329
+ return { mcpServers: manifest.data.mcpServers };
330
+ }
331
+
332
+ /**
333
+ * Project one spec-shaped entry onto the assistant's transport union. The
334
+ * two vocabularies already agree on type names and required fields, so this
335
+ * is a field copy plus path interpolation for the stdio case.
336
+ */
337
+ function projectTransport(
338
+ entry: z.infer<typeof PluginMcpServerSchema>,
339
+ pluginRoot: string,
340
+ ): McpTransport {
341
+ if (entry.type === "stdio") {
342
+ const pluginData = join(pluginRoot, "data");
343
+ const expand = (v: string): string =>
344
+ interpolatePluginPaths(v, pluginRoot, pluginData);
345
+ return {
346
+ type: "stdio",
347
+ command: entry.command,
348
+ args: (entry.args ?? []).map(expand),
349
+ ...(entry.env && {
350
+ env: Object.fromEntries(
351
+ Object.entries(entry.env).map(([k, v]) => [k, expand(v)]),
352
+ ),
353
+ }),
354
+ };
355
+ }
356
+ return {
357
+ type: entry.type,
358
+ url: entry.url,
359
+ ...(entry.headers && { headers: entry.headers }),
360
+ };
361
+ }
@@ -26,9 +26,9 @@
26
26
  * - Plugins are never "registered" as a unit — we register their tools into
27
27
  * the global tool registry.
28
28
  *
29
- * Tools are populated at boot by `loadUserPlugins()`; `getHooksFor` reads
30
- * hooks on every dispatch via {@link getUserHookEntriesFor}, which resolves
31
- * each hook through the hook loader on demand.
29
+ * Tools are populated at boot by `loadUserPlugins()`; hook dispatch reads
30
+ * discovered plugin names from this cache via {@link getDiscoveredUserPluginNames}
31
+ * and resolves each hook through the hook loader on demand.
32
32
  */
33
33
 
34
34
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
@@ -38,7 +38,6 @@ import type { Logger } from "pino";
38
38
 
39
39
  import {
40
40
  clearPluginHooks,
41
- collectUserHookEntries,
42
41
  evictHooksForOwner,
43
42
  hasWorkspaceHooks,
44
43
  type HookOwnerKind,
@@ -47,7 +46,7 @@ import {
47
46
  runShutdownHook,
48
47
  WORKSPACE_HOOKS_OWNER,
49
48
  } from "../hooks/hook-loader.js";
50
- import type { HookFunction, ShutdownReason } from "../plugin-api/types.js";
49
+ import type { ShutdownReason } from "../plugin-api/types.js";
51
50
  import {
52
51
  registerPluginSecretPatterns,
53
52
  resetPluginSecretPatternsForTests,
@@ -76,11 +75,7 @@ import {
76
75
  importWithTimeout,
77
76
  setSurfaceImportTimeout,
78
77
  } from "./surface-import.js";
79
- import type { HookEntry, PluginCredentialKeyPattern } from "./types.js";
80
-
81
- // Re-export for type compat — consumers that import HookFunction from
82
- // the mtime cache module still resolve.
83
- export type { HookFunction } from "./types.js";
78
+ import type { PluginCredentialKeyPattern } from "./types.js";
84
79
 
85
80
  const log = getLogger("plugin-mtime-cache");
86
81
 
@@ -206,48 +201,15 @@ let lastVersions: Record<string, PluginSourceVersion> = {};
206
201
  /** In-flight reconcile — concurrent imperative pokes await it rather than racing. */
207
202
  let reconcileInFlight: Promise<void> | null = null;
208
203
 
209
- // ─── Hook reads ──────────────────────────────────────────────────────────────
210
-
211
- /**
212
- * Get all hooks for a given event name from user plugins and standalone
213
- * workspace hooks, from the hook loader's in-memory cache. Plugin hooks run
214
- * in install-date order, the workspace hook runs last.
215
- *
216
- * This is a pure cache read: it never scans disk, activates a plugin, or
217
- * runs `init`. Activation happens only at boot ({@link populateCacheAtBoot})
218
- * and through the imperative install/uninstall poke
219
- * ({@link reconcilePluginSourcesNow}) — both main-daemon paths — so a
220
- * sidecar process that dispatches hooks (a worker running conversation
221
- * turns) can never bring a plugin up in its own process.
222
- *
223
- * `effectiveEnabledPlugins` carries the per-chat plugin scope: when non-null,
224
- * user plugins outside the set are skipped (standalone workspace hooks always
225
- * run). `null`/omitted means no per-chat restriction.
226
- */
227
- export async function getUserHookEntriesFor<TCtx = unknown>(
228
- hookName: string,
229
- effectiveEnabledPlugins?: Set<string> | null,
230
- ): Promise<HookEntry<TCtx>[]> {
231
- return collectUserHookEntries<TCtx>(
232
- hookName,
233
- discoveredPluginDirs.values(),
234
- effectiveEnabledPlugins,
235
- );
236
- }
204
+ // ─── Discovery reads ─────────────────────────────────────────────────────────
237
205
 
238
206
  /**
239
- * {@link getUserHookEntriesFor} without owner attribution — returns just the
240
- * hook functions in the same order.
207
+ * Plugin names currently in the discovery cache, in install-date order.
208
+ * Hook lookup in `hooks/registry.ts` walks this set; the cache itself does
209
+ * not resolve hooks.
241
210
  */
242
- export async function getUserHooksFor<TCtx = unknown>(
243
- hookName: string,
244
- effectiveEnabledPlugins?: Set<string> | null,
245
- ): Promise<HookFunction<TCtx>[]> {
246
- const entries = await getUserHookEntriesFor<TCtx>(
247
- hookName,
248
- effectiveEnabledPlugins,
249
- );
250
- return entries.map((e) => e.fn);
211
+ export function getDiscoveredUserPluginNames(): Iterable<string> {
212
+ return discoveredPluginDirs.values();
251
213
  }
252
214
 
253
215
  // ─── Source-versions reconcile ───────────────────────────────────────────────
@@ -300,6 +262,18 @@ export async function reconcilePluginSourcesNow(): Promise<void> {
300
262
  } catch (err) {
301
263
  log.error({ err }, "plugin schedule reconcile failed");
302
264
  }
265
+ // Converge plugin-declared MCP servers the same way, so an install
266
+ // brings its `mcp.json` servers up and an uninstall/disable takes their
267
+ // connected clients and registered tools back down. Reloads only when
268
+ // the declared set actually moved. Lazily imported for the same reason
269
+ // as the schedule reconciler, and equally self-contained.
270
+ try {
271
+ const { reconcilePluginMcpServers } =
272
+ await import("../daemon/mcp-reload-service.js");
273
+ await reconcilePluginMcpServers();
274
+ } catch (err) {
275
+ log.error({ err }, "plugin MCP reconcile failed");
276
+ }
303
277
  })().finally(() => {
304
278
  reconcileInFlight = null;
305
279
  });
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Default-plugin surface for sidecar worker processes.
3
+ *
4
+ * Worker processes that run real agent conversations (the memory jobs worker,
5
+ * the schedule worker) register the first-party default plugins' hooks and
6
+ * runtime injectors so those conversations get the same hook-driven behavior as
7
+ * conversations in the daemon: image captioning and vision-rejection recovery,
8
+ * tool-result truncation, history repair, and the per-turn runtime injections.
9
+ * Hook and injector dispatch is process-global, so one call at worker startup
10
+ * covers every conversation that process runs.
11
+ *
12
+ * Plugin `init` hooks are deliberately not run here. Registration is pure
13
+ * hook-table and injector-table population, and the default plugins degrade
14
+ * gracefully without their init-owned state (the image-fallback caption cache
15
+ * falls back to in-memory caching when its SQLite handle is absent). The memory
16
+ * plugin's init must never run in a worker: it starts the memory jobs worker
17
+ * process itself.
18
+ */
19
+
20
+ import {
21
+ registerDefaultPluginInjectors,
22
+ registerDefaultPlugins,
23
+ } from "./defaults/index.js";
24
+
25
+ /**
26
+ * Register the first-party default plugins' hooks and runtime injectors in the
27
+ * current process. Both registration functions are idempotent, so repeat calls
28
+ * are safe.
29
+ */
30
+ export function registerWorkerPluginSurface(): void {
31
+ registerDefaultPlugins();
32
+ registerDefaultPluginInjectors();
33
+ }
@@ -49,6 +49,7 @@ describe("isConnectionCompatibleWithModel", () => {
49
49
  expect(isConnectionCompatibleWithModel(conn, "gpt-5")).toBe(false);
50
50
  expect(isConnectionCompatibleWithModel(conn, "gpt-5.4-nano")).toBe(false);
51
51
  expect(isConnectionCompatibleWithModel(conn, "gpt-5.5-pro")).toBe(false);
52
+ expect(isConnectionCompatibleWithModel(conn, "gpt-5.3-codex")).toBe(false);
52
53
  });
53
54
 
54
55
  test("oauth_subscription connection is compatible with a Codex model", () => {
@@ -59,7 +60,6 @@ describe("isConnectionCompatibleWithModel", () => {
59
60
  expect(isConnectionCompatibleWithModel(conn, "gpt-5.5")).toBe(true);
60
61
  expect(isConnectionCompatibleWithModel(conn, "gpt-5.4")).toBe(true);
61
62
  expect(isConnectionCompatibleWithModel(conn, "gpt-5.4-mini")).toBe(true);
62
- expect(isConnectionCompatibleWithModel(conn, "gpt-5.3-codex")).toBe(true);
63
63
  });
64
64
 
65
65
  test("undefined model applies no gating (compatible)", () => {