@vellumai/assistant 0.8.12 → 0.9.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 (442) hide show
  1. package/AGENTS.md +0 -14
  2. package/ARCHITECTURE.md +45 -45
  3. package/README.md +1 -1
  4. package/bun.lock +200 -154
  5. package/docs/architecture/integrations.md +3 -3
  6. package/docs/architecture/memory.md +2 -2
  7. package/docs/architecture/security.md +10 -10
  8. package/docs/runbook-trusted-contacts.md +12 -12
  9. package/docs/skills.md +6 -6
  10. package/docs/workflows-testing.md +221 -0
  11. package/docs/workflows.md +510 -0
  12. package/examples/plugins/echo/README.md +5 -5
  13. package/knip.json +2 -0
  14. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +105 -0
  15. package/node_modules/@vellumai/gateway-client/src/index.ts +12 -0
  16. package/openapi.yaml +7197 -5708
  17. package/package.json +8 -4
  18. package/scripts/generate-openapi.ts +66 -114
  19. package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
  20. package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
  21. package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
  22. package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  24. package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
  25. package/src/__tests__/app-compiler.test.ts +7 -1
  26. package/src/__tests__/app-dir-path-guard.test.ts +27 -3
  27. package/src/__tests__/app-executors.test.ts +43 -0
  28. package/src/__tests__/approval-cascade.test.ts +0 -5
  29. package/src/__tests__/approval-routes-http.test.ts +91 -0
  30. package/src/__tests__/assistant-stream-state.test.ts +107 -0
  31. package/src/__tests__/browser-fill-credential.test.ts +3 -3
  32. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
  33. package/src/__tests__/compaction-events.test.ts +63 -7
  34. package/src/__tests__/compaction-trail-store.test.ts +74 -1
  35. package/src/__tests__/compaction.benchmark.test.ts +63 -41
  36. package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
  37. package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
  39. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
  40. package/src/__tests__/conversation-history-web-search.test.ts +7 -0
  41. package/src/__tests__/conversation-process-callsite.test.ts +0 -5
  42. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
  43. package/src/__tests__/conversation-queue.test.ts +0 -5
  44. package/src/__tests__/conversation-slash-queue.test.ts +0 -5
  45. package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
  46. package/src/__tests__/conversation-speed-override.test.ts +0 -5
  47. package/src/__tests__/conversation-surfaces-data-persist.test.ts +97 -0
  48. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  49. package/src/__tests__/conversation-usage.test.ts +2 -0
  50. package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
  51. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
  52. package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
  53. package/src/__tests__/credential-broker-server-use.test.ts +2 -2
  54. package/src/__tests__/credential-broker.test.ts +1 -1
  55. package/src/__tests__/credential-prompt-route.test.ts +417 -0
  56. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  57. package/src/__tests__/credential-vault.test.ts +37 -0
  58. package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
  59. package/src/__tests__/dynamic-page-surface.test.ts +219 -0
  60. package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
  61. package/src/__tests__/gateway-flag-listener.test.ts +24 -7
  62. package/src/__tests__/guardian-action-sweep.test.ts +56 -219
  63. package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
  64. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  65. package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
  66. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
  67. package/src/__tests__/llm-resolver.test.ts +115 -0
  68. package/src/__tests__/managed-profile-guard.test.ts +6 -5
  69. package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
  70. package/src/__tests__/media-generate-image.test.ts +20 -9
  71. package/src/__tests__/mock-gateway-ipc.ts +23 -0
  72. package/src/__tests__/model-intents.test.ts +1 -1
  73. package/src/__tests__/normalize-onboarding.test.ts +26 -0
  74. package/src/__tests__/notification-decision-strategy.test.ts +4 -2
  75. package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
  76. package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
  77. package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
  78. package/src/__tests__/require-fresh-approval.test.ts +425 -1
  79. package/src/__tests__/resolve-app-id.test.ts +56 -0
  80. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
  81. package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
  82. package/src/__tests__/schedule-routes.test.ts +257 -4
  83. package/src/__tests__/schedule-store.test.ts +60 -0
  84. package/src/__tests__/schedule-tools.test.ts +247 -2
  85. package/src/__tests__/skill-execute-input.test.ts +85 -0
  86. package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
  87. package/src/__tests__/skills.test.ts +3 -3
  88. package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
  89. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  90. package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
  91. package/src/__tests__/system-prompt.test.ts +1 -1
  92. package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
  93. package/src/__tests__/task-scheduler.test.ts +299 -0
  94. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
  95. package/src/__tests__/tool-result-spool.test.ts +3 -1
  96. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
  97. package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
  98. package/src/agent/compaction-circuit.ts +11 -0
  99. package/src/agent/loop.ts +181 -12
  100. package/src/api/constants/call-sites.ts +12 -0
  101. package/src/api/events/assistant-thinking-delta.ts +10 -0
  102. package/src/api/events/usage-update.ts +7 -0
  103. package/src/api/index.ts +4 -1
  104. package/src/api/responses/memory-v3-selection-log.ts +18 -11
  105. package/src/approvals/approval-primitive.ts +2 -2
  106. package/src/background-wake/background-wake-routes.test.ts +5 -2
  107. package/src/bundler/compiler-tools.ts +1 -1
  108. package/src/bundler/package-resolver.ts +0 -1
  109. package/src/calls/call-domain.ts +1 -1
  110. package/src/calls/guardian-action-sweep.ts +16 -93
  111. package/src/cli/AGENTS.md +4 -0
  112. package/src/cli/commands/__tests__/schedules.test.ts +430 -1
  113. package/src/cli/commands/credentials.ts +28 -24
  114. package/src/cli/commands/image-generation.ts +23 -9
  115. package/src/cli/commands/notifications.ts +1 -1
  116. package/src/cli/commands/plugins.ts +89 -46
  117. package/src/cli/commands/schedules.ts +384 -11
  118. package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
  119. package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
  120. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
  121. package/src/cli/lib/inspect-plugin.ts +62 -1
  122. package/src/cli/lib/install-from-github.ts +52 -5
  123. package/src/cli/lib/upgrade-plugin.ts +18 -0
  124. package/src/config/__tests__/workflows-schema.test.ts +60 -0
  125. package/src/config/bundled-skills/acp/SKILL.md +2 -2
  126. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  127. package/src/config/bundled-skills/app-builder/tools/app-create.ts +6 -1
  128. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +7 -1
  129. package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +7 -1
  130. package/src/config/bundled-skills/app-builder/tools/app-update.ts +10 -1
  131. package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
  132. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
  133. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
  134. package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
  135. package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
  136. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
  137. package/src/config/bundled-skills/schedule/SKILL.md +7 -2
  138. package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
  139. package/src/config/bundled-skills/workflows/SKILL.md +214 -0
  140. package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
  141. package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
  142. package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
  143. package/src/config/bundled-tool-registry.ts +14 -2
  144. package/src/config/call-site-defaults.ts +5 -0
  145. package/src/config/feature-flag-registry.json +12 -4
  146. package/src/config/llm-context-resolution.ts +8 -0
  147. package/src/config/llm-resolver.ts +30 -0
  148. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
  149. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
  150. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
  151. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
  152. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
  153. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
  154. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
  155. package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
  156. package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
  157. package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
  158. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
  159. package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
  160. package/src/config/schema.ts +2 -0
  161. package/src/config/schemas/call-site-catalog.ts +7 -0
  162. package/src/config/schemas/heartbeat.ts +4 -1
  163. package/src/config/schemas/llm.ts +33 -26
  164. package/src/config/schemas/memory-retrospective.ts +19 -0
  165. package/src/config/schemas/platform.ts +8 -0
  166. package/src/config/schemas/services.ts +5 -2
  167. package/src/config/schemas/workflows.ts +42 -0
  168. package/src/config/skills.ts +3 -3
  169. package/src/context/compactor.ts +273 -39
  170. package/src/context/post-turn-tool-result-truncation.ts +23 -6
  171. package/src/context/tool-result-spool.ts +12 -17
  172. package/src/credential-execution/executable-discovery.ts +1 -1
  173. package/src/credential-execution/process-manager.ts +37 -3
  174. package/src/credential-execution/prompted-credential.ts +205 -0
  175. package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
  176. package/src/daemon/conversation-process.ts +11 -2
  177. package/src/daemon/conversation-surfaces.ts +167 -3
  178. package/src/daemon/conversation-tool-setup.ts +103 -26
  179. package/src/daemon/conversation-usage.ts +2 -0
  180. package/src/daemon/conversation.ts +115 -11
  181. package/src/daemon/handlers/shared.ts +26 -14
  182. package/src/daemon/host-cu-proxy.ts +15 -12
  183. package/src/daemon/host-file-proxy.ts +15 -12
  184. package/src/daemon/host-transfer-proxy.ts +30 -24
  185. package/src/daemon/lifecycle.ts +40 -3
  186. package/src/daemon/message-protocol.ts +3 -0
  187. package/src/daemon/message-types/messages.ts +2 -10
  188. package/src/daemon/message-types/workflows.ts +49 -0
  189. package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
  190. package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
  191. package/src/daemon/process-message.ts +6 -0
  192. package/src/daemon/tool-setup-types.ts +57 -0
  193. package/src/daemon/wake-conversation-ops.ts +18 -0
  194. package/src/heartbeat/heartbeat-run-store.ts +8 -2
  195. package/src/home/feed-types.ts +1 -1
  196. package/src/ipc/gateway-flag-listener.ts +28 -6
  197. package/src/mcp/mcp-auth-state.ts +8 -20
  198. package/src/media/__tests__/image-models.test.ts +57 -0
  199. package/src/media/image-models.ts +66 -0
  200. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
  201. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
  202. package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
  203. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
  204. package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
  205. package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
  206. package/src/memory/auto-analysis-enqueue.ts +14 -1
  207. package/src/memory/compaction-log-store-clickhouse.ts +6 -4
  208. package/src/memory/conversation-crud.ts +9 -2
  209. package/src/memory/conversation-disk-view.ts +1 -1
  210. package/src/memory/conversation-queries.ts +22 -7
  211. package/src/memory/db-init.ts +20 -0
  212. package/src/memory/db-maintenance.ts +16 -0
  213. package/src/memory/embedding-runtime-manager.ts +1 -1
  214. package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
  215. package/src/memory/llm-request-log-source-local.ts +19 -1
  216. package/src/memory/llm-request-log-source.ts +35 -6
  217. package/src/memory/llm-request-log-store.ts +90 -2
  218. package/src/memory/memory-retrospective-constants.ts +9 -0
  219. package/src/memory/memory-retrospective-enqueue.ts +3 -6
  220. package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
  221. package/src/memory/memory-retrospective-job.ts +500 -208
  222. package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
  223. package/src/memory/memory-retrospective-state.ts +85 -2
  224. package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
  225. package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
  226. package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
  227. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
  228. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
  229. package/src/memory/migrations/284-workflow-runs.ts +51 -0
  230. package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
  231. package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
  232. package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
  233. package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
  234. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
  235. package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
  236. package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
  237. package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
  238. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
  239. package/src/memory/migrations/index.ts +10 -0
  240. package/src/memory/preloaded-apps.ts +116 -0
  241. package/src/memory/schema/infrastructure.ts +4 -0
  242. package/src/memory/schema/memory-core.ts +4 -0
  243. package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
  244. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
  245. package/src/memory/v2/__tests__/page-store.test.ts +13 -2
  246. package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
  247. package/src/memory/v2/frontmatter-sweep.ts +7 -6
  248. package/src/memory/v2/page-store.ts +4 -3
  249. package/src/memory/v2/qdrant.ts +42 -3
  250. package/src/memory/v2/types.ts +16 -10
  251. package/src/messaging/draft-store.ts +1 -1
  252. package/src/notifications/access-request-copy.ts +200 -113
  253. package/src/notifications/adapters/slack.ts +250 -111
  254. package/src/notifications/adapters/telegram.ts +7 -44
  255. package/src/notifications/approval-card-builder.ts +93 -0
  256. package/src/notifications/broadcaster.ts +74 -0
  257. package/src/notifications/conversation-pairing.ts +8 -6
  258. package/src/notifications/copy-composer.ts +32 -26
  259. package/src/notifications/decision-engine.ts +59 -7
  260. package/src/notifications/guardian-question-mode.ts +145 -155
  261. package/src/notifications/home-feed-side-effect.ts +28 -11
  262. package/src/notifications/notification-utils.ts +66 -0
  263. package/src/notifications/signal.ts +6 -0
  264. package/src/notifications/tool-approval-copy.ts +142 -0
  265. package/src/notifications/types.ts +19 -0
  266. package/src/permissions/threshold.ts +11 -0
  267. package/src/plugin-api/types.ts +16 -4
  268. package/src/plugins/defaults/compaction/window-manager.ts +44 -0
  269. package/src/plugins/defaults/index.ts +46 -0
  270. package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
  271. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
  272. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
  273. package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
  274. package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
  275. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
  276. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
  277. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
  278. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
  282. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
  283. package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
  284. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
  285. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
  286. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
  287. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
  288. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
  289. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
  290. package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
  291. package/src/prompts/__tests__/system-prompt.test.ts +100 -1
  292. package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
  293. package/src/prompts/normalize-onboarding.ts +2 -0
  294. package/src/prompts/persona-resolver.ts +3 -0
  295. package/src/prompts/system-prompt.ts +51 -2
  296. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
  297. package/src/prompts/templates/system-sections.ts +8 -3
  298. package/src/providers/call-site-routing.ts +6 -3
  299. package/src/providers/fireworks/client.ts +3 -0
  300. package/src/providers/inference/auth.ts +52 -46
  301. package/src/providers/model-intents.ts +2 -2
  302. package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
  303. package/src/providers/openai/chat-completions-provider.ts +47 -9
  304. package/src/providers/openai/coerce-object-args.ts +104 -0
  305. package/src/providers/retry.ts +8 -5
  306. package/src/providers/types.ts +10 -0
  307. package/src/runtime/__tests__/agent-wake.test.ts +629 -7
  308. package/src/runtime/access-request-helper.ts +16 -9
  309. package/src/runtime/agent-wake.ts +302 -51
  310. package/src/runtime/assistant-stream-state.ts +141 -8
  311. package/src/runtime/background-job-runner.ts +9 -0
  312. package/src/runtime/channel-approval-types.ts +1 -0
  313. package/src/runtime/channel-invite-transports/telegram.ts +6 -5
  314. package/src/runtime/channel-invite-transports/voice.ts +2 -2
  315. package/src/runtime/channel-invite-types.ts +4 -2
  316. package/src/runtime/channel-retry-sweep.ts +19 -41
  317. package/src/runtime/finalize-event-delivery.ts +72 -0
  318. package/src/runtime/guardian-action-message-composer.ts +0 -54
  319. package/src/runtime/http-server.ts +6 -14
  320. package/src/runtime/http-types.ts +0 -1
  321. package/src/runtime/message-composer-types.ts +0 -9
  322. package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
  323. package/src/runtime/middleware/auth.ts +27 -3
  324. package/src/runtime/middleware/rate-limiter.ts +28 -1
  325. package/src/runtime/migrations/vbundle-builder.ts +6 -5
  326. package/src/runtime/pending-interactions.ts +20 -1
  327. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
  328. package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
  329. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
  330. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
  331. package/src/runtime/routes/approval-routes.ts +35 -8
  332. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
  333. package/src/runtime/routes/btw-routes.ts +37 -1
  334. package/src/runtime/routes/channel-delivery-routes.ts +11 -7
  335. package/src/runtime/routes/channel-route-definitions.ts +3 -0
  336. package/src/runtime/routes/channel-route-shared.ts +3 -1
  337. package/src/runtime/routes/consolidation-routes.ts +17 -13
  338. package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
  339. package/src/runtime/routes/conversation-list-routes.ts +41 -4
  340. package/src/runtime/routes/conversation-query-routes.ts +196 -11
  341. package/src/runtime/routes/conversation-routes.ts +15 -1
  342. package/src/runtime/routes/credential-prompt-routes.ts +39 -17
  343. package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
  344. package/src/runtime/routes/heartbeat-routes.ts +18 -13
  345. package/src/runtime/routes/image-generation-routes.ts +20 -2
  346. package/src/runtime/routes/inbound-message-handler.ts +13 -12
  347. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +32 -31
  348. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +7 -5
  349. package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -21
  350. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
  351. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +6 -15
  352. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +1 -1
  353. package/src/runtime/routes/index.ts +6 -0
  354. package/src/runtime/routes/log-export/AGENTS.md +1 -1
  355. package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
  356. package/src/runtime/routes/migration-routes.ts +5 -9
  357. package/src/runtime/routes/plugins-routes.ts +26 -0
  358. package/src/runtime/routes/ps-routes.ts +10 -8
  359. package/src/runtime/routes/retrospective-routes.ts +235 -0
  360. package/src/runtime/routes/runs-pagination.ts +75 -0
  361. package/src/runtime/routes/schedule-routes.ts +367 -59
  362. package/src/runtime/routes/sounds-config-routes.ts +239 -0
  363. package/src/runtime/routes/surface-action-routes.ts +84 -4
  364. package/src/runtime/routes/workflow-routes.test.ts +372 -0
  365. package/src/runtime/routes/workflow-routes.ts +363 -0
  366. package/src/runtime/routes/workspace-routes.test.ts +61 -1
  367. package/src/runtime/routes/workspace-routes.ts +26 -1
  368. package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
  369. package/src/runtime/services/analyze-conversation.ts +26 -13
  370. package/src/schedule/inference-profile.ts +28 -0
  371. package/src/schedule/schedule-store.ts +152 -4
  372. package/src/schedule/scheduler-types.ts +6 -0
  373. package/src/schedule/scheduler.ts +96 -0
  374. package/src/security/secret-allowlist.ts +1 -1
  375. package/src/skills/path-classifier.ts +1 -1
  376. package/src/tools/apps/executors.ts +23 -0
  377. package/src/tools/apps/resolve-app-id.ts +42 -0
  378. package/src/tools/browser/browser-execution.ts +9 -11
  379. package/src/tools/credentials/broker.ts +4 -4
  380. package/src/tools/credentials/vault.ts +26 -137
  381. package/src/tools/executor.ts +69 -0
  382. package/src/tools/flag-gated-tools.test.ts +76 -0
  383. package/src/tools/permission-checker.ts +8 -1
  384. package/src/tools/registry.ts +51 -0
  385. package/src/tools/schedule/create.ts +77 -1
  386. package/src/tools/schedule/list.ts +1 -0
  387. package/src/tools/schedule/update.ts +73 -1
  388. package/src/tools/skills/execute.ts +56 -0
  389. package/src/tools/terminal/shell.ts +1 -1
  390. package/src/tools/ui-surface/definitions.ts +91 -2
  391. package/src/tools/workflows/manage-workflows.ts +183 -0
  392. package/src/tools/workflows/run-workflow.test.ts +442 -0
  393. package/src/tools/workflows/run-workflow.ts +88 -0
  394. package/src/types/onboarding-context.ts +2 -0
  395. package/src/usage/attribution.ts +24 -0
  396. package/src/util/canonicalize-identity.ts +12 -3
  397. package/src/util/platform.ts +17 -17
  398. package/src/watcher/__tests__/engine.test.ts +24 -0
  399. package/src/watcher/__tests__/telemetry.test.ts +135 -0
  400. package/src/watcher/engine.ts +7 -0
  401. package/src/watcher/telemetry.ts +74 -0
  402. package/src/workflows/capabilities.test.ts +365 -0
  403. package/src/workflows/capabilities.ts +359 -0
  404. package/src/workflows/deterministic-stringify.ts +27 -0
  405. package/src/workflows/engine-integration.test.ts +656 -0
  406. package/src/workflows/engine.test.ts +1144 -0
  407. package/src/workflows/engine.ts +1078 -0
  408. package/src/workflows/fanout-load.test.ts +168 -0
  409. package/src/workflows/journal-store.test.ts +369 -0
  410. package/src/workflows/journal-store.ts +470 -0
  411. package/src/workflows/leaf-runner.test.ts +704 -0
  412. package/src/workflows/leaf-runner.ts +589 -0
  413. package/src/workflows/library.test.ts +134 -0
  414. package/src/workflows/library.ts +124 -0
  415. package/src/workflows/run-manager.test.ts +711 -0
  416. package/src/workflows/run-manager.ts +593 -0
  417. package/src/workflows/sandbox-escape.test.ts +339 -0
  418. package/src/workflows/sandbox.test.ts +251 -0
  419. package/src/workflows/sandbox.ts +447 -0
  420. package/src/workspace/adaptive-thinking-repair.ts +33 -11
  421. package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
  422. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
  423. package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
  424. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
  425. package/src/workspace/migrations/031-drop-user-md.ts +1 -4
  426. package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
  427. package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
  428. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
  429. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
  430. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
  431. package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
  432. package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
  433. package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
  434. package/src/workspace/migrations/registry.ts +6 -0
  435. package/src/workspace/migrations/runner.ts +1 -1
  436. package/tsconfig.json +1 -1
  437. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
  438. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
  439. package/src/__tests__/guardian-action-store.test.ts +0 -106
  440. package/src/daemon/guardian-action-generators.ts +0 -71
  441. package/src/memory/guardian-action-store.ts +0 -484
  442. package/src/runtime/guardian-action-grant-minter.ts +0 -150
@@ -0,0 +1,168 @@
1
+ /**
2
+ * OPT-IN, real-provider fan-out load & cost guard for the workflow engine.
3
+ *
4
+ * ─── Manual / opt-in only ───────────────────────────────────────────────────
5
+ * This suite is SKIPPED unless `ANTHROPIC_API_KEY` is set. When a key IS
6
+ * present it makes ~200 REAL leaf provider calls (one forced-schema
7
+ * `workflowLeaf` call per leaf), so it is never run in CI. Run it by hand
8
+ * when you want to validate the engine against the live provider:
9
+ *
10
+ * ANTHROPIC_API_KEY=sk-... bun test src/workflows/fanout-load.test.ts
11
+ *
12
+ * Cost: each leaf is a tiny Haiku-class (cost-optimized) structured-output
13
+ * call with a small per-leaf context. One full run is ~200 such calls and
14
+ * typically costs a few US cents (well under $0.25). The assertion band below
15
+ * caps it at $1 as a blunt runaway-cost tripwire — if a run ever approaches
16
+ * that, something is mis-resolved (wrong model/profile) and the guard should
17
+ * fail loudly.
18
+ *
19
+ * ─── What it guards against ─────────────────────────────────────────────────
20
+ * It reproduces the shape that previously caused rate-limit collapse: many
21
+ * concurrent leaves with non-trivial per-leaf context. The historical failure
22
+ * mode was a `StructuredOutput` fast-fail cluster — leaves returning null /
23
+ * zero output tokens en masse, sub-minute "completion", because the token/min
24
+ * ceiling was blown by an over-wide fan-out. At the default
25
+ * `maxConcurrentLeaves: 6` the engine throttles the fan-out, so the collapse
26
+ * must NOT recur. This test asserts that: every leaf returns real non-zero
27
+ * output, the run completes (not `cap_exceeded` / `failed`), wall-clock lands
28
+ * in a generous band, and summed cost stays within a sane envelope.
29
+ */
30
+
31
+ import { describe, expect, test } from "bun:test";
32
+
33
+ import { z } from "zod";
34
+
35
+ import { getConfig } from "../config/loader.js";
36
+ import type { TrustContext } from "../daemon/trust-context.js";
37
+ import { initializeDb } from "../memory/db-init.js";
38
+ import { listUsageEvents } from "../memory/llm-usage-store.js";
39
+ import { resolveCapabilities } from "./capabilities.js";
40
+ import { executeWorkflow } from "./engine.js";
41
+ import * as journalStore from "./journal-store.js";
42
+ import { runLeaf } from "./leaf-runner.js";
43
+
44
+ const apiKey = process.env.ANTHROPIC_API_KEY;
45
+
46
+ // Number of concurrent leaves the historical collapse fanned out over; the
47
+ // guard runs the same shape at the engine's default concurrency.
48
+ const LEAF_COUNT = 200;
49
+
50
+ // Generous wall-clock ceiling. At concurrency 6 with ~200 short leaves this
51
+ // is well under a couple minutes in practice; the band only fails on a true
52
+ // stall, not on normal provider jitter.
53
+ const MAX_WALL_CLOCK_MS = 8 * 60_000;
54
+
55
+ // Runaway-cost tripwire (see header). A healthy run is a few cents.
56
+ const MAX_COST_USD = 1;
57
+
58
+ const TRUST: TrustContext = { sourceChannel: "vellum", trustClass: "guardian" };
59
+
60
+ describe.skipIf(!apiKey)(
61
+ "Workflow fan-out load & cost guard — real provider",
62
+ () => {
63
+ test(
64
+ "200-leaf fan-out at default concurrency does not rate-limit-collapse",
65
+ async () => {
66
+ initializeDb();
67
+
68
+ // Non-trivial per-leaf context mirrors the real fan-out shape: each
69
+ // leaf judges a distinct synthetic record, not a one-token prompt.
70
+ const items = Array.from({ length: LEAF_COUNT }, (_, i) => ({
71
+ id: i,
72
+ subject: `synthetic-record-${i}`,
73
+ body:
74
+ `Incident report #${i}. A background job processed batch ${i % 7} ` +
75
+ `with status code ${200 + (i % 5)} and emitted ${3 + (i % 11)} ` +
76
+ `warnings about resource contention on shard ${i % 13}. The ` +
77
+ `operator noted latency drift of ${(i % 9) * 4}ms over the window.`,
78
+ }));
79
+
80
+ // Schema-forced judgement per leaf: a small structured output that
81
+ // forces a real `workflowLeaf` provider call and a non-empty result.
82
+ const judgement = z.object({
83
+ severity: z.enum(["low", "medium", "high"]),
84
+ summary: z.string(),
85
+ });
86
+
87
+ const scriptSource = `
88
+ export const meta = {
89
+ name: "fanout-load-guard",
90
+ description: "200-leaf real-provider fan-out load & cost guard",
91
+ };
92
+
93
+ const results = map(args.items, (item) =>
94
+ leaf(
95
+ "Classify the severity of this incident and summarize it in one short " +
96
+ "sentence.\\n\\n" +
97
+ "Subject: " + item.subject + "\\n" +
98
+ "Body: " + item.body,
99
+ { schema: args.schema, label: "judge-" + item.id },
100
+ ),
101
+ );
102
+
103
+ return results;
104
+ `;
105
+
106
+ const runId = `fanout-load-${Date.now()}`;
107
+ const startedAt = Date.now();
108
+
109
+ const result = await executeWorkflow({
110
+ runId,
111
+ scriptSource,
112
+ args: { items, schema: judgement },
113
+ capabilities: resolveCapabilities({
114
+ tools: [],
115
+ hostFunctions: [],
116
+ persona: false,
117
+ }),
118
+ config: getConfig().workflows,
119
+ journal: journalStore,
120
+ leafRunner: runLeaf,
121
+ trustContext: TRUST,
122
+ });
123
+
124
+ const wallClockMs = Date.now() - startedAt;
125
+
126
+ // The engine must have throttled, not collapsed: the run completes.
127
+ expect(result.status).toBe("completed");
128
+ expect(result.agentsSpawned).toBe(LEAF_COUNT);
129
+
130
+ // No `StructuredOutput` fast-fail cluster: every leaf returns a real,
131
+ // schema-valid, non-null result (the collapse manifested as all-null).
132
+ const leafResults = result.result as Array<z.infer<
133
+ typeof judgement
134
+ > | null>;
135
+ expect(Array.isArray(leafResults)).toBe(true);
136
+ expect(leafResults).toHaveLength(LEAF_COUNT);
137
+ const nulls = leafResults.filter((r) => r == null);
138
+ expect(nulls).toHaveLength(0);
139
+ const invalid = leafResults.filter(
140
+ (r) => !judgement.safeParse(r).success,
141
+ );
142
+ expect(invalid).toHaveLength(0);
143
+
144
+ // Real, non-zero output tokens in aggregate (the collapse zeroed these
145
+ // out as leaves fast-failed before producing any tokens).
146
+ expect(result.outputTokens).toBeGreaterThan(0);
147
+ expect(result.inputTokens).toBeGreaterThan(0);
148
+
149
+ // Wall-clock within a generous band: not a sub-second mass-fail, and
150
+ // not an indefinite stall.
151
+ expect(wallClockMs).toBeLessThan(MAX_WALL_CLOCK_MS);
152
+
153
+ // Summed cost over this run's `workflowLeaf` usage events stays within
154
+ // a sane envelope. Filter by call site + the run's start time so we
155
+ // only sum events this run produced (the table is shared).
156
+ const events = listUsageEvents({ limit: 10_000 });
157
+ const runCostUsd = events
158
+ .filter(
159
+ (e) => e.callSite === "workflowLeaf" && e.createdAt >= startedAt,
160
+ )
161
+ .reduce((sum, e) => sum + (e.estimatedCostUsd ?? 0), 0);
162
+ expect(runCostUsd).toBeGreaterThan(0);
163
+ expect(runCostUsd).toBeLessThan(MAX_COST_USD);
164
+ },
165
+ MAX_WALL_CLOCK_MS + 60_000,
166
+ );
167
+ },
168
+ );
@@ -0,0 +1,369 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ // Silence the logger.
4
+ mock.module("../util/logger.js", () => ({
5
+ getLogger: () =>
6
+ new Proxy({} as Record<string, unknown>, {
7
+ get: () => () => {},
8
+ }),
9
+ }));
10
+
11
+ import { getDb, getSqlite } from "../memory/db-connection.js";
12
+ import { initializeDb } from "../memory/db-init.js";
13
+ import { migrateWorkflowRuns } from "../memory/migrations/284-workflow-runs.js";
14
+ import {
15
+ appendJournalEntry,
16
+ createRun,
17
+ finishRun,
18
+ getJournal,
19
+ getRun,
20
+ listRuns,
21
+ pruneRuns,
22
+ updateRun,
23
+ } from "./journal-store.js";
24
+
25
+ initializeDb();
26
+
27
+ const DAY_MS = 24 * 60 * 60 * 1000;
28
+
29
+ function resetTables(): void {
30
+ getSqlite().exec("DELETE FROM workflow_journal");
31
+ getSqlite().exec("DELETE FROM workflow_runs");
32
+ }
33
+
34
+ describe("workflow journal store", () => {
35
+ beforeEach(() => {
36
+ resetTables();
37
+ });
38
+
39
+ test("the migration is idempotent (re-running is a no-op)", () => {
40
+ // initializeDb (called at module load) already ran the migration once.
41
+ // Running it directly a second and third time must not throw and must
42
+ // leave the tables intact and usable.
43
+ expect(() => migrateWorkflowRuns(getDb())).not.toThrow();
44
+ expect(() => migrateWorkflowRuns(getDb())).not.toThrow();
45
+
46
+ createRun({
47
+ id: "wf-idem",
48
+ scriptSource: "console.log(1)",
49
+ scriptHash: "h1",
50
+ });
51
+ expect(getRun("wf-idem")?.id).toBe("wf-idem");
52
+ });
53
+
54
+ test("createRun round-trips all fields with sane defaults", () => {
55
+ const run = createRun({
56
+ id: "wf-1",
57
+ name: "nightly digest",
58
+ scriptSource: "await agent('x')",
59
+ scriptHash: "hash-abc",
60
+ args: { topic: "ai" },
61
+ capabilities: ["search", "email"],
62
+ conversationId: "conv-xyz",
63
+ trust: { sourceChannel: "slack", trustClass: "unknown" },
64
+ });
65
+
66
+ expect(run).toMatchObject({
67
+ id: "wf-1",
68
+ name: "nightly digest",
69
+ scriptSource: "await agent('x')",
70
+ scriptHash: "hash-abc",
71
+ args: { topic: "ai" },
72
+ capabilities: ["search", "email"],
73
+ status: "running",
74
+ conversationId: "conv-xyz",
75
+ trust: { sourceChannel: "slack", trustClass: "unknown" },
76
+ agentsSpawned: 0,
77
+ inputTokens: 0,
78
+ outputTokens: 0,
79
+ result: null,
80
+ error: null,
81
+ finishedAt: null,
82
+ });
83
+ expect(run.createdAt).toBeGreaterThan(0);
84
+ expect(run.updatedAt).toBeGreaterThan(0);
85
+
86
+ expect(getRun("wf-1")).toEqual(run);
87
+ });
88
+
89
+ test("optional fields default to null", () => {
90
+ const run = createRun({
91
+ id: "wf-min",
92
+ scriptSource: "noop",
93
+ scriptHash: "h",
94
+ });
95
+ expect(run).toMatchObject({
96
+ name: null,
97
+ args: null,
98
+ capabilities: null,
99
+ conversationId: null,
100
+ trust: null,
101
+ });
102
+ });
103
+
104
+ test("updateRun patches only provided fields and bumps updated_at", () => {
105
+ const created = createRun({
106
+ id: "wf-2",
107
+ scriptSource: "s",
108
+ scriptHash: "h",
109
+ });
110
+
111
+ const updated = updateRun("wf-2", {
112
+ status: "running",
113
+ agentsSpawned: 3,
114
+ inputTokens: 100,
115
+ outputTokens: 50,
116
+ conversationId: "conv-new",
117
+ });
118
+
119
+ expect(updated).toMatchObject({
120
+ agentsSpawned: 3,
121
+ inputTokens: 100,
122
+ outputTokens: 50,
123
+ conversationId: "conv-new",
124
+ // Untouched fields preserved.
125
+ scriptSource: "s",
126
+ scriptHash: "h",
127
+ });
128
+ expect(updated!.updatedAt!).toBeGreaterThanOrEqual(created.updatedAt!);
129
+ });
130
+
131
+ test("updateRun returns null for an unknown run", () => {
132
+ expect(updateRun("nope", { status: "failed" })).toBeNull();
133
+ });
134
+
135
+ test("finishRun stamps terminal status, result, and finished_at", () => {
136
+ createRun({ id: "wf-3", scriptSource: "s", scriptHash: "h" });
137
+
138
+ const finished = finishRun("wf-3", {
139
+ status: "completed",
140
+ result: { ok: true, count: 2 },
141
+ });
142
+
143
+ expect(finished).toMatchObject({
144
+ status: "completed",
145
+ result: { ok: true, count: 2 },
146
+ error: null,
147
+ });
148
+ expect(finished!.finishedAt).toBeGreaterThan(0);
149
+ });
150
+
151
+ test("finishRun records failure error text", () => {
152
+ createRun({ id: "wf-fail", scriptSource: "s", scriptHash: "h" });
153
+ const finished = finishRun("wf-fail", {
154
+ status: "failed",
155
+ error: "boom",
156
+ });
157
+ expect(finished).toMatchObject({ status: "failed", error: "boom" });
158
+ });
159
+
160
+ test("appendJournalEntry + getJournal round-trips in seq order", () => {
161
+ createRun({ id: "wf-j", scriptSource: "s", scriptHash: "h" });
162
+
163
+ appendJournalEntry({
164
+ runId: "wf-j",
165
+ seq: 1,
166
+ callHash: "c1",
167
+ kind: "agent",
168
+ request: { prompt: "a" },
169
+ result: { text: "ra" },
170
+ status: "completed",
171
+ });
172
+ appendJournalEntry({
173
+ runId: "wf-j",
174
+ seq: 0,
175
+ callHash: "c0",
176
+ kind: "agent",
177
+ request: { prompt: "b" },
178
+ result: 123,
179
+ status: "completed",
180
+ });
181
+
182
+ const journal = getJournal("wf-j");
183
+ expect(journal.map((e) => e.seq)).toEqual([0, 1]);
184
+ expect(journal[0]).toMatchObject({
185
+ runId: "wf-j",
186
+ seq: 0,
187
+ callHash: "c0",
188
+ kind: "agent",
189
+ request: { prompt: "b" },
190
+ result: 123,
191
+ status: "completed",
192
+ });
193
+ expect(journal[1]).toMatchObject({
194
+ kind: "agent",
195
+ request: { prompt: "a" },
196
+ result: { text: "ra" },
197
+ });
198
+ });
199
+
200
+ test("appendJournalEntry is idempotent on (run_id, seq)", () => {
201
+ createRun({ id: "wf-dup", scriptSource: "s", scriptHash: "h" });
202
+
203
+ appendJournalEntry({
204
+ runId: "wf-dup",
205
+ seq: 0,
206
+ callHash: "c0",
207
+ kind: "agent",
208
+ status: "completed",
209
+ });
210
+ // Replayed append for the same seq must not double-insert.
211
+ appendJournalEntry({
212
+ runId: "wf-dup",
213
+ seq: 0,
214
+ callHash: "c0",
215
+ kind: "agent",
216
+ status: "completed",
217
+ });
218
+
219
+ expect(getJournal("wf-dup")).toHaveLength(1);
220
+ });
221
+
222
+ test("appendJournalEntry upserts a changed-hash re-run at the same seq", () => {
223
+ createRun({ id: "wf-resume", scriptSource: "s", scriptHash: "h" });
224
+
225
+ // First run: leaf at seq 0 produces one (hash, result).
226
+ appendJournalEntry({
227
+ runId: "wf-resume",
228
+ seq: 0,
229
+ callHash: "hash-old",
230
+ kind: "agent",
231
+ request: { prompt: "old" },
232
+ result: { text: "old-result" },
233
+ status: "completed",
234
+ });
235
+
236
+ // Resume: the leaf's input CHANGED, so it re-runs and re-appends a new
237
+ // (hash, result) at the SAME seq. The stale row must be overwritten.
238
+ appendJournalEntry({
239
+ runId: "wf-resume",
240
+ seq: 0,
241
+ callHash: "hash-new",
242
+ kind: "agent",
243
+ request: { prompt: "new" },
244
+ result: { text: "new-result" },
245
+ status: "completed",
246
+ });
247
+
248
+ const journal = getJournal("wf-resume");
249
+ // Still exactly one row at seq 0 (no duplicate).
250
+ expect(journal).toHaveLength(1);
251
+ expect(journal[0]).toMatchObject({
252
+ seq: 0,
253
+ callHash: "hash-new",
254
+ request: { prompt: "new" },
255
+ result: { text: "new-result" },
256
+ status: "completed",
257
+ });
258
+ });
259
+
260
+ test("listRuns returns newest-first and honors limit + status filter", () => {
261
+ // created_at is wall-clock; force distinct ordering via direct timestamps.
262
+ createRun({ id: "old", scriptSource: "s", scriptHash: "h" });
263
+ getSqlite().exec(
264
+ "UPDATE workflow_runs SET created_at = 1000 WHERE id = 'old'",
265
+ );
266
+ createRun({ id: "mid", scriptSource: "s", scriptHash: "h" });
267
+ getSqlite().exec(
268
+ "UPDATE workflow_runs SET created_at = 2000 WHERE id = 'mid'",
269
+ );
270
+ createRun({ id: "new", scriptSource: "s", scriptHash: "h" });
271
+ getSqlite().exec(
272
+ "UPDATE workflow_runs SET created_at = 3000 WHERE id = 'new'",
273
+ );
274
+ finishRun("new", { status: "completed" });
275
+
276
+ expect(listRuns({ limit: 10 }).map((r) => r.id)).toEqual([
277
+ "new",
278
+ "mid",
279
+ "old",
280
+ ]);
281
+ expect(listRuns({ limit: 1 }).map((r) => r.id)).toEqual(["new"]);
282
+ expect(
283
+ listRuns({ limit: 10, status: "completed" }).map((r) => r.id),
284
+ ).toEqual(["new"]);
285
+ });
286
+
287
+ test("pruneRuns deletes finished runs past retention but keeps running ones", () => {
288
+ const now = Date.now();
289
+
290
+ // Old finished run — should be pruned.
291
+ createRun({ id: "old-done", scriptSource: "s", scriptHash: "h" });
292
+ finishRun("old-done", { status: "completed" });
293
+ appendJournalEntry({
294
+ runId: "old-done",
295
+ seq: 0,
296
+ callHash: "c",
297
+ kind: "agent",
298
+ status: "completed",
299
+ });
300
+ getSqlite().exec(
301
+ `UPDATE workflow_runs SET created_at = ${now - 10 * DAY_MS} WHERE id = 'old-done'`,
302
+ );
303
+
304
+ // Old but still running — must be kept.
305
+ createRun({ id: "old-running", scriptSource: "s", scriptHash: "h" });
306
+ getSqlite().exec(
307
+ `UPDATE workflow_runs SET created_at = ${now - 10 * DAY_MS} WHERE id = 'old-running'`,
308
+ );
309
+
310
+ // Recent finished run — within retention, kept.
311
+ createRun({ id: "recent-done", scriptSource: "s", scriptHash: "h" });
312
+ finishRun("recent-done", { status: "completed" });
313
+
314
+ const deleted = pruneRuns(7);
315
+ expect(deleted).toBe(1);
316
+ expect(getRun("old-done")).toBeNull();
317
+ expect(getJournal("old-done")).toHaveLength(0);
318
+ expect(getRun("old-running")).not.toBeNull();
319
+ expect(getRun("recent-done")).not.toBeNull();
320
+ });
321
+
322
+ test("pruneRuns never reaps a resumable 'interrupted' run, even past retention", () => {
323
+ const now = Date.now();
324
+
325
+ // Old interrupted run (crash-orphaned, awaiting resume) with a journal —
326
+ // pruning it would destroy resumability, so it MUST be kept.
327
+ createRun({ id: "old-interrupted", scriptSource: "s", scriptHash: "h" });
328
+ updateRun("old-interrupted", { status: "interrupted" });
329
+ appendJournalEntry({
330
+ runId: "old-interrupted",
331
+ seq: 0,
332
+ callHash: "c",
333
+ kind: "agent",
334
+ status: "completed",
335
+ });
336
+ getSqlite().exec(
337
+ `UPDATE workflow_runs SET created_at = ${now - 100 * DAY_MS} WHERE id = 'old-interrupted'`,
338
+ );
339
+
340
+ const deleted = pruneRuns(7);
341
+ expect(deleted).toBe(0);
342
+ expect(getRun("old-interrupted")).not.toBeNull();
343
+ // Its journal survives too, so resume can still replay.
344
+ expect(getJournal("old-interrupted")).toHaveLength(1);
345
+ });
346
+
347
+ test("pruneRuns reaps every TERMINAL status past retention", () => {
348
+ const now = Date.now();
349
+ const terminal = [
350
+ "completed",
351
+ "failed",
352
+ "aborted",
353
+ "cap_exceeded",
354
+ ] as const;
355
+ for (const status of terminal) {
356
+ createRun({ id: `old-${status}`, scriptSource: "s", scriptHash: "h" });
357
+ finishRun(`old-${status}`, { status });
358
+ getSqlite().exec(
359
+ `UPDATE workflow_runs SET created_at = ${now - 10 * DAY_MS} WHERE id = 'old-${status}'`,
360
+ );
361
+ }
362
+
363
+ const deleted = pruneRuns(7);
364
+ expect(deleted).toBe(terminal.length);
365
+ for (const status of terminal) {
366
+ expect(getRun(`old-${status}`)).toBeNull();
367
+ }
368
+ });
369
+ });