@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
@@ -32,18 +32,14 @@ import {
32
32
  // ── Mock state ────────────────────────────────────────────────────
33
33
 
34
34
  let mockWorkspaceDir: string = "";
35
- let mockVellumGuardian:
36
- | {
37
- contact: { userFile: string | null };
38
- channel: Record<string, unknown>;
39
- }
40
- | null = null;
41
- let mockAnyGuardian:
42
- | {
43
- contact: { userFile: string | null };
44
- channels: Record<string, unknown>[];
45
- }
46
- | null = null;
35
+ let mockVellumGuardian: {
36
+ contact: { userFile: string | null };
37
+ channel: Record<string, unknown>;
38
+ } | null = null;
39
+ let mockAnyGuardian: {
40
+ contact: { userFile: string | null };
41
+ channels: Record<string, unknown>[];
42
+ } | null = null;
47
43
 
48
44
  // ── Mock modules (must precede imports from the module under test) ──
49
45
 
@@ -52,7 +48,7 @@ mock.module("../util/platform.js", () => ({
52
48
  }));
53
49
 
54
50
  mock.module("../contacts/contact-store.js", () => ({
55
- findContactByChannelExternalId: () => null,
51
+ findContactByAddress: () => null,
56
52
  findGuardianForChannel: (channelType: string) =>
57
53
  channelType === "vellum" ? mockVellumGuardian : null,
58
54
  listGuardianChannels: () => mockAnyGuardian,
@@ -60,12 +56,14 @@ mock.module("../contacts/contact-store.js", () => ({
60
56
 
61
57
  // Import AFTER mocks so the module under test binds to the stubbed
62
58
  // implementations.
59
+ import type { TrustContext } from "../daemon/trust-context.js";
63
60
  import {
64
61
  ensureGuardianPersonaFile,
65
62
  isGuardianPersonaCustomized,
66
63
  resolveGuardianPersona,
67
64
  resolveGuardianPersonaPath,
68
65
  resolveGuardianPersonaStrict,
66
+ resolveUserSlug,
69
67
  } from "../prompts/persona-resolver.js";
70
68
 
71
69
  // ── Temp workspace scaffold ───────────────────────────────────────
@@ -138,7 +136,8 @@ describe("ensureGuardianPersonaFile", () => {
138
136
  const userFile = "alice.md";
139
137
  const dir = join(mockWorkspaceDir, "users");
140
138
  const filePath = join(dir, userFile);
141
- const existingContent = "# Existing user notes\n\n- Likes sparkling water\n";
139
+ const existingContent =
140
+ "# Existing user notes\n\n- Likes sparkling water\n";
142
141
 
143
142
  mkdirSync(dir, { recursive: true });
144
143
  writeFileSync(filePath, existingContent, "utf-8");
@@ -249,3 +248,39 @@ describe("isGuardianPersonaCustomized", () => {
249
248
  expect(isGuardianPersonaCustomized(filePath)).toBe(true);
250
249
  });
251
250
  });
251
+
252
+ // ── resolveUserSlug — background/scheduled guardian turns ──────────
253
+ //
254
+ // Background and scheduled turns carry a guardian trust context with no
255
+ // `requesterExternalUserId`. They must resolve the guardian's user file
256
+ // (parity with foreground guardian turns), not fall through to default.
257
+
258
+ describe("resolveUserSlug (guardian trust, no requester identity)", () => {
259
+ test("guardian trust context without requesterExternalUserId resolves the guardian user file", () => {
260
+ mockVellumGuardian = {
261
+ contact: { userFile: "alice.md" },
262
+ channel: {},
263
+ };
264
+
265
+ const trustContext = {
266
+ sourceChannel: "vellum",
267
+ trustClass: "guardian",
268
+ } as TrustContext;
269
+
270
+ expect(resolveUserSlug(trustContext)).toBe("alice");
271
+ });
272
+
273
+ test("non-guardian trust context without requesterExternalUserId does not borrow the guardian persona", () => {
274
+ mockVellumGuardian = {
275
+ contact: { userFile: "alice.md" },
276
+ channel: {},
277
+ };
278
+
279
+ const trustContext = {
280
+ sourceChannel: "vellum",
281
+ trustClass: "trusted_contact",
282
+ } as TrustContext;
283
+
284
+ expect(resolveUserSlug(trustContext)).toBeNull();
285
+ });
286
+ });
@@ -0,0 +1,178 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { afterEach, describe, expect, test } from "bun:test";
3
+
4
+ import { invalidateConfigCache } from "../config/loader.js";
5
+ import { getModelProfiles } from "../plugin-api/index.js";
6
+ import { getWorkspaceConfigPath } from "../util/platform.js";
7
+
8
+ /**
9
+ * `getModelProfiles()` — the runtime handle a plugin (e.g. a model router) calls
10
+ * to learn which inference profiles a workspace defines. These tests pin its
11
+ * contract: presentation ordering (`profileOrder` then alphabetical tail), mix
12
+ * profiles included and flagged via `isMix`, disabled profiles included and
13
+ * flagged via `isDisabled`, and the per-field fallbacks (label → key,
14
+ * description → null, isActive → `llm.activeProfile`).
15
+ */
16
+
17
+ function writeFixtureConfig(config: Record<string, unknown>): void {
18
+ writeFileSync(getWorkspaceConfigPath(), JSON.stringify(config), "utf-8");
19
+ invalidateConfigCache();
20
+ }
21
+
22
+ describe("getModelProfiles", () => {
23
+ afterEach(() => {
24
+ invalidateConfigCache();
25
+ });
26
+
27
+ test("orders profiles by profileOrder then the remaining keys alphabetically", () => {
28
+ // GIVEN a workspace whose profileOrder names some keys, with a duplicate
29
+ // ("balanced") and a key that resolves to no profile ("ghost").
30
+ writeFixtureConfig({
31
+ llm: {
32
+ profiles: { zeta: {}, alpha: {}, balanced: {}, "cost-optimized": {} },
33
+ profileOrder: ["balanced", "cost-optimized", "balanced", "ghost"],
34
+ },
35
+ });
36
+ // WHEN the profiles are listed.
37
+ const keys = getModelProfiles().map((p) => p.key);
38
+ // THEN profileOrder keys come first (deduped, existing-only), then the rest
39
+ // alphabetically.
40
+ expect(keys).toEqual(["balanced", "cost-optimized", "alpha", "zeta"]);
41
+ });
42
+
43
+ test("includes mix profiles and flags them via isMix", () => {
44
+ // GIVEN a workspace with a weighted mix profile alongside plain ones.
45
+ writeFixtureConfig({
46
+ llm: {
47
+ profiles: {
48
+ alpha: {},
49
+ beta: {},
50
+ blend: {
51
+ mix: [
52
+ { profile: "alpha", weight: 1 },
53
+ { profile: "beta", weight: 1 },
54
+ ],
55
+ },
56
+ },
57
+ profileOrder: ["alpha", "beta", "blend"],
58
+ },
59
+ });
60
+ // WHEN the profiles are listed.
61
+ const flags = getModelProfiles().map((p) => [p.key, p.isMix]);
62
+ // THEN the mix profile is present and flagged isMix, while plain profiles are not.
63
+ expect(flags).toEqual([
64
+ ["alpha", false],
65
+ ["beta", false],
66
+ ["blend", true],
67
+ ]);
68
+ });
69
+
70
+ test("marks an active mix profile via isActive", () => {
71
+ // GIVEN a workspace whose activeProfile is itself a weighted mix.
72
+ writeFixtureConfig({
73
+ llm: {
74
+ profiles: {
75
+ alpha: {},
76
+ blend: {
77
+ mix: [
78
+ { profile: "alpha", weight: 1 },
79
+ { profile: "beta", weight: 1 },
80
+ ],
81
+ },
82
+ beta: {},
83
+ },
84
+ profileOrder: ["alpha", "blend", "beta"],
85
+ activeProfile: "blend",
86
+ },
87
+ });
88
+ // WHEN the profiles are listed.
89
+ const active = getModelProfiles()
90
+ .filter((p) => p.isActive)
91
+ .map((p) => p.key);
92
+ // THEN the active mix profile is the one flagged isActive.
93
+ expect(active).toEqual(["blend"]);
94
+ });
95
+
96
+ test("includes disabled profiles and flags them via isDisabled", () => {
97
+ // GIVEN a workspace with one active and one disabled profile.
98
+ writeFixtureConfig({
99
+ llm: {
100
+ profiles: {
101
+ alpha: { status: "active" },
102
+ beta: { status: "disabled" },
103
+ },
104
+ profileOrder: ["alpha", "beta"],
105
+ },
106
+ });
107
+ // WHEN the profiles are listed.
108
+ const flags = getModelProfiles().map((p) => [p.key, p.isDisabled]);
109
+ // THEN the disabled profile is present and marked isDisabled.
110
+ expect(flags).toEqual([
111
+ ["alpha", false],
112
+ ["beta", true],
113
+ ]);
114
+ });
115
+
116
+ test("falls back to the key when a profile has no label", () => {
117
+ // GIVEN a workspace where one profile sets a label and one does not.
118
+ writeFixtureConfig({
119
+ llm: {
120
+ profiles: { balanced: { label: "Balanced" }, terse: {} },
121
+ profileOrder: ["balanced", "terse"],
122
+ },
123
+ });
124
+ // WHEN the profiles are listed.
125
+ const byKey = Object.fromEntries(
126
+ getModelProfiles().map((p) => [p.key, p.label]),
127
+ );
128
+ // THEN the unlabeled profile's label falls back to its key.
129
+ expect(byKey.balanced).toBe("Balanced");
130
+ expect(byKey.terse).toBe("terse");
131
+ });
132
+
133
+ test("marks the workspace active profile via isActive", () => {
134
+ // GIVEN a workspace whose activeProfile is "beta".
135
+ writeFixtureConfig({
136
+ llm: {
137
+ profiles: { alpha: {}, beta: {} },
138
+ profileOrder: ["alpha", "beta"],
139
+ activeProfile: "beta",
140
+ },
141
+ });
142
+ // WHEN the profiles are listed.
143
+ const flags = getModelProfiles().map((p) => [p.key, p.isActive]);
144
+ // THEN only the active profile is marked isActive.
145
+ expect(flags).toEqual([
146
+ ["alpha", false],
147
+ ["beta", true],
148
+ ]);
149
+ });
150
+
151
+ test("reports description as null when a profile sets none", () => {
152
+ // GIVEN a workspace where one profile has a description and one does not.
153
+ writeFixtureConfig({
154
+ llm: {
155
+ profiles: {
156
+ documented: { description: "Cheaper models, slower" },
157
+ bare: {},
158
+ },
159
+ profileOrder: ["documented", "bare"],
160
+ },
161
+ });
162
+ // WHEN the profiles are listed.
163
+ const byKey = Object.fromEntries(
164
+ getModelProfiles().map((p) => [p.key, p.description]),
165
+ );
166
+ // THEN a missing description is reported as null.
167
+ expect(byKey.documented).toBe("Cheaper models, slower");
168
+ expect(byKey.bare).toBeNull();
169
+ });
170
+
171
+ test("returns an empty list when the workspace defines no profiles", () => {
172
+ // GIVEN a workspace with no profiles defined.
173
+ writeFixtureConfig({ llm: { profiles: {}, profileOrder: [] } });
174
+ // WHEN the profiles are listed.
175
+ // THEN the result is empty.
176
+ expect(getModelProfiles()).toEqual([]);
177
+ });
178
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `getConfiguredProvider` is part of the public `@vellumai/plugin-api` runtime
3
+ * surface, so a user-installable plugin can run inference through the
4
+ * workspace's configured profiles/credentials (managed-proxy or BYOK) without
5
+ * supplying its own API key.
6
+ */
7
+ import { describe, expect, test } from "bun:test";
8
+
9
+ import { PLUGIN_API_EXPORTS } from "../embedded/plugin-api.js";
10
+ import * as pluginApi from "../plugin-api/index.js";
11
+
12
+ describe("plugin-api provider access", () => {
13
+ test("getConfiguredProvider is exported as a runtime value", () => {
14
+ expect(typeof pluginApi.getConfiguredProvider).toBe("function");
15
+ });
16
+
17
+ test("getConfiguredProvider is in the shim-rebound runtime surface", () => {
18
+ // The boot-time shim re-binds every name in PLUGIN_API_EXPORTS from the
19
+ // globalThis-parked namespace, so a plugin importing the bare specifier
20
+ // gets the assistant's real resolver (bound to its initialized provider
21
+ // registry), not a disjoint, uninitialized module copy.
22
+ expect(PLUGIN_API_EXPORTS).toContain("getConfiguredProvider");
23
+ });
24
+ });
@@ -190,13 +190,16 @@ describe("plugin tool contributions", () => {
190
190
  registerPlugin(plugin);
191
191
 
192
192
  await bootstrapPlugins();
193
- // No tool should have been registered.
194
- expect(getAllTools()).toHaveLength(0);
193
+ // `bootstrapPlugins` also registers the first-party defaults (the advisor
194
+ // default contributes the `advisor` tool), so the global tool set is not
195
+ // empty. What matters here is that the no-tools plugin contributed nothing
196
+ // of its own — its tool refcount stays at zero.
197
+ expect(getPluginRefCount("no-tools")).toBe(0);
195
198
 
196
199
  // Shutdown must also be safe — `unregisterPluginTools` is idempotent for
197
200
  // plugins that never contributed any tools.
198
201
  await runShutdownHooks("test-shutdown");
199
- expect(getAllTools()).toHaveLength(0);
202
+ expect(getPluginRefCount("no-tools")).toBe(0);
200
203
  });
201
204
 
202
205
  test("tools declared before init() runs are only visible after bootstrap", async () => {
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Post-compaction re-injection idempotency.
3
+ *
4
+ * The agent loop hands the post-compaction hook the full injected continuation
5
+ * history (it does not pre-strip it), so the hook must clear the tail's stale
6
+ * per-turn injection blocks before re-applying them. Without that strip,
7
+ * `applyRuntimeInjections` double-stacks every non-presence-gated block —
8
+ * `<turn_context>`, `<config_reset_notice>`, `<active_documents>`,
9
+ * `<document_comments>` — because it appends to the tail without removing an
10
+ * existing copy. These tests cover the strip primitive directly and the
11
+ * end-to-end re-injection it protects.
12
+ */
13
+ import { afterEach, describe, expect, test } from "bun:test";
14
+
15
+ import {
16
+ clearConversations,
17
+ setConversation,
18
+ } from "../daemon/conversation-registry.js";
19
+ import { applyRuntimeInjections } from "../daemon/conversation-runtime-assembly.js";
20
+ import { stripTailInjectionsForReinjection } from "../plugins/defaults/memory-retrieval/tail-reinjection-strip.js";
21
+ import type { Message } from "../providers/types.js";
22
+
23
+ const WORKSPACE_BLOCK = "<workspace>\nRoot: /sandbox\n</workspace>";
24
+ const INFO_BLOCK = "<info>\nRemembered fact about project-x\n</info>";
25
+ const TURN_CONTEXT_BLOCK =
26
+ "<turn_context>\ncurrent_time: noon\n</turn_context>";
27
+ const CONFIG_RESET_BLOCK =
28
+ "<config_reset_notice>\nSettings were reset.\n</config_reset_notice>";
29
+ const ACTIVE_DOCUMENTS_BLOCK =
30
+ "<active_documents>\nThe following documents are open: notes.md\n</active_documents>";
31
+ const DOCUMENT_COMMENTS_BLOCK =
32
+ "<document_comments>\nOpen comments on notes.md\n</document_comments>";
33
+ const TURN_BODY = "Please continue the task.";
34
+
35
+ function userMsg(...texts: string[]): Message {
36
+ return {
37
+ role: "user",
38
+ content: texts.map((text) => ({ type: "text" as const, text })),
39
+ };
40
+ }
41
+
42
+ function assistantMsg(text: string): Message {
43
+ return { role: "assistant", content: [{ type: "text", text }] };
44
+ }
45
+
46
+ function tailContentTexts(messages: Message[]): string[] {
47
+ const last = messages[messages.length - 1];
48
+ return last.content.map((block) => (block.type === "text" ? block.text : ""));
49
+ }
50
+
51
+ function countTailBlocksWithPrefix(
52
+ messages: Message[],
53
+ prefix: string,
54
+ ): number {
55
+ return tailContentTexts(messages).filter((text) => text.startsWith(prefix))
56
+ .length;
57
+ }
58
+
59
+ describe("stripTailInjectionsForReinjection", () => {
60
+ test("clears every per-turn injection block from the tail user message", () => {
61
+ // GIVEN a tail user message carrying the real turn body plus the full
62
+ // per-turn injection set, including the four blocks compaction keeps in
63
+ // durable history
64
+ const messages: Message[] = [
65
+ userMsg(
66
+ TURN_BODY,
67
+ WORKSPACE_BLOCK,
68
+ INFO_BLOCK,
69
+ TURN_CONTEXT_BLOCK,
70
+ CONFIG_RESET_BLOCK,
71
+ ACTIVE_DOCUMENTS_BLOCK,
72
+ DOCUMENT_COMMENTS_BLOCK,
73
+ ),
74
+ ];
75
+
76
+ // WHEN the tail injections are stripped for re-injection
77
+ const result = stripTailInjectionsForReinjection(messages);
78
+
79
+ // THEN only the real turn body survives on the tail
80
+ expect(tailContentTexts(result)).toEqual([TURN_BODY]);
81
+ });
82
+
83
+ test("leaves injection blocks on earlier messages untouched", () => {
84
+ // GIVEN an earlier user message and the tail both carrying a turn-context
85
+ // block
86
+ const messages: Message[] = [
87
+ userMsg("Earlier turn", TURN_CONTEXT_BLOCK),
88
+ assistantMsg("Working on it."),
89
+ userMsg(TURN_BODY, TURN_CONTEXT_BLOCK),
90
+ ];
91
+
92
+ // WHEN the tail injections are stripped
93
+ const result = stripTailInjectionsForReinjection(messages);
94
+
95
+ // THEN the earlier message keeps its historical turn-context grounding
96
+ expect(result[0].content).toHaveLength(2);
97
+ expect((result[0].content[1] as { text: string }).text).toBe(
98
+ TURN_CONTEXT_BLOCK,
99
+ );
100
+ // AND only the tail is cleared
101
+ expect(tailContentTexts(result)).toEqual([TURN_BODY]);
102
+ });
103
+
104
+ test("returns the messages unchanged when the tail carries no injections", () => {
105
+ // GIVEN a tail with only real user content
106
+ const messages: Message[] = [userMsg(TURN_BODY)];
107
+
108
+ // WHEN the tail injections are stripped
109
+ const result = stripTailInjectionsForReinjection(messages);
110
+
111
+ // THEN the array is returned unchanged
112
+ expect(result).toBe(messages);
113
+ });
114
+
115
+ test("preserves the tail user message even when every block is stripped", () => {
116
+ // GIVEN a tail composed entirely of injection blocks
117
+ const messages: Message[] = [
118
+ assistantMsg("Earlier reply."),
119
+ userMsg(WORKSPACE_BLOCK, TURN_CONTEXT_BLOCK),
120
+ ];
121
+
122
+ // WHEN the tail injections are stripped
123
+ const result = stripTailInjectionsForReinjection(messages);
124
+
125
+ // THEN the tail user message remains so the re-injection tail invariant
126
+ // holds, just with empty content
127
+ expect(result).toHaveLength(2);
128
+ expect(result[1].role).toBe("user");
129
+ expect(result[1].content).toHaveLength(0);
130
+ });
131
+
132
+ test("is idempotent — stripping an already-stripped tail is a no-op", () => {
133
+ // GIVEN a tail that has already been stripped once
134
+ const messages: Message[] = [userMsg(TURN_BODY, TURN_CONTEXT_BLOCK)];
135
+ const once = stripTailInjectionsForReinjection(messages);
136
+
137
+ // WHEN it is stripped again
138
+ const twice = stripTailInjectionsForReinjection(once);
139
+
140
+ // THEN the second strip changes nothing
141
+ expect(twice).toBe(once);
142
+ expect(tailContentTexts(twice)).toEqual([TURN_BODY]);
143
+ });
144
+ });
145
+
146
+ describe("applyRuntimeInjections re-injection idempotency", () => {
147
+ // `applyRuntimeInjections` synthesizes this conversation id when no
148
+ // turnContext is supplied, so the injectors resolve their blocks from the
149
+ // registry under this key.
150
+ const FALLBACK_CONVERSATION_ID = "runtime-assembly-fallback";
151
+
152
+ // Seed the fallback conversation with a workspace block (presence-gated) and
153
+ // a frozen temporal snapshot so the non-presence-gated `<turn_context>` block
154
+ // is produced every assembly.
155
+ function seedConversation(): void {
156
+ setConversation(FALLBACK_CONVERSATION_ID, {
157
+ conversationId: FALLBACK_CONVERSATION_ID,
158
+ workingDir: "/sandbox",
159
+ workspaceTopLevelContext: WORKSPACE_BLOCK,
160
+ workspaceTopLevelDirty: false,
161
+ currentTurnTemporalSnapshot: { clientTimezone: null },
162
+ } as never);
163
+ }
164
+
165
+ afterEach(() => {
166
+ clearConversations();
167
+ });
168
+
169
+ test("re-injecting an already-injected base without the strip double-stacks the non-presence-gated block", async () => {
170
+ // GIVEN a fresh turn assembled once, so its tail carries one workspace and
171
+ // one turn-context block
172
+ seedConversation();
173
+ const { messages: injectedOnce } = await applyRuntimeInjections(
174
+ [userMsg(TURN_BODY)],
175
+ { conversationId: FALLBACK_CONVERSATION_ID },
176
+ );
177
+ expect(countTailBlocksWithPrefix(injectedOnce, "<turn_context>")).toBe(1);
178
+
179
+ // WHEN injections are applied again to that already-injected base
180
+ const { messages: injectedTwice } = await applyRuntimeInjections(
181
+ injectedOnce,
182
+ { conversationId: FALLBACK_CONVERSATION_ID },
183
+ );
184
+
185
+ // THEN the non-presence-gated turn-context block double-stacks
186
+ expect(countTailBlocksWithPrefix(injectedTwice, "<turn_context>")).toBe(2);
187
+ // AND the presence-gated workspace block stays single
188
+ expect(countTailBlocksWithPrefix(injectedTwice, "<workspace>")).toBe(1);
189
+ });
190
+
191
+ test("stripping the tail before re-injecting keeps every block single", async () => {
192
+ // GIVEN a fresh turn assembled once
193
+ seedConversation();
194
+ const { messages: injectedOnce } = await applyRuntimeInjections(
195
+ [userMsg(TURN_BODY)],
196
+ { conversationId: FALLBACK_CONVERSATION_ID },
197
+ );
198
+
199
+ // WHEN the tail is stripped before re-injection, exactly as the
200
+ // post-compaction hook does
201
+ const { messages: reinjected } = await applyRuntimeInjections(
202
+ stripTailInjectionsForReinjection(injectedOnce),
203
+ { conversationId: FALLBACK_CONVERSATION_ID },
204
+ );
205
+
206
+ // THEN both the non-presence-gated and presence-gated blocks stay single
207
+ expect(countTailBlocksWithPrefix(reinjected, "<turn_context>")).toBe(1);
208
+ expect(countTailBlocksWithPrefix(reinjected, "<workspace>")).toBe(1);
209
+ // AND the real turn body survives
210
+ expect(
211
+ tailContentTexts(reinjected).some((text) => text === TURN_BODY),
212
+ ).toBe(true);
213
+ });
214
+ });
@@ -261,3 +261,79 @@ describe("SendMessageOptions.config.overrideProfile", () => {
261
261
  expect(captured?.overrideProfile).toBeUndefined();
262
262
  });
263
263
  });
264
+
265
+ describe("SendMessageOptions.config.forceOverrideProfile", () => {
266
+ test("CallSiteConfiguredProvider forwards forceOverrideProfile into the send config", async () => {
267
+ let captured: SendMessageOptions | undefined;
268
+ const inner: Provider = {
269
+ name: "anthropic",
270
+ async sendMessage(
271
+ _messages: Message[],
272
+ options?: SendMessageOptions,
273
+ ): Promise<ProviderResponse> {
274
+ captured = options;
275
+ return makeResponse("anthropic");
276
+ },
277
+ };
278
+
279
+ const provider = new CallSiteConfiguredProvider(
280
+ inner,
281
+ "inference",
282
+ "strong",
283
+ true,
284
+ );
285
+ await provider.sendMessage(DUMMY_MESSAGES, {});
286
+
287
+ expect(captured?.config).toMatchObject({
288
+ callSite: "inference",
289
+ overrideProfile: "strong",
290
+ forceOverrideProfile: true,
291
+ });
292
+ });
293
+
294
+ test("forceOverrideProfile floats the override above a call-site profile pin", async () => {
295
+ // The advisor scenario in miniature: the `inference` call site is pinned to
296
+ // a cheap profile, but a caller forces a stronger profile for its own send.
297
+ setLlmConfig({
298
+ default: { provider: "anthropic", model: "claude-opus-4-7" },
299
+ profiles: {
300
+ cheap: { provider: "anthropic", model: "claude-haiku-4-5-20251001" },
301
+ strong: { provider: "anthropic", model: "claude-opus-4-8" },
302
+ },
303
+ callSites: { inference: { profile: "cheap" } },
304
+ });
305
+
306
+ const send = async (force: boolean) => {
307
+ let captured: Record<string, unknown> | undefined;
308
+ const inner: Provider = {
309
+ name: "anthropic",
310
+ async sendMessage(
311
+ _messages: Message[],
312
+ options?: SendMessageOptions,
313
+ ): Promise<ProviderResponse> {
314
+ captured = options?.config as Record<string, unknown> | undefined;
315
+ return makeResponse("anthropic");
316
+ },
317
+ };
318
+ const provider = new RetryProvider(inner);
319
+ await provider.sendMessage(DUMMY_MESSAGES, {
320
+ config: {
321
+ callSite: "inference",
322
+ overrideProfile: "strong",
323
+ ...(force ? { forceOverrideProfile: true } : {}),
324
+ },
325
+ });
326
+ return captured;
327
+ };
328
+
329
+ // Without the flag, the call-site pin (`cheap`) outranks `overrideProfile`.
330
+ expect((await send(false))?.model).toBe("claude-haiku-4-5-20251001");
331
+
332
+ // With the flag, the forced `strong` profile wins over the call-site pin.
333
+ const forced = await send(true);
334
+ expect(forced?.model).toBe("claude-opus-4-8");
335
+ // The routing keys are stripped before the provider wire request.
336
+ expect(forced?.overrideProfile).toBeUndefined();
337
+ expect(forced?.forceOverrideProfile).toBeUndefined();
338
+ });
339
+ });