@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
@@ -20,7 +20,7 @@ import { z } from "zod";
20
20
 
21
21
  import { validateInferenceProfileConfig } from "../../api/constants/profile-config-validation.js";
22
22
  import {
23
- getEffectiveProfilesForProvider,
23
+ getUserSelectableProfilesForProvider,
24
24
  MANAGED_PROFILE_NAMES,
25
25
  resolveDefaultProfileForProvider,
26
26
  } from "../../config/default-profile-catalog.js";
@@ -436,8 +436,9 @@ function fragmentFromBody(
436
436
 
437
437
  /**
438
438
  * Enumerate every live reference to profile `name` in the raw `llm` config
439
- * block: `activeProfile`, `advisorProfile`, each `callSites.<id>.profile`, and
440
- * every mix arm (`profiles.<mix>.mix[].profile`). Deleting a profile while any
439
+ * block: `activeProfile`, `advisorProfile`, each `callSites.<id>.profile`,
440
+ * every mix arm (`profiles.<mix>.mix[].profile`), and every fallback pointer
441
+ * (`profiles.<p>.fallbackProfile`). Deleting a profile while any
441
442
  * of these point at it would leave a dangling reference that `LLMSchema`'s
442
443
  * superRefine rejects on the next load — silently resetting the user's chat
443
444
  * model or call-site pins. The delete handler rejects instead.
@@ -467,13 +468,17 @@ export function collectProfileReferences(
467
468
  const profiles = asPlainObject(llm.profiles);
468
469
  if (profiles) {
469
470
  for (const [profileName, profileEntry] of Object.entries(profiles)) {
470
- const mix = asPlainObject(profileEntry)?.mix;
471
+ const entry = asPlainObject(profileEntry);
472
+ const mix = entry?.mix;
471
473
  if (
472
474
  Array.isArray(mix) &&
473
475
  mix.some((arm) => asPlainObject(arm)?.profile === name)
474
476
  ) {
475
477
  refs.push(`llm.profiles.${profileName}.mix`);
476
478
  }
479
+ if (entry?.fallbackProfile === name) {
480
+ refs.push(`llm.profiles.${profileName}.fallbackProfile`);
481
+ }
477
482
  }
478
483
  }
479
484
  return refs;
@@ -514,7 +519,7 @@ function assertSaneMaxTokens(
514
519
 
515
520
  async function handleListProfiles() {
516
521
  const config = getConfigReadOnly();
517
- const effective = getEffectiveProfilesForProvider(
522
+ const effective = getUserSelectableProfilesForProvider(
518
523
  config.llm.profiles,
519
524
  config.llm.defaultProvider ?? null,
520
525
  );
@@ -870,7 +875,7 @@ async function handleSetActiveProfile({ body = {} }: RouteHandlerArgs) {
870
875
  // is rejected here instead of silently stripped on the next config load —
871
876
  // which would reset the user's chat-model selection.
872
877
  const config = getConfigReadOnly();
873
- const effective = getEffectiveProfilesForProvider(
878
+ const effective = getUserSelectableProfilesForProvider(
874
879
  config.llm.profiles,
875
880
  config.llm.defaultProvider ?? null,
876
881
  );
@@ -1011,7 +1016,7 @@ export const ROUTES: RouteDefinition[] = [
1011
1016
  "404": { description: "Profile not found" },
1012
1017
  "409": {
1013
1018
  description:
1014
- "Profile is still referenced by activeProfile, advisorProfile, a call site, a default-tier override, or a mix arm",
1019
+ "Profile is still referenced by activeProfile, advisorProfile, a call site, a default-tier override, a mix arm, or another profile's fallbackProfile",
1015
1020
  },
1016
1021
  },
1017
1022
  handler: handleDeleteProfile,
@@ -86,7 +86,7 @@ const savedConnectionResponseSchema = ProviderConnectionSchema.extend({
86
86
  * Parse and validate `base_url` and `models` from the request body.
87
87
  *
88
88
  * `base_url` is only accepted for providers in
89
- * `PROVIDERS_ALLOWING_CUSTOM_BASE_URL` (openai-compatible and ollama).
89
+ * `PROVIDERS_ALLOWING_CUSTOM_BASE_URL` (openai-compatible, ollama, opencode).
90
90
  * For all other providers, supplying `base_url` returns a 400. This prevents
91
91
  * API-key exfiltration: an attacker cannot create an `anthropic` connection
92
92
  * with a `base_url` pointing to their own server, which would redirect all
@@ -119,7 +119,7 @@ async function parseCustomProviderFields(
119
119
  !PROVIDERS_ALLOWING_CUSTOM_BASE_URL.has(provider)
120
120
  ) {
121
121
  throw new BadRequestError(
122
- `base_url is only valid for openai-compatible and ollama providers. Remove base_url or use a provider that accepts a custom endpoint.`,
122
+ `base_url is only valid for openai-compatible, ollama, and opencode providers. Remove base_url or use a provider that accepts a custom endpoint.`,
123
123
  );
124
124
  }
125
125
 
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { z } from "zod";
9
9
 
10
- import { getEffectiveProfilesForProvider } from "../../config/default-profile-catalog.js";
10
+ import { getUserSelectableProfilesForProvider } from "../../config/default-profile-catalog.js";
11
11
  import type { ResolutionFallbackReason } from "../../config/llm-resolver.js";
12
12
  import { selectWinningProfile } from "../../config/llm-resolver.js";
13
13
  import { getConfigReadOnly } from "../../config/loader.js";
@@ -42,7 +42,7 @@ async function handleInferenceSend({ body = {} }: RouteHandlerArgs) {
42
42
  // Validate --profile against the configured profile catalog.
43
43
  if (profile !== undefined) {
44
44
  const { llm } = getConfigReadOnly();
45
- const profiles = getEffectiveProfilesForProvider(
45
+ const profiles = getUserSelectableProfilesForProvider(
46
46
  llm?.profiles,
47
47
  llm?.defaultProvider ?? null,
48
48
  );
@@ -24,9 +24,9 @@ import { z } from "zod";
24
24
  import {
25
25
  getIngressConfigResult,
26
26
  isVelayManagedIngress,
27
- loadLastTunnelRecord,
28
27
  loadPairingTunnelRecord,
29
28
  loadRecordedAssistantId,
29
+ loadRestartTunnelRecord,
30
30
  } from "../../daemon/handlers/config-ingress.js";
31
31
  import { probeTunnel } from "../../inbound/tunnel-probe.js";
32
32
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
@@ -100,7 +100,7 @@ async function handleIngressStatus(): Promise<IngressStatusResponse> {
100
100
  if (isVelayManagedIngress() && !pairingTunnel) {
101
101
  return { state: "unconfigured" };
102
102
  }
103
- const lastTunnel = pairingTunnel ?? loadLastTunnelRecord();
103
+ const lastTunnel = loadRestartTunnelRecord();
104
104
 
105
105
  const publicBaseUrl =
106
106
  pairingTunnel?.publicBaseUrl ?? config.publicBaseUrl.trim();
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Route handlers for Discord channel configuration.
3
+ *
4
+ * GET /v1/integrations/discord/config: whether a bot token is stored
5
+ * POST /v1/integrations/discord/config: validate and store a bot token
6
+ * DELETE /v1/integrations/discord/config: clear it, disconnecting the bot
7
+ *
8
+ * The bot the assistant is reached through, not the user-scoped `discord`
9
+ * OAuth provider, which is a separate surface.
10
+ */
11
+
12
+ import { z } from "zod";
13
+
14
+ import {
15
+ clearDiscordChannelConfig,
16
+ DiscordChannelConfigResultSchema,
17
+ getDiscordChannelConfig,
18
+ setDiscordChannelConfig,
19
+ } from "../../../daemon/handlers/config-discord-channel.js";
20
+ import { ACTOR_PRINCIPALS } from "../../auth/route-policy.js";
21
+ import { BadRequestError, ServiceUnavailableError } from "../errors.js";
22
+ import { parseBody } from "../parse-body.js";
23
+ import type { RouteDefinition, RouteHandlerArgs } from "../types.js";
24
+
25
+ /**
26
+ * One named schema for the connect body, parsed at the boundary and reused as
27
+ * the route's declaration. `requestBody` is a codegen signal, not a runtime
28
+ * check: the router hands the body straight to the handler, so a cast here
29
+ * would narrow nothing while a bot token flowed to secure storage.
30
+ */
31
+ const SetDiscordConfigBodySchema = z.object({
32
+ botToken: z.string().min(1).describe("Discord bot token"),
33
+ });
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Handlers
37
+ // ---------------------------------------------------------------------------
38
+
39
+ async function handleGetDiscordConfig() {
40
+ return getDiscordChannelConfig();
41
+ }
42
+
43
+ async function handleSetDiscordConfig({ body = {} }: RouteHandlerArgs) {
44
+ const { botToken } = parseBody(SetDiscordConfigBodySchema, body);
45
+ const result = await setDiscordChannelConfig(botToken);
46
+ if (!result.success) {
47
+ throw new BadRequestError(
48
+ result.error ?? "Failed to set Discord channel config",
49
+ );
50
+ }
51
+ return result;
52
+ }
53
+
54
+ async function handleClearDiscordConfig() {
55
+ const result = await clearDiscordChannelConfig();
56
+ if (!result.success) {
57
+ // The token is still stored and the bot still connected; a 200 envelope
58
+ // here reads as a completed disconnect to every generated client.
59
+ throw new ServiceUnavailableError(
60
+ result.error ?? "Failed to clear Discord channel config",
61
+ );
62
+ }
63
+ return result;
64
+ }
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Routes
68
+ // ---------------------------------------------------------------------------
69
+
70
+ export const ROUTES: RouteDefinition[] = [
71
+ {
72
+ operationId: "integrations_discord_config_get",
73
+ endpoint: "integrations/discord/config",
74
+ method: "GET",
75
+ policy: {
76
+ requiredScopes: ["settings.read"],
77
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
78
+ },
79
+ summary: "Get Discord config",
80
+ description: "Check whether a Discord bot token is configured.",
81
+ tags: ["integrations"],
82
+ responseBody: DiscordChannelConfigResultSchema,
83
+ handler: () => handleGetDiscordConfig(),
84
+ },
85
+ {
86
+ operationId: "integrations_discord_config_post",
87
+ endpoint: "integrations/discord/config",
88
+ method: "POST",
89
+ policy: {
90
+ requiredScopes: ["settings.write"],
91
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
92
+ },
93
+ summary: "Set Discord config",
94
+ description:
95
+ "Validate a Discord bot token and store it. Storing it is what " +
96
+ "connects the bot: the gateway's Discord client is credential-gated " +
97
+ "and starts on the watcher's next tick.",
98
+ tags: ["integrations"],
99
+ handler: handleSetDiscordConfig,
100
+ requestBody: SetDiscordConfigBodySchema,
101
+ responseBody: DiscordChannelConfigResultSchema,
102
+ },
103
+ {
104
+ operationId: "integrations_discord_config_delete",
105
+ endpoint: "integrations/discord/config",
106
+ method: "DELETE",
107
+ policy: {
108
+ requiredScopes: ["settings.write"],
109
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
110
+ },
111
+ summary: "Clear Discord config",
112
+ description:
113
+ "Clear the stored Discord bot token, which disconnects the bot on the " +
114
+ "gateway's next watcher tick. Room choices are left alone.",
115
+ tags: ["integrations"],
116
+ responseBody: DiscordChannelConfigResultSchema,
117
+ handler: () => handleClearDiscordConfig(),
118
+ },
119
+ ];
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  import { CALL_SITE_DEFAULTS } from "../../config/call-site-defaults.js";
4
- import { getEffectiveProfilesForProvider } from "../../config/default-profile-catalog.js";
4
+ import { getUserSelectableProfilesForProvider } from "../../config/default-profile-catalog.js";
5
5
  import { resolveDefaultProfileKey } from "../../config/llm-resolver.js";
6
6
  import { loadConfig } from "../../config/loader.js";
7
7
  import {
@@ -66,7 +66,7 @@ export type LlmProfilesListResult = z.infer<
66
66
 
67
67
  async function handleListProfiles(): Promise<LlmProfilesListResult> {
68
68
  const { llm } = loadConfig();
69
- const profiles = getEffectiveProfilesForProvider(
69
+ const profiles = getUserSelectableProfilesForProvider(
70
70
  llm?.profiles,
71
71
  llm?.defaultProvider ?? null,
72
72
  );
@@ -15,7 +15,11 @@
15
15
  import { z } from "zod";
16
16
 
17
17
  import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
18
- import type { McpConfig, McpServerConfig } from "../../config/schemas/mcp.js";
18
+ import {
19
+ DEFAULT_MCP_RISK_LEVEL,
20
+ type McpConfig,
21
+ type McpServerConfig,
22
+ } from "../../config/schemas/mcp.js";
19
23
  import { estimateToolDefinitionTokens } from "../../context/token-estimator.js";
20
24
  import { reloadMcpServers } from "../../daemon/mcp-reload-service.js";
21
25
  import { McpClient } from "../../mcp/client.js";
@@ -312,7 +316,7 @@ async function handleMcpList(_args: {
312
316
  status,
313
317
  transport: safeTransport as McpServerEntry["transport"],
314
318
  enabled,
315
- defaultRiskLevel: config.defaultRiskLevel ?? "high",
319
+ defaultRiskLevel: config.defaultRiskLevel ?? DEFAULT_MCP_RISK_LEVEL,
316
320
  hasOAuth,
317
321
  hasStaticAuth,
318
322
  authType,
@@ -551,10 +555,9 @@ async function handleMcpAdd({
551
555
  const { name, transportType, url, command, args, risk, disabled, headers } =
552
556
  parseBody(McpAddParams, body);
553
557
 
554
- const riskLevel = risk ?? "high";
555
- if (!["low", "medium", "high"].includes(riskLevel)) {
558
+ if (risk !== undefined && !["low", "medium", "high"].includes(risk)) {
556
559
  throw new BadRequestError(
557
- `Invalid risk level: ${riskLevel}. Must be low, medium, or high`,
560
+ `Invalid risk level: ${risk}. Must be low, medium, or high`,
558
561
  );
559
562
  }
560
563
 
@@ -597,10 +600,13 @@ async function handleMcpAdd({
597
600
  );
598
601
  }
599
602
 
603
+ // Writing no `defaultRiskLevel` when the caller named none leaves the level
604
+ // to `McpServerConfigSchema`, so the entry tracks the shipped default
605
+ // instead of freezing whichever level was current on the day it was added.
600
606
  serverMap[name] = {
601
607
  transport,
602
608
  enabled: !disabled,
603
- defaultRiskLevel: riskLevel,
609
+ ...(risk === undefined ? {} : { defaultRiskLevel: risk }),
604
610
  };
605
611
 
606
612
  // Store auth headers in credential store, not config
@@ -44,6 +44,7 @@ import {
44
44
  } from "../../security/secure-keys.js";
45
45
  import {
46
46
  getCredentialMetadata,
47
+ persistCredentialMetadata,
47
48
  upsertCredentialMetadata,
48
49
  } from "../../tools/credentials/metadata-store.js";
49
50
  import { getLogger } from "../../util/logger.js";
@@ -156,7 +157,8 @@ export async function reconcileVellumMetadataFromCes(warningSink: {
156
157
  if (getCredentialMetadata("vellum", field)) {
157
158
  continue;
158
159
  }
159
- upsertCredentialMetadata("vellum", field, {});
160
+ const metadata = upsertCredentialMetadata("vellum", field, {});
161
+ await persistCredentialMetadata(metadata);
160
162
  log.info(
161
163
  { field },
162
164
  "Reconciled vellum:* metadata entry from CES after import",
@@ -0,0 +1,136 @@
1
+ /**
2
+ * In-memory registry of in-flight OAuth flows, keyed by a state token.
3
+ *
4
+ * Browser-mediated OAuth finishes out of band: the daemon starts a flow, hands
5
+ * the client a state token, and completes the flow in the background (loopback
6
+ * capture, device-code poll) or on a later request (a pasted code). The client
7
+ * polls a status route with the state token to learn the outcome.
8
+ *
9
+ * Each caller creates its own registry so flows from different providers never
10
+ * share a key space. Entries expire after a TTL so the map cannot grow
11
+ * unbounded; call `cleanupExpired()` when starting a flow.
12
+ */
13
+
14
+ const DEFAULT_PENDING_FLOW_TTL_MS = 10 * 60 * 1000; // 10 minutes
15
+
16
+ export type PendingFlowStatus = "pending" | "connected" | "error";
17
+
18
+ /** Fields every tracked flow carries, whatever the provider or transport. */
19
+ export interface PendingFlowFields {
20
+ status: PendingFlowStatus;
21
+ /** Human-readable failure detail, set when `status` is `error`. */
22
+ error?: string;
23
+ /** Machine-readable failure code, when the provider supplies one. */
24
+ errorCode?: string;
25
+ createdAt: number;
26
+ }
27
+
28
+ /** A tracked flow: the common fields plus whatever the caller stashed on it. */
29
+ export type PendingFlow<TExtra extends object> = PendingFlowFields & TExtra;
30
+
31
+ /** The subset of a flow that status routes report back to clients. */
32
+ export interface PendingFlowStatusReport {
33
+ status: PendingFlowStatus;
34
+ error?: string;
35
+ errorCode?: string;
36
+ }
37
+
38
+ export interface PendingFlowMarkOptions {
39
+ error?: string;
40
+ errorCode?: string;
41
+ }
42
+
43
+ export interface PendingFlowRegistry<TExtra extends object> {
44
+ /** How long an entry lives before `cleanupExpired()` drops it. */
45
+ readonly ttlMs: number;
46
+ /** Track a new pending flow under `state` and return the stored entry. */
47
+ start(state: string, extra: TExtra): PendingFlow<TExtra>;
48
+ get(state: string): PendingFlow<TExtra> | undefined;
49
+ delete(state: string): void;
50
+ /** Update a tracked flow's status in place, if it still exists. */
51
+ mark(
52
+ state: string,
53
+ status: PendingFlowStatus,
54
+ options?: PendingFlowMarkOptions,
55
+ ): void;
56
+ isExpired(flow: PendingFlowFields, now?: number): boolean;
57
+ /** Status view of a tracked flow, or `undefined` when there is no such flow. */
58
+ readStatus(state: string): PendingFlowStatusReport | undefined;
59
+ /** Drop entries older than the TTL. */
60
+ cleanupExpired(): void;
61
+ }
62
+
63
+ export interface PendingFlowRegistryOptions {
64
+ ttlMs?: number;
65
+ }
66
+
67
+ export function createPendingFlowRegistry<
68
+ TExtra extends object = Record<string, never>,
69
+ >(options: PendingFlowRegistryOptions = {}): PendingFlowRegistry<TExtra> {
70
+ const ttlMs = options.ttlMs ?? DEFAULT_PENDING_FLOW_TTL_MS;
71
+ const flows = new Map<string, PendingFlow<TExtra>>();
72
+
73
+ return {
74
+ ttlMs,
75
+
76
+ start(state, extra) {
77
+ const flow = {
78
+ ...extra,
79
+ status: "pending",
80
+ createdAt: Date.now(),
81
+ } as PendingFlow<TExtra>;
82
+ flows.set(state, flow);
83
+ return flow;
84
+ },
85
+
86
+ get(state) {
87
+ return flows.get(state);
88
+ },
89
+
90
+ delete(state) {
91
+ flows.delete(state);
92
+ },
93
+
94
+ mark(state, status, markOptions) {
95
+ const flow = flows.get(state);
96
+ if (!flow) {
97
+ return;
98
+ }
99
+ flow.status = status;
100
+ if (markOptions?.error !== undefined) {
101
+ flow.error = markOptions.error;
102
+ }
103
+ if (markOptions?.errorCode !== undefined) {
104
+ flow.errorCode = markOptions.errorCode;
105
+ }
106
+ },
107
+
108
+ isExpired(flow, now = Date.now()) {
109
+ return now - flow.createdAt > ttlMs;
110
+ },
111
+
112
+ readStatus(state) {
113
+ const flow = flows.get(state);
114
+ if (!flow) {
115
+ return undefined;
116
+ }
117
+ const report: PendingFlowStatusReport = { status: flow.status };
118
+ if (flow.error !== undefined) {
119
+ report.error = flow.error;
120
+ }
121
+ if (flow.errorCode !== undefined) {
122
+ report.errorCode = flow.errorCode;
123
+ }
124
+ return report;
125
+ },
126
+
127
+ cleanupExpired() {
128
+ const cutoff = Date.now() - ttlMs;
129
+ for (const [state, flow] of flows) {
130
+ if (flow.createdAt < cutoff) {
131
+ flows.delete(state);
132
+ }
133
+ }
134
+ },
135
+ };
136
+ }
@@ -30,6 +30,8 @@ import {
30
30
  scrubStoredCredentialFromTranscripts,
31
31
  } from "../../daemon/credential-transcript-scrub.js";
32
32
  import { syncManualTokenConnection } from "../../oauth/manual-token-connection.js";
33
+ import { syncAvatarToPlatform } from "../../platform/sync-avatar.js";
34
+ import { syncWorkspaceIdentityToPlatform } from "../../platform/sync-identity.js";
33
35
  import { maybeReseedCapabilitiesAfterManagedCredential } from "../../plugins/defaults/memory/substrate/boot-maintenance.js";
34
36
  import { validateAnthropicApiKey } from "../../providers/anthropic/client.js";
35
37
  import { validateAtlasCloudApiKey } from "../../providers/atlascloud/client.js";
@@ -423,6 +425,11 @@ async function handleAddSecret({ body }: RouteHandlerArgs) {
423
425
  // defaulting; the hook no-ops until the connection is complete.
424
426
  // Detached — must not block the response.
425
427
  void maybeDefaultSpeechToManaged();
428
+ // Same last-write-wins shape: the startup syncs no-op before live
429
+ // registration, so re-enqueue them here. Both dedup and no-op until
430
+ // the client and assistant id exist.
431
+ syncWorkspaceIdentityToPlatform();
432
+ syncAvatarToPlatform();
426
433
  }
427
434
  log.info({ service, field }, "Credential added via HTTP");
428
435
  return { success: true, type, name };
@@ -13,9 +13,15 @@ import { extname, join } from "node:path";
13
13
 
14
14
  import { z } from "zod";
15
15
 
16
- import { listProviderEntries } from "../../providers/speech-to-text/provider-catalog.js";
16
+ import {
17
+ baseModelFamilyFor,
18
+ listProviderEntries,
19
+ listProviderModelFamilies,
20
+ turnDetectionLanguagesFor,
21
+ } from "../../providers/speech-to-text/provider-catalog.js";
17
22
  import { resolveBatchTranscriber } from "../../providers/speech-to-text/resolve.js";
18
23
  import { normalizeSttError } from "../../stt/daemon-batch-transcriber.js";
24
+ import type { SttRole } from "../../stt/roles.js";
19
25
  import type { BatchTranscriber, SttErrorCategory } from "../../stt/types.js";
20
26
  import { SttError } from "../../stt/types.js";
21
27
  import { getLogger } from "../../util/logger.js";
@@ -232,10 +238,12 @@ const STT_ERROR_MAP: Record<SttErrorCategory, () => RouteError> = {
232
238
  * surfaced verbatim. `null` means nothing is configured at all, and any other
233
239
  * throw is an unexpected resolver failure.
234
240
  */
235
- async function resolveBatchTranscriberOrUnavailable(): Promise<BatchTranscriber> {
241
+ async function resolveBatchTranscriberOrUnavailable(
242
+ role: SttRole,
243
+ ): Promise<BatchTranscriber> {
236
244
  let transcriber: BatchTranscriber | null;
237
245
  try {
238
- transcriber = await resolveBatchTranscriber();
246
+ transcriber = await resolveBatchTranscriber({ role });
239
247
  } catch (err) {
240
248
  if (err instanceof SttError) {
241
249
  throw new ServiceUnavailableError(err.message);
@@ -257,10 +265,30 @@ async function resolveBatchTranscriberOrUnavailable(): Promise<BatchTranscriber>
257
265
  // ---------------------------------------------------------------------------
258
266
 
259
267
  function handleListProviders() {
260
- const entries = listProviderEntries();
268
+ // Model-family rows share their parent's credential and are selected
269
+ // through `services.stt.providers.<id>.model`, so a provider picker must
270
+ // not offer them as separate providers.
271
+ const entries = listProviderEntries().filter(
272
+ (entry) => entry.variantOf === undefined,
273
+ );
261
274
  const providers = entries.map((e) => ({
262
275
  id: e.id,
263
276
  displayName: e.displayName,
277
+ // The families selectable through `services.stt.providers.<id>.model`.
278
+ // Variant rows are filtered out above because a provider picker must not
279
+ // offer them as providers, which leaves this the only way a client can
280
+ // learn a family exists at all.
281
+ modelFamilies: listProviderModelFamilies(e.id),
282
+ // Which of those families the provider runs when none is named. Sent
283
+ // explicitly rather than left as "the first entry", so a client that
284
+ // needs to write the non-variant family is not depending on array order
285
+ // across the wire.
286
+ baseModelFamily: baseModelFamilyFor(e.id),
287
+ // The spoken languages this provider's turn-detecting family serves, when
288
+ // it has one. Reported rather than left for a client to know, so the
289
+ // roster lives in one place instead of being copied into every surface
290
+ // that has to decide whether to offer turn detection.
291
+ turnDetectionLanguages: turnDetectionLanguagesFor(e.id),
264
292
  subtitle: e.subtitle,
265
293
  setupMode: e.setupMode,
266
294
  setupHint: e.setupHint,
@@ -317,7 +345,7 @@ async function handleTranscribe({ body }: RouteHandlerArgs) {
317
345
  }
318
346
 
319
347
  // -- Resolve transcriber --------------------------------------------------
320
- const transcriber = await resolveBatchTranscriberOrUnavailable();
348
+ const transcriber = await resolveBatchTranscriberOrUnavailable("dictation");
321
349
 
322
350
  // -- Transcribe with timeout ----------------------------------------------
323
351
  const abortController = new AbortController();
@@ -375,7 +403,7 @@ async function handleTranscribeFile({ body }: RouteHandlerArgs) {
375
403
  );
376
404
  }
377
405
 
378
- const transcriber = await resolveBatchTranscriberOrUnavailable();
406
+ const transcriber = await resolveBatchTranscriberOrUnavailable("batch");
379
407
 
380
408
  const startTime = Date.now();
381
409
  let wavPath: string | null = null;
@@ -449,6 +477,10 @@ export const ROUTES: RouteDefinition[] = [
449
477
  conversationStreamingMode: z.string().optional(),
450
478
  // Optional on the wire so older generated clients keep validating.
451
479
  languageSelection: z.enum(["manual", "auto"]).optional(),
480
+ // Optional on the wire so older generated clients keep validating.
481
+ modelFamilies: z.array(z.string()).optional(),
482
+ baseModelFamily: z.string().optional(),
483
+ turnDetectionLanguages: z.array(z.string()).optional(),
452
484
  credentialsGuide: z.string().optional(),
453
485
  }),
454
486
  ),
@@ -94,7 +94,7 @@ export const ROUTES: RouteDefinition[] = [
94
94
  ? (getSchedule(scheduleId)?.inferenceProfile ?? null)
95
95
  : null;
96
96
 
97
- return wakeAgentForOpportunity({
97
+ const result = await wakeAgentForOpportunity({
98
98
  conversationId,
99
99
  hint,
100
100
  source,
@@ -108,6 +108,13 @@ export const ROUTES: RouteDefinition[] = [
108
108
  ? { untrustedOutput: { content: externalContent, source: "webhook" } }
109
109
  : {}),
110
110
  });
111
+ // Pin the wire shape to the declared responseBody: WakeResult carries
112
+ // in-process diagnostics (exitReason) beyond this route's contract.
113
+ return {
114
+ invoked: result.invoked,
115
+ producedToolCalls: result.producedToolCalls,
116
+ ...(result.reason !== undefined ? { reason: result.reason } : {}),
117
+ };
111
118
  },
112
119
  },
113
120
  ];
@@ -6,7 +6,10 @@
6
6
  * deliberately not a second story: binary frames or base64 `audio` events in,
7
7
  * a `{ type: "stop" }` text frame to flush, and a `StreamingTranscriber`
8
8
  * resolved by `resolveStreamingTranscriber()` so provider selection,
9
- * credentials, and language live in exactly one place.
9
+ * credentials, and language live in exactly one place. Watch is its own STT
10
+ * consumer there (`services.stt.roles.watch`, falling back to the global
11
+ * provider), because it shares dictation's transport without sharing its
12
+ * batch fallback.
10
13
  *
11
14
  * What differs is everything downstream of a transcript. Dictation hands its
12
15
  * text back to the client; a watch session hands each final to
@@ -421,11 +424,12 @@ export class WatchStreamSession {
421
424
  }
422
425
  const { resolveStreamingTranscriber } =
423
426
  await import("../../providers/speech-to-text/resolve.js");
424
- return resolveStreamingTranscriber(
425
- this.options.sampleRate !== undefined
427
+ return resolveStreamingTranscriber({
428
+ role: "watch",
429
+ ...(this.options.sampleRate !== undefined
426
430
  ? { sampleRate: this.options.sampleRate }
427
- : {},
428
- );
431
+ : {}),
432
+ });
429
433
  }
430
434
 
431
435
  /**
@@ -5,6 +5,7 @@ import {
5
5
  conversationMetadataSyncTag,
6
6
  SYNC_TAGS,
7
7
  } from "../../daemon/message-types/sync.js";
8
+ import { syncAvatarToPlatform } from "../../platform/sync-avatar.js";
8
9
  import { getAvatarImagePath } from "../../util/platform.js";
9
10
  import { broadcastMessage } from "../assistant-event-hub.js";
10
11
  import { isStreamSeqStampingDisabled } from "../assistant-stream-state.js";
@@ -31,6 +32,7 @@ export function publishAvatarChanged(originClientId?: string): void {
31
32
  avatarPath: getAvatarImagePath(),
32
33
  });
33
34
  void publishSyncInvalidation([SYNC_TAGS.assistantAvatar], originClientId);
35
+ syncAvatarToPlatform();
34
36
  }
35
37
 
36
38
  export function publishIdentityChanged(
@@ -4,9 +4,9 @@
4
4
  *
5
5
  * The gateway resolves a per-actor verdict from its ACL DB and stamps it onto
6
6
  * inbound `sourceMetadata`. These pure mappers turn that verdict into the same
7
- * {@link TrustContext} the local resolver would have produced ACL + identity
8
- * only. INFO fields (notes, userFile, contactType, interactionCount) are never
9
- * carried on the wire; the consumer re-joins them locally by contactId.
7
+ * {@link TrustContext} the local resolver would have produced: ACL + identity
8
+ * only. INFO fields (notes, userFile, contactType) are never carried on the
9
+ * wire; the consumer re-joins them locally by contactId.
10
10
  */
11
11
 
12
12
  import type { TrustVerdict } from "@vellumai/gateway-client";