@vellumai/assistant 0.8.2 → 0.8.4

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 (503) hide show
  1. package/ARCHITECTURE.md +11 -12
  2. package/docker-entrypoint.sh +13 -2
  3. package/docker-init-apt-root.sh +79 -6
  4. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  5. package/openapi.yaml +945 -36
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
  8. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  9. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +272 -0
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  14. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  15. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  16. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  17. package/src/__tests__/compaction-events.test.ts +1 -1
  18. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  19. package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
  20. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  22. package/src/__tests__/config-watcher.test.ts +1 -1
  23. package/src/__tests__/context-token-estimator.test.ts +112 -57
  24. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  27. package/src/__tests__/conversation-agent-loop.test.ts +77 -3
  28. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  29. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  30. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  32. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  33. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  34. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  35. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  36. package/src/__tests__/conversation-pairing.test.ts +2 -2
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  39. package/src/__tests__/conversation-queue.test.ts +1 -1
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
  41. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  42. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  43. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  44. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  45. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  46. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  47. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  48. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  49. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  50. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  51. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  52. package/src/__tests__/date-context.test.ts +45 -0
  53. package/src/__tests__/dm-backfill.test.ts +64 -0
  54. package/src/__tests__/dm-persistence.test.ts +33 -0
  55. package/src/__tests__/document-find-replace.test.ts +501 -0
  56. package/src/__tests__/external-plugin-loader.test.ts +91 -19
  57. package/src/__tests__/first-greeting.test.ts +23 -2
  58. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  59. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  60. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  61. package/src/__tests__/heartbeat-service.test.ts +24 -164
  62. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  63. package/src/__tests__/host-app-control-proxy.test.ts +241 -0
  64. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  66. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  67. package/src/__tests__/host-file-proxy.test.ts +8 -1
  68. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  69. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  70. package/src/__tests__/identity-routes.test.ts +57 -0
  71. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  72. package/src/__tests__/injector-background-turn.test.ts +153 -0
  73. package/src/__tests__/injector-chain.test.ts +7 -0
  74. package/src/__tests__/injector-document-comments.test.ts +378 -0
  75. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  76. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
  77. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  78. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  79. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  80. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  81. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  82. package/src/__tests__/llm-catalog-parity.test.ts +3 -0
  83. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  84. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  85. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  86. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
  87. package/src/__tests__/llm-resolver.test.ts +340 -3
  88. package/src/__tests__/log-export-routes.test.ts +99 -2
  89. package/src/__tests__/managed-profile-guard.test.ts +10 -0
  90. package/src/__tests__/message-queue-steer.test.ts +114 -0
  91. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  92. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  93. package/src/__tests__/notification-deep-link.test.ts +15 -0
  94. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  95. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  96. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  97. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  98. package/src/__tests__/openai-provider.test.ts +323 -3
  99. package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
  100. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  101. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  102. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  103. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  104. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  105. package/src/__tests__/platform-proxy-context.test.ts +6 -1
  106. package/src/__tests__/platform.test.ts +0 -3
  107. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  108. package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
  109. package/src/__tests__/plugin-types.test.ts +2 -2
  110. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  111. package/src/__tests__/process-message-display-content.test.ts +21 -16
  112. package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
  113. package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  115. package/src/__tests__/server-history-render.test.ts +83 -4
  116. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  117. package/src/__tests__/system-prompt.test.ts +57 -101
  118. package/src/__tests__/terminal-tools.test.ts +11 -1
  119. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  120. package/src/__tests__/thread-backfill.test.ts +370 -22
  121. package/src/__tests__/tool-executor.test.ts +90 -1
  122. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  123. package/src/__tests__/twilio-routes.test.ts +1 -1
  124. package/src/__tests__/web-fetch.test.ts +2 -2
  125. package/src/__tests__/workspace-git-service.test.ts +88 -5
  126. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  127. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  128. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  129. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  130. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  131. package/src/a2a/__tests__/task-store.test.ts +246 -0
  132. package/src/a2a/agent-card.ts +58 -0
  133. package/src/a2a/feature-gate.ts +8 -0
  134. package/src/a2a/protocol-constants.ts +21 -0
  135. package/src/a2a/protocol-errors.ts +50 -0
  136. package/src/a2a/protocol-types.ts +162 -0
  137. package/src/a2a/task-store.ts +168 -0
  138. package/src/agent/attachments.ts +1 -0
  139. package/src/agent/loop.ts +208 -22
  140. package/src/background-wake/next-wake.test.ts +289 -0
  141. package/src/background-wake/next-wake.ts +172 -0
  142. package/src/browser/operations.ts +15 -0
  143. package/src/channels/config.ts +9 -0
  144. package/src/channels/types.ts +14 -0
  145. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  146. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  147. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  148. package/src/cli/commands/__tests__/schedules.test.ts +469 -0
  149. package/src/cli/commands/conversations.ts +128 -1
  150. package/src/cli/commands/inference-providers.ts +147 -1
  151. package/src/cli/commands/memory-v2.ts +308 -0
  152. package/src/cli/commands/notifications.ts +89 -37
  153. package/src/cli/commands/plugins.ts +67 -0
  154. package/src/cli/commands/schedules.ts +297 -5
  155. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  156. package/src/cli/lib/install-from-github.ts +8 -9
  157. package/src/cli/lib/search-plugins.ts +163 -0
  158. package/src/cli/program.ts +14 -0
  159. package/src/cli/utils/conversation-id.ts +17 -5
  160. package/src/config/assistant-feature-flags.ts +24 -54
  161. package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
  162. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  163. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  164. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  165. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  166. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  167. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  168. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  169. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  170. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  171. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  172. package/src/config/bundled-tool-registry.ts +22 -12
  173. package/src/config/call-site-defaults.ts +124 -0
  174. package/src/config/feature-flag-registry.json +111 -23
  175. package/src/config/llm-resolver.ts +66 -1
  176. package/src/config/schema.ts +2 -0
  177. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
  178. package/src/config/schemas/call-site-catalog.ts +21 -0
  179. package/src/config/schemas/channels.ts +9 -0
  180. package/src/config/schemas/conversations.ts +10 -0
  181. package/src/config/schemas/heartbeat.ts +14 -0
  182. package/src/config/schemas/llm.ts +4 -3
  183. package/src/config/schemas/memory-retrospective.ts +1 -1
  184. package/src/config/schemas/memory-v2.ts +51 -4
  185. package/src/config/schemas/memory.ts +3 -1
  186. package/src/config/seed-inference-profiles.ts +99 -29
  187. package/src/context/compactor.ts +80 -13
  188. package/src/context/token-estimator.ts +72 -31
  189. package/src/context/window-manager.ts +25 -0
  190. package/src/credential-health/credential-health-service.ts +34 -19
  191. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
  192. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  193. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  194. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  195. package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
  196. package/src/daemon/conversation-agent-loop.ts +252 -56
  197. package/src/daemon/conversation-lifecycle.ts +142 -116
  198. package/src/daemon/conversation-messaging.ts +3 -0
  199. package/src/daemon/conversation-process.ts +273 -0
  200. package/src/daemon/conversation-queue-manager.ts +14 -0
  201. package/src/daemon/conversation-runtime-assembly.ts +144 -75
  202. package/src/daemon/conversation-slash.ts +37 -5
  203. package/src/daemon/conversation-surfaces.ts +45 -2
  204. package/src/daemon/conversation-tool-setup.ts +7 -0
  205. package/src/daemon/conversation.ts +42 -12
  206. package/src/daemon/date-context.ts +40 -0
  207. package/src/daemon/first-greeting.ts +10 -0
  208. package/src/daemon/guardian-action-generators.ts +1 -125
  209. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  210. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  211. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  212. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  213. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  214. package/src/daemon/handlers/config-a2a.ts +449 -0
  215. package/src/daemon/handlers/config-model.test.ts +1 -0
  216. package/src/daemon/handlers/conversations.ts +80 -0
  217. package/src/daemon/handlers/shared.ts +92 -29
  218. package/src/daemon/host-app-control-proxy.ts +69 -18
  219. package/src/daemon/host-bash-proxy.ts +1 -1
  220. package/src/daemon/host-cu-proxy.ts +1 -1
  221. package/src/daemon/host-file-proxy.ts +1 -1
  222. package/src/daemon/host-proxy-preactivation.ts +85 -18
  223. package/src/daemon/host-transfer-proxy.ts +1 -1
  224. package/src/daemon/lifecycle.ts +67 -65
  225. package/src/daemon/memory-v2-startup.ts +49 -13
  226. package/src/daemon/message-protocol.ts +4 -0
  227. package/src/daemon/message-types/conversations.ts +8 -0
  228. package/src/daemon/message-types/document-comments.ts +50 -0
  229. package/src/daemon/message-types/messages.ts +68 -1
  230. package/src/daemon/message-types/notifications.ts +21 -0
  231. package/src/daemon/message-types/surfaces.ts +3 -1
  232. package/src/daemon/message-types/web-activity.ts +57 -0
  233. package/src/daemon/pkb-reminder-builder.test.ts +10 -53
  234. package/src/daemon/pkb-reminder-builder.ts +4 -19
  235. package/src/daemon/plugin-source-watcher.ts +135 -3
  236. package/src/daemon/process-message.ts +72 -12
  237. package/src/daemon/query-complexity-router.ts +75 -0
  238. package/src/daemon/skill-memory-refresh.ts +5 -1
  239. package/src/daemon/trust-context.ts +6 -0
  240. package/src/daemon/wake-target-adapter.ts +2 -0
  241. package/src/documents/document-comments-store.test.ts +338 -0
  242. package/src/documents/document-comments-store.ts +237 -0
  243. package/src/documents/document-store.ts +202 -0
  244. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  245. package/src/export/transcript-formatter.ts +54 -20
  246. package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
  247. package/src/heartbeat/heartbeat-service.ts +35 -191
  248. package/src/home/__tests__/feed-types.test.ts +40 -0
  249. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  250. package/src/home/feed-types.ts +20 -3
  251. package/src/home/home-content-refresh.ts +52 -0
  252. package/src/home/home-greeting-cache.ts +69 -0
  253. package/src/home/home-greeting.ts +94 -0
  254. package/src/home/suggested-prompts.ts +177 -9
  255. package/src/ipc/cli-client.ts +147 -45
  256. package/src/memory/__tests__/conversation-queries.test.ts +220 -0
  257. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  258. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  259. package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
  260. package/src/memory/conversation-crud.ts +133 -43
  261. package/src/memory/conversation-queries.ts +87 -1
  262. package/src/memory/conversation-title-service.ts +26 -4
  263. package/src/memory/db-init.ts +22 -0
  264. package/src/memory/delivery-crud.ts +41 -0
  265. package/src/memory/delivery-status.ts +141 -15
  266. package/src/memory/external-conversation-store.ts +32 -1
  267. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
  268. package/src/memory/graph/conversation-graph-memory.ts +18 -6
  269. package/src/memory/graph/tools.ts +6 -37
  270. package/src/memory/invite-store.ts +53 -0
  271. package/src/memory/jobs-worker.ts +21 -1
  272. package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
  273. package/src/memory/llm-request-log-store.ts +92 -1
  274. package/src/memory/memory-retrospective-constants.ts +28 -0
  275. package/src/memory/memory-retrospective-enqueue.ts +4 -22
  276. package/src/memory/memory-retrospective-job.ts +438 -21
  277. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  278. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  279. package/src/memory/migrations/100-core-tables.ts +1 -0
  280. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  281. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  282. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  283. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  284. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  285. package/src/memory/migrations/253-document-comments.ts +47 -0
  286. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  287. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  288. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  289. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  290. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  291. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  292. package/src/memory/migrations/index.ts +20 -0
  293. package/src/memory/migrations/registry.ts +33 -0
  294. package/src/memory/onboarding-events-store.ts +7 -0
  295. package/src/memory/schema/a2a.ts +15 -0
  296. package/src/memory/schema/calls.ts +1 -0
  297. package/src/memory/schema/conversations.ts +3 -0
  298. package/src/memory/schema/index.ts +1 -0
  299. package/src/memory/schema/inference.ts +2 -0
  300. package/src/memory/schema/infrastructure.ts +2 -0
  301. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  302. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  303. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  304. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  305. package/src/memory/v2/__tests__/injection.test.ts +221 -17
  306. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  307. package/src/memory/v2/__tests__/router.test.ts +489 -1
  308. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  309. package/src/memory/v2/activation-store.ts +14 -16
  310. package/src/memory/v2/cli-command-content.ts +19 -0
  311. package/src/memory/v2/cli-command-store.ts +304 -0
  312. package/src/memory/v2/consolidation-job.ts +14 -0
  313. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  314. package/src/memory/v2/injection-events.ts +101 -0
  315. package/src/memory/v2/injection.ts +69 -29
  316. package/src/memory/v2/page-index.ts +246 -19
  317. package/src/memory/v2/page-store.ts +18 -0
  318. package/src/memory/v2/router.ts +209 -55
  319. package/src/memory/v2/static-context.ts +4 -4
  320. package/src/memory/v2/types.ts +23 -0
  321. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  322. package/src/messaging/providers/a2a/deliver.ts +156 -0
  323. package/src/messaging/providers/gmail/client.ts +9 -2
  324. package/src/messaging/providers/index.ts +18 -3
  325. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  327. package/src/messaging/providers/slack/adapter.ts +178 -25
  328. package/src/messaging/providers/slack/api.test.ts +54 -0
  329. package/src/messaging/providers/slack/api.ts +119 -3
  330. package/src/messaging/providers/slack/client.ts +12 -0
  331. package/src/messaging/providers/slack/deep-link.ts +20 -1
  332. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  333. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  334. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  335. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  336. package/src/messaging/providers/slack/send.test.ts +77 -0
  337. package/src/messaging/providers/slack/send.ts +8 -2
  338. package/src/messaging/providers/slack/types.ts +14 -0
  339. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  340. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  341. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  342. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
  343. package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
  344. package/src/notifications/adapters/macos.ts +12 -2
  345. package/src/notifications/broadcaster.ts +29 -4
  346. package/src/notifications/conversation-seed-composer.ts +14 -2
  347. package/src/notifications/copy-composer.ts +17 -64
  348. package/src/notifications/decision-engine.ts +111 -44
  349. package/src/notifications/deferred-emit.ts +135 -0
  350. package/src/notifications/deterministic-checks.ts +96 -0
  351. package/src/notifications/emit-signal.ts +10 -1
  352. package/src/notifications/home-feed-side-effect.ts +136 -27
  353. package/src/notifications/signal.ts +0 -4
  354. package/src/notifications/types.ts +8 -0
  355. package/src/oauth/connect-orchestrator.ts +3 -0
  356. package/src/oauth/credential-token-resolver.ts +2 -0
  357. package/src/oauth/manual-token-connection.ts +19 -0
  358. package/src/oauth/oauth-store.ts +12 -0
  359. package/src/oauth/platform-connection.test.ts +43 -3
  360. package/src/oauth/platform-connection.ts +13 -4
  361. package/src/oauth/seed-providers.ts +22 -0
  362. package/src/permissions/prompter.ts +5 -2
  363. package/src/permissions/secret-prompter.ts +4 -1
  364. package/src/plugins/defaults/injectors.ts +118 -26
  365. package/src/plugins/external-plugin-loader.ts +82 -10
  366. package/src/plugins/types.ts +16 -7
  367. package/src/prompts/__tests__/system-prompt.test.ts +44 -45
  368. package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
  369. package/src/prompts/normalize-onboarding.ts +40 -0
  370. package/src/prompts/sections.ts +32 -14
  371. package/src/prompts/system-prompt.ts +105 -76
  372. package/src/prompts/template-detection.ts +37 -0
  373. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  374. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  375. package/src/prompts/templates/VOICE.md +3 -0
  376. package/src/prompts/templates/system-sections.ts +51 -10
  377. package/src/providers/__tests__/inference.test.ts +2 -0
  378. package/src/providers/anthropic/client.ts +132 -5
  379. package/src/providers/call-site-routing.ts +24 -6
  380. package/src/providers/connection-resolution.ts +63 -13
  381. package/src/providers/fireworks/client.ts +20 -2
  382. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  383. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  384. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  385. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  386. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  387. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  388. package/src/providers/inference/adapter-factory.ts +24 -21
  389. package/src/providers/inference/auth.ts +15 -3
  390. package/src/providers/inference/backfill.ts +14 -1
  391. package/src/providers/inference/codex-token-refresh.ts +128 -0
  392. package/src/providers/inference/connections.ts +85 -5
  393. package/src/providers/inference/resolve-auth.ts +50 -5
  394. package/src/providers/model-catalog.ts +244 -242
  395. package/src/providers/model-intents.ts +3 -3
  396. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  397. package/src/providers/openai/chat-completions-provider.ts +215 -25
  398. package/src/providers/openai/responses-provider.ts +9 -3
  399. package/src/providers/openrouter/client.ts +46 -4
  400. package/src/providers/platform-proxy/constants.ts +3 -4
  401. package/src/providers/provider-catalog-visibility.ts +3 -1
  402. package/src/providers/provider-send-message.ts +27 -12
  403. package/src/providers/registry.ts +30 -1
  404. package/src/providers/types.ts +25 -0
  405. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  406. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  407. package/src/runtime/agent-wake.ts +212 -57
  408. package/src/runtime/auth/route-policy.ts +20 -3
  409. package/src/runtime/background-job-runner.ts +26 -0
  410. package/src/runtime/channel-reply-delivery.ts +182 -47
  411. package/src/runtime/channel-retry-sweep.ts +141 -16
  412. package/src/runtime/http-server.ts +7 -16
  413. package/src/runtime/http-types.ts +7 -51
  414. package/src/runtime/pending-interactions.ts +51 -8
  415. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  416. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  417. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
  418. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  419. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  420. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  421. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  422. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  423. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  424. package/src/runtime/routes/approval-routes.ts +4 -1
  425. package/src/runtime/routes/channel-availability-routes.ts +5 -0
  426. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  427. package/src/runtime/routes/consolidation-routes.ts +100 -0
  428. package/src/runtime/routes/content-source-routes.ts +78 -0
  429. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  430. package/src/runtime/routes/conversation-query-routes.ts +130 -12
  431. package/src/runtime/routes/conversation-routes.ts +288 -76
  432. package/src/runtime/routes/document-comments-routes.ts +287 -0
  433. package/src/runtime/routes/documents-routes.ts +33 -0
  434. package/src/runtime/routes/home-feed-routes.ts +6 -3
  435. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  436. package/src/runtime/routes/host-browser-routes.ts +8 -1
  437. package/src/runtime/routes/identity-routes.ts +21 -0
  438. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  439. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  440. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  441. package/src/runtime/routes/index.ts +14 -4
  442. package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
  443. package/src/runtime/routes/integrations/a2a.ts +294 -0
  444. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  445. package/src/runtime/routes/log-export-routes.ts +39 -0
  446. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  447. package/src/runtime/routes/notification-routes.ts +19 -2
  448. package/src/runtime/routes/question-routes.ts +4 -1
  449. package/src/runtime/routes/sanity-routes.ts +159 -0
  450. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  451. package/src/runtime/routes/subagents-routes.ts +41 -0
  452. package/src/runtime/services/conversation-serializer.ts +30 -4
  453. package/src/schedule/integration-status.ts +3 -1
  454. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  455. package/src/security/oauth2-device-code.ts +307 -0
  456. package/src/security/oauth2.ts +26 -9
  457. package/src/security/secure-keys.ts +5 -0
  458. package/src/skills/catalog-install.ts +6 -2
  459. package/src/subagent/manager.ts +2 -0
  460. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  461. package/src/tools/browser/browser-execution.ts +93 -0
  462. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  463. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  464. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  465. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  466. package/src/tools/browser/cdp-client/factory.ts +87 -3
  467. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  468. package/src/tools/browser/cdp-client/types.ts +36 -0
  469. package/src/tools/browser/pinned-tabs.ts +90 -0
  470. package/src/tools/document/document-comment-tool.test.ts +379 -0
  471. package/src/tools/document/document-comment-tool.ts +156 -0
  472. package/src/tools/document/document-tool.ts +128 -2
  473. package/src/tools/memory/register.ts +1 -9
  474. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  475. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  476. package/src/tools/network/domain-normalize.ts +17 -0
  477. package/src/tools/network/web-fetch.ts +213 -64
  478. package/src/tools/network/web-search.ts +191 -66
  479. package/src/tools/registry.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +3 -2
  481. package/src/tools/tool-approval-handler.ts +19 -12
  482. package/src/tools/types.ts +41 -2
  483. package/src/tools/ui-surface/definitions.ts +3 -1
  484. package/src/types/onboarding-context.ts +4 -0
  485. package/src/util/__tests__/favicon.test.ts +84 -0
  486. package/src/util/favicon.ts +40 -0
  487. package/src/util/platform.ts +0 -5
  488. package/src/workspace/git-service.ts +75 -4
  489. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  490. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  491. package/src/workspace/migrations/registry.ts +4 -0
  492. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  493. package/src/config/bundled-skills/document/SKILL.md +0 -54
  494. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  495. package/src/daemon/seed-files.ts +0 -18
  496. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  497. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
  498. package/src/runtime/routes/interface-routes.ts +0 -43
  499. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  500. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  501. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  502. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  503. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -50,7 +50,7 @@ import { ensureGroupMigration } from "./conversation-group-migration.js";
50
50
  import { getDb, getSqliteFrom } from "./db-connection.js";
51
51
  import { forkGraphMemoryState } from "./graph/graph-memory-state-store.js";
52
52
  import { indexMessageNow } from "./indexer.js";
53
- import { MEMORY_RETROSPECTIVE_SOURCE } from "./memory-retrospective-constants.js";
53
+ import { MEMORY_RETROSPECTIVE_SOURCES } from "./memory-retrospective-constants.js";
54
54
  import { forkRetrospectiveState } from "./memory-retrospective-state.js";
55
55
  import { rawExec, rawGet, rawRun } from "./raw-query.js";
56
56
  import {
@@ -190,6 +190,7 @@ export interface ConversationRow {
190
190
  contextSummary: string | null;
191
191
  contextCompactedMessageCount: number;
192
192
  contextCompactedAt: number | null;
193
+ cleanedAt: number | null;
193
194
  slackContextCompactionWatermarkTs: string | null;
194
195
  slackContextCompactionWatermarkAt: number | null;
195
196
  conversationType: string;
@@ -206,6 +207,7 @@ export interface ConversationRow {
206
207
  inferenceProfile: string | null;
207
208
  inferenceProfileSessionId: string | null;
208
209
  inferenceProfileExpiresAt: number | null;
210
+ lastNotifiedInferenceProfile: string | null;
209
211
  }
210
212
 
211
213
  export const parseConversation = createRowMapper<
@@ -222,6 +224,7 @@ export const parseConversation = createRowMapper<
222
224
  contextSummary: "contextSummary",
223
225
  contextCompactedMessageCount: "contextCompactedMessageCount",
224
226
  contextCompactedAt: "contextCompactedAt",
227
+ cleanedAt: "cleanedAt",
225
228
  slackContextCompactionWatermarkTs: "slackContextCompactionWatermarkTs",
226
229
  slackContextCompactionWatermarkAt: "slackContextCompactionWatermarkAt",
227
230
  conversationType: "conversationType",
@@ -238,6 +241,7 @@ export const parseConversation = createRowMapper<
238
241
  inferenceProfile: "inferenceProfile",
239
242
  inferenceProfileSessionId: "inferenceProfileSessionId",
240
243
  inferenceProfileExpiresAt: "inferenceProfileExpiresAt",
244
+ lastNotifiedInferenceProfile: "lastNotifiedInferenceProfile",
241
245
  });
242
246
 
243
247
  export interface MessageRow {
@@ -482,7 +486,7 @@ export function findMostRecentRetrospectiveFor(
482
486
  .from(conversations)
483
487
  .where(
484
488
  and(
485
- eq(conversations.source, MEMORY_RETROSPECTIVE_SOURCE),
489
+ inArray(conversations.source, MEMORY_RETROSPECTIVE_SOURCES),
486
490
  eq(conversations.forkParentConversationId, currentId),
487
491
  ),
488
492
  )
@@ -535,6 +539,32 @@ function getConversationGroupId(conversationId: string): string | null {
535
539
  export function forkConversation(params: {
536
540
  conversationId: string;
537
541
  throughMessageId?: string;
542
+ /**
543
+ * Override the fork's `source` column. Defaults to the standard
544
+ * `createConversation` default (`"user"`). Used by fork-based memory
545
+ * retrospectives to mark the fork as a retrospective artifact distinct
546
+ * from a user-initiated fork, so dedup and cleanup queries can scope
547
+ * correctly.
548
+ */
549
+ source?: string;
550
+ /**
551
+ * Optional title for the fork. Defaults to `<parent title> (Fork)`.
552
+ */
553
+ title?: string;
554
+ /**
555
+ * Override the fork's `conversationType` column. Defaults to `"standard"`.
556
+ * Used by fork-based memory retrospectives to bucket the fork as a
557
+ * `"background"` conversation so it doesn't surface in the user's
558
+ * conversation list.
559
+ */
560
+ conversationType?: ConversationCreateType;
561
+ /**
562
+ * Override the fork's `groupId`. Defaults to the parent conversation's
563
+ * group (or `"system:all"` when the parent has none). Used by fork-based
564
+ * memory retrospectives to route the fork into a dedicated background
565
+ * group.
566
+ */
567
+ groupId?: string;
538
568
  }): ConversationRow {
539
569
  const { conversationId, throughMessageId } = params;
540
570
  const db = getDb();
@@ -576,8 +606,19 @@ export function forkConversation(params: {
576
606
  copyBoundaryIndex >= 0
577
607
  ? sourceMessages.slice(0, copyBoundaryIndex + 1)
578
608
  : ([] as MessageRow[]);
609
+
610
+ // Inherit /clean state only when the fork boundary is at-or-after the
611
+ // clean event. Pre-clean forks branch from history that pre-dates the
612
+ // clean, so the marker would be a no-op and is misleading to copy.
613
+ const sourceCleanedAt = sourceConversation.cleanedAt ?? null;
614
+ const boundaryMessageCreatedAt = messagesToCopy.at(-1)?.createdAt ?? null;
615
+ const inheritsCleanedAt =
616
+ sourceCleanedAt != null &&
617
+ boundaryMessageCreatedAt != null &&
618
+ boundaryMessageCreatedAt >= sourceCleanedAt;
579
619
  const forkParentMessageId = messagesToCopy.at(-1)?.id ?? null;
580
- const forkTitle = `${sourceConversation.title ?? "Untitled"} (Fork)`;
620
+ const forkTitle =
621
+ params.title ?? `${sourceConversation.title ?? "Untitled"} (Fork)`;
581
622
 
582
623
  // Collect disk-sync work to run after the transaction commits.
583
624
  const diskSyncQueue: Array<{
@@ -597,8 +638,9 @@ export function forkConversation(params: {
597
638
  const forkedConversation = db.transaction(() => {
598
639
  const fc = createConversation({
599
640
  title: forkTitle,
600
- conversationType: "standard",
601
- groupId: parentGroupId ?? "system:all",
641
+ conversationType: params.conversationType ?? "standard",
642
+ groupId: params.groupId ?? parentGroupId ?? "system:all",
643
+ ...(params.source != null ? { source: params.source } : {}),
602
644
  });
603
645
 
604
646
  db.update(conversations)
@@ -620,6 +662,7 @@ export function forkConversation(params: {
620
662
  slackContextCompactionWatermarkAt: preserveSourceCompactionState
621
663
  ? sourceConversation.slackContextCompactionWatermarkAt
622
664
  : null,
665
+ cleanedAt: inheritsCleanedAt ? sourceCleanedAt : null,
623
666
  inferenceProfile: sourceConversation.inferenceProfile,
624
667
  })
625
668
  .where(eq(conversations.id, fc.id))
@@ -1229,10 +1272,14 @@ interface PaginatedMessagesResult {
1229
1272
  hasMore: boolean;
1230
1273
  }
1231
1274
 
1275
+ const PAGINATION_CHUNK_MIN = 50;
1276
+ const PAGINATION_SCAN_CAP = 10_000;
1277
+
1232
1278
  export function getMessagesPaginated(
1233
1279
  conversationId: string,
1234
1280
  limit: number | undefined,
1235
1281
  beforeTimestamp?: number,
1282
+ filter?: (row: MessageRow) => boolean,
1236
1283
  ): PaginatedMessagesResult {
1237
1284
  const db = getDb();
1238
1285
 
@@ -1248,51 +1295,69 @@ export function getMessagesPaginated(
1248
1295
  .orderBy(asc(messages.createdAt))
1249
1296
  .all()
1250
1297
  .map(parseMessage);
1251
- return { messages: rows, hasMore: false };
1298
+ return {
1299
+ messages: filter ? rows.filter(filter) : rows,
1300
+ hasMore: false,
1301
+ };
1252
1302
  }
1253
1303
 
1254
- const conditions = [eq(messages.conversationId, conversationId)];
1255
- if (beforeTimestamp !== undefined) {
1256
- conditions.push(lt(messages.createdAt, beforeTimestamp));
1257
- }
1304
+ // Walk pages newest→oldest, applying `filter` in TS (metadata parsing is
1305
+ // JSON, not a structured column). Keep fetching until we have `limit + 1`
1306
+ // visible rows or the DB is exhausted, so `hasMore` and the cursor reflect
1307
+ // the visible page rather than the unfiltered row count. Without this loop,
1308
+ // a fully-hidden page returns `{ messages: [], hasMore: true }` with no
1309
+ // cursor, which stalls the web client's older-page fetch.
1310
+ let cursorCreatedAt = beforeTimestamp;
1311
+ let cursorMessageId: string | undefined;
1312
+ const visible: MessageRow[] = [];
1313
+ const chunkSize = Math.max(limit + 1, PAGINATION_CHUNK_MIN);
1314
+ // Bound the work a single request can do when `filter` rejects nearly every
1315
+ // row — otherwise a pathological filter against a huge conversation would
1316
+ // tie up a connection for thousands of roundtrips.
1317
+ let rowsScanned = 0;
1318
+
1319
+ while (visible.length < limit + 1 && rowsScanned < PAGINATION_SCAN_CAP) {
1320
+ const cursorPredicate =
1321
+ cursorCreatedAt === undefined
1322
+ ? undefined
1323
+ : cursorMessageId === undefined
1324
+ ? lt(messages.createdAt, cursorCreatedAt)
1325
+ : or(
1326
+ lt(messages.createdAt, cursorCreatedAt),
1327
+ and(
1328
+ eq(messages.createdAt, cursorCreatedAt),
1329
+ lt(messages.id, cursorMessageId),
1330
+ ),
1331
+ );
1258
1332
 
1259
- const rows = db
1260
- .select()
1261
- .from(messages)
1262
- .where(and(...conditions))
1263
- .orderBy(desc(messages.createdAt))
1264
- .limit(limit + 1)
1265
- .all()
1266
- .map(parseMessage);
1333
+ const chunk = db
1334
+ .select()
1335
+ .from(messages)
1336
+ .where(and(eq(messages.conversationId, conversationId), cursorPredicate))
1337
+ .orderBy(desc(messages.createdAt), desc(messages.id))
1338
+ .limit(chunkSize)
1339
+ .all()
1340
+ .map(parseMessage);
1267
1341
 
1268
- const hasMore = rows.length > limit;
1269
- if (hasMore) {
1270
- rows.splice(limit);
1342
+ if (chunk.length === 0) break;
1343
+ rowsScanned += chunk.length;
1344
+
1345
+ for (const row of chunk) {
1346
+ if (!filter || filter(row)) visible.push(row);
1347
+ if (visible.length >= limit + 1) break;
1348
+ }
1349
+
1350
+ if (chunk.length < chunkSize) break;
1351
+ const lastRow = chunk[chunk.length - 1];
1352
+ cursorCreatedAt = lastRow.createdAt;
1353
+ cursorMessageId = lastRow.id;
1271
1354
  }
1272
- rows.reverse();
1273
1355
 
1274
- return { messages: rows, hasMore };
1275
- }
1356
+ const hasMore = visible.length > limit;
1357
+ if (hasMore) visible.splice(limit);
1358
+ visible.reverse();
1276
1359
 
1277
- export function getLastAssistantTimestampBefore(
1278
- conversationId: string,
1279
- beforeTimestamp: number,
1280
- ): number {
1281
- const db = getDb();
1282
- const row = db
1283
- .select({ createdAt: messages.createdAt })
1284
- .from(messages)
1285
- .where(
1286
- and(
1287
- eq(messages.conversationId, conversationId),
1288
- eq(messages.role, "assistant"),
1289
- lt(messages.createdAt, beforeTimestamp),
1290
- ),
1291
- )
1292
- .orderBy(desc(messages.createdAt))
1293
- .limit(1)
1294
- .get();
1295
- return row?.createdAt ?? 0;
1360
+ return { messages: visible, hasMore };
1296
1361
  }
1297
1362
 
1298
1363
  export function getLastUserTimestampBefore(
@@ -1386,6 +1451,20 @@ export function updateConversationContextWindow(
1386
1451
  .run();
1387
1452
  }
1388
1453
 
1454
+ export function setConversationCleanedAt(
1455
+ id: string,
1456
+ cleanedAt: number | null,
1457
+ ): void {
1458
+ const db = getDb();
1459
+ db.update(conversations)
1460
+ .set({
1461
+ cleanedAt,
1462
+ updatedAt: Date.now(),
1463
+ })
1464
+ .where(eq(conversations.id, id))
1465
+ .run();
1466
+ }
1467
+
1389
1468
  export function updateConversationSlackContextWatermark(
1390
1469
  id: string,
1391
1470
  watermarkTs: string,
@@ -1609,6 +1688,17 @@ export function getConversationOverrideProfile(
1609
1688
  return getConversationOverrideProfileFromRow(getConversation(conversationId));
1610
1689
  }
1611
1690
 
1691
+ export function setLastNotifiedInferenceProfile(
1692
+ conversationId: string,
1693
+ profileKey: string | null,
1694
+ ): void {
1695
+ rawRun(
1696
+ "UPDATE conversations SET last_notified_inference_profile = ? WHERE id = ?",
1697
+ profileKey,
1698
+ conversationId,
1699
+ );
1700
+ }
1701
+
1612
1702
  /**
1613
1703
  * Delete all conversations, messages, and related data (tool invocations,
1614
1704
  * memory segments, etc.) from the daemon database.
@@ -1,4 +1,4 @@
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
3
  import {
4
4
  parseExternalContentEnvelope,
@@ -86,6 +86,92 @@ export function listConversations(
86
86
  return query.all().map(parseConversation);
87
87
  }
88
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
+
89
175
  export function listPinnedConversations(): ConversationRow[] {
90
176
  ensureDisplayOrderMigration();
91
177
  ensureGroupMigration();
@@ -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. */
@@ -39,6 +39,7 @@ import {
39
39
  createWatchersAndLogsTables,
40
40
  migrate230AcpSessionHistory,
41
41
  migrate231RepairMemoryGraphEventDates,
42
+ migrateA2ATasks,
42
43
  migrateActivationState,
43
44
  migrateActivationStateFkCascade,
44
45
  migrateAddConversationInferenceProfile,
@@ -57,6 +58,7 @@ import {
57
58
  migrateCanonicalGuardianRequesterChatId,
58
59
  migrateCapabilityCardColumns,
59
60
  migrateChannelInboundDeliveredSegments,
61
+ migrateChannelInboundDeliveryAttempts,
60
62
  migrateChannelInteractionColumns,
61
63
  migrateContactChannelsAccessFields,
62
64
  migrateContactChannelsTypeChatIdIndex,
@@ -64,13 +66,16 @@ import {
64
66
  migrateContactsNotesColumn,
65
67
  migrateContactsRolePrincipal,
66
68
  migrateContactsUserFileColumn,
69
+ migrateConversationCleanedAt,
67
70
  migrateConversationForkLineage,
68
71
  migrateConversationHostAccess,
69
72
  migrateConversationInferenceProfileSession,
73
+ migrateConversationLastNotifiedProfile,
70
74
  migrateConversationsArchivedAt,
71
75
  migrateConversationsLastMessageAt,
72
76
  migrateConversationsThreadTypeIndex,
73
77
  migrateCreateConversationGraphMemoryState,
78
+ migrateCreateDocumentComments,
74
79
  migrateCreateDocumentConversations,
75
80
  migrateCreateMemoryGraphNodeEdits,
76
81
  migrateCreateMemoryGraphTables,
@@ -95,6 +100,7 @@ import {
95
100
  migrateDropSetupSkillIdColumn,
96
101
  migrateDropSimplifiedMemory,
97
102
  migrateDropUsageCompositeIndexes,
103
+ migrateExternalConversationBindingChatName,
98
104
  migrateExternalConversationBindingThreadId,
99
105
  migrateFkCascadeRebuilds,
100
106
  migrateGuardianActionFollowup,
@@ -111,6 +117,7 @@ import {
111
117
  migrateHeartbeatRuns,
112
118
  migrateInviteCodeHashColumn,
113
119
  migrateInviteContactId,
120
+ migrateLlmRequestLogAgentLoopExitReason,
114
121
  migrateLlmRequestLogMessageId,
115
122
  migrateLlmRequestLogProvider,
116
123
  migrateLlmRequestLogsCreatedAtIndex,
@@ -120,6 +127,7 @@ import {
120
127
  migrateMemoryRecallLogsQueryContext,
121
128
  migrateMemoryRetrospectiveState,
122
129
  migrateMemoryV2ActivationLogs,
130
+ migrateMemoryV2InjectionEvents,
123
131
  migrateMessageBookmarks,
124
132
  migrateMessagesConversationCreatedAtIndex,
125
133
  migrateMessagesFtsBackfill,
@@ -142,6 +150,8 @@ import {
142
150
  migrateOAuthProvidersScopeSeparator,
143
151
  migrateOAuthProvidersTokenAuthMethodDefault,
144
152
  migrateOAuthProvidersTokenExchangeBodyFormat,
153
+ migrateOnboardingEventsPriorAssistants,
154
+ migrateProviderConnectionBaseUrlAndModels,
145
155
  migrateProviderConnectionStatusLabel,
146
156
  migrateReminderRoutingIntent,
147
157
  migrateRemindersToSchedules,
@@ -171,6 +181,7 @@ import {
171
181
  migrateSchemaIndexesAndColumns,
172
182
  migrateScrubCorruptedImageAttachments,
173
183
  migrateSlackCompactionWatermark,
184
+ migrateStripBaseUrlNonOpenaiCompatible,
174
185
  migrateStripIntegrationPrefixFromProviderKeys,
175
186
  migrateStripPlaceholderSentinelsFromMessages,
176
187
  migrateStripThinkingFromConsolidated,
@@ -430,6 +441,17 @@ export function initializeDb(): void {
430
441
  migrateExternalConversationBindingThreadId,
431
442
  createOnboardingEventsTable,
432
443
  migrateNormalizeSlackExternalContent,
444
+ migrateProviderConnectionBaseUrlAndModels,
445
+ migrateA2ATasks,
446
+ migrateLlmRequestLogAgentLoopExitReason,
447
+ migrateCreateDocumentComments,
448
+ migrateExternalConversationBindingChatName,
449
+ migrateChannelInboundDeliveryAttempts,
450
+ migrateMemoryV2InjectionEvents,
451
+ migrateConversationLastNotifiedProfile,
452
+ migrateStripBaseUrlNonOpenaiCompatible,
453
+ migrateOnboardingEventsPriorAssistants,
454
+ migrateConversationCleanedAt,
433
455
  ];
434
456
 
435
457
  // Run each migration step, catching and logging individual failures so one
@@ -313,6 +313,47 @@ export function storePayload(
313
313
  .run();
314
314
  }
315
315
 
316
+ /**
317
+ * Persist the assistant reply row generated for an inbound event so callback
318
+ * delivery retries target that exact response instead of the latest message in
319
+ * the conversation.
320
+ */
321
+ export function storeReplyMessageId(
322
+ eventId: string,
323
+ replyMessageId: string,
324
+ ): void {
325
+ const db = getDb();
326
+ const row = db
327
+ .select({ rawPayload: channelInboundEvents.rawPayload })
328
+ .from(channelInboundEvents)
329
+ .where(eq(channelInboundEvents.id, eventId))
330
+ .get();
331
+ if (!row?.rawPayload) return;
332
+
333
+ let payload: Record<string, unknown>;
334
+ try {
335
+ const parsed = JSON.parse(row.rawPayload) as unknown;
336
+ if (
337
+ parsed === null ||
338
+ typeof parsed !== "object" ||
339
+ Array.isArray(parsed)
340
+ ) {
341
+ return;
342
+ }
343
+ payload = parsed as Record<string, unknown>;
344
+ } catch {
345
+ return;
346
+ }
347
+
348
+ db.update(channelInboundEvents)
349
+ .set({
350
+ rawPayload: JSON.stringify({ ...payload, replyMessageId }),
351
+ updatedAt: Date.now(),
352
+ })
353
+ .where(eq(channelInboundEvents.id, eventId))
354
+ .run();
355
+ }
356
+
316
357
  /**
317
358
  * Clear a previously stored payload. Used when the ingress check
318
359
  * detects secret-bearing content — the payload must not remain on disk.