@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
@@ -2,7 +2,11 @@
2
2
  * Channel delivery routes: delivery ack, dead letters, reply delivery,
3
3
  * and post-decision delivery scheduling.
4
4
  */
5
- import { acknowledgeDelivery, getDeadLetterEvents, replayDeadLetters } from "../../memory/delivery-status.js";
5
+ import {
6
+ acknowledgeDelivery,
7
+ getDeadLetterEvents,
8
+ replayDeadLetters,
9
+ } from "../../memory/delivery-status.js";
6
10
  import { BadRequestError, NotFoundError } from "./errors.js";
7
11
  import type { RouteHandlerArgs } from "./types.js";
8
12
 
@@ -10,6 +14,7 @@ export {
10
14
  type DeliverReplyOptions,
11
15
  deliverReplyViaCallback,
12
16
  } from "../channel-reply-delivery.js";
17
+ export { finalizeEventDelivery } from "../finalize-event-delivery.js";
13
18
 
14
19
  // ---------------------------------------------------------------------------
15
20
  // Dead letter management
@@ -36,12 +41,11 @@ export function handleReplayDeadLetters({ body = {} }: RouteHandlerArgs) {
36
41
  // ---------------------------------------------------------------------------
37
42
 
38
43
  export function handleChannelDeliveryAck({ body = {} }: RouteHandlerArgs) {
39
- const { sourceChannel, conversationExternalId, externalMessageId } =
40
- body as {
41
- sourceChannel?: string;
42
- conversationExternalId?: string;
43
- externalMessageId?: string;
44
- };
44
+ const { sourceChannel, conversationExternalId, externalMessageId } = body as {
45
+ sourceChannel?: string;
46
+ conversationExternalId?: string;
47
+ externalMessageId?: string;
48
+ };
45
49
 
46
50
  if (!sourceChannel || typeof sourceChannel !== "string") {
47
51
  throw new BadRequestError("sourceChannel is required");
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Static ROUTES array for channel endpoints.
3
3
  */
4
+ import { RuntimeInboundPayloadSchema } from "@vellumai/gateway-client";
5
+
4
6
  import { ACTOR_PRINCIPALS, GATEWAY_PRINCIPALS } from "../auth/route-policy.js";
5
7
  import {
6
8
  handleChannelDeliveryAck,
@@ -38,6 +40,7 @@ export const CHANNEL_ROUTES: RouteDefinition[] = [
38
40
  summary: "Process inbound channel message",
39
41
  description: "Receive an inbound message from a channel integration.",
40
42
  tags: ["channels"],
43
+ requestBody: RuntimeInboundPayloadSchema,
41
44
  handler: handleChannelInbound,
42
45
  },
43
46
  {
@@ -62,7 +62,9 @@ export function parseCallbackData(
62
62
  ? ("whatsapp_button" as const)
63
63
  : sourceChannel === "slack"
64
64
  ? ("slack_button" as const)
65
- : ("telegram_button" as const);
65
+ : sourceChannel === "vellum"
66
+ ? ("vellum_surface" as const)
67
+ : ("telegram_button" as const);
66
68
  return { action: action as ApprovalAction, source, requestId };
67
69
  }
68
70
 
@@ -31,6 +31,13 @@ import { getUsageCostForConversationWindow } from "../../memory/llm-usage-store.
31
31
  import { MEMORY_V2_CONSOLIDATION_SOURCE } from "../../memory/v2/constants.js";
32
32
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
33
33
  import { BadRequestError } from "./errors.js";
34
+ import {
35
+ paginateRuns,
36
+ parseRunsBeforeCursor,
37
+ parseRunsLimit,
38
+ RUNS_NEXT_CURSOR_SCHEMA,
39
+ RUNS_PAGINATION_QUERY_PARAMS,
40
+ } from "./runs-pagination.js";
34
41
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
35
42
 
36
43
  function isConsolidationAvailable(): boolean {
@@ -162,13 +169,7 @@ export const ROUTES: RouteDefinition[] = [
162
169
  "on the conversation row. Shape mirrors `heartbeat/runs` so the " +
163
170
  "schedules settings UI can reuse its run-row component.",
164
171
  tags: ["consolidation"],
165
- queryParams: [
166
- {
167
- name: "limit",
168
- schema: { type: "integer" },
169
- description: "Max runs to return (default 20, max 100)",
170
- },
171
- ],
172
+ queryParams: RUNS_PAGINATION_QUERY_PARAMS(20),
172
173
  responseBody: z.object({
173
174
  runs: z
174
175
  .array(
@@ -189,16 +190,18 @@ export const ROUTES: RouteDefinition[] = [
189
190
  }),
190
191
  )
191
192
  .describe("Consolidation run records"),
193
+ nextCursor: RUNS_NEXT_CURSOR_SCHEMA,
192
194
  }),
193
195
  handler: async ({ queryParams }: RouteHandlerArgs) => {
194
196
  const params = queryParams ?? {};
195
- const rawLimit = Number(params.limit ?? 20);
196
- const limit = Number.isFinite(rawLimit)
197
- ? Math.min(Math.max(Math.floor(rawLimit), 1), 100)
198
- : 20;
199
- const rows = listConversationsBySource(
200
- MEMORY_V2_CONSOLIDATION_SOURCE,
197
+ const limit = parseRunsLimit(params, 20);
198
+ const before = parseRunsBeforeCursor(params);
199
+ const { rows, nextCursor } = paginateRuns(
200
+ listConversationsBySource(MEMORY_V2_CONSOLIDATION_SOURCE, limit + 1, {
201
+ beforeCreatedAt: before,
202
+ }),
201
203
  limit,
204
+ (c) => c.createdAt,
202
205
  );
203
206
  // Aggregate assistant-message stats in one batched query: presence of
204
207
  // an assistant message is the strongest "agent emitted output" signal
@@ -212,6 +215,7 @@ export const ROUTES: RouteDefinition[] = [
212
215
  );
213
216
  const now = Date.now();
214
217
  return {
218
+ nextCursor,
215
219
  runs: rows.map((c) => {
216
220
  const stat = assistantStats.get(c.id);
217
221
  const hasAssistantOutput = (stat?.count ?? 0) > 0;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Route definitions for the per-conversation **compaction trail** view.
2
+ * Route definitions for the per-conversation **compaction** view.
3
3
  *
4
4
  * GET /v1/conversations/:id/compaction?callId=…
5
5
  *
@@ -8,48 +8,42 @@
8
8
  *
9
9
  * # Scope
10
10
  *
11
- * Returns every `call_site = "compactionAgent"` row that ran in the
12
- * **same agent turn** as the call identified by `callId`, ordered
13
- * chronologically. This is the data the Inspector's "Compaction" tab
14
- * shows when you select a call in the rail: it answers "what did the
15
- * compactor do across this whole turn?" regardless of where the
16
- * selected call sits within the turn.
11
+ * Returns the compaction(s) attributed to the call identified by
12
+ * `callId`, ordered chronologically. A compaction is attributed to the
13
+ * next real LLM call that ran after it, so the compactions for a given
14
+ * call are those that landed strictly between the previous real
15
+ * (non-`compactionAgent`) call and the selected call. This is the data
16
+ * the Inspector's "Compaction" tab shows when you select a call in the
17
+ * rail: it answers "which compaction reshaped the context that this
18
+ * call ran against?".
17
19
  *
18
- * Turn bounds are resolved by `getTurnTimeBounds` in
19
- * `memory/conversation-crud.ts`, which walks the `messages` table to
20
- * find the real user message that started the turn and the next real
21
- * user message (or end-of-conversation) that ends it. Tool-result user
22
- * messages are not turn boundaries. When the conversation has no other
23
- * messages around the selected call, the window collapses to the
24
- * call's own `createdAt` and every compaction strictly before it is
25
- * returned — same as the legacy behavior.
20
+ * The window is usually empty or holds a single compaction, but the
21
+ * recovery cascade can fire several compactions before one call lands,
22
+ * so the response carries a list rather than a single object.
26
23
  *
27
- * # Why turn-scoped, not call-scoped
28
- *
29
- * A single turn can run dozens of compactions (mid-loop proactive,
30
- * convergence-reducer, and emergency cycles all flow through the same
31
- * `compactionAgent` call site). Constraining the trail to the window
32
- * between two adjacent calls hides most of that activity from the UI
33
- * — especially for the call that *yields* on a budget-exhausted error,
34
- * which has no compaction immediately before it but had several
35
- * earlier in the same turn. The turn is the right unit of analysis.
24
+ * The floor is resolved by `getPreviousNonCompactionCallCreatedAt` on
25
+ * the log source and the ceiling is the selected call's own
26
+ * `createdAt`. Both bounds are exclusive (the stores use strict `>` /
27
+ * `<` predicates), so a compaction whose timestamp lands strictly
28
+ * between the two real calls is in scope without any boundary fudging.
36
29
  *
37
30
  * # Data sources
38
31
  *
39
32
  * When `compactionLogs.destination = "clickhouse"` is configured, the
40
- * trail is served from the first-class compaction log: the agent loop's
33
+ * data is served from the first-class compaction log: the agent loop's
41
34
  * start/end event pairs written by
42
- * `memory/compaction-log-store-clickhouse.ts`. Those rows carry real
43
- * durations, summary-model token totals, and trigger reasons none of
44
- * which exist on `llm_request_logs`.
35
+ * `memory/compaction-log-store-clickhouse.ts`. Those rows carry the
36
+ * before/after context-token and message counts, real durations, the
37
+ * trigger, and the summary text — none of which exist on
38
+ * `llm_request_logs`.
45
39
  *
46
40
  * The legacy projection over `llm_request_logs` rows with
47
41
  * `call_site = "compactionAgent"` remains as the fallback: it serves
48
- * turns that predate the compaction log (the table is append-only from
42
+ * calls that predate the compaction log (the table is append-only from
49
43
  * the moment the destination is configured), assistants that never opt
50
- * in, and reads where the ClickHouse query fails. On the legacy path
51
- * `durationMs` is always `null` the column doesn't exist on
52
- * `llm_request_logs`.
44
+ * in, and reads where the ClickHouse query fails. The legacy path can
45
+ * only recover the summarizer call's own model/usage/text the
46
+ * before/after counts, duration, and trigger land as `null`.
53
47
  */
54
48
 
55
49
  import { z } from "zod";
@@ -58,12 +52,9 @@ import {
58
52
  type CompactionLogEvent,
59
53
  getCompactionLogStore,
60
54
  } from "../../memory/compaction-log-store-clickhouse.js";
61
- import {
62
- getConversation,
63
- getTurnTimeBounds,
64
- } from "../../memory/conversation-crud.js";
55
+ import { getConversation } from "../../memory/conversation-crud.js";
65
56
  import { getLlmRequestLogSource } from "../../memory/llm-request-log-source.js";
66
- import type { LogRow } from "../../memory/llm-request-log-store.js";
57
+ import type { CompactionAgentLogRow } from "../../memory/llm-request-log-store.js";
67
58
  import { getLogger } from "../../util/logger.js";
68
59
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
69
60
  import { BadRequestError, NotFoundError } from "./errors.js";
@@ -76,29 +67,59 @@ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
76
67
  const log = getLogger("conversation-compaction-routes");
77
68
 
78
69
  /**
79
- * Wire shape for a single compaction event. Mirrors the React Query
80
- * response type at
81
- * `apps/web/src/domains/chat/inspector/compaction-trail-types.ts`.
82
- * Keep the two in sync — until the OpenAPI client generator picks up
83
- * this route, the frontend type is hand-maintained.
70
+ * Wire shape for a single compaction attributed to an LLM call.
71
+ *
72
+ * `responseBody` (below) is the source-of-truth for the generated
73
+ * OpenAPI client type the frontend imports
74
+ * (`ConversationsByIdCompactionGetResponse` in
75
+ * `apps/web/src/generated/daemon/types.gen`); regenerate the client with
76
+ * `bun run openapi-ts` after changing the schema.
77
+ *
78
+ * `null` means the value isn't known for the underlying row — either the
79
+ * legacy projection can't recover it, or the attempt never wrote its end
80
+ * row. The frontend renders missing values as a placeholder.
84
81
  */
85
82
  export interface CompactionTrailEvent {
86
83
  id: string;
84
+ /** Epoch-ms timestamp the compaction started. */
87
85
  createdAt: number;
88
- model: string | null;
89
- provider: string | null;
90
- inputTokens: number | null;
91
- outputTokens: number | null;
86
+ /** What triggered the compaction (e.g. `budget`, `overflow`). */
87
+ trigger: string | null;
88
+ /** True when the attempt actually reduced the context. */
89
+ compacted: boolean | null;
90
+ /** True when the summarizer provider call threw. */
91
+ summaryFailed: boolean | null;
92
+ /**
93
+ * Why a no-op attempt did nothing (e.g. below the auto threshold,
94
+ * compaction disabled). Set only when `compacted` is false.
95
+ */
96
+ skipReason: string | null;
97
+ /** Estimated context input tokens before the compaction ran. */
98
+ contextTokensBefore: number | null;
99
+ /** Estimated context input tokens after the compaction ran. */
100
+ contextTokensAfter: number | null;
101
+ /** Message count in the context before the compaction ran. */
102
+ messagesBefore: number | null;
103
+ /** Message count in the context after the compaction ran. */
104
+ messagesAfter: number | null;
105
+ /** How many messages were folded into the summary. */
106
+ compactedMessages: number | null;
107
+ /** How many recent messages were preserved verbatim past the summary. */
108
+ preservedTailMessages: number | null;
92
109
  /**
93
110
  * Per-attempt wall-clock latency, in milliseconds. Populated from the
94
- * compaction log when configured; always `null` on the legacy
111
+ * compaction log when configured; `null` on the legacy
95
112
  * `llm_request_logs` path, which has no duration column.
96
113
  */
97
114
  durationMs: number | null;
98
- responsePreview: string | null;
99
- requestMessageCount: number | null;
100
- stopReason: string | null;
101
- estimatedCostUsd: number | null;
115
+ /** Model that produced the summary. */
116
+ summaryModel: string | null;
117
+ /** Input tokens the summarizer call itself consumed. */
118
+ summaryInputTokens: number | null;
119
+ /** Output tokens the summarizer call itself produced. */
120
+ summaryOutputTokens: number | null;
121
+ /** The summary text that replaced the compacted span. */
122
+ summaryText: string | null;
102
123
  }
103
124
 
104
125
  export interface CompactionTrailResponse {
@@ -109,15 +130,21 @@ export interface CompactionTrailResponse {
109
130
  const compactionTrailEventSchema = z.object({
110
131
  id: z.string(),
111
132
  createdAt: z.number(),
112
- model: z.string().nullable(),
113
- provider: z.string().nullable(),
114
- inputTokens: z.number().nullable(),
115
- outputTokens: z.number().nullable(),
133
+ trigger: z.string().nullable(),
134
+ compacted: z.boolean().nullable(),
135
+ summaryFailed: z.boolean().nullable(),
136
+ skipReason: z.string().nullable(),
137
+ contextTokensBefore: z.number().nullable(),
138
+ contextTokensAfter: z.number().nullable(),
139
+ messagesBefore: z.number().nullable(),
140
+ messagesAfter: z.number().nullable(),
141
+ compactedMessages: z.number().nullable(),
142
+ preservedTailMessages: z.number().nullable(),
116
143
  durationMs: z.number().nullable(),
117
- responsePreview: z.string().nullable(),
118
- requestMessageCount: z.number().nullable(),
119
- stopReason: z.string().nullable(),
120
- estimatedCostUsd: z.number().nullable(),
144
+ summaryModel: z.string().nullable(),
145
+ summaryInputTokens: z.number().nullable(),
146
+ summaryOutputTokens: z.number().nullable(),
147
+ summaryText: z.string().nullable(),
121
148
  });
122
149
 
123
150
  const compactionTrailResponseSchema = z.object({
@@ -126,7 +153,7 @@ const compactionTrailResponseSchema = z.object({
126
153
  });
127
154
 
128
155
  // ---------------------------------------------------------------------------
129
- // Projection — LogRow → CompactionTrailEvent
156
+ // Projection — CompactionAgentLogRow → CompactionTrailEvent
130
157
  // ---------------------------------------------------------------------------
131
158
 
132
159
  function tryParseJson(value: string): unknown {
@@ -138,22 +165,27 @@ function tryParseJson(value: string): unknown {
138
165
  }
139
166
 
140
167
  /**
141
- * Project a raw `llm_request_logs` row into the compaction-trail wire
142
- * shape. Reuses `normalizeLlmContextPayloads` so model/provider/token
143
- * extraction stays consistent with what the rest of the inspector shows.
168
+ * Project a compaction-agent `llm_request_logs` row into the compaction
169
+ * wire shape. This degraded fallback can only recover what the
170
+ * summarizer call itself recorded: `normalizeLlmContextPayloads` pulls
171
+ * the summary model, the summarizer's own token usage, and the summary
172
+ * text from the response payload.
144
173
  *
145
- * Fields the normalizer can't derive (today: `durationMs`) land as
146
- * `null` see the `CompactionTrailEvent.durationMs` doc comment for the
147
- * rationale.
174
+ * The before/after context-token and message counts, the duration, and
175
+ * the trigger never existed on `llm_request_logs`, so they land as
176
+ * `null`. The request payload — an entire near-limit context window per
177
+ * compaction — is never loaded; its message count is computed in SQL and
178
+ * arrives on the row as `requestMessageCount` (the messages fed to the
179
+ * summarizer, i.e. the messages that were compacted).
148
180
  *
149
181
  * Exported only for unit tests; the route handler is the sole production
150
182
  * caller.
151
183
  */
152
184
  export function projectLogRowToCompactionTrailEvent(
153
- log: LogRow,
185
+ log: CompactionAgentLogRow,
154
186
  ): CompactionTrailEvent {
155
187
  const normalized = normalizeLlmContextPayloads({
156
- requestPayload: tryParseJson(log.requestPayload),
188
+ requestPayload: undefined,
157
189
  responsePayload: tryParseJson(log.responsePayload),
158
190
  createdAt: log.createdAt,
159
191
  });
@@ -161,28 +193,33 @@ export function projectLogRowToCompactionTrailEvent(
161
193
  return {
162
194
  id: log.id,
163
195
  createdAt: log.createdAt,
164
- model: summary?.model ?? null,
165
- // Prefer the normalized provider (derived from payload shape) over
166
- // the stored column. Stored `provider` is the originating call's
167
- // own identifier and matches the normalizer in all cases we ship
168
- // today, but the normalizer is the source-of-truth used by sibling
169
- // inspector tabs — keep that alignment.
170
- provider: summary?.provider ?? log.provider ?? null,
171
- inputTokens: summary?.inputTokens ?? null,
172
- outputTokens: summary?.outputTokens ?? null,
196
+ trigger: null,
197
+ compacted: null,
198
+ summaryFailed: null,
199
+ skipReason: null,
200
+ contextTokensBefore: null,
201
+ contextTokensAfter: null,
202
+ messagesBefore: null,
203
+ messagesAfter: null,
204
+ compactedMessages: log.requestMessageCount,
205
+ preservedTailMessages: null,
173
206
  durationMs: null,
174
- responsePreview: summary?.responsePreview ?? null,
175
- requestMessageCount: summary?.requestMessageCount ?? null,
176
- stopReason: summary?.stopReason ?? null,
177
- estimatedCostUsd: summary?.estimatedCostUsd ?? null,
207
+ summaryModel: summary?.model ?? null,
208
+ summaryInputTokens: summary?.inputTokens ?? null,
209
+ summaryOutputTokens: summary?.outputTokens ?? null,
210
+ summaryText: summary?.responsePreview ?? null,
178
211
  };
179
212
  }
180
213
 
181
214
  /**
182
- * Project a paired compaction-log event into the trail wire shape. Token
183
- * counts are the summarizer's own usage (what the compaction itself cost),
184
- * matching what the legacy path derived from the compaction agent's
185
- * request/response payloads.
215
+ * Project a paired compaction-log event into the wire shape. The headline
216
+ * before/after figures are the context reduction the compaction achieved
217
+ * (`contextTokensBefore`/`After`, `messagesBefore`/`After`); the
218
+ * summarizer's own usage (`summaryInputTokens`/`OutputTokens`) is the
219
+ * separate cost of running the compaction itself.
220
+ *
221
+ * `trigger` stores `""` for unknown on the row; map it back to `null` so
222
+ * the wire shape's "not known" sentinel stays consistent.
186
223
  *
187
224
  * Exported only for unit tests; the route handler is the sole production
188
225
  * caller.
@@ -193,15 +230,21 @@ export function projectCompactionLogEventToTrailEvent(
193
230
  return {
194
231
  id: event.compactionId,
195
232
  createdAt: event.startedAt,
196
- model: event.summaryModel,
197
- provider: null,
198
- inputTokens: event.summaryInputTokens,
199
- outputTokens: event.summaryOutputTokens,
233
+ trigger: event.trigger === "" ? null : event.trigger,
234
+ compacted: event.compacted,
235
+ summaryFailed: event.summaryFailed,
236
+ skipReason: event.reason,
237
+ contextTokensBefore: event.previousEstimatedInputTokens,
238
+ contextTokensAfter: event.estimatedInputTokens,
239
+ messagesBefore: event.preMessageCount,
240
+ messagesAfter: event.resultMessageCount,
241
+ compactedMessages: event.compactedMessages,
242
+ preservedTailMessages: event.preservedTailMessages,
200
243
  durationMs: event.durationMs,
201
- responsePreview: event.summaryText,
202
- requestMessageCount: event.preMessageCount,
203
- stopReason: event.reason,
204
- estimatedCostUsd: null,
244
+ summaryModel: event.summaryModel,
245
+ summaryInputTokens: event.summaryInputTokens,
246
+ summaryOutputTokens: event.summaryOutputTokens,
247
+ summaryText: event.summaryText,
205
248
  };
206
249
  }
207
250
 
@@ -232,7 +275,10 @@ async function handleGetCompactionTrail({
232
275
  }
233
276
 
234
277
  const source = await getLlmRequestLogSource();
235
- const selectedCall = await source.getRequestLogById(callId);
278
+ // Metadata-only lookup the handler needs the call's conversation
279
+ // scope and `createdAt` anchor, never its payloads (a single request
280
+ // payload can be a full context window).
281
+ const selectedCall = await source.getRequestLogMetaById(callId);
236
282
  if (!selectedCall) {
237
283
  throw new NotFoundError(`LLM call ${callId} not found`);
238
284
  }
@@ -246,34 +292,28 @@ async function handleGetCompactionTrail({
246
292
  );
247
293
  }
248
294
 
249
- // Resolve the turn window from the `messages` table every
250
- // compaction between the user message that started this turn and the
251
- // next real user message (or end-of-conversation) is in scope.
252
- //
253
- // `getCompactionLogsBetween` uses strict `>` / `<` predicates, so we
254
- // shift the inclusive bounds by 1ms to capture rows landing on the
255
- // boundaries themselves (e.g. an emergency compaction that fires the
256
- // same millisecond as the assistant message terminating the turn).
257
- //
258
- // When `getTurnTimeBounds` returns `null` (the only-message case), the
259
- // turn-bound contract isn't established. Fall back to the selected
260
- // call's own `createdAt` as the ceiling and an open floor — the same
261
- // shape the route exposed before turn-scoping, so callers don't see
262
- // a regression on conversations with a single message.
263
- const turnBounds = getTurnTimeBounds(conversationId, selectedCall.createdAt);
264
- const afterCreatedAt: number | null =
265
- turnBounds !== null ? turnBounds.startTime - 1 : null;
266
- const beforeCreatedAt: number =
267
- turnBounds !== null ? turnBounds.endTime + 1 : selectedCall.createdAt;
295
+ // A compaction is attributed to the next real LLM call that ran after
296
+ // it, so the compactions for the selected call are those that landed
297
+ // strictly between the previous real (non-`compactionAgent`) call and
298
+ // the selected call. The floor is the previous real call's `createdAt`
299
+ // (null when the selected call is the first real call in the
300
+ // conversation, leaving an open floor); the ceiling is the selected
301
+ // call's own `createdAt`. Both stores use strict `>` / `<` predicates,
302
+ // so no boundary fudging is needed.
303
+ const afterCreatedAt = await source.getPreviousNonCompactionCallCreatedAt(
304
+ conversationId,
305
+ selectedCall.createdAt,
306
+ );
307
+ const beforeCreatedAt = selectedCall.createdAt;
268
308
  // Prefer the first-class compaction log when the assistant has opted
269
- // in. Zero rows means the turn predates the log (it is append-only from
309
+ // in. Zero rows means the call predates the log (it is append-only from
270
310
  // the moment the destination is configured), so fall through to the
271
- // legacy projection rather than returning an empty trail; same for a
311
+ // legacy projection rather than returning an empty result; same for a
272
312
  // failed ClickHouse read. Writes are best-effort and the start/end rows
273
313
  // land independently, so an event without its end row (no `finishedAt`)
274
314
  // means the end write failed or is lagging — in that case the legacy
275
315
  // projection may still have the summarizer call details, so fall back
276
- // rather than serve a trail with null model/tokens/duration.
316
+ // rather than serve null counts/duration.
277
317
  const compactionStore = getCompactionLogStore();
278
318
  if (compactionStore) {
279
319
  try {
@@ -324,9 +364,9 @@ export const ROUTES: RouteDefinition[] = [
324
364
  requiredScopes: ["chat.read"],
325
365
  allowedPrincipalTypes: ACTOR_PRINCIPALS,
326
366
  },
327
- summary: "Get the compaction trail leading up to an LLM call",
367
+ summary: "Get the compaction(s) attributed to an LLM call",
328
368
  description:
329
- 'Return the chronological list of compaction events that ran in the same agent turn as the call identified by `callId`. Turn bounds are walked from the `messages` table (real user messages tool-result user messages are not boundaries). Served from the first-class compaction log when `compactionLogs.destination = "clickhouse"` is configured, falling back to the legacy projection over `llm_request_logs` rows where `call_site = "compactionAgent"`. When the conversation has no other messages around the selected call, every compaction strictly before the call\'s `createdAt` is in scope. Drives the Inspector\'s Compaction tab.',
369
+ 'Return the chronological list of compactions attributed to the call identified by `callId` those that ran strictly between the previous real (non-`compactionAgent`) LLM call and the selected call. Served from the first-class compaction log when `compactionLogs.destination = "clickhouse"` is configured, falling back to the legacy projection over `llm_request_logs` rows where `call_site = "compactionAgent"`. Usually empty or a single compaction; the recovery cascade can fire several before one call lands. Drives the Inspector\'s Compaction tab.',
330
370
  tags: ["conversations"],
331
371
  pathParams: [
332
372
  {
@@ -340,7 +380,7 @@ export const ROUTES: RouteDefinition[] = [
340
380
  required: true,
341
381
  schema: { type: "string" },
342
382
  description:
343
- "ID of the selected LLM call from the rail. Defines the chronological cutoff for the trail.",
383
+ "ID of the selected LLM call from the rail. Defines the chronological cutoff for the attributed compactions.",
344
384
  },
345
385
  ],
346
386
  responseBody: compactionTrailResponseSchema,
@@ -217,17 +217,35 @@ function handleListConversations({ queryParams = {} }: RouteHandlerArgs) {
217
217
  ? "all"
218
218
  : "active";
219
219
 
220
- let rows = listConversations(limit, conversationType, offset, archiveStatus);
221
- const totalCount = countConversations(conversationType, archiveStatus);
220
+ const originChannel =
221
+ queryParams.originChannel !== undefined && queryParams.originChannel !== ""
222
+ ? queryParams.originChannel
223
+ : undefined;
224
+
225
+ let rows = listConversations(
226
+ limit,
227
+ conversationType,
228
+ offset,
229
+ archiveStatus,
230
+ originChannel,
231
+ );
232
+ const totalCount = countConversations(
233
+ conversationType,
234
+ archiveStatus,
235
+ originChannel,
236
+ );
222
237
 
223
238
  // On the first page, ensure all pinned conversations are included
224
239
  // even if they fall outside the paginated window. Pinned injection is
225
240
  // skipped in archived/all views since the Archive page renders archived
226
- // rows in archive-time order, not pin order.
241
+ // rows in archive-time order, not pin order. Also skipped for
242
+ // channel-scoped queries — those return only items matching the
243
+ // requested origin channel; pinned items render in their own section.
227
244
  if (
228
245
  offset === 0 &&
229
246
  conversationType === "standard" &&
230
- archiveStatus === "active"
247
+ archiveStatus === "active" &&
248
+ originChannel === undefined
231
249
  ) {
232
250
  const pinned = listPinnedConversations(archiveStatus);
233
251
  const seen = new Set(rows.map((c) => c.id));
@@ -452,6 +470,25 @@ export const ROUTES: RouteDefinition[] = [
452
470
  'Filter by archive state. Defaults to "active" (non-archived rows only). Pass "archived" to list only archived rows (for the Archive page) or "all" to include both.',
453
471
  schema: { type: "string", enum: ["active", "archived", "all"] },
454
472
  },
473
+ {
474
+ name: "originChannel",
475
+ type: "string",
476
+ required: false,
477
+ description:
478
+ "Filter by origin channel. When provided, only conversations with this exact origin_channel value are returned. Omit to include all channels.",
479
+ schema: {
480
+ type: "string",
481
+ enum: [
482
+ "slack",
483
+ "telegram",
484
+ "whatsapp",
485
+ "email",
486
+ "a2a",
487
+ "vellum",
488
+ "phone",
489
+ ],
490
+ },
491
+ },
455
492
  ],
456
493
  responseBody: listConversationsResponseSchema,
457
494
  handler: handleListConversations,