@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
@@ -0,0 +1,267 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import type { OwnerConsent } from "./client.js";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Mutable mock state
7
+ // ---------------------------------------------------------------------------
8
+
9
+ let mockClient: {
10
+ platformAssistantId: string;
11
+ getOwnerConsent: () => Promise<OwnerConsent | null>;
12
+ } | null = null;
13
+ let createCallCount = 0;
14
+ // Legacy fail-closed opt-out marker surfaced via getConfigReadOnly(). Default
15
+ // off/absent so existing behavior is unchanged.
16
+ let mockLegacyTelemetryOptOut: boolean | undefined = false;
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Module mocks (must precede the import under test)
20
+ // ---------------------------------------------------------------------------
21
+
22
+ mock.module("./client.js", () => ({
23
+ VellumPlatformClient: {
24
+ create: async () => {
25
+ createCallCount += 1;
26
+ return mockClient;
27
+ },
28
+ },
29
+ }));
30
+
31
+ mock.module("../config/loader.js", () => ({
32
+ getConfigReadOnly: () => ({
33
+ legacyTelemetryOptOut: mockLegacyTelemetryOptOut,
34
+ }),
35
+ }));
36
+
37
+ mock.module("../util/logger.js", () => ({
38
+ getLogger: () =>
39
+ new Proxy({} as Record<string, unknown>, {
40
+ get: () => () => {},
41
+ }),
42
+ }));
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Import under test (after mocks)
46
+ // ---------------------------------------------------------------------------
47
+
48
+ import {
49
+ __setCachedShareAnalyticsForTest,
50
+ __setCachedShareDiagnosticsForTest,
51
+ __setCachedShareDiagnosticsVersionForTest,
52
+ getCachedShareAnalytics,
53
+ getCachedShareDiagnostics,
54
+ getCachedShareDiagnosticsVersion,
55
+ refreshConsentCache,
56
+ startConsentRefresh,
57
+ stopConsentRefresh,
58
+ } from "./consent-cache.js";
59
+
60
+ /**
61
+ * Build a mock owner consent. Fields default to `false` so existing
62
+ * share_analytics-focused cases don't have to spell them out.
63
+ */
64
+ function makeConsent(overrides: Partial<OwnerConsent> = {}): OwnerConsent {
65
+ return {
66
+ shareAnalytics: false,
67
+ shareDiagnostics: false,
68
+ shareDiagnosticsAcceptedVersion: "",
69
+ ...overrides,
70
+ };
71
+ }
72
+
73
+ function makeClient(consent: OwnerConsent | null, assistantId = "asst_1") {
74
+ return {
75
+ platformAssistantId: assistantId,
76
+ getOwnerConsent: async () => consent,
77
+ };
78
+ }
79
+
80
+ describe("consent-cache", () => {
81
+ beforeEach(() => {
82
+ mockClient = null;
83
+ createCallCount = 0;
84
+ mockLegacyTelemetryOptOut = false;
85
+ __setCachedShareAnalyticsForTest(false);
86
+ __setCachedShareDiagnosticsForTest(false);
87
+ __setCachedShareDiagnosticsVersionForTest("");
88
+ });
89
+
90
+ afterEach(async () => {
91
+ await stopConsentRefresh();
92
+ });
93
+
94
+ test("defaults to false before any refresh", () => {
95
+ expect(getCachedShareAnalytics()).toBe(false);
96
+ });
97
+
98
+ test("stays false when no platform client is available", async () => {
99
+ mockClient = null;
100
+ await refreshConsentCache();
101
+ expect(getCachedShareAnalytics()).toBe(false);
102
+ });
103
+
104
+ test("becomes true after a successful fetch reporting shareAnalytics: true", async () => {
105
+ mockClient = makeClient(makeConsent({ shareAnalytics: true }));
106
+ await refreshConsentCache();
107
+ expect(getCachedShareAnalytics()).toBe(true);
108
+ });
109
+
110
+ test("a null fetch keeps the last known value", async () => {
111
+ mockClient = makeClient(makeConsent({ shareAnalytics: true }));
112
+ await refreshConsentCache();
113
+ expect(getCachedShareAnalytics()).toBe(true);
114
+
115
+ // Transient failure: consent endpoint returns null.
116
+ mockClient = makeClient(null);
117
+ await refreshConsentCache();
118
+ expect(getCachedShareAnalytics()).toBe(true);
119
+ });
120
+
121
+ test("a missing client flips a prior opt-in back to false", async () => {
122
+ __setCachedShareAnalyticsForTest(true);
123
+ mockClient = null;
124
+ await refreshConsentCache();
125
+ expect(getCachedShareAnalytics()).toBe(false);
126
+ });
127
+
128
+ test("a client without a resolvable assistant identity fails closed", async () => {
129
+ __setCachedShareAnalyticsForTest(true);
130
+ // Identity cleared mid-session (e.g. assistantId emptied while base URL +
131
+ // API key persist). getOwnerConsent must not be relied upon to preserve the
132
+ // prior opt-in here — fail closed instead.
133
+ mockClient = makeClient(makeConsent({ shareAnalytics: true }), "");
134
+ await refreshConsentCache();
135
+ expect(getCachedShareAnalytics()).toBe(false);
136
+ });
137
+
138
+ test("legacy opt-out marker keeps analytics off despite platform opt-in", async () => {
139
+ mockLegacyTelemetryOptOut = true;
140
+ mockClient = makeClient(makeConsent({ shareAnalytics: true }));
141
+ await refreshConsentCache();
142
+ // Platform reports opt-in, but the fail-closed marker forces off.
143
+ expect(getCachedShareAnalytics()).toBe(false);
144
+ });
145
+
146
+ test("a fetch reporting shareAnalytics: false turns the cache off", async () => {
147
+ __setCachedShareAnalyticsForTest(true);
148
+ mockClient = makeClient(makeConsent({ shareDiagnostics: true }));
149
+ await refreshConsentCache();
150
+ expect(getCachedShareAnalytics()).toBe(false);
151
+ });
152
+
153
+ test("startConsentRefresh is idempotent and runs an immediate refresh", async () => {
154
+ mockClient = makeClient(makeConsent({ shareAnalytics: true }));
155
+
156
+ startConsentRefresh();
157
+ startConsentRefresh(); // no-op: timer already running
158
+ // Let the fire-and-forget immediate refresh settle.
159
+ await new Promise((resolve) => setTimeout(resolve, 0));
160
+
161
+ expect(getCachedShareAnalytics()).toBe(true);
162
+ // One immediate refresh from the first call; the second is a no-op.
163
+ expect(createCallCount).toBe(1);
164
+ });
165
+
166
+ test("stopConsentRefresh clears the timer (start can run again)", async () => {
167
+ startConsentRefresh();
168
+ await stopConsentRefresh();
169
+ // A fresh start after stop performs another immediate refresh.
170
+ mockClient = makeClient(makeConsent({ shareAnalytics: true }));
171
+ startConsentRefresh();
172
+ await new Promise((resolve) => setTimeout(resolve, 0));
173
+ expect(getCachedShareAnalytics()).toBe(true);
174
+ });
175
+
176
+ // share_diagnostics tracks the same refresh as share_analytics; Sentry's
177
+ // beforeSend re-reads it per event so a revocation is honored within a cycle.
178
+ test("diagnostics defaults to false before any refresh", () => {
179
+ expect(getCachedShareDiagnostics()).toBe(false);
180
+ });
181
+
182
+ test("diagnostics becomes true after a fetch reporting shareDiagnostics: true", async () => {
183
+ mockClient = makeClient(makeConsent({ shareDiagnostics: true }));
184
+ await refreshConsentCache();
185
+ expect(getCachedShareDiagnostics()).toBe(true);
186
+ });
187
+
188
+ test("a later fetch reporting shareDiagnostics: false honors the revocation", async () => {
189
+ mockClient = makeClient(makeConsent({ shareDiagnostics: true }));
190
+ await refreshConsentCache();
191
+ expect(getCachedShareDiagnostics()).toBe(true);
192
+
193
+ mockClient = makeClient(makeConsent({ shareDiagnostics: false }));
194
+ await refreshConsentCache();
195
+ expect(getCachedShareDiagnostics()).toBe(false);
196
+ });
197
+
198
+ test("a null diagnostics fetch keeps the last known value", async () => {
199
+ mockClient = makeClient(makeConsent({ shareDiagnostics: true }));
200
+ await refreshConsentCache();
201
+ expect(getCachedShareDiagnostics()).toBe(true);
202
+
203
+ mockClient = makeClient(null);
204
+ await refreshConsentCache();
205
+ expect(getCachedShareDiagnostics()).toBe(true);
206
+ });
207
+
208
+ test("a missing client flips a prior diagnostics opt-in back to false", async () => {
209
+ __setCachedShareDiagnosticsForTest(true);
210
+ mockClient = null;
211
+ await refreshConsentCache();
212
+ expect(getCachedShareDiagnostics()).toBe(false);
213
+ });
214
+
215
+ test("a client without a resolvable assistant identity fails diagnostics closed", async () => {
216
+ __setCachedShareDiagnosticsForTest(true);
217
+ mockClient = makeClient(makeConsent({ shareDiagnostics: true }), "");
218
+ await refreshConsentCache();
219
+ expect(getCachedShareDiagnostics()).toBe(false);
220
+ });
221
+
222
+ test("caches the accepted diagnostics-consent version from a successful fetch", async () => {
223
+ mockClient = makeClient(
224
+ makeConsent({
225
+ shareDiagnostics: true,
226
+ shareDiagnosticsAcceptedVersion: "2026-06-18",
227
+ }),
228
+ );
229
+ await refreshConsentCache();
230
+ expect(getCachedShareDiagnosticsVersion()).toBe("2026-06-18");
231
+ });
232
+
233
+ test("a missing client clears a prior cached diagnostics version", async () => {
234
+ __setCachedShareDiagnosticsVersionForTest("2026-06-18");
235
+ mockClient = null;
236
+ await refreshConsentCache();
237
+ expect(getCachedShareDiagnosticsVersion()).toBe("");
238
+ });
239
+
240
+ test("a client without a resolvable assistant identity clears the diagnostics version", async () => {
241
+ __setCachedShareDiagnosticsVersionForTest("2026-06-18");
242
+ mockClient = makeClient(
243
+ makeConsent({
244
+ shareDiagnostics: true,
245
+ shareDiagnosticsAcceptedVersion: "2026-06-18",
246
+ }),
247
+ "",
248
+ );
249
+ await refreshConsentCache();
250
+ expect(getCachedShareDiagnosticsVersion()).toBe("");
251
+ });
252
+
253
+ test("a null fetch keeps the last known diagnostics version", async () => {
254
+ mockClient = makeClient(
255
+ makeConsent({
256
+ shareDiagnostics: true,
257
+ shareDiagnosticsAcceptedVersion: "2026-06-18",
258
+ }),
259
+ );
260
+ await refreshConsentCache();
261
+ expect(getCachedShareDiagnosticsVersion()).toBe("2026-06-18");
262
+
263
+ mockClient = makeClient(null);
264
+ await refreshConsentCache();
265
+ expect(getCachedShareDiagnosticsVersion()).toBe("2026-06-18");
266
+ });
267
+ });
@@ -0,0 +1,174 @@
1
+ /**
2
+ * In-memory cache of the platform owner's telemetry consent.
3
+ *
4
+ * Three values are cached, all refreshed from the same owner-consent fetch:
5
+ * - `share_analytics`: gates usage telemetry collection.
6
+ * - `share_diagnostics`: gates crash diagnostics (read by Sentry `beforeSend`),
7
+ * and composes the per-turn trace-collection gate alongside the
8
+ * `trace-collection` feature flag.
9
+ * - `share_diagnostics_accepted_version`: the consent version the owner
10
+ * accepted; the disclosing-version half of the trace-collection gate (see
11
+ * telemetry/trace-collection-policy.ts).
12
+ *
13
+ * Hot-path gates (record-time telemetry writes, Sentry `beforeSend`) need a
14
+ * synchronous, I/O-free read, so this module owns the values and refreshes them
15
+ * periodically in the background. Default-off until the first successful fetch:
16
+ * an absent session, a disabled platform, or a transient fetch failure all leave
17
+ * the values untouched (initial `false`), so we never report analytics or send
18
+ * crash diagnostics without a confirmed opt-in.
19
+ */
20
+
21
+ import { getConfigReadOnly } from "../config/loader.js";
22
+ import { getLogger } from "../util/logger.js";
23
+ import { VellumPlatformClient } from "./client.js";
24
+
25
+ const log = getLogger("consent-cache");
26
+
27
+ const REFRESH_INTERVAL_MS = 5 * 60_000; // refresh consent every 5 min
28
+
29
+ let cachedShareAnalytics = false; // default-off until first success
30
+ let cachedShareDiagnostics = false; // default-off until first success
31
+ let cachedShareDiagnosticsVersion = ""; // "" fails the trace disclosure gate
32
+ // Fail-closed marker for a workspace that locally opted out of usage data
33
+ // before telemetry moved to platform `share_analytics` consent (migration 106).
34
+ // While set, telemetry stays off regardless of platform consent. Cleared by a
35
+ // future cross-repo reconciliation once the platform exposes an explicit
36
+ // re-consent signal; not auto-cleared here.
37
+ let legacyOptOut = false;
38
+ let refreshTimer: ReturnType<typeof setInterval> | null = null;
39
+
40
+ /**
41
+ * Synchronous hot-path accessor for the effective `share_analytics` consent.
42
+ * Never does I/O; returns `false` until a successful refresh proves otherwise,
43
+ * and stays `false` while the legacy fail-closed opt-out marker is set.
44
+ */
45
+ export function getCachedShareAnalytics(): boolean {
46
+ return cachedShareAnalytics && !legacyOptOut;
47
+ }
48
+
49
+ /**
50
+ * Synchronous hot-path accessor for the `share_diagnostics` consent (read by
51
+ * Sentry `beforeSend`). Never does I/O; returns `false` until a successful
52
+ * refresh proves otherwise. Because every Sentry event re-reads this, a
53
+ * mid-session opt-out is honored within one refresh cycle.
54
+ */
55
+ export function getCachedShareDiagnostics(): boolean {
56
+ return cachedShareDiagnostics;
57
+ }
58
+
59
+ /**
60
+ * Synchronous hot-path accessor for the owner's accepted diagnostics-consent
61
+ * version ("YYYY-MM-DD", or "" until a successful refresh / if never accepted).
62
+ * The disclosing-version half of the per-turn trace-collection gate.
63
+ */
64
+ export function getCachedShareDiagnosticsVersion(): string {
65
+ return cachedShareDiagnosticsVersion;
66
+ }
67
+
68
+ /**
69
+ * Refresh the cached consent from the platform.
70
+ *
71
+ * No platform session / features disabled (`create()` is null) → default-off.
72
+ * No resolvable assistant identity (no owner whose consent we can attest to) →
73
+ * fail closed. A successful fetch adopts the reported values. A `null` fetch
74
+ * (transient failure / undeployed endpoint) leaves the previous values
75
+ * unchanged so a known opt-in is not flipped off mid-session.
76
+ */
77
+ export async function refreshConsentCache(): Promise<void> {
78
+ legacyOptOut = getConfigReadOnly().legacyTelemetryOptOut === true;
79
+
80
+ const client = await VellumPlatformClient.create();
81
+ if (!client) {
82
+ setCachedShareAnalytics(false);
83
+ setCachedShareDiagnostics(false);
84
+ setCachedShareDiagnosticsVersion("");
85
+ return;
86
+ }
87
+
88
+ // No resolvable owner identity → fail closed (don't ride a stale opt-in).
89
+ if (!client.platformAssistantId) {
90
+ setCachedShareAnalytics(false);
91
+ setCachedShareDiagnostics(false);
92
+ setCachedShareDiagnosticsVersion("");
93
+ return;
94
+ }
95
+
96
+ const consent = await client.getOwnerConsent();
97
+ if (consent) {
98
+ setCachedShareAnalytics(consent.shareAnalytics);
99
+ setCachedShareDiagnostics(consent.shareDiagnostics);
100
+ setCachedShareDiagnosticsVersion(consent.shareDiagnosticsAcceptedVersion);
101
+ }
102
+ }
103
+
104
+ function setCachedShareAnalytics(value: boolean): void {
105
+ if (value !== cachedShareAnalytics) {
106
+ log.debug(
107
+ { from: cachedShareAnalytics, to: value },
108
+ "share_analytics consent changed",
109
+ );
110
+ cachedShareAnalytics = value;
111
+ }
112
+ }
113
+
114
+ function setCachedShareDiagnostics(value: boolean): void {
115
+ if (value !== cachedShareDiagnostics) {
116
+ log.debug(
117
+ { from: cachedShareDiagnostics, to: value },
118
+ "share_diagnostics consent changed",
119
+ );
120
+ cachedShareDiagnostics = value;
121
+ }
122
+ }
123
+
124
+ function setCachedShareDiagnosticsVersion(value: string): void {
125
+ if (value !== cachedShareDiagnosticsVersion) {
126
+ log.debug(
127
+ { from: cachedShareDiagnosticsVersion, to: value },
128
+ "share_diagnostics_accepted_version changed",
129
+ );
130
+ cachedShareDiagnosticsVersion = value;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Begin periodic consent refresh. Idempotent — a second call is a no-op while a
136
+ * timer is already running. Runs one immediate refresh, then every 5 minutes.
137
+ */
138
+ export function startConsentRefresh(): void {
139
+ if (refreshTimer) {
140
+ return;
141
+ }
142
+
143
+ refreshConsentCache().catch((err) => {
144
+ log.debug({ err }, "initial consent refresh failed");
145
+ });
146
+ refreshTimer = setInterval(() => {
147
+ refreshConsentCache().catch((err) => {
148
+ log.debug({ err }, "consent refresh failed");
149
+ });
150
+ }, REFRESH_INTERVAL_MS);
151
+ }
152
+
153
+ /** Stop periodic consent refresh and clear the timer. */
154
+ export async function stopConsentRefresh(): Promise<void> {
155
+ if (refreshTimer) {
156
+ clearInterval(refreshTimer);
157
+ refreshTimer = null;
158
+ }
159
+ }
160
+
161
+ /** Test-only: override the cached analytics value without going through a refresh. */
162
+ export function __setCachedShareAnalyticsForTest(value: boolean): void {
163
+ cachedShareAnalytics = value;
164
+ }
165
+
166
+ /** Test-only: override the cached diagnostics value without going through a refresh. */
167
+ export function __setCachedShareDiagnosticsForTest(value: boolean): void {
168
+ cachedShareDiagnostics = value;
169
+ }
170
+
171
+ /** Test-only: override the cached diagnostics-consent version directly. */
172
+ export function __setCachedShareDiagnosticsVersionForTest(value: string): void {
173
+ cachedShareDiagnosticsVersion = value;
174
+ }
@@ -20,7 +20,7 @@ export const HOOKS = {
20
20
  SHUTDOWN: "shutdown",
21
21
  /** Fires once per user turn, immediately before the agent loop receives `runMessages`. */
22
22
  USER_PROMPT_SUBMIT: "user-prompt-submit",
23
- /** Fires immediately before each provider call. A hook may edit the outbound request (e.g. the system prompt) and opt the turn into deferred output streaming. */
23
+ /** Fires immediately before each provider call. A hook may edit the outbound request (e.g. the system prompt), route the call to a different inference profile, and opt the turn into deferred output streaming. */
24
24
  PRE_MODEL_CALL: "pre-model-call",
25
25
  /** Fires once per tool result, after the tool returns and before the result is sent to the provider. */
26
26
  POST_TOOL_USE: "post-tool-use",
@@ -32,6 +32,11 @@
32
32
  *
33
33
  * - {@link assistantEventHub} — the assistant's pub/sub hub for runtime events
34
34
  * - {@link getSecureKeyAsync} — read a secret from secure storage
35
+ * - {@link getModelProfiles} — list the workspace inference profiles a plugin
36
+ * can route to (e.g. a model router building its category → profile map)
37
+ * - {@link getConfiguredProvider} — resolve a {@link Provider} for a call site
38
+ * (optionally overriding the profile) and run inference through the
39
+ * workspace's configured profiles and credentials — no plugin-supplied API key
35
40
  *
36
41
  * - {@link PluginInitContext} — passed to `init` hook at bootstrap
37
42
  * - {@link PluginShutdownContext} — passed to `shutdown` hook at teardown
@@ -40,7 +45,8 @@
40
45
  * - {@link PostCompactContext} — passed to `post-compact` hook, fired after
41
46
  * the agent loop compacts a conversation mid-turn to re-apply injections
42
47
  * - {@link PreModelCallContext} — passed to `pre-model-call` hook, fired
43
- * before each provider call to edit the request / defer output streaming
48
+ * before each provider call to edit the request, route it to a different
49
+ * inference profile, or defer output streaming
44
50
  * - {@link PostToolUseContext} — passed to `post-tool-use` hook, fired once
45
51
  * per tool result before it joins the provider-bound history
46
52
  * - {@link StopContext} — passed to `stop` hook, the definitive terminal hook
@@ -77,8 +83,23 @@ export type {
77
83
  ToolUseContent,
78
84
  WebSearchToolResultContent,
79
85
  } from "../providers/types.js";
86
+ // Provider + inference types. A plugin that runs its own inference through
87
+ // `getConfiguredProvider` names these to type the provider handle it gets back,
88
+ // the request options it passes to `sendMessage`, and the response.
89
+ export type {
90
+ Provider,
91
+ ProviderEvent,
92
+ ProviderResponse,
93
+ SendMessageConfig,
94
+ SendMessageOptions,
95
+ } from "../providers/types.js";
96
+ // Call-site identifier accepted by `getConfiguredProvider`. Plugins typically
97
+ // pass `"inference"` (the general-purpose call site) and pick the model via the
98
+ // `overrideProfile` option.
99
+ export type { LLMCallSite } from "../config/schemas/llm.js";
80
100
  export type {
81
101
  AgentLoopExitReason,
102
+ ModelProfileInfo,
82
103
  PluginHookFn,
83
104
  PluginInitContext,
84
105
  PluginLogger,
@@ -110,3 +131,14 @@ export type {
110
131
  } from "../runtime/assistant-event-hub.js";
111
132
  export { assistantEventHub } from "../runtime/assistant-event-hub.js";
112
133
  export { getSecureKeyAsync } from "../security/secure-keys.js";
134
+ export { getModelProfiles } from "./model-profiles.js";
135
+ // Resolve a provider for a call site (optionally overriding the profile) so a
136
+ // plugin can run inference through the workspace's configured profiles and
137
+ // credentials — managed-proxy or BYOK — without supplying its own API key.
138
+ // Pair with `getModelProfiles` to pick a profile. Returns `null` when no
139
+ // provider is configured. By default `overrideProfile` layers below any
140
+ // per-call-site config the workspace has pinned (e.g. a cheap `inference`
141
+ // profile), so it loses to that pin; pass `forceOverrideProfile: true` to
142
+ // float the chosen profile above the call-site layers when the plugin must
143
+ // run on a specific profile regardless of workspace tuning.
144
+ export { getConfiguredProvider } from "../providers/provider-send-message.js";
@@ -0,0 +1,33 @@
1
+ import { getConfig } from "../config/loader.js";
2
+ import { orderProfileKeys } from "../config/profile-order.js";
3
+ import type { ModelProfileInfo } from "./types.js";
4
+
5
+ /**
6
+ * List the workspace inference profiles a plugin can route to, in the order the
7
+ * `/model` picker presents them (`llm.profileOrder` first, then the rest
8
+ * alphabetically). Disabled profiles are included and flagged via
9
+ * {@link ModelProfileInfo.isDisabled}; weighted "mix" profiles are included and
10
+ * flagged via {@link ModelProfileInfo.isMix}, since a mix is itself a valid
11
+ * routing target (it resolves to one constituent per conversation).
12
+ *
13
+ * Reads the live in-memory config, so the result reflects the current profile
14
+ * set each time it is called.
15
+ */
16
+ export function getModelProfiles(): ModelProfileInfo[] {
17
+ const { llm } = getConfig();
18
+ const { profiles, activeProfile } = llm;
19
+ const result: ModelProfileInfo[] = [];
20
+ for (const key of orderProfileKeys(profiles, llm.profileOrder)) {
21
+ const entry = profiles[key];
22
+ if (entry == null) continue;
23
+ result.push({
24
+ key,
25
+ label: entry.label ?? key,
26
+ description: entry.description ?? null,
27
+ isActive: key === activeProfile,
28
+ isDisabled: entry.status === "disabled",
29
+ isMix: entry.mix != null,
30
+ });
31
+ }
32
+ return result;
33
+ }
@@ -94,6 +94,35 @@ export interface PluginInitContext {
94
94
  assistantVersion: string;
95
95
  }
96
96
 
97
+ // ─── Model profiles ──────────────────────────────────────────────────────────
98
+
99
+ /**
100
+ * A workspace inference profile a plugin can route to. Returned by
101
+ * {@link getModelProfiles}; {@link key} is the value a `pre-model-call` hook
102
+ * assigns to `PreModelCallContext.modelProfile` to route a call. A model router
103
+ * reads this list (typically at `init`) to learn which profiles exist before
104
+ * mapping a classified message onto one.
105
+ */
106
+ export interface ModelProfileInfo {
107
+ /** Profile key in `llm.profiles`; assignable to `PreModelCallContext.modelProfile`. */
108
+ readonly key: string;
109
+ /** Human-readable label, falling back to {@link key} when none is set. */
110
+ readonly label: string;
111
+ /** Author-supplied description, or `null` when none is set. */
112
+ readonly description: string | null;
113
+ /** Whether this is the workspace's active profile. */
114
+ readonly isActive: boolean;
115
+ /** Whether the profile is disabled; routing to it is rejected by the resolver. */
116
+ readonly isDisabled: boolean;
117
+ /**
118
+ * Whether this is a weighted "mix" profile — an A/B blend that resolves to one
119
+ * of its constituent profiles per conversation via a seeded weighted pick.
120
+ * Routing to its {@link key} is valid; it directs the call into the blend
121
+ * rather than at a single fixed model.
122
+ */
123
+ readonly isMix: boolean;
124
+ }
125
+
97
126
  // ─── Shutdown context ────────────────────────────────────────────────────────
98
127
 
99
128
  /**
@@ -438,8 +467,9 @@ export interface StopContext {
438
467
  * and compaction work can share a conversation), hooks MUST self-gate on
439
468
  * {@link callSite} / {@link conversationId} before acting.
440
469
  *
441
- * A hook may edit the outbound request by replacing {@link systemPrompt}, and may
442
- * opt this turn into deferred output streaming via {@link deferAssistantOutput}.
470
+ * A hook may edit the outbound request by replacing {@link systemPrompt}, route
471
+ * the call to a different inference profile via {@link modelProfile}, and opt
472
+ * this turn into deferred output streaming via {@link deferAssistantOutput}.
443
473
  * Mutate the context in place or return a new one; throwing is contained by the
444
474
  * loop (the call proceeds with the original request).
445
475
  */
@@ -456,6 +486,24 @@ export interface PreModelCallContext {
456
486
  * append a section); the loop sends the resulting value.
457
487
  */
458
488
  systemPrompt: string | null;
489
+ /**
490
+ * Inference profile to route THIS provider call to, named by its key in the
491
+ * workspace `llm.profiles`. Seeded with the call's already-resolved override
492
+ * profile, or `null` when none applies. A hook may replace it to select a
493
+ * different profile per call — the lever a model router uses to map a
494
+ * classified message onto a profile (model + provider connection + sampling
495
+ * settings). For the user-facing `mainAgent` call the resolver layers the
496
+ * named profile at the top of precedence (above the workspace active
497
+ * profile), so the hook's choice wins; a key with no matching profile falls
498
+ * through unchanged (no throw). Honored only when {@link callSite} is set.
499
+ * Set to `null` to apply no override.
500
+ *
501
+ * Context-window sizing and overflow recovery for this call are computed from
502
+ * the profile resolved before the hook runs. Routing a near-budget
503
+ * conversation to a profile with a smaller context window relies on the loop's
504
+ * overflow recovery (compact and retry) rather than proactive compaction.
505
+ */
506
+ modelProfile: string | null;
459
507
  /**
460
508
  * Seeded `false`. When a hook sets it `true`, the loop suppresses this turn's
461
509
  * live assistant `text_delta` stream; a `post-model-call` hook is then
@@ -0,0 +1,56 @@
1
+ import { describe, expect, mock, test } from "bun:test";
2
+
3
+ // Drive the gate off a controllable llm config + a stubbed default-profile
4
+ // resolver, so we can assert the default-on semantics precisely.
5
+ let mockLlm: {
6
+ profiles: Record<string, { advisorEnabled?: boolean | null }>;
7
+ activeProfile?: string;
8
+ } = { profiles: {} };
9
+
10
+ mock.module("../../../../config/loader.js", () => ({
11
+ getConfig: () => ({ llm: mockLlm }),
12
+ }));
13
+ mock.module("../../../../config/llm-resolver.js", () => ({
14
+ resolveDefaultProfileKey: () => "balanced",
15
+ }));
16
+
17
+ const { advisorEnabledForProfile } = await import("../advisor-gate.js");
18
+
19
+ describe("advisorEnabledForProfile", () => {
20
+ test("default-on when the profile omits the flag", () => {
21
+ mockLlm = { profiles: { p: {} }, activeProfile: "p" };
22
+ expect(advisorEnabledForProfile("p")).toBe(true);
23
+ });
24
+
25
+ test("default-on when the flag is null", () => {
26
+ mockLlm = { profiles: { p: { advisorEnabled: null } }, activeProfile: "p" };
27
+ expect(advisorEnabledForProfile("p")).toBe(true);
28
+ });
29
+
30
+ test("disabled only on an explicit false", () => {
31
+ mockLlm = {
32
+ profiles: { p: { advisorEnabled: false } },
33
+ activeProfile: "p",
34
+ };
35
+ expect(advisorEnabledForProfile("p")).toBe(false);
36
+ });
37
+
38
+ test("enabled on an explicit true", () => {
39
+ mockLlm = { profiles: { p: { advisorEnabled: true } }, activeProfile: "p" };
40
+ expect(advisorEnabledForProfile("p")).toBe(true);
41
+ });
42
+
43
+ test("falls back to the active profile when modelProfile is null", () => {
44
+ mockLlm = {
45
+ profiles: { a: { advisorEnabled: false } },
46
+ activeProfile: "a",
47
+ };
48
+ expect(advisorEnabledForProfile(null)).toBe(false);
49
+ });
50
+
51
+ test("falls back to the call-site default profile when neither is set", () => {
52
+ // resolveDefaultProfileKey is stubbed to "balanced".
53
+ mockLlm = { profiles: { balanced: { advisorEnabled: false } } };
54
+ expect(advisorEnabledForProfile(null)).toBe(false);
55
+ });
56
+ });