@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
@@ -0,0 +1,78 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+
3
+ import { credentialKey } from "@vellumai/credential-storage";
4
+ import type { CredentialRecord } from "@vellumai/service-contracts/credential-rpc";
5
+
6
+ import type { CredentialRecordBackend } from "../security/ces-rpc-record-backend.js";
7
+ import {
8
+ _setMetadataPath,
9
+ persistCredentialMetadata,
10
+ setCredentialRecordBackend,
11
+ upsertCredentialMetadata,
12
+ } from "../tools/credentials/metadata-store.js";
13
+
14
+ function makeBackend(): CredentialRecordBackend & {
15
+ store: Map<string, CredentialRecord>;
16
+ } {
17
+ const store = new Map<string, CredentialRecord>();
18
+ return {
19
+ store,
20
+ isAvailable: () => true,
21
+ get: async (account) => store.get(account),
22
+ set: async (account, record) => {
23
+ store.set(account, record);
24
+ return true;
25
+ },
26
+ delete: async (account) => {
27
+ if (!store.has(account)) {
28
+ return "not-found";
29
+ }
30
+ store.delete(account);
31
+ return "deleted";
32
+ },
33
+ list: async () =>
34
+ [...store.entries()].map(([account, record]) => ({ account, record })),
35
+ bulkSet: async (records) => {
36
+ for (const { account, record } of records) {
37
+ store.set(account, record);
38
+ }
39
+ return records.map((entry) => ({ account: entry.account, ok: true }));
40
+ },
41
+ };
42
+ }
43
+
44
+ describe("CES credential record write-through", () => {
45
+ afterEach(() => {
46
+ setCredentialRecordBackend(undefined);
47
+ _setMetadataPath(null);
48
+ });
49
+
50
+ test("upsert write-through updates the CES backend", async () => {
51
+ const backend = makeBackend();
52
+ setCredentialRecordBackend(backend);
53
+
54
+ const created = upsertCredentialMetadata("slack_channel", "bot_token", {
55
+ allowedTools: ["bash"],
56
+ });
57
+ await persistCredentialMetadata(created);
58
+
59
+ const stored = backend.store.get(
60
+ credentialKey("slack_channel", "bot_token"),
61
+ );
62
+ expect(stored?.allowedTools).toEqual(["bash"]);
63
+ expect(stored?.credentialId).toBe(created.credentialId);
64
+ });
65
+
66
+ test("test metadata path override skips CES write-through", async () => {
67
+ const backend = makeBackend();
68
+ setCredentialRecordBackend(backend);
69
+ _setMetadataPath("/tmp/does-not-write-through-metadata.json");
70
+
71
+ const created = upsertCredentialMetadata("github", "token", {
72
+ allowedTools: ["bash"],
73
+ });
74
+ await persistCredentialMetadata(created);
75
+
76
+ expect(backend.store.size).toBe(0);
77
+ });
78
+ });
@@ -749,12 +749,28 @@ describe("credentials routes", () => {
749
749
  expect(entry.service).toBe("vercel");
750
750
  expect(entry.field).toBe("api_token");
751
751
  expect(entry.hasSecret).toBe(true);
752
- expect(entry.scrubbedValue).toBe("****alue");
752
+ expect(entry.scrubbedValue).toBe("supe****");
753
753
 
754
754
  // AND the raw secret never appears anywhere in the response
755
755
  expect(JSON.stringify(result)).not.toContain(SECRET_VALUE);
756
756
  });
757
757
 
758
+ test("masked preview keeps a vendor token prefix", async () => {
759
+ const token = "cfat_000000000000000000000000000000000000abcd";
760
+ await setRoute!.handler({
761
+ body: {
762
+ service: "cloudflare",
763
+ field: "api_token",
764
+ value: token,
765
+ },
766
+ });
767
+
768
+ const result = (await listRoute!.handler({ body: {} })) as ListResponse;
769
+ expect(result.credentials).toHaveLength(1);
770
+ expect(result.credentials[0].scrubbedValue).toBe("cfat****");
771
+ expect(JSON.stringify(result)).not.toContain(token);
772
+ });
773
+
758
774
  test("filters credentials by search substring", async () => {
759
775
  /**
760
776
  * The optional `search` filter restricts the listing to credentials
@@ -179,6 +179,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
179
179
  "providers/inference/credential-slot-repair.ts", // boot repair: copies the shared openai-compatible slot value into per-connection slots (get/set of provider API keys only; values never logged or returned)
180
180
  "runtime/routes/inference-provider-connection-routes.ts", // connection delete removes its dedicated per-connection key slot (deleteSecureKeyAsync only; no reads)
181
181
  "daemon/handlers/config-slack-channel.ts", // Slack channel config credential management
182
+ "daemon/handlers/config-discord-channel.ts", // Discord channel bot token management
182
183
  "providers/platform-proxy/context.ts", // managed proxy API key lookup for provider initialization
183
184
  "platform/client.ts", // platform client credential store fallback for standalone CLI auth
184
185
  "mcp/mcp-header-store.ts", // MCP static auth header persistence (credential store CRUD + legacy migration)
@@ -203,8 +204,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
203
204
  "providers/registry.ts", // provider registry API key lookup for initialization
204
205
  "providers/inference/resolve-auth.ts", // provider_connection auth resolver (api_key path reads vault, mirrors registry.ts)
205
206
  "providers/inference/codex-token-refresh.ts", // Codex OAuth token refresh (reads/writes access_token, refresh_token, expires_at)
206
- "cli/commands/inference-providers.ts", // ChatGPT subscription OAuth token storage
207
- "runtime/routes/chatgpt-subscription-auth-routes.ts", // ChatGPT subscription daemon OAuth flow (stores tokens in CES)
207
+ "providers/inference/chatgpt-subscription-credentials.ts", // ChatGPT subscription sign-in token storage, shared by the daemon routes and the CLI (setSecureKeyAsync only; no reads)
208
208
  "providers/provider-availability.ts", // provider availability API key check
209
209
  "media/image-credentials.ts", // shared image-gen credential resolver (provider API key lookup)
210
210
  "persistence/embeddings/embedding-backend.ts", // embedding backend API key lookup
@@ -231,7 +231,6 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
231
231
  "runtime/routes/avatar-routes.ts", // avatar generate route reads platform_base_url from credential store
232
232
  "cli/commands/keys.ts", // CLI provider key management
233
233
  "cli/commands/oauth/connect.ts", // CLI OAuth connect stored-secret verification
234
- "runtime/routes/chatgpt-subscription-auth-routes.ts", // ChatGPT subscription OAuth token storage
235
234
  "runtime/routes/identity-routes.ts", // health/readyz endpoint checks CES connectivity via getCesClient
236
235
  "tools/executor.ts", // CES approval bridge resolves the CES RPC client via getCesClient
237
236
  "tools/network/web-fetch.ts", // Firecrawl /scrape BYOK fetch provider API key lookup (firecrawl provider key)
@@ -470,24 +469,24 @@ describe("Invariant 4: credentials only used for allowed purpose", () => {
470
469
  });
471
470
 
472
471
  // PR 18 — vault policy fields with strict defaults
473
- test("credential without explicit policy gets strict defaults (deny all)", () => {
472
+ test("credential without explicit policy gets strict defaults (deny all)", async () => {
474
473
  // A credential stored without allowed_tools defaults to empty array,
475
474
  // which the broker's isToolAllowed check fails closed on.
476
475
  upsertCredentialMetadata("test-svc", "pass", {});
477
476
 
478
- const result = broker.authorize({
477
+ const result = await broker.browserFill({
479
478
  service: "test-svc",
480
479
  field: "pass",
481
480
  toolName: "browser_fill_credential",
481
+ fill: async () => {},
482
482
  });
483
483
 
484
- expect(result.authorized).toBe(false);
485
- expect(!result.authorized && result.reason).toContain(
486
- "No tools are currently allowed",
487
- );
484
+ expect(result.success).toBe(false);
485
+ expect(result.reason).toContain("No tools are currently allowed");
488
486
  });
489
487
  });
490
488
 
489
+
491
490
  // ---------------------------------------------------------------------------
492
491
  // Invariant 6 — oauth2ClientSecret never in plaintext metadata
493
492
  // ---------------------------------------------------------------------------
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Managed backup profiles and the `fallbackProfile` pointers of the vellum
3
+ * column (`default-profile-catalog.ts`).
4
+ *
5
+ * The invariants under test:
6
+ * - Every default profile's managed (vellum) implementation points at a
7
+ * backup profile that exists in the code catalog.
8
+ * - Cross-provider rule: a backup pins its model at a DIFFERENT managed
9
+ * upstream than its primary, so an outage at the primary's provider can
10
+ * be served by the backup. Holds for the experiment arms too.
11
+ * - Probe coverage: every backup model is a catalog member of its upstream
12
+ * provider, which is what feeds the platform's model liveness probe.
13
+ * - Scoping: backups exist for the managed column only. A BYOK or chatgpt
14
+ * default provider materializes no backup profiles, and its primaries
15
+ * carry no `fallbackProfile` pointers.
16
+ * - The effective catalog parses under `LLMSchema` (exercising the
17
+ * `fallbackProfile` superRefine against the real entries), and backups
18
+ * are delete-protected like the other managed defaults.
19
+ */
20
+
21
+ import { afterEach, describe, expect, test } from "bun:test";
22
+
23
+ import { BALANCED_MODEL_EXPERIMENT_FLAG_KEY } from "../config/balanced-model-experiment.js";
24
+ import {
25
+ CODE_DEFAULT_PROFILE_ENTRIES,
26
+ getEffectiveProfile,
27
+ getEffectiveProfiles,
28
+ getEffectiveProfilesForProvider,
29
+ INVARIANT_PROFILE_NAMES,
30
+ MANAGED_PROFILE_NAMES,
31
+ MANAGED_PROFILE_TEMPLATES,
32
+ PROFILE_IMPLS,
33
+ resolveDefaultProfileForProvider,
34
+ } from "../config/default-profile-catalog.js";
35
+ import {
36
+ BACKUP_PROFILE_KEYS,
37
+ DEFAULT_PROFILE_KEYS,
38
+ DEFAULT_PROFILE_PROVIDERS,
39
+ FALLBACK_PROFILE_BY_KEY,
40
+ } from "../config/default-profile-names.js";
41
+ import { clearCachedOverrides } from "../config/feature-flag-cache.js";
42
+ import {
43
+ type DefaultProviderConfig,
44
+ LLMSchema,
45
+ type ProfileEntry,
46
+ } from "../config/schemas/llm.js";
47
+ import { isModelInCatalog } from "../providers/model-catalog.js";
48
+ import { getManagedUpstream } from "../providers/vellum-model-routing.js";
49
+ import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
50
+
51
+ const vellum: DefaultProviderConfig = { provider: "vellum" };
52
+
53
+ afterEach(() => {
54
+ clearCachedOverrides();
55
+ });
56
+
57
+ describe("fallbackProfile pointers on the vellum column", () => {
58
+ test("every default profile's vellum impl points at an existing backup", () => {
59
+ for (const key of DEFAULT_PROFILE_KEYS) {
60
+ const impl = PROFILE_IMPLS[key].vellum;
61
+ expect(impl.fallbackProfile).toBe(FALLBACK_PROFILE_BY_KEY[key]);
62
+ const backup = CODE_DEFAULT_PROFILE_ENTRIES[FALLBACK_PROFILE_BY_KEY[key]];
63
+ expect(backup).toBeDefined();
64
+ expect(backup.provider).toBe("vellum");
65
+ expect(backup.source).toBe("managed");
66
+ // Single hop: a backup never declares a fallback of its own.
67
+ expect(backup.fallbackProfile).toBeUndefined();
68
+ }
69
+ });
70
+
71
+ test("no BYOK or chatgpt column impl carries fallbackProfile", () => {
72
+ for (const key of DEFAULT_PROFILE_KEYS) {
73
+ for (const provider of DEFAULT_PROFILE_PROVIDERS) {
74
+ if (provider === "vellum") {
75
+ continue;
76
+ }
77
+ expect(PROFILE_IMPLS[key][provider].fallbackProfile).toBeUndefined();
78
+ }
79
+ // A default-capable provider without a named matrix column
80
+ // materializes from the shared BYOK templates: no pointer either.
81
+ const entry = resolveDefaultProfileForProvider(undefined, key, {
82
+ provider: "together",
83
+ });
84
+ expect(entry?.fallbackProfile).toBeUndefined();
85
+ }
86
+ });
87
+ });
88
+
89
+ describe("cross-provider rule", () => {
90
+ test("every backup dispatches through a different managed upstream than its primary", () => {
91
+ for (const key of DEFAULT_PROFILE_KEYS) {
92
+ const primary = CODE_DEFAULT_PROFILE_ENTRIES[key];
93
+ const backup = CODE_DEFAULT_PROFILE_ENTRIES[FALLBACK_PROFILE_BY_KEY[key]];
94
+ const primaryUpstream = getManagedUpstream(primary.model as string);
95
+ const backupUpstream = getManagedUpstream(backup.model as string);
96
+ expect(primaryUpstream).not.toBeNull();
97
+ expect(backupUpstream).not.toBeNull();
98
+ expect(backupUpstream).not.toBe(primaryUpstream);
99
+ }
100
+ });
101
+
102
+ test("the balanced experiment arms keep the cross-provider split", () => {
103
+ const backup =
104
+ CODE_DEFAULT_PROFILE_ENTRIES[FALLBACK_PROFILE_BY_KEY.balanced];
105
+ const backupUpstream = getManagedUpstream(backup.model as string);
106
+ for (const arm of ["terra", "glm-5p2"]) {
107
+ setOverridesForTesting({ [BALANCED_MODEL_EXPERIMENT_FLAG_KEY]: arm });
108
+ const armed = resolveDefaultProfileForProvider(
109
+ undefined,
110
+ "balanced",
111
+ vellum,
112
+ );
113
+ expect(armed?.fallbackProfile).toBe(FALLBACK_PROFILE_BY_KEY.balanced);
114
+ const armedUpstream = getManagedUpstream(armed?.model as string);
115
+ expect(armedUpstream).not.toBeNull();
116
+ expect(armedUpstream).not.toBe(backupUpstream);
117
+ }
118
+ });
119
+
120
+ test("every backup model is a catalog member of its upstream (liveness-probe coverage)", () => {
121
+ for (const key of BACKUP_PROFILE_KEYS) {
122
+ const model = CODE_DEFAULT_PROFILE_ENTRIES[key].model as string;
123
+ const upstream = getManagedUpstream(model);
124
+ expect(upstream).not.toBeNull();
125
+ expect(isModelInCatalog(upstream as string, model)).toBe(true);
126
+ }
127
+ });
128
+ });
129
+
130
+ describe("managed-column-only scoping", () => {
131
+ test("the managed column materializes the backups after the primaries", () => {
132
+ const effective = getEffectiveProfilesForProvider(undefined, vellum);
133
+ for (const key of BACKUP_PROFILE_KEYS) {
134
+ expect(effective[key]).toBeDefined();
135
+ expect(effective[key]?.provider).toBe("vellum");
136
+ expect(effective[key]?.source).toBe("managed");
137
+ }
138
+ // The seeder inserts missing managed keys in MANAGED_PROFILE_TEMPLATES
139
+ // order, which is what places backups after the primaries in
140
+ // `profileOrder` presentation.
141
+ expect(Object.keys(MANAGED_PROFILE_TEMPLATES)).toEqual([
142
+ ...DEFAULT_PROFILE_KEYS,
143
+ ...BACKUP_PROFILE_KEYS,
144
+ ]);
145
+ });
146
+
147
+ test("a null defaultProvider (pre-defaultProvider install) keeps its backups", () => {
148
+ const effective = getEffectiveProfiles(undefined);
149
+ for (const key of BACKUP_PROFILE_KEYS) {
150
+ expect(effective[key]).toBeDefined();
151
+ expect(
152
+ resolveDefaultProfileForProvider(undefined, key, null),
153
+ ).toBeDefined();
154
+ }
155
+ });
156
+
157
+ test("a BYOK default provider materializes no backups and no pointers", () => {
158
+ for (const provider of ["anthropic", "openai", "gemini"] as const) {
159
+ const effective = getEffectiveProfilesForProvider(undefined, {
160
+ provider,
161
+ });
162
+ for (const key of BACKUP_PROFILE_KEYS) {
163
+ expect(effective[key]).toBeUndefined();
164
+ }
165
+ for (const key of DEFAULT_PROFILE_KEYS) {
166
+ expect(effective[key]?.fallbackProfile).toBeUndefined();
167
+ }
168
+ }
169
+ });
170
+
171
+ test("the chatgpt column materializes no backups and no pointers", () => {
172
+ const effective = getEffectiveProfilesForProvider(undefined, {
173
+ provider: "chatgpt",
174
+ });
175
+ for (const key of BACKUP_PROFILE_KEYS) {
176
+ expect(effective[key]).toBeUndefined();
177
+ }
178
+ for (const key of DEFAULT_PROFILE_KEYS) {
179
+ expect(effective[key]?.fallbackProfile).toBeUndefined();
180
+ }
181
+ });
182
+
183
+ test("a persisted managed stub does not resurrect a backup off the managed column", () => {
184
+ // A `config get` -> `config set` round-trip on a managed install can
185
+ // leave a thin managed stub for a backup in `llm.profiles`
186
+ // (`normalizeManagedProfileWrites`). The stub is the workspace's slot for
187
+ // a code-owned body, not a profile, so once the column has no body behind
188
+ // it the name must stop being listed rather than resolve to a
189
+ // providerless entry the pickers would offer and `LLMSchema` would
190
+ // reject.
191
+ const workspace: Record<string, ProfileEntry> = {
192
+ "balanced-backup": { source: "managed" },
193
+ };
194
+ for (const provider of ["anthropic", "chatgpt"] as const) {
195
+ const effective = getEffectiveProfilesForProvider(workspace, {
196
+ provider,
197
+ });
198
+ expect(effective["balanced-backup"]).toBeUndefined();
199
+ expect(
200
+ resolveDefaultProfileForProvider(workspace, "balanced-backup", {
201
+ provider,
202
+ }),
203
+ ).toBeUndefined();
204
+ }
205
+ // The managed column still has a body for the stub to stand for.
206
+ expect(
207
+ getEffectiveProfilesForProvider(workspace, vellum)["balanced-backup"]
208
+ ?.model,
209
+ ).toBe(CODE_DEFAULT_PROFILE_ENTRIES["balanced-backup"]?.model);
210
+ });
211
+
212
+ test("a user-owned entry under a backup name stays listed off the managed column", () => {
213
+ // Nothing code-owned resolves there, so the entry is simply a profile of
214
+ // that name and keeps its own body.
215
+ const workspace: Record<string, ProfileEntry> = {
216
+ "balanced-backup": {
217
+ source: "user",
218
+ provider: "anthropic",
219
+ model: "claude-opus-4-7",
220
+ },
221
+ };
222
+ const effective = getEffectiveProfilesForProvider(workspace, {
223
+ provider: "anthropic",
224
+ });
225
+ expect(effective["balanced-backup"]?.model).toBe("claude-opus-4-7");
226
+ });
227
+ });
228
+
229
+ describe("effective-profile machinery", () => {
230
+ test("the full effective catalog parses under LLMSchema", () => {
231
+ // Exercises the fallbackProfile superRefine (target exists, no self
232
+ // reference, no mix target, single hop) against the real entries.
233
+ for (const profiles of [
234
+ getEffectiveProfiles(undefined),
235
+ getEffectiveProfilesForProvider(undefined, vellum),
236
+ ]) {
237
+ const parsed = LLMSchema.parse({
238
+ profiles,
239
+ activeProfile: "balanced",
240
+ });
241
+ for (const key of DEFAULT_PROFILE_KEYS) {
242
+ expect(parsed.profiles[key]?.fallbackProfile).toBe(
243
+ FALLBACK_PROFILE_BY_KEY[key],
244
+ );
245
+ }
246
+ }
247
+ });
248
+
249
+ test("a workspace entry never overlays a backup, whatever its source", () => {
250
+ // Backups are code-owned (`CODE_OWNED_PROFILE_NAMES`), so unlike the
251
+ // primaries they take no workspace overlay at all: not the usual
252
+ // label/status/topP whitelist from a managed stub, and not a user-owned
253
+ // shadow. Disabling a backup through a hand-edited config would strand
254
+ // the primary's fallback exactly when it is needed.
255
+ for (const source of ["managed", "user"] as const) {
256
+ const workspace: Record<string, ProfileEntry> = {
257
+ "balanced-backup": {
258
+ source,
259
+ label: "My Backup",
260
+ status: "disabled",
261
+ topP: 0.7,
262
+ // Stale content drift on disk must lose to the code default body.
263
+ provider: "anthropic",
264
+ model: "claude-sonnet-4-6",
265
+ maxTokens: 1,
266
+ },
267
+ };
268
+ const entry = getEffectiveProfile(workspace, "balanced-backup");
269
+ const body = CODE_DEFAULT_PROFILE_ENTRIES["balanced-backup"];
270
+ expect(entry?.label).toBe(body.label);
271
+ expect(entry?.status).toBeUndefined();
272
+ expect(entry?.topP).toBe(body.topP);
273
+ expect(entry?.model).toBe(body.model);
274
+ expect(entry?.maxTokens).toBe(body.maxTokens);
275
+ expect(String(entry?.provider)).toBe("vellum");
276
+ }
277
+ });
278
+
279
+ test("backup profiles are delete-protected like the other managed defaults", () => {
280
+ // Membership in these sets is what wires the route-level deletion
281
+ // rejection (`rejectManagedProfileDeletion`, `handleCreateProfile`) and
282
+ // the commitConfigWrite invariant guard
283
+ // (`assertInvariantProfilesPreserved`).
284
+ for (const key of BACKUP_PROFILE_KEYS) {
285
+ expect(MANAGED_PROFILE_NAMES.has(key)).toBe(true);
286
+ expect(INVARIANT_PROFILE_NAMES.has(key)).toBe(true);
287
+ }
288
+ });
289
+ });
@@ -9,6 +9,8 @@
9
9
  */
10
10
  import { beforeEach, describe, expect, mock, test } from "bun:test";
11
11
 
12
+ import { readProviderMetadata } from "../messaging/read-provider-metadata.js";
13
+
12
14
  mock.module("../config/env.js", () => ({
13
15
  isHttpAuthDisabled: () => true,
14
16
  getGatewayInternalBaseUrl: () => "http://127.0.0.1:7830",
@@ -249,7 +251,10 @@ describe("Slack delete propagation", () => {
249
251
  expect(slackMeta!.deletedAt).toBeUndefined();
250
252
  });
251
253
 
252
- test("delete for row without slackMeta is a no-op (legacy row)", async () => {
254
+ test("delete for row without slackMeta stamps the neutral metadata", async () => {
255
+ // A legacy pre-enrichment row still gets its delete marked: the neutral
256
+ // envelope is synthesized so readProviderMetadata serves the stamp to
257
+ // every channel-agnostic reader, and content stays for audit.
253
258
  const seeded = seedSlackMessage({
254
259
  externalChatId: "C0123CHANNEL",
255
260
  originalTs: "2222.2222",
@@ -257,6 +262,7 @@ describe("Slack delete propagation", () => {
257
262
  withSlackMeta: false,
258
263
  });
259
264
 
265
+ const before = Date.now();
260
266
  const req = buildSlackDeleteRequest({
261
267
  externalChatId: seeded.externalChatId,
262
268
  deletedTs: seeded.originalTs,
@@ -265,7 +271,7 @@ describe("Slack delete propagation", () => {
265
271
  const json = (await resp.json()) as Record<string, unknown>;
266
272
 
267
273
  expect(json.accepted).toBe(true);
268
- expect(json.deleted).toBe(false);
274
+ expect(json.deleted).toBe(true);
269
275
 
270
276
  const db = getDb();
271
277
  const row = db
@@ -277,6 +283,90 @@ describe("Slack delete propagation", () => {
277
283
  expect(row!.content).toBe("Legacy pre-upgrade text");
278
284
  const parsed = JSON.parse(row!.metadata!) as Record<string, unknown>;
279
285
  expect(parsed.slackMeta).toBeUndefined();
286
+ const neutral = readProviderMetadata(row!.metadata);
287
+ expect(neutral).not.toBeNull();
288
+ expect(neutral!.source).toBe("slack");
289
+ expect(neutral!.messageId).toBe(seeded.originalTs);
290
+ expect(neutral!.deletedAt).toBeDefined();
291
+ expect(neutral!.deletedAt!).toBeGreaterThanOrEqual(before);
292
+ });
293
+
294
+ test("a flat-legacy row's fields survive the delete stamp", async () => {
295
+ // Rows written before slackMeta nesting carry the Slack envelope flat in
296
+ // messages.metadata. The stamp bases on the mapped envelope, so thread
297
+ // and display identity remain readable beside deletedAt instead of
298
+ // being shadowed by a minimal synthesis.
299
+ const seeded = seedSlackMessage({
300
+ externalChatId: "C0123CHANNEL",
301
+ originalTs: "3333.3333",
302
+ content: "Flat legacy text",
303
+ withSlackMeta: false,
304
+ });
305
+ const db = getDb();
306
+ db.update(messages)
307
+ .set({
308
+ metadata: JSON.stringify({
309
+ source: "slack",
310
+ channelId: seeded.externalChatId,
311
+ channelTs: seeded.originalTs,
312
+ threadTs: "3000.0001",
313
+ eventKind: "message",
314
+ displayName: "Flat User",
315
+ }),
316
+ })
317
+ .where(eq(messages.id, seeded.messageId))
318
+ .run();
319
+
320
+ const req = buildSlackDeleteRequest({
321
+ externalChatId: seeded.externalChatId,
322
+ deletedTs: seeded.originalTs,
323
+ });
324
+ const resp = await handleChannelInbound(req, undefined, TEST_BEARER_TOKEN);
325
+ const json = (await resp.json()) as Record<string, unknown>;
326
+ expect(json.deleted).toBe(true);
327
+
328
+ const row = db
329
+ .select()
330
+ .from(messages)
331
+ .where(eq(messages.id, seeded.messageId))
332
+ .get();
333
+ const neutral = readProviderMetadata(row!.metadata);
334
+ expect(neutral).not.toBeNull();
335
+ expect(neutral!.deletedAt).toBeDefined();
336
+ expect(neutral!.threadId).toBe("3000.0001");
337
+ expect(neutral!.displayName).toBe("Flat User");
338
+ });
339
+
340
+ test("a row with malformed metadata still records its delete", async () => {
341
+ const seeded = seedSlackMessage({
342
+ externalChatId: "C0123CHANNEL",
343
+ originalTs: "4444.4444",
344
+ content: "Row with broken envelope",
345
+ withSlackMeta: false,
346
+ });
347
+ const db = getDb();
348
+ db.update(messages)
349
+ .set({ metadata: "{not json" })
350
+ .where(eq(messages.id, seeded.messageId))
351
+ .run();
352
+
353
+ const req = buildSlackDeleteRequest({
354
+ externalChatId: seeded.externalChatId,
355
+ deletedTs: seeded.originalTs,
356
+ });
357
+ const resp = await handleChannelInbound(req, undefined, TEST_BEARER_TOKEN);
358
+ const json = (await resp.json()) as Record<string, unknown>;
359
+ expect(json.accepted).toBe(true);
360
+ expect(json.deleted).toBe(true);
361
+
362
+ const row = db
363
+ .select()
364
+ .from(messages)
365
+ .where(eq(messages.id, seeded.messageId))
366
+ .get();
367
+ const neutral = readProviderMetadata(row!.metadata);
368
+ expect(neutral).not.toBeNull();
369
+ expect(neutral!.deletedAt).toBeDefined();
280
370
  });
281
371
 
282
372
  test("delete missing sourceMetadata.messageId is a no-op", async () => {
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The Discord callback seam, end to end through the join rather than either
3
+ * half of it.
4
+ *
5
+ * Discord's send, render and DM delivery each have their own unit coverage,
6
+ * and the gateway's inbound has schema, admission and normalizer coverage.
7
+ * Nothing exercised the point where they meet: a reply addressed to a Discord
8
+ * callback resolving to the Discord transport and being delivered with the
9
+ * chat id it was given. That join is where a channel goes silently deaf while
10
+ * every component around it still passes, which is the shape the Slack
11
+ * plan-mode bug had.
12
+ */
13
+
14
+ import { describe, expect, mock, test } from "bun:test";
15
+
16
+ const sent: Array<{ channelId: string; text: string }> = [];
17
+
18
+ // Spread the real module rather than listing the exports this test happens to
19
+ // need. `mock.module` replaces the module wholesale, so a partial stub breaks
20
+ // the transport's import of any export it omits, and would break again the
21
+ // next time `send.ts` grows one.
22
+ const actualSend = await import("../messaging/providers/discord/send.js");
23
+
24
+ mock.module("../messaging/providers/discord/send.js", () => ({
25
+ ...actualSend,
26
+ sendDiscordReply: async (target: { channelId: string }, text: string) => {
27
+ sent.push({ channelId: target.channelId, text });
28
+ return { messageId: "1401234567890123456" };
29
+ },
30
+ }));
31
+
32
+ const { channelForCallback } =
33
+ await import("../messaging/providers/callback-routing.js");
34
+ const { deliverDirect, getTransportForCallback, isDirectDelivery } =
35
+ await import("../messaging/providers/index.js");
36
+
37
+ type ChannelReplyPayload = Parameters<typeof deliverDirect>[1];
38
+
39
+ const DISCORD_CALLBACK = "https://gateway.example/deliver/discord";
40
+
41
+ describe("a Discord reply reaches the Discord transport", () => {
42
+ test("the callback resolves to discord, absolute and relative", () => {
43
+ expect(channelForCallback(DISCORD_CALLBACK)).toBe("discord");
44
+ // Off-channel guardian flows emit a base-less callback.
45
+ expect(channelForCallback("/deliver/discord")).toBe("discord");
46
+ expect(channelForCallback("/deliver/discord?x=1")).toBe("discord");
47
+ });
48
+
49
+ test("the resolved transport is the one that can send", () => {
50
+ expect(isDirectDelivery(DISCORD_CALLBACK)).toBe(true);
51
+ const transport = getTransportForCallback(DISCORD_CALLBACK);
52
+ expect(transport).toBeDefined();
53
+ expect(typeof transport?.deliver).toBe("function");
54
+ });
55
+
56
+ test("a reply is delivered to the chat id it was addressed to", async () => {
57
+ sent.length = 0;
58
+ const payload: ChannelReplyPayload = {
59
+ chatId: "1409876543210987654",
60
+ text: "hello from the assistant",
61
+ };
62
+ await deliverDirect(DISCORD_CALLBACK, payload);
63
+
64
+ expect(sent).toHaveLength(1);
65
+ // `POST /channels/{id}/messages` addresses a channel and nothing more, so
66
+ // the chat id is the channel the reply lands in.
67
+ expect(sent[0]?.channelId).toBe("1409876543210987654");
68
+ expect(sent[0]?.text).toContain("hello from the assistant");
69
+ });
70
+
71
+ test("a threaded reply lands in the thread, which is its own channel", async () => {
72
+ sent.length = 0;
73
+ const payload: ChannelReplyPayload = {
74
+ chatId: "1409876543210987654",
75
+ text: "in the thread",
76
+ };
77
+ await deliverDirect(
78
+ `${DISCORD_CALLBACK}?threadId=1405555555555555555`,
79
+ payload,
80
+ );
81
+
82
+ expect(sent[0]?.channelId).toBe("1405555555555555555");
83
+ });
84
+
85
+ test("Discord declines streaming rather than faking it", () => {
86
+ // No primitive exists, so the transport omits the method and the caller
87
+ // sends the finished reply. An implementation here would be a repeated
88
+ // edit, which is the approach the platforms discourage.
89
+ expect(getTransportForCallback(DISCORD_CALLBACK)?.streamReply).toBe(
90
+ undefined,
91
+ );
92
+ });
93
+ });