@vellumai/assistant 0.10.3 → 0.10.4-dev.202607010028.1a4efcc

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 (2236) hide show
  1. package/ARCHITECTURE.md +13 -13
  2. package/Dockerfile +5 -15
  3. package/bun.lock +165 -3
  4. package/docs/architecture/memory.md +26 -0
  5. package/eslint-rules/cli-no-daemon-internals.js +9 -8
  6. package/knip.json +0 -1
  7. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +18 -0
  8. package/node_modules/@vellumai/service-contracts/src/__tests__/package-boundary.test.ts +1 -5
  9. package/openapi.yaml +1057 -88
  10. package/package.json +8 -3
  11. package/scripts/generate-openapi.ts +149 -62
  12. package/src/__tests__/acp-session.test.ts +2 -2
  13. package/src/__tests__/activation-early-marking.test.ts +4 -4
  14. package/src/__tests__/actor-token-service.test.ts +2 -3
  15. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
  16. package/src/__tests__/agent-loop-compaction-events.test.ts +1 -0
  17. package/src/__tests__/agent-loop-compaction-strip.test.ts +2 -0
  18. package/src/__tests__/agent-loop-exit-reason.test.ts +4 -1
  19. package/src/__tests__/agent-loop-override-profile.test.ts +2 -2
  20. package/src/__tests__/agent-loop-regrowth-guard.test.ts +8 -0
  21. package/src/__tests__/agent-loop-set-system-prompt.test.ts +139 -0
  22. package/src/__tests__/agent-loop.test.ts +1 -0
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +3 -3
  24. package/src/__tests__/agent-wake-override-profile.test.ts +3 -3
  25. package/src/__tests__/annotate-activity-metadata.test.ts +4 -4
  26. package/src/__tests__/annotate-risk-options.test.ts +4 -4
  27. package/src/__tests__/anthropic-provider.test.ts +53 -2
  28. package/src/__tests__/app-builder-tool-scripts.test.ts +2 -2
  29. package/src/__tests__/app-bundler.test.ts +1 -1
  30. package/src/__tests__/app-conversation-ids-backfill.test.ts +4 -4
  31. package/src/__tests__/app-conversation-ids.test.ts +1 -1
  32. package/src/__tests__/app-dir-path-guard.test.ts +2 -2
  33. package/src/__tests__/app-executors.test.ts +1 -1
  34. package/src/__tests__/app-git-history.test.ts +2 -2
  35. package/src/__tests__/app-git-service.test.ts +2 -2
  36. package/src/__tests__/app-routes-csp.test.ts +2 -2
  37. package/src/__tests__/app-source-watcher.test.ts +1 -1
  38. package/src/__tests__/app-store-dir-names.test.ts +1 -1
  39. package/src/__tests__/approval-cascade.test.ts +7 -7
  40. package/src/__tests__/approval-interception-trust-gates.test.ts +1 -1
  41. package/src/__tests__/approval-primitive.test.ts +3 -3
  42. package/src/__tests__/approval-routes-http.test.ts +2 -2
  43. package/src/__tests__/assistant-event-hub-machine-name.test.ts +1 -1
  44. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -2
  45. package/src/__tests__/assistant-events-sse-shed.test.ts +2 -2
  46. package/src/__tests__/assistant-stream-state.test.ts +3 -76
  47. package/src/__tests__/attachment-upload-trusted-source.test.ts +4 -2
  48. package/src/__tests__/attachments-store.test.ts +9 -6
  49. package/src/__tests__/audit-log-rotation.test.ts +6 -4
  50. package/src/__tests__/auth-fallback-events-store.test.ts +4 -4
  51. package/src/__tests__/auto-analysis-end-to-end.test.ts +13 -9
  52. package/src/__tests__/background-shell-bash.test.ts +34 -0
  53. package/src/__tests__/background-shell-host-bash.test.ts +12 -0
  54. package/src/__tests__/background-tool-registry.test.ts +97 -0
  55. package/src/__tests__/background-tool-routes.test.ts +72 -1
  56. package/src/__tests__/background-workers-disk-pressure.test.ts +17 -10
  57. package/src/__tests__/btw-routes.test.ts +4 -5
  58. package/src/__tests__/build-persisted-content.test.ts +4 -4
  59. package/src/__tests__/call-controller.test.ts +6 -6
  60. package/src/__tests__/call-conversation-messages.test.ts +4 -4
  61. package/src/__tests__/call-domain.test.ts +5 -5
  62. package/src/__tests__/call-pointer-messages.test.ts +5 -5
  63. package/src/__tests__/call-recovery.test.ts +3 -3
  64. package/src/__tests__/call-routes-http.test.ts +3 -3
  65. package/src/__tests__/call-store.test.ts +3 -3
  66. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -3
  67. package/src/__tests__/canonical-guardian-store.test.ts +3 -3
  68. package/src/__tests__/channel-approval-routes.test.ts +30 -33
  69. package/src/__tests__/channel-delivery-store.test.ts +36 -8
  70. package/src/__tests__/channel-guardian.test.ts +62 -54
  71. package/src/__tests__/channel-inbound-disk-pressure.test.ts +15 -23
  72. package/src/__tests__/channel-reply-delivery.test.ts +8 -4
  73. package/src/__tests__/channel-retry-sweep.test.ts +49 -4
  74. package/src/__tests__/clawhub.test.ts +4 -0
  75. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +2 -2
  76. package/src/__tests__/compaction-events.test.ts +8 -8
  77. package/src/__tests__/compaction-ledger-store.test.ts +128 -0
  78. package/src/__tests__/compaction-trail-store.test.ts +5 -5
  79. package/src/__tests__/compaction.benchmark.test.ts +5 -5
  80. package/src/__tests__/compactor-call-site-logging.test.ts +5 -5
  81. package/src/__tests__/compactor-image-manifest-trust.test.ts +7 -4
  82. package/src/__tests__/compactor-low-watermark-cut.test.ts +5 -5
  83. package/src/__tests__/compactor-preserved-tail-count.test.ts +4 -4
  84. package/src/__tests__/compactor-summary-call-truncation.test.ts +5 -5
  85. package/src/__tests__/compactor-web-search-strip.test.ts +4 -4
  86. package/src/__tests__/config-get-vision-flag.test.ts +1 -1
  87. package/src/__tests__/config-loader-backfill.test.ts +230 -102
  88. package/src/__tests__/config-set-route.test.ts +5 -2
  89. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +3 -3
  90. package/src/__tests__/config-watcher-skill-reseed.test.ts +39 -112
  91. package/src/__tests__/config-watcher.test.ts +54 -31
  92. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +4 -4
  93. package/src/__tests__/consult-deadline.test.ts +60 -0
  94. package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
  95. package/src/__tests__/contact-store-user-file.test.ts +98 -91
  96. package/src/__tests__/contacts-relay-reads.test.ts +27 -12
  97. package/src/__tests__/contacts-tools.test.ts +2 -2
  98. package/src/__tests__/contacts-write.test.ts +2 -4
  99. package/src/__tests__/context-search-agent-protocol.test.ts +2 -2
  100. package/src/__tests__/context-search-agent-runner.test.ts +2 -2
  101. package/src/__tests__/context-search-conversations-source.test.ts +5 -5
  102. package/src/__tests__/context-search-fanout.test.ts +3 -3
  103. package/src/__tests__/context-search-memory-source.test.ts +9 -8
  104. package/src/__tests__/context-search-memory-v2-source.test.ts +9 -8
  105. package/src/__tests__/context-search-types.test.ts +2 -2
  106. package/src/__tests__/context-search-workspace-source.test.ts +2 -2
  107. package/src/__tests__/conversation-abort-tool-results.test.ts +5 -5
  108. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +4 -3
  109. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +40 -11
  110. package/src/__tests__/conversation-agent-loop-overflow.test.ts +11 -8
  111. package/src/__tests__/conversation-agent-loop.test.ts +150 -20
  112. package/src/__tests__/conversation-analysis-routes.test.ts +4 -4
  113. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -1
  114. package/src/__tests__/conversation-app-control-lifecycle.test.ts +7 -7
  115. package/src/__tests__/conversation-attachments.test.ts +7 -4
  116. package/src/__tests__/conversation-attention-store.test.ts +4 -4
  117. package/src/__tests__/conversation-attention-telegram.test.ts +17 -16
  118. package/src/__tests__/conversation-clear-safety.test.ts +3 -4
  119. package/src/__tests__/conversation-confirmation-signals.test.ts +7 -7
  120. package/src/__tests__/conversation-crud-inference-profile.test.ts +3 -3
  121. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +25 -26
  122. package/src/__tests__/conversation-directories-parse.test.ts +1 -1
  123. package/src/__tests__/conversation-disk-view-integration.test.ts +5 -5
  124. package/src/__tests__/conversation-disk-view.test.ts +6 -6
  125. package/src/__tests__/conversation-error.test.ts +18 -18
  126. package/src/__tests__/conversation-evictor.test.ts +32 -7
  127. package/src/__tests__/conversation-fork-crud.test.ts +441 -44
  128. package/src/__tests__/conversation-fork-retrospective.test.ts +12 -10
  129. package/src/__tests__/conversation-fork-route.test.ts +6 -3
  130. package/src/__tests__/conversation-history-web-search.test.ts +8 -145
  131. package/src/__tests__/conversation-inference-profile-list.test.ts +3 -3
  132. package/src/__tests__/conversation-inference-profile-route.test.ts +3 -3
  133. package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
  134. package/src/__tests__/conversation-initial-prompt.test.ts +110 -0
  135. package/src/__tests__/conversation-key-store-disk-view.test.ts +7 -4
  136. package/src/__tests__/conversation-lifecycle.test.ts +10 -13
  137. package/src/__tests__/conversation-list-source.test.ts +3 -3
  138. package/src/__tests__/conversation-load-history-repair.test.ts +4 -4
  139. package/src/__tests__/conversation-load-history-stripped.test.ts +4 -4
  140. package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +191 -0
  141. package/src/__tests__/conversation-pairing.test.ts +4 -4
  142. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +5 -5
  143. package/src/__tests__/conversation-process-callsite.test.ts +42 -5
  144. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -5
  145. package/src/__tests__/conversation-queue.test.ts +142 -9
  146. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -5
  147. package/src/__tests__/conversation-routes-guardian-reply.test.ts +6 -6
  148. package/src/__tests__/conversation-routes-slash-commands.test.ts +57 -10
  149. package/src/__tests__/conversation-runtime-assembly.test.ts +25 -19
  150. package/src/__tests__/conversation-runtime-workspace.test.ts +8 -1
  151. package/src/__tests__/conversation-skill-tools.test.ts +155 -2
  152. package/src/__tests__/conversation-slash-queue.test.ts +6 -6
  153. package/src/__tests__/conversation-slash-unknown.test.ts +6 -6
  154. package/src/__tests__/conversation-speed-override.test.ts +7 -7
  155. package/src/__tests__/conversation-starter-routes.test.ts +2 -2
  156. package/src/__tests__/conversation-store.test.ts +6 -6
  157. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +8 -5
  158. package/src/__tests__/conversation-surfaces-data-persist.test.ts +2 -2
  159. package/src/__tests__/conversation-sync-tags.test.ts +7 -4
  160. package/src/__tests__/conversation-title-service.test.ts +223 -203
  161. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
  162. package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
  163. package/src/__tests__/conversation-unread-route.test.ts +2 -2
  164. package/src/__tests__/conversation-usage.test.ts +6 -6
  165. package/src/__tests__/conversation-wipe.test.ts +6 -4
  166. package/src/__tests__/conversation-workspace-cache-state.test.ts +9 -9
  167. package/src/__tests__/conversation-workspace-injection.test.ts +9 -9
  168. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +9 -9
  169. package/src/__tests__/conversations-import-system-filter.test.ts +5 -5
  170. package/src/__tests__/credential-security-invariants.test.ts +4 -7
  171. package/src/__tests__/db-acp-history.test.ts +4 -4
  172. package/src/__tests__/db-activation-state-fk-cascade.test.ts +4 -4
  173. package/src/__tests__/db-activation-state.test.ts +3 -3
  174. package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
  175. package/src/__tests__/db-connection-isolation.test.ts +1 -1
  176. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +3 -3
  177. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +6 -6
  178. package/src/__tests__/db-drop-contact-acl-columns-migration.test.ts +159 -0
  179. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +3 -3
  180. package/src/__tests__/db-memory-graph-event-date-repair.test.ts +3 -3
  181. package/src/__tests__/db-migration-rollback.test.ts +48 -43
  182. package/src/__tests__/db-proxy-transaction.test.ts +2 -2
  183. package/src/__tests__/db-rename-inference-profile-snake-case-migration.test.ts +3 -3
  184. package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -4
  185. package/src/__tests__/db-slack-compaction-watermark-migration.test.ts +3 -3
  186. package/src/__tests__/db-slack-external-content-normalization.test.ts +4 -4
  187. package/src/__tests__/db-test-helpers.ts +7 -0
  188. package/src/__tests__/delete-propagation.test.ts +9 -7
  189. package/src/__tests__/deterministic-verification-control-plane.test.ts +3 -3
  190. package/src/__tests__/device-id.test.ts +38 -1
  191. package/src/__tests__/dm-backfill.test.ts +9 -7
  192. package/src/__tests__/dm-persistence.test.ts +5 -5
  193. package/src/__tests__/document-conversations.test.ts +3 -3
  194. package/src/__tests__/document-create-dedupe.test.ts +1 -1
  195. package/src/__tests__/document-find-replace.test.ts +1 -1
  196. package/src/__tests__/document-tool-security.test.ts +1 -1
  197. package/src/__tests__/document-update-default-surface.test.ts +1 -1
  198. package/src/__tests__/drop-capability-card-state-migration.test.ts +3 -5
  199. package/src/__tests__/edit-propagation.test.ts +6 -6
  200. package/src/__tests__/embedding-managed-proxy-selection.test.ts +2 -2
  201. package/src/__tests__/emit-signal-routing-intent.test.ts +2 -6
  202. package/src/__tests__/empty-state-greeting-cache.test.ts +1 -1
  203. package/src/__tests__/events-client-registration.test.ts +1 -1
  204. package/src/__tests__/exploration-drift-hook.test.ts +7 -23
  205. package/src/__tests__/file-write-tool.test.ts +3 -3
  206. package/src/__tests__/filing-service.test.ts +6 -5
  207. package/src/__tests__/followup-tools.test.ts +2 -2
  208. package/src/__tests__/get-skill-detail-audit.test.ts +1 -1
  209. package/src/__tests__/graph-extraction-event-date.test.ts +1 -1
  210. package/src/__tests__/guardian-action-sweep.test.ts +3 -3
  211. package/src/__tests__/guardian-binding-drift-heal.test.ts +48 -25
  212. package/src/__tests__/guardian-card-withdrawal.test.ts +3 -3
  213. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +4 -4
  214. package/src/__tests__/guardian-dispatch.test.ts +28 -11
  215. package/src/__tests__/guardian-expiry-notifier.test.ts +4 -4
  216. package/src/__tests__/guardian-outbound-http.test.ts +3 -4
  217. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +3 -3
  218. package/src/__tests__/guardian-routing-invariants.test.ts +3 -3
  219. package/src/__tests__/guardian-routing-state.test.ts +12 -14
  220. package/src/__tests__/guardian-verification-voice-binding.test.ts +4 -4
  221. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +1 -1
  222. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -3
  223. package/src/__tests__/heartbeat-disk-pressure.test.ts +7 -13
  224. package/src/__tests__/heartbeat-service.test.ts +31 -19
  225. package/src/__tests__/helpers/channel-test-adapter.ts +25 -18
  226. package/src/__tests__/helpers/create-guardian-binding.ts +16 -24
  227. package/src/__tests__/helpers/derive-guardian-delivery.ts +66 -0
  228. package/src/__tests__/helpers/gateway-acl-store.ts +81 -0
  229. package/src/__tests__/helpers/mock-logger.ts +1 -0
  230. package/src/__tests__/helpers/seed-contact-channel.ts +117 -0
  231. package/src/__tests__/history-repair-hook.test.ts +1 -1
  232. package/src/__tests__/home-state-routes.test.ts +1 -1
  233. package/src/__tests__/http-conversation-lineage.test.ts +4 -4
  234. package/src/__tests__/http-user-message-parity.test.ts +8 -12
  235. package/src/__tests__/identity-routes.test.ts +113 -25
  236. package/src/__tests__/image-recovery-hook.test.ts +3 -3
  237. package/src/__tests__/inbound-invite-redemption.test.ts +87 -12
  238. package/src/__tests__/inbound-slack-persistence.test.ts +5 -5
  239. package/src/__tests__/inbound-trust-verdict.test.ts +5 -4
  240. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +13 -6
  241. package/src/__tests__/inference-profile-reaper.test.ts +3 -3
  242. package/src/__tests__/inference-profile-session-handler.test.ts +3 -3
  243. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -3
  244. package/src/__tests__/init-feature-flag-overrides.test.ts +1 -4
  245. package/src/__tests__/injection-block.test.ts +5 -2
  246. package/src/__tests__/injector-background-turn.test.ts +3 -5
  247. package/src/__tests__/injector-chain.test.ts +25 -16
  248. package/src/__tests__/injector-config-quarantine-notice.test.ts +3 -5
  249. package/src/__tests__/injector-disk-pressure.test.ts +10 -8
  250. package/src/__tests__/injector-document-comments.test.ts +5 -3
  251. package/src/__tests__/injector-pkb-v2-silenced.test.ts +5 -2
  252. package/src/__tests__/injector-registry-order-guard.test.ts +132 -0
  253. package/src/__tests__/injector-v3-suppression.test.ts +15 -9
  254. package/src/__tests__/install-meta.test.ts +104 -0
  255. package/src/__tests__/install-skill-routing.test.ts +1 -1
  256. package/src/__tests__/internal-telemetry-routes.test.ts +4 -4
  257. package/src/__tests__/invite-redemption-service.test.ts +274 -57
  258. package/src/__tests__/invite-routes-http.test.ts +37 -3
  259. package/src/__tests__/invite-service-ipc.test.ts +68 -5
  260. package/src/__tests__/job-handler-registry-guard.test.ts +129 -0
  261. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +8 -8
  262. package/src/__tests__/jobs-store-upsert-debounced.test.ts +7 -4
  263. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -8
  264. package/src/__tests__/list-messages-attachments.test.ts +7 -4
  265. package/src/__tests__/list-messages-background-tool-completion.test.ts +141 -0
  266. package/src/__tests__/list-messages-client-message-id.test.ts +6 -3
  267. package/src/__tests__/list-messages-hidden-metadata.test.ts +5 -5
  268. package/src/__tests__/list-messages-page-latest.test.ts +176 -7
  269. package/src/__tests__/list-messages-queued.test.ts +223 -0
  270. package/src/__tests__/list-messages-tool-merge.test.ts +6 -3
  271. package/src/__tests__/llm-context-route-provider.test.ts +3 -3
  272. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +4 -4
  273. package/src/__tests__/llm-request-log-call-site.test.ts +5 -5
  274. package/src/__tests__/llm-request-log-error-payload.test.ts +1 -1
  275. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -1
  276. package/src/__tests__/llm-request-log-source-factory.test.ts +3 -3
  277. package/src/__tests__/llm-request-log-turn-query.test.ts +9 -6
  278. package/src/__tests__/llm-usage-store.test.ts +137 -3
  279. package/src/__tests__/log-export-routes.test.ts +3 -3
  280. package/src/__tests__/log-export-workspace.test.ts +3 -3
  281. package/src/__tests__/managed-profile-guard.test.ts +1 -1
  282. package/src/__tests__/managed-skill-lifecycle.test.ts +3 -3
  283. package/src/__tests__/managed-store.test.ts +381 -0
  284. package/src/__tests__/mcp-config-secret-boundary.test.ts +5 -1
  285. package/src/__tests__/media-stream-server-integration.test.ts +1 -1
  286. package/src/__tests__/memory-jobs-worker-backoff.test.ts +4 -4
  287. package/src/__tests__/memory-jobs-worker-lanes.test.ts +11 -9
  288. package/src/__tests__/memory-recall-log-store.test.ts +4 -4
  289. package/src/__tests__/memory-retrieval-budget.test.ts +1 -1
  290. package/src/__tests__/memory-retrieval-hook.test.ts +8 -8
  291. package/src/__tests__/memory-upsert-concurrency.test.ts +9 -5
  292. package/src/__tests__/memory-v2-static-injector.test.ts +3 -3
  293. package/src/__tests__/messages-after-tiebreaker.test.ts +4 -4
  294. package/src/__tests__/messaging-send-tool.test.ts +5 -5
  295. package/src/__tests__/mock-gateway-ipc.ts +1 -1
  296. package/src/__tests__/mtime-cache.test.ts +19 -0
  297. package/src/__tests__/native-web-search.test.ts +11 -11
  298. package/src/__tests__/non-member-access-request.test.ts +24 -20
  299. package/src/__tests__/notification-candidate-guardian-context.test.ts +4 -4
  300. package/src/__tests__/notification-deep-link.test.ts +3 -3
  301. package/src/__tests__/notification-guardian-path.test.ts +3 -3
  302. package/src/__tests__/notification-schedule-notify-dedup.test.ts +3 -3
  303. package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
  304. package/src/__tests__/oauth-provider-visibility.test.ts +2 -2
  305. package/src/__tests__/oauth-store.test.ts +6 -6
  306. package/src/__tests__/onboarding-persona-write.test.ts +52 -22
  307. package/src/__tests__/onboarding-template-contract.test.ts +27 -43
  308. package/src/__tests__/openai-provider.test.ts +33 -0
  309. package/src/__tests__/openai-responses-provider.test.ts +40 -0
  310. package/src/__tests__/outbound-slack-persistence.test.ts +8 -8
  311. package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
  312. package/src/__tests__/persist-unsendable-image-downscale.test.ts +3 -3
  313. package/src/__tests__/persist-unsendable-image.test.ts +3 -3
  314. package/src/__tests__/persist-user-message-set-processing-failure.test.ts +70 -0
  315. package/src/__tests__/persistence-layering-guard.test.ts +222 -0
  316. package/src/__tests__/persistence-secret-redaction.test.ts +6 -6
  317. package/src/__tests__/persona-resolver.test.ts +75 -45
  318. package/src/__tests__/pkb-autoinject.test.ts +1 -1
  319. package/src/__tests__/platform-bash-auto-approve.test.ts +3 -3
  320. package/src/__tests__/playbook-execution.test.ts +4 -4
  321. package/src/__tests__/playbook-tools.test.ts +3 -3
  322. package/src/__tests__/plugin-bootstrap.test.ts +78 -19
  323. package/src/__tests__/plugin-config-data-migration.test.ts +263 -0
  324. package/src/__tests__/plugin-disabled-state.test.ts +323 -0
  325. package/src/__tests__/plugin-event-hub-facade.test.ts +323 -0
  326. package/src/__tests__/plugin-import-boundary-guard.test.ts +506 -0
  327. package/src/__tests__/plugin-injector-contribution.test.ts +133 -0
  328. package/src/__tests__/plugin-route-contribution.test.ts +62 -115
  329. package/src/__tests__/plugin-tool-contribution.test.ts +0 -22
  330. package/src/__tests__/pointer-conversation-trust.test.ts +141 -0
  331. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +8 -1
  332. package/src/__tests__/process-message-background-slack.test.ts +6 -6
  333. package/src/__tests__/process-message-display-content.test.ts +5 -5
  334. package/src/__tests__/processing-flag-persist-failure.test.ts +280 -0
  335. package/src/__tests__/provider-usage-tracking.test.ts +4 -4
  336. package/src/__tests__/prune-jobs-changes-parser.test.ts +1 -1
  337. package/src/__tests__/prune-old-conversations-job.test.ts +5 -5
  338. package/src/__tests__/prune-old-tool-invocations-job.test.ts +97 -0
  339. package/src/__tests__/published-app-updater.test.ts +3 -3
  340. package/src/__tests__/qdrant-collection-migration.test.ts +5 -2
  341. package/src/__tests__/qdrant-manager.test.ts +1 -1
  342. package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +141 -0
  343. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
  344. package/src/__tests__/reaction-persistence.test.ts +60 -10
  345. package/src/__tests__/rebuild-index-graph-nodes.test.ts +5 -5
  346. package/src/__tests__/recording-handler.test.ts +2 -2
  347. package/src/__tests__/relay-server.test.ts +183 -142
  348. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  349. package/src/__tests__/resolve-app-id.test.ts +3 -3
  350. package/src/__tests__/runtime-attachment-metadata.test.ts +16 -18
  351. package/src/__tests__/runtime-events-sse-bilingual.test.ts +3 -3
  352. package/src/__tests__/runtime-events-sse-parity.test.ts +3 -3
  353. package/src/__tests__/runtime-events-sse-reconnect.test.ts +3 -3
  354. package/src/__tests__/runtime-events-sse.test.ts +3 -3
  355. package/src/__tests__/scaffold-managed-skill-tool.test.ts +489 -2
  356. package/src/__tests__/schedule-retry.test.ts +91 -67
  357. package/src/__tests__/schedule-routes-workflow-validation.test.ts +47 -43
  358. package/src/__tests__/schedule-routes.test.ts +199 -187
  359. package/src/__tests__/schedule-store.test.ts +174 -172
  360. package/src/__tests__/schedule-tools.test.ts +2 -2
  361. package/src/__tests__/scheduler-disk-pressure.test.ts +20 -9
  362. package/src/__tests__/scheduler-recurrence.test.ts +72 -122
  363. package/src/__tests__/scheduler-reuse-conversation.test.ts +65 -60
  364. package/src/__tests__/scheduler-wake.test.ts +21 -31
  365. package/src/__tests__/scoped-approval-grants.test.ts +4 -4
  366. package/src/__tests__/scoped-grant-security-matrix.test.ts +4 -4
  367. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +2 -2
  368. package/src/__tests__/search-skills-unified.test.ts +1 -1
  369. package/src/__tests__/secret-ingress-channel.test.ts +3 -3
  370. package/src/__tests__/secret-ingress-http.test.ts +6 -6
  371. package/src/__tests__/secret-routes-platform-proxy.test.ts +9 -8
  372. package/src/__tests__/send-endpoint-busy.test.ts +6 -6
  373. package/src/__tests__/sequence-store.test.ts +3 -3
  374. package/src/__tests__/server-history-render.test.ts +7 -4
  375. package/src/__tests__/settings-routes.test.ts +34 -2
  376. package/src/__tests__/shell-observability.test.ts +1 -0
  377. package/src/__tests__/skill-boundary-guard.test.ts +3 -4
  378. package/src/__tests__/skill-docs-sync-guard.test.ts +34 -0
  379. package/src/__tests__/skill-projection-feature-flag.test.ts +2 -1
  380. package/src/__tests__/skill-projection.benchmark.test.ts +2 -1
  381. package/src/__tests__/skill-script-runner-host.test.ts +53 -0
  382. package/src/__tests__/skill-script-runner-sandbox.test.ts +5 -2
  383. package/src/__tests__/skill-tool-factory.test.ts +20 -0
  384. package/src/__tests__/skill-version-hash.test.ts +32 -0
  385. package/src/__tests__/skills-file-content-endpoint.test.ts +1 -1
  386. package/src/__tests__/skills-files-catalog-fallback.test.ts +1 -1
  387. package/src/__tests__/slack-inbound-verification.test.ts +2 -2
  388. package/src/__tests__/slack-messaging-token-resolution.test.ts +2 -2
  389. package/src/__tests__/slack-reaction-canonical-approval.test.ts +4 -4
  390. package/src/__tests__/slack-share-routes.test.ts +1 -1
  391. package/src/__tests__/slim-skill-category.test.ts +34 -2
  392. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +13 -36
  393. package/src/__tests__/starter-task-flow.test.ts +1 -1
  394. package/src/__tests__/strip-memory-injections.test.ts +2 -2
  395. package/src/__tests__/stt-hints.test.ts +6 -3
  396. package/src/__tests__/subagent-allowlist-validation.test.ts +47 -0
  397. package/src/__tests__/subagent-call-site-routing.test.ts +2 -2
  398. package/src/__tests__/subagent-detail.test.ts +1 -1
  399. package/src/__tests__/subagent-fork-notifications.test.ts +14 -2
  400. package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
  401. package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
  402. package/src/__tests__/subagent-notify-parent.test.ts +3 -3
  403. package/src/__tests__/subagent-persistence.test.ts +122 -0
  404. package/src/__tests__/subagent-role-registry.test.ts +17 -4
  405. package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
  406. package/src/__tests__/subagent-spawn-tool-fork.test.ts +3 -3
  407. package/src/__tests__/subagent-terminal-message.test.ts +99 -0
  408. package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
  409. package/src/__tests__/subagent-tools.test.ts +396 -4
  410. package/src/__tests__/suggestion-routes.test.ts +4 -4
  411. package/src/__tests__/system-prompt.test.ts +4 -4
  412. package/src/__tests__/task-compiler.test.ts +2 -2
  413. package/src/__tests__/task-management-tools.test.ts +2 -2
  414. package/src/__tests__/task-memory-cleanup.test.ts +9 -7
  415. package/src/__tests__/task-progress-nudge-hook.test.ts +37 -94
  416. package/src/__tests__/task-scheduler.test.ts +94 -115
  417. package/src/__tests__/test-support/tool-invocation-seed.ts +5 -2
  418. package/src/__tests__/thread-backfill.test.ts +7 -7
  419. package/src/__tests__/title-generate-hook.test.ts +6 -6
  420. package/src/__tests__/tool-approval-handler.test.ts +4 -4
  421. package/src/__tests__/tool-audit-listener.test.ts +1 -1
  422. package/src/__tests__/tool-error-hook.test.ts +2 -0
  423. package/src/__tests__/tool-executor-lifecycle-events.test.ts +5 -5
  424. package/src/__tests__/tool-executor-redaction.test.ts +11 -0
  425. package/src/__tests__/tool-executor.test.ts +24 -2
  426. package/src/__tests__/tool-grant-request-escalation.test.ts +5 -5
  427. package/src/__tests__/tool-preview-lifecycle.test.ts +102 -17
  428. package/src/__tests__/tool-result-metadata-plumbing.test.ts +4 -4
  429. package/src/__tests__/tool-result-truncate-hook.test.ts +2 -0
  430. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -1
  431. package/src/__tests__/tool-start-timestamp.test.ts +69 -5
  432. package/src/__tests__/tools-get-route.test.ts +44 -1
  433. package/src/__tests__/trace-emitter.test.ts +1 -1
  434. package/src/__tests__/trust-context-guards.test.ts +1 -1
  435. package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
  436. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -7
  437. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +20 -12
  438. package/src/__tests__/trusted-contact-multichannel.test.ts +17 -8
  439. package/src/__tests__/trusted-contact-verification.test.ts +60 -57
  440. package/src/__tests__/turn-boundary-resolution.test.ts +7 -4
  441. package/src/__tests__/turn-call-site.test.ts +29 -0
  442. package/src/__tests__/turn-events-store.test.ts +8 -5
  443. package/src/__tests__/twilio-routes.test.ts +3 -3
  444. package/src/__tests__/unified-turn-context-client-os.test.ts +45 -0
  445. package/src/__tests__/usage-cache-backfill-migration.test.ts +4 -8
  446. package/src/__tests__/usage-grouped-buckets.test.ts +1 -1
  447. package/src/__tests__/usage-routes.test.ts +61 -3
  448. package/src/__tests__/verification-control-plane-policy.test.ts +6 -6
  449. package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
  450. package/src/__tests__/voice-invite-redemption.test.ts +183 -24
  451. package/src/__tests__/voice-quality.test.ts +38 -12
  452. package/src/__tests__/voice-scoped-grant-consumer.test.ts +22 -14
  453. package/src/__tests__/voice-session-bridge.test.ts +16 -10
  454. package/src/__tests__/web-search-backend-failure.test.ts +4 -4
  455. package/src/__tests__/workspace-lifecycle.test.ts +29 -18
  456. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -4
  457. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +5 -5
  458. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +3 -3
  459. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
  460. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +2 -2
  461. package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
  462. package/src/__tests__/workspace-migration-113-swap-balanced-profile-to-glm-5p2.test.ts +241 -0
  463. package/src/__tests__/workspace-migration-114-swap-quality-profile-to-opus.test.ts +172 -0
  464. package/src/__tests__/workspace-migration-116-rename-memory-plugin-disabled-sentinel.test.ts +128 -0
  465. package/src/__tests__/workspace-migration-117-normalize-stale-lean-memory-v3-defaults.test.ts +221 -0
  466. package/src/__tests__/workspace-migration-118-seed-now-md.test.ts +120 -0
  467. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -1
  468. package/src/__tests__/workspace-migration-drop-user-md.test.ts +273 -238
  469. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +41 -49
  470. package/src/a2a/__tests__/task-store.test.ts +2 -2
  471. package/src/a2a/task-store.ts +4 -4
  472. package/src/acp/__tests__/client-handler.test.ts +680 -0
  473. package/src/acp/__tests__/helpers/acp-history-db.ts +32 -4
  474. package/src/acp/__tests__/prepare-agent-env.test.ts +0 -97
  475. package/src/acp/__tests__/session-manager-persistence.test.ts +215 -7
  476. package/src/acp/__tests__/session-manager-resume.test.ts +113 -2
  477. package/src/acp/__tests__/session-manager-startup.test.ts +3 -3
  478. package/src/acp/__tests__/session-manager-usage.test.ts +113 -0
  479. package/src/acp/auto-install.ts +12 -8
  480. package/src/acp/client-handler.ts +140 -21
  481. package/src/acp/prepare-agent-env.ts +0 -12
  482. package/src/acp/resolve-agent.test.ts +6 -45
  483. package/src/acp/resolve-agent.ts +2 -4
  484. package/src/acp/session-manager.ts +127 -4
  485. package/src/acp/types.ts +18 -0
  486. package/src/agent/loop-exclusive-tool.test.ts +19 -15
  487. package/src/agent/loop-native-web-search.test.ts +200 -0
  488. package/src/agent/loop.ts +321 -149
  489. package/src/api/events/acp-session-completed.ts +33 -0
  490. package/src/api/events/acp-session-error.ts +21 -0
  491. package/src/api/events/acp-session-spawned.ts +32 -0
  492. package/src/api/events/acp-session-update.test.ts +122 -0
  493. package/src/api/events/acp-session-update.ts +51 -0
  494. package/src/api/events/acp-session-usage.test.ts +49 -0
  495. package/src/api/events/acp-session-usage.ts +31 -0
  496. package/src/api/events/background-tool-completed.test.ts +51 -0
  497. package/src/api/events/background-tool-completed.ts +29 -0
  498. package/src/api/events/background-tool-started.test.ts +36 -0
  499. package/src/api/events/background-tool-started.ts +28 -0
  500. package/src/api/events/conversation-error.ts +0 -1
  501. package/src/api/events/subagent-status-changed.ts +4 -1
  502. package/src/api/events/tool-use-preview-start.ts +11 -0
  503. package/src/api/events/tool-use-start.ts +14 -0
  504. package/src/api/index.ts +51 -0
  505. package/src/api/responses/conversation-message.ts +80 -1
  506. package/src/api/responses/llm-request-log-entry.ts +41 -0
  507. package/src/api/responses/memory-v3-selection-log.ts +5 -7
  508. package/src/approvals/approval-primitive.ts +1 -1
  509. package/src/approvals/guardian-card-withdrawal.ts +2 -2
  510. package/src/approvals/guardian-decision-primitive.ts +1 -1
  511. package/src/approvals/guardian-expiry-notifier.ts +1 -1
  512. package/src/approvals/guardian-request-resolvers.ts +19 -7
  513. package/src/approvals/scoped-approval-grants.ts +551 -0
  514. package/src/apps/app-store.ts +1058 -0
  515. package/src/apps/published-pages-store.ts +67 -0
  516. package/src/apps/shared-app-links-store.ts +130 -0
  517. package/src/background-wake/background-wake-routes.test.ts +53 -23
  518. package/src/background-wake/wake-intent-hooks.test.ts +18 -12
  519. package/src/backup/restore.ts +1 -1
  520. package/src/bundler/app-bundler.ts +1 -1
  521. package/src/calls/__tests__/relay-setup-router.test.ts +11 -19
  522. package/src/calls/call-controller.ts +5 -5
  523. package/src/calls/call-conversation-messages.ts +1 -1
  524. package/src/calls/call-domain.ts +5 -5
  525. package/src/calls/call-pointer-messages.ts +1 -1
  526. package/src/calls/call-store.ts +3 -3
  527. package/src/calls/guardian-action-sweep.ts +1 -1
  528. package/src/calls/guardian-dispatch.ts +15 -12
  529. package/src/calls/inbound-trust-reader.ts +7 -1
  530. package/src/calls/media-stream-server.ts +1 -1
  531. package/src/calls/relay-access-wait.ts +7 -7
  532. package/src/calls/relay-server.ts +25 -5
  533. package/src/calls/relay-setup-router.ts +11 -11
  534. package/src/calls/tts-call-strategy.ts +4 -3
  535. package/src/calls/voice-quality.ts +47 -59
  536. package/src/calls/voice-session-bridge.ts +2 -16
  537. package/src/channels/__tests__/types.test.ts +28 -0
  538. package/src/channels/channel-verification-sessions.ts +557 -0
  539. package/src/channels/types.ts +23 -0
  540. package/src/cli/commands/__tests__/conversations-new.test.ts +186 -0
  541. package/src/cli/commands/__tests__/conversations-slack.test.ts +2 -1
  542. package/src/cli/commands/__tests__/conversations-wake.test.ts +147 -0
  543. package/src/cli/commands/__tests__/ps.test.ts +205 -0
  544. package/src/cli/commands/__tests__/schedules.test.ts +52 -0
  545. package/src/cli/commands/contacts.ts +10 -7
  546. package/src/cli/commands/conversations.ts +90 -30
  547. package/src/cli/commands/db/__tests__/repair.test.ts +4 -3
  548. package/src/cli/commands/db/repair-step-conversation-backfill.ts +2 -2
  549. package/src/cli/commands/memory/__tests__/memory-v2-compare-render.test.ts +1 -1
  550. package/src/cli/commands/memory/__tests__/worker.test.ts +112 -183
  551. package/src/cli/commands/memory/memory-retrospective.ts +32 -28
  552. package/src/cli/commands/memory/memory-v2-compare-render.ts +1 -1
  553. package/src/cli/commands/memory/memory-v2.ts +2 -2
  554. package/src/cli/commands/memory/memory-v3.ts +2 -2
  555. package/src/cli/commands/memory/worker.ts +103 -113
  556. package/src/cli/commands/plugins.ts +150 -157
  557. package/src/cli/commands/ps.ts +90 -0
  558. package/src/cli/commands/schedules.ts +52 -8
  559. package/src/cli/commands/tools-run.ts +114 -0
  560. package/src/cli/commands/tools.ts +19 -5
  561. package/src/cli/lib/__tests__/cli-colors.test.ts +30 -1
  562. package/src/cli/lib/__tests__/install-from-github.test.ts +168 -8
  563. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +23 -18
  564. package/src/cli/lib/__tests__/parse-github-plugin-spec.test.ts +121 -0
  565. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +1 -0
  566. package/src/cli/lib/__tests__/publish-plugin.test.ts +98 -0
  567. package/src/cli/lib/__tests__/search-plugins.test.ts +29 -1
  568. package/src/cli/lib/__tests__/toggle-plugin.test.ts +10 -4
  569. package/src/cli/lib/cli-colors.ts +6 -0
  570. package/src/cli/lib/diff-plugin.ts +3 -7
  571. package/src/cli/lib/inspect-plugin.ts +2 -4
  572. package/src/cli/lib/install-from-github.ts +96 -24
  573. package/src/cli/lib/merge-plugin-tree.ts +5 -3
  574. package/src/cli/lib/parse-github-plugin-spec.ts +163 -0
  575. package/src/cli/lib/plugin-fingerprint.ts +22 -0
  576. package/src/cli/lib/publish-plugin.ts +231 -1
  577. package/src/cli/lib/search-plugins.ts +6 -0
  578. package/src/cli/lib/upgrade-plugin.ts +6 -5
  579. package/src/cli/program.ts +5 -8
  580. package/src/cli.ts +6 -3
  581. package/src/config/__tests__/deployment-context-defaults.test.ts +228 -0
  582. package/src/config/__tests__/proc-to-skills.test.ts +21 -0
  583. package/src/config/__tests__/sync-gated-profiles.test.ts +7 -9
  584. package/src/config/acp-defaults.test.ts +0 -10
  585. package/src/config/acp-defaults.ts +0 -6
  586. package/src/config/bundled-skills/AGENTS.md +11 -0
  587. package/src/config/bundled-skills/acp/SKILL.md +13 -45
  588. package/src/config/bundled-skills/acp/TOOLS.json +3 -3
  589. package/src/config/bundled-skills/app-builder/SKILL.md +5 -3
  590. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +9 -9
  591. package/src/config/bundled-skills/app-builder/tools/app-create.ts +2 -2
  592. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +2 -2
  593. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +2 -2
  594. package/src/config/bundled-skills/app-builder/tools/app-list.ts +1 -1
  595. package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +2 -2
  596. package/src/config/bundled-skills/app-builder/tools/app-update.ts +2 -2
  597. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  598. package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +1 -1
  599. package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +1 -1
  600. package/src/config/bundled-skills/media-processing/services/preprocess.ts +1 -1
  601. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +1 -1
  602. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  603. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +1 -1
  604. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +1 -1
  605. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  606. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +2 -2
  607. package/src/config/bundled-skills/media-processing/tools/media-status.ts +1 -1
  608. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +5 -5
  609. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +6 -6
  610. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +8 -5
  611. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +4 -1
  612. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  613. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -4
  614. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +7 -7
  615. package/src/config/bundled-skills/skill-management/TOOLS.json +45 -0
  616. package/src/config/bundled-skills/skill-management/tools/find-similar.ts +12 -0
  617. package/src/config/bundled-skills/subagent/SKILL.md +16 -1
  618. package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
  619. package/src/config/bundled-tool-registry.ts +2 -0
  620. package/src/config/call-site-defaults.ts +0 -8
  621. package/src/config/feature-flag-registry.json +16 -24
  622. package/src/config/llm-resolver.ts +17 -3
  623. package/src/config/loader.ts +38 -58
  624. package/src/config/memory-v3-gate.ts +16 -0
  625. package/src/config/schema.ts +2 -0
  626. package/src/config/schemas/__tests__/memory-v2.test.ts +2 -2
  627. package/src/config/schemas/__tests__/memory-v3.test.ts +16 -5
  628. package/src/config/schemas/call-site-catalog.ts +0 -19
  629. package/src/config/schemas/conversations.ts +8 -0
  630. package/src/config/schemas/heartbeat.ts +2 -5
  631. package/src/config/schemas/llm.ts +3 -14
  632. package/src/config/schemas/memory-lifecycle.ts +10 -1
  633. package/src/config/schemas/memory-v2.ts +1 -1
  634. package/src/config/schemas/memory-v3.ts +56 -6
  635. package/src/config/schemas/migrations.ts +25 -0
  636. package/src/config/schemas/services.ts +0 -37
  637. package/src/config/seed-inference-profiles.ts +88 -84
  638. package/src/config/sync-gated-profiles.ts +6 -20
  639. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +7 -8
  640. package/src/contacts/__tests__/guardian-contact-reader.test.ts +89 -0
  641. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +3 -14
  642. package/src/contacts/__tests__/member-write-relay.test.ts +35 -11
  643. package/src/contacts/canonical-guardian-store.ts +891 -0
  644. package/src/contacts/contact-store.ts +37 -245
  645. package/src/contacts/contacts-write.ts +18 -58
  646. package/src/contacts/gateway-channel-read.ts +51 -0
  647. package/src/contacts/guardian-contact-reader.ts +52 -0
  648. package/src/contacts/guardian-delivery-reader.ts +21 -23
  649. package/src/contacts/guardian-rate-limits.ts +191 -0
  650. package/src/contacts/member-write-relay.ts +25 -31
  651. package/src/contacts/notify-contacts-changed.ts +17 -0
  652. package/src/contacts/types.ts +3 -15
  653. package/src/context/compactor.ts +3 -3
  654. package/src/context/strip-injections.ts +1 -1
  655. package/src/conversations/__tests__/message-consolidation.test.ts +10 -26
  656. package/src/conversations/job-handlers/summarization.ts +246 -0
  657. package/src/conversations/message-consolidation.ts +1 -1
  658. package/src/credential-execution/ces-runtime.ts +285 -0
  659. package/src/daemon/__tests__/abort-null-controller.test.ts +167 -0
  660. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +25 -22
  661. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +5 -4
  662. package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
  663. package/src/daemon/__tests__/daemon-readiness.test.ts +51 -0
  664. package/src/daemon/__tests__/embedding-reconcile.test.ts +456 -0
  665. package/src/daemon/__tests__/inference-profile-notification.test.ts +9 -6
  666. package/src/daemon/__tests__/lifecycle-embedding-reconcile.test.ts +119 -0
  667. package/src/daemon/__tests__/native-web-search-metadata.test.ts +2 -2
  668. package/src/daemon/__tests__/wake-conversation-ops-completion.test.ts +107 -0
  669. package/src/daemon/__tests__/web-search-status-text.test.ts +3 -5
  670. package/src/daemon/app-source-watcher.ts +71 -10
  671. package/src/daemon/channel-ui-capability.ts +25 -0
  672. package/src/daemon/config-watcher.ts +71 -47
  673. package/src/daemon/conversation-agent-loop-handlers.ts +266 -49
  674. package/src/daemon/conversation-agent-loop.ts +166 -89
  675. package/src/daemon/conversation-attachments.ts +2 -2
  676. package/src/daemon/conversation-error.ts +8 -25
  677. package/src/daemon/conversation-evictor.ts +42 -9
  678. package/src/daemon/conversation-history.ts +8 -234
  679. package/src/daemon/conversation-initial-prompt.ts +77 -0
  680. package/src/daemon/conversation-launch.ts +2 -2
  681. package/src/daemon/conversation-lifecycle.ts +32 -5
  682. package/src/daemon/conversation-messaging.ts +25 -11
  683. package/src/daemon/conversation-notifiers.ts +1 -1
  684. package/src/daemon/conversation-process.ts +27 -15
  685. package/src/daemon/conversation-queue-manager.ts +6 -0
  686. package/src/daemon/conversation-registry.ts +6 -5
  687. package/src/daemon/conversation-runtime-assembly.ts +45 -31
  688. package/src/daemon/conversation-skill-tools.ts +26 -2
  689. package/src/daemon/conversation-slash.ts +1 -1
  690. package/src/daemon/conversation-store.ts +47 -42
  691. package/src/daemon/conversation-surfaces.ts +4 -4
  692. package/src/daemon/conversation-tool-setup.ts +2 -11
  693. package/src/daemon/conversation-usage.ts +4 -2
  694. package/src/daemon/conversation-workspace.ts +2 -2
  695. package/src/daemon/conversation.ts +104 -17
  696. package/src/daemon/daemon-readiness.ts +33 -0
  697. package/src/daemon/embedding-reconcile.ts +340 -0
  698. package/src/daemon/event-loop-watchdog.ts +1 -1
  699. package/src/daemon/external-plugins-bootstrap.ts +123 -92
  700. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +2 -3
  701. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +3 -5
  702. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +3 -3
  703. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +7 -13
  704. package/src/daemon/handlers/__tests__/config-a2a.test.ts +2 -2
  705. package/src/daemon/handlers/__tests__/config-channels.test.ts +22 -21
  706. package/src/daemon/handlers/config-a2a.ts +3 -3
  707. package/src/daemon/handlers/config-channels.ts +21 -33
  708. package/src/daemon/handlers/config-embeddings.ts +1 -1
  709. package/src/daemon/handlers/conversation-history.ts +2 -2
  710. package/src/daemon/handlers/conversations.ts +7 -56
  711. package/src/daemon/handlers/recording.ts +6 -3
  712. package/src/daemon/handlers/shared.ts +2 -0
  713. package/src/daemon/handlers/skills.ts +9 -1
  714. package/src/daemon/lifecycle.ts +714 -1242
  715. package/src/daemon/message-protocol.ts +3 -3
  716. package/src/daemon/message-provenance.ts +1 -1
  717. package/src/daemon/message-types/acp.ts +27 -1
  718. package/src/daemon/message-types/background-tools.ts +28 -0
  719. package/src/daemon/message-types/bookmarks.ts +1 -1
  720. package/src/daemon/message-types/conversations.ts +10 -0
  721. package/src/daemon/message-types/skills.ts +20 -2
  722. package/src/daemon/message-types/surfaces.ts +2 -0
  723. package/src/daemon/pointer-conversation-trust.ts +66 -0
  724. package/src/daemon/process-message.ts +24 -3
  725. package/src/daemon/providers-setup.ts +16 -14
  726. package/src/daemon/shutdown-handlers.ts +195 -166
  727. package/src/daemon/skill-memory-refresh.ts +3 -3
  728. package/src/daemon/status.ts +15 -0
  729. package/src/daemon/tool-setup-types.ts +17 -0
  730. package/src/daemon/tool-side-effects.ts +4 -4
  731. package/src/daemon/trace-emitter.ts +1 -1
  732. package/src/daemon/trust-context.ts +1 -1
  733. package/src/daemon/turn-call-site.ts +25 -0
  734. package/src/daemon/turn-latency-tracker.test.ts +137 -0
  735. package/src/daemon/turn-latency-tracker.ts +134 -0
  736. package/src/daemon/wake-conversation-ops.ts +14 -4
  737. package/src/documents/document-comments-store.test.ts +2 -2
  738. package/src/documents/document-comments-store.ts +1 -1
  739. package/src/documents/document-store.ts +1 -1
  740. package/src/events/tool-audit-listener.ts +4 -4
  741. package/src/events/tool-permission-telemetry-listener.ts +1 -1
  742. package/src/export/__tests__/transcript-formatter.test.ts +1 -1
  743. package/src/export/transcript-formatter.ts +1 -1
  744. package/src/followups/followup-store.ts +2 -2
  745. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +2 -2
  746. package/src/heartbeat/__tests__/heartbeat-service.test.ts +45 -39
  747. package/src/heartbeat/heartbeat-run-store.ts +3 -3
  748. package/src/heartbeat/heartbeat-service.ts +34 -11
  749. package/src/home/__tests__/relationship-state-writer.test.ts +1 -1
  750. package/src/home/__tests__/suggested-prompts.test.ts +1 -1
  751. package/src/home/conversation-starter-checkpoints.ts +64 -0
  752. package/src/home/feed-source-enrichment.ts +3 -3
  753. package/src/home/home-greeting-cache.ts +1 -1
  754. package/src/home/job-handlers/conversation-starters.ts +484 -0
  755. package/src/home/relationship-state-writer.ts +6 -1
  756. package/src/home/suggested-prompts.ts +1 -1
  757. package/src/hooks/hook-loader.ts +141 -6
  758. package/src/hooks/registry.ts +129 -0
  759. package/src/ipc/__tests__/attachment-ipc.test.ts +1 -1
  760. package/src/ipc/__tests__/cli-ipc.test.ts +1 -1
  761. package/src/ipc/__tests__/inject-local-actor-header.test.ts +33 -0
  762. package/src/ipc/__tests__/watcher-ipc.test.ts +1 -1
  763. package/src/ipc/assistant-server.ts +79 -33
  764. package/src/ipc/routes/__tests__/memory-v2-backfill.test.ts +3 -3
  765. package/src/ipc/routes/db-proxy-transaction.ts +1 -1
  766. package/src/ipc/routes/db-proxy.ts +4 -2
  767. package/src/jobs/register-job-handlers.ts +78 -0
  768. package/src/jobs/worker.ts +91 -0
  769. package/src/live-voice/__tests__/live-voice-archive.test.ts +5 -5
  770. package/src/live-voice/live-voice-archive.ts +2 -2
  771. package/src/mcp/manager.ts +11 -0
  772. package/src/media/app-icon-generator.ts +1 -1
  773. package/src/media/job-handlers/media-processing.ts +126 -0
  774. package/src/messaging/channel-binding-metadata.ts +31 -0
  775. package/src/messaging/channel-binding-schema.ts +51 -0
  776. package/src/messaging/content/parse.test.ts +34 -0
  777. package/src/messaging/content/parse.ts +23 -0
  778. package/src/messaging/providers/__tests__/callback-routing.test.ts +45 -0
  779. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +196 -0
  780. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +11 -0
  781. package/src/messaging/providers/a2a/deliver.ts +5 -1
  782. package/src/messaging/providers/a2a/transport.ts +10 -0
  783. package/src/messaging/providers/callback-routing.ts +48 -0
  784. package/src/messaging/providers/channel-transport.ts +55 -0
  785. package/src/messaging/providers/index.ts +65 -241
  786. package/src/messaging/providers/slack/binding-metadata.ts +62 -0
  787. package/src/messaging/providers/slack/render.test.ts +309 -0
  788. package/src/messaging/providers/slack/render.ts +459 -0
  789. package/src/messaging/providers/slack/send.test.ts +39 -0
  790. package/src/messaging/providers/slack/send.ts +38 -18
  791. package/src/messaging/providers/slack/transport.ts +92 -0
  792. package/src/messaging/providers/telegram-bot/adapter.ts +2 -2
  793. package/src/messaging/providers/telegram-bot/api.ts +17 -3
  794. package/src/messaging/providers/telegram-bot/render.test.ts +146 -0
  795. package/src/messaging/providers/telegram-bot/render.ts +292 -0
  796. package/src/messaging/providers/telegram-bot/send.test.ts +197 -0
  797. package/src/messaging/providers/telegram-bot/send.ts +70 -2
  798. package/src/messaging/providers/telegram-bot/transport.ts +60 -0
  799. package/src/messaging/providers/whatsapp/adapter.ts +2 -2
  800. package/src/messaging/providers/whatsapp/send.ts +1 -1
  801. package/src/messaging/providers/whatsapp/transport.ts +38 -0
  802. package/src/notifications/__tests__/broadcaster.test.ts +0 -8
  803. package/src/notifications/__tests__/connected-channels.test.ts +8 -36
  804. package/src/notifications/__tests__/destination-resolver.test.ts +12 -117
  805. package/src/notifications/__tests__/deterministic-checks.test.ts +3 -3
  806. package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -1
  807. package/src/notifications/broadcaster.ts +1 -1
  808. package/src/notifications/canonical-delivery-recorder.ts +1 -1
  809. package/src/notifications/conversation-candidates.ts +3 -3
  810. package/src/notifications/conversation-pairing.ts +25 -16
  811. package/src/notifications/decisions-store.ts +2 -2
  812. package/src/notifications/deliveries-store.ts +3 -3
  813. package/src/notifications/destination-resolver.ts +7 -23
  814. package/src/notifications/deterministic-checks.ts +2 -2
  815. package/src/notifications/emit-signal.ts +6 -12
  816. package/src/notifications/events-store.ts +2 -2
  817. package/src/notifications/home-feed-side-effect.ts +2 -2
  818. package/src/notifications/preferences-store.ts +2 -2
  819. package/src/notifications/signal.ts +1 -1
  820. package/src/oauth/oauth-store.ts +3 -3
  821. package/src/onboarding/__tests__/onboarding-events-store.test.ts +80 -0
  822. package/src/onboarding/checkin-event.test.ts +5 -2
  823. package/src/onboarding/checkin-event.ts +7 -1
  824. package/src/onboarding/onboarding-events-store.ts +161 -0
  825. package/src/permissions/checker.test.ts +319 -1
  826. package/src/permissions/checker.ts +52 -0
  827. package/src/permissions/types.ts +23 -2
  828. package/src/persistence/__tests__/db-init-migrations-ok.test.ts +20 -0
  829. package/src/persistence/__tests__/worker-control.test.ts +260 -0
  830. package/src/persistence/attachments-store.ts +1136 -0
  831. package/src/persistence/checkpoints.ts +65 -0
  832. package/src/persistence/compaction-ledger-store.ts +107 -0
  833. package/src/persistence/conversation-attention-store.ts +689 -0
  834. package/src/persistence/conversation-bootstrap.ts +59 -0
  835. package/src/persistence/conversation-crud.ts +3404 -0
  836. package/src/persistence/conversation-group-migration.test.ts +99 -0
  837. package/src/persistence/conversation-key-store.ts +274 -0
  838. package/src/persistence/conversation-queries.ts +768 -0
  839. package/src/persistence/conversation-row-batch-delete.ts +212 -0
  840. package/src/persistence/conversation-title-service.ts +755 -0
  841. package/src/persistence/db-async-query.ts +329 -0
  842. package/src/persistence/db-connection.ts +242 -0
  843. package/src/persistence/db-init.ts +282 -0
  844. package/src/persistence/db-maintenance.ts +158 -0
  845. package/src/persistence/embeddings/__tests__/embedding-identity.test.ts +156 -0
  846. package/src/persistence/embeddings/__tests__/messages-lexical-index.test.ts +307 -0
  847. package/src/persistence/embeddings/__tests__/reconcile-decision.test.ts +95 -0
  848. package/src/persistence/embeddings/embed.ts +87 -0
  849. package/src/persistence/embeddings/embedding-backend.test.ts +355 -0
  850. package/src/persistence/embeddings/embedding-backend.ts +968 -0
  851. package/src/persistence/embeddings/embedding-billing-breaker.ts +96 -0
  852. package/src/persistence/embeddings/embedding-cache.ts +139 -0
  853. package/src/persistence/embeddings/embedding-identity.ts +88 -0
  854. package/src/persistence/embeddings/embedding-local.ts +587 -0
  855. package/src/persistence/embeddings/embedding-ollama.ts +79 -0
  856. package/src/persistence/embeddings/embedding-runtime-manager.ts +573 -0
  857. package/src/persistence/embeddings/embedding-types.ts +97 -0
  858. package/src/persistence/embeddings/messages-lexical-index.ts +368 -0
  859. package/src/persistence/embeddings/qdrant-circuit-breaker.ts +119 -0
  860. package/src/persistence/embeddings/qdrant-client.test.ts +60 -0
  861. package/src/persistence/embeddings/qdrant-client.ts +842 -0
  862. package/src/persistence/embeddings/qdrant-manager.ts +450 -0
  863. package/src/persistence/embeddings/reconcile-decision.ts +53 -0
  864. package/src/persistence/embeddings/sparse-tokenize.ts +51 -0
  865. package/src/persistence/external-conversation-store.ts +315 -0
  866. package/src/persistence/fork-message-copy.ts +225 -0
  867. package/src/persistence/job-handlers/cleanup.ts +266 -0
  868. package/src/persistence/job-utils.ts +286 -0
  869. package/src/persistence/jobs-store.ts +866 -0
  870. package/src/persistence/jobs-worker.ts +842 -0
  871. package/src/persistence/llm-request-log-source-clickhouse.ts +525 -0
  872. package/src/persistence/llm-request-log-store.ts +740 -0
  873. package/src/persistence/llm-usage-store.ts +961 -0
  874. package/src/persistence/memory-lifecycle-hooks.test.ts +81 -0
  875. package/src/persistence/memory-lifecycle-hooks.ts +120 -0
  876. package/src/persistence/message-content.test.ts +35 -0
  877. package/src/persistence/migrations/126-backfill-guardian-principal-id.ts +300 -0
  878. package/src/persistence/migrations/140-backfill-usage-cache-accounting.ts +364 -0
  879. package/src/persistence/migrations/142-rename-verification-session-id-column.ts +55 -0
  880. package/src/persistence/migrations/182-oauth-providers-display-metadata.ts +22 -0
  881. package/src/persistence/migrations/196-strip-integration-prefix-from-provider-keys.ts +175 -0
  882. package/src/persistence/migrations/199-guardian-request-enrichment-columns.ts +50 -0
  883. package/src/persistence/migrations/202-memory-graph-tables.ts +128 -0
  884. package/src/persistence/migrations/203-drop-memory-items-tables.ts +53 -0
  885. package/src/persistence/migrations/206-scrub-corrupted-image-attachments.ts +121 -0
  886. package/src/persistence/migrations/209-strip-thinking-from-consolidated.ts +196 -0
  887. package/src/persistence/migrations/211-memory-recall-logs-query-context.ts +14 -0
  888. package/src/persistence/migrations/220-normalize-user-file-by-principal.ts +171 -0
  889. package/src/persistence/migrations/222-strip-placeholder-sentinels-from-messages.ts +179 -0
  890. package/src/persistence/migrations/236-tool-invocations-matched-rule-id.ts +23 -0
  891. package/src/persistence/migrations/243-provider-connections.ts +80 -0
  892. package/src/persistence/migrations/244-provider-connection-status-label.ts +29 -0
  893. package/src/persistence/migrations/246-backfill-provider-connection-label.ts +83 -0
  894. package/src/persistence/migrations/255-channel-inbound-delivery-attempts.ts +17 -0
  895. package/src/persistence/migrations/301-create-watchdog-events.ts +42 -0
  896. package/src/persistence/migrations/302-create-compaction-events.ts +107 -0
  897. package/src/persistence/migrations/303-add-conversation-creation-seq.ts +33 -0
  898. package/src/persistence/migrations/304-add-llm-usage-cron-run-id.ts +31 -0
  899. package/src/persistence/migrations/305-drop-contact-acl-columns.ts +49 -0
  900. package/src/persistence/migrations/306-rewrite-frontier-profile-pins.test.ts +110 -0
  901. package/src/persistence/migrations/306-rewrite-frontier-profile-pins.ts +65 -0
  902. package/src/persistence/migrations/307-acp-session-history-usage-columns.test.ts +124 -0
  903. package/src/persistence/migrations/307-acp-session-history-usage-columns.ts +36 -0
  904. package/src/persistence/migrations/308-acp-session-history-token-columns.test.ts +119 -0
  905. package/src/persistence/migrations/308-acp-session-history-token-columns.ts +32 -0
  906. package/src/persistence/migrations/309-drop-redundant-indexes.ts +60 -0
  907. package/src/persistence/migrations/310-llm-request-log-latency-breakdown.ts +37 -0
  908. package/src/persistence/migrations/311-create-subagents-table.ts +50 -0
  909. package/src/persistence/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +328 -0
  910. package/src/persistence/migrations/__tests__/222-strip-placeholder-sentinels-from-messages.test.ts +273 -0
  911. package/src/persistence/migrations/__tests__/244-provider-connection-status-label.test.ts +90 -0
  912. package/src/persistence/migrations/__tests__/246-backfill-provider-connection-label.test.ts +250 -0
  913. package/src/persistence/migrations/__tests__/run-migrations.test.ts +380 -0
  914. package/src/persistence/migrations/helpers/relocation.ts +230 -0
  915. package/src/persistence/migrations/validate-migration-state.ts +279 -0
  916. package/src/persistence/schedule-attribution-sql.ts +106 -0
  917. package/src/persistence/schema/acp.ts +51 -0
  918. package/src/persistence/schema/contacts.ts +110 -0
  919. package/src/persistence/schema/conversations.ts +235 -0
  920. package/src/persistence/schema/infrastructure.ts +389 -0
  921. package/src/persistence/steps.ts +1312 -0
  922. package/src/persistence/subagent-store.ts +122 -0
  923. package/src/persistence/worker-control.ts +277 -0
  924. package/src/platform/sync-identity.ts +24 -0
  925. package/src/playbooks/playbook-compiler.ts +2 -2
  926. package/src/plugin-api/event-hub-facade.ts +172 -0
  927. package/src/plugin-api/index.ts +14 -1
  928. package/src/plugin-api/types.ts +61 -24
  929. package/src/plugins/defaults/channel/injectors.ts +101 -0
  930. package/src/plugins/defaults/channel/package.json +14 -0
  931. package/src/plugins/defaults/compaction/compact.ts +2 -1
  932. package/src/plugins/defaults/compaction/context-overflow-reducer.ts +2 -1
  933. package/src/plugins/defaults/compaction/window-manager.ts +8 -7
  934. package/src/plugins/defaults/documents/injectors.ts +125 -0
  935. package/src/plugins/defaults/documents/package.json +14 -0
  936. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +6 -2
  937. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +10 -11
  938. package/src/plugins/defaults/history-repair/terminal.ts +1 -1
  939. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +23 -9
  940. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +2 -3
  941. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +7 -14
  942. package/src/plugins/defaults/image-fallback/src/vision-caption.ts +12 -4
  943. package/src/plugins/defaults/image-recovery/recover.ts +6 -3
  944. package/src/plugins/defaults/index.ts +235 -78
  945. package/src/plugins/defaults/injection-presence.ts +80 -0
  946. package/src/plugins/defaults/injector-order.ts +54 -0
  947. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +5 -2
  948. package/src/plugins/defaults/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  949. package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +41 -0
  950. package/src/plugins/defaults/memory/__tests__/bookmark-crud.test.ts +264 -0
  951. package/src/plugins/defaults/memory/__tests__/bookmark-schema.test.ts +181 -0
  952. package/src/plugins/defaults/memory/__tests__/clear-stale-processing-flags.test.ts +52 -0
  953. package/src/plugins/defaults/memory/__tests__/compaction-log-store-clickhouse.test.ts +432 -0
  954. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +843 -0
  955. package/src/plugins/defaults/memory/__tests__/conversation-row-batch-delete.test.ts +199 -0
  956. package/src/plugins/defaults/memory/__tests__/conversation-types.test.ts +36 -0
  957. package/src/plugins/defaults/memory/__tests__/db-async-query.test.ts +172 -0
  958. package/src/plugins/defaults/memory/__tests__/db-busy-timeout.test.ts +26 -0
  959. package/src/plugins/defaults/memory/__tests__/db-logs-attach.test.ts +113 -0
  960. package/src/plugins/defaults/memory/__tests__/db-maintenance.test.ts +238 -0
  961. package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +116 -0
  962. package/src/plugins/defaults/memory/__tests__/embedding-cache.test.ts +137 -0
  963. package/src/plugins/defaults/memory/__tests__/find-analysis-conversation.test.ts +197 -0
  964. package/src/plugins/defaults/memory/__tests__/find-most-recent-retrospective-for.test.ts +138 -0
  965. package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +232 -0
  966. package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +245 -0
  967. package/src/plugins/defaults/memory/__tests__/jobs-store-job-classes.test.ts +52 -0
  968. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +115 -0
  969. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +508 -0
  970. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +160 -0
  971. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +1568 -0
  972. package/src/plugins/defaults/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +549 -0
  973. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +195 -0
  974. package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +221 -0
  975. package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +275 -0
  976. package/src/plugins/defaults/memory/__tests__/memory-v3-selections-migration.test.ts +103 -0
  977. package/src/plugins/defaults/memory/__tests__/prompt-override.test.ts +192 -0
  978. package/src/plugins/defaults/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
  979. package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +130 -0
  980. package/src/plugins/defaults/memory/activation-session-store.ts +43 -0
  981. package/src/plugins/defaults/memory/anisotropy.ts +443 -0
  982. package/src/plugins/defaults/memory/context-search/agent-protocol.ts +428 -0
  983. package/src/plugins/defaults/memory/context-search/agent-runner.ts +1311 -0
  984. package/src/plugins/defaults/memory/context-search/search.ts +297 -0
  985. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +341 -0
  986. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +600 -0
  987. package/src/plugins/defaults/memory/context-search/sources/memory.ts +101 -0
  988. package/src/plugins/defaults/memory/context-search/types.ts +48 -0
  989. package/src/plugins/defaults/memory/filing-service.ts +430 -0
  990. package/src/plugins/defaults/memory/find-most-recent-retrospective-for.ts +66 -0
  991. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
  992. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +663 -0
  993. package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +321 -0
  994. package/src/plugins/defaults/memory/graph/bootstrap.test.ts +287 -0
  995. package/src/plugins/defaults/memory/graph/bootstrap.ts +544 -0
  996. package/src/plugins/defaults/memory/graph/capability-seed.ts +454 -0
  997. package/src/plugins/defaults/memory/graph/consolidation.ts +712 -0
  998. package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +1181 -0
  999. package/src/plugins/defaults/memory/graph/decay.ts +195 -0
  1000. package/src/plugins/defaults/memory/graph/extraction-job.ts +77 -0
  1001. package/src/plugins/defaults/memory/graph/extraction.ts +1462 -0
  1002. package/src/plugins/defaults/memory/graph/graph-memory-state-store.ts +50 -0
  1003. package/src/plugins/defaults/memory/graph/graph-search.test.ts +132 -0
  1004. package/src/plugins/defaults/memory/graph/graph-search.ts +294 -0
  1005. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +29 -0
  1006. package/src/plugins/defaults/memory/graph/injection.ts +469 -0
  1007. package/src/plugins/defaults/memory/graph/narrative.ts +267 -0
  1008. package/src/plugins/defaults/memory/graph/pattern-scan.ts +269 -0
  1009. package/src/plugins/defaults/memory/graph/retriever.test.ts +624 -0
  1010. package/src/plugins/defaults/memory/graph/retriever.ts +1357 -0
  1011. package/src/plugins/defaults/memory/graph/store.test.ts +1093 -0
  1012. package/src/plugins/defaults/memory/graph/store.ts +923 -0
  1013. package/src/plugins/defaults/memory/graph/tool-handlers.ts +195 -0
  1014. package/src/plugins/defaults/memory/graph/tools.ts +95 -0
  1015. package/src/plugins/defaults/memory/hooks/init.ts +26 -0
  1016. package/src/plugins/defaults/memory/hooks/post-compact.ts +115 -0
  1017. package/src/plugins/defaults/memory/hooks/shutdown.ts +14 -0
  1018. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +371 -0
  1019. package/src/plugins/defaults/memory/indexer.ts +409 -0
  1020. package/src/plugins/defaults/memory/injectors.ts +388 -0
  1021. package/src/plugins/defaults/memory/job-handlers/__tests__/backfill-lexical-index.test.ts +310 -0
  1022. package/src/plugins/defaults/memory/job-handlers/__tests__/index-message-lexical.test.ts +265 -0
  1023. package/src/plugins/defaults/memory/job-handlers/backfill-lexical-index.ts +108 -0
  1024. package/src/plugins/defaults/memory/job-handlers/backfill.ts +107 -0
  1025. package/src/plugins/defaults/memory/job-handlers/embedding.test.ts +236 -0
  1026. package/src/plugins/defaults/memory/job-handlers/embedding.ts +133 -0
  1027. package/src/plugins/defaults/memory/job-handlers/index-maintenance.ts +117 -0
  1028. package/src/plugins/defaults/memory/job-handlers/index-message-lexical.ts +106 -0
  1029. package/src/plugins/defaults/memory/job-handlers.ts +220 -0
  1030. package/src/plugins/defaults/memory/jobs/__tests__/embed-concept-page.test.ts +565 -0
  1031. package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +424 -0
  1032. package/src/plugins/defaults/memory/jobs/embed-pkb-file.test.ts +169 -0
  1033. package/src/plugins/defaults/memory/jobs/embed-pkb-file.ts +59 -0
  1034. package/src/plugins/defaults/memory/memory-recall-log-store.ts +172 -0
  1035. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +60 -0
  1036. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +131 -0
  1037. package/src/plugins/defaults/memory/memory-retrospective-fork-boundary.ts +94 -0
  1038. package/src/plugins/defaults/memory/memory-retrospective-job.ts +958 -0
  1039. package/src/plugins/defaults/memory/memory-retrospective-startup-cleanup.ts +264 -0
  1040. package/src/plugins/defaults/memory/memory-retrospective-state.ts +259 -0
  1041. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +91 -0
  1042. package/src/plugins/defaults/memory/memory-v2-activation-log-store.ts +216 -0
  1043. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +173 -0
  1044. package/src/plugins/defaults/memory/package.json +14 -0
  1045. package/src/plugins/defaults/memory/persistence-hooks.ts +119 -0
  1046. package/src/plugins/defaults/memory/pkb/autoinject.ts +61 -0
  1047. package/src/plugins/defaults/memory/pkb/context.ts +50 -0
  1048. package/src/plugins/defaults/memory/pkb/pkb-index.test.ts +371 -0
  1049. package/src/plugins/defaults/memory/pkb/pkb-index.ts +256 -0
  1050. package/src/plugins/defaults/memory/pkb/pkb-reconcile.test.ts +254 -0
  1051. package/src/plugins/defaults/memory/pkb/pkb-reconcile.ts +144 -0
  1052. package/src/plugins/defaults/memory/pkb/pkb-search.test.ts +589 -0
  1053. package/src/plugins/defaults/memory/pkb/pkb-search.ts +178 -0
  1054. package/src/plugins/defaults/memory/pkb/types.ts +67 -0
  1055. package/src/plugins/defaults/memory/rerank-local.ts +374 -0
  1056. package/src/plugins/defaults/memory/routes/__tests__/memory-v2-routes.test.ts +165 -0
  1057. package/src/plugins/defaults/memory/routes/__tests__/memory-v2-simulate-route.test.ts +347 -0
  1058. package/src/plugins/defaults/memory/routes/memory-eval-routes.ts +179 -0
  1059. package/src/plugins/defaults/memory/routes/memory-item-routes.test.ts +922 -0
  1060. package/src/plugins/defaults/memory/routes/memory-item-routes.ts +814 -0
  1061. package/src/plugins/defaults/memory/routes/memory-v2-routes.ts +850 -0
  1062. package/src/plugins/defaults/memory/routes/memory-v3-routes.ts +125 -0
  1063. package/src/plugins/defaults/memory/routes/messages-lexical-routes.ts +67 -0
  1064. package/src/plugins/defaults/memory/search/semantic.ts +241 -0
  1065. package/src/plugins/defaults/memory/segmenter.ts +76 -0
  1066. package/src/plugins/defaults/memory/startup.ts +234 -0
  1067. package/src/plugins/defaults/memory/tail-reinjection-strip.ts +65 -0
  1068. package/src/plugins/defaults/memory/task-memory-cleanup.ts +241 -0
  1069. package/src/plugins/defaults/memory/tools.test.ts +382 -0
  1070. package/src/plugins/defaults/memory/tools.ts +87 -0
  1071. package/src/plugins/defaults/memory/v2/__tests__/activation-store.test.ts +207 -0
  1072. package/src/plugins/defaults/memory/v2/__tests__/activation.test.ts +1273 -0
  1073. package/src/plugins/defaults/memory/v2/__tests__/backfill-jobs.test.ts +510 -0
  1074. package/src/plugins/defaults/memory/v2/__tests__/cli-command-store.test.ts +432 -0
  1075. package/src/plugins/defaults/memory/v2/__tests__/consolidation-job.test.ts +744 -0
  1076. package/src/plugins/defaults/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +205 -0
  1077. package/src/plugins/defaults/memory/v2/__tests__/frontmatter-sweep.test.ts +136 -0
  1078. package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +186 -0
  1079. package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +257 -0
  1080. package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +230 -0
  1081. package/src/plugins/defaults/memory/v2/__tests__/harness-runner.test.ts +135 -0
  1082. package/src/plugins/defaults/memory/v2/__tests__/injection-events.test.ts +318 -0
  1083. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +2308 -0
  1084. package/src/plugins/defaults/memory/v2/__tests__/memory-v2-startup.test.ts +86 -0
  1085. package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +940 -0
  1086. package/src/plugins/defaults/memory/v2/__tests__/page-index.test.ts +760 -0
  1087. package/src/plugins/defaults/memory/v2/__tests__/prompts-consolidation.test.ts +332 -0
  1088. package/src/plugins/defaults/memory/v2/__tests__/qdrant.test.ts +974 -0
  1089. package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +338 -0
  1090. package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +1198 -0
  1091. package/src/plugins/defaults/memory/v2/__tests__/sim.test.ts +704 -0
  1092. package/src/plugins/defaults/memory/v2/__tests__/skill-content.test.ts +115 -0
  1093. package/src/plugins/defaults/memory/v2/__tests__/skill-store.test.ts +986 -0
  1094. package/src/plugins/defaults/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
  1095. package/src/plugins/defaults/memory/v2/__tests__/static-context.test.ts +223 -0
  1096. package/src/plugins/defaults/memory/v2/__tests__/sweep-job.test.ts +530 -0
  1097. package/src/plugins/defaults/memory/v2/activation-store.ts +185 -0
  1098. package/src/plugins/defaults/memory/v2/activation.ts +560 -0
  1099. package/src/plugins/defaults/memory/v2/backfill-jobs.ts +296 -0
  1100. package/src/plugins/defaults/memory/v2/cli-command-store.ts +341 -0
  1101. package/src/plugins/defaults/memory/v2/consolidation-job.ts +550 -0
  1102. package/src/plugins/defaults/memory/v2/frontmatter-sweep.ts +98 -0
  1103. package/src/plugins/defaults/memory/v2/harness/compare.ts +57 -0
  1104. package/src/plugins/defaults/memory/v2/harness/oracle.ts +148 -0
  1105. package/src/plugins/defaults/memory/v2/harness/replay-input.ts +243 -0
  1106. package/src/plugins/defaults/memory/v2/harness/retriever.ts +74 -0
  1107. package/src/plugins/defaults/memory/v2/harness/router-retriever.ts +43 -0
  1108. package/src/plugins/defaults/memory/v2/injected-block-slugs.ts +79 -0
  1109. package/src/plugins/defaults/memory/v2/injection-events.ts +101 -0
  1110. package/src/plugins/defaults/memory/v2/injection.ts +932 -0
  1111. package/src/plugins/defaults/memory/v2/memory-v2-startup.test.ts +203 -0
  1112. package/src/plugins/defaults/memory/v2/memory-v2-startup.ts +378 -0
  1113. package/src/plugins/defaults/memory/v2/migration.ts +774 -0
  1114. package/src/plugins/defaults/memory/v2/page-index.ts +459 -0
  1115. package/src/plugins/defaults/memory/v2/page-store.ts +443 -0
  1116. package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +868 -0
  1117. package/src/plugins/defaults/memory/v2/prompts/router.ts +148 -0
  1118. package/src/plugins/defaults/memory/v2/qdrant.ts +939 -0
  1119. package/src/plugins/defaults/memory/v2/reranker.ts +184 -0
  1120. package/src/plugins/defaults/memory/v2/router.ts +602 -0
  1121. package/src/plugins/defaults/memory/v2/sim.ts +310 -0
  1122. package/src/plugins/defaults/memory/v2/skill-content.ts +61 -0
  1123. package/src/plugins/defaults/memory/v2/skill-store.ts +431 -0
  1124. package/src/plugins/defaults/memory/v2/sparse-bm25.ts +245 -0
  1125. package/src/plugins/defaults/memory/v2/static-context.ts +100 -0
  1126. package/src/plugins/defaults/memory/v2/sweep-job.ts +323 -0
  1127. package/src/plugins/defaults/memory/v3/__tests__/candidate-match.test.ts +316 -0
  1128. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +1163 -0
  1129. package/src/plugins/defaults/memory/v3/__tests__/dense.test.ts +211 -0
  1130. package/src/plugins/defaults/memory/v3/__tests__/edge.test.ts +247 -0
  1131. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +685 -0
  1132. package/src/plugins/defaults/memory/v3/__tests__/live-integration.test.ts +365 -0
  1133. package/src/plugins/defaults/memory/v3/__tests__/maintain-job.test.ts +1012 -0
  1134. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +977 -0
  1135. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +599 -0
  1136. package/src/plugins/defaults/memory/v3/__tests__/render-injection.test.ts +128 -0
  1137. package/src/plugins/defaults/memory/v3/__tests__/section-dense-store.test.ts +756 -0
  1138. package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +422 -0
  1139. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +501 -0
  1140. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +885 -0
  1141. package/src/plugins/defaults/memory/v3/__tests__/tuning-profile.test.ts +96 -0
  1142. package/src/plugins/defaults/memory/v3/candidate-match.ts +197 -0
  1143. package/src/plugins/defaults/memory/v3/capabilities.ts +88 -0
  1144. package/src/plugins/defaults/memory/v3/card.ts +160 -0
  1145. package/src/plugins/defaults/memory/v3/dense.ts +108 -0
  1146. package/src/plugins/defaults/memory/v3/edge.ts +252 -0
  1147. package/src/plugins/defaults/memory/v3/entity-lane.test.ts +124 -0
  1148. package/src/plugins/defaults/memory/v3/entity-lane.ts +113 -0
  1149. package/src/plugins/defaults/memory/v3/ever-injected-store.test.ts +307 -0
  1150. package/src/plugins/defaults/memory/v3/ever-injected-store.ts +278 -0
  1151. package/src/plugins/defaults/memory/v3/hot-set.test.ts +143 -0
  1152. package/src/plugins/defaults/memory/v3/hot-set.ts +85 -0
  1153. package/src/plugins/defaults/memory/v3/injector.ts +423 -0
  1154. package/src/plugins/defaults/memory/v3/learned-edges.test.ts +169 -0
  1155. package/src/plugins/defaults/memory/v3/learned-edges.ts +178 -0
  1156. package/src/plugins/defaults/memory/v3/maintain-job.ts +988 -0
  1157. package/src/plugins/defaults/memory/v3/orchestrate.ts +483 -0
  1158. package/src/plugins/defaults/memory/v3/page-content.ts +102 -0
  1159. package/src/plugins/defaults/memory/v3/pool-select.test.ts +147 -0
  1160. package/src/plugins/defaults/memory/v3/pool-select.ts +586 -0
  1161. package/src/plugins/defaults/memory/v3/prune.test.ts +760 -0
  1162. package/src/plugins/defaults/memory/v3/prune.ts +469 -0
  1163. package/src/plugins/defaults/memory/v3/render-injection.ts +92 -0
  1164. package/src/plugins/defaults/memory/v3/section-dense-store.ts +471 -0
  1165. package/src/plugins/defaults/memory/v3/section-needle.ts +215 -0
  1166. package/src/plugins/defaults/memory/v3/selection-log-store.ts +277 -0
  1167. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +656 -0
  1168. package/src/plugins/defaults/memory/v3/tuning-profile.ts +91 -0
  1169. package/src/plugins/defaults/memory/v3/types.ts +130 -0
  1170. package/src/plugins/defaults/memory/v3-eval/eval-packets.ts +731 -0
  1171. package/src/plugins/defaults/session/injectors.ts +84 -0
  1172. package/src/plugins/defaults/session/package.json +14 -0
  1173. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +6 -2
  1174. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +39 -45
  1175. package/src/plugins/defaults/title-generate/hooks/stop.ts +4 -5
  1176. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +3 -3
  1177. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +5 -3
  1178. package/src/plugins/defaults/turn-context/injectors.ts +53 -0
  1179. package/src/plugins/defaults/turn-context/package.json +14 -0
  1180. package/src/plugins/defaults/turn-context/unified-turn-context.ts +235 -0
  1181. package/src/plugins/defaults/workspace/injectors.ts +295 -0
  1182. package/src/plugins/defaults/workspace/package.json +14 -0
  1183. package/src/plugins/disabled-state.ts +31 -0
  1184. package/src/plugins/injector-registry.ts +127 -0
  1185. package/src/plugins/job-handler-registry.ts +94 -0
  1186. package/src/plugins/mtime-cache.ts +27 -47
  1187. package/src/plugins/pipeline.ts +1 -1
  1188. package/src/plugins/registry.ts +40 -132
  1189. package/src/plugins/types.ts +44 -1
  1190. package/src/prompts/persona-resolver.ts +43 -11
  1191. package/src/prompts/system-prompt.ts +1 -1
  1192. package/src/prompts/templates/BOOTSTRAP.md +16 -119
  1193. package/src/prompts/templates/SOUL.md +4 -0
  1194. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  1195. package/src/providers/__tests__/dispatch-connection-routing.test.ts +1 -1
  1196. package/src/providers/__tests__/inference.test.ts +6 -6
  1197. package/src/providers/__tests__/placeholder-sentinels.test.ts +80 -0
  1198. package/src/providers/__tests__/retry-callsite.test.ts +31 -3
  1199. package/src/providers/__tests__/satellite-connection-routing.test.ts +1 -1
  1200. package/src/providers/anthropic/client.ts +23 -24
  1201. package/src/providers/call-site-routing.ts +42 -1
  1202. package/src/providers/connection-resolution.ts +7 -3
  1203. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +7 -8
  1204. package/src/providers/inference/__tests__/base-url-security.test.ts +8 -8
  1205. package/src/providers/inference/__tests__/connections-label.test.ts +5 -5
  1206. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +4 -4
  1207. package/src/providers/inference/backfill.ts +9 -5
  1208. package/src/providers/inference/connections.ts +2 -2
  1209. package/src/providers/inference/resolve-auth.ts +3 -3
  1210. package/src/providers/model-catalog.ts +38 -0
  1211. package/src/providers/openai/chat-completions-provider.ts +14 -1
  1212. package/src/providers/openai/responses-provider.ts +18 -0
  1213. package/src/providers/placeholder-sentinels.ts +50 -15
  1214. package/src/providers/provider-send-message.ts +7 -1
  1215. package/src/providers/ratelimit.ts +6 -0
  1216. package/src/providers/registry.ts +3 -1
  1217. package/src/providers/retry.ts +22 -0
  1218. package/src/providers/stop-reasons.ts +32 -0
  1219. package/src/providers/tool-progress-events.test.ts +50 -0
  1220. package/src/providers/tool-progress-events.ts +99 -0
  1221. package/src/providers/types.ts +14 -1
  1222. package/src/providers/usage-tracking.ts +7 -1
  1223. package/src/runtime/__tests__/agent-wake.test.ts +100 -29
  1224. package/src/runtime/__tests__/background-job-runner.test.ts +19 -2
  1225. package/src/runtime/__tests__/channel-verification-service.test.ts +9 -5
  1226. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +37 -32
  1227. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +3 -3
  1228. package/src/runtime/__tests__/local-principal-trust.test.ts +16 -18
  1229. package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
  1230. package/src/runtime/__tests__/pre-first-message-gate.test.ts +1 -1
  1231. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +115 -168
  1232. package/src/runtime/access-request-helper.ts +4 -5
  1233. package/src/runtime/actor-trust-resolver.ts +44 -17
  1234. package/src/runtime/agent-wake.ts +69 -12
  1235. package/src/runtime/anchored-guardian.test.ts +7 -54
  1236. package/src/runtime/anchored-guardian.ts +4 -53
  1237. package/src/runtime/assistant-event-hub.ts +1 -1
  1238. package/src/runtime/assistant-event.ts +86 -12
  1239. package/src/runtime/assistant-stream-state.ts +12 -74
  1240. package/src/runtime/background-job-runner.ts +30 -10
  1241. package/src/runtime/channel-reply-delivery.ts +6 -11
  1242. package/src/runtime/channel-retry-sweep.ts +23 -3
  1243. package/src/runtime/channel-verification-service.ts +9 -9
  1244. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  1245. package/src/runtime/finalize-event-delivery.ts +2 -2
  1246. package/src/runtime/guardian-action-service.ts +1 -2
  1247. package/src/runtime/guardian-reply-router.ts +1 -1
  1248. package/src/runtime/guardian-vellum-migration.ts +18 -16
  1249. package/src/runtime/http-server.ts +45 -1
  1250. package/src/runtime/invite-redemption-service.ts +36 -13
  1251. package/src/runtime/invite-service.ts +6 -4
  1252. package/src/runtime/local-actor-identity.test.ts +108 -0
  1253. package/src/runtime/local-actor-identity.ts +27 -20
  1254. package/src/runtime/member-verdict-cache.ts +0 -0
  1255. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +28 -0
  1256. package/src/runtime/migrations/vbundle-streaming-importer.ts +1 -1
  1257. package/src/runtime/pre-first-message-gate.ts +1 -1
  1258. package/src/runtime/routes/__tests__/acp-routes.test.ts +74 -8
  1259. package/src/runtime/routes/__tests__/backup-routes.test.ts +1 -1
  1260. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +3 -3
  1261. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +17 -23
  1262. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +3 -3
  1263. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +5 -5
  1264. package/src/runtime/routes/__tests__/contact-routes.test.ts +188 -14
  1265. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +5 -5
  1266. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +5 -5
  1267. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +3 -3
  1268. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +12 -12
  1269. package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +4 -4
  1270. package/src/runtime/routes/__tests__/global-search-routes.test.ts +1 -2
  1271. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +3 -3
  1272. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +3 -3
  1273. package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +21 -7
  1274. package/src/runtime/routes/__tests__/memory-worker-routes.test.ts +306 -0
  1275. package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +70 -0
  1276. package/src/runtime/routes/__tests__/plugins-routes.test.ts +395 -26
  1277. package/src/runtime/routes/__tests__/ps-routes.test.ts +114 -0
  1278. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +6 -6
  1279. package/src/runtime/routes/__tests__/schedule-routes-create.test.ts +72 -0
  1280. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +4 -4
  1281. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +3 -2
  1282. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -1
  1283. package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +125 -0
  1284. package/src/runtime/routes/acp-routes-event-log.test.ts +283 -0
  1285. package/src/runtime/routes/acp-routes-list.test.ts +24 -25
  1286. package/src/runtime/routes/acp-routes.test.ts +4 -4
  1287. package/src/runtime/routes/acp-routes.ts +31 -4
  1288. package/src/runtime/routes/app-management-routes.ts +9 -9
  1289. package/src/runtime/routes/app-routes.ts +3 -7
  1290. package/src/runtime/routes/approval-routes.ts +2 -4
  1291. package/src/runtime/routes/attachment-routes.ts +2 -2
  1292. package/src/runtime/routes/audit-routes.ts +2 -3
  1293. package/src/runtime/routes/background-tool-routes.ts +34 -5
  1294. package/src/runtime/routes/background-wake-routes.ts +19 -5
  1295. package/src/runtime/routes/backup-routes.ts +1 -1
  1296. package/src/runtime/routes/bookmark-routes.ts +3 -3
  1297. package/src/runtime/routes/brain-graph-routes.ts +2 -2
  1298. package/src/runtime/routes/btw-routes.ts +1 -1
  1299. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +1 -1
  1300. package/src/runtime/routes/channel-delivery-routes.ts +1 -1
  1301. package/src/runtime/routes/channel-readiness-routes.ts +7 -3
  1302. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +3 -8
  1303. package/src/runtime/routes/consolidation-routes.ts +6 -6
  1304. package/src/runtime/routes/contact-routes.ts +98 -32
  1305. package/src/runtime/routes/conversation-attention-routes.ts +2 -2
  1306. package/src/runtime/routes/conversation-cli-routes.ts +32 -7
  1307. package/src/runtime/routes/conversation-compaction-routes.ts +4 -4
  1308. package/src/runtime/routes/conversation-list-routes.ts +9 -37
  1309. package/src/runtime/routes/conversation-management-routes.ts +9 -46
  1310. package/src/runtime/routes/conversation-query-routes.ts +35 -11
  1311. package/src/runtime/routes/conversation-routes.ts +174 -23
  1312. package/src/runtime/routes/conversation-starter-routes.ts +11 -5
  1313. package/src/runtime/routes/conversations-import-routes.ts +10 -6
  1314. package/src/runtime/routes/debug-routes.ts +3 -3
  1315. package/src/runtime/routes/defer-routes.ts +4 -4
  1316. package/src/runtime/routes/documents-routes.ts +1 -1
  1317. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -1
  1318. package/src/runtime/routes/events-routes.ts +4 -4
  1319. package/src/runtime/routes/filing-routes.ts +1 -1
  1320. package/src/runtime/routes/global-search-routes.ts +4 -4
  1321. package/src/runtime/routes/group-routes.ts +1 -1
  1322. package/src/runtime/routes/guardian-action-routes.ts +1 -1
  1323. package/src/runtime/routes/heartbeat-routes.ts +2 -2
  1324. package/src/runtime/routes/home-feed-routes.ts +10 -5
  1325. package/src/runtime/routes/identity-routes.ts +33 -14
  1326. package/src/runtime/routes/inbound-conversation.ts +3 -3
  1327. package/src/runtime/routes/inbound-message-handler.ts +25 -25
  1328. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +8 -4
  1329. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +13 -16
  1330. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +5 -5
  1331. package/src/runtime/routes/inbound-stages/background-dispatch.ts +15 -19
  1332. package/src/runtime/routes/inbound-stages/edit-intercept.ts +12 -12
  1333. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +2 -2
  1334. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +1 -1
  1335. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +1 -1
  1336. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +27 -8
  1337. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +5 -2
  1338. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  1339. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -1
  1340. package/src/runtime/routes/index.ts +8 -4
  1341. package/src/runtime/routes/inference-profile-session-handler.ts +2 -2
  1342. package/src/runtime/routes/inference-profile-session-reaper.ts +1 -1
  1343. package/src/runtime/routes/inference-provider-connection-routes.ts +5 -2
  1344. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +3 -4
  1345. package/src/runtime/routes/integrations/slack/share.ts +1 -1
  1346. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  1347. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +1 -1
  1348. package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
  1349. package/src/runtime/routes/log-export-routes.ts +2 -2
  1350. package/src/runtime/routes/memory-worker-routes.ts +227 -0
  1351. package/src/runtime/routes/migration-rollback-routes.ts +11 -12
  1352. package/src/runtime/routes/migration-routes.ts +211 -10
  1353. package/src/runtime/routes/notification-routes.ts +2 -2
  1354. package/src/runtime/routes/platform-routes.ts +3 -0
  1355. package/src/runtime/routes/playground/helpers.ts +3 -3
  1356. package/src/runtime/routes/plugins-routes.ts +180 -7
  1357. package/src/runtime/routes/ps-routes.ts +37 -66
  1358. package/src/runtime/routes/publish-routes.ts +3 -3
  1359. package/src/runtime/routes/rename-conversation-routes.ts +1 -1
  1360. package/src/runtime/routes/retrospective-routes.ts +5 -5
  1361. package/src/runtime/routes/schedule-routes.ts +88 -45
  1362. package/src/runtime/routes/secret-routes.ts +11 -10
  1363. package/src/runtime/routes/sequence-routes.ts +1 -1
  1364. package/src/runtime/routes/settings-routes.ts +26 -12
  1365. package/src/runtime/routes/skills-routes.ts +11 -1
  1366. package/src/runtime/routes/slack-channel-routes.ts +4 -4
  1367. package/src/runtime/routes/subagents-routes.ts +2 -2
  1368. package/src/runtime/routes/surface-conversation-resolver.ts +1 -1
  1369. package/src/runtime/routes/task-routes.ts +18 -29
  1370. package/src/runtime/routes/telemetry-routes.ts +1 -1
  1371. package/src/runtime/routes/trace-event-routes.ts +1 -1
  1372. package/src/runtime/routes/types.ts +7 -0
  1373. package/src/runtime/routes/usage-routes.ts +2 -2
  1374. package/src/runtime/routes/wake-conversation-routes.ts +44 -8
  1375. package/src/runtime/routes/wipe-conversation-routes.ts +3 -3
  1376. package/src/runtime/routes/work-items-routes.test.ts +1 -1
  1377. package/src/runtime/routes/work-items-routes.ts +1 -1
  1378. package/src/runtime/runtime-mode.ts +5 -7
  1379. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -2
  1380. package/src/runtime/services/__tests__/auto-analysis-enqueue.test.ts +362 -0
  1381. package/src/runtime/services/__tests__/auto-analysis-guard.test.ts +57 -0
  1382. package/src/runtime/services/__tests__/conversation-analyze-job.test.ts +178 -0
  1383. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -1
  1384. package/src/runtime/services/analyze-conversation.ts +22 -13
  1385. package/src/runtime/services/auto-analysis-enqueue.ts +130 -0
  1386. package/src/runtime/services/auto-analysis-guard.ts +13 -0
  1387. package/src/runtime/services/conversation-analyze-job.ts +53 -0
  1388. package/src/runtime/services/conversation-serializer.ts +10 -53
  1389. package/src/runtime/tool-grant-request-helper.ts +1 -1
  1390. package/src/runtime/trust-verdict-consumer.ts +36 -41
  1391. package/src/schedule/__tests__/run-script.test.ts +21 -0
  1392. package/src/schedule/retry-policy.ts +8 -8
  1393. package/src/schedule/run-script.ts +16 -4
  1394. package/src/schedule/schedule-recovery.ts +8 -5
  1395. package/src/schedule/schedule-store.ts +270 -162
  1396. package/src/schedule/schedule-usage-store.ts +2 -2
  1397. package/src/schedule/scheduler-types.ts +6 -0
  1398. package/src/schedule/scheduler.ts +218 -121
  1399. package/src/security/auth-fallback-events-store.ts +94 -0
  1400. package/src/security/redaction.ts +18 -14
  1401. package/src/sequence/engine.ts +1 -1
  1402. package/src/sequence/store.ts +3 -3
  1403. package/src/services/published-app-updater.ts +2 -2
  1404. package/src/signals/cancel.ts +1 -1
  1405. package/src/signals/user-message.ts +2 -2
  1406. package/src/skills/__tests__/categories-cache.test.ts +38 -0
  1407. package/src/skills/catalog-install.ts +2 -1
  1408. package/src/skills/categories-cache.ts +40 -5
  1409. package/src/skills/clawhub.ts +1 -0
  1410. package/src/skills/install-meta.ts +44 -0
  1411. package/src/skills/managed-store.ts +120 -3
  1412. package/src/skills/skillssh-registry.ts +1 -0
  1413. package/src/skills/version-hash.ts +20 -0
  1414. package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
  1415. package/src/subagent/__tests__/consult-transcript.test.ts +184 -0
  1416. package/src/subagent/consult-prompt.ts +57 -0
  1417. package/src/subagent/consult-transcript.ts +86 -0
  1418. package/src/subagent/index.ts +1 -1
  1419. package/src/subagent/manager.ts +502 -72
  1420. package/src/subagent/types.ts +14 -3
  1421. package/src/subagent/validate-allowlists.ts +65 -0
  1422. package/src/tasks/task-compiler.ts +2 -2
  1423. package/src/tasks/task-runner.ts +3 -3
  1424. package/src/tasks/task-store.ts +2 -2
  1425. package/src/telemetry/__tests__/watchdog-events-store.test.ts +161 -0
  1426. package/src/telemetry/skill-loaded-events-store.test.ts +152 -0
  1427. package/src/telemetry/skill-loaded-events-store.ts +95 -0
  1428. package/src/telemetry/tool-executed-events-store.test.ts +219 -0
  1429. package/src/telemetry/tool-executed-events-store.ts +102 -0
  1430. package/src/telemetry/tool-usage-store.ts +114 -0
  1431. package/src/telemetry/trace-event-store.ts +133 -0
  1432. package/src/telemetry/turn-events-store.ts +173 -0
  1433. package/src/telemetry/turn-trace-store.test.ts +736 -0
  1434. package/src/telemetry/turn-trace-store.ts +364 -0
  1435. package/src/telemetry/usage-telemetry-reporter.test.ts +12 -12
  1436. package/src/telemetry/usage-telemetry-reporter.ts +46 -17
  1437. package/src/telemetry/watchdog-events-store.ts +87 -0
  1438. package/src/tools/__tests__/run-standalone.test.ts +19 -0
  1439. package/src/tools/acp/list-agents.test.ts +0 -6
  1440. package/src/tools/acp/spawn.test.ts +18 -7
  1441. package/src/tools/acp/spawn.ts +1 -0
  1442. package/src/tools/apps/executors.ts +2 -2
  1443. package/src/tools/apps/resolve-app-id.ts +1 -1
  1444. package/src/tools/background-tool-registry.ts +61 -0
  1445. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +3 -3
  1446. package/src/tools/document/document-comment-tool.test.ts +2 -2
  1447. package/src/tools/filesystem/write.ts +5 -4
  1448. package/src/tools/host-terminal/host-shell.test.ts +397 -0
  1449. package/src/tools/host-terminal/host-shell.ts +203 -14
  1450. package/src/tools/policy-context.ts +21 -0
  1451. package/src/tools/registry.ts +41 -4
  1452. package/src/tools/run-standalone.ts +101 -0
  1453. package/src/tools/schedule/create.ts +2 -2
  1454. package/src/tools/schedule/delete.ts +1 -1
  1455. package/src/tools/schedule/update.ts +1 -1
  1456. package/src/tools/skills/find-similar-skills.test.ts +277 -0
  1457. package/src/tools/skills/find-similar-skills.ts +120 -0
  1458. package/src/tools/skills/scaffold-managed.ts +123 -4
  1459. package/src/tools/skills/skill-script-runner.ts +17 -0
  1460. package/src/tools/subagent/consult-deadline.ts +49 -0
  1461. package/src/tools/subagent/read.ts +1 -1
  1462. package/src/tools/subagent/spawn.ts +234 -5
  1463. package/src/tools/terminal/safe-env.ts +1 -0
  1464. package/src/tools/terminal/shell.test.ts +177 -0
  1465. package/src/tools/terminal/shell.ts +98 -4
  1466. package/src/tools/tool-approval-handler.ts +1 -1
  1467. package/src/tools/tool-manifest.ts +1 -1
  1468. package/src/tools/tool-types.ts +445 -0
  1469. package/src/tools/types.ts +28 -17
  1470. package/src/usage/types.ts +7 -0
  1471. package/src/util/__tests__/process-tree.test.ts +123 -0
  1472. package/src/util/device-id.ts +52 -12
  1473. package/src/util/logger.ts +9 -0
  1474. package/src/util/process-tree.ts +203 -0
  1475. package/src/util/sqlite-retry.ts +90 -0
  1476. package/src/watcher/__tests__/telemetry.test.ts +2 -2
  1477. package/src/watcher/telemetry.ts +2 -2
  1478. package/src/watcher/watcher-store.ts +3 -3
  1479. package/src/work-items/work-item-store.ts +2 -2
  1480. package/src/workflows/engine-integration.test.ts +2 -2
  1481. package/src/workflows/engine.test.ts +2 -2
  1482. package/src/workflows/fanout-load.test.ts +2 -2
  1483. package/src/workflows/journal-store.test.ts +3 -3
  1484. package/src/workflows/journal-store.ts +1 -1
  1485. package/src/workflows/leaf-runner.test.ts +9 -6
  1486. package/src/workflows/leaf-runner.ts +1 -1
  1487. package/src/workspace/heartbeat-service.ts +29 -0
  1488. package/src/workspace/migrations/011-backfill-installation-id.ts +1 -1
  1489. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +5 -2
  1490. package/src/workspace/migrations/031-drop-user-md.ts +258 -148
  1491. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +1 -1
  1492. package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +1 -1
  1493. package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
  1494. package/src/workspace/migrations/113-swap-balanced-profile-to-glm-5p2.ts +101 -0
  1495. package/src/workspace/migrations/114-swap-quality-profile-to-opus.ts +83 -0
  1496. package/src/workspace/migrations/115-drop-frontier-profile.ts +129 -0
  1497. package/src/workspace/migrations/116-rename-memory-plugin-disabled-sentinel.ts +53 -0
  1498. package/src/workspace/migrations/117-normalize-stale-lean-memory-v3-defaults.ts +116 -0
  1499. package/src/workspace/migrations/118-seed-now-md.ts +64 -0
  1500. package/src/workspace/migrations/__tests__/backfill-v3-leaves.test.ts +5 -2
  1501. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +4 -4
  1502. package/src/workspace/migrations/registry.ts +14 -0
  1503. package/drizzle/0000_dizzy_maggott.sql +0 -301
  1504. package/drizzle/meta/0000_snapshot.json +0 -1933
  1505. package/drizzle/meta/_journal.json +0 -13
  1506. package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +0 -887
  1507. package/node_modules/@vellumai/skill-host-contracts/bun.lock +0 -24
  1508. package/node_modules/@vellumai/skill-host-contracts/package.json +0 -18
  1509. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -97
  1510. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +0 -1349
  1511. package/node_modules/@vellumai/skill-host-contracts/src/index.ts +0 -6
  1512. package/node_modules/@vellumai/skill-host-contracts/src/runtime-mode.ts +0 -11
  1513. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +0 -32
  1514. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +0 -333
  1515. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +0 -446
  1516. package/node_modules/@vellumai/skill-host-contracts/tsconfig.json +0 -20
  1517. package/node_modules/@vellumai/skill-host-contracts/tsconfig.test.json +0 -12
  1518. package/src/__tests__/config-managed-gemini-defaults.test.ts +0 -277
  1519. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -203
  1520. package/src/__tests__/slack-block-formatting.test.ts +0 -210
  1521. package/src/background-wake/runtime-registry.ts +0 -24
  1522. package/src/contacts/contact-events.ts +0 -21
  1523. package/src/daemon/__tests__/daemon-skill-host.test.ts +0 -273
  1524. package/src/daemon/__tests__/meet-host-supervisor.test.ts +0 -587
  1525. package/src/daemon/__tests__/meet-manifest-loader.test.ts +0 -476
  1526. package/src/daemon/daemon-skill-host.ts +0 -262
  1527. package/src/daemon/meet-host-startup.ts +0 -51
  1528. package/src/daemon/meet-host-supervisor.ts +0 -782
  1529. package/src/daemon/meet-manifest-loader.ts +0 -396
  1530. package/src/daemon/memory-v2-startup.test.ts +0 -203
  1531. package/src/daemon/memory-v2-startup.ts +0 -345
  1532. package/src/daemon/message-types/meet.ts +0 -143
  1533. package/src/daemon/server.ts +0 -423
  1534. package/src/daemon/shutdown-registry.ts +0 -40
  1535. package/src/filing/filing-service.ts +0 -429
  1536. package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -253
  1537. package/src/ipc/__tests__/skill-server.test.ts +0 -182
  1538. package/src/ipc/skill-ipc-types.ts +0 -54
  1539. package/src/ipc/skill-routes/__tests__/config.test.ts +0 -146
  1540. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +0 -448
  1541. package/src/ipc/skill-routes/__tests__/identity.test.ts +0 -62
  1542. package/src/ipc/skill-routes/__tests__/log.test.ts +0 -133
  1543. package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -203
  1544. package/src/ipc/skill-routes/__tests__/platform.test.ts +0 -111
  1545. package/src/ipc/skill-routes/__tests__/providers.test.ts +0 -265
  1546. package/src/ipc/skill-routes/__tests__/registries.test.ts +0 -400
  1547. package/src/ipc/skill-routes/config.ts +0 -47
  1548. package/src/ipc/skill-routes/events.ts +0 -147
  1549. package/src/ipc/skill-routes/identity.ts +0 -21
  1550. package/src/ipc/skill-routes/index.ts +0 -37
  1551. package/src/ipc/skill-routes/log.ts +0 -40
  1552. package/src/ipc/skill-routes/memory.ts +0 -92
  1553. package/src/ipc/skill-routes/platform.ts +0 -39
  1554. package/src/ipc/skill-routes/providers.ts +0 -162
  1555. package/src/ipc/skill-routes/registries.ts +0 -340
  1556. package/src/ipc/skill-server.ts +0 -795
  1557. package/src/ipc/skill-socket-path.ts +0 -20
  1558. package/src/memory/__tests__/301-create-watchdog-events.test.ts +0 -110
  1559. package/src/memory/__tests__/activation-session-store.test.ts +0 -41
  1560. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +0 -359
  1561. package/src/memory/__tests__/auto-analysis-guard.test.ts +0 -57
  1562. package/src/memory/__tests__/bookmark-crud.test.ts +0 -264
  1563. package/src/memory/__tests__/bookmark-schema.test.ts +0 -181
  1564. package/src/memory/__tests__/compaction-log-store-clickhouse.test.ts +0 -432
  1565. package/src/memory/__tests__/conversation-analyze-job.test.ts +0 -178
  1566. package/src/memory/__tests__/conversation-group-migration.test.ts +0 -99
  1567. package/src/memory/__tests__/conversation-queries.test.ts +0 -843
  1568. package/src/memory/__tests__/conversation-types.test.ts +0 -36
  1569. package/src/memory/__tests__/db-async-query.test.ts +0 -165
  1570. package/src/memory/__tests__/db-logs-attach.test.ts +0 -110
  1571. package/src/memory/__tests__/db-maintenance.test.ts +0 -235
  1572. package/src/memory/__tests__/db-memory-attach.test.ts +0 -113
  1573. package/src/memory/__tests__/find-analysis-conversation.test.ts +0 -197
  1574. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +0 -140
  1575. package/src/memory/__tests__/fork-message-copy.test.ts +0 -232
  1576. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +0 -241
  1577. package/src/memory/__tests__/jobs-store-job-classes.test.ts +0 -52
  1578. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +0 -113
  1579. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +0 -505
  1580. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +0 -160
  1581. package/src/memory/__tests__/memory-retrospective-job.test.ts +0 -1443
  1582. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +0 -544
  1583. package/src/memory/__tests__/memory-retrospective-state.test.ts +0 -195
  1584. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -221
  1585. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +0 -275
  1586. package/src/memory/__tests__/memory-v3-selections-migration.test.ts +0 -103
  1587. package/src/memory/__tests__/message-content.test.ts +0 -35
  1588. package/src/memory/__tests__/onboarding-events-store.test.ts +0 -80
  1589. package/src/memory/__tests__/prompt-override.test.ts +0 -192
  1590. package/src/memory/__tests__/qdrant-client-sentinel.test.ts +0 -49
  1591. package/src/memory/__tests__/table-relocation.test.ts +0 -129
  1592. package/src/memory/__tests__/watchdog-events-store.test.ts +0 -161
  1593. package/src/memory/activation-session-store.ts +0 -43
  1594. package/src/memory/anisotropy.ts +0 -443
  1595. package/src/memory/app-store.ts +0 -1058
  1596. package/src/memory/attachments-store.ts +0 -1136
  1597. package/src/memory/auth-fallback-events-store.ts +0 -94
  1598. package/src/memory/auto-analysis-enqueue.ts +0 -127
  1599. package/src/memory/auto-analysis-guard.ts +0 -17
  1600. package/src/memory/canonical-guardian-store.ts +0 -891
  1601. package/src/memory/channel-verification-sessions.ts +0 -557
  1602. package/src/memory/checkpoints.ts +0 -65
  1603. package/src/memory/context-search/agent-protocol.ts +0 -428
  1604. package/src/memory/context-search/agent-runner.ts +0 -1310
  1605. package/src/memory/context-search/search.ts +0 -297
  1606. package/src/memory/context-search/sources/conversations.ts +0 -341
  1607. package/src/memory/context-search/sources/memory-v2.ts +0 -600
  1608. package/src/memory/context-search/sources/memory.ts +0 -101
  1609. package/src/memory/context-search/types.ts +0 -48
  1610. package/src/memory/conversation-analyze-job.ts +0 -53
  1611. package/src/memory/conversation-attention-store.ts +0 -689
  1612. package/src/memory/conversation-bootstrap.ts +0 -50
  1613. package/src/memory/conversation-crud.ts +0 -3361
  1614. package/src/memory/conversation-key-store.ts +0 -274
  1615. package/src/memory/conversation-queries.ts +0 -768
  1616. package/src/memory/conversation-starter-checkpoints.ts +0 -64
  1617. package/src/memory/conversation-title-service.ts +0 -606
  1618. package/src/memory/db-async-query.ts +0 -298
  1619. package/src/memory/db-connection.ts +0 -228
  1620. package/src/memory/db-init.ts +0 -265
  1621. package/src/memory/db-maintenance.ts +0 -154
  1622. package/src/memory/embed.ts +0 -73
  1623. package/src/memory/embedding-backend.test.ts +0 -204
  1624. package/src/memory/embedding-backend.ts +0 -855
  1625. package/src/memory/embedding-billing-breaker.ts +0 -96
  1626. package/src/memory/embedding-local.ts +0 -587
  1627. package/src/memory/embedding-ollama.ts +0 -79
  1628. package/src/memory/embedding-runtime-manager.ts +0 -573
  1629. package/src/memory/embedding-types.ts +0 -90
  1630. package/src/memory/external-conversation-store.ts +0 -315
  1631. package/src/memory/fork-message-copy.ts +0 -170
  1632. package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +0 -119
  1633. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +0 -655
  1634. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +0 -321
  1635. package/src/memory/graph/bootstrap.test.ts +0 -280
  1636. package/src/memory/graph/bootstrap.ts +0 -532
  1637. package/src/memory/graph/capability-seed.ts +0 -451
  1638. package/src/memory/graph/consolidation.ts +0 -712
  1639. package/src/memory/graph/conversation-graph-memory.ts +0 -1185
  1640. package/src/memory/graph/decay.ts +0 -195
  1641. package/src/memory/graph/extraction-job.ts +0 -74
  1642. package/src/memory/graph/extraction.ts +0 -1462
  1643. package/src/memory/graph/graph-memory-state-store.ts +0 -50
  1644. package/src/memory/graph/graph-search.test.ts +0 -129
  1645. package/src/memory/graph/graph-search.ts +0 -292
  1646. package/src/memory/graph/image-ref-utils.ts +0 -29
  1647. package/src/memory/graph/injection.ts +0 -469
  1648. package/src/memory/graph/narrative.ts +0 -267
  1649. package/src/memory/graph/pattern-scan.ts +0 -269
  1650. package/src/memory/graph/retriever.test.ts +0 -624
  1651. package/src/memory/graph/retriever.ts +0 -1356
  1652. package/src/memory/graph/store.test.ts +0 -1093
  1653. package/src/memory/graph/store.ts +0 -923
  1654. package/src/memory/graph/tool-handlers.ts +0 -195
  1655. package/src/memory/graph/tools.ts +0 -95
  1656. package/src/memory/guardian-rate-limits.ts +0 -191
  1657. package/src/memory/indexer.ts +0 -406
  1658. package/src/memory/job-handlers/backfill.ts +0 -104
  1659. package/src/memory/job-handlers/cleanup.ts +0 -237
  1660. package/src/memory/job-handlers/conversation-starters.ts +0 -481
  1661. package/src/memory/job-handlers/embedding.test.ts +0 -234
  1662. package/src/memory/job-handlers/embedding.ts +0 -133
  1663. package/src/memory/job-handlers/index-maintenance.ts +0 -111
  1664. package/src/memory/job-handlers/media-processing.ts +0 -120
  1665. package/src/memory/job-handlers/summarization.ts +0 -240
  1666. package/src/memory/job-utils.ts +0 -286
  1667. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +0 -559
  1668. package/src/memory/jobs/embed-concept-page.ts +0 -417
  1669. package/src/memory/jobs/embed-pkb-file.test.ts +0 -169
  1670. package/src/memory/jobs/embed-pkb-file.ts +0 -59
  1671. package/src/memory/jobs-store.ts +0 -813
  1672. package/src/memory/jobs-worker.ts +0 -975
  1673. package/src/memory/llm-request-log-source-clickhouse.ts +0 -521
  1674. package/src/memory/llm-request-log-store.ts +0 -717
  1675. package/src/memory/llm-usage-store.ts +0 -900
  1676. package/src/memory/memory-recall-log-store.ts +0 -172
  1677. package/src/memory/memory-retrospective-constants.ts +0 -50
  1678. package/src/memory/memory-retrospective-enqueue.ts +0 -125
  1679. package/src/memory/memory-retrospective-fork-boundary.ts +0 -94
  1680. package/src/memory/memory-retrospective-job.ts +0 -879
  1681. package/src/memory/memory-retrospective-startup-cleanup.ts +0 -261
  1682. package/src/memory/memory-retrospective-state.ts +0 -245
  1683. package/src/memory/memory-retrospective-trigger-check.ts +0 -91
  1684. package/src/memory/memory-v2-activation-log-store.ts +0 -216
  1685. package/src/memory/memory-v2-concept-frequency.ts +0 -173
  1686. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +0 -303
  1687. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +0 -365
  1688. package/src/memory/migrations/142-rename-verification-session-id-column.ts +0 -55
  1689. package/src/memory/migrations/182-oauth-providers-display-metadata.ts +0 -20
  1690. package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +0 -179
  1691. package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +0 -64
  1692. package/src/memory/migrations/202-memory-graph-tables.ts +0 -130
  1693. package/src/memory/migrations/203-drop-memory-items-tables.ts +0 -55
  1694. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +0 -124
  1695. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +0 -152
  1696. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +0 -16
  1697. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +0 -183
  1698. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +0 -76
  1699. package/src/memory/migrations/236-tool-invocations-matched-rule-id.ts +0 -21
  1700. package/src/memory/migrations/243-provider-connections.ts +0 -68
  1701. package/src/memory/migrations/244-provider-connection-status-label.ts +0 -23
  1702. package/src/memory/migrations/246-backfill-provider-connection-label.ts +0 -81
  1703. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +0 -19
  1704. package/src/memory/migrations/301-create-watchdog-events.ts +0 -45
  1705. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +0 -224
  1706. package/src/memory/migrations/__tests__/244-provider-connection-status-label.test.ts +0 -84
  1707. package/src/memory/migrations/__tests__/246-backfill-provider-connection-label.test.ts +0 -192
  1708. package/src/memory/migrations/__tests__/run-migrations.test.ts +0 -378
  1709. package/src/memory/migrations/helpers/relocation.ts +0 -227
  1710. package/src/memory/migrations/validate-migration-state.ts +0 -277
  1711. package/src/memory/onboarding-events-store.ts +0 -161
  1712. package/src/memory/pkb/autoinject.ts +0 -61
  1713. package/src/memory/pkb/context.ts +0 -50
  1714. package/src/memory/pkb/pkb-index.test.ts +0 -368
  1715. package/src/memory/pkb/pkb-index.ts +0 -255
  1716. package/src/memory/pkb/pkb-reconcile.test.ts +0 -251
  1717. package/src/memory/pkb/pkb-reconcile.ts +0 -148
  1718. package/src/memory/pkb/pkb-search.test.ts +0 -586
  1719. package/src/memory/pkb/pkb-search.ts +0 -178
  1720. package/src/memory/pkb/types.ts +0 -67
  1721. package/src/memory/published-pages-store.ts +0 -67
  1722. package/src/memory/qdrant-circuit-breaker.ts +0 -119
  1723. package/src/memory/qdrant-client.test.ts +0 -60
  1724. package/src/memory/qdrant-client.ts +0 -834
  1725. package/src/memory/qdrant-manager.ts +0 -422
  1726. package/src/memory/rerank-local.ts +0 -374
  1727. package/src/memory/schedule-attribution-sql.ts +0 -104
  1728. package/src/memory/schema/acp.ts +0 -34
  1729. package/src/memory/schema/contacts.ts +0 -116
  1730. package/src/memory/schema/conversations.ts +0 -197
  1731. package/src/memory/schema/infrastructure.ts +0 -381
  1732. package/src/memory/scoped-approval-grants.ts +0 -551
  1733. package/src/memory/search/semantic.ts +0 -238
  1734. package/src/memory/segmenter.ts +0 -76
  1735. package/src/memory/shared-app-links-store.ts +0 -130
  1736. package/src/memory/skill-loaded-events-store.test.ts +0 -152
  1737. package/src/memory/skill-loaded-events-store.ts +0 -95
  1738. package/src/memory/sparse-tokenize.ts +0 -49
  1739. package/src/memory/steps.ts +0 -573
  1740. package/src/memory/task-memory-cleanup.ts +0 -236
  1741. package/src/memory/tool-executed-events-store.test.ts +0 -219
  1742. package/src/memory/tool-executed-events-store.ts +0 -102
  1743. package/src/memory/tool-usage-store.ts +0 -113
  1744. package/src/memory/trace-event-store.ts +0 -133
  1745. package/src/memory/turn-events-store.ts +0 -173
  1746. package/src/memory/turn-trace-store.test.ts +0 -736
  1747. package/src/memory/turn-trace-store.ts +0 -364
  1748. package/src/memory/v2/__tests__/activation-store.test.ts +0 -204
  1749. package/src/memory/v2/__tests__/activation.test.ts +0 -1227
  1750. package/src/memory/v2/__tests__/backfill-jobs.test.ts +0 -504
  1751. package/src/memory/v2/__tests__/cli-command-store.test.ts +0 -429
  1752. package/src/memory/v2/__tests__/consolidation-job.test.ts +0 -747
  1753. package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +0 -189
  1754. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +0 -136
  1755. package/src/memory/v2/__tests__/harness-compare.test.ts +0 -186
  1756. package/src/memory/v2/__tests__/harness-oracle.test.ts +0 -257
  1757. package/src/memory/v2/__tests__/harness-replay-input.test.ts +0 -230
  1758. package/src/memory/v2/__tests__/harness-runner.test.ts +0 -135
  1759. package/src/memory/v2/__tests__/injection-events.test.ts +0 -318
  1760. package/src/memory/v2/__tests__/injection.test.ts +0 -2301
  1761. package/src/memory/v2/__tests__/migration.test.ts +0 -939
  1762. package/src/memory/v2/__tests__/page-index.test.ts +0 -760
  1763. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +0 -313
  1764. package/src/memory/v2/__tests__/qdrant.test.ts +0 -950
  1765. package/src/memory/v2/__tests__/reranker.test.ts +0 -338
  1766. package/src/memory/v2/__tests__/router.test.ts +0 -1197
  1767. package/src/memory/v2/__tests__/sim.test.ts +0 -670
  1768. package/src/memory/v2/__tests__/skill-content.test.ts +0 -115
  1769. package/src/memory/v2/__tests__/skill-store.test.ts +0 -983
  1770. package/src/memory/v2/__tests__/sparse-bm25.test.ts +0 -292
  1771. package/src/memory/v2/__tests__/static-context.test.ts +0 -223
  1772. package/src/memory/v2/__tests__/sweep-job.test.ts +0 -526
  1773. package/src/memory/v2/activation-store.ts +0 -185
  1774. package/src/memory/v2/activation.ts +0 -548
  1775. package/src/memory/v2/backfill-jobs.ts +0 -296
  1776. package/src/memory/v2/cli-command-store.ts +0 -341
  1777. package/src/memory/v2/consolidation-job.ts +0 -561
  1778. package/src/memory/v2/frontmatter-sweep.ts +0 -98
  1779. package/src/memory/v2/harness/compare.ts +0 -57
  1780. package/src/memory/v2/harness/oracle.ts +0 -145
  1781. package/src/memory/v2/harness/replay-input.ts +0 -240
  1782. package/src/memory/v2/harness/retriever.ts +0 -74
  1783. package/src/memory/v2/harness/router-retriever.ts +0 -43
  1784. package/src/memory/v2/injected-block-slugs.ts +0 -79
  1785. package/src/memory/v2/injection-events.ts +0 -101
  1786. package/src/memory/v2/injection.ts +0 -932
  1787. package/src/memory/v2/migration.ts +0 -776
  1788. package/src/memory/v2/page-index.ts +0 -459
  1789. package/src/memory/v2/page-store.ts +0 -443
  1790. package/src/memory/v2/prompts/consolidation.ts +0 -857
  1791. package/src/memory/v2/prompts/router.ts +0 -148
  1792. package/src/memory/v2/qdrant.ts +0 -892
  1793. package/src/memory/v2/reranker.ts +0 -184
  1794. package/src/memory/v2/router.ts +0 -601
  1795. package/src/memory/v2/sim.ts +0 -296
  1796. package/src/memory/v2/skill-content.ts +0 -61
  1797. package/src/memory/v2/skill-store.ts +0 -431
  1798. package/src/memory/v2/sparse-bm25.ts +0 -245
  1799. package/src/memory/v2/static-context.ts +0 -100
  1800. package/src/memory/v2/sweep-job.ts +0 -320
  1801. package/src/memory/v3-eval/eval-packets.ts +0 -731
  1802. package/src/memory/watchdog-events-store.ts +0 -87
  1803. package/src/memory/worker-control.ts +0 -118
  1804. package/src/memory/worker-process.ts +0 -72
  1805. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
  1806. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
  1807. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
  1808. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -314
  1809. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
  1810. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
  1811. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
  1812. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +0 -147
  1813. package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
  1814. package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
  1815. package/src/plugins/defaults/advisor/config.ts +0 -21
  1816. package/src/plugins/defaults/advisor/consult.ts +0 -197
  1817. package/src/plugins/defaults/advisor/context-pack.ts +0 -288
  1818. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
  1819. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
  1820. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
  1821. package/src/plugins/defaults/advisor/package.json +0 -14
  1822. package/src/plugins/defaults/advisor/steering.ts +0 -79
  1823. package/src/plugins/defaults/advisor/tools/advisor.ts +0 -92
  1824. package/src/plugins/defaults/advisor/transcript.ts +0 -76
  1825. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +0 -116
  1826. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +0 -370
  1827. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +0 -41
  1828. package/src/plugins/defaults/memory-retrieval/injectors.ts +0 -1061
  1829. package/src/plugins/defaults/memory-retrieval/package.json +0 -14
  1830. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +0 -64
  1831. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +0 -231
  1832. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +0 -1164
  1833. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +0 -181
  1834. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +0 -247
  1835. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +0 -717
  1836. package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +0 -365
  1837. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +0 -736
  1838. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +0 -839
  1839. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +0 -598
  1840. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +0 -128
  1841. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +0 -469
  1842. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +0 -425
  1843. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +0 -493
  1844. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +0 -856
  1845. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +0 -91
  1846. package/src/plugins/defaults/memory-v3-shadow/card.ts +0 -160
  1847. package/src/plugins/defaults/memory-v3-shadow/dense.ts +0 -97
  1848. package/src/plugins/defaults/memory-v3-shadow/edge.ts +0 -252
  1849. package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.test.ts +0 -305
  1850. package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.ts +0 -278
  1851. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +0 -14
  1852. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +0 -19
  1853. package/src/plugins/defaults/memory-v3-shadow/hot-set.test.ts +0 -138
  1854. package/src/plugins/defaults/memory-v3-shadow/hot-set.ts +0 -85
  1855. package/src/plugins/defaults/memory-v3-shadow/injector.ts +0 -453
  1856. package/src/plugins/defaults/memory-v3-shadow/learned-edges.test.ts +0 -169
  1857. package/src/plugins/defaults/memory-v3-shadow/learned-edges.ts +0 -178
  1858. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +0 -794
  1859. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +0 -431
  1860. package/src/plugins/defaults/memory-v3-shadow/package.json +0 -14
  1861. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +0 -102
  1862. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +0 -146
  1863. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +0 -578
  1864. package/src/plugins/defaults/memory-v3-shadow/prune.test.ts +0 -758
  1865. package/src/plugins/defaults/memory-v3-shadow/prune.ts +0 -471
  1866. package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +0 -92
  1867. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +0 -269
  1868. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +0 -200
  1869. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +0 -281
  1870. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +0 -642
  1871. package/src/plugins/defaults/memory-v3-shadow/types.ts +0 -127
  1872. package/src/plugins/feature-gate.ts +0 -22
  1873. package/src/runtime/__tests__/slack-block-formatting.test.ts +0 -510
  1874. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +0 -165
  1875. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +0 -347
  1876. package/src/runtime/routes/memory-eval-routes.ts +0 -179
  1877. package/src/runtime/routes/memory-item-routes.test.ts +0 -912
  1878. package/src/runtime/routes/memory-item-routes.ts +0 -815
  1879. package/src/runtime/routes/memory-v2-routes.ts +0 -850
  1880. package/src/runtime/routes/memory-v3-routes.ts +0 -122
  1881. package/src/runtime/routes/secrets-deps.ts +0 -24
  1882. package/src/runtime/slack-block-formatting.ts +0 -601
  1883. package/src/tools/memory/register.test.ts +0 -382
  1884. package/src/tools/memory/register.ts +0 -81
  1885. /package/src/{memory → apps}/app-git-service.ts +0 -0
  1886. /package/src/{memory → channels}/slack-thread-store.ts +0 -0
  1887. /package/src/{memory → home}/conversation-starter-validation.ts +0 -0
  1888. /package/src/{memory → persistence}/auto-analysis-constants.ts +0 -0
  1889. /package/src/{memory → persistence}/bookmark-crud.ts +0 -0
  1890. /package/src/{memory → persistence}/cleanup-schedule-state.ts +0 -0
  1891. /package/src/{memory → persistence}/compaction-log-store-clickhouse.ts +0 -0
  1892. /package/src/{memory → persistence}/conversation-directories.ts +0 -0
  1893. /package/src/{memory → persistence}/conversation-disk-view.ts +0 -0
  1894. /package/src/{memory → persistence}/conversation-display-order-migration.ts +0 -0
  1895. /package/src/{memory → persistence}/conversation-group-migration.ts +0 -0
  1896. /package/src/{memory → persistence}/conversation-types.ts +0 -0
  1897. /package/src/{memory → persistence}/db-singleton.ts +0 -0
  1898. /package/src/{memory → persistence}/delivery-channels.ts +0 -0
  1899. /package/src/{memory → persistence}/delivery-crud.ts +0 -0
  1900. /package/src/{memory → persistence}/delivery-status.ts +0 -0
  1901. /package/src/{memory → persistence/embeddings}/__tests__/sparse-tokenize.test.ts +0 -0
  1902. /package/src/{memory → persistence/embeddings}/embedding-gemini.test.ts +0 -0
  1903. /package/src/{memory → persistence/embeddings}/embedding-gemini.ts +0 -0
  1904. /package/src/{memory → persistence/embeddings}/embedding-openai.ts +0 -0
  1905. /package/src/{memory → persistence/embeddings}/embedding-types.test.ts +0 -0
  1906. /package/src/{memory → persistence}/fingerprint.ts +0 -0
  1907. /package/src/{memory → persistence}/group-crud.ts +0 -0
  1908. /package/src/{memory → persistence}/invite-store.ts +0 -0
  1909. /package/src/{memory → persistence}/lifecycle-events-store.ts +0 -0
  1910. /package/src/{memory → persistence}/llm-request-log-source-local.ts +0 -0
  1911. /package/src/{memory → persistence}/llm-request-log-source.ts +0 -0
  1912. /package/src/{memory → persistence}/media-store.ts +0 -0
  1913. /package/src/{memory → persistence}/message-content.ts +0 -0
  1914. /package/src/{memory → persistence}/migrations/000-core-tables.ts +0 -0
  1915. /package/src/{memory → persistence}/migrations/001-job-deferrals.ts +0 -0
  1916. /package/src/{memory → persistence}/migrations/002-tool-invocations-fk.ts +0 -0
  1917. /package/src/{memory → persistence}/migrations/003-memory-fts-backfill.ts +0 -0
  1918. /package/src/{memory → persistence}/migrations/004-entity-relation-dedup.ts +0 -0
  1919. /package/src/{memory → persistence}/migrations/005-fingerprint-scope-unique.ts +0 -0
  1920. /package/src/{memory → persistence}/migrations/006-scope-salted-fingerprints.ts +0 -0
  1921. /package/src/{memory → persistence}/migrations/007-assistant-id-to-self.ts +0 -0
  1922. /package/src/{memory → persistence}/migrations/008-remove-assistant-id-columns.ts +0 -0
  1923. /package/src/{memory → persistence}/migrations/009-llm-usage-events-drop-assistant-id.ts +0 -0
  1924. /package/src/{memory → persistence}/migrations/011-call-sessions-provider-sid-dedup.ts +0 -0
  1925. /package/src/{memory → persistence}/migrations/012-call-sessions-add-initiated-from.ts +0 -0
  1926. /package/src/{memory → persistence}/migrations/013-guardian-action-tables.ts +0 -0
  1927. /package/src/{memory → persistence}/migrations/014-backfill-inbox-thread-state.ts +0 -0
  1928. /package/src/{memory → persistence}/migrations/015-drop-active-search-index.ts +0 -0
  1929. /package/src/{memory → persistence}/migrations/016-memory-segments-indexes.ts +0 -0
  1930. /package/src/{memory → persistence}/migrations/017-memory-items-indexes.ts +0 -0
  1931. /package/src/{memory → persistence}/migrations/018-remaining-table-indexes.ts +0 -0
  1932. /package/src/{memory → persistence}/migrations/019-notification-tables-schema-migration.ts +0 -0
  1933. /package/src/{memory → persistence}/migrations/020-rename-macos-ios-channel-to-vellum.ts +0 -0
  1934. /package/src/{memory → persistence}/migrations/021-conversation-status-indexes.ts +0 -0
  1935. /package/src/{memory → persistence}/migrations/022-add-origin-interface.ts +0 -0
  1936. /package/src/{memory → persistence}/migrations/023-memory-item-sources-indexes.ts +0 -0
  1937. /package/src/{memory → persistence}/migrations/024-embedding-vector-blob.ts +0 -0
  1938. /package/src/{memory → persistence}/migrations/025-messages-fts-backfill.ts +0 -0
  1939. /package/src/{memory → persistence}/migrations/026-guardian-verification-sessions.ts +0 -0
  1940. /package/src/{memory → persistence}/migrations/026a-embeddings-nullable-vector-json.ts +0 -0
  1941. /package/src/{memory → persistence}/migrations/027-notification-delivery-pairing-columns.ts +0 -0
  1942. /package/src/{memory → persistence}/migrations/027a-guardian-bootstrap-token.ts +0 -0
  1943. /package/src/{memory → persistence}/migrations/028-call-session-mode.ts +0 -0
  1944. /package/src/{memory → persistence}/migrations/028-notification-delivery-client-ack.ts +0 -0
  1945. /package/src/{memory → persistence}/migrations/029-channel-inbound-delivered-segments.ts +0 -0
  1946. /package/src/{memory → persistence}/migrations/030-guardian-action-followup.ts +0 -0
  1947. /package/src/{memory → persistence}/migrations/030-guardian-verification-purpose.ts +0 -0
  1948. /package/src/{memory → persistence}/migrations/031-conversations-thread-type-index.ts +0 -0
  1949. /package/src/{memory → persistence}/migrations/032-guardian-delivery-conversation-index.ts +0 -0
  1950. /package/src/{memory → persistence}/migrations/032-notification-delivery-thread-decision.ts +0 -0
  1951. /package/src/{memory → persistence}/migrations/033-scoped-approval-grants.ts +0 -0
  1952. /package/src/{memory → persistence}/migrations/034-guardian-action-tool-metadata.ts +0 -0
  1953. /package/src/{memory → persistence}/migrations/035-guardian-action-supersession.ts +0 -0
  1954. /package/src/{memory → persistence}/migrations/036-normalize-phone-identities.ts +0 -0
  1955. /package/src/{memory → persistence}/migrations/037-voice-invite-columns.ts +0 -0
  1956. /package/src/{memory → persistence}/migrations/040-invite-code-hash-column.ts +0 -0
  1957. /package/src/{memory → persistence}/migrations/041-approval-prompt-ts-tracker.ts +0 -0
  1958. /package/src/{memory → persistence}/migrations/101-watchers-and-logs.ts +0 -0
  1959. /package/src/{memory → persistence}/migrations/102-alter-table-columns.ts +0 -0
  1960. /package/src/{memory → persistence}/migrations/104-core-indexes.ts +0 -0
  1961. /package/src/{memory → persistence}/migrations/105-contacts-and-triage.ts +0 -0
  1962. /package/src/{memory → persistence}/migrations/106-call-sessions.ts +0 -0
  1963. /package/src/{memory → persistence}/migrations/107-followups.ts +0 -0
  1964. /package/src/{memory → persistence}/migrations/108-tasks-and-work-items.ts +0 -0
  1965. /package/src/{memory → persistence}/migrations/109-external-conversation-bindings.ts +0 -0
  1966. /package/src/{memory → persistence}/migrations/110-channel-guardian.ts +0 -0
  1967. /package/src/{memory → persistence}/migrations/111-media-assets.ts +0 -0
  1968. /package/src/{memory → persistence}/migrations/112-assistant-inbox.ts +0 -0
  1969. /package/src/{memory → persistence}/migrations/114-notifications.ts +0 -0
  1970. /package/src/{memory → persistence}/migrations/115-sequences.ts +0 -0
  1971. /package/src/{memory → persistence}/migrations/116-messages-fts.ts +0 -0
  1972. /package/src/{memory → persistence}/migrations/117-conversation-attention.ts +0 -0
  1973. /package/src/{memory → persistence}/migrations/118-reminder-routing-intent.ts +0 -0
  1974. /package/src/{memory → persistence}/migrations/119-schema-indexes-and-columns.ts +0 -0
  1975. /package/src/{memory → persistence}/migrations/120-fk-cascade-rebuilds.ts +0 -0
  1976. /package/src/{memory → persistence}/migrations/121-canonical-guardian-requests.ts +0 -0
  1977. /package/src/{memory → persistence}/migrations/122-canonical-guardian-requester-chat-id.ts +0 -0
  1978. /package/src/{memory → persistence}/migrations/123-canonical-guardian-deliveries-destination-index.ts +0 -0
  1979. /package/src/{memory → persistence}/migrations/124-voice-invite-display-metadata.ts +0 -0
  1980. /package/src/{memory → persistence}/migrations/125-guardian-principal-id-columns.ts +0 -0
  1981. /package/src/{memory → persistence}/migrations/127-guardian-principal-id-not-null.ts +0 -0
  1982. /package/src/{memory → persistence}/migrations/128-contacts-role-principal.ts +0 -0
  1983. /package/src/{memory → persistence}/migrations/129-contact-channels-access-fields.ts +0 -0
  1984. /package/src/{memory → persistence}/migrations/130-contact-channels-type-ext-chat-id-index.ts +0 -0
  1985. /package/src/{memory → persistence}/migrations/131-drop-legacy-member-guardian-tables.ts +0 -0
  1986. /package/src/{memory → persistence}/migrations/132-contacts-assistant-id.ts +0 -0
  1987. /package/src/{memory → persistence}/migrations/133-assistant-contact-metadata.ts +0 -0
  1988. /package/src/{memory → persistence}/migrations/134-contacts-notes-column.ts +0 -0
  1989. /package/src/{memory → persistence}/migrations/135-backfill-contact-interaction-stats.ts +0 -0
  1990. /package/src/{memory → persistence}/migrations/136-drop-assistant-id-columns.ts +0 -0
  1991. /package/src/{memory → persistence}/migrations/137-usage-dashboard-indexes.ts +0 -0
  1992. /package/src/{memory → persistence}/migrations/139-drop-usage-composite-indexes.ts +0 -0
  1993. /package/src/{memory → persistence}/migrations/141-rename-verification-table.ts +0 -0
  1994. /package/src/{memory → persistence}/migrations/143-rename-guardian-verification-values.ts +0 -0
  1995. /package/src/{memory → persistence}/migrations/144-rename-voice-to-phone.ts +0 -0
  1996. /package/src/{memory → persistence}/migrations/145-drop-accounts-table.ts +0 -0
  1997. /package/src/{memory → persistence}/migrations/146-schedule-oneshot-routing.ts +0 -0
  1998. /package/src/{memory → persistence}/migrations/147-migrate-reminders-to-schedules.ts +0 -0
  1999. /package/src/{memory → persistence}/migrations/148-drop-reminders-table.ts +0 -0
  2000. /package/src/{memory → persistence}/migrations/149-oauth-tables.ts +0 -0
  2001. /package/src/{memory → persistence}/migrations/150-oauth-apps-client-secret-path.ts +0 -0
  2002. /package/src/{memory → persistence}/migrations/151-oauth-providers-ping-url.ts +0 -0
  2003. /package/src/{memory → persistence}/migrations/152-memory-item-supersession.ts +0 -0
  2004. /package/src/{memory → persistence}/migrations/153-drop-entity-tables.ts +0 -0
  2005. /package/src/{memory → persistence}/migrations/154-drop-fts.ts +0 -0
  2006. /package/src/{memory → persistence}/migrations/155-drop-conflicts.ts +0 -0
  2007. /package/src/{memory → persistence}/migrations/156-call-session-invite-metadata.ts +0 -0
  2008. /package/src/{memory → persistence}/migrations/157-invite-contact-id.ts +0 -0
  2009. /package/src/{memory → persistence}/migrations/158-channel-interaction-columns.ts +0 -0
  2010. /package/src/{memory → persistence}/migrations/159-drop-contact-interaction-columns.ts +0 -0
  2011. /package/src/{memory → persistence}/migrations/160-drop-loopback-port-column.ts +0 -0
  2012. /package/src/{memory → persistence}/migrations/161-drop-orphaned-media-tables.ts +0 -0
  2013. /package/src/{memory → persistence}/migrations/162-guardian-timestamps-epoch-ms.ts +0 -0
  2014. /package/src/{memory → persistence}/migrations/163-rename-notification-thread-columns.ts +0 -0
  2015. /package/src/{memory → persistence}/migrations/164-rename-conversation-type-column.ts +0 -0
  2016. /package/src/{memory → persistence}/migrations/165-rename-inbox-thread-state-table.ts +0 -0
  2017. /package/src/{memory → persistence}/migrations/166-rename-followups-thread-id.ts +0 -0
  2018. /package/src/{memory → persistence}/migrations/167-rename-sequence-enrollments-thread-id.ts +0 -0
  2019. /package/src/{memory → persistence}/migrations/168-rename-sequence-steps-reply-key.ts +0 -0
  2020. /package/src/{memory → persistence}/migrations/169-rename-gmail-provider-key-to-google.ts +0 -0
  2021. /package/src/{memory → persistence}/migrations/170-thread-starters-table.ts +0 -0
  2022. /package/src/{memory → persistence}/migrations/171-capability-card-columns.ts +0 -0
  2023. /package/src/{memory → persistence}/migrations/172-rename-created-by-session-id.ts +0 -0
  2024. /package/src/{memory → persistence}/migrations/173-rename-source-session-id.ts +0 -0
  2025. /package/src/{memory → persistence}/migrations/174-rename-thread-starters-table.ts +0 -0
  2026. /package/src/{memory → persistence}/migrations/175-create-lifecycle-events.ts +0 -0
  2027. /package/src/{memory → persistence}/migrations/176-drop-capability-card-state.ts +0 -0
  2028. /package/src/{memory → persistence}/migrations/177-create-trace-events-table.ts +0 -0
  2029. /package/src/{memory → persistence}/migrations/178-oauth-providers-managed-service-config-key.ts +0 -0
  2030. /package/src/{memory → persistence}/migrations/179-llm-request-log-message-id.ts +0 -0
  2031. /package/src/{memory → persistence}/migrations/180-backfill-inline-attachments-to-disk.ts +0 -0
  2032. /package/src/{memory → persistence}/migrations/181-rename-thread-starters-checkpoints.ts +0 -0
  2033. /package/src/{memory → persistence}/migrations/183-add-conversation-fork-lineage.ts +0 -0
  2034. /package/src/{memory → persistence}/migrations/184-llm-request-log-provider.ts +0 -0
  2035. /package/src/{memory → persistence}/migrations/188-schedule-quiet-flag.ts +0 -0
  2036. /package/src/{memory → persistence}/migrations/189-drop-simplified-memory.ts +0 -0
  2037. /package/src/{memory → persistence}/migrations/190-call-session-skip-disclosure.ts +0 -0
  2038. /package/src/{memory → persistence}/migrations/191-backfill-audio-attachment-mime-types.ts +0 -0
  2039. /package/src/{memory → persistence}/migrations/192-contacts-user-file-column.ts +0 -0
  2040. /package/src/{memory → persistence}/migrations/193-add-source-type-columns.ts +0 -0
  2041. /package/src/{memory → persistence}/migrations/194-memory-recall-logs.ts +0 -0
  2042. /package/src/{memory → persistence}/migrations/195-oauth-providers-ping-config.ts +0 -0
  2043. /package/src/{memory → persistence}/migrations/196-messages-conversation-created-at-index.ts +0 -0
  2044. /package/src/{memory → persistence}/migrations/197-oauth-providers-behavior-columns.ts +0 -0
  2045. /package/src/{memory → persistence}/migrations/198-drop-setup-skill-id-column.ts +0 -0
  2046. /package/src/{memory → persistence}/migrations/200-usage-llm-call-count.ts +0 -0
  2047. /package/src/{memory → persistence}/migrations/201-oauth-providers-feature-flag.ts +0 -0
  2048. /package/src/{memory → persistence}/migrations/202-drop-callback-transport-column.ts +0 -0
  2049. /package/src/{memory → persistence}/migrations/204-rename-memory-graph-type-values.ts +0 -0
  2050. /package/src/{memory → persistence}/migrations/205-memory-graph-image-refs.ts +0 -0
  2051. /package/src/{memory → persistence}/migrations/206-memory-graph-node-edits.ts +0 -0
  2052. /package/src/{memory → persistence}/migrations/207-conversation-graph-memory-state.ts +0 -0
  2053. /package/src/{memory → persistence}/migrations/208-conversations-last-message-at.ts +0 -0
  2054. /package/src/{memory → persistence}/migrations/210-schedule-reuse-conversation.ts +0 -0
  2055. /package/src/{memory → persistence}/migrations/212-llm-request-logs-created-at-index.ts +0 -0
  2056. /package/src/{memory → persistence}/migrations/213-oauth-providers-scope-separator.ts +0 -0
  2057. /package/src/{memory → persistence}/migrations/214-oauth-providers-refresh-url.ts +0 -0
  2058. /package/src/{memory → persistence}/migrations/215-oauth-providers-revoke.ts +0 -0
  2059. /package/src/{memory → persistence}/migrations/216-oauth-providers-token-auth-method.ts +0 -0
  2060. /package/src/{memory → persistence}/migrations/217-conversation-host-access.ts +0 -0
  2061. /package/src/{memory → persistence}/migrations/218-oauth-providers-logo-url.ts +0 -0
  2062. /package/src/{memory → persistence}/migrations/219-oauth-providers-token-exchange-body-format.ts +0 -0
  2063. /package/src/{memory → persistence}/migrations/221-conversations-archived-at.ts +0 -0
  2064. /package/src/{memory → persistence}/migrations/223-schedule-script-column.ts +0 -0
  2065. /package/src/{memory → persistence}/migrations/224-oauth-providers-managed-service-is-paid.ts +0 -0
  2066. /package/src/{memory → persistence}/migrations/225-oauth-providers-available-scopes.ts +0 -0
  2067. /package/src/{memory → persistence}/migrations/226-schedule-wake-conversation-id.ts +0 -0
  2068. /package/src/{memory → persistence}/migrations/227-add-conversation-inference-profile.ts +0 -0
  2069. /package/src/{memory → persistence}/migrations/228-rename-inference-profile-snake-case.ts +0 -0
  2070. /package/src/{memory → persistence}/migrations/229-delete-private-conversations.test.ts +0 -0
  2071. /package/src/{memory → persistence}/migrations/229-delete-private-conversations.ts +0 -0
  2072. /package/src/{memory → persistence}/migrations/230-acp-session-history.ts +0 -0
  2073. /package/src/{memory → persistence}/migrations/231-repair-memory-graph-event-dates.ts +0 -0
  2074. /package/src/{memory → persistence}/migrations/232-activation-state.ts +0 -0
  2075. /package/src/{memory → persistence}/migrations/233-document-conversations.ts +0 -0
  2076. /package/src/{memory → persistence}/migrations/234-memory-v2-activation-logs.ts +0 -0
  2077. /package/src/{memory → persistence}/migrations/235-llm-usage-attribution.ts +0 -0
  2078. /package/src/{memory → persistence}/migrations/235-slack-compaction-watermark.ts +0 -0
  2079. /package/src/{memory → persistence}/migrations/237-heartbeat-runs.ts +0 -0
  2080. /package/src/{memory → persistence}/migrations/238-schedule-retry-policy.ts +0 -0
  2081. /package/src/{memory → persistence}/migrations/239-trace-events-created-at-index.ts +0 -0
  2082. /package/src/{memory → persistence}/migrations/240-conversation-inference-profile-session.ts +0 -0
  2083. /package/src/{memory → persistence}/migrations/241-activation-state-fk-cascade.ts +0 -0
  2084. /package/src/{memory → persistence}/migrations/242-message-bookmarks.ts +0 -0
  2085. /package/src/{memory → persistence}/migrations/245-memory-retrospective-state.ts +0 -0
  2086. /package/src/{memory → persistence}/migrations/247-external-conversation-binding-thread-id.ts +0 -0
  2087. /package/src/{memory → persistence}/migrations/248-create-onboarding-events.ts +0 -0
  2088. /package/src/{memory → persistence}/migrations/249-normalize-slack-external-content.ts +0 -0
  2089. /package/src/{memory → persistence}/migrations/250-provider-connection-base-url-and-models.ts +0 -0
  2090. /package/src/{memory → persistence}/migrations/251-a2a-tasks.ts +0 -0
  2091. /package/src/{memory → persistence}/migrations/252-llm-request-log-agent-loop-exit-reason.ts +0 -0
  2092. /package/src/{memory → persistence}/migrations/253-conversation-last-notified-profile.ts +0 -0
  2093. /package/src/{memory → persistence}/migrations/253-document-comments.ts +0 -0
  2094. /package/src/{memory → persistence}/migrations/254-external-conversation-binding-chat-name.ts +0 -0
  2095. /package/src/{memory → persistence}/migrations/256-memory-v2-injection-events.ts +0 -0
  2096. /package/src/{memory → persistence}/migrations/257-strip-base-url-non-openai-compatible.ts +0 -0
  2097. /package/src/{memory → persistence}/migrations/258-onboarding-events-prior-assistants.ts +0 -0
  2098. /package/src/{memory → persistence}/migrations/259-conversation-cleaned-at.ts +0 -0
  2099. /package/src/{memory → persistence}/migrations/260-rename-cleaned-at.ts +0 -0
  2100. /package/src/{memory → persistence}/migrations/261-llm-usage-add-raw-usage.ts +0 -0
  2101. /package/src/{memory → persistence}/migrations/262-memory-v3-coactivation.ts +0 -0
  2102. /package/src/{memory → persistence}/migrations/263-memory-v3-auto-edges.ts +0 -0
  2103. /package/src/{memory → persistence}/migrations/264-llm-request-log-call-site.ts +0 -0
  2104. /package/src/{memory → persistence}/migrations/265-drop-provider-connection-status.ts +0 -0
  2105. /package/src/{memory → persistence}/migrations/266-messages-client-message-id.ts +0 -0
  2106. /package/src/{memory → persistence}/migrations/267-llm-usage-events-add-assistant-version.ts +0 -0
  2107. /package/src/{memory → persistence}/migrations/268-add-memory-v3-selections.ts +0 -0
  2108. /package/src/{memory → persistence}/migrations/269-schedule-script-timeout.ts +0 -0
  2109. /package/src/{memory → persistence}/migrations/270-messages-role-created-at-index.ts +0 -0
  2110. /package/src/{memory → persistence}/migrations/270-schedule-description.ts +0 -0
  2111. /package/src/{memory → persistence}/migrations/270-schedule-source-conversation.ts +0 -0
  2112. /package/src/{memory → persistence}/migrations/271-create-auth-fallback-events.ts +0 -0
  2113. /package/src/{memory → persistence}/migrations/272-acp-session-history-cwd.ts +0 -0
  2114. /package/src/{memory → persistence}/migrations/273-onboarding-events-funnel-columns.ts +0 -0
  2115. /package/src/{memory → persistence}/migrations/274-create-activation-sessions.ts +0 -0
  2116. /package/src/{memory → persistence}/migrations/275-tool-invocations-add-skill-id.test.ts +0 -0
  2117. /package/src/{memory → persistence}/migrations/275-tool-invocations-add-skill-id.ts +0 -0
  2118. /package/src/{memory → persistence}/migrations/276-tool-invocations-created-at-id-index.test.ts +0 -0
  2119. /package/src/{memory → persistence}/migrations/276-tool-invocations-created-at-id-index.ts +0 -0
  2120. /package/src/{memory → persistence}/migrations/277-add-memory-v3-ever-injected.ts +0 -0
  2121. /package/src/{memory → persistence}/migrations/278-tool-invocations-telemetry-columns.test.ts +0 -0
  2122. /package/src/{memory → persistence}/migrations/278-tool-invocations-telemetry-columns.ts +0 -0
  2123. /package/src/{memory → persistence}/migrations/279-create-skill-loaded-events.test.ts +0 -0
  2124. /package/src/{memory → persistence}/migrations/279-create-skill-loaded-events.ts +0 -0
  2125. /package/src/{memory → persistence}/migrations/280-conversations-surfaced-at.test.ts +0 -0
  2126. /package/src/{memory → persistence}/migrations/280-conversations-surfaced-at.ts +0 -0
  2127. /package/src/{memory → persistence}/migrations/281-memory-retrospective-remembered-log.ts +0 -0
  2128. /package/src/{memory → persistence}/migrations/282-schedule-inference-profile.test.ts +0 -0
  2129. /package/src/{memory → persistence}/migrations/282-schedule-inference-profile.ts +0 -0
  2130. /package/src/{memory → persistence}/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +0 -0
  2131. /package/src/{memory → persistence}/migrations/283-memory-v3-selections-message-id-and-sections.ts +0 -0
  2132. /package/src/{memory → persistence}/migrations/284-workflow-runs.ts +0 -0
  2133. /package/src/{memory → persistence}/migrations/285-schedule-workflow-mode.ts +0 -0
  2134. /package/src/{memory → persistence}/migrations/286-workflow-run-trust.ts +0 -0
  2135. /package/src/{memory → persistence}/migrations/287-conversation-origin-channel-index.ts +0 -0
  2136. /package/src/{memory → persistence}/migrations/288-backfill-origin-channel-from-bindings.ts +0 -0
  2137. /package/src/{memory → persistence}/migrations/289-contact-channels-unique-ext-user.ts +0 -0
  2138. /package/src/{memory → persistence}/migrations/290-schedule-capabilities.test.ts +0 -0
  2139. /package/src/{memory → persistence}/migrations/290-schedule-capabilities.ts +0 -0
  2140. /package/src/{memory → persistence}/migrations/291-contact-channels-renormalize-addresses.ts +0 -0
  2141. /package/src/{memory → persistence}/migrations/292-schedule-default-no-reuse-conversation.test.ts +0 -0
  2142. /package/src/{memory → persistence}/migrations/292-schedule-default-no-reuse-conversation.ts +0 -0
  2143. /package/src/{memory → persistence}/migrations/293-workflow-journal-leaf-tokens.ts +0 -0
  2144. /package/src/{memory → persistence}/migrations/294-drop-external-user-id.ts +0 -0
  2145. /package/src/{memory → persistence}/migrations/295-drop-approval-prompt-ts-tracker.ts +0 -0
  2146. /package/src/{memory → persistence}/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +0 -0
  2147. /package/src/{memory → persistence}/migrations/296-rewrite-balanced-economy-profile-pins.ts +0 -0
  2148. /package/src/{memory → persistence}/migrations/297-move-llm-request-logs-to-logs-db.ts +0 -0
  2149. /package/src/{memory → persistence}/migrations/298-move-memory-jobs-to-memory-db.ts +0 -0
  2150. /package/src/{memory → persistence}/migrations/299-canonical-guardian-deliveries-conversation-index.ts +0 -0
  2151. /package/src/{memory → persistence}/migrations/300-add-processing-started-at.ts +0 -0
  2152. /package/src/{memory → persistence}/migrations/__tests__/014-backfill-inbox-thread-state.test.ts +0 -0
  2153. /package/src/{memory → persistence}/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +0 -0
  2154. /package/src/{memory → persistence}/migrations/__tests__/136-drop-assistant-id-columns.test.ts +0 -0
  2155. /package/src/{memory → persistence}/migrations/__tests__/234-memory-v2-activation-logs.test.ts +0 -0
  2156. /package/src/{memory → persistence}/migrations/__tests__/245-memory-retrospective-state.test.ts +0 -0
  2157. /package/src/{memory → persistence}/migrations/__tests__/267-llm-usage-events-add-assistant-version.test.ts +0 -0
  2158. /package/src/{memory → persistence}/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +0 -0
  2159. /package/src/{memory → persistence}/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +0 -0
  2160. /package/src/{memory → persistence}/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +0 -0
  2161. /package/src/{memory → persistence}/migrations/__tests__/297-move-llm-request-logs.test.ts +0 -0
  2162. /package/src/{memory → persistence}/migrations/run-migrations.ts +0 -0
  2163. /package/src/{memory → persistence}/migrations/schema-introspection.ts +0 -0
  2164. /package/src/{memory → persistence}/raw-query.ts +0 -0
  2165. /package/src/{memory → persistence}/schema/a2a.ts +0 -0
  2166. /package/src/{memory → persistence}/schema/bookmarks.ts +0 -0
  2167. /package/src/{memory → persistence}/schema/calls.ts +0 -0
  2168. /package/src/{memory → persistence}/schema/guardian.ts +0 -0
  2169. /package/src/{memory → persistence}/schema/index.ts +0 -0
  2170. /package/src/{memory → persistence}/schema/inference.ts +0 -0
  2171. /package/src/{memory → persistence}/schema/memory-core.ts +0 -0
  2172. /package/src/{memory → persistence}/schema/memory-graph.ts +0 -0
  2173. /package/src/{memory → persistence}/schema/notifications.ts +0 -0
  2174. /package/src/{memory → persistence}/schema/oauth.ts +0 -0
  2175. /package/src/{memory → persistence}/schema/tasks.ts +0 -0
  2176. /package/src/{memory → persistence}/schema.ts +0 -0
  2177. /package/src/{memory → persistence}/usage-buckets.ts +0 -0
  2178. /package/src/{memory → persistence}/usage-grouped-buckets.ts +0 -0
  2179. /package/src/{memory → plugins/defaults/memory}/__tests__/fixtures/memory-v2-activation-fixtures.ts +0 -0
  2180. /package/src/{memory → plugins/defaults/memory}/__tests__/memory-retrospective-trigger-check.test.ts +0 -0
  2181. /package/src/{memory → plugins/defaults/memory}/anisotropy.test.ts +0 -0
  2182. /package/src/{memory → plugins/defaults/memory}/context-search/__tests__/agent-runner-redaction.test.ts +0 -0
  2183. /package/src/{memory → plugins/defaults/memory}/context-search/format.ts +0 -0
  2184. /package/src/{memory → plugins/defaults/memory}/context-search/limits.ts +0 -0
  2185. /package/src/{memory → plugins/defaults/memory}/context-search/sources/workspace.ts +0 -0
  2186. /package/src/{memory → plugins/defaults/memory}/graph/decay.test.ts +0 -0
  2187. /package/src/{memory → plugins/defaults/memory}/graph/extraction.test.ts +0 -0
  2188. /package/src/{memory → plugins/defaults/memory}/graph/injection.test.ts +0 -0
  2189. /package/src/{memory → plugins/defaults/memory}/graph/scoring.test.ts +0 -0
  2190. /package/src/{memory → plugins/defaults/memory}/graph/scoring.ts +0 -0
  2191. /package/src/{memory → plugins/defaults/memory}/graph/serendipity.ts +0 -0
  2192. /package/src/{memory → plugins/defaults/memory}/graph/triggers.test.ts +0 -0
  2193. /package/src/{memory → plugins/defaults/memory}/graph/triggers.ts +0 -0
  2194. /package/src/{memory → plugins/defaults/memory}/graph/types.ts +0 -0
  2195. /package/src/plugins/defaults/{memory-retrieval → memory}/hooks/__tests__/user-prompt-submit.test.ts +0 -0
  2196. /package/src/{memory → plugins/defaults/memory}/memory-marker.ts +0 -0
  2197. /package/src/{memory → plugins/defaults/memory}/prompt-override.ts +0 -0
  2198. /package/src/{memory → plugins/defaults/memory}/retrieval-budget.ts +0 -0
  2199. /package/src/{memory → plugins/defaults/memory}/v2/__tests__/__snapshots__/prompts-router.test.ts.snap +0 -0
  2200. /package/src/{memory → plugins/defaults/memory}/v2/__tests__/concept-page-frontmatter-schema.test.ts +0 -0
  2201. /package/src/{memory → plugins/defaults/memory}/v2/__tests__/edge-index.test.ts +0 -0
  2202. /package/src/{memory → plugins/defaults/memory}/v2/__tests__/harness-metrics.test.ts +0 -0
  2203. /package/src/{memory → plugins/defaults/memory}/v2/__tests__/injected-block-slugs.test.ts +0 -0
  2204. /package/src/{memory → plugins/defaults/memory}/v2/__tests__/page-store.test.ts +0 -0
  2205. /package/src/{memory → plugins/defaults/memory}/v2/__tests__/prompts-router.test.ts +0 -0
  2206. /package/src/{memory → plugins/defaults/memory}/v2/cli-command-content.ts +0 -0
  2207. /package/src/{memory → plugins/defaults/memory}/v2/constants.ts +0 -0
  2208. /package/src/{memory → plugins/defaults/memory}/v2/edge-index.ts +0 -0
  2209. /package/src/{memory → plugins/defaults/memory}/v2/harness/metrics.ts +0 -0
  2210. /package/src/{memory → plugins/defaults/memory}/v2/harness/runner.ts +0 -0
  2211. /package/src/{memory → plugins/defaults/memory}/v2/harness/trace.ts +0 -0
  2212. /package/src/{memory → plugins/defaults/memory}/v2/now-text.ts +0 -0
  2213. /package/src/{memory → plugins/defaults/memory}/v2/prompts/sweep.ts +0 -0
  2214. /package/src/{memory → plugins/defaults/memory}/v2/types.ts +0 -0
  2215. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/__tests__/capabilities.test.ts +0 -0
  2216. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/__tests__/card.test.ts +0 -0
  2217. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/__tests__/fresh-set.test.ts +0 -0
  2218. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/__tests__/section-needle.test.ts +0 -0
  2219. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/__tests__/sections.test.ts +0 -0
  2220. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/__tests__/types.test.ts +0 -0
  2221. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/card.test.ts +0 -0
  2222. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/core-set.test.ts +0 -0
  2223. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/core-set.ts +0 -0
  2224. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/data/README.md +0 -0
  2225. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/data/assignments.json +0 -0
  2226. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/data/core.json +0 -0
  2227. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-x.md +0 -0
  2228. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/data/leaves/domain-a/topic-y.md +0 -0
  2229. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/data/leaves/domain-b/topic-z.md +0 -0
  2230. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/fresh-set.ts +0 -0
  2231. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/llm-retry.ts +0 -0
  2232. /package/src/plugins/defaults/{memory-v3-shadow → memory/v3}/sections.ts +0 -0
  2233. /package/src/{memory → plugins/defaults/memory}/v3-eval/__tests__/eval-packets.test.ts +0 -0
  2234. /package/src/{memory → plugins/defaults/memory}/v3-eval/__tests__/eval-tally.test.ts +0 -0
  2235. /package/src/{memory → plugins/defaults/memory}/v3-eval/eval-tally.ts +0 -0
  2236. /package/src/{memory → plugins/defaults/memory}/validation.ts +0 -0
@@ -0,0 +1,3404 @@
1
+ import { mkdirSync, rmSync } from "node:fs";
2
+
3
+ import {
4
+ and,
5
+ asc,
6
+ count,
7
+ desc,
8
+ eq,
9
+ gt,
10
+ gte,
11
+ inArray,
12
+ isNotNull,
13
+ isNull,
14
+ like,
15
+ lt,
16
+ lte,
17
+ or,
18
+ sql,
19
+ } from "drizzle-orm";
20
+ import { v4 as uuid } from "uuid";
21
+ import { z } from "zod";
22
+
23
+ import type { ChannelId, InterfaceId } from "../channels/types.js";
24
+ import { parseChannelId, parseInterfaceId } from "../channels/types.js";
25
+ import { CHANNEL_IDS, isChannelId } from "../channels/types.js";
26
+ import { findDisplayTurnEndIndex } from "../conversations/message-consolidation.js";
27
+ import { findConversation } from "../daemon/conversation-registry.js";
28
+ import { conversationMetadataSyncTag } from "../daemon/message-types/sync.js";
29
+ import type { TrustContext } from "../daemon/trust-context.js";
30
+ import { clearAllConversationIds } from "../home/feed-writer.js";
31
+ import { getCurrentSeq } from "../runtime/assistant-stream-state.js";
32
+ import { publishSyncInvalidation } from "../runtime/sync/sync-publisher.js";
33
+ import { UserError } from "../util/errors.js";
34
+ import { safeParseRecord } from "../util/json.js";
35
+ import { getLogger } from "../util/logger.js";
36
+ import { getLogsDbPath } from "../util/logs-db-path.js";
37
+ import { getConversationsDir } from "../util/platform.js";
38
+ import { createRowMapper } from "../util/row-mapper.js";
39
+ import { withSqliteRetry } from "../util/sqlite-retry.js";
40
+ import {
41
+ deleteOrphanAttachments,
42
+ linkAttachmentToMessage,
43
+ } from "./attachments-store.js";
44
+ import { AUTO_ANALYSIS_SOURCE } from "./auto-analysis-constants.js";
45
+ import {
46
+ appendCompactionEvent,
47
+ forkCompactionLedger,
48
+ getLatestCompactionEventAtOrBefore,
49
+ } from "./compaction-ledger-store.js";
50
+ import {
51
+ projectAssistantMessage,
52
+ seedForkedConversationAttention,
53
+ } from "./conversation-attention-store.js";
54
+ import {
55
+ initConversationDir,
56
+ removeConversationDir,
57
+ syncMessageToDisk,
58
+ updateMetaFile,
59
+ } from "./conversation-disk-view.js";
60
+ import { ensureDisplayOrderMigration } from "./conversation-display-order-migration.js";
61
+ import { ensureGroupMigration } from "./conversation-group-migration.js";
62
+ import { deleteConversationRowsInBatches } from "./conversation-row-batch-delete.js";
63
+ import { runAsyncSqlite } from "./db-async-query.js";
64
+ import {
65
+ type DrizzleDb,
66
+ getDb,
67
+ getLogsDb,
68
+ getSqliteFrom,
69
+ } from "./db-connection.js";
70
+ import {
71
+ copyForkMessagesViaSubprocess,
72
+ type ForkIdPair,
73
+ } from "./fork-message-copy.js";
74
+ import { getMemoryPersistenceHooks } from "./memory-lifecycle-hooks.js";
75
+ import {
76
+ rawExec,
77
+ rawGet,
78
+ rawLogsRun,
79
+ rawMemoryRun,
80
+ rawRun,
81
+ } from "./raw-query.js";
82
+ import {
83
+ channelInboundEvents,
84
+ conversations,
85
+ llmRequestLogs,
86
+ memoryEmbeddings,
87
+ memorySegments,
88
+ memorySummaries,
89
+ messageAttachments,
90
+ messages,
91
+ skillLoadedEvents,
92
+ toolInvocations,
93
+ } from "./schema/index.js";
94
+
95
+ const log = getLogger("conversation-store");
96
+
97
+ /**
98
+ * The logs connection (`assistant-logs.db`), where `llm_request_logs` lives.
99
+ * Throws if the file cannot be opened — the few call sites here that touch
100
+ * request logs (conversation deletes, turn-window anchoring) have no fallback.
101
+ */
102
+ function logsDb(): DrizzleDb {
103
+ const db = getLogsDb();
104
+ if (!db) {
105
+ throw new Error("logs database unavailable");
106
+ }
107
+ return db;
108
+ }
109
+
110
+ // ── Message metadata Zod schema ──────────────────────────────────────
111
+ // Validates the JSON stored in messages.metadata. Known fields are typed;
112
+ // extra keys are allowed via passthrough so callers can attach ad-hoc data.
113
+
114
+ const channelIdSchema = z.enum(CHANNEL_IDS);
115
+ // Accept both canonical INTERFACE_IDS and the legacy "vellum" alias,
116
+ // normalizing to "web" on read so downstream code only handles canonical IDs.
117
+ const interfaceIdSchema = z
118
+ .string()
119
+ .transform((v) => parseInterfaceId(v))
120
+ .refine((v): v is InterfaceId => v !== null);
121
+
122
+ const subagentNotificationSchema = z.object({
123
+ subagentId: z.string(),
124
+ label: z.string(),
125
+ status: z.enum(["running", "completed", "failed", "aborted"]),
126
+ error: z.string().optional(),
127
+ conversationId: z.string().optional(),
128
+ objective: z.string().optional(),
129
+ });
130
+
131
+ const acpNotificationSchema = z.object({
132
+ acpSessionId: z.string(),
133
+ agent: z.string().optional(),
134
+ });
135
+
136
+ const backgroundToolCompletionMetadataSchema = z.object({
137
+ id: z.string(),
138
+ toolName: z.string(),
139
+ conversationId: z.string(),
140
+ command: z.string(),
141
+ startedAt: z.number(),
142
+ status: z.enum(["completed", "failed", "cancelled"]),
143
+ exitCode: z.number().nullable(),
144
+ output: z.string(),
145
+ completedAt: z.number(),
146
+ });
147
+
148
+ export const messageMetadataSchema = z
149
+ .object({
150
+ userMessageChannel: channelIdSchema.optional(),
151
+ assistantMessageChannel: channelIdSchema.optional(),
152
+ userMessageInterface: interfaceIdSchema.optional(),
153
+ assistantMessageInterface: interfaceIdSchema.optional(),
154
+ /**
155
+ * Optional client-side metadata bag attached to user messages by HTTP
156
+ * header middleware (reads `x-vellum-browser-family`,
157
+ * `x-vellum-browser-version`, `x-vellum-client-os`,
158
+ * `x-vellum-interface-version`). Forwarded verbatim onto
159
+ * `TurnTelemetryEvent.client` for downstream analytics. Kept as a
160
+ * permissive `record` so adding a new client field doesn't require a
161
+ * migration -- dbt can unpack later via JSON_VALUE.
162
+ */
163
+ client: z.record(z.string(), z.unknown()).optional(),
164
+ subagentNotification: subagentNotificationSchema.optional(),
165
+ acpNotification: acpNotificationSchema.optional(),
166
+ /**
167
+ * Trust class of the actor at the time this message was persisted.
168
+ * This is a durable snapshot -- it does NOT change if the actor's
169
+ * trust status changes later. Used by the memory write gate (indexer)
170
+ * and read gate (conversation history loading) to enforce trust-aware access.
171
+ */
172
+ provenanceTrustClass: z
173
+ .enum(["guardian", "trusted_contact", "unverified_contact", "unknown"])
174
+ .optional(),
175
+ provenanceSourceChannel: channelIdSchema.optional(),
176
+ provenanceGuardianExternalUserId: z.string().optional(),
177
+ provenanceRequesterIdentifier: z.string().optional(),
178
+ automated: z.boolean().optional(),
179
+ /**
180
+ * Structured terminal record stamped onto a `<background_event
181
+ * source="background-tool">` wake so the web can rebuild the inline
182
+ * bash/host_bash card from history after a daemon restart.
183
+ */
184
+ backgroundToolCompletion: backgroundToolCompletionMetadataSchema.optional(),
185
+ forkSourceMessageId: z.string().optional(),
186
+ /** Image source paths from desktop attachments, keyed by filename. */
187
+ imageSourcePaths: z.record(z.string(), z.string()).optional(),
188
+ memoryInjectedBlock: z.string().optional(),
189
+ /** Memory-v3 frozen net-new card block (unwrapped) — the v3 counterpart
190
+ * of `memoryInjectedBlock`. A row carries at most one of the two. The key
191
+ * matches the memory plugin's `MEMORY_V3_INJECTED_BLOCK_METADATA_KEY`, kept
192
+ * as a literal here (like `memoryInjectedBlock`) so the storage schema does
193
+ * not import the memory feature. */
194
+ memoryV3InjectedBlock: z.string().optional(),
195
+ turnContextBlock: z.string().optional(),
196
+ pkbSystemReminderBlock: z.string().optional(),
197
+ workspaceBlock: z.string().optional(),
198
+ nowScratchpadBlock: z.string().optional(),
199
+ pkbContextBlock: z.string().optional(),
200
+ memoryV2StaticBlock: z.string().optional(),
201
+ /** `<background_turn>` block (background/scheduled non-interactive turns),
202
+ * rehydrated by `loadFromDb` for reload/fork prefix-cache parity. */
203
+ backgroundTurnBlock: z.string().optional(),
204
+ /** `<channel_capabilities>` block, rehydrated for the same reason. */
205
+ channelCapabilitiesBlock: z.string().optional(),
206
+ /** `<non_interactive_context>` block, rehydrated for the same reason. */
207
+ nonInteractiveContextBlock: z.string().optional(),
208
+ })
209
+ .passthrough();
210
+
211
+ function cloneForkMessageMetadata(
212
+ metadata: string | null,
213
+ sourceMessageId: string,
214
+ ): string {
215
+ if (!metadata) {
216
+ return JSON.stringify({ forkSourceMessageId: sourceMessageId });
217
+ }
218
+
219
+ try {
220
+ const parsed = JSON.parse(metadata);
221
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
222
+ const sourceRecord = parsed as Record<string, unknown>;
223
+ const forkSourceMessageId =
224
+ typeof sourceRecord.forkSourceMessageId === "string"
225
+ ? sourceRecord.forkSourceMessageId
226
+ : sourceMessageId;
227
+ return JSON.stringify({
228
+ ...sourceRecord,
229
+ forkSourceMessageId,
230
+ });
231
+ }
232
+ } catch {
233
+ // Fall through to source-only metadata.
234
+ }
235
+
236
+ return JSON.stringify({ forkSourceMessageId: sourceMessageId });
237
+ }
238
+
239
+ /**
240
+ * Extract provenance metadata fields from a TrustContext.
241
+ * When no guardian context is provided, defaults to 'unknown' because the
242
+ * absence of trust context means we cannot verify trust —
243
+ * callers with actual guardian trust should always supply a real context.
244
+ */
245
+ export function provenanceFromTrustContext(
246
+ ctx: TrustContext | null | undefined,
247
+ ): Record<string, unknown> {
248
+ if (!ctx) return { provenanceTrustClass: "unknown" };
249
+ return {
250
+ provenanceTrustClass: ctx.trustClass,
251
+ provenanceSourceChannel: ctx.sourceChannel,
252
+ provenanceGuardianExternalUserId: ctx.guardianExternalUserId,
253
+ provenanceRequesterIdentifier: ctx.requesterIdentifier,
254
+ };
255
+ }
256
+
257
+ /** Extract image file paths from resolved attachments for message metadata. */
258
+ export function extractImageSourcePaths(
259
+ attachments: ReadonlyArray<{
260
+ filename: string;
261
+ mimeType: string;
262
+ filePath?: string;
263
+ }>,
264
+ ): Record<string, string> | undefined {
265
+ const paths: Record<string, string> = {};
266
+ for (let i = 0; i < attachments.length; i++) {
267
+ const a = attachments[i];
268
+ if (a.filePath && a.mimeType.toLowerCase().startsWith("image/")) {
269
+ paths[`${i}:${a.filename}`] = a.filePath;
270
+ }
271
+ }
272
+ return Object.keys(paths).length > 0 ? paths : undefined;
273
+ }
274
+
275
+ export interface ConversationRow {
276
+ id: string;
277
+ title: string | null;
278
+ createdAt: number;
279
+ updatedAt: number;
280
+ totalInputTokens: number;
281
+ totalOutputTokens: number;
282
+ totalEstimatedCost: number;
283
+ contextSummary: string | null;
284
+ contextCompactedMessageCount: number;
285
+ contextCompactedAt: number | null;
286
+ historyStrippedAt: number | null;
287
+ slackContextCompactionWatermarkTs: string | null;
288
+ slackContextCompactionWatermarkAt: number | null;
289
+ conversationType: string;
290
+ source: string;
291
+ memoryScopeId: string;
292
+ originChannel: string | null;
293
+ originInterface: string | null;
294
+ forkParentConversationId: string | null;
295
+ forkParentMessageId: string | null;
296
+ isAutoTitle: number;
297
+ scheduleJobId: string | null;
298
+ lastMessageAt: number | null;
299
+ archivedAt: number | null;
300
+ surfacedAt: number | null;
301
+ inferenceProfile: string | null;
302
+ inferenceProfileSessionId: string | null;
303
+ inferenceProfileExpiresAt: number | null;
304
+ lastNotifiedInferenceProfile: string | null;
305
+ processingStartedAt: number | null;
306
+ }
307
+
308
+ export const parseConversation = createRowMapper<
309
+ typeof conversations.$inferSelect,
310
+ ConversationRow
311
+ >({
312
+ id: "id",
313
+ title: "title",
314
+ createdAt: "createdAt",
315
+ updatedAt: "updatedAt",
316
+ totalInputTokens: "totalInputTokens",
317
+ totalOutputTokens: "totalOutputTokens",
318
+ totalEstimatedCost: "totalEstimatedCost",
319
+ contextSummary: "contextSummary",
320
+ contextCompactedMessageCount: "contextCompactedMessageCount",
321
+ contextCompactedAt: "contextCompactedAt",
322
+ historyStrippedAt: "historyStrippedAt",
323
+ slackContextCompactionWatermarkTs: "slackContextCompactionWatermarkTs",
324
+ slackContextCompactionWatermarkAt: "slackContextCompactionWatermarkAt",
325
+ conversationType: "conversationType",
326
+ source: "source",
327
+ memoryScopeId: "memoryScopeId",
328
+ originChannel: "originChannel",
329
+ originInterface: "originInterface",
330
+ forkParentConversationId: "forkParentConversationId",
331
+ forkParentMessageId: "forkParentMessageId",
332
+ isAutoTitle: "isAutoTitle",
333
+ scheduleJobId: "scheduleJobId",
334
+ lastMessageAt: "lastMessageAt",
335
+ archivedAt: "archivedAt",
336
+ surfacedAt: "surfacedAt",
337
+ inferenceProfile: "inferenceProfile",
338
+ inferenceProfileSessionId: "inferenceProfileSessionId",
339
+ inferenceProfileExpiresAt: "inferenceProfileExpiresAt",
340
+ lastNotifiedInferenceProfile: "lastNotifiedInferenceProfile",
341
+ processingStartedAt: "processingStartedAt",
342
+ });
343
+
344
+ /** Allowed values for the `role` column on `messages`. */
345
+ export type MessageRole = "user" | "assistant" | "system";
346
+
347
+ export interface MessageRow {
348
+ id: string;
349
+ conversationId: string;
350
+ role: string;
351
+ content: string;
352
+ createdAt: number;
353
+ metadata: string | null;
354
+ clientMessageId: string | null;
355
+ }
356
+
357
+ const parseMessage = createRowMapper<typeof messages.$inferSelect, MessageRow>({
358
+ id: "id",
359
+ conversationId: "conversationId",
360
+ role: "role",
361
+ content: "content",
362
+ createdAt: "createdAt",
363
+ metadata: "metadata",
364
+ clientMessageId: "clientMessageId",
365
+ });
366
+
367
+ export type ConversationCreateType = "standard" | "background" | "scheduled";
368
+
369
+ /**
370
+ * Monotonic timestamp source for message ordering. Two messages saved within
371
+ * the same millisecond (e.g., tool_results user message + assistant message in
372
+ * message_complete) would get the same Date.now(), making their reload order
373
+ * non-deterministic. This counter ensures every call returns a strictly
374
+ * increasing value so insertion order is always preserved.
375
+ */
376
+ let lastTimestamp = 0;
377
+ function monotonicNow(): number {
378
+ const now = Date.now();
379
+ lastTimestamp = Math.max(now, lastTimestamp + 1);
380
+ return lastTimestamp;
381
+ }
382
+
383
+ // ── insertMessageCore ─────────────────────────────────────────────────
384
+
385
+ /** Shape returned by {@link insertMessageCore} and its public wrappers. */
386
+ interface InsertedMessage {
387
+ id: string;
388
+ conversationId: string;
389
+ role: MessageRole;
390
+ content: string;
391
+ createdAt: number;
392
+ metadata?: string;
393
+ clientMessageId?: string;
394
+ deduplicated: boolean;
395
+ }
396
+
397
+ interface InsertMessageCoreParams {
398
+ conversationId: string;
399
+ role: MessageRole;
400
+ content: string;
401
+ metadata?: Record<string, unknown>;
402
+ clientMessageId?: string;
403
+ }
404
+
405
+ /**
406
+ * Core message persistence primitive shared by {@link addMessage} and
407
+ * {@link reserveMessage}.
408
+ *
409
+ * Inserts a message row inside a transaction that atomically bumps the
410
+ * parent conversation's `updatedAt` / `lastMessageAt` timestamps and
411
+ * conditionally sets the conversation's `originChannel` when the first
412
+ * channel-originated message arrives.
413
+ *
414
+ * When a `clientMessageId` is provided the insert runs inside a
415
+ * SAVEPOINT. If the partial unique index on
416
+ * `(conversation_id, client_message_id)` raises
417
+ * `SQLITE_CONSTRAINT_UNIQUE`, the SAVEPOINT is rolled back, the
418
+ * existing row is fetched, and returned with `deduplicated: true`.
419
+ * This makes the operation idempotent for client-generated
420
+ * correlation nonces.
421
+ *
422
+ * Retries up to 3 times on `SQLITE_BUSY*` / `SQLITE_IOERR*` to handle
423
+ * WAL contention. The timestamp is recomputed each attempt so a late
424
+ * retry doesn't persist a stale `updatedAt`.
425
+ */
426
+ async function insertMessageCore(
427
+ params: InsertMessageCoreParams,
428
+ ): Promise<InsertedMessage> {
429
+ const { conversationId, role, content, metadata, clientMessageId } = params;
430
+ const db = getDb();
431
+ const messageId = uuid();
432
+
433
+ if (metadata) {
434
+ const result = messageMetadataSchema.safeParse(metadata);
435
+ if (!result.success) {
436
+ log.warn(
437
+ { conversationId, messageId, issues: result.error.issues },
438
+ "Invalid message metadata, storing as-is",
439
+ );
440
+ }
441
+ }
442
+
443
+ const metadataStr = metadata ? JSON.stringify(metadata) : undefined;
444
+ const originChannelCandidate =
445
+ metadata && isChannelId(metadata.userMessageChannel)
446
+ ? metadata.userMessageChannel
447
+ : null;
448
+
449
+ // The timestamp is recomputed each attempt so a late retry doesn't persist a
450
+ // stale `updatedAt`.
451
+ return withSqliteRetry(
452
+ (): InsertedMessage => {
453
+ const now = monotonicNow();
454
+ const values = {
455
+ id: messageId,
456
+ conversationId,
457
+ role,
458
+ content,
459
+ createdAt: now,
460
+ ...(metadataStr ? { metadata: metadataStr } : {}),
461
+ ...(clientMessageId ? { clientMessageId } : {}),
462
+ };
463
+
464
+ if (clientMessageId) {
465
+ // Idempotent insert: skip silently if this clientMessageId was
466
+ // already persisted for the conversation.
467
+ const raw = getSqliteFrom(db);
468
+ raw.exec("SAVEPOINT insert_msg");
469
+ try {
470
+ db.insert(messages).values(values).run();
471
+ if (originChannelCandidate) {
472
+ db.update(conversations)
473
+ .set({ originChannel: originChannelCandidate })
474
+ .where(
475
+ and(
476
+ eq(conversations.id, conversationId),
477
+ isNull(conversations.originChannel),
478
+ ),
479
+ )
480
+ .run();
481
+ }
482
+ db.update(conversations)
483
+ .set({ updatedAt: now, lastMessageAt: now })
484
+ .where(eq(conversations.id, conversationId))
485
+ .run();
486
+ raw.exec("RELEASE insert_msg");
487
+ } catch (insertErr) {
488
+ raw.exec("ROLLBACK TO insert_msg");
489
+ raw.exec("RELEASE insert_msg");
490
+ const code = (insertErr as { code?: string }).code ?? "";
491
+ if (code === "SQLITE_CONSTRAINT_UNIQUE") {
492
+ // Duplicate clientMessageId — return the existing row.
493
+ const existing = db
494
+ .select()
495
+ .from(messages)
496
+ .where(
497
+ and(
498
+ eq(messages.conversationId, conversationId),
499
+ eq(messages.clientMessageId, clientMessageId),
500
+ ),
501
+ )
502
+ .get();
503
+ if (existing) {
504
+ return {
505
+ id: existing.id,
506
+ conversationId: existing.conversationId,
507
+ role: existing.role as MessageRole,
508
+ content: existing.content,
509
+ createdAt: existing.createdAt,
510
+ ...(existing.metadata ? { metadata: existing.metadata } : {}),
511
+ clientMessageId: existing.clientMessageId ?? undefined,
512
+ deduplicated: true,
513
+ };
514
+ }
515
+ }
516
+ throw insertErr;
517
+ }
518
+ } else {
519
+ // No clientMessageId — standard insert inside a transaction.
520
+ db.transaction((tx) => {
521
+ tx.insert(messages).values(values).run();
522
+ if (originChannelCandidate) {
523
+ tx.update(conversations)
524
+ .set({ originChannel: originChannelCandidate })
525
+ .where(
526
+ and(
527
+ eq(conversations.id, conversationId),
528
+ isNull(conversations.originChannel),
529
+ ),
530
+ )
531
+ .run();
532
+ }
533
+ tx.update(conversations)
534
+ .set({ updatedAt: now, lastMessageAt: now })
535
+ .where(eq(conversations.id, conversationId))
536
+ .run();
537
+ });
538
+ }
539
+
540
+ return {
541
+ id: messageId,
542
+ conversationId,
543
+ role,
544
+ content,
545
+ createdAt: now,
546
+ ...(metadataStr ? { metadata: metadataStr } : {}),
547
+ ...(clientMessageId ? { clientMessageId } : {}),
548
+ deduplicated: false,
549
+ };
550
+ },
551
+ { op: "insertMessageCore", context: { conversationId } },
552
+ );
553
+ }
554
+
555
+ export function createConversation(
556
+ titleOrOpts?:
557
+ | string
558
+ | {
559
+ title?: string;
560
+ /**
561
+ * Override the `is_auto_title` column (schema default 1). Pass
562
+ * `AUTO_TITLE_DETERMINISTIC` (2) when the title was derived
563
+ * deterministically at bootstrap so later generation passes know
564
+ * they may replace it.
565
+ */
566
+ isAutoTitle?: number;
567
+ conversationType?: ConversationCreateType;
568
+ source?: string;
569
+ scheduleJobId?: string;
570
+ groupId?: string;
571
+ forkParentConversationId?: string;
572
+ },
573
+ ) {
574
+ const db = getDb();
575
+ const now = Date.now();
576
+ const initialSeq = getCurrentSeq();
577
+ const opts =
578
+ typeof titleOrOpts === "string"
579
+ ? { title: titleOrOpts }
580
+ : (titleOrOpts ?? {});
581
+ const requestedConversationType = opts.conversationType;
582
+ const conversationType: ConversationCreateType =
583
+ requestedConversationType ?? "standard";
584
+ const source = opts.source ?? "user";
585
+ const groupId = opts.groupId;
586
+ const id = uuid();
587
+ const memoryScopeId = "default";
588
+
589
+ // Ensure group_id column exists for deterministic schema readiness,
590
+ // even when this conversation has no groupId (a subsequent query or
591
+ // reorder may reference the column).
592
+ ensureGroupMigration();
593
+
594
+ const conversation = {
595
+ id,
596
+ title: opts.title ?? null,
597
+ ...(opts.isAutoTitle !== undefined
598
+ ? { isAutoTitle: opts.isAutoTitle }
599
+ : {}),
600
+ createdAt: now,
601
+ updatedAt: now,
602
+ totalInputTokens: 0,
603
+ totalOutputTokens: 0,
604
+ totalEstimatedCost: 0,
605
+ contextSummary: null as string | null,
606
+ contextCompactedMessageCount: 0,
607
+ contextCompactedAt: null as number | null,
608
+ slackContextCompactionWatermarkTs: null as string | null,
609
+ slackContextCompactionWatermarkAt: null as number | null,
610
+ conversationType,
611
+ source,
612
+ memoryScopeId,
613
+ scheduleJobId: opts.scheduleJobId ?? null,
614
+ forkParentConversationId: opts.forkParentConversationId ?? null,
615
+ // Snapshot↔stream alignment baseline, captured at the creation instant.
616
+ // 0 (nothing stamped yet this process) is stored as NULL so `/messages`
617
+ // reports null and the client cold-starts rather than aligning to seq 0.
618
+ seq: initialSeq > 0 ? initialSeq : null,
619
+ };
620
+
621
+ // Insert the row and set its group_id (raw-SQL-only, not in the Drizzle
622
+ // schema, so it's a second statement) atomically. A SAVEPOINT — not
623
+ // `db.transaction()` — because createConversation also runs INSIDE the fork
624
+ // paths' transactions, where a nested `BEGIN` would error; a savepoint nests
625
+ // cleanly and is still atomic at the top level. createConversation is a
626
+ // synchronous primitive and does not retry on contention: callers on
627
+ // write-contended paths wrap the call in `withSqliteRetry`, and because the
628
+ // insert+update are atomic here, such a retry re-runs the whole thing cleanly
629
+ // (a failed attempt rolls back, so no half-written row is ever left behind).
630
+ const effectiveGroupId = groupId ?? "system:all";
631
+ const raw = getSqliteFrom(db);
632
+ raw.exec("SAVEPOINT create_conv");
633
+ try {
634
+ db.insert(conversations).values(conversation).run();
635
+ rawRun(
636
+ "UPDATE conversations SET group_id = ?, is_pinned = ? WHERE id = ?",
637
+ effectiveGroupId,
638
+ effectiveGroupId === "system:pinned" ? 1 : 0,
639
+ id,
640
+ );
641
+ raw.exec("RELEASE create_conv");
642
+ } catch (err) {
643
+ raw.exec("ROLLBACK TO create_conv");
644
+ raw.exec("RELEASE create_conv");
645
+ throw err;
646
+ }
647
+
648
+ initConversationDir({ ...conversation, originChannel: null });
649
+
650
+ return conversation;
651
+ }
652
+
653
+ export function getConversation(id: string): ConversationRow | null {
654
+ const db = getDb();
655
+ const row = db
656
+ .select()
657
+ .from(conversations)
658
+ .where(eq(conversations.id, id))
659
+ .get();
660
+ return row ? parseConversation(row) : null;
661
+ }
662
+
663
+ /**
664
+ * Count conversations that reference a given schedule job ID.
665
+ * Useful for determining whether a schedule can be safely deleted
666
+ * (i.e. no other conversations still reference it).
667
+ */
668
+ export function countConversationsByScheduleJobId(
669
+ scheduleJobId: string,
670
+ ): number {
671
+ return (
672
+ rawGet<{ c: number }>(
673
+ "SELECT COUNT(*) AS c FROM conversations WHERE schedule_job_id = ?",
674
+ scheduleJobId,
675
+ )?.c ?? 0
676
+ );
677
+ }
678
+
679
+ /**
680
+ * Find the rolling analysis conversation for a given source conversation,
681
+ * or null if none exists yet. Used by the auto-analyze loop to append
682
+ * to an existing analysis conversation rather than creating a new one
683
+ * each time the analyze job fires.
684
+ *
685
+ * Returns the most recently updated match if multiple exist (defensive —
686
+ * shouldn't happen in normal operation but the contract is well-defined).
687
+ *
688
+ * Hits `idx_conversations_fork_parent_conversation_id` for the
689
+ * `forkParentConversationId` lookup.
690
+ */
691
+ export function findAnalysisConversationFor(
692
+ parentConversationId: string,
693
+ ): { id: string } | null {
694
+ const db = getDb();
695
+ const row = db
696
+ .select({ id: conversations.id })
697
+ .from(conversations)
698
+ .where(
699
+ and(
700
+ eq(conversations.source, AUTO_ANALYSIS_SOURCE),
701
+ eq(conversations.forkParentConversationId, parentConversationId),
702
+ ),
703
+ )
704
+ .orderBy(desc(conversations.updatedAt))
705
+ .limit(1)
706
+ .get();
707
+ return row ? { id: row.id } : null;
708
+ }
709
+
710
+ /**
711
+ * Returns the `source` column for the given conversation, or null if
712
+ * not found. Tiny convenience used by the recursion guard in the
713
+ * auto-analyze loop.
714
+ */
715
+ export function getConversationSource(conversationId: string): string | null {
716
+ const db = getDb();
717
+ const row = db
718
+ .select({ source: conversations.source })
719
+ .from(conversations)
720
+ .where(eq(conversations.id, conversationId))
721
+ .get();
722
+ return row?.source ?? null;
723
+ }
724
+
725
+ /**
726
+ * Fetch group_id for a conversation via raw SQL. group_id is NOT in the
727
+ * Drizzle schema (raw-query-only pattern), so ConversationRow doesn't
728
+ * include it. This helper is used by forkConversation to inherit group_id.
729
+ */
730
+ function getConversationGroupId(conversationId: string): string | null {
731
+ ensureGroupMigration();
732
+ const row = rawGet<{ group_id: string | null }>(
733
+ "SELECT group_id FROM conversations WHERE id = ?",
734
+ conversationId,
735
+ );
736
+ return row?.group_id ?? null;
737
+ }
738
+
739
+ export function forkConversation(params: {
740
+ conversationId: string;
741
+ throughMessageId?: string;
742
+ /**
743
+ * Override the fork's `source` column. Defaults to the standard
744
+ * `createConversation` default (`"user"`). Used by fork-based memory
745
+ * retrospectives to mark the fork as a retrospective artifact distinct
746
+ * from a user-initiated fork, so dedup and cleanup queries can scope
747
+ * correctly.
748
+ */
749
+ source?: string;
750
+ /**
751
+ * Optional title for the fork. Defaults to `<parent title> (Fork)`.
752
+ */
753
+ title?: string;
754
+ /**
755
+ * Override the fork's `conversationType` column. Defaults to `"standard"`.
756
+ * Used by fork-based memory retrospectives to bucket the fork as a
757
+ * `"background"` conversation so it doesn't surface in the user's
758
+ * conversation list.
759
+ */
760
+ conversationType?: ConversationCreateType;
761
+ /**
762
+ * Override the fork's `groupId`. Defaults to the parent conversation's
763
+ * group (or `"system:all"` when the parent has none). Used by fork-based
764
+ * memory retrospectives to route the fork into a dedicated background
765
+ * group.
766
+ */
767
+ groupId?: string;
768
+ }): ConversationRow {
769
+ const { conversationId, throughMessageId } = params;
770
+ const db = getDb();
771
+ const sourceConversation = getConversation(conversationId);
772
+
773
+ if (!sourceConversation) {
774
+ throw new UserError(`Conversation ${conversationId} not found`);
775
+ }
776
+ const sourceMessages = getMessages(conversationId);
777
+ if (throughMessageId != null) {
778
+ // `getMessages` orders by `createdAt` only; when rows share an identical
779
+ // millisecond timestamp the tie order is unspecified. Callers that pin the
780
+ // fork to a cutoff choose it from a `(createdAt, id)` cursor (e.g. the
781
+ // memory-retrospective job, via `getMessagesAfter`), so slicing through
782
+ // `throughMessageId` under the unstable order could include same-timestamp
783
+ // siblings the cursor considers *after* the cutoff (reprocessed next run)
784
+ // or exclude ones it considers *before* it (skipped forever). Re-sort on
785
+ // `(createdAt, id)` so the slice agrees with the cutoff. The unpinned full
786
+ // fork copies every row regardless of order, so it keeps source order.
787
+ sourceMessages.sort(
788
+ (a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id),
789
+ );
790
+ }
791
+
792
+ if (sourceMessages.length === 0) {
793
+ throw new UserError(
794
+ `Conversation ${conversationId} has no persisted messages to fork`,
795
+ );
796
+ }
797
+
798
+ const initialBoundaryIndex =
799
+ throughMessageId == null
800
+ ? sourceMessages.length - 1
801
+ : sourceMessages.findIndex((message) => message.id === throughMessageId);
802
+
803
+ if (throughMessageId != null && initialBoundaryIndex === -1) {
804
+ throw new UserError(
805
+ `Message ${throughMessageId} does not belong to conversation ${conversationId}`,
806
+ );
807
+ }
808
+
809
+ // Extend the boundary to cover the full display turn the client
810
+ // addressed. The read-path collapses each assistant turn across
811
+ // multiple DB rows — consecutive assistant rows AND tool-result-only
812
+ // user rows between them — so "fork through message X" semantically
813
+ // means "fork through the entire display turn containing X" no matter
814
+ // which DB row in the cluster the client supplied. Single source of
815
+ // truth is `findDisplayTurnEndIndex`, shared with the read path so
816
+ // both stay in sync.
817
+ const copyBoundaryIndex = findDisplayTurnEndIndex(
818
+ sourceMessages,
819
+ initialBoundaryIndex,
820
+ );
821
+
822
+ const messagesToCopy =
823
+ copyBoundaryIndex >= 0
824
+ ? sourceMessages.slice(0, copyBoundaryIndex + 1)
825
+ : ([] as MessageRow[]);
826
+
827
+ // Inherit the history-strip marker only when the fork boundary is at-or-
828
+ // after the strip event. Pre-strip forks branch from history that pre-
829
+ // dates the strip, so the marker would be a no-op and is misleading to
830
+ // copy.
831
+ const sourceHistoryStrippedAt = sourceConversation.historyStrippedAt ?? null;
832
+ const boundaryMessageCreatedAt = messagesToCopy.at(-1)?.createdAt ?? null;
833
+ const inheritsHistoryStrippedAt =
834
+ sourceHistoryStrippedAt != null &&
835
+ boundaryMessageCreatedAt != null &&
836
+ boundaryMessageCreatedAt >= sourceHistoryStrippedAt;
837
+
838
+ // Inherit compaction by the same temporal rule: apply the most recent
839
+ // compaction whose event time is at-or-before the forked-from message. A
840
+ // compaction that ran after the boundary message did not exist at that point
841
+ // in the conversation, so the fork branches from full uncompacted history.
842
+ const inheritedCompaction = getLatestCompactionEventAtOrBefore(
843
+ sourceConversation.id,
844
+ boundaryMessageCreatedAt,
845
+ );
846
+ // The Slack chronological-context watermark is single-valued on the source
847
+ // row and reflects only the latest compaction, so carry it only when the
848
+ // fork inherits that latest compaction. Pairing the latest watermark with an
849
+ // older inherited summary (a fork between two compactions) would filter out
850
+ // Slack messages the older summary does not cover.
851
+ const inheritsLatestCompaction =
852
+ inheritedCompaction != null &&
853
+ inheritedCompaction.compactedAt === sourceConversation.contextCompactedAt;
854
+ const forkParentMessageId = messagesToCopy.at(-1)?.id ?? null;
855
+ const forkTitle =
856
+ params.title ?? `${sourceConversation.title ?? "Untitled"} (Fork)`;
857
+
858
+ // Collect disk-sync work to run after the transaction commits.
859
+ const diskSyncQueue: Array<{
860
+ conversationId: string;
861
+ messageId: string;
862
+ createdAt: number;
863
+ }> = [];
864
+
865
+ // Wrap all DB mutations in a single transaction so a mid-flight failure
866
+ // rolls back cleanly instead of leaving a partial fork. Helper functions
867
+ // (linkAttachmentToMessage, relinkAttachments, seedForkedConversationAttention)
868
+ // use the same underlying bun:sqlite connection, so their writes participate
869
+ // in this transaction automatically.
870
+ // Inherit group_id from parent via raw SQL helper (group_id is not in Drizzle schema)
871
+ const parentGroupId = getConversationGroupId(conversationId);
872
+
873
+ const forkedConversation = db.transaction(() => {
874
+ const fc = createConversation({
875
+ title: forkTitle,
876
+ conversationType: params.conversationType ?? "standard",
877
+ groupId: params.groupId ?? parentGroupId ?? "system:all",
878
+ ...(params.source != null ? { source: params.source } : {}),
879
+ });
880
+
881
+ db.update(conversations)
882
+ .set({
883
+ forkParentConversationId: sourceConversation.id,
884
+ forkParentMessageId,
885
+ contextSummary: inheritedCompaction?.summary ?? null,
886
+ contextCompactedMessageCount:
887
+ inheritedCompaction?.compactedMessageCount ?? 0,
888
+ contextCompactedAt: inheritedCompaction?.compactedAt ?? null,
889
+ slackContextCompactionWatermarkTs: inheritsLatestCompaction
890
+ ? sourceConversation.slackContextCompactionWatermarkTs
891
+ : null,
892
+ slackContextCompactionWatermarkAt: inheritsLatestCompaction
893
+ ? sourceConversation.slackContextCompactionWatermarkAt
894
+ : null,
895
+ historyStrippedAt: inheritsHistoryStrippedAt
896
+ ? sourceHistoryStrippedAt
897
+ : null,
898
+ inferenceProfile: sourceConversation.inferenceProfile,
899
+ })
900
+ .where(eq(conversations.id, fc.id))
901
+ .run();
902
+
903
+ const forkedMessageIds = new Map<string, string>();
904
+ let latestForkedAssistant: {
905
+ messageId: string;
906
+ messageAt: number;
907
+ } | null = null;
908
+
909
+ const forkedMessageValues = messagesToCopy.map((message) => {
910
+ const forkedMessageId = uuid();
911
+ forkedMessageIds.set(message.id, forkedMessageId);
912
+
913
+ if (message.role === "assistant") {
914
+ latestForkedAssistant = {
915
+ messageId: forkedMessageId,
916
+ messageAt: message.createdAt,
917
+ };
918
+ }
919
+
920
+ return {
921
+ id: forkedMessageId,
922
+ conversationId: fc.id,
923
+ role: message.role,
924
+ content: message.content,
925
+ createdAt: message.createdAt,
926
+ metadata: cloneForkMessageMetadata(message.metadata, message.id),
927
+ };
928
+ });
929
+
930
+ // Insert in chunks of one multi-row statement each so a large fork takes
931
+ // the SQLite write lock O(messages / chunk) times instead of once per row.
932
+ const FORK_INSERT_CHUNK_SIZE = 200;
933
+ for (
934
+ let i = 0;
935
+ i < forkedMessageValues.length;
936
+ i += FORK_INSERT_CHUNK_SIZE
937
+ ) {
938
+ const chunk = forkedMessageValues.slice(i, i + FORK_INSERT_CHUNK_SIZE);
939
+ db.insert(messages).values(chunk).run();
940
+ }
941
+
942
+ populateForkContentsInProcess({
943
+ fork: fc,
944
+ sourceConversationId: sourceConversation.id,
945
+ messagesToCopy,
946
+ forkedMessageIds,
947
+ latestForkedAssistant,
948
+ isFullHistoryFork: copyBoundaryIndex === sourceMessages.length - 1,
949
+ inheritedCompactedMessageCount:
950
+ inheritedCompaction?.compactedMessageCount ?? 0,
951
+ diskSyncQueue,
952
+ });
953
+
954
+ return fc;
955
+ });
956
+
957
+ // Disk-view sync runs after commit — file I/O is idempotent and
958
+ // conversation deletion cleans up orphaned directories.
959
+ for (const entry of diskSyncQueue) {
960
+ syncMessageToDisk(entry.conversationId, entry.messageId, entry.createdAt);
961
+ }
962
+
963
+ const persistedFork = getConversation(forkedConversation.id);
964
+ if (!persistedFork) {
965
+ throw new Error(
966
+ `Failed to load forked conversation ${forkedConversation.id} after creation`,
967
+ );
968
+ }
969
+
970
+ return persistedFork;
971
+ }
972
+
973
+ interface PopulateForkContentsArgs {
974
+ /** The freshly-created fork conversation row (needs `id` + `createdAt`). */
975
+ fork: { id: string; createdAt: number };
976
+ sourceConversationId: string;
977
+ messagesToCopy: MessageRow[];
978
+ /** Source→fork message-id map for every copied row. */
979
+ forkedMessageIds: Map<string, string>;
980
+ latestForkedAssistant: { messageId: string; messageAt: number } | null;
981
+ /** `copyBoundaryIndex === sourceMessages.length - 1` for the source. */
982
+ isFullHistoryFork: boolean;
983
+ /**
984
+ * Count of leading messages behind the compaction event this fork inherits
985
+ * (0 when the fork branches from uncompacted history). Memory-slug seeding
986
+ * skips this prefix, since rows behind the fork's summary are not rendered
987
+ * and must stay re-injectable.
988
+ */
989
+ inheritedCompactedMessageCount: number;
990
+ /**
991
+ * When provided, a disk-sync entry is appended per copied message for the
992
+ * caller to flush after commit. Omitted by the retrospective fork, whose
993
+ * throwaway conversation needs no disk-view projection.
994
+ */
995
+ diskSyncQueue?: Array<{
996
+ conversationId: string;
997
+ messageId: string;
998
+ createdAt: number;
999
+ }>;
1000
+ }
1001
+
1002
+ /**
1003
+ * In-process tail of a conversation fork: relink each copied message's
1004
+ * attachments (scoped per-conversation), set `lastMessageAt`, seed attention,
1005
+ * and carry the parent's per-conversation memory state.
1006
+ *
1007
+ * Assumes the fork's message ROWS already exist — copied either in-process
1008
+ * (synchronous fork) or via the off-event-loop subprocess (retrospective
1009
+ * fork). Single source of truth shared by `forkConversation` and
1010
+ * `forkConversationForRetrospective`; must run inside a transaction on the
1011
+ * main connection.
1012
+ */
1013
+ function populateForkContentsInProcess(args: PopulateForkContentsArgs): void {
1014
+ const {
1015
+ fork,
1016
+ sourceConversationId,
1017
+ messagesToCopy,
1018
+ forkedMessageIds,
1019
+ latestForkedAssistant,
1020
+ isFullHistoryFork,
1021
+ inheritedCompactedMessageCount,
1022
+ diskSyncQueue,
1023
+ } = args;
1024
+ const db = getDb();
1025
+
1026
+ const attachmentIdMap = new Map<string, string>();
1027
+ for (const message of messagesToCopy) {
1028
+ const forkedMessageId = forkedMessageIds.get(message.id);
1029
+ if (!forkedMessageId) continue;
1030
+
1031
+ const attachmentLinks = db
1032
+ .select({
1033
+ attachmentId: messageAttachments.attachmentId,
1034
+ position: messageAttachments.position,
1035
+ })
1036
+ .from(messageAttachments)
1037
+ .where(eq(messageAttachments.messageId, message.id))
1038
+ .orderBy(messageAttachments.position)
1039
+ .all();
1040
+ const uncachedAttachmentLinks = attachmentLinks.filter(
1041
+ (link) => !attachmentIdMap.has(link.attachmentId),
1042
+ );
1043
+ const stagingMessageId = uncachedAttachmentLinks.length > 0 ? uuid() : null;
1044
+
1045
+ if (stagingMessageId) {
1046
+ db.insert(messages)
1047
+ .values({
1048
+ id: stagingMessageId,
1049
+ conversationId: fork.id,
1050
+ role: message.role,
1051
+ content: "",
1052
+ createdAt: message.createdAt,
1053
+ metadata: null,
1054
+ })
1055
+ .run();
1056
+ }
1057
+
1058
+ for (const link of attachmentLinks) {
1059
+ const cachedAttachmentId = attachmentIdMap.get(link.attachmentId);
1060
+ if (cachedAttachmentId) {
1061
+ db.insert(messageAttachments)
1062
+ .values({
1063
+ id: uuid(),
1064
+ messageId: forkedMessageId,
1065
+ attachmentId: cachedAttachmentId,
1066
+ position: link.position,
1067
+ createdAt: Date.now(),
1068
+ })
1069
+ .run();
1070
+ continue;
1071
+ }
1072
+
1073
+ const scopedAttachmentId = linkAttachmentToMessage(
1074
+ stagingMessageId ?? forkedMessageId,
1075
+ link.attachmentId,
1076
+ link.position,
1077
+ );
1078
+ attachmentIdMap.set(link.attachmentId, scopedAttachmentId);
1079
+ }
1080
+
1081
+ if (stagingMessageId) {
1082
+ relinkAttachments([stagingMessageId], forkedMessageId);
1083
+ db.delete(messages).where(eq(messages.id, stagingMessageId)).run();
1084
+ }
1085
+
1086
+ diskSyncQueue?.push({
1087
+ conversationId: fork.id,
1088
+ messageId: forkedMessageId,
1089
+ createdAt: fork.createdAt,
1090
+ });
1091
+ }
1092
+
1093
+ // Set lastMessageAt to the max createdAt of copied messages so the
1094
+ // forked conversation sorts correctly by message recency.
1095
+ const lastCopiedMessage = messagesToCopy.at(-1);
1096
+ if (lastCopiedMessage) {
1097
+ db.update(conversations)
1098
+ .set({ lastMessageAt: lastCopiedMessage.createdAt })
1099
+ .where(eq(conversations.id, fork.id))
1100
+ .run();
1101
+ }
1102
+
1103
+ seedForkedConversationAttention({
1104
+ conversationId: fork.id,
1105
+ latestAssistantMessageId: latestForkedAssistant?.messageId ?? null,
1106
+ latestAssistantMessageAt: latestForkedAssistant?.messageAt ?? null,
1107
+ });
1108
+
1109
+ // Carry the parent's per-conversation memory state into the child (activation
1110
+ // and injection logs, graph state, retrospective state). Runs synchronously
1111
+ // inside the fork transaction; a no-op when memory is absent or disabled.
1112
+ getMemoryPersistenceHooks().onConversationForked({
1113
+ db,
1114
+ sourceConversationId,
1115
+ forkId: fork.id,
1116
+ isFullHistoryFork,
1117
+ messagesToCopy,
1118
+ forkedMessageIds,
1119
+ inheritedCompactedMessageCount,
1120
+ });
1121
+
1122
+ // Carry the source's compaction events that predate the fork boundary so the
1123
+ // fork owns a correct ledger for its own future forks/compactions.
1124
+ forkCompactionLedger(
1125
+ db,
1126
+ sourceConversationId,
1127
+ fork.id,
1128
+ messagesToCopy.at(-1)?.createdAt ?? null,
1129
+ );
1130
+ }
1131
+
1132
+ /**
1133
+ * Resolve the fork id + timestamp of the LAST assistant message among the
1134
+ * copied rows. The synchronous copy loop tracks this inline; the off-loop
1135
+ * subprocess copy does not, so the async fork derives it from the id map.
1136
+ */
1137
+ function latestForkedAssistantFrom(
1138
+ messagesToCopy: MessageRow[],
1139
+ forkedMessageIds: Map<string, string>,
1140
+ ): { messageId: string; messageAt: number } | null {
1141
+ for (let i = messagesToCopy.length - 1; i >= 0; i--) {
1142
+ const message = messagesToCopy[i]!;
1143
+ if (message.role !== "assistant") continue;
1144
+ const forkedMessageId = forkedMessageIds.get(message.id);
1145
+ if (forkedMessageId) {
1146
+ return { messageId: forkedMessageId, messageAt: message.createdAt };
1147
+ }
1148
+ }
1149
+ return null;
1150
+ }
1151
+
1152
+ /**
1153
+ * Async variant of {@link forkConversation} for the memory-retrospective job,
1154
+ * which forks the entire source conversation into a throwaway background
1155
+ * conversation on a hot path that must not stall the daemon.
1156
+ *
1157
+ * The dominant cost — copying every source message row — runs OFF the event
1158
+ * loop in a `sqlite3` subprocess (see {@link copyForkMessagesViaSubprocess}),
1159
+ * so `/healthz` and gateway IPC stay responsive during the copy. The cheap
1160
+ * tail (conversation row, attachment relink, memory-state seeding) runs
1161
+ * in-process and reuses {@link populateForkContentsInProcess}, the same helper
1162
+ * the synchronous fork uses, so the two paths cannot drift on that logic. The
1163
+ * boundary/compaction computation mirrors {@link forkConversation} and is
1164
+ * pinned by a parity test.
1165
+ *
1166
+ * The disk-view projection (`syncMessageToDisk`) is intentionally skipped: the
1167
+ * fork is GC'd after the retrospective pass and never browsed, and the agent
1168
+ * reads it from the database, not the on-disk JSONL.
1169
+ *
1170
+ * Atomicity spans two connections (the in-process row/tail and the subprocess
1171
+ * copy), so a mid-flight failure can leave a partial fork. The partial is
1172
+ * deleted best-effort on error; a crash between phases is reclaimed by the
1173
+ * worker's startup orphan sweep. Callers only ever observe a fully-built fork
1174
+ * because the returned promise resolves after every phase commits.
1175
+ */
1176
+ export async function forkConversationForRetrospective(params: {
1177
+ conversationId: string;
1178
+ throughMessageId?: string;
1179
+ source?: string;
1180
+ title?: string;
1181
+ conversationType?: ConversationCreateType;
1182
+ groupId?: string;
1183
+ }): Promise<ConversationRow> {
1184
+ const { conversationId, throughMessageId } = params;
1185
+ const db = getDb();
1186
+ const sourceConversation = getConversation(conversationId);
1187
+ if (!sourceConversation) {
1188
+ throw new UserError(`Conversation ${conversationId} not found`);
1189
+ }
1190
+
1191
+ const sourceMessages = getMessages(conversationId);
1192
+ if (throughMessageId != null) {
1193
+ // Re-sort on `(createdAt, id)` so the cutoff slice agrees with the cursor
1194
+ // the caller chose it from — see the matching note in `forkConversation`.
1195
+ sourceMessages.sort(
1196
+ (a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id),
1197
+ );
1198
+ }
1199
+ if (sourceMessages.length === 0) {
1200
+ throw new UserError(
1201
+ `Conversation ${conversationId} has no persisted messages to fork`,
1202
+ );
1203
+ }
1204
+
1205
+ const initialBoundaryIndex =
1206
+ throughMessageId == null
1207
+ ? sourceMessages.length - 1
1208
+ : sourceMessages.findIndex((message) => message.id === throughMessageId);
1209
+ if (throughMessageId != null && initialBoundaryIndex === -1) {
1210
+ throw new UserError(
1211
+ `Message ${throughMessageId} does not belong to conversation ${conversationId}`,
1212
+ );
1213
+ }
1214
+
1215
+ const copyBoundaryIndex = findDisplayTurnEndIndex(
1216
+ sourceMessages,
1217
+ initialBoundaryIndex,
1218
+ );
1219
+ const messagesToCopy =
1220
+ copyBoundaryIndex >= 0
1221
+ ? sourceMessages.slice(0, copyBoundaryIndex + 1)
1222
+ : ([] as MessageRow[]);
1223
+
1224
+ const sourceHistoryStrippedAt = sourceConversation.historyStrippedAt ?? null;
1225
+ const boundaryMessageCreatedAt = messagesToCopy.at(-1)?.createdAt ?? null;
1226
+ const inheritsHistoryStrippedAt =
1227
+ sourceHistoryStrippedAt != null &&
1228
+ boundaryMessageCreatedAt != null &&
1229
+ boundaryMessageCreatedAt >= sourceHistoryStrippedAt;
1230
+ // Inherit the most recent compaction whose event time is at-or-before the
1231
+ // forked-from message (see `forkConversation`).
1232
+ const inheritedCompaction = getLatestCompactionEventAtOrBefore(
1233
+ sourceConversation.id,
1234
+ boundaryMessageCreatedAt,
1235
+ );
1236
+ // Carry the Slack watermark only when inheriting the latest compaction
1237
+ // (see `forkConversation`).
1238
+ const inheritsLatestCompaction =
1239
+ inheritedCompaction != null &&
1240
+ inheritedCompaction.compactedAt === sourceConversation.contextCompactedAt;
1241
+ const forkParentMessageId = messagesToCopy.at(-1)?.id ?? null;
1242
+ const forkTitle =
1243
+ params.title ?? `${sourceConversation.title ?? "Untitled"} (Fork)`;
1244
+ const parentGroupId = getConversationGroupId(conversationId);
1245
+
1246
+ // Pre-generate the id map in JS so the same map drives the off-loop copy and
1247
+ // the in-process attachment relink that follows.
1248
+ const idPairs: ForkIdPair[] = messagesToCopy.map((message) => ({
1249
+ oldId: message.id,
1250
+ newId: uuid(),
1251
+ }));
1252
+ const forkedMessageIds = new Map<string, string>(
1253
+ idPairs.map((pair) => [pair.oldId, pair.newId]),
1254
+ );
1255
+
1256
+ // Phase 1 (in-process, tiny): create the fork conversation row + lineage so
1257
+ // the subprocess connection sees it before inserting messages. The whole
1258
+ // transaction is the retry unit — it rolls back atomically on contention, so
1259
+ // re-running it (with a fresh conversation id) is safe.
1260
+ const fork = await withSqliteRetry(
1261
+ () =>
1262
+ db.transaction(() => {
1263
+ const fc = createConversation({
1264
+ title: forkTitle,
1265
+ conversationType: params.conversationType ?? "standard",
1266
+ groupId: params.groupId ?? parentGroupId ?? "system:all",
1267
+ ...(params.source != null ? { source: params.source } : {}),
1268
+ });
1269
+ db.update(conversations)
1270
+ .set({
1271
+ forkParentConversationId: sourceConversation.id,
1272
+ forkParentMessageId,
1273
+ contextSummary: inheritedCompaction?.summary ?? null,
1274
+ contextCompactedMessageCount:
1275
+ inheritedCompaction?.compactedMessageCount ?? 0,
1276
+ contextCompactedAt: inheritedCompaction?.compactedAt ?? null,
1277
+ slackContextCompactionWatermarkTs: inheritsLatestCompaction
1278
+ ? sourceConversation.slackContextCompactionWatermarkTs
1279
+ : null,
1280
+ slackContextCompactionWatermarkAt: inheritsLatestCompaction
1281
+ ? sourceConversation.slackContextCompactionWatermarkAt
1282
+ : null,
1283
+ historyStrippedAt: inheritsHistoryStrippedAt
1284
+ ? sourceHistoryStrippedAt
1285
+ : null,
1286
+ inferenceProfile: sourceConversation.inferenceProfile,
1287
+ })
1288
+ .where(eq(conversations.id, fc.id))
1289
+ .run();
1290
+ return fc;
1291
+ }),
1292
+ { op: "forkConversationForRetrospective.create" },
1293
+ );
1294
+
1295
+ try {
1296
+ // Phase 2 (off the event loop): copy the message rows in a sqlite3
1297
+ // subprocess so the daemon stays responsive during the heavy copy.
1298
+ const copy = await copyForkMessagesViaSubprocess({
1299
+ forkConversationId: fork.id,
1300
+ idPairs,
1301
+ });
1302
+ if (!copy.ok) {
1303
+ throw new Error(
1304
+ `fork message copy failed (${copy.backend}): ${copy.error ?? "unknown"}`,
1305
+ );
1306
+ }
1307
+
1308
+ // Phase 3 (in-process): attachments + memory-state seeding, reusing the
1309
+ // same helper as the synchronous fork. Disk-view projection is skipped.
1310
+ const latestForkedAssistant = latestForkedAssistantFrom(
1311
+ messagesToCopy,
1312
+ forkedMessageIds,
1313
+ );
1314
+ db.transaction(() => {
1315
+ populateForkContentsInProcess({
1316
+ fork,
1317
+ sourceConversationId: sourceConversation.id,
1318
+ messagesToCopy,
1319
+ forkedMessageIds,
1320
+ latestForkedAssistant,
1321
+ isFullHistoryFork: copyBoundaryIndex === sourceMessages.length - 1,
1322
+ inheritedCompactedMessageCount:
1323
+ inheritedCompaction?.compactedMessageCount ?? 0,
1324
+ });
1325
+ });
1326
+
1327
+ const persistedFork = getConversation(fork.id);
1328
+ if (!persistedFork) {
1329
+ throw new Error(
1330
+ `Failed to load forked conversation ${fork.id} after creation`,
1331
+ );
1332
+ }
1333
+ return persistedFork;
1334
+ } catch (err) {
1335
+ try {
1336
+ deleteConversation(fork.id);
1337
+ } catch {
1338
+ // Best-effort cleanup; the worker's startup orphan sweep is the backstop.
1339
+ }
1340
+ throw err;
1341
+ }
1342
+ }
1343
+
1344
+ /**
1345
+ * Delete a conversation and all its messages, cleaning up orphaned memory
1346
+ * artifacts (embeddings). Returns segment IDs so callers can clean up
1347
+ * the corresponding Qdrant vector entries.
1348
+ */
1349
+ export function deleteConversation(id: string): DeletedMemoryIds {
1350
+ const db = getDb();
1351
+ const result: DeletedMemoryIds = {
1352
+ segmentIds: [],
1353
+ deletedSummaryIds: [],
1354
+ };
1355
+
1356
+ // Capture createdAt before the transaction deletes the row — needed to
1357
+ // resolve the conversation's disk-view directory path after deletion.
1358
+ const convBeforeDelete = getConversation(id);
1359
+ const createdAtForDiskCleanup = convBeforeDelete?.createdAt;
1360
+
1361
+ // llm_request_logs lives in the dedicated logs connection, so it is deleted
1362
+ // there — separately from the main-DB transaction below.
1363
+ logsDb()
1364
+ .delete(llmRequestLogs)
1365
+ .where(eq(llmRequestLogs.conversationId, id))
1366
+ .run();
1367
+
1368
+ db.transaction((tx) => {
1369
+ // Collect all message IDs for this conversation.
1370
+ const messageRows = tx
1371
+ .select({ id: messages.id })
1372
+ .from(messages)
1373
+ .where(eq(messages.conversationId, id))
1374
+ .all();
1375
+ const messageIds = messageRows.map((r) => r.id);
1376
+
1377
+ if (messageIds.length > 0) {
1378
+ // Collect memory segment IDs linked to these messages before cascade.
1379
+ const linkedSegments = tx
1380
+ .select({ id: memorySegments.id })
1381
+ .from(memorySegments)
1382
+ .where(inArray(memorySegments.messageId, messageIds))
1383
+ .all();
1384
+ result.segmentIds = linkedSegments.map((r) => r.id);
1385
+
1386
+ // Delete non-cascading tables first.
1387
+ tx.delete(toolInvocations)
1388
+ .where(eq(toolInvocations.conversationId, id))
1389
+ .run();
1390
+ tx.delete(skillLoadedEvents)
1391
+ .where(eq(skillLoadedEvents.conversationId, id))
1392
+ .run();
1393
+ // Cascade deletes memory_segments, message_attachments.
1394
+ tx.delete(messages).where(eq(messages.conversationId, id)).run();
1395
+
1396
+ // Clean up segment embeddings.
1397
+ if (result.segmentIds.length > 0) {
1398
+ tx.delete(memoryEmbeddings)
1399
+ .where(
1400
+ and(
1401
+ eq(memoryEmbeddings.targetType, "segment"),
1402
+ inArray(memoryEmbeddings.targetId, result.segmentIds),
1403
+ ),
1404
+ )
1405
+ .run();
1406
+ }
1407
+ } else {
1408
+ // No messages — just clean up non-message tables.
1409
+ tx.delete(toolInvocations)
1410
+ .where(eq(toolInvocations.conversationId, id))
1411
+ .run();
1412
+ tx.delete(skillLoadedEvents)
1413
+ .where(eq(skillLoadedEvents.conversationId, id))
1414
+ .run();
1415
+ }
1416
+
1417
+ tx.delete(conversations).where(eq(conversations.id, id)).run();
1418
+ });
1419
+
1420
+ // Remove the conversation's disk-view directory after the DB transaction
1421
+ if (createdAtForDiskCleanup != null) {
1422
+ removeConversationDir(id, createdAtForDiskCleanup);
1423
+ }
1424
+
1425
+ return result;
1426
+ }
1427
+
1428
+ /**
1429
+ * Delete a conversation like {@link deleteConversation}, but move the bulk
1430
+ * row deletes off the event loop in lock-friendly batches (see
1431
+ * {@link deleteConversationRowsInBatches}). The synchronous variant runs each
1432
+ * `DELETE` as one implicit transaction that holds the SQLite write lock for its
1433
+ * full duration — fine for an interactive conversation, but a conversation that
1434
+ * carries a full copy of a source's message history (e.g. a memory-retrospective
1435
+ * GC target) has huge `messages` and `llm_request_logs` tables, so on a large
1436
+ * database those single deletes peg the event loop and starve live user turns.
1437
+ * This variant sends the two heavy tables (`messages` on the main DB,
1438
+ * `llm_request_logs` on the logs DB) in chunks that each release the lock,
1439
+ * while the cheap bookkeeping (non-cascading tables, segment embeddings, the
1440
+ * conversation row) stays in-process.
1441
+ *
1442
+ * The `messages` batch enables `PRAGMA foreign_keys=ON` so the cascade
1443
+ * (memory_segments, message_attachments, …) fires just as it does on the daemon
1444
+ * connection. Segment ids are captured up front (before the cascade removes
1445
+ * them) so the caller can still clean up the corresponding Qdrant vectors. A
1446
+ * subprocess failure throws — any rows already deleted are gone, the
1447
+ * conversation row remains, and the worker's orphan sweep is the backstop, so
1448
+ * callers should treat this as best-effort like the synchronous variant.
1449
+ */
1450
+ export async function deleteConversationGently(
1451
+ id: string,
1452
+ ): Promise<DeletedMemoryIds> {
1453
+ const db = getDb();
1454
+ const result: DeletedMemoryIds = {
1455
+ segmentIds: [],
1456
+ deletedSummaryIds: [],
1457
+ };
1458
+
1459
+ // Capture createdAt before deletion — needed to resolve the conversation's
1460
+ // disk-view directory path afterwards.
1461
+ const convBeforeDelete = getConversation(id);
1462
+ const createdAtForDiskCleanup = convBeforeDelete?.createdAt;
1463
+
1464
+ // Collect the linked memory segment ids before the message cascade removes
1465
+ // them, so the caller can clean up the matching Qdrant vector entries.
1466
+ result.segmentIds = db
1467
+ .select({ id: memorySegments.id })
1468
+ .from(memorySegments)
1469
+ .where(eq(memorySegments.conversationId, id))
1470
+ .all()
1471
+ .map((r) => r.id);
1472
+
1473
+ // llm_request_logs lives in the dedicated logs connection, and each row is
1474
+ // bulky, so drain it off the event loop in batches against the logs DB file.
1475
+ const logsDel = await deleteConversationRowsInBatches({
1476
+ conversationId: id,
1477
+ table: "llm_request_logs",
1478
+ dbPath: getLogsDbPath(),
1479
+ });
1480
+ if (!logsDel.ok) {
1481
+ throw new Error(
1482
+ `gentle conversation delete failed (llm_request_logs, ${logsDel.backend}): ${logsDel.error ?? "unknown"}`,
1483
+ );
1484
+ }
1485
+
1486
+ // Bulk message delete off the event loop, in lock-friendly batches. Cascades
1487
+ // to memory_segments, message_attachments, bookmarks, channel_inbound_events.
1488
+ const del = await deleteConversationRowsInBatches({
1489
+ conversationId: id,
1490
+ table: "messages",
1491
+ enableForeignKeys: true,
1492
+ });
1493
+ if (!del.ok) {
1494
+ throw new Error(
1495
+ `gentle conversation delete failed (messages, ${del.backend}): ${del.error ?? "unknown"}`,
1496
+ );
1497
+ }
1498
+
1499
+ // Remaining cleanup is cheap (bounded, non-message tables) so it stays a
1500
+ // single in-process transaction.
1501
+ db.transaction((tx) => {
1502
+ tx.delete(toolInvocations)
1503
+ .where(eq(toolInvocations.conversationId, id))
1504
+ .run();
1505
+ tx.delete(skillLoadedEvents)
1506
+ .where(eq(skillLoadedEvents.conversationId, id))
1507
+ .run();
1508
+
1509
+ // Clean up segment embeddings (not FK-linked to segments, so the message
1510
+ // cascade above did not remove them).
1511
+ if (result.segmentIds.length > 0) {
1512
+ tx.delete(memoryEmbeddings)
1513
+ .where(
1514
+ and(
1515
+ eq(memoryEmbeddings.targetType, "segment"),
1516
+ inArray(memoryEmbeddings.targetId, result.segmentIds),
1517
+ ),
1518
+ )
1519
+ .run();
1520
+ }
1521
+
1522
+ // Conversation row deletion cascades to remaining dependent tables.
1523
+ tx.delete(conversations).where(eq(conversations.id, id)).run();
1524
+ });
1525
+
1526
+ // Remove the conversation's disk-view directory after the DB transaction.
1527
+ if (createdAtForDiskCleanup != null) {
1528
+ removeConversationDir(id, createdAtForDiskCleanup);
1529
+ }
1530
+
1531
+ return result;
1532
+ }
1533
+
1534
+ /**
1535
+ * Wipe a conversation and revert all memory changes it caused.
1536
+ *
1537
+ * Extends `deleteConversation` with:
1538
+ * - Cancelling pending memory jobs before deletion
1539
+ * - Deleting conversation-scoped memory summaries and their embeddings
1540
+ */
1541
+ export function wipeConversation(id: string): WipeConversationResult {
1542
+ const db = getDb();
1543
+ const deletedSummaryIds: string[] = [];
1544
+
1545
+ // Step A — Cancel pending memory jobs (before deleting messages, since
1546
+ // the cancellation queries join on `messages`). Cleanup runs even while the
1547
+ // memory plugin is disabled; returns 0 when memory is not present.
1548
+ const cancelledJobCount = getMemoryPersistenceHooks().onConversationWiped(id);
1549
+
1550
+ // Step C — Delete conversation-scoped memory summaries and their embeddings.
1551
+ const summaryRows = db
1552
+ .select({ id: memorySummaries.id })
1553
+ .from(memorySummaries)
1554
+ .where(
1555
+ and(
1556
+ eq(memorySummaries.scope, "conversation"),
1557
+ eq(memorySummaries.scopeKey, id),
1558
+ ),
1559
+ )
1560
+ .all();
1561
+ const summaryIds = summaryRows.map((r) => r.id);
1562
+ if (summaryIds.length > 0) {
1563
+ db.delete(memoryEmbeddings)
1564
+ .where(
1565
+ and(
1566
+ eq(memoryEmbeddings.targetType, "summary"),
1567
+ inArray(memoryEmbeddings.targetId, summaryIds),
1568
+ ),
1569
+ )
1570
+ .run();
1571
+ db.delete(memorySummaries)
1572
+ .where(inArray(memorySummaries.id, summaryIds))
1573
+ .run();
1574
+ }
1575
+ deletedSummaryIds.push(...summaryIds);
1576
+
1577
+ // Step D — Delegate to deleteConversation which handles messages (cascade
1578
+ // segments, attachments), llmRequestLogs, toolInvocations,
1579
+ // skillLoadedEvents, embeddings, and the conversation row.
1580
+ const deletedMemoryIds = deleteConversation(id);
1581
+
1582
+ // Step E — Return the combined result.
1583
+ return {
1584
+ ...deletedMemoryIds,
1585
+ deletedSummaryIds: [
1586
+ ...deletedSummaryIds,
1587
+ ...deletedMemoryIds.deletedSummaryIds,
1588
+ ],
1589
+ cancelledJobCount,
1590
+ };
1591
+ }
1592
+
1593
+ /** Options for {@link addMessage}. Only `skipIndexing` and `clientMessageId`
1594
+ * have defaults; `metadata` is genuinely optional. */
1595
+ export interface AddMessageOptions {
1596
+ metadata?: Record<string, unknown>;
1597
+ skipIndexing?: boolean;
1598
+ /** Client-generated nonce for idempotent inserts. When provided,
1599
+ * duplicate inserts for the same `(conversationId, clientMessageId)`
1600
+ * pair are silently skipped. */
1601
+ clientMessageId?: string;
1602
+ }
1603
+
1604
+ /**
1605
+ * Persist a message and run post-insert side effects (memory indexing,
1606
+ * attention projection). Delegates the core insert + retry logic to
1607
+ * {@link insertMessageCore}.
1608
+ */
1609
+ export async function addMessage(
1610
+ conversationId: string,
1611
+ role: MessageRole,
1612
+ content: string,
1613
+ options?: AddMessageOptions,
1614
+ ) {
1615
+ const { metadata, skipIndexing, clientMessageId } = options ?? {};
1616
+ const inserted = await insertMessageCore({
1617
+ conversationId,
1618
+ role,
1619
+ content,
1620
+ metadata,
1621
+ clientMessageId,
1622
+ });
1623
+
1624
+ if (inserted.deduplicated) {
1625
+ return inserted;
1626
+ }
1627
+
1628
+ const message = inserted;
1629
+
1630
+ if (!skipIndexing) {
1631
+ try {
1632
+ const parsed = metadata
1633
+ ? messageMetadataSchema.safeParse(metadata)
1634
+ : null;
1635
+ const provenanceTrustClass = parsed?.success
1636
+ ? parsed.data.provenanceTrustClass
1637
+ : undefined;
1638
+ const automated = parsed?.success ? parsed.data.automated : undefined;
1639
+ await getMemoryPersistenceHooks().onMessagePersisted({
1640
+ messageId: message.id,
1641
+ conversationId: message.conversationId,
1642
+ role: message.role,
1643
+ content: message.content,
1644
+ createdAt: message.createdAt,
1645
+ provenanceTrustClass,
1646
+ automated,
1647
+ });
1648
+ } catch (err) {
1649
+ log.warn(
1650
+ { err, conversationId, messageId: message.id },
1651
+ "Failed to index message for memory",
1652
+ );
1653
+ }
1654
+ }
1655
+
1656
+ if (role === "assistant") {
1657
+ try {
1658
+ const attentionStateChanged = projectAssistantMessage({
1659
+ conversationId,
1660
+ messageId: message.id,
1661
+ messageAt: message.createdAt,
1662
+ });
1663
+ if (attentionStateChanged) {
1664
+ void publishSyncInvalidation([
1665
+ conversationMetadataSyncTag(conversationId),
1666
+ ]);
1667
+ }
1668
+ } catch (err) {
1669
+ log.warn(
1670
+ { err, conversationId, messageId: message.id },
1671
+ "Failed to project assistant message for attention tracking",
1672
+ );
1673
+ }
1674
+ }
1675
+
1676
+ return message;
1677
+ }
1678
+
1679
+ export function getMessages(conversationId: string): MessageRow[] {
1680
+ const db = getDb();
1681
+ return db
1682
+ .select()
1683
+ .from(messages)
1684
+ .where(eq(messages.conversationId, conversationId))
1685
+ .orderBy(asc(messages.createdAt))
1686
+ .all()
1687
+ .map(parseMessage);
1688
+ }
1689
+
1690
+ /**
1691
+ * Return raw `metadata` strings for messages whose metadata looks like it may
1692
+ * contain Slack metadata, capped at `limit` and skipping the first `offset`
1693
+ * matches. Pushes `LIKE` + `LIMIT`/`OFFSET` into SQL so warm Slack DM
1694
+ * conversations don't require a full-table scan on the webhook critical path.
1695
+ * The substring match is an indexable prefilter only — callers must parse and
1696
+ * validate each returned string against the Slack metadata schema, because a
1697
+ * malformed row (partial write, legacy format, unrelated key accidentally
1698
+ * containing the literal) can still slip through the substring match. Callers
1699
+ * that need a fixed number of *valid* rows should iterate with increasing
1700
+ * offsets until the target is reached (capped at a reasonable maximum to bound
1701
+ * scan cost).
1702
+ */
1703
+ export function selectSlackMetaCandidateMetadata(
1704
+ conversationId: string,
1705
+ limit: number,
1706
+ offset = 0,
1707
+ opts?: { includeFlatLegacy?: boolean },
1708
+ ): string[] {
1709
+ const db = getDb();
1710
+ const rows = db
1711
+ .select({ metadata: messages.metadata })
1712
+ .from(messages)
1713
+ .where(
1714
+ and(
1715
+ eq(messages.conversationId, conversationId),
1716
+ opts?.includeFlatLegacy
1717
+ ? or(
1718
+ like(messages.metadata, '%"slackMeta"%'),
1719
+ like(messages.metadata, '%"source":"slack"%'),
1720
+ )
1721
+ : like(messages.metadata, '%"slackMeta"%'),
1722
+ ),
1723
+ )
1724
+ .orderBy(asc(messages.createdAt))
1725
+ .limit(limit)
1726
+ .offset(offset)
1727
+ .all();
1728
+ const out: string[] = [];
1729
+ for (const r of rows) {
1730
+ if (typeof r.metadata === "string" && r.metadata.length > 0) {
1731
+ out.push(r.metadata);
1732
+ }
1733
+ }
1734
+ return out;
1735
+ }
1736
+
1737
+ /**
1738
+ * Count messages in a conversation that were created strictly after the
1739
+ * `afterMessageId` reference message. If `afterMessageId` is `null` or empty,
1740
+ * counts all messages in the conversation. If the referenced message no
1741
+ * longer exists (e.g. deleted by a separate flow), returns 0 — callers
1742
+ * decide how to react to a vanished reference, and the conservative answer
1743
+ * here is "no new work."
1744
+ *
1745
+ * Used by the memory-retrospective trigger check to decide whether to fire
1746
+ * the message-count trigger without loading message bodies.
1747
+ */
1748
+ export function countMessagesAfter(
1749
+ conversationId: string,
1750
+ afterMessageId: string | null,
1751
+ ): number {
1752
+ const db = getDb();
1753
+ if (afterMessageId === null || afterMessageId === "") {
1754
+ const row = db
1755
+ .select({ c: count() })
1756
+ .from(messages)
1757
+ .where(eq(messages.conversationId, conversationId))
1758
+ .get();
1759
+ return row?.c ?? 0;
1760
+ }
1761
+ const ref = db
1762
+ .select({ createdAt: messages.createdAt })
1763
+ .from(messages)
1764
+ .where(eq(messages.id, afterMessageId))
1765
+ .get();
1766
+ if (!ref) return 0;
1767
+ // Tie-breaker on `messages.id` so rows that share a millisecond timestamp
1768
+ // with the reference are not permanently skipped. Mirrors the
1769
+ // `(createdAt, id)` cursor pattern used by the backfill job-handler and
1770
+ // turn-events-store.
1771
+ const row = db
1772
+ .select({ c: count() })
1773
+ .from(messages)
1774
+ .where(
1775
+ and(
1776
+ eq(messages.conversationId, conversationId),
1777
+ or(
1778
+ gt(messages.createdAt, ref.createdAt),
1779
+ and(
1780
+ eq(messages.createdAt, ref.createdAt),
1781
+ gt(messages.id, afterMessageId),
1782
+ ),
1783
+ ),
1784
+ ),
1785
+ )
1786
+ .get();
1787
+ return row?.c ?? 0;
1788
+ }
1789
+
1790
+ /**
1791
+ * Return messages in a conversation created strictly after the
1792
+ * `afterMessageId` reference. If the reference is `null`/empty, returns all
1793
+ * messages. If the reference doesn't exist, returns an empty array (mirrors
1794
+ * `countMessagesAfter`'s conservative semantics). Used by the
1795
+ * memory-retrospective job handler to load the message slice it processes.
1796
+ */
1797
+ export function getMessagesAfter(
1798
+ conversationId: string,
1799
+ afterMessageId: string | null,
1800
+ ): MessageRow[] {
1801
+ const db = getDb();
1802
+ if (afterMessageId === null || afterMessageId === "") {
1803
+ // Secondary `asc(messages.id)` matches the non-null path's cursor
1804
+ // ordering, so callers tracking `cutoffMessageId` across runs see a
1805
+ // consistent ordering when multiple rows share a millisecond timestamp.
1806
+ return db
1807
+ .select()
1808
+ .from(messages)
1809
+ .where(eq(messages.conversationId, conversationId))
1810
+ .orderBy(asc(messages.createdAt), asc(messages.id))
1811
+ .all()
1812
+ .map(parseMessage);
1813
+ }
1814
+ const ref = db
1815
+ .select({ createdAt: messages.createdAt })
1816
+ .from(messages)
1817
+ .where(eq(messages.id, afterMessageId))
1818
+ .get();
1819
+ if (!ref) return [];
1820
+ // Same `(createdAt, id)` cursor as `countMessagesAfter` — rows sharing
1821
+ // the reference's millisecond timestamp would otherwise be skipped.
1822
+ return db
1823
+ .select()
1824
+ .from(messages)
1825
+ .where(
1826
+ and(
1827
+ eq(messages.conversationId, conversationId),
1828
+ or(
1829
+ gt(messages.createdAt, ref.createdAt),
1830
+ and(
1831
+ eq(messages.createdAt, ref.createdAt),
1832
+ gt(messages.id, afterMessageId),
1833
+ ),
1834
+ ),
1835
+ ),
1836
+ )
1837
+ .orderBy(asc(messages.createdAt), asc(messages.id))
1838
+ .all()
1839
+ .map(parseMessage);
1840
+ }
1841
+
1842
+ /**
1843
+ * Efficient existence check — returns true if the conversation has at least
1844
+ * one message row. Uses `LIMIT 1` + `select({ 1 })` to avoid loading and
1845
+ * parsing any message content.
1846
+ */
1847
+ export function hasMessages(conversationId: string): boolean {
1848
+ const db = getDb();
1849
+ const row = db
1850
+ .select({ one: sql`1` })
1851
+ .from(messages)
1852
+ .where(eq(messages.conversationId, conversationId))
1853
+ .limit(1)
1854
+ .get();
1855
+ return row !== undefined;
1856
+ }
1857
+
1858
+ interface PaginatedMessagesResult {
1859
+ messages: MessageRow[];
1860
+ hasMore: boolean;
1861
+ /**
1862
+ * Position of the last row scanned when the loop stops on
1863
+ * `PAGINATION_SCAN_CAP` rather than DB exhaustion. Callers derive their
1864
+ * client cursor from the visible page's oldest row, but a cap-truncated
1865
+ * page can be empty (a contiguous block of filtered-out rows longer than
1866
+ * the cap), leaving nothing to resume from. Surfacing the last scanned
1867
+ * `(createdAt, id)` lets the caller hand the client a cursor so it can
1868
+ * request the next window and keep draining instead of stalling.
1869
+ */
1870
+ nextCursor?: { createdAt: number; id: string };
1871
+ }
1872
+
1873
+ const PAGINATION_CHUNK_MIN = 50;
1874
+ const PAGINATION_SCAN_CAP = 10_000;
1875
+
1876
+ // Test-only override for PAGINATION_SCAN_CAP so tests can exercise the
1877
+ // cap-truncation branch with a small cap instead of seeding >10k rows (which
1878
+ // makes the suite slow and the post-test DELETE flaky under parallel CI load).
1879
+ // `undefined` restores the production cap.
1880
+ let paginationScanCapOverride: number | undefined;
1881
+ export function _setPaginationScanCapForTesting(cap: number | undefined): void {
1882
+ paginationScanCapOverride = cap;
1883
+ }
1884
+
1885
+ export function getMessagesPaginated(
1886
+ conversationId: string,
1887
+ limit: number | undefined,
1888
+ beforeTimestamp?: number,
1889
+ filter?: (row: MessageRow) => boolean,
1890
+ ): PaginatedMessagesResult {
1891
+ const db = getDb();
1892
+
1893
+ if (limit === undefined) {
1894
+ const conditions = [eq(messages.conversationId, conversationId)];
1895
+ if (beforeTimestamp !== undefined) {
1896
+ conditions.push(lt(messages.createdAt, beforeTimestamp));
1897
+ }
1898
+ const rows = db
1899
+ .select()
1900
+ .from(messages)
1901
+ .where(and(...conditions))
1902
+ .orderBy(asc(messages.createdAt))
1903
+ .all()
1904
+ .map(parseMessage);
1905
+ return {
1906
+ messages: filter ? rows.filter(filter) : rows,
1907
+ hasMore: false,
1908
+ };
1909
+ }
1910
+
1911
+ // Walk pages newest→oldest, applying `filter` in TS (metadata parsing is
1912
+ // JSON, not a structured column). Keep fetching until we have `limit + 1`
1913
+ // visible rows or the DB is exhausted, so `hasMore` and the cursor reflect
1914
+ // the visible page rather than the unfiltered row count. Without this loop,
1915
+ // a fully-hidden page returns `{ messages: [], hasMore: true }` with no
1916
+ // cursor, which stalls the web client's older-page fetch.
1917
+ let cursorCreatedAt = beforeTimestamp;
1918
+ let cursorMessageId: string | undefined;
1919
+ const visible: MessageRow[] = [];
1920
+ const chunkSize = Math.max(limit + 1, PAGINATION_CHUNK_MIN);
1921
+ // Bound the work a single request can do when `filter` rejects nearly every
1922
+ // row — otherwise a pathological filter against a huge conversation would
1923
+ // tie up a connection for thousands of roundtrips.
1924
+ let rowsScanned = 0;
1925
+ // Distinguish "stopped because we hit the scan cap" from "stopped because the
1926
+ // DB ran out of rows". On a cap-truncated stop there may be more visible rows
1927
+ // past the scanned window, so `hasMore` must stay true and we record the last
1928
+ // scanned position as a resume cursor (the visible page may be empty).
1929
+ let scanCapTruncated = false;
1930
+ let lastScanned: { createdAt: number; id: string } | undefined;
1931
+ const scanCap = paginationScanCapOverride ?? PAGINATION_SCAN_CAP;
1932
+
1933
+ while (visible.length < limit + 1) {
1934
+ if (rowsScanned >= scanCap) {
1935
+ scanCapTruncated = true;
1936
+ break;
1937
+ }
1938
+ const cursorPredicate =
1939
+ cursorCreatedAt === undefined
1940
+ ? undefined
1941
+ : cursorMessageId === undefined
1942
+ ? lt(messages.createdAt, cursorCreatedAt)
1943
+ : or(
1944
+ lt(messages.createdAt, cursorCreatedAt),
1945
+ and(
1946
+ eq(messages.createdAt, cursorCreatedAt),
1947
+ lt(messages.id, cursorMessageId),
1948
+ ),
1949
+ );
1950
+
1951
+ const chunk = db
1952
+ .select()
1953
+ .from(messages)
1954
+ .where(and(eq(messages.conversationId, conversationId), cursorPredicate))
1955
+ .orderBy(desc(messages.createdAt), desc(messages.id))
1956
+ .limit(chunkSize)
1957
+ .all()
1958
+ .map(parseMessage);
1959
+
1960
+ if (chunk.length === 0) break;
1961
+ rowsScanned += chunk.length;
1962
+
1963
+ for (const row of chunk) {
1964
+ if (!filter || filter(row)) visible.push(row);
1965
+ if (visible.length >= limit + 1) break;
1966
+ }
1967
+
1968
+ if (chunk.length < chunkSize) break;
1969
+ const lastRow = chunk[chunk.length - 1];
1970
+ lastScanned = { createdAt: lastRow.createdAt, id: lastRow.id };
1971
+ cursorCreatedAt = lastRow.createdAt;
1972
+ cursorMessageId = lastRow.id;
1973
+ }
1974
+
1975
+ const filledPage = visible.length > limit;
1976
+ // A cap-truncated stop means the DB may still hold older visible rows past
1977
+ // the scanned window, so report `hasMore: true` to keep the client draining
1978
+ // — returning `false` here is the stall this loop exists to prevent.
1979
+ const hasMore = filledPage || scanCapTruncated;
1980
+ if (filledPage) visible.splice(limit);
1981
+ visible.reverse();
1982
+
1983
+ // Only hand back a resume cursor when the cap (not DB exhaustion) cut the
1984
+ // search short; callers fall back to it when the visible page came back
1985
+ // empty and has no oldest row to anchor the next request.
1986
+ const nextCursor = scanCapTruncated ? lastScanned : undefined;
1987
+
1988
+ return { messages: visible, hasMore, nextCursor };
1989
+ }
1990
+
1991
+ export function getLastUserTimestampBefore(
1992
+ conversationId: string,
1993
+ beforeTimestamp: number,
1994
+ ): number {
1995
+ const db = getDb();
1996
+ const row = db
1997
+ .select({ createdAt: messages.createdAt })
1998
+ .from(messages)
1999
+ .where(
2000
+ and(
2001
+ eq(messages.conversationId, conversationId),
2002
+ eq(messages.role, "user"),
2003
+ lt(messages.createdAt, beforeTimestamp),
2004
+ ),
2005
+ )
2006
+ .orderBy(desc(messages.createdAt))
2007
+ .limit(1)
2008
+ .get();
2009
+ return row?.createdAt ?? 0;
2010
+ }
2011
+
2012
+ /**
2013
+ * Most recent user-message timestamp (epoch ms) across all conversations, or
2014
+ * `0` when no user message exists.
2015
+ *
2016
+ * Ordered by `created_at` rather than insertion order, because `forkConversation`
2017
+ * copies a parent's messages into the fork while preserving their original
2018
+ * `created_at`. Those copies receive fresh row ids, so an insertion-order scan
2019
+ * could surface an old forked turn as if it were the latest activity and let
2020
+ * maintenance run while the user is in fact active. The `(role, created_at)`
2021
+ * index (`idx_messages_role_created_at`) makes this an indexed seek to the
2022
+ * newest `role = "user"` row rather than a scan of the whole (potentially
2023
+ * multi-GB) table.
2024
+ */
2025
+ export function getLastUserMessageTimestamp(): number {
2026
+ const db = getDb();
2027
+ const row = db
2028
+ .select({ createdAt: messages.createdAt })
2029
+ .from(messages)
2030
+ .where(eq(messages.role, "user"))
2031
+ .orderBy(desc(messages.createdAt))
2032
+ .limit(1)
2033
+ .get();
2034
+ return row?.createdAt ?? 0;
2035
+ }
2036
+
2037
+ /** Fetch a single message by ID, optionally scoped to a specific conversation. */
2038
+ export function getMessageById(
2039
+ messageId: string,
2040
+ conversationId?: string,
2041
+ ): MessageRow | null {
2042
+ const db = getDb();
2043
+ const conditions = [eq(messages.id, messageId)];
2044
+ if (conversationId) {
2045
+ conditions.push(eq(messages.conversationId, conversationId));
2046
+ }
2047
+ const row = db
2048
+ .select()
2049
+ .from(messages)
2050
+ .where(and(...conditions))
2051
+ .get();
2052
+ return row ? parseMessage(row) : null;
2053
+ }
2054
+
2055
+ export function updateConversationTitle(
2056
+ id: string,
2057
+ title: string,
2058
+ isAutoTitle?: number,
2059
+ ): void {
2060
+ const db = getDb();
2061
+ const set: Record<string, unknown> = { title, updatedAt: Date.now() };
2062
+ if (isAutoTitle !== undefined) set.isAutoTitle = isAutoTitle;
2063
+ db.update(conversations).set(set).where(eq(conversations.id, id)).run();
2064
+
2065
+ // Update disk view meta.json with the new title
2066
+ const conv = getConversation(id);
2067
+ if (conv) {
2068
+ updateMetaFile(conv);
2069
+ }
2070
+ }
2071
+
2072
+ export function updateConversationUsage(
2073
+ id: string,
2074
+ totalInputTokens: number,
2075
+ totalOutputTokens: number,
2076
+ totalEstimatedCost: number,
2077
+ ): void {
2078
+ const db = getDb();
2079
+ db.update(conversations)
2080
+ .set({
2081
+ totalInputTokens,
2082
+ totalOutputTokens,
2083
+ totalEstimatedCost,
2084
+ updatedAt: Date.now(),
2085
+ })
2086
+ .where(eq(conversations.id, id))
2087
+ .run();
2088
+ }
2089
+
2090
+ export function updateConversationContextWindow(
2091
+ id: string,
2092
+ contextSummary: string,
2093
+ contextCompactedMessageCount: number,
2094
+ ): void {
2095
+ const db = getDb();
2096
+ const now = Date.now();
2097
+ // Update the hot-path cache columns and append the event to the ledger in a
2098
+ // single transaction so the latest ledger event always matches the cache.
2099
+ db.transaction(() => {
2100
+ db.update(conversations)
2101
+ .set({
2102
+ contextSummary,
2103
+ contextCompactedMessageCount,
2104
+ contextCompactedAt: now,
2105
+ updatedAt: now,
2106
+ })
2107
+ .where(eq(conversations.id, id))
2108
+ .run();
2109
+ if (contextCompactedMessageCount > 0) {
2110
+ appendCompactionEvent(id, {
2111
+ compactedAt: now,
2112
+ summary: contextSummary,
2113
+ compactedMessageCount: contextCompactedMessageCount,
2114
+ });
2115
+ }
2116
+ });
2117
+ }
2118
+
2119
+ export function setConversationHistoryStrippedAt(
2120
+ id: string,
2121
+ historyStrippedAt: number | null,
2122
+ ): void {
2123
+ const db = getDb();
2124
+ db.update(conversations)
2125
+ .set({
2126
+ historyStrippedAt,
2127
+ updatedAt: Date.now(),
2128
+ })
2129
+ .where(eq(conversations.id, id))
2130
+ .run();
2131
+ }
2132
+
2133
+ export function updateConversationSlackContextWatermark(
2134
+ id: string,
2135
+ watermarkTs: string,
2136
+ compactedAt: number = Date.now(),
2137
+ ): void {
2138
+ const db = getDb();
2139
+ db.update(conversations)
2140
+ .set({
2141
+ slackContextCompactionWatermarkTs: watermarkTs,
2142
+ slackContextCompactionWatermarkAt: compactedAt,
2143
+ updatedAt: Date.now(),
2144
+ })
2145
+ .where(eq(conversations.id, id))
2146
+ .run();
2147
+ }
2148
+
2149
+ export function archiveConversation(id: string): boolean {
2150
+ const conv = getConversation(id);
2151
+ if (!conv) return false;
2152
+ const now = Date.now();
2153
+ rawRun(
2154
+ "UPDATE conversations SET archived_at = ?, updated_at = ? WHERE id = ?",
2155
+ now,
2156
+ now,
2157
+ id,
2158
+ );
2159
+ return true;
2160
+ }
2161
+
2162
+ export function unarchiveConversation(id: string): boolean {
2163
+ const conv = getConversation(id);
2164
+ if (!conv) return false;
2165
+ const now = Date.now();
2166
+ rawRun(
2167
+ "UPDATE conversations SET archived_at = NULL, updated_at = ? WHERE id = ?",
2168
+ now,
2169
+ id,
2170
+ );
2171
+ return true;
2172
+ }
2173
+
2174
+ /**
2175
+ * Persist the processing-start timestamp for a conversation. Called by
2176
+ * `Conversation.setProcessing(true)` so out-of-process callers can detect
2177
+ * mid-turn state by reading the `conversations` row directly. Pass `null`
2178
+ * to clear (turn ended).
2179
+ */
2180
+ export function setConversationProcessingStartedAt(
2181
+ id: string,
2182
+ startedAt: number | null,
2183
+ ): void {
2184
+ rawRun(
2185
+ "UPDATE conversations SET processing_started_at = ? WHERE id = ?",
2186
+ startedAt,
2187
+ id,
2188
+ );
2189
+ }
2190
+
2191
+ /**
2192
+ * Clear the persisted processing flag on every conversation that still has one
2193
+ * set, returning the number of rows cleared. Called at daemon startup to reset
2194
+ * conversations whose `processing_started_at` was left non-NULL because the
2195
+ * previous process shut down mid-turn — the in-memory agent loop driving that
2196
+ * turn is gone, so the flag is stale.
2197
+ */
2198
+ export function clearStaleProcessingFlags(): number {
2199
+ return rawRun(
2200
+ "UPDATE conversations SET processing_started_at = NULL WHERE processing_started_at IS NOT NULL",
2201
+ );
2202
+ }
2203
+
2204
+ /**
2205
+ * Read whether a conversation is currently processing. Checks the in-memory
2206
+ * `Conversation._processing` flag first (hot path for resident conversations),
2207
+ * falling back to the persisted `processing_started_at` column for cold
2208
+ * (evicted / never-loaded) conversations. This is the single entry point for
2209
+ * processing state — callers don't need to layer `findConversation` themselves.
2210
+ * Returns `false` when the conversation row doesn't exist.
2211
+ */
2212
+ export function isConversationProcessing(id: string): boolean {
2213
+ const inMemory = findConversation(id)?.isProcessing();
2214
+ if (inMemory != null) return inMemory;
2215
+ const row = rawGet<{ processing_started_at: number | null }>(
2216
+ "SELECT processing_started_at FROM conversations WHERE id = ?",
2217
+ id,
2218
+ );
2219
+ return row?.processing_started_at != null;
2220
+ }
2221
+
2222
+ /**
2223
+ * Highest stream `seq` whose content is durably persisted to this
2224
+ * conversation's message rows, read from the `conversations.seq` column. This
2225
+ * is the snapshot↔stream alignment baseline `/messages` returns so a client
2226
+ * applies only stream events with a higher `seq`. `null` when none was
2227
+ * recorded (created before any stream activity, row predates the column, or
2228
+ * the conversation row is absent), in which case the client cold-starts.
2229
+ *
2230
+ * Seeded at creation with the global high-water seq and advanced on each
2231
+ * persistence flush by {@link recordConversationPersistedSeq}.
2232
+ */
2233
+ export function getConversationPersistedSeq(id: string): number | null {
2234
+ const row = rawGet<{ seq: number | null }>(
2235
+ "SELECT seq FROM conversations WHERE id = ?",
2236
+ id,
2237
+ );
2238
+ return row?.seq ?? null;
2239
+ }
2240
+
2241
+ /**
2242
+ * Record that conversation `id` has durably persisted all of its events
2243
+ * through `seq`, writing the `conversations.seq` column. Called at each
2244
+ * persistence flush with the `seq` of the last event whose content the write
2245
+ * committed.
2246
+ *
2247
+ * Monotonic: the `WHERE seq IS NULL OR seq < ?` guard makes the update raise
2248
+ * the high-water mark only, so out-of-order async commits never regress it.
2249
+ * Non-positive or non-finite `seq` values are ignored.
2250
+ */
2251
+ export function recordConversationPersistedSeq(id: string, seq: number): void {
2252
+ if (!Number.isFinite(seq) || seq <= 0) return;
2253
+ rawRun(
2254
+ "UPDATE conversations SET seq = ? WHERE id = ? AND (seq IS NULL OR seq < ?)",
2255
+ seq,
2256
+ id,
2257
+ seq,
2258
+ );
2259
+ }
2260
+
2261
+ /**
2262
+ * Set or clear the `surfaced_at` promotion marker for a conversation.
2263
+ *
2264
+ * A non-null `surfaced_at` promotes a background/scheduled conversation
2265
+ * into the default ("standard") conversation listing so clients show it in
2266
+ * the Recents sidebar grouping. Promotion is always explicit — callers are
2267
+ * product flows that decide a background run deserves foreground visibility
2268
+ * (e.g. the user sent a follow-up message in it). Nothing sets this
2269
+ * automatically.
2270
+ *
2271
+ * Returns `null` when the conversation does not exist; otherwise the new
2272
+ * `surfacedAt` value (`number` when surfacing, `null` when clearing).
2273
+ */
2274
+ export function setConversationSurfaced(
2275
+ id: string,
2276
+ surfaced: boolean,
2277
+ ): { surfacedAt: number | null } | null {
2278
+ const conv = getConversation(id);
2279
+ if (!conv) return null;
2280
+ const now = Date.now();
2281
+ const surfacedAt = surfaced ? now : null;
2282
+ rawRun(
2283
+ "UPDATE conversations SET surfaced_at = ?, updated_at = ? WHERE id = ?",
2284
+ surfacedAt,
2285
+ now,
2286
+ id,
2287
+ );
2288
+ return { surfacedAt };
2289
+ }
2290
+
2291
+ /**
2292
+ * Set or clear the inference profile override for a conversation.
2293
+ * Pass `null` to clear the override and fall back to the workspace
2294
+ * `llm.activeProfile` resolution.
2295
+ *
2296
+ * Also clears any stale session columns (`inferenceProfileSessionId`,
2297
+ * `inferenceProfileExpiresAt`) so that the reaper and lazy expiry check
2298
+ * cannot later clobber the newly-set profile.
2299
+ */
2300
+ export function setConversationInferenceProfile(
2301
+ conversationId: string,
2302
+ profile: string | null,
2303
+ ): void {
2304
+ const db = getDb();
2305
+ db.update(conversations)
2306
+ .set({
2307
+ inferenceProfile: profile,
2308
+ inferenceProfileSessionId: null,
2309
+ inferenceProfileExpiresAt: null,
2310
+ updatedAt: Date.now(),
2311
+ })
2312
+ .where(eq(conversations.id, conversationId))
2313
+ .run();
2314
+ }
2315
+
2316
+ /**
2317
+ * Atomically set the inference profile, session id, and expiry timestamp for
2318
+ * a conversation. Pass `null` for all three to clear the session-backed
2319
+ * override and fall back to the workspace `llm.activeProfile` resolution.
2320
+ */
2321
+ export function setConversationInferenceProfileSession(
2322
+ conversationId: string,
2323
+ profile: string | null,
2324
+ sessionId: string | null,
2325
+ expiresAt: number | null,
2326
+ ): void {
2327
+ const db = getDb();
2328
+ db.update(conversations)
2329
+ .set({
2330
+ inferenceProfile: profile,
2331
+ inferenceProfileSessionId: sessionId,
2332
+ inferenceProfileExpiresAt: expiresAt,
2333
+ updatedAt: Date.now(),
2334
+ })
2335
+ .where(eq(conversations.id, conversationId))
2336
+ .run();
2337
+ }
2338
+
2339
+ /**
2340
+ * Clear all conversations whose session-backed inference profile has expired.
2341
+ * Returns an array of `{ conversationId, sessionId }` for each cleared row so
2342
+ * callers can emit the appropriate update events.
2343
+ */
2344
+ export function clearExpiredInferenceProfiles(
2345
+ now: number,
2346
+ ): Array<{ conversationId: string; sessionId: string | null }> {
2347
+ const raw = getSqliteFrom(getDb());
2348
+ // Two-step approach: SELECT to get pre-clear sessionIds, then UPDATE.
2349
+ // The UPDATE re-applies the WHERE condition for CAS safety.
2350
+ // RETURNING the id lets us know which rows were actually cleared.
2351
+ const expired = raw
2352
+ .prepare(
2353
+ `
2354
+ SELECT id AS conversationId, inference_profile_session_id AS sessionId
2355
+ FROM conversations
2356
+ WHERE inference_profile_expires_at IS NOT NULL AND inference_profile_expires_at <= ?
2357
+ `,
2358
+ )
2359
+ .all(now) as Array<{ conversationId: string; sessionId: string | null }>;
2360
+
2361
+ if (expired.length === 0) return [];
2362
+
2363
+ const ids = expired.map((r) => r.conversationId);
2364
+ const placeholders = ids.map(() => "?").join(", ");
2365
+
2366
+ const actuallyCleared = raw
2367
+ .prepare(
2368
+ `
2369
+ UPDATE conversations
2370
+ SET inference_profile = NULL, inference_profile_session_id = NULL, inference_profile_expires_at = NULL
2371
+ WHERE id IN (${placeholders}) AND inference_profile_expires_at IS NOT NULL AND inference_profile_expires_at <= ?
2372
+ RETURNING id AS conversationId
2373
+ `,
2374
+ )
2375
+ .all(...ids, now) as Array<{ conversationId: string }>;
2376
+
2377
+ const clearedSet = new Set(actuallyCleared.map((r) => r.conversationId));
2378
+ return expired.filter((r) => clearedSet.has(r.conversationId));
2379
+ }
2380
+
2381
+ /**
2382
+ * List conversations with an active (non-expired) session-backed inference
2383
+ * profile. Pass a `conversationId` to narrow to a single conversation.
2384
+ */
2385
+ export function listActiveInferenceProfileSessions(
2386
+ conversationId?: string,
2387
+ ): Array<{
2388
+ conversationId: string;
2389
+ conversationTitle: string | null;
2390
+ profile: string;
2391
+ sessionId: string;
2392
+ expiresAt: number;
2393
+ }> {
2394
+ const db = getDb();
2395
+ const now = Date.now();
2396
+ const baseConditions = [
2397
+ isNotNull(conversations.inferenceProfile),
2398
+ isNotNull(conversations.inferenceProfileExpiresAt),
2399
+ gt(conversations.inferenceProfileExpiresAt, now),
2400
+ isNotNull(conversations.inferenceProfileSessionId),
2401
+ ];
2402
+ if (conversationId) {
2403
+ baseConditions.push(eq(conversations.id, conversationId));
2404
+ }
2405
+ return db
2406
+ .select({
2407
+ conversationId: conversations.id,
2408
+ conversationTitle: conversations.title,
2409
+ profile: conversations.inferenceProfile,
2410
+ sessionId: conversations.inferenceProfileSessionId,
2411
+ expiresAt: conversations.inferenceProfileExpiresAt,
2412
+ })
2413
+ .from(conversations)
2414
+ .where(and(...baseConditions))
2415
+ .all() as Array<{
2416
+ conversationId: string;
2417
+ conversationTitle: string | null;
2418
+ profile: string;
2419
+ sessionId: string;
2420
+ expiresAt: number;
2421
+ }>;
2422
+ }
2423
+
2424
+ /**
2425
+ * The conversation fields needed to resolve a per-turn inference-profile
2426
+ * override. Satisfied by both the DB {@link ConversationRow} and the live
2427
+ * in-memory `Conversation`, so callers can derive the override from whichever
2428
+ * representation they already hold without a redundant row fetch.
2429
+ */
2430
+ export interface OverrideProfileFields {
2431
+ conversationType?: string | null;
2432
+ inferenceProfile?: string | null;
2433
+ inferenceProfileExpiresAt?: number | null;
2434
+ }
2435
+
2436
+ /**
2437
+ * Resolve the per-turn inference-profile override from a conversation's
2438
+ * fields. Returns the `inferenceProfile` for interactive conversations,
2439
+ * `undefined` for automation threads (subagent fan-out, scheduled
2440
+ * tasks) so they run on the workspace defaults rather than
2441
+ * inheriting an interactive override.
2442
+ */
2443
+ export function resolveOverrideProfile(
2444
+ fields: OverrideProfileFields | null,
2445
+ ): string | undefined {
2446
+ if (
2447
+ fields?.conversationType === "background" ||
2448
+ fields?.conversationType === "scheduled"
2449
+ ) {
2450
+ return undefined;
2451
+ }
2452
+ // Treat an expired session as if the override is absent. The eager reaper
2453
+ // clears the row and emits the update event; the lazy check here ensures
2454
+ // correctness on read paths before the reaper fires.
2455
+ //
2456
+ // `<=` (not `<`) for boundary consistency with the rest of the session
2457
+ // logic: the reaper SQL uses `expires_at <= ?`, and the active-session
2458
+ // queries use `expiresAt > now` (i.e. treat exact-expiry as inactive).
2459
+ // Without this, a session at the exact-expiry millisecond would be served
2460
+ // for one extra turn here while being cleared by the reaper.
2461
+ if (
2462
+ fields?.inferenceProfileExpiresAt != null &&
2463
+ fields.inferenceProfileExpiresAt <= Date.now()
2464
+ ) {
2465
+ return undefined;
2466
+ }
2467
+ return fields?.inferenceProfile ?? undefined;
2468
+ }
2469
+
2470
+ /**
2471
+ * Resolve the per-turn inference-profile override by conversation id.
2472
+ * Convenience wrapper for standalone callers (e.g. subagent spawn,
2473
+ * opportunity-wake) that don't already have the conversation in hand.
2474
+ */
2475
+ export function getConversationOverrideProfile(
2476
+ conversationId: string,
2477
+ ): string | undefined {
2478
+ return resolveOverrideProfile(getConversation(conversationId));
2479
+ }
2480
+
2481
+ export function setLastNotifiedInferenceProfile(
2482
+ conversationId: string,
2483
+ profileKey: string | null,
2484
+ ): void {
2485
+ rawRun(
2486
+ "UPDATE conversations SET last_notified_inference_profile = ? WHERE id = ?",
2487
+ profileKey,
2488
+ conversationId,
2489
+ );
2490
+ }
2491
+
2492
+ /**
2493
+ * Delete all conversations, messages, and related data (tool invocations,
2494
+ * memory segments, etc.) from the daemon database.
2495
+ * Returns { conversations, messages } counts.
2496
+ *
2497
+ * Each bulk DELETE is dispatched through {@link runAsyncSqlite}: when
2498
+ * the host has a `sqlite3` CLI it executes in a subprocess and the
2499
+ * daemon's main event loop stays responsive while large tables
2500
+ * (`llm_request_logs`, `tool_invocations`, etc.) are wiped. On hosts
2501
+ * without the CLI the abstraction falls back to in-process blocking
2502
+ * execution — the same behaviour the daemon had before.
2503
+ */
2504
+ export async function clearAll(): Promise<{
2505
+ conversations: number;
2506
+ messages: number;
2507
+ }> {
2508
+ const msgCount =
2509
+ rawGet<{ c: number }>("SELECT COUNT(*) AS c FROM messages")?.c ?? 0;
2510
+ const convCount =
2511
+ rawGet<{ c: number }>("SELECT COUNT(*) AS c FROM conversations")?.c ?? 0;
2512
+
2513
+ // Each DELETE goes through `runAsyncSqlite`. The original code threw
2514
+ // on rawExec failure; mirror that here by throwing when the async
2515
+ // result reports `ok: false`, so the route handler still returns 500.
2516
+ const runOrThrow = async (
2517
+ sql: string,
2518
+ options?: { dbPath?: string },
2519
+ ): Promise<void> => {
2520
+ const result = await runAsyncSqlite(sql, `clearAll: ${sql}`, options);
2521
+ if (!result.ok) {
2522
+ throw new Error(
2523
+ `clearAll: \`${sql}\` failed (${result.backend}): ${result.error ?? "unknown"}`,
2524
+ );
2525
+ }
2526
+ };
2527
+
2528
+ // Delete in dependency order. Cascades handle memory_segments and
2529
+ // tool_invocations, but we explicitly clear non-cascading memory
2530
+ // tables too.
2531
+ //
2532
+ // FTS virtual tables are cleared before their base tables. If an FTS
2533
+ // table is corrupted, the DELETE will fail — we drop the associated
2534
+ // triggers so that the subsequent base-table DELETEs don't also fail
2535
+ // (SQLite triggers are atomic with the triggering statement, so a
2536
+ // corrupted FTS table would roll back every base-table DELETE).
2537
+ await runOrThrow("DELETE FROM memory_segments");
2538
+ await runOrThrow("DELETE FROM memory_summaries");
2539
+ await runOrThrow("DELETE FROM memory_embeddings");
2540
+ // memory_jobs and llm_request_logs each live in their own dedicated
2541
+ // connection; clear them directly on those connections rather than through a
2542
+ // sqlite3 subprocess.
2543
+ rawMemoryRun("DELETE FROM memory_jobs");
2544
+ await runOrThrow("DELETE FROM memory_checkpoints");
2545
+ rawLogsRun("DELETE FROM llm_request_logs");
2546
+ await runOrThrow("DELETE FROM llm_usage_events");
2547
+ await runOrThrow("DELETE FROM message_attachments");
2548
+ await runOrThrow("DELETE FROM attachments");
2549
+ await runOrThrow("DELETE FROM tool_invocations");
2550
+ await runOrThrow("DELETE FROM skill_loaded_events");
2551
+ let messagesFtsCorrupted = false;
2552
+ const ftsResult = await runAsyncSqlite(
2553
+ "DELETE FROM messages_fts",
2554
+ "clearAll:messages-fts",
2555
+ );
2556
+ if (!ftsResult.ok) {
2557
+ log.warn(
2558
+ { error: ftsResult.error, backend: ftsResult.backend },
2559
+ "clearAll: failed to clear messages_fts — dropping triggers so base-table cleanup can proceed",
2560
+ );
2561
+ rawExec("DROP TRIGGER IF EXISTS messages_fts_ai");
2562
+ rawExec("DROP TRIGGER IF EXISTS messages_fts_ad");
2563
+ rawExec("DROP TRIGGER IF EXISTS messages_fts_au");
2564
+ messagesFtsCorrupted = true;
2565
+ }
2566
+ await runOrThrow("DELETE FROM messages");
2567
+ await runOrThrow("DELETE FROM conversations");
2568
+
2569
+ // Record audit event — lifecycle_events is NOT deleted by clearAll(),
2570
+ // so this survives the wipe and provides a permanent trail.
2571
+ rawRun(
2572
+ `INSERT INTO lifecycle_events (id, event_name, created_at) VALUES (?, ?, ?)`,
2573
+ uuid(),
2574
+ "conversations_clear_all",
2575
+ Date.now(),
2576
+ );
2577
+
2578
+ // Rebuild corrupted FTS tables and restore triggers after all base-table
2579
+ // DELETEs have completed. Dropping the virtual table clears the corruption,
2580
+ // and recreating it + triggers means subsequent writes maintain FTS
2581
+ // consistency without requiring a daemon restart.
2582
+ if (messagesFtsCorrupted) {
2583
+ rawExec("DROP TABLE IF EXISTS messages_fts");
2584
+ rawExec(
2585
+ `CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(message_id UNINDEXED, content)`,
2586
+ );
2587
+ rawExec(
2588
+ `CREATE TRIGGER IF NOT EXISTS messages_fts_ai AFTER INSERT ON messages BEGIN INSERT INTO messages_fts(message_id, content) VALUES (new.id, new.content); END`,
2589
+ );
2590
+ rawExec(
2591
+ `CREATE TRIGGER IF NOT EXISTS messages_fts_ad AFTER DELETE ON messages BEGIN DELETE FROM messages_fts WHERE message_id = old.id; END`,
2592
+ );
2593
+ rawExec(
2594
+ `CREATE TRIGGER IF NOT EXISTS messages_fts_au AFTER UPDATE ON messages BEGIN DELETE FROM messages_fts WHERE message_id = old.id; INSERT INTO messages_fts(message_id, content) VALUES (new.id, new.content); END`,
2595
+ );
2596
+ }
2597
+
2598
+ // Clear the disk-view conversations directory and recreate it empty
2599
+ try {
2600
+ rmSync(getConversationsDir(), { recursive: true, force: true });
2601
+ mkdirSync(getConversationsDir(), { recursive: true });
2602
+ } catch (err) {
2603
+ log.warn({ err }, "clearAll: failed to reset conversations directory");
2604
+ }
2605
+
2606
+ void clearAllConversationIds();
2607
+
2608
+ return { conversations: convCount, messages: msgCount };
2609
+ }
2610
+
2611
+ export function deleteLastExchange(conversationId: string): number {
2612
+ const db = getDb();
2613
+
2614
+ // Find the last user message's id
2615
+ const lastUserMsg = db
2616
+ .select({ id: messages.id })
2617
+ .from(messages)
2618
+ .where(
2619
+ and(
2620
+ eq(messages.conversationId, conversationId),
2621
+ eq(messages.role, "user"),
2622
+ ),
2623
+ )
2624
+ .orderBy(sql`rowid DESC`)
2625
+ .limit(1)
2626
+ .get();
2627
+
2628
+ if (!lastUserMsg) return 0;
2629
+
2630
+ // Use rowid to identify the last user message and everything after it.
2631
+ // rowid is monotonically increasing for inserts, so this is safe even if
2632
+ // multiple messages share the same millisecond timestamp.
2633
+ const rowidSubquery = sql`(SELECT rowid FROM messages WHERE id = ${lastUserMsg.id})`;
2634
+ const condition = and(
2635
+ eq(messages.conversationId, conversationId),
2636
+ sql`rowid >= ${rowidSubquery}`,
2637
+ );
2638
+
2639
+ const [{ deleted }] = db
2640
+ .select({ deleted: count() })
2641
+ .from(messages)
2642
+ .where(condition)
2643
+ .all();
2644
+ if (deleted === 0) return 0;
2645
+
2646
+ // Collect attachment IDs linked to the messages being deleted so we can
2647
+ // scope orphan cleanup to only those candidates (not freshly uploaded ones).
2648
+ const messageIds = db
2649
+ .select({ id: messages.id })
2650
+ .from(messages)
2651
+ .where(condition)
2652
+ .all()
2653
+ .map((r) => r.id);
2654
+ const candidateAttachmentIds =
2655
+ messageIds.length > 0
2656
+ ? db
2657
+ .select({ attachmentId: messageAttachments.attachmentId })
2658
+ .from(messageAttachments)
2659
+ .where(inArray(messageAttachments.messageId, messageIds))
2660
+ .all()
2661
+ .map((r) => r.attachmentId)
2662
+ .filter((id): id is string => id != null)
2663
+ : [];
2664
+
2665
+ db.transaction((tx) => {
2666
+ tx.delete(messages).where(condition).run();
2667
+ const maxResult = tx
2668
+ .select({ maxCreatedAt: sql<number | null>`MAX(${messages.createdAt})` })
2669
+ .from(messages)
2670
+ .where(eq(messages.conversationId, conversationId))
2671
+ .get();
2672
+ tx.update(conversations)
2673
+ .set({
2674
+ updatedAt: Date.now(),
2675
+ lastMessageAt: maxResult?.maxCreatedAt ?? null,
2676
+ })
2677
+ .where(eq(conversations.id, conversationId))
2678
+ .run();
2679
+ });
2680
+
2681
+ deleteOrphanAttachments(candidateAttachmentIds);
2682
+
2683
+ return deleted;
2684
+ }
2685
+
2686
+ /**
2687
+ * IDs collected during message deletion for Qdrant vector cleanup.
2688
+ * Callers must delete these from the Qdrant collection after the
2689
+ * SQLite transaction commits.
2690
+ */
2691
+ interface DeletedMemoryIds {
2692
+ segmentIds: string[];
2693
+ deletedSummaryIds: string[];
2694
+ }
2695
+
2696
+ interface WipeConversationResult extends DeletedMemoryIds {
2697
+ cancelledJobCount: number;
2698
+ }
2699
+
2700
+ /**
2701
+ * Reserve an empty message row so the agent loop can stamp outbound
2702
+ * streaming events with a stable identity before content is produced.
2703
+ *
2704
+ * Intentionally skips Qdrant indexing and attention projection — an empty
2705
+ * placeholder is meaningless for either. The caller writes final content
2706
+ * via {@link updateMessageContent} and handles indexing/projection itself.
2707
+ *
2708
+ * Delegates the core insert + retry logic to {@link insertMessageCore}.
2709
+ */
2710
+ export async function reserveMessage(
2711
+ conversationId: string,
2712
+ role: MessageRole,
2713
+ metadata?: Record<string, unknown>,
2714
+ ) {
2715
+ return insertMessageCore({
2716
+ conversationId,
2717
+ role,
2718
+ content: "[]",
2719
+ metadata,
2720
+ });
2721
+ }
2722
+
2723
+ /**
2724
+ * Update the content of an existing message. Used when consolidating
2725
+ * multiple assistant messages into one.
2726
+ */
2727
+ export function updateMessageContent(
2728
+ messageId: string,
2729
+ newContent: string,
2730
+ ): void {
2731
+ const db = getDb();
2732
+ db.update(messages)
2733
+ .set({ content: newContent })
2734
+ .where(eq(messages.id, messageId))
2735
+ .run();
2736
+ }
2737
+
2738
+ /**
2739
+ * Merge `updates` into the metadata JSON of an existing message.
2740
+ * Reads the current metadata, shallow-merges the new fields, and writes back.
2741
+ */
2742
+ export function updateMessageMetadata(
2743
+ messageId: string,
2744
+ updates: Record<string, unknown>,
2745
+ ): void {
2746
+ const db = getDb();
2747
+ const row = db
2748
+ .select({ metadata: messages.metadata })
2749
+ .from(messages)
2750
+ .where(eq(messages.id, messageId))
2751
+ .get();
2752
+ const existing = row?.metadata ? JSON.parse(row.metadata) : {};
2753
+ db.update(messages)
2754
+ .set({ metadata: JSON.stringify({ ...existing, ...updates }) })
2755
+ .where(eq(messages.id, messageId))
2756
+ .run();
2757
+ }
2758
+
2759
+ /**
2760
+ * Atomically update both `content` and (shallow-merged) `metadata` for a
2761
+ * message. Used by edit-propagation paths that need to update the message
2762
+ * body and stamp metadata (e.g. `slackMeta.editedAt`) in a single
2763
+ * transaction so a partial write cannot leak.
2764
+ *
2765
+ * `metadataUpdates` is shallow-merged into the existing top-level metadata
2766
+ * object. To merge into a nested sub-key (e.g. `slackMeta`), the caller
2767
+ * must compute the merged sub-value first and pass `{ slackMeta: merged }`.
2768
+ */
2769
+ export function updateMessageContentAndMetadata(
2770
+ messageId: string,
2771
+ newContent: string,
2772
+ metadataUpdates: Record<string, unknown>,
2773
+ ): void {
2774
+ const db = getDb();
2775
+ db.transaction((tx) => {
2776
+ const row = tx
2777
+ .select({ metadata: messages.metadata })
2778
+ .from(messages)
2779
+ .where(eq(messages.id, messageId))
2780
+ .get();
2781
+ const existing = row?.metadata ? safeParseRecord(row.metadata) : {};
2782
+ tx.update(messages)
2783
+ .set({
2784
+ content: newContent,
2785
+ metadata: JSON.stringify({ ...existing, ...metadataUpdates }),
2786
+ })
2787
+ .where(eq(messages.id, messageId))
2788
+ .run();
2789
+ });
2790
+ }
2791
+
2792
+ /**
2793
+ * Re-link all attachments from a set of source messages to a target message.
2794
+ * Used during message consolidation so that attachments linked to deleted
2795
+ * messages survive the ON DELETE CASCADE on message_attachments.
2796
+ */
2797
+ export function relinkAttachments(
2798
+ fromMessageIds: string[],
2799
+ toMessageId: string,
2800
+ ): number {
2801
+ if (fromMessageIds.length === 0) return 0;
2802
+ const db = getDb();
2803
+
2804
+ // Count how many links will be moved before updating.
2805
+ const [{ total }] = db
2806
+ .select({ total: count() })
2807
+ .from(messageAttachments)
2808
+ .where(inArray(messageAttachments.messageId, fromMessageIds))
2809
+ .all();
2810
+
2811
+ if (total === 0) return 0;
2812
+
2813
+ db.update(messageAttachments)
2814
+ .set({ messageId: toMessageId })
2815
+ .where(inArray(messageAttachments.messageId, fromMessageIds))
2816
+ .run();
2817
+
2818
+ return total;
2819
+ }
2820
+
2821
+ /**
2822
+ * Delete a single message by ID without cascading to message_runs or
2823
+ * channel_inbound_events. Nullable FK columns in those tables are set to
2824
+ * NULL before the message row is removed, so associated run and event
2825
+ * records survive.
2826
+ *
2827
+ * Returns segment IDs so the caller can clean up the corresponding
2828
+ * Qdrant vector entries.
2829
+ */
2830
+ export function deleteMessageById(messageId: string): DeletedMemoryIds {
2831
+ const db = getDb();
2832
+ const result: DeletedMemoryIds = {
2833
+ segmentIds: [],
2834
+ deletedSummaryIds: [],
2835
+ };
2836
+
2837
+ // Collect attachment IDs linked to this message before cascade-delete
2838
+ // so we can scope orphan cleanup to only those candidates.
2839
+ const candidateAttachmentIds = db
2840
+ .select({ attachmentId: messageAttachments.attachmentId })
2841
+ .from(messageAttachments)
2842
+ .where(eq(messageAttachments.messageId, messageId))
2843
+ .all()
2844
+ .map((r) => r.attachmentId)
2845
+ .filter((id): id is string => id !== undefined);
2846
+
2847
+ // Look up the conversation before the transaction so we can recalculate lastMessageAt.
2848
+ const msgRow = db
2849
+ .select({ conversationId: messages.conversationId })
2850
+ .from(messages)
2851
+ .where(eq(messages.id, messageId))
2852
+ .get();
2853
+
2854
+ db.transaction((tx) => {
2855
+ // Collect memory segment IDs linked to this message before cascade.
2856
+ const linkedSegments = tx
2857
+ .select({ id: memorySegments.id })
2858
+ .from(memorySegments)
2859
+ .where(eq(memorySegments.messageId, messageId))
2860
+ .all();
2861
+ result.segmentIds = linkedSegments.map((r) => r.id);
2862
+
2863
+ // Detach nullable FK references so the cascade doesn't destroy them.
2864
+ tx.update(channelInboundEvents)
2865
+ .set({ messageId: null })
2866
+ .where(eq(channelInboundEvents.messageId, messageId))
2867
+ .run();
2868
+
2869
+ // Now safe to delete — NOT NULL cascades remove memory_segments
2870
+ // and message_attachments.
2871
+ tx.delete(messages).where(eq(messages.id, messageId)).run();
2872
+
2873
+ // Recalculate lastMessageAt after deletion.
2874
+ if (msgRow) {
2875
+ const maxResult = tx
2876
+ .select({
2877
+ maxCreatedAt: sql<number | null>`MAX(${messages.createdAt})`,
2878
+ })
2879
+ .from(messages)
2880
+ .where(eq(messages.conversationId, msgRow.conversationId))
2881
+ .get();
2882
+ tx.update(conversations)
2883
+ .set({ lastMessageAt: maxResult?.maxCreatedAt ?? null })
2884
+ .where(eq(conversations.id, msgRow.conversationId))
2885
+ .run();
2886
+ }
2887
+
2888
+ // Clean up segment embeddings from SQLite (Qdrant cleanup is the caller's job).
2889
+ if (result.segmentIds.length > 0) {
2890
+ tx.delete(memoryEmbeddings)
2891
+ .where(
2892
+ and(
2893
+ eq(memoryEmbeddings.targetType, "segment"),
2894
+ inArray(memoryEmbeddings.targetId, result.segmentIds),
2895
+ ),
2896
+ )
2897
+ .run();
2898
+ }
2899
+ });
2900
+
2901
+ deleteOrphanAttachments(candidateAttachmentIds);
2902
+
2903
+ return result;
2904
+ }
2905
+
2906
+ export function setConversationOriginChannelIfUnset(
2907
+ conversationId: string,
2908
+ channel: ChannelId,
2909
+ ): void {
2910
+ const db = getDb();
2911
+ db.update(conversations)
2912
+ .set({ originChannel: channel })
2913
+ .where(
2914
+ and(
2915
+ eq(conversations.id, conversationId),
2916
+ isNull(conversations.originChannel),
2917
+ ),
2918
+ )
2919
+ .run();
2920
+ }
2921
+
2922
+ export function getConversationOriginChannel(
2923
+ conversationId: string,
2924
+ ): ChannelId | null {
2925
+ const db = getDb();
2926
+ const row = db
2927
+ .select({ originChannel: conversations.originChannel })
2928
+ .from(conversations)
2929
+ .where(eq(conversations.id, conversationId))
2930
+ .get();
2931
+ return parseChannelId(row?.originChannel) ?? null;
2932
+ }
2933
+
2934
+ export function setConversationOriginInterfaceIfUnset(
2935
+ conversationId: string,
2936
+ interfaceId: InterfaceId,
2937
+ ): void {
2938
+ const db = getDb();
2939
+ db.update(conversations)
2940
+ .set({ originInterface: interfaceId })
2941
+ .where(
2942
+ and(
2943
+ eq(conversations.id, conversationId),
2944
+ isNull(conversations.originInterface),
2945
+ ),
2946
+ )
2947
+ .run();
2948
+ }
2949
+
2950
+ export function getConversationOriginInterface(
2951
+ conversationId: string,
2952
+ ): InterfaceId | null {
2953
+ const db = getDb();
2954
+ const row = db
2955
+ .select({ originInterface: conversations.originInterface })
2956
+ .from(conversations)
2957
+ .where(eq(conversations.id, conversationId))
2958
+ .get();
2959
+ return parseInterfaceId(row?.originInterface) ?? null;
2960
+ }
2961
+
2962
+ /**
2963
+ * Return the most recent non-null provenanceTrustClass from user messages
2964
+ * in the given conversation, or `undefined` if none is found.
2965
+ *
2966
+ * Used by the pointer message trust resolver to detect conversations
2967
+ * whose audience is a guardian, trusted_contact, or unverified_contact
2968
+ * outside desktop-origin conversations.
2969
+ */
2970
+ export function getConversationRecentProvenanceTrustClass(
2971
+ conversationId: string,
2972
+ ):
2973
+ | "guardian"
2974
+ | "trusted_contact"
2975
+ | "unverified_contact"
2976
+ | "unknown"
2977
+ | undefined {
2978
+ const row = rawGet<{ metadata: string | null }>(
2979
+ `SELECT metadata FROM messages
2980
+ WHERE conversation_id = ? AND role = 'user' AND metadata IS NOT NULL
2981
+ ORDER BY created_at DESC LIMIT 1`,
2982
+ conversationId,
2983
+ );
2984
+ if (!row?.metadata) return undefined;
2985
+ try {
2986
+ const parsed = messageMetadataSchema.safeParse(JSON.parse(row.metadata));
2987
+ return parsed.success ? parsed.data.provenanceTrustClass : undefined;
2988
+ } catch {
2989
+ return undefined;
2990
+ }
2991
+ }
2992
+
2993
+ // ---------------------------------------------------------------------------
2994
+ // CRUD functions for display_order and is_pinned
2995
+ // ---------------------------------------------------------------------------
2996
+
2997
+ export function batchSetDisplayOrders(
2998
+ updates: Array<{
2999
+ id: string;
3000
+ displayOrder: number | null;
3001
+ isPinned?: boolean;
3002
+ groupId?: string | null;
3003
+ }>,
3004
+ ): void {
3005
+ ensureDisplayOrderMigration();
3006
+ ensureGroupMigration();
3007
+ rawExec("BEGIN");
3008
+ try {
3009
+ for (const update of updates) {
3010
+ if (update.groupId !== undefined) {
3011
+ // New client: groupId is authoritative.
3012
+ // Derive is_pinned from groupId.
3013
+ // Sanitize: if groupId is null or references a deleted/unknown group,
3014
+ // fall back to "system:all" to avoid FK violation that would roll back
3015
+ // the entire batch.
3016
+ let safeGroupId = update.groupId;
3017
+ if (safeGroupId === null) {
3018
+ safeGroupId = "system:all";
3019
+ } else if (
3020
+ !rawGet<{ id: string }>(
3021
+ "SELECT id FROM conversation_groups WHERE id = ?",
3022
+ safeGroupId,
3023
+ )
3024
+ ) {
3025
+ safeGroupId = "system:all";
3026
+ }
3027
+ // Moving a conversation into the Scheduled/Background system groups
3028
+ // is an explicit demotion out of Recents, so clear any `surfaced_at`
3029
+ // promotion in the same write — otherwise the surfaced marker would
3030
+ // keep the row in the standard listing and the move would appear to
3031
+ // do nothing.
3032
+ const clearsSurfaced =
3033
+ safeGroupId === "system:background" ||
3034
+ safeGroupId === "system:scheduled";
3035
+ rawRun(
3036
+ `UPDATE conversations SET display_order = ?, is_pinned = ?, group_id = ?${
3037
+ clearsSurfaced ? ", surfaced_at = NULL" : ""
3038
+ } WHERE id = ?`,
3039
+ update.displayOrder,
3040
+ safeGroupId === "system:pinned" ? 1 : 0,
3041
+ safeGroupId,
3042
+ update.id,
3043
+ );
3044
+ } else if (update.isPinned === undefined) {
3045
+ // Only displayOrder provided — preserve existing pin state and group.
3046
+ rawRun(
3047
+ "UPDATE conversations SET display_order = ? WHERE id = ?",
3048
+ update.displayOrder,
3049
+ update.id,
3050
+ );
3051
+ } else if (update.isPinned) {
3052
+ rawRun(
3053
+ "UPDATE conversations SET display_order = ?, is_pinned = 1, group_id = 'system:pinned' WHERE id = ?",
3054
+ update.displayOrder,
3055
+ update.id,
3056
+ );
3057
+ } else {
3058
+ // Restore system group from source/conversationType when unpinning,
3059
+ // instead of clearing to NULL (which would lose provenance).
3060
+ rawRun(
3061
+ `UPDATE conversations SET display_order = ?, is_pinned = 0,
3062
+ group_id = CASE WHEN group_id = 'system:pinned' THEN
3063
+ CASE
3064
+ WHEN source IN ('schedule', 'reminder') THEN 'system:scheduled'
3065
+ WHEN source IN ('heartbeat', 'task') THEN 'system:background'
3066
+ WHEN conversation_type = 'background' AND COALESCE(source, '') != 'notification' THEN 'system:background'
3067
+ ELSE 'system:all'
3068
+ END
3069
+ ELSE group_id END
3070
+ WHERE id = ?`,
3071
+ update.displayOrder,
3072
+ update.id,
3073
+ );
3074
+ }
3075
+ }
3076
+ rawExec("COMMIT");
3077
+ } catch (err) {
3078
+ rawExec("ROLLBACK");
3079
+ throw err;
3080
+ }
3081
+ }
3082
+
3083
+ export function getDisplayMetaForConversations(
3084
+ conversationIds: string[],
3085
+ ): Map<
3086
+ string,
3087
+ { displayOrder: number | null; isPinned: boolean; groupId: string | null }
3088
+ > {
3089
+ ensureDisplayOrderMigration();
3090
+ ensureGroupMigration();
3091
+ const result = new Map<
3092
+ string,
3093
+ { displayOrder: number | null; isPinned: boolean; groupId: string | null }
3094
+ >();
3095
+ if (conversationIds.length === 0) return result;
3096
+ for (const id of conversationIds) {
3097
+ const row = rawGet<{
3098
+ display_order: number | null;
3099
+ is_pinned: number | null;
3100
+ group_id: string | null;
3101
+ }>(
3102
+ "SELECT display_order, is_pinned, group_id FROM conversations WHERE id = ?",
3103
+ id,
3104
+ );
3105
+ result.set(id, {
3106
+ displayOrder: row?.display_order ?? null,
3107
+ isPinned: (row?.is_pinned ?? 0) === 1,
3108
+ groupId: row?.group_id ?? null,
3109
+ });
3110
+ }
3111
+ return result;
3112
+ }
3113
+
3114
+ // ── Turn boundary resolution ─────────────────────────────────────────
3115
+
3116
+ /**
3117
+ * Returns `true` if a message is a tool-result user message — i.e. its
3118
+ * role is "user" and its content is a JSON array where every block has
3119
+ * `type === "tool_result"`. These synthetic user messages are injected
3120
+ * between assistant messages within a single agent turn and should NOT
3121
+ * be treated as turn boundaries.
3122
+ */
3123
+ function isToolResultMessage(role: string, content: string): boolean {
3124
+ if (role !== "user") return false;
3125
+ try {
3126
+ const parsed = JSON.parse(content);
3127
+ if (!Array.isArray(parsed) || parsed.length === 0) return false;
3128
+ return parsed.every(
3129
+ (block: unknown) =>
3130
+ block != null &&
3131
+ typeof block === "object" &&
3132
+ (block as Record<string, unknown>).type === "tool_result",
3133
+ );
3134
+ } catch {
3135
+ return false;
3136
+ }
3137
+ }
3138
+
3139
+ /**
3140
+ * Returns the time boundaries (start/end `createdAt` values) for the turn
3141
+ * containing the given message. The bounds span from the real user message
3142
+ * that started the turn to just before the real user message that starts the
3143
+ * next turn (or to the end of the conversation if this is the last turn).
3144
+ *
3145
+ * Also extends the end boundary to capture orphaned LLM request logs from
3146
+ * deleted intermediate messages (e.g. removed by retry/deleteLastExchange).
3147
+ *
3148
+ * Returns null if the message is the only one in the conversation.
3149
+ */
3150
+ export function getTurnTimeBounds(
3151
+ conversationId: string,
3152
+ messageCreatedAt: number,
3153
+ ): { startTime: number; endTime: number } | null {
3154
+ const db = getDb();
3155
+
3156
+ // Walk backward (by rowid, not just createdAt) to find the real user
3157
+ // message that starts this turn.
3158
+ const rowidSubquery = sql`(
3159
+ SELECT rowid FROM messages
3160
+ WHERE conversation_id = ${conversationId}
3161
+ AND created_at <= ${messageCreatedAt}
3162
+ ORDER BY rowid DESC LIMIT 1
3163
+ )`;
3164
+ const backwardRows = db
3165
+ .select({
3166
+ role: messages.role,
3167
+ content: messages.content,
3168
+ createdAt: messages.createdAt,
3169
+ })
3170
+ .from(messages)
3171
+ .where(
3172
+ and(
3173
+ eq(messages.conversationId, conversationId),
3174
+ sql`rowid <= ${rowidSubquery}`,
3175
+ ),
3176
+ )
3177
+ .orderBy(sql`rowid DESC`)
3178
+ .limit(50)
3179
+ .all();
3180
+
3181
+ let startTime = messageCreatedAt;
3182
+ for (const row of backwardRows) {
3183
+ if (row.role === "user" && !isToolResultMessage(row.role, row.content)) {
3184
+ startTime = row.createdAt;
3185
+ break;
3186
+ }
3187
+ }
3188
+
3189
+ // Walk forward (by rowid) to find the next real user message.
3190
+ const forwardRowidSubquery = sql`(
3191
+ SELECT rowid FROM messages
3192
+ WHERE conversation_id = ${conversationId}
3193
+ AND created_at >= ${messageCreatedAt}
3194
+ ORDER BY rowid DESC LIMIT 1
3195
+ )`;
3196
+ const forwardRows = db
3197
+ .select({
3198
+ role: messages.role,
3199
+ content: messages.content,
3200
+ createdAt: messages.createdAt,
3201
+ })
3202
+ .from(messages)
3203
+ .where(
3204
+ and(
3205
+ eq(messages.conversationId, conversationId),
3206
+ sql`rowid > ${forwardRowidSubquery}`,
3207
+ ),
3208
+ )
3209
+ .orderBy(sql`rowid ASC`)
3210
+ .limit(50)
3211
+ .all();
3212
+
3213
+ let endTime = messageCreatedAt;
3214
+ let nextTurnStart: number | null = null;
3215
+ for (const row of forwardRows) {
3216
+ if (row.role === "user" && !isToolResultMessage(row.role, row.content)) {
3217
+ nextTurnStart = row.createdAt;
3218
+ break;
3219
+ }
3220
+ endTime = row.createdAt;
3221
+ }
3222
+
3223
+ // When the next turn start has a strictly greater timestamp, use it minus
3224
+ // 1ms as the hard upper bound. When timestamps collide (e.g. in tests),
3225
+ // don't extend — the message-ID-based query is authoritative.
3226
+ if (nextTurnStart != null && nextTurnStart > endTime) {
3227
+ endTime = nextTurnStart - 1;
3228
+ }
3229
+
3230
+ // Extend end boundary to the latest log that falls within the turn window.
3231
+ // Orphaned logs from deleted intermediate messages may have timestamps
3232
+ // beyond any surviving message. Cap at 30 minutes to avoid sweeping in
3233
+ // logs from a much later turn.
3234
+ const MAX_TURN_DURATION_MS = 30 * 60 * 1000;
3235
+ const hardCeiling =
3236
+ nextTurnStart != null && nextTurnStart > startTime
3237
+ ? nextTurnStart - 1
3238
+ : startTime + MAX_TURN_DURATION_MS;
3239
+
3240
+ if (hardCeiling > endTime) {
3241
+ // llm_request_logs lives in the dedicated logs connection.
3242
+ const latestLog = logsDb()
3243
+ .select({ createdAt: llmRequestLogs.createdAt })
3244
+ .from(llmRequestLogs)
3245
+ .where(
3246
+ and(
3247
+ eq(llmRequestLogs.conversationId, conversationId),
3248
+ gte(llmRequestLogs.createdAt, startTime),
3249
+ lte(llmRequestLogs.createdAt, hardCeiling),
3250
+ ),
3251
+ )
3252
+ .orderBy(desc(llmRequestLogs.createdAt))
3253
+ .limit(1)
3254
+ .get();
3255
+
3256
+ if (latestLog && latestLog.createdAt > endTime) {
3257
+ endTime = latestLog.createdAt;
3258
+ }
3259
+ }
3260
+
3261
+ return { startTime, endTime };
3262
+ }
3263
+
3264
+ /**
3265
+ * Resolve all assistant message IDs that belong to the same agent turn
3266
+ * as the given `messageId`. A "turn" is bounded by:
3267
+ * - The start of the conversation, or
3268
+ * - A user message whose content is NOT a tool_result array.
3269
+ *
3270
+ * Within a multi-step agent loop, the pattern is:
3271
+ * user msg → assistant A1 → user (tool_result) → assistant A2 → ...
3272
+ * All assistant messages from A1 through the queried message (and beyond,
3273
+ * up to the next real user message) are part of the same turn.
3274
+ *
3275
+ * Returns `[messageId]` as a fallback if the message is not found,
3276
+ * preserving backward compatibility for callers.
3277
+ */
3278
+ export function getAssistantMessageIdsInTurn(messageId: string): string[] {
3279
+ const db = getDb();
3280
+
3281
+ // Look up the target message to get its conversationId and createdAt.
3282
+ const target = getMessageById(messageId);
3283
+ if (!target) return [messageId];
3284
+
3285
+ // Walk backward from the target message to find the turn boundary.
3286
+ // Limit to 50 rows — sufficient for even aggressive tool-use loops.
3287
+ const backwardRows = db
3288
+ .select({
3289
+ id: messages.id,
3290
+ role: messages.role,
3291
+ content: messages.content,
3292
+ createdAt: messages.createdAt,
3293
+ })
3294
+ .from(messages)
3295
+ .where(
3296
+ and(
3297
+ eq(messages.conversationId, target.conversationId),
3298
+ lte(messages.createdAt, target.createdAt),
3299
+ ),
3300
+ )
3301
+ .orderBy(desc(messages.createdAt))
3302
+ .limit(50)
3303
+ .all();
3304
+
3305
+ const assistantIds: string[] = [];
3306
+ let boundaryCreatedAt: number | null = null;
3307
+
3308
+ for (const row of backwardRows) {
3309
+ if (row.role === "assistant") {
3310
+ assistantIds.push(row.id);
3311
+ } else if (row.role === "user") {
3312
+ if (isToolResultMessage(row.role, row.content)) {
3313
+ // Tool-result user message — still within the same turn, continue.
3314
+ continue;
3315
+ }
3316
+ // Real user message — this is the turn boundary.
3317
+ boundaryCreatedAt = row.createdAt;
3318
+ break;
3319
+ }
3320
+ }
3321
+
3322
+ // Walk forward from the target to collect any later assistant messages
3323
+ // still within the same turn (e.g. when querying an intermediate
3324
+ // message like A1 in a multi-step turn A1 → tool_result → A2).
3325
+ const forwardRows = db
3326
+ .select({
3327
+ id: messages.id,
3328
+ role: messages.role,
3329
+ content: messages.content,
3330
+ createdAt: messages.createdAt,
3331
+ })
3332
+ .from(messages)
3333
+ .where(
3334
+ and(
3335
+ eq(messages.conversationId, target.conversationId),
3336
+ gt(messages.createdAt, target.createdAt),
3337
+ ),
3338
+ )
3339
+ .orderBy(asc(messages.createdAt))
3340
+ .limit(50)
3341
+ .all();
3342
+
3343
+ for (const row of forwardRows) {
3344
+ if (row.role === "assistant") {
3345
+ if (!assistantIds.includes(row.id)) {
3346
+ assistantIds.push(row.id);
3347
+ }
3348
+ } else if (row.role === "user") {
3349
+ if (isToolResultMessage(row.role, row.content)) {
3350
+ // Tool-result user message — still within the same turn.
3351
+ continue;
3352
+ }
3353
+ // Real user message — end of the turn.
3354
+ break;
3355
+ }
3356
+ }
3357
+
3358
+ // Also query forward from the backward-walk boundary to pick up any
3359
+ // assistant messages between the boundary and the target that may have
3360
+ // been missed (e.g. due to the 50-row limit in the backward walk).
3361
+ if (boundaryCreatedAt != null) {
3362
+ const gapRows = db
3363
+ .select({
3364
+ id: messages.id,
3365
+ role: messages.role,
3366
+ createdAt: messages.createdAt,
3367
+ })
3368
+ .from(messages)
3369
+ .where(
3370
+ and(
3371
+ eq(messages.conversationId, target.conversationId),
3372
+ gt(messages.createdAt, boundaryCreatedAt),
3373
+ lte(messages.createdAt, target.createdAt),
3374
+ ),
3375
+ )
3376
+ .orderBy(asc(messages.createdAt))
3377
+ .all();
3378
+
3379
+ for (const row of gapRows) {
3380
+ if (row.role === "assistant" && !assistantIds.includes(row.id)) {
3381
+ assistantIds.push(row.id);
3382
+ }
3383
+ }
3384
+ }
3385
+
3386
+ // Sort by createdAt to ensure stable ordering.
3387
+ // Re-fetch createdAt for all collected IDs so the sort is accurate.
3388
+ if (assistantIds.length <= 1) return assistantIds;
3389
+
3390
+ const idSet = new Set(assistantIds);
3391
+ const sorted = db
3392
+ .select({ id: messages.id, createdAt: messages.createdAt })
3393
+ .from(messages)
3394
+ .where(
3395
+ and(
3396
+ eq(messages.conversationId, target.conversationId),
3397
+ inArray(messages.id, [...idSet]),
3398
+ ),
3399
+ )
3400
+ .orderBy(asc(messages.createdAt))
3401
+ .all();
3402
+
3403
+ return sorted.map((r) => r.id);
3404
+ }