@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
@@ -1,23 +1,22 @@
1
1
  /**
2
2
  * OAuthClientProvider implementation for MCP servers.
3
3
  *
4
- * Uses secure-keys (credential store) for persistent credential storage
5
- * and either a loopback HTTP server or the gateway callback registry
6
- * for the browser callback.
4
+ * Credentials persist through secure-keys (the credential store). The
5
+ * browser callback arrives on the assistant's shared OAuth callback route,
6
+ * which the gateway already serves and forwards to
7
+ * `POST /v1/internal/oauth/callback`; the runtime matches the arriving
8
+ * OAuth `state` against `security/oauth-callback-registry.ts` and resolves
9
+ * the waiting flow.
7
10
  *
8
- * Two callback transports:
9
- *
10
- * 1. **Loopback** (default) — starts a temporary HTTP server on localhost.
11
- * Works when the daemon runs on the user's machine (desktop app).
12
- *
13
- * 2. **Gateway** — routes callbacks through the platform's public ingress
14
- * URL and the in-memory oauth-callback-registry. Used when the daemon
15
- * runs inside Docker/platform where localhost is unreachable from the
16
- * user's browser.
11
+ * The redirect URI comes from `resolveOauthCallbackUrl`, so it is the same
12
+ * stable URL for every server and every attempt. That stability is what
13
+ * makes dynamic client registration and Client ID Metadata Documents
14
+ * usable: both pin `redirect_uris` at registration time, and the
15
+ * authorization server matches the value exactly on each authorization
16
+ * request.
17
17
  */
18
18
 
19
19
  import { randomBytes } from "node:crypto";
20
- import { createServer, type Server } from "node:http";
21
20
 
22
21
  import type {
23
22
  OAuthClientProvider,
@@ -29,6 +28,8 @@ import type {
29
28
  OAuthTokens,
30
29
  } from "@modelcontextprotocol/sdk/shared/auth.js";
31
30
 
31
+ import { getPlatformAssistantId } from "../config/env.js";
32
+ import { getAssistantName } from "../daemon/identity-helpers.js";
32
33
  import {
33
34
  deleteSecureKeyAsync,
34
35
  getSecureKeyAsync,
@@ -36,12 +37,10 @@ import {
36
37
  } from "../security/secure-keys.js";
37
38
  import { openInHostBrowser } from "../util/browser.js";
38
39
  import { getLogger } from "../util/logger.js";
40
+ import { APP_VERSION } from "../version.js";
39
41
 
40
42
  const log = getLogger("mcp-oauth");
41
43
 
42
- const CALLBACK_PATH = "/oauth/callback";
43
- const CALLBACK_TIMEOUT_MS = 2 * 60 * 1000; // 2 minutes
44
-
45
44
  // Credential store key helpers
46
45
  function tokensKey(serverId: string): string {
47
46
  return `mcp:${serverId}:tokens`;
@@ -52,15 +51,38 @@ function clientInfoKey(serverId: string): string {
52
51
  function discoveryKey(serverId: string): string {
53
52
  return `mcp:${serverId}:discovery`;
54
53
  }
54
+ function clientBindingKey(serverId: string): string {
55
+ return `mcp:${serverId}:client_binding`;
56
+ }
57
+
58
+ /**
59
+ * Logo shown on an authorization server's consent screen.
60
+ *
61
+ * Anonymously fetchable, which is the requirement: the server loads it
62
+ * without credentials. It identifies Vellum rather than the individual
63
+ * assistant, so every assistant a person runs presents the same mark.
64
+ */
65
+ const CLIENT_LOGO_URI = "https://www.vellum.ai/favicon.svg";
66
+
67
+ /**
68
+ * What a stored client registration was made against.
69
+ *
70
+ * A client identifier belongs to the authorization server that issued it and
71
+ * is registered for one set of redirect URIs, so reusing a registration after
72
+ * either changes is invalid. Recording both is what lets a registration be
73
+ * reused when they still hold, which is the difference between registering
74
+ * once per assistant and registering once per attempt.
75
+ */
76
+ interface ClientRegistrationBinding {
77
+ issuer: string | null;
78
+ redirectUri: string;
79
+ }
55
80
 
56
81
  export interface McpOAuthCallbackResult {
57
82
  /** Resolves with the authorization code when the callback is received. */
58
83
  codePromise: Promise<string>;
59
84
  }
60
85
 
61
- /** Which callback transport to use for receiving the OAuth redirect. */
62
- export type McpOAuthCallbackTransport = "loopback" | "gateway";
63
-
64
86
  export interface McpOAuthProviderOptions {
65
87
  /**
66
88
  * If provided, called with the authorization URL during
@@ -75,37 +97,29 @@ export class McpOAuthProvider implements OAuthClientProvider {
75
97
  private readonly serverId: string;
76
98
  private readonly serverUrl: string;
77
99
  private readonly interactive: boolean;
78
- private readonly callbackTransport: McpOAuthCallbackTransport;
79
100
  private _codeVerifier: string | undefined;
80
101
  private _state: string | undefined;
81
102
  private _redirectUrl: string | undefined;
82
103
  private _codePromise: Promise<string> | null = null;
83
- private callbackServer: Server | null = null;
84
- private callbackTimeout: ReturnType<typeof setTimeout> | null = null;
85
- /** Deferred resolver/rejector for the gateway code promise. */
86
- private _gatewayCodeResolve: ((code: string) => void) | undefined;
87
- private _gatewayCodeReject: ((err: Error) => void) | undefined;
104
+ /** Deferred resolver/rejector for the callback code promise. */
105
+ private _codeResolve: ((code: string) => void) | undefined;
106
+ private _codeReject: ((err: Error) => void) | undefined;
88
107
  private readonly _onAuthorizationUrl: ((url: string) => void) | undefined;
89
108
 
90
109
  /**
91
110
  * @param interactive When true (e.g. `mcp auth` CLI), opens browser for OAuth.
92
111
  * When false (daemon), logs a message instead.
93
- * @param callbackTransport Which transport to use for the OAuth redirect.
94
- * - `"loopback"` (default): localhost HTTP server — for desktop clients.
95
- * - `"gateway"`: platform ingress + callback registry — for Docker/platform.
96
112
  * @param options Additional options for the provider.
97
113
  */
98
114
  constructor(
99
115
  serverId: string,
100
116
  serverUrl: string,
101
117
  interactive = false,
102
- callbackTransport: McpOAuthCallbackTransport = "loopback",
103
118
  options: McpOAuthProviderOptions = {},
104
119
  ) {
105
120
  this.serverId = serverId;
106
121
  this.serverUrl = serverUrl;
107
122
  this.interactive = interactive;
108
- this.callbackTransport = callbackTransport;
109
123
  this._onAuthorizationUrl = options.onAuthorizationUrl;
110
124
  }
111
125
 
@@ -117,13 +131,36 @@ export class McpOAuthProvider implements OAuthClientProvider {
117
131
 
118
132
  // --- clientMetadata ---
119
133
 
134
+ /**
135
+ * Identity presented at dynamic registration, and on the consent screen
136
+ * the user reads before approving.
137
+ *
138
+ * The client is this assistant, not the Vellum product and not the plugin
139
+ * that declared the server: each assistant registers separately, with its
140
+ * own redirect URI, so its own name is what makes the consent screen
141
+ * meaningful when a person runs several. `software_id` carries the
142
+ * assistant id so a server can correlate an assistant's registrations
143
+ * across servers without treating every assistant as the same client.
144
+ *
145
+ * `logo_uri` is the Vellum mark rather than the assistant's own avatar.
146
+ * An authorization server fetching a logo is anonymous, and the avatar is
147
+ * served only behind authentication, so there is no per-assistant URL to
148
+ * give it. A stable public identity per assistant is what would supply
149
+ * one, and the same prerequisite would let this move to Client ID
150
+ * Metadata Documents and drop registration altogether.
151
+ */
120
152
  get clientMetadata(): OAuthClientMetadata {
153
+ const assistantName = getAssistantName();
154
+ const assistantId = getPlatformAssistantId().trim();
121
155
  return {
122
- client_name: "Vellum Assistant",
156
+ client_name: assistantName ?? "Vellum Assistant",
157
+ logo_uri: CLIENT_LOGO_URI,
123
158
  redirect_uris: this._redirectUrl ? [this._redirectUrl] : [],
124
159
  token_endpoint_auth_method: "none",
125
160
  grant_types: ["authorization_code", "refresh_token"],
126
161
  response_types: ["code"],
162
+ ...(assistantId.length > 0 && { software_id: assistantId }),
163
+ software_version: APP_VERSION,
127
164
  };
128
165
  }
129
166
 
@@ -214,13 +251,26 @@ export class McpOAuthProvider implements OAuthClientProvider {
214
251
 
215
252
  // --- Client Information ---
216
253
 
254
+ /**
255
+ * The stored registration, or `undefined` to make the SDK register a new
256
+ * one.
257
+ *
258
+ * Returning the stored value is the normal case: dynamic registration
259
+ * writes a record on the authorization server, so re-registering per
260
+ * attempt accumulates records nobody cleans up. It is withheld only when
261
+ * the registration provably no longer applies, because the redirect URI
262
+ * it was made for changed or because a different authorization server now
263
+ * fronts the resource.
264
+ */
217
265
  async clientInformation(): Promise<OAuthClientInformationMixed | undefined> {
218
266
  const raw = await getSecureKeyAsync(clientInfoKey(this.serverId));
219
267
  if (!raw) {
220
268
  return undefined;
221
269
  }
270
+
271
+ let info: OAuthClientInformationMixed;
222
272
  try {
223
- return JSON.parse(raw) as OAuthClientInformationMixed;
273
+ info = JSON.parse(raw) as OAuthClientInformationMixed;
224
274
  } catch {
225
275
  log.warn(
226
276
  { serverId: this.serverId },
@@ -228,6 +278,16 @@ export class McpOAuthProvider implements OAuthClientProvider {
228
278
  );
229
279
  return undefined;
230
280
  }
281
+
282
+ const stale = await this.describeStaleBinding();
283
+ if (stale) {
284
+ log.info(
285
+ { serverId: this.serverId, reason: stale },
286
+ "Stored client registration no longer applies; registering again",
287
+ );
288
+ return undefined;
289
+ }
290
+ return info;
231
291
  }
232
292
 
233
293
  async saveClientInformation(
@@ -244,9 +304,76 @@ export class McpOAuthProvider implements OAuthClientProvider {
244
304
  );
245
305
  return;
246
306
  }
307
+
308
+ // Record what the registration was made against, so a later run can tell
309
+ // whether reusing it is still valid.
310
+ if (this._redirectUrl) {
311
+ const binding: ClientRegistrationBinding = {
312
+ issuer: await this.currentIssuer(),
313
+ redirectUri: this._redirectUrl,
314
+ };
315
+ const boundOk = await setSecureKeyAsync(
316
+ clientBindingKey(this.serverId),
317
+ JSON.stringify(binding),
318
+ );
319
+ if (!boundOk) {
320
+ log.warn(
321
+ { serverId: this.serverId },
322
+ "Failed to persist OAuth client binding; the registration will be remade on the next flow",
323
+ );
324
+ }
325
+ }
326
+
247
327
  log.info({ serverId: this.serverId }, "OAuth client information saved");
248
328
  }
249
329
 
330
+ /** Issuer of the authorization server currently in play, when known. */
331
+ private async currentIssuer(): Promise<string | null> {
332
+ const state = await this.discoveryState();
333
+ return (
334
+ state?.authorizationServerMetadata?.issuer ??
335
+ state?.authorizationServerUrl ??
336
+ null
337
+ );
338
+ }
339
+
340
+ /**
341
+ * Why the stored registration cannot be reused, or null when it can.
342
+ *
343
+ * The redirect URI is only checked while a flow is being prepared:
344
+ * outside one there is no redirect URI to compare against, and a silent
345
+ * reconnect must not be turned into a registration.
346
+ *
347
+ * The issuer is only checked when discovery has run. An unverifiable
348
+ * issuer keeps the registration rather than discarding it, since the
349
+ * redirect check already covers the case this plugin actually changes.
350
+ */
351
+ private async describeStaleBinding(): Promise<string | null> {
352
+ const raw = await getSecureKeyAsync(clientBindingKey(this.serverId));
353
+ if (!raw) {
354
+ // Registered before the binding was recorded. Keep it: discarding
355
+ // every pre-existing registration would remake all of them at once.
356
+ return null;
357
+ }
358
+
359
+ let binding: ClientRegistrationBinding;
360
+ try {
361
+ binding = JSON.parse(raw) as ClientRegistrationBinding;
362
+ } catch {
363
+ return "stored binding is unreadable";
364
+ }
365
+
366
+ if (this._redirectUrl && binding.redirectUri !== this._redirectUrl) {
367
+ return "redirect URI changed";
368
+ }
369
+
370
+ const issuer = await this.currentIssuer();
371
+ if (issuer && binding.issuer && binding.issuer !== issuer) {
372
+ return "authorization server changed";
373
+ }
374
+ return null;
375
+ }
376
+
250
377
  // --- Code Verifier (in-memory, ephemeral) ---
251
378
 
252
379
  async saveCodeVerifier(verifier: string): Promise<void> {
@@ -312,31 +439,24 @@ export class McpOAuthProvider implements OAuthClientProvider {
312
439
  async redirectToAuthorization(authorizationUrl: URL): Promise<void> {
313
440
  const url = authorizationUrl.toString();
314
441
 
315
- // For gateway transport, extract the SDK-generated `state` from the
316
- // authorization URL and register it with the callback registry now.
317
- if (
318
- this.callbackTransport === "gateway" &&
319
- this._gatewayCodeResolve &&
320
- this._gatewayCodeReject
321
- ) {
442
+ // The callback is demultiplexed by OAuth `state`, and the SDK mints it
443
+ // while building this URL, so this is the earliest point the pending
444
+ // callback can be registered.
445
+ if (this._codeResolve && this._codeReject) {
322
446
  const sdkState = authorizationUrl.searchParams.get("state");
323
447
  if (sdkState) {
324
448
  // Dynamic import to avoid circular deps
325
449
  const { registerPendingCallback } =
326
450
  await import("../security/oauth-callback-registry.js");
327
- registerPendingCallback(
328
- sdkState,
329
- this._gatewayCodeResolve,
330
- this._gatewayCodeReject,
331
- );
451
+ registerPendingCallback(sdkState, this._codeResolve, this._codeReject);
332
452
  log.info(
333
453
  { serverId: this.serverId, state: sdkState },
334
- "MCP OAuth gateway callback registered with SDK state",
454
+ "MCP OAuth callback registered with SDK state",
335
455
  );
336
456
  } else {
337
457
  log.warn(
338
458
  { serverId: this.serverId },
339
- "Authorization URL missing state parameter — gateway callback may not resolve",
459
+ "Authorization URL missing state parameter, callback may not resolve",
340
460
  );
341
461
  }
342
462
  }
@@ -404,6 +524,10 @@ export class McpOAuthProvider implements OAuthClientProvider {
404
524
  "OAuth client information key not found in secure storage (already removed)",
405
525
  );
406
526
  }
527
+ // The binding describes the registration being dropped, so it goes
528
+ // with it. Leaving it would let a later registration inherit the
529
+ // previous one's issuer and redirect URI.
530
+ await deleteSecureKeyAsync(clientBindingKey(this.serverId));
407
531
  }
408
532
  if (scope === "all" || scope === "verifier") {
409
533
  this._codeVerifier = undefined;
@@ -425,52 +549,29 @@ export class McpOAuthProvider implements OAuthClientProvider {
425
549
  }
426
550
  }
427
551
 
428
- // --- Callback Server ---
552
+ // --- Callback ---
429
553
 
430
554
  /**
431
- * Start listening for the OAuth callback.
555
+ * Prepare to receive the OAuth callback.
432
556
  *
433
- * - **Loopback transport**: starts a temporary HTTP server on localhost.
434
- * - **Gateway transport**: registers a pending callback with the
435
- * oauth-callback-registry and resolves a public redirect URL via
436
- * the platform callback registration system.
557
+ * Resolves the shared redirect URI the gateway serves and creates a
558
+ * deferred code promise. There is no listener to start: the gateway
559
+ * already accepts `webhooks/oauth/callback` and forwards it to
560
+ * `POST /v1/internal/oauth/callback`, which resolves the waiting flow
561
+ * by matching the arriving OAuth `state`.
437
562
  *
438
- * Returns a promise that resolves with the authorization code promise.
563
+ * `registerPendingCallback` is deferred to `redirectToAuthorization`,
564
+ * the first point at which the SDK-generated `state` is known.
439
565
  */
440
- startCallbackServer(): Promise<McpOAuthCallbackResult> {
441
- if (this.callbackTransport === "gateway") {
442
- return this.startGatewayCallback();
443
- }
444
- return this.startLoopbackServer();
445
- }
446
-
447
- /**
448
- * Gateway transport: resolve the public redirect URL and create a
449
- * deferred code promise. The actual `registerPendingCallback` call
450
- * is deferred until `redirectToAuthorization` where we can extract
451
- * the SDK-generated `state` parameter from the authorization URL.
452
- */
453
- private async startGatewayCallback(): Promise<McpOAuthCallbackResult> {
454
- const { resolveCallbackUrl } =
455
- await import("../inbound/platform-callback-registration.js");
456
- const { getOAuthCallbackUrl } =
457
- await import("../inbound/public-ingress-urls.js");
458
- const { loadConfig } = await import("../config/loader.js");
459
-
460
- const appConfig = loadConfig();
461
- const redirectUrl = await resolveCallbackUrl(
462
- () => getOAuthCallbackUrl(appConfig),
463
- "webhooks/oauth/callback",
464
- "mcp_oauth",
465
- );
566
+ async startCallbackServer(): Promise<McpOAuthCallbackResult> {
567
+ const { resolveOauthCallbackUrl } =
568
+ await import("../inbound/oauth-callback-url.js");
466
569
 
467
- this._redirectUrl = redirectUrl;
570
+ this._redirectUrl = await resolveOauthCallbackUrl();
468
571
 
469
- // Create a deferred promise — it will be wired to the callback
470
- // registry in redirectToAuthorization() once we know the SDK's state.
471
572
  const codePromise = new Promise<string>((resolve, reject) => {
472
- this._gatewayCodeResolve = resolve;
473
- this._gatewayCodeReject = reject;
573
+ this._codeResolve = resolve;
574
+ this._codeReject = reject;
474
575
  });
475
576
  this._codePromise = codePromise;
476
577
 
@@ -481,154 +582,32 @@ export class McpOAuthProvider implements OAuthClientProvider {
481
582
  void codePromise.catch(() => {});
482
583
 
483
584
  log.info(
484
- { serverId: this.serverId, redirectUrl },
485
- "MCP OAuth gateway callback prepared (awaiting state from auth URL)",
585
+ { serverId: this.serverId, redirectUrl: this._redirectUrl },
586
+ "MCP OAuth callback prepared (awaiting state from auth URL)",
486
587
  );
487
588
 
488
589
  return { codePromise };
489
590
  }
490
591
 
491
- /**
492
- * Loopback transport: start a temporary HTTP server on localhost.
493
- */
494
- private startLoopbackServer(): Promise<McpOAuthCallbackResult> {
495
- return new Promise((resolveSetup, rejectSetup) => {
496
- let settled = false;
497
- let listening = false;
498
- let codeResolve: (code: string) => void;
499
- let codeReject: (err: Error) => void;
500
-
501
- const codePromise = new Promise<string>((resolve, reject) => {
502
- codeResolve = resolve;
503
- codeReject = reject;
504
- });
505
- this._codePromise = codePromise;
506
-
507
- const server = createServer((req, res) => {
508
- if (settled) {
509
- res.writeHead(400, { "Content-Type": "text/html" });
510
- res.end(renderPage("Authorization already completed", false));
511
- return;
512
- }
513
-
514
- const url = new URL(req.url ?? "/", "http://127.0.0.1");
515
- if (url.pathname !== CALLBACK_PATH) {
516
- res.writeHead(404, { "Content-Type": "text/plain" });
517
- res.end("Not found");
518
- return;
519
- }
520
-
521
- const code = url.searchParams.get("code");
522
- const error = url.searchParams.get("error");
523
-
524
- settled = true;
525
-
526
- if (error) {
527
- const errorDesc = url.searchParams.get("error_description") ?? error;
528
- res.writeHead(200, { "Content-Type": "text/html" });
529
- res.end(renderPage(`Authorization failed: ${errorDesc}`, false));
530
- cleanup();
531
- codeReject(new Error(`MCP OAuth authorization denied: ${error}`));
532
- return;
533
- }
534
-
535
- if (!code) {
536
- res.writeHead(400, { "Content-Type": "text/html" });
537
- res.end(renderPage("Missing authorization code", false));
538
- cleanup();
539
- codeReject(
540
- new Error("MCP OAuth callback missing authorization code"),
541
- );
542
- return;
543
- }
544
-
545
- res.writeHead(200, { "Content-Type": "text/html" });
546
- res.end(
547
- renderPage("Authorization successful! You can close this tab.", true),
548
- );
549
- cleanup();
550
- codeResolve(code);
551
- });
552
-
553
- this.callbackServer = server;
554
-
555
- const timeout = setTimeout(() => {
556
- if (!settled) {
557
- settled = true;
558
- cleanup();
559
- codeReject(new Error("MCP OAuth callback timed out"));
560
- }
561
- }, CALLBACK_TIMEOUT_MS);
562
- if (typeof timeout === "object" && "unref" in timeout) {
563
- timeout.unref();
564
- }
565
- this.callbackTimeout = timeout;
566
-
567
- const cleanup = () => {
568
- if (this.callbackTimeout) {
569
- clearTimeout(this.callbackTimeout);
570
- this.callbackTimeout = null;
571
- }
572
- if (this.callbackServer) {
573
- this.callbackServer.close();
574
- this.callbackServer = null;
575
- }
576
- };
577
-
578
- server.listen(0, "127.0.0.1", () => {
579
- const addr = server.address() as { port: number };
580
- this._redirectUrl = `http://127.0.0.1:${addr.port}${CALLBACK_PATH}`;
581
- listening = true;
582
- log.info(
583
- { serverId: this.serverId, redirectUrl: this._redirectUrl },
584
- "OAuth callback server started",
585
- );
586
- resolveSetup({ codePromise });
587
- });
588
-
589
- server.on("error", (err) => {
590
- const message = `MCP OAuth callback server error: ${err.message}`;
591
- if (!listening) {
592
- settled = true;
593
- cleanup();
594
- rejectSetup(new Error(message));
595
- } else if (!settled) {
596
- settled = true;
597
- cleanup();
598
- codeReject(new Error(message));
599
- }
600
- });
601
- });
602
- }
603
-
604
- /** Returns the code promise from the running callback server. */
592
+ /** Returns the code promise created by {@link startCallbackServer}. */
605
593
  waitForCode(): Promise<string> {
606
594
  if (!this._codePromise) {
607
595
  throw new Error(
608
- "Callback server not started — call startCallbackServer() first",
596
+ "Callback not prepared, call startCallbackServer() first",
609
597
  );
610
598
  }
611
599
  return this._codePromise;
612
600
  }
613
601
 
614
- /** Stop the callback server if it's still running. */
602
+ /**
603
+ * Abandon a prepared callback. Rejects the deferred promise so callers
604
+ * awaiting the code do not hang until the registry's own TTL fires.
605
+ */
615
606
  stopCallbackServer(): void {
616
- if (this.callbackTimeout) {
617
- clearTimeout(this.callbackTimeout);
618
- this.callbackTimeout = null;
619
- }
620
- if (this.callbackServer) {
621
- this.callbackServer.close();
622
- this.callbackServer = null;
623
- }
624
- // Gateway transport cleanup — reject the deferred promise so callers
625
- // awaiting codePromise don't hang indefinitely.
626
- if (this._gatewayCodeReject) {
627
- this._gatewayCodeReject(
628
- new Error("MCP OAuth gateway callback cancelled"),
629
- );
630
- this._gatewayCodeResolve = undefined;
631
- this._gatewayCodeReject = undefined;
607
+ if (this._codeReject) {
608
+ this._codeReject(new Error("MCP OAuth callback cancelled"));
609
+ this._codeResolve = undefined;
610
+ this._codeReject = undefined;
632
611
  }
633
612
  }
634
613
  }
@@ -650,14 +629,17 @@ export async function hasMcpOAuthTokens(serverId: string): Promise<boolean> {
650
629
  export async function deleteMcpOAuthCredentials(
651
630
  serverId: string,
652
631
  ): Promise<{ ok: boolean; failedKeys: string[] }> {
653
- const [tokensResult, clientResult, discoveryResult] = await Promise.all([
654
- deleteSecureKeyAsync(tokensKey(serverId)),
655
- deleteSecureKeyAsync(clientInfoKey(serverId)),
656
- deleteSecureKeyAsync(discoveryKey(serverId)),
657
- ]);
632
+ const [tokensResult, clientResult, bindingResult, discoveryResult] =
633
+ await Promise.all([
634
+ deleteSecureKeyAsync(tokensKey(serverId)),
635
+ deleteSecureKeyAsync(clientInfoKey(serverId)),
636
+ deleteSecureKeyAsync(clientBindingKey(serverId)),
637
+ deleteSecureKeyAsync(discoveryKey(serverId)),
638
+ ]);
658
639
  const results = [
659
640
  { key: "tokens", result: tokensResult },
660
641
  { key: "client_info", result: clientResult },
642
+ { key: "client_binding", result: bindingResult },
661
643
  { key: "discovery", result: discoveryResult },
662
644
  ];
663
645
  const failedKeys = results
@@ -678,20 +660,3 @@ export async function deleteMcpOAuthCredentials(
678
660
  );
679
661
  return { ok, failedKeys };
680
662
  }
681
-
682
- // --- HTML rendering ---
683
-
684
- function escapeHtml(s: string): string {
685
- return s
686
- .replace(/&/g, "&amp;")
687
- .replace(/</g, "&lt;")
688
- .replace(/>/g, "&gt;")
689
- .replace(/"/g, "&quot;")
690
- .replace(/'/g, "&#39;");
691
- }
692
-
693
- function renderPage(message: string, success: boolean): string {
694
- const title = success ? "Authorization Successful" : "Authorization Failed";
695
- const color = success ? "#4CAF50" : "#f44336";
696
- return `<!DOCTYPE html><html><head><title>${escapeHtml(title)}</title><style>body{font-family:system-ui,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#f5f5f5}div{text-align:center;padding:2rem;background:white;border-radius:8px;box-shadow:0 2px 4px rgba(0,0,0,0.1)}h1{color:${color}}</style></head><body><div><h1>${escapeHtml(title)}</h1><p>${escapeHtml(message)}</p></div></body></html>`;
697
- }