@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
@@ -50,6 +50,7 @@ import {
50
50
  recordCallEvent,
51
51
  updateCallSession,
52
52
  } from "./call-store.js";
53
+ import { getChannelAdmissionPolicy } from "./channel-admission-reader.js";
53
54
  import { finalizeCall } from "./finalize-call.js";
54
55
  import { MediaStreamOutput } from "./media-stream-output.js";
55
56
  import { parseMediaStreamFrame } from "./media-stream-parser.js";
@@ -90,6 +91,18 @@ export class MediaStreamCallSession {
90
91
  private streamSid: string | null = null;
91
92
  private callSid: string | null = null;
92
93
  private disposed = false;
94
+ // True from the synchronous start of handleStart until setup routing
95
+ // completes. Resolving the admission floor yields the event loop, so
96
+ // transcripts/barge-in arriving in this window are ignored — a denied or
97
+ // not-yet-authorized caller's speech is never persisted before the floor
98
+ // and trust ACL are applied. The controller-existence checks below also
99
+ // gate persistence, but this guard makes the intent explicit and covers
100
+ // the brief async window before the controller is created.
101
+ private setupRouting = false;
102
+ // Resolves when the async setup routing kicked off by the `start` frame
103
+ // settles. Exposed via whenSetupSettled() for deterministic test awaiting,
104
+ // since handleStart now yields on the admission-policy read.
105
+ private setupSettled: Promise<void> = Promise.resolve();
93
106
 
94
107
  // ── Operational diagnostics counters ──────────────────────────────
95
108
  /** Number of barge-in attempts that were accepted (assistant was speaking). */
@@ -141,6 +154,14 @@ export class MediaStreamCallSession {
141
154
  return this.controller;
142
155
  }
143
156
 
157
+ /**
158
+ * Resolves once the async setup routing started by the `start` frame has
159
+ * settled. Test-only convenience — production code does not await this.
160
+ */
161
+ whenSetupSettled(): Promise<void> {
162
+ return this.setupSettled;
163
+ }
164
+
144
165
  /**
145
166
  * Feed a raw WebSocket message into the session.
146
167
  *
@@ -154,7 +175,16 @@ export class MediaStreamCallSession {
154
175
  // Intercept `start` to bootstrap the session before forwarding.
155
176
  const parseResult = parseMediaStreamFrame(raw);
156
177
  if (parseResult.ok && parseResult.event.event === "start") {
157
- this.handleStart(parseResult.event);
178
+ // handleStart resolves the admission floor asynchronously; the
179
+ // setupRouting guard set synchronously at its top ensures inbound
180
+ // frames forwarded below are ignored until routing completes.
181
+ this.setupSettled = this.handleStart(parseResult.event).catch((err) => {
182
+ log.error(
183
+ { err, callSessionId: this.callSessionId },
184
+ "Media-stream setup routing failed",
185
+ );
186
+ this.setupRouting = false;
187
+ });
158
188
  }
159
189
 
160
190
  // Always forward to the STT session (it handles all event types).
@@ -296,7 +326,12 @@ export class MediaStreamCallSession {
296
326
 
297
327
  // ── Internal: media-stream event handlers ─────────────────────────
298
328
 
299
- private handleStart(event: MediaStreamStartEvent): void {
329
+ private async handleStart(event: MediaStreamStartEvent): Promise<void> {
330
+ // Enter the setup-pending window synchronously, before any await. The
331
+ // admission-policy read below yields the event loop, so frames forwarded
332
+ // to the STT session can produce transcripts/barge-in before routing
333
+ // completes — those are dropped while setupRouting is true.
334
+ this.setupRouting = true;
300
335
  this.streamSid = event.streamSid;
301
336
  this.callSid = event.start.callSid;
302
337
 
@@ -337,12 +372,32 @@ export class MediaStreamCallSession {
337
372
  const from = session?.fromNumber ?? "";
338
373
  const to = session?.toNumber ?? "";
339
374
 
375
+ // Resolve the phone channel's inbound admission floor so the trust floor
376
+ // (e.g. guardian_only) is enforced on this transport too — not just the
377
+ // gateway webhook's no_one kill switch. The reader fails open to `null`
378
+ // by contract, so a transport hiccup admits the caller.
379
+ const admissionPolicy = await getChannelAdmissionPolicy("phone");
380
+
381
+ // The admission-policy read above yields the event loop; if Twilio closed
382
+ // the WebSocket meanwhile, the close handler will have called destroy().
383
+ // Abort setup so we don't create a controller or speak on a disposed
384
+ // session.
385
+ if (this.disposed) {
386
+ log.info(
387
+ { callSessionId: this.callSessionId },
388
+ "Media-stream session disposed during admission read — aborting setup",
389
+ );
390
+ this.setupRouting = false;
391
+ return;
392
+ }
393
+
340
394
  const { outcome, resolved } = routeSetup({
341
395
  callSessionId: this.callSessionId,
342
396
  session: session ?? null,
343
397
  from,
344
398
  to,
345
399
  customParameters: event.start.customParameters,
400
+ admissionPolicy,
346
401
  });
347
402
 
348
403
  log.info(
@@ -377,6 +432,10 @@ export class MediaStreamCallSession {
377
432
  );
378
433
  registerCallController(this.callSessionId, this.controller);
379
434
 
435
+ // Routing/ACL cleared — leave the setup-pending window so subsequent
436
+ // transcripts and barge-in are handled normally.
437
+ this.setupRouting = false;
438
+
380
439
  // Fire the initial greeting.
381
440
  this.controller.startInitialGreeting().catch((err) => {
382
441
  log.error(
@@ -525,6 +584,18 @@ export class MediaStreamCallSession {
525
584
 
526
585
  private handleTranscriptFinal(text: string, _durationMs: number): void {
527
586
  if (!text.trim()) return;
587
+
588
+ // Drop transcripts arriving while setup routing is still pending so a
589
+ // not-yet-authorized / floor-denied caller's speech is never persisted
590
+ // before the admission floor and trust ACL are applied.
591
+ if (this.setupRouting) {
592
+ log.debug(
593
+ { callSessionId: this.callSessionId },
594
+ "Transcript received during setup routing — dropping",
595
+ );
596
+ return;
597
+ }
598
+
528
599
  this.transcriptFinalsProduced++;
529
600
 
530
601
  if (!this.controller) {
@@ -560,6 +631,17 @@ export class MediaStreamCallSession {
560
631
  }
561
632
 
562
633
  private handleDtmf(digit: string): void {
634
+ // Drop DTMF arriving while setup routing is still pending so a
635
+ // not-yet-authorized / floor-denied caller's digit is never persisted
636
+ // before the admission floor and trust ACL are applied.
637
+ if (this.setupRouting) {
638
+ log.debug(
639
+ { callSessionId: this.callSessionId, digit },
640
+ "DTMF received during setup routing — dropping",
641
+ );
642
+ return;
643
+ }
644
+
563
645
  log.info(
564
646
  { callSessionId: this.callSessionId, digit },
565
647
  "DTMF digit received on media-stream",
@@ -249,7 +249,7 @@ export function emitAccessRequestCallbackHandoff(
249
249
  try {
250
250
  const contactResult = findContactChannel({
251
251
  channelType: "phone",
252
- externalUserId: fromNumber,
252
+ address: fromNumber,
253
253
  externalChatId: fromNumber,
254
254
  });
255
255
  if (
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { randomInt } from "node:crypto";
10
10
 
11
+ import type { AdmissionPolicy } from "@vellumai/gateway-client";
11
12
  import type { ServerWebSocket } from "bun";
12
13
 
13
14
  import {
@@ -47,6 +48,7 @@ import {
47
48
  updateCallSession,
48
49
  } from "./call-store.js";
49
50
  import { ConversationRelayTransport } from "./call-transport.js";
51
+ import { getChannelAdmissionPolicy } from "./channel-admission-reader.js";
50
52
  import { finalizeCall } from "./finalize-call.js";
51
53
  import {
52
54
  classifyWaitUtterance,
@@ -170,6 +172,11 @@ export function setRelayBroadcast(fn: (msg: ServerMessage) => void): void {
170
172
  */
171
173
  type RelayConnectionState =
172
174
  | "connected"
175
+ // Transient state held synchronously from the start of handleSetup until
176
+ // routing/ACL completes. Prompts arriving in this window are dropped so a
177
+ // not-yet-authorized caller's speech is never persisted/emitted before the
178
+ // admission floor and trust ACL are applied.
179
+ | "setting_up"
173
180
  | "verification_pending"
174
181
  | "awaiting_name"
175
182
  | "awaiting_guardian_decision"
@@ -550,15 +557,58 @@ export class RelayConnection {
550
557
  "ConversationRelay setup received",
551
558
  );
552
559
 
560
+ // Enter the setup-pending state synchronously, before any await. The
561
+ // admission-policy read below yields the event loop, and inbound frames are
562
+ // dispatched fire-and-forget, so a `prompt` frame can arrive before routing
563
+ // completes. handlePrompt drops prompts while in "setting_up" so a blocked
564
+ // or admission-denied caller's speech is never stored before the ACL runs.
565
+ this.connectionState = "setting_up";
566
+
553
567
  const session = getCallSession(this.callSessionId);
554
568
  this.recordSetupBookkeeping(session, msg);
555
569
 
570
+ // Resolve the phone channel's inbound admission floor. The reader fails
571
+ // open to `null` by contract, so a transport hiccup admits the caller.
572
+ const admissionPolicy = await getChannelAdmissionPolicy("phone");
573
+
574
+ try {
575
+ await this.routeSetupOutcome(msg, session, admissionPolicy);
576
+ } catch (err) {
577
+ // Never leave the connection stranded in "setting_up": a setup that
578
+ // throws before reaching a terminal outcome would otherwise drop every
579
+ // prompt forever. Tear the call down instead.
580
+ log.error(
581
+ { err, callSessionId: this.callSessionId },
582
+ "Setup routing failed — disconnecting",
583
+ );
584
+ this.connectionState = "disconnecting";
585
+ updateCallSession(this.callSessionId, {
586
+ status: "failed",
587
+ endedAt: Date.now(),
588
+ lastError: "Setup routing failed",
589
+ });
590
+ this.endSession("Setup routing failed");
591
+ }
592
+ }
593
+
594
+ /**
595
+ * Apply the routing/ACL outcome for a setup frame. Synchronous helpers that
596
+ * fire-and-forget (greeting, verification, name capture) are awaited here only
597
+ * when they themselves are async. Each reachable outcome transitions the
598
+ * connection out of "setting_up" into its terminal state.
599
+ */
600
+ private async routeSetupOutcome(
601
+ msg: RelaySetupMessage,
602
+ session: ReturnType<typeof getCallSession>,
603
+ admissionPolicy: AdmissionPolicy | null,
604
+ ): Promise<void> {
556
605
  const { outcome, resolved } = routeSetup({
557
606
  callSessionId: this.callSessionId,
558
607
  session,
559
608
  from: msg.from,
560
609
  to: msg.to,
561
610
  customParameters: msg.customParameters,
611
+ admissionPolicy,
562
612
  });
563
613
 
564
614
  const initialTrustContext = toTrustContext(
@@ -634,6 +684,9 @@ export class RelayConnection {
634
684
  );
635
685
  }
636
686
  }
687
+ // Routing/ACL cleared — leave the setup-pending state so subsequent
688
+ // prompts are handled normally.
689
+ this.connectionState = "connected";
637
690
  this.startNormalCallFlow(controller, outcome.isInbound);
638
691
  return;
639
692
  }
@@ -1822,6 +1875,13 @@ export class RelayConnection {
1822
1875
  return;
1823
1876
  }
1824
1877
 
1878
+ // Prompts arriving before setup routing/ACL completes are dropped — never
1879
+ // persisted or emitted — so a not-yet-authorized caller's speech can't be
1880
+ // stored ahead of the admission floor / trust ACL decision.
1881
+ if (this.connectionState === "setting_up") {
1882
+ return;
1883
+ }
1884
+
1825
1885
  if (!msg.last) {
1826
1886
  // Partial transcript, wait for final
1827
1887
  return;
@@ -2024,6 +2084,12 @@ export class RelayConnection {
2024
2084
  return;
2025
2085
  }
2026
2086
 
2087
+ // Drop DTMF arriving before setup routing/ACL completes (same reasoning as
2088
+ // handlePrompt): don't record input from a not-yet-authorized caller.
2089
+ if (this.connectionState === "setting_up") {
2090
+ return;
2091
+ }
2092
+
2027
2093
  // Ignore DTMF during name capture and guardian decision wait
2028
2094
  if (
2029
2095
  this.connectionState === "awaiting_name" ||
@@ -6,6 +6,8 @@
6
6
  * next — without performing any side effects itself.
7
7
  */
8
8
 
9
+ import type { AdmissionPolicy } from "@vellumai/gateway-client";
10
+
9
11
  import { getConfig } from "../config/loader.js";
10
12
  import { findActiveVoiceInvites } from "../memory/invite-store.js";
11
13
  import {
@@ -14,6 +16,10 @@ import {
14
16
  } from "../runtime/actor-trust-resolver.js";
15
17
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
16
18
  import { getPendingSession } from "../runtime/channel-verification-service.js";
19
+ import {
20
+ type AdmissionPolicyResult,
21
+ enforceAdmissionPolicy,
22
+ } from "../runtime/routes/inbound-stages/admission-policy.js";
17
23
  import { getLogger } from "../util/logger.js";
18
24
  import type { CallSession } from "./types.js";
19
25
 
@@ -27,6 +33,12 @@ interface SetupContext {
27
33
  from: string;
28
34
  to: string;
29
35
  customParameters?: Record<string, string>;
36
+ /**
37
+ * Per-channel inbound admission floor for the `phone` channel, supplied by
38
+ * the caller. When absent/`null`, the floor check is skipped entirely —
39
+ * preserving all pre-admission behavior.
40
+ */
41
+ admissionPolicy?: AdmissionPolicy | null;
30
42
  }
31
43
 
32
44
  // ── Setup outcomes ───────────────────────────────────────────────────
@@ -184,7 +196,55 @@ export function routeSetup(ctx: SetupContext): {
184
196
  // ── Inbound call ACL evaluation ─────────────────────────────────
185
197
  const pendingChallenge = getPendingSession("phone");
186
198
 
187
- if (actorTrust.trustClass === "unknown" && !pendingChallenge) {
199
+ // An admission floor is "active" only when a policy applies and no pending
200
+ // verification challenge is in flight. While active, the floor IS the access
201
+ // decision: an admitted caller bypasses the legacy identity flows
202
+ // (unverified_caller / name_capture) and connects directly. When inactive
203
+ // (null policy, flag off, exempt channel, reader failed open, or a pending
204
+ // challenge), those legacy flows are preserved unchanged.
205
+ const floorActive = ctx.admissionPolicy != null && !pendingChallenge;
206
+
207
+ // Inbound admission floor verdict; defaults to admitted when inactive.
208
+ const floorVerdict = floorActive
209
+ ? enforceAdmissionPolicy({
210
+ sourceChannel: "phone",
211
+ trustClass: actorTrust.trustClass,
212
+ memberStatus: actorTrust.memberRecord?.channel.status,
213
+ policy: ctx.admissionPolicy!,
214
+ })
215
+ : ({ admitted: true } as const);
216
+
217
+ // Floor-deny outcome shared by the unknown-caller and member-caller branches.
218
+ // Live calls cannot await async re-verification, so the floor's
219
+ // `shouldChallenge` upgrade UX is not surfaced — same rationale as `escalate`.
220
+ const floorDeny = (
221
+ denyVerdict: Extract<AdmissionPolicyResult, { admitted: false }>,
222
+ ) => {
223
+ log.info(
224
+ {
225
+ callSessionId: ctx.callSessionId,
226
+ from: ctx.from,
227
+ trustClass: actorTrust.trustClass,
228
+ effectivePolicy: denyVerdict.effectivePolicy,
229
+ },
230
+ "Inbound voice ACL: admission floor denied caller",
231
+ );
232
+ return {
233
+ outcome: {
234
+ action: "deny" as const,
235
+ message:
236
+ "This number is not authorized to reach the assistant right now.",
237
+ logReason: `Inbound voice admission floor: ${denyVerdict.effectivePolicy}`,
238
+ },
239
+ resolved,
240
+ };
241
+ };
242
+
243
+ if (
244
+ (actorTrust.trustClass === "unknown" ||
245
+ actorTrust.trustClass === "unverified_contact") &&
246
+ !pendingChallenge
247
+ ) {
188
248
  // Check for blocked caller
189
249
  if (actorTrust.memberRecord?.channel.status === "blocked") {
190
250
  log.info(
@@ -241,6 +301,21 @@ export function routeSetup(ctx: SetupContext): {
241
301
  };
242
302
  }
243
303
 
304
+ // When a floor is active it is the access decision: an admitted caller
305
+ // connects directly (skipping unverified_caller / name_capture), and a
306
+ // below-floor caller is denied. Invites (handled above) bypass the floor
307
+ // as an explicit grant. When the floor is inactive (null policy), fall
308
+ // through to the legacy identity flows below.
309
+ if (floorActive) {
310
+ if (!floorVerdict.admitted) {
311
+ return floorDeny(floorVerdict);
312
+ }
313
+ return {
314
+ outcome: { action: "normal_call" as const, isInbound: true },
315
+ resolved,
316
+ };
317
+ }
318
+
244
319
  // Known caller whose channel hasn't passed verification yet —
245
320
  // mirrors the gateway's pre-intercept (twilio-voice-webhook.ts) so
246
321
  // calls slipping past it (e.g. canonicalization mismatch between
@@ -345,6 +420,12 @@ export function routeSetup(ctx: SetupContext): {
345
420
  };
346
421
  }
347
422
 
423
+ // Admission floor: deny member/guardian callers below the floor (e.g.
424
+ // `guardian_only` denies a trusted_contact).
425
+ if (!floorVerdict.admitted) {
426
+ return floorDeny(floorVerdict);
427
+ }
428
+
348
429
  // Guardian and trusted-contact callers proceed normally
349
430
  return {
350
431
  outcome: { action: "normal_call", isInbound: true },
@@ -54,6 +54,7 @@ import {
54
54
  releaseCallbackClaim,
55
55
  updateCallSession,
56
56
  } from "./call-store.js";
57
+ import { getChannelAdmissionPolicy } from "./channel-admission-reader.js";
57
58
  import { routeSetup } from "./relay-setup-router.js";
58
59
  import { resolveCallHints } from "./stt-hints.js";
59
60
  import { resolveTelephonySttRouting } from "./telephony-stt-routing.js";
@@ -277,10 +278,10 @@ const TWIML_HEADERS = { "Content-Type": "text/xml" } as const;
277
278
  * query for outbound calls). Returns a TwiML string. Throws RouteError
278
279
  * subclasses on failure.
279
280
  */
280
- function processVoiceWebhook(
281
+ async function processVoiceWebhook(
281
282
  params: Record<string, string>,
282
283
  callSessionId: string | null,
283
- ): string {
284
+ ): Promise<string> {
284
285
  const callSid = params.CallSid ?? null;
285
286
  const callerFrom = params.From ?? "";
286
287
  const callerTo = params.To ?? "";
@@ -371,7 +372,7 @@ export async function handleVoiceWebhook(req: Request): Promise<Response> {
371
372
  const params = Object.fromEntries(formBody.entries());
372
373
 
373
374
  try {
374
- return twimlResponse(processVoiceWebhook(params, callSessionId));
375
+ return twimlResponse(await processVoiceWebhook(params, callSessionId));
375
376
  } catch (err) {
376
377
  if (err instanceof RouteError) {
377
378
  return new Response(err.message, { status: err.statusCode });
@@ -399,7 +400,7 @@ export async function handleVoiceWebhook(req: Request): Promise<Response> {
399
400
  * the Twilio setup payload. The persisted call session mode is the
400
401
  * primary signal for deterministic flow selection in the relay server.
401
402
  */
402
- function buildVoiceWebhookTwiml(
403
+ async function buildVoiceWebhookTwiml(
403
404
  callSessionId: string,
404
405
  sessionContext: {
405
406
  task: string | null;
@@ -410,7 +411,7 @@ function buildVoiceWebhookTwiml(
410
411
  inviteGuardianName: string | null;
411
412
  } | null,
412
413
  verificationSessionId?: string | null,
413
- ): string {
414
+ ): Promise<string> {
414
415
  const cfg = loadConfig();
415
416
  const profile = resolveVoiceQualityProfile(cfg);
416
417
 
@@ -476,11 +477,19 @@ function buildVoiceWebhookTwiml(
476
477
  const from = sessionContext?.fromNumber ?? "";
477
478
  const to = sessionContext?.toNumber ?? "";
478
479
 
480
+ // Resolve the phone channel's inbound admission floor so this preflight
481
+ // classification matches the real enforcement at stream start — a
482
+ // floor-denied caller is recognized as `deny` here, not `normal_call`.
483
+ // The reader fails open to `null` by contract, so a transport hiccup
484
+ // admits the caller.
485
+ const admissionPolicy = await getChannelAdmissionPolicy("phone");
486
+
479
487
  const { outcome } = routeSetup({
480
488
  callSessionId,
481
489
  session: session ?? null,
482
490
  from,
483
491
  to,
492
+ admissionPolicy,
484
493
  });
485
494
 
486
495
  // The media-stream transport supports normal_call and deny (which speaks
@@ -780,9 +789,9 @@ const EMPTY_TWIML = '<?xml version="1.0" encoding="UTF-8"?><Response/>';
780
789
  * Internal voice-webhook handler for gateway→runtime forwarding.
781
790
  * Accepts JSON body `{ params, originalUrl? }` from the gateway.
782
791
  */
783
- export function handleInternalVoiceWebhook({
792
+ export async function handleInternalVoiceWebhook({
784
793
  body = {},
785
- }: RouteHandlerArgs): RouteResponse {
794
+ }: RouteHandlerArgs): Promise<RouteResponse> {
786
795
  const { params = {}, originalUrl } = body as {
787
796
  params?: Record<string, string>;
788
797
  originalUrl?: string;
@@ -798,7 +807,7 @@ export function handleInternalVoiceWebhook({
798
807
  }
799
808
  }
800
809
 
801
- const twiml = processVoiceWebhook(params, callSessionId);
810
+ const twiml = await processVoiceWebhook(params, callSessionId);
802
811
  return new RouteResponse(twiml, TWIML_HEADERS);
803
812
  }
804
813
 
@@ -553,8 +553,8 @@ export async function startVoiceTurn(
553
553
  } else if (msg.type === "secret_request") {
554
554
  if (usesLocalInteractiveApprovals) {
555
555
  // Local live voice runs alongside the desktop client, which has a
556
- // secret-entry UI (SecretPromptManager). Forward the broadcast and
557
- // let the prompter's existing registration handle the response.
556
+ // secret-entry UI. Forward the broadcast and let the prompter's
557
+ // existing registration handle the response.
558
558
  broadcastMessage(msg);
559
559
  return;
560
560
  }
@@ -13,6 +13,7 @@ interface ClientEntryJSON {
13
13
  machineName?: string;
14
14
  connectedAt: string;
15
15
  lastActiveAt: string;
16
+ degraded?: boolean;
16
17
  }
17
18
 
18
19
  interface ListClientsResponse {
@@ -111,6 +112,7 @@ Examples:
111
112
  "LABEL",
112
113
  "CONNECTED",
113
114
  "LAST ACTIVE",
115
+ "STATUS",
114
116
  ];
115
117
  const rows: string[][] = entries.map((e: ClientEntryJSON) => [
116
118
  e.clientId,
@@ -119,6 +121,7 @@ Examples:
119
121
  e.machineName ?? "—",
120
122
  formatRelativeTime(e.connectedAt),
121
123
  formatRelativeTime(e.lastActiveAt),
124
+ e.degraded ? "degraded" : "—",
122
125
  ]);
123
126
 
124
127
  // Calculate column widths
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
- import type { ComparisonReport } from "../../../memory/v2/harness/runner.js";
3
+ import type { ComparisonReport } from "../../../../memory/v2/harness/runner.js";
4
4
  import {
5
5
  renderComparisonReport,
6
6
  renderTurnTrace,
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * Validates:
5
5
  * - Subcommand registration (reembed, reembed-skills, activation, validate,
6
- * ema, simulate) under `memory v2`. The `memory` parent is created by the
7
- * v2 registrar itself; v1 had its own subcommands but those were retired.
6
+ * ema, simulate) under `memory v2`. v1 had its own subcommands but those
7
+ * were retired.
8
8
  * - Each mutating subcommand maps to the right `memory_v2_backfill` op.
9
9
  * - `validate` calls `memory_v2_validate` and pretty-prints the report.
10
10
  * - `reembed-skills` calls `memory_v2_reembed_skills` synchronously.
@@ -40,7 +40,7 @@ let logOutput: string[] = [];
40
40
  // Mocks
41
41
  // ---------------------------------------------------------------------------
42
42
 
43
- mock.module("../../../ipc/cli-client.js", () => ({
43
+ mock.module("../../../../ipc/cli-client.js", () => ({
44
44
  cliIpcCall: async (method: string, params?: any) => {
45
45
  lastIpcCall = { method, params };
46
46
  return mockIpcResult;
@@ -56,7 +56,7 @@ const fakeLogger = {
56
56
  error: capture,
57
57
  debug: () => {},
58
58
  };
59
- mock.module("../../../util/logger.js", () => ({
59
+ mock.module("../../../../util/logger.js", () => ({
60
60
  getLogger: () => fakeLogger,
61
61
  getCliLogger: () => fakeLogger,
62
62
  }));
@@ -72,8 +72,8 @@ const { registerMemoryV2Command } = await import("../memory-v2.js");
72
72
  // ---------------------------------------------------------------------------
73
73
 
74
74
  /**
75
- * Build a fresh program and register the v2 subgroup. The registrar creates
76
- * the `memory` parent itself, so callers don't need to stub one.
75
+ * Build a fresh program with a `memory` parent and register the v2 subgroup
76
+ * under it mirroring how `registerMemoryCommand` wires the namespace.
77
77
  */
78
78
  function buildProgram(): Command {
79
79
  const program = new Command();
@@ -82,7 +82,8 @@ function buildProgram(): Command {
82
82
  writeErr: () => {},
83
83
  writeOut: () => {},
84
84
  });
85
- registerMemoryV2Command(program);
85
+ const memory = program.command("memory");
86
+ registerMemoryV2Command(memory);
86
87
  return program;
87
88
  }
88
89
 
@@ -40,7 +40,7 @@ let logOutput: string[] = [];
40
40
  // Mocks
41
41
  // ---------------------------------------------------------------------------
42
42
 
43
- mock.module("../../../ipc/cli-client.js", () => ({
43
+ mock.module("../../../../ipc/cli-client.js", () => ({
44
44
  cliIpcCall: async (
45
45
  method: string,
46
46
  params?: any,
@@ -60,7 +60,7 @@ const fakeLogger = {
60
60
  error: capture,
61
61
  debug: () => {},
62
62
  };
63
- mock.module("../../../util/logger.js", () => ({
63
+ mock.module("../../../../util/logger.js", () => ({
64
64
  getLogger: () => fakeLogger,
65
65
  getCliLogger: () => fakeLogger,
66
66
  }));
@@ -82,7 +82,8 @@ function buildProgram(): Command {
82
82
  writeErr: () => {},
83
83
  writeOut: () => {},
84
84
  });
85
- registerMemoryV3Command(program);
85
+ const memory = program.command("memory");
86
+ registerMemoryV3Command(memory);
86
87
  return program;
87
88
  }
88
89
 
@@ -144,7 +145,7 @@ describe("subcommand registration", () => {
144
145
  const v3 = memory!.commands.find((c) => c.name() === "v3");
145
146
  expect(v3).toBeDefined();
146
147
  const subcommandNames = v3!.commands.map((c) => c.name()).sort();
147
- expect(subcommandNames).toEqual(["backfill-sections", "rebuild-index"]);
148
+ expect(subcommandNames).toEqual(["backfill-sections", "eval", "rebuild-index"]);
148
149
  });
149
150
  });
150
151
 
@@ -0,0 +1,30 @@
1
+ import type { Command } from "commander";
2
+
3
+ import { registerCommand } from "../../lib/register-command.js";
4
+ import { registerMemoryV2Command } from "./memory-v2.js";
5
+ import { registerMemoryV3Command } from "./memory-v3.js";
6
+
7
+ export function registerMemoryCommand(program: Command): void {
8
+ registerCommand(program, {
9
+ name: "memory",
10
+ transport: "ipc",
11
+ description: "Inspect and maintain the assistant memory subsystem",
12
+ build: (memory) => {
13
+ memory.addHelpText(
14
+ "after",
15
+ `
16
+ The memory subsystem retrieves concept pages two ways: the v2 concept-page
17
+ activation model (prose pages with directed edges) and the v3 section-lane
18
+ model (section-grain lanes cached as live shadow state). Each subgroup exposes
19
+ operator-facing maintenance verbs — reindexing, backfills, validation, and evals.
20
+
21
+ Examples:
22
+ $ assistant memory v2 validate
23
+ $ assistant memory v3 rebuild-index`,
24
+ );
25
+
26
+ registerMemoryV2Command(memory);
27
+ registerMemoryV3Command(memory);
28
+ },
29
+ });
30
+ }
@@ -9,7 +9,7 @@
9
9
  * type-only imports but forbids pulling in daemon runtime modules.
10
10
  */
11
11
 
12
- import type { ComparisonReport } from "../../memory/v2/harness/runner.js";
12
+ import type { ComparisonReport } from "../../../memory/v2/harness/runner.js";
13
13
 
14
14
  function sortedKs(report: ComparisonReport): number[] {
15
15
  return [...report.ks].sort((a, b) => a - b);