@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
@@ -11,8 +11,12 @@ import {
11
11
  parseVellumModel,
12
12
  } from "../../providers/vellum-model-routing.js";
13
13
  import {
14
+ BACKUP_PROFILE_KEYS,
14
15
  DEFAULT_PROFILE_KEYS,
15
16
  DEFAULT_PROFILE_PROVIDERS,
17
+ FALLBACK_PROFILE_BY_KEY,
18
+ isBackupProfileKey,
19
+ isDefaultProfileKey,
16
20
  } from "../default-profile-names.js";
17
21
 
18
22
  /**
@@ -51,12 +55,13 @@ export const KNOWN_LLM_PROVIDERS = [
51
55
  "atlascloud",
52
56
  "together",
53
57
  "litellm",
58
+ "opencode",
54
59
  "baseten",
55
60
  "poolside",
56
- // Routing identities rather than adapters: "vellum" = the platform-managed
57
- // route (upstream derived from the model at dispatch), "chatgpt" = the
58
- // subscription route to OpenAI. Neither has a PROVIDER_CATALOG entry;
59
- // dispatch substitutes the real upstream before any adapter lookup.
61
+ // Routing identities: "vellum" = the platform-managed route (upstream
62
+ // derived from the model at dispatch) and the catalog owner of
63
+ // Vellum-hosted GPU models; "chatgpt" = the subscription route to OpenAI.
64
+ // Dispatch substitutes a concrete upstream before adapter lookup.
60
65
  "vellum",
61
66
  "chatgpt",
62
67
  ] as const;
@@ -82,7 +87,8 @@ export function unknownLlmProviderIssue(provider: string): string | null {
82
87
  * connection can serve the shared BYOK templates (fixed base URL, and a
83
88
  * non-empty catalog `defaultModel` for the intent fallback in
84
89
  * `resolveModelIntent`). Deliberately narrower than `LLMProvider`: keyless
85
- * (ollama) and endpoint-supplied (openai-compatible, litellm) providers have
90
+ * (ollama) and endpoint-supplied (openai-compatible, litellm, opencode)
91
+ * providers have
86
92
  * no code-resolvable default profile implementation.
87
93
  */
88
94
  export const DEFAULT_PROVIDER_CHOICES: readonly LLMProvider[] = [
@@ -629,6 +635,13 @@ export const ProfileEntry = LLMConfigFragment.extend({
629
635
  * may accompany `mix`.
630
636
  */
631
637
  mix: MixSchema.optional(),
638
+ /**
639
+ * Code-owned backup profile for a Vellum-managed default profile. This is
640
+ * read-only metadata from the default-profile catalog. Config write paths
641
+ * reject user-authored values because custom and BYOK fallback routes are
642
+ * not supported.
643
+ */
644
+ fallbackProfile: z.string().min(1).optional().meta({ readOnly: true }),
632
645
  });
633
646
  export type ProfileEntry = z.infer<typeof ProfileEntry>;
634
647
 
@@ -674,10 +687,210 @@ export type DefaultProviderConfig = z.infer<typeof DefaultProviderSchema>;
674
687
  */
675
688
  const DefaultProviderField = DefaultProviderSchema.optional().catch(undefined);
676
689
 
690
+ /**
691
+ * Whether the managed backup profiles (`BACKUP_PROFILE_KEYS`) resolve under a
692
+ * given `llm.defaultProvider`.
693
+ *
694
+ * Backups are companions of the managed (`vellum`) column only: on a BYOK or
695
+ * ChatGPT default provider `defaultProfileBodyForProvider` returns `undefined`
696
+ * for them, because the install may hold no credential for the backup's
697
+ * upstream. So a reference to one is a target that can never resolve there,
698
+ * and the schema must reject it rather than preserve a selection the picker
699
+ * cannot show.
700
+ *
701
+ * Accepts an unknown value so the raw write paths (which validate on-disk
702
+ * shapes without a full parse) can share the rule. A missing or malformed
703
+ * value resolves to the managed column: `DefaultProviderField` catches an
704
+ * invalid value to `undefined`, and an install predating
705
+ * `llm.defaultProvider` is managed by definition.
706
+ */
707
+ export function backupProfilesResolveUnderDefaultProvider(
708
+ defaultProvider: unknown,
709
+ ): boolean {
710
+ const provider =
711
+ defaultProvider != null &&
712
+ typeof defaultProvider === "object" &&
713
+ !Array.isArray(defaultProvider)
714
+ ? (defaultProvider as Record<string, unknown>).provider
715
+ : undefined;
716
+ return typeof provider !== "string" || provider === "vellum";
717
+ }
718
+
719
+ /**
720
+ * Why a referenced profile name does not resolve, for the reference error
721
+ * messages. A backup key under a non-managed default provider gets its own
722
+ * reason: the name is real and code-defined, it just has no body outside the
723
+ * managed column, and "not defined in llm.profiles" would send the reader
724
+ * looking for a missing entry that was never supposed to exist.
725
+ */
726
+ function unresolvableProfileReason(
727
+ name: string,
728
+ backupsResolve: boolean,
729
+ ): string {
730
+ return !backupsResolve &&
731
+ (BACKUP_PROFILE_KEYS as readonly string[]).includes(name)
732
+ ? "is a managed backup profile, which resolves only while llm.defaultProvider is the managed provider"
733
+ : "is not defined in llm.profiles";
734
+ }
735
+
736
+ /**
737
+ * The `llm.profiles` keys that are reference targets in their own right,
738
+ * given whether the managed backups resolve under the current
739
+ * `llm.defaultProvider`.
740
+ *
741
+ * Every on-disk key qualifies but one: a reserved backup name whose entry is
742
+ * a thin `source: "managed"` stub. The stub is not a profile, it is the
743
+ * workspace's slot for a code-owned one, and it can reach disk on a managed
744
+ * install through nothing more than a `config get` -> `config set` round-trip
745
+ * of the effective profile list (`normalizeManagedProfileWrites` reduces the
746
+ * echoed body to exactly that stub). Counting it as an ordinary raw key would
747
+ * let it launder a backup reference past the provider gate: after a switch to
748
+ * a BYOK or ChatGPT default provider the reference would keep validating
749
+ * while the code-owned body it stands for has resolved to nothing, leaving a
750
+ * selection that names a providerless stub.
751
+ *
752
+ * A genuinely user-owned entry under a backup name is the opposite case: it
753
+ * carries its own body, and with no code-owned body to lose on a non-managed
754
+ * column `resolveAgainstBody` resolves the workspace entry itself. So it
755
+ * stays a valid target on every column. The `source` test is the same one
756
+ * the resolver uses, which is what keeps the two in step.
757
+ */
758
+ function referenceableProfileKeys(
759
+ profiles: Record<string, unknown> | undefined,
760
+ backupsResolve: boolean,
761
+ ): string[] {
762
+ return Object.entries(profiles ?? {})
763
+ .filter(([name, value]) => {
764
+ if (backupsResolve || !isBackupProfileKey(name)) {
765
+ return true;
766
+ }
767
+ const entry =
768
+ value != null && typeof value === "object" && !Array.isArray(value)
769
+ ? (value as Record<string, unknown>)
770
+ : null;
771
+ return entry != null && entry.source !== "managed";
772
+ })
773
+ .map(([name]) => name);
774
+ }
775
+
677
776
  // ---------------------------------------------------------------------------
678
777
  // Top-level LLM schema
679
778
  // ---------------------------------------------------------------------------
680
779
 
780
+ /**
781
+ * Cross-profile integrity checks for `fallbackProfile` metadata. Only the
782
+ * exact code-owned mapping on managed default profiles is accepted. The
783
+ * referenced profile must exist, must not be a mix, and must not declare a
784
+ * fallback of its own.
785
+ *
786
+ * Shared by `LLMSchema.superRefine` (full-config load) and the config write
787
+ * paths (`commitConfigWrite`), which persist raw config without a
788
+ * full-schema parse. Accepts a raw or parsed `llm.profiles` record; entries
789
+ * are read defensively so the raw on-disk shape is safe to pass.
790
+ *
791
+ * `defaultProvider` is the sibling `llm.defaultProvider` value (raw or
792
+ * parsed): it decides whether the managed backups are valid targets, since
793
+ * they resolve on the managed column alone.
794
+ */
795
+ export function collectFallbackProfileIssues(
796
+ profiles: Record<string, unknown> | undefined,
797
+ defaultProvider?: unknown,
798
+ ): { profileName: string; message: string }[] {
799
+ const issues: { profileName: string; message: string }[] = [];
800
+ const entries = Object.entries(profiles ?? {});
801
+ const readEntry = (value: unknown): Record<string, unknown> | null =>
802
+ value != null && typeof value === "object" && !Array.isArray(value)
803
+ ? (value as Record<string, unknown>)
804
+ : null;
805
+ // The always-available default profiles are code-defined
806
+ // (`default-profile-catalog.ts`) and resolve whether or not they are
807
+ // materialized in `llm.profiles`, so their names are always valid
808
+ // fallback targets (same rule as call-site `profile` references). The
809
+ // managed backups resolve the same way, but on the managed column only,
810
+ // so they are valid targets only under a managed `llm.defaultProvider`.
811
+ // A persisted managed stub for a backup name does not change that, see
812
+ // `referenceableProfileKeys`.
813
+ const backupsResolve =
814
+ backupProfilesResolveUnderDefaultProvider(defaultProvider);
815
+ const profileNames = new Set([
816
+ ...referenceableProfileKeys(profiles, backupsResolve),
817
+ ...DEFAULT_PROFILE_KEYS,
818
+ ...(backupsResolve ? BACKUP_PROFILE_KEYS : []),
819
+ ]);
820
+ const mixProfileNames = new Set(
821
+ entries
822
+ .filter(([, value]) => readEntry(value)?.mix != null)
823
+ .map(([name]) => name),
824
+ );
825
+ for (const [name, value] of entries) {
826
+ const entry = readEntry(value);
827
+ const fallback = entry?.fallbackProfile;
828
+ if (fallback == null) {
829
+ continue;
830
+ }
831
+ // Raw writes (e.g. `config set`) can carry a non-string value the field
832
+ // schema would reject on the next full parse; flag it here so it never
833
+ // reaches disk.
834
+ if (typeof fallback !== "string" || fallback.length === 0) {
835
+ issues.push({
836
+ profileName: name,
837
+ message: `Profile "${name}" declares a fallbackProfile that must be a non-empty string naming another profile.`,
838
+ });
839
+ continue;
840
+ }
841
+ const expectedFallback = isDefaultProfileKey(name)
842
+ ? FALLBACK_PROFILE_BY_KEY[name]
843
+ : undefined;
844
+ if (entry?.source !== "managed" || fallback !== expectedFallback) {
845
+ issues.push({
846
+ profileName: name,
847
+ message: `Profile "${name}" cannot configure fallbackProfile. Automatic fallbacks are code-owned for Vellum-managed default profiles.`,
848
+ });
849
+ continue;
850
+ }
851
+ // (e) A mix carries no route of its own to fall back from.
852
+ if (entry?.mix != null) {
853
+ issues.push({
854
+ profileName: name,
855
+ message: `Mix profile "${name}" cannot also set "fallbackProfile"; a mix only references other profiles plus metadata.`,
856
+ });
857
+ continue;
858
+ }
859
+ // (b) No self-reference.
860
+ if (fallback === name) {
861
+ issues.push({
862
+ profileName: name,
863
+ message: `Profile "${name}" cannot declare itself as its fallbackProfile.`,
864
+ });
865
+ continue;
866
+ }
867
+ // (a) Referenced profile must exist.
868
+ if (!profileNames.has(fallback)) {
869
+ issues.push({
870
+ profileName: name,
871
+ message: `Profile "${name}" declares fallbackProfile "${fallback}" which ${unresolvableProfileReason(fallback, backupsResolve)}.`,
872
+ });
873
+ continue;
874
+ }
875
+ // (c) A fallback target must be a standard (non-mix) profile.
876
+ if (mixProfileNames.has(fallback)) {
877
+ issues.push({
878
+ profileName: name,
879
+ message: `Profile "${name}" declares fallbackProfile "${fallback}" which is a mix profile; a fallback must be a standard profile.`,
880
+ });
881
+ continue;
882
+ }
883
+ // (d) Single hop only: the target must not declare its own fallback.
884
+ if (readEntry(profiles?.[fallback])?.fallbackProfile != null) {
885
+ issues.push({
886
+ profileName: name,
887
+ message: `Profile "${name}" declares fallbackProfile "${fallback}" which sets its own fallbackProfile; fallback is a single hop, chains are not allowed.`,
888
+ });
889
+ }
890
+ }
891
+ return issues;
892
+ }
893
+
681
894
  export const LLMSchema = z
682
895
  .object({
683
896
  profiles: z.record(z.string().min(1), ProfileEntry).default({}),
@@ -740,12 +953,29 @@ export const LLMSchema = z
740
953
  // The always-available default profiles are code-defined
741
954
  // (`default-profile-catalog.ts`) and resolve whether or not they are
742
955
  // materialized in `llm.profiles`, so their names are always valid
743
- // reference targets. The flag-gated `os-beta` is excluded: it resolves
744
- // only while a workspace entry exists, so a reference to it is valid
745
- // only when that entry is present in `config.profiles`.
956
+ // reference targets. The managed backups (`BACKUP_PROFILE_KEYS`) are
957
+ // code-defined on the same terms and are listed in the effective
958
+ // catalog, so a selection naming one (`activeProfile`, `advisorProfile`,
959
+ // a call-site pin) must survive the next load rather than being stripped
960
+ // back to a default. Backups are scoped to the managed column though, so
961
+ // they join the set only under a managed `llm.defaultProvider`: on a BYOK
962
+ // or ChatGPT default provider they have no body to resolve to, and
963
+ // keeping the reference would strand a selection the picker cannot show.
964
+ // The flag-gated `os-beta` is excluded: it resolves only while a
965
+ // workspace entry exists, so a reference to it is valid only when that
966
+ // entry is present in `config.profiles`. A backup name materialized as a
967
+ // thin managed stub does not re-enter the set on a non-managed column
968
+ // either, see `referenceableProfileKeys`.
969
+ const backupsResolve = backupProfilesResolveUnderDefaultProvider(
970
+ config.defaultProvider,
971
+ );
746
972
  const profileNames = new Set([
747
- ...Object.keys(config.profiles ?? {}),
973
+ ...referenceableProfileKeys(
974
+ config.profiles as Record<string, unknown> | undefined,
975
+ backupsResolve,
976
+ ),
748
977
  ...DEFAULT_PROFILE_KEYS,
978
+ ...(backupsResolve ? BACKUP_PROFILE_KEYS : []),
749
979
  ]);
750
980
  for (const [siteId, siteConfig] of Object.entries(config.callSites ?? {})) {
751
981
  if (siteConfig?.profile == null) {
@@ -755,7 +985,7 @@ export const LLMSchema = z
755
985
  ctx.addIssue({
756
986
  code: "custom",
757
987
  path: ["callSites", siteId, "profile"],
758
- message: `Profile "${siteConfig.profile}" referenced by call site "${siteId}" is not defined in llm.profiles`,
988
+ message: `Profile "${siteConfig.profile}" referenced by call site "${siteId}" ${unresolvableProfileReason(siteConfig.profile, backupsResolve)}`,
759
989
  });
760
990
  }
761
991
  }
@@ -766,7 +996,7 @@ export const LLMSchema = z
766
996
  ctx.addIssue({
767
997
  code: "custom",
768
998
  path: ["activeProfile"],
769
- message: `Profile "${config.activeProfile}" referenced by llm.activeProfile is not defined in llm.profiles`,
999
+ message: `Profile "${config.activeProfile}" referenced by llm.activeProfile ${unresolvableProfileReason(config.activeProfile, backupsResolve)}`,
770
1000
  });
771
1001
  }
772
1002
  if (
@@ -776,18 +1006,20 @@ export const LLMSchema = z
776
1006
  ctx.addIssue({
777
1007
  code: "custom",
778
1008
  path: ["advisorProfile"],
779
- message: `Profile "${config.advisorProfile}" referenced by llm.advisorProfile is not defined in llm.profiles`,
1009
+ message: `Profile "${config.advisorProfile}" referenced by llm.advisorProfile ${unresolvableProfileReason(config.advisorProfile, backupsResolve)}`,
780
1010
  });
781
1011
  }
782
1012
 
783
1013
  // --- Mix profile validation --------------------------------------------
784
1014
  // Config keys a mix profile must NOT also set (a mix only references other
785
1015
  // profiles + metadata). Derived from the fragment shape plus the
786
- // ProfileEntry-only `provider_connection` so it can't drift if a new config
787
- // field is added to `LLMConfigFragment`.
1016
+ // ProfileEntry-only `provider_connection` and `fallbackProfile` (a mix
1017
+ // carries no config of its own, so it has no route to fall back from) so
1018
+ // it can't drift if a new config field is added to `LLMConfigFragment`.
788
1019
  const MIX_DISALLOWED_CONFIG_KEYS = [
789
1020
  ...Object.keys(LLMConfigFragment.shape),
790
1021
  "provider_connection",
1022
+ "fallbackProfile",
791
1023
  ];
792
1024
  const mixProfileNames = new Set(
793
1025
  Object.entries(config.profiles ?? {})
@@ -824,7 +1056,7 @@ export const LLMSchema = z
824
1056
  ctx.addIssue({
825
1057
  code: "custom",
826
1058
  path: ["profiles", name, "mix", index, "profile"],
827
- message: `Mix profile "${name}" references profile "${arm.profile}" which is not defined in llm.profiles.`,
1059
+ message: `Mix profile "${name}" references profile "${arm.profile}" which ${unresolvableProfileReason(arm.profile, backupsResolve)}.`,
828
1060
  });
829
1061
  continue;
830
1062
  }
@@ -838,6 +1070,22 @@ export const LLMSchema = z
838
1070
  }
839
1071
  }
840
1072
  }
1073
+
1074
+ // --- fallbackProfile validation ----------------------------------------
1075
+ // Cross-profile fallback rules live in `collectFallbackProfileIssues`
1076
+ // (shared with the config write paths). A mix profile setting
1077
+ // `fallbackProfile` is also rejected by the mix validation above
1078
+ // (MIX_DISALLOWED_CONFIG_KEYS).
1079
+ for (const issue of collectFallbackProfileIssues(
1080
+ config.profiles,
1081
+ config.defaultProvider,
1082
+ )) {
1083
+ ctx.addIssue({
1084
+ code: "custom",
1085
+ path: ["profiles", issue.profileName, "fallbackProfile"],
1086
+ message: issue.message,
1087
+ });
1088
+ }
841
1089
  });
842
1090
 
843
1091
  export type LLMConfig = z.infer<typeof LLMSchema>;
@@ -1,5 +1,13 @@
1
1
  import { z } from "zod";
2
2
 
3
+ /**
4
+ * Risk level an MCP server's tools start at when its config entry does not
5
+ * name one. Callers that write or display a server entry must reference this
6
+ * rather than repeating a literal, so the level stays code-owned: an entry
7
+ * that omits the field picks up whatever this constant says on every load.
8
+ */
9
+ export const DEFAULT_MCP_RISK_LEVEL = "medium";
10
+
3
11
  const McpStdioTransportSchema = z
4
12
  .object({
5
13
  type: z.literal("stdio"),
@@ -66,9 +74,9 @@ export const McpServerConfigSchema = z
66
74
  .enum(["low", "medium", "high"], {
67
75
  error: "mcp server defaultRiskLevel must be one of: low, medium, high",
68
76
  })
69
- .default("high")
77
+ .default(DEFAULT_MCP_RISK_LEVEL)
70
78
  .describe(
71
- "Default risk level assigned to tools from this server (affects approval requirements)",
79
+ "Risk level tools from this server start at (affects approval requirements). A tool's own MCP annotations move it one step from here: destructiveHint up, readOnlyHint down.",
72
80
  ),
73
81
  maxTools: z
74
82
  .number({ error: "mcp server maxTools must be a number" })
@@ -2,11 +2,6 @@ import { z } from "zod";
2
2
 
3
3
  export const MemoryRetrospectiveConfigSchema = z
4
4
  .object({
5
- // This kill switch exists only because conversation forking is expensive:
6
- // a fork full-copies the source conversation (messages + attachments), so
7
- // on a large conversation the retrospective's write burst can lock the
8
- // SQLite table against other writers. Until forking is referential, an
9
- // operator needs a way to stop retrospectives without turning off memory.
10
5
  enabled: z
11
6
  .boolean({ error: "memory.retrospective.enabled must be a boolean" })
12
7
  .default(true)
@@ -14,16 +9,6 @@ export const MemoryRetrospectiveConfigSchema = z
14
9
  "Whether the memory-retrospective background pass runs. When false, no retrospective is enqueued by any trigger (interval, message count, pre-compaction, scheduled sweep), the scheduled sweep job stops being queued, and any row already queued completes as a no-op. The rest of the memory system (extraction, retrieval, embeddings, `<memory>` injection) is unaffected. Use `memory.enabled` to disable memory as a whole.",
15
10
  ),
16
11
 
17
- forkStrategy: z
18
- .enum(["cloning", "reference"], {
19
- error:
20
- "memory.retrospective.forkStrategy must be 'cloning' or 'reference'",
21
- })
22
- .default("cloning")
23
- .describe(
24
- "How a retrospective's fork of the source conversation is materialized. `cloning` (default) full-copies the source conversation's messages and attachments into the fork. `reference` makes the fork referential: it holds only its own messages and reads the source's through `fork_parent_message_id`, so a fork costs a single row instead of a full copy of the conversation.",
25
- ),
26
-
27
12
  timeThresholdMs: z
28
13
  .number({
29
14
  error: "memory.retrospective.timeThresholdMs must be a number",
@@ -102,7 +87,7 @@ export const MemoryRetrospectiveConfigSchema = z
102
87
  })
103
88
  .default(false)
104
89
  .describe(
105
- "When false (default), superseded retrospective conversations are deleted once a newer run succeeds the persisted remembered_log on memory_retrospective_state is the dedup baseline (the most recent run is scanned only as a fallback for state rows that predate the log column), so older runs are dead weight (fork-based runs each carry a full copy of the source conversation's messages). Operators who want to retain the full run history set this to true; retained runs also skip the startup orphan sweep so they survive restarts.",
90
+ "When false (default), superseded retrospective conversations are deleted once a newer run succeeds. The persisted remembered_log on memory_retrospective_state is the dedup baseline (the most recent run is scanned only as a fallback for state rows that predate the log column), so older runs are leftover review transcripts. Operators who want to retain the full run history set this to true; retained runs also skip the startup orphan sweep so they survive restarts.",
106
91
  ),
107
92
 
108
93
  matchConversationProfile: z
@@ -130,6 +115,3 @@ export const MemoryRetrospectiveConfigSchema = z
130
115
  export type MemoryRetrospectiveConfig = z.infer<
131
116
  typeof MemoryRetrospectiveConfigSchema
132
117
  >;
133
-
134
- /** How a retrospective materializes its fork of the source conversation. */
135
- export type MemoryForkStrategy = MemoryRetrospectiveConfig["forkStrategy"];
@@ -136,6 +136,21 @@ const HubspotOAuthServiceSchema = BaseServiceSchema.extend({
136
136
  mode: ServiceModeSchema.default("your-own"),
137
137
  });
138
138
 
139
+ const MondayOAuthServiceSchema = BaseServiceSchema.extend({
140
+ mode: ServiceModeSchema.default("your-own"),
141
+ });
142
+
143
+ export const EventbriteOAuthServiceSchema = BaseServiceSchema.extend({
144
+ mode: ServiceModeSchema.default("your-own"),
145
+ });
146
+ export type EventbriteOAuthService = z.infer<
147
+ typeof EventbriteOAuthServiceSchema
148
+ >;
149
+
150
+ const CalendlyOAuthServiceSchema = BaseServiceSchema.extend({
151
+ mode: ServiceModeSchema.default("your-own"),
152
+ });
153
+
139
154
  export const ServicesSchema = z.object({
140
155
  inference: InferenceServiceSchema.default(InferenceServiceSchema.parse({})),
141
156
  "image-generation": ImageGenerationServiceSchema.default(
@@ -184,6 +199,15 @@ export const ServicesSchema = z.object({
184
199
  "hubspot-oauth": HubspotOAuthServiceSchema.default(
185
200
  HubspotOAuthServiceSchema.parse({}),
186
201
  ),
202
+ "monday-oauth": MondayOAuthServiceSchema.default(
203
+ MondayOAuthServiceSchema.parse({}),
204
+ ),
205
+ "eventbrite-oauth": EventbriteOAuthServiceSchema.default(
206
+ EventbriteOAuthServiceSchema.parse({}),
207
+ ),
208
+ "calendly-oauth": CalendlyOAuthServiceSchema.default(
209
+ CalendlyOAuthServiceSchema.parse({}),
210
+ ),
187
211
  });
188
212
  export type Services = z.infer<typeof ServicesSchema>;
189
213
 
@@ -1,12 +1,20 @@
1
1
  import { z } from "zod";
2
2
 
3
+ import { listProviderModelFamilies } from "../../providers/speech-to-text/provider-catalog.js";
4
+ import {
5
+ STT_ROLE_REQUIREMENTS,
6
+ STT_ROLES,
7
+ type SttRole,
8
+ sttRoleCapabilityGap,
9
+ } from "../../stt/roles.js";
10
+ import type { SttProviderId } from "../../stt/types.js";
11
+
3
12
  /**
4
13
  * Valid STT provider identifiers. New providers append here and register
5
14
  * an adapter.
6
15
  */
7
16
  export const VALID_STT_PROVIDERS = [
8
17
  "deepgram",
9
- "deepgram-flux",
10
18
  "google-gemini",
11
19
  "openai-whisper",
12
20
  "xai",
@@ -38,12 +46,126 @@ const STT_PROVIDER_ALIASES: Record<
38
46
  * The map only holds entries the user has explicitly configured — it is
39
47
  * NOT required to enumerate every known provider.
40
48
  */
41
- export const SttProvidersSchema = z.record(
42
- z.string(),
43
- z.record(z.string(), z.unknown()).default({}),
44
- );
49
+ export const SttProvidersSchema = z
50
+ .record(z.string(), z.record(z.string(), z.unknown()).default({}))
51
+ .check((ctx) => {
52
+ // `model` is only meaningful for providers the catalog knows, and the map
53
+ // is deliberately open to ids it does not: entries for future providers
54
+ // must keep round-tripping untouched. So validate the key where it means
55
+ // something and leave the rest alone.
56
+ for (const [providerId, settings] of Object.entries(ctx.value)) {
57
+ const model = settings?.model;
58
+ if (model === undefined) {
59
+ continue;
60
+ }
61
+ const families = listProviderModelFamilies(providerId as SttProviderId);
62
+ if (families.length === 0) {
63
+ continue;
64
+ }
65
+ if (typeof model !== "string" || !families.includes(model as never)) {
66
+ ctx.issues.push({
67
+ code: "custom",
68
+ path: [providerId, "model"],
69
+ message: `services.stt.providers.${providerId}.model must be one of: ${families.join(", ")}`,
70
+ input: model,
71
+ });
72
+ }
73
+ }
74
+ });
45
75
  export type SttProviders = z.infer<typeof SttProvidersSchema>;
46
76
 
77
+ /**
78
+ * Per-consumer provider overrides: `services.stt.roles.<role>`.
79
+ *
80
+ * Sparse: an unset role falls back to `services.stt.provider`. Capability is
81
+ * per boundary while the base setting is global, so a provider that is right
82
+ * for live voice can be incapable of batch transcription. A role is how one
83
+ * consumer says which provider and model family it needs without moving the
84
+ * others onto it.
85
+ *
86
+ * Validation is fail-closed: a pair the provider cannot serve is rejected
87
+ * here rather than at dial time. Accepting it and falling back at resolve
88
+ * time is the silent substitution this config exists to make visible.
89
+ */
90
+ export const SttRolesSchema = z
91
+ .partialRecord(
92
+ z.enum(STT_ROLES),
93
+ z.object({
94
+ provider: z
95
+ .preprocess(
96
+ (v) => {
97
+ if (typeof v !== "string") {
98
+ return v;
99
+ }
100
+ const k = v.trim().toLowerCase();
101
+ return STT_PROVIDER_ALIASES[k] ?? k;
102
+ },
103
+ z.enum(VALID_STT_PROVIDERS, {
104
+ error: `a services.stt.roles provider must be one of: ${VALID_STT_PROVIDERS.join(", ")}`,
105
+ }),
106
+ )
107
+ .describe("Provider this consumer uses"),
108
+ model: z
109
+ .string({ error: "a services.stt.roles model must be a string" })
110
+ .optional()
111
+ .describe(
112
+ "Model family this consumer uses. Omitted runs the provider's default family",
113
+ ),
114
+ }),
115
+ )
116
+ .check((ctx) => {
117
+ for (const [role, selection] of Object.entries(ctx.value)) {
118
+ if (selection === undefined) {
119
+ continue;
120
+ }
121
+ const families = listProviderModelFamilies(
122
+ selection.provider as SttProviderId,
123
+ );
124
+ if (
125
+ selection.model !== undefined &&
126
+ !families.includes(selection.model as never)
127
+ ) {
128
+ ctx.issues.push({
129
+ code: "custom",
130
+ path: [role, "model"],
131
+ message:
132
+ families.length === 0
133
+ ? `services.stt.roles.${role}: ${selection.provider} offers a single model, so model cannot be set`
134
+ : `services.stt.roles.${role}.model must be one of: ${families.join(", ")}`,
135
+ input: selection.model,
136
+ });
137
+ continue;
138
+ }
139
+ const gap = sttRoleCapabilityGap(role as SttRole, selection);
140
+ if (gap !== null) {
141
+ ctx.issues.push({
142
+ code: "custom",
143
+ path: [role],
144
+ message: `services.stt.roles.${role} cannot be ${describeSelection(selection)}: ${gap}`,
145
+ input: selection,
146
+ });
147
+ }
148
+ }
149
+ })
150
+ .describe(
151
+ `Per-consumer STT provider overrides (${STT_ROLES.join(", ")}). An unset role uses services.stt.provider`,
152
+ );
153
+
154
+ export type SttRoles = z.infer<typeof SttRolesSchema>;
155
+
156
+ /** How a role's selection reads in an error: "deepgram" or "deepgram/flux". */
157
+ function describeSelection(selection: {
158
+ provider: string;
159
+ model?: string | undefined;
160
+ }): string {
161
+ return selection.model === undefined
162
+ ? `"${selection.provider}"`
163
+ : `"${selection.provider}" running ${selection.model}`;
164
+ }
165
+
166
+ /** Re-exported so config consumers need not reach into the stt module. */
167
+ export { STT_ROLE_REQUIREMENTS, STT_ROLES, type SttRole };
168
+
47
169
  /**
48
170
  * Canonical STT service configuration.
49
171
  *
@@ -98,6 +220,7 @@ export const SttServiceSchema = z
98
220
  "BCP-47 language code (e.g. 'en-US', 'hi') or 'multi' for code-switching across languages. Defaults to 'multi'; providers that detect natively ignore it",
99
221
  ),
100
222
  providers: SttProvidersSchema.default({}),
223
+ roles: SttRolesSchema.default({}),
101
224
  })
102
225
  .describe(
103
226
  "Speech-to-text service configuration -- provider selection, spoken language, and per-provider settings",