@vellumai/assistant 0.11.3 → 0.11.4-staging.2

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 (341) 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 +139 -37
  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 +17 -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 +177 -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__/ui-shape-teaching.test.ts +33 -0
  67. package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
  68. package/src/__tests__/user-plugin-loader.test.ts +1 -1
  69. package/src/__tests__/visible-app-context.test.ts +16 -9
  70. package/src/__tests__/voice-config-update.test.ts +40 -0
  71. package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
  72. package/src/__tests__/worker-plugin-surface.test.ts +77 -0
  73. package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
  74. package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
  75. package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
  76. package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
  77. package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
  78. package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
  79. package/src/acp/__tests__/auth-required.test.ts +161 -0
  80. package/src/acp/acp-claude-oauth.ts +19 -2
  81. package/src/acp/agent-process.test.ts +100 -0
  82. package/src/acp/agent-process.ts +29 -26
  83. package/src/acp/auth-required.ts +102 -0
  84. package/src/acp/session-manager.test.ts +119 -0
  85. package/src/acp/session-manager.ts +68 -2
  86. package/src/api/events/acp-auth-required.ts +55 -0
  87. package/src/api/index.ts +7 -0
  88. package/src/api/surfaces.ts +7 -3
  89. package/src/apps/app-store.ts +3 -0
  90. package/src/bundler/package-resolver.ts +2 -30
  91. package/src/calls/__tests__/voice-session-bridge.test.ts +173 -1
  92. package/src/calls/__tests__/voice-triage-escalate.test.ts +94 -0
  93. package/src/calls/call-controller.ts +19 -3
  94. package/src/calls/call-setup-flow.ts +0 -1
  95. package/src/calls/media-stream-stt-session.ts +15 -0
  96. package/src/calls/voice-session-bridge.ts +71 -16
  97. package/src/calls/voice-triage-escalate.ts +104 -2
  98. package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
  99. package/src/channels/config.ts +13 -0
  100. package/src/channels/plugin-channel-declarations.ts +108 -0
  101. package/src/channels/types.ts +30 -0
  102. package/src/cli/AGENTS.md +5 -2
  103. package/src/cli/commands/credentials.help.ts +2 -2
  104. package/src/cli/commands/inference-providers.ts +1 -1
  105. package/src/cli/commands/mcp.help.ts +13 -4
  106. package/src/cli/commands/mcp.ts +9 -0
  107. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
  108. package/src/cli/commands/memory/index.help.ts +43 -1
  109. package/src/cli/commands/memory/memory-v3.ts +64 -0
  110. package/src/cli/commands/stt.help.ts +27 -2
  111. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
  112. package/src/cli/lib/bundled-marketplace.json +13 -0
  113. package/src/cli/lib/upgrade-plugin.ts +42 -0
  114. package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
  115. package/src/config/__tests__/default-provider.test.ts +6 -1
  116. package/src/config/__tests__/profile-materialization.test.ts +75 -19
  117. package/src/config/bundled-skills/acp/SKILL.md +6 -7
  118. package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
  119. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  120. package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
  121. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  122. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
  123. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
  124. package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
  125. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
  126. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
  127. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
  128. package/src/config/call-site-defaults.ts +4 -5
  129. package/src/config/default-profile-catalog.ts +83 -12
  130. package/src/config/default-profile-names.ts +4 -1
  131. package/src/config/default-provider-resolution.ts +4 -0
  132. package/src/config/llm-context-resolution.ts +11 -3
  133. package/src/config/llm-resolver.ts +28 -1
  134. package/src/config/profile-materialization.ts +70 -22
  135. package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
  136. package/src/config/schemas/call-site-catalog.ts +4 -4
  137. package/src/config/schemas/live-voice.ts +57 -23
  138. package/src/config/schemas/llm.ts +59 -32
  139. package/src/config/schemas/mcp.ts +23 -0
  140. package/src/config/schemas/plugin-updates.ts +6 -2
  141. package/src/config/schemas/stt.ts +1 -0
  142. package/src/context/outbound-sanitize.ts +96 -1
  143. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
  144. package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
  145. package/src/daemon/conversation-agent-loop.ts +17 -6
  146. package/src/daemon/conversation-messaging.ts +5 -1
  147. package/src/daemon/conversation-notifiers.ts +9 -1
  148. package/src/daemon/conversation-process.ts +36 -6
  149. package/src/daemon/conversation-runtime-assembly.ts +3 -4
  150. package/src/daemon/conversation-surfaces.ts +27 -5
  151. package/src/daemon/conversation-tool-setup.ts +1 -2
  152. package/src/daemon/conversation.ts +48 -0
  153. package/src/daemon/interactive-turn-sender.ts +59 -0
  154. package/src/daemon/mcp-reload-service.ts +36 -6
  155. package/src/daemon/process-message.ts +24 -24
  156. package/src/daemon/providers-setup.ts +6 -3
  157. package/src/daemon/trust-context-types.ts +29 -0
  158. package/src/daemon/wake-conversation-ops.ts +3 -2
  159. package/src/documents/document-store.ts +138 -5
  160. package/src/hooks/hook-loader.ts +3 -3
  161. package/src/hooks/registry.ts +50 -6
  162. package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
  163. package/src/inbound/oauth-callback-url.ts +61 -0
  164. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
  165. package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
  166. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +932 -0
  167. package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
  168. package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
  169. package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
  170. package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
  171. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
  172. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
  173. package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
  174. package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
  175. package/src/live-voice/live-voice-archive.ts +2 -0
  176. package/src/live-voice/live-voice-metrics.ts +57 -32
  177. package/src/live-voice/live-voice-photo.ts +1 -2
  178. package/src/live-voice/live-voice-session.ts +535 -314
  179. package/src/live-voice/progress-narration.ts +277 -0
  180. package/src/live-voice/protocol.ts +21 -1
  181. package/src/mcp/__tests__/effective-config.test.ts +238 -0
  182. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  183. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
  184. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
  185. package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
  186. package/src/mcp/client.ts +16 -11
  187. package/src/mcp/effective-config.ts +113 -0
  188. package/src/mcp/manager.ts +11 -6
  189. package/src/mcp/mcp-auth-orchestrator.ts +13 -22
  190. package/src/mcp/mcp-oauth-provider.ts +205 -240
  191. package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
  192. package/src/monitoring/plugin-auto-update.ts +128 -24
  193. package/src/notifications/signal.ts +1 -0
  194. package/src/permissions/confirmation-guardian-request.test.ts +15 -11
  195. package/src/permissions/confirmation-guardian-request.ts +2 -2
  196. package/src/permissions/question-guardian-request.test.ts +14 -6
  197. package/src/permissions/question-guardian-request.ts +1 -2
  198. package/src/persistence/attachments-store.ts +8 -1
  199. package/src/persistence/bookmark-crud.ts +3 -7
  200. package/src/persistence/conversation-attention-store.ts +16 -45
  201. package/src/persistence/conversation-crud.ts +33 -4
  202. package/src/persistence/conversation-lineage.ts +9 -0
  203. package/src/persistence/conversation-queries.ts +108 -41
  204. package/src/persistence/delivery-crud.ts +38 -29
  205. package/src/persistence/external-conversation-store.ts +32 -4
  206. package/src/persistence/llm-request-log-store.ts +4 -10
  207. package/src/persistence/llm-usage-store.ts +8 -3
  208. package/src/persistence/message-reads.test.ts +197 -0
  209. package/src/persistence/message-reads.ts +211 -0
  210. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
  211. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
  212. package/src/persistence/real-user-turn-filter.ts +27 -3
  213. package/src/persistence/steps.ts +9 -0
  214. package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
  215. package/src/plugin-api/conversation-turn.ts +168 -5
  216. package/src/plugin-api/index.ts +21 -5
  217. package/src/plugin-api/vision-support.test.ts +1 -1
  218. package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
  219. package/src/plugins/defaults/memory/AGENTS.md +4 -0
  220. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
  221. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
  222. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
  223. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
  224. package/src/plugins/defaults/memory/buffer-format.ts +165 -0
  225. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
  226. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
  227. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
  228. package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
  229. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +8 -12
  230. package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
  231. package/src/plugins/defaults/memory/indexer.ts +3 -1
  232. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
  233. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
  234. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
  235. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +33 -0
  236. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +199 -2
  237. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +25 -18
  238. package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
  239. package/src/plugins/defaults/memory/substrate/static-context.ts +160 -4
  240. package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
  241. package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
  242. package/src/plugins/defaults/memory/v3/prune.ts +2 -0
  243. package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
  244. package/src/plugins/defaults/memory/worker.ts +6 -3
  245. package/src/plugins/external-plugin-loader.ts +47 -0
  246. package/src/plugins/mcp-servers.ts +361 -0
  247. package/src/plugins/mtime-cache.ts +23 -49
  248. package/src/plugins/worker-plugin-surface.ts +33 -0
  249. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  250. package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
  251. package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
  252. package/src/providers/__tests__/retry-callsite.test.ts +5 -2
  253. package/src/providers/call-site-routing.ts +30 -3
  254. package/src/providers/connection-resolution.ts +194 -11
  255. package/src/providers/inference/auth.ts +6 -0
  256. package/src/providers/inference/connection-availability.ts +24 -2
  257. package/src/providers/inference/connections.ts +2 -0
  258. package/src/providers/model-catalog.ts +3 -3
  259. package/src/providers/model-intents.ts +28 -8
  260. package/src/providers/openai/chat-completions-provider.ts +5 -6
  261. package/src/providers/openai/codex-models.ts +2 -1
  262. package/src/providers/provider-send-message.ts +32 -3
  263. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
  264. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +620 -0
  265. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
  266. package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
  267. package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
  268. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +719 -0
  269. package/src/providers/speech-to-text/provider-catalog.ts +99 -8
  270. package/src/providers/speech-to-text/resolve.ts +25 -2
  271. package/src/routes/worker.ts +17 -5
  272. package/src/runtime/access-request-helper.ts +9 -12
  273. package/src/runtime/agent-wake.ts +3 -3
  274. package/src/runtime/pre-first-message-gate.ts +4 -0
  275. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
  276. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +219 -1
  277. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
  278. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
  279. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
  280. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
  281. package/src/runtime/routes/__tests__/plugins-routes.test.ts +44 -0
  282. package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
  283. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +62 -1
  284. package/src/runtime/routes/channel-availability-routes.ts +32 -14
  285. package/src/runtime/routes/channel-route-shared.ts +0 -6
  286. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
  287. package/src/runtime/routes/conversation-list-routes.ts +112 -1
  288. package/src/runtime/routes/conversation-query-routes.ts +40 -27
  289. package/src/runtime/routes/credential-prompt-routes.ts +4 -7
  290. package/src/runtime/routes/default-provider-routes.ts +15 -0
  291. package/src/runtime/routes/inbound-message-handler.ts +17 -41
  292. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
  293. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
  294. package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
  295. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
  296. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
  297. package/src/runtime/routes/inbound-stages/edit-intercept.ts +1 -3
  298. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
  299. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -4
  300. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +0 -1
  301. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +11 -20
  302. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
  303. package/src/runtime/routes/inference-profiles-routes.ts +20 -11
  304. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
  305. package/src/runtime/routes/log-export-routes.ts +3 -0
  306. package/src/runtime/routes/mcp-auth-routes.ts +148 -57
  307. package/src/runtime/routes/plugins-routes.ts +21 -3
  308. package/src/runtime/routes/stt-routes.ts +31 -25
  309. package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
  310. package/src/runtime/routes/user-route-dispatcher.ts +39 -14
  311. package/src/runtime/routes/user-route-import.ts +108 -0
  312. package/src/schedule/worker.ts +6 -0
  313. package/src/security/oauth2.ts +6 -22
  314. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
  315. package/src/stt/__tests__/types.test.ts +94 -0
  316. package/src/stt/daemon-batch-transcriber.ts +10 -0
  317. package/src/stt/stt-stream-session.ts +8 -4
  318. package/src/stt/types.ts +103 -0
  319. package/src/subagent/manager.ts +1 -3
  320. package/src/subagent/types.ts +7 -6
  321. package/src/tools/acp/spawn.test.ts +97 -0
  322. package/src/tools/acp/spawn.ts +32 -0
  323. package/src/tools/document/document-tool.ts +12 -3
  324. package/src/tools/registry.ts +2 -1
  325. package/src/tools/ui-surface/surface-shape-docs.ts +11 -0
  326. package/src/tools/workflows/run-workflow.ts +1 -2
  327. package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
  328. package/src/tts/reasoning-tag-filter.ts +89 -0
  329. package/src/util/think-tag-stream.ts +95 -0
  330. package/src/workspace/byok-default-profile-ensure.ts +76 -24
  331. package/src/workspace/custom-profile-ensure.ts +4 -24
  332. package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
  333. package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
  334. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
  335. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
  336. package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
  337. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
  338. package/src/workspace/migrations/registry.ts +10 -0
  339. package/src/workspace/provider-commit-message-generator.ts +7 -5
  340. package/src/live-voice/__tests__/front-decision.test.ts +0 -645
  341. package/src/live-voice/front-decision.ts +0 -476
@@ -24,6 +24,10 @@ interface McpServerEntry {
24
24
  defaultRiskLevel: string;
25
25
  allowedTools?: string[];
26
26
  blockedTools?: string[];
27
+ /** Workspace `config.json` or a plugin's `mcp.json`. */
28
+ source?: "workspace" | "plugin";
29
+ /** Plugin that declared the server, when `source` is `plugin`. */
30
+ pluginName?: string;
27
31
  }
28
32
 
29
33
  // ---------------------------------------------------------------------------
@@ -81,6 +85,11 @@ async function pollMcpAuthStatus(
81
85
  function printServerEntry(entry: McpServerEntry): void {
82
86
  log.info(` ${entry.id}`);
83
87
  log.info(` Status: ${entry.status}`);
88
+ // Only plugin-declared servers name a source. A workspace server is the
89
+ // default case and printing "workspace" on every one of them is noise.
90
+ if (entry.source === "plugin") {
91
+ log.info(` Source: plugin (${entry.pluginName ?? "unknown"})`);
92
+ }
84
93
  log.info(` Transport: ${entry.transport?.type ?? "unknown"}`);
85
94
  if (entry.transport?.type === "stdio") {
86
95
  log.info(
@@ -2,12 +2,13 @@
2
2
  * Tests for the `assistant memory v3` CLI subgroup (section-lane model).
3
3
  *
4
4
  * Validates:
5
- * - Subcommand registration (rebuild-index, backfill-sections) under
6
- * `memory v3`.
7
- * - Each subcommand maps to the right `cliIpcCall` method.
5
+ * - Subcommand registration (rebuild-index, backfill-sections, gate-stats)
6
+ * under `memory v3`.
7
+ * - `rebuild-index` and `backfill-sections` route through IPC to the daemon.
8
8
  * - `backfill-sections` passes a long IPC timeout (the one-time full-corpus
9
- * embed easily outlasts the default 60s), while `rebuild-index` does not.
10
- * - IPC error paths return a non-zero exit code without throwing.
9
+ * embed easily outlasts the default 60s).
10
+ * - `gate-stats` calls handleMemoryV3GateStats directly — no daemon required.
11
+ * - Error paths return a non-zero exit code without throwing.
11
12
  */
12
13
 
13
14
  import { beforeEach, describe, expect, mock, test } from "bun:test";
@@ -39,6 +40,47 @@ let mockIpcResult: {
39
40
  /** Captured log output for assertion. */
40
41
  let logOutput: string[] = [];
41
42
 
43
+ /** Shared fake gate-stats payload used across gate-stats tests. */
44
+ const fakeGateStats = {
45
+ lookbackDays: 30,
46
+ totalRuns: 10,
47
+ buckets: [
48
+ {
49
+ pageCountRange: "0–9",
50
+ total: 2,
51
+ scored: 1,
52
+ passed: 2,
53
+ scoredPassRate: 1,
54
+ reasons: { dense_pass: 1, dense_disabled: 1 },
55
+ },
56
+ {
57
+ pageCountRange: "10–49",
58
+ total: 5,
59
+ scored: 4,
60
+ passed: 3,
61
+ scoredPassRate: 0.75,
62
+ reasons: { dense_pass: 3, fail_no_signal: 1 },
63
+ },
64
+ {
65
+ pageCountRange: "50–199",
66
+ total: 2,
67
+ scored: 2,
68
+ passed: 1,
69
+ scoredPassRate: 0.5,
70
+ reasons: { dense_pass: 1, fail_no_signal: 1 },
71
+ },
72
+ {
73
+ pageCountRange: "200+",
74
+ total: 1,
75
+ scored: 1,
76
+ passed: 0,
77
+ scoredPassRate: 0,
78
+ reasons: { fail_no_signal: 1 },
79
+ },
80
+ ],
81
+ unknownPageCount: { total: 0, passed: 0, reasons: {} },
82
+ };
83
+
42
84
  // ---------------------------------------------------------------------------
43
85
  // Mocks
44
86
  // ---------------------------------------------------------------------------
@@ -68,6 +110,33 @@ mock.module("../../../../util/logger.js", () => ({
68
110
  getCliLogger: () => fakeLogger,
69
111
  }));
70
112
 
113
+ // ---------------------------------------------------------------------------
114
+ // gate-stats direct-invocation mocks
115
+ // ---------------------------------------------------------------------------
116
+
117
+ /** Args captured from the last handleMemoryV3GateStats call. */
118
+ let lastGateStatsArgs: { lookbackDays: number; db: unknown } | null = null;
119
+
120
+ /** What handleMemoryV3GateStats returns, or an error to throw. */
121
+ let gateStatsImpl: (() => unknown) | null = null;
122
+
123
+ mock.module(
124
+ "../../../../plugins/defaults/memory/src/memory-v3-routes.js",
125
+ () => ({
126
+ handleMemoryV3GateStats: (lookbackDays: number, db: unknown) => {
127
+ lastGateStatsArgs = { lookbackDays, db };
128
+ if (gateStatsImpl) {
129
+ return gateStatsImpl();
130
+ }
131
+ return fakeGateStats;
132
+ },
133
+ }),
134
+ );
135
+
136
+ mock.module("../../../../persistence/db-connection.js", () => ({
137
+ getTelemetrySqlite: () => ({}),
138
+ }));
139
+
71
140
  // ---------------------------------------------------------------------------
72
141
  // Import modules under test (after mocks)
73
142
  // ---------------------------------------------------------------------------
@@ -135,6 +204,8 @@ beforeEach(() => {
135
204
  ok: true,
136
205
  result: { articles: 3, sections: 12, failures: 0 },
137
206
  };
207
+ lastGateStatsArgs = null;
208
+ gateStatsImpl = null;
138
209
  logOutput = [];
139
210
  process.exitCode = 0;
140
211
  });
@@ -155,6 +226,7 @@ describe("subcommand registration", () => {
155
226
  "backfill-sections",
156
227
  "eval",
157
228
  "eval-tally",
229
+ "gate-stats",
158
230
  "rebuild-index",
159
231
  ]);
160
232
  });
@@ -236,3 +308,54 @@ describe("memory v3 backfill-sections", () => {
236
308
  expect(exitCode).toBe(1);
237
309
  });
238
310
  });
311
+
312
+ // ---------------------------------------------------------------------------
313
+ // gate-stats (direct invocation — no daemon required)
314
+ // ---------------------------------------------------------------------------
315
+
316
+ describe("memory v3 gate-stats", () => {
317
+ test("calls handleMemoryV3GateStats directly with default lookback (no IPC)", async () => {
318
+ const { exitCode } = await runCommand(["memory", "v3", "gate-stats"]);
319
+
320
+ expect(exitCode).toBe(0);
321
+ // Must NOT have gone through IPC.
322
+ expect(lastIpcCall).toBeNull();
323
+ // Must have called the handler directly with the default lookback.
324
+ expect(lastGateStatsArgs!.lookbackDays).toBe(30);
325
+ });
326
+
327
+ test("forwards --lookback-days directly to handleMemoryV3GateStats", async () => {
328
+ await runCommand(["memory", "v3", "gate-stats", "--lookback-days", "7"]);
329
+
330
+ expect(lastIpcCall).toBeNull();
331
+ expect(lastGateStatsArgs!.lookbackDays).toBe(7);
332
+ });
333
+
334
+ test("prints formatted table on success (no --json)", async () => {
335
+ await runCommand(["memory", "v3", "gate-stats"]);
336
+
337
+ expect(logOutput.some((l) => l.includes("Gate-stats"))).toBe(true);
338
+ expect(logOutput.some((l) => l.includes("10–49"))).toBe(true);
339
+ expect(logOutput.some((l) => l.includes("75.0%"))).toBe(true);
340
+ });
341
+
342
+ test("emits raw JSON with --json", async () => {
343
+ await runCommand(["memory", "v3", "gate-stats", "--json"]);
344
+
345
+ const raw = logOutput.join("");
346
+ const parsed = JSON.parse(raw) as typeof fakeGateStats;
347
+ expect(parsed.totalRuns).toBe(10);
348
+ expect(parsed.buckets).toHaveLength(4);
349
+ });
350
+
351
+ test("exits with code 1 when handleMemoryV3GateStats throws", async () => {
352
+ gateStatsImpl = () => {
353
+ throw new Error("telemetry DB locked");
354
+ };
355
+
356
+ const { exitCode } = await runCommand(["memory", "v3", "gate-stats"]);
357
+
358
+ expect(exitCode).toBe(1);
359
+ expect(logOutput.some((l) => l.includes("telemetry DB locked"))).toBe(true);
360
+ });
361
+ });
@@ -606,7 +606,9 @@ that live state safely.
606
606
 
607
607
  Examples:
608
608
  $ assistant memory v3 rebuild-index
609
- $ assistant memory v3 backfill-sections`,
609
+ $ assistant memory v3 backfill-sections
610
+ $ assistant memory v3 gate-stats
611
+ $ assistant memory v3 gate-stats --lookback-days 7 --json`,
610
612
  subcommands: [
611
613
  {
612
614
  name: "rebuild-index",
@@ -716,6 +718,46 @@ Examples:
716
718
  $ assistant memory v3 eval --snapshot .mv3/snapshot/concepts --staging .mv3/staging --out .mv3/eval
717
719
  $ assistant memory v3 eval --snapshot .mv3/snapshot/concepts --staging .mv3/staging --out .mv3/eval --turns-file .mv3/eval/key.json
718
720
  $ assistant memory v3 eval --snapshot .mv3/snapshot/concepts --staging .mv3/staging --out .mv3/eval --exclude-conversation <migration-conv-id>`,
721
+ },
722
+ {
723
+ name: "gate-stats",
724
+ description:
725
+ "Show injection gate pass rates bucketed by corpus size (read-only)",
726
+ options: [
727
+ {
728
+ flags: "--lookback-days <n>",
729
+ description: "Days of telemetry to aggregate (1–90, default 30)",
730
+ defaultValue: "30",
731
+ },
732
+ {
733
+ flags: "--json",
734
+ description: "Emit raw JSON instead of a formatted table",
735
+ },
736
+ ],
737
+ helpText: `
738
+ Reads the memory v3 injection gate telemetry outbox and prints pass rates
739
+ and reason distributions grouped by concept page count bucket
740
+ (0–9 / 10–49 / 50–199 / 200+). Scored runs (dense lane was available and
741
+ actually weighed scores) are reported separately from pass-open shortcuts
742
+ (dense disabled / unavailable / gate threw) so scoredPassRate reflects
743
+ only contested gate decisions — the signal relevant for threshold calibration.
744
+
745
+ Coverage is limited to runs still pending platform flush. In a healthy system
746
+ the outbox holds only the last few minutes to hours of events, so
747
+ --lookback-days is an upper bound, not a guarantee; long-window aggregation
748
+ lives on the platform side on top of flushed watchdog events.
749
+
750
+ Reads the telemetry database directly — the assistant does not need to be
751
+ running.
752
+
753
+ Intended use: verify that gate firing rates shift as expected after a
754
+ config or threshold change, and check whether the bm25-auto-calibration
755
+ flag is worth enabling for large corpora.
756
+
757
+ Examples:
758
+ $ assistant memory v3 gate-stats
759
+ $ assistant memory v3 gate-stats --lookback-days 7
760
+ $ assistant memory v3 gate-stats --json | jq '.buckets[] | select(.pageCountRange == "200+")'`,
719
761
  },
720
762
  {
721
763
  name: "eval-tally",
@@ -25,6 +25,7 @@ import type {
25
25
  MemoryEvalTallyResult,
26
26
  } from "../../../plugins/defaults/memory/src/memory-eval-routes.js";
27
27
  import type {
28
+ GateStatsResponse,
28
29
  MemoryV3BackfillSectionsResult,
29
30
  MemoryV3RebuildIndexResult,
30
31
  } from "../../../plugins/defaults/memory/src/memory-v3-routes.js";
@@ -52,6 +53,36 @@ function collectRepeatable(value: string, acc: string[]): string[] {
52
53
  return [...acc, value];
53
54
  }
54
55
 
56
+ /** Render gate-stats as a human-readable table. */
57
+ function formatGateStats(r: GateStatsResponse): string {
58
+ const lines: string[] = [
59
+ `Gate-stats (last ${r.lookbackDays} day${r.lookbackDays === 1 ? "" : "s"}, ${r.totalRuns} run${r.totalRuns === 1 ? "" : "s"})`,
60
+ "",
61
+ `Bucket Total Scored Passed ScoredPassRate Top reasons`,
62
+ ];
63
+ for (const b of r.buckets) {
64
+ const rate =
65
+ b.scoredPassRate !== null
66
+ ? `${(b.scoredPassRate * 100).toFixed(1)}%`
67
+ : "n/a";
68
+ const topReasons = Object.entries(b.reasons)
69
+ .sort(([, a], [, bv]) => (bv as number) - (a as number))
70
+ .slice(0, 3)
71
+ .map(([k, v]) => `${k}: ${v}`)
72
+ .join(", ");
73
+ lines.push(
74
+ `${b.pageCountRange.padEnd(10)} ${String(b.total).padStart(5)} ${String(b.scored).padStart(6)} ${String(b.passed).padStart(6)} ${rate.padStart(14)} ${topReasons}`,
75
+ );
76
+ }
77
+ if (r.unknownPageCount.total > 0) {
78
+ lines.push("");
79
+ lines.push(
80
+ `Unknown page count: ${r.unknownPageCount.total} total, ${r.unknownPageCount.passed} passed`,
81
+ );
82
+ }
83
+ return lines.join("\n");
84
+ }
85
+
55
86
  /**
56
87
  * Read the `turn` ids from a prior run's `key.json` or `packets.json` (both are
57
88
  * arrays of objects carrying a `turn` field). Used to pin `--turns-file` so a
@@ -271,4 +302,37 @@ export function registerMemoryV3Command(memory: Command): void {
271
302
  log.info(formatTally(payload));
272
303
  },
273
304
  );
305
+
306
+ // ── gate-stats ────────────────────────────────────────────────────────
307
+ // Reads the telemetry DB directly — no daemon required.
308
+
309
+ subcommand(v3, "gate-stats").action(
310
+ async (opts: { lookbackDays: string; json?: boolean }) => {
311
+ const lookbackDays = Number(opts.lookbackDays);
312
+ const [{ handleMemoryV3GateStats }, { getTelemetrySqlite }] =
313
+ await Promise.all([
314
+ import("../../../plugins/defaults/memory/src/memory-v3-routes.js"),
315
+ import("../../../persistence/db-connection.js"),
316
+ ]);
317
+ let payload;
318
+ try {
319
+ payload = handleMemoryV3GateStats(
320
+ Number.isFinite(lookbackDays) ? lookbackDays : 30,
321
+ getTelemetrySqlite(),
322
+ );
323
+ } catch (err) {
324
+ log.error(
325
+ "Failed to read gate stats: %s",
326
+ err instanceof Error ? err.message : String(err),
327
+ );
328
+ process.exitCode = 1;
329
+ return;
330
+ }
331
+ if (opts.json === true) {
332
+ log.info(JSON.stringify(payload, null, 2));
333
+ return;
334
+ }
335
+ log.info(formatGateStats(payload));
336
+ },
337
+ );
274
338
  }
@@ -1,7 +1,31 @@
1
- /** Declarative help for the `assistant stt` command. */
1
+ /**
2
+ * Declarative help for the `assistant stt` command.
3
+ *
4
+ * The advertised provider list is derived from the STT provider catalog, the
5
+ * single source of truth for which providers exist and which boundaries they
6
+ * serve, so the help cannot drift from what the daemon actually accepts.
7
+ */
2
8
 
9
+ // provider-catalog is an execution-free data leaf (a literal map behind
10
+ // type-only imports, no adapter or credential graph), consumed synchronously
11
+ // to derive this module's constant. Pure data from pure data, so a lazy
12
+ // `import()` has nothing to defer.
13
+ // eslint-disable-next-line cli/no-daemon-internals
14
+ import {
15
+ listProviderIds,
16
+ supportsBoundary,
17
+ } from "../../providers/speech-to-text/provider-catalog.js";
3
18
  import type { CliCommandHelp } from "../lib/cli-command-help.js";
4
19
 
20
+ /**
21
+ * `stt transcribe` runs the `daemon-batch` boundary, so streaming-only
22
+ * providers are omitted: naming one would advertise a configuration that
23
+ * fails on every file.
24
+ */
25
+ const batchProviders = listProviderIds()
26
+ .filter((id) => supportsBoundary(id, "daemon-batch"))
27
+ .join(", ");
28
+
5
29
  export const sttHelp: CliCommandHelp = {
6
30
  name: "stt",
7
31
  description: "Speech-to-text operations",
@@ -11,7 +35,8 @@ audio and video files. The provider is set via:
11
35
 
12
36
  $ assistant config set services.stt.provider <provider>
13
37
 
14
- Supported providers: openai-whisper, deepgram, google-gemini, xai.
38
+ Supported providers: ${batchProviders}.
39
+ Streaming-only providers serve live speech and cannot transcribe files.
15
40
 
16
41
  Examples:
17
42
  $ assistant stt transcribe --file /path/to/meeting.wav
@@ -32,6 +32,7 @@ import { computeFingerprint } from "../plugin-fingerprint.js";
32
32
  import { PluginNotInstalledError } from "../uninstall-plugin.js";
33
33
  import {
34
34
  PluginMergeBaselineError,
35
+ PluginNotCuratedError,
35
36
  PluginNotUpgradableError,
36
37
  upgradePlugin,
37
38
  } from "../upgrade-plugin.js";
@@ -574,6 +575,44 @@ describe("upgradePlugin — direct GitHub-URL installs", () => {
574
575
  expect(calls[0]?.[0]).toBe("ls-remote");
575
576
  });
576
577
 
578
+ test("marketplaceOnly refuses the direct path instead of following the ref", async () => {
579
+ // GIVEN a direct install tracking `main` at SHA_A, absent from the
580
+ // marketplace, whose branch has advanced to SHA_B
581
+ installCopy(pluginsDir, "level-up", { commit: SHA_A, ref: "main" });
582
+ const fetch = makeFetch({ manifest: undefined });
583
+ const calls: string[][] = [];
584
+ const runGit = directGitRunner({ main: SHA_B }, SHA_B, { calls });
585
+
586
+ // WHEN a caller that only accepts a curated pin asks for the upgrade
587
+ const upgrade = upgradePlugin(
588
+ { name: "level-up", marketplaceOnly: true },
589
+ { fetch, runGit, workspacePluginsDir: pluginsDir },
590
+ );
591
+
592
+ // THEN it is refused, and the mutable ref is never even resolved
593
+ await expect(upgrade).rejects.toThrow(PluginNotCuratedError);
594
+ expect(calls).toEqual([]);
595
+ // AND the install stays exactly where it was
596
+ expect(sidecarCommit(pluginsDir, "level-up")).toBe(SHA_A);
597
+ });
598
+
599
+ test("marketplaceOnly still upgrades a plugin the catalog claims", async () => {
600
+ // GIVEN an install the marketplace pins at SHA_B
601
+ installCopy(pluginsDir, "level-up", { commit: SHA_A });
602
+ const fetch = makeFetch({ manifest: manifestWith("level-up", SHA_B) });
603
+ const runGit = fakeGitRunner(SHA_B);
604
+
605
+ // WHEN the same curated-only caller asks for the upgrade
606
+ const result = await upgradePlugin(
607
+ { name: "level-up", marketplaceOnly: true },
608
+ { fetch, runGit, workspacePluginsDir: pluginsDir },
609
+ );
610
+
611
+ // THEN the flag is inert: the curated pin is taken as usual
612
+ expect(result.outcome).toBe("upgraded");
613
+ expect(result.toCommit).toBe(SHA_B);
614
+ });
615
+
577
616
  test("is a no-op when the recorded branch still points at the installed commit", async () => {
578
617
  // GIVEN a direct install tracking `main` at SHA_A
579
618
  installCopy(pluginsDir, "level-up", { commit: SHA_A, ref: "main" });
@@ -422,6 +422,19 @@
422
422
  "license": "MIT",
423
423
  "icon": "✈️"
424
424
  },
425
+ {
426
+ "name": "unabyss",
427
+ "source": {
428
+ "source": "github",
429
+ "repo": "Unabyss/unabyss-vellum",
430
+ "ref": "9f0bb0753dad09dace8578e242961bc2da912af3"
431
+ },
432
+ "description": "Personal and company context from Unabyss, available to your agent through the Unabyss MCP server.",
433
+ "category": "memory",
434
+ "homepage": "https://unabyss.com",
435
+ "license": "MIT",
436
+ "icon": "🌊"
437
+ },
425
438
  {
426
439
  "name": "vellum-client-qa",
427
440
  "source": {
@@ -17,6 +17,11 @@
17
17
  * verbatim, with no curated adapter overlay, exactly as the original untrusted
18
18
  * install was (see {@link directUpgrade}).
19
19
  *
20
+ * Because that target is a mutable ref, taking it means running whatever
21
+ * upstream pushed since, which is a choice only a human should make. A caller
22
+ * with nobody in the loop passes `marketplaceOnly` to rule it out: the direct
23
+ * path then throws {@link PluginNotCuratedError} instead of advancing.
24
+ *
20
25
  * This is deliberately a distinct operation from install: `install` is
21
26
  * first-time materialization (and errors on an existing install unless
22
27
  * `--force` is passed), whereas `upgrade` moves an existing install forward.
@@ -114,6 +119,20 @@ export interface UpgradePluginOptions {
114
119
  * {@link DEFAULT_PLUGIN_UPGRADE_STRATEGY}.
115
120
  */
116
121
  readonly strategy?: PluginUpgradeStrategy;
122
+ /**
123
+ * Refuse to advance an install the marketplace does not claim, instead of
124
+ * falling back to {@link directUpgrade}.
125
+ *
126
+ * A direct install's upgrade target is a mutable upstream ref, so taking it
127
+ * means fetching and executing whatever was pushed since. Callers with
128
+ * nobody in the loop (the unattended auto-update sweep) set this so that
129
+ * outcome is impossible for them, and it is enforced here rather than by the
130
+ * caller's own pre-check: the catalog is fetched again inside this call, so
131
+ * an entry that disappears in between would otherwise silently reroute a
132
+ * curated upgrade into a direct one. Defaults to false, which is what an
133
+ * interactive `assistant plugins upgrade` wants.
134
+ */
135
+ readonly marketplaceOnly?: boolean;
117
136
  }
118
137
 
119
138
  /** Dependencies injected by the caller. */
@@ -208,6 +227,23 @@ export class PluginNotUpgradableError extends Error {
208
227
  }
209
228
  }
210
229
 
230
+ /**
231
+ * A `marketplaceOnly` upgrade was requested for an install the marketplace does
232
+ * not claim, so the only revision available is a mutable upstream ref the
233
+ * caller refuses to take.
234
+ *
235
+ * Distinct from {@link PluginNotUpgradableError}: the install *can* be
236
+ * upgraded, just not without a human choosing to accept unreviewed code.
237
+ */
238
+ export class PluginNotCuratedError extends Error {
239
+ constructor(readonly pluginName: string) {
240
+ super(
241
+ `Plugin "${pluginName}" cannot be upgraded automatically: it has no marketplace entry, so its only upgrade target is a mutable upstream ref. Run 'assistant plugins upgrade ${pluginName}' to move it deliberately.`,
242
+ );
243
+ this.name = "PluginNotCuratedError";
244
+ }
245
+ }
246
+
211
247
  /**
212
248
  * A merge strategy (`ours`/`theirs`) was requested but the install-time
213
249
  * baseline needed for a three-way merge cannot be reconstructed.
@@ -244,6 +280,8 @@ function pluginTarget(name: string, deps: UpgradePluginDeps): string {
244
280
  * Throws {@link PluginNotInstalledError} when no copy is installed,
245
281
  * {@link PluginNotUpgradableError} when the install is neither in the
246
282
  * marketplace nor carries a recorded GitHub source to advance,
283
+ * {@link PluginNotCuratedError} when `marketplaceOnly` was requested for an
284
+ * install the marketplace does not claim,
247
285
  * {@link PluginMergeBaselineError} when a merge strategy is requested but the
248
286
  * install-time baseline cannot be reconstructed,
249
287
  * {@link PluginSourceUnavailableError} when the marketplace catalog or the
@@ -302,6 +340,10 @@ export async function upgradePlugin(
302
340
  "it has no marketplace entry and no installed copy to upgrade",
303
341
  );
304
342
  }
343
+ if (opts.marketplaceOnly) {
344
+ // The caller only accepts a curated pin, and this install has none.
345
+ throw new PluginNotCuratedError(name);
346
+ }
305
347
  return directUpgrade({ name, local, dryRun, strategy }, deps);
306
348
  }
307
349
  case "remote-unavailable":
@@ -7,6 +7,7 @@ import {
7
7
  CODE_DEFAULT_PROFILE_ENTRIES,
8
8
  getEffectiveProfile,
9
9
  getEffectiveProfiles,
10
+ getEffectiveProfilesForProvider,
10
11
  PROFILE_IMPLS,
11
12
  resolveDefaultProfileForProvider,
12
13
  } from "../default-profile-catalog.js";
@@ -22,6 +23,7 @@ import {
22
23
  import {
23
24
  type DefaultProviderConfig,
24
25
  type LLMCallSite,
26
+ LLMCallSiteEnum,
25
27
  LLMSchema,
26
28
  type ProfileEntry,
27
29
  } from "../schemas/llm.js";
@@ -245,7 +247,10 @@ describe("resolver integration", () => {
245
247
  },
246
248
  });
247
249
  const body = CODE_DEFAULT_PROFILE_ENTRIES["latency-optimized"]!;
248
- for (const callSite of ["voiceFrontDoor", "voiceFrontDecision"] as const) {
250
+ for (const callSite of [
251
+ "voiceFrontDoor",
252
+ "voiceProgressNarration",
253
+ ] as const) {
249
254
  const resolved = resolveCallSiteConfig(callSite, llm);
250
255
  expect(resolved.model).toBe(body.model as string);
251
256
  expect(resolved.model).not.toBe("claude-opus-4-6");
@@ -265,6 +270,12 @@ describe("resolver integration", () => {
265
270
  });
266
271
 
267
272
  describe("schema validation", () => {
273
+ test("voiceFrontDoor is the only front callsite", () => {
274
+ expect(
275
+ LLMCallSiteEnum.options.filter((callSite) => callSite.includes("Front")),
276
+ ).toEqual(["voiceFrontDoor"]);
277
+ });
278
+
268
279
  test("always-available default names are valid references; os-beta only when materialized", () => {
269
280
  expect(() => LLMSchema.parse({ activeProfile: "balanced" })).not.toThrow();
270
281
  expect(() =>
@@ -323,7 +334,7 @@ describe("resolveDefaultProfileForProvider", () => {
323
334
  expect(typeof entry?.model).toBe("string");
324
335
  expect(entry?.provider).toBeDefined();
325
336
  // Identity columns stamp no connection; BYOK columns always do.
326
- if (entry?.provider === "vellum") {
337
+ if (entry?.provider === "vellum" || entry?.provider === "chatgpt") {
327
338
  expect(entry?.provider_connection).toBeUndefined();
328
339
  } else {
329
340
  expect(entry?.provider_connection).toBeDefined();
@@ -333,6 +344,27 @@ describe("resolveDefaultProfileForProvider", () => {
333
344
  }
334
345
  });
335
346
 
347
+ test("the chatgpt column resolves Codex-pinned models with no connection stamp", () => {
348
+ const byKey: Record<string, string> = {
349
+ balanced: "gpt-5.6-luna",
350
+ "quality-optimized": "gpt-5.6-sol",
351
+ "cost-optimized": "gpt-5.6-luna",
352
+ "latency-optimized": "gpt-5.6-luna",
353
+ };
354
+ const effective = getEffectiveProfilesForProvider(undefined, dp("chatgpt"));
355
+ for (const [key, model] of Object.entries(byKey)) {
356
+ const entry = effective[key];
357
+ expect(entry?.provider).toBe("chatgpt");
358
+ expect(entry?.model).toBe(model);
359
+ expect(entry?.provider_connection).toBeUndefined();
360
+ expect(entry?.source).toBe("managed");
361
+ }
362
+ // Cost and Speed both opt fully out of reasoning.
363
+ expect(effective["cost-optimized"]?.effort).toBe("none");
364
+ expect(effective["latency-optimized"]?.effort).toBe("none");
365
+ expect(effective.balanced?.thinking?.enabled).toBe(true);
366
+ });
367
+
336
368
  test("a provider without a named matrix column materializes from the shared BYOK templates", () => {
337
369
  const entry = resolveDefaultProfileForProvider(
338
370
  undefined,
@@ -157,6 +157,12 @@ describe("resolveDefaultConnectionName", () => {
157
157
  );
158
158
  });
159
159
 
160
+ test("chatgpt resolves to the canonical subscription connection name", () => {
161
+ expect(resolveDefaultConnectionName({ provider: "chatgpt" })).toBe(
162
+ "chatgpt-subscription",
163
+ );
164
+ });
165
+
160
166
  test("every other provider resolves to its personal connection", () => {
161
167
  expect(resolveDefaultConnectionName({ provider: "anthropic" })).toBe(
162
168
  "anthropic-personal",
@@ -221,7 +227,6 @@ describe("getDefaultProvider / setDefaultProvider", () => {
221
227
  test("setDefaultProvider validates the provider before writing", () => {
222
228
  expect(() =>
223
229
  setDefaultProvider({
224
- // @ts-expect-error deliberately invalid for the test
225
230
  provider: "not-a-provider",
226
231
  }),
227
232
  ).toThrow();