@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
@@ -55,6 +55,87 @@ function shouldUseEphemeral(sourceChannel: string, chatId: string): boolean {
55
55
  return sourceChannel === "slack" && chatId.startsWith("C");
56
56
  }
57
57
 
58
+ /**
59
+ * Deliver the verification code straight to the requester's Slack DM so the
60
+ * guardian is never an out-of-band courier for the secret.
61
+ *
62
+ * Slack is the only channel with a guaranteed private path to the requester:
63
+ * posting to their user ID (`U…`) opens a 1:1 DM. The `threadTs` query param is
64
+ * dropped because it points at the guardian's channel thread and would raise
65
+ * `thread_not_found` in the DM. The code is sent as a durable (non-ephemeral)
66
+ * message the requester can refer back to when verifying.
67
+ *
68
+ * The verification session is identity-bound to the requester, so delivering
69
+ * the code to them directly does not widen who can consume it — it only removes
70
+ * the guardian relay step.
71
+ *
72
+ * Returns whether the code was delivered.
73
+ */
74
+ async function deliverVerificationCodeToSlackRequester(params: {
75
+ replyCallbackUrl: string;
76
+ requesterExternalUserId: string;
77
+ verificationCode: string;
78
+ assistantId: string;
79
+ }): Promise<boolean> {
80
+ let callbackUrl = params.replyCallbackUrl;
81
+ try {
82
+ const url = new URL(params.replyCallbackUrl);
83
+ url.searchParams.delete("threadTs");
84
+ callbackUrl = url.toString();
85
+ } catch {
86
+ // Relative path (e.g. the desktop "/deliver/slack" target) — use as-is;
87
+ // it carries no threadTs to strip.
88
+ }
89
+
90
+ try {
91
+ await deliverChannelReply(callbackUrl, {
92
+ chatId: params.requesterExternalUserId,
93
+ text:
94
+ "Great news — your access request was approved! " +
95
+ `Your verification code is: \`${params.verificationCode}\`. ` +
96
+ "Reply with it here to complete verification. The code expires in 10 minutes.",
97
+ assistantId: params.assistantId,
98
+ });
99
+ return true;
100
+ } catch (err) {
101
+ log.error(
102
+ { err, requesterExternalUserId: params.requesterExternalUserId },
103
+ "Failed to auto-deliver verification code to Slack requester",
104
+ );
105
+ return false;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Build a requester-facing channel notice (approval/denial/courier text).
111
+ *
112
+ * Posts to the originating chat. On a Slack shared channel it goes out as an
113
+ * ephemeral message visible only to the requester — and because
114
+ * `chat.postEphemeral` needs a channel ID, `chatId` stays the channel
115
+ * (`requesterChatId`), never the requester's `U…` user ID.
116
+ */
117
+ function buildRequesterChannelNotice(params: {
118
+ channel: string;
119
+ requesterChatId: string;
120
+ requesterExternalUserId: string;
121
+ text: string;
122
+ assistantId: string;
123
+ }): Parameters<typeof deliverChannelReply>[1] {
124
+ const payload: Parameters<typeof deliverChannelReply>[1] = {
125
+ chatId: params.requesterChatId,
126
+ text: params.text,
127
+ assistantId: params.assistantId,
128
+ };
129
+ if (
130
+ shouldUseEphemeral(params.channel, params.requesterChatId) &&
131
+ params.requesterExternalUserId
132
+ ) {
133
+ payload.ephemeral = true;
134
+ payload.user = params.requesterExternalUserId;
135
+ }
136
+ return payload;
137
+ }
138
+
58
139
  // ---------------------------------------------------------------------------
59
140
  // Types
60
141
  // ---------------------------------------------------------------------------
@@ -363,8 +444,6 @@ const accessRequestResolver: GuardianRequestResolver = {
363
444
  const requesterExternalUserId = request.requesterExternalUserId ?? "";
364
445
  const requesterChatId =
365
446
  request.requesterChatId ?? request.requesterExternalUserId ?? "";
366
- const requesterLabel =
367
- requesterExternalUserId || requesterChatId || "the requester";
368
447
  const decidedByExternalUserId = ctx.actor.actorExternalUserId ?? "";
369
448
  const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
370
449
  const desktopDeliverUrl = resolveDeliverCallbackUrlForChannel(channel);
@@ -373,16 +452,23 @@ const accessRequestResolver: GuardianRequestResolver = {
373
452
  const requesterContactResult = requesterExternalUserId
374
453
  ? findContactChannel({
375
454
  channelType: channel,
376
- externalUserId: requesterExternalUserId,
455
+ address: requesterExternalUserId,
377
456
  })
378
457
  : null;
379
458
  const requesterDisplayName =
380
459
  requesterContactResult?.contact.displayName ?? null;
381
460
 
461
+ // Guardian-facing label prefers the contact display name over the raw ID.
462
+ const requesterLabel =
463
+ requesterDisplayName ||
464
+ requesterExternalUserId ||
465
+ requesterChatId ||
466
+ "the requester";
467
+
382
468
  const decidedByContactResult = decidedByExternalUserId
383
469
  ? findContactChannel({
384
470
  channelType: channel,
385
- externalUserId: decidedByExternalUserId,
471
+ address: decidedByExternalUserId,
386
472
  })
387
473
  : null;
388
474
  const decidedByDisplayName =
@@ -397,22 +483,15 @@ const accessRequestResolver: GuardianRequestResolver = {
397
483
  // Deliver denial notification and lifecycle signals when channel context is available
398
484
  if (channelDeliveryContext) {
399
485
  try {
400
- const denialPayload: Parameters<typeof deliverChannelReply>[1] = {
401
- chatId: requesterChatId,
402
- text: "Your access request has been denied.",
403
- assistantId,
404
- };
405
- // On Slack shared channels, deliver as ephemeral so only the requester sees the denial
406
- if (
407
- shouldUseEphemeral(channel, requesterChatId) &&
408
- requesterExternalUserId
409
- ) {
410
- denialPayload.ephemeral = true;
411
- denialPayload.user = requesterExternalUserId;
412
- }
413
486
  await deliverChannelReply(
414
487
  channelDeliveryContext.replyCallbackUrl,
415
- denialPayload,
488
+ buildRequesterChannelNotice({
489
+ channel,
490
+ requesterChatId,
491
+ requesterExternalUserId,
492
+ text: "Your access request has been denied.",
493
+ assistantId,
494
+ }),
416
495
  );
417
496
  } catch (err) {
418
497
  log.error(
@@ -563,7 +642,7 @@ const accessRequestResolver: GuardianRequestResolver = {
563
642
 
564
643
  // Deliver verification code to guardian
565
644
  const codeText =
566
- `You approved access for ${requesterExternalUserId}. ` +
645
+ `You approved access for ${requesterLabel}. ` +
567
646
  `Give them this verification code: \`${session.secret}\`. ` +
568
647
  `The code expires in 10 minutes.`;
569
648
  try {
@@ -629,12 +708,8 @@ const accessRequestResolver: GuardianRequestResolver = {
629
708
  }
630
709
  }
631
710
 
632
- // Notify the requester. For Slack, route to DM via the user ID and
633
- // strip threadTs (which belongs to the guardian's channel thread).
634
- const requesterTargetChatId =
635
- channel === "slack" && requesterExternalUserId
636
- ? requesterExternalUserId
637
- : requesterChatId;
711
+ // Strip threadTs from the requester reply URL it belongs to the
712
+ // guardian's channel thread and would cause thread_not_found in a DM.
638
713
  let requesterCallbackUrl = channelDeliveryContext.replyCallbackUrl;
639
714
  if (channel === "slack" && requesterExternalUserId) {
640
715
  try {
@@ -647,47 +722,58 @@ const accessRequestResolver: GuardianRequestResolver = {
647
722
  }
648
723
 
649
724
  if (codeDelivered) {
650
- try {
651
- const approvalPayload: Parameters<typeof deliverChannelReply>[1] = {
652
- chatId: requesterTargetChatId,
653
- text:
654
- "Your access request has been approved! " +
655
- "Please enter the 6-digit verification code you receive from the guardian.",
656
- assistantId,
657
- };
658
- // On Slack shared channels, deliver as ephemeral so only the requester sees
659
- if (
660
- shouldUseEphemeral(channel, requesterChatId) &&
661
- requesterExternalUserId
662
- ) {
663
- approvalPayload.ephemeral = true;
664
- approvalPayload.user = requesterExternalUserId;
665
- }
666
- await deliverChannelReply(requesterCallbackUrl, approvalPayload);
725
+ // On Slack, deliver the code straight to the requester's DM so the
726
+ // guardian doesn't have to relay it. Other channels (and a failed Slack
727
+ // delivery) fall back to the courier notice — there is no guaranteed
728
+ // private path to the requester elsewhere (e.g. group chats).
729
+ const requesterCodeDelivered =
730
+ channel === "slack" && requesterExternalUserId
731
+ ? await deliverVerificationCodeToSlackRequester({
732
+ replyCallbackUrl: channelDeliveryContext.replyCallbackUrl,
733
+ requesterExternalUserId,
734
+ verificationCode: session.secret,
735
+ assistantId,
736
+ })
737
+ : false;
738
+
739
+ if (requesterCodeDelivered) {
667
740
  requesterNotified = true;
668
- } catch (err) {
669
- log.error(
670
- { err, requesterChatId },
671
- "Failed to notify requester of access request approval",
672
- );
741
+ } else {
742
+ try {
743
+ await deliverChannelReply(
744
+ requesterCallbackUrl,
745
+ buildRequesterChannelNotice({
746
+ channel,
747
+ requesterChatId,
748
+ requesterExternalUserId,
749
+ text:
750
+ "Your access request has been approved! " +
751
+ "Please enter the 6-digit verification code you receive from the guardian.",
752
+ assistantId,
753
+ }),
754
+ );
755
+ requesterNotified = true;
756
+ } catch (err) {
757
+ log.error(
758
+ { err, requesterChatId },
759
+ "Failed to notify requester of access request approval",
760
+ );
761
+ }
673
762
  }
674
763
  } else {
675
764
  try {
676
- const failurePayload: Parameters<typeof deliverChannelReply>[1] = {
677
- chatId: requesterTargetChatId,
678
- text:
679
- "Your access request was approved, but we were unable to " +
680
- "deliver the verification code. Please try again later.",
681
- assistantId,
682
- };
683
- if (
684
- shouldUseEphemeral(channel, requesterChatId) &&
685
- requesterExternalUserId
686
- ) {
687
- failurePayload.ephemeral = true;
688
- failurePayload.user = requesterExternalUserId;
689
- }
690
- await deliverChannelReply(requesterCallbackUrl, failurePayload);
765
+ await deliverChannelReply(
766
+ requesterCallbackUrl,
767
+ buildRequesterChannelNotice({
768
+ channel,
769
+ requesterChatId,
770
+ requesterExternalUserId,
771
+ text:
772
+ "Your access request was approved, but we were unable to " +
773
+ "deliver the verification code. Please try again later.",
774
+ assistantId,
775
+ }),
776
+ );
691
777
  } catch (err) {
692
778
  log.error(
693
779
  { err, requesterChatId },
@@ -721,26 +807,43 @@ const accessRequestResolver: GuardianRequestResolver = {
721
807
  });
722
808
  }
723
809
  } else if (desktopDeliverUrl && requesterChatId) {
724
- // For Slack, route to DM via requesterExternalUserId (user ID) instead
725
- // of requesterChatId (channel ID) to avoid posting in public channels.
726
- const targetChatId =
810
+ // Guardian decided off-channel (e.g. desktop) but the requester is on a
811
+ // deliverable channel. On Slack, DM the code directly (parity with the
812
+ // on-channel path); otherwise fall back to the courier notice.
813
+ const requesterCodeDelivered =
727
814
  channel === "slack" && requesterExternalUserId
728
- ? requesterExternalUserId
729
- : requesterChatId;
730
- try {
731
- await deliverChannelReply(desktopDeliverUrl, {
732
- chatId: targetChatId,
733
- text:
734
- "Your access request has been approved! " +
735
- "Please enter the 6-digit verification code you receive from the guardian.",
736
- assistantId,
737
- });
815
+ ? await deliverVerificationCodeToSlackRequester({
816
+ replyCallbackUrl: desktopDeliverUrl,
817
+ requesterExternalUserId,
818
+ verificationCode: session.secret,
819
+ assistantId,
820
+ })
821
+ : false;
822
+
823
+ if (requesterCodeDelivered) {
738
824
  requesterNotified = true;
739
- } catch (err) {
740
- log.error(
741
- { err, requesterChatId },
742
- "Failed to notify requester of access request approval (desktop decision path)",
743
- );
825
+ } else {
826
+ // For Slack, route to DM via requesterExternalUserId (user ID) instead
827
+ // of requesterChatId (channel ID) to avoid posting in public channels.
828
+ const targetChatId =
829
+ channel === "slack" && requesterExternalUserId
830
+ ? requesterExternalUserId
831
+ : requesterChatId;
832
+ try {
833
+ await deliverChannelReply(desktopDeliverUrl, {
834
+ chatId: targetChatId,
835
+ text:
836
+ "Your access request has been approved! " +
837
+ "Please enter the 6-digit verification code you receive from the guardian.",
838
+ assistantId,
839
+ });
840
+ requesterNotified = true;
841
+ } catch (err) {
842
+ log.error(
843
+ { err, requesterChatId },
844
+ "Failed to notify requester of access request approval (desktop decision path)",
845
+ );
846
+ }
744
847
  }
745
848
  }
746
849
 
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Tests for the gateway-backed channel admission policy reader.
3
+ *
4
+ * The reader fails open (returns null = admit) so a gateway IPC failure can
5
+ * never block a live call. These tests pin that contract plus the TTL cache.
6
+ */
7
+
8
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
9
+
10
+ // ── Controllable IPC mock ────────────────────────────────────────────────────
11
+
12
+ type IpcHandler = (params?: Record<string, unknown>) => unknown;
13
+
14
+ const ipcHandlers = new Map<string, IpcHandler>();
15
+ const ipcCallLog: Array<{
16
+ method: string;
17
+ params?: Record<string, unknown>;
18
+ timeoutMs?: number;
19
+ }> = [];
20
+
21
+ mock.module("../../ipc/gateway-client.js", () => ({
22
+ ipcCall: async (
23
+ method: string,
24
+ params?: Record<string, unknown>,
25
+ timeoutMs?: number,
26
+ ) => {
27
+ ipcCallLog.push({ method, params, timeoutMs });
28
+ const handler = ipcHandlers.get(method);
29
+ return handler ? handler(params) : undefined;
30
+ },
31
+ ipcCallPersistent: async () => undefined,
32
+ resetPersistentClient: () => {},
33
+ }));
34
+
35
+ import {
36
+ _clearCacheForTesting,
37
+ getChannelAdmissionPolicy,
38
+ } from "../channel-admission-reader.js";
39
+
40
+ const METHOD = "get_channel_admission_policy";
41
+
42
+ function countCalls(method: string): number {
43
+ return ipcCallLog.filter((c) => c.method === method).length;
44
+ }
45
+
46
+ describe("getChannelAdmissionPolicy", () => {
47
+ beforeEach(() => {
48
+ _clearCacheForTesting();
49
+ ipcHandlers.clear();
50
+ ipcCallLog.length = 0;
51
+ });
52
+
53
+ test("returns the gateway-resolved policy and caches it within the TTL", async () => {
54
+ ipcHandlers.set(METHOD, () => ({ policy: "guardian_only" }));
55
+
56
+ expect(await getChannelAdmissionPolicy("telegram")).toBe("guardian_only");
57
+ // Second call within TTL is served from cache — no extra IPC round-trip.
58
+ expect(await getChannelAdmissionPolicy("telegram")).toBe("guardian_only");
59
+ expect(countCalls(METHOD)).toBe(1);
60
+ });
61
+
62
+ test("bounds the IPC read with a short timeout so it fails open promptly", async () => {
63
+ ipcHandlers.set(METHOD, () => ({ policy: "guardian_only" }));
64
+
65
+ await getChannelAdmissionPolicy("telegram");
66
+
67
+ const call = ipcCallLog.find((c) => c.method === METHOD);
68
+ expect(call?.timeoutMs).toBe(1_000);
69
+ });
70
+
71
+ test("returns null when IPC transport fails (undefined)", async () => {
72
+ ipcHandlers.set(METHOD, () => undefined);
73
+ expect(await getChannelAdmissionPolicy("telegram")).toBeNull();
74
+ });
75
+
76
+ test("returns null when the gateway explicitly resolves no policy", async () => {
77
+ ipcHandlers.set(METHOD, () => ({ policy: null }));
78
+ expect(await getChannelAdmissionPolicy("telegram")).toBeNull();
79
+ });
80
+
81
+ test("returns null when the IPC call throws", async () => {
82
+ ipcHandlers.set(METHOD, () => {
83
+ throw new Error("socket exploded");
84
+ });
85
+ expect(await getChannelAdmissionPolicy("telegram")).toBeNull();
86
+ });
87
+
88
+ test("returns null for an invalid policy string", async () => {
89
+ ipcHandlers.set(METHOD, () => ({ policy: "definitely-not-a-policy" }));
90
+ expect(await getChannelAdmissionPolicy("telegram")).toBeNull();
91
+ });
92
+
93
+ test("caches the explicit no-enforcement (null) answer within the TTL", async () => {
94
+ // The gateway successfully said "no enforcement" — a real, cacheable answer.
95
+ ipcHandlers.set(METHOD, () => ({ policy: null }));
96
+ expect(await getChannelAdmissionPolicy("telegram")).toBeNull();
97
+ expect(await getChannelAdmissionPolicy("telegram")).toBeNull();
98
+ expect(countCalls(METHOD)).toBe(1);
99
+ });
100
+
101
+ test("does NOT cache a transport failure (undefined) — re-consults the gateway", async () => {
102
+ // First setup: gateway hiccup → fail open to null, NOT cached.
103
+ ipcHandlers.set(METHOD, () => undefined);
104
+ expect(await getChannelAdmissionPolicy("telegram")).toBeNull();
105
+
106
+ // Gateway recovers with a restrictive policy: the next setup must re-attempt
107
+ // the IPC (not serve a stale fail-open null) and honor the now-recovered floor.
108
+ ipcHandlers.set(METHOD, () => ({ policy: "guardian_only" }));
109
+ expect(await getChannelAdmissionPolicy("telegram")).toBe("guardian_only");
110
+ expect(countCalls(METHOD)).toBe(2);
111
+ });
112
+
113
+ test("does NOT cache a thrown IPC error — re-consults the gateway", async () => {
114
+ ipcHandlers.set(METHOD, () => {
115
+ throw new Error("socket exploded");
116
+ });
117
+ expect(await getChannelAdmissionPolicy("telegram")).toBeNull();
118
+
119
+ ipcHandlers.set(METHOD, () => ({ policy: "no_one" }));
120
+ expect(await getChannelAdmissionPolicy("telegram")).toBe("no_one");
121
+ expect(countCalls(METHOD)).toBe(2);
122
+ });
123
+
124
+ test("does NOT cache a malformed shape — re-consults the gateway", async () => {
125
+ ipcHandlers.set(METHOD, () => ({ policy: "definitely-not-a-policy" }));
126
+ expect(await getChannelAdmissionPolicy("telegram")).toBeNull();
127
+
128
+ ipcHandlers.set(METHOD, () => ({ policy: "guardian_only" }));
129
+ expect(await getChannelAdmissionPolicy("telegram")).toBe("guardian_only");
130
+ expect(countCalls(METHOD)).toBe(2);
131
+ });
132
+ });