@vellumai/assistant 0.10.4-staging.1 → 0.10.4

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