@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
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
- import { buildToolApprovalSeedContentBlocks } from "../notifications/tool-approval-copy.js";
3
+ import { buildToolApprovalSeedContentBlocks } from "../notifications/approval-card-data.js";
4
4
 
5
5
  describe("buildToolApprovalSeedContentBlocks", () => {
6
6
  const toolApprovalPayload: Record<string, unknown> = {
@@ -1,11 +1,11 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
- // Toggle for the collectUsageData opt-out gate the listener consults when
3
+ // Toggle for the share_analytics opt-out gate the listener consults when
4
4
  // populating the telemetry columns.
5
- let collectUsageData = true;
5
+ let shareAnalytics = true;
6
6
 
7
- mock.module("../config/loader.js", () => ({
8
- getConfig: () => ({ collectUsageData }),
7
+ mock.module("../platform/consent-cache.js", () => ({
8
+ getCachedShareAnalytics: () => shareAnalytics,
9
9
  }));
10
10
 
11
11
  import { createToolAuditListener } from "../events/tool-audit-listener.js";
@@ -17,7 +17,7 @@ import {
17
17
 
18
18
  describe("tool audit listener", () => {
19
19
  beforeEach(() => {
20
- collectUsageData = true;
20
+ shareAnalytics = true;
21
21
  });
22
22
 
23
23
  test("records executed events with truncated output", () => {
@@ -361,8 +361,8 @@ describe("tool audit listener", () => {
361
361
  );
362
362
  });
363
363
 
364
- test("persists NULL telemetry columns when usage data collection is opted out", () => {
365
- collectUsageData = false;
364
+ test("persists NULL telemetry columns when share_analytics is opted out", () => {
365
+ shareAnalytics = false;
366
366
  const records: ToolInvocationRecord[] = [];
367
367
  const listener = createToolAuditListener((record) => records.push(record));
368
368
 
@@ -34,9 +34,6 @@ const mockConfig = {
34
34
  permissions: {
35
35
  mode: "workspace" as const,
36
36
  },
37
- // The audit listener nulls the telemetry columns when this is false; the
38
- // end-to-end listener tests below assert the opted-in sizing behavior.
39
- collectUsageData: true,
40
37
  };
41
38
 
42
39
  let checkerDecision: "allow" | "prompt" | "deny" = "allow";
@@ -56,6 +53,12 @@ mock.module("../config/loader.js", () => ({
56
53
  setNestedValue: () => {},
57
54
  }));
58
55
 
56
+ // Analytics consent is granted so the audit listener populates the telemetry
57
+ // columns; the end-to-end listener tests below assert the opted-in sizing.
58
+ mock.module("../platform/consent-cache.js", () => ({
59
+ getCachedShareAnalytics: () => true,
60
+ }));
61
+
59
62
  mock.module("../util/logger.js", () => ({
60
63
  getLogger: () =>
61
64
  new Proxy({} as Record<string, unknown>, {
@@ -587,36 +587,6 @@ describe("isSideEffectTool", () => {
587
587
  expect(isSideEffectTool("nonexistent_tool")).toBe(false);
588
588
  expect(isSideEffectTool("")).toBe(false);
589
589
  });
590
-
591
- describe("action-aware classification for mixed-action tools", () => {
592
- test("credential_store store is a side-effect", () => {
593
- expect(isSideEffectTool("credential_store", { action: "store" })).toBe(
594
- true,
595
- );
596
- });
597
-
598
- test("credential_store delete is a side-effect", () => {
599
- expect(isSideEffectTool("credential_store", { action: "delete" })).toBe(
600
- true,
601
- );
602
- });
603
-
604
- test("credential_store prompt is a side-effect", () => {
605
- expect(isSideEffectTool("credential_store", { action: "prompt" })).toBe(
606
- true,
607
- );
608
- });
609
-
610
- test("credential_store list is NOT a side-effect", () => {
611
- expect(isSideEffectTool("credential_store", { action: "list" })).toBe(
612
- false,
613
- );
614
- });
615
-
616
- test("credential_store without input is NOT a side-effect", () => {
617
- expect(isSideEffectTool("credential_store")).toBe(false);
618
- });
619
- });
620
590
  });
621
591
 
622
592
  // ---------------------------------------------------------------------------
@@ -751,10 +721,6 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
751
721
  { name: "document_create", input: { title: "doc", content: "body" } },
752
722
  { name: "document_update", input: { id: "doc-1", content: "updated" } },
753
723
  { name: "document_delete", input: { surface_id: "doc-1" } },
754
- {
755
- name: "credential_store",
756
- input: { action: "store", name: "api-key", value: "secret" },
757
- },
758
724
  ];
759
725
 
760
726
  for (const { name, input } of sideEffectTools) {
@@ -817,51 +783,6 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
817
783
  expect(promptCalled).toBe(true);
818
784
  });
819
785
 
820
- // ── Credential store action-aware (PR fix9) ──────────
821
-
822
- test("credential_store store forces prompt under forcePromptSideEffects", async () => {
823
- checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
824
-
825
- const executor = new ToolExecutor(makeTrackingPrompter());
826
- const result = await executor.execute(
827
- "credential_store",
828
- { action: "store", name: "api-key", value: "sk-secret-123" },
829
- makeContext({ forcePromptSideEffects: true }),
830
- );
831
-
832
- expect(result.isError).toBe(false);
833
- expect(promptCalled).toBe(true);
834
- });
835
-
836
- test("credential_store delete forces prompt under forcePromptSideEffects", async () => {
837
- checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
838
-
839
- const executor = new ToolExecutor(makeTrackingPrompter());
840
- const result = await executor.execute(
841
- "credential_store",
842
- { action: "delete", name: "api-key" },
843
- makeContext({ forcePromptSideEffects: true }),
844
- );
845
-
846
- expect(result.isError).toBe(false);
847
- expect(promptCalled).toBe(true);
848
- });
849
-
850
- test("credential_store list does NOT force prompt under forcePromptSideEffects", async () => {
851
- checkResultOverride = { decision: "allow", reason: "Matched trust rule" };
852
-
853
- const executor = new ToolExecutor(makeTrackingPrompter());
854
- const result = await executor.execute(
855
- "credential_store",
856
- { action: "list" },
857
- makeContext({ forcePromptSideEffects: true }),
858
- );
859
-
860
- expect(result.isError).toBe(false);
861
- // list is read-only — must NOT trigger forced prompting
862
- expect(promptCalled).toBe(false);
863
- });
864
-
865
786
  // ── Workspace mode + forcePromptSideEffects interaction ──────────
866
787
 
867
788
  test("workspace mode allow → prompt promotion still works for side-effect tools under forcePromptSideEffects", async () => {
@@ -169,8 +169,10 @@ async function simulateNotifierPoll(params: {
169
169
  notifiedRequestIds,
170
170
  } = params;
171
171
 
172
- // Gate check: only trusted contacts with guardian route
173
- if (trustClass !== "trusted_contact" || !guardianExternalUserId) {
172
+ // Gate check: only identity-known non-guardian contacts with guardian route
173
+ const isIdentityKnownNonGuardian =
174
+ trustClass === "trusted_contact" || trustClass === "unverified_contact";
175
+ if (!isIdentityKnownNonGuardian || !guardianExternalUserId) {
174
176
  return false;
175
177
  }
176
178
 
@@ -92,7 +92,7 @@ mock.module("../runtime/channel-verification-service.js", () => ({
92
92
  return null;
93
93
  },
94
94
  createOutboundSession: () => ({
95
- conversationId: "test-session",
95
+ sessionId: "test-session",
96
96
  secret: "123456",
97
97
  }),
98
98
  bindSessionIdentity: () => {},
@@ -108,16 +108,23 @@ mock.module("../runtime/channel-verification-service.js", () => ({
108
108
  }),
109
109
  }));
110
110
 
111
- // Mock gateway client — capture delivery calls
111
+ // Mock gateway client — capture delivery calls. `failDeliveryWhen` lets a test
112
+ // simulate a delivery failure for a specific payload (e.g. a DM that can't be
113
+ // opened) so fallback paths can be exercised.
112
114
  const deliveredReplies: Array<{
113
115
  url: string;
114
116
  payload: Record<string, unknown>;
115
117
  }> = [];
118
+ let failDeliveryWhen: ((payload: Record<string, unknown>) => boolean) | null =
119
+ null;
116
120
  mock.module("../runtime/gateway-client.js", () => ({
117
121
  deliverChannelReply: async (
118
122
  url: string,
119
123
  payload: Record<string, unknown>,
120
124
  ) => {
125
+ if (failDeliveryWhen?.(payload)) {
126
+ throw new Error("simulated delivery failure");
127
+ }
121
128
  deliveredReplies.push({ url, payload });
122
129
  return { ok: true };
123
130
  },
@@ -150,6 +157,7 @@ mock.module("../config/env.js", () => ({
150
157
  import { applyCanonicalGuardianDecision } from "../approvals/guardian-decision-primitive.js";
151
158
  import type { ActorContext } from "../approvals/guardian-request-resolvers.js";
152
159
  import { getResolver } from "../approvals/guardian-request-resolvers.js";
160
+ import { upsertContactChannel } from "../contacts/contacts-write.js";
153
161
  import type { TrustContext } from "../daemon/trust-context.js";
154
162
  import {
155
163
  createCanonicalGuardianRequest,
@@ -564,7 +572,7 @@ describe("(d) unknown actor flow: fail-closed with no interactive approval", ()
564
572
 
565
573
  expect("skipped" in result && result.skipped).toBe(true);
566
574
  if ("skipped" in result) {
567
- expect(result.reason).toBe("not_trusted_contact");
575
+ expect(result.reason).toBe("not_bridgeable_trust_class");
568
576
  }
569
577
  });
570
578
  });
@@ -1088,3 +1096,212 @@ describe("cross-milestone integration checks", () => {
1088
1096
  });
1089
1097
  });
1090
1098
 
1099
+ // ===========================================================================
1100
+ // (g) access_request resolver: requester verification-code delivery
1101
+ //
1102
+ // On approval the requester must receive the 6-digit code so the guardian
1103
+ // never has to relay it by hand. On Slack the code is DM'd straight to the
1104
+ // requester (a private path is guaranteed via their user ID); other channels
1105
+ // keep the courier message because a private path to the requester is not
1106
+ // guaranteed there (e.g. a group chat would leak the secret).
1107
+ // ===========================================================================
1108
+
1109
+ describe("(g) access_request resolver: requester code delivery", () => {
1110
+ const REQUESTER_UID = "U_REQUESTER";
1111
+ const GUARDIAN_UID = "U_GUARDIAN";
1112
+
1113
+ function createAccessRequest(overrides: Record<string, unknown> = {}) {
1114
+ return createCanonicalGuardianRequest({
1115
+ id: `access-req-${Date.now()}-${Math.random().toString(36).slice(2)}`,
1116
+ kind: "access_request",
1117
+ sourceType: "channel",
1118
+ sourceChannel: "slack",
1119
+ conversationId: "conv-access-slack",
1120
+ requesterExternalUserId: REQUESTER_UID,
1121
+ requesterChatId: "C_SHARED_CHANNEL",
1122
+ guardianExternalUserId: GUARDIAN_UID,
1123
+ guardianPrincipalId: "test-principal-id",
1124
+ toolName: "ingress_access_request",
1125
+ expiresAt: Date.now() + 60_000,
1126
+ ...overrides,
1127
+ });
1128
+ }
1129
+
1130
+ beforeEach(() => {
1131
+ resetTables();
1132
+ deliveredReplies.length = 0;
1133
+ emittedSignals.length = 0;
1134
+ failDeliveryWhen = null;
1135
+ });
1136
+
1137
+ test("on-channel Slack approval DMs the verification code to the requester", async () => {
1138
+ const req = createAccessRequest();
1139
+
1140
+ const result = await applyCanonicalGuardianDecision({
1141
+ requestId: req.id,
1142
+ action: "approve_once",
1143
+ actorContext: guardianActor({
1144
+ channel: "slack",
1145
+ actorExternalUserId: GUARDIAN_UID,
1146
+ }),
1147
+ channelDeliveryContext: {
1148
+ replyCallbackUrl:
1149
+ "http://localhost:3000/deliver/slack?threadTs=111.222",
1150
+ guardianChatId: "C_SHARED_CHANNEL",
1151
+ assistantId: "self",
1152
+ },
1153
+ });
1154
+
1155
+ expect(result.applied).toBe(true);
1156
+
1157
+ // The requester receives the actual code in their DM (chatId = user ID),
1158
+ // not a "ask the guardian" courier message.
1159
+ const requesterCodeReply = deliveredReplies.find(
1160
+ (r) =>
1161
+ r.payload.chatId === REQUESTER_UID &&
1162
+ typeof r.payload.text === "string" &&
1163
+ (r.payload.text as string).includes("123456"),
1164
+ );
1165
+ expect(requesterCodeReply).toBeDefined();
1166
+ expect(requesterCodeReply!.payload.text).toContain(
1167
+ "your access request was approved",
1168
+ );
1169
+ // The code DM is durable, never ephemeral.
1170
+ expect(requesterCodeReply!.payload.ephemeral).toBeUndefined();
1171
+ // threadTs (the guardian's channel thread) is stripped for the DM.
1172
+ expect(requesterCodeReply!.url).not.toContain("threadTs");
1173
+
1174
+ // No courier "receive from the guardian" message goes to the requester.
1175
+ const courier = deliveredReplies.find(
1176
+ (r) =>
1177
+ typeof r.payload.text === "string" &&
1178
+ (r.payload.text as string).includes("receive from the guardian"),
1179
+ );
1180
+ expect(courier).toBeUndefined();
1181
+ });
1182
+
1183
+ test("desktop-decided approval DMs the code to the Slack requester via the deliver path", async () => {
1184
+ const req = createAccessRequest();
1185
+
1186
+ const result = await applyCanonicalGuardianDecision({
1187
+ requestId: req.id,
1188
+ action: "approve_once",
1189
+ // Desktop decision: no channelDeliveryContext, actor on the vellum channel.
1190
+ actorContext: guardianActor({
1191
+ channel: "vellum",
1192
+ actorExternalUserId: undefined,
1193
+ }),
1194
+ });
1195
+
1196
+ expect(result.applied).toBe(true);
1197
+
1198
+ const requesterCodeReply = deliveredReplies.find(
1199
+ (r) =>
1200
+ r.payload.chatId === REQUESTER_UID &&
1201
+ typeof r.payload.text === "string" &&
1202
+ (r.payload.text as string).includes("123456"),
1203
+ );
1204
+ expect(requesterCodeReply).toBeDefined();
1205
+ expect(requesterCodeReply!.url).toContain("/deliver/slack");
1206
+ expect(requesterCodeReply!.payload.text).toContain(
1207
+ "your access request was approved",
1208
+ );
1209
+ });
1210
+
1211
+ test("non-Slack channel keeps the courier message and never delivers the code to the requester chat", async () => {
1212
+ const req = createAccessRequest({
1213
+ sourceChannel: "telegram",
1214
+ requesterChatId: "requester-chat-1",
1215
+ conversationId: "conv-access-telegram",
1216
+ });
1217
+
1218
+ const result = await applyCanonicalGuardianDecision({
1219
+ requestId: req.id,
1220
+ action: "approve_once",
1221
+ actorContext: guardianActor({
1222
+ channel: "telegram",
1223
+ actorExternalUserId: GUARDIAN_UID,
1224
+ }),
1225
+ channelDeliveryContext: {
1226
+ replyCallbackUrl: "http://localhost:3000/deliver/telegram",
1227
+ guardianChatId: "guardian-chat-1",
1228
+ assistantId: "self",
1229
+ },
1230
+ });
1231
+
1232
+ expect(result.applied).toBe(true);
1233
+
1234
+ // The requester is told to expect the code from the guardian; the secret is
1235
+ // never delivered to the requester's (possibly group) chat.
1236
+ const requesterReply = deliveredReplies.find(
1237
+ (r) => r.payload.chatId === "requester-chat-1",
1238
+ );
1239
+ expect(requesterReply).toBeDefined();
1240
+ expect(requesterReply!.payload.text).toContain("receive from the guardian");
1241
+ expect(requesterReply!.payload.text).not.toContain("123456");
1242
+ });
1243
+
1244
+ test("Slack shared-channel fallback posts an ephemeral notice to the channel when the DM fails", async () => {
1245
+ const req = createAccessRequest();
1246
+
1247
+ // Make the direct DM (to the U... user ID) fail so the courier fallback runs.
1248
+ failDeliveryWhen = (payload) => payload.chatId === REQUESTER_UID;
1249
+
1250
+ const result = await applyCanonicalGuardianDecision({
1251
+ requestId: req.id,
1252
+ action: "approve_once",
1253
+ actorContext: guardianActor({
1254
+ channel: "slack",
1255
+ actorExternalUserId: GUARDIAN_UID,
1256
+ }),
1257
+ channelDeliveryContext: {
1258
+ replyCallbackUrl:
1259
+ "http://localhost:3000/deliver/slack?threadTs=111.222",
1260
+ guardianChatId: "C_SHARED_CHANNEL",
1261
+ assistantId: "self",
1262
+ },
1263
+ });
1264
+
1265
+ expect(result.applied).toBe(true);
1266
+
1267
+ // The courier notice falls back to an ephemeral message targeting the
1268
+ // originating channel (C...), since chat.postEphemeral needs a channel ID —
1269
+ // not the requester's user ID.
1270
+ const courier = deliveredReplies.find(
1271
+ (r) =>
1272
+ typeof r.payload.text === "string" &&
1273
+ (r.payload.text as string).includes("receive from the guardian"),
1274
+ );
1275
+ expect(courier).toBeDefined();
1276
+ expect(courier!.payload.chatId).toBe("C_SHARED_CHANNEL");
1277
+ expect(courier!.payload.ephemeral).toBe(true);
1278
+ expect(courier!.payload.user).toBe(REQUESTER_UID);
1279
+ });
1280
+
1281
+ test("guardian-facing reply uses the requester's display name, not the raw ID", async () => {
1282
+ // Seed a contact so the resolver can resolve a display name.
1283
+ upsertContactChannel({
1284
+ sourceChannel: "slack",
1285
+ externalUserId: REQUESTER_UID,
1286
+ displayName: "Alice",
1287
+ status: "unverified",
1288
+ });
1289
+
1290
+ const req = createAccessRequest();
1291
+
1292
+ const result = await applyCanonicalGuardianDecision({
1293
+ requestId: req.id,
1294
+ action: "approve_once",
1295
+ // Desktop decision → resolver returns guardianReplyText for assertion.
1296
+ actorContext: guardianActor({
1297
+ channel: "vellum",
1298
+ actorExternalUserId: undefined,
1299
+ }),
1300
+ });
1301
+
1302
+ expect(result.applied).toBe(true);
1303
+ const replyText = result.applied ? result.resolverReplyText : undefined;
1304
+ expect(replyText).toContain("Alice");
1305
+ expect(replyText).not.toContain(REQUESTER_UID);
1306
+ });
1307
+ });
@@ -266,7 +266,7 @@ for (const config of CHANNEL_CONFIGS) {
266
266
 
267
267
  const contactResult = findContactChannel({
268
268
  channelType: config.channel,
269
- externalUserId: config.senderExternalUserId,
269
+ address: config.senderExternalUserId,
270
270
  });
271
271
 
272
272
  expect(contactResult).not.toBeNull();
@@ -288,7 +288,7 @@ for (const config of CHANNEL_CONFIGS) {
288
288
  // Should be found on this channel
289
289
  const sameChanResult = findContactChannel({
290
290
  channelType: config.channel,
291
- externalUserId: config.senderExternalUserId,
291
+ address: config.senderExternalUserId,
292
292
  });
293
293
  expect(sameChanResult).not.toBeNull();
294
294
 
@@ -296,7 +296,7 @@ for (const config of CHANNEL_CONFIGS) {
296
296
  const otherChannel = config.channel === "telegram" ? "slack" : "telegram";
297
297
  const crossChanResult = findContactChannel({
298
298
  channelType: otherChannel,
299
- externalUserId: config.senderExternalUserId,
299
+ address: config.senderExternalUserId,
300
300
  });
301
301
  expect(crossChanResult).toBeNull();
302
302
  });
@@ -102,13 +102,13 @@ describe("trusted contact verification → member activation", () => {
102
102
  // Verify: active member record exists
103
103
  const contactResult = findContactChannel({
104
104
  channelType: "telegram",
105
- externalUserId: "requester-user-123",
105
+ address: "requester-user-123",
106
106
  });
107
107
 
108
108
  expect(contactResult).not.toBeNull();
109
109
  expect(contactResult!.channel.status).toBe("active");
110
110
  expect(contactResult!.channel.policy).toBe("allow");
111
- expect(contactResult!.channel.externalUserId).toBe("requester-user-123");
111
+ expect(contactResult!.channel.address).toBe("requester-user-123");
112
112
  expect(contactResult!.channel.externalChatId).toBe("requester-chat-123");
113
113
  expect(contactResult!.contact.displayName).toBe("Requester Name");
114
114
  expect(contactResult!.channel.type).toBe("telegram");
@@ -236,7 +236,7 @@ describe("trusted contact verification → member activation", () => {
236
236
  // Simulate the ACL check that inbound-message-handler performs
237
237
  const contactResult = findContactChannel({
238
238
  channelType: "telegram",
239
- externalUserId: "requester-user-456",
239
+ address: "requester-user-456",
240
240
  });
241
241
 
242
242
  expect(contactResult).not.toBeNull();
@@ -274,7 +274,7 @@ describe("trusted contact verification → member activation", () => {
274
274
  // Member should be found via contacts
275
275
  const contactResult = findContactChannel({
276
276
  channelType: "telegram",
277
- externalUserId: "user-cross-test",
277
+ address: "user-cross-test",
278
278
  });
279
279
  expect(contactResult).not.toBeNull();
280
280
  expect(contactResult!.channel.status).toBe("active");
@@ -282,7 +282,7 @@ describe("trusted contact verification → member activation", () => {
282
282
  // Member should NOT be found for a different channel type
283
283
  const otherChannel = findContactChannel({
284
284
  channelType: "slack",
285
- externalUserId: "user-cross-test",
285
+ address: "user-cross-test",
286
286
  });
287
287
  expect(otherChannel).toBeNull();
288
288
  });
@@ -306,7 +306,7 @@ describe("trusted contact verification → member activation", () => {
306
306
  // Verify the member is indeed revoked (ACL would reject)
307
307
  const revokedResult = findContactChannel({
308
308
  channelType: "telegram",
309
- externalUserId: "user-revoked",
309
+ address: "user-revoked",
310
310
  });
311
311
  expect(revokedResult).not.toBeNull();
312
312
  expect(revokedResult!.channel.status).toBe("revoked");
@@ -345,7 +345,7 @@ describe("trusted contact verification → member activation", () => {
345
345
  // Verify: member is now active again
346
346
  const reactivatedResult = findContactChannel({
347
347
  channelType: "telegram",
348
- externalUserId: "user-revoked",
348
+ address: "user-revoked",
349
349
  });
350
350
  expect(reactivatedResult).not.toBeNull();
351
351
  expect(reactivatedResult!.channel.status).toBe("active");
@@ -390,9 +390,7 @@ describe("trusted contact verification → member activation", () => {
390
390
  // The original guardian binding should remain intact
391
391
  const guardianResult = findGuardianForChannel("telegram");
392
392
  expect(guardianResult).not.toBeNull();
393
- expect(guardianResult!.channel.externalUserId).toBe(
394
- "guardian-user-original",
395
- );
393
+ expect(guardianResult!.channel.address).toBe("guardian-user-original");
396
394
  });
397
395
 
398
396
  test("guardian inbound verification succeeds but does not create binding", async () => {
@@ -92,8 +92,21 @@ let mockRouteSetupResult: {
92
92
  },
93
93
  };
94
94
 
95
+ // Captures the last context passed to routeSetup so preflight tests can
96
+ // assert the resolved phone admission policy was threaded through.
97
+ let lastRouteSetupCtx: Record<string, unknown> | null = null;
95
98
  mock.module("../calls/relay-setup-router.js", () => ({
96
- routeSetup: () => mockRouteSetupResult,
99
+ routeSetup: (ctx: Record<string, unknown>) => {
100
+ lastRouteSetupCtx = ctx;
101
+ return mockRouteSetupResult;
102
+ },
103
+ }));
104
+
105
+ // Mock the phone channel admission reader used by the media-stream preflight.
106
+ // Tests override mockAdmissionPolicy to exercise floor classification.
107
+ let mockAdmissionPolicy: unknown = null;
108
+ mock.module("../calls/channel-admission-reader.js", () => ({
109
+ getChannelAdmissionPolicy: async () => mockAdmissionPolicy,
97
110
  }));
98
111
 
99
112
  mock.module("../config/env.js", () => ({
@@ -454,6 +467,9 @@ describe("twilio webhook routes", () => {
454
467
  mockTwilioApiValidationBody = JSON.stringify({ sid: "AC_validated" });
455
468
  // Reset STT config to defaults between tests
456
469
  mockConfigObj.services.stt.provider = "deepgram" as any;
470
+ // Reset admission policy + captured routeSetup context between tests
471
+ mockAdmissionPolicy = null;
472
+ lastRouteSetupCtx = null;
457
473
  // Reset routeSetup mock to default normal_call
458
474
  mockRouteSetupResult = {
459
475
  outcome: { action: "normal_call", isInbound: true },
@@ -1228,6 +1244,70 @@ describe("twilio webhook routes", () => {
1228
1244
  expect(twiml).not.toContain("<ConversationRelay");
1229
1245
  });
1230
1246
 
1247
+ test("media-stream: resolves the phone admission floor and threads it into the preflight routeSetup", async () => {
1248
+ mockConfigObj.services.stt.provider = "openai-whisper" as any;
1249
+ mockAdmissionPolicy = "guardian_only";
1250
+ mockRouteSetupResult = {
1251
+ outcome: { action: "normal_call", isInbound: true },
1252
+ resolved: {
1253
+ assistantId: "self",
1254
+ isInbound: true,
1255
+ otherPartyNumber: "+15559998888",
1256
+ actorTrust: { trustClass: "guardian", memberRecord: null },
1257
+ },
1258
+ };
1259
+
1260
+ const session = createTestSession("conv-ms-floor-1", "CA_ms_floor_1");
1261
+ const req = makeVoiceRequest(session.id, { CallSid: "CA_ms_floor_1" });
1262
+
1263
+ const res = await handleVoiceWebhook(req);
1264
+ expect(res.status).toBe(200);
1265
+
1266
+ // The resolved floor was passed into the preflight routeSetup so its
1267
+ // classification matches the real stream-start enforcement.
1268
+ expect(lastRouteSetupCtx).not.toBeNull();
1269
+ expect(lastRouteSetupCtx?.admissionPolicy).toBe("guardian_only");
1270
+ });
1271
+
1272
+ test("media-stream: floor-denied caller classified deny still produces Stream TwiML (deny handled at stream level)", async () => {
1273
+ mockConfigObj.services.stt.provider = "openai-whisper" as any;
1274
+ mockAdmissionPolicy = "guardian_only";
1275
+ // With the floor wired, the real router returns `deny` for a
1276
+ // below-floor trusted_contact caller; the mock reflects that.
1277
+ mockRouteSetupResult = {
1278
+ outcome: {
1279
+ action: "deny",
1280
+ message:
1281
+ "This number is not authorized to reach the assistant right now.",
1282
+ logReason: "Inbound voice admission floor: guardian_only",
1283
+ },
1284
+ resolved: {
1285
+ assistantId: "self",
1286
+ isInbound: true,
1287
+ otherPartyNumber: "+14155550000",
1288
+ actorTrust: { trustClass: "trusted_contact", memberRecord: null },
1289
+ },
1290
+ };
1291
+
1292
+ const session = createTestSession(
1293
+ "conv-ms-floor-deny-1",
1294
+ "CA_ms_floor_deny_1",
1295
+ );
1296
+ const req = makeVoiceRequest(session.id, {
1297
+ CallSid: "CA_ms_floor_deny_1",
1298
+ });
1299
+
1300
+ const res = await handleVoiceWebhook(req);
1301
+ expect(res.status).toBe(200);
1302
+
1303
+ // Policy was threaded; deny is supported on media-stream, so Stream
1304
+ // TwiML is still produced (denial spoken + torn down at stream start).
1305
+ expect(lastRouteSetupCtx?.admissionPolicy).toBe("guardian_only");
1306
+ const twiml = await res.text();
1307
+ expect(twiml).toContain("<Stream");
1308
+ expect(twiml).not.toContain("<ConversationRelay");
1309
+ });
1310
+
1231
1311
  test("media-stream: deny setup proceeds with Stream TwiML (deny is handled at stream level)", async () => {
1232
1312
  mockConfigObj.services.stt.provider = "openai-whisper" as any;
1233
1313
  mockRouteSetupResult = {
@@ -180,7 +180,7 @@ describe("redeemVoiceInviteCode", () => {
180
180
 
181
181
  const channelResult = findContactChannel({
182
182
  channelType: "phone",
183
- externalUserId: phone,
183
+ address: phone,
184
184
  });
185
185
 
186
186
  expect(channelResult).not.toBeNull();
@@ -368,7 +368,6 @@ describe("redeemVoiceInviteCode", () => {
368
368
  {
369
369
  type: "phone",
370
370
  address: phone,
371
- externalUserId: phone,
372
371
  status: "revoked",
373
372
  },
374
373
  ],
@@ -399,7 +398,7 @@ describe("redeemVoiceInviteCode", () => {
399
398
  // Verify the redeemer's phone is now bound to Mom's contact
400
399
  const contactResult = findContactChannel({
401
400
  channelType: "phone",
402
- externalUserId: phone,
401
+ address: phone,
403
402
  });
404
403
  expect(contactResult).not.toBeNull();
405
404
  expect(contactResult!.contact.id).toBe(momContact.id);