@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
@@ -438,7 +438,7 @@ describe("assistant mcp add", () => {
438
438
  expect(stderr).toContain("--url is required");
439
439
  });
440
440
 
441
- test("defaults risk to high", async () => {
441
+ test("sends no risk when --risk is omitted, leaving the config default", async () => {
442
442
  const { exitCode } = await runMcpAdd("default-risk", [
443
443
  "-t",
444
444
  "sse",
@@ -447,6 +447,28 @@ describe("assistant mcp add", () => {
447
447
  ]);
448
448
  expect(exitCode).toBe(0);
449
449
 
450
+ const addCall = mockCliIpcCallFn.mock.calls.find(
451
+ (c) => c[0] === "internal_mcp_add",
452
+ );
453
+ expect(addCall).toBeDefined();
454
+ const body = (addCall![1] as Record<string, unknown>).body as Record<
455
+ string,
456
+ unknown
457
+ >;
458
+ expect(body.risk).toBeUndefined();
459
+ });
460
+
461
+ test("forwards an explicit --risk", async () => {
462
+ const { exitCode } = await runMcpAdd("explicit-risk", [
463
+ "-t",
464
+ "sse",
465
+ "-u",
466
+ "https://example.com/sse",
467
+ "-r",
468
+ "high",
469
+ ]);
470
+ expect(exitCode).toBe(0);
471
+
450
472
  const addCall = mockCliIpcCallFn.mock.calls.find(
451
473
  (c) => c[0] === "internal_mcp_add",
452
474
  );
@@ -40,60 +40,69 @@ function serverConfig(defaultRiskLevel: ServerRisk) {
40
40
  };
41
41
  }
42
42
 
43
- function toolWithAnnotations(readOnlyHint?: boolean) {
43
+ interface RiskAnnotations {
44
+ readOnlyHint?: boolean;
45
+ destructiveHint?: boolean;
46
+ }
47
+
48
+ function toolWithAnnotations(annotations?: RiskAnnotations) {
44
49
  return {
45
50
  name: "get-summary",
46
51
  description: "Read a summary",
47
52
  inputSchema: { type: "object", properties: {} },
48
- ...(readOnlyHint === undefined ? {} : { annotations: { readOnlyHint } }),
53
+ ...(annotations === undefined ? {} : { annotations }),
49
54
  };
50
55
  }
51
56
 
52
- describe("MCP tool risk from readOnlyHint annotations", () => {
57
+ describe("MCP tool risk from annotations", () => {
53
58
  const fakeManager = { callTool: jest.fn() } as never;
54
59
 
55
- test("readOnlyHint true on a medium-risk server lowers risk to Low", () => {
56
- const tool = createMcpTool(
57
- toolWithAnnotations(true),
58
- "fathom",
59
- serverConfig("medium"),
60
+ function riskFor(server: ServerRisk, annotations?: RiskAnnotations) {
61
+ return createMcpTool(
62
+ toolWithAnnotations(annotations),
63
+ "server",
64
+ serverConfig(server),
60
65
  fakeManager,
61
- );
66
+ ).defaultRiskLevel;
67
+ }
62
68
 
63
- expect(tool.defaultRiskLevel).toBe(RiskLevel.Low);
69
+ test("no annotations keeps the server level", () => {
70
+ expect(riskFor("low")).toBe(RiskLevel.Low);
71
+ expect(riskFor("medium")).toBe(RiskLevel.Medium);
72
+ expect(riskFor("high")).toBe(RiskLevel.High);
64
73
  });
65
74
 
66
- test("readOnlyHint true on a high-risk server leaves risk at High", () => {
67
- const tool = createMcpTool(
68
- toolWithAnnotations(true),
69
- "untrusted",
70
- serverConfig("high"),
71
- fakeManager,
72
- );
75
+ test("readOnlyHint steps down from the medium default", () => {
76
+ expect(riskFor("medium", { readOnlyHint: true })).toBe(RiskLevel.Low);
77
+ });
73
78
 
74
- expect(tool.defaultRiskLevel).toBe(RiskLevel.High);
79
+ test("readOnlyHint cannot lower a server the user pinned to high", () => {
80
+ expect(riskFor("high", { readOnlyHint: true })).toBe(RiskLevel.High);
75
81
  });
76
82
 
77
- test("missing annotations keeps the server default risk", () => {
78
- const tool = createMcpTool(
79
- toolWithAnnotations(undefined),
80
- "fathom",
81
- serverConfig("medium"),
82
- fakeManager,
83
- );
83
+ test("readOnlyHint cannot step below low", () => {
84
+ expect(riskFor("low", { readOnlyHint: true })).toBe(RiskLevel.Low);
85
+ });
84
86
 
85
- expect(tool.defaultRiskLevel).toBe(RiskLevel.Medium);
87
+ test("destructiveHint steps up one level", () => {
88
+ expect(riskFor("low", { destructiveHint: true })).toBe(RiskLevel.Medium);
89
+ expect(riskFor("medium", { destructiveHint: true })).toBe(RiskLevel.High);
86
90
  });
87
91
 
88
- test("readOnlyHint false on a low-risk server keeps risk at Low", () => {
89
- const tool = createMcpTool(
90
- toolWithAnnotations(false),
91
- "trusted",
92
- serverConfig("low"),
93
- fakeManager,
94
- );
92
+ test("destructiveHint cannot step above high", () => {
93
+ expect(riskFor("high", { destructiveHint: true })).toBe(RiskLevel.High);
94
+ });
95
+
96
+ test("destructiveHint wins when a server sends both", () => {
97
+ expect(
98
+ riskFor("medium", { readOnlyHint: true, destructiveHint: true }),
99
+ ).toBe(RiskLevel.High);
100
+ });
95
101
 
96
- expect(tool.defaultRiskLevel).toBe(RiskLevel.Low);
102
+ test("hints set to false leave the server level alone", () => {
103
+ expect(
104
+ riskFor("medium", { readOnlyHint: false, destructiveHint: false }),
105
+ ).toBe(RiskLevel.Medium);
97
106
  });
98
107
  });
99
108
 
@@ -774,6 +774,7 @@ describe("MediaStreamSttSession", () => {
774
774
 
775
775
  expect(resolveStreamingTranscriber).toHaveBeenCalledTimes(1);
776
776
  expect(resolveStreamingTranscriber).toHaveBeenCalledWith({
777
+ role: "telephony",
777
778
  sampleRate: 16_000,
778
779
  utteranceBoundaryFinals: true,
779
780
  utteranceEndMs: 1000,
@@ -787,6 +788,7 @@ describe("MediaStreamSttSession", () => {
787
788
  const { session } = await startStreamingSession();
788
789
 
789
790
  expect(resolveStreamingTranscriber).toHaveBeenCalledWith({
791
+ role: "telephony",
790
792
  sampleRate: 16_000,
791
793
  utteranceBoundaryFinals: true,
792
794
  utteranceEndMs: 2500,
@@ -629,3 +629,109 @@ describe("access-request instruction enforcement", () => {
629
629
  expect(decision.renderedCopy.vellum?.body).not.toContain("reject");
630
630
  });
631
631
  });
632
+
633
+ /**
634
+ * A question is the one payload whose copy cannot be composed: the guardian is
635
+ * choosing between the words it carries. The engine replaces title / body /
636
+ * deliveryText wholesale with the model's version, which is how a question
637
+ * reached a channel as a summary of itself with its options missing.
638
+ */
639
+ describe("ask_question copy is pinned to the question", () => {
640
+ const askQuestionSignal = (): NotificationSignal => ({
641
+ signalId: "sig-ask-question-1",
642
+ createdAt: Date.now(),
643
+ sourceChannel: "slack",
644
+ sourceContextId: "conv-1",
645
+ sourceEventName: "guardian.question",
646
+ contextPayload: {
647
+ requestId: "req-ask-1",
648
+ requestCode: "08B619",
649
+ requestKind: "pending_question",
650
+ questionText: "What should I dig into?",
651
+ options: [
652
+ { id: "opt-thread", label: "This Slack thread" },
653
+ { id: "opt-pr", label: "The pull request" },
654
+ ],
655
+ },
656
+ attentionHints: {
657
+ requiresAction: true,
658
+ urgency: "high",
659
+ isAsyncBackground: false,
660
+ visibleInSourceNow: false,
661
+ },
662
+ });
663
+
664
+ beforeEach(() => {
665
+ configuredProvider = { sendMessage: async () => ({ content: [] }) };
666
+ // The tool-use envelope `classifyWithLLM` reads, not the decision fields
667
+ // alone: it takes `toolBlock.input`, and the field names inside must be
668
+ // the ones `validateDecisionOutput` requires. Either mismatch drops the
669
+ // decision to the fallback path, where this suite's own copy is correct
670
+ // and the pin under test never runs.
671
+ extractedToolUse = {
672
+ name: "record_notification_decision",
673
+ input: {
674
+ shouldNotify: true,
675
+ selectedChannels: ["slack"],
676
+ reasoningSummary: "guardian needs to answer",
677
+ dedupeKey: "ask-question-req-ask-1",
678
+ confidence: 0.9,
679
+ renderedCopy: {
680
+ slack: {
681
+ title: "Guardian question",
682
+ body: "Guardian wants to know where to focus",
683
+ deliveryText: "Guardian wants to know where to focus",
684
+ },
685
+ },
686
+ },
687
+ };
688
+ });
689
+
690
+ test("the composed paraphrase does not replace the question", async () => {
691
+ const decision = await evaluateSignal(askQuestionSignal(), [
692
+ "slack",
693
+ ] as NotificationChannel[]);
694
+
695
+ // Guards the fixture itself: a shape the validator rejects would fall
696
+ // back, and the assertions below would then be testing the fallback copy
697
+ // rather than the pin that overrides a composed paraphrase.
698
+ expect(decision.fallbackUsed).toBe(false);
699
+
700
+ const copy = decision.renderedCopy.slack;
701
+ // `deliveryText` is what the Slack adapter sends, ahead of body and title.
702
+ expect(copy?.deliveryText).toContain("What should I dig into?");
703
+ expect(copy?.deliveryText).not.toContain("wants to know where to focus");
704
+ expect(copy?.body).toContain("What should I dig into?");
705
+ });
706
+
707
+ test("the options ride along, numbered as the resolver orders them", async () => {
708
+ const decision = await evaluateSignal(askQuestionSignal(), [
709
+ "slack",
710
+ ] as NotificationChannel[]);
711
+
712
+ expect(decision.fallbackUsed).toBe(false);
713
+ const delivered = decision.renderedCopy.slack?.deliveryText ?? "";
714
+ expect(delivered).toContain("1. This Slack thread");
715
+ expect(delivered).toContain("2. The pull request");
716
+ expect(delivered).toContain("08B619");
717
+ });
718
+
719
+ test("a voice tool approval keeps its composed copy", async () => {
720
+ const signal = askQuestionSignal();
721
+ // A `pending_question` carrying a tool name is an approval, not a
722
+ // question, so composing its copy is correct.
723
+ signal.contextPayload = {
724
+ ...signal.contextPayload,
725
+ toolName: "bash",
726
+ options: undefined,
727
+ };
728
+ const decision = await evaluateSignal(signal, [
729
+ "slack",
730
+ ] as NotificationChannel[]);
731
+
732
+ expect(decision.fallbackUsed).toBe(false);
733
+ expect(decision.renderedCopy.slack?.deliveryText).toBe(
734
+ "Guardian wants to know where to focus",
735
+ );
736
+ });
737
+ });
@@ -0,0 +1,162 @@
1
+ /**
2
+ * DiscordAdapter delivery behavior.
3
+ *
4
+ * The invariant under test: the adapter addresses the *person*. Every send
5
+ * and update resolves the DM channel from the guardian's user snowflake at
6
+ * call time, so a room id stored anywhere can never become a delivery
7
+ * target, and approval notifications carry the typed-command instructions
8
+ * because nothing ingests component presses yet.
9
+ */
10
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
11
+
12
+ const dmOpens: string[] = [];
13
+ const sendCalls: Array<{ channelId: string; text: string }> = [];
14
+ const editCalls: Array<{ channelId: string; messageId: string; text: string }> =
15
+ [];
16
+
17
+ mock.module("../messaging/providers/discord/api.js", () => ({
18
+ openDiscordDmChannel: async (userId: string) => {
19
+ dmOpens.push(userId);
20
+ return `dm-for-${userId}`;
21
+ },
22
+ }));
23
+
24
+ mock.module("../messaging/providers/discord/send.js", () => ({
25
+ sendDiscordReply: async (target: { channelId: string }, text: string) => {
26
+ sendCalls.push({ channelId: target.channelId, text });
27
+ return { lastMessageId: String(2000 + sendCalls.length) };
28
+ },
29
+ editDiscordMessage: async (
30
+ target: { channelId: string },
31
+ messageId: string,
32
+ text: string,
33
+ ) => {
34
+ editCalls.push({ channelId: target.channelId, messageId, text });
35
+ },
36
+ }));
37
+
38
+ import type {
39
+ ChannelDeliveryPayload,
40
+ ChannelDestination,
41
+ } from "../notifications/types.js";
42
+
43
+ const { DiscordAdapter } = await import("../notifications/adapters/discord.js");
44
+
45
+ const GUARDIAN_USER_ID = "111222333444555666";
46
+
47
+ function makePayload(
48
+ overrides?: Partial<ChannelDeliveryPayload>,
49
+ ): ChannelDeliveryPayload {
50
+ return {
51
+ sourceEventName: "schedule.notify",
52
+ copy: {
53
+ title: "Heads up",
54
+ body: "Your task finished.",
55
+ },
56
+ urgency: "medium",
57
+ ...overrides,
58
+ };
59
+ }
60
+
61
+ function makeDestination(
62
+ overrides?: Partial<ChannelDestination>,
63
+ ): ChannelDestination {
64
+ return {
65
+ channel: "discord",
66
+ endpoint: GUARDIAN_USER_ID,
67
+ ...overrides,
68
+ };
69
+ }
70
+
71
+ beforeEach(() => {
72
+ dmOpens.length = 0;
73
+ sendCalls.length = 0;
74
+ editCalls.length = 0;
75
+ });
76
+
77
+ describe("DiscordAdapter.send", () => {
78
+ test("resolves the DM channel from the guardian's user snowflake", async () => {
79
+ const adapter = new DiscordAdapter();
80
+ const result = await adapter.send(makePayload(), makeDestination());
81
+
82
+ expect(result.success).toBe(true);
83
+ expect(result.messageId).toBe("2001");
84
+ expect(dmOpens).toEqual([GUARDIAN_USER_ID]);
85
+ expect(sendCalls).toHaveLength(1);
86
+ expect(sendCalls[0].channelId).toBe(`dm-for-${GUARDIAN_USER_ID}`);
87
+ expect(sendCalls[0].text).toContain("Your task finished.");
88
+ });
89
+
90
+ test("approval notifications carry the typed-command instructions", async () => {
91
+ const adapter = new DiscordAdapter();
92
+ const result = await adapter.send(
93
+ makePayload({
94
+ approvalContext: {
95
+ requestId: "req-1",
96
+ actions: [{ id: "approve_once", label: "Approve once" }],
97
+ plainTextFallback: 'Reply "approve" or "reject" to decide.',
98
+ },
99
+ }),
100
+ makeDestination(),
101
+ );
102
+
103
+ expect(result.success).toBe(true);
104
+ expect(sendCalls).toHaveLength(1);
105
+ expect(sendCalls[0].text).toContain(
106
+ 'Reply "approve" or "reject" to decide.',
107
+ );
108
+ });
109
+
110
+ test("a destination without a guardian user id fails without calling the API", async () => {
111
+ const adapter = new DiscordAdapter();
112
+ const result = await adapter.send(
113
+ makePayload(),
114
+ makeDestination({ endpoint: undefined }),
115
+ );
116
+
117
+ expect(result.success).toBe(false);
118
+ expect(dmOpens).toHaveLength(0);
119
+ expect(sendCalls).toHaveLength(0);
120
+ });
121
+ });
122
+
123
+ describe("DiscordAdapter.update", () => {
124
+ test("edits the card through the same person-resolved DM channel", async () => {
125
+ const adapter = new DiscordAdapter();
126
+ const result = await adapter.update(
127
+ {
128
+ deliveryId: "delivery-1",
129
+ destination: GUARDIAN_USER_ID,
130
+ messageId: "2001",
131
+ },
132
+ { body: "This approval request has been resolved." },
133
+ );
134
+
135
+ expect(result.success).toBe(true);
136
+ expect(result.messageId).toBe("2001");
137
+ expect(dmOpens).toEqual([GUARDIAN_USER_ID]);
138
+ expect(editCalls).toEqual([
139
+ {
140
+ channelId: `dm-for-${GUARDIAN_USER_ID}`,
141
+ messageId: "2001",
142
+ text: "This approval request has been resolved.",
143
+ },
144
+ ]);
145
+ });
146
+
147
+ test("a delivery row without a captured message id cannot be updated", async () => {
148
+ const adapter = new DiscordAdapter();
149
+ const result = await adapter.update(
150
+ {
151
+ deliveryId: "delivery-2",
152
+ destination: GUARDIAN_USER_ID,
153
+ messageId: null,
154
+ },
155
+ { body: "resolved" },
156
+ );
157
+
158
+ expect(result.success).toBe(false);
159
+ expect(result.error).toContain("missing_message_id");
160
+ expect(editCalls).toHaveLength(0);
161
+ });
162
+ });
@@ -133,6 +133,7 @@ mock.module("../inbound/public-ingress-urls.js", () => ({
133
133
  // ---------------------------------------------------------------------------
134
134
 
135
135
  import { orchestrateOAuthConnect } from "../oauth/connect-orchestrator.js";
136
+ import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
136
137
  import { setConfig } from "./helpers/set-config.js";
137
138
 
138
139
  /** Seed `ingress.publicBaseUrl` in the real workspace config. */
@@ -219,6 +220,7 @@ beforeEach(() => {
219
220
  setPublicBaseUrl("");
220
221
  mockIdentityResult = "user@example.com";
221
222
  mockProviderStore = {};
223
+ setOverridesForTesting({});
222
224
 
223
225
  mockPrepareResult = {
224
226
  authorizeUrl: "https://provider.example.com/authorize?prepared",
@@ -805,3 +807,96 @@ describe("orchestrateOAuthConnect — transport selection", () => {
805
807
  });
806
808
  });
807
809
  });
810
+
811
+ // ---------------------------------------------------------------------------
812
+ // Feature-flag gating
813
+ // ---------------------------------------------------------------------------
814
+
815
+ /**
816
+ * A provider whose seed entry declares a `featureFlag` is only connectable
817
+ * while that flag is enabled. The orchestrator is the shared choke point for
818
+ * every connect path (runtime routes, gateway, CLI, credential vault tool),
819
+ * so the gate lives here rather than in each entry point.
820
+ */
821
+ describe("orchestrateOAuthConnect — feature-flag gating", () => {
822
+ const GATED_PROVIDER = makeProviderRow({
823
+ provider: "gated",
824
+ displayLabel: "Gated",
825
+ loopbackPort: 17399,
826
+ featureFlag: "gated-provider-flag",
827
+ });
828
+
829
+ test("refuses to connect a gated provider when its flag is disabled", async () => {
830
+ mockProviderStore["gated"] = GATED_PROVIDER;
831
+ setOverridesForTesting({ "gated-provider-flag": false });
832
+
833
+ const result = await orchestrateOAuthConnect({
834
+ service: "gated",
835
+ clientId: "client-id",
836
+ isInteractive: false,
837
+ callbackTransport: "loopback",
838
+ });
839
+
840
+ expect(result.success).toBe(false);
841
+ // No authorization flow may be started for a gated provider.
842
+ expect(lastPrepareArgs).toBeNull();
843
+ expect(lastStartArgs).toBeNull();
844
+ });
845
+
846
+ test("gated provider is indistinguishable from an unregistered one", async () => {
847
+ mockProviderStore["gated"] = GATED_PROVIDER;
848
+ setOverridesForTesting({ "gated-provider-flag": false });
849
+
850
+ const gated = await orchestrateOAuthConnect({
851
+ service: "gated",
852
+ clientId: "client-id",
853
+ isInteractive: false,
854
+ callbackTransport: "loopback",
855
+ });
856
+ const absent = await orchestrateOAuthConnect({
857
+ service: "not-a-provider",
858
+ clientId: "client-id",
859
+ isInteractive: false,
860
+ callbackTransport: "loopback",
861
+ });
862
+
863
+ expect(gated.success).toBe(false);
864
+ expect(absent.success).toBe(false);
865
+ if (gated.success || absent.success) {
866
+ return;
867
+ }
868
+ // Identical wording so the gate does not leak that the provider exists.
869
+ expect(gated.error).toBe(absent.error.replace("not-a-provider", "gated"));
870
+ });
871
+
872
+ test("connects a gated provider once its flag is enabled", async () => {
873
+ mockProviderStore["gated"] = GATED_PROVIDER;
874
+ setOverridesForTesting({ "gated-provider-flag": true });
875
+
876
+ const result = await orchestrateOAuthConnect({
877
+ service: "gated",
878
+ clientId: "client-id",
879
+ isInteractive: false,
880
+ callbackTransport: "loopback",
881
+ });
882
+
883
+ expect(result.success).toBe(true);
884
+ expect(lastPrepareArgs).not.toBeNull();
885
+ });
886
+
887
+ test("leaves ungated providers connectable", async () => {
888
+ mockProviderStore["google"] = GOOGLE_PROVIDER;
889
+ setOverridesForTesting({});
890
+
891
+ const result = await orchestrateOAuthConnect({
892
+ service: "google",
893
+ clientId: "client-id",
894
+ isInteractive: false,
895
+ callbackTransport: "loopback",
896
+ });
897
+
898
+ expect(GOOGLE_PROVIDER.featureFlag).toBeNull();
899
+ expect(result.success).toBe(true);
900
+ expect(lastPrepareArgs).not.toBeNull();
901
+ });
902
+ });
@@ -2,18 +2,16 @@
2
2
  * Regression test for the durable-downscale path of the image-too-large
3
3
  * recovery (JARVIS-1041 review follow-up).
4
4
  *
5
- * When an oversized stored image *can* be shrunk on this host (the common macOS
6
- * path where `sips` is available), `persistUnsendableImageDowngrades` must write
7
- * the downscaled bytes back to the DB not leave the original in place. The
5
+ * When an oversized stored image can be shrunk,
6
+ * `persistUnsendableImageDowngrades` must write the downscaled bytes back to
7
+ * the DB, not leave the original in place. The
8
8
  * latest tool-result media is intentionally kept in context, so leaving the
9
9
  * full-size block would rehydrate and re-reject on every later turn instead of
10
10
  * durably self-healing the conversation.
11
11
  *
12
- * `optimizeImageForTransport` needs `sips` and a decodable image to actually
13
- * downscale, which is not portable to CI, so it is mocked here to simulate a
14
- * successful shrink. The mock is process-global, so this case lives in its own
15
- * file (the test runner isolates each file in its own process) to avoid
16
- * disturbing the no-op-resize cases in persist-unsendable-image.test.ts.
12
+ * The encoder is mocked here to isolate the durable persistence behavior from
13
+ * image decoding. The mock is process-global, so this case lives in its own
14
+ * file to avoid disturbing the no-op resize cases.
17
15
  */
18
16
 
19
17
  import { beforeEach, describe, expect, mock, test } from "bun:test";
@@ -43,9 +43,9 @@ function resetTables(): void {
43
43
  /**
44
44
  * Build a minimal PNG whose IHDR declares the given dimensions. Only the
45
45
  * 8-byte signature and the width/height fields (read by `parseImageDimensions`)
46
- * need to be correct; the rest is padding. `optimizeImageForTransport` cannot
47
- * downscale this off macOS (no `sips`), so it stays a no-op exactly the
48
- * host condition that produces an unsendable stored image.
46
+ * need to be correct; the rest is padding. The image is not decodable, so
47
+ * `optimizeImageForTransport` leaves it unchanged and produces the unsendable
48
+ * stored-image condition.
49
49
  */
50
50
  function makePngBase64(width: number, height: number, padBytes = 0): string {
51
51
  const header = Buffer.from(
@@ -1,10 +1,11 @@
1
1
  import { existsSync, rmSync } from "node:fs";
2
2
  import { homedir, tmpdir } from "node:os";
3
- import { join } from "node:path";
3
+ import { join, win32 } from "node:path";
4
4
  import { afterAll, afterEach, describe, expect, test } from "bun:test";
5
5
 
6
6
  import {
7
7
  ensureDataDir,
8
+ formatHomeRelativePath,
8
9
  getDataDir,
9
10
  getDbPath,
10
11
  getHistoryPath,
@@ -64,6 +65,23 @@ afterEach(() => {
64
65
  // Workspace helpers resolve under VELLUM_WORKSPACE_DIR when set,
65
66
  // otherwise under ~/.vellum/workspace.
66
67
  describe("path characterization", () => {
68
+ test("formats Windows workspace paths relative to the home directory", () => {
69
+ expect(
70
+ formatHomeRelativePath(
71
+ "C:\\Users\\Alice\\.vellum\\workspace",
72
+ "C:\\Users\\Alice",
73
+ win32,
74
+ ),
75
+ ).toBe("~\\.vellum\\workspace");
76
+ expect(
77
+ formatHomeRelativePath(
78
+ "D:\\Vellum\\workspace",
79
+ "C:\\Users\\Alice",
80
+ win32,
81
+ ),
82
+ ).toBe("D:\\Vellum\\workspace");
83
+ });
84
+
67
85
  test("all path helpers resolve to expected locations", () => {
68
86
  // Without VELLUM_WORKSPACE_DIR override, workspace is under ~/.vellum
69
87
  delete process.env.VELLUM_WORKSPACE_DIR;
@@ -128,7 +128,7 @@ const BASELINE: Record<string, readonly string[]> = {
128
128
  "../../../../daemon/embedding-reconcile.js",
129
129
  "../../../../daemon/trust-context.js",
130
130
  "../../../../daemon/turn-latency-sub-spans.js",
131
- "../../../../notifications/emit-signal.js",
131
+ "../../../../notifications/background-failure-signal.js",
132
132
  "../../../../persistence/checkpoints.js",
133
133
  "../../../../persistence/conversation-types.js",
134
134
  "../../../../persistence/db-connection.js",
@@ -173,6 +173,10 @@ const BASELINE: Record<string, readonly string[]> = {
173
173
  "../../../context/strip-injections.js",
174
174
  "../../../context/token-estimator.js",
175
175
  "../../../conversations/job-handlers/summarization.js",
176
+ // The standalone memory worker hosts real agent conversations, so its
177
+ // entry point starts the same eviction sweep the daemon starts at
178
+ // startup. No plugin-api equivalent.
179
+ "../../../daemon/conversation-evictor.js",
176
180
  "../../../daemon/date-context.js",
177
181
  "../../../daemon/disk-pressure-background-gate.js",
178
182
  "../../../daemon/embedding-reconcile.js",
@@ -19,6 +19,23 @@ function makeConfig(): AssistantConfig {
19
19
  }
20
20
 
21
21
  describe("getVisibleProviderCatalog", () => {
22
+ test("hides Vellum-hosted GPU models unless developer mode is on", () => {
23
+ setOverridesForTesting({});
24
+ const hidden = getVisibleProviderCatalog(makeConfig());
25
+ expect(hidden.find((p) => p.id === "vellum")).toBeUndefined();
26
+ expect(
27
+ hidden
28
+ .flatMap((p) => p.models)
29
+ .some((m) => m.id === "qwen/qwen3-8b"),
30
+ ).toBe(false);
31
+
32
+ setOverridesForTesting({ "settings-developer-nav": true });
33
+ const visible = getVisibleProviderCatalog(makeConfig());
34
+ const vellum = visible.find((p) => p.id === "vellum");
35
+ expect(vellum).toBeDefined();
36
+ expect(vellum!.models.map((m) => m.id)).toEqual(["qwen/qwen3-8b"]);
37
+ });
38
+
22
39
  test("shows openai-compatible endpoints unconditionally (GA'ed)", () => {
23
40
  setOverridesForTesting({});
24
41