@vellumai/assistant 0.11.6 → 0.11.7-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (478) hide show
  1. package/ARCHITECTURE.md +31 -19
  2. package/Dockerfile +1 -0
  3. package/docs/architecture/security.md +3 -3
  4. package/docs/flux-turn-detection-spike.md +8 -7
  5. package/docs/runbook-trusted-contacts.md +14 -0
  6. package/docs/stt-provider-onboarding.md +8 -4
  7. package/knip.json +1 -0
  8. package/node_modules/@vellumai/avatar-manifest/package.json +18 -0
  9. package/node_modules/@vellumai/avatar-manifest/src/__tests__/manifest.test.ts +173 -0
  10. package/node_modules/@vellumai/avatar-manifest/src/__tests__/read.test.ts +114 -0
  11. package/node_modules/@vellumai/avatar-manifest/src/index.ts +29 -0
  12. package/node_modules/@vellumai/avatar-manifest/src/layout.ts +18 -0
  13. package/node_modules/@vellumai/avatar-manifest/src/manifest.ts +156 -0
  14. package/node_modules/@vellumai/avatar-manifest/src/read.ts +109 -0
  15. package/node_modules/@vellumai/avatar-manifest/tsconfig.json +20 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  17. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  18. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  19. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  20. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  21. package/node_modules/@vellumai/ces-client/src/http-credentials.ts +161 -0
  22. package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
  23. package/node_modules/@vellumai/environments/package.json +2 -1
  24. package/node_modules/@vellumai/environments/src/shell.test.ts +56 -0
  25. package/node_modules/@vellumai/environments/src/shell.ts +65 -0
  26. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  27. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  28. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  29. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  30. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  31. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +32 -0
  32. package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -0
  33. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +82 -0
  34. package/node_modules/@vellumai/gateway-client/src/__tests__/plugin-admission-denied-contract.test.ts +56 -0
  35. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +18 -0
  36. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +5 -11
  37. package/node_modules/@vellumai/gateway-client/src/channel-permission-contract.ts +3 -1
  38. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +9 -0
  39. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +4 -0
  40. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +75 -0
  41. package/node_modules/@vellumai/gateway-client/src/index.ts +17 -0
  42. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +8 -0
  43. package/node_modules/@vellumai/gateway-client/src/plugin-admission-denied-contract.ts +47 -0
  44. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +11 -0
  45. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  46. package/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  47. package/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  48. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  49. package/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  50. package/openapi.yaml +419 -55
  51. package/package.json +4 -1
  52. package/scripts/sync-llm-catalog.ts +6 -0
  53. package/src/__tests__/approval-card-edit-inbound-handoff.test.ts +199 -0
  54. package/src/__tests__/approval-card-edit.test.ts +153 -0
  55. package/src/__tests__/attachments-store-heic-normalize.test.ts +1 -1
  56. package/src/__tests__/available-skills.test.ts +54 -0
  57. package/src/__tests__/avatar-identity-sync.test.ts +5 -1
  58. package/src/__tests__/catalog-install-normalize.test.ts +9 -1
  59. package/src/__tests__/channel-policy.test.ts +1 -3
  60. package/src/__tests__/channel-readiness-discord.test.ts +9 -70
  61. package/src/__tests__/compactor-call-site-logging.test.ts +61 -0
  62. package/src/__tests__/config-loader-backfill.test.ts +69 -0
  63. package/src/__tests__/config-schema.test.ts +2 -3
  64. package/src/__tests__/conversation-agent-loop.test.ts +238 -0
  65. package/src/__tests__/conversation-attachments.test.ts +0 -1
  66. package/src/__tests__/conversation-error.test.ts +44 -0
  67. package/src/__tests__/conversation-evictor.test.ts +19 -0
  68. package/src/__tests__/conversation-fork-referential.test.ts +73 -63
  69. package/src/__tests__/conversation-fork-retrospective.test.ts +99 -74
  70. package/src/__tests__/conversation-runtime-assembly.test.ts +23 -0
  71. package/src/__tests__/conversation-slash.test.ts +9 -0
  72. package/src/__tests__/conversation-title-service.test.ts +97 -0
  73. package/src/__tests__/conversation-tool-setup-attribution.test.ts +16 -0
  74. package/src/__tests__/credential-broker-server-use.test.ts +0 -218
  75. package/src/__tests__/credential-broker.test.ts +43 -420
  76. package/src/__tests__/credential-record-write-through.test.ts +78 -0
  77. package/src/__tests__/credential-routes.test.ts +17 -1
  78. package/src/__tests__/credential-security-invariants.test.ts +8 -9
  79. package/src/__tests__/default-profile-catalog-fallback.test.ts +289 -0
  80. package/src/__tests__/delete-propagation.test.ts +92 -2
  81. package/src/__tests__/discord-callback-round-trip.test.ts +93 -0
  82. package/src/__tests__/discord-channel-config.test.ts +237 -0
  83. package/src/__tests__/edit-propagation.test.ts +42 -2
  84. package/src/__tests__/evict-conversations-for-reload.test.ts +51 -1
  85. package/src/__tests__/fallback-breaker.test.ts +1335 -0
  86. package/src/__tests__/gateway-threshold-reader-mock.ts +6 -0
  87. package/src/__tests__/guardian-card-withdrawal.test.ts +30 -0
  88. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -2
  89. package/src/__tests__/image-conversion.test.ts +38 -4
  90. package/src/__tests__/install-skill-routing.test.ts +25 -0
  91. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  92. package/src/__tests__/llm-resolver.test.ts +74 -0
  93. package/src/__tests__/llm-schema-fallback-profile.test.ts +359 -0
  94. package/src/__tests__/managed-fallback-dispatch.test.ts +430 -0
  95. package/src/__tests__/managed-profile-guard.test.ts +28 -0
  96. package/src/__tests__/managed-speech-defaults.test.ts +196 -1
  97. package/src/__tests__/mcp-cli.test.ts +23 -1
  98. package/src/__tests__/mcp-tool-annotations-risk.test.ts +43 -34
  99. package/src/__tests__/media-stream-stt-session.test.ts +2 -0
  100. package/src/__tests__/notification-decision-fallback.test.ts +106 -0
  101. package/src/__tests__/notification-discord-adapter.test.ts +162 -0
  102. package/src/__tests__/oauth-connect-orchestrator.test.ts +95 -0
  103. package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -8
  104. package/src/__tests__/persist-unsendable-image.test.ts +3 -3
  105. package/src/__tests__/platform.test.ts +19 -1
  106. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  107. package/src/__tests__/provider-catalog-visibility.test.ts +17 -0
  108. package/src/__tests__/provider-platform-proxy-integration.test.ts +8 -1
  109. package/src/__tests__/provider-usage-tracking.test.ts +56 -0
  110. package/src/__tests__/require-fresh-approval.test.ts +5 -2
  111. package/src/__tests__/retry-fallback-escalation.test.ts +1093 -0
  112. package/src/__tests__/schedule-retry.test.ts +183 -4
  113. package/src/__tests__/scheduler-reuse-conversation.test.ts +6 -6
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +55 -0
  115. package/src/__tests__/skill-load-tool.test.ts +34 -0
  116. package/src/__tests__/skill-tool-factory.test.ts +6 -1
  117. package/src/__tests__/skill-tool-manifest.test.ts +20 -0
  118. package/src/__tests__/skills.test.ts +25 -0
  119. package/src/__tests__/subagent-manager-notify.test.ts +38 -0
  120. package/src/__tests__/terminal-tools.test.ts +2 -0
  121. package/src/__tests__/tool-approval-handler.test.ts +146 -2
  122. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +108 -2
  123. package/src/__tests__/tool-audit.test.ts +111 -40
  124. package/src/__tests__/tool-executor-lifecycle-events.test.ts +228 -18
  125. package/src/__tests__/ui-shape-teaching.test.ts +1 -1
  126. package/src/__tests__/verification-control-plane-policy.test.ts +6 -2
  127. package/src/__tests__/voice-config-update.test.ts +43 -0
  128. package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +480 -0
  129. package/src/__tests__/workspace-migration-148-strip-unsupported-fallback-profiles.test.ts +167 -0
  130. package/src/__tests__/workspace-migration-149-repoint-backup-profile-selections.test.ts +144 -0
  131. package/src/agent/loop.ts +31 -2
  132. package/src/approvals/guardian-card-withdrawal.ts +22 -0
  133. package/src/avatar/__tests__/ensure-raster.test.ts +291 -0
  134. package/src/avatar/avatar-manifest.ts +48 -100
  135. package/src/avatar/avatar-store.ts +7 -7
  136. package/src/avatar/ensure-raster.ts +146 -0
  137. package/src/avatar/resvg-lazy.test.ts +0 -1
  138. package/src/avatar/traits-png-sync.ts +9 -10
  139. package/src/background-wake/wake-intent-hooks.test.ts +0 -1
  140. package/src/backup/__tests__/paths.test.ts +6 -2
  141. package/src/backup/paths.ts +6 -4
  142. package/src/bundler/app-compiler.ts +2 -2
  143. package/src/bundler/compiler-tools.test.ts +36 -0
  144. package/src/bundler/compiler-tools.ts +54 -18
  145. package/src/calls/__tests__/telephony-synthesis-language.test.ts +81 -0
  146. package/src/calls/__tests__/voice-session-bridge.test.ts +57 -0
  147. package/src/calls/media-stream-stt-session.ts +2 -1
  148. package/src/calls/telephony-synthesis-language.ts +13 -5
  149. package/src/calls/voice-session-bridge.ts +19 -4
  150. package/src/channels/config.ts +23 -12
  151. package/src/cli/AGENTS.md +3 -1
  152. package/src/cli/commands/backup.help.ts +10 -13
  153. package/src/cli/commands/contacts.help.ts +16 -4
  154. package/src/cli/commands/contacts.ts +5 -1
  155. package/src/cli/commands/credentials.help.ts +1 -1
  156. package/src/cli/commands/inference-providers.ts +18 -53
  157. package/src/cli/commands/mcp.help.ts +6 -5
  158. package/src/cli/commands/mcp.ts +1 -1
  159. package/src/cli/commands/memory/memory-retrospective.ts +1 -0
  160. package/src/cli/commands/stt.help.ts +2 -2
  161. package/src/config/__tests__/backup-schema.test.ts +1 -1
  162. package/src/config/__tests__/default-profile-catalog.test.ts +83 -2
  163. package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +1 -1
  164. package/src/config/__tests__/memory-retrospective-schema.test.ts +12 -15
  165. package/src/config/bundled-skills/computer-use/SKILL.md +6 -6
  166. package/src/config/bundled-skills/computer-use/TOOLS.json +9 -6
  167. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +3 -1
  168. package/src/config/bundled-skills/media-processing/services/preprocess.ts +1 -1
  169. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  170. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +16 -15
  171. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -1
  172. package/src/config/bundled-skills/settings/TOOLS.json +11 -5
  173. package/src/config/bundled-skills/settings/tools/open-system-settings.test.ts +85 -0
  174. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +33 -8
  175. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +54 -3
  176. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +1 -1
  177. package/src/config/default-profile-catalog.ts +195 -10
  178. package/src/config/default-profile-names.ts +41 -0
  179. package/src/config/feature-flag-registry.json +34 -2
  180. package/src/config/inference-profile-validation.ts +11 -2
  181. package/src/config/llm-resolver.ts +25 -0
  182. package/src/config/loader.ts +3 -0
  183. package/src/config/managed-speech-defaults.ts +160 -10
  184. package/src/config/schema.ts +2 -0
  185. package/src/config/schemas/__tests__/live-voice.test.ts +7 -6
  186. package/src/config/schemas/__tests__/stt.test.ts +94 -0
  187. package/src/config/schemas/backup.ts +1 -1
  188. package/src/config/schemas/channels.ts +32 -0
  189. package/src/config/schemas/live-voice.ts +13 -4
  190. package/src/config/schemas/llm.ts +263 -15
  191. package/src/config/schemas/mcp.ts +10 -2
  192. package/src/config/schemas/memory-retrospective.ts +1 -19
  193. package/src/config/schemas/services.ts +24 -0
  194. package/src/config/schemas/stt.ts +128 -5
  195. package/src/config/seed-inference-profiles.ts +32 -3
  196. package/src/config/skill-state.ts +5 -0
  197. package/src/config/skills.ts +21 -0
  198. package/src/contacts/notify-contacts-changed.ts +5 -2
  199. package/src/context/compactor.ts +74 -1
  200. package/src/daemon/__tests__/conversation-tool-setup.test.ts +138 -1
  201. package/src/daemon/config-watcher.ts +2 -1
  202. package/src/daemon/conversation-agent-loop-handlers.ts +27 -0
  203. package/src/daemon/conversation-agent-loop.ts +86 -10
  204. package/src/daemon/conversation-error.ts +25 -0
  205. package/src/daemon/conversation-evictor.ts +2 -4
  206. package/src/daemon/conversation-runtime-assembly.ts +19 -7
  207. package/src/daemon/conversation-skill-tools.ts +5 -1
  208. package/src/daemon/conversation-slash.ts +2 -2
  209. package/src/daemon/conversation-store.ts +8 -1
  210. package/src/daemon/conversation-tool-setup.ts +37 -0
  211. package/src/daemon/conversation.ts +0 -4
  212. package/src/daemon/handlers/channel-config-result.ts +30 -0
  213. package/src/daemon/handlers/config-discord-channel.ts +301 -0
  214. package/src/daemon/handlers/config-ingress.ts +9 -0
  215. package/src/daemon/handlers/config-slack-channel.ts +13 -15
  216. package/src/daemon/handlers/config-telegram.ts +2 -5
  217. package/src/daemon/handlers/conversations.ts +0 -2
  218. package/src/daemon/handlers/skills.ts +25 -7
  219. package/src/daemon/lifecycle.ts +3 -1
  220. package/src/daemon/message-types/messages.ts +0 -2
  221. package/src/daemon/tool-setup-types.ts +5 -3
  222. package/src/daemon/trust-context-types.test.ts +33 -0
  223. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  224. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +58 -4
  225. package/src/live-voice/__tests__/live-voice-managed-speech-fallback.test.ts +8 -3
  226. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +73 -11
  227. package/src/live-voice/__tests__/live-voice-session-preflight.test.ts +148 -2
  228. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +64 -0
  229. package/src/live-voice/__tests__/live-voice-stt.test.ts +3 -0
  230. package/src/live-voice/__tests__/live-voice-text-turn.test.ts +391 -0
  231. package/src/live-voice/__tests__/protocol.test.ts +224 -0
  232. package/src/live-voice/live-voice-credential-preflight.ts +5 -1
  233. package/src/live-voice/live-voice-session-manager.ts +39 -2
  234. package/src/live-voice/live-voice-session.ts +224 -9
  235. package/src/live-voice/protocol.ts +187 -1
  236. package/src/messaging/providers/discord/api.ts +2 -1
  237. package/src/messaging/providers/discord/withdraw.ts +62 -0
  238. package/src/messaging/read-provider-metadata.ts +32 -0
  239. package/src/monitoring/__tests__/file-descriptors.test.ts +32 -0
  240. package/src/monitoring/__tests__/process-memory.test.ts +34 -1
  241. package/src/monitoring/db-integrity-sample.ts +1 -0
  242. package/src/monitoring/file-descriptors.ts +39 -6
  243. package/src/monitoring/process-memory.ts +37 -5
  244. package/src/monitoring/resource-sampler.ts +25 -8
  245. package/src/notifications/__tests__/activity-failed-dedupe.test.ts +161 -0
  246. package/src/notifications/__tests__/background-failure-signal.test.ts +107 -0
  247. package/src/notifications/__tests__/connected-channels.test.ts +12 -0
  248. package/src/notifications/__tests__/copy-composer.test.ts +83 -0
  249. package/src/notifications/__tests__/destination-resolver.test.ts +38 -0
  250. package/src/notifications/activity-failed-dedupe.ts +95 -0
  251. package/src/notifications/adapters/discord.ts +132 -0
  252. package/src/notifications/adapters/shared.ts +16 -0
  253. package/src/notifications/adapters/slack.ts +6 -1
  254. package/src/notifications/adapters/telegram.ts +5 -8
  255. package/src/notifications/approval-card-data.ts +94 -1
  256. package/src/notifications/background-failure-signal.ts +116 -0
  257. package/src/notifications/broadcaster.ts +3 -9
  258. package/src/notifications/copy-composer.ts +41 -8
  259. package/src/notifications/decision-engine.ts +55 -0
  260. package/src/notifications/destination-resolver.ts +45 -5
  261. package/src/notifications/emit-signal.ts +19 -5
  262. package/src/notifications/guardian-question-mode.ts +57 -0
  263. package/src/notifications/types.ts +2 -8
  264. package/src/oauth/__tests__/seed-providers-managed.test.ts +18 -0
  265. package/src/oauth/connect-orchestrator.ts +16 -0
  266. package/src/oauth/seed-providers.ts +262 -0
  267. package/src/permissions/checker.test.ts +21 -0
  268. package/src/permissions/checker.ts +6 -0
  269. package/src/permissions/gateway-threshold-reader.test.ts +184 -0
  270. package/src/permissions/gateway-threshold-reader.ts +144 -10
  271. package/src/permissions/prompter.ts +0 -6
  272. package/src/permissions/types.ts +5 -4
  273. package/src/persistence/__tests__/bulk-write-gate.test.ts +0 -38
  274. package/src/persistence/bulk-write-gate.ts +9 -10
  275. package/src/persistence/conversation-crud.ts +88 -134
  276. package/src/persistence/conversation-title-service.ts +43 -29
  277. package/src/persistence/embeddings/embedding-local.ts +14 -85
  278. package/src/persistence/embeddings/embedding-runtime-manager.ts +1 -0
  279. package/src/persistence/lifecycle-events-store.test.ts +50 -0
  280. package/src/persistence/lifecycle-events-store.ts +75 -5
  281. package/src/platform/platform-patch-queue.test.ts +368 -0
  282. package/src/platform/platform-patch-queue.ts +190 -0
  283. package/src/platform/sync-avatar.test.ts +527 -0
  284. package/src/platform/sync-avatar.ts +209 -0
  285. package/src/platform/sync-identity.test.ts +75 -0
  286. package/src/platform/sync-identity.ts +17 -110
  287. package/src/plugin-api/model-profiles.test.ts +2 -2
  288. package/src/plugin-api/model-profiles.ts +7 -7
  289. package/src/plugin-api/transcription-session.ts +1 -1
  290. package/src/plugins/defaults/compaction/window-manager.ts +4 -0
  291. package/src/plugins/defaults/image-recovery/recover.ts +2 -2
  292. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +133 -34
  293. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +21 -31
  294. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +13 -9
  295. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +31 -4
  296. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +0 -11
  297. package/src/plugins/defaults/memory/memory-retrospective-fork-boundary.ts +27 -13
  298. package/src/plugins/defaults/memory/memory-retrospective-job.ts +183 -113
  299. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +13 -13
  300. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +2 -2
  301. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +1 -1
  302. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +4 -2
  303. package/src/plugins/defaults/memory/substrate/sweep-job.ts +9 -42
  304. package/src/plugins/defaults/memory/v2/rerank-local.ts +1 -0
  305. package/src/plugins/defaults/memory/worker.ts +7 -0
  306. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  307. package/src/providers/__tests__/retry-callsite.test.ts +55 -0
  308. package/src/providers/__tests__/vellum-connection-routing.test.ts +21 -3
  309. package/src/providers/connection-resolution.ts +2 -17
  310. package/src/providers/fallback-breaker.ts +389 -0
  311. package/src/providers/inference/__tests__/adapter-factory-opencode.test.ts +92 -0
  312. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +16 -0
  313. package/src/providers/inference/__tests__/base-url-security.test.ts +12 -0
  314. package/src/providers/inference/adapter-factory.ts +246 -3
  315. package/src/providers/inference/auth.ts +15 -14
  316. package/src/providers/inference/chatgpt-subscription-auth.ts +70 -0
  317. package/src/providers/inference/chatgpt-subscription-credentials.ts +84 -0
  318. package/src/providers/inference/resolve-auth.ts +1 -1
  319. package/src/providers/media-resolve.ts +2 -3
  320. package/src/providers/model-catalog.ts +100 -0
  321. package/src/providers/openai/__tests__/api-error-normalization.test.ts +22 -0
  322. package/src/providers/openai/__tests__/chat-template-flatten.test.ts +198 -0
  323. package/src/providers/openai/api-error-normalization.ts +13 -0
  324. package/src/providers/openai/chat-completions-provider.ts +84 -0
  325. package/src/providers/opencode/client.test.ts +150 -0
  326. package/src/providers/opencode/client.ts +61 -0
  327. package/src/providers/platform-proxy/constants.ts +5 -0
  328. package/src/providers/provider-resolvability.ts +20 -0
  329. package/src/providers/provider-secret-catalog.ts +3 -2
  330. package/src/providers/retry.ts +956 -68
  331. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +42 -0
  332. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +136 -0
  333. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +120 -0
  334. package/src/providers/speech-to-text/__tests__/resolve.test.ts +316 -12
  335. package/src/providers/speech-to-text/deepgram-flux-frames.ts +83 -3
  336. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +132 -4
  337. package/src/providers/speech-to-text/provider-catalog.ts +191 -3
  338. package/src/providers/speech-to-text/resolve.ts +132 -24
  339. package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +296 -0
  340. package/src/providers/types.ts +39 -0
  341. package/src/providers/usage-tracking.ts +12 -1
  342. package/src/providers/vellum/client.ts +29 -0
  343. package/src/providers/vellum-model-routing.test.ts +2 -0
  344. package/src/providers/vellum-model-routing.ts +3 -3
  345. package/src/runtime/AGENTS.md +1 -2
  346. package/src/runtime/__tests__/agent-wake.test.ts +105 -3
  347. package/src/runtime/__tests__/background-job-runner.test.ts +89 -3
  348. package/src/runtime/agent-wake.ts +44 -5
  349. package/src/runtime/background-job-runner.ts +68 -50
  350. package/src/runtime/channel-readiness-service.ts +0 -39
  351. package/src/runtime/http-server.ts +12 -4
  352. package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +18 -18
  353. package/src/runtime/routes/__tests__/backup-routes.test.ts +9 -5
  354. package/src/runtime/routes/__tests__/chatgpt-subscription-auth-routes.test.ts +372 -0
  355. package/src/runtime/routes/__tests__/contact-routes.test.ts +7 -0
  356. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +96 -0
  357. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +37 -0
  358. package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +2 -1
  359. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +87 -0
  360. package/src/runtime/routes/acp-claude-auth-routes.ts +25 -54
  361. package/src/runtime/routes/approval-routes.ts +0 -12
  362. package/src/runtime/routes/avatar-routes.ts +17 -30
  363. package/src/runtime/routes/backup-routes.ts +2 -2
  364. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +296 -113
  365. package/src/runtime/routes/contact-prompt-routes.ts +65 -6
  366. package/src/runtime/routes/contact-routes.ts +20 -15
  367. package/src/runtime/routes/conversation-query-routes.ts +98 -10
  368. package/src/runtime/routes/conversation-routes.ts +2 -2
  369. package/src/runtime/routes/credential-routes.ts +2 -1
  370. package/src/runtime/routes/guardian-approval-interception.ts +12 -9
  371. package/src/runtime/routes/inbound-message-handler.ts +47 -48
  372. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +18 -13
  373. package/src/runtime/routes/inbound-stages/admission-policy.ts +2 -2
  374. package/src/runtime/routes/inbound-stages/edit-intercept.ts +20 -2
  375. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +9 -8
  376. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -1
  377. package/src/runtime/routes/index.ts +2 -0
  378. package/src/runtime/routes/inference-profile-session-handler.ts +2 -2
  379. package/src/runtime/routes/inference-profiles-routes.ts +12 -7
  380. package/src/runtime/routes/inference-provider-connection-routes.ts +2 -2
  381. package/src/runtime/routes/inference-send-routes.ts +2 -2
  382. package/src/runtime/routes/ingress-status-routes.ts +2 -2
  383. package/src/runtime/routes/integrations/discord.ts +119 -0
  384. package/src/runtime/routes/llm-call-sites-routes.ts +2 -2
  385. package/src/runtime/routes/mcp-auth-routes.ts +12 -6
  386. package/src/runtime/routes/migration-routes.ts +3 -1
  387. package/src/runtime/routes/oauth-pending-flows.ts +136 -0
  388. package/src/runtime/routes/secret-routes.ts +7 -0
  389. package/src/runtime/routes/stt-routes.ts +38 -6
  390. package/src/runtime/routes/wake-conversation-routes.ts +8 -1
  391. package/src/runtime/routes/watch-routes.ts +9 -5
  392. package/src/runtime/sync/resource-sync-events.ts +2 -0
  393. package/src/runtime/trust-verdict-consumer.ts +3 -3
  394. package/src/schedule/__tests__/worker-feature-flags.test.ts +58 -0
  395. package/src/schedule/retry-policy.ts +8 -8
  396. package/src/schedule/run-script.ts +14 -23
  397. package/src/schedule/schedule-recovery.ts +0 -1
  398. package/src/schedule/scheduler.ts +89 -51
  399. package/src/schedule/worker.ts +57 -0
  400. package/src/security/__tests__/openai-device-auth.test.ts +355 -0
  401. package/src/security/ces-rpc-record-backend.ts +123 -0
  402. package/src/security/oauth2.ts +7 -0
  403. package/src/security/openai-device-auth.ts +364 -0
  404. package/src/security/secure-keys.ts +15 -0
  405. package/src/skills/available-skills.ts +10 -1
  406. package/src/skills/catalog-install.ts +19 -1
  407. package/src/skills/inline-command-runner.ts +6 -2
  408. package/src/skills/platform-compatibility.test.ts +49 -0
  409. package/src/skills/platform-compatibility.ts +65 -0
  410. package/src/skills/tool-manifest.ts +20 -0
  411. package/src/stt/__tests__/roles.test.ts +152 -0
  412. package/src/stt/daemon-batch-transcriber.ts +4 -1
  413. package/src/stt/roles.ts +180 -0
  414. package/src/stt/stt-stream-session.ts +3 -2
  415. package/src/stt/types.ts +33 -3
  416. package/src/subagent/manager.ts +12 -0
  417. package/src/telemetry/__tests__/live-voice-funnel.test.ts +34 -0
  418. package/src/telemetry/live-voice-funnel.ts +12 -2
  419. package/src/telemetry/telemetry-wire.generated.ts +6 -0
  420. package/src/telemetry/tool-audit.ts +83 -30
  421. package/src/telemetry/tool-executed-events-store.test.ts +0 -1
  422. package/src/telemetry/turn-outcome.ts +75 -4
  423. package/src/tools/client-os.ts +115 -0
  424. package/src/tools/computer-use/definitions.ts +11 -7
  425. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -2
  426. package/src/tools/credentials/broker-types.ts +0 -71
  427. package/src/tools/credentials/broker.ts +6 -201
  428. package/src/tools/credentials/metadata-store.ts +65 -16
  429. package/src/tools/credentials/store.ts +2 -0
  430. package/src/tools/executor.ts +2 -8
  431. package/src/tools/host-shell.test.ts +75 -0
  432. package/src/tools/host-shell.ts +24 -0
  433. package/src/tools/host-terminal/host-shell.ts +15 -44
  434. package/src/tools/mcp/mcp-tool-factory.ts +44 -9
  435. package/src/tools/permission-checker.ts +73 -21
  436. package/src/tools/policy-context.ts +1 -0
  437. package/src/tools/shared/filesystem/image-read.ts +1 -1
  438. package/src/tools/skills/find-similar-skills.ts +10 -2
  439. package/src/tools/skills/load.ts +41 -6
  440. package/src/tools/skills/sandbox-runner.ts +8 -16
  441. package/src/tools/skills/skill-tool-factory.ts +1 -0
  442. package/src/tools/terminal/__tests__/safe-env.test.ts +64 -1
  443. package/src/tools/terminal/safe-env.ts +39 -9
  444. package/src/tools/terminal/shell.ts +12 -31
  445. package/src/tools/tool-approval-handler.ts +64 -10
  446. package/src/tools/tool-types.ts +10 -0
  447. package/src/tools/types.ts +19 -3
  448. package/src/tools/ui-surface/surface-shape-docs.ts +4 -3
  449. package/src/tools/workflows/manage-workflows.ts +2 -2
  450. package/src/tools/workflows/run-workflow.test.ts +2 -1
  451. package/src/util/__tests__/file-use.test.ts +61 -0
  452. package/src/util/__tests__/process-table.test.ts +66 -0
  453. package/src/util/__tests__/process-tree.test.ts +46 -0
  454. package/src/util/errors.ts +1 -0
  455. package/src/util/file-use.ts +91 -0
  456. package/src/util/host-process.test.ts +17 -0
  457. package/src/util/host-process.ts +75 -0
  458. package/src/util/image-conversion.ts +39 -7
  459. package/src/util/platform.ts +32 -13
  460. package/src/util/process-table.ts +249 -0
  461. package/src/util/process-tree.ts +85 -129
  462. package/src/util/provider-error-patterns.ts +20 -0
  463. package/src/util/spawn.ts +9 -2
  464. package/src/watch/__tests__/watch-retro.test.ts +48 -1
  465. package/src/watch/watch-retro.ts +46 -4
  466. package/src/workspace/git-service.ts +4 -14
  467. package/src/workspace/migrations/147-rename-colliding-backup-profile-names.ts +360 -0
  468. package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +117 -0
  469. package/src/workspace/migrations/149-repoint-backup-profile-selections.ts +204 -0
  470. package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +139 -0
  471. package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +160 -0
  472. package/src/workspace/migrations/registry.ts +8 -0
  473. package/src/__tests__/permission-types.test.ts +0 -17
  474. package/src/channels/gateway-discord-admission.ts +0 -32
  475. package/src/persistence/fork-message-copy.ts +0 -239
  476. package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +0 -221
  477. package/src/security/__tests__/oauth2-device-code.test.ts +0 -431
  478. package/src/security/oauth2-device-code.ts +0 -312
@@ -2,28 +2,50 @@ import type {
2
2
  ToolContext,
3
3
  ToolExecutionResult,
4
4
  } from "../../../../tools/types.js";
5
+ import { isWindows } from "../../../../util/platform.js";
5
6
 
6
7
  const PANES = {
7
8
  microphone: {
8
- url: "x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone",
9
9
  label: "Microphone privacy",
10
- instruction: "Please toggle Vellum Assistant on.",
10
+ urls: {
11
+ macos:
12
+ "x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone",
13
+ windows: "ms-settings:privacy-microphone",
14
+ },
11
15
  },
12
16
  speech_recognition: {
13
- url: "x-apple.systempreferences:com.apple.preference.security?Privacy_SpeechRecognition",
14
17
  label: "Speech Recognition privacy",
15
- instruction: "Please toggle Vellum Assistant on.",
18
+ urls: {
19
+ macos:
20
+ "x-apple.systempreferences:com.apple.preference.security?Privacy_SpeechRecognition",
21
+ windows: "ms-settings:privacy-speech",
22
+ },
16
23
  },
17
24
  } as const;
18
25
 
19
26
  type PaneName = keyof typeof PANES;
27
+ type SettingsPlatform = keyof (typeof PANES)[PaneName]["urls"];
20
28
 
29
+ const VALID_PLATFORMS: SettingsPlatform[] = ["macos", "windows"];
21
30
  const VALID_PANES = Object.keys(PANES) as PaneName[];
22
31
 
23
32
  export async function run(
24
33
  input: Record<string, unknown>,
25
34
  context: ToolContext,
26
35
  ): Promise<ToolExecutionResult> {
36
+ const requestedPlatform = input.platform as string | undefined;
37
+ if (
38
+ requestedPlatform !== undefined &&
39
+ !VALID_PLATFORMS.includes(requestedPlatform as SettingsPlatform)
40
+ ) {
41
+ return {
42
+ content: `Error: unknown platform "${requestedPlatform}". Valid platforms: ${VALID_PLATFORMS.join(
43
+ ", ",
44
+ )}`,
45
+ isError: true,
46
+ };
47
+ }
48
+
27
49
  const pane = input.pane as string;
28
50
  if (!VALID_PANES.includes(pane as PaneName)) {
29
51
  return {
@@ -34,20 +56,23 @@ export async function run(
34
56
  };
35
57
  }
36
58
 
59
+ const platform =
60
+ (requestedPlatform as SettingsPlatform | undefined) ??
61
+ (isWindows() ? "windows" : "macos");
37
62
  const meta = PANES[pane as PaneName];
38
63
 
39
- // Send open_url to the client - the x-apple.systempreferences: scheme
40
- // opens System Settings directly without a browser confirmation dialog.
41
64
  if (context.sendToClient) {
42
65
  context.sendToClient({
43
66
  type: "open_url",
44
- url: meta.url,
67
+ url: meta.urls[platform],
45
68
  conversationId: context.conversationId,
46
69
  });
47
70
  }
48
71
 
72
+ const settingsName =
73
+ platform === "windows" ? "Windows Settings" : "System Settings";
49
74
  return {
50
- content: `Opened System Settings to ${meta.label}. ${meta.instruction}`,
75
+ content: `Opened ${settingsName} to ${meta.label}. Please enable Vellum Assistant.`,
51
76
  isError: false,
52
77
  };
53
78
  }
@@ -4,6 +4,11 @@ import {
4
4
  DEEPGRAM_MULTI_LANGUAGE_CODES,
5
5
  DEEPGRAM_NOVA3_MONOLINGUAL_CODES,
6
6
  } from "../../../../providers/speech-to-text/deepgram.js";
7
+ import {
8
+ isManagedSttProvider,
9
+ listProviderModelFamilies,
10
+ } from "../../../../providers/speech-to-text/provider-catalog.js";
11
+ import type { SttProviderId } from "../../../../stt/types.js";
7
12
  import type {
8
13
  ToolContext,
9
14
  ToolExecutionResult,
@@ -47,6 +52,7 @@ const VOICE_SETTINGS = {
47
52
  },
48
53
  stt_provider: { type: "string" },
49
54
  stt_language: { type: "string" },
55
+ stt_model: { type: "string" },
50
56
  } satisfies Record<string, VoiceSettingMeta>;
51
57
 
52
58
  type VoiceSettingName = keyof typeof VOICE_SETTINGS;
@@ -64,6 +70,7 @@ const FRIENDLY_NAMES: Record<VoiceSettingName, string> = {
64
70
  fish_audio_reference_id: "Fish Audio voice",
65
71
  stt_provider: "Speech-to-text provider",
66
72
  stt_language: "Speech-to-text language",
73
+ stt_model: "Speech-to-text model family",
67
74
  };
68
75
 
69
76
  /**
@@ -293,6 +300,25 @@ function validateSetting(
293
300
  }
294
301
  return { ok: true, coerced: value.trim() };
295
302
  }
303
+ case "stt_model": {
304
+ const families = listProviderModelFamilies(
305
+ (activeSttProviderId ?? "") as SttProviderId,
306
+ );
307
+ if (families.length === 0) {
308
+ return {
309
+ ok: false,
310
+ error: `the active speech-to-text provider${activeSttProviderId ? ` (${activeSttProviderId})` : ""} offers a single model, so stt_model cannot be set`,
311
+ };
312
+ }
313
+ const normalized = typeof value === "string" ? value.trim() : "";
314
+ if (!families.includes(normalized as never)) {
315
+ return {
316
+ ok: false,
317
+ error: `stt_model must be one of: ${families.join(", ")}`,
318
+ };
319
+ }
320
+ return { ok: true, coerced: normalized };
321
+ }
296
322
  case "stt_language": {
297
323
  if (typeof value !== "string") {
298
324
  return {
@@ -410,7 +436,9 @@ export async function run(
410
436
  // same source the sparse-config write-seeding below reads): the extended
411
437
  // roster and "multi" are only valid where nova-3 runs.
412
438
  const activeSttProviderId =
413
- setting === "stt_language" ? getConfig().services.stt.provider : undefined;
439
+ setting === "stt_language" || setting === "stt_model"
440
+ ? getConfig().services.stt.provider
441
+ : undefined;
414
442
 
415
443
  const validation = validateSetting(
416
444
  setting,
@@ -422,9 +450,14 @@ export async function run(
422
450
  return { content: `Error: ${validation.error}`, isError: true };
423
451
  }
424
452
 
453
+ // Managed speech has more than one STT id, so the gate reads the catalog
454
+ // rather than a literal: without this, setting a managed provider other
455
+ // than "vellum" on a disconnected assistant persists happily and then
456
+ // resolves no transcriber at all.
425
457
  const wantsManagedSpeech =
426
- (setting === "stt_provider" || setting === "tts_provider") &&
427
- validation.coerced === "vellum";
458
+ (setting === "stt_provider" &&
459
+ isManagedSttProvider(validation.coerced as SttProviderId)) ||
460
+ (setting === "tts_provider" && validation.coerced === "vellum");
428
461
  if (wantsManagedSpeech && !(await managedSpeechAvailable())) {
429
462
  return {
430
463
  content:
@@ -504,6 +537,24 @@ export async function run(
504
537
  invalidateConfigCache();
505
538
  }
506
539
 
540
+ if (setting === "stt_model") {
541
+ // Scoped to the provider that is active now, which validation already
542
+ // checked can serve the family. The provider is seeded for the same
543
+ // reason stt_language seeds it: a sparse block missing `provider` fails
544
+ // SttServiceSchema and trips the loader's salvage ladder.
545
+ const provider = getConfig().services.stt.provider;
546
+ if (!rawSttProviderPresent(raw)) {
547
+ setNestedValue(raw, "services.stt.provider", provider);
548
+ }
549
+ setNestedValue(
550
+ raw,
551
+ `services.stt.providers.${provider}.model`,
552
+ validation.coerced,
553
+ );
554
+ saveRawConfig(raw);
555
+ invalidateConfigCache();
556
+ }
557
+
507
558
  // Provider scoping happened in validation (xai only accepts its verified
508
559
  // set); for the remaining providers the write is unconditional. Auto-detect
509
560
  // providers ignore the value at resolve time, so writing it is harmless and
@@ -239,7 +239,7 @@ export async function run(
239
239
  // the model verbatim rather than as the "nothing configured" copy below.
240
240
  let transcriber: BatchTranscriber | null;
241
241
  try {
242
- transcriber = await resolveBatchTranscriber();
242
+ transcriber = await resolveBatchTranscriber({ role: "batch" });
243
243
  } catch (err) {
244
244
  return { content: (err as Error).message, isError: true };
245
245
  }
@@ -9,15 +9,21 @@ import type { ModelIntent } from "../providers/types.js";
9
9
  import { getManagedUpstream } from "../providers/vellum-model-routing.js";
10
10
  import { getBalancedModelExperimentArm } from "./balanced-model-experiment.js";
11
11
  import {
12
+ BACKUP_PROFILE_KEYS,
13
+ type BackupProfileKey,
12
14
  DEFAULT_PROFILE_KEYS,
13
15
  DEFAULT_PROFILE_PROVIDERS,
14
16
  type DefaultProfileKey,
15
17
  type DefaultProfileProvider,
18
+ FALLBACK_PROFILE_BY_KEY,
19
+ isBackupProfileKey,
20
+ isDefaultProfileKey,
16
21
  isDefaultProfileProvider,
17
22
  OS_BETA_PROFILE_KEY,
18
23
  } from "./default-profile-names.js";
19
24
  import { resolveDefaultConnectionName } from "./default-provider-resolution.js";
20
25
  import {
26
+ backupProfilesResolveUnderDefaultProvider,
21
27
  DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS,
22
28
  DEFAULT_PROVIDER_CHOICES,
23
29
  type DefaultProviderConfig,
@@ -70,6 +76,13 @@ type ProfileImpls = Record<DefaultProfileKey, DefaultProfileTemplate>;
70
76
  * concrete dispatch providers, and the model is what selects the upstream.
71
77
  * Overwritten in workspace config on every daemon boot so Vellum can push
72
78
  * model/config updates to customers in new releases.
79
+ *
80
+ * Each implementation points at its managed backup profile via
81
+ * `fallbackProfile` (`BACKUP_PROFILE_IMPLS` below): a backup pins a model at
82
+ * a different upstream provider, so an outage-type failure at the primary's
83
+ * provider can be served by re-sending on the backup. Vellum column only:
84
+ * the BYOK and chatgpt columns carry no pointers, since those installs may
85
+ * hold no credential for the backup's provider.
73
86
  */
74
87
  const VELLUM_PROFILE_IMPLS: ProfileImpls = {
75
88
  balanced: {
@@ -78,6 +91,7 @@ const VELLUM_PROFILE_IMPLS: ProfileImpls = {
78
91
  source: "managed",
79
92
  label: "Balanced",
80
93
  description: "Good balance of quality, cost, and speed",
94
+ fallbackProfile: FALLBACK_PROFILE_BY_KEY.balanced,
81
95
  maxTokens: 32000,
82
96
  effort: "high",
83
97
  thinking: { enabled: true, streamThinking: true },
@@ -91,6 +105,7 @@ const VELLUM_PROFILE_IMPLS: ProfileImpls = {
91
105
  source: "managed",
92
106
  label: "Quality",
93
107
  description: "High-quality results with the most capable model",
108
+ fallbackProfile: FALLBACK_PROFILE_BY_KEY["quality-optimized"],
94
109
  maxTokens: 32000,
95
110
  effort: "high",
96
111
  thinking: { enabled: true, streamThinking: true },
@@ -107,6 +122,7 @@ const VELLUM_PROFILE_IMPLS: ProfileImpls = {
107
122
  // surface the live model beside the description, so a model name in
108
123
  // this copy would go stale the moment the pin moves.
109
124
  description: "Cheapest responses, for high-volume work",
125
+ fallbackProfile: FALLBACK_PROFILE_BY_KEY["cost-optimized"],
110
126
  maxTokens: 8192,
111
127
  // Explicit reasoning opt-out. OpenAI-compat APIs default reasoning to
112
128
  // "medium" when the field is omitted, and effort-driven providers encode
@@ -136,6 +152,7 @@ const VELLUM_PROFILE_IMPLS: ProfileImpls = {
136
152
  source: "managed",
137
153
  label: "Speed",
138
154
  description: "Fastest responses, with reasoning turned off",
155
+ fallbackProfile: FALLBACK_PROFILE_BY_KEY["latency-optimized"],
139
156
  maxTokens: 8192,
140
157
  // Explicit reasoning opt-out, matching `cost-optimized` above: this
141
158
  // profile advertises reasoning as off, and OpenAI-compat APIs default
@@ -149,6 +166,80 @@ const VELLUM_PROFILE_IMPLS: ProfileImpls = {
149
166
  },
150
167
  };
151
168
 
169
+ /**
170
+ * The managed backup profiles, one per default profile: the route a primary's
171
+ * `fallbackProfile` pointer names. Vellum column only: backups never
172
+ * materialize for BYOK or chatgpt installs. Like the primaries, models are
173
+ * pinned (never intents) and stamped `provider: "vellum"`, so dispatch
174
+ * derives the upstream from the model. Every backup pins its model at a
175
+ * DIFFERENT upstream provider than its primary (that cross-provider split is
176
+ * the entire point), and the module-load validation below enforces
177
+ * routability.
178
+ *
179
+ * Descriptions name the tier they back, never the concrete model: clients
180
+ * surface the live model beside the description, so a model name in this
181
+ * copy would go stale the moment a pin moves.
182
+ */
183
+ const BACKUP_PROFILE_IMPLS: Record<BackupProfileKey, DefaultProfileTemplate> = {
184
+ "balanced-backup": {
185
+ model: "claude-sonnet-5",
186
+ provider: "vellum",
187
+ source: "managed",
188
+ label: "Balanced Backup",
189
+ description: "Automatic backup for the Balanced profile",
190
+ maxTokens: 32000,
191
+ effort: "high",
192
+ thinking: { enabled: true, streamThinking: true },
193
+ contextWindow: {
194
+ maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS,
195
+ },
196
+ },
197
+ "quality-optimized-backup": {
198
+ model: "claude-opus-5",
199
+ provider: "vellum",
200
+ source: "managed",
201
+ label: "Quality Backup",
202
+ description: "Automatic backup for the Quality profile",
203
+ maxTokens: 32000,
204
+ effort: "high",
205
+ thinking: { enabled: true, streamThinking: true },
206
+ contextWindow: {
207
+ maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS,
208
+ },
209
+ },
210
+ "cost-optimized-backup": {
211
+ model: "gemini-3.1-flash-lite",
212
+ provider: "vellum",
213
+ source: "managed",
214
+ label: "Cost Backup",
215
+ description: "Automatic backup for the Cost profile",
216
+ maxTokens: 8192,
217
+ // Explicit reasoning opt-out, matching the primary Cost profile: OpenAI-
218
+ // compat APIs default reasoning to "medium" when the field is omitted,
219
+ // and effort-driven providers encode disabled thinking through this same
220
+ // knob (see DISABLED_THINKING_USES_EFFORT_PROVIDERS in
221
+ // providers/retry.ts).
222
+ effort: "none",
223
+ thinking: { enabled: false, streamThinking: false },
224
+ contextWindow: {
225
+ maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS,
226
+ },
227
+ },
228
+ "latency-optimized-backup": {
229
+ model: "claude-haiku-4-5-20251001",
230
+ provider: "vellum",
231
+ source: "managed",
232
+ label: "Speed Backup",
233
+ description: "Automatic backup for the Speed profile",
234
+ maxTokens: 8192,
235
+ effort: "low",
236
+ thinking: { enabled: false, streamThinking: false },
237
+ contextWindow: {
238
+ maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS,
239
+ },
240
+ },
241
+ };
242
+
152
243
  /**
153
244
  * Arm to managed model pin for the `experiment-balanced-model-2026-08-06` A/B
154
245
  * test (`balanced-model-experiment.ts` owns the flag read). An arm repoints
@@ -335,14 +426,18 @@ export const PROFILE_IMPLS: Record<
335
426
  >;
336
427
 
337
428
  /**
338
- * Managed profiles, i.e. the `vellum` column keyed by profile name. Keyed by
339
- * the user-facing defaults only: an internal profile is code-resolved and
340
- * never listed or ordered.
429
+ * Managed profiles, i.e. the `vellum` column keyed by profile name, plus the
430
+ * managed backup profiles. Backups come after the primaries, which is what
431
+ * places them after the primaries in the seeded `profileOrder`: the seeder
432
+ * inserts missing managed keys in this record's order. Keyed by the
433
+ * user-facing defaults only: an internal profile is code-resolved and never
434
+ * listed or ordered.
341
435
  */
342
436
  export const MANAGED_PROFILE_TEMPLATES: Record<string, DefaultProfileTemplate> =
343
- Object.fromEntries(
344
- DEFAULT_PROFILE_KEYS.map((key) => [key, PROFILE_IMPLS[key].vellum]),
345
- );
437
+ Object.fromEntries([
438
+ ...DEFAULT_PROFILE_KEYS.map((key) => [key, PROFILE_IMPLS[key].vellum]),
439
+ ...BACKUP_PROFILE_KEYS.map((key) => [key, BACKUP_PROFILE_IMPLS[key]]),
440
+ ]);
346
441
 
347
442
  /**
348
443
  * The values BYOK hatch seeding wrote onto each `custom-*` copy, for the keys
@@ -413,8 +508,23 @@ export const OS_BETA_PROFILE_TEMPLATE: DefaultProfileTemplate = {
413
508
  * audible dead air rather than a slow reply. A same-named
414
509
  * workspace entry stays on disk and stays listed; it just never governs what
415
510
  * this name resolves to.
511
+ *
512
+ * All four backups carry the same protection rather than only
513
+ * `latency-optimized-backup`. The narrow argument covers that one alone: it
514
+ * serves the live-voice path whenever the primary falls back, so a shadow
515
+ * reintroduces exactly the dead-air risk the primary's protection exists to
516
+ * prevent. The broader argument covers all four, and is why the whole set is
517
+ * here: a backup only earns its place by pinning a model at a different
518
+ * upstream than the primary it backs (see `BACKUP_PROFILE_IMPLS`), and a
519
+ * user-owned shadow can silently repoint it at the primary's own provider,
520
+ * so the fallback re-sends into the outage it exists to route around. A
521
+ * backup also has no per-provider matrix column, so there is nothing a
522
+ * workspace overlay could legitimately own on it.
416
523
  */
417
- export const CODE_OWNED_PROFILE_NAMES = new Set<string>(["latency-optimized"]);
524
+ export const CODE_OWNED_PROFILE_NAMES = new Set<string>([
525
+ "latency-optimized",
526
+ ...BACKUP_PROFILE_KEYS,
527
+ ]);
418
528
 
419
529
  // All managed profiles, including the flag-gated os-beta, are invariant:
420
530
  // their MANAGED-SOURCE entries are read-only to user-facing writes except
@@ -423,6 +533,7 @@ export const CODE_OWNED_PROFILE_NAMES = new Set<string>(["latency-optimized"]);
423
533
  // the on-disk entry's `source` being `managed`.
424
534
  export const INVARIANT_PROFILE_NAMES = new Set<string>([
425
535
  ...DEFAULT_PROFILE_KEYS,
536
+ ...BACKUP_PROFILE_KEYS,
426
537
  OS_BETA_PROFILE_KEY,
427
538
  ]);
428
539
 
@@ -435,6 +546,7 @@ export const INVARIANT_PROFILE_NAMES = new Set<string>([
435
546
  // same-named user profile.
436
547
  export const MANAGED_PROFILE_NAMES = new Set<string>([
437
548
  ...DEFAULT_PROFILE_KEYS,
549
+ ...BACKUP_PROFILE_KEYS,
438
550
  OS_BETA_PROFILE_KEY,
439
551
  ]);
440
552
 
@@ -502,6 +614,22 @@ for (const key of DEFAULT_PROFILE_KEYS) {
502
614
  }
503
615
  }
504
616
 
617
+ // Backup profiles get the same eager verification as the vellum column: a
618
+ // pinned model that no managed upstream serves would make the fallback route
619
+ // undispatchable exactly when it is needed. The cross-provider rule (backup
620
+ // upstream differs from the primary's) is asserted in
621
+ // __tests__/default-profile-catalog-fallback.test.ts, arm pins included.
622
+ for (const key of BACKUP_PROFILE_KEYS) {
623
+ const impl = BACKUP_PROFILE_IMPLS[key];
624
+ if (impl.model == null || getManagedUpstream(impl.model) === null) {
625
+ throw new Error(
626
+ `BACKUP_PROFILE_IMPLS[${key}] references model "${impl.model ?? ""}" ` +
627
+ `which is not served by any managed upstream. ` +
628
+ `Update model-catalog.ts or default-profile-catalog.ts.`,
629
+ );
630
+ }
631
+ }
632
+
505
633
  // The experiment arms substitute into the managed column at request time, so
506
634
  // they need the same routability guarantee as the pins validated above: a
507
635
  // LaunchDarkly arm must never select a model no managed upstream serves.
@@ -542,6 +670,10 @@ function buildDefaultProfileEntries(): Record<string, ProfileEntry> {
542
670
  const impl = PROFILE_IMPLS[key].vellum;
543
671
  entries[key] = materializeProfile(impl, impl.provider);
544
672
  }
673
+ for (const key of BACKUP_PROFILE_KEYS) {
674
+ const impl = BACKUP_PROFILE_IMPLS[key];
675
+ entries[key] = materializeProfile(impl, impl.provider);
676
+ }
545
677
  entries[OS_BETA_PROFILE_KEY] = materializeProfile(
546
678
  OS_BETA_PROFILE_TEMPLATE,
547
679
  OS_BETA_PROFILE_TEMPLATE.provider,
@@ -617,6 +749,16 @@ function resolveAgainstBody(
617
749
  body: ProfileEntry | undefined,
618
750
  ): ProfileEntry | undefined {
619
751
  if (body == null) {
752
+ // A managed stub is the workspace's slot for a code-owned profile, never
753
+ // a profile of its own, so with no body behind it there is nothing to
754
+ // resolve. Only a backup name reaches this branch with a stub (the
755
+ // default keys always have a body), and it reaches it on the columns
756
+ // where the backups do not exist. Resolving to the stub would list a
757
+ // profile with no provider or model and let a reference to it look
758
+ // valid, which is precisely what `LLMSchema` rejects on those columns.
759
+ if (isBackupProfileKey(name) && workspace?.source === "managed") {
760
+ return undefined;
761
+ }
620
762
  return workspace;
621
763
  }
622
764
  if (CODE_OWNED_PROFILE_NAMES.has(name)) {
@@ -670,9 +812,7 @@ export function resolveDefaultProfileForProvider(
670
812
  );
671
813
  }
672
814
 
673
- export function isDefaultProfileKey(name: string): name is DefaultProfileKey {
674
- return (DEFAULT_PROFILE_KEYS as readonly string[]).includes(name);
675
- }
815
+ export { isDefaultProfileKey } from "./default-profile-names.js";
676
816
 
677
817
  /**
678
818
  * The implementation of default profile `key` on `provider`: the named matrix
@@ -706,6 +846,18 @@ function defaultProfileBodyForProvider(
706
846
  defaultProvider: DefaultProviderConfig | null,
707
847
  ): ProfileEntry | undefined {
708
848
  if (!isDefaultProfileKey(name)) {
849
+ // Backup profiles are companions of the managed (`vellum`) column only:
850
+ // under a BYOK or chatgpt default provider the primaries carry no
851
+ // `fallbackProfile` pointers, and the backups must not materialize
852
+ // either, since the install may hold no credential for the backup's
853
+ // provider. A null defaultProvider predates `llm.defaultProvider` and
854
+ // resolves to the managed column, so it keeps its backups.
855
+ if (
856
+ isBackupProfileKey(name) &&
857
+ !backupProfilesResolveUnderDefaultProvider(defaultProvider)
858
+ ) {
859
+ return undefined;
860
+ }
709
861
  return CODE_DEFAULT_PROFILE_ENTRIES[name];
710
862
  }
711
863
  if (defaultProvider == null) {
@@ -752,6 +904,14 @@ function clampMaxTokensToModelCap(body: ProfileEntry): ProfileEntry {
752
904
  * available code default, merged per `getEffectiveProfile`. This is the
753
905
  * record all runtime readers of `llm.profiles` should consume; the raw
754
906
  * workspace record is a write-path concern.
907
+ *
908
+ * Deliberately provider-agnostic: it lists the managed backups whatever
909
+ * `llm.defaultProvider` says, because it resolves names against the passed
910
+ * catalog alone and never reads config. Internal resolution and reference
911
+ * validation use `getEffectiveProfilesForProvider`; user-facing selectors use
912
+ * `getUserSelectableProfilesForProvider`, which additionally removes backups
913
+ * from the managed column. Keeping those views separate lets automatic
914
+ * fallback resolve an internal route without offering it as a direct choice.
755
915
  */
756
916
  export function getEffectiveProfiles(
757
917
  workspaceProfiles: Record<string, ProfileEntry> | undefined,
@@ -794,7 +954,32 @@ export function getEffectiveProfilesForProvider(
794
954
  );
795
955
  if (entry != null) {
796
956
  effective[name] = entry;
957
+ continue;
797
958
  }
959
+ // Nothing resolved, so the name must not survive from the workspace
960
+ // spread above: a backup carrying only a managed stub on a column that
961
+ // has no backups would otherwise be listed as an available profile with
962
+ // no provider or model behind it.
963
+ delete effective[name];
798
964
  }
799
965
  return effective;
800
966
  }
967
+
968
+ /**
969
+ * The effective profile view used by user-facing selectors. Managed backups
970
+ * remain in the full effective catalog for automatic fallback resolution, but
971
+ * are internal routes rather than profiles a user can choose directly.
972
+ */
973
+ export function getUserSelectableProfilesForProvider(
974
+ workspaceProfiles: Record<string, ProfileEntry> | undefined,
975
+ defaultProvider: DefaultProviderConfig | null,
976
+ ): Record<string, ProfileEntry> {
977
+ const selectable = getEffectiveProfilesForProvider(
978
+ workspaceProfiles,
979
+ defaultProvider,
980
+ );
981
+ for (const name of BACKUP_PROFILE_KEYS) {
982
+ delete selectable[name];
983
+ }
984
+ return selectable;
985
+ }
@@ -26,6 +26,47 @@ export const DEFAULT_PROFILE_KEYS = [
26
26
  ] as const;
27
27
  export type DefaultProfileKey = (typeof DEFAULT_PROFILE_KEYS)[number];
28
28
 
29
+ /**
30
+ * Stable keys of the managed backup profiles, one per default profile.
31
+ * Deliberately NOT part of `DEFAULT_PROFILE_KEYS`: that array drives picker
32
+ * order and the intent x provider matrix, while backups are companions of
33
+ * the managed (`vellum`) column only. Each backup pins a model at a
34
+ * different upstream provider than its primary, so an outage at the
35
+ * primary's provider can be served by re-sending on the backup (see the
36
+ * `fallbackProfile` field on `ProfileEntry`).
37
+ */
38
+ export const BACKUP_PROFILE_KEYS = [
39
+ "balanced-backup",
40
+ "quality-optimized-backup",
41
+ "cost-optimized-backup",
42
+ "latency-optimized-backup",
43
+ ] as const;
44
+ export type BackupProfileKey = (typeof BACKUP_PROFILE_KEYS)[number];
45
+
46
+ /**
47
+ * The backup profile each default profile falls back to. Applied to the
48
+ * managed (`vellum`) column implementations only: BYOK installs may hold no
49
+ * credential for the backup's provider, so their columns carry no
50
+ * `fallbackProfile` pointers.
51
+ */
52
+ export const FALLBACK_PROFILE_BY_KEY: Record<
53
+ DefaultProfileKey,
54
+ BackupProfileKey
55
+ > = {
56
+ balanced: "balanced-backup",
57
+ "quality-optimized": "quality-optimized-backup",
58
+ "cost-optimized": "cost-optimized-backup",
59
+ "latency-optimized": "latency-optimized-backup",
60
+ };
61
+
62
+ export function isDefaultProfileKey(value: string): value is DefaultProfileKey {
63
+ return (DEFAULT_PROFILE_KEYS as readonly string[]).includes(value);
64
+ }
65
+
66
+ export function isBackupProfileKey(value: string): value is BackupProfileKey {
67
+ return (BACKUP_PROFILE_KEYS as readonly string[]).includes(value);
68
+ }
69
+
29
70
  /**
30
71
  * Flag-gated default profile: only available while the `os-beta` feature
31
72
  * flag has reconciled it into the workspace (see `sync-gated-profiles.ts`).
@@ -289,7 +289,7 @@
289
289
  "scope": "assistant",
290
290
  "key": "channel-trust-floors",
291
291
  "label": "Channel Trust Floors",
292
- "description": "Expose the Channel Trust Floors settings card (per-channel inbound admission policy) on the Privacy settings page. When off, the card is hidden and channels fall back to their default admission floors. On by default; disable to hide the card.",
292
+ "description": "Server-side switch for admission-floor enforcement: the gateway kill switch, the runtime floor stage, and enforcement-time policy resolution gate on it. Configuration reads and writes stay available while it is off. On by default. Client UI visibility is version-gated, not flag-gated.",
293
293
  "defaultEnabled": true
294
294
  },
295
295
  {
@@ -386,7 +386,39 @@
386
386
  "scope": "client",
387
387
  "key": "obscure-credits",
388
388
  "label": "Obscure Credits",
389
- "description": "Replaces the dollar amounts on the billing Plan section with a usage-relative view: the current-plan tile trades its price footer for a Usage Balance bar (managed spend this billing cycle against the package's included credits) and the credits spec chip reads \"<Package> usage, reset monthly\" instead of naming a dollar bundle. Spec chips wrap horizontally to fit the shorter labels. Off leaves the Plan section exactly as it ships today.",
389
+ "description": "Obscures raw credit dollar amounts across the web billing surfaces in favor of a usage-relative view. The Plan section's current-plan tile trades its price footer for a Usage Balance bar and its credits spec chip reads \"<Package> usage, reset monthly\"; the plans takeover, custom-plan modal, and Pro provisioning takeover swap dollar credit wording for the catalog's usage labels; the Credits balance tile and the chat preferences menu show only credit bought or earned beyond the plan's included grants. Chat low-balance and daily-limit banners still name credits. Off leaves every surface exactly as it ships today.",
390
+ "defaultEnabled": false
391
+ },
392
+ {
393
+ "id": "ios-avatar-app-icon",
394
+ "scope": "client",
395
+ "key": "ios-avatar-app-icon",
396
+ "label": "iOS Avatar App Icon",
397
+ "description": "Shows the App icon picker in Settings -> General, where any user chooses an eyes-on-color home screen icon for iOS, seeds the picker from their assistant's character avatar, or resets to the default. Every change is a press, and iOS confirms each one with an alert of its own. Off hides the picker and leaves the default icon in place.",
398
+ "defaultEnabled": false
399
+ },
400
+ {
401
+ "id": "channel-conversation-sidecar",
402
+ "scope": "client",
403
+ "key": "channel-conversation-sidecar",
404
+ "label": "Channel Conversation Sidecar",
405
+ "description": "Renders a conversation bound to an external channel (Slack, Telegram, Discord, phone, email, and any other channel adapter) as two lanes in web chat: Vellum stays the primary full-width lane, and the canonical external-channel transcript moves into the existing read-only right drawer, opened from a channel-thread control in the chat header. Rows the client can attribute to the external channel are drawn once, in the drawer, instead of being blended into the Vellum lane. The drawer offers an Open in <channel> secondary action and a per-message Reference in Vellum action that stages one quoted reference above the Vellum composer. The Vellum composer stays the only input; nothing about delivery, routing, or permissions changes. Off leaves the header deep-link pill and the blended transcript unchanged.",
406
+ "defaultEnabled": false
407
+ },
408
+ {
409
+ "id": "monday-oauth",
410
+ "scope": "assistant",
411
+ "key": "monday-oauth",
412
+ "label": "monday.com Integration",
413
+ "description": "Gates the seeded monday.com OAuth provider. When off, the provider is hidden from GET /v1/oauth/providers, its get-by-id route, and the connect/update routes, so it cannot be listed or connected from the CLI, gateway, or web integrations list. The row is still seeded into oauth_providers on startup; only its visibility changes.",
414
+ "defaultEnabled": false
415
+ },
416
+ {
417
+ "id": "chatgpt-device-code-login",
418
+ "scope": "client",
419
+ "key": "chatgpt-device-code-login",
420
+ "label": "ChatGPT Device Code Login",
421
+ "description": "Gates the device-code sign-in as the primary ChatGPT subscription connect flow in the web client. Off: the redirect-and-paste flow is the only flow the connect section shows, with no device code UI and no other-sign-in-options disclosure. On: device code leads, and the redirect-and-paste flow stays reachable behind that disclosure and takes the section over when the assistant has no device-auth route.",
390
422
  "defaultEnabled": false
391
423
  }
392
424
  ]
@@ -1,16 +1,25 @@
1
- import { getEffectiveProfiles } from "./default-profile-catalog.js";
1
+ import { getUserSelectableProfilesForProvider } from "./default-profile-catalog.js";
2
2
  import { getConfigReadOnly } from "./loader.js";
3
3
 
4
4
  /**
5
5
  * Validate an inference-profile key against the effective profile catalog
6
6
  * (code-defined defaults + workspace `llm.profiles`). Returns a user-facing
7
7
  * error message when the key is empty or unknown, or `null` when valid.
8
+ *
9
+ * Resolved through the `llm.defaultProvider`-aware user-selectable view so
10
+ * availability matches what a direct profile pin can choose. Managed backup
11
+ * routes remain available to automatic fallback resolution but are not
12
+ * reported as selectable profiles.
8
13
  */
9
14
  export function validateInferenceProfileKey(profile: string): string | null {
10
15
  if (!profile.trim()) {
11
16
  return "inferenceProfile must be a non-empty string";
12
17
  }
13
- const profiles = getEffectiveProfiles(getConfigReadOnly().llm?.profiles);
18
+ const llm = getConfigReadOnly().llm;
19
+ const profiles = getUserSelectableProfilesForProvider(
20
+ llm?.profiles,
21
+ llm?.defaultProvider ?? null,
22
+ );
14
23
  const entry = profiles[profile];
15
24
  if (entry === undefined) {
16
25
  const available = Object.keys(profiles).sort();