@vellumai/assistant 0.9.0 → 0.10.0-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (572) hide show
  1. package/ARCHITECTURE.md +18 -34
  2. package/bun.lock +7 -8
  3. package/docs/activation-funnel-telemetry.md +28 -22
  4. package/docs/architecture/security.md +29 -28
  5. package/docs/stt-provider-onboarding.md +3 -5
  6. package/docs/workflows-testing.md +13 -44
  7. package/docs/workflows.md +3 -5
  8. package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
  9. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
  10. package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
  11. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  12. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  13. package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
  14. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
  15. package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
  16. package/openapi.yaml +976 -63
  17. package/package.json +2 -1
  18. package/scripts/sync-llm-catalog.ts +6 -15
  19. package/scripts/sync-web-search-catalog.ts +3 -11
  20. package/src/__tests__/access-request-card-view.test.ts +98 -0
  21. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  22. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
  23. package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
  24. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  25. package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
  26. package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
  27. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  28. package/src/__tests__/app-compiler.test.ts +15 -1
  29. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  30. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  31. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  32. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  33. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  34. package/src/__tests__/btw-routes.test.ts +6 -8
  35. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  36. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  37. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  38. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  39. package/src/__tests__/checker.test.ts +0 -3
  40. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  41. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  43. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  44. package/src/__tests__/config-schema.test.ts +35 -0
  45. package/src/__tests__/config-watcher.test.ts +0 -18
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  47. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  48. package/src/__tests__/contacts-tools.test.ts +29 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  52. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  53. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  54. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  55. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  56. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  57. package/src/__tests__/conversation-title-service.test.ts +62 -0
  58. package/src/__tests__/credential-broker.test.ts +449 -1
  59. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  60. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  61. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  62. package/src/__tests__/credential-routes.test.ts +360 -0
  63. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  64. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  65. package/src/__tests__/disk-usage.test.ts +65 -0
  66. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  67. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  68. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  69. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  70. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  71. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  72. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  73. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  74. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  75. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  76. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  77. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  78. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  79. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  80. package/src/__tests__/identity-routes.test.ts +0 -189
  81. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  82. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  83. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  84. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  85. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  86. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  87. package/src/__tests__/llm-resolver.test.ts +70 -24
  88. package/src/__tests__/llm-schema.test.ts +1 -0
  89. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  90. package/src/__tests__/mcp-health-check.test.ts +6 -7
  91. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  92. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  93. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  94. package/src/__tests__/path-policy.test.ts +34 -0
  95. package/src/__tests__/persona-resolver.test.ts +49 -14
  96. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  97. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  98. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  99. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  100. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  101. package/src/__tests__/reaction-persistence.test.ts +150 -29
  102. package/src/__tests__/registry.test.ts +2 -7
  103. package/src/__tests__/relay-server.test.ts +285 -0
  104. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  105. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  106. package/src/__tests__/schedule-routes.test.ts +0 -30
  107. package/src/__tests__/schedule-tools.test.ts +2 -18
  108. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  109. package/src/__tests__/skill-execute-input.test.ts +51 -1
  110. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  111. package/src/__tests__/skills.test.ts +51 -0
  112. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  113. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  114. package/src/__tests__/subagent-tools.test.ts +266 -0
  115. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  116. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  117. package/src/__tests__/title-generate-hook.test.ts +100 -3
  118. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  119. package/src/__tests__/token-manager.test.ts +519 -0
  120. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  121. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  122. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  123. package/src/__tests__/tool-executor.test.ts +0 -79
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  126. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  127. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  128. package/src/__tests__/twilio-routes.test.ts +81 -1
  129. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  130. package/src/__tests__/weak-open-model.test.ts +30 -0
  131. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  132. package/src/__tests__/workspace-greetings.test.ts +152 -0
  133. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  134. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  136. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  137. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  138. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  139. package/src/agent/loop.ts +49 -29
  140. package/src/api/README.md +6 -6
  141. package/src/api/events/tool-result.ts +6 -0
  142. package/src/api/events/workflow-completed.ts +53 -0
  143. package/src/api/events/workflow-leaf-finished.ts +38 -0
  144. package/src/api/events/workflow-leaf-started.ts +35 -0
  145. package/src/api/events/workflow-progress.ts +32 -0
  146. package/src/api/events/workflow-started.ts +31 -0
  147. package/src/api/index.ts +40 -0
  148. package/src/api/responses/conversation-message.ts +28 -4
  149. package/src/api/responses/home.ts +26 -4
  150. package/src/api/responses/workflow-journal.ts +53 -0
  151. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  152. package/src/approvals/guardian-decision-primitive.ts +26 -3
  153. package/src/approvals/guardian-request-resolvers.ts +183 -80
  154. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  155. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  156. package/src/calls/call-pointer-messages.ts +10 -4
  157. package/src/calls/channel-admission-reader.ts +104 -0
  158. package/src/calls/guardian-dispatch.ts +17 -45
  159. package/src/calls/media-stream-server.ts +84 -2
  160. package/src/calls/relay-access-wait.ts +1 -1
  161. package/src/calls/relay-server.ts +66 -0
  162. package/src/calls/relay-setup-router.ts +82 -1
  163. package/src/calls/twilio-routes.ts +17 -8
  164. package/src/calls/voice-session-bridge.ts +2 -2
  165. package/src/cli/commands/clients.ts +3 -0
  166. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  167. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  168. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  169. package/src/cli/commands/memory/index.ts +30 -0
  170. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  171. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  172. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  173. package/src/cli/commands/oauth/status.test.ts +36 -0
  174. package/src/cli/commands/oauth/status.ts +23 -3
  175. package/src/cli/commands/plugins.ts +197 -4
  176. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  177. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  178. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  179. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  180. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  181. package/src/cli/lib/diff-plugin.ts +346 -0
  182. package/src/cli/lib/inspect-plugin.ts +12 -1
  183. package/src/cli/lib/install-from-github.ts +105 -17
  184. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  185. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  186. package/src/cli/lib/plugin-surfaces.ts +104 -0
  187. package/src/cli/lib/upgrade-plugin.ts +298 -10
  188. package/src/cli/program.ts +2 -6
  189. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  190. package/src/config/assistant-feature-flags.ts +22 -7
  191. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  192. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  193. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  194. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  195. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  196. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  197. package/src/config/bundled-tool-registry.ts +2 -7
  198. package/src/config/call-site-defaults.ts +15 -2
  199. package/src/config/feature-flag-registry.json +46 -31
  200. package/src/config/inference-profile-validation.ts +26 -0
  201. package/src/config/llm-resolver.ts +3 -0
  202. package/src/config/loader.ts +4 -0
  203. package/src/config/memory-v3-gate.ts +11 -0
  204. package/src/config/profile-order.ts +28 -0
  205. package/src/config/schema.ts +8 -6
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  207. package/src/config/schemas/call-site-catalog.ts +7 -0
  208. package/src/config/schemas/channels.ts +11 -0
  209. package/src/config/schemas/elevenlabs.ts +0 -1
  210. package/src/config/schemas/llm.ts +31 -0
  211. package/src/config/schemas/memory-lifecycle.ts +3 -7
  212. package/src/config/schemas/memory-v3.ts +6 -0
  213. package/src/config/schemas/platform.ts +0 -8
  214. package/src/config/schemas/services.ts +18 -0
  215. package/src/config/seed-inference-profiles.ts +109 -44
  216. package/src/config/skills.ts +21 -0
  217. package/src/config/sync-gated-profiles.ts +220 -0
  218. package/src/contacts/contact-store.ts +89 -106
  219. package/src/contacts/contacts-write.ts +5 -22
  220. package/src/contacts/types.ts +0 -1
  221. package/src/context/compactor.ts +88 -54
  222. package/src/context/strip-injections.ts +58 -10
  223. package/src/context/token-estimator.ts +1 -1
  224. package/src/credential-execution/process-manager.ts +55 -14
  225. package/src/credential-execution/prompted-credential.ts +2 -3
  226. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  227. package/src/daemon/config-watcher.ts +0 -4
  228. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  229. package/src/daemon/conversation-agent-loop.ts +114 -22
  230. package/src/daemon/conversation-history.ts +1 -1
  231. package/src/daemon/conversation-lifecycle.ts +3 -5
  232. package/src/daemon/conversation-process.ts +13 -5
  233. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  234. package/src/daemon/conversation-slash.ts +2 -23
  235. package/src/daemon/conversation-surfaces.ts +26 -0
  236. package/src/daemon/conversation-tool-setup.ts +27 -14
  237. package/src/daemon/conversation.ts +66 -14
  238. package/src/daemon/disk-pressure-policy.ts +5 -3
  239. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  240. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  241. package/src/daemon/handlers/config-a2a.ts +0 -2
  242. package/src/daemon/handlers/config-channels.ts +15 -16
  243. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  244. package/src/daemon/handlers/conversations.ts +107 -0
  245. package/src/daemon/host-browser-proxy.ts +41 -0
  246. package/src/daemon/lifecycle.ts +55 -27
  247. package/src/daemon/message-provenance.ts +2 -0
  248. package/src/daemon/message-types/contacts.ts +0 -1
  249. package/src/daemon/message-types/conversations.ts +3 -3
  250. package/src/daemon/message-types/sync.ts +0 -1
  251. package/src/daemon/message-types/web-activity.ts +7 -1
  252. package/src/daemon/message-types/workflows.ts +83 -1
  253. package/src/daemon/orphan-reaper.test.ts +0 -19
  254. package/src/daemon/orphan-reaper.ts +2 -24
  255. package/src/daemon/server.ts +0 -10
  256. package/src/daemon/tool-setup-types.ts +4 -0
  257. package/src/daemon/trust-context.ts +1 -1
  258. package/src/events/tool-audit-listener.ts +2 -2
  259. package/src/home/feed-source-enrichment.test.ts +151 -0
  260. package/src/home/feed-source-enrichment.ts +176 -0
  261. package/src/home/relationship-state.ts +2 -4
  262. package/src/instrument.ts +18 -6
  263. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  264. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  265. package/src/ipc/assistant-server.ts +37 -4
  266. package/src/ipc/gateway-flag-listener.ts +18 -2
  267. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  268. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  269. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  270. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  271. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  272. package/src/memory/auth-fallback-events-store.ts +2 -2
  273. package/src/memory/auto-analysis-enqueue.ts +3 -5
  274. package/src/memory/bookmark-crud.ts +1 -2
  275. package/src/memory/canonical-guardian-store.ts +39 -1
  276. package/src/memory/conversation-crud.ts +9 -4
  277. package/src/memory/conversation-key-store.ts +17 -2
  278. package/src/memory/conversation-title-service.ts +64 -7
  279. package/src/memory/db-init.ts +17 -17
  280. package/src/memory/embedding-backend.ts +38 -1
  281. package/src/memory/embedding-billing-breaker.ts +96 -0
  282. package/src/memory/jobs-store.ts +25 -13
  283. package/src/memory/jobs-worker.ts +54 -1
  284. package/src/memory/lifecycle-events-store.ts +2 -2
  285. package/src/memory/memory-retrospective-constants.ts +4 -4
  286. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  287. package/src/memory/memory-retrospective-job.ts +28 -227
  288. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  289. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  290. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  291. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  292. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  293. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  294. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  295. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  296. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  297. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  298. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  299. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  300. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  301. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  302. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  303. package/src/memory/migrations/index.ts +6 -0
  304. package/src/memory/migrations/run-migrations.ts +41 -0
  305. package/src/memory/migrations/validate-migration-state.ts +1 -1
  306. package/src/memory/onboarding-events-store.ts +3 -3
  307. package/src/memory/schema/contacts.ts +0 -5
  308. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  309. package/src/memory/skill-loaded-events-store.ts +2 -2
  310. package/src/memory/tool-executed-events-store.test.ts +7 -7
  311. package/src/memory/turn-trace-store.test.ts +736 -0
  312. package/src/memory/turn-trace-store.ts +364 -0
  313. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  314. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  315. package/src/memory/v2/consolidation-job.ts +2 -2
  316. package/src/memory/v2/skill-content.ts +25 -7
  317. package/src/memory/v2/skill-store.ts +7 -1
  318. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  319. package/src/memory/v3-eval/eval-packets.ts +546 -0
  320. package/src/messaging/providers/slack/adapter.ts +1 -1
  321. package/src/messaging/providers/slack/api.ts +31 -0
  322. package/src/messaging/providers/slack/send.test.ts +114 -2
  323. package/src/messaging/providers/slack/send.ts +30 -7
  324. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  325. package/src/messaging/providers/slack/withdraw.ts +161 -0
  326. package/src/notifications/AGENTS.md +2 -0
  327. package/src/notifications/access-request-copy.ts +72 -59
  328. package/src/notifications/adapters/shared.ts +29 -0
  329. package/src/notifications/adapters/slack.ts +58 -103
  330. package/src/notifications/adapters/telegram.ts +2 -20
  331. package/src/notifications/approval-card-data.ts +333 -0
  332. package/src/notifications/broadcaster.ts +16 -3
  333. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  334. package/src/notifications/copy-composer.ts +3 -3
  335. package/src/notifications/decision-engine.ts +4 -2
  336. package/src/notifications/destination-resolver.ts +4 -6
  337. package/src/notifications/guardian-question-mode.ts +10 -0
  338. package/src/notifications/home-feed-side-effect.ts +7 -16
  339. package/src/notifications/notification-utils.ts +19 -20
  340. package/src/notifications/signal.ts +79 -43
  341. package/src/notifications/types.ts +98 -121
  342. package/src/oauth/AGENTS.md +5 -24
  343. package/src/permissions/checker.test.ts +51 -0
  344. package/src/permissions/checker.ts +185 -26
  345. package/src/permissions/ipc-risk-types.ts +24 -0
  346. package/src/permissions/question-prompter.test.ts +27 -0
  347. package/src/permissions/question-prompter.ts +4 -0
  348. package/src/platform/client.test.ts +119 -0
  349. package/src/platform/client.ts +66 -0
  350. package/src/platform/consent-cache.test.ts +267 -0
  351. package/src/platform/consent-cache.ts +174 -0
  352. package/src/plugin-api/constants.ts +1 -1
  353. package/src/plugin-api/index.ts +33 -1
  354. package/src/plugin-api/model-profiles.ts +33 -0
  355. package/src/plugin-api/types.ts +50 -2
  356. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  357. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  358. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  359. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  360. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  361. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  362. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  363. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  364. package/src/plugins/defaults/advisor/config.ts +21 -0
  365. package/src/plugins/defaults/advisor/consult.ts +93 -0
  366. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  367. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  368. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  369. package/src/plugins/defaults/advisor/package.json +14 -0
  370. package/src/plugins/defaults/advisor/steering.ts +67 -0
  371. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  372. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  373. package/src/plugins/defaults/index.ts +60 -0
  374. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  375. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  376. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  377. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  378. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  379. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  380. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  381. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  382. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  384. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  385. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  386. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  387. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  388. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  389. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  390. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  391. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  392. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  393. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  394. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  395. package/src/prompts/persona-resolver.ts +14 -4
  396. package/src/prompts/templates/system-sections.ts +7 -2
  397. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  398. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  399. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  400. package/src/providers/atlascloud/client.ts +85 -0
  401. package/src/providers/fetch-provider-catalog.ts +85 -0
  402. package/src/providers/inference/adapter-factory.ts +3 -0
  403. package/src/providers/model-catalog.ts +58 -0
  404. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  405. package/src/providers/openai/chat-completions-provider.ts +7 -0
  406. package/src/providers/openai/responses-provider.ts +10 -0
  407. package/src/providers/provider-send-message.ts +11 -3
  408. package/src/providers/retry.ts +53 -12
  409. package/src/providers/search-provider-catalog.ts +10 -0
  410. package/src/providers/weak-open-model.ts +22 -0
  411. package/src/runtime/AGENTS.md +0 -1
  412. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  413. package/src/runtime/__tests__/client-health.test.ts +44 -0
  414. package/src/runtime/access-request-helper.ts +21 -53
  415. package/src/runtime/actor-trust-resolver.ts +59 -63
  416. package/src/runtime/agent-wake.ts +52 -0
  417. package/src/runtime/assistant-event-hub.ts +18 -4
  418. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  419. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  420. package/src/runtime/btw-sidechain.ts +3 -6
  421. package/src/runtime/capabilities.test.ts +120 -0
  422. package/src/runtime/capabilities.ts +197 -0
  423. package/src/runtime/channel-approval-types.ts +22 -45
  424. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  425. package/src/runtime/channel-retry-sweep.ts +1 -0
  426. package/src/runtime/channel-verification-service.ts +3 -3
  427. package/src/runtime/client-health.ts +26 -0
  428. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  429. package/src/runtime/effective-capabilities.test.ts +128 -0
  430. package/src/runtime/effective-capabilities.ts +84 -0
  431. package/src/runtime/guardian-reply-router.ts +106 -21
  432. package/src/runtime/invite-redemption-service.ts +9 -25
  433. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  434. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  435. package/src/runtime/pending-interactions.ts +15 -0
  436. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  437. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  438. package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
  439. package/src/runtime/routes/app-routes.ts +1 -1
  440. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  441. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  442. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  443. package/src/runtime/routes/btw-routes.ts +1 -27
  444. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  445. package/src/runtime/routes/client-routes.ts +10 -0
  446. package/src/runtime/routes/contact-routes.ts +31 -8
  447. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  448. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  449. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  450. package/src/runtime/routes/conversation-routes.ts +39 -14
  451. package/src/runtime/routes/credential-routes.ts +40 -16
  452. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  453. package/src/runtime/routes/events-routes.ts +1 -3
  454. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  455. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  456. package/src/runtime/routes/home-feed-routes.ts +8 -3
  457. package/src/runtime/routes/identity-routes.ts +1 -296
  458. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  459. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  460. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  461. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  462. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  463. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  464. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  465. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  466. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  467. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  468. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  469. package/src/runtime/routes/index.ts +2 -0
  470. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  471. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  472. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  473. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  474. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  475. package/src/runtime/routes/notification-routes.ts +122 -133
  476. package/src/runtime/routes/platform-routes.ts +2 -2
  477. package/src/runtime/routes/plugins-routes.ts +202 -3
  478. package/src/runtime/routes/schedule-routes.ts +0 -22
  479. package/src/runtime/routes/secret-routes.ts +10 -0
  480. package/src/runtime/routes/surface-action-routes.ts +2 -1
  481. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  482. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  483. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  484. package/src/runtime/routes/workflow-routes.ts +131 -29
  485. package/src/runtime/routes/workspace-greetings.ts +55 -0
  486. package/src/runtime/sync/resource-sync-events.ts +1 -11
  487. package/src/runtime/tool-grant-request-helper.ts +18 -16
  488. package/src/runtime/trust-context-resolver.ts +8 -5
  489. package/src/schedule/inference-profile.ts +2 -14
  490. package/src/schedule/schedule-store.ts +1 -1
  491. package/src/schedule/scheduler-types.ts +5 -1
  492. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  493. package/src/security/secret-patterns.ts +3 -0
  494. package/src/subagent/manager.ts +17 -4
  495. package/src/subagent/types.ts +6 -0
  496. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  497. package/src/telemetry/trace-collection-policy.ts +30 -0
  498. package/src/telemetry/types.ts +89 -0
  499. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  500. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  501. package/src/tools/AGENTS.md +3 -3
  502. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  503. package/src/tools/browser/browser-execution.ts +30 -19
  504. package/src/tools/document/document-tool.ts +2 -3
  505. package/src/tools/executor.ts +5 -3
  506. package/src/tools/host-terminal/host-shell.ts +5 -4
  507. package/src/tools/memory/register.ts +2 -2
  508. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  509. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  510. package/src/tools/network/web-fetch.ts +372 -1
  511. package/src/tools/network/web-search-error.ts +1 -1
  512. package/src/tools/network/web-search.ts +213 -10
  513. package/src/tools/permission-checker.ts +4 -3
  514. package/src/tools/registry.ts +20 -0
  515. package/src/tools/schedule/create.ts +7 -12
  516. package/src/tools/schedule/update.ts +4 -11
  517. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  518. package/src/tools/side-effects.ts +2 -17
  519. package/src/tools/skills/execute.ts +33 -0
  520. package/src/tools/subagent/spawn.ts +61 -12
  521. package/src/tools/terminal/shell.ts +10 -4
  522. package/src/tools/tool-approval-handler.ts +18 -13
  523. package/src/tools/tool-manifest.ts +0 -2
  524. package/src/tools/types.ts +9 -0
  525. package/src/tools/ui-surface/definitions.ts +64 -3
  526. package/src/tools/verification-control-plane-policy.ts +3 -1
  527. package/src/tools/workflows/run-workflow.test.ts +8 -18
  528. package/src/tools/workflows/run-workflow.ts +1 -0
  529. package/src/util/disk-usage.ts +78 -23
  530. package/src/util/platform.ts +10 -3
  531. package/src/watcher/telemetry.ts +2 -2
  532. package/src/workflows/capabilities.ts +2 -3
  533. package/src/workflows/engine.test.ts +175 -1
  534. package/src/workflows/engine.ts +82 -0
  535. package/src/workflows/journal-store.test.ts +70 -0
  536. package/src/workflows/journal-store.ts +18 -3
  537. package/src/workflows/run-manager.test.ts +171 -28
  538. package/src/workflows/run-manager.ts +66 -24
  539. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  540. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  541. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  542. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  543. package/src/workspace/migrations/registry.ts +8 -0
  544. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  545. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  546. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  547. package/src/__tests__/credential-vault.test.ts +0 -1706
  548. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  549. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  550. package/src/cli/commands/__tests__/task.test.ts +0 -914
  551. package/src/cli/commands/task.ts +0 -771
  552. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  553. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  554. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  555. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  556. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  557. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  558. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  559. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  560. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  561. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  562. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  563. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  564. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  565. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  566. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  567. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  568. package/src/memory/preloaded-apps.ts +0 -116
  569. package/src/notifications/tool-approval-copy.ts +0 -142
  570. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  571. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  572. package/src/tools/credentials/vault.ts +0 -712
@@ -13,7 +13,7 @@ export interface TrustContext {
13
13
  /** Channel through which the inbound message arrived. */
14
14
  sourceChannel: ChannelId;
15
15
  /** Trust classification -- see {@link TrustClass} for semantics. */
16
- trustClass: "guardian" | "trusted_contact" | "unknown";
16
+ trustClass: TrustClass;
17
17
  /** Chat/conversation ID for delivering guardian notifications. */
18
18
  guardianChatId?: string;
19
19
  /** Canonical external user ID of the guardian for this (assistant, channel) binding. */
@@ -1,8 +1,8 @@
1
- import { getConfig } from "../config/loader.js";
2
1
  import {
3
2
  recordToolInvocation,
4
3
  type ToolInvocationRecord,
5
4
  } from "../memory/tool-usage-store.js";
5
+ import { getCachedShareAnalytics } from "../platform/consent-cache.js";
6
6
  import { redactJsonStringLeaves } from "../security/redact-json.js";
7
7
  import { redactSecrets } from "../security/secret-scanner.js";
8
8
  import {
@@ -168,7 +168,7 @@ function telemetryColumns(
168
168
  rawInput: string,
169
169
  resultBytes: number,
170
170
  ): TelemetryColumns {
171
- if (!getConfig().collectUsageData) return NULL_TELEMETRY_COLUMNS;
171
+ if (!getCachedShareAnalytics()) return NULL_TELEMETRY_COLUMNS;
172
172
  return {
173
173
  argBytes: event.inputBytes ?? Buffer.byteLength(rawInput, "utf8"),
174
174
  resultBytes,
@@ -0,0 +1,151 @@
1
+ import { describe, expect, it } from "bun:test";
2
+
3
+ import { type FeedItem } from "../api/responses/home.js";
4
+ import {
5
+ classifyConversationSource,
6
+ enrichFeedItemsWithSource,
7
+ type FeedSourceEnrichmentDeps,
8
+ } from "./feed-source-enrichment.js";
9
+
10
+ function makeItem(partial: Partial<FeedItem> & Pick<FeedItem, "id">): FeedItem {
11
+ return {
12
+ type: "notification",
13
+ priority: 50,
14
+ summary: "summary",
15
+ timestamp: "2026-06-18T00:00:00.000Z",
16
+ createdAt: "2026-06-18T00:00:00.000Z",
17
+ status: "new",
18
+ ...partial,
19
+ };
20
+ }
21
+
22
+ describe("classifyConversationSource", () => {
23
+ it("maps each known producer source to its coarse type", () => {
24
+ expect(classifyConversationSource("heartbeat")).toBe("heartbeat");
25
+ expect(classifyConversationSource("memory-retrospective")).toBe(
26
+ "memory_consolidation",
27
+ );
28
+ expect(classifyConversationSource("memory-retrospective-fork")).toBe(
29
+ "memory_consolidation",
30
+ );
31
+ expect(classifyConversationSource("schedule")).toBe("schedule");
32
+ expect(classifyConversationSource("auto-analysis")).toBe("auto_analysis");
33
+ expect(classifyConversationSource("user")).toBe("user");
34
+ expect(classifyConversationSource("home-feed")).toBe("user");
35
+ });
36
+
37
+ it("falls through to 'other' for unknown / paired-delivery / empty sources", () => {
38
+ expect(classifyConversationSource("notification")).toBe("other");
39
+ expect(classifyConversationSource("something-else")).toBe("other");
40
+ expect(classifyConversationSource(null)).toBe("other");
41
+ expect(classifyConversationSource(undefined)).toBe("other");
42
+ });
43
+ });
44
+
45
+ describe("enrichFeedItemsWithSource", () => {
46
+ const deps = (
47
+ conv: Record<string, { source: string; scheduleJobId: string | null }>,
48
+ schedules: Record<string, string> = {},
49
+ ): FeedSourceEnrichmentDeps => ({
50
+ getConversationRow: (id) => conv[id] ?? null,
51
+ getScheduleName: (id) => schedules[id] ?? null,
52
+ });
53
+
54
+ it("classifies a heartbeat item with a static label", () => {
55
+ const [item] = enrichFeedItemsWithSource(
56
+ [makeItem({ id: "n1", conversationId: "c1" })],
57
+ deps({ c1: { source: "heartbeat", scheduleJobId: null } }),
58
+ );
59
+ expect(item.sourceType).toBe("heartbeat");
60
+ expect(item.sourceKey).toBe("heartbeat");
61
+ expect(item.sourceLabel).toBe("Heartbeat");
62
+ });
63
+
64
+ it("classifies a memory-consolidation item", () => {
65
+ const [item] = enrichFeedItemsWithSource(
66
+ [makeItem({ id: "n2", conversationId: "c2" })],
67
+ deps({ c2: { source: "memory-retrospective", scheduleJobId: null } }),
68
+ );
69
+ expect(item.sourceType).toBe("memory_consolidation");
70
+ expect(item.sourceKey).toBe("memory_consolidation");
71
+ expect(item.sourceLabel).toBe("Memory consolidation");
72
+ });
73
+
74
+ it("gives distinct keys and names to two different schedules", () => {
75
+ const [a, b] = enrichFeedItemsWithSource(
76
+ [
77
+ makeItem({ id: "n3", conversationId: "cA" }),
78
+ makeItem({ id: "n4", conversationId: "cB" }),
79
+ ],
80
+ deps(
81
+ {
82
+ cA: { source: "schedule", scheduleJobId: "sched-A" },
83
+ cB: { source: "schedule", scheduleJobId: "sched-B" },
84
+ },
85
+ { "sched-A": "Morning digest", "sched-B": "Evening recap" },
86
+ ),
87
+ );
88
+ expect(a.sourceType).toBe("schedule");
89
+ expect(a.sourceKey).toBe("schedule:sched-A");
90
+ expect(a.sourceLabel).toBe("Morning digest");
91
+ // scheduleId recovered from the conversation row is surfaced in metadata.
92
+ expect(a.metadata?.scheduleId).toBe("sched-A");
93
+
94
+ expect(b.sourceKey).toBe("schedule:sched-B");
95
+ expect(b.sourceLabel).toBe("Evening recap");
96
+ });
97
+
98
+ it("prefers an explicit metadata.scheduleId over the conversation row", () => {
99
+ const [item] = enrichFeedItemsWithSource(
100
+ [
101
+ makeItem({
102
+ id: "n5",
103
+ conversationId: "cX",
104
+ metadata: { scheduleId: "from-payload" },
105
+ }),
106
+ ],
107
+ deps(
108
+ { cX: { source: "schedule", scheduleJobId: "from-row" } },
109
+ { "from-payload": "Payload schedule" },
110
+ ),
111
+ );
112
+ expect(item.sourceKey).toBe("schedule:from-payload");
113
+ expect(item.sourceLabel).toBe("Payload schedule");
114
+ });
115
+
116
+ it("labels a schedule with no resolvable name as 'Scheduled'", () => {
117
+ const [item] = enrichFeedItemsWithSource(
118
+ [makeItem({ id: "n6", conversationId: "cY" })],
119
+ deps({ cY: { source: "schedule", scheduleJobId: "gone" } }),
120
+ );
121
+ expect(item.sourceKey).toBe("schedule:gone");
122
+ expect(item.sourceLabel).toBe("Scheduled");
123
+ });
124
+
125
+ it("classifies items with no source conversation as 'other'", () => {
126
+ const [item] = enrichFeedItemsWithSource(
127
+ [makeItem({ id: "n7" })],
128
+ deps({}),
129
+ );
130
+ expect(item.sourceType).toBe("other");
131
+ expect(item.sourceKey).toBe("other");
132
+ expect(item.sourceLabel).toBe("Other");
133
+ });
134
+
135
+ it("resolves each distinct conversation at most once", () => {
136
+ const calls: string[] = [];
137
+ enrichFeedItemsWithSource(
138
+ [
139
+ makeItem({ id: "n8", conversationId: "dup" }),
140
+ makeItem({ id: "n9", conversationId: "dup" }),
141
+ ],
142
+ {
143
+ getConversationRow: (id) => {
144
+ calls.push(id);
145
+ return { source: "heartbeat", scheduleJobId: null };
146
+ },
147
+ },
148
+ );
149
+ expect(calls).toEqual(["dup"]);
150
+ });
151
+ });
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Read-time enrichment of home-feed items with their source-conversation
3
+ * classification.
4
+ *
5
+ * The activity feed surfaces notifications produced by many background
6
+ * flows — the periodic heartbeat, memory-consolidation passes, each
7
+ * recurring schedule, auto-analysis runs, etc. Clients let the user filter
8
+ * the feed by that producer, so every item is tagged with:
9
+ *
10
+ * - `sourceType` — coarse producer category.
11
+ * - `sourceKey` — stable filter id (`schedule:<id>` per schedule,
12
+ * otherwise the `sourceType`).
13
+ * - `sourceLabel` — human-readable display (a schedule's name, or a
14
+ * static label such as "Heartbeat").
15
+ *
16
+ * The classification is derived from the source conversation's `source`
17
+ * column (and, for schedules, its name) rather than persisted onto the
18
+ * feed item. Resolving at read time keeps labels fresh across schedule
19
+ * renames and retroactively classifies items written before this feature
20
+ * existed, with no migration.
21
+ *
22
+ * The lookups are injectable so the enrichment can be unit-tested without
23
+ * a live database; production callers use the defaults.
24
+ */
25
+
26
+ import {
27
+ type FeedItem,
28
+ type FeedItemSourceType,
29
+ } from "../api/responses/home.js";
30
+ import { AUTO_ANALYSIS_SOURCE } from "../memory/auto-analysis-constants.js";
31
+ import { getConversation } from "../memory/conversation-crud.js";
32
+ import {
33
+ MEMORY_RETROSPECTIVE_FORK_SOURCE,
34
+ MEMORY_RETROSPECTIVE_SOURCE,
35
+ } from "../memory/memory-retrospective-constants.js";
36
+ import { getSchedule } from "../schedule/schedule-store.js";
37
+
38
+ /** Minimal source-conversation shape the enrichment needs. */
39
+ interface ConversationSourceRow {
40
+ source: string;
41
+ scheduleJobId: string | null;
42
+ }
43
+
44
+ export interface FeedSourceEnrichmentDeps {
45
+ /** Resolve the source columns for a conversation, or null when missing. */
46
+ getConversationRow?: (id: string) => ConversationSourceRow | null;
47
+ /** Resolve a schedule's display name, or null when missing. */
48
+ getScheduleName?: (id: string) => string | null;
49
+ }
50
+
51
+ /**
52
+ * Map a conversation's `source` column to a coarse feed source type.
53
+ * Mechanical mapping over the known producer sources; anything else
54
+ * (including the broadcaster's paired `"notification"` delivery
55
+ * conversations) falls through to `"other"`.
56
+ */
57
+ export function classifyConversationSource(
58
+ source: string | null | undefined,
59
+ ): FeedItemSourceType {
60
+ switch (source) {
61
+ case "heartbeat":
62
+ return "heartbeat";
63
+ case MEMORY_RETROSPECTIVE_SOURCE:
64
+ case MEMORY_RETROSPECTIVE_FORK_SOURCE:
65
+ return "memory_consolidation";
66
+ case "schedule":
67
+ return "schedule";
68
+ case AUTO_ANALYSIS_SOURCE:
69
+ return "auto_analysis";
70
+ case "user":
71
+ case "home-feed":
72
+ return "user";
73
+ default:
74
+ return "other";
75
+ }
76
+ }
77
+
78
+ /** Static display labels for non-schedule source types. */
79
+ const STATIC_SOURCE_LABELS: Record<
80
+ Exclude<FeedItemSourceType, "schedule">,
81
+ string
82
+ > = {
83
+ heartbeat: "Heartbeat",
84
+ memory_consolidation: "Memory consolidation",
85
+ auto_analysis: "Auto-analysis",
86
+ user: "Conversation",
87
+ other: "Other",
88
+ };
89
+
90
+ function defaultGetConversationRow(id: string): ConversationSourceRow | null {
91
+ try {
92
+ const row = getConversation(id);
93
+ return row
94
+ ? { source: row.source, scheduleJobId: row.scheduleJobId }
95
+ : null;
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+
101
+ function defaultGetScheduleName(id: string): string | null {
102
+ try {
103
+ return getSchedule(id)?.name ?? null;
104
+ } catch {
105
+ return null;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Return a copy of each feed item enriched with `sourceType`, `sourceKey`,
111
+ * and `sourceLabel`. Conversation and schedule lookups are memoized across
112
+ * the batch so each distinct source is resolved at most once.
113
+ */
114
+ export function enrichFeedItemsWithSource(
115
+ items: FeedItem[],
116
+ deps: FeedSourceEnrichmentDeps = {},
117
+ ): FeedItem[] {
118
+ if (items.length === 0) return items;
119
+
120
+ const getConversationRow =
121
+ deps.getConversationRow ?? defaultGetConversationRow;
122
+ const getScheduleName = deps.getScheduleName ?? defaultGetScheduleName;
123
+
124
+ const convCache = new Map<string, ConversationSourceRow | null>();
125
+ const resolveConv = (id: string): ConversationSourceRow | null => {
126
+ if (!convCache.has(id)) convCache.set(id, getConversationRow(id));
127
+ return convCache.get(id) ?? null;
128
+ };
129
+
130
+ const scheduleNameCache = new Map<string, string | null>();
131
+ const resolveScheduleName = (id: string): string | null => {
132
+ if (!scheduleNameCache.has(id)) {
133
+ scheduleNameCache.set(id, getScheduleName(id));
134
+ }
135
+ return scheduleNameCache.get(id) ?? null;
136
+ };
137
+
138
+ return items.map((item) => {
139
+ const row = item.conversationId ? resolveConv(item.conversationId) : null;
140
+ const sourceType = classifyConversationSource(row?.source);
141
+
142
+ const metadataScheduleId =
143
+ typeof item.metadata?.scheduleId === "string"
144
+ ? item.metadata.scheduleId
145
+ : undefined;
146
+ const scheduleId = metadataScheduleId ?? row?.scheduleJobId ?? undefined;
147
+
148
+ let sourceKey: string;
149
+ let sourceLabel: string;
150
+ if (sourceType === "schedule" && scheduleId) {
151
+ sourceKey = `schedule:${scheduleId}`;
152
+ sourceLabel = resolveScheduleName(scheduleId) ?? "Scheduled";
153
+ } else if (sourceType === "schedule") {
154
+ sourceKey = "schedule";
155
+ sourceLabel = "Scheduled";
156
+ } else {
157
+ sourceKey = sourceType;
158
+ sourceLabel = STATIC_SOURCE_LABELS[sourceType];
159
+ }
160
+
161
+ // Surface the scheduleId in metadata when it was recovered from the
162
+ // source conversation so clients have a single place to read it.
163
+ const metadata =
164
+ scheduleId !== undefined && metadataScheduleId === undefined
165
+ ? { ...(item.metadata ?? {}), scheduleId }
166
+ : item.metadata;
167
+
168
+ return {
169
+ ...item,
170
+ sourceType,
171
+ sourceKey,
172
+ sourceLabel,
173
+ ...(metadata ? { metadata } : {}),
174
+ };
175
+ });
176
+ }
@@ -5,10 +5,8 @@
5
5
  * relationship with the user: which tier they're at, what facts the
6
6
  * assistant has learned about them, and which capabilities are unlocked.
7
7
  *
8
- * The TypeScript types here are the source of truth. A structurally
9
- * identical Swift definition lives at
10
- * `clients/shared/Models/RelationshipState.swift` — any change here must
11
- * be mirrored there (and the contract test guards the default list).
8
+ * The TypeScript types here are the source of truth; the contract test
9
+ * guards the default list.
12
10
  */
13
11
 
14
12
  export const RELATIONSHIP_STATE_VERSION = 1 as const;
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 },