@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,43 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+
3
+ import type { Message } from "../../../../providers/types.js";
4
+ import {
5
+ getCapture,
6
+ recordMessages,
7
+ recordSystemPrompt,
8
+ resetAdvisorStateForTests,
9
+ seedCapture,
10
+ } from "../advisor-state-store.js";
11
+
12
+ const userMsg = (t: string): Message => ({
13
+ role: "user",
14
+ content: [{ type: "text", text: t }],
15
+ });
16
+
17
+ afterEach(() => {
18
+ resetAdvisorStateForTests();
19
+ });
20
+
21
+ describe("advisor state store", () => {
22
+ test("records system prompt and messages independently per conversation", () => {
23
+ recordSystemPrompt("c1", "system A");
24
+ recordMessages("c1", [userMsg("hello")]);
25
+ recordSystemPrompt("c2", "system B");
26
+
27
+ expect(getCapture("c1")?.systemPrompt).toBe("system A");
28
+ expect(getCapture("c1")?.messages).toEqual([userMsg("hello")]);
29
+ expect(getCapture("c2")?.systemPrompt).toBe("system B");
30
+ expect(getCapture("c2")?.messages).toEqual([]);
31
+ });
32
+
33
+ test("seedCapture snapshots (copies) the array", () => {
34
+ const live: Message[] = [userMsg("one")];
35
+ seedCapture("c1", live);
36
+ live.push(userMsg("two"));
37
+ expect(getCapture("c1")?.messages).toEqual([userMsg("one")]);
38
+ });
39
+
40
+ test("getCapture returns undefined for an unseen conversation", () => {
41
+ expect(getCapture("nope")).toBeUndefined();
42
+ });
43
+ });
@@ -0,0 +1,137 @@
1
+ /**
2
+ * End-to-end integration test: drives the REAL agent loop with all first-party
3
+ * defaults registered (so the advisor plugin's hooks + tool are live), and lets
4
+ * the REAL consult run. Only the provider boundary is stubbed:
5
+ * - the executor's provider (a scripted mock provider), and
6
+ * - `getConfiguredProvider`, so the advisor sub-call resolves to a second
7
+ * scripted mock provider instead of a configured one.
8
+ *
9
+ * The advisor's inference genuinely runs through `consult` → `sendMessage`
10
+ * against the injected advisor provider, exercising the full hook-capture →
11
+ * tool.execute → consult → routed-inference path through the loop.
12
+ */
13
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
14
+
15
+ import {
16
+ createMockProvider,
17
+ textResponse,
18
+ } from "../../../../__tests__/helpers/mock-provider.js";
19
+ import { AgentLoop } from "../../../../agent/loop.js";
20
+ import type {
21
+ ContentBlock,
22
+ Message,
23
+ ProviderResponse,
24
+ } from "../../../../providers/types.js";
25
+
26
+ const ADVICE = "ADVICE: use a channel-based worker pool with graceful drain.";
27
+ let advisorProvider: ReturnType<typeof createMockProvider> | null = null;
28
+
29
+ const realProviderSendMessage =
30
+ await import("../../../../providers/provider-send-message.js");
31
+ mock.module("../../../../providers/provider-send-message.js", () => ({
32
+ ...realProviderSendMessage,
33
+ getConfiguredProvider: async () => advisorProvider?.provider ?? null,
34
+ }));
35
+
36
+ const { resetPluginRegistryAndRegisterDefaults } =
37
+ await import("../../index.js");
38
+ const advisorTool = (await import("../tools/advisor.js")).default;
39
+
40
+ const userTurn: Message = {
41
+ role: "user",
42
+ content: [{ type: "text", text: "build a worker pool" }],
43
+ };
44
+
45
+ function textOf(content: ReadonlyArray<ContentBlock>): string {
46
+ return content.map((b) => (b.type === "text" ? b.text : "")).join("");
47
+ }
48
+
49
+ describe("advisor — agent-loop integration", () => {
50
+ beforeEach(() => {
51
+ resetPluginRegistryAndRegisterDefaults();
52
+ advisorProvider = createMockProvider([textResponse(ADVICE)]);
53
+ });
54
+
55
+ test("model calls advisor → hooks capture → consult routes through inference → advice returns", async () => {
56
+ const consultThenText: ProviderResponse = {
57
+ content: [
58
+ { type: "text", text: "Let me consult the advisor." },
59
+ { type: "tool_use", id: "call-1", name: "advisor", input: {} },
60
+ ],
61
+ model: "mock-model",
62
+ usage: { inputTokens: 10, outputTokens: 5 },
63
+ stopReason: "tool_use",
64
+ };
65
+ const { provider } = createMockProvider([
66
+ consultThenText,
67
+ textResponse("Done — implemented the worker pool."),
68
+ ]);
69
+
70
+ const conversationId = "advisor-itest";
71
+ const loop = new AgentLoop({
72
+ provider,
73
+ systemPrompt: "You are a coding agent.",
74
+ conversationId,
75
+ tools: [
76
+ {
77
+ name: "advisor",
78
+ description: "",
79
+ input_schema: { type: "object", properties: {} },
80
+ },
81
+ ],
82
+ toolExecutor: async (name, input) =>
83
+ name === "advisor"
84
+ ? advisorTool.execute!(input, { conversationId } as never)
85
+ : { content: `unknown tool ${name}`, isError: true },
86
+ });
87
+
88
+ const { history } = await loop.run({
89
+ requestId: "req-1",
90
+ messages: [userTurn],
91
+ onEvent: () => {},
92
+ callSite: "mainAgent",
93
+ trust: { sourceChannel: "vellum", trustClass: "unknown" },
94
+ });
95
+
96
+ // The advisor sub-call happened, routed through the dedicated advisor call site.
97
+ expect(advisorProvider!.calls).toHaveLength(1);
98
+ const sub = advisorProvider!.calls[0];
99
+ expect(sub.options?.config?.callSite).toBe("advisor");
100
+ // No `advisorProfile` configured in the default test config, so no override
101
+ // is passed and the `advisor` call site resolves its own default profile.
102
+ expect(sub.options?.config?.overrideProfile).toBeUndefined();
103
+ expect(sub.options?.config?.tool_choice).toEqual({ type: "none" });
104
+ // No advisor-specific output cap — the resolver applies the profile budget.
105
+ expect(sub.options?.config?.max_tokens).toBeUndefined();
106
+
107
+ // The advisor saw the captured transcript (task present; pending tool_use stripped).
108
+ expect(textOf(sub.messages[0].content)).toContain("build a worker pool");
109
+ expect(textOf(sub.messages[sub.messages.length - 1].content)).toContain(
110
+ "focused strategic guidance",
111
+ );
112
+ // It also saw the model's CURRENT turn — the text it wrote right before the
113
+ // `advisor` tool_use — which `post-model-call` lifts out of `ctx.content`.
114
+ const transcript = sub.messages.map((m) => textOf(m.content)).join("\n");
115
+ expect(transcript).toContain("Let me consult the advisor.");
116
+
117
+ // The advisor saw the executor's system prompt (via pre-model-call).
118
+ expect(sub.options?.systemPrompt).toContain("senior advisor");
119
+ expect(sub.options?.systemPrompt).toContain("You are a coding agent.");
120
+
121
+ // The advice flowed back into the executor's history as the tool result.
122
+ const toolResult = history
123
+ .flatMap((m) => m.content)
124
+ .find((b) => b.type === "tool_result");
125
+ expect((toolResult as { content: string }).content).toContain(
126
+ "channel-based worker pool",
127
+ );
128
+
129
+ // The loop completed with the final answer.
130
+ const lastAssistant = [...history]
131
+ .reverse()
132
+ .find((m) => m.role === "assistant")!;
133
+ expect(textOf(lastAssistant.content)).toContain(
134
+ "implemented the worker pool",
135
+ );
136
+ });
137
+ });
@@ -0,0 +1,153 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import type { Message } from "../../../../providers/types.js";
4
+
5
+ // Stub the provider resolution; spread the real module so `extractAllText` /
6
+ // `userMessage` (which the consult also uses) keep working.
7
+ let sendMessageArgs: Record<string, unknown> | null = null;
8
+ let responseText = "Use a channel-based worker pool; drain on shutdown.";
9
+ let sendMessageError: Error | null = null;
10
+ let providerResolves = true;
11
+
12
+ const fakeProvider = {
13
+ name: "mock-advisor-provider",
14
+ async sendMessage(messages: unknown, options: unknown) {
15
+ sendMessageArgs = { messages, options } as Record<string, unknown>;
16
+ if (sendMessageError) throw sendMessageError;
17
+ return {
18
+ content: [{ type: "text", text: responseText }],
19
+ model: "mock-model",
20
+ usage: { inputTokens: 1, outputTokens: 1 },
21
+ stopReason: "end_turn",
22
+ };
23
+ },
24
+ };
25
+
26
+ const realPsm = await import("../../../../providers/provider-send-message.js");
27
+ mock.module("../../../../providers/provider-send-message.js", () => ({
28
+ ...realPsm,
29
+ getConfiguredProvider: async () => (providerResolves ? fakeProvider : null),
30
+ }));
31
+
32
+ const { consultAdvisor } = await import("../consult.js");
33
+ const advisorTool = (await import("../tools/advisor.js")).default;
34
+ const { recordSystemPrompt, recordMessages, resetAdvisorStateForTests } =
35
+ await import("../advisor-state-store.js");
36
+
37
+ const userMsg = (t: string): Message => ({
38
+ role: "user",
39
+ content: [{ type: "text", text: t }],
40
+ });
41
+
42
+ function optionConfig(): Record<string, unknown> {
43
+ const options = sendMessageArgs?.options as Record<string, unknown>;
44
+ return options.config as Record<string, unknown>;
45
+ }
46
+
47
+ beforeEach(() => {
48
+ sendMessageArgs = null;
49
+ responseText = "Use a channel-based worker pool; drain on shutdown.";
50
+ sendMessageError = null;
51
+ providerResolves = true;
52
+ resetAdvisorStateForTests();
53
+ });
54
+
55
+ describe("consultAdvisor", () => {
56
+ test("routes through the advisor call site, tools off, returns advice", async () => {
57
+ const messages: Message[] = [
58
+ userMsg("build a worker pool"),
59
+ {
60
+ role: "assistant",
61
+ content: [
62
+ { type: "thinking", thinking: "secret", signature: "s" },
63
+ { type: "text", text: "let me consult the advisor" },
64
+ { type: "tool_use", id: "t1", name: "advisor", input: {} },
65
+ ],
66
+ },
67
+ ];
68
+
69
+ const advice = await consultAdvisor({
70
+ systemPrompt: "You are a coding agent.",
71
+ messages,
72
+ });
73
+
74
+ expect(advice).toBe(responseText);
75
+
76
+ const config = optionConfig();
77
+ expect(config.callSite).toBe("advisor");
78
+ // No `advisorProfile` is configured in the default test config, so the
79
+ // consult passes no override and the `advisor` call site resolves to its
80
+ // default profile (`quality-optimized`).
81
+ expect(config.overrideProfile).toBeUndefined();
82
+ expect(config.tool_choice).toEqual({ type: "none" });
83
+ // No advisor-specific output cap — the resolver applies the profile budget.
84
+ expect(config.max_tokens).toBeUndefined();
85
+
86
+ const sent = sendMessageArgs?.messages as Message[];
87
+ expect(sent[0]).toEqual(userMsg("build a worker pool"));
88
+ expect(sent[1]).toEqual({
89
+ role: "assistant",
90
+ content: [{ type: "text", text: "let me consult the advisor" }],
91
+ });
92
+ const lastText = (sent[sent.length - 1].content[0] as { text: string })
93
+ .text;
94
+ expect(lastText).toContain("focused strategic guidance");
95
+ // The request carries no word limit.
96
+ expect(lastText).not.toContain("words");
97
+
98
+ const options = sendMessageArgs?.options as { systemPrompt: string };
99
+ expect(options.systemPrompt).toContain("senior advisor");
100
+ expect(options.systemPrompt).toContain("You are a coding agent.");
101
+ });
102
+
103
+ test("soft-fails when no provider is configured", async () => {
104
+ providerResolves = false;
105
+ const advice = await consultAdvisor({
106
+ systemPrompt: null,
107
+ messages: [userMsg("hi")],
108
+ });
109
+ expect(advice).toContain("no inference provider");
110
+ });
111
+
112
+ test("returns a notice when there is no usable transcript", async () => {
113
+ const advice = await consultAdvisor({ systemPrompt: null, messages: [] });
114
+ expect(advice).toContain("no conversation context");
115
+ expect(sendMessageArgs).toBeNull();
116
+ });
117
+
118
+ test("falls back to a notice when the advisor returns blank text", async () => {
119
+ responseText = " ";
120
+ const advice = await consultAdvisor({
121
+ systemPrompt: null,
122
+ messages: [userMsg("hi")],
123
+ });
124
+ expect(advice).toContain("no guidance");
125
+ });
126
+ });
127
+
128
+ describe("advisor tool.execute", () => {
129
+ test("reads the captured transcript and returns guidance as a non-error result", async () => {
130
+ recordSystemPrompt("c1", "You are a coding agent.");
131
+ recordMessages("c1", [userMsg("build a worker pool")]);
132
+
133
+ const result = await advisorTool.execute?.({}, {
134
+ conversationId: "c1",
135
+ } as never);
136
+
137
+ expect(result?.isError).toBe(false);
138
+ expect(result?.content).toBe(responseText);
139
+ });
140
+
141
+ test("degrades to a benign result (never throws) when the consult fails", async () => {
142
+ recordMessages("c2", [userMsg("hi")]);
143
+ sendMessageError = new Error("kaboom");
144
+
145
+ const result = await advisorTool.execute?.({}, {
146
+ conversationId: "c2",
147
+ } as never);
148
+
149
+ expect(result?.isError).toBe(false);
150
+ expect(result?.content).toContain("advisor unavailable");
151
+ expect(result?.content).toContain("kaboom");
152
+ });
153
+ });
@@ -0,0 +1,138 @@
1
+ import { beforeEach, describe, expect, test } from "bun:test";
2
+
3
+ import type {
4
+ Message,
5
+ PostModelCallContext,
6
+ PreModelCallContext,
7
+ UserPromptSubmitContext,
8
+ } from "@vellumai/plugin-api";
9
+
10
+ import {
11
+ getCapture,
12
+ resetAdvisorStateForTests,
13
+ } from "../advisor-state-store.js";
14
+ import postModelCall from "../hooks/post-model-call.js";
15
+ import preModelCall from "../hooks/pre-model-call.js";
16
+ import userPromptSubmit from "../hooks/user-prompt-submit.js";
17
+ import { STEERING_MARKER } from "../steering.js";
18
+
19
+ const logger = { info() {}, warn() {}, error() {}, debug() {} };
20
+ const userMsg = (t: string): Message => ({
21
+ role: "user",
22
+ content: [{ type: "text", text: t }],
23
+ });
24
+
25
+ beforeEach(() => {
26
+ resetAdvisorStateForTests();
27
+ });
28
+
29
+ describe("pre-model-call hook", () => {
30
+ test("records the original system prompt and injects steering on mainAgent", async () => {
31
+ const ctx = {
32
+ conversationId: "c1",
33
+ callSite: "mainAgent",
34
+ systemPrompt: "BASE PROMPT",
35
+ deferAssistantOutput: false,
36
+ logger,
37
+ } as unknown as PreModelCallContext;
38
+
39
+ await preModelCall(ctx);
40
+
41
+ expect(ctx.systemPrompt).toContain(STEERING_MARKER);
42
+ expect(ctx.systemPrompt?.startsWith("BASE PROMPT")).toBe(true);
43
+ expect(getCapture("c1")?.systemPrompt).toBe("BASE PROMPT");
44
+ });
45
+
46
+ test("ignores non-mainAgent calls", async () => {
47
+ const ctx = {
48
+ conversationId: "bg",
49
+ callSite: "inference",
50
+ systemPrompt: "BG",
51
+ deferAssistantOutput: false,
52
+ logger,
53
+ } as unknown as PreModelCallContext;
54
+
55
+ await preModelCall(ctx);
56
+ expect(ctx.systemPrompt).toBe("BG");
57
+ expect(getCapture("bg")).toBeUndefined();
58
+ });
59
+ });
60
+
61
+ describe("post-model-call hook", () => {
62
+ const base = {
63
+ content: [],
64
+ stopReason: "end_turn",
65
+ decision: "stop" as const,
66
+ logger,
67
+ };
68
+
69
+ test("snapshots the transcript on mainAgent", async () => {
70
+ const messages = [userMsg("hi")];
71
+ await postModelCall({
72
+ ...base,
73
+ conversationId: "c1",
74
+ callSite: "mainAgent",
75
+ messages,
76
+ } as unknown as PostModelCallContext);
77
+ expect(getCapture("c1")?.messages).toEqual(messages);
78
+ });
79
+
80
+ test("appends the model's current turn (ctx.content) to the snapshot", async () => {
81
+ const messages = [userMsg("task")];
82
+ const assistantTurn: Message = {
83
+ role: "assistant",
84
+ content: [
85
+ { type: "text", text: "thinking about it" },
86
+ { type: "tool_use", id: "t1", name: "advisor", input: {} },
87
+ ],
88
+ };
89
+ await postModelCall({
90
+ ...base,
91
+ content: assistantTurn.content,
92
+ conversationId: "c4",
93
+ callSite: "mainAgent",
94
+ messages,
95
+ } as unknown as PostModelCallContext);
96
+ const captured = getCapture("c4")?.messages;
97
+ expect(captured).toHaveLength(2);
98
+ expect(captured?.[1]).toEqual(assistantTurn);
99
+ });
100
+
101
+ test("ignores the advisor's own inference sub-call (recursion safety) and errors", async () => {
102
+ await postModelCall({
103
+ ...base,
104
+ conversationId: "c2",
105
+ callSite: "inference",
106
+ messages: [userMsg("hi")],
107
+ } as unknown as PostModelCallContext);
108
+ expect(getCapture("c2")).toBeUndefined();
109
+
110
+ await postModelCall({
111
+ ...base,
112
+ conversationId: "c3",
113
+ callSite: "mainAgent",
114
+ messages: [userMsg("hi")],
115
+ error: new Error("boom"),
116
+ stopReason: null,
117
+ } as unknown as PostModelCallContext);
118
+ expect(getCapture("c3")).toBeUndefined();
119
+ });
120
+ });
121
+
122
+ describe("user-prompt-submit hook", () => {
123
+ test("seeds the capture with the inbound history", async () => {
124
+ const messages = [userMsg("task")];
125
+ await userPromptSubmit({
126
+ conversationId: "c1",
127
+ latestMessages: messages,
128
+ originalMessages: messages,
129
+ prompt: "task",
130
+ userMessageId: "u1",
131
+ requestId: "r1",
132
+ modelProfileKey: null,
133
+ isNonInteractive: false,
134
+ logger,
135
+ } as unknown as UserPromptSubmitContext);
136
+ expect(getCapture("c1")?.messages).toEqual(messages);
137
+ });
138
+ });
@@ -0,0 +1,147 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import type { ContentBlock, Message } from "../../../../providers/types.js";
4
+ import { toAdvisorMessages } from "../transcript.js";
5
+
6
+ const text = (t: string): ContentBlock => ({ type: "text", text: t });
7
+
8
+ describe("toAdvisorMessages", () => {
9
+ test("drops thinking and redacted-thinking blocks", () => {
10
+ const messages: Message[] = [
11
+ { role: "user", content: [text("do the task")] },
12
+ {
13
+ role: "assistant",
14
+ content: [
15
+ { type: "thinking", thinking: "secret", signature: "sig" },
16
+ { type: "redacted_thinking", data: "blob" },
17
+ text("here is my answer"),
18
+ ],
19
+ },
20
+ ];
21
+ const out = toAdvisorMessages(messages);
22
+ expect(out).toHaveLength(2);
23
+ expect(out[1].content).toEqual([text("here is my answer")]);
24
+ });
25
+
26
+ test("strips the pending advisor tool_use from the final assistant turn", () => {
27
+ const messages: Message[] = [
28
+ { role: "user", content: [text("task")] },
29
+ {
30
+ role: "assistant",
31
+ content: [
32
+ text("let me consult the advisor"),
33
+ { type: "tool_use", id: "t1", name: "advisor", input: {} },
34
+ ],
35
+ },
36
+ ];
37
+ const out = toAdvisorMessages(messages);
38
+ expect(out[1].content).toEqual([text("let me consult the advisor")]);
39
+ });
40
+
41
+ test("preserves completed client tool_use / tool_result pairs in earlier turns", () => {
42
+ const messages: Message[] = [
43
+ { role: "user", content: [text("task")] },
44
+ {
45
+ role: "assistant",
46
+ content: [{ type: "tool_use", id: "a", name: "bash", input: {} }],
47
+ },
48
+ {
49
+ role: "user",
50
+ content: [{ type: "tool_result", tool_use_id: "a", content: "output" }],
51
+ },
52
+ { role: "assistant", content: [text("done")] },
53
+ ];
54
+ const out = toAdvisorMessages(messages);
55
+ expect(out[1].content[0]).toEqual({
56
+ type: "tool_use",
57
+ id: "a",
58
+ name: "bash",
59
+ input: {},
60
+ });
61
+ expect(out[2].content[0]).toEqual({
62
+ type: "tool_result",
63
+ tool_use_id: "a",
64
+ content: "output",
65
+ });
66
+ });
67
+
68
+ test("drops a web-search server_tool_use AND its result together — no orphan", () => {
69
+ const messages: Message[] = [
70
+ { role: "user", content: [text("look it up")] },
71
+ {
72
+ role: "assistant",
73
+ content: [
74
+ text("searching"),
75
+ { type: "server_tool_use", id: "ws1", name: "web_search", input: {} },
76
+ ],
77
+ },
78
+ {
79
+ role: "user",
80
+ content: [
81
+ {
82
+ type: "web_search_tool_result",
83
+ tool_use_id: "ws1",
84
+ content: [{ title: "x", url: "y" }],
85
+ },
86
+ ],
87
+ },
88
+ { role: "assistant", content: [text("found it; here is the answer")] },
89
+ ];
90
+ const out = toAdvisorMessages(messages);
91
+ const flat = out.flatMap((m) => m.content);
92
+ expect(flat.some((b) => b.type === "server_tool_use")).toBe(false);
93
+ expect(flat.some((b) => b.type === "web_search_tool_result")).toBe(false);
94
+ expect(flat).toContainEqual(text("searching"));
95
+ expect(flat).toContainEqual(text("found it; here is the answer"));
96
+ });
97
+
98
+ test("keeps top-level image blocks", () => {
99
+ const img: ContentBlock = {
100
+ type: "image",
101
+ source: { type: "base64", media_type: "image/png", data: "x" },
102
+ };
103
+ const messages: Message[] = [
104
+ { role: "user", content: [text("look at this"), img] },
105
+ ];
106
+ const out = toAdvisorMessages(messages);
107
+ expect(out[0].content).toEqual([text("look at this"), img]);
108
+ });
109
+
110
+ test("keeps image contentBlocks on a tool_result, dropping disallowed ones", () => {
111
+ const img: ContentBlock = {
112
+ type: "image",
113
+ source: { type: "base64", media_type: "image/png", data: "x" },
114
+ };
115
+ const messages: Message[] = [
116
+ {
117
+ role: "user",
118
+ content: [
119
+ {
120
+ type: "tool_result",
121
+ tool_use_id: "a",
122
+ content: "screenshot",
123
+ contentBlocks: [
124
+ img,
125
+ {
126
+ type: "file",
127
+ source: {
128
+ type: "base64",
129
+ media_type: "application/pdf",
130
+ data: "z",
131
+ filename: "f.pdf",
132
+ },
133
+ },
134
+ ],
135
+ },
136
+ ],
137
+ },
138
+ ];
139
+ const out = toAdvisorMessages(messages);
140
+ expect(out[0].content[0]).toEqual({
141
+ type: "tool_result",
142
+ tool_use_id: "a",
143
+ content: "screenshot",
144
+ contentBlocks: [img],
145
+ });
146
+ });
147
+ });
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Per-chat-profile gate for the advisor.
3
+ *
4
+ * The advisor is active or not depending on the chat profile the turn routes
5
+ * to (`ProfileEntry.advisorEnabled`). Default-on: the advisor runs unless a
6
+ * profile sets `advisorEnabled: false` explicitly — absent/null both mean on,
7
+ * preserving the prior always-on behavior for profiles that never set it.
8
+ */
9
+
10
+ import { resolveDefaultProfileKey } from "../../../config/llm-resolver.js";
11
+ import { getConfig } from "../../../config/loader.js";
12
+
13
+ /**
14
+ * Whether the advisor is enabled for the chat profile this turn uses.
15
+ *
16
+ * `modelProfile` is the call's already-resolved profile — `PreModelCallContext.modelProfile`
17
+ * from the steering hook, or `ToolContext.overrideProfile` from the tool. Pass
18
+ * `null` when no per-turn override applies, in which case the workspace active
19
+ * profile — or the `mainAgent` call-site default — applies.
20
+ */
21
+ export function advisorEnabledForProfile(modelProfile: string | null): boolean {
22
+ const { llm } = getConfig();
23
+ const key =
24
+ modelProfile ??
25
+ llm.activeProfile ??
26
+ resolveDefaultProfileKey("mainAgent", llm);
27
+ const entry = key ? llm.profiles[key] : undefined;
28
+ return entry?.advisorEnabled !== false;
29
+ }