@vellumai/assistant 0.8.1 → 0.8.3

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 (630) hide show
  1. package/ARCHITECTURE.md +13 -19
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +17 -0
  5. package/docker-init-apt-root.sh +167 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +642 -5
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
  21. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  23. package/src/__tests__/anthropic-provider.test.ts +45 -0
  24. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  25. package/src/__tests__/app-executors.test.ts +220 -4
  26. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  27. package/src/__tests__/bundled-asset.test.ts +6 -6
  28. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  29. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  30. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  31. package/src/__tests__/clawhub.test.ts +75 -16
  32. package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
  33. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  34. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  35. package/src/__tests__/config-schema.test.ts +21 -0
  36. package/src/__tests__/config-set-route.test.ts +80 -0
  37. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  38. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  39. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  40. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  41. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  42. package/src/__tests__/context-token-estimator.test.ts +31 -65
  43. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  46. package/src/__tests__/conversation-agent-loop.test.ts +59 -1
  47. package/src/__tests__/conversation-error.test.ts +42 -3
  48. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  49. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  50. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  51. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  52. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  53. package/src/__tests__/conversation-pairing.test.ts +54 -0
  54. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  55. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  56. package/src/__tests__/conversation-queue.test.ts +4 -1
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
  58. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  59. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  60. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  61. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  64. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  65. package/src/__tests__/date-context.test.ts +45 -0
  66. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  67. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  68. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  69. package/src/__tests__/dm-backfill.test.ts +121 -10
  70. package/src/__tests__/document-tool-security.test.ts +258 -0
  71. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  72. package/src/__tests__/edit-propagation.test.ts +33 -0
  73. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  74. package/src/__tests__/external-plugin-loader.test.ts +151 -55
  75. package/src/__tests__/filing-service.test.ts +140 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  77. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  78. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  79. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  80. package/src/__tests__/heartbeat-service.test.ts +24 -164
  81. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  82. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  83. package/src/__tests__/helpers/wait-for.ts +21 -0
  84. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  85. package/src/__tests__/history-repair.test.ts +73 -0
  86. package/src/__tests__/host-app-control-proxy.test.ts +507 -10
  87. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  88. package/src/__tests__/image-credentials.test.ts +1 -1
  89. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  90. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  91. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  92. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  93. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  94. package/src/__tests__/injector-background-turn.test.ts +153 -0
  95. package/src/__tests__/injector-chain.test.ts +15 -8
  96. package/src/__tests__/install-skill-routing.test.ts +155 -37
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
  98. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  99. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  100. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  101. package/src/__tests__/llm-catalog-parity.test.ts +58 -13
  102. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  103. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  104. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
  105. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  106. package/src/__tests__/llm-resolver.test.ts +255 -2
  107. package/src/__tests__/llm-usage-store.test.ts +114 -0
  108. package/src/__tests__/managed-profile-guard.test.ts +41 -29
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  110. package/src/__tests__/managed-store.test.ts +84 -192
  111. package/src/__tests__/media-generate-image.test.ts +1 -1
  112. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  113. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  114. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  115. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  116. package/src/__tests__/notification-deep-link.test.ts +15 -0
  117. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  118. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  119. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  120. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  121. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  122. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  123. package/src/__tests__/openai-provider.test.ts +242 -3
  124. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  125. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  126. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  127. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  128. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  129. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
  130. package/src/__tests__/platform.test.ts +2 -0
  131. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  132. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  133. package/src/__tests__/plugin-external-api.test.ts +68 -0
  134. package/src/__tests__/plugin-registry.test.ts +0 -77
  135. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  136. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  137. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  138. package/src/__tests__/plugin-types.test.ts +3 -13
  139. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  140. package/src/__tests__/process-message-display-content.test.ts +421 -0
  141. package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  143. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
  144. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  145. package/src/__tests__/schedule-routes.test.ts +50 -3
  146. package/src/__tests__/schedule-store.test.ts +94 -0
  147. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  148. package/src/__tests__/schema-transforms.test.ts +20 -0
  149. package/src/__tests__/search-skills-unified.test.ts +0 -5
  150. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
  151. package/src/__tests__/server-history-render.test.ts +43 -0
  152. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  153. package/src/__tests__/skill-load-tool.test.ts +27 -89
  154. package/src/__tests__/skill-memory.test.ts +23 -3
  155. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  156. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  157. package/src/__tests__/skills-install-extract.test.ts +49 -38
  158. package/src/__tests__/skills-install-staging.test.ts +159 -0
  159. package/src/__tests__/skills-uninstall.test.ts +9 -41
  160. package/src/__tests__/skills.test.ts +51 -58
  161. package/src/__tests__/slack-channel-config.test.ts +9 -0
  162. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  163. package/src/__tests__/system-prompt.test.ts +670 -63
  164. package/src/__tests__/terminal-tools.test.ts +28 -1
  165. package/src/__tests__/thread-backfill.test.ts +557 -27
  166. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  167. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  168. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  169. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  170. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  171. package/src/__tests__/tool-executor.test.ts +16 -4
  172. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  173. package/src/__tests__/turn-events-store.test.ts +256 -0
  174. package/src/__tests__/twilio-routes.test.ts +4 -0
  175. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  176. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  177. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  178. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  179. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  180. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  181. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  182. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  183. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  184. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  185. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  186. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  187. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  188. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  189. package/src/a2a/__tests__/task-store.test.ts +246 -0
  190. package/src/a2a/agent-card.ts +58 -0
  191. package/src/a2a/feature-gate.ts +8 -0
  192. package/src/a2a/protocol-constants.ts +21 -0
  193. package/src/a2a/protocol-errors.ts +50 -0
  194. package/src/a2a/protocol-types.ts +162 -0
  195. package/src/a2a/task-store.ts +168 -0
  196. package/src/acp/resolve-agent.ts +1 -1
  197. package/src/agent/image-optimize.ts +13 -5
  198. package/src/agent/loop.ts +167 -18
  199. package/src/calls/voice-session-bridge.ts +61 -42
  200. package/src/channels/config.ts +9 -0
  201. package/src/channels/types.ts +122 -0
  202. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  203. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  204. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  205. package/src/cli/commands/__tests__/schedules.test.ts +960 -0
  206. package/src/cli/commands/changelog.ts +106 -42
  207. package/src/cli/commands/conversations.ts +102 -17
  208. package/src/cli/commands/default-action.ts +10 -53
  209. package/src/cli/commands/notifications.ts +388 -346
  210. package/src/cli/commands/plugins.ts +252 -0
  211. package/src/cli/commands/schedules.ts +683 -0
  212. package/src/cli/commands/telemetry.ts +40 -0
  213. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  214. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  215. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  216. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  217. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  218. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  219. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  220. package/src/cli/lib/cli-colors.ts +12 -0
  221. package/src/cli/lib/confirm-prompt.ts +79 -0
  222. package/src/cli/lib/install-from-github.ts +303 -0
  223. package/src/cli/lib/list-installed-plugins.ts +137 -0
  224. package/src/cli/lib/search-plugins.ts +163 -0
  225. package/src/cli/lib/uninstall-plugin.ts +82 -0
  226. package/src/cli/lib/unknown-command.ts +111 -0
  227. package/src/cli/program.ts +52 -2
  228. package/src/config/assistant-feature-flags.ts +24 -54
  229. package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
  230. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  231. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  232. package/src/config/bundled-skills/document/SKILL.md +23 -3
  233. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  234. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  235. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  236. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  237. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  238. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  239. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  240. package/src/config/bundled-tool-registry.ts +6 -0
  241. package/src/config/call-site-defaults.ts +105 -0
  242. package/src/config/feature-flag-registry.json +41 -9
  243. package/src/config/llm-resolver.ts +52 -1
  244. package/src/config/loader.ts +64 -38
  245. package/src/config/schema.ts +9 -10
  246. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  247. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  248. package/src/config/schemas/channels.ts +17 -0
  249. package/src/config/schemas/compaction.ts +28 -0
  250. package/src/config/schemas/conversations.ts +10 -0
  251. package/src/config/schemas/heartbeat.ts +23 -0
  252. package/src/config/schemas/llm-request-logs.ts +31 -7
  253. package/src/config/schemas/llm.ts +1 -0
  254. package/src/config/schemas/memory-retrieval.ts +18 -0
  255. package/src/config/schemas/memory-retrospective.ts +1 -1
  256. package/src/config/schemas/memory-v2.ts +4 -4
  257. package/src/config/schemas/memory.ts +3 -1
  258. package/src/config/schemas/tools.ts +14 -0
  259. package/src/config/seed-inference-profiles.ts +99 -29
  260. package/src/config/skills.ts +3 -96
  261. package/src/context/compactor.ts +1107 -0
  262. package/src/context/token-estimator.ts +34 -36
  263. package/src/context/window-manager.ts +197 -1520
  264. package/src/credential-execution/managed-catalog.ts +37 -0
  265. package/src/credential-health/credential-health-service.ts +280 -19
  266. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
  267. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  268. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  269. package/src/daemon/approval-generators.ts +8 -6
  270. package/src/daemon/config-watcher.ts +94 -31
  271. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  272. package/src/daemon/conversation-agent-loop.ts +198 -11
  273. package/src/daemon/conversation-error.ts +171 -37
  274. package/src/daemon/conversation-lifecycle.ts +53 -40
  275. package/src/daemon/conversation-messaging.ts +25 -6
  276. package/src/daemon/conversation-process.ts +49 -12
  277. package/src/daemon/conversation-runtime-assembly.ts +25 -1
  278. package/src/daemon/conversation-slash.ts +12 -5
  279. package/src/daemon/conversation-store.ts +11 -4
  280. package/src/daemon/conversation-tool-setup.ts +39 -7
  281. package/src/daemon/conversation.ts +33 -8
  282. package/src/daemon/date-context.ts +40 -0
  283. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  284. package/src/daemon/first-greeting.ts +22 -2
  285. package/src/daemon/guardian-action-generators.ts +1 -125
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  287. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  288. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  289. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  290. package/src/daemon/handlers/config-a2a.ts +289 -0
  291. package/src/daemon/handlers/config-model.ts +6 -5
  292. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  293. package/src/daemon/handlers/conversations.ts +1 -0
  294. package/src/daemon/handlers/shared.ts +14 -5
  295. package/src/daemon/handlers/skills.ts +111 -108
  296. package/src/daemon/history-repair.ts +28 -1
  297. package/src/daemon/host-app-control-proxy.ts +153 -27
  298. package/src/daemon/host-proxy-preactivation.ts +85 -18
  299. package/src/daemon/lifecycle.ts +89 -91
  300. package/src/daemon/meet-host-supervisor.ts +5 -4
  301. package/src/daemon/memory-v2-startup.ts +85 -0
  302. package/src/daemon/message-protocol.ts +1 -0
  303. package/src/daemon/message-types/conversations.ts +25 -0
  304. package/src/daemon/message-types/messages.ts +61 -0
  305. package/src/daemon/message-types/notifications.ts +21 -0
  306. package/src/daemon/message-types/subagents.ts +1 -0
  307. package/src/daemon/message-types/sync.ts +1 -0
  308. package/src/daemon/pkb-reminder-builder.test.ts +11 -54
  309. package/src/daemon/pkb-reminder-builder.ts +5 -20
  310. package/src/daemon/plugin-source-watcher.ts +146 -0
  311. package/src/daemon/process-message.ts +24 -3
  312. package/src/daemon/server.ts +11 -2
  313. package/src/daemon/skill-memory-refresh.ts +33 -0
  314. package/src/daemon/wake-target-adapter.ts +2 -0
  315. package/src/documents/document-store.ts +221 -3
  316. package/src/embedded/plugin-api.ts +40 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  318. package/src/export/transcript-formatter.ts +54 -20
  319. package/src/filing/filing-service.ts +39 -0
  320. package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
  321. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  322. package/src/heartbeat/heartbeat-service.ts +73 -189
  323. package/src/home/__tests__/feed-types.test.ts +80 -0
  324. package/src/home/feed-types.ts +36 -2
  325. package/src/home/post-connect-feed.ts +1 -0
  326. package/src/index.ts +18 -1
  327. package/src/ipc/cli-client.ts +147 -45
  328. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  329. package/src/mcp/client.ts +20 -4
  330. package/src/media/image-credentials.ts +3 -3
  331. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  332. package/src/memory/__tests__/conversation-queries.test.ts +483 -0
  333. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  334. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  335. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  336. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  337. package/src/memory/__tests__/message-content.test.ts +35 -0
  338. package/src/memory/bookmark-crud.ts +42 -10
  339. package/src/memory/context-search/sources/conversations.ts +62 -2
  340. package/src/memory/context-search/sources/workspace.ts +4 -0
  341. package/src/memory/conversation-crud.ts +63 -19
  342. package/src/memory/conversation-queries.ts +197 -11
  343. package/src/memory/conversation-title-service.ts +26 -4
  344. package/src/memory/db-init.ts +12 -0
  345. package/src/memory/delivery-crud.ts +152 -5
  346. package/src/memory/embedding-backend.ts +4 -4
  347. package/src/memory/external-conversation-store.ts +66 -5
  348. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
  349. package/src/memory/graph/conversation-graph-memory.ts +49 -21
  350. package/src/memory/graph/tools.ts +9 -40
  351. package/src/memory/indexer.ts +34 -29
  352. package/src/memory/invite-store.ts +53 -0
  353. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  354. package/src/memory/jobs/embed-concept-page.ts +20 -11
  355. package/src/memory/jobs-worker.ts +6 -1
  356. package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
  357. package/src/memory/llm-request-log-source.ts +19 -52
  358. package/src/memory/llm-request-log-store.ts +92 -1
  359. package/src/memory/llm-usage-store.ts +125 -5
  360. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  361. package/src/memory/memory-retrospective-job.ts +33 -6
  362. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  363. package/src/memory/message-content.ts +1 -1
  364. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  365. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  366. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  367. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  368. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  369. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  370. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  371. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  372. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  373. package/src/memory/migrations/index.ts +9 -0
  374. package/src/memory/migrations/registry.ts +16 -0
  375. package/src/memory/onboarding-events-store.ts +106 -0
  376. package/src/memory/schema/a2a.ts +15 -0
  377. package/src/memory/schema/bookmarks.ts +0 -2
  378. package/src/memory/schema/calls.ts +1 -0
  379. package/src/memory/schema/index.ts +1 -0
  380. package/src/memory/schema/inference.ts +3 -3
  381. package/src/memory/schema/infrastructure.ts +13 -0
  382. package/src/memory/turn-events-store.ts +127 -2
  383. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  384. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  385. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  386. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  387. package/src/memory/v2/__tests__/injection.test.ts +288 -11
  388. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  389. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  390. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  391. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  392. package/src/memory/v2/__tests__/router.test.ts +15 -0
  393. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  394. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  395. package/src/memory/v2/activation-store.ts +14 -16
  396. package/src/memory/v2/cli-command-content.ts +19 -0
  397. package/src/memory/v2/cli-command-store.ts +304 -0
  398. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  399. package/src/memory/v2/injection.ts +81 -26
  400. package/src/memory/v2/migration.ts +49 -19
  401. package/src/memory/v2/page-index.ts +63 -8
  402. package/src/memory/v2/prompts/router.ts +11 -8
  403. package/src/memory/v2/prompts/sweep.ts +2 -2
  404. package/src/memory/v2/qdrant.ts +135 -7
  405. package/src/memory/v2/router.ts +9 -8
  406. package/src/memory/v2/skill-store.ts +120 -35
  407. package/src/memory/v2/static-context.ts +4 -4
  408. package/src/memory/v2/types.ts +23 -0
  409. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  410. package/src/messaging/providers/a2a/deliver.ts +156 -0
  411. package/src/messaging/providers/gmail/client.ts +9 -2
  412. package/src/messaging/providers/index.ts +11 -2
  413. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  414. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  415. package/src/messaging/providers/slack/adapter.ts +43 -5
  416. package/src/messaging/providers/slack/client.ts +27 -0
  417. package/src/messaging/providers/slack/deep-link.ts +65 -0
  418. package/src/messaging/providers/slack/download.ts +104 -0
  419. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  420. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  421. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  422. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  423. package/src/messaging/providers/slack/types.ts +20 -1
  424. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  425. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  426. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  427. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  428. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  429. package/src/notifications/adapters/macos.ts +12 -2
  430. package/src/notifications/broadcaster.ts +29 -4
  431. package/src/notifications/conversation-pairing.ts +2 -1
  432. package/src/notifications/copy-composer.ts +17 -64
  433. package/src/notifications/decision-engine.ts +113 -45
  434. package/src/notifications/deterministic-checks.ts +96 -0
  435. package/src/notifications/emit-signal.ts +21 -1
  436. package/src/notifications/home-feed-side-effect.ts +138 -5
  437. package/src/notifications/signal.ts +3 -5
  438. package/src/notifications/types.ts +8 -0
  439. package/src/oauth/connection-resolver.ts +8 -4
  440. package/src/oauth/platform-connection.test.ts +43 -3
  441. package/src/oauth/platform-connection.ts +19 -6
  442. package/src/oauth/seed-providers.ts +10 -1
  443. package/src/permissions/checker.ts +2 -0
  444. package/src/permissions/ipc-risk-types.ts +1 -0
  445. package/src/permissions/question-prompter.test.ts +416 -0
  446. package/src/permissions/question-prompter.ts +294 -0
  447. package/src/platform/client.test.ts +1 -1
  448. package/src/platform/client.ts +1 -1
  449. package/src/plugin-api/constants.ts +26 -0
  450. package/src/plugin-api/index.ts +34 -1
  451. package/src/plugin-api/types.ts +104 -22
  452. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  453. package/src/plugins/defaults/compaction.ts +0 -4
  454. package/src/plugins/defaults/empty-response.ts +0 -2
  455. package/src/plugins/defaults/history-repair.ts +0 -2
  456. package/src/plugins/defaults/injectors.ts +74 -22
  457. package/src/plugins/defaults/llm-call.ts +0 -2
  458. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  459. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  460. package/src/plugins/defaults/persistence.ts +0 -2
  461. package/src/plugins/defaults/title-generate.ts +0 -5
  462. package/src/plugins/defaults/token-estimate.ts +0 -2
  463. package/src/plugins/defaults/tool-error.ts +0 -7
  464. package/src/plugins/defaults/tool-execute.ts +0 -2
  465. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  466. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  467. package/src/plugins/external-api.ts +104 -0
  468. package/src/plugins/external-plugin-loader.ts +187 -42
  469. package/src/plugins/feature-gate.ts +22 -0
  470. package/src/plugins/pipeline.ts +37 -0
  471. package/src/plugins/registry.ts +48 -80
  472. package/src/plugins/types.ts +40 -26
  473. package/src/plugins/user-loader.ts +21 -2
  474. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  475. package/src/proactive-artifact/job.test.ts +37 -5
  476. package/src/prompts/__tests__/system-prompt.test.ts +10 -43
  477. package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
  478. package/src/prompts/normalize-onboarding.ts +27 -0
  479. package/src/prompts/sections.ts +302 -0
  480. package/src/prompts/system-prompt.ts +63 -174
  481. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  482. package/src/prompts/templates/system-sections.ts +164 -0
  483. package/src/providers/__tests__/inference.test.ts +24 -7
  484. package/src/providers/anthropic/client.ts +28 -28
  485. package/src/providers/call-site-routing.ts +24 -6
  486. package/src/providers/connection-resolution.ts +68 -11
  487. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  488. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  489. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  490. package/src/providers/inference/adapter-factory.ts +32 -6
  491. package/src/providers/inference/auth.ts +12 -0
  492. package/src/providers/inference/backfill.ts +14 -1
  493. package/src/providers/inference/connections.ts +159 -34
  494. package/src/providers/inference/resolve-auth.ts +14 -4
  495. package/src/providers/model-catalog.ts +249 -12
  496. package/src/providers/model-intents.ts +3 -3
  497. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  498. package/src/providers/openai/chat-completions-provider.ts +169 -8
  499. package/src/providers/openrouter/client.ts +49 -4
  500. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
  501. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  502. package/src/providers/provider-availability.ts +17 -2
  503. package/src/providers/provider-catalog-visibility.ts +38 -0
  504. package/src/providers/provider-send-message.ts +27 -12
  505. package/src/providers/registry.ts +52 -15
  506. package/src/providers/retry.ts +47 -1
  507. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  508. package/src/runtime/agent-wake.ts +103 -15
  509. package/src/runtime/auth/route-policy.ts +21 -1
  510. package/src/runtime/btw-sidechain.ts +2 -0
  511. package/src/runtime/http-server.ts +7 -16
  512. package/src/runtime/http-types.ts +19 -47
  513. package/src/runtime/migrations/origin-mode.ts +1 -1
  514. package/src/runtime/pending-interactions.ts +1 -0
  515. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  516. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  517. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  518. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
  519. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  520. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  521. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  522. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  523. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  524. package/src/runtime/routes/acp-routes.ts +5 -3
  525. package/src/runtime/routes/auth-routes.ts +1 -1
  526. package/src/runtime/routes/bookmark-routes.ts +5 -3
  527. package/src/runtime/routes/btw-routes.ts +5 -1
  528. package/src/runtime/routes/channel-availability-routes.ts +126 -0
  529. package/src/runtime/routes/consolidation-routes.ts +100 -0
  530. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  531. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  532. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  533. package/src/runtime/routes/conversation-query-routes.ts +99 -35
  534. package/src/runtime/routes/conversation-routes.ts +97 -11
  535. package/src/runtime/routes/documents-routes.ts +25 -86
  536. package/src/runtime/routes/group-routes.ts +5 -0
  537. package/src/runtime/routes/inbound-conversation.ts +28 -8
  538. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  539. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  540. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  541. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  542. package/src/runtime/routes/index.ts +8 -0
  543. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  544. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  545. package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
  546. package/src/runtime/routes/integrations/a2a.ts +235 -0
  547. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  548. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  549. package/src/runtime/routes/integrations/twilio.ts +6 -13
  550. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  551. package/src/runtime/routes/notification-routes.ts +1 -1
  552. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  553. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  554. package/src/runtime/routes/question-routes.ts +259 -0
  555. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  556. package/src/runtime/routes/schedule-routes.ts +4 -7
  557. package/src/runtime/routes/subagents-routes.ts +98 -18
  558. package/src/runtime/routes/telemetry-routes.ts +27 -0
  559. package/src/runtime/routes/tts-routes.ts +27 -2
  560. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  561. package/src/runtime/routes/workspace-routes.ts +28 -0
  562. package/src/runtime/services/conversation-serializer.ts +39 -7
  563. package/src/runtime/sync/resource-sync-events.ts +93 -1
  564. package/src/schedule/schedule-store.ts +27 -2
  565. package/src/schedule/scheduler.ts +9 -1
  566. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  567. package/src/security/untrusted-content.ts +93 -8
  568. package/src/skills/catalog-files.ts +1 -1
  569. package/src/skills/catalog-install.ts +233 -116
  570. package/src/skills/clawhub.ts +70 -13
  571. package/src/skills/managed-store.ts +4 -119
  572. package/src/skills/skillssh-registry.ts +27 -48
  573. package/src/subagent/manager.ts +17 -7
  574. package/src/telemetry/types.ts +113 -1
  575. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  576. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  577. package/src/tools/apps/executors.ts +58 -7
  578. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  579. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  580. package/src/tools/browser/browser-execution.ts +15 -11
  581. package/src/tools/computer-use/definitions.ts +3 -3
  582. package/src/tools/credentials/vault.ts +1 -1
  583. package/src/tools/document/document-tool.ts +124 -1
  584. package/src/tools/filesystem/edit.ts +1 -1
  585. package/src/tools/filesystem/list.ts +1 -1
  586. package/src/tools/filesystem/read.ts +1 -1
  587. package/src/tools/filesystem/write.ts +5 -2
  588. package/src/tools/host-filesystem/transfer.ts +1 -1
  589. package/src/tools/host-terminal/host-shell.ts +1 -1
  590. package/src/tools/memory/register.ts +1 -9
  591. package/src/tools/permission-checker.ts +1 -1
  592. package/src/tools/registry.ts +17 -7
  593. package/src/tools/schedule/create.ts +2 -2
  594. package/src/tools/schema-transforms.ts +7 -2
  595. package/src/tools/side-effects.ts +1 -0
  596. package/src/tools/skills/delete-managed.ts +4 -4
  597. package/src/tools/skills/execute.ts +1 -1
  598. package/src/tools/skills/scaffold-managed.ts +3 -2
  599. package/src/tools/subagent/notify-parent.ts +1 -1
  600. package/src/tools/system/request-permission.ts +2 -2
  601. package/src/tools/terminal/safe-env.ts +60 -1
  602. package/src/tools/tool-manifest.ts +2 -0
  603. package/src/tools/types.ts +107 -21
  604. package/src/tools/ui-surface/definitions.ts +6 -5
  605. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  606. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  607. package/src/types/onboarding-context.ts +2 -0
  608. package/src/util/errors.ts +17 -0
  609. package/src/util/platform.ts +10 -0
  610. package/src/watcher/__tests__/engine.test.ts +22 -0
  611. package/src/watcher/engine.ts +6 -2
  612. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  613. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  614. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  615. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  616. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  617. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  618. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  619. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  620. package/src/workspace/migrations/registry.ts +10 -0
  621. package/src/workspace/migrations/runner.ts +39 -9
  622. package/src/workspace/migrations/types.ts +4 -0
  623. package/examples/plugins/echo/bun.lock +0 -25
  624. package/src/__tests__/context-window-manager.test.ts +0 -2481
  625. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  626. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  627. package/src/context/prompts/compact.md +0 -26
  628. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  629. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  630. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -14,6 +14,7 @@ import {
14
14
  like,
15
15
  lt,
16
16
  lte,
17
+ or,
17
18
  sql,
18
19
  } from "drizzle-orm";
19
20
  import { v4 as uuid } from "uuid";
@@ -86,6 +87,7 @@ const subagentNotificationSchema = z.object({
86
87
  status: z.enum(["running", "completed", "failed", "aborted"]),
87
88
  error: z.string().optional(),
88
89
  conversationId: z.string().optional(),
90
+ objective: z.string().optional(),
89
91
  });
90
92
 
91
93
  export const messageMetadataSchema = z
@@ -94,6 +96,16 @@ export const messageMetadataSchema = z
94
96
  assistantMessageChannel: channelIdSchema.optional(),
95
97
  userMessageInterface: interfaceIdSchema.optional(),
96
98
  assistantMessageInterface: interfaceIdSchema.optional(),
99
+ /**
100
+ * Optional client-side metadata bag attached to user messages by HTTP
101
+ * header middleware (reads `x-vellum-browser-family`,
102
+ * `x-vellum-browser-version`, `x-vellum-client-os`,
103
+ * `x-vellum-interface-version`). Forwarded verbatim onto
104
+ * `TurnTelemetryEvent.client` for downstream analytics. Kept as a
105
+ * permissive `record` so adding a new client field doesn't require a
106
+ * migration -- dbt can unpack later via JSON_VALUE.
107
+ */
108
+ client: z.record(z.string(), z.unknown()).optional(),
97
109
  subagentNotification: subagentNotificationSchema.optional(),
98
110
  /**
99
111
  * Trust class of the actor at the time this message was persisted.
@@ -727,9 +739,14 @@ export function forkConversation(params: {
727
739
 
728
740
  // Carry the parent's per-conversation memory state into the child so the
729
741
  // forked thread resumes with the same activation/injection log and
730
- // in-context tracker the parent had at fork time.
731
- forkActivationState(db, sourceConversation.id, fc.id);
732
- forkGraphMemoryState(sourceConversation.id, fc.id);
742
+ // in-context tracker the parent had at fork time. Only valid for
743
+ // full-history forks: a truncated fork would inherit activation/tracker
744
+ // entries for turns the child does not actually contain.
745
+ const isFullHistoryFork = copyBoundaryIndex === sourceMessages.length - 1;
746
+ if (isFullHistoryFork) {
747
+ forkActivationState(db, sourceConversation.id, fc.id);
748
+ forkGraphMemoryState(sourceConversation.id, fc.id);
749
+ }
733
750
  forkRetrospectiveState({
734
751
  database: db,
735
752
  sourceConversationId: sourceConversation.id,
@@ -1040,22 +1057,23 @@ export function getMessages(conversationId: string): MessageRow[] {
1040
1057
  }
1041
1058
 
1042
1059
  /**
1043
- * Return raw `metadata` strings for messages whose metadata contains the
1044
- * literal substring `"slackMeta"`, capped at `limit` and skipping the first
1045
- * `offset` matches. Pushes `LIKE` + `LIMIT`/`OFFSET` into SQL so warm Slack
1046
- * DM conversations don't require a full-table scan on the webhook critical
1047
- * path. The substring match is an indexable prefilter only — callers must
1048
- * parse and validate each returned string against the Slack metadata schema,
1049
- * because a malformed row (partial write, legacy format, unrelated key
1050
- * accidentally containing the literal) can still slip through the substring
1051
- * match. Callers that need a fixed number of *valid* rows should iterate
1052
- * with increasing offsets until the target is reached (capped at a
1053
- * reasonable maximum to bound scan cost).
1060
+ * Return raw `metadata` strings for messages whose metadata looks like it may
1061
+ * contain Slack metadata, capped at `limit` and skipping the first `offset`
1062
+ * matches. Pushes `LIKE` + `LIMIT`/`OFFSET` into SQL so warm Slack DM
1063
+ * conversations don't require a full-table scan on the webhook critical path.
1064
+ * The substring match is an indexable prefilter only — callers must parse and
1065
+ * validate each returned string against the Slack metadata schema, because a
1066
+ * malformed row (partial write, legacy format, unrelated key accidentally
1067
+ * containing the literal) can still slip through the substring match. Callers
1068
+ * that need a fixed number of *valid* rows should iterate with increasing
1069
+ * offsets until the target is reached (capped at a reasonable maximum to bound
1070
+ * scan cost).
1054
1071
  */
1055
1072
  export function selectSlackMetaCandidateMetadata(
1056
1073
  conversationId: string,
1057
1074
  limit: number,
1058
1075
  offset = 0,
1076
+ opts?: { includeFlatLegacy?: boolean },
1059
1077
  ): string[] {
1060
1078
  const db = getDb();
1061
1079
  const rows = db
@@ -1064,7 +1082,12 @@ export function selectSlackMetaCandidateMetadata(
1064
1082
  .where(
1065
1083
  and(
1066
1084
  eq(messages.conversationId, conversationId),
1067
- like(messages.metadata, '%"slackMeta"%'),
1085
+ opts?.includeFlatLegacy
1086
+ ? or(
1087
+ like(messages.metadata, '%"slackMeta"%'),
1088
+ like(messages.metadata, '%"source":"slack"%'),
1089
+ )
1090
+ : like(messages.metadata, '%"slackMeta"%'),
1068
1091
  ),
1069
1092
  )
1070
1093
  .orderBy(asc(messages.createdAt))
@@ -1110,13 +1133,23 @@ export function countMessagesAfter(
1110
1133
  .where(eq(messages.id, afterMessageId))
1111
1134
  .get();
1112
1135
  if (!ref) return 0;
1136
+ // Tie-breaker on `messages.id` so rows that share a millisecond timestamp
1137
+ // with the reference are not permanently skipped. Mirrors the
1138
+ // `(createdAt, id)` cursor pattern used by the backfill job-handler and
1139
+ // turn-events-store.
1113
1140
  const row = db
1114
1141
  .select({ c: count() })
1115
1142
  .from(messages)
1116
1143
  .where(
1117
1144
  and(
1118
1145
  eq(messages.conversationId, conversationId),
1119
- gt(messages.createdAt, ref.createdAt),
1146
+ or(
1147
+ gt(messages.createdAt, ref.createdAt),
1148
+ and(
1149
+ eq(messages.createdAt, ref.createdAt),
1150
+ gt(messages.id, afterMessageId),
1151
+ ),
1152
+ ),
1120
1153
  ),
1121
1154
  )
1122
1155
  .get();
@@ -1136,11 +1169,14 @@ export function getMessagesAfter(
1136
1169
  ): MessageRow[] {
1137
1170
  const db = getDb();
1138
1171
  if (afterMessageId === null || afterMessageId === "") {
1172
+ // Secondary `asc(messages.id)` matches the non-null path's cursor
1173
+ // ordering, so callers tracking `cutoffMessageId` across runs see a
1174
+ // consistent ordering when multiple rows share a millisecond timestamp.
1139
1175
  return db
1140
1176
  .select()
1141
1177
  .from(messages)
1142
1178
  .where(eq(messages.conversationId, conversationId))
1143
- .orderBy(asc(messages.createdAt))
1179
+ .orderBy(asc(messages.createdAt), asc(messages.id))
1144
1180
  .all()
1145
1181
  .map(parseMessage);
1146
1182
  }
@@ -1150,16 +1186,24 @@ export function getMessagesAfter(
1150
1186
  .where(eq(messages.id, afterMessageId))
1151
1187
  .get();
1152
1188
  if (!ref) return [];
1189
+ // Same `(createdAt, id)` cursor as `countMessagesAfter` — rows sharing
1190
+ // the reference's millisecond timestamp would otherwise be skipped.
1153
1191
  return db
1154
1192
  .select()
1155
1193
  .from(messages)
1156
1194
  .where(
1157
1195
  and(
1158
1196
  eq(messages.conversationId, conversationId),
1159
- gt(messages.createdAt, ref.createdAt),
1197
+ or(
1198
+ gt(messages.createdAt, ref.createdAt),
1199
+ and(
1200
+ eq(messages.createdAt, ref.createdAt),
1201
+ gt(messages.id, afterMessageId),
1202
+ ),
1203
+ ),
1160
1204
  ),
1161
1205
  )
1162
- .orderBy(asc(messages.createdAt))
1206
+ .orderBy(asc(messages.createdAt), asc(messages.id))
1163
1207
  .all()
1164
1208
  .map(parseMessage);
1165
1209
  }
@@ -1,5 +1,11 @@
1
- import { and, count, desc, eq, sql } from "drizzle-orm";
1
+ import { and, count, desc, eq, inArray, isNull, sql } from "drizzle-orm";
2
2
 
3
+ import {
4
+ parseExternalContentEnvelope,
5
+ type UntrustedContentSource,
6
+ unwrapExternalContentForDisplay,
7
+ wrapUntrustedContent,
8
+ } from "../security/untrusted-content.js";
3
9
  import { getLogger } from "../util/logger.js";
4
10
  import type { ConversationRow } from "./conversation-crud.js";
5
11
  import { parseConversation } from "./conversation-crud.js";
@@ -55,9 +61,14 @@ export function listConversations(
55
61
  // SQLite file without running migrations in-process, so legacy private rows
56
62
  // can briefly exist before migration cleanup. Hide them from foreground
57
63
  // lists until the next migration pass deletes them.
64
+ //
65
+ // group_id is checked alongside conversationType so that conversations
66
+ // routed to system:background (e.g. heartbeat) via conversationMetadata
67
+ // but created with conversationType "standard" (vellum channel strategy)
68
+ // appear in the correct bucket.
58
69
  const typeCond = backgroundOnly
59
- ? sql`${conversations.conversationType} IN ('background', 'scheduled') AND (${conversations.source} IS NULL OR ${conversations.source} != 'subagent')`
60
- : sql`${conversations.conversationType} NOT IN ('background', 'scheduled', 'private')`;
70
+ ? sql`(${conversations.conversationType} IN ('background', 'scheduled') OR group_id IN ('system:background', 'system:scheduled')) AND (${conversations.source} IS NULL OR ${conversations.source} != 'subagent')`
71
+ : sql`${conversations.conversationType} NOT IN ('background', 'scheduled', 'private') AND COALESCE(group_id, 'system:all') NOT IN ('system:background', 'system:scheduled')`;
61
72
  const where = includeArchived
62
73
  ? typeCond
63
74
  : sql`${typeCond} AND ${conversations.archivedAt} IS NULL`;
@@ -75,6 +86,92 @@ export function listConversations(
75
86
  return query.all().map(parseConversation);
76
87
  }
77
88
 
89
+ /**
90
+ * List conversations matching an exact `source` value, ordered by `createdAt`
91
+ * descending. The surgical filter for "find every background run produced by
92
+ * job X" — heartbeat, memory_v2_consolidation, watcher-engine, etc. — since
93
+ * `source` is the canonical job-class distinguisher across the background
94
+ * bucket. `conversationType` + `group_id` only narrow to "background vs
95
+ * scheduled vs standard"; neither identifies which job produced the row.
96
+ *
97
+ * Filter is exact (no `LIKE`, no implicit exclusions): the route layer is
98
+ * responsible for knowing which source constants exist and passing one. The
99
+ * defensive `source != 'subagent'` carve-out applied by `listConversations`
100
+ * is deliberately NOT replicated here — a caller asking for an exact source
101
+ * gets exactly that source.
102
+ *
103
+ * @param source Exact match against `conversations.source`. Pass the
104
+ * canonical constant (e.g. `MEMORY_V2_CONSOLIDATION_SOURCE`).
105
+ * @param limit Maximum rows to return (default 20).
106
+ * @param opts.includeArchived Include rows with non-null `archivedAt`.
107
+ * Defaults to `true` so callers that want a full
108
+ * run history get one; pass `false` for views
109
+ * that hide archived rows.
110
+ */
111
+ export function listConversationsBySource(
112
+ source: string,
113
+ limit = 20,
114
+ opts?: { includeArchived?: boolean },
115
+ ): ConversationRow[] {
116
+ const db = getDb();
117
+ const includeArchived = opts?.includeArchived ?? true;
118
+ const where = includeArchived
119
+ ? eq(conversations.source, source)
120
+ : and(eq(conversations.source, source), isNull(conversations.archivedAt));
121
+ const rows = db
122
+ .select()
123
+ .from(conversations)
124
+ .where(where)
125
+ .orderBy(desc(conversations.createdAt))
126
+ .limit(limit)
127
+ .all();
128
+ return rows.map(parseConversation);
129
+ }
130
+
131
+ /**
132
+ * Per-conversation aggregate of messages with a specific role. Powers
133
+ * heartbeat-shaped run endpoints (e.g. `consolidation/runs`) that need a
134
+ * "did the agent emit any output?" signal stronger than
135
+ * `conversations.lastMessageAt` — which is bumped by the kickoff user
136
+ * prompt and so cannot distinguish "agent ran" from "agent dispatched but
137
+ * crashed before responding".
138
+ *
139
+ * Single batched aggregate query (no N+1). Conversations with zero matching
140
+ * messages are NOT present in the returned map — callers should treat a
141
+ * missing key as `{ count: 0, lastAt: null }`.
142
+ *
143
+ * @param conversationIds Conversation ids to look up (empty → empty map).
144
+ * @param role Message role to count (default `"assistant"`).
145
+ */
146
+ export function getMessageRoleStatsByConversation(
147
+ conversationIds: string[],
148
+ role: string = "assistant",
149
+ ): Map<string, { count: number; lastAt: number }> {
150
+ if (conversationIds.length === 0) return new Map();
151
+ const db = getDb();
152
+ const rows = db
153
+ .select({
154
+ conversationId: messages.conversationId,
155
+ count: sql<number>`COUNT(*)`.as("count"),
156
+ lastAt: sql<number>`MAX(${messages.createdAt})`.as("last_at"),
157
+ })
158
+ .from(messages)
159
+ .where(
160
+ and(
161
+ inArray(messages.conversationId, conversationIds),
162
+ eq(messages.role, role),
163
+ ),
164
+ )
165
+ .groupBy(messages.conversationId)
166
+ .all();
167
+ return new Map(
168
+ rows.map((r) => [
169
+ r.conversationId,
170
+ { count: Number(r.count), lastAt: Number(r.lastAt) },
171
+ ]),
172
+ );
173
+ }
174
+
78
175
  export function listPinnedConversations(): ConversationRow[] {
79
176
  ensureDisplayOrderMigration();
80
177
  ensureGroupMigration();
@@ -151,10 +248,11 @@ export function listConversationsByTitlePrefix(
151
248
  }
152
249
 
153
250
  export function countConversations(backgroundOnly = false): number {
251
+ ensureGroupMigration();
154
252
  const db = getDb();
155
253
  const where = backgroundOnly
156
- ? sql`${conversations.conversationType} IN ('background', 'scheduled') AND (${conversations.source} IS NULL OR ${conversations.source} != 'subagent')`
157
- : sql`${conversations.conversationType} NOT IN ('background', 'scheduled')`;
254
+ ? sql`(${conversations.conversationType} IN ('background', 'scheduled') OR group_id IN ('system:background', 'system:scheduled')) AND (${conversations.source} IS NULL OR ${conversations.source} != 'subagent')`
255
+ : sql`${conversations.conversationType} NOT IN ('background', 'scheduled', 'private') AND COALESCE(group_id, 'system:all') NOT IN ('system:background', 'system:scheduled')`;
158
256
  const [{ total }] = db
159
257
  .select({ total: count() })
160
258
  .from(conversations)
@@ -233,6 +331,7 @@ export function searchConversations(
233
331
  ): ConversationSearchResult[] {
234
332
  if (!query.trim()) return [];
235
333
 
334
+ ensureGroupMigration();
236
335
  const db = getDb();
237
336
  const limit = opts?.limit ?? 20;
238
337
  const maxMsgsPerConv = opts?.maxMessagesPerConversation ?? 3;
@@ -262,7 +361,7 @@ export function searchConversations(
262
361
  FROM messages_fts f
263
362
  JOIN messages m ON m.id = f.message_id
264
363
  JOIN conversations c ON c.id = m.conversation_id
265
- WHERE messages_fts MATCH ? AND c.conversation_type NOT IN ('background', 'scheduled', 'private') AND c.archived_at IS NULL
364
+ WHERE messages_fts MATCH ? AND c.conversation_type NOT IN ('background', 'scheduled', 'private') AND COALESCE(c.group_id, 'system:all') NOT IN ('system:background', 'system:scheduled') AND c.archived_at IS NULL
266
365
  LIMIT 1000
267
366
  `,
268
367
  ftsMatch,
@@ -287,7 +386,7 @@ export function searchConversations(
287
386
  SELECT DISTINCT m.conversation_id
288
387
  FROM messages m
289
388
  JOIN conversations c ON c.id = m.conversation_id
290
- WHERE m.content LIKE ? ESCAPE '\\' AND c.conversation_type NOT IN ('background', 'scheduled', 'private') AND c.archived_at IS NULL
389
+ WHERE m.content LIKE ? ESCAPE '\\' AND c.conversation_type NOT IN ('background', 'scheduled', 'private') AND COALESCE(c.group_id, 'system:all') NOT IN ('system:background', 'system:scheduled') AND c.archived_at IS NULL
291
390
  LIMIT 1000
292
391
  `,
293
392
  likePattern,
@@ -302,6 +401,7 @@ export function searchConversations(
302
401
  .where(
303
402
  and(
304
403
  sql`${conversations.conversationType} NOT IN ('background', 'scheduled', 'private')`,
404
+ sql`COALESCE(group_id, 'system:all') NOT IN ('system:background', 'system:scheduled')`,
305
405
  sql`${conversations.title} LIKE ${titlePattern} ESCAPE '\\'`,
306
406
  sql`${conversations.archivedAt} IS NULL`,
307
407
  ),
@@ -403,29 +503,68 @@ export function searchConversations(
403
503
  * text; we extract a readable snippet in either case.
404
504
  */
405
505
  export function buildExcerpt(rawContent: string, query: string): string {
506
+ return buildExcerptWithExternalContentMode(rawContent, query, "display");
507
+ }
508
+
509
+ /**
510
+ * Build an excerpt for model-facing recall evidence. Unlike display excerpts,
511
+ * this keeps complete external_content envelopes around untrusted snippets so
512
+ * the model still sees clear third-party content boundaries.
513
+ */
514
+ export function buildRecallEvidenceExcerpt(
515
+ rawContent: string,
516
+ query: string,
517
+ ): string {
518
+ return buildExcerptWithExternalContentMode(rawContent, query, "preserve");
519
+ }
520
+
521
+ function buildExcerptWithExternalContentMode(
522
+ rawContent: string,
523
+ query: string,
524
+ externalContentMode: "display" | "preserve",
525
+ ): string {
406
526
  // Try to extract plain text from JSON content blocks first.
407
527
  let text = rawContent;
408
528
  try {
409
529
  const parsed = JSON.parse(rawContent);
410
530
  if (Array.isArray(parsed)) {
411
531
  const parts: string[] = [];
532
+ let preservedExternalContent = false;
412
533
  for (const block of parsed) {
413
534
  if (typeof block === "object" && block != null) {
414
535
  if (block.type === "text" && typeof block.text === "string") {
415
- parts.push(block.text);
536
+ if (externalContentMode === "display") {
537
+ parts.push(unwrapExternalContentForDisplay(block.text));
538
+ } else {
539
+ const excerpt = buildRecallEvidenceText(block.text, query);
540
+ parts.push(excerpt.text);
541
+ preservedExternalContent ||= excerpt.preservedExternalContent;
542
+ }
416
543
  } else if (
417
544
  block.type === "tool_result" ||
418
545
  block.type === "web_search_tool_result"
419
546
  ) {
420
547
  const inner = Array.isArray(block.content) ? block.content : [];
421
548
  for (const ib of inner) {
422
- if (ib?.type === "text" && typeof ib.text === "string")
423
- parts.push(ib.text);
549
+ if (ib?.type === "text" && typeof ib.text === "string") {
550
+ if (externalContentMode === "display") {
551
+ parts.push(unwrapExternalContentForDisplay(ib.text));
552
+ } else {
553
+ const excerpt = buildRecallEvidenceText(ib.text, query);
554
+ parts.push(excerpt.text);
555
+ preservedExternalContent ||= excerpt.preservedExternalContent;
556
+ }
557
+ }
424
558
  }
425
559
  }
426
560
  }
427
561
  }
428
- if (parts.length > 0) text = parts.join(" ");
562
+ if (parts.length > 0) {
563
+ text = parts.join(" ");
564
+ if (externalContentMode === "preserve" && preservedExternalContent) {
565
+ return text;
566
+ }
567
+ }
429
568
  } else if (typeof parsed === "string") {
430
569
  text = parsed;
431
570
  }
@@ -433,6 +572,53 @@ export function buildExcerpt(rawContent: string, query: string): string {
433
572
  // Not JSON — use as-is
434
573
  }
435
574
 
575
+ if (externalContentMode === "display") {
576
+ text = unwrapExternalContentForDisplay(text);
577
+ } else {
578
+ const envelope = parseExternalContentEnvelope(text);
579
+ if (envelope) {
580
+ const innerExcerpt = buildExcerptFromText(envelope.content, query);
581
+ return wrapRecallEvidenceExcerpt(
582
+ innerExcerpt,
583
+ envelope.source,
584
+ envelope.origin,
585
+ );
586
+ }
587
+ }
588
+
589
+ return buildExcerptFromText(text, query);
590
+ }
591
+
592
+ function buildRecallEvidenceText(
593
+ text: string,
594
+ query: string,
595
+ ): { text: string; preservedExternalContent: boolean } {
596
+ const envelope = parseExternalContentEnvelope(text);
597
+ if (!envelope) {
598
+ return { text, preservedExternalContent: false };
599
+ }
600
+ const innerExcerpt = buildExcerptFromText(envelope.content, query);
601
+ return {
602
+ text: wrapRecallEvidenceExcerpt(
603
+ innerExcerpt,
604
+ envelope.source,
605
+ envelope.origin,
606
+ ),
607
+ preservedExternalContent: true,
608
+ };
609
+ }
610
+
611
+ function wrapRecallEvidenceExcerpt(
612
+ excerpt: string,
613
+ source: UntrustedContentSource,
614
+ origin?: string,
615
+ ): string {
616
+ return origin
617
+ ? wrapUntrustedContent(excerpt, { source, sourceDetail: origin })
618
+ : wrapUntrustedContent(excerpt, { source });
619
+ }
620
+
621
+ function buildExcerptFromText(text: string, query: string): string {
436
622
  const WINDOW = 100;
437
623
  const lowerText = text.toLowerCase();
438
624
  const lowerQuery = query.toLowerCase();
@@ -298,9 +298,11 @@ function buildTitleSystemPrompt(): string {
298
298
  "You generate ultra-concise conversation titles. Output ONLY the title text — no explanation, no quotes, no markdown, no preamble.",
299
299
  "",
300
300
  "Rules:",
301
- "- 2–6 words. Titles longer than 6 words are unacceptable — ruthlessly compress",
302
- "- Summarize the TOPIC, not the request or instructions",
303
- "- Noun phrases are ideal (e.g. 'Auth Middleware Rewrite', 'Docker Volume Mounts')",
301
+ "- 2–5 words maximum. Titles longer than 5 words are unacceptable — ruthlessly compress to a short noun phrase",
302
+ "- 40 characters absolute maximum if your title exceeds 40 characters it will be truncated and look broken",
303
+ "- Summarize only the TOPIC, not the request or instructions",
304
+ "- Noun phrases are ideal (e.g. 'Auth Middleware Rewrite', 'Docker Volume Mounts', 'Onboarding Flow')",
305
+ "- Think: what would make a scannable sidebar label?",
304
306
  "- Do NOT echo back what the user asked you to do",
305
307
  "- Do NOT respond to the conversation content",
306
308
  "- Do NOT assess feasibility or comment on capabilities",
@@ -353,13 +355,33 @@ const META_FAILURE_TITLES = new Set([
353
355
  "no content",
354
356
  ]);
355
357
 
358
+ const MAX_TITLE_LENGTH = 40;
359
+ const MAX_TITLE_WORDS = 7;
360
+
361
+ function truncateTitle(title: string): string {
362
+ if (title.length <= MAX_TITLE_LENGTH) return title;
363
+ const words = title.split(/\s+/);
364
+ if (words.length <= MAX_TITLE_WORDS) {
365
+ // Long words but few of them — truncate to char limit at word boundary
366
+ let result = "";
367
+ for (const word of words) {
368
+ const candidate = result ? result + " " + word : word;
369
+ if (candidate.length > MAX_TITLE_LENGTH) break;
370
+ result = candidate;
371
+ }
372
+ return result || title.slice(0, MAX_TITLE_LENGTH);
373
+ }
374
+ // Too many words — trim to 5 words
375
+ return words.slice(0, 5).join(" ");
376
+ }
377
+
356
378
  function normalizeTitle(raw: string): string {
357
379
  let title = raw.trim().replace(/^["']|["']$/g, "");
358
380
  title = stripMarkdown(title);
359
381
  if (META_FAILURE_TITLES.has(title.toLowerCase())) {
360
382
  return "";
361
383
  }
362
- return title;
384
+ return truncateTitle(title);
363
385
  }
364
386
 
365
387
  /** Strip common markdown formatting so titles render as plain text. */
@@ -32,12 +32,14 @@ import {
32
32
  createMessagesFts,
33
33
  createNotificationTables,
34
34
  createOAuthTables,
35
+ createOnboardingEventsTable,
35
36
  createScopedApprovalGrantsTable,
36
37
  createSequenceTables,
37
38
  createTasksAndWorkItemsTables,
38
39
  createWatchersAndLogsTables,
39
40
  migrate230AcpSessionHistory,
40
41
  migrate231RepairMemoryGraphEventDates,
42
+ migrateA2ATasks,
41
43
  migrateActivationState,
42
44
  migrateActivationStateFkCascade,
43
45
  migrateAddConversationInferenceProfile,
@@ -94,6 +96,7 @@ import {
94
96
  migrateDropSetupSkillIdColumn,
95
97
  migrateDropSimplifiedMemory,
96
98
  migrateDropUsageCompositeIndexes,
99
+ migrateExternalConversationBindingThreadId,
97
100
  migrateFkCascadeRebuilds,
98
101
  migrateGuardianActionFollowup,
99
102
  migrateGuardianActionSupersession,
@@ -109,6 +112,7 @@ import {
109
112
  migrateHeartbeatRuns,
110
113
  migrateInviteCodeHashColumn,
111
114
  migrateInviteContactId,
115
+ migrateLlmRequestLogAgentLoopExitReason,
112
116
  migrateLlmRequestLogMessageId,
113
117
  migrateLlmRequestLogProvider,
114
118
  migrateLlmRequestLogsCreatedAtIndex,
@@ -122,6 +126,7 @@ import {
122
126
  migrateMessagesConversationCreatedAtIndex,
123
127
  migrateMessagesFtsBackfill,
124
128
  migrateNormalizePhoneIdentities,
129
+ migrateNormalizeSlackExternalContent,
125
130
  migrateNormalizeUserFileByPrincipal,
126
131
  migrateNotificationDeliveryThreadDecision,
127
132
  migrateOAuthAppsClientSecretPath,
@@ -139,6 +144,7 @@ import {
139
144
  migrateOAuthProvidersScopeSeparator,
140
145
  migrateOAuthProvidersTokenAuthMethodDefault,
141
146
  migrateOAuthProvidersTokenExchangeBodyFormat,
147
+ migrateProviderConnectionBaseUrlAndModels,
142
148
  migrateProviderConnectionStatusLabel,
143
149
  migrateReminderRoutingIntent,
144
150
  migrateRemindersToSchedules,
@@ -424,6 +430,12 @@ export function initializeDb(): void {
424
430
  migrateProviderConnectionStatusLabel,
425
431
  migrateMemoryRetrospectiveState,
426
432
  migrateBackfillProviderConnectionLabel,
433
+ migrateExternalConversationBindingThreadId,
434
+ createOnboardingEventsTable,
435
+ migrateNormalizeSlackExternalContent,
436
+ migrateProviderConnectionBaseUrlAndModels,
437
+ migrateA2ATasks,
438
+ migrateLlmRequestLogAgentLoopExitReason,
427
439
  ];
428
440
 
429
441
  // Run each migration step, catching and logging individual failures so one