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