@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
@@ -1,4 +1,4 @@
1
- import { and, count, desc, eq, inArray, isNull, sql } from "drizzle-orm";
1
+ import { and, count, desc, eq, inArray, isNull, lt, sql } from "drizzle-orm";
2
2
 
3
3
  import {
4
4
  parseExternalContentEnvelope,
@@ -166,13 +166,17 @@ export function listConversations(
166
166
  conversationType: ConversationType = "standard",
167
167
  offset = 0,
168
168
  archiveStatus: ArchiveStatusFilter = "active",
169
+ originChannel?: string,
169
170
  ): ConversationRow[] {
170
171
  ensureDisplayOrderMigration();
171
172
  ensureGroupMigration();
172
173
  const db = getDb();
173
174
  const typeCond = conversationTypeClause(conversationType);
174
175
  const archiveCond = archiveStatusClause(archiveStatus);
175
- const where = archiveCond ? sql`${typeCond} AND ${archiveCond}` : typeCond;
176
+ const channelCond = originChannel
177
+ ? eq(conversations.originChannel, originChannel)
178
+ : undefined;
179
+ const where = and(typeCond, archiveCond ?? undefined, channelCond);
176
180
  const query = db
177
181
  .select()
178
182
  .from(conversations)
@@ -208,17 +212,24 @@ export function listConversations(
208
212
  * Defaults to `true` so callers that want a full
209
213
  * run history get one; pass `false` for views
210
214
  * that hide archived rows.
215
+ * @param opts.beforeCreatedAt Only return rows with `createdAt` strictly
216
+ * older than this epoch-millis cursor (for
217
+ * paginating into history).
211
218
  */
212
219
  export function listConversationsBySource(
213
220
  source: string,
214
221
  limit = 20,
215
- opts?: { includeArchived?: boolean },
222
+ opts?: { includeArchived?: boolean; beforeCreatedAt?: number },
216
223
  ): ConversationRow[] {
217
224
  const db = getDb();
218
225
  const includeArchived = opts?.includeArchived ?? true;
219
- const where = includeArchived
220
- ? eq(conversations.source, source)
221
- : and(eq(conversations.source, source), isNull(conversations.archivedAt));
226
+ const where = and(
227
+ eq(conversations.source, source),
228
+ includeArchived ? undefined : isNull(conversations.archivedAt),
229
+ opts?.beforeCreatedAt != null
230
+ ? lt(conversations.createdAt, opts.beforeCreatedAt)
231
+ : undefined,
232
+ );
222
233
  const rows = db
223
234
  .select()
224
235
  .from(conversations)
@@ -361,12 +372,16 @@ export function listConversationsByTitlePrefix(
361
372
  export function countConversations(
362
373
  conversationType: ConversationType = "standard",
363
374
  archiveStatus: ArchiveStatusFilter = "active",
375
+ originChannel?: string,
364
376
  ): number {
365
377
  ensureGroupMigration();
366
378
  const db = getDb();
367
379
  const typeCond = conversationTypeClause(conversationType);
368
380
  const archiveCond = archiveStatusClause(archiveStatus);
369
- const where = archiveCond ? sql`${typeCond} AND ${archiveCond}` : typeCond;
381
+ const channelCond = originChannel
382
+ ? eq(conversations.originChannel, originChannel)
383
+ : undefined;
384
+ const where = and(typeCond, archiveCond ?? undefined, channelCond);
370
385
  const [{ total }] = db
371
386
  .select({ total: count() })
372
387
  .from(conversations)
@@ -55,6 +55,7 @@ import {
55
55
  migrateBackfillContactInteractionStats,
56
56
  migrateBackfillGuardianPrincipalId,
57
57
  migrateBackfillInlineAttachmentsToDisk,
58
+ migrateBackfillOriginChannelFromBindings,
58
59
  migrateBackfillProviderConnectionLabel,
59
60
  migrateBackfillUsageCacheAccounting,
60
61
  migrateCallSessionInviteMetadata,
@@ -68,6 +69,7 @@ import {
68
69
  migrateChannelInteractionColumns,
69
70
  migrateContactChannelsAccessFields,
70
71
  migrateContactChannelsTypeChatIdIndex,
72
+ migrateContactChannelsUniqueExtUser,
71
73
  migrateContactsAssistantId,
72
74
  migrateContactsNotesColumn,
73
75
  migrateContactsRolePrincipal,
@@ -77,6 +79,7 @@ import {
77
79
  migrateConversationHostAccess,
78
80
  migrateConversationInferenceProfileSession,
79
81
  migrateConversationLastNotifiedProfile,
82
+ migrateConversationOriginChannelIndex,
80
83
  migrateConversationsArchivedAt,
81
84
  migrateConversationsLastMessageAt,
82
85
  migrateConversationsSurfacedAt,
@@ -136,11 +139,13 @@ import {
136
139
  migrateMemoryGraphImageRefs,
137
140
  migrateMemoryItemSupersession,
138
141
  migrateMemoryRecallLogsQueryContext,
142
+ migrateMemoryRetrospectiveRememberedLog,
139
143
  migrateMemoryRetrospectiveState,
140
144
  migrateMemoryV2ActivationLogs,
141
145
  migrateMemoryV2InjectionEvents,
142
146
  migrateMemoryV3AutoEdges,
143
147
  migrateMemoryV3Coactivation,
148
+ migrateMemoryV3SelectionsMessageIdAndSections,
144
149
  migrateMessageBookmarks,
145
150
  migrateMessagesClientMessageId,
146
151
  migrateMessagesConversationCreatedAtIndex,
@@ -189,7 +194,9 @@ import {
189
194
  migrateRenameVerificationSessionIdColumn,
190
195
  migrateRenameVerificationTable,
191
196
  migrateRenameVoiceToPhone,
197
+ migrateScheduleCapabilities,
192
198
  migrateScheduleDescription,
199
+ migrateScheduleInferenceProfile,
193
200
  migrateScheduleOneShotRouting,
194
201
  migrateScheduleQuietFlag,
195
202
  migrateScheduleRetryPolicy,
@@ -198,6 +205,7 @@ import {
198
205
  migrateScheduleScriptTimeout,
199
206
  migrateScheduleSourceConversation,
200
207
  migrateScheduleWakeConversationId,
208
+ migrateScheduleWorkflowMode,
201
209
  migrateSchemaIndexesAndColumns,
202
210
  migrateScrubCorruptedImageAttachments,
203
211
  migrateSlackCompactionWatermark,
@@ -214,6 +222,8 @@ import {
214
222
  migrateUsageLlmCallCount,
215
223
  migrateVoiceInviteColumns,
216
224
  migrateVoiceInviteDisplayMetadata,
225
+ migrateWorkflowRuns,
226
+ migrateWorkflowRunTrust,
217
227
  recoverCrashedMigrations,
218
228
  runComplexMigrations,
219
229
  runLateMigrations,
@@ -498,6 +508,16 @@ export function initializeDb(): void {
498
508
  migrateToolInvocationsTelemetryColumns,
499
509
  createSkillLoadedEventsTable,
500
510
  migrateConversationsSurfacedAt,
511
+ migrateMemoryRetrospectiveRememberedLog,
512
+ migrateScheduleInferenceProfile,
513
+ migrateMemoryV3SelectionsMessageIdAndSections,
514
+ migrateWorkflowRuns,
515
+ migrateScheduleWorkflowMode,
516
+ migrateWorkflowRunTrust,
517
+ migrateConversationOriginChannelIndex,
518
+ migrateBackfillOriginChannelFromBindings,
519
+ migrateContactChannelsUniqueExtUser,
520
+ migrateScheduleCapabilities,
501
521
  ];
502
522
 
503
523
  // Run each migration step, catching and logging individual failures so one
@@ -3,6 +3,7 @@ import { statSync } from "node:fs";
3
3
  import { getConfig } from "../config/loader.js";
4
4
  import { getLogger } from "../util/logger.js";
5
5
  import { getDbPath } from "../util/platform.js";
6
+ import { pruneRuns } from "../workflows/journal-store.js";
6
7
  import { getMemoryCheckpoint, setMemoryCheckpoint } from "./checkpoints.js";
7
8
  import { getLastUserMessageTimestamp } from "./conversation-crud.js";
8
9
  import { runAsyncSqlite } from "./db-async-query.js";
@@ -56,6 +57,21 @@ async function runDbMaintenance(): Promise<void> {
56
57
  "Starting database maintenance",
57
58
  );
58
59
 
60
+ // Prune finished workflow runs (and their journals) past the retention
61
+ // window BEFORE VACUUM so the freed pages are reclaimed in the same pass.
62
+ // This is a fast bounded DELETE on the small workflow tables, so it runs on
63
+ // the main connection (`rawRun`) — unlike VACUUM/optimize it doesn't need the
64
+ // async/subprocess path. The maintenance scheduler below already defers this
65
+ // whole routine to an idle window.
66
+ try {
67
+ const deletedRuns = pruneRuns(getConfig().workflows.journalRetentionDays);
68
+ if (deletedRuns > 0) {
69
+ log.info({ deletedRuns }, "Pruned expired workflow runs");
70
+ }
71
+ } catch (err) {
72
+ log.warn({ err }, "Workflow run pruning failed (non-fatal)");
73
+ }
74
+
59
75
  // VACUUM is the long-running one — minutes on a multi-GB DB. PRAGMA
60
76
  // optimize is fast but routed through the same async path for
61
77
  // consistency and to keep both off the main thread when the CLI
@@ -7,7 +7,7 @@
7
7
  * imported files, so we spawn a standalone bun process that runs an embed
8
8
  * worker script communicating via JSON-lines over stdin/stdout.
9
9
  *
10
- * The runtime is stored in ~/.vellum/workspace/embedding-models/ and used
10
+ * The runtime is stored in $VELLUM_WORKSPACE_DIR/embedding-models/ and used
11
11
  * by embedding-local.ts on demand.
12
12
  *
13
13
  * Follows the same download/install pattern as qdrant-manager.ts.
@@ -25,7 +25,11 @@ import {
25
25
  messageMetadataSchema,
26
26
  } from "./conversation-crud.js";
27
27
  import type { LlmRequestLogSource } from "./llm-request-log-source.js";
28
- import type { LogRow } from "./llm-request-log-store.js";
28
+ import type {
29
+ CompactionAgentLogRow,
30
+ LogMetaRow,
31
+ LogRow,
32
+ } from "./llm-request-log-store.js";
29
33
 
30
34
  const log = getLogger("clickhouse-llm-request-log-source");
31
35
 
@@ -70,6 +74,23 @@ interface ClickHouseRow {
70
74
  call_site: string;
71
75
  }
72
76
 
77
+ /** Metadata-only wire row — `ClickHouseRow` without the payload columns. */
78
+ type ClickHouseMetaRow = Omit<
79
+ ClickHouseRow,
80
+ "request_payload" | "response_payload"
81
+ >;
82
+
83
+ /**
84
+ * Wire row for the compaction-trail query: metadata plus the summarizer
85
+ * response payload and a message count computed server-side. ClickHouse's
86
+ * `JSONLength` returns 0 for missing/invalid paths; `exec` callers map the
87
+ * `nullif(…, 0)` result, so the field arrives as `number | null`.
88
+ */
89
+ type ClickHouseCompactionRow = ClickHouseMetaRow & {
90
+ response_payload: string;
91
+ request_message_count: number | string | null;
92
+ };
93
+
73
94
  /** Injectable fetch override for tests. Defaults to globalThis.fetch. */
74
95
  export type ClickHouseFetch = typeof fetch;
75
96
 
@@ -143,10 +164,36 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
143
164
  ORDER BY created_at DESC
144
165
  LIMIT 1
145
166
  FORMAT JSONEachRow`;
146
- const rows = await this.exec(sql, { assistant_id: aid, log_id: logId });
167
+ const rows = await this.exec<ClickHouseRow>(sql, {
168
+ assistant_id: aid,
169
+ log_id: logId,
170
+ });
147
171
  return rows[0] ? this.toLogRow(rows[0]) : null;
148
172
  }
149
173
 
174
+ async getRequestLogMetaById(logId: string): Promise<LogMetaRow | null> {
175
+ const aid = await this.assistantId();
176
+ const sql = `SELECT
177
+ id,
178
+ conversation_id,
179
+ message_id,
180
+ provider,
181
+ toUnixTimestamp64Milli(created_at) AS created_at,
182
+ agent_loop_exit_reason,
183
+ call_site
184
+ FROM ${this.tableRef()}
185
+ WHERE assistant_id = {assistant_id:String}
186
+ AND id = {log_id:String}
187
+ ORDER BY created_at DESC
188
+ LIMIT 1
189
+ FORMAT JSONEachRow`;
190
+ const rows = await this.exec<ClickHouseMetaRow>(sql, {
191
+ assistant_id: aid,
192
+ log_id: logId,
193
+ });
194
+ return rows[0] ? this.toLogMetaRow(rows[0]) : null;
195
+ }
196
+
150
197
  async getRequestLogsByMessageId(messageId: string): Promise<LogRow[]> {
151
198
  const turnIds = this.resolveTurnMessageIds(messageId);
152
199
  let rows = await this.selectByMessageIds(turnIds);
@@ -202,18 +249,46 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
202
249
  ORDER BY created_at ASC, id ASC
203
250
  LIMIT 1 BY id
204
251
  FORMAT JSONEachRow`;
205
- const rows = await this.exec(sql, {
252
+ const rows = await this.exec<ClickHouseRow>(sql, {
206
253
  assistant_id: aid,
207
254
  conversation_id: conversationId,
208
255
  });
209
256
  return rows.map((r) => this.toLogRow(r));
210
257
  }
211
258
 
259
+ async getPreviousNonCompactionCallCreatedAt(
260
+ conversationId: string,
261
+ beforeCreatedAt: number,
262
+ ): Promise<number | null> {
263
+ const aid = await this.assistantId();
264
+ // `call_site != 'compactionAgent'` keeps the pre-migration-264 rows
265
+ // (stored as the empty-string default) in scope as real calls and
266
+ // excludes only the summarizer's own logs, matching the local
267
+ // store's `isNull(callSite) OR callSite != 'compactionAgent'`.
268
+ const sql = `SELECT
269
+ toUnixTimestamp64Milli(created_at) AS created_at
270
+ FROM ${this.tableRef()}
271
+ WHERE assistant_id = {assistant_id:String}
272
+ AND conversation_id = {conversation_id:String}
273
+ AND call_site != {call_site:String}
274
+ AND created_at < fromUnixTimestamp64Milli({before_created_at:Int64})
275
+ ORDER BY created_at DESC, id DESC
276
+ LIMIT 1
277
+ FORMAT JSONEachRow`;
278
+ const rows = await this.exec<{ created_at: string }>(sql, {
279
+ assistant_id: aid,
280
+ conversation_id: conversationId,
281
+ call_site: "compactionAgent",
282
+ before_created_at: String(beforeCreatedAt),
283
+ });
284
+ return rows[0] ? Number(rows[0].created_at) : null;
285
+ }
286
+
212
287
  async getCompactionLogsBetween(
213
288
  conversationId: string,
214
289
  afterCreatedAt: number | null,
215
290
  beforeCreatedAt: number,
216
- ): Promise<LogRow[]> {
291
+ ): Promise<CompactionAgentLogRow[]> {
217
292
  const aid = await this.assistantId();
218
293
  // `call_site` is bound as a literal via type-bound parameter (not
219
294
  // string interpolation) for parity with the rest of this class —
@@ -237,13 +312,23 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
237
312
  afterPredicate =
238
313
  " AND created_at > fromUnixTimestamp64Milli({after_created_at:Int64})";
239
314
  }
315
+ // The request payload — an entire near-limit context window per
316
+ // compaction — is never transferred: the trail only needs the count
317
+ // of messages it contained, computed server-side across the three
318
+ // provider shapes (`messages`, `contents`, `input`). `JSONLength`
319
+ // returns 0 for missing paths or invalid JSON; `nullif` maps that
320
+ // back to NULL.
240
321
  const sql = `SELECT
241
322
  id,
242
323
  conversation_id,
243
324
  message_id,
244
325
  provider,
245
- request_payload,
246
326
  response_payload,
327
+ nullif(greatest(
328
+ JSONLength(request_payload, 'messages'),
329
+ JSONLength(request_payload, 'contents'),
330
+ JSONLength(request_payload, 'input')
331
+ ), 0) AS request_message_count,
247
332
  toUnixTimestamp64Milli(created_at) AS created_at,
248
333
  agent_loop_exit_reason,
249
334
  call_site
@@ -255,8 +340,15 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
255
340
  ORDER BY created_at ASC, id ASC
256
341
  LIMIT 1 BY id
257
342
  FORMAT JSONEachRow`;
258
- const rows = await this.exec(sql, params);
259
- return rows.map((r) => this.toLogRow(r));
343
+ const rows = await this.exec<ClickHouseCompactionRow>(sql, params);
344
+ return rows.map((r) => ({
345
+ ...this.toLogMetaRow(r),
346
+ responsePayload: r.response_payload,
347
+ requestMessageCount:
348
+ r.request_message_count === null
349
+ ? null
350
+ : Number(r.request_message_count),
351
+ }));
260
352
  }
261
353
 
262
354
  private async selectByMessageIds(ids: string[]): Promise<LogRow[]> {
@@ -293,7 +385,7 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
293
385
  ORDER BY created_at ASC, id ASC
294
386
  LIMIT 1 BY id
295
387
  FORMAT JSONEachRow`;
296
- const rows = await this.exec(sql, params);
388
+ const rows = await this.exec<ClickHouseRow>(sql, params);
297
389
  return rows.map((r) => this.toLogRow(r));
298
390
  }
299
391
 
@@ -304,10 +396,10 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
304
396
  return `\`${this.config.table.replace(/`/g, "``")}\``;
305
397
  }
306
398
 
307
- private async exec(
399
+ private async exec<Row>(
308
400
  sql: string,
309
401
  params: Record<string, string>,
310
- ): Promise<ClickHouseRow[]> {
402
+ ): Promise<Row[]> {
311
403
  const baseUrl = await this.url();
312
404
  const password = await this.password();
313
405
 
@@ -352,12 +444,12 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
352
444
  const text = await res.text();
353
445
  if (text.trim().length === 0) return [];
354
446
 
355
- const rows: ClickHouseRow[] = [];
447
+ const rows: Row[] = [];
356
448
  for (const line of text.split("\n")) {
357
449
  const trimmed = line.trim();
358
450
  if (trimmed.length === 0) continue;
359
451
  try {
360
- rows.push(JSON.parse(trimmed) as ClickHouseRow);
452
+ rows.push(JSON.parse(trimmed) as Row);
361
453
  } catch (err) {
362
454
  throw new Error(
363
455
  `Failed to parse ClickHouse JSONEachRow line: ${err instanceof Error ? err.message : String(err)}`,
@@ -368,6 +460,14 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
368
460
  }
369
461
 
370
462
  private toLogRow(row: ClickHouseRow): LogRow {
463
+ return {
464
+ ...this.toLogMetaRow(row),
465
+ requestPayload: row.request_payload,
466
+ responsePayload: row.response_payload,
467
+ };
468
+ }
469
+
470
+ private toLogMetaRow(row: ClickHouseMetaRow): LogMetaRow {
371
471
  return {
372
472
  id: row.id,
373
473
  conversationId: row.conversation_id,
@@ -376,8 +476,6 @@ export class ClickHouseLlmRequestLogSource implements LlmRequestLogSource {
376
476
  // overhead). Map empty back to null to match the local LogRow shape.
377
477
  messageId: row.message_id === "" ? null : row.message_id,
378
478
  provider: row.provider === "" ? null : row.provider,
379
- requestPayload: row.request_payload,
380
- responsePayload: row.response_payload,
381
479
  createdAt: Number(row.created_at),
382
480
  agentLoopExitReason:
383
481
  row.agent_loop_exit_reason === "" ? null : row.agent_loop_exit_reason,
@@ -10,10 +10,14 @@
10
10
  */
11
11
  import type { LlmRequestLogSource } from "./llm-request-log-source.js";
12
12
  import {
13
+ type CompactionAgentLogRow,
13
14
  getCompactionLogsBetween,
15
+ getPreviousNonCompactionCallCreatedAt,
14
16
  getRequestLogById,
17
+ getRequestLogMetaById,
15
18
  getRequestLogsByConversationId,
16
19
  getRequestLogsByMessageId,
20
+ type LogMetaRow,
17
21
  type LogRow,
18
22
  } from "./llm-request-log-store.js";
19
23
 
@@ -22,6 +26,10 @@ export class LocalLlmRequestLogSource implements LlmRequestLogSource {
22
26
  return getRequestLogById(logId);
23
27
  }
24
28
 
29
+ async getRequestLogMetaById(logId: string): Promise<LogMetaRow | null> {
30
+ return getRequestLogMetaById(logId);
31
+ }
32
+
25
33
  async getRequestLogsByMessageId(messageId: string): Promise<LogRow[]> {
26
34
  return getRequestLogsByMessageId(messageId);
27
35
  }
@@ -32,11 +40,21 @@ export class LocalLlmRequestLogSource implements LlmRequestLogSource {
32
40
  return getRequestLogsByConversationId(conversationId);
33
41
  }
34
42
 
43
+ async getPreviousNonCompactionCallCreatedAt(
44
+ conversationId: string,
45
+ beforeCreatedAt: number,
46
+ ): Promise<number | null> {
47
+ return getPreviousNonCompactionCallCreatedAt(
48
+ conversationId,
49
+ beforeCreatedAt,
50
+ );
51
+ }
52
+
35
53
  async getCompactionLogsBetween(
36
54
  conversationId: string,
37
55
  afterCreatedAt: number | null,
38
56
  beforeCreatedAt: number,
39
- ): Promise<LogRow[]> {
57
+ ): Promise<CompactionAgentLogRow[]> {
40
58
  return getCompactionLogsBetween(
41
59
  conversationId,
42
60
  afterCreatedAt,
@@ -17,12 +17,24 @@
17
17
  * Config edits take effect on the next request without an invalidation hook.
18
18
  */
19
19
  import { getConfig } from "../config/loader.js";
20
- import type { LogRow } from "./llm-request-log-store.js";
20
+ import type {
21
+ CompactionAgentLogRow,
22
+ LogMetaRow,
23
+ LogRow,
24
+ } from "./llm-request-log-store.js";
21
25
 
22
26
  export interface LlmRequestLogSource {
23
27
  /** Fetch a single log row by its primary key. Returns null if not found. */
24
28
  getRequestLogById(logId: string): Promise<LogRow | null>;
25
29
 
30
+ /**
31
+ * Fetch a single log row's metadata (no payloads) by its primary key.
32
+ * Prefer this when the caller only needs to locate or validate the row
33
+ * — request payloads can be entire context windows, so loading them for
34
+ * a conversation-scope check or a `createdAt` anchor is wasteful.
35
+ */
36
+ getRequestLogMetaById(logId: string): Promise<LogMetaRow | null>;
37
+
26
38
  /**
27
39
  * Fetch every LLM request log associated with the given message,
28
40
  * including all assistant messages in the same agent turn. Implementations
@@ -39,22 +51,39 @@ export interface LlmRequestLogSource {
39
51
  */
40
52
  getRequestLogsByConversationId(conversationId: string): Promise<LogRow[]>;
41
53
 
54
+ /**
55
+ * Return the `createdAt` of the most recent **non-`compactionAgent`**
56
+ * LLM call in the conversation that ran strictly before
57
+ * `beforeCreatedAt`, or `null` when the selected call is the first real
58
+ * call in the conversation.
59
+ *
60
+ * Drives the call-scoped floor for the Inspector's Compaction tab: a
61
+ * compaction is attributed to the next real call that ran after it, so
62
+ * the compactions for a given call are those that landed strictly
63
+ * between the previous real call and the selected call.
64
+ */
65
+ getPreviousNonCompactionCallCreatedAt(
66
+ conversationId: string,
67
+ beforeCreatedAt: number,
68
+ ): Promise<number | null>;
69
+
42
70
  /**
43
71
  * Fetch every `callSite = "compactionAgent"` log row in the conversation
44
72
  * whose `createdAt` falls in the **open window**
45
73
  * `(afterCreatedAt, beforeCreatedAt)`, ordered chronologically.
46
74
  *
47
- * Drives the Inspector's Compaction tab. The route handler computes
48
- * the window from the turn the selected call belongs to via
49
- * `getTurnTimeBounds` in `memory/conversation-crud.ts`; this source
50
- * method is bound-agnostic. See `getCompactionLogsBetween` in
75
+ * The legacy fallback for the Inspector's Compaction tab, used when the
76
+ * ClickHouse compaction log is unavailable. The route handler resolves
77
+ * the floor from `getPreviousNonCompactionCallCreatedAt` and the ceiling
78
+ * from the selected call's `createdAt`; this source method is
79
+ * bound-agnostic. See `getCompactionLogsBetween` in
51
80
  * `llm-request-log-store.ts` for the SQL.
52
81
  */
53
82
  getCompactionLogsBetween(
54
83
  conversationId: string,
55
84
  afterCreatedAt: number | null,
56
85
  beforeCreatedAt: number,
57
- ): Promise<LogRow[]>;
86
+ ): Promise<CompactionAgentLogRow[]>;
58
87
  }
59
88
 
60
89
  /**
@@ -9,6 +9,8 @@ import {
9
9
  isNull,
10
10
  lt,
11
11
  lte,
12
+ ne,
13
+ or,
12
14
  sql,
13
15
  } from "drizzle-orm";
14
16
  import { v4 as uuid } from "uuid";
@@ -48,6 +50,20 @@ export type LogRow = {
48
50
  callSite: string | null;
49
51
  };
50
52
 
53
+ /** `LogRow` without the payload columns — for reads that only need metadata. */
54
+ export type LogMetaRow = Omit<LogRow, "requestPayload" | "responsePayload">;
55
+
56
+ /**
57
+ * Compaction-trail row: metadata plus the (small) summarizer response
58
+ * payload and a message count computed in SQL. The request payload — an
59
+ * entire near-limit context window per compaction — is deliberately never
60
+ * loaded; the trail only needs the count of messages it contained.
61
+ */
62
+ export type CompactionAgentLogRow = LogMetaRow & {
63
+ responsePayload: string;
64
+ requestMessageCount: number | null;
65
+ };
66
+
51
67
  /**
52
68
  * Build the structured response-payload object recorded in
53
69
  * `llm_request_logs.responsePayload` for a provider-rejected LLM call.
@@ -399,6 +415,43 @@ function selectUnlinkedLogsInRange(
399
415
  .all();
400
416
  }
401
417
 
418
+ /**
419
+ * Return the `createdAt` of the most recent **non-`compactionAgent`** LLM
420
+ * call in the conversation that ran strictly before `beforeCreatedAt`, or
421
+ * `null` when no such call exists (the selected call is the first real
422
+ * call in the conversation).
423
+ *
424
+ * Drives the call-scoped floor for the Inspector's Compaction tab. A
425
+ * compaction is attributed to the next real call that runs after it, so
426
+ * the trail for a given call is the set of compactions that landed
427
+ * strictly between the previous real call and the selected call. NULL
428
+ * `callSite` rows (pre-migration-264) are treated as real calls — only
429
+ * `compactionAgent` rows are excluded.
430
+ */
431
+ export function getPreviousNonCompactionCallCreatedAt(
432
+ conversationId: string,
433
+ beforeCreatedAt: number,
434
+ ): number | null {
435
+ const db = getDb();
436
+ const row = db
437
+ .select({ createdAt: llmRequestLogs.createdAt })
438
+ .from(llmRequestLogs)
439
+ .where(
440
+ and(
441
+ eq(llmRequestLogs.conversationId, conversationId),
442
+ lt(llmRequestLogs.createdAt, beforeCreatedAt),
443
+ or(
444
+ isNull(llmRequestLogs.callSite),
445
+ ne(llmRequestLogs.callSite, "compactionAgent"),
446
+ ),
447
+ ),
448
+ )
449
+ .orderBy(desc(llmRequestLogs.createdAt), desc(llmRequestLogs.id))
450
+ .limit(1)
451
+ .get();
452
+ return row?.createdAt ?? null;
453
+ }
454
+
402
455
  /**
403
456
  * Fetch every `compactionAgent` log row in the conversation whose
404
457
  * `createdAt` falls in the **open window** `(afterCreatedAt, beforeCreatedAt)`,
@@ -426,7 +479,7 @@ export function getCompactionLogsBetween(
426
479
  conversationId: string,
427
480
  afterCreatedAt: number | null,
428
481
  beforeCreatedAt: number,
429
- ): LogRow[] {
482
+ ): CompactionAgentLogRow[] {
430
483
  const db = getDb();
431
484
  const predicates = [
432
485
  eq(llmRequestLogs.conversationId, conversationId),
@@ -442,8 +495,18 @@ export function getCompactionLogsBetween(
442
495
  conversationId: llmRequestLogs.conversationId,
443
496
  messageId: llmRequestLogs.messageId,
444
497
  provider: llmRequestLogs.provider,
445
- requestPayload: llmRequestLogs.requestPayload,
446
498
  responsePayload: llmRequestLogs.responsePayload,
499
+ // Count the request's messages in SQL instead of loading the payload:
500
+ // `messages` (Anthropic / OpenAI chat-completions), `contents`
501
+ // (Gemini), `input` (OpenAI Responses). NULL when the payload is
502
+ // malformed or none of the arrays exist.
503
+ requestMessageCount: sql<
504
+ number | null
505
+ >`CASE WHEN json_valid(${llmRequestLogs.requestPayload}) THEN coalesce(
506
+ json_array_length(${llmRequestLogs.requestPayload}, '$.messages'),
507
+ json_array_length(${llmRequestLogs.requestPayload}, '$.contents'),
508
+ json_array_length(${llmRequestLogs.requestPayload}, '$.input')
509
+ ) ELSE NULL END`,
447
510
  createdAt: llmRequestLogs.createdAt,
448
511
  agentLoopExitReason: llmRequestLogs.agentLoopExitReason,
449
512
  callSite: llmRequestLogs.callSite,
@@ -454,6 +517,31 @@ export function getCompactionLogsBetween(
454
517
  .all();
455
518
  }
456
519
 
520
+ /**
521
+ * Metadata-only lookup by primary key. Used where the caller needs to
522
+ * locate or validate a log (conversation scoping, `createdAt` anchoring)
523
+ * without paying to load its payloads — a single request payload can be a
524
+ * full context window.
525
+ */
526
+ export function getRequestLogMetaById(logId: string): LogMetaRow | null {
527
+ const db = getDb();
528
+ return (
529
+ db
530
+ .select({
531
+ id: llmRequestLogs.id,
532
+ conversationId: llmRequestLogs.conversationId,
533
+ messageId: llmRequestLogs.messageId,
534
+ provider: llmRequestLogs.provider,
535
+ createdAt: llmRequestLogs.createdAt,
536
+ agentLoopExitReason: llmRequestLogs.agentLoopExitReason,
537
+ callSite: llmRequestLogs.callSite,
538
+ })
539
+ .from(llmRequestLogs)
540
+ .where(eq(llmRequestLogs.id, logId))
541
+ .get() ?? null
542
+ );
543
+ }
544
+
457
545
  export function getRequestLogById(logId: string): LogRow | null {
458
546
  const db = getDb();
459
547
  return (
@@ -24,6 +24,15 @@ export const MEMORY_RETROSPECTIVE_SOURCES: readonly string[] = [
24
24
  MEMORY_RETROSPECTIVE_FORK_SOURCE,
25
25
  ];
26
26
 
27
+ /**
28
+ * Whether a conversation `source` value marks a memory-retrospective
29
+ * background conversation (either kind). Shared predicate for the recursion
30
+ * and auto-analysis guards.
31
+ */
32
+ export function isMemoryRetrospectiveSource(source: string): boolean {
33
+ return MEMORY_RETROSPECTIVE_SOURCES.includes(source);
34
+ }
35
+
27
36
  /**
28
37
  * Dedicated `group_id` value for memory-retrospective background
29
38
  * conversations. Placed under `system:background` alongside auto-analysis,