@vellumai/assistant 0.9.1-staging.1 → 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 (433) hide show
  1. package/docs/activation-funnel-telemetry.md +24 -18
  2. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  3. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  4. package/node_modules/@vellumai/gateway-client/src/index.ts +15 -0
  5. package/openapi.yaml +852 -15
  6. package/package.json +1 -1
  7. package/src/__tests__/access-request-card-view.test.ts +98 -0
  8. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  9. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +59 -7
  10. package/src/__tests__/agent-loop-compaction-strip.test.ts +17 -16
  11. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  12. package/src/__tests__/app-compiler.test.ts +15 -1
  13. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  14. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  15. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  16. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  18. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  19. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  20. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +174 -30
  22. package/src/__tests__/config-schema.test.ts +35 -0
  23. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  24. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  25. package/src/__tests__/contacts-tools.test.ts +29 -0
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  27. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  28. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  29. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  30. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  31. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  32. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  33. package/src/__tests__/conversation-title-service.test.ts +62 -0
  34. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  35. package/src/__tests__/credential-prompt-route.test.ts +1 -0
  36. package/src/__tests__/credential-security-invariants.test.ts +2 -0
  37. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  38. package/src/__tests__/disk-usage.test.ts +65 -0
  39. package/src/__tests__/dynamic-page-surface.test.ts +51 -0
  40. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  41. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  42. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  43. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  44. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  45. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  46. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  47. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  48. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  49. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  50. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  51. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  52. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  53. package/src/__tests__/invite-redemption-service.test.ts +0 -3
  54. package/src/__tests__/llm-catalog-parity.test.ts +30 -1
  55. package/src/__tests__/llm-resolver.test.ts +21 -0
  56. package/src/__tests__/llm-schema.test.ts +1 -0
  57. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  58. package/src/__tests__/mcp-health-check.test.ts +6 -7
  59. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  60. package/src/__tests__/path-policy.test.ts +34 -0
  61. package/src/__tests__/persona-resolver.test.ts +38 -0
  62. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  63. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  64. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  65. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  66. package/src/__tests__/reaction-persistence.test.ts +150 -29
  67. package/src/__tests__/relay-server.test.ts +285 -0
  68. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  69. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  70. package/src/__tests__/skill-execute-input.test.ts +5 -0
  71. package/src/__tests__/skills.test.ts +51 -0
  72. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  73. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  74. package/src/__tests__/subagent-tools.test.ts +150 -0
  75. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  76. package/src/__tests__/title-generate-hook.test.ts +100 -3
  77. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  78. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  79. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  80. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  81. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  82. package/src/__tests__/trusted-contact-verification.test.ts +2 -4
  83. package/src/__tests__/twilio-routes.test.ts +81 -1
  84. package/src/__tests__/voice-invite-redemption.test.ts +0 -1
  85. package/src/__tests__/weak-open-model.test.ts +30 -0
  86. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  87. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  88. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  89. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  90. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  91. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  92. package/src/agent/loop.ts +33 -33
  93. package/src/api/events/tool-result.ts +6 -0
  94. package/src/api/events/workflow-completed.ts +53 -0
  95. package/src/api/events/workflow-leaf-finished.ts +38 -0
  96. package/src/api/events/workflow-leaf-started.ts +35 -0
  97. package/src/api/events/workflow-progress.ts +32 -0
  98. package/src/api/events/workflow-started.ts +31 -0
  99. package/src/api/index.ts +40 -0
  100. package/src/api/responses/conversation-message.ts +26 -0
  101. package/src/api/responses/home.ts +26 -0
  102. package/src/api/responses/workflow-journal.ts +53 -0
  103. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  104. package/src/approvals/guardian-decision-primitive.ts +26 -3
  105. package/src/approvals/guardian-request-resolvers.ts +181 -78
  106. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  107. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  108. package/src/calls/call-pointer-messages.ts +10 -4
  109. package/src/calls/channel-admission-reader.ts +104 -0
  110. package/src/calls/guardian-dispatch.ts +17 -45
  111. package/src/calls/media-stream-server.ts +84 -2
  112. package/src/calls/relay-server.ts +66 -0
  113. package/src/calls/relay-setup-router.ts +82 -1
  114. package/src/calls/twilio-routes.ts +17 -8
  115. package/src/cli/commands/clients.ts +3 -0
  116. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  117. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  118. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  119. package/src/cli/commands/memory/index.ts +30 -0
  120. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  121. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  122. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  123. package/src/cli/commands/oauth/status.test.ts +36 -0
  124. package/src/cli/commands/oauth/status.ts +23 -3
  125. package/src/cli/commands/plugins.ts +57 -5
  126. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  127. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +134 -4
  128. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  129. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +53 -11
  130. package/src/cli/lib/inspect-plugin.ts +12 -1
  131. package/src/cli/lib/merge-plugin-tree.ts +149 -49
  132. package/src/cli/lib/plugin-surfaces.ts +104 -0
  133. package/src/cli/lib/upgrade-plugin.ts +64 -36
  134. package/src/cli/program.ts +2 -4
  135. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  136. package/src/config/assistant-feature-flags.ts +22 -7
  137. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  138. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  139. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  140. package/src/config/bundled-skills/workflows/SKILL.md +14 -7
  141. package/src/config/call-site-defaults.ts +3 -0
  142. package/src/config/feature-flag-registry.json +49 -18
  143. package/src/config/llm-resolver.ts +3 -0
  144. package/src/config/memory-v3-gate.ts +11 -0
  145. package/src/config/schema.ts +8 -6
  146. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  147. package/src/config/schemas/call-site-catalog.ts +7 -0
  148. package/src/config/schemas/channels.ts +11 -0
  149. package/src/config/schemas/llm.ts +31 -0
  150. package/src/config/schemas/memory-lifecycle.ts +3 -7
  151. package/src/config/schemas/memory-v3.ts +6 -0
  152. package/src/config/schemas/services.ts +18 -0
  153. package/src/config/seed-inference-profiles.ts +94 -34
  154. package/src/config/skills.ts +21 -0
  155. package/src/config/sync-gated-profiles.ts +220 -0
  156. package/src/contacts/contact-store.ts +2 -10
  157. package/src/contacts/contacts-write.ts +1 -2
  158. package/src/contacts/types.ts +0 -1
  159. package/src/context/compactor.ts +86 -52
  160. package/src/context/strip-injections.ts +58 -10
  161. package/src/context/token-estimator.ts +1 -1
  162. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  163. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  164. package/src/daemon/conversation-agent-loop.ts +100 -19
  165. package/src/daemon/conversation-history.ts +1 -1
  166. package/src/daemon/conversation-lifecycle.ts +3 -5
  167. package/src/daemon/conversation-process.ts +13 -5
  168. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  169. package/src/daemon/conversation-surfaces.ts +26 -0
  170. package/src/daemon/conversation-tool-setup.ts +16 -11
  171. package/src/daemon/conversation.ts +64 -14
  172. package/src/daemon/disk-pressure-policy.ts +5 -3
  173. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  174. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  175. package/src/daemon/handlers/config-a2a.ts +0 -2
  176. package/src/daemon/handlers/config-channels.ts +5 -10
  177. package/src/daemon/handlers/config-slack-channel.ts +20 -0
  178. package/src/daemon/handlers/conversations.ts +107 -0
  179. package/src/daemon/host-browser-proxy.ts +41 -0
  180. package/src/daemon/lifecycle.ts +55 -20
  181. package/src/daemon/message-provenance.ts +2 -0
  182. package/src/daemon/message-types/contacts.ts +0 -1
  183. package/src/daemon/message-types/web-activity.ts +7 -1
  184. package/src/daemon/message-types/workflows.ts +83 -1
  185. package/src/daemon/tool-setup-types.ts +4 -0
  186. package/src/daemon/trust-context.ts +1 -1
  187. package/src/events/tool-audit-listener.ts +2 -2
  188. package/src/home/feed-source-enrichment.test.ts +151 -0
  189. package/src/home/feed-source-enrichment.ts +176 -0
  190. package/src/instrument.ts +18 -6
  191. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  192. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  193. package/src/ipc/assistant-server.ts +37 -4
  194. package/src/ipc/gateway-flag-listener.ts +18 -2
  195. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  196. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  197. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  198. package/src/memory/__tests__/memory-retrospective-job.test.ts +34 -0
  199. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  200. package/src/memory/auth-fallback-events-store.ts +2 -2
  201. package/src/memory/auto-analysis-enqueue.ts +3 -5
  202. package/src/memory/canonical-guardian-store.ts +39 -1
  203. package/src/memory/conversation-crud.ts +9 -4
  204. package/src/memory/conversation-key-store.ts +17 -2
  205. package/src/memory/conversation-title-service.ts +64 -7
  206. package/src/memory/db-init.ts +10 -0
  207. package/src/memory/embedding-backend.ts +15 -1
  208. package/src/memory/jobs-worker.ts +2 -1
  209. package/src/memory/lifecycle-events-store.ts +2 -2
  210. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  211. package/src/memory/memory-retrospective-job.ts +9 -0
  212. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  213. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  214. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  215. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +10 -0
  216. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  217. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  218. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  219. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  220. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  221. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  222. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  223. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  224. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  225. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +30 -0
  226. package/src/memory/migrations/index.ts +5 -0
  227. package/src/memory/onboarding-events-store.ts +3 -3
  228. package/src/memory/schema/contacts.ts +0 -1
  229. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  230. package/src/memory/skill-loaded-events-store.ts +2 -2
  231. package/src/memory/tool-executed-events-store.test.ts +7 -7
  232. package/src/memory/turn-trace-store.test.ts +736 -0
  233. package/src/memory/turn-trace-store.ts +364 -0
  234. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  235. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  236. package/src/memory/v2/consolidation-job.ts +2 -2
  237. package/src/memory/v2/skill-content.ts +25 -7
  238. package/src/memory/v2/skill-store.ts +7 -1
  239. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  240. package/src/memory/v3-eval/eval-packets.ts +546 -0
  241. package/src/messaging/providers/slack/api.ts +31 -0
  242. package/src/messaging/providers/slack/send.test.ts +114 -2
  243. package/src/messaging/providers/slack/send.ts +30 -7
  244. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  245. package/src/messaging/providers/slack/withdraw.ts +161 -0
  246. package/src/notifications/AGENTS.md +2 -0
  247. package/src/notifications/access-request-copy.ts +72 -59
  248. package/src/notifications/adapters/slack.ts +55 -73
  249. package/src/notifications/approval-card-data.ts +333 -0
  250. package/src/notifications/broadcaster.ts +6 -2
  251. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  252. package/src/notifications/copy-composer.ts +3 -3
  253. package/src/notifications/decision-engine.ts +4 -2
  254. package/src/notifications/destination-resolver.ts +4 -6
  255. package/src/notifications/guardian-question-mode.ts +10 -0
  256. package/src/notifications/home-feed-side-effect.ts +3 -13
  257. package/src/notifications/notification-utils.ts +2 -1
  258. package/src/notifications/signal.ts +79 -43
  259. package/src/notifications/types.ts +98 -128
  260. package/src/permissions/checker.test.ts +51 -0
  261. package/src/permissions/checker.ts +185 -26
  262. package/src/permissions/ipc-risk-types.ts +24 -0
  263. package/src/permissions/question-prompter.test.ts +27 -0
  264. package/src/permissions/question-prompter.ts +4 -0
  265. package/src/platform/client.test.ts +119 -0
  266. package/src/platform/client.ts +66 -0
  267. package/src/platform/consent-cache.test.ts +267 -0
  268. package/src/platform/consent-cache.ts +174 -0
  269. package/src/plugin-api/index.ts +27 -0
  270. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  271. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  272. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  273. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  274. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  275. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  276. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  277. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  278. package/src/plugins/defaults/advisor/config.ts +21 -0
  279. package/src/plugins/defaults/advisor/consult.ts +93 -0
  280. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  281. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  282. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  283. package/src/plugins/defaults/advisor/package.json +14 -0
  284. package/src/plugins/defaults/advisor/steering.ts +67 -0
  285. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  286. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  287. package/src/plugins/defaults/index.ts +35 -0
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  290. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  291. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  293. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +75 -7
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  297. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  298. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  299. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +37 -4
  300. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  301. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  302. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  303. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -12
  304. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  305. package/src/prompts/persona-resolver.ts +12 -2
  306. package/src/prompts/templates/system-sections.ts +7 -2
  307. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  308. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  309. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  310. package/src/providers/atlascloud/client.ts +85 -0
  311. package/src/providers/fetch-provider-catalog.ts +85 -0
  312. package/src/providers/inference/adapter-factory.ts +3 -0
  313. package/src/providers/model-catalog.ts +58 -0
  314. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  315. package/src/providers/openai/chat-completions-provider.ts +7 -0
  316. package/src/providers/openai/responses-provider.ts +10 -0
  317. package/src/providers/provider-send-message.ts +11 -3
  318. package/src/providers/retry.ts +53 -12
  319. package/src/providers/search-provider-catalog.ts +10 -0
  320. package/src/providers/weak-open-model.ts +22 -0
  321. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  322. package/src/runtime/__tests__/client-health.test.ts +44 -0
  323. package/src/runtime/access-request-helper.ts +21 -53
  324. package/src/runtime/actor-trust-resolver.ts +49 -21
  325. package/src/runtime/agent-wake.ts +52 -0
  326. package/src/runtime/assistant-event-hub.ts +18 -4
  327. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  328. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  329. package/src/runtime/capabilities.test.ts +120 -0
  330. package/src/runtime/capabilities.ts +197 -0
  331. package/src/runtime/channel-approval-types.ts +5 -1
  332. package/src/runtime/channel-retry-sweep.ts +1 -0
  333. package/src/runtime/channel-verification-service.ts +1 -2
  334. package/src/runtime/client-health.ts +26 -0
  335. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  336. package/src/runtime/effective-capabilities.test.ts +128 -0
  337. package/src/runtime/effective-capabilities.ts +84 -0
  338. package/src/runtime/guardian-reply-router.ts +106 -21
  339. package/src/runtime/invite-redemption-service.ts +6 -22
  340. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  341. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  342. package/src/runtime/pending-interactions.ts +15 -0
  343. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  344. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  345. package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -13
  346. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  347. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  348. package/src/runtime/routes/client-routes.ts +10 -0
  349. package/src/runtime/routes/contact-routes.ts +31 -8
  350. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  351. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  352. package/src/runtime/routes/conversation-routes.ts +37 -12
  353. package/src/runtime/routes/events-routes.ts +1 -3
  354. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  355. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  356. package/src/runtime/routes/home-feed-routes.ts +8 -3
  357. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  358. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +88 -6
  359. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  360. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  361. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  362. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  363. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  364. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  365. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  366. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  367. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  368. package/src/runtime/routes/index.ts +2 -0
  369. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  370. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  371. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  372. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  373. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  374. package/src/runtime/routes/notification-routes.ts +122 -133
  375. package/src/runtime/routes/platform-routes.ts +2 -2
  376. package/src/runtime/routes/plugins-routes.ts +40 -7
  377. package/src/runtime/routes/secret-routes.ts +10 -0
  378. package/src/runtime/routes/surface-action-routes.ts +2 -1
  379. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  380. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  381. package/src/runtime/routes/workflow-routes.test.ts +225 -1
  382. package/src/runtime/routes/workflow-routes.ts +131 -1
  383. package/src/runtime/tool-grant-request-helper.ts +18 -16
  384. package/src/runtime/trust-context-resolver.ts +8 -5
  385. package/src/schedule/schedule-store.ts +1 -1
  386. package/src/schedule/scheduler-types.ts +5 -1
  387. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  388. package/src/security/secret-patterns.ts +3 -0
  389. package/src/subagent/manager.ts +11 -4
  390. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  391. package/src/telemetry/trace-collection-policy.ts +30 -0
  392. package/src/telemetry/types.ts +89 -0
  393. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  394. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  395. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  396. package/src/tools/browser/browser-execution.ts +29 -18
  397. package/src/tools/document/document-tool.ts +2 -3
  398. package/src/tools/executor.ts +5 -3
  399. package/src/tools/host-terminal/host-shell.ts +5 -4
  400. package/src/tools/memory/register.ts +2 -2
  401. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  402. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  403. package/src/tools/network/web-fetch.ts +372 -1
  404. package/src/tools/network/web-search.ts +213 -10
  405. package/src/tools/permission-checker.ts +3 -2
  406. package/src/tools/registry.ts +20 -0
  407. package/src/tools/schedule/create.ts +4 -3
  408. package/src/tools/schedule/update.ts +2 -1
  409. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  410. package/src/tools/skills/execute.ts +1 -2
  411. package/src/tools/subagent/spawn.ts +37 -13
  412. package/src/tools/terminal/shell.ts +10 -4
  413. package/src/tools/tool-approval-handler.ts +17 -10
  414. package/src/tools/types.ts +9 -0
  415. package/src/tools/ui-surface/definitions.ts +25 -2
  416. package/src/tools/verification-control-plane-policy.ts +3 -1
  417. package/src/tools/workflows/run-workflow.ts +1 -0
  418. package/src/util/disk-usage.ts +78 -23
  419. package/src/util/platform.ts +8 -1
  420. package/src/watcher/telemetry.ts +2 -2
  421. package/src/workflows/engine.test.ts +175 -1
  422. package/src/workflows/engine.ts +82 -0
  423. package/src/workflows/journal-store.test.ts +70 -0
  424. package/src/workflows/journal-store.ts +18 -3
  425. package/src/workflows/run-manager.test.ts +171 -3
  426. package/src/workflows/run-manager.ts +64 -0
  427. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  428. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  429. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  430. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  431. package/src/workspace/migrations/registry.ts +8 -0
  432. package/src/notifications/tool-approval-copy.ts +0 -142
  433. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Render-time enrichment of history tool calls with in-flight question context.
3
+ *
4
+ * `pendingQuestion` — the outstanding `ask_question` prompt for a tool call
5
+ * still awaiting a user answer — is read from the in-memory
6
+ * `pending-interactions` registry (the authoritative store of unresolved
7
+ * prompts) and stamped onto its tool call so the web/API clients can restore
8
+ * the same question card on a cold reconnect (or a history reopen after the
9
+ * live event buffer has aged out) that the live `question_request` SSE stream
10
+ * would have produced. It appears only while the prompt is genuinely
11
+ * outstanding. Mirrors the confirmation enrichment in
12
+ * `tool-call-confirmation-enrichment.ts`.
13
+ */
14
+
15
+ import type { QuestionEntry } from "../../api/events/question-request.js";
16
+ import type { ConversationMessageToolCall } from "../../api/responses/conversation-message.js";
17
+ import { getByConversation } from "../pending-interactions.js";
18
+
19
+ /** A pending question matched to the tool call it prompts for, keyed by `toolUseId`. */
20
+ interface PendingQuestionMatch {
21
+ requestId: string;
22
+ entries: QuestionEntry[];
23
+ }
24
+
25
+ /**
26
+ * Build the `toolUseId → pending question` lookup for a conversation from the
27
+ * registry. Only question interactions that carry both a `toolUseId` and
28
+ * `questionDetails` can be stamped onto a wire tool call.
29
+ */
30
+ export function collectPendingQuestions(
31
+ conversationId: string,
32
+ ): Map<string, PendingQuestionMatch> {
33
+ const byToolUseId = new Map<string, PendingQuestionMatch>();
34
+ for (const interaction of getByConversation(conversationId)) {
35
+ if (
36
+ interaction.kind === "question" &&
37
+ interaction.questionDetails &&
38
+ interaction.toolUseId
39
+ ) {
40
+ byToolUseId.set(interaction.toolUseId, {
41
+ requestId: interaction.requestId,
42
+ entries: interaction.questionDetails.entries,
43
+ });
44
+ }
45
+ }
46
+ return byToolUseId;
47
+ }
48
+
49
+ /**
50
+ * Layer any outstanding `pendingQuestion` onto a message's rendered tool calls.
51
+ * Returns a new array; tool calls without a match are returned unchanged.
52
+ */
53
+ export function enrichToolCallsWithQuestion(
54
+ toolCalls: ConversationMessageToolCall[],
55
+ opts: { pendingQuestions: ReadonlyMap<string, PendingQuestionMatch> },
56
+ ): ConversationMessageToolCall[] {
57
+ if (opts.pendingQuestions.size === 0) return toolCalls;
58
+ return toolCalls.map((tc) => {
59
+ const match = tc.id ? opts.pendingQuestions.get(tc.id) : undefined;
60
+ if (!match) return tc;
61
+ return {
62
+ ...tc,
63
+ pendingQuestion: { requestId: match.requestId, entries: match.entries },
64
+ };
65
+ });
66
+ }
@@ -1,6 +1,9 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
2
 
3
- import type { WorkflowRun } from "../../workflows/journal-store.js";
3
+ import type {
4
+ WorkflowJournalEntry,
5
+ WorkflowRun,
6
+ } from "../../workflows/journal-store.js";
4
7
  import type { SavedWorkflowEntry } from "../../workflows/library.js";
5
8
  import {
6
9
  WorkflowResumeNotPossibleError,
@@ -58,6 +61,22 @@ interface FakeManager {
58
61
  resume: (id: string) => { runId: string };
59
62
  }
60
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
+
61
80
  function setup(opts: {
62
81
  runs?: WorkflowRun[];
63
82
  saved?: SavedWorkflowEntry[];
@@ -65,6 +84,8 @@ function setup(opts: {
65
84
  resume?: (id: string) => { runId: string };
66
85
  /** Resolved auto-approve threshold for the resume posture gate. */
67
86
  threshold?: "none" | "low" | "medium" | "high";
87
+ /** Journal entries returned by getJournal, keyed by run id. */
88
+ journal?: Record<string, WorkflowJournalEntry[]>;
68
89
  }): {
69
90
  aborted: string[];
70
91
  resumed: string[];
@@ -97,6 +118,7 @@ function setup(opts: {
97
118
  getManager: () => manager,
98
119
  listWorkflows: () => opts.saved ?? [],
99
120
  getAutoApproveThreshold: async () => opts.threshold ?? "none",
121
+ getJournal: (runId) => opts.journal?.[runId] ?? [],
100
122
  });
101
123
  return { aborted, resumed, listCalls };
102
124
  }
@@ -303,6 +325,208 @@ describe("workflow routes (happy paths)", () => {
303
325
  });
304
326
  });
305
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
+
306
530
  // ---------------------------------------------------------------------------
307
531
  // Unknown run → 404
308
532
  // ---------------------------------------------------------------------------
@@ -12,7 +12,11 @@ import { z } from "zod";
12
12
  import { getAutoApproveThreshold } from "../../permissions/gateway-threshold-reader.js";
13
13
  import { isFullAccessThreshold } from "../../permissions/threshold.js";
14
14
  import { manifestGrantsSideEffects } from "../../workflows/capabilities.js";
15
- import type { WorkflowRun } from "../../workflows/journal-store.js";
15
+ import {
16
+ getJournal,
17
+ type WorkflowJournalEntry,
18
+ type WorkflowRun,
19
+ } from "../../workflows/journal-store.js";
16
20
  import { listWorkflows } from "../../workflows/library.js";
17
21
  import {
18
22
  getWorkflowRunManager,
@@ -40,6 +44,7 @@ export interface WorkflowRoutesDeps {
40
44
  >;
41
45
  listWorkflows: typeof listWorkflows;
42
46
  getAutoApproveThreshold: typeof getAutoApproveThreshold;
47
+ getJournal: typeof getJournal;
43
48
  }
44
49
 
45
50
  function defaultDeps(): WorkflowRoutesDeps {
@@ -47,6 +52,7 @@ function defaultDeps(): WorkflowRoutesDeps {
47
52
  getManager: getWorkflowRunManager,
48
53
  listWorkflows,
49
54
  getAutoApproveThreshold,
55
+ getJournal,
50
56
  };
51
57
  }
52
58
 
@@ -93,6 +99,29 @@ const savedWorkflowSchema = z.object({
93
99
  path: z.string(),
94
100
  });
95
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
+
96
125
  // ---------------------------------------------------------------------------
97
126
  // Handlers (transport-agnostic)
98
127
  // ---------------------------------------------------------------------------
@@ -122,6 +151,64 @@ export function toWireRun(run: WorkflowRun): WorkflowRunWire {
122
151
  };
123
152
  }
124
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
+
125
212
  function parseLimit(raw: string | undefined): number | undefined {
126
213
  if (raw === undefined) return undefined;
127
214
  const n = Number(raw);
@@ -156,6 +243,28 @@ function handleGetRun(id: string) {
156
243
  return toWireRun(run);
157
244
  }
158
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
+
159
268
  function handleAbortRun(id: string) {
160
269
  // status() is the source of truth for existence; abort() itself is a no-op
161
270
  // for unknown/finished runs, so 404 on an unknown id is surfaced here.
@@ -271,6 +380,27 @@ export const ROUTES: RouteDefinition[] = [
271
380
  additionalResponses: { "404": { description: "Run not found" } },
272
381
  handler: ({ pathParams }: RouteHandlerArgs) => handleGetRun(pathParams!.id),
273
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
+ },
274
404
  {
275
405
  operationId: "abortWorkflowRun",
276
406
  endpoint: "workflows/runs/:id/abort",
@@ -13,12 +13,14 @@
13
13
 
14
14
  import type { ChannelId } from "../channels/types.js";
15
15
  import {
16
- createCanonicalGuardianDelivery,
17
16
  createCanonicalGuardianRequest,
18
17
  listCanonicalGuardianRequests,
19
18
  } from "../memory/canonical-guardian-store.js";
19
+ import {
20
+ recordApprovalCardDelivery,
21
+ recordGuardianRequestDeliveries,
22
+ } from "../notifications/canonical-delivery-recorder.js";
20
23
  import { emitNotificationSignal } from "../notifications/emit-signal.js";
21
- import type { NotificationSourceChannel } from "../notifications/signal.js";
22
24
  import { getLogger } from "../util/logger.js";
23
25
  import { getGuardianBinding } from "./channel-verification-service.js";
24
26
  import { GUARDIAN_APPROVAL_TTL_MS } from "./routes/channel-route-shared.js";
@@ -140,12 +142,16 @@ export function createOrReuseToolGrantRequest(
140
142
  canonicalRequest.requestCode ??
141
143
  canonicalRequest.id.slice(0, 6).toUpperCase();
142
144
 
145
+ // The vellum delivery row is created up front in onConversationCreated so the
146
+ // in-app client sees it immediately; the post-resolve recorder reuses it.
147
+ let vellumDeliveryId: string | undefined;
148
+
143
149
  // Emit notification so guardian is alerted. Uses 'guardian.question' as
144
150
  // sourceEventName so that existing request-code guidance in the notification
145
151
  // pipeline is preserved.
146
152
  const signalPromise = emitNotificationSignal({
147
153
  sourceEventName: "guardian.question",
148
- sourceChannel: sourceChannel as NotificationSourceChannel,
154
+ sourceChannel,
149
155
  sourceContextId: conversationId,
150
156
  requiresConversation: true,
151
157
  attentionHints: {
@@ -167,25 +173,21 @@ export function createOrReuseToolGrantRequest(
167
173
  },
168
174
  dedupeKey: `tool-grant-request:${canonicalRequest.id}`,
169
175
  onConversationCreated: (info) => {
170
- createCanonicalGuardianDelivery({
176
+ vellumDeliveryId = recordApprovalCardDelivery({
171
177
  requestId: canonicalRequest.id,
172
- destinationChannel: "vellum",
173
- destinationConversationId: info.conversationId,
174
- });
178
+ channel: "vellum",
179
+ conversationId: info.conversationId,
180
+ })?.id;
175
181
  },
176
182
  });
177
183
 
178
184
  // Record deliveries from the notification pipeline results (fire-and-forget).
179
185
  void signalPromise.then((signalResult) => {
180
- for (const result of signalResult.deliveryResults) {
181
- if (result.channel === "vellum") continue; // handled in onConversationCreated
182
- createCanonicalGuardianDelivery({
183
- requestId: canonicalRequest.id,
184
- destinationChannel: result.channel,
185
- destinationChatId:
186
- result.destination.length > 0 ? result.destination : undefined,
187
- });
188
- }
186
+ recordGuardianRequestDeliveries({
187
+ requestId: canonicalRequest.id,
188
+ deliveryResults: signalResult.deliveryResults,
189
+ vellumDeliveryId,
190
+ });
189
191
  });
190
192
 
191
193
  log.info(
@@ -16,6 +16,7 @@ import {
16
16
  type ResolveActorTrustInput,
17
17
  toTrustContext,
18
18
  } from "./actor-trust-resolver.js";
19
+ import { resolveCapabilities } from "./capabilities.js";
19
20
 
20
21
  /**
21
22
  * Resolve route-level trust context from canonical identity state.
@@ -64,14 +65,15 @@ export interface RoutingState {
64
65
  * Compute the routing state for a channel actor turn.
65
66
  *
66
67
  * Guardian actors are always interactive (they self-approve).
67
- * Trusted contacts are only interactive when a guardian binding exists
68
- * to receive approval notifications. Unknown actors are never interactive.
68
+ * Trusted and unverified contacts are only interactive when a guardian
69
+ * binding exists to receive approval notifications. Unknown actors are
70
+ * never interactive.
69
71
  */
70
72
  export function resolveRoutingState(
71
73
  ctx: Pick<TrustContext, "trustClass" | "guardianExternalUserId">,
72
74
  ): RoutingState {
75
+ const caps = resolveCapabilities(ctx.trustClass);
73
76
  const isGuardian = ctx.trustClass === "guardian";
74
- const isTrustedContact = ctx.trustClass === "trusted_contact";
75
77
 
76
78
  // Guardians self-approve — they are always interactive and route-resolvable.
77
79
  if (isGuardian) {
@@ -82,12 +84,13 @@ export function resolveRoutingState(
82
84
  };
83
85
  }
84
86
 
85
- // Trusted contacts can be interactive only if a guardian destination
87
+ // Identity-known non-guardian contacts (trusted_contact /
88
+ // unverified_contact) can be interactive only if a guardian destination
86
89
  // exists. The guardian binding populates guardianExternalUserId during
87
90
  // trust resolution; its presence means there is a verified guardian
88
91
  // to route approval notifications to.
89
92
  const guardianRouteResolvable = !!ctx.guardianExternalUserId;
90
- if (isTrustedContact) {
93
+ if (caps.mayBeInteractive) {
91
94
  return {
92
95
  canBeInteractive: true,
93
96
  guardianRouteResolvable,
@@ -158,7 +158,7 @@ export function createSchedule(params: {
158
158
  const routingIntent = params.routingIntent ?? "all_channels";
159
159
  const routingHints = params.routingHints ?? {};
160
160
  const quiet = params.quiet ?? false;
161
- const reuseConversation = params.reuseConversation ?? !isOneShot;
161
+ const reuseConversation = params.reuseConversation ?? false;
162
162
  const maxRetries = params.maxRetries ?? 3;
163
163
  const retryBackoffMs = params.retryBackoffMs ?? 60000;
164
164
  const timeoutMs = params.timeoutMs ?? null;
@@ -7,7 +7,11 @@
7
7
  import type { LLMCallSite } from "../config/schemas/llm.js";
8
8
 
9
9
  export interface ScheduleMessageOptions {
10
- trustClass?: "guardian" | "trusted_contact" | "unknown";
10
+ trustClass?:
11
+ | "guardian"
12
+ | "trusted_contact"
13
+ | "unverified_contact"
14
+ | "unknown";
11
15
  taskRunId?: string;
12
16
  /**
13
17
  * Optional LLM call-site identifier propagated to the per-call provider
@@ -53,6 +53,7 @@ describe("getProviderKeyAsync env-var fallback (regression #27126)", () => {
53
53
  "BRAVE_API_KEY",
54
54
  "PERPLEXITY_API_KEY",
55
55
  "TAVILY_API_KEY",
56
+ "FIRECRAWL_API_KEY",
56
57
  "ANTHROPIC_API_KEY",
57
58
  "OPENAI_API_KEY",
58
59
  ];
@@ -103,6 +104,11 @@ describe("getProviderKeyAsync env-var fallback (regression #27126)", () => {
103
104
  expect(await getProviderKeyAsync("tavily")).toBe("tavily-env-test");
104
105
  });
105
106
 
107
+ test("returns FIRECRAWL_API_KEY from process.env when secure store is empty", async () => {
108
+ process.env.FIRECRAWL_API_KEY = "fc-env-test";
109
+ expect(await getProviderKeyAsync("firecrawl")).toBe("fc-env-test");
110
+ });
111
+
106
112
  test("returns ANTHROPIC_API_KEY from process.env when secure store is empty (LLM regression)", async () => {
107
113
  process.env.ANTHROPIC_API_KEY = "anthropic-env-test";
108
114
  expect(await getProviderKeyAsync("anthropic")).toBe("anthropic-env-test");
@@ -133,4 +133,7 @@ export const PREFIX_PATTERNS: SecretPrefixPattern[] = [
133
133
 
134
134
  // -- Tavily --
135
135
  { label: "Tavily API Key", regex: /tvly-[A-Za-z0-9]{20,}/ },
136
+
137
+ // -- Firecrawl --
138
+ { label: "Firecrawl API Key", regex: /fc-[A-Za-z0-9]{20,}/ },
136
139
  ];