@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
@@ -13,15 +13,16 @@
13
13
  */
14
14
 
15
15
  import type { TrustContext } from "../daemon/trust-context.js";
16
+ import type { CanonicalGuardianRequest } from "../memory/canonical-guardian-store.js";
16
17
  import {
17
- type CanonicalGuardianRequest,
18
- createCanonicalGuardianDelivery,
19
- } from "../memory/canonical-guardian-store.js";
18
+ recordApprovalCardDelivery,
19
+ recordGuardianRequestDeliveries,
20
+ } from "../notifications/canonical-delivery-recorder.js";
20
21
  import { emitNotificationSignal } from "../notifications/emit-signal.js";
21
- import type { NotificationSourceChannel } from "../notifications/signal.js";
22
22
  import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
23
23
  import { getLogger } from "../util/logger.js";
24
24
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "./assistant-scope.js";
25
+ import { resolveCapabilities } from "./capabilities.js";
25
26
  import { getGuardianBinding } from "./channel-verification-service.js";
26
27
 
27
28
  const log = getLogger("confirmation-request-guardian-bridge");
@@ -48,7 +49,7 @@ export type BridgeConfirmationRequestResult =
48
49
  | {
49
50
  skipped: true;
50
51
  reason:
51
- | "not_trusted_contact"
52
+ | "not_bridgeable_trust_class"
52
53
  | "no_guardian_binding"
53
54
  | "missing_guardian_identity"
54
55
  | "binding_identity_mismatch";
@@ -59,11 +60,13 @@ export type BridgeConfirmationRequestResult =
59
60
  // ---------------------------------------------------------------------------
60
61
 
61
62
  /**
62
- * Bridge a trusted-contact confirmation_request to a guardian.question notification.
63
+ * Bridge a non-guardian contact confirmation_request to a guardian.question
64
+ * notification.
63
65
  *
64
- * Only emits when the session belongs to a trusted-contact actor with a
65
- * resolvable guardian binding. Guardian and unknown actors are skipped — guardians
66
- * self-approve, and unknown actors are already fail-closed by the routing layer.
66
+ * Only emits when the session belongs to a trusted_contact or unverified_contact
67
+ * actor with a resolvable guardian binding. Guardian and unknown actors are
68
+ * skipped — guardians self-approve, and unknown actors are already fail-closed
69
+ * by the routing layer.
67
70
  *
68
71
  * Fire-and-forget safe: notification emission errors are logged but not propagated.
69
72
  */
@@ -78,10 +81,14 @@ export function bridgeConfirmationRequestToGuardian(
78
81
  assistantId = DAEMON_INTERNAL_ASSISTANT_ID,
79
82
  } = params;
80
83
 
81
- // Only bridge for trusted-contact sessions. Guardians self-approve and
82
- // unknown actors are fail-closed by the routing layer.
83
- if (trustContext.trustClass !== "trusted_contact") {
84
- return { skipped: true, reason: "not_trusted_contact" };
84
+ // Only bridge for actors whose sensitive tool approval escalates-and-waits.
85
+ // Guardians self-approve and unknown actors are fail-closed by the routing
86
+ // layer, so neither needs a guardian bridge.
87
+ if (
88
+ resolveCapabilities(trustContext.trustClass).sensitiveToolApproval !==
89
+ "escalate-and-wait"
90
+ ) {
91
+ return { skipped: true, reason: "not_bridgeable_trust_class" };
85
92
  }
86
93
 
87
94
  if (!trustContext.guardianExternalUserId) {
@@ -144,10 +151,14 @@ export function bridgeConfirmationRequestToGuardian(
144
151
  ? `Approve tool: ${toolName} — ${canonicalRequest.activityText}`
145
152
  : `Approve tool: ${toolName}`;
146
153
 
154
+ // The vellum delivery row is created up front in onConversationCreated so the
155
+ // in-app client sees it immediately; the post-resolve recorder reuses it.
156
+ let vellumDeliveryId: string | undefined;
157
+
147
158
  // Emit guardian.question notification so the guardian is alerted.
148
159
  const signalPromise = emitNotificationSignal({
149
160
  sourceEventName: "guardian.question",
150
- sourceChannel: sourceChannel as NotificationSourceChannel,
161
+ sourceChannel,
151
162
  sourceContextId: conversationId,
152
163
  requiresConversation: true,
153
164
  attentionHints: {
@@ -157,7 +168,7 @@ export function bridgeConfirmationRequestToGuardian(
157
168
  visibleInSourceNow: false,
158
169
  },
159
170
  contextPayload: {
160
- requestKind: "tool_approval" as const,
171
+ requestKind: "tool_approval",
161
172
  requestId: canonicalRequest.id,
162
173
  requestCode:
163
174
  canonicalRequest.requestCode ??
@@ -168,29 +179,27 @@ export function bridgeConfirmationRequestToGuardian(
168
179
  requesterIdentifier: senderLabel,
169
180
  toolName,
170
181
  questionText,
182
+ riskLevel: canonicalRequest.riskLevel ?? undefined,
183
+ commandPreview: canonicalRequest.commandPreview ?? undefined,
171
184
  },
172
185
  dedupeKey: `tc-confirmation-request:${canonicalRequest.id}`,
173
186
  onConversationCreated: (info) => {
174
- createCanonicalGuardianDelivery({
187
+ vellumDeliveryId = recordApprovalCardDelivery({
175
188
  requestId: canonicalRequest.id,
176
- destinationChannel: "vellum",
177
- destinationConversationId: info.conversationId,
178
- });
189
+ channel: "vellum",
190
+ conversationId: info.conversationId,
191
+ })?.id;
179
192
  },
180
193
  });
181
194
 
182
- // Record channel deliveries from the notification pipeline (fire-and-forget).
195
+ // Record deliveries from the notification pipeline (fire-and-forget).
183
196
  void signalPromise
184
197
  .then((signalResult) => {
185
- for (const result of signalResult.deliveryResults) {
186
- if (result.channel === "vellum") continue; // handled in onConversationCreated
187
- createCanonicalGuardianDelivery({
188
- requestId: canonicalRequest.id,
189
- destinationChannel: result.channel,
190
- destinationChatId:
191
- result.destination.length > 0 ? result.destination : undefined,
192
- });
193
- }
198
+ recordGuardianRequestDeliveries({
199
+ requestId: canonicalRequest.id,
200
+ deliveryResults: signalResult.deliveryResults,
201
+ vellumDeliveryId,
202
+ });
194
203
  })
195
204
  .catch((err) => {
196
205
  log.warn(
@@ -0,0 +1,128 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ canActOnPrivilegedDocuments,
5
+ isArchiveBySenderAuthorized,
6
+ isUntrustedShellLockdownActive,
7
+ } from "./effective-capabilities.js";
8
+
9
+ describe("canActOnPrivilegedDocuments", () => {
10
+ test("guardian is privileged regardless of channel", () => {
11
+ expect(canActOnPrivilegedDocuments({ trustClass: "guardian" })).toBe(true);
12
+ expect(
13
+ canActOnPrivilegedDocuments({
14
+ trustClass: "guardian",
15
+ executionChannel: "telegram",
16
+ }),
17
+ ).toBe(true);
18
+ });
19
+
20
+ test("non-guardian is privileged only on a privileged channel", () => {
21
+ expect(
22
+ canActOnPrivilegedDocuments({
23
+ trustClass: "trusted_contact",
24
+ executionChannel: "telegram",
25
+ }),
26
+ ).toBe(false);
27
+ expect(
28
+ canActOnPrivilegedDocuments({
29
+ trustClass: "trusted_contact",
30
+ executionChannel: "vellum",
31
+ }),
32
+ ).toBe(true);
33
+ });
34
+
35
+ test("the vellum channel grants access even to unknown actors", () => {
36
+ expect(
37
+ canActOnPrivilegedDocuments({
38
+ trustClass: "unknown",
39
+ executionChannel: "vellum",
40
+ }),
41
+ ).toBe(true);
42
+ expect(canActOnPrivilegedDocuments({ trustClass: "unknown" })).toBe(false);
43
+ });
44
+ });
45
+
46
+ describe("isUntrustedShellLockdownActive", () => {
47
+ test("inactive when the lockdown flag is off", () => {
48
+ expect(
49
+ isUntrustedShellLockdownActive({
50
+ trustClass: "unknown",
51
+ lockdownEnabled: false,
52
+ }),
53
+ ).toBe(false);
54
+ });
55
+
56
+ test("inactive for guardians even when the flag is on", () => {
57
+ expect(
58
+ isUntrustedShellLockdownActive({
59
+ trustClass: "guardian",
60
+ lockdownEnabled: true,
61
+ }),
62
+ ).toBe(false);
63
+ });
64
+
65
+ test("active for non-unsandboxed actors when the flag is on", () => {
66
+ expect(
67
+ isUntrustedShellLockdownActive({
68
+ trustClass: "trusted_contact",
69
+ lockdownEnabled: true,
70
+ }),
71
+ ).toBe(true);
72
+ expect(
73
+ isUntrustedShellLockdownActive({
74
+ trustClass: "unknown",
75
+ lockdownEnabled: true,
76
+ }),
77
+ ).toBe(true);
78
+ });
79
+ });
80
+
81
+ describe("isArchiveBySenderAuthorized", () => {
82
+ test("any non-self authorization path suffices", () => {
83
+ expect(
84
+ isArchiveBySenderAuthorized({
85
+ trustClass: "unknown",
86
+ triggeredBySurfaceAction: true,
87
+ }),
88
+ ).toBe(true);
89
+ expect(
90
+ isArchiveBySenderAuthorized({
91
+ trustClass: "unknown",
92
+ batchAuthorizedByTask: true,
93
+ }),
94
+ ).toBe(true);
95
+ expect(
96
+ isArchiveBySenderAuthorized({
97
+ trustClass: "unknown",
98
+ approvedViaPrompt: true,
99
+ }),
100
+ ).toBe(true);
101
+ });
102
+
103
+ test("user_approved self-authorizes only for a permitted trust class", () => {
104
+ expect(
105
+ isArchiveBySenderAuthorized({
106
+ trustClass: "guardian",
107
+ userApproved: true,
108
+ }),
109
+ ).toBe(true);
110
+ expect(
111
+ isArchiveBySenderAuthorized({
112
+ trustClass: "trusted_contact",
113
+ userApproved: true,
114
+ }),
115
+ ).toBe(false);
116
+ });
117
+
118
+ test("unauthorized when no path applies", () => {
119
+ expect(isArchiveBySenderAuthorized({ trustClass: "guardian" })).toBe(false);
120
+ expect(
121
+ isArchiveBySenderAuthorized({
122
+ trustClass: "trusted_contact",
123
+ triggeredBySurfaceAction: false,
124
+ userApproved: false,
125
+ }),
126
+ ).toBe(false);
127
+ });
128
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Effective capabilities — trust-class capabilities COMPOSED with runtime context.
3
+ *
4
+ * `resolveCapabilities` (`capabilities.ts`) answers "what may this trust class
5
+ * do" from the actor's class alone, and stays deliberately pure (no context
6
+ * dependencies) so it remains the single fail-closed trust boundary. Some real
7
+ * decisions additionally depend on runtime context — the channel a request
8
+ * arrived on, surface actions, task authorization, a feature flag. Those
9
+ * compositions belong here, in named/testable helpers, rather than re-derived
10
+ * inline at each call site (which scatters a single policy across the codebase).
11
+ *
12
+ * Scope: this module composes capabilities with *actor/request* context. It does
13
+ * not read global config or feature flags itself — callers resolve those and
14
+ * pass the result in (e.g. `lockdownEnabled`), keeping this layer focused on the
15
+ * capability composition. `resolveRoutingState` in `trust-context-resolver.ts`
16
+ * is the same shape (capability + guardian-route context → `promptWaitingAllowed`)
17
+ * and predates this module; it stays where it is.
18
+ */
19
+ import type { TrustClass } from "./actor-trust-resolver.js";
20
+ import { resolveCapabilities } from "./capabilities.js";
21
+
22
+ type RawTrustClass = TrustClass | (string & {}) | undefined;
23
+
24
+ /**
25
+ * Channels that are themselves privileged document surfaces. Actors on these get
26
+ * privileged document access regardless of trust class — the `vellum` first-party
27
+ * console is the operator's own surface, not an external contact channel.
28
+ */
29
+ const PRIVILEGED_DOCUMENT_CHANNELS = new Set<string>(["vellum"]);
30
+
31
+ /**
32
+ * Whether an actor may perform privileged (non-conversation-scoped) document
33
+ * operations. True when the trust class grants it OR the request arrived on a
34
+ * privileged channel.
35
+ */
36
+ export function canActOnPrivilegedDocuments(actor: {
37
+ trustClass: RawTrustClass;
38
+ executionChannel?: string;
39
+ }): boolean {
40
+ return (
41
+ resolveCapabilities(actor.trustClass).canAccessPrivilegedDocuments ||
42
+ (actor.executionChannel != null &&
43
+ PRIVILEGED_DOCUMENT_CHANNELS.has(actor.executionChannel))
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Whether the CES shell lockdown applies to this actor. Active when the lockdown
49
+ * flag is enabled AND the actor's trust class cannot run an unsandboxed shell.
50
+ * Callers resolve the flag (`isCesShellLockdownEnabled(config)`) and pass it in.
51
+ */
52
+ export function isUntrustedShellLockdownActive(actor: {
53
+ trustClass: RawTrustClass;
54
+ lockdownEnabled: boolean;
55
+ }): boolean {
56
+ return (
57
+ actor.lockdownEnabled &&
58
+ !resolveCapabilities(actor.trustClass).canRunUnsandboxedShell
59
+ );
60
+ }
61
+
62
+ /**
63
+ * Whether an archive-by-sender invocation is authorized. Any one of a surface
64
+ * action, a task-batch authorization, or an explicit prompt approval suffices.
65
+ * Absent those, the actor's own `user_approved` flag only counts when its trust
66
+ * class may self-authorize archive-by-sender.
67
+ */
68
+ export function isArchiveBySenderAuthorized(args: {
69
+ trustClass: RawTrustClass;
70
+ triggeredBySurfaceAction?: boolean;
71
+ batchAuthorizedByTask?: boolean;
72
+ approvedViaPrompt?: boolean;
73
+ userApproved?: boolean;
74
+ }): boolean {
75
+ const selfAuthorized =
76
+ args.userApproved === true &&
77
+ resolveCapabilities(args.trustClass).canSelfAuthorizeArchiveBySender;
78
+ return (
79
+ args.triggeredBySurfaceAction === true ||
80
+ args.batchAuthorizedByTask === true ||
81
+ args.approvedViaPrompt === true ||
82
+ selfAuthorized
83
+ );
84
+ }
@@ -30,6 +30,7 @@ import {
30
30
  type CanonicalGuardianRequest,
31
31
  getCanonicalGuardianRequest,
32
32
  getCanonicalGuardianRequestByCode,
33
+ getPendingCanonicalRequestByDestinationMessage,
33
34
  isRequestExpired,
34
35
  listCanonicalGuardianRequests,
35
36
  } from "../memory/canonical-guardian-store.js";
@@ -47,6 +48,7 @@ import type {
47
48
  ApprovalConversationContext,
48
49
  ApprovalConversationGenerator,
49
50
  } from "./http-types.js";
51
+ import { parseReactionCallbackData } from "./routes/channel-route-shared.js";
50
52
 
51
53
  const log = getLogger("guardian-reply-router");
52
54
 
@@ -54,6 +56,26 @@ const log = getLogger("guardian-reply-router");
54
56
  // Types
55
57
  // ---------------------------------------------------------------------------
56
58
 
59
+ /**
60
+ * How to scope a guardian's pending requests when resolving an inbound reply.
61
+ * The three states are mutually exclusive and named so the security-critical
62
+ * `blocked` cannot be confused with the absence of a hint:
63
+ *
64
+ * - `scoped`: resolve only these request ids, and constrain request-code
65
+ * routing to this set (delivery-/conversation-scoped hints).
66
+ * - `blocked`: fail closed — no pending requests and no identity fallback.
67
+ * The Slack cross-chat guard: a guardian's unrelated message in a chat
68
+ * where no card was delivered must not resolve a request delivered
69
+ * elsewhere. Explicit callbacks and request codes still work (they carry
70
+ * their own request id).
71
+ * - `identity-fallback`: discover pending requests by guardian identity,
72
+ * conversation, or principal. The default when no scope is supplied.
73
+ */
74
+ export type GuardianPendingScope =
75
+ | { mode: "scoped"; requestIds: string[] }
76
+ | { mode: "blocked" }
77
+ | { mode: "identity-fallback" };
78
+
57
79
  /** Context for an inbound message that may be a guardian reply. */
58
80
  export interface GuardianReplyContext {
59
81
  /** The raw message text (trimmed). */
@@ -66,8 +88,18 @@ export interface GuardianReplyContext {
66
88
  conversationId: string;
67
89
  /** Callback data from button presses (e.g. `apr:<requestId>:<action>`). */
68
90
  callbackData?: string;
69
- /** IDs of known pending canonical requests for this guardian. */
70
- pendingRequestIds?: string[];
91
+ /**
92
+ * For emoji-reaction decisions (`callbackData` of `reaction:<emoji>`): the
93
+ * channel-native id (e.g. Slack `ts`) of the message the reaction was
94
+ * attached to. Used to recover the target request from its delivery record.
95
+ */
96
+ reactedMessageTs?: string;
97
+ /**
98
+ * How to scope this guardian's pending requests (see {@link
99
+ * GuardianPendingScope}). Omitted is equivalent to
100
+ * `{ mode: "identity-fallback" }`.
101
+ */
102
+ pendingScope?: GuardianPendingScope;
71
103
  /** Conversation generator for NL classification (injected by daemon). */
72
104
  approvalConversationGenerator?: ApprovalConversationGenerator;
73
105
  /** Optional channel delivery context for resolver-driven side effects. */
@@ -204,30 +236,33 @@ function parseRequestCode(
204
236
  /** Find all pending canonical requests for a guardian actor. */
205
237
  function findPendingCanonicalRequests(
206
238
  actor: ActorContext,
207
- pendingRequestIds?: string[],
239
+ scope: GuardianPendingScope,
208
240
  conversationId?: string,
209
241
  ): CanonicalGuardianRequest[] {
242
+ // `blocked` fails closed: no pending requests and no identity fallback — the
243
+ // Slack cross-chat hijack guard.
244
+ if (scope.mode === "blocked") {
245
+ return [];
246
+ }
247
+
210
248
  let results: CanonicalGuardianRequest[];
211
249
 
212
- // When explicit IDs are provided, look them up directly
213
- if (pendingRequestIds) {
214
- if (pendingRequestIds.length === 0) {
215
- return [];
216
- }
217
- results = pendingRequestIds
250
+ if (scope.mode === "scoped") {
251
+ // Resolve exactly the supplied ids.
252
+ results = scope.requestIds
218
253
  .map(getCanonicalGuardianRequest)
219
254
  .filter((r): r is CanonicalGuardianRequest => r?.status === "pending");
220
255
  } else if (actor.actorExternalUserId) {
221
- // Query by guardian identity when available
256
+ // identity-fallback: query by guardian identity when available
222
257
  results = listCanonicalGuardianRequests({
223
258
  status: "pending",
224
259
  guardianExternalUserId: actor.actorExternalUserId,
225
260
  });
226
261
  } else if (conversationId) {
227
- // Actors without an actorExternalUserId: scope by conversationId so the NL
228
- // path can discover pending requests bound to this conversation.
229
- // Include guardianPrincipalId filter when available so the guardian only
230
- // sees requests they are authorized to act on.
262
+ // identity-fallback without an actorExternalUserId: scope by conversationId
263
+ // so the NL path can discover pending requests bound to this conversation.
264
+ // Include guardianPrincipalId when available so the guardian only sees
265
+ // requests they are authorized to act on.
231
266
  results = listCanonicalGuardianRequests({
232
267
  status: "pending",
233
268
  conversationId,
@@ -236,9 +271,8 @@ function findPendingCanonicalRequests(
236
271
  : {}),
237
272
  });
238
273
  } else if (actor.guardianPrincipalId) {
239
- // Actors with a guardianPrincipalId but no actorExternalUserId or
240
- // conversationId: query by principal so desktop sessions can still
241
- // discover pending guardian work via their bound principal.
274
+ // identity-fallback by principal: desktop sessions discover pending
275
+ // guardian work via their bound principal.
242
276
  results = listCanonicalGuardianRequests({
243
277
  status: "pending",
244
278
  guardianPrincipalId: actor.guardianPrincipalId,
@@ -284,22 +318,68 @@ export async function routeGuardianReply(
284
318
  ): Promise<GuardianReplyResult> {
285
319
  const {
286
320
  messageText,
321
+ channel,
287
322
  actor,
288
323
  conversationId,
289
324
  callbackData,
325
+ reactedMessageTs,
290
326
  approvalConversationGenerator,
291
327
  channelDeliveryContext,
292
328
  emissionContext,
293
329
  } = ctx;
330
+
331
+ // ── 0. Reaction decisions (emoji on a delivered approval card) ──
332
+ // A reaction carries an emoji plus the message it is attached to. Map the
333
+ // emoji to an action and recover the target request from that card's
334
+ // delivery record. Addressing by the reacted message disambiguates precisely
335
+ // even when several cards are pending in the same chat, so — unlike the
336
+ // text/NL paths — no clarification prompt is ever needed. `reaction_removed`
337
+ // never expresses intent and is filtered out before reaching the router.
338
+ if (
339
+ callbackData?.startsWith("reaction:") &&
340
+ !callbackData.startsWith("reaction_removed:")
341
+ ) {
342
+ const reaction = parseReactionCallbackData(callbackData);
343
+ const guardianChatId = channelDeliveryContext?.guardianChatId;
344
+ if (!reaction || !reactedMessageTs || !guardianChatId) {
345
+ // Unknown emoji, or missing addressing context — not an actionable
346
+ // approval reaction. Leave it for the caller to persist as a transcript
347
+ // signal (it must not trigger an agent turn).
348
+ return notConsumed();
349
+ }
350
+ const request = getPendingCanonicalRequestByDestinationMessage(
351
+ channel,
352
+ guardianChatId,
353
+ reactedMessageTs,
354
+ );
355
+ if (!request) {
356
+ // The reacted message is not a known pending approval card (a stray
357
+ // reaction, or one whose request was already resolved). Never approve
358
+ // off an unrecognized message.
359
+ return notConsumed();
360
+ }
361
+ return applyDecision(
362
+ request.id,
363
+ reaction.action,
364
+ actor,
365
+ undefined,
366
+ channelDeliveryContext,
367
+ emissionContext,
368
+ );
369
+ }
370
+
371
+ const pendingScope: GuardianPendingScope = ctx.pendingScope ?? {
372
+ mode: "identity-fallback",
373
+ };
294
374
  const pendingRequests = findPendingCanonicalRequests(
295
375
  actor,
296
- ctx.pendingRequestIds,
376
+ pendingScope,
297
377
  conversationId,
298
378
  );
379
+ // Request codes carry their own id, so constrain them only under an explicit
380
+ // scope; under blocked/identity-fallback they still resolve cross-chat.
299
381
  const scopedPendingRequestIds =
300
- ctx.pendingRequestIds && ctx.pendingRequestIds.length > 0
301
- ? new Set(ctx.pendingRequestIds)
302
- : null;
382
+ pendingScope.mode === "scoped" ? new Set(pendingScope.requestIds) : null;
303
383
 
304
384
  // ── 1. Deterministic callback parsing (button presses) ──
305
385
  // No conversationId scoping here — the guardian's reply comes from a
@@ -800,6 +880,7 @@ function resolveRequestInstructionMode(
800
880
  type CanonicalFailureReason =
801
881
  | "already_resolved"
802
882
  | "identity_mismatch"
883
+ | "request_misconfigured"
803
884
  | "invalid_action"
804
885
  | "expired";
805
886
 
@@ -819,6 +900,10 @@ function failureReplyText(
819
900
  return "This request has expired.";
820
901
  case "identity_mismatch":
821
902
  return "You don't have permission to decide on this request.";
903
+ case "request_misconfigured":
904
+ // The actor is authorized; the request record itself is incomplete
905
+ // (e.g. missing its bound principal). Do not imply a permission problem.
906
+ return "Something went wrong with this request on our end, so I couldn't apply your decision.";
822
907
  case "invalid_action":
823
908
  return buildGuardianInvalidActionReply(
824
909
  resolveRequestInstructionMode(request),
@@ -129,7 +129,7 @@ export function redeemInvite(params: {
129
129
  : undefined;
130
130
  const contactResult = findContactChannel({
131
131
  channelType: sourceChannel,
132
- externalUserId: canonicalUserId,
132
+ address: canonicalUserId,
133
133
  externalChatId: externalChatId,
134
134
  });
135
135
  const existingChannel = contactResult?.channel ?? null;
@@ -164,20 +164,12 @@ export function redeemInvite(params: {
164
164
  // Sentinel error used to trigger a transaction rollback when the invite
165
165
  // was concurrently revoked/expired between pre-validation and write time.
166
166
  const STALE_INVITE = Symbol("stale_invite");
167
- const canonicalMemberId = existingChannel.externalUserId
168
- ? canonicalizeInboundIdentity(
169
- sourceChannel as ChannelId,
170
- existingChannel.externalUserId,
171
- )
172
- : null;
167
+ const canonicalMemberId = existingChannel.address;
173
168
  const canonicalCallerId = externalUserId
174
169
  ? canonicalizeInboundIdentity(sourceChannel as ChannelId, externalUserId)
175
170
  : null;
176
- const memberMatchesSender = !!(
177
- canonicalMemberId &&
178
- canonicalCallerId &&
179
- canonicalMemberId === canonicalCallerId
180
- );
171
+ const memberMatchesSender =
172
+ !!canonicalCallerId && canonicalMemberId === canonicalCallerId;
181
173
  const preservedDisplayName =
182
174
  memberMatchesSender && existingContact?.displayName?.trim().length
183
175
  ? existingContact.displayName
@@ -359,7 +351,7 @@ export function redeemVoiceInviteCode(params: {
359
351
  callerExternalUserId;
360
352
  const voiceContactResult = findContactChannel({
361
353
  channelType: "phone",
362
- externalUserId: canonicalCallerId,
354
+ address: canonicalCallerId,
363
355
  });
364
356
  const existingVoiceChannel = voiceContactResult?.channel ?? null;
365
357
  const voiceContact = voiceContactResult?.contact ?? null;
@@ -511,7 +503,7 @@ export function redeemInviteByCode(params: {
511
503
  : undefined;
512
504
  const contactResult = findContactChannel({
513
505
  channelType: sourceChannel,
514
- externalUserId: canonicalUserId,
506
+ address: canonicalUserId,
515
507
  externalChatId: externalChatId,
516
508
  });
517
509
  const existingChannel = contactResult?.channel ?? null;
@@ -542,20 +534,12 @@ export function redeemInviteByCode(params: {
542
534
  // an invite use atomically.
543
535
  if (existingChannel && !targetMismatch) {
544
536
  const STALE_INVITE_REACTIVATE = Symbol("stale_invite_reactivate");
545
- const canonicalMemberId = existingChannel.externalUserId
546
- ? canonicalizeInboundIdentity(
547
- sourceChannel as ChannelId,
548
- existingChannel.externalUserId,
549
- )
550
- : null;
537
+ const canonicalMemberId = existingChannel.address;
551
538
  const canonicalCallerId = externalUserId
552
539
  ? canonicalizeInboundIdentity(sourceChannel as ChannelId, externalUserId)
553
540
  : null;
554
- const memberMatchesSender = !!(
555
- canonicalMemberId &&
556
- canonicalCallerId &&
557
- canonicalMemberId === canonicalCallerId
558
- );
541
+ const memberMatchesSender =
542
+ !!canonicalCallerId && canonicalMemberId === canonicalCallerId;
559
543
  const preservedDisplayName =
560
544
  memberMatchesSender && existingContact?.displayName?.trim().length
561
545
  ? existingContact.displayName