@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
package/src/instrument.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  getPlatformUserId,
8
8
  getSentryDsn,
9
9
  } from "./config/env.js";
10
+ import { getCachedShareDiagnostics } from "./platform/consent-cache.js";
10
11
  import { APP_VERSION, COMMIT_SHA } from "./version.js";
11
12
 
12
13
  /** Patterns that match sensitive data in Sentry event values. */
@@ -41,9 +42,11 @@ function redactObject(obj: unknown): unknown {
41
42
  /**
42
43
  * Call after dotenv has loaded so SENTRY_DSN_ASSISTANT is available.
43
44
  * Initializes Sentry when the DSN is set; no-ops when empty/unset so
44
- * local dev builds don't send crash reports. If the user later opts out
45
- * via the sendDiagnostics config key (or VELLUM_DEV=1), call closeSentry()
46
- * after config is loaded to stop future event capturing.
45
+ * local dev builds don't send crash reports. Events are dropped by
46
+ * beforeSend until the platform share_diagnostics consent confirms opt-in
47
+ * (re-read from the consent cache per event, so a revocation takes effect
48
+ * within one refresh cycle); VELLUM_DEV=1 and legacyDiagnosticsOptOut
49
+ * hard-disable via closeSentry() after config loads.
47
50
  */
48
51
  export function initSentry(): void {
49
52
  const dsn = getSentryDsn();
@@ -54,6 +57,14 @@ export function initSentry(): void {
54
57
  dist: COMMIT_SHA,
55
58
  environment: process.env.VELLUM_ENVIRONMENT ?? "production",
56
59
  sendDefaultPii: false,
60
+ // Fail-closed: suppress client-report pings (discarded-event counts) so an
61
+ // assistant with unconfirmed share_diagnostics consent stays network-silent.
62
+ sendClientReports: false,
63
+ // Drop the default ProcessSession integration: it starts a release-health
64
+ // session on init and flushes a session envelope on process exit (a network
65
+ // request not covered by the beforeSend event gate), violating fail-closed.
66
+ integrations: (defaults) =>
67
+ defaults.filter((i) => i.name !== "ProcessSession"),
57
68
  serverName: hostname(),
58
69
  initialScope: {
59
70
  tags: {
@@ -77,6 +88,7 @@ export function initSentry(): void {
77
88
  },
78
89
  },
79
90
  beforeSend(event) {
91
+ if (!getCachedShareDiagnostics()) return null;
80
92
  if (event.exception?.values) {
81
93
  event.exception.values = event.exception.values.map((ex) => ({
82
94
  ...ex,
@@ -101,9 +113,9 @@ export function initSentry(): void {
101
113
  }
102
114
 
103
115
  /**
104
- * Stop capturing future Sentry events. Called after config loads when the
105
- * user has disabled sendDiagnostics so that early-startup crashes are
106
- * still captured but subsequent events are suppressed.
116
+ * Stop capturing future Sentry events. Called after config loads for the
117
+ * legacyDiagnosticsOptOut local opt-out (or VELLUM_DEV=1) to hard-disable
118
+ * crash reporting independent of the share_diagnostics consent gate.
107
119
  */
108
120
  export async function closeSentry(): Promise<void> {
109
121
  await Sentry.close();
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Unit tests for how `AssistantIpcServer.sendResult` handles a binary or
3
+ * streaming handler result over the IPC transport.
4
+ *
5
+ * A `RouteResponse` (or a bare `Uint8Array` / `ReadableStream` / `Blob`)
6
+ * wraps raw bytes that can't be carried as a JSON `result` field. Rather than
7
+ * silently serialize the body into garbage, the server reports a structured
8
+ * `BINARY_UNSUPPORTED_OVER_IPC` error; the gateway IPC proxy uses that signal
9
+ * to fall back to the HTTP proxy, which streams binary responses correctly.
10
+ */
11
+
12
+ import { describe, expect, test } from "bun:test";
13
+
14
+ import { RouteResponse } from "../../runtime/routes/types.js";
15
+ import { AssistantIpcServer } from "../assistant-server.js";
16
+
17
+ /**
18
+ * `sendResult` is private; access it through an interface cast so the test
19
+ * exercises the real production path without a test-only API on the class.
20
+ */
21
+ type PrivateApi = {
22
+ sendResult(
23
+ socket: unknown,
24
+ reader: unknown,
25
+ requestId: string,
26
+ value: unknown,
27
+ ): void;
28
+ };
29
+
30
+ /**
31
+ * Drive `sendResult` with a capturing socket in legacy (newline-delimited
32
+ * JSON) mode — the same wire shape the gateway's IPC client speaks — and
33
+ * return the parsed response envelope.
34
+ */
35
+ function captureSendResult(value: unknown): Record<string, unknown> {
36
+ const server = new AssistantIpcServer() as unknown as PrivateApi;
37
+ const writes: string[] = [];
38
+ const socket = {
39
+ destroyed: false,
40
+ write: (chunk: string) => {
41
+ writes.push(chunk);
42
+ return true;
43
+ },
44
+ };
45
+ const reader = { isLegacy: true };
46
+
47
+ server.sendResult(socket, reader, "req-1", value);
48
+
49
+ expect(writes).toHaveLength(1);
50
+ return JSON.parse(writes[0]) as Record<string, unknown>;
51
+ }
52
+
53
+ describe("AssistantIpcServer.sendResult binary handling", () => {
54
+ test("a binary RouteResponse is reported as BINARY_UNSUPPORTED_OVER_IPC", () => {
55
+ const env = captureSendResult(
56
+ new RouteResponse(new Uint8Array([137, 80, 78, 71]), {
57
+ "content-type": "image/png",
58
+ }),
59
+ );
60
+
61
+ expect(env.id).toBe("req-1");
62
+ expect(env.statusCode).toBe(421);
63
+ expect(env.errorCode).toBe("BINARY_UNSUPPORTED_OVER_IPC");
64
+ // The binary body must never be JSON-serialized into a `result` field.
65
+ expect("result" in env).toBe(false);
66
+ });
67
+
68
+ test("a bare Uint8Array result is also reported as unsupported", () => {
69
+ const env = captureSendResult(new Uint8Array([1, 2, 3]));
70
+
71
+ expect(env.errorCode).toBe("BINARY_UNSUPPORTED_OVER_IPC");
72
+ expect("result" in env).toBe(false);
73
+ });
74
+
75
+ test("a plain JSON result still serializes into `result`", () => {
76
+ const env = captureSendResult({ ok: true, count: 2 });
77
+
78
+ expect(env.result).toEqual({ ok: true, count: 2 });
79
+ expect(env.errorCode).toBeUndefined();
80
+ });
81
+ });
@@ -166,4 +166,24 @@ describe("assistant clients list over IPC — same-user filter", () => {
166
166
  const ids = parsed.clients.map((c) => c.clientId).sort();
167
167
  expect(ids).toEqual(["client-other", "client-self"]);
168
168
  });
169
+
170
+ test("--json output carries the degraded flag (false for fresh clients)", async () => {
171
+ registerClient({
172
+ clientId: "client-self-1",
173
+ actorPrincipalId: "guardian-local",
174
+ });
175
+
176
+ await startServer();
177
+
178
+ const { stdout } = await runAssistantCommandFull(
179
+ "clients",
180
+ "list",
181
+ "--json",
182
+ );
183
+ const parsed = JSON.parse(stdout.trim()) as {
184
+ clients: Array<{ clientId: string; degraded?: boolean }>;
185
+ };
186
+ expect(parsed.clients).toHaveLength(1);
187
+ expect(parsed.clients[0].degraded).toBe(false);
188
+ });
169
189
  });
@@ -31,10 +31,7 @@
31
31
  import { existsSync, unlinkSync } from "node:fs";
32
32
  import { createServer, type Server, type Socket } from "node:net";
33
33
 
34
- import {
35
- ensureSocketDir,
36
- SocketWatchdog,
37
- } from "@vellumai/ipc-server-utils";
34
+ import { ensureSocketDir, SocketWatchdog } from "@vellumai/ipc-server-utils";
38
35
 
39
36
  import { findLocalGuardianPrincipalId } from "../runtime/local-actor-identity.js";
40
37
  import { RouteError } from "../runtime/routes/errors.js";
@@ -43,6 +40,7 @@ import type {
43
40
  RouteDefinition,
44
41
  RouteHandlerArgs,
45
42
  } from "../runtime/routes/types.js";
43
+ import { RouteResponse } from "../runtime/routes/types.js";
46
44
  import { getLogger } from "../util/logger.js";
47
45
  import {
48
46
  type IpcEnvelope,
@@ -134,6 +132,26 @@ function isIpcBinaryResponse(value: unknown): value is IpcBinaryResponse {
134
132
  );
135
133
  }
136
134
 
135
+ /**
136
+ * A handler result whose body is raw bytes or a stream rather than a JSON
137
+ * value — `RouteResponse` (e.g. `workspace/file/content` returns a
138
+ * `Bun.file`), or a bare `Uint8Array` / `ReadableStream` / `Blob`.
139
+ *
140
+ * These cannot be carried as a JSON `result` field, so over the IPC
141
+ * transport they are reported as a structured error rather than silently
142
+ * JSON-serialized into garbage. Distinct from the `IpcBinaryResponse` /
143
+ * `IpcStreamingResponse` wrappers, which are explicit binary envelopes the
144
+ * framing protocol does transmit.
145
+ */
146
+ function isNonJsonIpcResult(value: unknown): boolean {
147
+ return (
148
+ value instanceof RouteResponse ||
149
+ value instanceof Uint8Array ||
150
+ value instanceof ReadableStream ||
151
+ value instanceof Blob
152
+ );
153
+ }
154
+
137
155
  // ---------------------------------------------------------------------------
138
156
  // Server
139
157
  // ---------------------------------------------------------------------------
@@ -401,6 +419,8 @@ export class AssistantIpcServer {
401
419
  * Route a handler result to the appropriate send path:
402
420
  * - IpcStreamingResponse → chunked binary frames
403
421
  * - IpcBinaryResponse → single binary frame with content-length
422
+ * - A raw binary/stream result (RouteResponse, Uint8Array, ...) → structured
423
+ * BINARY_UNSUPPORTED_OVER_IPC error (the transport can't carry it as JSON)
404
424
  * - Everything else → JSON response
405
425
  */
406
426
  private sendResult(
@@ -420,6 +440,19 @@ export class AssistantIpcServer {
420
440
  },
421
441
  };
422
442
  this.sendResponse(socket, reader, envelope, value.binary);
443
+ } else if (isNonJsonIpcResult(value)) {
444
+ // A binary/streaming handler result (e.g. a file-content RouteResponse
445
+ // wrapping a Bun.file) cannot be carried as a JSON `result`. Report a
446
+ // structured error instead of silently serializing it into garbage; the
447
+ // gateway IPC proxy treats this code as a signal to retry over HTTP,
448
+ // which streams binary correctly.
449
+ this.sendResponse(socket, reader, {
450
+ id: requestId,
451
+ error:
452
+ "Binary/streaming responses are not supported over the IPC transport; use HTTP",
453
+ statusCode: 421,
454
+ errorCode: "BINARY_UNSUPPORTED_OVER_IPC",
455
+ });
423
456
  } else {
424
457
  this.sendResponse(socket, reader, { id: requestId, result: value });
425
458
  }
@@ -1,7 +1,9 @@
1
1
  import { connect, type Socket } from "node:net";
2
2
 
3
3
  import { refreshOverridesFromGateway } from "../config/assistant-feature-flags.js";
4
+ import { reconcileFlagGatedProfiles } from "../config/sync-gated-profiles.js";
4
5
  import { SYNC_TAGS } from "../daemon/message-types/sync.js";
6
+ import { publishConfigChanged } from "../runtime/sync/resource-sync-events.js";
5
7
  import { publishSyncInvalidation } from "../runtime/sync/sync-publisher.js";
6
8
  import { syncFlagGatedTools } from "../tools/registry.js";
7
9
  import { getLogger } from "../util/logger.js";
@@ -19,11 +21,25 @@ const log = getLogger("gateway-flag-listener");
19
21
  * they live in a flag-gated bundled skill surfaced via `skill_load` against the
20
22
  * live flag cache, so they need no registry sync here. `syncFlagGatedTools` is
21
23
  * idempotent and enable-only, so re-running it on every refresh is safe; it also
22
- * never throws (it logs internally).
24
+ * never throws (it logs internally). After tools sync, `reconcileFlagGatedProfiles`
25
+ * adds or removes the flag-gated managed profile (OS Beta); when it reports a
26
+ * change a `config_changed` broadcast refreshes the profile picker on clients.
27
+ * The profile reconcile runs only when the refresh confirmed flags loaded from
28
+ * the gateway — a transient IPC failure leaves the cache unset and resolves
29
+ * `os-beta` to its registry default `false`, which would remove the user's
30
+ * profile and reset their selection. Tool sync tolerates the default and stays
31
+ * unconditional.
23
32
  */
24
33
  function refreshFlagsAndSyncTools(context: string): void {
25
34
  refreshOverridesFromGateway()
26
- .then(() => syncFlagGatedTools())
35
+ .then(async (loaded) => {
36
+ await syncFlagGatedTools();
37
+ if (loaded && reconcileFlagGatedProfiles()) {
38
+ // Reuse the config-changed broadcast clients already consume so the
39
+ // profile picker reflects the added/removed managed profile.
40
+ publishConfigChanged();
41
+ }
42
+ })
27
43
  .catch((err) => {
28
44
  log.warn(
29
45
  { err },
@@ -76,17 +76,6 @@ mock.module("../jobs-store.js", () => ({
76
76
  },
77
77
  }));
78
78
 
79
- // Mirror production semantics from `isUntrustedTrustClass` in
80
- // actor-trust-resolver.ts: anything that isn't `guardian` is untrusted.
81
- // Keeping these in sync guards the compaction trust boundary — a drifting
82
- // mock would let regressions pass as false positives.
83
- mock.module("../../runtime/actor-trust-resolver.js", () => ({
84
- isUntrustedTrustClass: (trustClass: string | undefined) =>
85
- trustClass === "trusted_contact" ||
86
- trustClass === "unknown" ||
87
- trustClass === undefined,
88
- }));
89
-
90
79
  import {
91
80
  enqueueAutoAnalysisIfEnabled,
92
81
  enqueueAutoAnalysisOnCompaction,
@@ -315,9 +304,9 @@ describe("enqueueAutoAnalysisOnCompaction", () => {
315
304
  });
316
305
 
317
306
  test("undefined trust class — skips (fail-closed when trust is unresolved)", () => {
318
- // `isUntrustedTrustClass(undefined)` is true in production, so
319
- // compaction-triggered analysis must NOT fire when the caller cannot
320
- // establish a trust class.
307
+ // `resolveCapabilities(undefined).canAccessMemory` is false in
308
+ // production, so compaction-triggered analysis must NOT fire when the
309
+ // caller cannot establish a trust class.
321
310
  enqueueAutoAnalysisOnCompaction("c1", undefined);
322
311
 
323
312
  expect(enqueueCalls).toHaveLength(0);
@@ -332,8 +321,8 @@ describe("enqueueAutoAnalysisOnCompaction", () => {
332
321
  });
333
322
 
334
323
  test("trusted_contact trust class — skips (only guardian is trusted)", () => {
335
- // trusted_contact is in the untrusted set per production
336
- // `isUntrustedTrustClass`, so compaction-triggered analysis must NOT
324
+ // trusted_contact lacks `canAccessMemory` per production
325
+ // `resolveCapabilities`, so compaction-triggered analysis must NOT
337
326
  // fire. Only `guardian` passes the gate.
338
327
  enqueueAutoAnalysisOnCompaction("c1", "trusted_contact");
339
328
 
@@ -48,14 +48,12 @@ mock.module("../../config/assistant-feature-flags.js", () => ({
48
48
  isAssistantFeatureFlagEnabled: () => true,
49
49
  }));
50
50
 
51
- // Stub trust resolver — never claim the actor is untrusted in tests.
52
- mock.module("../../runtime/actor-trust-resolver.js", () => ({
53
- isUntrustedTrustClass: () => false,
54
- }));
55
-
56
51
  // Stub the conversation-source lookup so the recursion guards in the
57
52
  // retrospective and auto-analysis paths fall through to the enqueue.
53
+ // `getConversation` returning null keeps `isLowYieldRetrospectiveSource`
54
+ // false, so the retrospective is enqueued rather than skipped.
58
55
  mock.module("../conversation-crud.js", () => ({
56
+ getConversation: () => null,
59
57
  getConversationSource: () => null,
60
58
  reserveMessage: mock(async () => ({ id: "msg-reserve" })),
61
59
  }));
@@ -117,12 +115,10 @@ mock.module("../db-connection.js", () => ({
117
115
 
118
116
  // Now load the real modules under test.
119
117
  const { isMemoryEnabled } = await import("../jobs-store.js");
120
- const { enqueueAutoAnalysisIfEnabled } = await import(
121
- "../auto-analysis-enqueue.js"
122
- );
123
- const { enqueueMemoryRetrospectiveIfEnabled } = await import(
124
- "../memory-retrospective-enqueue.js"
125
- );
118
+ const { enqueueAutoAnalysisIfEnabled } =
119
+ await import("../auto-analysis-enqueue.js");
120
+ const { enqueueMemoryRetrospectiveIfEnabled } =
121
+ await import("../memory-retrospective-enqueue.js");
126
122
 
127
123
  beforeEach(() => {
128
124
  dbInserts.length = 0;
@@ -12,6 +12,8 @@ mock.module("../../util/logger.js", () => ({
12
12
  // ---------------------------------------------------------------------------
13
13
 
14
14
  let sourceTag: string | null = null;
15
+ let convType = "standard";
16
+ let convSource = "user";
15
17
  const upsertCalls: Array<{
16
18
  payload: { conversationId: string };
17
19
  runAfter: number;
@@ -19,6 +21,10 @@ const upsertCalls: Array<{
19
21
 
20
22
  mock.module("../conversation-crud.js", () => ({
21
23
  getConversationSource: (_id: string) => sourceTag,
24
+ getConversation: (_id: string) => ({
25
+ conversationType: convType,
26
+ source: convSource,
27
+ }),
22
28
  reserveMessage: mock(async () => ({ id: "msg-reserve" })),
23
29
  }));
24
30
 
@@ -31,13 +37,6 @@ mock.module("../jobs-store.js", () => ({
31
37
  },
32
38
  }));
33
39
 
34
- mock.module("../../runtime/actor-trust-resolver.js", () => ({
35
- isUntrustedTrustClass: (trustClass: string | undefined) =>
36
- trustClass === "trusted_contact" ||
37
- trustClass === "unknown" ||
38
- trustClass === undefined,
39
- }));
40
-
41
40
  import {
42
41
  enqueueMemoryRetrospectiveIfEnabled,
43
42
  enqueueMemoryRetrospectiveOnCompaction,
@@ -47,6 +46,8 @@ import {
47
46
  describe("enqueueMemoryRetrospectiveIfEnabled", () => {
48
47
  beforeEach(() => {
49
48
  sourceTag = null;
49
+ convType = "standard";
50
+ convSource = "user";
50
51
  upsertCalls.length = 0;
51
52
  });
52
53
 
@@ -85,6 +86,35 @@ describe("enqueueMemoryRetrospectiveIfEnabled", () => {
85
86
  });
86
87
  expect(upsertCalls).toHaveLength(0);
87
88
  });
89
+
90
+ test("scheduled conversation — skips enqueue", () => {
91
+ convType = "scheduled";
92
+ enqueueMemoryRetrospectiveIfEnabled({
93
+ conversationId: "c1",
94
+ trigger: "interval",
95
+ });
96
+ expect(upsertCalls).toHaveLength(0);
97
+ });
98
+
99
+ test("memory_v2_consolidation source — skips enqueue", () => {
100
+ convType = "background";
101
+ convSource = "memory_v2_consolidation";
102
+ enqueueMemoryRetrospectiveIfEnabled({
103
+ conversationId: "c1",
104
+ trigger: "interval",
105
+ });
106
+ expect(upsertCalls).toHaveLength(0);
107
+ });
108
+
109
+ test("heartbeat (background) source — still enqueues", () => {
110
+ convType = "background";
111
+ convSource = "heartbeat";
112
+ enqueueMemoryRetrospectiveIfEnabled({
113
+ conversationId: "c1",
114
+ trigger: "interval",
115
+ });
116
+ expect(upsertCalls).toHaveLength(1);
117
+ });
88
118
  });
89
119
 
90
120
  describe("isMemoryRetrospectiveConversation", () => {
@@ -685,6 +685,40 @@ describe("memoryRetrospectiveJob", () => {
685
685
  expect(wakeCalls[0]!.opts.toolGateMode).toBe("execution");
686
686
  });
687
687
 
688
+ // -------------------------------------------------------------------------
689
+ // Source background-turn parity (isNonInteractive)
690
+ // -------------------------------------------------------------------------
691
+
692
+ test("background source → wake runs non-interactive (reproduces <background_turn>)", async () => {
693
+ conversationOverrides["src-conv-1"] = {
694
+ source: "heartbeat",
695
+ forkParentMessageId: null,
696
+ title: "Heartbeat",
697
+ conversationType: "background",
698
+ };
699
+
700
+ const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
701
+
702
+ expect(outcome.kind).toBe("invoked");
703
+ expect(wakeCalls).toHaveLength(1);
704
+ expect(wakeCalls[0]!.opts.isNonInteractive).toBe(true);
705
+ });
706
+
707
+ test("standard source → wake stays interactive (no spurious <background_turn>)", async () => {
708
+ conversationOverrides["src-conv-1"] = {
709
+ source: "user",
710
+ forkParentMessageId: null,
711
+ title: "Source conversation",
712
+ conversationType: "standard",
713
+ };
714
+
715
+ const outcome = await memoryRetrospectiveJob(makeJob(), stubConfig);
716
+
717
+ expect(outcome.kind).toBe("invoked");
718
+ expect(wakeCalls).toHaveLength(1);
719
+ expect(wakeCalls[0]!.opts.isNonInteractive).toBe(false);
720
+ });
721
+
688
722
  // -------------------------------------------------------------------------
689
723
  // Source-derived persona override
690
724
  // -------------------------------------------------------------------------
@@ -8,10 +8,10 @@ mock.module("../../util/logger.js", () => ({
8
8
  }),
9
9
  }));
10
10
 
11
- // Mutable usage-data gate, flipped per-test.
12
- let collectUsageData = true;
13
- mock.module("../../config/loader.js", () => ({
14
- getConfig: () => ({ collectUsageData }),
11
+ // Mutable consent gate, flipped per-test.
12
+ let shareAnalytics = true;
13
+ mock.module("../../platform/consent-cache.js", () => ({
14
+ getCachedShareAnalytics: () => shareAnalytics,
15
15
  }));
16
16
 
17
17
  import { getDb } from "../db-connection.js";
@@ -30,7 +30,7 @@ function resetTable(): void {
30
30
 
31
31
  describe("onboarding-events-store: recordActivationEvent", () => {
32
32
  beforeEach(() => {
33
- collectUsageData = true;
33
+ shareAnalytics = true;
34
34
  resetTable();
35
35
  });
36
36
 
@@ -66,8 +66,8 @@ describe("onboarding-events-store: recordActivationEvent", () => {
66
66
  expect(rows[0]!.stepIndex).toBe(2);
67
67
  });
68
68
 
69
- test("returns null and writes no row when collectUsageData is disabled", () => {
70
- collectUsageData = false;
69
+ test("returns null and writes no row when share_analytics is disabled", () => {
70
+ shareAnalytics = false;
71
71
  const event = recordActivationEvent({
72
72
  stepName: "activation_moment_1_complete",
73
73
  sessionId: "conv-3",
@@ -1,7 +1,7 @@
1
1
  import { and, asc, eq, gt, or } from "drizzle-orm";
2
2
  import { v4 as uuid } from "uuid";
3
3
 
4
- import { getConfig } from "../config/loader.js";
4
+ import { getCachedShareAnalytics } from "../platform/consent-cache.js";
5
5
  import { getDb } from "./db-connection.js";
6
6
  import { authFallbackEvents } from "./schema.js";
7
7
 
@@ -36,7 +36,7 @@ export function recordAuthFallbackCounts(
36
36
  windowEnd: number,
37
37
  counts: AuthFallbackCount[],
38
38
  ): number {
39
- if (!getConfig().collectUsageData) return 0;
39
+ if (!getCachedShareAnalytics()) return 0;
40
40
  if (counts.length === 0) return 0;
41
41
  const db = getDb();
42
42
  const createdAt = Date.now();
@@ -1,9 +1,7 @@
1
1
  import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
2
2
  import { getConfig } from "../config/loader.js";
3
- import {
4
- isUntrustedTrustClass,
5
- type TrustClass,
6
- } from "../runtime/actor-trust-resolver.js";
3
+ import { type TrustClass } from "../runtime/actor-trust-resolver.js";
4
+ import { resolveCapabilities } from "../runtime/capabilities.js";
7
5
  import { getLogger } from "../util/logger.js";
8
6
  import { isAutoAnalysisConversation } from "./auto-analysis-guard.js";
9
7
  import { isMemoryEnabled, upsertAutoAnalysisJob } from "./jobs-store.js";
@@ -118,7 +116,7 @@ export function enqueueAutoAnalysisOnCompaction(
118
116
  conversationId: string,
119
117
  trustClass: TrustClass | undefined,
120
118
  ): void {
121
- if (isUntrustedTrustClass(trustClass)) {
119
+ if (!resolveCapabilities(trustClass).canAccessMemory) {
122
120
  return;
123
121
  }
124
122
  try {
@@ -74,7 +74,7 @@ export interface CanonicalGuardianRequest {
74
74
  updatedAt: number;
75
75
  }
76
76
 
77
- interface CanonicalGuardianDelivery {
77
+ export interface CanonicalGuardianDelivery {
78
78
  id: string;
79
79
  requestId: string;
80
80
  destinationChannel: string;
@@ -674,6 +674,44 @@ export function listPendingCanonicalGuardianRequestsByDestinationChat(
674
674
  return pendingRequests;
675
675
  }
676
676
 
677
+ /**
678
+ * Find the pending canonical request whose guardian-facing delivery landed on a
679
+ * specific channel message (channel + chat + message id).
680
+ *
681
+ * This is the addressing key for emoji-reaction decisions: a reaction carries
682
+ * only the message it is attached to, so the delivered card's message id is how
683
+ * we recover which request the guardian acted on — even when several cards are
684
+ * pending in the same chat. Returns null when no delivery matches or the matched
685
+ * request is no longer pending.
686
+ */
687
+ export function getPendingCanonicalRequestByDestinationMessage(
688
+ destinationChannel: string,
689
+ destinationChatId: string,
690
+ destinationMessageId: string,
691
+ ): CanonicalGuardianRequest | null {
692
+ const db = getDb();
693
+
694
+ const delivery = db
695
+ .select()
696
+ .from(canonicalGuardianDeliveries)
697
+ .where(
698
+ and(
699
+ eq(canonicalGuardianDeliveries.destinationChannel, destinationChannel),
700
+ eq(canonicalGuardianDeliveries.destinationChatId, destinationChatId),
701
+ eq(
702
+ canonicalGuardianDeliveries.destinationMessageId,
703
+ destinationMessageId,
704
+ ),
705
+ ),
706
+ )
707
+ .get();
708
+
709
+ if (!delivery) return null;
710
+
711
+ const request = getCanonicalGuardianRequest(delivery.requestId);
712
+ return request && request.status === "pending" ? request : null;
713
+ }
714
+
677
715
  // ---------------------------------------------------------------------------
678
716
  // Conversation scope helpers
679
717
  // ---------------------------------------------------------------------------
@@ -129,7 +129,7 @@ export const messageMetadataSchema = z
129
129
  * and read gate (conversation history loading) to enforce trust-aware access.
130
130
  */
131
131
  provenanceTrustClass: z
132
- .enum(["guardian", "trusted_contact", "unknown"])
132
+ .enum(["guardian", "trusted_contact", "unverified_contact", "unknown"])
133
133
  .optional(),
134
134
  provenanceSourceChannel: channelIdSchema.optional(),
135
135
  provenanceGuardianExternalUserId: z.string().optional(),
@@ -2575,12 +2575,17 @@ export function getConversationOriginInterface(
2575
2575
  * in the given conversation, or `undefined` if none is found.
2576
2576
  *
2577
2577
  * Used by the pointer message trust resolver to detect conversations
2578
- * whose audience is a guardian or trusted_contact outside desktop-origin
2579
- * conversations.
2578
+ * whose audience is a guardian, trusted_contact, or unverified_contact
2579
+ * outside desktop-origin conversations.
2580
2580
  */
2581
2581
  export function getConversationRecentProvenanceTrustClass(
2582
2582
  conversationId: string,
2583
- ): "guardian" | "trusted_contact" | "unknown" | undefined {
2583
+ ):
2584
+ | "guardian"
2585
+ | "trusted_contact"
2586
+ | "unverified_contact"
2587
+ | "unknown"
2588
+ | undefined {
2584
2589
  const row = rawGet<{ metadata: string | null }>(
2585
2590
  `SELECT metadata FROM messages
2586
2591
  WHERE conversation_id = ? AND role = 'user' AND metadata IS NOT NULL