@vellumai/assistant 0.9.0 → 0.10.0-dev.202606192226.8ed8a6a

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 (624) hide show
  1. package/ARCHITECTURE.md +19 -35
  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 +1128 -119
  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__/agent-loop-pushes-post-hook-prompt.test.ts +306 -0
  28. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  29. package/src/__tests__/app-compiler.test.ts +15 -1
  30. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  31. package/src/__tests__/approval-cascade.test.ts +6 -0
  32. package/src/__tests__/approval-routes-http.test.ts +128 -0
  33. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  34. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  35. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  36. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  37. package/src/__tests__/btw-routes.test.ts +6 -8
  38. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  39. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  40. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  41. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  42. package/src/__tests__/checker.test.ts +0 -3
  43. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  44. package/src/__tests__/compaction-events.test.ts +6 -0
  45. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  46. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  47. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  48. package/src/__tests__/config-schema.test.ts +35 -0
  49. package/src/__tests__/config-watcher.test.ts +0 -18
  50. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  51. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  52. package/src/__tests__/contacts-tools.test.ts +29 -0
  53. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  54. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  55. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  56. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  57. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  58. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -0
  59. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  60. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  61. package/src/__tests__/conversation-process-callsite.test.ts +6 -0
  62. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  63. package/src/__tests__/conversation-queue.test.ts +6 -0
  64. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  65. package/src/__tests__/conversation-slash-queue.test.ts +6 -0
  66. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  67. package/src/__tests__/conversation-speed-override.test.ts +6 -0
  68. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  69. package/src/__tests__/conversation-title-service.test.ts +62 -0
  70. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -0
  71. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  72. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  73. package/src/__tests__/credential-broker.test.ts +449 -1
  74. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  75. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  76. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  77. package/src/__tests__/credential-routes.test.ts +360 -0
  78. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  79. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  80. package/src/__tests__/disk-pressure-tools.test.ts +41 -1
  81. package/src/__tests__/disk-usage.test.ts +65 -0
  82. package/src/__tests__/document-tool-security.test.ts +9 -6
  83. package/src/__tests__/document-update-default-surface.test.ts +202 -0
  84. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  85. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  86. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  87. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  88. package/src/__tests__/gemini-count-tokens.test.ts +70 -0
  89. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  90. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  91. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  92. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  93. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  94. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  95. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  96. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  97. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  98. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  99. package/src/__tests__/identity-routes.test.ts +0 -189
  100. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  101. package/src/__tests__/injector-disk-pressure.test.ts +10 -5
  102. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  103. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  104. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  105. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  106. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  107. package/src/__tests__/llm-resolver.test.ts +70 -24
  108. package/src/__tests__/llm-schema.test.ts +1 -0
  109. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  110. package/src/__tests__/mcp-health-check.test.ts +6 -7
  111. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  112. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  113. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  114. package/src/__tests__/path-policy.test.ts +34 -0
  115. package/src/__tests__/persona-resolver.test.ts +49 -14
  116. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  117. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  118. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  119. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  120. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  121. package/src/__tests__/reaction-persistence.test.ts +150 -29
  122. package/src/__tests__/registry.test.ts +2 -7
  123. package/src/__tests__/relay-server.test.ts +285 -0
  124. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  125. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  126. package/src/__tests__/schedule-routes.test.ts +0 -30
  127. package/src/__tests__/schedule-tools.test.ts +2 -18
  128. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  129. package/src/__tests__/secret-response-routing.test.ts +35 -0
  130. package/src/__tests__/secret-routes-platform-proxy.test.ts +16 -2
  131. package/src/__tests__/skill-execute-input.test.ts +177 -1
  132. package/src/__tests__/skill-load-tool.test.ts +72 -0
  133. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  134. package/src/__tests__/skills.test.ts +51 -0
  135. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  136. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  137. package/src/__tests__/subagent-tools.test.ts +266 -0
  138. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  139. package/src/__tests__/system-storage-cleanup-skill.test.ts +56 -0
  140. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  141. package/src/__tests__/title-generate-hook.test.ts +100 -3
  142. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  143. package/src/__tests__/token-manager.test.ts +519 -0
  144. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  145. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  146. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  147. package/src/__tests__/tool-executor.test.ts +0 -79
  148. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  149. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  150. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  151. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  152. package/src/__tests__/twilio-routes.test.ts +81 -1
  153. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  154. package/src/__tests__/weak-open-model.test.ts +30 -0
  155. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  156. package/src/__tests__/workspace-greetings.test.ts +152 -0
  157. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  158. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  159. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  160. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  161. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  162. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  163. package/src/agent/loop.ts +75 -29
  164. package/src/api/README.md +6 -6
  165. package/src/api/events/tool-result.ts +6 -0
  166. package/src/api/events/workflow-completed.ts +53 -0
  167. package/src/api/events/workflow-leaf-finished.ts +38 -0
  168. package/src/api/events/workflow-leaf-started.ts +35 -0
  169. package/src/api/events/workflow-progress.ts +32 -0
  170. package/src/api/events/workflow-started.ts +31 -0
  171. package/src/api/index.ts +40 -0
  172. package/src/api/responses/conversation-message.ts +28 -4
  173. package/src/api/responses/home.ts +26 -4
  174. package/src/api/responses/workflow-journal.ts +53 -0
  175. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  176. package/src/approvals/guardian-decision-primitive.ts +26 -3
  177. package/src/approvals/guardian-request-resolvers.ts +183 -80
  178. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  179. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  180. package/src/calls/call-pointer-messages.ts +10 -4
  181. package/src/calls/channel-admission-reader.ts +104 -0
  182. package/src/calls/guardian-dispatch.ts +17 -45
  183. package/src/calls/media-stream-server.ts +84 -2
  184. package/src/calls/relay-access-wait.ts +1 -1
  185. package/src/calls/relay-server.ts +66 -0
  186. package/src/calls/relay-setup-router.ts +82 -1
  187. package/src/calls/twilio-routes.ts +17 -8
  188. package/src/calls/voice-session-bridge.ts +2 -2
  189. package/src/cli/commands/clients.ts +3 -0
  190. package/src/cli/commands/db/status.ts +37 -3
  191. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  192. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  193. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  194. package/src/cli/commands/memory/index.ts +30 -0
  195. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  196. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  197. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  198. package/src/cli/commands/oauth/status.test.ts +36 -0
  199. package/src/cli/commands/oauth/status.ts +23 -3
  200. package/src/cli/commands/plugins.ts +400 -14
  201. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  202. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  203. package/src/cli/lib/__tests__/install-from-github.test.ts +31 -0
  204. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  205. package/src/cli/lib/__tests__/plugin-pin-history.test.ts +162 -0
  206. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  207. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  208. package/src/cli/lib/diff-plugin.ts +346 -0
  209. package/src/cli/lib/inspect-plugin.ts +12 -1
  210. package/src/cli/lib/install-from-github.ts +128 -19
  211. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  212. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  213. package/src/cli/lib/plugin-marketplace.ts +11 -0
  214. package/src/cli/lib/plugin-pin-history.ts +257 -0
  215. package/src/cli/lib/plugin-surfaces.ts +104 -0
  216. package/src/cli/lib/upgrade-plugin.ts +298 -10
  217. package/src/cli/program.ts +2 -6
  218. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  219. package/src/config/assistant-feature-flags.ts +22 -7
  220. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  221. package/src/config/bundled-skills/document-editor/SKILL.md +3 -3
  222. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  223. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  224. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  225. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  226. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  227. package/src/config/bundled-skills/system-storage-cleanup/SKILL.md +74 -0
  228. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  229. package/src/config/bundled-tool-registry.ts +2 -7
  230. package/src/config/call-site-defaults.ts +15 -2
  231. package/src/config/feature-flag-registry.json +46 -31
  232. package/src/config/inference-profile-validation.ts +26 -0
  233. package/src/config/llm-resolver.ts +3 -0
  234. package/src/config/loader.ts +4 -0
  235. package/src/config/memory-v3-gate.ts +11 -0
  236. package/src/config/profile-order.ts +28 -0
  237. package/src/config/schema.ts +8 -6
  238. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  239. package/src/config/schemas/call-site-catalog.ts +7 -0
  240. package/src/config/schemas/channels.ts +11 -0
  241. package/src/config/schemas/elevenlabs.ts +0 -1
  242. package/src/config/schemas/llm.ts +31 -0
  243. package/src/config/schemas/memory-lifecycle.ts +3 -7
  244. package/src/config/schemas/memory-v3.ts +6 -0
  245. package/src/config/schemas/platform.ts +0 -8
  246. package/src/config/schemas/services.ts +18 -0
  247. package/src/config/seed-inference-profiles.ts +109 -44
  248. package/src/config/skills.ts +21 -0
  249. package/src/config/sync-gated-profiles.ts +220 -0
  250. package/src/contacts/contact-store.ts +89 -106
  251. package/src/contacts/contacts-write.ts +5 -22
  252. package/src/contacts/types.ts +0 -1
  253. package/src/context/compactor.ts +88 -54
  254. package/src/context/strip-injections.ts +58 -10
  255. package/src/context/token-estimator.ts +1 -1
  256. package/src/credential-execution/process-manager.ts +55 -14
  257. package/src/credential-execution/prompted-credential.ts +2 -3
  258. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  259. package/src/daemon/__tests__/conversation-tool-setup.test.ts +44 -0
  260. package/src/daemon/config-watcher.ts +0 -4
  261. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  262. package/src/daemon/conversation-agent-loop.ts +114 -22
  263. package/src/daemon/conversation-history.ts +1 -1
  264. package/src/daemon/conversation-lifecycle.ts +3 -5
  265. package/src/daemon/conversation-process.ts +13 -5
  266. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  267. package/src/daemon/conversation-slash.ts +2 -23
  268. package/src/daemon/conversation-surfaces.ts +26 -0
  269. package/src/daemon/conversation-tool-setup.ts +53 -19
  270. package/src/daemon/conversation.ts +131 -20
  271. package/src/daemon/disk-pressure-policy.ts +5 -3
  272. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  273. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  274. package/src/daemon/handlers/config-a2a.ts +0 -2
  275. package/src/daemon/handlers/config-channels.ts +15 -16
  276. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  277. package/src/daemon/handlers/conversations.ts +107 -0
  278. package/src/daemon/host-browser-proxy.ts +41 -0
  279. package/src/daemon/lifecycle.ts +55 -27
  280. package/src/daemon/memory-v2-startup.test.ts +131 -0
  281. package/src/daemon/memory-v2-startup.ts +100 -1
  282. package/src/daemon/message-provenance.ts +2 -0
  283. package/src/daemon/message-types/contacts.ts +0 -1
  284. package/src/daemon/message-types/conversations.ts +3 -3
  285. package/src/daemon/message-types/sync.ts +0 -1
  286. package/src/daemon/message-types/web-activity.ts +7 -1
  287. package/src/daemon/message-types/workflows.ts +83 -1
  288. package/src/daemon/orphan-reaper.test.ts +0 -19
  289. package/src/daemon/orphan-reaper.ts +2 -24
  290. package/src/daemon/server.ts +0 -10
  291. package/src/daemon/tool-setup-types.ts +4 -0
  292. package/src/daemon/trust-context.ts +1 -1
  293. package/src/daemon/wake-conversation-ops.ts +1 -0
  294. package/src/events/tool-audit-listener.ts +2 -2
  295. package/src/home/feed-source-enrichment.test.ts +151 -0
  296. package/src/home/feed-source-enrichment.ts +176 -0
  297. package/src/home/relationship-state.ts +2 -4
  298. package/src/instrument.ts +18 -6
  299. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  300. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  301. package/src/ipc/assistant-server.ts +37 -4
  302. package/src/ipc/gateway-flag-listener.ts +18 -2
  303. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  304. package/src/memory/__tests__/db-logs-attach.test.ts +122 -0
  305. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  306. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  307. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  308. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  309. package/src/memory/auth-fallback-events-store.ts +2 -2
  310. package/src/memory/auto-analysis-enqueue.ts +3 -5
  311. package/src/memory/bookmark-crud.ts +1 -2
  312. package/src/memory/canonical-guardian-store.ts +39 -1
  313. package/src/memory/conversation-crud.ts +9 -4
  314. package/src/memory/conversation-key-store.ts +17 -2
  315. package/src/memory/conversation-title-service.ts +64 -7
  316. package/src/memory/db-async-query.ts +20 -2
  317. package/src/memory/db-connection.ts +49 -5
  318. package/src/memory/db-init.ts +17 -17
  319. package/src/memory/embedding-backend.ts +38 -1
  320. package/src/memory/embedding-billing-breaker.ts +96 -0
  321. package/src/memory/jobs-store.ts +25 -13
  322. package/src/memory/jobs-worker.ts +54 -1
  323. package/src/memory/lifecycle-events-store.ts +2 -2
  324. package/src/memory/memory-retrospective-constants.ts +4 -4
  325. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  326. package/src/memory/memory-retrospective-job.ts +28 -227
  327. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  328. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  329. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  330. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  331. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  332. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  333. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  334. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  335. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  336. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  337. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  338. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  339. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  340. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  341. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  342. package/src/memory/migrations/index.ts +6 -0
  343. package/src/memory/migrations/run-migrations.ts +41 -0
  344. package/src/memory/migrations/validate-migration-state.ts +1 -1
  345. package/src/memory/onboarding-events-store.ts +3 -3
  346. package/src/memory/schema/contacts.ts +0 -5
  347. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  348. package/src/memory/skill-loaded-events-store.ts +2 -2
  349. package/src/memory/tool-executed-events-store.test.ts +7 -7
  350. package/src/memory/turn-trace-store.test.ts +736 -0
  351. package/src/memory/turn-trace-store.ts +364 -0
  352. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  353. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  354. package/src/memory/v2/consolidation-job.ts +2 -2
  355. package/src/memory/v2/skill-content.ts +25 -7
  356. package/src/memory/v2/skill-store.ts +7 -1
  357. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  358. package/src/memory/v3-eval/eval-packets.ts +546 -0
  359. package/src/messaging/providers/slack/adapter.ts +1 -1
  360. package/src/messaging/providers/slack/api.ts +31 -0
  361. package/src/messaging/providers/slack/send.test.ts +114 -2
  362. package/src/messaging/providers/slack/send.ts +30 -7
  363. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  364. package/src/messaging/providers/slack/withdraw.ts +161 -0
  365. package/src/notifications/AGENTS.md +2 -0
  366. package/src/notifications/access-request-copy.ts +72 -59
  367. package/src/notifications/adapters/shared.ts +29 -0
  368. package/src/notifications/adapters/slack.ts +58 -103
  369. package/src/notifications/adapters/telegram.ts +2 -20
  370. package/src/notifications/approval-card-data.ts +333 -0
  371. package/src/notifications/broadcaster.ts +16 -3
  372. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  373. package/src/notifications/copy-composer.ts +3 -3
  374. package/src/notifications/decision-engine.ts +4 -2
  375. package/src/notifications/destination-resolver.ts +4 -6
  376. package/src/notifications/guardian-question-mode.ts +10 -0
  377. package/src/notifications/home-feed-side-effect.ts +7 -16
  378. package/src/notifications/notification-utils.ts +19 -20
  379. package/src/notifications/signal.ts +79 -43
  380. package/src/notifications/types.ts +98 -121
  381. package/src/oauth/AGENTS.md +5 -24
  382. package/src/permissions/checker.test.ts +51 -0
  383. package/src/permissions/checker.ts +185 -26
  384. package/src/permissions/ipc-risk-types.ts +24 -0
  385. package/src/permissions/question-prompter.test.ts +27 -0
  386. package/src/permissions/question-prompter.ts +4 -0
  387. package/src/permissions/secret-prompter.ts +15 -1
  388. package/src/platform/client.test.ts +119 -0
  389. package/src/platform/client.ts +66 -0
  390. package/src/platform/consent-cache.test.ts +267 -0
  391. package/src/platform/consent-cache.ts +174 -0
  392. package/src/plugin-api/constants.ts +1 -1
  393. package/src/plugin-api/index.ts +33 -1
  394. package/src/plugin-api/model-profiles.ts +33 -0
  395. package/src/plugin-api/types.ts +50 -2
  396. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  397. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  398. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  399. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  400. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  401. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  402. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  403. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  404. package/src/plugins/defaults/advisor/config.ts +21 -0
  405. package/src/plugins/defaults/advisor/consult.ts +93 -0
  406. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  407. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  408. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  409. package/src/plugins/defaults/advisor/package.json +14 -0
  410. package/src/plugins/defaults/advisor/steering.ts +67 -0
  411. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  412. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  413. package/src/plugins/defaults/compaction/window-manager.ts +18 -0
  414. package/src/plugins/defaults/index.ts +60 -0
  415. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  416. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  417. package/src/plugins/defaults/memory-retrieval/injectors.ts +4 -4
  418. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  419. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  420. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  421. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  422. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  423. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  424. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  425. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  426. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  427. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  428. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  429. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  430. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  431. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  432. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  433. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  434. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  435. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  436. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  437. package/src/prompts/persona-resolver.ts +14 -4
  438. package/src/prompts/templates/system-sections.ts +7 -2
  439. package/src/providers/__tests__/count-tokens-forwarding.test.ts +98 -0
  440. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  441. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  442. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  443. package/src/providers/anthropic/client.ts +230 -175
  444. package/src/providers/atlascloud/client.ts +85 -0
  445. package/src/providers/call-site-routing.ts +10 -0
  446. package/src/providers/fetch-provider-catalog.ts +85 -0
  447. package/src/providers/gemini/client.ts +43 -0
  448. package/src/providers/inference/adapter-factory.ts +3 -0
  449. package/src/providers/model-catalog.ts +58 -0
  450. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  451. package/src/providers/openai/chat-completions-provider.ts +7 -0
  452. package/src/providers/openai/responses-provider.ts +10 -0
  453. package/src/providers/provider-send-message.ts +11 -3
  454. package/src/providers/ratelimit.ts +9 -0
  455. package/src/providers/retry.ts +62 -12
  456. package/src/providers/search-provider-catalog.ts +10 -0
  457. package/src/providers/types.ts +16 -0
  458. package/src/providers/usage-tracking.ts +7 -0
  459. package/src/providers/weak-open-model.ts +22 -0
  460. package/src/runtime/AGENTS.md +0 -1
  461. package/src/runtime/__tests__/agent-wake.test.ts +227 -0
  462. package/src/runtime/__tests__/client-health.test.ts +44 -0
  463. package/src/runtime/access-request-helper.ts +21 -53
  464. package/src/runtime/actor-trust-resolver.ts +59 -63
  465. package/src/runtime/agent-wake.ts +71 -0
  466. package/src/runtime/assistant-event-hub.ts +18 -4
  467. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  468. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  469. package/src/runtime/btw-sidechain.ts +3 -6
  470. package/src/runtime/capabilities.test.ts +120 -0
  471. package/src/runtime/capabilities.ts +197 -0
  472. package/src/runtime/channel-approval-types.ts +22 -45
  473. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  474. package/src/runtime/channel-retry-sweep.ts +1 -0
  475. package/src/runtime/channel-verification-service.ts +3 -3
  476. package/src/runtime/client-health.ts +26 -0
  477. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  478. package/src/runtime/effective-capabilities.test.ts +128 -0
  479. package/src/runtime/effective-capabilities.ts +84 -0
  480. package/src/runtime/guardian-reply-router.ts +106 -21
  481. package/src/runtime/invite-redemption-service.ts +9 -25
  482. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +126 -0
  483. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  484. package/src/runtime/pending-interactions.ts +35 -0
  485. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  486. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  487. package/src/runtime/routes/__tests__/plugins-routes.test.ts +383 -1
  488. package/src/runtime/routes/app-routes.ts +1 -1
  489. package/src/runtime/routes/approval-routes.ts +40 -9
  490. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  491. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  492. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  493. package/src/runtime/routes/btw-routes.ts +1 -27
  494. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  495. package/src/runtime/routes/client-routes.ts +10 -0
  496. package/src/runtime/routes/contact-routes.ts +31 -8
  497. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  498. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  499. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  500. package/src/runtime/routes/conversation-routes.ts +39 -14
  501. package/src/runtime/routes/credential-routes.ts +40 -16
  502. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  503. package/src/runtime/routes/events-routes.ts +1 -3
  504. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  505. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  506. package/src/runtime/routes/home-feed-routes.ts +8 -3
  507. package/src/runtime/routes/identity-routes.ts +1 -296
  508. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  509. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  510. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  511. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  512. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  513. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  514. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  515. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  516. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  517. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  518. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  519. package/src/runtime/routes/index.ts +2 -0
  520. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  521. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  522. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  523. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  524. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  525. package/src/runtime/routes/notification-routes.ts +122 -133
  526. package/src/runtime/routes/platform-routes.ts +2 -2
  527. package/src/runtime/routes/plugins-routes.ts +366 -11
  528. package/src/runtime/routes/schedule-routes.ts +0 -22
  529. package/src/runtime/routes/secret-routes.ts +15 -0
  530. package/src/runtime/routes/surface-action-routes.ts +2 -1
  531. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  532. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  533. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  534. package/src/runtime/routes/workflow-routes.ts +131 -29
  535. package/src/runtime/routes/workspace-greetings.ts +55 -0
  536. package/src/runtime/sync/resource-sync-events.ts +1 -11
  537. package/src/runtime/tool-grant-request-helper.ts +18 -16
  538. package/src/runtime/trust-context-resolver.ts +8 -5
  539. package/src/schedule/inference-profile.ts +2 -14
  540. package/src/schedule/schedule-store.ts +1 -1
  541. package/src/schedule/scheduler-types.ts +5 -1
  542. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  543. package/src/security/secret-patterns.ts +3 -0
  544. package/src/subagent/manager.ts +17 -4
  545. package/src/subagent/types.ts +6 -0
  546. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  547. package/src/telemetry/trace-collection-policy.ts +30 -0
  548. package/src/telemetry/types.ts +89 -0
  549. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  550. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  551. package/src/tools/AGENTS.md +3 -3
  552. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  553. package/src/tools/browser/browser-execution.ts +30 -19
  554. package/src/tools/document/document-tool.ts +30 -6
  555. package/src/tools/executor.ts +5 -3
  556. package/src/tools/host-terminal/host-shell.ts +5 -4
  557. package/src/tools/memory/register.ts +2 -2
  558. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  559. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  560. package/src/tools/network/web-fetch.ts +372 -1
  561. package/src/tools/network/web-search-error.ts +1 -1
  562. package/src/tools/network/web-search.ts +213 -10
  563. package/src/tools/permission-checker.ts +4 -3
  564. package/src/tools/registry.ts +20 -0
  565. package/src/tools/schedule/create.ts +7 -12
  566. package/src/tools/schedule/update.ts +4 -11
  567. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  568. package/src/tools/side-effects.ts +2 -17
  569. package/src/tools/skills/execute.ts +99 -2
  570. package/src/tools/skills/load.ts +43 -2
  571. package/src/tools/subagent/spawn.ts +64 -15
  572. package/src/tools/terminal/shell.ts +10 -4
  573. package/src/tools/tool-approval-handler.ts +18 -13
  574. package/src/tools/tool-manifest.ts +0 -2
  575. package/src/tools/types.ts +10 -0
  576. package/src/tools/ui-surface/definitions.ts +64 -3
  577. package/src/tools/verification-control-plane-policy.ts +3 -1
  578. package/src/tools/workflows/run-workflow.test.ts +8 -18
  579. package/src/tools/workflows/run-workflow.ts +1 -0
  580. package/src/util/disk-usage.ts +78 -23
  581. package/src/util/logs-db-path.ts +22 -0
  582. package/src/util/platform.ts +10 -3
  583. package/src/watcher/telemetry.ts +2 -2
  584. package/src/workflows/capabilities.ts +2 -3
  585. package/src/workflows/engine.test.ts +175 -1
  586. package/src/workflows/engine.ts +82 -0
  587. package/src/workflows/journal-store.test.ts +70 -0
  588. package/src/workflows/journal-store.ts +18 -3
  589. package/src/workflows/run-manager.test.ts +171 -28
  590. package/src/workflows/run-manager.ts +66 -24
  591. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  592. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  593. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  594. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  595. package/src/workspace/migrations/registry.ts +8 -0
  596. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  597. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  598. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  599. package/src/__tests__/credential-vault.test.ts +0 -1706
  600. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  601. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  602. package/src/cli/commands/__tests__/task.test.ts +0 -914
  603. package/src/cli/commands/task.ts +0 -771
  604. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  605. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  606. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  607. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  608. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  609. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  610. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  611. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  612. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  613. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  614. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  615. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  616. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  617. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  618. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  619. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  620. package/src/memory/preloaded-apps.ts +0 -116
  621. package/src/notifications/tool-approval-copy.ts +0 -142
  622. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  623. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  624. package/src/tools/credentials/vault.ts +0 -712
@@ -11,11 +11,9 @@ mock.module("../../util/logger.js", () => ({
11
11
  }));
12
12
 
13
13
  // ── Mutable mock state ────────────────────────────────────────────────
14
- // `flagEnabled` toggles the `workflows` feature flag; `configThrows`
15
- // simulates config not yet loaded (test-setup race). The run-manager mock
16
- // records the args of the last `start()` call for assertion.
14
+ // `configThrows` simulates config not yet loaded (test-setup race). The
15
+ // run-manager mock records the args of the last `start()` call for assertion.
17
16
 
18
- let flagEnabled = true;
19
17
  let configThrows = false;
20
18
 
21
19
  const realLoader = await import("../../config/loader.js");
@@ -35,13 +33,6 @@ mock.module("../../config/loader.js", () => ({
35
33
  }) as unknown as ReturnType<typeof realLoader.loadConfig>,
36
34
  }));
37
35
 
38
- const realFlags = await import("../../config/assistant-feature-flags.js");
39
- mock.module("../../config/assistant-feature-flags.js", () => ({
40
- ...realFlags,
41
- isAssistantFeatureFlagEnabled: (key: string) =>
42
- key === "workflows" ? flagEnabled : false,
43
- }));
44
-
45
36
  // No live conversation in tests — the tool falls back to a synthetic trust
46
37
  // context built from the tool context's trustClass.
47
38
  const realRegistry = await import("../../daemon/conversation-registry.js");
@@ -92,7 +83,6 @@ function makeContext(): Parameters<typeof executeRunWorkflow>[1] {
92
83
  }
93
84
 
94
85
  beforeEach(() => {
95
- flagEnabled = true;
96
86
  configThrows = false;
97
87
  startThrows = null;
98
88
  lastStartArgs = null;
@@ -105,10 +95,10 @@ beforeEach(() => {
105
95
  });
106
96
 
107
97
  describe("workflow tools are served by the workflows skill", () => {
108
- // The cutover moved run_workflow / manage_workflows out of the always-on tool
109
- // manifest into the flag-gated `workflows` bundled skill (loaded via
110
- // skill_load, invoked via skill_execute). The skill manifest is the
111
- // source of truth; assert it declares both as in-process host tools.
98
+ // run_workflow / manage_workflows are served by the `workflows` bundled skill
99
+ // (loaded via skill_load, invoked via skill_execute), not the always-on tool
100
+ // manifest. The skill manifest is the source of truth; assert it declares both
101
+ // as in-process host tools.
112
102
  test("the workflows skill TOOLS.json declares both host-executed tools", async () => {
113
103
  const { readFileSync } = await import("node:fs");
114
104
  const { join } = await import("node:path");
@@ -211,13 +201,13 @@ describe("run_workflow launch", () => {
211
201
  });
212
202
 
213
203
  test("surfaces a run-manager start error as a tool error", async () => {
214
- startThrows = new Error("Workflows are not enabled.");
204
+ startThrows = new Error("run manager exploded");
215
205
  const res = await executeRunWorkflow(
216
206
  { script: "export const meta = {};" },
217
207
  makeContext(),
218
208
  );
219
209
  expect(res.isError).toBe(true);
220
- expect(res.content).toContain("Workflows are not enabled.");
210
+ expect(res.content).toContain("run manager exploded");
221
211
  });
222
212
  });
223
213
 
@@ -68,6 +68,7 @@ export async function executeRunWorkflow(
68
68
  args,
69
69
  manifest,
70
70
  conversationId: context.conversationId,
71
+ ...(context.toolUseId ? { toolUseId: context.toolUseId } : {}),
71
72
  ...(label ? { label } : {}),
72
73
  trustContext,
73
74
  });
@@ -1,7 +1,11 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { existsSync, statfsSync } from "node:fs";
3
3
 
4
- import { getMinikubeStorageSize } from "../config/env-registry.js";
4
+ import {
5
+ getIsContainerized,
6
+ getIsPlatform,
7
+ getMinikubeStorageSize,
8
+ } from "../config/env-registry.js";
5
9
  import { getWorkspaceDir } from "./platform.js";
6
10
 
7
11
  export interface DiskUsageInfo {
@@ -58,6 +62,57 @@ export function __resetDiskUsageCacheForTests(): void {
58
62
  duCachePaths = null;
59
63
  }
60
64
 
65
+ /**
66
+ * How the workspace volume's capacity should be reported when statfsSync
67
+ * cannot see the volume directly.
68
+ *
69
+ * - `fixed-cap`: minikube hostPath PVC — the PVC request is the hard
70
+ * capacity, so usage is measured with `du` and free space is whatever
71
+ * remains within the quota.
72
+ * - `host-free`: local Docker named volume — the volume is backed by the
73
+ * host (or Colima VM) filesystem and can grow into its free space, so
74
+ * usage is measured with `du` and the effective capacity is current usage
75
+ * plus the host's remaining headroom.
76
+ * - `none`: statfsSync already reports the volume accurately (bare metal, or
77
+ * a platform-managed instance on a CSI-backed PVC), so use it directly.
78
+ */
79
+ type WorkspaceVolumeReporting =
80
+ | { kind: "none" }
81
+ | { kind: "fixed-cap"; totalBytes: number }
82
+ | { kind: "host-free" };
83
+
84
+ /**
85
+ * Decide how to report the workspace volume's capacity. Both minikube
86
+ * hostPath PVCs and local Docker named volumes are backed by the host
87
+ * filesystem, so statfsSync reports the host's entire disk rather than the
88
+ * workspace volume — in both cases we measure actual usage with `du` instead.
89
+ */
90
+ function classifyWorkspaceVolume(
91
+ fsTotalBytes: number,
92
+ ): WorkspaceVolumeReporting {
93
+ // Minikube mode: the platform passes the PVC storage size so we can report
94
+ // accurate capacity. Detect the hostPath case by comparing filesystem size
95
+ // against the PVC size — if the filesystem is larger, statfsSync is seeing
96
+ // the host disk and we should measure the directory instead.
97
+ const storageSizeRaw = getMinikubeStorageSize();
98
+ if (storageSizeRaw) {
99
+ const pvcTotalBytes = parseK8sMemoryBytes(storageSizeRaw);
100
+ if (pvcTotalBytes !== null && fsTotalBytes > pvcTotalBytes * 1.1) {
101
+ return { kind: "fixed-cap", totalBytes: pvcTotalBytes };
102
+ }
103
+ return { kind: "none" };
104
+ }
105
+
106
+ // Local Docker hatch: the workspace is a Docker named volume backed by the
107
+ // host filesystem. Platform-managed remote instances run on CSI-backed PVCs
108
+ // where statfsSync already reports the volume, so they are excluded.
109
+ if (getIsContainerized() && !getIsPlatform()) {
110
+ return { kind: "host-free" };
111
+ }
112
+
113
+ return { kind: "none" };
114
+ }
115
+
61
116
  export function getDiskUsageInfo(): DiskUsageInfo | null {
62
117
  try {
63
118
  const wsDir = getWorkspaceDir();
@@ -68,28 +123,28 @@ export function getDiskUsageInfo(): DiskUsageInfo | null {
68
123
  const bytesToMb = (b: number) =>
69
124
  Math.round((b / (1024 * 1024)) * 100) / 100;
70
125
 
71
- // Minikube mode: the platform passes the PVC storage size so we can
72
- // report accurate capacity. On hostPath-backed PVCs statfsSync reports
73
- // the host's entire filesystem rather than the PVC. Detect this by
74
- // comparing filesystem size against PVC size — if the filesystem is
75
- // larger, measure actual directory usage with `du` instead.
76
- const storageSizeRaw = getMinikubeStorageSize();
77
- if (storageSizeRaw) {
78
- const pvcTotalBytes = parseK8sMemoryBytes(storageSizeRaw);
79
- if (pvcTotalBytes !== null && fsTotalBytes > pvcTotalBytes * 1.1) {
80
- const volumePaths = [diskPath];
81
- if (diskPath !== "/data" && existsSync("/data")) {
82
- volumePaths.push("/data");
83
- }
84
- const usedBytes = getCachedDirectorySizeBytes(volumePaths);
85
- if (usedBytes !== null) {
86
- return {
87
- path: diskPath,
88
- totalMb: bytesToMb(pvcTotalBytes),
89
- usedMb: bytesToMb(usedBytes),
90
- freeMb: bytesToMb(Math.max(0, pvcTotalBytes - usedBytes)),
91
- };
92
- }
126
+ const reporting = classifyWorkspaceVolume(fsTotalBytes);
127
+ if (reporting.kind !== "none") {
128
+ const volumePaths = [diskPath];
129
+ if (diskPath !== "/data" && existsSync("/data")) {
130
+ volumePaths.push("/data");
131
+ }
132
+ const usedBytes = getCachedDirectorySizeBytes(volumePaths);
133
+ if (usedBytes !== null) {
134
+ const totalBytes =
135
+ reporting.kind === "fixed-cap"
136
+ ? reporting.totalBytes
137
+ : usedBytes + fsFreeBytes;
138
+ const freeBytes =
139
+ reporting.kind === "fixed-cap"
140
+ ? Math.max(0, reporting.totalBytes - usedBytes)
141
+ : fsFreeBytes;
142
+ return {
143
+ path: diskPath,
144
+ totalMb: bytesToMb(totalBytes),
145
+ usedMb: bytesToMb(usedBytes),
146
+ freeMb: bytesToMb(freeBytes),
147
+ };
93
148
  }
94
149
  }
95
150
 
@@ -0,0 +1,22 @@
1
+ import { join } from "node:path";
2
+
3
+ import { getDataDir } from "./platform.js";
4
+
5
+ /**
6
+ * Path to the secondary SQLite file that houses heavy append-only tables
7
+ * (LLM request logs, and other log/event tables over time). It lives in the
8
+ * same `data/db` directory as the main DB and is ATTACHed to the daemon's
9
+ * connection as the `logs` schema (see `memory/db-connection.ts`). Splitting
10
+ * these tables into their own file keeps the main DB — and its WAL — small and
11
+ * lets the two files VACUUM/checkpoint independently.
12
+ *
13
+ * Kept in its own leaf module rather than alongside `getDbPath()` in
14
+ * `platform.ts`: `platform.ts` is imported very early and widely, and adding an
15
+ * export to it that low-level consumers (e.g. `db-connection.ts`) pull in
16
+ * across the daemon's large, cyclic import graph trips a Bun link-order bug
17
+ * ("Export named 'getLogsDbPath' not found"). Isolating it here keeps
18
+ * `platform.ts`'s module shape stable.
19
+ */
20
+ export function getLogsDbPath(): string {
21
+ return join(getDataDir(), "db", "assistant-logs.db");
22
+ }
@@ -314,7 +314,14 @@ export function getWorkspaceToolsDir(): string {
314
314
  return join(getWorkspaceDir(), "tools");
315
315
  }
316
316
 
317
- /** Returns $VELLUM_WORKSPACE_DIR/routes — user-defined HTTP route handlers. */
317
+ /**
318
+ * Returns $VELLUM_WORKSPACE_DIR/routes — user-defined HTTP route handlers.
319
+ *
320
+ * Handler modules under this directory are dynamic-imported by the user-route
321
+ * dispatcher and their exported HTTP-method functions are executed on the
322
+ * next matching request, so the file risk classifier escalates writes under
323
+ * this path to High for the same reason `plugins/` and `tools/` are escalated.
324
+ */
318
325
  export function getWorkspaceRoutesDir(): string {
319
326
  return join(getWorkspaceDir(), "routes");
320
327
  }
@@ -402,8 +409,8 @@ export function getSkillRuntimePath(
402
409
  *
403
410
  * Resolution order:
404
411
  *
405
- * 1. macOS `.app` bundle: `Contents/Resources/bun` — shipped by
406
- * `clients/macos/build.sh` at a version that matches `.tool-versions`.
412
+ * 1. macOS `.app` bundle: `Contents/Resources/bun` — bundled at a version
413
+ * that matches `.tool-versions`.
407
414
  * 2. Next-to-binary: `<execDir>/bun` for Docker/generic compiled layouts
408
415
  * that stage a bun binary alongside the daemon (PR 29 wires this up).
409
416
  *
@@ -4,8 +4,8 @@
4
4
  * ahead of the planned watcher → skills-and-schedules migration.
5
5
  *
6
6
  * Both event shapes ship through the existing lifecycle-event telemetry
7
- * pipeline, so they inherit its `collectUsageData` opt-out gate and need
8
- * no wire-contract or platform-side changes:
7
+ * pipeline, so they inherit its platform `share_analytics` consent gate and
8
+ * need no wire-contract or platform-side changes:
9
9
  *
10
10
  * - `watcher_enabled:<providerId>` — one per enabled watcher, at most
11
11
  * once per 24h per daemon. Counts devices that have watchers
@@ -29,9 +29,8 @@
29
29
  * (If host execution is ever wanted, the deliberate path is to thread the
30
30
  * originating tool context through the engine — not to relax this gate.)
31
31
  *
32
- * This module is pure logic: it performs no feature-flag checks and no I/O
33
- * beyond the synchronous tool-registry lookup. The `workflows` flag gates the
34
- * callers, not this code.
32
+ * This module is pure logic: it performs no I/O beyond the synchronous
33
+ * tool-registry lookup.
35
34
  */
36
35
 
37
36
  import { z } from "zod";
@@ -16,7 +16,11 @@ import type { TrustContext } from "../daemon/trust-context.js";
16
16
  import { getSqlite } from "../memory/db-connection.js";
17
17
  import { initializeDb } from "../memory/db-init.js";
18
18
  import type { ResolvedCapabilities } from "./capabilities.js";
19
- import { executeWorkflow, extractWorkflowMeta } from "./engine.js";
19
+ import {
20
+ executeWorkflow,
21
+ extractWorkflowMeta,
22
+ type WorkflowLeafEvent,
23
+ } from "./engine.js";
20
24
  import * as journalStore from "./journal-store.js";
21
25
  import type { LeafResult, RunLeafOptions } from "./leaf-runner.js";
22
26
 
@@ -677,6 +681,21 @@ describe("executeWorkflow — resume", () => {
677
681
  );
678
682
  expect(journalA.map((e) => e.seq)).toEqual([0, 1, 2]);
679
683
  });
684
+
685
+ test("a completed leaf persists its per-leaf token usage in the journal", async () => {
686
+ const runner = makeFakeRunner();
687
+ await execute("wf-leaf-tokens", `return agent("alpha");`, runner.runner);
688
+
689
+ const journal = journalStore.getJournal("wf-leaf-tokens");
690
+ expect(journal).toHaveLength(1);
691
+ // The fake runner reports 10/5 per leaf; the completed entry carries them.
692
+ expect(journal[0]).toMatchObject({
693
+ seq: 0,
694
+ status: "completed",
695
+ inputTokens: 10,
696
+ outputTokens: 5,
697
+ });
698
+ });
680
699
  });
681
700
 
682
701
  describe("executeWorkflow — schema vs tool leaf tool forwarding", () => {
@@ -1142,3 +1161,158 @@ describe("executeWorkflow — nested workflow()", () => {
1142
1161
  expect(second.prompts).toEqual([]);
1143
1162
  });
1144
1163
  });
1164
+
1165
+ describe("executeWorkflow — onLeaf observe-only hook", () => {
1166
+ beforeEach(resetTables);
1167
+
1168
+ /** Run a workflow, collecting every onLeaf event. */
1169
+ function executeWithLeafEvents(
1170
+ runId: string,
1171
+ scriptSource: string,
1172
+ runner: typeof import("./leaf-runner.js").runLeaf,
1173
+ config: WorkflowsConfig = configWith(),
1174
+ signal?: AbortSignal,
1175
+ ): {
1176
+ promise: ReturnType<typeof executeWorkflow>;
1177
+ events: WorkflowLeafEvent[];
1178
+ } {
1179
+ const events: WorkflowLeafEvent[] = [];
1180
+ const promise = executeWorkflow({
1181
+ runId,
1182
+ scriptSource: META + scriptSource,
1183
+ args: null,
1184
+ capabilities: CAPABILITIES,
1185
+ config,
1186
+ journal: journalStore,
1187
+ leafRunner: runner,
1188
+ trustContext: TRUST,
1189
+ onLeaf: (e) => events.push(e),
1190
+ ...(signal ? { signal } : {}),
1191
+ });
1192
+ return { promise, events };
1193
+ }
1194
+
1195
+ test("a single agent() emits leaf_started then leaf_finished:completed at seq 0", async () => {
1196
+ const fake = makeFakeRunner();
1197
+ const { promise, events } = executeWithLeafEvents(
1198
+ "wf-leaf-single",
1199
+ `return agent("solo");`,
1200
+ fake.runner,
1201
+ );
1202
+ const res = await promise;
1203
+ expect(res.status).toBe("completed");
1204
+
1205
+ expect(events).toEqual([
1206
+ { type: "leaf_started", seq: 0, promptSummary: "solo" },
1207
+ {
1208
+ type: "leaf_finished",
1209
+ seq: 0,
1210
+ status: "completed",
1211
+ inputTokens: 10,
1212
+ outputTokens: 5,
1213
+ resultSummary: "out:solo",
1214
+ },
1215
+ ]);
1216
+ });
1217
+
1218
+ test("a 3-spec parallel() emits 3 started + 3 finished across seqs 0,1,2", async () => {
1219
+ const fake = makeFakeRunner();
1220
+ const { promise, events } = executeWithLeafEvents(
1221
+ "wf-leaf-parallel",
1222
+ `return parallel([leaf("a"), leaf("b"), leaf("c")]);`,
1223
+ fake.runner,
1224
+ );
1225
+ const res = await promise;
1226
+ expect(res.status).toBe("completed");
1227
+
1228
+ const started = events.filter((e) => e.type === "leaf_started");
1229
+ const finished = events.filter((e) => e.type === "leaf_finished");
1230
+ expect(started.map((e) => e.seq).sort((a, b) => a - b)).toEqual([0, 1, 2]);
1231
+ expect(finished.map((e) => e.seq).sort((a, b) => a - b)).toEqual([0, 1, 2]);
1232
+ expect(finished.every((e) => e.status === "completed")).toBe(true);
1233
+ });
1234
+
1235
+ test("a throwing (non-abort) leaf emits leaf_started then leaf_finished:failed", async () => {
1236
+ const fake = makeFakeRunner({ failOn: (p) => p === "boom" });
1237
+ const { promise, events } = executeWithLeafEvents(
1238
+ "wf-leaf-fail",
1239
+ `return parallel([leaf("boom")]);`,
1240
+ fake.runner,
1241
+ );
1242
+ await promise;
1243
+
1244
+ expect(events).toEqual([
1245
+ { type: "leaf_started", seq: 0, promptSummary: "boom" },
1246
+ {
1247
+ type: "leaf_finished",
1248
+ seq: 0,
1249
+ status: "failed",
1250
+ inputTokens: 0,
1251
+ outputTokens: 0,
1252
+ resultSummary: "leaf failed: boom",
1253
+ },
1254
+ ]);
1255
+ });
1256
+
1257
+ test("an aborted in-flight leaf emits leaf_started but NO leaf_finished", async () => {
1258
+ const controller = new AbortController();
1259
+ let started = false;
1260
+ const runner = (async (): Promise<
1261
+ import("./leaf-runner.js").LeafResult
1262
+ > => {
1263
+ started = true;
1264
+ await new Promise<void>((resolve) => {
1265
+ if (controller.signal.aborted) return resolve();
1266
+ controller.signal.addEventListener("abort", () => resolve(), {
1267
+ once: true,
1268
+ });
1269
+ });
1270
+ const err = new Error("The operation was aborted");
1271
+ err.name = "AbortError";
1272
+ throw err;
1273
+ }) as unknown as typeof import("./leaf-runner.js").runLeaf;
1274
+
1275
+ const { promise, events } = executeWithLeafEvents(
1276
+ "wf-leaf-abort",
1277
+ `return agent("long-running");`,
1278
+ runner,
1279
+ configWith(),
1280
+ controller.signal,
1281
+ );
1282
+
1283
+ await waitUntil(() => started);
1284
+ controller.abort();
1285
+
1286
+ const res = await promise;
1287
+ expect(res.status).toBe("aborted");
1288
+ expect(events).toEqual([
1289
+ { type: "leaf_started", seq: 0, promptSummary: "long-running" },
1290
+ ]);
1291
+ });
1292
+
1293
+ test("a journal-replayed leaf emits neither leaf_started nor leaf_finished", async () => {
1294
+ const script = `return agent("alpha");`;
1295
+
1296
+ // First run journals the leaf as completed.
1297
+ const first = makeFakeRunner();
1298
+ const res1 = await executeWithLeafEvents(
1299
+ "wf-leaf-replay",
1300
+ script,
1301
+ first.runner,
1302
+ ).promise;
1303
+ expect(res1.status).toBe("completed");
1304
+ expect(first.prompts).toEqual(["alpha"]);
1305
+
1306
+ // Resume: the leaf replays from the journal, so no onLeaf events fire.
1307
+ const second = makeFakeRunner();
1308
+ const { promise, events } = executeWithLeafEvents(
1309
+ "wf-leaf-replay",
1310
+ script,
1311
+ second.runner,
1312
+ );
1313
+ const res2 = await promise;
1314
+ expect(res2.status).toBe("completed");
1315
+ expect(second.prompts).toEqual([]);
1316
+ expect(events).toEqual([]);
1317
+ });
1318
+ });
@@ -73,6 +73,30 @@ export type WorkflowProgressEvent =
73
73
  | { type: "phase"; title: string }
74
74
  | { type: "log"; message: string };
75
75
 
76
+ /**
77
+ * An observe-only lifecycle event for a single leaf — the source of a live leaf
78
+ * tree. Emitted once when a leaf STARTS (after the cap check + spawn increment)
79
+ * and once when it FINISHES (completed or failed). A journal-replayed leaf and
80
+ * an aborted in-flight leaf emit neither.
81
+ */
82
+ export type WorkflowLeafEvent =
83
+ | {
84
+ type: "leaf_started";
85
+ seq: number;
86
+ label?: string;
87
+ phase?: string;
88
+ promptSummary: string;
89
+ }
90
+ | {
91
+ type: "leaf_finished";
92
+ seq: number;
93
+ status: "completed" | "failed";
94
+ label?: string;
95
+ inputTokens: number;
96
+ outputTokens: number;
97
+ resultSummary: string;
98
+ };
99
+
76
100
  /** The journal-store surface the engine depends on (injectable for tests). */
77
101
  export interface WorkflowJournal {
78
102
  appendJournalEntry: typeof JournalStore.appendJournalEntry;
@@ -108,6 +132,13 @@ export interface ExecuteWorkflowOptions {
108
132
  trustContext: TrustContext;
109
133
  /** Receives `phase`/`log` progress events from the script. */
110
134
  onProgress?: (event: WorkflowProgressEvent) => void;
135
+ /**
136
+ * Observe-only per-leaf lifecycle hook. Emits once on START (after the cap
137
+ * check + spawn increment; NEVER on a journal-replay short-circuit) and once
138
+ * on FINISH (completed or failed). An aborted in-flight leaf emits NEITHER a
139
+ * finish nor a spurious failed.
140
+ */
141
+ onLeaf?: (event: WorkflowLeafEvent) => void;
111
142
  /** Cooperative cancellation for the whole run. */
112
143
  signal?: AbortSignal;
113
144
  }
@@ -451,6 +482,26 @@ function skipRegexLiteral(source: string, start: number): number {
451
482
  return source.length - 1;
452
483
  }
453
484
 
485
+ /**
486
+ * A bounded string summary of a leaf prompt or result, for an observe-only
487
+ * {@link WorkflowLeafEvent}. Strings pass through; everything else is
488
+ * JSON-serialized (falling back to `String(value)` if it cannot be). Truncated
489
+ * to `max` chars with a trailing ellipsis.
490
+ */
491
+ function summarizeForLeafEvent(value: unknown, max = 200): string {
492
+ let text: string;
493
+ if (typeof value === "string") {
494
+ text = value;
495
+ } else {
496
+ try {
497
+ text = JSON.stringify(value);
498
+ } catch {
499
+ text = String(value);
500
+ }
501
+ }
502
+ return text.length > max ? `${text.slice(0, max)}…` : text;
503
+ }
504
+
454
505
  function callHashOf(prompt: string, opts: LeafCallOptions): string {
455
506
  return createHash("sha256")
456
507
  .update(deterministicStringify({ prompt, opts }))
@@ -542,6 +593,7 @@ export async function executeWorkflow(
542
593
  leafRunner,
543
594
  trustContext,
544
595
  onProgress,
596
+ onLeaf,
545
597
  signal,
546
598
  } = opts;
547
599
 
@@ -630,6 +682,14 @@ export async function executeWorkflow(
630
682
  }
631
683
  agentsSpawned += 1;
632
684
 
685
+ onLeaf?.({
686
+ type: "leaf_started",
687
+ seq,
688
+ ...(leafOpts.label !== undefined ? { label: leafOpts.label } : {}),
689
+ ...(leafOpts.phase !== undefined ? { phase: leafOpts.phase } : {}),
690
+ promptSummary: summarizeForLeafEvent(prompt),
691
+ });
692
+
633
693
  try {
634
694
  // A leaf is EITHER a schema leaf (structured output via forced
635
695
  // tool-choice — `schema` set, no tools) OR a tool leaf (free-form with
@@ -666,6 +726,17 @@ export async function executeWorkflow(
666
726
  request: { prompt, opts: leafOpts },
667
727
  result: result.output,
668
728
  status: "completed",
729
+ inputTokens: result.inputTokens,
730
+ outputTokens: result.outputTokens,
731
+ });
732
+ onLeaf?.({
733
+ type: "leaf_finished",
734
+ seq,
735
+ status: "completed",
736
+ ...(leafOpts.label !== undefined ? { label: leafOpts.label } : {}),
737
+ inputTokens: result.inputTokens,
738
+ outputTokens: result.outputTokens,
739
+ resultSummary: summarizeForLeafEvent(result.output),
669
740
  });
670
741
  return { output: result.output, failed: false };
671
742
  } catch (err) {
@@ -693,6 +764,17 @@ export async function executeWorkflow(
693
764
  { err, runId, seq, label: leafOpts.label },
694
765
  "Workflow leaf failed",
695
766
  );
767
+ onLeaf?.({
768
+ type: "leaf_finished",
769
+ seq,
770
+ status: "failed",
771
+ ...(leafOpts.label !== undefined ? { label: leafOpts.label } : {}),
772
+ inputTokens: 0,
773
+ outputTokens: 0,
774
+ resultSummary: summarizeForLeafEvent(
775
+ err instanceof Error ? err.message : String(err),
776
+ ),
777
+ });
696
778
  return { output: null, failed: true };
697
779
  }
698
780
  };
@@ -197,6 +197,76 @@ describe("workflow journal store", () => {
197
197
  });
198
198
  });
199
199
 
200
+ test("appendJournalEntry round-trips per-leaf token usage", () => {
201
+ createRun({ id: "wf-tok", scriptSource: "s", scriptHash: "h" });
202
+
203
+ appendJournalEntry({
204
+ runId: "wf-tok",
205
+ seq: 0,
206
+ callHash: "c0",
207
+ kind: "agent",
208
+ request: { prompt: "with tokens" },
209
+ result: { text: "r" },
210
+ status: "completed",
211
+ inputTokens: 120,
212
+ outputTokens: 45,
213
+ });
214
+ // A leaf with no recorded usage stores NULL → reads back as undefined.
215
+ appendJournalEntry({
216
+ runId: "wf-tok",
217
+ seq: 1,
218
+ callHash: "c1",
219
+ kind: "agent",
220
+ request: { prompt: "no tokens" },
221
+ result: { error: "boom" },
222
+ status: "failed",
223
+ });
224
+
225
+ const journal = getJournal("wf-tok");
226
+ expect(journal[0]).toMatchObject({
227
+ seq: 0,
228
+ inputTokens: 120,
229
+ outputTokens: 45,
230
+ });
231
+ expect(journal[1].inputTokens).toBeUndefined();
232
+ expect(journal[1].outputTokens).toBeUndefined();
233
+ });
234
+
235
+ test("appendJournalEntry upserts token usage on a changed-hash re-run", () => {
236
+ createRun({ id: "wf-tok-resume", scriptSource: "s", scriptHash: "h" });
237
+
238
+ appendJournalEntry({
239
+ runId: "wf-tok-resume",
240
+ seq: 0,
241
+ callHash: "hash-old",
242
+ kind: "agent",
243
+ result: { text: "old" },
244
+ status: "completed",
245
+ inputTokens: 10,
246
+ outputTokens: 5,
247
+ });
248
+ // Resume re-runs the leaf with new usage; the upsert overwrites the tokens.
249
+ appendJournalEntry({
250
+ runId: "wf-tok-resume",
251
+ seq: 0,
252
+ callHash: "hash-new",
253
+ kind: "agent",
254
+ result: { text: "new" },
255
+ status: "completed",
256
+ inputTokens: 30,
257
+ outputTokens: 12,
258
+ });
259
+
260
+ const journal = getJournal("wf-tok-resume");
261
+ expect(journal).toHaveLength(1);
262
+ expect(journal[0]).toMatchObject({
263
+ seq: 0,
264
+ callHash: "hash-new",
265
+ inputTokens: 30,
266
+ outputTokens: 12,
267
+ });
268
+ });
269
+
200
270
  test("appendJournalEntry is idempotent on (run_id, seq)", () => {
201
271
  createRun({ id: "wf-dup", scriptSource: "s", scriptHash: "h" });
202
272