@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
@@ -1,7 +1,9 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
2
 
3
- import type { AssistantConfig } from "../../config/schema.js";
4
- import type { WorkflowRun } from "../../workflows/journal-store.js";
3
+ import type {
4
+ WorkflowJournalEntry,
5
+ WorkflowRun,
6
+ } from "../../workflows/journal-store.js";
5
7
  import type { SavedWorkflowEntry } from "../../workflows/library.js";
6
8
  import {
7
9
  WorkflowResumeNotPossibleError,
@@ -59,14 +61,31 @@ interface FakeManager {
59
61
  resume: (id: string) => { runId: string };
60
62
  }
61
63
 
64
+ function makeJournalEntry(
65
+ overrides: Partial<WorkflowJournalEntry> = {},
66
+ ): WorkflowJournalEntry {
67
+ return {
68
+ runId: "run-1",
69
+ seq: 0,
70
+ callHash: "call-0",
71
+ kind: "agent",
72
+ request: null,
73
+ result: null,
74
+ status: "completed",
75
+ createdAt: 1000,
76
+ ...overrides,
77
+ };
78
+ }
79
+
62
80
  function setup(opts: {
63
- flagEnabled?: boolean;
64
81
  runs?: WorkflowRun[];
65
82
  saved?: SavedWorkflowEntry[];
66
83
  /** Custom resume impl; defaults to a success that records the id. */
67
84
  resume?: (id: string) => { runId: string };
68
85
  /** Resolved auto-approve threshold for the resume posture gate. */
69
86
  threshold?: "none" | "low" | "medium" | "high";
87
+ /** Journal entries returned by getJournal, keyed by run id. */
88
+ journal?: Record<string, WorkflowJournalEntry[]>;
70
89
  }): {
71
90
  aborted: string[];
72
91
  resumed: string[];
@@ -98,9 +117,8 @@ function setup(opts: {
98
117
  __setWorkflowRoutesDeps({
99
118
  getManager: () => manager,
100
119
  listWorkflows: () => opts.saved ?? [],
101
- getConfig: () => ({}) as AssistantConfig,
102
- isFlagEnabled: () => opts.flagEnabled ?? true,
103
120
  getAutoApproveThreshold: async () => opts.threshold ?? "none",
121
+ getJournal: (runId) => opts.journal?.[runId] ?? [],
104
122
  });
105
123
  return { aborted, resumed, listCalls };
106
124
  }
@@ -113,10 +131,9 @@ afterEach(() => {
113
131
  // Happy paths
114
132
  // ---------------------------------------------------------------------------
115
133
 
116
- describe("workflow routes (flag on)", () => {
134
+ describe("workflow routes (happy paths)", () => {
117
135
  beforeEach(() => {
118
136
  setup({
119
- flagEnabled: true,
120
137
  runs: [
121
138
  makeRun({ id: "run-1" }),
122
139
  makeRun({ id: "run-2", status: "completed" }),
@@ -145,7 +162,6 @@ describe("workflow routes (flag on)", () => {
145
162
 
146
163
  test("listWorkflowRuns honors limit + status query params", async () => {
147
164
  const { listCalls } = setup({
148
- flagEnabled: true,
149
165
  runs: [makeRun({ id: "run-1", status: "completed" })],
150
166
  });
151
167
  await route("listWorkflowRuns").handler({
@@ -163,7 +179,6 @@ describe("workflow routes (flag on)", () => {
163
179
 
164
180
  test("abortWorkflowRun aborts a known run", async () => {
165
181
  const { aborted } = setup({
166
- flagEnabled: true,
167
182
  runs: [makeRun({ id: "run-1" })],
168
183
  });
169
184
  const result = (await route("abortWorkflowRun").handler({
@@ -175,7 +190,6 @@ describe("workflow routes (flag on)", () => {
175
190
 
176
191
  test("resumeWorkflowRun resumes an interrupted run", async () => {
177
192
  const { resumed } = setup({
178
- flagEnabled: true,
179
193
  runs: [makeRun({ id: "run-1", status: "interrupted" })],
180
194
  });
181
195
  const result = (await route("resumeWorkflowRun").handler({
@@ -192,7 +206,6 @@ describe("workflow routes (flag on)", () => {
192
206
  // full-access posture it must refuse rather than silently bypass consent —
193
207
  // resume() is never called.
194
208
  const { resumed } = setup({
195
- flagEnabled: true,
196
209
  threshold: "medium",
197
210
  runs: [
198
211
  makeRun({
@@ -213,7 +226,6 @@ describe("workflow routes (flag on)", () => {
213
226
  // high-risk tools, so no prompt is needed — the side-effecting resume
214
227
  // proceeds directly.
215
228
  const { resumed } = setup({
216
- flagEnabled: true,
217
229
  threshold: "high",
218
230
  runs: [
219
231
  makeRun({
@@ -236,7 +248,6 @@ describe("workflow routes (flag on)", () => {
236
248
  // the object shape too — a strict parse would treat it as read-only and let
237
249
  // the side-effecting resume through without approval.
238
250
  const { resumed } = setup({
239
- flagEnabled: true,
240
251
  threshold: "medium",
241
252
  runs: [
242
253
  makeRun({
@@ -259,7 +270,6 @@ describe("workflow routes (flag on)", () => {
259
270
  // An explicit empty manifest grants no side effects, so the route resumes
260
271
  // it directly — the gate keys on the stored manifest, not run existence.
261
272
  const { resumed } = setup({
262
- flagEnabled: true,
263
273
  threshold: "medium",
264
274
  runs: [
265
275
  makeRun({
@@ -275,7 +285,6 @@ describe("workflow routes (flag on)", () => {
275
285
 
276
286
  test("resumeWorkflowRun maps a non-interrupted run to a 409 ConflictError", async () => {
277
287
  setup({
278
- flagEnabled: true,
279
288
  runs: [makeRun({ id: "run-1", status: "completed" })],
280
289
  resume: (id) => {
281
290
  throw new WorkflowResumeNotPossibleError(
@@ -292,7 +301,6 @@ describe("workflow routes (flag on)", () => {
292
301
 
293
302
  test("resumeWorkflowRun maps a cap error to a 429 TooManyRequestsError", async () => {
294
303
  setup({
295
- flagEnabled: true,
296
304
  runs: [makeRun({ id: "run-1", status: "interrupted" })],
297
305
  resume: () => {
298
306
  throw new WorkflowRunCapError(3);
@@ -317,20 +325,222 @@ describe("workflow routes (flag on)", () => {
317
325
  });
318
326
  });
319
327
 
328
+ // ---------------------------------------------------------------------------
329
+ // Journal projection
330
+ // ---------------------------------------------------------------------------
331
+
332
+ interface WireLeaf {
333
+ seq: number;
334
+ kind: string;
335
+ label?: string;
336
+ phase?: string;
337
+ promptSummary?: string;
338
+ status: string;
339
+ resultSummary?: string;
340
+ inputTokens?: number;
341
+ outputTokens?: number;
342
+ createdAt: number | null;
343
+ }
344
+
345
+ interface WireJournal {
346
+ runId: string;
347
+ status: string;
348
+ agentsSpawned: number;
349
+ inputTokens: number;
350
+ outputTokens: number;
351
+ leaves: WireLeaf[];
352
+ }
353
+
354
+ describe("getWorkflowRunJournal", () => {
355
+ test("projects agent leaves with label/phase/promptSummary/resultSummary extracted", async () => {
356
+ setup({
357
+ runs: [makeRun({ id: "run-1" })],
358
+ journal: {
359
+ "run-1": [
360
+ makeJournalEntry({
361
+ seq: 0,
362
+ kind: "agent",
363
+ request: {
364
+ prompt: "Investigate the failing build",
365
+ opts: { label: "investigate", phase: "triage" },
366
+ },
367
+ result: { summary: "found the flaky test" },
368
+ status: "completed",
369
+ inputTokens: 120,
370
+ outputTokens: 45,
371
+ }),
372
+ ],
373
+ },
374
+ });
375
+ const result = (await route("getWorkflowRunJournal").handler({
376
+ pathParams: { id: "run-1" },
377
+ })) as WireJournal;
378
+
379
+ expect(result.runId).toBe("run-1");
380
+ expect(result.status).toBe("running");
381
+ expect(result.agentsSpawned).toBe(2);
382
+ expect(result.leaves.map((l) => l.seq)).toEqual([0]);
383
+
384
+ const first = result.leaves[0];
385
+ expect(first.kind).toBe("agent");
386
+ expect(first.label).toBe("investigate");
387
+ expect(first.phase).toBe("triage");
388
+ expect(first.promptSummary).toBe("Investigate the failing build");
389
+ expect(first.resultSummary).toContain("found the flaky test");
390
+ // Per-leaf token usage is projected onto the wire leaf.
391
+ expect(first.inputTokens).toBe(120);
392
+ expect(first.outputTokens).toBe(45);
393
+ // The bulky raw request/result payloads are dropped.
394
+ expect(first).not.toHaveProperty("request");
395
+ expect(first).not.toHaveProperty("result");
396
+ });
397
+
398
+ test("omits per-leaf token fields when the journal entry carries none", async () => {
399
+ setup({
400
+ runs: [makeRun({ id: "run-1" })],
401
+ journal: {
402
+ "run-1": [
403
+ makeJournalEntry({
404
+ seq: 0,
405
+ kind: "agent",
406
+ request: { prompt: "no tokens recorded", opts: {} },
407
+ status: "completed",
408
+ }),
409
+ ],
410
+ },
411
+ });
412
+ const result = (await route("getWorkflowRunJournal").handler({
413
+ pathParams: { id: "run-1" },
414
+ })) as WireJournal;
415
+
416
+ const leaf = result.leaves[0];
417
+ expect(leaf).not.toHaveProperty("inputTokens");
418
+ expect(leaf).not.toHaveProperty("outputTokens");
419
+ });
420
+
421
+ test("excludes kind:workflow entries so the backfill matches the live agent-only stream", async () => {
422
+ // The live `workflow_leaf_*` stream is emitted only for agent leaves; nested
423
+ // `workflow(name)` resolutions never reach it. The journal route must filter
424
+ // to agent leaves too, or a nested-workflow run renders a phantom unlabeled
425
+ // node on backfill and a different leaf set than live.
426
+ setup({
427
+ runs: [makeRun({ id: "run-1" })],
428
+ journal: {
429
+ "run-1": [
430
+ makeJournalEntry({
431
+ seq: 0,
432
+ kind: "agent",
433
+ request: { prompt: "first agent", opts: { label: "first" } },
434
+ status: "completed",
435
+ }),
436
+ makeJournalEntry({
437
+ seq: 1,
438
+ kind: "workflow",
439
+ request: { name: "child", args: { foo: 1 } },
440
+ result: "child done",
441
+ status: "completed",
442
+ }),
443
+ makeJournalEntry({
444
+ seq: 2,
445
+ kind: "agent",
446
+ request: { prompt: "second agent", opts: { label: "second" } },
447
+ status: "completed",
448
+ }),
449
+ ],
450
+ },
451
+ });
452
+ const result = (await route("getWorkflowRunJournal").handler({
453
+ pathParams: { id: "run-1" },
454
+ })) as WireJournal;
455
+
456
+ // Only the agent leaves survive, in seq order; the workflow-kind entry (seq
457
+ // 1) is dropped entirely.
458
+ expect(result.leaves.map((l) => l.seq)).toEqual([0, 2]);
459
+ expect(result.leaves.every((l) => l.kind === "agent")).toBe(true);
460
+ expect(result.leaves.map((l) => l.label)).toEqual(["first", "second"]);
461
+ });
462
+
463
+ test("surfaces a failed leaf's { error } result as resultSummary", async () => {
464
+ setup({
465
+ runs: [makeRun({ id: "run-1" })],
466
+ journal: {
467
+ "run-1": [
468
+ makeJournalEntry({
469
+ seq: 0,
470
+ status: "failed",
471
+ request: { prompt: "do the thing", opts: {} },
472
+ result: { error: "agent crashed: out of tokens" },
473
+ }),
474
+ ],
475
+ },
476
+ });
477
+ const result = (await route("getWorkflowRunJournal").handler({
478
+ pathParams: { id: "run-1" },
479
+ })) as WireJournal;
480
+
481
+ expect(result.leaves[0].status).toBe("failed");
482
+ expect(result.leaves[0].resultSummary).toContain(
483
+ "agent crashed: out of tokens",
484
+ );
485
+ });
486
+
487
+ test("truncates a long prompt/result to ~200 chars", async () => {
488
+ const longPrompt = "x".repeat(500);
489
+ const longResult = "y".repeat(500);
490
+ setup({
491
+ runs: [makeRun({ id: "run-1" })],
492
+ journal: {
493
+ "run-1": [
494
+ makeJournalEntry({
495
+ seq: 0,
496
+ request: { prompt: longPrompt, opts: {} },
497
+ result: longResult,
498
+ }),
499
+ ],
500
+ },
501
+ });
502
+ const result = (await route("getWorkflowRunJournal").handler({
503
+ pathParams: { id: "run-1" },
504
+ })) as WireJournal;
505
+
506
+ const leaf = result.leaves[0];
507
+ expect(leaf.promptSummary!.length).toBeLessThanOrEqual(201);
508
+ expect(leaf.promptSummary!.startsWith("x")).toBe(true);
509
+ // A truncated summary ends with the ellipsis marker.
510
+ expect(leaf.promptSummary!.endsWith("…")).toBe(true);
511
+ expect(leaf.resultSummary!.length).toBeLessThanOrEqual(201);
512
+ });
513
+
514
+ test("returns an empty leaf list for a run with no journal entries", async () => {
515
+ setup({ runs: [makeRun({ id: "run-1" })] });
516
+ const result = (await route("getWorkflowRunJournal").handler({
517
+ pathParams: { id: "run-1" },
518
+ })) as WireJournal;
519
+ expect(result.leaves).toEqual([]);
520
+ });
521
+
522
+ test("throws NotFoundError for an unknown id", () => {
523
+ setup({ runs: [] });
524
+ expect(() =>
525
+ route("getWorkflowRunJournal").handler({ pathParams: { id: "nope" } }),
526
+ ).toThrow(NotFoundError);
527
+ });
528
+ });
529
+
320
530
  // ---------------------------------------------------------------------------
321
531
  // Unknown run → 404
322
532
  // ---------------------------------------------------------------------------
323
533
 
324
534
  describe("workflow routes (unknown run)", () => {
325
535
  test("getWorkflowRun throws NotFoundError for an unknown id", () => {
326
- setup({ flagEnabled: true, runs: [] });
536
+ setup({ runs: [] });
327
537
  expect(() =>
328
538
  route("getWorkflowRun").handler({ pathParams: { id: "nope" } }),
329
539
  ).toThrow(NotFoundError);
330
540
  });
331
541
 
332
542
  test("abortWorkflowRun throws NotFoundError for an unknown id", () => {
333
- const { aborted } = setup({ flagEnabled: true, runs: [] });
543
+ const { aborted } = setup({ runs: [] });
334
544
  expect(() =>
335
545
  route("abortWorkflowRun").handler({ pathParams: { id: "nope" } }),
336
546
  ).toThrow(NotFoundError);
@@ -338,35 +548,10 @@ describe("workflow routes (unknown run)", () => {
338
548
  });
339
549
 
340
550
  test("resumeWorkflowRun throws NotFoundError for an unknown id", async () => {
341
- const { resumed } = setup({ flagEnabled: true, runs: [] });
551
+ const { resumed } = setup({ runs: [] });
342
552
  await expect(
343
553
  route("resumeWorkflowRun").handler({ pathParams: { id: "nope" } }),
344
554
  ).rejects.toThrow(NotFoundError);
345
555
  expect(resumed).toEqual([]);
346
556
  });
347
557
  });
348
-
349
- // ---------------------------------------------------------------------------
350
- // Flag off → every route 404s
351
- // ---------------------------------------------------------------------------
352
-
353
- describe("workflow routes (flag off)", () => {
354
- beforeEach(() => {
355
- setup({ flagEnabled: false, runs: [makeRun({ id: "run-1" })], saved: [] });
356
- });
357
-
358
- test.each([
359
- ["listWorkflowRuns", { queryParams: {} }],
360
- ["getWorkflowRun", { pathParams: { id: "run-1" } }],
361
- ["abortWorkflowRun", { pathParams: { id: "run-1" } }],
362
- ["listSavedWorkflows", {}],
363
- ] as const)("%s throws NotFoundError when the flag is off", (op, args) => {
364
- expect(() => route(op).handler(args)).toThrow(NotFoundError);
365
- });
366
-
367
- test("resumeWorkflowRun rejects with NotFoundError when the flag is off", async () => {
368
- await expect(
369
- route("resumeWorkflowRun").handler({ pathParams: { id: "run-1" } }),
370
- ).rejects.toThrow(NotFoundError);
371
- });
372
- });
@@ -5,21 +5,18 @@
5
5
  * shared ROUTES array. They are read/abort surfaces over the workflow run
6
6
  * manager and the saved-workflow library — a human (via the `vellum workflows`
7
7
  * CLI or the app) can inspect runs and abort an in-flight one.
8
- *
9
- * Every handler is gated on the `workflows` feature flag: when it is off, the
10
- * routes behave as if they do not exist (404), so disabling the flag fully
11
- * hides the surface.
12
8
  */
13
9
 
14
10
  import { z } from "zod";
15
11
 
16
- import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
17
- import { getConfig } from "../../config/loader.js";
18
- import type { AssistantConfig } from "../../config/schema.js";
19
12
  import { getAutoApproveThreshold } from "../../permissions/gateway-threshold-reader.js";
20
13
  import { isFullAccessThreshold } from "../../permissions/threshold.js";
21
14
  import { manifestGrantsSideEffects } from "../../workflows/capabilities.js";
22
- import type { WorkflowRun } from "../../workflows/journal-store.js";
15
+ import {
16
+ getJournal,
17
+ type WorkflowJournalEntry,
18
+ type WorkflowRun,
19
+ } from "../../workflows/journal-store.js";
23
20
  import { listWorkflows } from "../../workflows/library.js";
24
21
  import {
25
22
  getWorkflowRunManager,
@@ -46,19 +43,16 @@ export interface WorkflowRoutesDeps {
46
43
  "list" | "status" | "abort" | "resume"
47
44
  >;
48
45
  listWorkflows: typeof listWorkflows;
49
- getConfig: () => AssistantConfig;
50
- isFlagEnabled: (config: AssistantConfig) => boolean;
51
46
  getAutoApproveThreshold: typeof getAutoApproveThreshold;
47
+ getJournal: typeof getJournal;
52
48
  }
53
49
 
54
50
  function defaultDeps(): WorkflowRoutesDeps {
55
51
  return {
56
52
  getManager: getWorkflowRunManager,
57
53
  listWorkflows,
58
- getConfig,
59
- isFlagEnabled: (config) =>
60
- isAssistantFeatureFlagEnabled("workflows", config),
61
54
  getAutoApproveThreshold,
55
+ getJournal,
62
56
  };
63
57
  }
64
58
 
@@ -105,21 +99,33 @@ const savedWorkflowSchema = z.object({
105
99
  path: z.string(),
106
100
  });
107
101
 
102
+ const workflowLeafSchema = z.object({
103
+ seq: z.number(),
104
+ kind: z.enum(["agent", "workflow"]),
105
+ label: z.string().optional(),
106
+ phase: z.string().optional(),
107
+ promptSummary: z.string().optional(),
108
+ status: z.string(),
109
+ resultSummary: z.string().optional(),
110
+ inputTokens: z.number().optional(),
111
+ outputTokens: z.number().optional(),
112
+ createdAt: z.number().nullable(),
113
+ });
114
+
115
+ const workflowJournalSchema = z.object({
116
+ runId: z.string(),
117
+ status: z.enum(VALID_RUN_STATUSES).optional(),
118
+ agentsSpawned: z.number().optional(),
119
+ inputTokens: z.number().optional(),
120
+ outputTokens: z.number().optional(),
121
+ phase: z.string().optional(),
122
+ leaves: z.array(workflowLeafSchema),
123
+ });
124
+
108
125
  // ---------------------------------------------------------------------------
109
126
  // Handlers (transport-agnostic)
110
127
  // ---------------------------------------------------------------------------
111
128
 
112
- /**
113
- * Throw a 404 when the `workflows` flag is off, hiding the whole surface. The
114
- * NotFoundError (rather than a 403) keeps a disabled-flag indistinguishable
115
- * from a route that does not exist.
116
- */
117
- function assertFlagEnabled(): void {
118
- if (!deps.isFlagEnabled(deps.getConfig())) {
119
- throw new NotFoundError("Workflows are not enabled.");
120
- }
121
- }
122
-
123
129
  /**
124
130
  * The single compact-run wire contract, inferred from {@link workflowRunSchema}.
125
131
  * The HTTP routes return this shape; other producers (e.g. the CLI's client-side
@@ -145,6 +151,64 @@ export function toWireRun(run: WorkflowRun): WorkflowRunWire {
145
151
  };
146
152
  }
147
153
 
154
+ /**
155
+ * Render an arbitrary journal value (prompt, result, `{ error }`) into a short
156
+ * single-string preview, truncating to `max` characters. Objects are
157
+ * JSON-stringified so a structured result still produces a readable summary.
158
+ */
159
+ function summarize(value: unknown, max = 200): string | undefined {
160
+ if (value === undefined || value === null) return undefined;
161
+ let text: string;
162
+ if (typeof value === "string") {
163
+ text = value;
164
+ } else {
165
+ try {
166
+ text = JSON.stringify(value);
167
+ } catch {
168
+ return undefined;
169
+ }
170
+ }
171
+ return text.length > max ? `${text.slice(0, max)}…` : text;
172
+ }
173
+
174
+ /**
175
+ * Project a journal entry into the bounded leaf wire shape, dropping the bulky
176
+ * raw `request`/`result` payloads in favor of short summaries. The `request`
177
+ * and `result` columns are `unknown`, so read their fields defensively.
178
+ */
179
+ export function toWireLeaf(
180
+ entry: WorkflowJournalEntry,
181
+ ): z.infer<typeof workflowLeafSchema> {
182
+ const request =
183
+ entry.request && typeof entry.request === "object"
184
+ ? (entry.request as Record<string, unknown>)
185
+ : undefined;
186
+ const opts =
187
+ request?.opts && typeof request.opts === "object"
188
+ ? (request.opts as Record<string, unknown>)
189
+ : undefined;
190
+ const label = typeof opts?.label === "string" ? opts.label : undefined;
191
+ const phase = typeof opts?.phase === "string" ? opts.phase : undefined;
192
+ const promptSummary = summarize(request?.prompt);
193
+ const resultSummary = summarize(entry.result);
194
+ return {
195
+ seq: entry.seq,
196
+ kind: entry.kind,
197
+ ...(label !== undefined ? { label } : {}),
198
+ ...(phase !== undefined ? { phase } : {}),
199
+ ...(promptSummary !== undefined ? { promptSummary } : {}),
200
+ status: entry.status,
201
+ ...(resultSummary !== undefined ? { resultSummary } : {}),
202
+ ...(entry.inputTokens !== undefined
203
+ ? { inputTokens: entry.inputTokens }
204
+ : {}),
205
+ ...(entry.outputTokens !== undefined
206
+ ? { outputTokens: entry.outputTokens }
207
+ : {}),
208
+ createdAt: entry.createdAt,
209
+ };
210
+ }
211
+
148
212
  function parseLimit(raw: string | undefined): number | undefined {
149
213
  if (raw === undefined) return undefined;
150
214
  const n = Number(raw);
@@ -162,7 +226,6 @@ function parseStatus(
162
226
  }
163
227
 
164
228
  function handleListRuns(queryParams: Record<string, string>) {
165
- assertFlagEnabled();
166
229
  const limit = parseLimit(queryParams.limit);
167
230
  const status = parseStatus(queryParams.status);
168
231
  const runs = deps.getManager().list({
@@ -173,7 +236,6 @@ function handleListRuns(queryParams: Record<string, string>) {
173
236
  }
174
237
 
175
238
  function handleGetRun(id: string) {
176
- assertFlagEnabled();
177
239
  const run = deps.getManager().status(id);
178
240
  if (!run) {
179
241
  throw new NotFoundError(`Workflow run ${id} not found`);
@@ -181,8 +243,29 @@ function handleGetRun(id: string) {
181
243
  return toWireRun(run);
182
244
  }
183
245
 
246
+ function handleGetRunJournal(id: string) {
247
+ const run = deps.getManager().status(id);
248
+ if (!run) {
249
+ throw new NotFoundError(`Workflow run ${id} not found`);
250
+ }
251
+ return {
252
+ runId: run.id,
253
+ status: run.status,
254
+ agentsSpawned: run.agentsSpawned,
255
+ inputTokens: run.inputTokens,
256
+ outputTokens: run.outputTokens,
257
+ // Only agent leaves; the live `workflow_leaf_*` stream is emitted solely for
258
+ // agent leaves (nested workflow resolutions never reach it), so the
259
+ // backfilled set must match. `kind: "workflow"` entries carry no label and
260
+ // would render as phantom unlabeled nodes.
261
+ leaves: deps
262
+ .getJournal(id)
263
+ .filter((entry) => entry.kind === "agent")
264
+ .map(toWireLeaf),
265
+ };
266
+ }
267
+
184
268
  function handleAbortRun(id: string) {
185
- assertFlagEnabled();
186
269
  // status() is the source of truth for existence; abort() itself is a no-op
187
270
  // for unknown/finished runs, so 404 on an unknown id is surfaced here.
188
271
  const manager = deps.getManager();
@@ -194,7 +277,6 @@ function handleAbortRun(id: string) {
194
277
  }
195
278
 
196
279
  async function handleResumeRun(id: string) {
197
- assertFlagEnabled();
198
280
  const manager = deps.getManager();
199
281
  // status() is the source of truth for existence, so 404 an unknown id here
200
282
  // (matching abort) rather than letting resume's "not_found" reach the client
@@ -245,7 +327,6 @@ async function handleResumeRun(id: string) {
245
327
  }
246
328
 
247
329
  function handleListSavedWorkflows() {
248
- assertFlagEnabled();
249
330
  return { workflows: deps.listWorkflows() };
250
331
  }
251
332
 
@@ -299,6 +380,27 @@ export const ROUTES: RouteDefinition[] = [
299
380
  additionalResponses: { "404": { description: "Run not found" } },
300
381
  handler: ({ pathParams }: RouteHandlerArgs) => handleGetRun(pathParams!.id),
301
382
  },
383
+ {
384
+ operationId: "getWorkflowRunJournal",
385
+ endpoint: "workflows/runs/:id/journal",
386
+ method: "GET",
387
+ // Unlike the run list/status routes (settings metadata), the journal returns
388
+ // per-leaf prompt/result summaries — conversation/work-product content — so it
389
+ // requires `chat.read`, matching the subagent-detail route (the analogous
390
+ // content surface), not the `settings.read` used by the management routes.
391
+ policy: {
392
+ requiredScopes: ["chat.read"],
393
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
394
+ },
395
+ summary: "Get workflow run journal",
396
+ description:
397
+ "Return a workflow run's leaf journal as bounded per-leaf summaries (one entry per finished leaf).",
398
+ tags: ["workflows"],
399
+ responseBody: workflowJournalSchema,
400
+ additionalResponses: { "404": { description: "Run not found" } },
401
+ handler: ({ pathParams }: RouteHandlerArgs) =>
402
+ handleGetRunJournal(pathParams!.id),
403
+ },
302
404
  {
303
405
  operationId: "abortWorkflowRun",
304
406
  endpoint: "workflows/runs/:id/abort",
@@ -0,0 +1,55 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+
3
+ import { getWorkspacePromptPath } from "../../util/platform.js";
4
+
5
+ function readWorkspaceFile(name: string): string {
6
+ try {
7
+ const path = getWorkspacePromptPath(name);
8
+ if (!existsSync(path)) return "";
9
+ return readFileSync(path, "utf-8");
10
+ } catch {
11
+ return "";
12
+ }
13
+ }
14
+
15
+ /**
16
+ * Parse the `## Greetings` section from SOUL.md. Returns bullet items as an
17
+ * array of strings, or `null` if the section is missing or empty.
18
+ */
19
+ export function parseGreetingsSection(content: string): string[] | null {
20
+ let inSection = false;
21
+ let sectionLevel: number | null = null;
22
+ const greetings: string[] = [];
23
+
24
+ for (const line of content.split("\n")) {
25
+ const trimmed = line.trim();
26
+ const heading = /^(#{1,6})\s+(.+?)\s*#*$/.exec(trimmed);
27
+ if (heading) {
28
+ const level = heading[1]!.length;
29
+ const title = heading[2]!.trim();
30
+ if (inSection) {
31
+ if (sectionLevel !== null && level <= sectionLevel) break;
32
+ continue;
33
+ }
34
+ if (level === 2 && /^greetings$/i.test(title)) {
35
+ inSection = true;
36
+ sectionLevel = level;
37
+ }
38
+ continue;
39
+ }
40
+ if (!inSection) continue;
41
+ const bullet = /^(?:[-*+]\s+|\d+[.)]\s+)(.+)$/.exec(trimmed);
42
+ const greeting = bullet?.[1]?.trim();
43
+ if (greeting) {
44
+ greetings.push(greeting);
45
+ }
46
+ }
47
+
48
+ return greetings.length > 0 ? greetings : null;
49
+ }
50
+
51
+ export function readWorkspaceGreetings(): string[] | null {
52
+ const soulContent = readWorkspaceFile("SOUL.md");
53
+ if (!soulContent) return null;
54
+ return parseGreetingsSection(soulContent);
55
+ }