@vellumai/assistant 0.11.3 → 0.11.4-dev.202608190019.b94dbf2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (596) hide show
  1. package/AGENTS.md +9 -3
  2. package/ARCHITECTURE.md +32 -10
  3. package/docs/architecture/memory.md +43 -5
  4. package/docs/architecture/security.md +96 -152
  5. package/docs/architecture/turn-actor.md +70 -0
  6. package/docs/browser-use-architecture-phase2.md +128 -56
  7. package/docs/flux-turn-detection-spike.md +248 -0
  8. package/docs/guardian-request-flow.md +35 -0
  9. package/docs/stt-provider-onboarding.md +3 -1
  10. package/knip.json +3 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  13. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  19. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  20. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  21. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  23. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
  24. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
  25. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  26. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  27. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  28. package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  29. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  30. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  31. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  32. package/openapi.yaml +289 -113
  33. package/package.json +1 -1
  34. package/scripts/voice-ttft-spike.ts +3 -3
  35. package/scripts/write-plugin-api-shim.ts +10 -0
  36. package/src/__tests__/app-compiler.test.ts +38 -3
  37. package/src/__tests__/app-control-flow.test.ts +1 -0
  38. package/src/__tests__/approval-routes-http.test.ts +2 -2
  39. package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
  40. package/src/__tests__/attachments-store.test.ts +21 -12
  41. package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
  42. package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
  43. package/src/__tests__/call-site-routing-provider.test.ts +1 -1
  44. package/src/__tests__/channel-availability-routes.test.ts +14 -1
  45. package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
  46. package/src/__tests__/channel-delivery-store.test.ts +14 -14
  47. package/src/__tests__/channel-inbound-disk-pressure.test.ts +4 -4
  48. package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
  49. package/src/__tests__/checker.test.ts +20 -314
  50. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
  51. package/src/__tests__/compaction-events.test.ts +8 -10
  52. package/src/__tests__/config-loader-backfill.test.ts +3 -3
  53. package/src/__tests__/config-schema.test.ts +25 -10
  54. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  55. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
  56. package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
  57. package/src/__tests__/conversation-agent-loop.test.ts +32 -21
  58. package/src/__tests__/conversation-attachments.test.ts +0 -1
  59. package/src/__tests__/conversation-attention-store.test.ts +63 -0
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
  61. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -0
  63. package/src/__tests__/conversation-fork-referential.test.ts +67 -0
  64. package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
  65. package/src/__tests__/conversation-load-history-repair.test.ts +209 -0
  66. package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
  67. package/src/__tests__/conversation-queue.test.ts +154 -4
  68. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  69. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
  71. package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +187 -102
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
  75. package/src/__tests__/conversation-slash-queue.test.ts +3 -0
  76. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  77. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
  78. package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
  79. package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
  80. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
  81. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
  82. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
  83. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
  85. package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
  86. package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
  87. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
  88. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
  89. package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
  90. package/src/__tests__/credential-prompt-route.test.ts +7 -10
  91. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  92. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  93. package/src/__tests__/custom-profile-ensure.test.ts +5 -1
  94. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  95. package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
  96. package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
  97. package/src/__tests__/document-append-idempotency.test.ts +233 -0
  98. package/src/__tests__/document-sync-tags.test.ts +0 -75
  99. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  100. package/src/__tests__/edit-propagation.test.ts +107 -11
  101. package/src/__tests__/file-ops-service.test.ts +163 -30
  102. package/src/__tests__/filesystem-tools.test.ts +23 -24
  103. package/src/__tests__/gateway-only-guard.test.ts +2 -5
  104. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  105. package/src/__tests__/helpers/mock-actor-context.ts +49 -0
  106. package/src/__tests__/helpers/mock-conversation.ts +13 -1
  107. package/src/__tests__/host-file-read-tool.test.ts +16 -19
  108. package/src/__tests__/http-user-message-parity.test.ts +1 -1
  109. package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
  110. package/src/__tests__/injector-chain.test.ts +63 -41
  111. package/src/__tests__/injector-disk-pressure.test.ts +11 -23
  112. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  113. package/src/__tests__/llm-context-resolution.test.ts +73 -1
  114. package/src/__tests__/llm-schema.test.ts +5 -2
  115. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
  116. package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
  117. package/src/__tests__/media-generate-image.test.ts +131 -21
  118. package/src/__tests__/memory-retrieval-hook.test.ts +100 -7
  119. package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
  120. package/src/__tests__/mtime-cache.test.ts +3 -1
  121. package/src/__tests__/non-member-access-request.test.ts +0 -20
  122. package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
  123. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  124. package/src/__tests__/plugin-api-store-credential.test.ts +261 -0
  125. package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
  126. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  127. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  128. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  129. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  130. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -4
  131. package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
  132. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
  133. package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
  134. package/src/__tests__/proxy-approval-callback.test.ts +1 -0
  135. package/src/__tests__/qdrant-manager.test.ts +14 -1
  136. package/src/__tests__/reaction-persistence.test.ts +200 -7
  137. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  138. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  139. package/src/__tests__/run-conversation-turn-persistence.test.ts +460 -105
  140. package/src/__tests__/run-due-schedules.test.ts +21 -0
  141. package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
  142. package/src/__tests__/schedule-routes.test.ts +23 -0
  143. package/src/__tests__/schedule-store.test.ts +17 -0
  144. package/src/__tests__/scoped-approval-grants.test.ts +11 -6
  145. package/src/__tests__/secret-ingress-channel.test.ts +0 -1
  146. package/src/__tests__/secret-ingress-http.test.ts +1 -1
  147. package/src/__tests__/send-endpoint-busy.test.ts +3 -3
  148. package/src/__tests__/skills.test.ts +32 -0
  149. package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
  150. package/src/__tests__/starter-task-flow.test.ts +5 -4
  151. package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
  152. package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
  153. package/src/__tests__/subagent-spawn-and-await.test.ts +18 -17
  154. package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
  155. package/src/__tests__/subagent-tools.test.ts +81 -101
  156. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
  157. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  158. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  159. package/src/__tests__/tool-executor.test.ts +68 -39
  160. package/src/__tests__/turn-events-store.test.ts +43 -0
  161. package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
  162. package/src/__tests__/ui-shape-teaching.test.ts +33 -0
  163. package/src/__tests__/ui-visual-surface.test.ts +1 -1
  164. package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
  165. package/src/__tests__/ui-work-result-surface.test.ts +1 -1
  166. package/src/__tests__/user-plugin-loader.test.ts +3 -1
  167. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  168. package/src/__tests__/visible-app-context.test.ts +16 -9
  169. package/src/__tests__/voice-config-update.test.ts +40 -0
  170. package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
  171. package/src/__tests__/voice-session-bridge.test.ts +85 -29
  172. package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
  173. package/src/__tests__/worker-plugin-surface.test.ts +77 -0
  174. package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
  175. package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
  176. package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
  177. package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
  178. package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
  179. package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
  180. package/src/acp/__tests__/auth-required.test.ts +161 -0
  181. package/src/acp/acp-claude-oauth.ts +19 -2
  182. package/src/acp/agent-process.test.ts +100 -0
  183. package/src/acp/agent-process.ts +29 -26
  184. package/src/acp/auth-required.ts +102 -0
  185. package/src/acp/session-manager.test.ts +119 -0
  186. package/src/acp/session-manager.ts +76 -3
  187. package/src/api/events/acp-auth-required.ts +55 -0
  188. package/src/api/events/host-file.ts +2 -2
  189. package/src/api/index.ts +7 -0
  190. package/src/api/surfaces.ts +12 -3
  191. package/src/apps/app-store.ts +3 -0
  192. package/src/bundler/package-resolver.ts +2 -30
  193. package/src/calls/__tests__/voice-session-bridge.test.ts +194 -11
  194. package/src/calls/__tests__/voice-triage-escalate.test.ts +102 -0
  195. package/src/calls/call-controller.ts +19 -3
  196. package/src/calls/call-setup-flow.ts +0 -1
  197. package/src/calls/media-stream-stt-session.ts +15 -0
  198. package/src/calls/voice-session-bridge.ts +115 -41
  199. package/src/calls/voice-triage-escalate.ts +105 -2
  200. package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
  201. package/src/channels/config.ts +13 -0
  202. package/src/channels/plugin-channel-declarations.ts +108 -0
  203. package/src/channels/types.ts +30 -0
  204. package/src/cli/AGENTS.md +5 -2
  205. package/src/cli/bundled-modules.ts +29 -0
  206. package/src/cli/commands/credentials.help.ts +2 -2
  207. package/src/cli/commands/db/repair.ts +4 -8
  208. package/src/cli/commands/domain.ts +6 -3
  209. package/src/cli/commands/email.ts +6 -3
  210. package/src/cli/commands/inference-providers.ts +1 -1
  211. package/src/cli/commands/keys.ts +8 -3
  212. package/src/cli/commands/mcp.help.ts +13 -4
  213. package/src/cli/commands/mcp.ts +9 -0
  214. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  215. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
  216. package/src/cli/commands/memory/index.help.ts +84 -28
  217. package/src/cli/commands/memory/index.ts +2 -0
  218. package/src/cli/commands/memory/memory-v2.ts +58 -54
  219. package/src/cli/commands/memory/memory-v3.ts +64 -0
  220. package/src/cli/commands/memory/memory-validate.ts +18 -0
  221. package/src/cli/commands/plugins.ts +85 -36
  222. package/src/cli/commands/schedules.ts +35 -1
  223. package/src/cli/commands/stt.help.ts +27 -2
  224. package/src/cli/commands/trust.ts +3 -14
  225. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
  226. package/src/cli/lib/bundled-marketplace.json +14 -1
  227. package/src/cli/lib/upgrade-plugin.ts +42 -0
  228. package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
  229. package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
  230. package/src/config/__tests__/default-provider.test.ts +6 -1
  231. package/src/config/__tests__/profile-materialization.test.ts +75 -19
  232. package/src/config/assistant-feature-flags.ts +36 -15
  233. package/src/config/balanced-model-experiment.ts +35 -0
  234. package/src/config/bundled-skills/acp/SKILL.md +6 -7
  235. package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
  236. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  237. package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
  238. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
  239. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
  240. package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
  241. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  242. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
  243. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
  244. package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
  245. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
  246. package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
  247. package/src/config/bundled-skills/subagent/SKILL.md +17 -12
  248. package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
  249. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
  250. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
  251. package/src/config/call-site-defaults.ts +11 -5
  252. package/src/config/default-profile-catalog.ts +179 -16
  253. package/src/config/default-profile-names.ts +4 -1
  254. package/src/config/default-provider-resolution.ts +4 -0
  255. package/src/config/feature-flag-registry.json +12 -12
  256. package/src/config/llm-context-resolution.ts +11 -3
  257. package/src/config/llm-resolver.ts +28 -1
  258. package/src/config/profile-materialization.ts +70 -22
  259. package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
  260. package/src/config/schemas/call-site-catalog.ts +4 -4
  261. package/src/config/schemas/live-voice.ts +57 -23
  262. package/src/config/schemas/llm.ts +59 -32
  263. package/src/config/schemas/mcp.ts +23 -0
  264. package/src/config/schemas/plugin-updates.ts +6 -2
  265. package/src/config/schemas/stt.ts +1 -0
  266. package/src/config/skills.ts +9 -2
  267. package/src/context/outbound-sanitize.ts +96 -1
  268. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
  269. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
  270. package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
  271. package/src/daemon/conversation-agent-loop.ts +31 -19
  272. package/src/daemon/conversation-messaging.ts +5 -1
  273. package/src/daemon/conversation-notifiers.ts +20 -10
  274. package/src/daemon/conversation-process.ts +20 -6
  275. package/src/daemon/conversation-runtime-assembly.ts +12 -6
  276. package/src/daemon/conversation-store.ts +14 -4
  277. package/src/daemon/conversation-surfaces.ts +75 -26
  278. package/src/daemon/conversation-tool-setup.ts +3 -6
  279. package/src/daemon/conversation.ts +154 -51
  280. package/src/daemon/doordash-steps.ts +2 -2
  281. package/src/daemon/lifecycle.ts +14 -1
  282. package/src/daemon/mcp-reload-service.ts +36 -6
  283. package/src/daemon/process-message.ts +13 -26
  284. package/src/daemon/providers-setup.ts +6 -3
  285. package/src/daemon/trust-context-types.ts +29 -0
  286. package/src/daemon/trust-context.ts +12 -7
  287. package/src/daemon/wake-conversation-ops.ts +3 -2
  288. package/src/daemon/windows-compiled-entry.ts +4 -0
  289. package/src/documents/document-store.ts +143 -240
  290. package/src/hooks/hook-loader.ts +3 -3
  291. package/src/hooks/registry.ts +103 -45
  292. package/src/hooks/types.ts +5 -0
  293. package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
  294. package/src/inbound/oauth-callback-url.ts +61 -0
  295. package/src/ipc/gateway-client.test.ts +1 -1
  296. package/src/ipc/gateway-client.ts +14 -21
  297. package/src/ipc/gateway-flag-listener.ts +17 -3
  298. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
  299. package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
  300. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +1050 -0
  301. package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
  302. package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
  303. package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
  304. package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
  305. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
  306. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
  307. package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
  308. package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
  309. package/src/live-voice/live-voice-archive.ts +2 -0
  310. package/src/live-voice/live-voice-manager.ts +16 -3
  311. package/src/live-voice/live-voice-metrics.ts +57 -32
  312. package/src/live-voice/live-voice-photo.ts +1 -2
  313. package/src/live-voice/live-voice-session.ts +598 -323
  314. package/src/live-voice/progress-narration.ts +277 -0
  315. package/src/live-voice/protocol.ts +21 -1
  316. package/src/live-voice/windows-compiled-live-voice.ts +4 -0
  317. package/src/mcp/__tests__/effective-config.test.ts +238 -0
  318. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  319. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
  320. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
  321. package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
  322. package/src/mcp/client.ts +16 -11
  323. package/src/mcp/effective-config.ts +113 -0
  324. package/src/mcp/manager.ts +11 -6
  325. package/src/mcp/mcp-auth-orchestrator.ts +13 -22
  326. package/src/mcp/mcp-oauth-provider.ts +205 -240
  327. package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
  328. package/src/monitoring/control.ts +1 -0
  329. package/src/monitoring/db-integrity-sample.ts +4 -5
  330. package/src/monitoring/plugin-auto-update.ts +128 -24
  331. package/src/notifications/AGENTS.md +2 -0
  332. package/src/notifications/approval-card-data.ts +33 -0
  333. package/src/notifications/signal.ts +1 -0
  334. package/src/permissions/AGENTS.md +16 -0
  335. package/src/permissions/checker.test.ts +75 -122
  336. package/src/permissions/checker.ts +44 -560
  337. package/src/permissions/confirmation-guardian-request.test.ts +15 -11
  338. package/src/permissions/confirmation-guardian-request.ts +2 -2
  339. package/src/permissions/prompter.ts +1 -5
  340. package/src/permissions/question-guardian-request.test.ts +14 -6
  341. package/src/permissions/question-guardian-request.ts +1 -2
  342. package/src/persistence/attachments-store.ts +8 -1
  343. package/src/persistence/bookmark-crud.ts +3 -7
  344. package/src/persistence/conversation-attention-store.ts +16 -45
  345. package/src/persistence/conversation-crud.ts +33 -4
  346. package/src/persistence/conversation-lineage.ts +9 -0
  347. package/src/persistence/conversation-queries.ts +182 -52
  348. package/src/persistence/delivery-crud.ts +150 -36
  349. package/src/persistence/embeddings/qdrant-manager.ts +84 -49
  350. package/src/persistence/external-conversation-store.ts +32 -4
  351. package/src/persistence/llm-request-log-store.ts +4 -10
  352. package/src/persistence/llm-usage-store.ts +8 -3
  353. package/src/persistence/message-reads.test.ts +197 -0
  354. package/src/persistence/message-reads.ts +211 -0
  355. package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
  356. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
  357. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
  358. package/src/persistence/real-user-turn-filter.ts +27 -3
  359. package/src/persistence/schema/documents.ts +4 -4
  360. package/src/persistence/steps.ts +9 -0
  361. package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
  362. package/src/plugin-api/constants.ts +8 -0
  363. package/src/plugin-api/conversation-turn.ts +195 -5
  364. package/src/plugin-api/index.ts +38 -6
  365. package/src/plugin-api/resolve-credential.ts +3 -2
  366. package/src/plugin-api/store-credential.ts +146 -0
  367. package/src/plugin-api/vision-support.test.ts +1 -1
  368. package/src/plugin-api/webhook-url.ts +13 -11
  369. package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
  370. package/src/plugins/defaults/main.ts +21 -7
  371. package/src/plugins/defaults/memory/AGENTS.md +15 -3
  372. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
  373. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
  374. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
  375. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
  376. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  377. package/src/plugins/defaults/memory/buffer-format.ts +165 -0
  378. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
  379. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
  380. package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
  381. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
  382. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
  383. package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
  384. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +17 -24
  385. package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
  386. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
  387. package/src/plugins/defaults/memory/indexer.ts +3 -1
  388. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
  389. package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
  390. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
  391. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  392. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
  393. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +147 -0
  394. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  395. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  396. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  397. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  398. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  399. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  400. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +195 -58
  401. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +93 -24
  402. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  403. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  404. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  405. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  406. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  407. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  408. package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
  409. package/src/plugins/defaults/memory/substrate/static-context.ts +160 -33
  410. package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
  411. package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
  412. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
  413. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
  414. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  415. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  416. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  417. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  418. package/src/plugins/defaults/memory/v3/injector.ts +8 -0
  419. package/src/plugins/defaults/memory/v3/prune.ts +2 -0
  420. package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
  421. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
  422. package/src/plugins/defaults/memory/worker-control.ts +1 -0
  423. package/src/plugins/defaults/memory/worker.ts +6 -3
  424. package/src/plugins/defaults/worker-entrypoints.ts +3 -0
  425. package/src/plugins/external-plugin-loader.ts +47 -0
  426. package/src/plugins/mcp-servers.ts +361 -0
  427. package/src/plugins/mtime-cache.ts +37 -46
  428. package/src/plugins/pipeline.ts +14 -7
  429. package/src/plugins/plugin-execution-context.ts +35 -11
  430. package/src/plugins/worker-plugin-surface.ts +33 -0
  431. package/src/prompts/templates/system-sections.ts +0 -7
  432. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  433. package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
  434. package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
  435. package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
  436. package/src/providers/__tests__/retry-callsite.test.ts +25 -2
  437. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  438. package/src/providers/anthropic/client.ts +332 -241
  439. package/src/providers/call-site-routing.ts +30 -3
  440. package/src/providers/connection-resolution.ts +194 -11
  441. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +4 -0
  442. package/src/providers/inference/adapter-factory.ts +3 -0
  443. package/src/providers/inference/auth.ts +6 -0
  444. package/src/providers/inference/connection-availability.ts +24 -2
  445. package/src/providers/inference/connections.ts +2 -0
  446. package/src/providers/model-catalog.ts +3 -3
  447. package/src/providers/model-intents.ts +28 -8
  448. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +163 -40
  449. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  450. package/src/providers/openai/chat-completions-provider.ts +133 -36
  451. package/src/providers/openai/codex-models.ts +2 -1
  452. package/src/providers/provider-send-message.ts +32 -3
  453. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
  454. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +625 -0
  455. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
  456. package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
  457. package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
  458. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +679 -0
  459. package/src/providers/speech-to-text/provider-catalog.ts +99 -8
  460. package/src/providers/speech-to-text/resolve.ts +25 -2
  461. package/src/routes/control.ts +1 -0
  462. package/src/routes/route-host-client.ts +1 -0
  463. package/src/routes/route-host-protocol.ts +7 -0
  464. package/src/routes/worker.ts +48 -15
  465. package/src/runtime/AGENTS.md +16 -17
  466. package/src/runtime/access-request-helper.ts +9 -12
  467. package/src/runtime/agent-wake.ts +18 -15
  468. package/src/runtime/effective-capabilities.test.ts +43 -0
  469. package/src/runtime/effective-capabilities.ts +27 -0
  470. package/src/runtime/guardian-reply-router.ts +6 -2
  471. package/src/runtime/pre-first-message-gate.ts +4 -0
  472. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
  473. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +522 -2
  474. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
  475. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
  476. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
  477. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
  478. package/src/runtime/routes/__tests__/plugins-routes.test.ts +132 -35
  479. package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
  480. package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
  481. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  482. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  483. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +133 -1
  484. package/src/runtime/routes/channel-availability-routes.ts +32 -14
  485. package/src/runtime/routes/channel-route-shared.ts +0 -6
  486. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
  487. package/src/runtime/routes/conversation-list-routes.ts +181 -23
  488. package/src/runtime/routes/conversation-management-routes.ts +17 -12
  489. package/src/runtime/routes/conversation-query-routes.ts +40 -27
  490. package/src/runtime/routes/conversation-routes.ts +43 -29
  491. package/src/runtime/routes/credential-prompt-routes.ts +4 -7
  492. package/src/runtime/routes/credential-routes.ts +26 -82
  493. package/src/runtime/routes/default-provider-routes.ts +15 -0
  494. package/src/runtime/routes/documents-routes.ts +3 -222
  495. package/src/runtime/routes/inbound-message-handler.ts +21 -45
  496. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
  497. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
  498. package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
  499. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
  500. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
  501. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -79
  502. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
  503. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -7
  504. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -8
  505. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -79
  506. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
  507. package/src/runtime/routes/inference-profiles-routes.ts +20 -11
  508. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
  509. package/src/runtime/routes/log-export-routes.ts +3 -0
  510. package/src/runtime/routes/mcp-auth-routes.ts +148 -57
  511. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
  512. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
  513. package/src/runtime/routes/playground/inject-failures.ts +2 -2
  514. package/src/runtime/routes/playground/reset-circuit.ts +1 -1
  515. package/src/runtime/routes/plugins-routes.ts +38 -8
  516. package/src/runtime/routes/schedule-routes.ts +94 -7
  517. package/src/runtime/routes/settings-routes.ts +6 -6
  518. package/src/runtime/routes/stt-routes.ts +31 -25
  519. package/src/runtime/routes/surface-action-routes.ts +20 -19
  520. package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
  521. package/src/runtime/routes/user-route-dispatcher.ts +69 -15
  522. package/src/runtime/routes/user-route-import.ts +108 -0
  523. package/src/runtime/routes/user-route-resolution.ts +18 -1
  524. package/src/runtime/routes/workspace-routes.ts +0 -9
  525. package/src/runtime/routes/workspace-utils.ts +3 -13
  526. package/src/runtime/services/conversation-serializer.ts +7 -2
  527. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
  528. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
  529. package/src/schedule/plugin-schedule-availability.ts +58 -0
  530. package/src/schedule/plugin-schedule-declarations.ts +23 -27
  531. package/src/schedule/plugin-schedule-reconciler.ts +12 -3
  532. package/src/schedule/schedule-store.ts +5 -1
  533. package/src/schedule/scheduler.ts +9 -4
  534. package/src/schedule/worker-control.ts +1 -0
  535. package/src/schedule/worker.ts +6 -0
  536. package/src/security/oauth2.ts +6 -22
  537. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
  538. package/src/stt/__tests__/types.test.ts +94 -0
  539. package/src/stt/daemon-batch-transcriber.ts +10 -0
  540. package/src/stt/stt-stream-session.ts +8 -4
  541. package/src/stt/types.ts +103 -0
  542. package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
  543. package/src/subagent/consult-context.ts +11 -11
  544. package/src/subagent/consult-prompt.ts +26 -35
  545. package/src/subagent/manager.ts +21 -40
  546. package/src/subagent/notify.ts +7 -1
  547. package/src/subagent/types.ts +15 -12
  548. package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
  549. package/src/tools/acp/spawn.test.ts +97 -0
  550. package/src/tools/acp/spawn.ts +38 -4
  551. package/src/tools/credentials/ref-parse.ts +35 -0
  552. package/src/tools/credentials/resolve.ts +4 -10
  553. package/src/tools/credentials/store.ts +168 -0
  554. package/src/tools/document/document-tool.ts +12 -3
  555. package/src/tools/executor.ts +53 -15
  556. package/src/tools/filesystem/read.ts +27 -10
  557. package/src/tools/host-filesystem/read.ts +27 -15
  558. package/src/tools/network/url-safety.ts +9 -16
  559. package/src/tools/permission-checker.ts +43 -52
  560. package/src/tools/registry.ts +2 -1
  561. package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
  562. package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
  563. package/src/tools/shared/filesystem/types.ts +5 -5
  564. package/src/tools/skills/scaffold-managed.ts +25 -7
  565. package/src/tools/subagent/spawn.ts +28 -88
  566. package/src/tools/tool-types.ts +26 -13
  567. package/src/tools/types.ts +6 -6
  568. package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
  569. package/src/tools/workflows/run-workflow.ts +1 -2
  570. package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
  571. package/src/tts/reasoning-tag-filter.ts +89 -0
  572. package/src/util/__tests__/worker-process-command.test.ts +37 -0
  573. package/src/util/logger.ts +16 -0
  574. package/src/util/think-tag-stream.ts +95 -0
  575. package/src/util/worker-process.ts +37 -4
  576. package/src/windows-compiled-cli.ts +32 -0
  577. package/src/windows-compiled-entry.ts +4 -0
  578. package/src/windows-compiled-logger.ts +6 -0
  579. package/src/windows-compiled-worker-entry.ts +29 -0
  580. package/src/workspace/byok-default-profile-ensure.ts +76 -24
  581. package/src/workspace/custom-profile-ensure.ts +4 -24
  582. package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
  583. package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
  584. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
  585. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
  586. package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
  587. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
  588. package/src/workspace/migrations/registry.ts +10 -0
  589. package/src/workspace/provider-commit-message-generator.ts +7 -5
  590. package/src/__tests__/document-workspace-file.test.ts +0 -467
  591. package/src/live-voice/__tests__/front-decision.test.ts +0 -645
  592. package/src/live-voice/front-decision.ts +0 -476
  593. package/src/permissions/ipc-risk-types.ts +0 -143
  594. package/src/permissions/risk-types.ts +0 -76
  595. package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
  596. package/src/subagent/consult-transcript.ts +0 -90
@@ -45,9 +45,16 @@ export const CALL_SITE_DEFAULTS: Record<LLMCallSite, CallSiteDefaultConfig> = {
45
45
  profile: "cost-optimized",
46
46
  contextWindow: { maxInputTokens: 1000000 },
47
47
  },
48
+ // Forced-tool selection over a numbered candidate pool: the model picks ids,
49
+ // it does not reason its way to an answer. `effort` has to be named here
50
+ // because a call-site tweak only overrides the fields it lists, so leaving it
51
+ // off inherits `balanced`'s `effort: "high"` (see default-profile-catalog).
52
+ // Low effort matches `recall`, the sibling site doing the same kind of
53
+ // bounded judgment, and keeps retrieval latency low on escalated voice turns.
48
54
  memoryV3SelectL2: {
49
55
  profile: "balanced",
50
56
  temperature: 0,
57
+ effort: "low",
51
58
  thinking: { enabled: false, streamThinking: false },
52
59
  },
53
60
  recall: {
@@ -121,22 +128,21 @@ export const CALL_SITE_DEFAULTS: Record<LLMCallSite, CallSiteDefaultConfig> = {
121
128
  effort: "low",
122
129
  thinking: { enabled: false },
123
130
  },
124
- // Endpoint decisions gate live-voice turn-end latency, and `cost-optimized`'s
125
- // upstream cannot fit any usable decision budget (~1s+ per forced tool call).
131
+ // Progress narration only helps when it arrives before the next real output.
126
132
  // `latency-optimized` is the latency-class profile (see
127
133
  // default-profile-catalog.ts): managed installs get the pinned latency model,
128
134
  // BYOK installs resolve their own provider's latency model through the intent
129
135
  // table rather than a model id they may hold no credential for. The profile
130
136
  // is user-facing ("Speed"), so a user edit to it moves this call site too.
131
- voiceFrontDecision: {
137
+ voiceProgressNarration: {
132
138
  profile: "latency-optimized",
133
139
  effort: "low",
134
140
  thinking: { enabled: false },
135
141
  },
136
142
  // The front-door leg fronts EVERY unified live-voice turn and its leading
137
143
  // tokens ARE the endpointing/triage verdict, so both TTFT variance and
138
- // judgment quality gate the whole call. Same latency class as the endpoint
139
- // decider: live drives showed the cost-optimized upstream with multi-second
144
+ // judgment quality gate the whole call. Live drives showed the
145
+ // cost-optimized upstream with multi-second
140
146
  // cross-session TTFT tails and over-escalation of small talk under open-task
141
147
  // context pressure.
142
148
  voiceFrontDoor: {
@@ -4,8 +4,10 @@ import {
4
4
  isModelInCatalog,
5
5
  } from "../providers/model-catalog.js";
6
6
  import { resolveModelIntent } from "../providers/model-intents.js";
7
+ import { isCodexSubscriptionModel } from "../providers/openai/codex-models.js";
7
8
  import type { ModelIntent } from "../providers/types.js";
8
9
  import { getManagedUpstream } from "../providers/vellum-model-routing.js";
10
+ import { getBalancedModelExperimentArm } from "./balanced-model-experiment.js";
9
11
  import {
10
12
  DEFAULT_PROFILE_KEYS,
11
13
  DEFAULT_PROFILE_PROVIDERS,
@@ -31,7 +33,8 @@ import {
31
33
  * structured as an intent × provider matrix: each default profile is an
32
34
  * intent, and each provider that can serve default profiles has a concrete
33
35
  * implementation of that intent (model, token budget, effort, thinking).
34
- * The `vellum` column is the platform-managed implementation; the other
36
+ * The `vellum` column is the platform-managed implementation and the
37
+ * `chatgpt` column is the ChatGPT-subscription implementation; the other
35
38
  * columns are the BYOK implementations resolved through `llm.defaultProvider`
36
39
  * on off-platform installs.
37
40
  *
@@ -96,7 +99,7 @@ const VELLUM_PROFILE_IMPLS: ProfileImpls = {
96
99
  },
97
100
  },
98
101
  "cost-optimized": {
99
- model: "accounts/fireworks/models/deepseek-v4-flash",
102
+ model: "accounts/fireworks/models/deepseek-v4-flash-0731",
100
103
  provider: "vellum",
101
104
  source: "managed",
102
105
  label: "Cost",
@@ -147,6 +150,108 @@ const VELLUM_PROFILE_IMPLS: ProfileImpls = {
147
150
  },
148
151
  };
149
152
 
153
+ /**
154
+ * Arm to managed model pin for the `experiment-balanced-model-2026-08-06` A/B
155
+ * test (`balanced-model-experiment.ts` owns the flag read). An arm repoints
156
+ * the model of the managed (`vellum`) implementation of `balanced` and nothing
157
+ * else: effort, thinking, token budget, label and description all stay on the
158
+ * shipped body, and the `chatgpt` and BYOK columns are untouched because those
159
+ * installs run the provider their user chose and sit outside the experiment.
160
+ *
161
+ * `control` is absent by design. It, an arm this build does not know, and an
162
+ * unset flag all resolve to the shipped body, so no LaunchDarkly value can
163
+ * strand an install on a model that is not pinned here. A `Map` rather than an
164
+ * object literal keeps that true for every string LaunchDarkly can send: the
165
+ * arm is remote input, and an object lookup would resolve `constructor` or
166
+ * `toString` to an inherited `Object.prototype` member instead of missing.
167
+ *
168
+ * `glm-5p2` is text-only: Balanced does not pass `doesSupportVision` on that
169
+ * arm, so image input routes through the image-fallback captioning plugin.
170
+ */
171
+ const BALANCED_EXPERIMENT_MODELS = new Map<string, string>([
172
+ ["terra", "gpt-5.6-terra"],
173
+ ["glm-5p2", "accounts/fireworks/models/glm-5p2"],
174
+ ]);
175
+
176
+ /**
177
+ * The managed (`vellum`) implementation of a default profile, carrying the
178
+ * balanced-model experiment arm. Resolved per call rather than materialized
179
+ * once: the gateway pushes flag changes to a running daemon, so the arm can
180
+ * move under a live process.
181
+ */
182
+ function managedProfileImpl(key: DefaultProfileKey): DefaultProfileTemplate {
183
+ const impl = VELLUM_PROFILE_IMPLS[key];
184
+ if (key !== "balanced") {
185
+ return impl;
186
+ }
187
+ const arm = getBalancedModelExperimentArm();
188
+ const model = arm == null ? undefined : BALANCED_EXPERIMENT_MODELS.get(arm);
189
+ return model == null ? impl : { ...impl, model };
190
+ }
191
+
192
+ /**
193
+ * The `chatgpt` column: ChatGPT-subscription implementations, stamped
194
+ * `provider: "chatgpt"` so dispatch routes through the canonical
195
+ * `chatgpt-subscription` row via `resolveRoutingIdentity` with no pinned
196
+ * connection. Models are pinned (never intents): the intent tables are
197
+ * keyed by concrete dispatch providers, and the Codex endpoint serves only
198
+ * `CODEX_SUBSCRIPTION_MODEL_IDS`. Cost and Speed are identical
199
+ * implementations here: the subscription serves no tier cheaper or faster
200
+ * than luna, and both profiles advertise reasoning off.
201
+ */
202
+ const CHATGPT_PROFILE_IMPLS: ProfileImpls = {
203
+ balanced: {
204
+ model: "gpt-5.6-luna",
205
+ provider: "chatgpt",
206
+ source: "managed",
207
+ label: "Balanced",
208
+ description: "Good balance of quality, cost, and speed",
209
+ // Matches the vellum column's Balanced (same model): the Codex path
210
+ // sends no max_output_tokens, so this only sizes internal budgeting.
211
+ maxTokens: 32000,
212
+ effort: "high",
213
+ thinking: { enabled: true, streamThinking: true },
214
+ contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
215
+ },
216
+ "quality-optimized": {
217
+ model: "gpt-5.6-sol",
218
+ provider: "chatgpt",
219
+ source: "managed",
220
+ label: "Quality",
221
+ description: "Best results with the most capable model",
222
+ maxTokens: 32000,
223
+ effort: "high",
224
+ thinking: { enabled: true, streamThinking: true },
225
+ contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
226
+ },
227
+ "cost-optimized": {
228
+ model: "gpt-5.6-luna",
229
+ provider: "chatgpt",
230
+ source: "managed",
231
+ label: "Cost",
232
+ description: "Cheapest responses, for high-volume work",
233
+ maxTokens: 8192,
234
+ effort: "none",
235
+ thinking: { enabled: false, streamThinking: false },
236
+ contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
237
+ },
238
+ "latency-optimized": {
239
+ model: "gpt-5.6-luna",
240
+ provider: "chatgpt",
241
+ source: "managed",
242
+ label: "Speed",
243
+ description: "Fastest responses, with reasoning turned off",
244
+ maxTokens: 8192,
245
+ // Explicit reasoning opt-out, matching the other columns: this profile
246
+ // advertises reasoning as off, and OpenAI-compat APIs default reasoning
247
+ // to "medium" when the field is omitted, so the opt-out has to be stated
248
+ // rather than implied.
249
+ effort: "none",
250
+ thinking: { enabled: false, streamThinking: false },
251
+ contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
252
+ },
253
+ };
254
+
150
255
  /**
151
256
  * The BYOK implementation of each default profile intent, shared by every
152
257
  * non-vellum provider. The concrete model resolves per provider from the
@@ -219,7 +324,9 @@ export const PROFILE_IMPLS: Record<
219
324
  provider,
220
325
  provider === "vellum"
221
326
  ? VELLUM_PROFILE_IMPLS[key]
222
- : { ...BYOK_PROFILE_IMPLS[key], provider },
327
+ : provider === "chatgpt"
328
+ ? CHATGPT_PROFILE_IMPLS[key]
329
+ : { ...BYOK_PROFILE_IMPLS[key], provider },
223
330
  ]),
224
331
  ) as Record<DefaultProfileProvider, DefaultProfileTemplate>,
225
332
  ]),
@@ -302,9 +409,9 @@ export const OS_BETA_PROFILE_TEMPLATE: DefaultProfileTemplate = {
302
409
  /**
303
410
  * Profiles whose body is code-owned outright: no workspace overlay, and no
304
411
  * user-owned shadow. The shadow rule below lets a user replace a default they
305
- * can select, but `latency-optimized` fronts every live-voice turn through
306
- * `voiceFrontDecision`/`voiceFrontDoor`, where a model outside the latency
307
- * envelope is audible dead air rather than a slow reply. A same-named
412
+ * can select, but `latency-optimized` serves `voiceFrontDoor` and
413
+ * `voiceProgressNarration`, where a model outside the latency envelope is
414
+ * audible dead air rather than a slow reply. A same-named
308
415
  * workspace entry stays on disk and stays listed; it just never governs what
309
416
  * this name resolves to.
310
417
  */
@@ -372,21 +479,23 @@ for (const key of DEFAULT_PROFILE_KEYS) {
372
479
  `PROFILE_IMPLS[${key}][${provider}] must set exactly one of \`intent\` or \`model\`.`,
373
480
  );
374
481
  }
375
- if (impl.provider === "vellum" && impl.model == null) {
482
+ if (ROUTING_IDENTITY_PROVIDERS.has(impl.provider) && impl.model == null) {
376
483
  throw new Error(
377
- `PROFILE_IMPLS[${key}][${provider}] must pin a \`model\`: the vellum ` +
378
- `column has no intent table, and the model selects the upstream.`,
484
+ `PROFILE_IMPLS[${key}][${provider}] must pin a \`model\`: routing ` +
485
+ `identities have no intent table, and the model selects the route.`,
379
486
  );
380
487
  }
381
488
  if (impl.model != null) {
382
489
  const routable =
383
490
  impl.provider === "vellum"
384
491
  ? getManagedUpstream(impl.model) !== null
385
- : isModelInCatalog(impl.provider, impl.model);
492
+ : impl.provider === "chatgpt"
493
+ ? isCodexSubscriptionModel(impl.model)
494
+ : isModelInCatalog(impl.provider, impl.model);
386
495
  if (!routable) {
387
496
  throw new Error(
388
497
  `PROFILE_IMPLS[${key}][${provider}] references model "${impl.model}" ` +
389
- `which is not ${impl.provider === "vellum" ? "served by any managed upstream" : `in PROVIDER_CATALOG for provider "${impl.provider}"`}. ` +
498
+ `which is not ${impl.provider === "vellum" ? "served by any managed upstream" : impl.provider === "chatgpt" ? "in CODEX_SUBSCRIPTION_MODEL_IDS" : `in PROVIDER_CATALOG for provider "${impl.provider}"`}. ` +
390
499
  `Update model-catalog.ts or default-profile-catalog.ts.`,
391
500
  );
392
501
  }
@@ -394,6 +503,19 @@ for (const key of DEFAULT_PROFILE_KEYS) {
394
503
  }
395
504
  }
396
505
 
506
+ // The experiment arms substitute into the managed column at request time, so
507
+ // they need the same routability guarantee as the pins validated above: a
508
+ // LaunchDarkly arm must never select a model no managed upstream serves.
509
+ for (const [arm, model] of BALANCED_EXPERIMENT_MODELS) {
510
+ if (getManagedUpstream(model) === null) {
511
+ throw new Error(
512
+ `BALANCED_EXPERIMENT_MODELS["${arm}"] references model "${model}" which ` +
513
+ `is not served by any managed upstream. ` +
514
+ `Update model-catalog.ts or default-profile-catalog.ts.`,
515
+ );
516
+ }
517
+ }
518
+
397
519
  // Provider choices without a named column materialize from the shared BYOK
398
520
  // templates; verify each one's resolved model lands in the catalog.
399
521
  for (const provider of DEFAULT_PROVIDER_CHOICES) {
@@ -432,6 +554,14 @@ function buildDefaultProfileEntries(): Record<string, ProfileEntry> {
432
554
  * The materialized code-default bodies keyed by profile name — the
433
555
  * code-owned content a managed-source workspace entry resolves to. These are
434
556
  * the `vellum` column (the managed implementations).
557
+ *
558
+ * Materialized once at module load, so this is the shipped catalog: the
559
+ * balanced-model experiment arm is applied by the provider-aware resolvers
560
+ * (`resolveDefaultProfileForProvider`, `getEffectiveProfilesForProvider`),
561
+ * which are what every runtime and client-facing reader of a default profile's
562
+ * content goes through. The name-only readers that serve from this record
563
+ * (`getEffectiveProfile`, `getEffectiveProfiles`) consume a profile's
564
+ * existence and status, never its model.
435
565
  */
436
566
  export const CODE_DEFAULT_PROFILE_ENTRIES: Readonly<
437
567
  Record<string, ProfileEntry>
@@ -517,7 +647,9 @@ function resolveAgainstBody(
517
647
  * Non-obvious rules:
518
648
  *
519
649
  * - The `vellum` column stamps `provider: "vellum"` with no connection —
520
- * dispatch derives the upstream from the model per-request.
650
+ * dispatch derives the upstream from the model per-request. The `chatgpt`
651
+ * column likewise stamps its routing identity with no connection;
652
+ * dispatch resolves the canonical subscription row per-request.
521
653
  * - A default provider without a named matrix column materializes from the
522
654
  * shared `BYOK_PROFILE_IMPLS` templates, with `resolveModelIntent`
523
655
  * falling back to the provider's catalog `defaultModel`.
@@ -543,17 +675,48 @@ export function isDefaultProfileKey(name: string): name is DefaultProfileKey {
543
675
  return (DEFAULT_PROFILE_KEYS as readonly string[]).includes(name);
544
676
  }
545
677
 
678
+ /**
679
+ * The implementation of default profile `key` on `provider`: the named matrix
680
+ * column when the provider has one, the shared BYOK template otherwise. The
681
+ * managed column carries the balanced-model experiment arm, which is why the
682
+ * lookup runs through here rather than reading `PROFILE_IMPLS` directly.
683
+ */
684
+ function defaultProfileImplForProvider(
685
+ key: DefaultProfileKey,
686
+ provider: NonNullable<ProfileEntry["provider"]>,
687
+ ): DefaultProfileTemplate {
688
+ if (!isDefaultProfileProvider(provider)) {
689
+ return { ...BYOK_PROFILE_IMPLS[key], provider };
690
+ }
691
+ if (provider === "vellum") {
692
+ return managedProfileImpl(key);
693
+ }
694
+ return PROFILE_IMPLS[key][provider];
695
+ }
696
+
697
+ /**
698
+ * The code-owned body a default profile name resolves to under the given
699
+ * default provider. This is the single choke point where a default profile key
700
+ * becomes a concrete body, so every consumer (the runtime resolver through
701
+ * `resolveDefaultProfileForProvider`, the client-facing listing through
702
+ * `getEffectiveProfilesForProvider`) reports the same model the request runs
703
+ * on, experiment arm included.
704
+ */
546
705
  function defaultProfileBodyForProvider(
547
706
  name: string,
548
707
  defaultProvider: DefaultProviderConfig | null,
549
708
  ): ProfileEntry | undefined {
550
- if (defaultProvider == null || !isDefaultProfileKey(name)) {
709
+ if (!isDefaultProfileKey(name)) {
551
710
  return CODE_DEFAULT_PROFILE_ENTRIES[name];
552
711
  }
712
+ if (defaultProvider == null) {
713
+ // The frozen `CODE_DEFAULT_PROFILE_ENTRIES` body, re-materialized so an
714
+ // install that predates `llm.defaultProvider` still sees the arm.
715
+ const managed = managedProfileImpl(name);
716
+ return materializeProfile(managed, managed.provider);
717
+ }
553
718
  const { provider } = defaultProvider;
554
- const impl = isDefaultProfileProvider(provider)
555
- ? PROFILE_IMPLS[name][provider]
556
- : { ...BYOK_PROFILE_IMPLS[name], provider };
719
+ const impl = defaultProfileImplForProvider(name, provider);
557
720
  return clampMaxTokensToModelCap({
558
721
  ...materializeProfile(
559
722
  impl,
@@ -38,7 +38,9 @@ export const OS_BETA_PROFILE_KEY = "os-beta";
38
38
  /**
39
39
  * The named columns of the intent × provider matrix. `vellum` is the
40
40
  * platform-managed column (routed through the single `vellum` connection to
41
- * an underlying provider per profile); the rest are BYOK columns whose
41
+ * an underlying provider per profile) and `chatgpt` is the
42
+ * ChatGPT-subscription column (routed through the `chatgpt-subscription`
43
+ * connection to the Codex endpoint); the rest are BYOK columns whose
42
44
  * models resolve per provider via `resolveModelIntent`. The full set of
43
45
  * providers that can back `llm.defaultProvider` is wider, see
44
46
  * `DEFAULT_PROVIDER_CHOICES` in `schemas/llm.ts`.
@@ -53,6 +55,7 @@ export const DEFAULT_PROFILE_PROVIDERS = [
53
55
  "gemini",
54
56
  "fireworks",
55
57
  "openrouter",
58
+ "chatgpt",
56
59
  "vellum",
57
60
  ] as const;
58
61
  export type DefaultProfileProvider = (typeof DEFAULT_PROFILE_PROVIDERS)[number];
@@ -6,6 +6,7 @@
6
6
  * conveniences (`getDefaultProvider()` without an argument,
7
7
  * `setDefaultProvider`) live in `default-provider.ts`.
8
8
  */
9
+ import { CHATGPT_SUBSCRIPTION_CONNECTION_NAME } from "../providers/inference/auth.js";
9
10
  import { VELLUM_MANAGED_CONNECTION_NAME } from "../providers/vellum-model-routing.js";
10
11
  import type { DefaultProviderConfig } from "./schemas/llm.js";
11
12
  import type { AssistantConfig } from "./types.js";
@@ -29,5 +30,8 @@ export function resolveDefaultConnectionName(
29
30
  if (dp.provider === "vellum") {
30
31
  return VELLUM_MANAGED_CONNECTION_NAME;
31
32
  }
33
+ if (dp.provider === "chatgpt") {
34
+ return CHATGPT_SUBSCRIPTION_CONNECTION_NAME;
35
+ }
32
36
  return `${dp.provider}-personal`;
33
37
  }
@@ -36,15 +36,6 @@
36
36
  "defaultEnabled": "control",
37
37
  "values": ["control", "variant-a", "personal-page"]
38
38
  },
39
- {
40
- "id": "experiment-billing-cta-2026-07-23",
41
- "scope": "client",
42
- "key": "experiment-billing-cta-2026-07-23",
43
- "label": "Experiment: Billing CTA 2026-07-23",
44
- "description": "Credit paywall CTA experiment. control = single Add Credits CTA for everyone (opens Add Credits modal). upgrade-cta = FREE users (plan_id base) get a single Upgrade CTA (View Plans takeover) instead; PAID users still get Add Credits.",
45
- "defaultEnabled": "control",
46
- "values": ["control", "upgrade-cta"]
47
- },
48
39
  {
49
40
  "id": "local-docker-enabled",
50
41
  "scope": "client",
@@ -247,10 +238,10 @@
247
238
  },
248
239
  {
249
240
  "id": "web-remote-ingress",
250
- "scope": "assistant",
241
+ "scope": "client",
251
242
  "key": "web-remote-ingress",
252
243
  "label": "Web Remote Ingress",
253
- "description": "Serve the web client over a public tunnel and enable browser pairing for self-hosted assistants.",
244
+ "description": "Show the pair-a-device card in web settings. Visibility only; pairing itself is always available.",
254
245
  "defaultEnabled": false
255
246
  },
256
247
  {
@@ -371,7 +362,16 @@
371
362
  "key": "plugin-schedules",
372
363
  "label": "Plugin Schedules",
373
364
  "description": "Plugins declare recurring schedules as files that register and run automatically",
374
- "defaultEnabled": false
365
+ "defaultEnabled": true
366
+ },
367
+ {
368
+ "id": "experiment-balanced-model-2026-08-06",
369
+ "scope": "assistant",
370
+ "key": "experiment-balanced-model-2026-08-06",
371
+ "label": "Experiment: Balanced Profile Model",
372
+ "description": "Multivariate experiment on the model the managed Balanced inference profile resolves to. control = gpt-5.6-luna (the shipped pin); terra = gpt-5.6-terra; glm-5p2 = accounts/fireworks/models/glm-5p2. Only the managed (vellum) implementation moves: ChatGPT-subscription and BYOK installs run the provider the user chose and are outside the experiment. Rollout and targeting are managed in the LaunchDarkly dashboard; any value that is not an arm name resolves to control.",
373
+ "defaultEnabled": "control",
374
+ "values": ["control", "terra", "glm-5p2"]
375
375
  }
376
376
  ]
377
377
  }
@@ -1,3 +1,4 @@
1
+ import { resolveEntryProviderKind } from "../providers/connection-resolution.js";
1
2
  import { ROUTING_IDENTITY_PROVIDERS } from "../providers/inference/auth.js";
2
3
  import {
3
4
  getCatalogProviderForModel,
@@ -54,11 +55,18 @@ export function resolveEffectiveContextWindow({
54
55
  forceOverrideProfile,
55
56
  selectionSeed,
56
57
  });
57
- // Routing identities are not catalog providers; the model's catalog owner
58
- // carries its context-window limits.
58
+ // Routing identities dispatch built-in catalog models, so the model's
59
+ // catalog owner carries their limits. An entry-name provider resolves
60
+ // through its row's kind instead: a custom-endpoint kind has no catalog
61
+ // models, so its models keep the conservative default even when a model
62
+ // id collides with a built-in one (the custom endpoint's "gpt-5.5" is not
63
+ // OpenAI's). Labels with no row fall back to the model's catalog owner.
59
64
  const catalogProviderId = ROUTING_IDENTITY_PROVIDERS.has(resolved.provider)
60
65
  ? getCatalogProviderForModel(resolved.model)
61
- : resolved.provider;
66
+ : PROVIDER_CATALOG.some((p) => p.id === resolved.provider)
67
+ ? resolved.provider
68
+ : (resolveEntryProviderKind(resolved.provider, resolved.model) ??
69
+ getCatalogProviderForModel(resolved.model));
62
70
  const catalogModel = PROVIDER_CATALOG.find(
63
71
  (provider) => provider.id === catalogProviderId,
64
72
  )?.models.find((model) => model.id === resolved.model);
@@ -76,6 +76,15 @@ import {
76
76
  */
77
77
  export interface ResolveCallSiteOpts {
78
78
  overrideProfile?: string;
79
+ /**
80
+ * Whether a profile's provider value can actually dispatch: a known
81
+ * vendor, or a connection entry row. Selection is pure and DB-blind, so
82
+ * dispatch-side callers supply this (see `dispatchProviderResolvable` in
83
+ * `connection-resolution.ts`); a profile failing it is unusable and
84
+ * selection falls through to the next rung, keeping model and transport
85
+ * coherent. When absent, every provider is assumed resolvable.
86
+ */
87
+ isResolvableProvider?: (provider: string) => boolean;
79
88
  /**
80
89
  * Float `overrideProfile` above the call-site layers for non-main-agent call
81
90
  * sites. Retained for API compatibility; under single-winner selection the
@@ -112,7 +121,11 @@ export interface ResolveCallSiteOpts {
112
121
  }) => void;
113
122
  }
114
123
 
115
- export type ResolutionFallbackReason = "missing" | "disabled" | "incomplete";
124
+ export type ResolutionFallbackReason =
125
+ | "missing"
126
+ | "disabled"
127
+ | "incomplete"
128
+ | "unresolvable";
116
129
 
117
130
  export interface ResolvedCallSiteConfig {
118
131
  config: z.infer<typeof LLMConfigBase>;
@@ -313,6 +326,20 @@ function usableEntry(
313
326
  report(name, "incomplete");
314
327
  return undefined;
315
328
  }
329
+ // A provider the caller cannot resolve (not a known vendor, not an entry
330
+ // row) makes the profile unusable, and selection falls through to the
331
+ // next rung the same way an incomplete profile does: the fallback keeps
332
+ // model and transport coherent, where a dispatch-time fallback would pair
333
+ // this profile's model with the default transport. Selection is DB-blind,
334
+ // so the predicate is supplied by dispatch-side callers; when absent,
335
+ // every provider is assumed resolvable.
336
+ if (
337
+ opts.isResolvableProvider != null &&
338
+ !opts.isResolvableProvider(entry.provider)
339
+ ) {
340
+ report(name, "unresolvable");
341
+ return undefined;
342
+ }
316
343
  return { name, entry };
317
344
  }
318
345
 
@@ -1,9 +1,12 @@
1
+ import { getDb } from "../persistence/db-connection.js";
1
2
  import { ROUTING_IDENTITY_PROVIDERS } from "../providers/inference/auth.js";
3
+ import { getConnection } from "../providers/inference/connections.js";
2
4
  import {
3
5
  getCatalogProviderForModel,
4
6
  isModelInCatalog,
5
7
  } from "../providers/model-catalog.js";
6
8
  import {
9
+ getManagedUpstream,
7
10
  MANAGED_ROUTABLE_PROVIDERS,
8
11
  VELLUM_MANAGED_CONNECTION_NAME,
9
12
  } from "../providers/vellum-model-routing.js";
@@ -97,35 +100,80 @@ export function completeCustomProfile(
97
100
  }
98
101
  }
99
102
 
100
- // A provider-specific connection baked onto a different provider would pin
101
- // a mismatch (dispatch auto-resolves an absent connection by provider
102
- // instead). The Vellum managed connection must survive a provider change —
103
- // dispatch routes it via `expectedProvider` — but only onto providers it
104
- // can actually route; a non-managed-routable provider (openrouter, ollama)
105
- // would hit the mismatch path instead of auto-resolution.
106
- // Routing-identity providers resolve their connection per-request from
107
- // the provider value; a stamped provider_connection would be dead weight
108
- // at best and a misroute at worst.
103
+ // Completion never stamps a `provider_connection`; an inherited binding
104
+ // would only re-introduce the collapsed field on disk. The default's
105
+ // explicit binding is instead inherited IN the provider value, the
106
+ // entries-model representation: the vellum binding becomes the routing
107
+ // identity (only when it can serve the model, or the read-path schema
108
+ // would strip the profile), and a same-vendor binding becomes the entry
109
+ // name, so a completed profile keeps signing with the credential the
110
+ // workspace default names rather than whatever auto-resolution finds
111
+ // first.
109
112
  if (
110
113
  completed.provider !== undefined &&
111
- ROUTING_IDENTITY_PROVIDERS.has(completed.provider)
114
+ !ROUTING_IDENTITY_PROVIDERS.has(completed.provider) &&
115
+ profile.provider_connection === undefined &&
116
+ dflt.provider_connection !== undefined
112
117
  ) {
113
- return structuredClone(completed);
118
+ if (dflt.provider_connection === VELLUM_MANAGED_CONNECTION_NAME) {
119
+ // Only managed-servable pairs inherit the managed binding; anything
120
+ // else inherits nothing and lets dispatch auto-resolve by vendor,
121
+ // matching the pre-entries inheritance contract.
122
+ if (
123
+ MANAGED_ROUTABLE_PROVIDERS.has(completed.provider) &&
124
+ completed.model !== undefined &&
125
+ getManagedUpstream(completed.model) !== null
126
+ ) {
127
+ completed.provider = "vellum";
128
+ }
129
+ } else if (completed.provider === dflt.provider) {
130
+ // Folding to the entry name is only safe against a verified row; a
131
+ // dangling or kind-disagreeing binding stays in the legacy field,
132
+ // where the collapse migration's recovery can judge it.
133
+ if (bindingRowKind(dflt.provider_connection) === completed.provider) {
134
+ completed.provider = dflt.provider_connection;
135
+ } else {
136
+ completed.provider_connection = dflt.provider_connection;
137
+ }
138
+ }
114
139
  }
140
+ return structuredClone(completed);
141
+ }
115
142
 
116
- const vellumRoutable =
117
- dflt.provider_connection === VELLUM_MANAGED_CONNECTION_NAME &&
118
- completed.provider !== undefined &&
119
- MANAGED_ROUTABLE_PROVIDERS.has(completed.provider);
120
- if (
121
- profile.provider_connection === undefined &&
122
- dflt.provider_connection !== undefined &&
123
- (completed.provider === dflt.provider || vellumRoutable)
124
- ) {
125
- completed.provider_connection = dflt.provider_connection;
143
+ /**
144
+ * `{...raw, ...completed}` recursively: completed (schema-known) values win,
145
+ * raw keys the schema stripped survive at every depth. Used by boot
146
+ * materialization and the config write path so both preserve unknown keys
147
+ * the same way after `safeParse`.
148
+ */
149
+ export function mergePreservingUnknownKeys(
150
+ raw: Record<string, unknown>,
151
+ completed: Record<string, unknown>,
152
+ ): Record<string, unknown> {
153
+ const out: Record<string, unknown> = { ...raw, ...completed };
154
+ for (const [key, value] of Object.entries(completed)) {
155
+ const rawValue = raw[key];
156
+ if (isRecord(value) && isRecord(rawValue)) {
157
+ out[key] = mergePreservingUnknownKeys(rawValue, value);
158
+ }
126
159
  }
160
+ return out;
161
+ }
127
162
 
128
- return structuredClone(completed);
163
+ function isRecord(value: unknown): value is Record<string, unknown> {
164
+ return value !== null && typeof value === "object" && !Array.isArray(value);
165
+ }
166
+
167
+ /**
168
+ * The provider kind stored on a connection row, or null when the row is
169
+ * missing or the DB is unavailable (both mean "unverifiable" to callers).
170
+ */
171
+ function bindingRowKind(name: string): string | null {
172
+ try {
173
+ return getConnection(getDb(), name)?.provider ?? null;
174
+ } catch {
175
+ return null;
176
+ }
129
177
  }
130
178
 
131
179
  type PlainObject = Record<string, unknown>;