@vellumai/assistant 0.11.6 → 0.11.7-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (478) hide show
  1. package/ARCHITECTURE.md +31 -19
  2. package/Dockerfile +1 -0
  3. package/docs/architecture/security.md +3 -3
  4. package/docs/flux-turn-detection-spike.md +8 -7
  5. package/docs/runbook-trusted-contacts.md +14 -0
  6. package/docs/stt-provider-onboarding.md +8 -4
  7. package/knip.json +1 -0
  8. package/node_modules/@vellumai/avatar-manifest/package.json +18 -0
  9. package/node_modules/@vellumai/avatar-manifest/src/__tests__/manifest.test.ts +173 -0
  10. package/node_modules/@vellumai/avatar-manifest/src/__tests__/read.test.ts +114 -0
  11. package/node_modules/@vellumai/avatar-manifest/src/index.ts +29 -0
  12. package/node_modules/@vellumai/avatar-manifest/src/layout.ts +18 -0
  13. package/node_modules/@vellumai/avatar-manifest/src/manifest.ts +156 -0
  14. package/node_modules/@vellumai/avatar-manifest/src/read.ts +109 -0
  15. package/node_modules/@vellumai/avatar-manifest/tsconfig.json +20 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  17. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  18. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  19. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  20. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  21. package/node_modules/@vellumai/ces-client/src/http-credentials.ts +161 -0
  22. package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
  23. package/node_modules/@vellumai/environments/package.json +2 -1
  24. package/node_modules/@vellumai/environments/src/shell.test.ts +56 -0
  25. package/node_modules/@vellumai/environments/src/shell.ts +65 -0
  26. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  27. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  28. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  29. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  30. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  31. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +32 -0
  32. package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -0
  33. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +82 -0
  34. package/node_modules/@vellumai/gateway-client/src/__tests__/plugin-admission-denied-contract.test.ts +56 -0
  35. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +18 -0
  36. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +5 -11
  37. package/node_modules/@vellumai/gateway-client/src/channel-permission-contract.ts +3 -1
  38. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +9 -0
  39. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +4 -0
  40. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +75 -0
  41. package/node_modules/@vellumai/gateway-client/src/index.ts +17 -0
  42. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +8 -0
  43. package/node_modules/@vellumai/gateway-client/src/plugin-admission-denied-contract.ts +47 -0
  44. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +11 -0
  45. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  46. package/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  47. package/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  48. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  49. package/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  50. package/openapi.yaml +419 -55
  51. package/package.json +4 -1
  52. package/scripts/sync-llm-catalog.ts +6 -0
  53. package/src/__tests__/approval-card-edit-inbound-handoff.test.ts +199 -0
  54. package/src/__tests__/approval-card-edit.test.ts +153 -0
  55. package/src/__tests__/attachments-store-heic-normalize.test.ts +1 -1
  56. package/src/__tests__/available-skills.test.ts +54 -0
  57. package/src/__tests__/avatar-identity-sync.test.ts +5 -1
  58. package/src/__tests__/catalog-install-normalize.test.ts +9 -1
  59. package/src/__tests__/channel-policy.test.ts +1 -3
  60. package/src/__tests__/channel-readiness-discord.test.ts +9 -70
  61. package/src/__tests__/compactor-call-site-logging.test.ts +61 -0
  62. package/src/__tests__/config-loader-backfill.test.ts +69 -0
  63. package/src/__tests__/config-schema.test.ts +2 -3
  64. package/src/__tests__/conversation-agent-loop.test.ts +238 -0
  65. package/src/__tests__/conversation-attachments.test.ts +0 -1
  66. package/src/__tests__/conversation-error.test.ts +44 -0
  67. package/src/__tests__/conversation-evictor.test.ts +19 -0
  68. package/src/__tests__/conversation-fork-referential.test.ts +73 -63
  69. package/src/__tests__/conversation-fork-retrospective.test.ts +99 -74
  70. package/src/__tests__/conversation-runtime-assembly.test.ts +23 -0
  71. package/src/__tests__/conversation-slash.test.ts +9 -0
  72. package/src/__tests__/conversation-title-service.test.ts +97 -0
  73. package/src/__tests__/conversation-tool-setup-attribution.test.ts +16 -0
  74. package/src/__tests__/credential-broker-server-use.test.ts +0 -218
  75. package/src/__tests__/credential-broker.test.ts +43 -420
  76. package/src/__tests__/credential-record-write-through.test.ts +78 -0
  77. package/src/__tests__/credential-routes.test.ts +17 -1
  78. package/src/__tests__/credential-security-invariants.test.ts +8 -9
  79. package/src/__tests__/default-profile-catalog-fallback.test.ts +289 -0
  80. package/src/__tests__/delete-propagation.test.ts +92 -2
  81. package/src/__tests__/discord-callback-round-trip.test.ts +93 -0
  82. package/src/__tests__/discord-channel-config.test.ts +237 -0
  83. package/src/__tests__/edit-propagation.test.ts +42 -2
  84. package/src/__tests__/evict-conversations-for-reload.test.ts +51 -1
  85. package/src/__tests__/fallback-breaker.test.ts +1335 -0
  86. package/src/__tests__/gateway-threshold-reader-mock.ts +6 -0
  87. package/src/__tests__/guardian-card-withdrawal.test.ts +30 -0
  88. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -2
  89. package/src/__tests__/image-conversion.test.ts +38 -4
  90. package/src/__tests__/install-skill-routing.test.ts +25 -0
  91. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  92. package/src/__tests__/llm-resolver.test.ts +74 -0
  93. package/src/__tests__/llm-schema-fallback-profile.test.ts +359 -0
  94. package/src/__tests__/managed-fallback-dispatch.test.ts +430 -0
  95. package/src/__tests__/managed-profile-guard.test.ts +28 -0
  96. package/src/__tests__/managed-speech-defaults.test.ts +196 -1
  97. package/src/__tests__/mcp-cli.test.ts +23 -1
  98. package/src/__tests__/mcp-tool-annotations-risk.test.ts +43 -34
  99. package/src/__tests__/media-stream-stt-session.test.ts +2 -0
  100. package/src/__tests__/notification-decision-fallback.test.ts +106 -0
  101. package/src/__tests__/notification-discord-adapter.test.ts +162 -0
  102. package/src/__tests__/oauth-connect-orchestrator.test.ts +95 -0
  103. package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -8
  104. package/src/__tests__/persist-unsendable-image.test.ts +3 -3
  105. package/src/__tests__/platform.test.ts +19 -1
  106. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  107. package/src/__tests__/provider-catalog-visibility.test.ts +17 -0
  108. package/src/__tests__/provider-platform-proxy-integration.test.ts +8 -1
  109. package/src/__tests__/provider-usage-tracking.test.ts +56 -0
  110. package/src/__tests__/require-fresh-approval.test.ts +5 -2
  111. package/src/__tests__/retry-fallback-escalation.test.ts +1093 -0
  112. package/src/__tests__/schedule-retry.test.ts +183 -4
  113. package/src/__tests__/scheduler-reuse-conversation.test.ts +6 -6
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +55 -0
  115. package/src/__tests__/skill-load-tool.test.ts +34 -0
  116. package/src/__tests__/skill-tool-factory.test.ts +6 -1
  117. package/src/__tests__/skill-tool-manifest.test.ts +20 -0
  118. package/src/__tests__/skills.test.ts +25 -0
  119. package/src/__tests__/subagent-manager-notify.test.ts +38 -0
  120. package/src/__tests__/terminal-tools.test.ts +2 -0
  121. package/src/__tests__/tool-approval-handler.test.ts +146 -2
  122. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +108 -2
  123. package/src/__tests__/tool-audit.test.ts +111 -40
  124. package/src/__tests__/tool-executor-lifecycle-events.test.ts +228 -18
  125. package/src/__tests__/ui-shape-teaching.test.ts +1 -1
  126. package/src/__tests__/verification-control-plane-policy.test.ts +6 -2
  127. package/src/__tests__/voice-config-update.test.ts +43 -0
  128. package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +480 -0
  129. package/src/__tests__/workspace-migration-148-strip-unsupported-fallback-profiles.test.ts +167 -0
  130. package/src/__tests__/workspace-migration-149-repoint-backup-profile-selections.test.ts +144 -0
  131. package/src/agent/loop.ts +31 -2
  132. package/src/approvals/guardian-card-withdrawal.ts +22 -0
  133. package/src/avatar/__tests__/ensure-raster.test.ts +291 -0
  134. package/src/avatar/avatar-manifest.ts +48 -100
  135. package/src/avatar/avatar-store.ts +7 -7
  136. package/src/avatar/ensure-raster.ts +146 -0
  137. package/src/avatar/resvg-lazy.test.ts +0 -1
  138. package/src/avatar/traits-png-sync.ts +9 -10
  139. package/src/background-wake/wake-intent-hooks.test.ts +0 -1
  140. package/src/backup/__tests__/paths.test.ts +6 -2
  141. package/src/backup/paths.ts +6 -4
  142. package/src/bundler/app-compiler.ts +2 -2
  143. package/src/bundler/compiler-tools.test.ts +36 -0
  144. package/src/bundler/compiler-tools.ts +54 -18
  145. package/src/calls/__tests__/telephony-synthesis-language.test.ts +81 -0
  146. package/src/calls/__tests__/voice-session-bridge.test.ts +57 -0
  147. package/src/calls/media-stream-stt-session.ts +2 -1
  148. package/src/calls/telephony-synthesis-language.ts +13 -5
  149. package/src/calls/voice-session-bridge.ts +19 -4
  150. package/src/channels/config.ts +23 -12
  151. package/src/cli/AGENTS.md +3 -1
  152. package/src/cli/commands/backup.help.ts +10 -13
  153. package/src/cli/commands/contacts.help.ts +16 -4
  154. package/src/cli/commands/contacts.ts +5 -1
  155. package/src/cli/commands/credentials.help.ts +1 -1
  156. package/src/cli/commands/inference-providers.ts +18 -53
  157. package/src/cli/commands/mcp.help.ts +6 -5
  158. package/src/cli/commands/mcp.ts +1 -1
  159. package/src/cli/commands/memory/memory-retrospective.ts +1 -0
  160. package/src/cli/commands/stt.help.ts +2 -2
  161. package/src/config/__tests__/backup-schema.test.ts +1 -1
  162. package/src/config/__tests__/default-profile-catalog.test.ts +83 -2
  163. package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +1 -1
  164. package/src/config/__tests__/memory-retrospective-schema.test.ts +12 -15
  165. package/src/config/bundled-skills/computer-use/SKILL.md +6 -6
  166. package/src/config/bundled-skills/computer-use/TOOLS.json +9 -6
  167. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +3 -1
  168. package/src/config/bundled-skills/media-processing/services/preprocess.ts +1 -1
  169. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  170. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +16 -15
  171. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -1
  172. package/src/config/bundled-skills/settings/TOOLS.json +11 -5
  173. package/src/config/bundled-skills/settings/tools/open-system-settings.test.ts +85 -0
  174. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +33 -8
  175. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +54 -3
  176. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +1 -1
  177. package/src/config/default-profile-catalog.ts +195 -10
  178. package/src/config/default-profile-names.ts +41 -0
  179. package/src/config/feature-flag-registry.json +34 -2
  180. package/src/config/inference-profile-validation.ts +11 -2
  181. package/src/config/llm-resolver.ts +25 -0
  182. package/src/config/loader.ts +3 -0
  183. package/src/config/managed-speech-defaults.ts +160 -10
  184. package/src/config/schema.ts +2 -0
  185. package/src/config/schemas/__tests__/live-voice.test.ts +7 -6
  186. package/src/config/schemas/__tests__/stt.test.ts +94 -0
  187. package/src/config/schemas/backup.ts +1 -1
  188. package/src/config/schemas/channels.ts +32 -0
  189. package/src/config/schemas/live-voice.ts +13 -4
  190. package/src/config/schemas/llm.ts +263 -15
  191. package/src/config/schemas/mcp.ts +10 -2
  192. package/src/config/schemas/memory-retrospective.ts +1 -19
  193. package/src/config/schemas/services.ts +24 -0
  194. package/src/config/schemas/stt.ts +128 -5
  195. package/src/config/seed-inference-profiles.ts +32 -3
  196. package/src/config/skill-state.ts +5 -0
  197. package/src/config/skills.ts +21 -0
  198. package/src/contacts/notify-contacts-changed.ts +5 -2
  199. package/src/context/compactor.ts +74 -1
  200. package/src/daemon/__tests__/conversation-tool-setup.test.ts +138 -1
  201. package/src/daemon/config-watcher.ts +2 -1
  202. package/src/daemon/conversation-agent-loop-handlers.ts +27 -0
  203. package/src/daemon/conversation-agent-loop.ts +86 -10
  204. package/src/daemon/conversation-error.ts +25 -0
  205. package/src/daemon/conversation-evictor.ts +2 -4
  206. package/src/daemon/conversation-runtime-assembly.ts +19 -7
  207. package/src/daemon/conversation-skill-tools.ts +5 -1
  208. package/src/daemon/conversation-slash.ts +2 -2
  209. package/src/daemon/conversation-store.ts +8 -1
  210. package/src/daemon/conversation-tool-setup.ts +37 -0
  211. package/src/daemon/conversation.ts +0 -4
  212. package/src/daemon/handlers/channel-config-result.ts +30 -0
  213. package/src/daemon/handlers/config-discord-channel.ts +301 -0
  214. package/src/daemon/handlers/config-ingress.ts +9 -0
  215. package/src/daemon/handlers/config-slack-channel.ts +13 -15
  216. package/src/daemon/handlers/config-telegram.ts +2 -5
  217. package/src/daemon/handlers/conversations.ts +0 -2
  218. package/src/daemon/handlers/skills.ts +25 -7
  219. package/src/daemon/lifecycle.ts +3 -1
  220. package/src/daemon/message-types/messages.ts +0 -2
  221. package/src/daemon/tool-setup-types.ts +5 -3
  222. package/src/daemon/trust-context-types.test.ts +33 -0
  223. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  224. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +58 -4
  225. package/src/live-voice/__tests__/live-voice-managed-speech-fallback.test.ts +8 -3
  226. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +73 -11
  227. package/src/live-voice/__tests__/live-voice-session-preflight.test.ts +148 -2
  228. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +64 -0
  229. package/src/live-voice/__tests__/live-voice-stt.test.ts +3 -0
  230. package/src/live-voice/__tests__/live-voice-text-turn.test.ts +391 -0
  231. package/src/live-voice/__tests__/protocol.test.ts +224 -0
  232. package/src/live-voice/live-voice-credential-preflight.ts +5 -1
  233. package/src/live-voice/live-voice-session-manager.ts +39 -2
  234. package/src/live-voice/live-voice-session.ts +224 -9
  235. package/src/live-voice/protocol.ts +187 -1
  236. package/src/messaging/providers/discord/api.ts +2 -1
  237. package/src/messaging/providers/discord/withdraw.ts +62 -0
  238. package/src/messaging/read-provider-metadata.ts +32 -0
  239. package/src/monitoring/__tests__/file-descriptors.test.ts +32 -0
  240. package/src/monitoring/__tests__/process-memory.test.ts +34 -1
  241. package/src/monitoring/db-integrity-sample.ts +1 -0
  242. package/src/monitoring/file-descriptors.ts +39 -6
  243. package/src/monitoring/process-memory.ts +37 -5
  244. package/src/monitoring/resource-sampler.ts +25 -8
  245. package/src/notifications/__tests__/activity-failed-dedupe.test.ts +161 -0
  246. package/src/notifications/__tests__/background-failure-signal.test.ts +107 -0
  247. package/src/notifications/__tests__/connected-channels.test.ts +12 -0
  248. package/src/notifications/__tests__/copy-composer.test.ts +83 -0
  249. package/src/notifications/__tests__/destination-resolver.test.ts +38 -0
  250. package/src/notifications/activity-failed-dedupe.ts +95 -0
  251. package/src/notifications/adapters/discord.ts +132 -0
  252. package/src/notifications/adapters/shared.ts +16 -0
  253. package/src/notifications/adapters/slack.ts +6 -1
  254. package/src/notifications/adapters/telegram.ts +5 -8
  255. package/src/notifications/approval-card-data.ts +94 -1
  256. package/src/notifications/background-failure-signal.ts +116 -0
  257. package/src/notifications/broadcaster.ts +3 -9
  258. package/src/notifications/copy-composer.ts +41 -8
  259. package/src/notifications/decision-engine.ts +55 -0
  260. package/src/notifications/destination-resolver.ts +45 -5
  261. package/src/notifications/emit-signal.ts +19 -5
  262. package/src/notifications/guardian-question-mode.ts +57 -0
  263. package/src/notifications/types.ts +2 -8
  264. package/src/oauth/__tests__/seed-providers-managed.test.ts +18 -0
  265. package/src/oauth/connect-orchestrator.ts +16 -0
  266. package/src/oauth/seed-providers.ts +262 -0
  267. package/src/permissions/checker.test.ts +21 -0
  268. package/src/permissions/checker.ts +6 -0
  269. package/src/permissions/gateway-threshold-reader.test.ts +184 -0
  270. package/src/permissions/gateway-threshold-reader.ts +144 -10
  271. package/src/permissions/prompter.ts +0 -6
  272. package/src/permissions/types.ts +5 -4
  273. package/src/persistence/__tests__/bulk-write-gate.test.ts +0 -38
  274. package/src/persistence/bulk-write-gate.ts +9 -10
  275. package/src/persistence/conversation-crud.ts +88 -134
  276. package/src/persistence/conversation-title-service.ts +43 -29
  277. package/src/persistence/embeddings/embedding-local.ts +14 -85
  278. package/src/persistence/embeddings/embedding-runtime-manager.ts +1 -0
  279. package/src/persistence/lifecycle-events-store.test.ts +50 -0
  280. package/src/persistence/lifecycle-events-store.ts +75 -5
  281. package/src/platform/platform-patch-queue.test.ts +368 -0
  282. package/src/platform/platform-patch-queue.ts +190 -0
  283. package/src/platform/sync-avatar.test.ts +527 -0
  284. package/src/platform/sync-avatar.ts +209 -0
  285. package/src/platform/sync-identity.test.ts +75 -0
  286. package/src/platform/sync-identity.ts +17 -110
  287. package/src/plugin-api/model-profiles.test.ts +2 -2
  288. package/src/plugin-api/model-profiles.ts +7 -7
  289. package/src/plugin-api/transcription-session.ts +1 -1
  290. package/src/plugins/defaults/compaction/window-manager.ts +4 -0
  291. package/src/plugins/defaults/image-recovery/recover.ts +2 -2
  292. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +133 -34
  293. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +21 -31
  294. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +13 -9
  295. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +31 -4
  296. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +0 -11
  297. package/src/plugins/defaults/memory/memory-retrospective-fork-boundary.ts +27 -13
  298. package/src/plugins/defaults/memory/memory-retrospective-job.ts +183 -113
  299. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +13 -13
  300. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +2 -2
  301. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +1 -1
  302. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +4 -2
  303. package/src/plugins/defaults/memory/substrate/sweep-job.ts +9 -42
  304. package/src/plugins/defaults/memory/v2/rerank-local.ts +1 -0
  305. package/src/plugins/defaults/memory/worker.ts +7 -0
  306. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  307. package/src/providers/__tests__/retry-callsite.test.ts +55 -0
  308. package/src/providers/__tests__/vellum-connection-routing.test.ts +21 -3
  309. package/src/providers/connection-resolution.ts +2 -17
  310. package/src/providers/fallback-breaker.ts +389 -0
  311. package/src/providers/inference/__tests__/adapter-factory-opencode.test.ts +92 -0
  312. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +16 -0
  313. package/src/providers/inference/__tests__/base-url-security.test.ts +12 -0
  314. package/src/providers/inference/adapter-factory.ts +246 -3
  315. package/src/providers/inference/auth.ts +15 -14
  316. package/src/providers/inference/chatgpt-subscription-auth.ts +70 -0
  317. package/src/providers/inference/chatgpt-subscription-credentials.ts +84 -0
  318. package/src/providers/inference/resolve-auth.ts +1 -1
  319. package/src/providers/media-resolve.ts +2 -3
  320. package/src/providers/model-catalog.ts +100 -0
  321. package/src/providers/openai/__tests__/api-error-normalization.test.ts +22 -0
  322. package/src/providers/openai/__tests__/chat-template-flatten.test.ts +198 -0
  323. package/src/providers/openai/api-error-normalization.ts +13 -0
  324. package/src/providers/openai/chat-completions-provider.ts +84 -0
  325. package/src/providers/opencode/client.test.ts +150 -0
  326. package/src/providers/opencode/client.ts +61 -0
  327. package/src/providers/platform-proxy/constants.ts +5 -0
  328. package/src/providers/provider-resolvability.ts +20 -0
  329. package/src/providers/provider-secret-catalog.ts +3 -2
  330. package/src/providers/retry.ts +956 -68
  331. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +42 -0
  332. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +136 -0
  333. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +120 -0
  334. package/src/providers/speech-to-text/__tests__/resolve.test.ts +316 -12
  335. package/src/providers/speech-to-text/deepgram-flux-frames.ts +83 -3
  336. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +132 -4
  337. package/src/providers/speech-to-text/provider-catalog.ts +191 -3
  338. package/src/providers/speech-to-text/resolve.ts +132 -24
  339. package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +296 -0
  340. package/src/providers/types.ts +39 -0
  341. package/src/providers/usage-tracking.ts +12 -1
  342. package/src/providers/vellum/client.ts +29 -0
  343. package/src/providers/vellum-model-routing.test.ts +2 -0
  344. package/src/providers/vellum-model-routing.ts +3 -3
  345. package/src/runtime/AGENTS.md +1 -2
  346. package/src/runtime/__tests__/agent-wake.test.ts +105 -3
  347. package/src/runtime/__tests__/background-job-runner.test.ts +89 -3
  348. package/src/runtime/agent-wake.ts +44 -5
  349. package/src/runtime/background-job-runner.ts +68 -50
  350. package/src/runtime/channel-readiness-service.ts +0 -39
  351. package/src/runtime/http-server.ts +12 -4
  352. package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +18 -18
  353. package/src/runtime/routes/__tests__/backup-routes.test.ts +9 -5
  354. package/src/runtime/routes/__tests__/chatgpt-subscription-auth-routes.test.ts +372 -0
  355. package/src/runtime/routes/__tests__/contact-routes.test.ts +7 -0
  356. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +96 -0
  357. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +37 -0
  358. package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +2 -1
  359. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +87 -0
  360. package/src/runtime/routes/acp-claude-auth-routes.ts +25 -54
  361. package/src/runtime/routes/approval-routes.ts +0 -12
  362. package/src/runtime/routes/avatar-routes.ts +17 -30
  363. package/src/runtime/routes/backup-routes.ts +2 -2
  364. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +296 -113
  365. package/src/runtime/routes/contact-prompt-routes.ts +65 -6
  366. package/src/runtime/routes/contact-routes.ts +20 -15
  367. package/src/runtime/routes/conversation-query-routes.ts +98 -10
  368. package/src/runtime/routes/conversation-routes.ts +2 -2
  369. package/src/runtime/routes/credential-routes.ts +2 -1
  370. package/src/runtime/routes/guardian-approval-interception.ts +12 -9
  371. package/src/runtime/routes/inbound-message-handler.ts +47 -48
  372. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +18 -13
  373. package/src/runtime/routes/inbound-stages/admission-policy.ts +2 -2
  374. package/src/runtime/routes/inbound-stages/edit-intercept.ts +20 -2
  375. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +9 -8
  376. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -1
  377. package/src/runtime/routes/index.ts +2 -0
  378. package/src/runtime/routes/inference-profile-session-handler.ts +2 -2
  379. package/src/runtime/routes/inference-profiles-routes.ts +12 -7
  380. package/src/runtime/routes/inference-provider-connection-routes.ts +2 -2
  381. package/src/runtime/routes/inference-send-routes.ts +2 -2
  382. package/src/runtime/routes/ingress-status-routes.ts +2 -2
  383. package/src/runtime/routes/integrations/discord.ts +119 -0
  384. package/src/runtime/routes/llm-call-sites-routes.ts +2 -2
  385. package/src/runtime/routes/mcp-auth-routes.ts +12 -6
  386. package/src/runtime/routes/migration-routes.ts +3 -1
  387. package/src/runtime/routes/oauth-pending-flows.ts +136 -0
  388. package/src/runtime/routes/secret-routes.ts +7 -0
  389. package/src/runtime/routes/stt-routes.ts +38 -6
  390. package/src/runtime/routes/wake-conversation-routes.ts +8 -1
  391. package/src/runtime/routes/watch-routes.ts +9 -5
  392. package/src/runtime/sync/resource-sync-events.ts +2 -0
  393. package/src/runtime/trust-verdict-consumer.ts +3 -3
  394. package/src/schedule/__tests__/worker-feature-flags.test.ts +58 -0
  395. package/src/schedule/retry-policy.ts +8 -8
  396. package/src/schedule/run-script.ts +14 -23
  397. package/src/schedule/schedule-recovery.ts +0 -1
  398. package/src/schedule/scheduler.ts +89 -51
  399. package/src/schedule/worker.ts +57 -0
  400. package/src/security/__tests__/openai-device-auth.test.ts +355 -0
  401. package/src/security/ces-rpc-record-backend.ts +123 -0
  402. package/src/security/oauth2.ts +7 -0
  403. package/src/security/openai-device-auth.ts +364 -0
  404. package/src/security/secure-keys.ts +15 -0
  405. package/src/skills/available-skills.ts +10 -1
  406. package/src/skills/catalog-install.ts +19 -1
  407. package/src/skills/inline-command-runner.ts +6 -2
  408. package/src/skills/platform-compatibility.test.ts +49 -0
  409. package/src/skills/platform-compatibility.ts +65 -0
  410. package/src/skills/tool-manifest.ts +20 -0
  411. package/src/stt/__tests__/roles.test.ts +152 -0
  412. package/src/stt/daemon-batch-transcriber.ts +4 -1
  413. package/src/stt/roles.ts +180 -0
  414. package/src/stt/stt-stream-session.ts +3 -2
  415. package/src/stt/types.ts +33 -3
  416. package/src/subagent/manager.ts +12 -0
  417. package/src/telemetry/__tests__/live-voice-funnel.test.ts +34 -0
  418. package/src/telemetry/live-voice-funnel.ts +12 -2
  419. package/src/telemetry/telemetry-wire.generated.ts +6 -0
  420. package/src/telemetry/tool-audit.ts +83 -30
  421. package/src/telemetry/tool-executed-events-store.test.ts +0 -1
  422. package/src/telemetry/turn-outcome.ts +75 -4
  423. package/src/tools/client-os.ts +115 -0
  424. package/src/tools/computer-use/definitions.ts +11 -7
  425. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -2
  426. package/src/tools/credentials/broker-types.ts +0 -71
  427. package/src/tools/credentials/broker.ts +6 -201
  428. package/src/tools/credentials/metadata-store.ts +65 -16
  429. package/src/tools/credentials/store.ts +2 -0
  430. package/src/tools/executor.ts +2 -8
  431. package/src/tools/host-shell.test.ts +75 -0
  432. package/src/tools/host-shell.ts +24 -0
  433. package/src/tools/host-terminal/host-shell.ts +15 -44
  434. package/src/tools/mcp/mcp-tool-factory.ts +44 -9
  435. package/src/tools/permission-checker.ts +73 -21
  436. package/src/tools/policy-context.ts +1 -0
  437. package/src/tools/shared/filesystem/image-read.ts +1 -1
  438. package/src/tools/skills/find-similar-skills.ts +10 -2
  439. package/src/tools/skills/load.ts +41 -6
  440. package/src/tools/skills/sandbox-runner.ts +8 -16
  441. package/src/tools/skills/skill-tool-factory.ts +1 -0
  442. package/src/tools/terminal/__tests__/safe-env.test.ts +64 -1
  443. package/src/tools/terminal/safe-env.ts +39 -9
  444. package/src/tools/terminal/shell.ts +12 -31
  445. package/src/tools/tool-approval-handler.ts +64 -10
  446. package/src/tools/tool-types.ts +10 -0
  447. package/src/tools/types.ts +19 -3
  448. package/src/tools/ui-surface/surface-shape-docs.ts +4 -3
  449. package/src/tools/workflows/manage-workflows.ts +2 -2
  450. package/src/tools/workflows/run-workflow.test.ts +2 -1
  451. package/src/util/__tests__/file-use.test.ts +61 -0
  452. package/src/util/__tests__/process-table.test.ts +66 -0
  453. package/src/util/__tests__/process-tree.test.ts +46 -0
  454. package/src/util/errors.ts +1 -0
  455. package/src/util/file-use.ts +91 -0
  456. package/src/util/host-process.test.ts +17 -0
  457. package/src/util/host-process.ts +75 -0
  458. package/src/util/image-conversion.ts +39 -7
  459. package/src/util/platform.ts +32 -13
  460. package/src/util/process-table.ts +249 -0
  461. package/src/util/process-tree.ts +85 -129
  462. package/src/util/provider-error-patterns.ts +20 -0
  463. package/src/util/spawn.ts +9 -2
  464. package/src/watch/__tests__/watch-retro.test.ts +48 -1
  465. package/src/watch/watch-retro.ts +46 -4
  466. package/src/workspace/git-service.ts +4 -14
  467. package/src/workspace/migrations/147-rename-colliding-backup-profile-names.ts +360 -0
  468. package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +117 -0
  469. package/src/workspace/migrations/149-repoint-backup-profile-selections.ts +204 -0
  470. package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +139 -0
  471. package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +160 -0
  472. package/src/workspace/migrations/registry.ts +8 -0
  473. package/src/__tests__/permission-types.test.ts +0 -17
  474. package/src/channels/gateway-discord-admission.ts +0 -32
  475. package/src/persistence/fork-message-copy.ts +0 -239
  476. package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +0 -221
  477. package/src/security/__tests__/oauth2-device-code.test.ts +0 -431
  478. package/src/security/oauth2-device-code.ts +0 -312
@@ -11,6 +11,7 @@ let promptDecision: "allow" | "deny" = "allow";
11
11
  let fakeToolResult: ToolExecutionResult = { content: "ok", isError: false };
12
12
  let toolThrow: Error | null = null;
13
13
  let grantConsumeOk = false;
14
+ let abortDuringCheck: AbortController | null = null;
14
15
 
15
16
  // ── audit-terminal captures ───────────────────────────────
16
17
  // The executor and its permission/approval collaborators no longer emit
@@ -31,7 +32,6 @@ interface ExecutedCapture {
31
32
  matchedTrustRuleId?: string;
32
33
  durationMs: number;
33
34
  attribution: UsageAttributionSnapshot | null;
34
- wasPrompted: boolean;
35
35
  }
36
36
 
37
37
  interface ErrorCapture {
@@ -57,13 +57,28 @@ interface DeniedCapture {
57
57
  riskLevel: string;
58
58
  matchedTrustRuleId?: string;
59
59
  durationMs: number;
60
- wasPrompted: boolean;
60
+ }
61
+
62
+ interface PromptTelemetryCapture {
63
+ toolName: string;
64
+ riskLevel: string;
65
+ riskThreshold?: string;
66
+ surface?: string;
67
+ conversationId?: string;
61
68
  }
62
69
 
63
70
  const executedCaptures: ExecutedCapture[] = [];
64
71
  const errorCaptures: ErrorCapture[] = [];
65
72
  const deniedCaptures: DeniedCapture[] = [];
66
- const promptedCaptures: string[] = [];
73
+ const promptedCaptures: PromptTelemetryCapture[] = [];
74
+ const decidedCaptures: Array<{
75
+ entry: PromptTelemetryCapture;
76
+ outcome: string;
77
+ }> = [];
78
+ const promptedToolNames = (): string[] =>
79
+ promptedCaptures.map((entry) => entry.toolName);
80
+ const decidedOutcomes = (): string[] =>
81
+ decidedCaptures.map((decided) => decided.outcome);
67
82
 
68
83
  mock.module("../telemetry/tool-audit.js", () => ({
69
84
  recordToolExecuted: (entry: ExecutedCapture) => {
@@ -75,8 +90,14 @@ mock.module("../telemetry/tool-audit.js", () => ({
75
90
  recordToolDenied: (entry: DeniedCapture) => {
76
91
  deniedCaptures.push(entry);
77
92
  },
78
- recordToolPermissionPrompted: (toolName: string) => {
79
- promptedCaptures.push(toolName);
93
+ recordToolPermissionPrompted: (entry: PromptTelemetryCapture) => {
94
+ promptedCaptures.push(entry);
95
+ },
96
+ recordToolPermissionDecided: (
97
+ entry: PromptTelemetryCapture,
98
+ outcome: string,
99
+ ) => {
100
+ decidedCaptures.push({ entry, outcome });
80
101
  },
81
102
  }));
82
103
 
@@ -105,7 +126,12 @@ mock.module("../permissions/checker.js", () => ({
105
126
  }
106
127
  return { level: checkerRisk };
107
128
  },
108
- check: async () => ({ decision: checkerDecision, reason: checkerReason }),
129
+ check: async () => {
130
+ // Lets a test abort the turn after risk classification (which rethrows an
131
+ // aborted signal) but before the prompt is surfaced.
132
+ abortDuringCheck?.abort();
133
+ return { decision: checkerDecision, reason: checkerReason };
134
+ },
109
135
  generateScopeOptions: () => [{ label: "/tmp", scope: "/tmp" }],
110
136
  }));
111
137
 
@@ -267,6 +293,12 @@ function makePrompter(
267
293
  promptImpl?: () => Promise<{
268
294
  decision: "allow" | "deny";
269
295
  decisionContext?: string;
296
+ /** Set by the real prompter when nobody answered within the timeout. */
297
+ wasTimeout?: boolean;
298
+ /** Set when a new user message superseded the outstanding prompt. */
299
+ wasSystemCancel?: boolean;
300
+ /** Set when the turn was cancelled while the prompt was outstanding. */
301
+ wasAbort?: boolean;
270
302
  }>,
271
303
  ) {
272
304
  return {
@@ -287,10 +319,12 @@ describe("ToolExecutor audit terminals", () => {
287
319
  fakeToolResult = { content: "ok", isError: false };
288
320
  toolThrow = null;
289
321
  grantConsumeOk = false;
322
+ abortDuringCheck = null;
290
323
  executedCaptures.length = 0;
291
324
  errorCaptures.length = 0;
292
325
  deniedCaptures.length = 0;
293
326
  promptedCaptures.length = 0;
327
+ decidedCaptures.length = 0;
294
328
  });
295
329
 
296
330
  test("records executed terminal for allowed execution", async () => {
@@ -314,16 +348,15 @@ describe("ToolExecutor audit terminals", () => {
314
348
  expect(executed.riskLevel).toBe("low");
315
349
  expect(executed.resultContent).toBe("ok");
316
350
  expect(executed.resultBytes).toBe(Buffer.byteLength("ok", "utf8"));
317
- expect(executed.wasPrompted).toBe(false);
318
351
  expect(executed.durationMs).toBeGreaterThanOrEqual(0);
319
352
 
320
353
  disposeToolProfiler("conversation-1");
321
354
  });
322
355
 
323
- test("reports wasPrompted per-invocation, not from the turn-level flag", async () => {
356
+ test("records permission telemetry per-invocation, not from the turn-level flag", async () => {
324
357
  // A prior tool this turn was interactively approved (turn-level
325
- // `approvedViaPrompt` already true), but THIS call is auto-approved. The
326
- // decided telemetry must not treat it as prompted.
358
+ // `approvedViaPrompt` already true), but THIS call is auto-approved, so it
359
+ // surfaces no prompt and records neither half of the pair.
327
360
  const executor = new ToolExecutor(makePrompter());
328
361
 
329
362
  const result = await executor.execute(
@@ -334,7 +367,8 @@ describe("ToolExecutor audit terminals", () => {
334
367
 
335
368
  expect(result).toMatchObject({ isError: false });
336
369
  expect(executedCaptures).toHaveLength(1);
337
- expect(executedCaptures[0].wasPrompted).toBe(false);
370
+ expect(promptedCaptures).toHaveLength(0);
371
+ expect(decidedCaptures).toHaveLength(0);
338
372
  });
339
373
 
340
374
  test("records denied terminal when user denies prompt", async () => {
@@ -355,14 +389,193 @@ describe("ToolExecutor audit terminals", () => {
355
389
  expect(result.content).toContain("Permission denied");
356
390
 
357
391
  // A prompt was surfaced, then the user's denial recorded a denied terminal.
358
- expect(promptedCaptures).toEqual(["bash"]);
392
+ expect(promptedToolNames()).toEqual(["bash"]);
393
+ expect(decidedOutcomes()).toEqual(["deny"]);
359
394
  expect(executedCaptures).toHaveLength(0);
360
395
  expect(deniedCaptures).toHaveLength(1);
361
396
  const denied = deniedCaptures[0];
362
397
  expect(denied.toolName).toBe("bash");
363
398
  expect(denied.riskLevel).toBe("medium");
364
399
  expect(denied.reason).toBe("Permission denied by user");
365
- expect(denied.wasPrompted).toBe(true);
400
+ });
401
+
402
+ test("records an allow decision when the user approves the prompt", async () => {
403
+ checkerDecision = "prompt";
404
+ checkerReason = "medium risk: requires approval";
405
+ checkerRisk = "medium";
406
+ promptDecision = "allow";
407
+
408
+ const executor = new ToolExecutor(makePrompter());
409
+
410
+ await executor.execute(
411
+ "bash",
412
+ { command: "ls -la" },
413
+ makeContext({ forcePromptSideEffects: true }),
414
+ );
415
+
416
+ expect(promptedToolNames()).toEqual(["bash"]);
417
+ expect(decidedOutcomes()).toEqual(["allow"]);
418
+ });
419
+
420
+ test("carries risk level and access preset onto both halves of the pair", async () => {
421
+ checkerDecision = "prompt";
422
+ checkerReason = "high risk: requires approval";
423
+ checkerRisk = "high";
424
+ promptDecision = "deny";
425
+
426
+ const executor = new ToolExecutor(makePrompter());
427
+
428
+ await executor.execute(
429
+ "bash",
430
+ { command: "rm -rf /tmp/x" },
431
+ makeContext({ forcePromptSideEffects: true, executionChannel: "slack" }),
432
+ );
433
+
434
+ const expected = {
435
+ toolName: "bash",
436
+ riskLevel: "high",
437
+ // The gateway is unreachable under test, so the reader falls back to the
438
+ // strict preset. What matters is that the resolved value is recorded.
439
+ riskThreshold: "none",
440
+ surface: "slack",
441
+ conversationId: "conversation-1",
442
+ };
443
+ expect(promptedCaptures[0]).toMatchObject(expected);
444
+ expect(decidedCaptures[0].entry).toMatchObject(expected);
445
+ });
446
+
447
+ // ── abandoned prompts ───────────────────────────────
448
+ // Every one of these resolves to a deny for the agent loop, so the only
449
+ // thing separating them from a real user denial is the decision outcome.
450
+ test.each([
451
+ ["timed out", { decision: "deny" as const, wasTimeout: true }],
452
+ [
453
+ "superseded by a new user message",
454
+ { decision: "deny" as const, wasSystemCancel: true },
455
+ ],
456
+ ["cancelled with the turn", { decision: "deny" as const, wasAbort: true }],
457
+ ])(
458
+ "records an abandoned decision when the prompt %s",
459
+ async (_label, response) => {
460
+ checkerDecision = "prompt";
461
+ checkerReason = "medium risk: requires approval";
462
+ checkerRisk = "medium";
463
+
464
+ const executor = new ToolExecutor(makePrompter(async () => response));
465
+
466
+ const result = await executor.execute(
467
+ "bash",
468
+ { command: "ls -la" },
469
+ makeContext({ forcePromptSideEffects: true }),
470
+ );
471
+
472
+ expect(result.isError).toBe(true);
473
+ expect(promptedToolNames()).toEqual(["bash"]);
474
+ expect(decidedOutcomes()).toEqual(["abandoned"]);
475
+ },
476
+ );
477
+
478
+ test("records nothing when the turn aborts before the prompt is surfaced", async () => {
479
+ checkerDecision = "prompt";
480
+ checkerReason = "medium risk: requires approval";
481
+ checkerRisk = "medium";
482
+
483
+ // The real prompter returns immediately on an already-aborted signal
484
+ // without registering or sending a confirmation request, so the user never
485
+ // sees a prompt and neither half of the pair may be recorded.
486
+ const controller = new AbortController();
487
+ abortDuringCheck = controller;
488
+ const executor = new ToolExecutor(
489
+ makePrompter(async () => ({ decision: "deny", wasAbort: true })),
490
+ );
491
+
492
+ const result = await executor.execute(
493
+ "bash",
494
+ { command: "ls -la" },
495
+ makeContext({ forcePromptSideEffects: true, signal: controller.signal }),
496
+ );
497
+
498
+ expect(result.isError).toBe(true);
499
+ // The invocation did reach the prompt branch and was denied there, so the
500
+ // empty telemetry below is the guard working, not the branch being skipped.
501
+ expect(deniedCaptures).toHaveLength(1);
502
+ expect(promptedCaptures).toHaveLength(0);
503
+ expect(decidedCaptures).toHaveLength(0);
504
+ });
505
+
506
+ test("records an abandoned decision when the prompter is disposed mid-prompt", async () => {
507
+ checkerDecision = "prompt";
508
+ checkerReason = "medium risk: requires approval";
509
+ checkerRisk = "medium";
510
+
511
+ const executor = new ToolExecutor(
512
+ makePrompter(async () => {
513
+ throw new Error("Prompter disposed");
514
+ }),
515
+ );
516
+
517
+ const result = await executor.execute(
518
+ "bash",
519
+ { command: "ls -la" },
520
+ makeContext({ forcePromptSideEffects: true }),
521
+ );
522
+
523
+ expect(result.isError).toBe(true);
524
+ expect(promptedToolNames()).toEqual(["bash"]);
525
+ expect(decidedOutcomes()).toEqual(["abandoned"]);
526
+ });
527
+
528
+ test("every prompt reconciles against exactly one decision", async () => {
529
+ checkerDecision = "prompt";
530
+ checkerReason = "medium risk: requires approval";
531
+ checkerRisk = "medium";
532
+
533
+ // One of each terminal shape a prompted invocation can reach: approved and
534
+ // the tool runs, approved and the tool throws, denied, and abandoned.
535
+ const runs: Array<() => Promise<unknown>> = [
536
+ async () => {
537
+ promptDecision = "allow";
538
+ toolThrow = null;
539
+ return new ToolExecutor(makePrompter()).execute(
540
+ "bash",
541
+ { command: "ls" },
542
+ makeContext({ forcePromptSideEffects: true }),
543
+ );
544
+ },
545
+ async () => {
546
+ promptDecision = "allow";
547
+ toolThrow = new Error("boom");
548
+ return new ToolExecutor(makePrompter()).execute(
549
+ "bash",
550
+ { command: "ls" },
551
+ makeContext({ forcePromptSideEffects: true }),
552
+ );
553
+ },
554
+ async () => {
555
+ promptDecision = "deny";
556
+ toolThrow = null;
557
+ return new ToolExecutor(makePrompter()).execute(
558
+ "bash",
559
+ { command: "ls" },
560
+ makeContext({ forcePromptSideEffects: true }),
561
+ );
562
+ },
563
+ async () =>
564
+ new ToolExecutor(
565
+ makePrompter(async () => ({ decision: "deny", wasTimeout: true })),
566
+ ).execute(
567
+ "bash",
568
+ { command: "ls" },
569
+ makeContext({ forcePromptSideEffects: true }),
570
+ ),
571
+ ];
572
+ for (const run of runs) {
573
+ await run();
574
+ }
575
+
576
+ expect(promptedCaptures).toHaveLength(runs.length);
577
+ expect(decidedCaptures).toHaveLength(runs.length);
578
+ expect(decidedOutcomes()).toEqual(["allow", "allow", "deny", "abandoned"]);
366
579
  });
367
580
 
368
581
  test("uses contextual deny messaging when provided by prompter", async () => {
@@ -392,7 +605,6 @@ describe("ToolExecutor audit terminals", () => {
392
605
  expect(deniedCaptures[0].reason).toBe(
393
606
  "Permission denied (bash): contextual policy",
394
607
  );
395
- expect(deniedCaptures[0].wasPrompted).toBe(true);
396
608
  });
397
609
 
398
610
  // executionTarget is no longer carried on any audit-terminal payload, so
@@ -435,7 +647,6 @@ describe("ToolExecutor audit terminals", () => {
435
647
  expect(promptedCaptures).toHaveLength(0);
436
648
  expect(deniedCaptures).toHaveLength(1);
437
649
  expect(deniedCaptures[0].reason).toBe("Blocked by deny rule: rm *");
438
- expect(deniedCaptures[0].wasPrompted).toBe(false);
439
650
  });
440
651
 
441
652
  test("records error terminal when tool execution throws", async () => {
@@ -766,7 +977,7 @@ describe("ToolExecutor audit terminals", () => {
766
977
  );
767
978
 
768
979
  // forcePromptSideEffects promotes the auto-allow to an interactive prompt.
769
- expect(promptedCaptures).toEqual(["bash"]);
980
+ expect(promptedToolNames()).toEqual(["bash"]);
770
981
  expect(executedCaptures).toHaveLength(1);
771
982
  expect(executedCaptures[0].toolName).toBe("bash");
772
983
  });
@@ -811,7 +1022,7 @@ describe("ToolExecutor audit terminals", () => {
811
1022
  );
812
1023
 
813
1024
  expect(result).toMatchObject({ content: "ok", isError: false });
814
- expect(promptedCaptures).toEqual(["file_edit"]);
1025
+ expect(promptedToolNames()).toEqual(["file_edit"]);
815
1026
  expect(executedCaptures).toHaveLength(1);
816
1027
  expect(executedCaptures[0].toolName).toBe("file_edit");
817
1028
  });
@@ -836,7 +1047,6 @@ describe("ToolExecutor audit terminals", () => {
836
1047
  expect(result.isError).toBe(true);
837
1048
  expect(deniedCaptures).toHaveLength(1);
838
1049
  expect(deniedCaptures[0].riskLevel).toBe("high");
839
- expect(deniedCaptures[0].wasPrompted).toBe(false);
840
1050
  });
841
1051
 
842
1052
  test("a gate-errored call records the classified risk", async () => {
@@ -159,7 +159,7 @@ describe("ui_show missing-content teaching", () => {
159
159
  {
160
160
  surfaceType: "channel_setup",
161
161
  data: { channel: "email" },
162
- expectInError: '"slack", "telegram", "phone"',
162
+ expectInError: '"slack", "telegram", "discord", "phone"',
163
163
  },
164
164
  {
165
165
  // Snake_case constraint keys are stripped by the schema, so without the
@@ -42,13 +42,16 @@ const auditCalls = {
42
42
  denied: [] as any[],
43
43
  error: [] as any[],
44
44
  executed: [] as any[],
45
- prompted: [] as string[],
45
+ prompted: [] as any[],
46
+ decided: [] as any[],
46
47
  };
47
48
  mock.module("../telemetry/tool-audit.js", () => ({
48
49
  recordToolDenied: (e: any) => auditCalls.denied.push(e),
49
50
  recordToolError: (e: any) => auditCalls.error.push(e),
50
51
  recordToolExecuted: (e: any) => auditCalls.executed.push(e),
51
- recordToolPermissionPrompted: (n: string) => auditCalls.prompted.push(n),
52
+ recordToolPermissionPrompted: (e: any) => auditCalls.prompted.push(e),
53
+ recordToolPermissionDecided: (e: any, outcome: string) =>
54
+ auditCalls.decided.push({ entry: e, outcome }),
52
55
  }));
53
56
 
54
57
  function resetAuditCalls(): void {
@@ -56,6 +59,7 @@ function resetAuditCalls(): void {
56
59
  auditCalls.error.length = 0;
57
60
  auditCalls.executed.length = 0;
58
61
  auditCalls.prompted.length = 0;
62
+ auditCalls.decided.length = 0;
59
63
  }
60
64
 
61
65
  mock.module("../tools/registry.js", () => ({
@@ -561,6 +561,49 @@ describe("voice_config_update — stt_provider", () => {
561
561
  expect((readConfig().services as any)?.stt?.provider).toBeUndefined();
562
562
  });
563
563
 
564
+ test("stt_model persists under the active provider", async () => {
565
+ // Flux stays reachable from this surface after ceasing to be a provider
566
+ // id: it is a model family on whichever provider is active.
567
+ writeConfig({ services: { stt: { provider: "deepgram" } } });
568
+ invalidateConfigCache();
569
+
570
+ const result = await run(
571
+ { setting: "stt_model", value: "flux" },
572
+ makeContext(),
573
+ );
574
+
575
+ expect(result.isError).toBe(false);
576
+ expect(
577
+ (readConfig().services as any)?.stt?.providers?.deepgram?.model,
578
+ ).toBe("flux");
579
+ });
580
+
581
+ test("stt_model rejects a family the active provider cannot serve", async () => {
582
+ writeConfig({ services: { stt: { provider: "openai-whisper" } } });
583
+ invalidateConfigCache();
584
+
585
+ const result = await run(
586
+ { setting: "stt_model", value: "flux" },
587
+ makeContext(),
588
+ );
589
+
590
+ expect(result.isError).toBe(true);
591
+ expect(result.content).toContain("single model");
592
+ });
593
+
594
+ test("stt_model rejects an unknown family", async () => {
595
+ writeConfig({ services: { stt: { provider: "deepgram" } } });
596
+ invalidateConfigCache();
597
+
598
+ const result = await run(
599
+ { setting: "stt_model", value: "flx" },
600
+ makeContext(),
601
+ );
602
+
603
+ expect(result.isError).toBe(true);
604
+ expect(result.content).toContain("stt_model must be one of");
605
+ });
606
+
564
607
  test("switching away from vellum takes effect", async () => {
565
608
  writeConfig({ services: { stt: { provider: "vellum" } } });
566
609
  invalidateConfigCache();