@vellumai/assistant 0.9.0 → 0.10.0-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 (572) hide show
  1. package/ARCHITECTURE.md +18 -34
  2. package/bun.lock +7 -8
  3. package/docs/activation-funnel-telemetry.md +28 -22
  4. package/docs/architecture/security.md +29 -28
  5. package/docs/stt-provider-onboarding.md +3 -5
  6. package/docs/workflows-testing.md +13 -44
  7. package/docs/workflows.md +3 -5
  8. package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
  9. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
  10. package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
  11. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  12. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  13. package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
  14. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
  15. package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
  16. package/openapi.yaml +976 -63
  17. package/package.json +2 -1
  18. package/scripts/sync-llm-catalog.ts +6 -15
  19. package/scripts/sync-web-search-catalog.ts +3 -11
  20. package/src/__tests__/access-request-card-view.test.ts +98 -0
  21. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  22. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
  23. package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
  24. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  25. package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
  26. package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
  27. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  28. package/src/__tests__/app-compiler.test.ts +15 -1
  29. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  30. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  31. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  32. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  33. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  34. package/src/__tests__/btw-routes.test.ts +6 -8
  35. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  36. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  37. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  38. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  39. package/src/__tests__/checker.test.ts +0 -3
  40. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  41. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  43. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  44. package/src/__tests__/config-schema.test.ts +35 -0
  45. package/src/__tests__/config-watcher.test.ts +0 -18
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  47. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  48. package/src/__tests__/contacts-tools.test.ts +29 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  52. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  53. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  54. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  55. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  56. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  57. package/src/__tests__/conversation-title-service.test.ts +62 -0
  58. package/src/__tests__/credential-broker.test.ts +449 -1
  59. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  60. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  61. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  62. package/src/__tests__/credential-routes.test.ts +360 -0
  63. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  64. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  65. package/src/__tests__/disk-usage.test.ts +65 -0
  66. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  67. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  68. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  69. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  70. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  71. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  72. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  73. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  74. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  75. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  76. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  77. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  78. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  79. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  80. package/src/__tests__/identity-routes.test.ts +0 -189
  81. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  82. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  83. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  84. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  85. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  86. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  87. package/src/__tests__/llm-resolver.test.ts +70 -24
  88. package/src/__tests__/llm-schema.test.ts +1 -0
  89. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  90. package/src/__tests__/mcp-health-check.test.ts +6 -7
  91. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  92. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  93. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  94. package/src/__tests__/path-policy.test.ts +34 -0
  95. package/src/__tests__/persona-resolver.test.ts +49 -14
  96. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  97. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  98. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  99. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  100. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  101. package/src/__tests__/reaction-persistence.test.ts +150 -29
  102. package/src/__tests__/registry.test.ts +2 -7
  103. package/src/__tests__/relay-server.test.ts +285 -0
  104. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  105. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  106. package/src/__tests__/schedule-routes.test.ts +0 -30
  107. package/src/__tests__/schedule-tools.test.ts +2 -18
  108. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  109. package/src/__tests__/skill-execute-input.test.ts +51 -1
  110. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  111. package/src/__tests__/skills.test.ts +51 -0
  112. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  113. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  114. package/src/__tests__/subagent-tools.test.ts +266 -0
  115. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  116. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  117. package/src/__tests__/title-generate-hook.test.ts +100 -3
  118. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  119. package/src/__tests__/token-manager.test.ts +519 -0
  120. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  121. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  122. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  123. package/src/__tests__/tool-executor.test.ts +0 -79
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  126. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  127. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  128. package/src/__tests__/twilio-routes.test.ts +81 -1
  129. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  130. package/src/__tests__/weak-open-model.test.ts +30 -0
  131. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  132. package/src/__tests__/workspace-greetings.test.ts +152 -0
  133. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  134. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  136. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  137. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  138. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  139. package/src/agent/loop.ts +49 -29
  140. package/src/api/README.md +6 -6
  141. package/src/api/events/tool-result.ts +6 -0
  142. package/src/api/events/workflow-completed.ts +53 -0
  143. package/src/api/events/workflow-leaf-finished.ts +38 -0
  144. package/src/api/events/workflow-leaf-started.ts +35 -0
  145. package/src/api/events/workflow-progress.ts +32 -0
  146. package/src/api/events/workflow-started.ts +31 -0
  147. package/src/api/index.ts +40 -0
  148. package/src/api/responses/conversation-message.ts +28 -4
  149. package/src/api/responses/home.ts +26 -4
  150. package/src/api/responses/workflow-journal.ts +53 -0
  151. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  152. package/src/approvals/guardian-decision-primitive.ts +26 -3
  153. package/src/approvals/guardian-request-resolvers.ts +183 -80
  154. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  155. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  156. package/src/calls/call-pointer-messages.ts +10 -4
  157. package/src/calls/channel-admission-reader.ts +104 -0
  158. package/src/calls/guardian-dispatch.ts +17 -45
  159. package/src/calls/media-stream-server.ts +84 -2
  160. package/src/calls/relay-access-wait.ts +1 -1
  161. package/src/calls/relay-server.ts +66 -0
  162. package/src/calls/relay-setup-router.ts +82 -1
  163. package/src/calls/twilio-routes.ts +17 -8
  164. package/src/calls/voice-session-bridge.ts +2 -2
  165. package/src/cli/commands/clients.ts +3 -0
  166. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  167. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  168. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  169. package/src/cli/commands/memory/index.ts +30 -0
  170. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  171. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  172. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  173. package/src/cli/commands/oauth/status.test.ts +36 -0
  174. package/src/cli/commands/oauth/status.ts +23 -3
  175. package/src/cli/commands/plugins.ts +197 -4
  176. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  177. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  178. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  179. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  180. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  181. package/src/cli/lib/diff-plugin.ts +346 -0
  182. package/src/cli/lib/inspect-plugin.ts +12 -1
  183. package/src/cli/lib/install-from-github.ts +105 -17
  184. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  185. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  186. package/src/cli/lib/plugin-surfaces.ts +104 -0
  187. package/src/cli/lib/upgrade-plugin.ts +298 -10
  188. package/src/cli/program.ts +2 -6
  189. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  190. package/src/config/assistant-feature-flags.ts +22 -7
  191. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  192. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  193. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  194. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  195. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  196. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  197. package/src/config/bundled-tool-registry.ts +2 -7
  198. package/src/config/call-site-defaults.ts +15 -2
  199. package/src/config/feature-flag-registry.json +46 -31
  200. package/src/config/inference-profile-validation.ts +26 -0
  201. package/src/config/llm-resolver.ts +3 -0
  202. package/src/config/loader.ts +4 -0
  203. package/src/config/memory-v3-gate.ts +11 -0
  204. package/src/config/profile-order.ts +28 -0
  205. package/src/config/schema.ts +8 -6
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  207. package/src/config/schemas/call-site-catalog.ts +7 -0
  208. package/src/config/schemas/channels.ts +11 -0
  209. package/src/config/schemas/elevenlabs.ts +0 -1
  210. package/src/config/schemas/llm.ts +31 -0
  211. package/src/config/schemas/memory-lifecycle.ts +3 -7
  212. package/src/config/schemas/memory-v3.ts +6 -0
  213. package/src/config/schemas/platform.ts +0 -8
  214. package/src/config/schemas/services.ts +18 -0
  215. package/src/config/seed-inference-profiles.ts +109 -44
  216. package/src/config/skills.ts +21 -0
  217. package/src/config/sync-gated-profiles.ts +220 -0
  218. package/src/contacts/contact-store.ts +89 -106
  219. package/src/contacts/contacts-write.ts +5 -22
  220. package/src/contacts/types.ts +0 -1
  221. package/src/context/compactor.ts +88 -54
  222. package/src/context/strip-injections.ts +58 -10
  223. package/src/context/token-estimator.ts +1 -1
  224. package/src/credential-execution/process-manager.ts +55 -14
  225. package/src/credential-execution/prompted-credential.ts +2 -3
  226. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  227. package/src/daemon/config-watcher.ts +0 -4
  228. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  229. package/src/daemon/conversation-agent-loop.ts +114 -22
  230. package/src/daemon/conversation-history.ts +1 -1
  231. package/src/daemon/conversation-lifecycle.ts +3 -5
  232. package/src/daemon/conversation-process.ts +13 -5
  233. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  234. package/src/daemon/conversation-slash.ts +2 -23
  235. package/src/daemon/conversation-surfaces.ts +26 -0
  236. package/src/daemon/conversation-tool-setup.ts +27 -14
  237. package/src/daemon/conversation.ts +66 -14
  238. package/src/daemon/disk-pressure-policy.ts +5 -3
  239. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  240. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  241. package/src/daemon/handlers/config-a2a.ts +0 -2
  242. package/src/daemon/handlers/config-channels.ts +15 -16
  243. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  244. package/src/daemon/handlers/conversations.ts +107 -0
  245. package/src/daemon/host-browser-proxy.ts +41 -0
  246. package/src/daemon/lifecycle.ts +55 -27
  247. package/src/daemon/message-provenance.ts +2 -0
  248. package/src/daemon/message-types/contacts.ts +0 -1
  249. package/src/daemon/message-types/conversations.ts +3 -3
  250. package/src/daemon/message-types/sync.ts +0 -1
  251. package/src/daemon/message-types/web-activity.ts +7 -1
  252. package/src/daemon/message-types/workflows.ts +83 -1
  253. package/src/daemon/orphan-reaper.test.ts +0 -19
  254. package/src/daemon/orphan-reaper.ts +2 -24
  255. package/src/daemon/server.ts +0 -10
  256. package/src/daemon/tool-setup-types.ts +4 -0
  257. package/src/daemon/trust-context.ts +1 -1
  258. package/src/events/tool-audit-listener.ts +2 -2
  259. package/src/home/feed-source-enrichment.test.ts +151 -0
  260. package/src/home/feed-source-enrichment.ts +176 -0
  261. package/src/home/relationship-state.ts +2 -4
  262. package/src/instrument.ts +18 -6
  263. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  264. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  265. package/src/ipc/assistant-server.ts +37 -4
  266. package/src/ipc/gateway-flag-listener.ts +18 -2
  267. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  268. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  269. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  270. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  271. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  272. package/src/memory/auth-fallback-events-store.ts +2 -2
  273. package/src/memory/auto-analysis-enqueue.ts +3 -5
  274. package/src/memory/bookmark-crud.ts +1 -2
  275. package/src/memory/canonical-guardian-store.ts +39 -1
  276. package/src/memory/conversation-crud.ts +9 -4
  277. package/src/memory/conversation-key-store.ts +17 -2
  278. package/src/memory/conversation-title-service.ts +64 -7
  279. package/src/memory/db-init.ts +17 -17
  280. package/src/memory/embedding-backend.ts +38 -1
  281. package/src/memory/embedding-billing-breaker.ts +96 -0
  282. package/src/memory/jobs-store.ts +25 -13
  283. package/src/memory/jobs-worker.ts +54 -1
  284. package/src/memory/lifecycle-events-store.ts +2 -2
  285. package/src/memory/memory-retrospective-constants.ts +4 -4
  286. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  287. package/src/memory/memory-retrospective-job.ts +28 -227
  288. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  289. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  290. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  291. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  292. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  293. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  294. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  295. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  296. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  297. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  298. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  299. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  300. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  301. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  302. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  303. package/src/memory/migrations/index.ts +6 -0
  304. package/src/memory/migrations/run-migrations.ts +41 -0
  305. package/src/memory/migrations/validate-migration-state.ts +1 -1
  306. package/src/memory/onboarding-events-store.ts +3 -3
  307. package/src/memory/schema/contacts.ts +0 -5
  308. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  309. package/src/memory/skill-loaded-events-store.ts +2 -2
  310. package/src/memory/tool-executed-events-store.test.ts +7 -7
  311. package/src/memory/turn-trace-store.test.ts +736 -0
  312. package/src/memory/turn-trace-store.ts +364 -0
  313. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  314. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  315. package/src/memory/v2/consolidation-job.ts +2 -2
  316. package/src/memory/v2/skill-content.ts +25 -7
  317. package/src/memory/v2/skill-store.ts +7 -1
  318. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  319. package/src/memory/v3-eval/eval-packets.ts +546 -0
  320. package/src/messaging/providers/slack/adapter.ts +1 -1
  321. package/src/messaging/providers/slack/api.ts +31 -0
  322. package/src/messaging/providers/slack/send.test.ts +114 -2
  323. package/src/messaging/providers/slack/send.ts +30 -7
  324. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  325. package/src/messaging/providers/slack/withdraw.ts +161 -0
  326. package/src/notifications/AGENTS.md +2 -0
  327. package/src/notifications/access-request-copy.ts +72 -59
  328. package/src/notifications/adapters/shared.ts +29 -0
  329. package/src/notifications/adapters/slack.ts +58 -103
  330. package/src/notifications/adapters/telegram.ts +2 -20
  331. package/src/notifications/approval-card-data.ts +333 -0
  332. package/src/notifications/broadcaster.ts +16 -3
  333. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  334. package/src/notifications/copy-composer.ts +3 -3
  335. package/src/notifications/decision-engine.ts +4 -2
  336. package/src/notifications/destination-resolver.ts +4 -6
  337. package/src/notifications/guardian-question-mode.ts +10 -0
  338. package/src/notifications/home-feed-side-effect.ts +7 -16
  339. package/src/notifications/notification-utils.ts +19 -20
  340. package/src/notifications/signal.ts +79 -43
  341. package/src/notifications/types.ts +98 -121
  342. package/src/oauth/AGENTS.md +5 -24
  343. package/src/permissions/checker.test.ts +51 -0
  344. package/src/permissions/checker.ts +185 -26
  345. package/src/permissions/ipc-risk-types.ts +24 -0
  346. package/src/permissions/question-prompter.test.ts +27 -0
  347. package/src/permissions/question-prompter.ts +4 -0
  348. package/src/platform/client.test.ts +119 -0
  349. package/src/platform/client.ts +66 -0
  350. package/src/platform/consent-cache.test.ts +267 -0
  351. package/src/platform/consent-cache.ts +174 -0
  352. package/src/plugin-api/constants.ts +1 -1
  353. package/src/plugin-api/index.ts +33 -1
  354. package/src/plugin-api/model-profiles.ts +33 -0
  355. package/src/plugin-api/types.ts +50 -2
  356. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  357. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  358. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  359. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  360. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  361. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  362. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  363. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  364. package/src/plugins/defaults/advisor/config.ts +21 -0
  365. package/src/plugins/defaults/advisor/consult.ts +93 -0
  366. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  367. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  368. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  369. package/src/plugins/defaults/advisor/package.json +14 -0
  370. package/src/plugins/defaults/advisor/steering.ts +67 -0
  371. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  372. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  373. package/src/plugins/defaults/index.ts +60 -0
  374. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  375. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  376. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  377. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  378. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  379. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  380. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  381. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  382. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  384. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  385. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  386. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  387. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  388. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  389. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  390. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  391. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  392. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  393. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  394. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  395. package/src/prompts/persona-resolver.ts +14 -4
  396. package/src/prompts/templates/system-sections.ts +7 -2
  397. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  398. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  399. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  400. package/src/providers/atlascloud/client.ts +85 -0
  401. package/src/providers/fetch-provider-catalog.ts +85 -0
  402. package/src/providers/inference/adapter-factory.ts +3 -0
  403. package/src/providers/model-catalog.ts +58 -0
  404. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  405. package/src/providers/openai/chat-completions-provider.ts +7 -0
  406. package/src/providers/openai/responses-provider.ts +10 -0
  407. package/src/providers/provider-send-message.ts +11 -3
  408. package/src/providers/retry.ts +53 -12
  409. package/src/providers/search-provider-catalog.ts +10 -0
  410. package/src/providers/weak-open-model.ts +22 -0
  411. package/src/runtime/AGENTS.md +0 -1
  412. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  413. package/src/runtime/__tests__/client-health.test.ts +44 -0
  414. package/src/runtime/access-request-helper.ts +21 -53
  415. package/src/runtime/actor-trust-resolver.ts +59 -63
  416. package/src/runtime/agent-wake.ts +52 -0
  417. package/src/runtime/assistant-event-hub.ts +18 -4
  418. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  419. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  420. package/src/runtime/btw-sidechain.ts +3 -6
  421. package/src/runtime/capabilities.test.ts +120 -0
  422. package/src/runtime/capabilities.ts +197 -0
  423. package/src/runtime/channel-approval-types.ts +22 -45
  424. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  425. package/src/runtime/channel-retry-sweep.ts +1 -0
  426. package/src/runtime/channel-verification-service.ts +3 -3
  427. package/src/runtime/client-health.ts +26 -0
  428. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  429. package/src/runtime/effective-capabilities.test.ts +128 -0
  430. package/src/runtime/effective-capabilities.ts +84 -0
  431. package/src/runtime/guardian-reply-router.ts +106 -21
  432. package/src/runtime/invite-redemption-service.ts +9 -25
  433. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  434. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  435. package/src/runtime/pending-interactions.ts +15 -0
  436. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  437. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  438. package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
  439. package/src/runtime/routes/app-routes.ts +1 -1
  440. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  441. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  442. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  443. package/src/runtime/routes/btw-routes.ts +1 -27
  444. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  445. package/src/runtime/routes/client-routes.ts +10 -0
  446. package/src/runtime/routes/contact-routes.ts +31 -8
  447. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  448. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  449. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  450. package/src/runtime/routes/conversation-routes.ts +39 -14
  451. package/src/runtime/routes/credential-routes.ts +40 -16
  452. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  453. package/src/runtime/routes/events-routes.ts +1 -3
  454. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  455. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  456. package/src/runtime/routes/home-feed-routes.ts +8 -3
  457. package/src/runtime/routes/identity-routes.ts +1 -296
  458. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  459. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  460. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  461. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  462. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  463. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  464. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  465. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  466. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  467. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  468. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  469. package/src/runtime/routes/index.ts +2 -0
  470. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  471. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  472. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  473. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  474. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  475. package/src/runtime/routes/notification-routes.ts +122 -133
  476. package/src/runtime/routes/platform-routes.ts +2 -2
  477. package/src/runtime/routes/plugins-routes.ts +202 -3
  478. package/src/runtime/routes/schedule-routes.ts +0 -22
  479. package/src/runtime/routes/secret-routes.ts +10 -0
  480. package/src/runtime/routes/surface-action-routes.ts +2 -1
  481. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  482. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  483. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  484. package/src/runtime/routes/workflow-routes.ts +131 -29
  485. package/src/runtime/routes/workspace-greetings.ts +55 -0
  486. package/src/runtime/sync/resource-sync-events.ts +1 -11
  487. package/src/runtime/tool-grant-request-helper.ts +18 -16
  488. package/src/runtime/trust-context-resolver.ts +8 -5
  489. package/src/schedule/inference-profile.ts +2 -14
  490. package/src/schedule/schedule-store.ts +1 -1
  491. package/src/schedule/scheduler-types.ts +5 -1
  492. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  493. package/src/security/secret-patterns.ts +3 -0
  494. package/src/subagent/manager.ts +17 -4
  495. package/src/subagent/types.ts +6 -0
  496. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  497. package/src/telemetry/trace-collection-policy.ts +30 -0
  498. package/src/telemetry/types.ts +89 -0
  499. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  500. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  501. package/src/tools/AGENTS.md +3 -3
  502. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  503. package/src/tools/browser/browser-execution.ts +30 -19
  504. package/src/tools/document/document-tool.ts +2 -3
  505. package/src/tools/executor.ts +5 -3
  506. package/src/tools/host-terminal/host-shell.ts +5 -4
  507. package/src/tools/memory/register.ts +2 -2
  508. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  509. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  510. package/src/tools/network/web-fetch.ts +372 -1
  511. package/src/tools/network/web-search-error.ts +1 -1
  512. package/src/tools/network/web-search.ts +213 -10
  513. package/src/tools/permission-checker.ts +4 -3
  514. package/src/tools/registry.ts +20 -0
  515. package/src/tools/schedule/create.ts +7 -12
  516. package/src/tools/schedule/update.ts +4 -11
  517. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  518. package/src/tools/side-effects.ts +2 -17
  519. package/src/tools/skills/execute.ts +33 -0
  520. package/src/tools/subagent/spawn.ts +61 -12
  521. package/src/tools/terminal/shell.ts +10 -4
  522. package/src/tools/tool-approval-handler.ts +18 -13
  523. package/src/tools/tool-manifest.ts +0 -2
  524. package/src/tools/types.ts +9 -0
  525. package/src/tools/ui-surface/definitions.ts +64 -3
  526. package/src/tools/verification-control-plane-policy.ts +3 -1
  527. package/src/tools/workflows/run-workflow.test.ts +8 -18
  528. package/src/tools/workflows/run-workflow.ts +1 -0
  529. package/src/util/disk-usage.ts +78 -23
  530. package/src/util/platform.ts +10 -3
  531. package/src/watcher/telemetry.ts +2 -2
  532. package/src/workflows/capabilities.ts +2 -3
  533. package/src/workflows/engine.test.ts +175 -1
  534. package/src/workflows/engine.ts +82 -0
  535. package/src/workflows/journal-store.test.ts +70 -0
  536. package/src/workflows/journal-store.ts +18 -3
  537. package/src/workflows/run-manager.test.ts +171 -28
  538. package/src/workflows/run-manager.ts +66 -24
  539. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  540. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  541. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  542. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  543. package/src/workspace/migrations/registry.ts +8 -0
  544. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  545. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  546. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  547. package/src/__tests__/credential-vault.test.ts +0 -1706
  548. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  549. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  550. package/src/cli/commands/__tests__/task.test.ts +0 -914
  551. package/src/cli/commands/task.ts +0 -771
  552. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  553. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  554. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  555. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  556. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  557. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  558. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  559. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  560. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  561. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  562. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  563. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  564. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  565. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  566. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  567. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  568. package/src/memory/preloaded-apps.ts +0 -116
  569. package/src/notifications/tool-approval-copy.ts +0 -142
  570. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  571. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  572. package/src/tools/credentials/vault.ts +0 -712
@@ -159,6 +159,23 @@ mock.module("../calls/relay-setup-router.js", () => ({
159
159
  routeSetup: jest.fn(() => mockRouteSetupResult),
160
160
  }));
161
161
 
162
+ // Mock the channel admission reader. handleStart awaits this before routing;
163
+ // tests override mockAdmissionPolicy to exercise floor enforcement. Returning
164
+ // a resolved promise introduces a microtask hop, so tests await
165
+ // session.whenSetupSettled() after sending the start frame.
166
+ let mockAdmissionPolicy: unknown = null;
167
+ // Optional gate: when set, getChannelAdmissionPolicy awaits this promise before
168
+ // resolving, letting a test dispose the session mid-read (simulating a WS close
169
+ // while the admission IPC read is pending).
170
+ let mockAdmissionGate: Promise<void> | null = null;
171
+ const mockGetChannelAdmissionPolicy = jest.fn(async () => {
172
+ if (mockAdmissionGate) await mockAdmissionGate;
173
+ return mockAdmissionPolicy;
174
+ });
175
+ mock.module("../calls/channel-admission-reader.js", () => ({
176
+ getChannelAdmissionPolicy: mockGetChannelAdmissionPolicy,
177
+ }));
178
+
162
179
  // Mock the actor trust resolver (used by handleStart to derive trust context)
163
180
  mock.module("../runtime/actor-trust-resolver.js", () => ({
164
181
  toTrustContext: jest.fn(() => ({
@@ -204,6 +221,7 @@ import {
204
221
  activeMediaStreamSessions,
205
222
  MediaStreamCallSession,
206
223
  } from "../calls/media-stream-server.js";
224
+ import { routeSetup } from "../calls/relay-setup-router.js";
207
225
 
208
226
  // ---------------------------------------------------------------------------
209
227
  // Mock WebSocket factory
@@ -304,6 +322,15 @@ function makeMarkMessage(name: string): string {
304
322
  });
305
323
  }
306
324
 
325
+ function makeDtmfMessage(digit: string): string {
326
+ return JSON.stringify({
327
+ event: "dtmf",
328
+ sequenceNumber: "60",
329
+ streamSid: "MZ00000000000000000000000000000000",
330
+ dtmf: { digit },
331
+ });
332
+ }
333
+
307
334
  // ---------------------------------------------------------------------------
308
335
  // Setup / teardown
309
336
  // ---------------------------------------------------------------------------
@@ -325,6 +352,10 @@ beforeEach(() => {
325
352
  (updateCallSession as jest.Mock).mockClear();
326
353
  (finalizeCall as jest.Mock).mockClear();
327
354
  (speakSystemPrompt as jest.Mock).mockClear();
355
+ (routeSetup as jest.Mock).mockClear();
356
+ mockGetChannelAdmissionPolicy.mockClear();
357
+ mockAdmissionPolicy = null;
358
+ mockAdmissionGate = null;
328
359
  // Reset routeSetup to default normal_call
329
360
  mockRouteSetupResult = {
330
361
  outcome: { action: "normal_call" as const, isInbound: true },
@@ -359,7 +390,7 @@ describe("MediaStreamCallSession", () => {
359
390
  });
360
391
 
361
392
  describe("start event handling", () => {
362
- test("start event registers a controller and records call_connected", () => {
393
+ test("start event registers a controller and records call_connected", async () => {
363
394
  const mock = createMockWs();
364
395
  // Set up a call session in the mock store
365
396
  mockSessions.set("call-1", {
@@ -373,6 +404,7 @@ describe("MediaStreamCallSession", () => {
373
404
 
374
405
  const session = new MediaStreamCallSession(mock.ws, "call-1");
375
406
  session.handleMessage(makeStartMessage());
407
+ await session.whenSetupSettled();
376
408
 
377
409
  // Controller should have been registered
378
410
  expect(registerCallController).toHaveBeenCalledWith(
@@ -484,7 +516,7 @@ describe("MediaStreamCallSession", () => {
484
516
  });
485
517
 
486
518
  describe("destroy", () => {
487
- test("destroys the controller and marks output as closed", () => {
519
+ test("destroys the controller and marks output as closed", async () => {
488
520
  const mock = createMockWs();
489
521
  mockSessions.set("call-1", {
490
522
  id: "call-1",
@@ -498,6 +530,7 @@ describe("MediaStreamCallSession", () => {
498
530
  const session = new MediaStreamCallSession(mock.ws, "call-1");
499
531
  // Trigger start to create a controller
500
532
  session.handleMessage(makeStartMessage());
533
+ await session.whenSetupSettled();
501
534
 
502
535
  session.destroy();
503
536
  expect(mockDestroy).toHaveBeenCalled();
@@ -715,7 +748,7 @@ describe("media-stream output egress", () => {
715
748
  expect(clearMessages.length).toBeGreaterThanOrEqual(1);
716
749
  });
717
750
 
718
- test("barge-in via speech start clears audio and interrupts controller", () => {
751
+ test("barge-in via speech start clears audio and interrupts controller", async () => {
719
752
  const mockWs = createMockWs();
720
753
  mockSessions.set("call-interrupt-1", {
721
754
  id: "call-interrupt-1",
@@ -728,6 +761,7 @@ describe("media-stream output egress", () => {
728
761
 
729
762
  const session = new MediaStreamCallSession(mockWs.ws, "call-interrupt-1");
730
763
  session.handleMessage(makeStartMessage());
764
+ await session.whenSetupSettled();
731
765
 
732
766
  // Verify the controller is created
733
767
  expect(session.getController()).not.toBeNull();
@@ -770,7 +804,7 @@ describe("activeMediaStreamSessions registry", () => {
770
804
 
771
805
  describe("media-stream setup outcome scenarios", () => {
772
806
  describe("deny outcome", () => {
773
- test("deny outcome records inbound_acl_denied event and sets status to failed", () => {
807
+ test("deny outcome records inbound_acl_denied event and sets status to failed", async () => {
774
808
  mockRouteSetupResult = {
775
809
  outcome: {
776
810
  action: "deny",
@@ -798,6 +832,7 @@ describe("media-stream setup outcome scenarios", () => {
798
832
 
799
833
  const session = new MediaStreamCallSession(mockWs.ws, "call-deny-1");
800
834
  session.handleMessage(makeStartMessage());
835
+ await session.whenSetupSettled();
801
836
 
802
837
  // Should record an inbound_acl_denied event
803
838
  expect(recordCallEvent).toHaveBeenCalledWith(
@@ -821,7 +856,7 @@ describe("media-stream setup outcome scenarios", () => {
821
856
  expect(registerCallController).not.toHaveBeenCalled();
822
857
  });
823
858
 
824
- test("deny outcome speaks the denial message", () => {
859
+ test("deny outcome speaks the denial message", async () => {
825
860
  mockRouteSetupResult = {
826
861
  outcome: {
827
862
  action: "deny",
@@ -852,6 +887,7 @@ describe("media-stream setup outcome scenarios", () => {
852
887
  "call-deny-speak-1",
853
888
  );
854
889
  session.handleMessage(makeStartMessage());
890
+ await session.whenSetupSettled();
855
891
 
856
892
  // speakSystemPrompt should be called with the denial message
857
893
  expect(speakSystemPrompt).toHaveBeenCalledWith(
@@ -860,7 +896,7 @@ describe("media-stream setup outcome scenarios", () => {
860
896
  );
861
897
  });
862
898
 
863
- test("deny outcome runs finalization", () => {
899
+ test("deny outcome runs finalization", async () => {
864
900
  mockRouteSetupResult = {
865
901
  outcome: {
866
902
  action: "deny",
@@ -891,6 +927,7 @@ describe("media-stream setup outcome scenarios", () => {
891
927
  "call-deny-finalize-1",
892
928
  );
893
929
  session.handleMessage(makeStartMessage());
930
+ await session.whenSetupSettled();
894
931
 
895
932
  // finalizeCall should be called because early teardown runs it inline
896
933
  expect(finalizeCall).toHaveBeenCalledWith(
@@ -901,7 +938,7 @@ describe("media-stream setup outcome scenarios", () => {
901
938
  });
902
939
 
903
940
  describe("unsupported interactive setup flow", () => {
904
- test("verification outcome records call_failed with preflight-bypass reason", () => {
941
+ test("verification outcome records call_failed with preflight-bypass reason", async () => {
905
942
  mockRouteSetupResult = {
906
943
  outcome: {
907
944
  action: "verification",
@@ -932,6 +969,7 @@ describe("media-stream setup outcome scenarios", () => {
932
969
  "call-unsup-verify-1",
933
970
  );
934
971
  session.handleMessage(makeStartMessage());
972
+ await session.whenSetupSettled();
935
973
 
936
974
  // Should record call_failed event with preflight-bypass note
937
975
  expect(recordCallEvent).toHaveBeenCalledWith(
@@ -956,7 +994,7 @@ describe("media-stream setup outcome scenarios", () => {
956
994
  expect(registerCallController).not.toHaveBeenCalled();
957
995
  });
958
996
 
959
- test("name_capture outcome speaks generic apology and tears down", () => {
997
+ test("name_capture outcome speaks generic apology and tears down", async () => {
960
998
  mockRouteSetupResult = {
961
999
  outcome: {
962
1000
  action: "name_capture",
@@ -987,6 +1025,7 @@ describe("media-stream setup outcome scenarios", () => {
987
1025
  "call-unsup-name-1",
988
1026
  );
989
1027
  session.handleMessage(makeStartMessage());
1028
+ await session.whenSetupSettled();
990
1029
 
991
1030
  // speakSystemPrompt should be called with the generic apology
992
1031
  expect(speakSystemPrompt).toHaveBeenCalledWith(
@@ -1001,7 +1040,7 @@ describe("media-stream setup outcome scenarios", () => {
1001
1040
  );
1002
1041
  });
1003
1042
 
1004
- test("callee_verification outcome fails with explicit reason", () => {
1043
+ test("callee_verification outcome fails with explicit reason", async () => {
1005
1044
  mockRouteSetupResult = {
1006
1045
  outcome: {
1007
1046
  action: "callee_verification",
@@ -1031,6 +1070,7 @@ describe("media-stream setup outcome scenarios", () => {
1031
1070
  "call-unsup-callee-1",
1032
1071
  );
1033
1072
  session.handleMessage(makeStartMessage());
1073
+ await session.whenSetupSettled();
1034
1074
 
1035
1075
  // Should record the failure with the specific action
1036
1076
  expect(recordCallEvent).toHaveBeenCalledWith(
@@ -1048,7 +1088,7 @@ describe("media-stream setup outcome scenarios", () => {
1048
1088
  );
1049
1089
  });
1050
1090
 
1051
- test("normal_call after deny scenario still creates controller", () => {
1091
+ test("normal_call after deny scenario still creates controller", async () => {
1052
1092
  // Verify that after a deny-scenario test, resetting to normal_call
1053
1093
  // properly creates a controller (no cross-test pollution).
1054
1094
  mockRouteSetupResult = {
@@ -1073,6 +1113,7 @@ describe("media-stream setup outcome scenarios", () => {
1073
1113
 
1074
1114
  const session = new MediaStreamCallSession(mockWs.ws, "call-reset-1");
1075
1115
  session.handleMessage(makeStartMessage());
1116
+ await session.whenSetupSettled();
1076
1117
 
1077
1118
  // Controller should be registered for normal calls
1078
1119
  expect(registerCallController).toHaveBeenCalledWith(
@@ -1088,7 +1129,7 @@ describe("media-stream setup outcome scenarios", () => {
1088
1129
  // ── Barge-in regression ──────────────────────────────────────────
1089
1130
 
1090
1131
  describe("barge-in gating", () => {
1091
- test("immediate inbound audio after stream start does not trigger handleInterrupt", () => {
1132
+ test("immediate inbound audio after stream start does not trigger handleInterrupt", async () => {
1092
1133
  const mockWs = createMockWs();
1093
1134
  mockSessions.set("call-bargein-1", {
1094
1135
  id: "call-bargein-1",
@@ -1103,6 +1144,7 @@ describe("media-stream setup outcome scenarios", () => {
1103
1144
 
1104
1145
  // Stream start bootstraps the controller
1105
1146
  session.handleMessage(makeStartMessage());
1147
+ await session.whenSetupSettled();
1106
1148
  expect(mockStartInitialGreeting).toHaveBeenCalled();
1107
1149
 
1108
1150
  // Immediate inbound audio (speech-like payloads) — before the
@@ -1130,7 +1172,7 @@ describe("media-stream setup outcome scenarios", () => {
1130
1172
  session.destroy();
1131
1173
  });
1132
1174
 
1133
- test("barge-in is accepted when controller is speaking", () => {
1175
+ test("barge-in is accepted when controller is speaking", async () => {
1134
1176
  // Configure mock to indicate the controller is speaking
1135
1177
  mockHandleBargeIn.mockReturnValue(true);
1136
1178
 
@@ -1146,6 +1188,7 @@ describe("media-stream setup outcome scenarios", () => {
1146
1188
 
1147
1189
  const session = new MediaStreamCallSession(mockWs.ws, "call-bargein-2");
1148
1190
  session.handleMessage(makeStartMessage());
1191
+ await session.whenSetupSettled();
1149
1192
 
1150
1193
  // Simulate inbound speech audio while assistant is speaking.
1151
1194
  // Use a high-amplitude mu-law payload so speech detection triggers.
@@ -1162,7 +1205,7 @@ describe("media-stream setup outcome scenarios", () => {
1162
1205
  // ── E2E regression scenario ──────────────────────────────────────
1163
1206
 
1164
1207
  describe("end-to-end regression: connected call that stays active", () => {
1165
- test("stream connects, inbound audio starts, call remains active for a turn, controller only destroyed at stop/hangup", () => {
1208
+ test("stream connects, inbound audio starts, call remains active for a turn, controller only destroyed at stop/hangup", async () => {
1166
1209
  const mockWs = createMockWs();
1167
1210
  mockSessions.set("call-e2e-1", {
1168
1211
  id: "call-e2e-1",
@@ -1177,6 +1220,7 @@ describe("media-stream setup outcome scenarios", () => {
1177
1220
 
1178
1221
  // 1. Stream connects — start event arrives
1179
1222
  session.handleMessage(makeStartMessage());
1223
+ await session.whenSetupSettled();
1180
1224
  expect(registerCallController).toHaveBeenCalledWith(
1181
1225
  "call-e2e-1",
1182
1226
  expect.anything(),
@@ -1231,4 +1275,264 @@ describe("media-stream setup outcome scenarios", () => {
1231
1275
  expect(mockDestroy).toHaveBeenCalled();
1232
1276
  });
1233
1277
  });
1278
+
1279
+ // ── Admission floor enforcement on the media-stream transport ────────
1280
+ // The phone channel is no longer exempt from per-channel admission, so the
1281
+ // trust floor must be enforced on this transport too — not just the
1282
+ // gateway's no_one kill switch. handleStart resolves the phone admission
1283
+ // policy and threads it into routeSetup; a floor-denied caller (e.g.
1284
+ // guardian_only vs a trusted_contact) produces a `deny` outcome here, which
1285
+ // speaks a denial + tears down and never starts a normal call.
1286
+
1287
+ describe("admission floor enforcement", () => {
1288
+ test("resolves the phone admission policy and threads it into routeSetup", async () => {
1289
+ mockAdmissionPolicy = "guardian_only";
1290
+
1291
+ const mockWs = createMockWs();
1292
+ mockSessions.set("call-floor-thread-1", {
1293
+ id: "call-floor-thread-1",
1294
+ conversationId: "conv-floor-thread-1",
1295
+ status: "initiated",
1296
+ task: null,
1297
+ startedAt: null,
1298
+ fromNumber: "+14155550000",
1299
+ toNumber: "+15550001111",
1300
+ });
1301
+
1302
+ const session = new MediaStreamCallSession(
1303
+ mockWs.ws,
1304
+ "call-floor-thread-1",
1305
+ );
1306
+ session.handleMessage(makeStartMessage());
1307
+ await session.whenSetupSettled();
1308
+
1309
+ expect(mockGetChannelAdmissionPolicy).toHaveBeenCalledWith("phone");
1310
+ expect(routeSetup).toHaveBeenCalledWith(
1311
+ expect.objectContaining({
1312
+ callSessionId: "call-floor-thread-1",
1313
+ admissionPolicy: "guardian_only",
1314
+ }),
1315
+ );
1316
+ });
1317
+
1318
+ test("guardian_only floor denies a trusted-contact caller — speaks denial, tears down, no controller", async () => {
1319
+ mockAdmissionPolicy = "guardian_only";
1320
+ // With the floor wired, the real router would return `deny` for a
1321
+ // below-floor (trusted_contact) caller; the mock reflects that outcome.
1322
+ mockRouteSetupResult = {
1323
+ outcome: {
1324
+ action: "deny",
1325
+ message:
1326
+ "This number is not authorized to reach the assistant right now.",
1327
+ logReason: "Inbound voice admission floor: guardian_only",
1328
+ },
1329
+ resolved: {
1330
+ assistantId: "self",
1331
+ isInbound: true,
1332
+ otherPartyNumber: "+14155550000",
1333
+ actorTrust: { trustClass: "trusted_contact", memberRecord: null },
1334
+ },
1335
+ };
1336
+
1337
+ const mockWs = createMockWs();
1338
+ mockSessions.set("call-floor-deny-1", {
1339
+ id: "call-floor-deny-1",
1340
+ conversationId: "conv-floor-deny-1",
1341
+ status: "initiated",
1342
+ task: null,
1343
+ startedAt: null,
1344
+ fromNumber: "+14155550000",
1345
+ toNumber: "+15550001111",
1346
+ });
1347
+
1348
+ const session = new MediaStreamCallSession(
1349
+ mockWs.ws,
1350
+ "call-floor-deny-1",
1351
+ );
1352
+ session.handleMessage(makeStartMessage());
1353
+ await session.whenSetupSettled();
1354
+
1355
+ // Policy was passed into routeSetup.
1356
+ expect(routeSetup).toHaveBeenCalledWith(
1357
+ expect.objectContaining({ admissionPolicy: "guardian_only" }),
1358
+ );
1359
+
1360
+ // Denial spoken, session failed, no controller, finalization ran.
1361
+ expect(speakSystemPrompt).toHaveBeenCalledWith(
1362
+ expect.anything(),
1363
+ "This number is not authorized to reach the assistant right now.",
1364
+ );
1365
+ expect(updateCallSession).toHaveBeenCalledWith(
1366
+ "call-floor-deny-1",
1367
+ expect.objectContaining({
1368
+ status: "failed",
1369
+ lastError: "Inbound voice admission floor: guardian_only",
1370
+ }),
1371
+ );
1372
+ expect(registerCallController).not.toHaveBeenCalled();
1373
+ expect(mockStartInitialGreeting).not.toHaveBeenCalled();
1374
+ expect(finalizeCall).toHaveBeenCalledWith(
1375
+ "call-floor-deny-1",
1376
+ "conv-floor-deny-1",
1377
+ );
1378
+ });
1379
+
1380
+ test("null policy (no enforcement) leaves behavior unchanged — normal call proceeds", async () => {
1381
+ mockAdmissionPolicy = null;
1382
+ // routeSetup default is normal_call.
1383
+
1384
+ const mockWs = createMockWs();
1385
+ mockSessions.set("call-floor-null-1", {
1386
+ id: "call-floor-null-1",
1387
+ conversationId: "conv-floor-null-1",
1388
+ status: "initiated",
1389
+ task: null,
1390
+ startedAt: null,
1391
+ fromNumber: "+14155550000",
1392
+ toNumber: "+15550001111",
1393
+ });
1394
+
1395
+ const session = new MediaStreamCallSession(
1396
+ mockWs.ws,
1397
+ "call-floor-null-1",
1398
+ );
1399
+ session.handleMessage(makeStartMessage());
1400
+ await session.whenSetupSettled();
1401
+
1402
+ // Null policy is still threaded through (router skips the floor).
1403
+ expect(routeSetup).toHaveBeenCalledWith(
1404
+ expect.objectContaining({ admissionPolicy: null }),
1405
+ );
1406
+
1407
+ // Normal call proceeds: controller registered + greeting fired.
1408
+ expect(registerCallController).toHaveBeenCalledWith(
1409
+ "call-floor-null-1",
1410
+ expect.anything(),
1411
+ );
1412
+ expect(mockStartInitialGreeting).toHaveBeenCalled();
1413
+ });
1414
+
1415
+ test("a floor-denied caller's transcript is dropped during setup routing", async () => {
1416
+ mockAdmissionPolicy = "guardian_only";
1417
+ mockRouteSetupResult = {
1418
+ outcome: {
1419
+ action: "deny",
1420
+ message:
1421
+ "This number is not authorized to reach the assistant right now.",
1422
+ logReason: "Inbound voice admission floor: guardian_only",
1423
+ },
1424
+ resolved: {
1425
+ assistantId: "self",
1426
+ isInbound: true,
1427
+ otherPartyNumber: "+14155550000",
1428
+ actorTrust: { trustClass: "trusted_contact", memberRecord: null },
1429
+ },
1430
+ };
1431
+
1432
+ const mockWs = createMockWs();
1433
+ mockSessions.set("call-floor-drop-1", {
1434
+ id: "call-floor-drop-1",
1435
+ conversationId: "conv-floor-drop-1",
1436
+ status: "initiated",
1437
+ task: null,
1438
+ startedAt: null,
1439
+ fromNumber: "+14155550000",
1440
+ toNumber: "+15550001111",
1441
+ });
1442
+
1443
+ const session = new MediaStreamCallSession(
1444
+ mockWs.ws,
1445
+ "call-floor-drop-1",
1446
+ );
1447
+ session.handleMessage(makeStartMessage());
1448
+ await session.whenSetupSettled();
1449
+
1450
+ // No controller exists for a denied caller, so even if a transcript
1451
+ // arrived it could never reach handleCallerUtterance / be persisted.
1452
+ expect(registerCallController).not.toHaveBeenCalled();
1453
+ expect(mockHandleCallerUtterance).not.toHaveBeenCalled();
1454
+ // No caller_spoke event recorded for the denied caller.
1455
+ const callerSpoke = mockEvents.filter(
1456
+ (e) =>
1457
+ e.callSessionId === "call-floor-drop-1" &&
1458
+ e.eventType === "caller_spoke",
1459
+ );
1460
+ expect(callerSpoke.length).toBe(0);
1461
+ });
1462
+
1463
+ test("a DTMF digit received during setup routing is dropped", async () => {
1464
+ // Gate the admission read so setupRouting is still true when the DTMF
1465
+ // frame arrives (simulating a digit during the admission IPC read).
1466
+ let releaseGate!: () => void;
1467
+ mockAdmissionGate = new Promise<void>((resolve) => {
1468
+ releaseGate = resolve;
1469
+ });
1470
+
1471
+ const mockWs = createMockWs();
1472
+ mockSessions.set("call-dtmf-drop-1", {
1473
+ id: "call-dtmf-drop-1",
1474
+ conversationId: "conv-dtmf-drop-1",
1475
+ status: "initiated",
1476
+ task: null,
1477
+ startedAt: null,
1478
+ fromNumber: "+14155550000",
1479
+ toNumber: "+15550001111",
1480
+ });
1481
+
1482
+ const session = new MediaStreamCallSession(mockWs.ws, "call-dtmf-drop-1");
1483
+ session.handleMessage(makeStartMessage());
1484
+
1485
+ // DTMF arrives while the admission read is still pending.
1486
+ session.handleMessage(makeDtmfMessage("5"));
1487
+
1488
+ releaseGate();
1489
+ await session.whenSetupSettled();
1490
+
1491
+ // The digit was dropped during setup: no caller_spoke event, no
1492
+ // controller interaction.
1493
+ const callerSpoke = mockEvents.filter(
1494
+ (e) =>
1495
+ e.callSessionId === "call-dtmf-drop-1" &&
1496
+ e.eventType === "caller_spoke",
1497
+ );
1498
+ expect(callerSpoke.length).toBe(0);
1499
+ expect(mockHandleCallerUtterance).not.toHaveBeenCalled();
1500
+ });
1501
+
1502
+ test("session disposed during the admission read aborts setup — no controller, no greeting", async () => {
1503
+ // Gate the admission read so the session can be disposed (as the WS
1504
+ // close handler does) while handleStart is awaiting it.
1505
+ let releaseGate!: () => void;
1506
+ mockAdmissionGate = new Promise<void>((resolve) => {
1507
+ releaseGate = resolve;
1508
+ });
1509
+
1510
+ const mockWs = createMockWs();
1511
+ mockSessions.set("call-disposed-1", {
1512
+ id: "call-disposed-1",
1513
+ conversationId: "conv-disposed-1",
1514
+ status: "initiated",
1515
+ task: null,
1516
+ startedAt: null,
1517
+ fromNumber: "+14155550000",
1518
+ toNumber: "+15550001111",
1519
+ });
1520
+
1521
+ const session = new MediaStreamCallSession(mockWs.ws, "call-disposed-1");
1522
+ session.handleMessage(makeStartMessage());
1523
+
1524
+ // Twilio closes the WebSocket mid-read: the server disposes the session.
1525
+ session.destroy();
1526
+
1527
+ // Now let the admission read resolve and setup routing resume.
1528
+ releaseGate();
1529
+ await session.whenSetupSettled();
1530
+
1531
+ // Setup must have aborted: routeSetup never ran, no controller, no greeting.
1532
+ expect(routeSetup).not.toHaveBeenCalled();
1533
+ expect(registerCallController).not.toHaveBeenCalled();
1534
+ expect(mockStartInitialGreeting).not.toHaveBeenCalled();
1535
+ expect(speakSystemPrompt).not.toHaveBeenCalled();
1536
+ });
1537
+ });
1234
1538
  });
@@ -3,17 +3,15 @@ import { describe, expect, test } from "bun:test";
3
3
  import { PROVIDER_SEED_DATA } from "../oauth/seed-providers.js";
4
4
 
5
5
  /**
6
- * Allowed CDN prefixes for ``logoUrl``. Mirrors the source registry in
7
- * ``clients/shared/Resources/integration-logos-manifest.json``:
6
+ * Allowed CDN prefixes for the ``logoUrl`` field on ``PROVIDER_SEED_DATA``
7
+ * (``assistant/src/oauth/seed-providers.ts``):
8
8
  *
9
9
  * - Simple Icons (CC0) is the default for most providers.
10
10
  * - thesvg via jsDelivr is the documented fallback for brands Simple Icons
11
11
  * doesn't host (e.g. Salesforce, which Simple Icons removed for
12
- * trademark reasons). Same source is already used for the bundled PDFs
13
- * of figma/github/gmail/linear/notion/outlook/slack.
12
+ * trademark reasons).
14
13
  *
15
- * Adding another CDN should be a deliberate choice — extend this list
16
- * and update the manifest in tandem.
14
+ * Adding another CDN should be a deliberate choice — extend this list.
17
15
  */
18
16
  const ALLOWED_LOGO_URL_PREFIXES = [
19
17
  "https://cdn.simpleicons.org/",
@@ -59,7 +59,7 @@ mock.module("../util/platform.js", () => ({
59
59
  }));
60
60
 
61
61
  mock.module("../contacts/contact-store.js", () => ({
62
- findContactByChannelExternalId: () => null,
62
+ findContactByAddress: () => null,
63
63
  findGuardianForChannel: (channelType: string) =>
64
64
  channelType === "vellum" ? mockVellumGuardian : null,
65
65
  listGuardianChannels: () => null,
@@ -12,6 +12,7 @@ import { afterEach, describe, expect, mock, test } from "bun:test";
12
12
 
13
13
  import {
14
14
  hostPolicy,
15
+ resolveRealPath,
15
16
  sandboxPolicy,
16
17
  } from "../tools/shared/filesystem/path-policy.js";
17
18
 
@@ -238,6 +239,39 @@ describe("hostPolicy", () => {
238
239
  });
239
240
  });
240
241
 
242
+ // ---------------------------------------------------------------------------
243
+ // resolveRealPath
244
+ // ---------------------------------------------------------------------------
245
+
246
+ describe("resolveRealPath", () => {
247
+ test("resolves an existing file's symlink to its real target", () => {
248
+ const dir = makeTempDir();
249
+ const target = join(dir, "real.txt");
250
+ writeFileSync(target, "x");
251
+ const link = join(dir, "link.txt");
252
+ symlinkSync(target, link);
253
+
254
+ expect(resolveRealPath(link)).toBe(target);
255
+ });
256
+
257
+ test("follows a symlinked ancestor for a not-yet-existing path", () => {
258
+ const real = makeTempDir();
259
+ const linkParent = makeTempDir();
260
+ const link = join(linkParent, "link-dir");
261
+ symlinkSync(real, link);
262
+
263
+ // The leaf does not exist yet; the symlinked ancestor must still resolve.
264
+ expect(resolveRealPath(join(link, "new-file.txt"))).toBe(
265
+ join(real, "new-file.txt"),
266
+ );
267
+ });
268
+
269
+ test("falls back to the lexical path when nothing on it exists", () => {
270
+ const phantom = join(tmpdir(), "definitely-not-here-12345", "nope.txt");
271
+ expect(resolveRealPath(phantom)).toBe(phantom);
272
+ });
273
+ });
274
+
241
275
  // ---------------------------------------------------------------------------
242
276
  // Baseline: Skill directory paths have no special treatment (PR 1)
243
277
  // ---------------------------------------------------------------------------