@vellumai/assistant 0.9.1-staging.1 → 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 (433) hide show
  1. package/docs/activation-funnel-telemetry.md +24 -18
  2. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  3. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  4. package/node_modules/@vellumai/gateway-client/src/index.ts +15 -0
  5. package/openapi.yaml +852 -15
  6. package/package.json +1 -1
  7. package/src/__tests__/access-request-card-view.test.ts +98 -0
  8. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  9. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +59 -7
  10. package/src/__tests__/agent-loop-compaction-strip.test.ts +17 -16
  11. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  12. package/src/__tests__/app-compiler.test.ts +15 -1
  13. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  14. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  15. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  16. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  18. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  19. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  20. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +174 -30
  22. package/src/__tests__/config-schema.test.ts +35 -0
  23. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  24. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  25. package/src/__tests__/contacts-tools.test.ts +29 -0
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  27. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  28. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  29. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  30. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  31. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  32. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  33. package/src/__tests__/conversation-title-service.test.ts +62 -0
  34. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  35. package/src/__tests__/credential-prompt-route.test.ts +1 -0
  36. package/src/__tests__/credential-security-invariants.test.ts +2 -0
  37. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  38. package/src/__tests__/disk-usage.test.ts +65 -0
  39. package/src/__tests__/dynamic-page-surface.test.ts +51 -0
  40. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  41. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  42. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  43. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  44. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  45. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  46. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  47. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  48. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  49. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  50. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  51. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  52. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  53. package/src/__tests__/invite-redemption-service.test.ts +0 -3
  54. package/src/__tests__/llm-catalog-parity.test.ts +30 -1
  55. package/src/__tests__/llm-resolver.test.ts +21 -0
  56. package/src/__tests__/llm-schema.test.ts +1 -0
  57. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  58. package/src/__tests__/mcp-health-check.test.ts +6 -7
  59. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  60. package/src/__tests__/path-policy.test.ts +34 -0
  61. package/src/__tests__/persona-resolver.test.ts +38 -0
  62. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  63. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  64. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  65. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  66. package/src/__tests__/reaction-persistence.test.ts +150 -29
  67. package/src/__tests__/relay-server.test.ts +285 -0
  68. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  69. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  70. package/src/__tests__/skill-execute-input.test.ts +5 -0
  71. package/src/__tests__/skills.test.ts +51 -0
  72. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  73. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  74. package/src/__tests__/subagent-tools.test.ts +150 -0
  75. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  76. package/src/__tests__/title-generate-hook.test.ts +100 -3
  77. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  78. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  79. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  80. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  81. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  82. package/src/__tests__/trusted-contact-verification.test.ts +2 -4
  83. package/src/__tests__/twilio-routes.test.ts +81 -1
  84. package/src/__tests__/voice-invite-redemption.test.ts +0 -1
  85. package/src/__tests__/weak-open-model.test.ts +30 -0
  86. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  87. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  88. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  89. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  90. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  91. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  92. package/src/agent/loop.ts +33 -33
  93. package/src/api/events/tool-result.ts +6 -0
  94. package/src/api/events/workflow-completed.ts +53 -0
  95. package/src/api/events/workflow-leaf-finished.ts +38 -0
  96. package/src/api/events/workflow-leaf-started.ts +35 -0
  97. package/src/api/events/workflow-progress.ts +32 -0
  98. package/src/api/events/workflow-started.ts +31 -0
  99. package/src/api/index.ts +40 -0
  100. package/src/api/responses/conversation-message.ts +26 -0
  101. package/src/api/responses/home.ts +26 -0
  102. package/src/api/responses/workflow-journal.ts +53 -0
  103. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  104. package/src/approvals/guardian-decision-primitive.ts +26 -3
  105. package/src/approvals/guardian-request-resolvers.ts +181 -78
  106. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  107. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  108. package/src/calls/call-pointer-messages.ts +10 -4
  109. package/src/calls/channel-admission-reader.ts +104 -0
  110. package/src/calls/guardian-dispatch.ts +17 -45
  111. package/src/calls/media-stream-server.ts +84 -2
  112. package/src/calls/relay-server.ts +66 -0
  113. package/src/calls/relay-setup-router.ts +82 -1
  114. package/src/calls/twilio-routes.ts +17 -8
  115. package/src/cli/commands/clients.ts +3 -0
  116. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  117. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  118. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  119. package/src/cli/commands/memory/index.ts +30 -0
  120. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  121. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  122. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  123. package/src/cli/commands/oauth/status.test.ts +36 -0
  124. package/src/cli/commands/oauth/status.ts +23 -3
  125. package/src/cli/commands/plugins.ts +57 -5
  126. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  127. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +134 -4
  128. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  129. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +53 -11
  130. package/src/cli/lib/inspect-plugin.ts +12 -1
  131. package/src/cli/lib/merge-plugin-tree.ts +149 -49
  132. package/src/cli/lib/plugin-surfaces.ts +104 -0
  133. package/src/cli/lib/upgrade-plugin.ts +64 -36
  134. package/src/cli/program.ts +2 -4
  135. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  136. package/src/config/assistant-feature-flags.ts +22 -7
  137. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  138. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  139. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  140. package/src/config/bundled-skills/workflows/SKILL.md +14 -7
  141. package/src/config/call-site-defaults.ts +3 -0
  142. package/src/config/feature-flag-registry.json +49 -18
  143. package/src/config/llm-resolver.ts +3 -0
  144. package/src/config/memory-v3-gate.ts +11 -0
  145. package/src/config/schema.ts +8 -6
  146. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  147. package/src/config/schemas/call-site-catalog.ts +7 -0
  148. package/src/config/schemas/channels.ts +11 -0
  149. package/src/config/schemas/llm.ts +31 -0
  150. package/src/config/schemas/memory-lifecycle.ts +3 -7
  151. package/src/config/schemas/memory-v3.ts +6 -0
  152. package/src/config/schemas/services.ts +18 -0
  153. package/src/config/seed-inference-profiles.ts +94 -34
  154. package/src/config/skills.ts +21 -0
  155. package/src/config/sync-gated-profiles.ts +220 -0
  156. package/src/contacts/contact-store.ts +2 -10
  157. package/src/contacts/contacts-write.ts +1 -2
  158. package/src/contacts/types.ts +0 -1
  159. package/src/context/compactor.ts +86 -52
  160. package/src/context/strip-injections.ts +58 -10
  161. package/src/context/token-estimator.ts +1 -1
  162. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  163. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  164. package/src/daemon/conversation-agent-loop.ts +100 -19
  165. package/src/daemon/conversation-history.ts +1 -1
  166. package/src/daemon/conversation-lifecycle.ts +3 -5
  167. package/src/daemon/conversation-process.ts +13 -5
  168. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  169. package/src/daemon/conversation-surfaces.ts +26 -0
  170. package/src/daemon/conversation-tool-setup.ts +16 -11
  171. package/src/daemon/conversation.ts +64 -14
  172. package/src/daemon/disk-pressure-policy.ts +5 -3
  173. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  174. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  175. package/src/daemon/handlers/config-a2a.ts +0 -2
  176. package/src/daemon/handlers/config-channels.ts +5 -10
  177. package/src/daemon/handlers/config-slack-channel.ts +20 -0
  178. package/src/daemon/handlers/conversations.ts +107 -0
  179. package/src/daemon/host-browser-proxy.ts +41 -0
  180. package/src/daemon/lifecycle.ts +55 -20
  181. package/src/daemon/message-provenance.ts +2 -0
  182. package/src/daemon/message-types/contacts.ts +0 -1
  183. package/src/daemon/message-types/web-activity.ts +7 -1
  184. package/src/daemon/message-types/workflows.ts +83 -1
  185. package/src/daemon/tool-setup-types.ts +4 -0
  186. package/src/daemon/trust-context.ts +1 -1
  187. package/src/events/tool-audit-listener.ts +2 -2
  188. package/src/home/feed-source-enrichment.test.ts +151 -0
  189. package/src/home/feed-source-enrichment.ts +176 -0
  190. package/src/instrument.ts +18 -6
  191. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  192. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  193. package/src/ipc/assistant-server.ts +37 -4
  194. package/src/ipc/gateway-flag-listener.ts +18 -2
  195. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  196. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  197. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  198. package/src/memory/__tests__/memory-retrospective-job.test.ts +34 -0
  199. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  200. package/src/memory/auth-fallback-events-store.ts +2 -2
  201. package/src/memory/auto-analysis-enqueue.ts +3 -5
  202. package/src/memory/canonical-guardian-store.ts +39 -1
  203. package/src/memory/conversation-crud.ts +9 -4
  204. package/src/memory/conversation-key-store.ts +17 -2
  205. package/src/memory/conversation-title-service.ts +64 -7
  206. package/src/memory/db-init.ts +10 -0
  207. package/src/memory/embedding-backend.ts +15 -1
  208. package/src/memory/jobs-worker.ts +2 -1
  209. package/src/memory/lifecycle-events-store.ts +2 -2
  210. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  211. package/src/memory/memory-retrospective-job.ts +9 -0
  212. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  213. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  214. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  215. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +10 -0
  216. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  217. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  218. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  219. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  220. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  221. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  222. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  223. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  224. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  225. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +30 -0
  226. package/src/memory/migrations/index.ts +5 -0
  227. package/src/memory/onboarding-events-store.ts +3 -3
  228. package/src/memory/schema/contacts.ts +0 -1
  229. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  230. package/src/memory/skill-loaded-events-store.ts +2 -2
  231. package/src/memory/tool-executed-events-store.test.ts +7 -7
  232. package/src/memory/turn-trace-store.test.ts +736 -0
  233. package/src/memory/turn-trace-store.ts +364 -0
  234. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  235. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  236. package/src/memory/v2/consolidation-job.ts +2 -2
  237. package/src/memory/v2/skill-content.ts +25 -7
  238. package/src/memory/v2/skill-store.ts +7 -1
  239. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  240. package/src/memory/v3-eval/eval-packets.ts +546 -0
  241. package/src/messaging/providers/slack/api.ts +31 -0
  242. package/src/messaging/providers/slack/send.test.ts +114 -2
  243. package/src/messaging/providers/slack/send.ts +30 -7
  244. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  245. package/src/messaging/providers/slack/withdraw.ts +161 -0
  246. package/src/notifications/AGENTS.md +2 -0
  247. package/src/notifications/access-request-copy.ts +72 -59
  248. package/src/notifications/adapters/slack.ts +55 -73
  249. package/src/notifications/approval-card-data.ts +333 -0
  250. package/src/notifications/broadcaster.ts +6 -2
  251. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  252. package/src/notifications/copy-composer.ts +3 -3
  253. package/src/notifications/decision-engine.ts +4 -2
  254. package/src/notifications/destination-resolver.ts +4 -6
  255. package/src/notifications/guardian-question-mode.ts +10 -0
  256. package/src/notifications/home-feed-side-effect.ts +3 -13
  257. package/src/notifications/notification-utils.ts +2 -1
  258. package/src/notifications/signal.ts +79 -43
  259. package/src/notifications/types.ts +98 -128
  260. package/src/permissions/checker.test.ts +51 -0
  261. package/src/permissions/checker.ts +185 -26
  262. package/src/permissions/ipc-risk-types.ts +24 -0
  263. package/src/permissions/question-prompter.test.ts +27 -0
  264. package/src/permissions/question-prompter.ts +4 -0
  265. package/src/platform/client.test.ts +119 -0
  266. package/src/platform/client.ts +66 -0
  267. package/src/platform/consent-cache.test.ts +267 -0
  268. package/src/platform/consent-cache.ts +174 -0
  269. package/src/plugin-api/index.ts +27 -0
  270. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  271. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  272. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  273. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  274. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  275. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  276. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  277. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  278. package/src/plugins/defaults/advisor/config.ts +21 -0
  279. package/src/plugins/defaults/advisor/consult.ts +93 -0
  280. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  281. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  282. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  283. package/src/plugins/defaults/advisor/package.json +14 -0
  284. package/src/plugins/defaults/advisor/steering.ts +67 -0
  285. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  286. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  287. package/src/plugins/defaults/index.ts +35 -0
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  290. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  291. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  293. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +75 -7
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  297. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  298. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  299. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +37 -4
  300. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  301. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  302. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  303. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -12
  304. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  305. package/src/prompts/persona-resolver.ts +12 -2
  306. package/src/prompts/templates/system-sections.ts +7 -2
  307. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  308. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  309. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  310. package/src/providers/atlascloud/client.ts +85 -0
  311. package/src/providers/fetch-provider-catalog.ts +85 -0
  312. package/src/providers/inference/adapter-factory.ts +3 -0
  313. package/src/providers/model-catalog.ts +58 -0
  314. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  315. package/src/providers/openai/chat-completions-provider.ts +7 -0
  316. package/src/providers/openai/responses-provider.ts +10 -0
  317. package/src/providers/provider-send-message.ts +11 -3
  318. package/src/providers/retry.ts +53 -12
  319. package/src/providers/search-provider-catalog.ts +10 -0
  320. package/src/providers/weak-open-model.ts +22 -0
  321. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  322. package/src/runtime/__tests__/client-health.test.ts +44 -0
  323. package/src/runtime/access-request-helper.ts +21 -53
  324. package/src/runtime/actor-trust-resolver.ts +49 -21
  325. package/src/runtime/agent-wake.ts +52 -0
  326. package/src/runtime/assistant-event-hub.ts +18 -4
  327. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  328. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  329. package/src/runtime/capabilities.test.ts +120 -0
  330. package/src/runtime/capabilities.ts +197 -0
  331. package/src/runtime/channel-approval-types.ts +5 -1
  332. package/src/runtime/channel-retry-sweep.ts +1 -0
  333. package/src/runtime/channel-verification-service.ts +1 -2
  334. package/src/runtime/client-health.ts +26 -0
  335. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  336. package/src/runtime/effective-capabilities.test.ts +128 -0
  337. package/src/runtime/effective-capabilities.ts +84 -0
  338. package/src/runtime/guardian-reply-router.ts +106 -21
  339. package/src/runtime/invite-redemption-service.ts +6 -22
  340. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  341. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  342. package/src/runtime/pending-interactions.ts +15 -0
  343. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  344. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  345. package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -13
  346. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  347. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  348. package/src/runtime/routes/client-routes.ts +10 -0
  349. package/src/runtime/routes/contact-routes.ts +31 -8
  350. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  351. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  352. package/src/runtime/routes/conversation-routes.ts +37 -12
  353. package/src/runtime/routes/events-routes.ts +1 -3
  354. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  355. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  356. package/src/runtime/routes/home-feed-routes.ts +8 -3
  357. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  358. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +88 -6
  359. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  360. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  361. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  362. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  363. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  364. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  365. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  366. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  367. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  368. package/src/runtime/routes/index.ts +2 -0
  369. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  370. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  371. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  372. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  373. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  374. package/src/runtime/routes/notification-routes.ts +122 -133
  375. package/src/runtime/routes/platform-routes.ts +2 -2
  376. package/src/runtime/routes/plugins-routes.ts +40 -7
  377. package/src/runtime/routes/secret-routes.ts +10 -0
  378. package/src/runtime/routes/surface-action-routes.ts +2 -1
  379. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  380. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  381. package/src/runtime/routes/workflow-routes.test.ts +225 -1
  382. package/src/runtime/routes/workflow-routes.ts +131 -1
  383. package/src/runtime/tool-grant-request-helper.ts +18 -16
  384. package/src/runtime/trust-context-resolver.ts +8 -5
  385. package/src/schedule/schedule-store.ts +1 -1
  386. package/src/schedule/scheduler-types.ts +5 -1
  387. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  388. package/src/security/secret-patterns.ts +3 -0
  389. package/src/subagent/manager.ts +11 -4
  390. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  391. package/src/telemetry/trace-collection-policy.ts +30 -0
  392. package/src/telemetry/types.ts +89 -0
  393. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  394. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  395. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  396. package/src/tools/browser/browser-execution.ts +29 -18
  397. package/src/tools/document/document-tool.ts +2 -3
  398. package/src/tools/executor.ts +5 -3
  399. package/src/tools/host-terminal/host-shell.ts +5 -4
  400. package/src/tools/memory/register.ts +2 -2
  401. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  402. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  403. package/src/tools/network/web-fetch.ts +372 -1
  404. package/src/tools/network/web-search.ts +213 -10
  405. package/src/tools/permission-checker.ts +3 -2
  406. package/src/tools/registry.ts +20 -0
  407. package/src/tools/schedule/create.ts +4 -3
  408. package/src/tools/schedule/update.ts +2 -1
  409. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  410. package/src/tools/skills/execute.ts +1 -2
  411. package/src/tools/subagent/spawn.ts +37 -13
  412. package/src/tools/terminal/shell.ts +10 -4
  413. package/src/tools/tool-approval-handler.ts +17 -10
  414. package/src/tools/types.ts +9 -0
  415. package/src/tools/ui-surface/definitions.ts +25 -2
  416. package/src/tools/verification-control-plane-policy.ts +3 -1
  417. package/src/tools/workflows/run-workflow.ts +1 -0
  418. package/src/util/disk-usage.ts +78 -23
  419. package/src/util/platform.ts +8 -1
  420. package/src/watcher/telemetry.ts +2 -2
  421. package/src/workflows/engine.test.ts +175 -1
  422. package/src/workflows/engine.ts +82 -0
  423. package/src/workflows/journal-store.test.ts +70 -0
  424. package/src/workflows/journal-store.ts +18 -3
  425. package/src/workflows/run-manager.test.ts +171 -3
  426. package/src/workflows/run-manager.ts +64 -0
  427. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  428. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  429. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  430. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  431. package/src/workspace/migrations/registry.ts +8 -0
  432. package/src/notifications/tool-approval-copy.ts +0 -142
  433. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
@@ -0,0 +1,120 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import type { TrustClass } from "./actor-trust-resolver.js";
4
+ import { type CapabilitySet, resolveCapabilities } from "./capabilities.js";
5
+
6
+ /**
7
+ * The capability matrix — the single source of truth for what each trust class
8
+ * may do. If a call site's behavior changes during migration, it changes here
9
+ * (reviewed in one diff) instead of across the ~40 inline conditionals.
10
+ */
11
+ const MATRIX: Record<TrustClass, CapabilitySet> = {
12
+ guardian: {
13
+ canSelfApproveTools: true,
14
+ sensitiveToolApproval: "self",
15
+ canManageSchedules: true,
16
+ canUseVerificationControlPlane: true,
17
+ canSelfAuthorizeArchiveBySender: true,
18
+ canAccessMemory: true,
19
+ canAccessPrivilegedDocuments: true,
20
+ canRunUnsandboxedShell: true,
21
+ mayBeInteractive: true,
22
+ canActUnderDiskPressureCleanup: true,
23
+ promptTrustGuidance: "none",
24
+ },
25
+ trusted_contact: {
26
+ canSelfApproveTools: false,
27
+ sensitiveToolApproval: "escalate-and-wait",
28
+ canManageSchedules: false,
29
+ canUseVerificationControlPlane: false,
30
+ canSelfAuthorizeArchiveBySender: false,
31
+ canAccessMemory: false,
32
+ canAccessPrivilegedDocuments: false,
33
+ canRunUnsandboxedShell: false,
34
+ mayBeInteractive: true,
35
+ canActUnderDiskPressureCleanup: false,
36
+ promptTrustGuidance: "social-engineering-defense",
37
+ },
38
+ unverified_contact: {
39
+ canSelfApproveTools: false,
40
+ sensitiveToolApproval: "escalate-and-wait",
41
+ canManageSchedules: false,
42
+ canUseVerificationControlPlane: false,
43
+ canSelfAuthorizeArchiveBySender: false,
44
+ canAccessMemory: false,
45
+ canAccessPrivilegedDocuments: false,
46
+ canRunUnsandboxedShell: false,
47
+ mayBeInteractive: true,
48
+ canActUnderDiskPressureCleanup: false,
49
+ promptTrustGuidance: "social-engineering-defense",
50
+ },
51
+ unknown: {
52
+ canSelfApproveTools: false,
53
+ sensitiveToolApproval: "deny",
54
+ canManageSchedules: false,
55
+ canUseVerificationControlPlane: false,
56
+ canSelfAuthorizeArchiveBySender: false,
57
+ canAccessMemory: false,
58
+ canAccessPrivilegedDocuments: false,
59
+ canRunUnsandboxedShell: false,
60
+ mayBeInteractive: false,
61
+ canActUnderDiskPressureCleanup: false,
62
+ promptTrustGuidance: "stranger-warning",
63
+ },
64
+ };
65
+
66
+ describe("resolveCapabilities", () => {
67
+ for (const trustClass of Object.keys(MATRIX) as TrustClass[]) {
68
+ test(`resolves the full capability set for "${trustClass}"`, () => {
69
+ expect(resolveCapabilities(trustClass)).toEqual(MATRIX[trustClass]);
70
+ });
71
+ }
72
+
73
+ test("undefined fail-closes to the `unknown` capability set", () => {
74
+ expect(resolveCapabilities(undefined)).toEqual(MATRIX.unknown);
75
+ });
76
+
77
+ test("an unrecognized/legacy string fail-closes to the `unknown` capability set", () => {
78
+ expect(resolveCapabilities("non_guardian")).toEqual(MATRIX.unknown);
79
+ expect(resolveCapabilities("some_future_role")).toEqual(MATRIX.unknown);
80
+ });
81
+
82
+ test("inherited Object.prototype keys fail-closed (no prototype read)", () => {
83
+ for (const key of [
84
+ "__proto__",
85
+ "constructor",
86
+ "toString",
87
+ "hasOwnProperty",
88
+ ]) {
89
+ expect(resolveCapabilities(key)).toEqual(MATRIX.unknown);
90
+ }
91
+ });
92
+
93
+ test("unverified_contact is byte-for-byte identical to trusted_contact (admission-only distinction)", () => {
94
+ expect(resolveCapabilities("unverified_contact")).toEqual(
95
+ resolveCapabilities("trusted_contact"),
96
+ );
97
+ });
98
+
99
+ test("only guardian self-approves; only guardian/contacts may be interactive", () => {
100
+ expect(resolveCapabilities("guardian").canSelfApproveTools).toBe(true);
101
+ expect(resolveCapabilities("trusted_contact").canSelfApproveTools).toBe(
102
+ false,
103
+ );
104
+
105
+ expect(resolveCapabilities("guardian").mayBeInteractive).toBe(true);
106
+ expect(resolveCapabilities("trusted_contact").mayBeInteractive).toBe(true);
107
+ expect(resolveCapabilities("unverified_contact").mayBeInteractive).toBe(
108
+ true,
109
+ );
110
+ expect(resolveCapabilities("unknown").mayBeInteractive).toBe(false);
111
+ });
112
+
113
+ test("sensitive tool approval is graded across the three tiers", () => {
114
+ expect(resolveCapabilities("guardian").sensitiveToolApproval).toBe("self");
115
+ expect(resolveCapabilities("trusted_contact").sensitiveToolApproval).toBe(
116
+ "escalate-and-wait",
117
+ );
118
+ expect(resolveCapabilities("unknown").sensitiveToolApproval).toBe("deny");
119
+ });
120
+ });
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Trust capabilities — what an actor may do once admitted.
3
+ *
4
+ * Separates *what an actor can do* (capabilities/permissions) from *who the
5
+ * actor is* (`TrustClass`, their role/level). The ~40+ decision sites
6
+ * read a named capability instead of re-deriving permissions inline from the
7
+ * This resolves the class to a named capability set in one place so call sites
8
+ * read a capability instead of re-deriving it.
9
+ *
10
+ * Stateless / derive-on-read: capabilities are derived from the already-resolved
11
+ * (and already-persisted) `trustClass` at point of use. Nothing here is stored;
12
+ * `trustClass` remains the persisted field in retry payloads, the journal store,
13
+ * and conversation CRUD.
14
+ *
15
+ * Admission is a SEPARATE axis: `TRUST_CLASS_RANK` vs `ADMISSION_FLOOR`
16
+ * ("who gets in the door") is intentionally not modeled here. `CapabilitySet`
17
+ * is the "what they may do once inside" axis.
18
+ *
19
+ * Context-dependent decisions (interactivity routing, self-approval races,
20
+ * channel-specific overrides) COMPOSE these primitives with runtime context.
21
+ * They are not encoded in the table; named composition helpers live in
22
+ * `effective-capabilities.ts` (and `resolveRoutingState` in
23
+ * `trust-context-resolver.ts`).
24
+ */
25
+
26
+ import type { TrustClass } from "./actor-trust-resolver.js";
27
+
28
+ /**
29
+ * Outcome when an actor invokes a tool that requires guardian approval.
30
+ * - `self`: the actor self-approves (guardian).
31
+ * - `escalate-and-wait`: route to the guardian and wait inline for a grant.
32
+ * - `deny`: fail-closed, no escalation or wait.
33
+ */
34
+ export type SensitiveToolApproval = "self" | "escalate-and-wait" | "deny";
35
+
36
+ /**
37
+ * Which trust-guidance block to inject into the model prompt. The capability
38
+ * layer owns the *selector*; the prompt layer owns the copy for each value.
39
+ */
40
+ export type PromptTrustGuidance =
41
+ | "none"
42
+ | "social-engineering-defense"
43
+ | "stranger-warning";
44
+
45
+ /**
46
+ * What an actor may do once admitted, derived purely from their trust class.
47
+ */
48
+ export interface CapabilitySet {
49
+ // --- Tool approval mechanism ---
50
+ /**
51
+ * Auto-approves *ordinary* tool calls (e.g. background/platform-hosted bash)
52
+ * and honors the actor's own pending-confirmation callback. The
53
+ * sensitive/guardian-required tool path is governed separately by
54
+ * `sensitiveToolApproval`; the two are independent levers.
55
+ */
56
+ canSelfApproveTools: boolean;
57
+ /** Outcome when a guardian-approval-required (sensitive) tool is invoked. */
58
+ sensitiveToolApproval: SensitiveToolApproval;
59
+
60
+ // --- Privileged tool gates ---
61
+ /** May create/update schedules. */
62
+ canManageSchedules: boolean;
63
+ /** May use verification control-plane tools. */
64
+ canUseVerificationControlPlane: boolean;
65
+ /**
66
+ * May treat its own `user_approved` flag as sufficient authorization to
67
+ * archive-by-sender. Non-guardians can still archive by sender, but must be
68
+ * authorized via surface action, task, or explicit prompt approval instead.
69
+ */
70
+ canSelfAuthorizeArchiveBySender: boolean;
71
+
72
+ // --- Data & memory ---
73
+ /**
74
+ * May access long-term / cross-conversation memory — both the memory *tools*
75
+ * (recall, auto-analysis, retrospection, graph extraction) and *visibility*
76
+ * of cross-conversation history in assembled context. Untrusted actors are
77
+ * walled off from both.
78
+ */
79
+ canAccessMemory: boolean;
80
+ /**
81
+ * May perform privileged (non-conversation-scoped) document operations from
82
+ * trust class alone. The effective decision also honors privileged channels —
83
+ * see `canActOnPrivilegedDocuments` in `effective-capabilities.ts`.
84
+ */
85
+ canAccessPrivilegedDocuments: boolean;
86
+
87
+ // --- Execution environment ---
88
+ /**
89
+ * May run the shell WITHOUT the untrusted sandbox / CES lockdown (no
90
+ * `VELLUM_UNTRUSTED_SHELL`, no credential-secrecy confinement).
91
+ */
92
+ canRunUnsandboxedShell: boolean;
93
+
94
+ // --- Interactivity & resource ---
95
+ /**
96
+ * Trust class alone permits interactive guardian-approval waits. Composed
97
+ * downstream with `guardianRouteResolvable` to derive `promptWaitingAllowed`
98
+ * (see `resolveRoutingState`).
99
+ */
100
+ mayBeInteractive: boolean;
101
+ /** May trigger cleanup-mode operations under disk pressure. */
102
+ canActUnderDiskPressureCleanup: boolean;
103
+
104
+ // --- Prompt shaping ---
105
+ /** Which trust-guidance block to inject into the model prompt. */
106
+ promptTrustGuidance: PromptTrustGuidance;
107
+ }
108
+
109
+ /**
110
+ * Guardian: full control-plane access, self-approves tools.
111
+ */
112
+ const GUARDIAN_CAPABILITIES: CapabilitySet = {
113
+ canSelfApproveTools: true,
114
+ sensitiveToolApproval: "self",
115
+ canManageSchedules: true,
116
+ canUseVerificationControlPlane: true,
117
+ canSelfAuthorizeArchiveBySender: true,
118
+ canAccessMemory: true,
119
+ canAccessPrivilegedDocuments: true,
120
+ canRunUnsandboxedShell: true,
121
+ mayBeInteractive: true,
122
+ canActUnderDiskPressureCleanup: true,
123
+ promptTrustGuidance: "none",
124
+ };
125
+
126
+ /**
127
+ * Trusted / unverified contacts: may invoke tools but escalate sensitive ones
128
+ * to the guardian; no privileged data access; sandboxed execution.
129
+ *
130
+ * `trusted_contact` and `unverified_contact` are deliberately identical here —
131
+ * the distinction is admission-only (see `actor-trust-resolver.ts`). The matrix
132
+ * test pins this invariant.
133
+ */
134
+ const CONTACT_CAPABILITIES: CapabilitySet = {
135
+ canSelfApproveTools: false,
136
+ sensitiveToolApproval: "escalate-and-wait",
137
+ canManageSchedules: false,
138
+ canUseVerificationControlPlane: false,
139
+ canSelfAuthorizeArchiveBySender: false,
140
+ canAccessMemory: false,
141
+ canAccessPrivilegedDocuments: false,
142
+ canRunUnsandboxedShell: false,
143
+ mayBeInteractive: true,
144
+ canActUnderDiskPressureCleanup: false,
145
+ promptTrustGuidance: "social-engineering-defense",
146
+ };
147
+
148
+ /**
149
+ * Unknown actors: fail-closed. No escalation, no interactivity, treated as a
150
+ * potential stranger in the prompt.
151
+ */
152
+ const UNKNOWN_CAPABILITIES: CapabilitySet = {
153
+ canSelfApproveTools: false,
154
+ sensitiveToolApproval: "deny",
155
+ canManageSchedules: false,
156
+ canUseVerificationControlPlane: false,
157
+ canSelfAuthorizeArchiveBySender: false,
158
+ canAccessMemory: false,
159
+ canAccessPrivilegedDocuments: false,
160
+ canRunUnsandboxedShell: false,
161
+ mayBeInteractive: false,
162
+ canActUnderDiskPressureCleanup: false,
163
+ promptTrustGuidance: "stranger-warning",
164
+ };
165
+
166
+ const CAPABILITIES_BY_CLASS: Record<TrustClass, CapabilitySet> = {
167
+ guardian: GUARDIAN_CAPABILITIES,
168
+ trusted_contact: CONTACT_CAPABILITIES,
169
+ unverified_contact: CONTACT_CAPABILITIES,
170
+ unknown: UNKNOWN_CAPABILITIES,
171
+ };
172
+
173
+ /**
174
+ * Resolve the capability set for a trust class. Pure and stateless.
175
+ *
176
+ * This is the single fail-closed trust boundary: any value that is not a
177
+ * recognized `TrustClass` — including `undefined` and legacy/persisted strings
178
+ * (e.g. `"non_guardian"`) — resolves to the `unknown` capability set. Callers
179
+ * pass their raw trust value directly; they never re-derive "is this a known
180
+ * class?" at the call site. The `(string & {})` member keeps autocomplete for
181
+ * the known classes while still accepting an arbitrary string.
182
+ *
183
+ * The lookup uses an own-property check so raw values that name inherited
184
+ * members (`"__proto__"`, `"constructor"`, `"toString"`) fail closed to the
185
+ * `unknown` set rather than reading off `Object.prototype`.
186
+ */
187
+ export function resolveCapabilities(
188
+ trustClass: TrustClass | (string & {}) | undefined,
189
+ ): CapabilitySet {
190
+ if (
191
+ trustClass != null &&
192
+ Object.prototype.hasOwnProperty.call(CAPABILITIES_BY_CLASS, trustClass)
193
+ ) {
194
+ return CAPABILITIES_BY_CLASS[trustClass as TrustClass];
195
+ }
196
+ return UNKNOWN_CAPABILITIES;
197
+ }
@@ -12,12 +12,16 @@ import type { ApprovalActionOption } from "@vellumai/gateway-client";
12
12
 
13
13
  import type { GuardianDecisionAction } from "./guardian-decision-types.js";
14
14
 
15
- // Re-export shared wire types so existing daemon imports keep working.
16
15
  export type {
17
16
  ApprovalActionOption,
18
17
  ApprovalUIMetadata,
19
18
  PermissionRequestDetails,
20
19
  } from "@vellumai/gateway-client";
20
+ // Re-export shared wire types + schemas so existing daemon imports keep working.
21
+ export {
22
+ ApprovalUIMetadataSchema,
23
+ PermissionRequestDetailsSchema,
24
+ } from "@vellumai/gateway-client";
21
25
 
22
26
  // ---------------------------------------------------------------------------
23
27
  // Approval actions (daemon-internal)
@@ -52,6 +52,7 @@ function parseTrustRuntimeContext(value: unknown): TrustContext | undefined {
52
52
  if (
53
53
  trustClass !== "guardian" &&
54
54
  trustClass !== "trusted_contact" &&
55
+ trustClass !== "unverified_contact" &&
55
56
  trustClass !== "unknown"
56
57
  ) {
57
58
  return undefined;
@@ -333,8 +333,7 @@ export function getGuardianBinding(
333
333
  id: result.channel.id,
334
334
  assistantId,
335
335
  channel,
336
- guardianExternalUserId:
337
- result.channel.externalUserId ?? result.channel.address ?? "",
336
+ guardianExternalUserId: result.channel.address,
338
337
  guardianDeliveryChatId: result.channel.externalChatId ?? "",
339
338
  guardianPrincipalId: result.contact.principalId ?? "",
340
339
  status: "active" as const,
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Health heuristics for connected SSE clients.
3
+ */
4
+
5
+ /** Keep-alive comment sent to idle clients every 7 s by default. */
6
+ export const DEFAULT_HEARTBEAT_INTERVAL_MS = 7_000;
7
+
8
+ /**
9
+ * Whether a client looks degraded: its last heartbeat (`lastActiveAt`) is
10
+ * stale by more than two heartbeat intervals. This surfaces a
11
+ * registered-but-not-heartbeating connection — the symptom of a flapping
12
+ * extension SSE stream — whether it never heartbeated or heartbeated and
13
+ * then froze. A healthy client (fresh or actively heartbeating) has a
14
+ * recent `lastActiveAt` and is not flagged.
15
+ *
16
+ * Limitation: a just-reconnected flapping client has a fresh
17
+ * `lastActiveAt` and looks healthy in a single snapshot — this is
18
+ * best-effort visibility, not a liveness guarantee.
19
+ */
20
+ export function isClientDegraded(
21
+ lastActiveAt: Date,
22
+ now: Date,
23
+ heartbeatIntervalMs: number,
24
+ ): boolean {
25
+ return now.getTime() - lastActiveAt.getTime() > 2 * heartbeatIntervalMs;
26
+ }
@@ -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
+ });