@vellumai/assistant 0.11.6 → 0.11.7-staging.1

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 (447) 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/environments/package.json +2 -1
  21. package/node_modules/@vellumai/environments/src/shell.test.ts +56 -0
  22. package/node_modules/@vellumai/environments/src/shell.ts +65 -0
  23. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  24. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  25. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  26. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  27. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +32 -0
  28. package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -0
  29. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +82 -0
  30. package/node_modules/@vellumai/gateway-client/src/__tests__/plugin-admission-denied-contract.test.ts +56 -0
  31. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +18 -0
  32. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +5 -11
  33. package/node_modules/@vellumai/gateway-client/src/channel-permission-contract.ts +3 -1
  34. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +9 -0
  35. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +4 -0
  36. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +75 -0
  37. package/node_modules/@vellumai/gateway-client/src/index.ts +17 -0
  38. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +8 -0
  39. package/node_modules/@vellumai/gateway-client/src/plugin-admission-denied-contract.ts +47 -0
  40. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +11 -0
  41. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  42. package/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  43. package/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  44. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  45. package/openapi.yaml +419 -55
  46. package/package.json +4 -1
  47. package/src/__tests__/approval-card-edit-inbound-handoff.test.ts +199 -0
  48. package/src/__tests__/approval-card-edit.test.ts +153 -0
  49. package/src/__tests__/attachments-store-heic-normalize.test.ts +1 -1
  50. package/src/__tests__/available-skills.test.ts +54 -0
  51. package/src/__tests__/avatar-identity-sync.test.ts +5 -1
  52. package/src/__tests__/catalog-install-normalize.test.ts +9 -1
  53. package/src/__tests__/channel-policy.test.ts +1 -3
  54. package/src/__tests__/channel-readiness-discord.test.ts +9 -70
  55. package/src/__tests__/compactor-call-site-logging.test.ts +61 -0
  56. package/src/__tests__/config-loader-backfill.test.ts +69 -0
  57. package/src/__tests__/config-schema.test.ts +2 -3
  58. package/src/__tests__/conversation-agent-loop.test.ts +238 -0
  59. package/src/__tests__/conversation-attachments.test.ts +0 -1
  60. package/src/__tests__/conversation-error.test.ts +44 -0
  61. package/src/__tests__/conversation-evictor.test.ts +19 -0
  62. package/src/__tests__/conversation-fork-referential.test.ts +73 -63
  63. package/src/__tests__/conversation-fork-retrospective.test.ts +99 -74
  64. package/src/__tests__/conversation-runtime-assembly.test.ts +23 -0
  65. package/src/__tests__/conversation-slash.test.ts +9 -0
  66. package/src/__tests__/conversation-title-service.test.ts +97 -0
  67. package/src/__tests__/conversation-tool-setup-attribution.test.ts +16 -0
  68. package/src/__tests__/credential-broker-server-use.test.ts +0 -218
  69. package/src/__tests__/credential-broker.test.ts +43 -420
  70. package/src/__tests__/credential-routes.test.ts +17 -1
  71. package/src/__tests__/credential-security-invariants.test.ts +8 -9
  72. package/src/__tests__/default-profile-catalog-fallback.test.ts +289 -0
  73. package/src/__tests__/discord-callback-round-trip.test.ts +93 -0
  74. package/src/__tests__/discord-channel-config.test.ts +237 -0
  75. package/src/__tests__/evict-conversations-for-reload.test.ts +51 -1
  76. package/src/__tests__/fallback-breaker.test.ts +1335 -0
  77. package/src/__tests__/gateway-threshold-reader-mock.ts +6 -0
  78. package/src/__tests__/guardian-card-withdrawal.test.ts +30 -0
  79. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -2
  80. package/src/__tests__/image-conversion.test.ts +38 -4
  81. package/src/__tests__/install-skill-routing.test.ts +25 -0
  82. package/src/__tests__/llm-resolver.test.ts +74 -0
  83. package/src/__tests__/llm-schema-fallback-profile.test.ts +359 -0
  84. package/src/__tests__/managed-fallback-dispatch.test.ts +430 -0
  85. package/src/__tests__/managed-profile-guard.test.ts +28 -0
  86. package/src/__tests__/managed-speech-defaults.test.ts +196 -1
  87. package/src/__tests__/mcp-cli.test.ts +23 -1
  88. package/src/__tests__/mcp-tool-annotations-risk.test.ts +43 -34
  89. package/src/__tests__/media-stream-stt-session.test.ts +2 -0
  90. package/src/__tests__/notification-decision-fallback.test.ts +106 -0
  91. package/src/__tests__/notification-discord-adapter.test.ts +162 -0
  92. package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -8
  93. package/src/__tests__/persist-unsendable-image.test.ts +3 -3
  94. package/src/__tests__/platform.test.ts +19 -1
  95. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  96. package/src/__tests__/provider-usage-tracking.test.ts +56 -0
  97. package/src/__tests__/require-fresh-approval.test.ts +5 -2
  98. package/src/__tests__/retry-fallback-escalation.test.ts +1093 -0
  99. package/src/__tests__/schedule-retry.test.ts +183 -4
  100. package/src/__tests__/scheduler-reuse-conversation.test.ts +6 -6
  101. package/src/__tests__/secret-routes-platform-proxy.test.ts +55 -0
  102. package/src/__tests__/skill-load-tool.test.ts +34 -0
  103. package/src/__tests__/skill-tool-factory.test.ts +6 -1
  104. package/src/__tests__/skill-tool-manifest.test.ts +20 -0
  105. package/src/__tests__/skills.test.ts +25 -0
  106. package/src/__tests__/subagent-manager-notify.test.ts +38 -0
  107. package/src/__tests__/terminal-tools.test.ts +2 -0
  108. package/src/__tests__/tool-approval-handler.test.ts +146 -2
  109. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +108 -2
  110. package/src/__tests__/tool-audit.test.ts +111 -40
  111. package/src/__tests__/tool-executor-lifecycle-events.test.ts +228 -18
  112. package/src/__tests__/ui-shape-teaching.test.ts +1 -1
  113. package/src/__tests__/verification-control-plane-policy.test.ts +6 -2
  114. package/src/__tests__/voice-config-update.test.ts +43 -0
  115. package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +480 -0
  116. package/src/__tests__/workspace-migration-148-strip-unsupported-fallback-profiles.test.ts +167 -0
  117. package/src/__tests__/workspace-migration-149-repoint-backup-profile-selections.test.ts +144 -0
  118. package/src/agent/loop.ts +31 -2
  119. package/src/approvals/guardian-card-withdrawal.ts +22 -0
  120. package/src/avatar/__tests__/ensure-raster.test.ts +291 -0
  121. package/src/avatar/avatar-manifest.ts +48 -100
  122. package/src/avatar/avatar-store.ts +7 -7
  123. package/src/avatar/ensure-raster.ts +146 -0
  124. package/src/avatar/resvg-lazy.test.ts +0 -1
  125. package/src/avatar/traits-png-sync.ts +9 -10
  126. package/src/background-wake/wake-intent-hooks.test.ts +0 -1
  127. package/src/backup/__tests__/paths.test.ts +6 -2
  128. package/src/backup/paths.ts +6 -4
  129. package/src/bundler/app-compiler.ts +2 -2
  130. package/src/bundler/compiler-tools.test.ts +36 -0
  131. package/src/bundler/compiler-tools.ts +54 -18
  132. package/src/calls/__tests__/telephony-synthesis-language.test.ts +81 -0
  133. package/src/calls/__tests__/voice-session-bridge.test.ts +20 -0
  134. package/src/calls/media-stream-stt-session.ts +2 -1
  135. package/src/calls/telephony-synthesis-language.ts +13 -5
  136. package/src/calls/voice-session-bridge.ts +6 -4
  137. package/src/channels/config.ts +23 -12
  138. package/src/cli/AGENTS.md +3 -1
  139. package/src/cli/commands/backup.help.ts +10 -13
  140. package/src/cli/commands/contacts.help.ts +16 -4
  141. package/src/cli/commands/contacts.ts +5 -1
  142. package/src/cli/commands/credentials.help.ts +1 -1
  143. package/src/cli/commands/inference-providers.ts +18 -53
  144. package/src/cli/commands/mcp.help.ts +6 -5
  145. package/src/cli/commands/mcp.ts +1 -1
  146. package/src/cli/commands/memory/memory-retrospective.ts +1 -0
  147. package/src/cli/commands/stt.help.ts +2 -2
  148. package/src/config/__tests__/backup-schema.test.ts +1 -1
  149. package/src/config/__tests__/default-profile-catalog.test.ts +83 -2
  150. package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +1 -1
  151. package/src/config/__tests__/memory-retrospective-schema.test.ts +12 -15
  152. package/src/config/bundled-skills/computer-use/SKILL.md +6 -6
  153. package/src/config/bundled-skills/computer-use/TOOLS.json +9 -6
  154. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +3 -1
  155. package/src/config/bundled-skills/media-processing/services/preprocess.ts +1 -1
  156. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  157. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +16 -15
  158. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -1
  159. package/src/config/bundled-skills/settings/TOOLS.json +11 -5
  160. package/src/config/bundled-skills/settings/tools/open-system-settings.test.ts +85 -0
  161. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +33 -8
  162. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +54 -3
  163. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +1 -1
  164. package/src/config/default-profile-catalog.ts +195 -10
  165. package/src/config/default-profile-names.ts +41 -0
  166. package/src/config/feature-flag-registry.json +18 -2
  167. package/src/config/inference-profile-validation.ts +11 -2
  168. package/src/config/llm-resolver.ts +25 -0
  169. package/src/config/loader.ts +3 -0
  170. package/src/config/managed-speech-defaults.ts +160 -10
  171. package/src/config/schema.ts +2 -0
  172. package/src/config/schemas/__tests__/live-voice.test.ts +7 -6
  173. package/src/config/schemas/__tests__/stt.test.ts +94 -0
  174. package/src/config/schemas/backup.ts +1 -1
  175. package/src/config/schemas/channels.ts +32 -0
  176. package/src/config/schemas/live-voice.ts +13 -4
  177. package/src/config/schemas/llm.ts +259 -11
  178. package/src/config/schemas/mcp.ts +10 -2
  179. package/src/config/schemas/memory-retrospective.ts +1 -19
  180. package/src/config/schemas/services.ts +24 -0
  181. package/src/config/schemas/stt.ts +128 -5
  182. package/src/config/seed-inference-profiles.ts +32 -3
  183. package/src/config/skill-state.ts +5 -0
  184. package/src/config/skills.ts +21 -0
  185. package/src/contacts/notify-contacts-changed.ts +5 -2
  186. package/src/context/compactor.ts +74 -1
  187. package/src/daemon/__tests__/conversation-tool-setup.test.ts +138 -1
  188. package/src/daemon/config-watcher.ts +2 -1
  189. package/src/daemon/conversation-agent-loop-handlers.ts +27 -0
  190. package/src/daemon/conversation-agent-loop.ts +86 -10
  191. package/src/daemon/conversation-error.ts +25 -0
  192. package/src/daemon/conversation-evictor.ts +2 -4
  193. package/src/daemon/conversation-runtime-assembly.ts +19 -7
  194. package/src/daemon/conversation-skill-tools.ts +5 -1
  195. package/src/daemon/conversation-slash.ts +2 -2
  196. package/src/daemon/conversation-store.ts +8 -1
  197. package/src/daemon/conversation-tool-setup.ts +37 -0
  198. package/src/daemon/conversation.ts +0 -4
  199. package/src/daemon/handlers/channel-config-result.ts +30 -0
  200. package/src/daemon/handlers/config-discord-channel.ts +301 -0
  201. package/src/daemon/handlers/config-ingress.ts +9 -0
  202. package/src/daemon/handlers/config-slack-channel.ts +13 -15
  203. package/src/daemon/handlers/config-telegram.ts +2 -5
  204. package/src/daemon/handlers/conversations.ts +0 -2
  205. package/src/daemon/handlers/skills.ts +25 -7
  206. package/src/daemon/lifecycle.ts +3 -1
  207. package/src/daemon/message-types/messages.ts +0 -2
  208. package/src/daemon/tool-setup-types.ts +5 -3
  209. package/src/daemon/trust-context-types.test.ts +33 -0
  210. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  211. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +58 -4
  212. package/src/live-voice/__tests__/live-voice-managed-speech-fallback.test.ts +8 -3
  213. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +73 -11
  214. package/src/live-voice/__tests__/live-voice-session-preflight.test.ts +148 -2
  215. package/src/live-voice/__tests__/live-voice-stt.test.ts +3 -0
  216. package/src/live-voice/__tests__/live-voice-text-turn.test.ts +391 -0
  217. package/src/live-voice/__tests__/protocol.test.ts +224 -0
  218. package/src/live-voice/live-voice-credential-preflight.ts +5 -1
  219. package/src/live-voice/live-voice-session-manager.ts +39 -2
  220. package/src/live-voice/live-voice-session.ts +213 -8
  221. package/src/live-voice/protocol.ts +187 -1
  222. package/src/messaging/providers/discord/api.ts +2 -1
  223. package/src/messaging/providers/discord/withdraw.ts +62 -0
  224. package/src/monitoring/__tests__/file-descriptors.test.ts +32 -0
  225. package/src/monitoring/__tests__/process-memory.test.ts +34 -1
  226. package/src/monitoring/db-integrity-sample.ts +1 -0
  227. package/src/monitoring/file-descriptors.ts +39 -6
  228. package/src/monitoring/process-memory.ts +37 -5
  229. package/src/monitoring/resource-sampler.ts +25 -8
  230. package/src/notifications/__tests__/activity-failed-dedupe.test.ts +161 -0
  231. package/src/notifications/__tests__/background-failure-signal.test.ts +107 -0
  232. package/src/notifications/__tests__/connected-channels.test.ts +12 -0
  233. package/src/notifications/__tests__/copy-composer.test.ts +83 -0
  234. package/src/notifications/__tests__/destination-resolver.test.ts +38 -0
  235. package/src/notifications/activity-failed-dedupe.ts +95 -0
  236. package/src/notifications/adapters/discord.ts +132 -0
  237. package/src/notifications/adapters/shared.ts +16 -0
  238. package/src/notifications/adapters/slack.ts +6 -1
  239. package/src/notifications/adapters/telegram.ts +5 -8
  240. package/src/notifications/approval-card-data.ts +94 -1
  241. package/src/notifications/background-failure-signal.ts +116 -0
  242. package/src/notifications/broadcaster.ts +3 -9
  243. package/src/notifications/copy-composer.ts +41 -8
  244. package/src/notifications/decision-engine.ts +55 -0
  245. package/src/notifications/destination-resolver.ts +45 -5
  246. package/src/notifications/emit-signal.ts +19 -5
  247. package/src/notifications/guardian-question-mode.ts +57 -0
  248. package/src/notifications/types.ts +2 -8
  249. package/src/oauth/__tests__/seed-providers-managed.test.ts +18 -0
  250. package/src/oauth/seed-providers.ts +261 -0
  251. package/src/permissions/checker.test.ts +21 -0
  252. package/src/permissions/checker.ts +6 -0
  253. package/src/permissions/gateway-threshold-reader.test.ts +184 -0
  254. package/src/permissions/gateway-threshold-reader.ts +144 -10
  255. package/src/permissions/prompter.ts +0 -6
  256. package/src/permissions/types.ts +5 -4
  257. package/src/persistence/__tests__/bulk-write-gate.test.ts +0 -38
  258. package/src/persistence/bulk-write-gate.ts +9 -10
  259. package/src/persistence/conversation-crud.ts +85 -133
  260. package/src/persistence/conversation-title-service.ts +43 -29
  261. package/src/persistence/embeddings/embedding-local.ts +14 -85
  262. package/src/persistence/embeddings/embedding-runtime-manager.ts +1 -0
  263. package/src/persistence/lifecycle-events-store.test.ts +50 -0
  264. package/src/persistence/lifecycle-events-store.ts +75 -5
  265. package/src/platform/platform-patch-queue.test.ts +368 -0
  266. package/src/platform/platform-patch-queue.ts +190 -0
  267. package/src/platform/sync-avatar.test.ts +527 -0
  268. package/src/platform/sync-avatar.ts +209 -0
  269. package/src/platform/sync-identity.test.ts +75 -0
  270. package/src/platform/sync-identity.ts +17 -110
  271. package/src/plugin-api/model-profiles.test.ts +2 -2
  272. package/src/plugin-api/model-profiles.ts +7 -7
  273. package/src/plugin-api/transcription-session.ts +1 -1
  274. package/src/plugins/defaults/compaction/window-manager.ts +4 -0
  275. package/src/plugins/defaults/image-recovery/recover.ts +2 -2
  276. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +133 -34
  277. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +21 -31
  278. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +13 -9
  279. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +31 -4
  280. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +0 -11
  281. package/src/plugins/defaults/memory/memory-retrospective-fork-boundary.ts +27 -13
  282. package/src/plugins/defaults/memory/memory-retrospective-job.ts +183 -113
  283. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +13 -13
  284. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +2 -2
  285. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +1 -1
  286. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +4 -2
  287. package/src/plugins/defaults/memory/substrate/sweep-job.ts +9 -42
  288. package/src/plugins/defaults/memory/v2/rerank-local.ts +1 -0
  289. package/src/plugins/defaults/memory/worker.ts +7 -0
  290. package/src/providers/__tests__/retry-callsite.test.ts +55 -0
  291. package/src/providers/connection-resolution.ts +2 -17
  292. package/src/providers/fallback-breaker.ts +389 -0
  293. package/src/providers/inference/__tests__/adapter-factory-opencode.test.ts +92 -0
  294. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +16 -0
  295. package/src/providers/inference/__tests__/base-url-security.test.ts +12 -0
  296. package/src/providers/inference/adapter-factory.ts +231 -3
  297. package/src/providers/inference/auth.ts +1 -0
  298. package/src/providers/inference/chatgpt-subscription-auth.ts +70 -0
  299. package/src/providers/inference/chatgpt-subscription-credentials.ts +84 -0
  300. package/src/providers/inference/resolve-auth.ts +1 -1
  301. package/src/providers/media-resolve.ts +2 -3
  302. package/src/providers/model-catalog.ts +75 -0
  303. package/src/providers/openai/__tests__/api-error-normalization.test.ts +22 -0
  304. package/src/providers/openai/__tests__/chat-template-flatten.test.ts +198 -0
  305. package/src/providers/openai/api-error-normalization.ts +13 -0
  306. package/src/providers/openai/chat-completions-provider.ts +84 -0
  307. package/src/providers/opencode/client.test.ts +150 -0
  308. package/src/providers/opencode/client.ts +61 -0
  309. package/src/providers/provider-resolvability.ts +20 -0
  310. package/src/providers/retry.ts +956 -68
  311. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +42 -0
  312. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +136 -0
  313. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +120 -0
  314. package/src/providers/speech-to-text/__tests__/resolve.test.ts +316 -12
  315. package/src/providers/speech-to-text/deepgram-flux-frames.ts +83 -3
  316. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +132 -4
  317. package/src/providers/speech-to-text/provider-catalog.ts +191 -3
  318. package/src/providers/speech-to-text/resolve.ts +132 -24
  319. package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +296 -0
  320. package/src/providers/types.ts +39 -0
  321. package/src/providers/usage-tracking.ts +12 -1
  322. package/src/runtime/AGENTS.md +1 -2
  323. package/src/runtime/__tests__/agent-wake.test.ts +105 -3
  324. package/src/runtime/__tests__/background-job-runner.test.ts +89 -3
  325. package/src/runtime/agent-wake.ts +44 -5
  326. package/src/runtime/background-job-runner.ts +68 -50
  327. package/src/runtime/channel-readiness-service.ts +0 -39
  328. package/src/runtime/http-server.ts +12 -4
  329. package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +18 -18
  330. package/src/runtime/routes/__tests__/backup-routes.test.ts +9 -5
  331. package/src/runtime/routes/__tests__/chatgpt-subscription-auth-routes.test.ts +372 -0
  332. package/src/runtime/routes/__tests__/contact-routes.test.ts +7 -0
  333. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +96 -0
  334. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +37 -0
  335. package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +2 -1
  336. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +87 -0
  337. package/src/runtime/routes/acp-claude-auth-routes.ts +25 -54
  338. package/src/runtime/routes/approval-routes.ts +0 -12
  339. package/src/runtime/routes/avatar-routes.ts +17 -30
  340. package/src/runtime/routes/backup-routes.ts +2 -2
  341. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +296 -113
  342. package/src/runtime/routes/contact-prompt-routes.ts +65 -6
  343. package/src/runtime/routes/contact-routes.ts +20 -15
  344. package/src/runtime/routes/conversation-query-routes.ts +98 -10
  345. package/src/runtime/routes/conversation-routes.ts +2 -2
  346. package/src/runtime/routes/credential-routes.ts +2 -1
  347. package/src/runtime/routes/guardian-approval-interception.ts +12 -9
  348. package/src/runtime/routes/inbound-message-handler.ts +21 -9
  349. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +18 -13
  350. package/src/runtime/routes/inbound-stages/admission-policy.ts +2 -2
  351. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +9 -8
  352. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -1
  353. package/src/runtime/routes/index.ts +2 -0
  354. package/src/runtime/routes/inference-profile-session-handler.ts +2 -2
  355. package/src/runtime/routes/inference-profiles-routes.ts +12 -7
  356. package/src/runtime/routes/inference-provider-connection-routes.ts +2 -2
  357. package/src/runtime/routes/inference-send-routes.ts +2 -2
  358. package/src/runtime/routes/ingress-status-routes.ts +2 -2
  359. package/src/runtime/routes/integrations/discord.ts +119 -0
  360. package/src/runtime/routes/llm-call-sites-routes.ts +2 -2
  361. package/src/runtime/routes/mcp-auth-routes.ts +12 -6
  362. package/src/runtime/routes/oauth-pending-flows.ts +136 -0
  363. package/src/runtime/routes/secret-routes.ts +7 -0
  364. package/src/runtime/routes/stt-routes.ts +38 -6
  365. package/src/runtime/routes/wake-conversation-routes.ts +8 -1
  366. package/src/runtime/routes/watch-routes.ts +9 -5
  367. package/src/runtime/sync/resource-sync-events.ts +2 -0
  368. package/src/runtime/trust-verdict-consumer.ts +3 -3
  369. package/src/schedule/__tests__/worker-feature-flags.test.ts +58 -0
  370. package/src/schedule/retry-policy.ts +8 -8
  371. package/src/schedule/run-script.ts +14 -23
  372. package/src/schedule/schedule-recovery.ts +0 -1
  373. package/src/schedule/scheduler.ts +89 -51
  374. package/src/schedule/worker.ts +57 -0
  375. package/src/security/__tests__/openai-device-auth.test.ts +355 -0
  376. package/src/security/oauth2.ts +7 -0
  377. package/src/security/openai-device-auth.ts +364 -0
  378. package/src/skills/available-skills.ts +10 -1
  379. package/src/skills/catalog-install.ts +19 -1
  380. package/src/skills/inline-command-runner.ts +6 -2
  381. package/src/skills/platform-compatibility.test.ts +49 -0
  382. package/src/skills/platform-compatibility.ts +65 -0
  383. package/src/skills/tool-manifest.ts +20 -0
  384. package/src/stt/__tests__/roles.test.ts +152 -0
  385. package/src/stt/daemon-batch-transcriber.ts +4 -1
  386. package/src/stt/roles.ts +180 -0
  387. package/src/stt/stt-stream-session.ts +3 -2
  388. package/src/stt/types.ts +33 -3
  389. package/src/subagent/manager.ts +12 -0
  390. package/src/telemetry/__tests__/live-voice-funnel.test.ts +34 -0
  391. package/src/telemetry/live-voice-funnel.ts +12 -2
  392. package/src/telemetry/telemetry-wire.generated.ts +6 -0
  393. package/src/telemetry/tool-audit.ts +83 -30
  394. package/src/telemetry/tool-executed-events-store.test.ts +0 -1
  395. package/src/telemetry/turn-outcome.ts +75 -4
  396. package/src/tools/client-os.ts +115 -0
  397. package/src/tools/computer-use/definitions.ts +11 -7
  398. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -2
  399. package/src/tools/credentials/broker-types.ts +0 -71
  400. package/src/tools/credentials/broker.ts +6 -201
  401. package/src/tools/executor.ts +2 -8
  402. package/src/tools/host-shell.test.ts +75 -0
  403. package/src/tools/host-shell.ts +24 -0
  404. package/src/tools/host-terminal/host-shell.ts +15 -44
  405. package/src/tools/mcp/mcp-tool-factory.ts +44 -9
  406. package/src/tools/permission-checker.ts +73 -21
  407. package/src/tools/policy-context.ts +1 -0
  408. package/src/tools/shared/filesystem/image-read.ts +1 -1
  409. package/src/tools/skills/find-similar-skills.ts +10 -2
  410. package/src/tools/skills/load.ts +41 -6
  411. package/src/tools/skills/sandbox-runner.ts +8 -16
  412. package/src/tools/skills/skill-tool-factory.ts +1 -0
  413. package/src/tools/terminal/__tests__/safe-env.test.ts +64 -1
  414. package/src/tools/terminal/safe-env.ts +39 -9
  415. package/src/tools/terminal/shell.ts +12 -31
  416. package/src/tools/tool-approval-handler.ts +64 -10
  417. package/src/tools/tool-types.ts +10 -0
  418. package/src/tools/types.ts +19 -3
  419. package/src/tools/ui-surface/surface-shape-docs.ts +4 -3
  420. package/src/tools/workflows/manage-workflows.ts +2 -2
  421. package/src/tools/workflows/run-workflow.test.ts +2 -1
  422. package/src/util/__tests__/file-use.test.ts +61 -0
  423. package/src/util/__tests__/process-table.test.ts +66 -0
  424. package/src/util/__tests__/process-tree.test.ts +46 -0
  425. package/src/util/errors.ts +1 -0
  426. package/src/util/file-use.ts +91 -0
  427. package/src/util/host-process.test.ts +17 -0
  428. package/src/util/host-process.ts +75 -0
  429. package/src/util/image-conversion.ts +39 -7
  430. package/src/util/platform.ts +32 -13
  431. package/src/util/process-table.ts +249 -0
  432. package/src/util/process-tree.ts +85 -129
  433. package/src/util/provider-error-patterns.ts +20 -0
  434. package/src/util/spawn.ts +9 -2
  435. package/src/workspace/git-service.ts +4 -14
  436. package/src/workspace/migrations/147-rename-colliding-backup-profile-names.ts +360 -0
  437. package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +117 -0
  438. package/src/workspace/migrations/149-repoint-backup-profile-selections.ts +204 -0
  439. package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +139 -0
  440. package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +160 -0
  441. package/src/workspace/migrations/registry.ts +8 -0
  442. package/src/__tests__/permission-types.test.ts +0 -17
  443. package/src/channels/gateway-discord-admission.ts +0 -32
  444. package/src/persistence/fork-message-copy.ts +0 -239
  445. package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +0 -221
  446. package/src/security/__tests__/oauth2-device-code.test.ts +0 -431
  447. package/src/security/oauth2-device-code.ts +0 -312
@@ -10,8 +10,7 @@
10
10
  import { ApprovalUIMetadataSchema } from "@vellumai/gateway-client";
11
11
  import { z } from "zod";
12
12
 
13
- import type { ChannelPolicies } from "../channels/config.js";
14
- import type { ChannelId } from "../channels/types.js";
13
+ import type { DeliverableChannelId } from "../channels/config.js";
15
14
  import { AccessRequestPayloadSchema } from "./access-request-copy.js";
16
15
  import { ToolApprovalSourceViewSchema } from "./guardian-question-mode.js";
17
16
  import { UrgencySchema } from "./urgency.js";
@@ -20,12 +19,7 @@ import { UrgencySchema } from "./urgency.js";
20
19
  * Derived from the channel policy registry: only channels whose
21
20
  * deliveryEnabled flag is true are valid notification channels.
22
21
  */
23
- export type NotificationChannel = {
24
- [K in keyof ChannelPolicies]: ChannelPolicies[K]["notification"]["deliveryEnabled"] extends true
25
- ? K
26
- : never;
27
- }[keyof ChannelPolicies] &
28
- ChannelId;
22
+ export type NotificationChannel = DeliverableChannelId;
29
23
 
30
24
  export const NotificationDeliveryStatusSchema = z.enum([
31
25
  "pending",
@@ -11,6 +11,24 @@ describe("PROVIDER_SEED_DATA managed mode wiring", () => {
11
11
  expect("github-oauth" in ServicesSchema.shape).toBe(true);
12
12
  });
13
13
 
14
+ test("monday only exposes scopes supported by its OAuth API", () => {
15
+ const monday = PROVIDER_SEED_DATA.monday;
16
+ expect(monday).toBeDefined();
17
+ expect(monday.defaultScopes).not.toContain("items:read");
18
+ expect(monday.defaultScopes).not.toContain("items:write");
19
+
20
+ const availableScopes = monday.availableScopes;
21
+ expect(Array.isArray(availableScopes)).toBe(true);
22
+ if (Array.isArray(availableScopes)) {
23
+ expect(availableScopes.map(({ scope }) => scope)).not.toContain(
24
+ "items:read",
25
+ );
26
+ expect(availableScopes.map(({ scope }) => scope)).not.toContain(
27
+ "items:write",
28
+ );
29
+ }
30
+ });
31
+
14
32
  test("google base URL is host-only so relative paths select the product", () => {
15
33
  // A host-only base URL (no product path) lets a relative request path pick
16
34
  // the Google product — Gmail (/gmail/v1/...), Calendar (/calendar/v3/...),
@@ -684,6 +684,267 @@ export const PROVIDER_SEED_DATA: Record<
684
684
  identityResponsePaths: ["email", "preferred_username"],
685
685
  },
686
686
 
687
+ monday: {
688
+ provider: "monday",
689
+ authorizeUrl: "https://auth.monday.com/oauth2/authorize",
690
+ tokenExchangeUrl: "https://auth.monday.com/oauth2/token",
691
+ pingUrl: "https://api.monday.com/v2",
692
+ pingMethod: "POST",
693
+ pingHeaders: { "Content-Type": "application/json" },
694
+ pingBody: { query: "{ me { id name email } }" },
695
+ baseUrl: "https://api.monday.com",
696
+ displayLabel: "monday.com",
697
+ description: "Boards, items, docs, and updates",
698
+ dashboardUrl: "https://auth.monday.com/apps",
699
+ clientIdPlaceholder: null,
700
+ // Simple Icons does not host a monday.com mark (both `monday` and
701
+ // `mondaydotcom` 404 on the CDN), so use the documented thesvg fallback.
702
+ logoUrl:
703
+ "https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/monday/default.svg",
704
+ // monday tokens do not expire under the legacy OAuth flow and no refresh
705
+ // token is issued, so there is no refreshUrl. The newer OAuth 2.1 flow
706
+ // adds expiry/refresh/revocation but is opt-in per app version in the
707
+ // Developer Center and posts JSON (not form-encoded) credentials, so it
708
+ // is deliberately not used here.
709
+ defaultScopes: [
710
+ "me:read",
711
+ "account:read",
712
+ "users:read",
713
+ "teams:read",
714
+ "workspaces:read",
715
+ "boards:read",
716
+ "boards:write",
717
+ "docs:read",
718
+ "updates:read",
719
+ "updates:write",
720
+ "assets:read",
721
+ "tags:read",
722
+ "notifications:write",
723
+ ],
724
+ availableScopes: [
725
+ { scope: "me:read", description: "Read the user's profile information" },
726
+ {
727
+ scope: "account:read",
728
+ description: "Read general information about the account",
729
+ },
730
+ {
731
+ scope: "users:read",
732
+ description: "Read profile information of the account's users",
733
+ },
734
+ {
735
+ scope: "users:write",
736
+ description: "Modify profile information of the account's users",
737
+ },
738
+ {
739
+ scope: "teams:read",
740
+ description: "Read information about the account's teams",
741
+ },
742
+ { scope: "teams:write", description: "Modify the account's teams" },
743
+ {
744
+ scope: "workspaces:read",
745
+ description: "Read a user's workspaces data",
746
+ },
747
+ {
748
+ scope: "workspaces:write",
749
+ description: "Modify a user's workspaces data",
750
+ },
751
+ { scope: "boards:read", description: "Read a user's board data" },
752
+ { scope: "boards:write", description: "Modify a user's board data" },
753
+ { scope: "docs:read", description: "Read a user's docs" },
754
+ { scope: "docs:write", description: "Modify a user's docs" },
755
+ {
756
+ scope: "updates:read",
757
+ description: "Read updates and replies the user can see",
758
+ },
759
+ {
760
+ scope: "updates:write",
761
+ description: "Post or edit updates on behalf of the user",
762
+ },
763
+ {
764
+ scope: "assets:read",
765
+ description: "Read data from assets the user has access to",
766
+ },
767
+ { scope: "tags:read", description: "Read the account's tags" },
768
+ {
769
+ scope: "notifications:write",
770
+ description: "Send notifications on behalf of the user",
771
+ },
772
+ {
773
+ scope: "webhooks:read",
774
+ description: "Read existing webhooks configuration",
775
+ },
776
+ {
777
+ scope: "webhooks:write",
778
+ description: "Create and modify webhooks",
779
+ },
780
+ {
781
+ scope: "departments:read",
782
+ description: "Read the account's department data",
783
+ },
784
+ {
785
+ scope: "departments:write",
786
+ description: "Modify the account's departments",
787
+ },
788
+ ],
789
+ tokenEndpointAuthMethod: "client_secret_post",
790
+ // 17337/17338 are taken by eventbrite/calendly (all three providers
791
+ // landed in parallel and independently picked the next free port at the
792
+ // time); 17339 is the real next-free slot once all three are seeded
793
+ // together.
794
+ loopbackPort: 17339,
795
+ managedServiceConfigKey: "monday-oauth",
796
+ injectionTemplates: [
797
+ {
798
+ hostPattern: "api.monday.com",
799
+ injectionType: "header",
800
+ headerName: "Authorization",
801
+ valuePrefix: "Bearer ",
802
+ },
803
+ ],
804
+ appType: "App",
805
+ identityUrl: "https://api.monday.com/v2",
806
+ identityMethod: "POST",
807
+ identityHeaders: { "Content-Type": "application/json" },
808
+ identityBody: { query: "{ me { id name email } }" },
809
+ identityResponsePaths: ["data.me.name", "data.me.email"],
810
+ },
811
+
812
+ eventbrite: {
813
+ provider: "eventbrite",
814
+ authorizeUrl: "https://www.eventbrite.com/oauth/authorize",
815
+ tokenExchangeUrl: "https://www.eventbrite.com/oauth/token",
816
+ refreshUrl: "https://www.eventbrite.com/oauth/token",
817
+ pingUrl: "https://www.eventbriteapi.com/v3/users/me/",
818
+ baseUrl: "https://www.eventbriteapi.com",
819
+ displayLabel: "Eventbrite",
820
+ description: "Events, attendees, and ticket orders",
821
+ dashboardUrl: "https://www.eventbrite.com/platform/api-keys/",
822
+ clientIdPlaceholder: null,
823
+ // Simple Icons does not host an Eventbrite mark (cdn.simpleicons.org
824
+ // 404s), so use the documented thesvg fallback.
825
+ logoUrl:
826
+ "https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/eventbrite/default.svg",
827
+ // Eventbrite has no granular OAuth scope system: an access token carries
828
+ // the full permissions of the user who authorized it, and the authorize
829
+ // endpoint ignores a `scope` parameter. Seed an empty set rather than
830
+ // inventing scope strings the provider would silently drop (same shape
831
+ // as Notion).
832
+ defaultScopes: [],
833
+ tokenEndpointAuthMethod: "client_secret_post",
834
+ loopbackPort: 17337,
835
+ managedServiceConfigKey: "eventbrite-oauth",
836
+ injectionTemplates: [
837
+ {
838
+ hostPattern: "www.eventbriteapi.com",
839
+ injectionType: "header",
840
+ headerName: "Authorization",
841
+ valuePrefix: "Bearer ",
842
+ },
843
+ ],
844
+ appType: "App",
845
+ identityUrl: "https://www.eventbriteapi.com/v3/users/me/",
846
+ identityResponsePaths: ["name", "first_name"],
847
+ },
848
+
849
+ calendly: {
850
+ provider: "calendly",
851
+ authorizeUrl: "https://auth.calendly.com/oauth/authorize",
852
+ tokenExchangeUrl: "https://auth.calendly.com/oauth/token",
853
+ pingUrl: "https://api.calendly.com/users/me",
854
+ baseUrl: "https://api.calendly.com",
855
+ displayLabel: "Calendly",
856
+ description: "Scheduling links and meetings",
857
+ dashboardUrl: "https://calendly.com/integrations/api_webhooks",
858
+ clientIdPlaceholder: null,
859
+ logoUrl: "https://cdn.simpleicons.org/calendly",
860
+ // A :write scope implicitly grants the matching :read in the same domain,
861
+ // so the baseline stays at the six that map to reading schedule data and
862
+ // creating bookings/links. Organization-wide visibility is opt-in.
863
+ defaultScopes: [
864
+ "users:read",
865
+ "event_types:read",
866
+ "scheduled_events:read",
867
+ "availability:read",
868
+ "scheduled_events:write",
869
+ "scheduling_links:write",
870
+ ],
871
+ availableScopes: [
872
+ { scope: "users:read", description: "Read the connected user's profile" },
873
+ {
874
+ scope: "event_types:read",
875
+ description: "Read event types and their available times",
876
+ },
877
+ {
878
+ scope: "event_types:write",
879
+ description: "Create and update event types",
880
+ },
881
+ {
882
+ scope: "scheduled_events:read",
883
+ description: "Read scheduled events and invitees",
884
+ },
885
+ {
886
+ scope: "scheduled_events:write",
887
+ description: "Create invitees, cancel events, mark no-shows",
888
+ },
889
+ {
890
+ scope: "availability:read",
891
+ description: "Read busy times and availability schedules",
892
+ },
893
+ {
894
+ scope: "availability:write",
895
+ description: "Update event type availability",
896
+ },
897
+ {
898
+ scope: "scheduling_links:write",
899
+ description: "Create single-use scheduling links",
900
+ },
901
+ {
902
+ scope: "shares:write",
903
+ description: "Create customized single-use scheduling links",
904
+ },
905
+ {
906
+ scope: "organizations:read",
907
+ description: "Read organization data and memberships",
908
+ },
909
+ {
910
+ scope: "groups:read",
911
+ description: "Read group details and relationships",
912
+ },
913
+ {
914
+ scope: "webhooks:read",
915
+ description: "Read webhook subscriptions",
916
+ },
917
+ {
918
+ scope: "webhooks:write",
919
+ description: "Create and delete webhook subscriptions",
920
+ },
921
+ { scope: "contacts:read", description: "Read contact details" },
922
+ {
923
+ scope: "contacts:write",
924
+ description: "Create, update, and delete contacts",
925
+ },
926
+ ],
927
+ tokenEndpointAuthMethod: "client_secret_basic",
928
+ // 17337 is taken by eventbrite (both providers landed in parallel and
929
+ // independently picked the next free port at the time); 17338 is the
930
+ // real next-free slot once both are seeded together.
931
+ loopbackPort: 17338,
932
+ managedServiceConfigKey: "calendly-oauth",
933
+ injectionTemplates: [
934
+ {
935
+ hostPattern: "api.calendly.com",
936
+ injectionType: "header",
937
+ headerName: "Authorization",
938
+ valuePrefix: "Bearer ",
939
+ },
940
+ ],
941
+ revokeUrl: "https://auth.calendly.com/oauth/revoke",
942
+ revokeBodyTemplate: { token: "{access_token}" },
943
+ appType: "App",
944
+ identityUrl: "https://api.calendly.com/users/me",
945
+ identityResponsePaths: ["resource.email", "resource.name"],
946
+ },
947
+
687
948
  figma: {
688
949
  provider: "figma",
689
950
  authorizeUrl: "https://www.figma.com/oauth",
@@ -547,6 +547,27 @@ describe("Permission Checker (gateway IPC)", () => {
547
547
  expect(result.level).toBe(RiskLevel.Medium);
548
548
  });
549
549
 
550
+ test("builds PowerShell params for Windows host_bash", async () => {
551
+ mockIpcClassifyRiskResult = {
552
+ risk: "high",
553
+ reason: "PowerShell mutation",
554
+ matchType: "registry",
555
+ scopeOptions: [],
556
+ };
557
+
558
+ await classifyRisk(
559
+ "host_bash",
560
+ { command: "Remove-Item C:\\Temp\\data" },
561
+ "C:\\Users\\Example",
562
+ undefined,
563
+ undefined,
564
+ undefined,
565
+ "powershell",
566
+ );
567
+
568
+ expect(lastClassifyRiskParams?.shell).toBe("powershell");
569
+ });
570
+
550
571
  test("builds params for file tools", async () => {
551
572
  mockIpcClassifyRiskResult = {
552
573
  risk: "low",
@@ -420,6 +420,7 @@ function buildClassifyRiskParams(
420
420
  input: Record<string, unknown>,
421
421
  workingDir?: string,
422
422
  manifestOverride?: ManifestOverride,
423
+ shell?: ClassifyRiskIpcParams["shell"],
423
424
  ): ClassifyRiskIpcParams {
424
425
  // ── Bash/host_bash ──
425
426
  if (toolName === "bash" || toolName === "host_bash") {
@@ -437,6 +438,7 @@ function buildClassifyRiskParams(
437
438
  return {
438
439
  tool: toolName,
439
440
  command: getStringField(input, "command"),
441
+ shell,
440
442
  workingDir,
441
443
  workspaceRoot: getWorkspaceDir(),
442
444
  isContainerized: getIsContainerized(),
@@ -577,6 +579,7 @@ export async function classifyRisk(
577
579
  _preParsed?: unknown,
578
580
  manifestOverride?: ManifestOverride,
579
581
  signal?: AbortSignal,
582
+ shell?: ClassifyRiskIpcParams["shell"],
580
583
  ): Promise<RiskClassificationWithMeta> {
581
584
  signal?.throwIfAborted();
582
585
 
@@ -585,6 +588,7 @@ export async function classifyRisk(
585
588
  input,
586
589
  workingDir,
587
590
  manifestOverride,
591
+ shell,
588
592
  );
589
593
  const gatewayResult = await ipcClassifyRisk(ipcParams, signal);
590
594
  // A mid-retry cancellation should surface as an AbortError, not the
@@ -709,6 +713,7 @@ export async function check(
709
713
  policyContext?.conversationId,
710
714
  policyContext?.executionContext,
711
715
  cellQuery,
716
+ policyContext?.requesterContactId,
712
717
  );
713
718
  const approvalContext: ApprovalContext = {
714
719
  riskLevel: risk,
@@ -740,6 +745,7 @@ export async function check(
740
745
  policyContext?.conversationId,
741
746
  policyContext?.executionContext,
742
747
  cellQuery,
748
+ policyContext?.requesterContactId,
743
749
  );
744
750
  if (freshThreshold !== null && freshThreshold !== threshold) {
745
751
  approvalDecision = defaultApprovalPolicy.evaluate({
@@ -37,6 +37,9 @@ import {
37
37
  _clearGlobalCacheForTesting,
38
38
  _resetFailureCoalesceForTesting,
39
39
  getAutoApproveThreshold,
40
+ getContactAutoApproveThreshold,
41
+ invalidateAllContactThresholdCaches,
42
+ invalidateContactThresholdCache,
40
43
  refreshAutoApproveThreshold,
41
44
  } from "./gateway-threshold-reader.js";
42
45
 
@@ -633,3 +636,184 @@ describe("contact cell layer failure semantics", () => {
633
636
  ).toBe("low");
634
637
  });
635
638
  });
639
+
640
+ describe("contact-level ceiling", () => {
641
+ beforeEach(() => {
642
+ _clearGlobalCacheForTesting();
643
+ _resetFailureCoalesceForTesting();
644
+ ipcHandlers.clear();
645
+ ipcCallLog.length = 0;
646
+ });
647
+
648
+ test("the conversation override beats the contact ceiling", async () => {
649
+ ipcHandlers.set("get_conversation_threshold", () => ({
650
+ threshold: "none",
651
+ }));
652
+ ipcHandlers.set("get_contact_threshold", () => ({ threshold: "high" }));
653
+ ipcHandlers.set("resolve_channel_permission_threshold", () => ({
654
+ resolved: { threshold: "low", scope: "channel" },
655
+ }));
656
+ setGlobals("low");
657
+
658
+ expect(
659
+ await getAutoApproveThreshold(
660
+ "conv-ct1",
661
+ "conversation",
662
+ CELL_QUERY,
663
+ "contact-1",
664
+ ),
665
+ ).toBe("none");
666
+ expect(countCalls("get_contact_threshold")).toBe(0);
667
+ expect(countCalls("resolve_channel_permission_threshold")).toBe(0);
668
+ });
669
+
670
+ test("the contact ceiling beats the cell", async () => {
671
+ ipcHandlers.set("get_conversation_threshold", () => null);
672
+ ipcHandlers.set("get_contact_threshold", () => ({ threshold: "high" }));
673
+ ipcHandlers.set("resolve_channel_permission_threshold", () => ({
674
+ resolved: { threshold: "none", scope: "channel" },
675
+ }));
676
+ setGlobals("low");
677
+
678
+ expect(
679
+ await getAutoApproveThreshold(
680
+ "conv-ct2",
681
+ "conversation",
682
+ CELL_QUERY,
683
+ "contact-1",
684
+ ),
685
+ ).toBe("high");
686
+ expect(countCalls("resolve_channel_permission_threshold")).toBe(0);
687
+ });
688
+
689
+ test("the contact ceiling is not collapsed", async () => {
690
+ ipcHandlers.set("get_conversation_threshold", () => null);
691
+ ipcHandlers.set("get_contact_threshold", () => ({ threshold: "medium" }));
692
+ ipcHandlers.set("resolve_channel_permission_threshold", () => ({
693
+ resolved: { threshold: "none", scope: "channel" },
694
+ }));
695
+ setGlobals("low");
696
+
697
+ expect(
698
+ await getAutoApproveThreshold(
699
+ "conv-ct3",
700
+ "conversation",
701
+ CELL_QUERY,
702
+ "contact-1",
703
+ ),
704
+ ).toBe("medium");
705
+ });
706
+
707
+ test("a null contact ceiling inherits the cell cascade", async () => {
708
+ ipcHandlers.set("get_conversation_threshold", () => null);
709
+ ipcHandlers.set("get_contact_threshold", () => null);
710
+ ipcHandlers.set("resolve_channel_permission_threshold", () => ({
711
+ resolved: { threshold: "none", scope: "channel" },
712
+ }));
713
+ setGlobals("high");
714
+
715
+ expect(
716
+ await getAutoApproveThreshold(
717
+ "conv-ct4",
718
+ "conversation",
719
+ CELL_QUERY,
720
+ "contact-1",
721
+ ),
722
+ ).toBe("none");
723
+ });
724
+
725
+ test("a contact-threshold transport failure falls through to the cell", async () => {
726
+ ipcHandlers.set("get_conversation_threshold", () => null);
727
+ ipcHandlers.set("resolve_channel_permission_threshold", () => ({
728
+ resolved: { threshold: "none", scope: "channel" },
729
+ }));
730
+ setGlobals("high");
731
+
732
+ expect(
733
+ await getAutoApproveThreshold(
734
+ "conv-ct-fail",
735
+ "conversation",
736
+ CELL_QUERY,
737
+ "contact-1",
738
+ ),
739
+ ).toBe("none");
740
+ });
741
+
742
+ test("refresh honors the contact ceiling after a conversation miss", async () => {
743
+ ipcHandlers.set("get_conversation_threshold", () => null);
744
+ ipcHandlers.set("get_contact_threshold", () => ({ threshold: "high" }));
745
+ ipcHandlers.set("resolve_channel_permission_threshold", () => ({
746
+ resolved: { threshold: "none", scope: "channel" },
747
+ }));
748
+ setGlobals("low");
749
+
750
+ expect(
751
+ await refreshAutoApproveThreshold(
752
+ "conv-ct5",
753
+ "conversation",
754
+ CELL_QUERY,
755
+ "contact-1",
756
+ ),
757
+ ).toBe("high");
758
+ expect(countCalls("resolve_channel_permission_threshold")).toBe(0);
759
+ });
760
+
761
+ test("refresh keeps the prompt when the contact lookup fails", async () => {
762
+ ipcHandlers.set("get_conversation_threshold", () => null);
763
+ ipcHandlers.set("resolve_channel_permission_threshold", () => ({
764
+ resolved: { threshold: "low", scope: "channel" },
765
+ }));
766
+ setGlobals("high");
767
+
768
+ expect(
769
+ await refreshAutoApproveThreshold(
770
+ "conv-ct6",
771
+ "conversation",
772
+ CELL_QUERY,
773
+ "contact-1",
774
+ ),
775
+ ).toBeNull();
776
+ expect(countCalls("resolve_channel_permission_threshold")).toBe(0);
777
+ });
778
+
779
+ test("getContactAutoApproveThreshold returns a live ceiling and caches it", async () => {
780
+ ipcHandlers.set("get_contact_threshold", () => ({ threshold: "high" }));
781
+
782
+ expect(await getContactAutoApproveThreshold("contact-1")).toBe("high");
783
+ expect(await getContactAutoApproveThreshold("contact-1")).toBe("high");
784
+ expect(countCalls("get_contact_threshold")).toBe(1);
785
+ });
786
+
787
+ test("invalidateContactThresholdCache drops a cached ceiling", async () => {
788
+ ipcHandlers.set("get_contact_threshold", () => ({ threshold: "high" }));
789
+ expect(await getContactAutoApproveThreshold("contact-1")).toBe("high");
790
+
791
+ invalidateContactThresholdCache("contact-1");
792
+ ipcHandlers.set("get_contact_threshold", () => ({ threshold: "low" }));
793
+
794
+ expect(await getContactAutoApproveThreshold("contact-1")).toBe("low");
795
+ expect(countCalls("get_contact_threshold")).toBe(2);
796
+ });
797
+
798
+ test("invalidateAllContactThresholdCaches drops every cached ceiling", async () => {
799
+ ipcHandlers.set("get_contact_threshold", () => ({ threshold: "high" }));
800
+ expect(await getContactAutoApproveThreshold("contact-1")).toBe("high");
801
+
802
+ invalidateAllContactThresholdCaches();
803
+ ipcHandlers.set("get_contact_threshold", () => ({ threshold: "none" }));
804
+
805
+ expect(await getContactAutoApproveThreshold("contact-1")).toBe("none");
806
+ expect(countCalls("get_contact_threshold")).toBe(2);
807
+ });
808
+
809
+ test("getContactAutoApproveThreshold returns null for an unset contact", async () => {
810
+ ipcHandlers.set("get_contact_threshold", () => null);
811
+
812
+ expect(await getContactAutoApproveThreshold("contact-1")).toBeNull();
813
+ });
814
+
815
+ test("getContactAutoApproveThreshold returns null when contactId is missing", async () => {
816
+ expect(await getContactAutoApproveThreshold(undefined)).toBeNull();
817
+ expect(countCalls("get_contact_threshold")).toBe(0);
818
+ });
819
+ });