@vellumai/assistant 0.5.16 → 0.6.0

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 (407) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +0 -3
  3. package/knip.json +2 -1
  4. package/openapi.yaml +660 -80
  5. package/package.json +1 -1
  6. package/src/__tests__/actor-token-service.test.ts +68 -0
  7. package/src/__tests__/agent-loop.test.ts +0 -32
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  9. package/src/__tests__/anthropic-provider.test.ts +57 -3
  10. package/src/__tests__/app-compiler.test.ts +120 -0
  11. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  12. package/src/__tests__/call-conversation-messages.test.ts +2 -6
  13. package/src/__tests__/call-domain.test.ts +2 -6
  14. package/src/__tests__/call-pointer-messages.test.ts +2 -14
  15. package/src/__tests__/call-recovery.test.ts +2 -6
  16. package/src/__tests__/call-routes-http.test.ts +2 -6
  17. package/src/__tests__/call-store.test.ts +2 -6
  18. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
  19. package/src/__tests__/canonical-guardian-store.test.ts +2 -6
  20. package/src/__tests__/channel-delivery-store.test.ts +2 -6
  21. package/src/__tests__/channel-retry-sweep.test.ts +2 -6
  22. package/src/__tests__/checker.test.ts +25 -3
  23. package/src/__tests__/clawhub.test.ts +54 -24
  24. package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
  25. package/src/__tests__/cli-memory.test.ts +74 -69
  26. package/src/__tests__/config-schema.test.ts +1 -1
  27. package/src/__tests__/config-set-platform-guard.test.ts +302 -0
  28. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
  29. package/src/__tests__/contacts-tools.test.ts +31 -0
  30. package/src/__tests__/context-overflow-reducer.test.ts +86 -0
  31. package/src/__tests__/context-token-estimator.test.ts +175 -10
  32. package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
  33. package/src/__tests__/conversation-agent-loop.test.ts +9 -0
  34. package/src/__tests__/conversation-attachments.test.ts +2 -6
  35. package/src/__tests__/conversation-attention-store.test.ts +2 -6
  36. package/src/__tests__/conversation-clear-safety.test.ts +2 -6
  37. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
  38. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
  39. package/src/__tests__/conversation-disk-view.test.ts +2 -6
  40. package/src/__tests__/conversation-error.test.ts +33 -2
  41. package/src/__tests__/conversation-fork-crud.test.ts +2 -6
  42. package/src/__tests__/conversation-history-web-search.test.ts +5 -0
  43. package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
  44. package/src/__tests__/conversation-media-retry.test.ts +91 -0
  45. package/src/__tests__/conversation-starter-routes.test.ts +20 -11
  46. package/src/__tests__/conversation-store.test.ts +2 -6
  47. package/src/__tests__/conversation-usage.test.ts +2 -6
  48. package/src/__tests__/conversation-wipe.test.ts +11 -408
  49. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  50. package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
  51. package/src/__tests__/credential-security-e2e.test.ts +2 -0
  52. package/src/__tests__/followup-tools.test.ts +2 -6
  53. package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
  54. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
  55. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
  56. package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
  57. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
  58. package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
  59. package/src/__tests__/guardian-action-store.test.ts +2 -6
  60. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
  61. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
  62. package/src/__tests__/guardian-dispatch.test.ts +2 -6
  63. package/src/__tests__/guardian-grant-minting.test.ts +2 -14
  64. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
  65. package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
  66. package/src/__tests__/guardian-routing-state.test.ts +2 -6
  67. package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
  68. package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
  69. package/src/__tests__/injection-block.test.ts +154 -0
  70. package/src/__tests__/install-meta.test.ts +506 -0
  71. package/src/__tests__/install-skill-routing.test.ts +292 -0
  72. package/src/__tests__/invite-redemption-service.test.ts +2 -6
  73. package/src/__tests__/invite-routes-http.test.ts +2 -6
  74. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
  75. package/src/__tests__/list-messages-attachments.test.ts +2 -6
  76. package/src/__tests__/llm-context-route-provider.test.ts +2 -6
  77. package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
  78. package/src/__tests__/llm-usage-store.test.ts +2 -6
  79. package/src/__tests__/log-export-workspace.test.ts +2 -6
  80. package/src/__tests__/managed-store.test.ts +38 -11
  81. package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
  82. package/src/__tests__/memory-recall-log-store.test.ts +2 -6
  83. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
  84. package/src/__tests__/non-member-access-request.test.ts +2 -6
  85. package/src/__tests__/notification-guardian-path.test.ts +2 -6
  86. package/src/__tests__/oauth-cli.test.ts +364 -2
  87. package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
  88. package/src/__tests__/outlook-attachments.test.ts +301 -0
  89. package/src/__tests__/outlook-automation-tools.test.ts +425 -0
  90. package/src/__tests__/outlook-categories.test.ts +212 -0
  91. package/src/__tests__/outlook-client-automation.test.ts +246 -0
  92. package/src/__tests__/outlook-compose-tools.test.ts +325 -0
  93. package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
  94. package/src/__tests__/outlook-email-watcher.test.ts +322 -0
  95. package/src/__tests__/outlook-follow-up.test.ts +196 -0
  96. package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
  97. package/src/__tests__/outlook-trash.test.ts +77 -0
  98. package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
  99. package/src/__tests__/platform-callback-registration.test.ts +4 -4
  100. package/src/__tests__/playbook-execution.test.ts +76 -80
  101. package/src/__tests__/playbook-tools.test.ts +5 -7
  102. package/src/__tests__/provider-error-scenarios.test.ts +21 -0
  103. package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
  104. package/src/__tests__/registry.test.ts +2 -2
  105. package/src/__tests__/require-fresh-approval.test.ts +64 -2
  106. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
  107. package/src/__tests__/runtime-events-sse.test.ts +2 -6
  108. package/src/__tests__/schedule-store.test.ts +2 -6
  109. package/src/__tests__/schedule-tools.test.ts +2 -6
  110. package/src/__tests__/scheduler-recurrence.test.ts +1 -5
  111. package/src/__tests__/scoped-approval-grants.test.ts +2 -6
  112. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
  113. package/src/__tests__/search-skills-unified.test.ts +421 -0
  114. package/src/__tests__/secret-onetime-send.test.ts +2 -0
  115. package/src/__tests__/send-endpoint-busy.test.ts +2 -6
  116. package/src/__tests__/sequence-store.test.ts +2 -6
  117. package/src/__tests__/server-history-render.test.ts +2 -6
  118. package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
  119. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  120. package/src/__tests__/skill-load-feature-flag.test.ts +11 -11
  121. package/src/__tests__/skill-memory.test.ts +140 -98
  122. package/src/__tests__/skills-uninstall.test.ts +2 -2
  123. package/src/__tests__/skills.test.ts +1 -1
  124. package/src/__tests__/slack-inbound-verification.test.ts +2 -6
  125. package/src/__tests__/task-compiler.test.ts +2 -6
  126. package/src/__tests__/task-management-tools.test.ts +2 -6
  127. package/src/__tests__/task-memory-cleanup.test.ts +173 -229
  128. package/src/__tests__/task-runner.test.ts +2 -6
  129. package/src/__tests__/task-scheduler.test.ts +2 -6
  130. package/src/__tests__/test-preload.ts +3 -0
  131. package/src/__tests__/tool-approval-handler.test.ts +2 -6
  132. package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
  133. package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
  134. package/src/__tests__/trust-store.test.ts +1 -1
  135. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
  136. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -6
  137. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
  138. package/src/__tests__/trusted-contact-verification.test.ts +2 -6
  139. package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
  140. package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
  141. package/src/__tests__/usage-routes.test.ts +2 -6
  142. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  143. package/src/__tests__/voice-invite-redemption.test.ts +2 -6
  144. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
  145. package/src/__tests__/voice-session-bridge.test.ts +2 -6
  146. package/src/__tests__/volume-security-guard.test.ts +2 -0
  147. package/src/__tests__/workspace-lifecycle.test.ts +29 -1
  148. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
  149. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
  150. package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
  151. package/src/__tests__/workspace-policy.test.ts +1 -1
  152. package/src/agent/attachments.ts +7 -2
  153. package/src/agent/image-optimize.ts +165 -0
  154. package/src/agent/loop.ts +1 -15
  155. package/src/bundler/app-compiler.ts +179 -2
  156. package/src/bundler/package-resolver.ts +3 -5
  157. package/src/cli/__tests__/notifications.test.ts +1 -2
  158. package/src/cli/cli-memory.ts +67 -64
  159. package/src/cli/commands/avatar.ts +3 -3
  160. package/src/cli/commands/config.ts +26 -13
  161. package/src/cli/commands/doctor.ts +2 -2
  162. package/src/cli/commands/memory.ts +41 -55
  163. package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
  164. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
  165. package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
  166. package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
  167. package/src/cli/commands/oauth/connect.ts +11 -6
  168. package/src/cli/commands/oauth/mode.ts +7 -0
  169. package/src/cli/commands/oauth/shared.ts +39 -3
  170. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  171. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  172. package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
  173. package/src/cli/commands/platform/index.ts +16 -16
  174. package/src/cli/commands/skills.ts +88 -16
  175. package/src/cli/commands/trust.ts +2 -2
  176. package/src/cli/lib/daemon-credential-client.ts +2 -3
  177. package/src/config/bundled-skills/acp/TOOLS.json +1 -1
  178. package/src/config/bundled-skills/contacts/SKILL.md +0 -1
  179. package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
  180. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
  181. package/src/config/bundled-skills/gmail/SKILL.md +2 -10
  182. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
  183. package/src/config/bundled-skills/messaging/SKILL.md +10 -18
  184. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
  185. package/src/config/bundled-skills/outlook/SKILL.md +189 -0
  186. package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
  187. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
  188. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
  189. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
  190. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
  191. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
  192. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
  193. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
  194. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
  195. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
  196. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
  197. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
  198. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
  199. package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
  200. package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
  201. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
  202. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
  203. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
  204. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
  205. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
  206. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
  207. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
  208. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
  209. package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
  210. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
  211. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
  212. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
  213. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
  214. package/src/config/bundled-skills/slack/SKILL.md +1 -7
  215. package/src/config/bundled-tool-registry.ts +56 -4
  216. package/src/config/env-registry.ts +15 -8
  217. package/src/config/feature-flag-registry.json +21 -124
  218. package/src/config/schemas/platform.ts +8 -0
  219. package/src/config/schemas/timeouts.ts +1 -1
  220. package/src/config/skills.ts +18 -7
  221. package/src/context/token-estimator.ts +25 -18
  222. package/src/context/window-manager.ts +6 -2
  223. package/src/credential-execution/process-manager.ts +3 -1
  224. package/src/daemon/context-overflow-reducer.ts +46 -2
  225. package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
  226. package/src/daemon/conversation-agent-loop.ts +96 -61
  227. package/src/daemon/conversation-error.ts +31 -8
  228. package/src/daemon/conversation-lifecycle.ts +33 -0
  229. package/src/daemon/conversation-media-retry.ts +85 -7
  230. package/src/daemon/conversation-notifiers.ts +4 -1
  231. package/src/daemon/conversation-runtime-assembly.ts +5 -0
  232. package/src/daemon/conversation.ts +41 -2
  233. package/src/daemon/daemon-control.ts +8 -2
  234. package/src/daemon/handlers/shared.ts +22 -12
  235. package/src/daemon/handlers/skills.ts +416 -202
  236. package/src/daemon/lifecycle.ts +40 -1
  237. package/src/daemon/main.ts +5 -1
  238. package/src/daemon/message-types/conversations.ts +4 -1
  239. package/src/daemon/message-types/messages.ts +3 -1
  240. package/src/daemon/message-types/skills.ts +97 -36
  241. package/src/daemon/providers-setup.ts +5 -0
  242. package/src/daemon/server.ts +11 -2
  243. package/src/daemon/tool-side-effects.ts +27 -5
  244. package/src/heartbeat/heartbeat-service.ts +1 -0
  245. package/src/hooks/cli.ts +2 -2
  246. package/src/hooks/runner.ts +15 -38
  247. package/src/inbound/platform-callback-registration.ts +14 -14
  248. package/src/memory/admin.ts +11 -45
  249. package/src/memory/conversation-bootstrap.ts +2 -0
  250. package/src/memory/conversation-crud.ts +242 -348
  251. package/src/memory/conversation-group-migration.ts +157 -0
  252. package/src/memory/conversation-queries.ts +4 -2
  253. package/src/memory/db-init.ts +30 -3
  254. package/src/memory/embed.ts +73 -0
  255. package/src/memory/embedding-backend.ts +8 -14
  256. package/src/memory/embedding-runtime-manager.ts +12 -114
  257. package/src/memory/fingerprint.ts +2 -2
  258. package/src/memory/graph/bootstrap.ts +512 -0
  259. package/src/memory/graph/capability-seed.ts +297 -0
  260. package/src/memory/graph/consolidation.ts +691 -0
  261. package/src/memory/graph/conversation-graph-memory.ts +630 -0
  262. package/src/memory/graph/decay.test.ts +208 -0
  263. package/src/memory/graph/decay.ts +195 -0
  264. package/src/memory/graph/extraction-job.ts +69 -0
  265. package/src/memory/graph/extraction.test.ts +936 -0
  266. package/src/memory/graph/extraction.ts +1254 -0
  267. package/src/memory/graph/graph-search.ts +266 -0
  268. package/src/memory/graph/image-ref-utils.ts +29 -0
  269. package/src/memory/graph/injection.test.ts +513 -0
  270. package/src/memory/graph/injection.ts +439 -0
  271. package/src/memory/graph/inspect.ts +534 -0
  272. package/src/memory/graph/narrative.ts +267 -0
  273. package/src/memory/graph/pattern-scan.ts +269 -0
  274. package/src/memory/graph/retriever.ts +1008 -0
  275. package/src/memory/graph/scoring.test.ts +548 -0
  276. package/src/memory/graph/scoring.ts +232 -0
  277. package/src/memory/graph/serendipity.ts +65 -0
  278. package/src/memory/graph/store.test.ts +1050 -0
  279. package/src/memory/graph/store.ts +699 -0
  280. package/src/memory/graph/tool-handlers.ts +426 -0
  281. package/src/memory/graph/tools.ts +141 -0
  282. package/src/memory/graph/triggers.test.ts +487 -0
  283. package/src/memory/graph/triggers.ts +223 -0
  284. package/src/memory/graph/types.ts +271 -0
  285. package/src/memory/group-crud.ts +191 -0
  286. package/src/memory/indexer.ts +37 -19
  287. package/src/memory/job-handlers/cleanup.ts +0 -53
  288. package/src/memory/job-handlers/conversation-starters.ts +91 -53
  289. package/src/memory/job-handlers/embedding.ts +5 -31
  290. package/src/memory/job-handlers/index-maintenance.ts +23 -11
  291. package/src/memory/job-handlers/summarization.ts +32 -17
  292. package/src/memory/job-utils.ts +1 -1
  293. package/src/memory/jobs-store.ts +50 -70
  294. package/src/memory/jobs-worker.ts +147 -112
  295. package/src/memory/message-content.ts +1 -0
  296. package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
  297. package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
  298. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
  299. package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
  300. package/src/memory/migrations/index.ts +4 -0
  301. package/src/memory/migrations/registry.ts +8 -0
  302. package/src/memory/qdrant-client.ts +44 -17
  303. package/src/memory/schema/index.ts +1 -0
  304. package/src/memory/schema/memory-graph.ts +139 -0
  305. package/src/memory/search/semantic.ts +47 -91
  306. package/src/memory/task-memory-cleanup.ts +28 -50
  307. package/src/messaging/providers/outlook/adapter.ts +8 -1
  308. package/src/messaging/providers/outlook/client.ts +299 -0
  309. package/src/messaging/providers/outlook/types.ts +118 -0
  310. package/src/notifications/adapters/macos.ts +1 -0
  311. package/src/notifications/copy-composer.ts +9 -0
  312. package/src/notifications/signal.ts +16 -0
  313. package/src/oauth/seed-providers.ts +2 -1
  314. package/src/permissions/checker.ts +24 -3
  315. package/src/permissions/defaults.ts +4 -4
  316. package/src/permissions/workspace-policy.ts +1 -1
  317. package/src/playbooks/playbook-compiler.ts +19 -18
  318. package/src/playbooks/types.ts +4 -3
  319. package/src/prompts/system-prompt.ts +3 -29
  320. package/src/providers/anthropic/client.ts +47 -19
  321. package/src/providers/gemini/client.ts +1 -1
  322. package/src/providers/openai/client.ts +1 -1
  323. package/src/providers/registry.ts +1 -1
  324. package/src/providers/retry.ts +19 -3
  325. package/src/runtime/actor-trust-resolver.ts +5 -1
  326. package/src/runtime/auth/route-policy.ts +7 -0
  327. package/src/runtime/guardian-reply-router.ts +5 -1
  328. package/src/runtime/http-server.ts +23 -3
  329. package/src/runtime/middleware/auth.ts +20 -0
  330. package/src/runtime/routes/attachment-routes.test.ts +106 -0
  331. package/src/runtime/routes/attachment-routes.ts +106 -16
  332. package/src/runtime/routes/brain-graph-routes.ts +21 -22
  333. package/src/runtime/routes/btw-routes.ts +8 -0
  334. package/src/runtime/routes/conversation-management-routes.ts +2 -0
  335. package/src/runtime/routes/conversation-starter-routes.ts +2 -2
  336. package/src/runtime/routes/debug-routes.ts +1 -1
  337. package/src/runtime/routes/global-search-routes.ts +21 -19
  338. package/src/runtime/routes/group-routes.ts +207 -0
  339. package/src/runtime/routes/guardian-action-routes.ts +21 -10
  340. package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
  341. package/src/runtime/routes/inbound-message-handler.ts +19 -0
  342. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
  343. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
  344. package/src/runtime/routes/memory-item-routes.test.ts +2 -14
  345. package/src/runtime/routes/memory-item-routes.ts +341 -388
  346. package/src/runtime/routes/schedule-routes.ts +2 -0
  347. package/src/runtime/routes/skills-routes.ts +103 -37
  348. package/src/runtime/routes/work-items-routes.test.ts +2 -6
  349. package/src/schedule/scheduler.ts +8 -1
  350. package/src/security/oauth2.ts +1 -1
  351. package/src/security/secure-keys.ts +4 -8
  352. package/src/shared/provider-env-vars.ts +19 -0
  353. package/src/skills/catalog-cache.ts +5 -0
  354. package/src/skills/catalog-install.ts +15 -14
  355. package/src/skills/clawhub.ts +134 -154
  356. package/src/skills/install-meta.ts +208 -0
  357. package/src/skills/managed-store.ts +27 -16
  358. package/src/skills/skill-memory.ts +152 -77
  359. package/src/skills/skillssh-registry.ts +19 -17
  360. package/src/tasks/task-runner.ts +3 -1
  361. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
  362. package/src/tools/browser/runtime-check.ts +3 -1
  363. package/src/tools/memory/register.ts +63 -46
  364. package/src/tools/permission-checker.ts +7 -1
  365. package/src/tools/shared/filesystem/image-read.ts +22 -85
  366. package/src/tools/terminal/safe-env.ts +1 -0
  367. package/src/tools/tool-manifest.ts +3 -3
  368. package/src/util/browser.ts +25 -10
  369. package/src/util/bun-runtime.ts +172 -0
  370. package/src/watcher/providers/outlook-calendar.ts +343 -0
  371. package/src/watcher/providers/outlook.ts +198 -0
  372. package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
  373. package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
  374. package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
  375. package/src/workspace/migrations/registry.ts +6 -0
  376. package/src/__tests__/context-memory-e2e.test.ts +0 -415
  377. package/src/__tests__/journal-context.test.ts +0 -268
  378. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
  379. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
  380. package/src/__tests__/memory-query-builder.test.ts +0 -59
  381. package/src/__tests__/memory-recall-quality.test.ts +0 -1046
  382. package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
  383. package/src/__tests__/memory-regressions.test.ts +0 -3696
  384. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
  385. package/src/daemon/conversation-memory.ts +0 -207
  386. package/src/memory/conversation-starters-cadence.ts +0 -74
  387. package/src/memory/items-extractor.ts +0 -860
  388. package/src/memory/job-handlers/batch-extraction.ts +0 -753
  389. package/src/memory/job-handlers/extraction.ts +0 -40
  390. package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
  391. package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
  392. package/src/memory/journal-memory.ts +0 -224
  393. package/src/memory/query-builder.ts +0 -47
  394. package/src/memory/query-expansion.ts +0 -83
  395. package/src/memory/retriever.test.ts +0 -1592
  396. package/src/memory/retriever.ts +0 -1331
  397. package/src/memory/search/formatting.test.ts +0 -140
  398. package/src/memory/search/formatting.ts +0 -262
  399. package/src/memory/search/mmr.ts +0 -139
  400. package/src/memory/search/ranking.ts +0 -15
  401. package/src/memory/search/staleness.ts +0 -40
  402. package/src/memory/search/tier-classifier.ts +0 -18
  403. package/src/memory/search/types.ts +0 -121
  404. package/src/prompts/journal-context.ts +0 -154
  405. package/src/tools/memory/definitions.ts +0 -69
  406. package/src/tools/memory/handlers.test.ts +0 -562
  407. package/src/tools/memory/handlers.ts +0 -434
@@ -1,753 +0,0 @@
1
- import { and, asc, desc, eq, gt, or, sql } from "drizzle-orm";
2
- import { v4 as uuid } from "uuid";
3
-
4
- import { estimateTextTokens } from "../../context/token-estimator.js";
5
- import { getAssistantName } from "../../daemon/identity-helpers.js";
6
- import { resolveGuardianPersona } from "../../prompts/persona-resolver.js";
7
- import {
8
- extractToolUse,
9
- getConfiguredProvider,
10
- userMessage,
11
- } from "../../providers/provider-send-message.js";
12
- import { BackendUnavailableError, ProviderError } from "../../util/errors.js";
13
- import { getLogger } from "../../util/logger.js";
14
- import {
15
- getMemoryCheckpoint,
16
- setMemoryCheckpoint,
17
- } from "../checkpoints.js";
18
- import { getConversationMemoryScopeId } from "../conversation-crud.js";
19
- import { maybeEnqueueConversationStartersJob } from "../conversation-starters-cadence.js";
20
- import { getDb } from "../db.js";
21
- import { computeMemoryFingerprint } from "../fingerprint.js";
22
- import {
23
- buildExtractionSystemPrompt,
24
- deduplicateItems,
25
- type ExtractedItem,
26
- EXTRACTION_KINDS,
27
- KIND_MIGRATION_MAP,
28
- type MemoryItemKind,
29
- type OverrideConfidence,
30
- parseScore,
31
- SUPERSEDE_KINDS,
32
- VALID_KINDS,
33
- VALID_OVERRIDE_CONFIDENCES,
34
- } from "../items-extractor.js";
35
- import { asString } from "../job-utils.js";
36
- import { enqueueMemoryJob, type MemoryJob } from "../jobs-store.js";
37
- import { extractTextFromStoredMessageContent } from "../message-content.js";
38
- import { withQdrantBreaker } from "../qdrant-circuit-breaker.js";
39
- import { getQdrantClient } from "../qdrant-client.js";
40
- import {
41
- memoryItems,
42
- memoryItemSources,
43
- memorySummaries,
44
- messages,
45
- } from "../schema.js";
46
- import { isConversationFailed } from "../task-memory-cleanup.js";
47
- import { clampUnitInterval } from "../validation.js";
48
-
49
- const log = getLogger("memory-batch-extraction");
50
-
51
- interface LLMBatchExtractedItem {
52
- kind: string;
53
- subject: string;
54
- statement: string;
55
- confidence: number;
56
- importance: number;
57
- supersedes: string | null;
58
- overrideConfidence: string;
59
- source_role?: "user" | "assistant";
60
- }
61
-
62
- interface BatchExtractResult {
63
- items: LLMBatchExtractedItem[];
64
- extraction_summary: string;
65
- conversation_summary: string;
66
- }
67
-
68
- export async function batchExtractJob(job: MemoryJob): Promise<void> {
69
- const conversationId = asString(job.payload.conversationId);
70
- const payloadScopeId = asString(job.payload.scopeId);
71
- if (!conversationId) return;
72
-
73
- // If the conversation has been marked as failed, skip extraction entirely.
74
- if (isConversationFailed(conversationId)) {
75
- log.info(
76
- { conversationId },
77
- "Skipping batch extraction for failed conversation",
78
- );
79
- return;
80
- }
81
-
82
- const db = getDb();
83
-
84
- // Resolve scopeId: prefer payload, fall back to conversation's stored scope
85
- const scopeId =
86
- payloadScopeId ?? getConversationMemoryScopeId(conversationId);
87
-
88
- // ── Load unextracted messages ────────────────────────────────────────
89
- const lastMessageIdKey = `batch_extract:${conversationId}:last_message_id`;
90
- const lastExtractedMessageId = getMemoryCheckpoint(lastMessageIdKey);
91
-
92
- let unextractedMessages: Array<{
93
- id: string;
94
- role: string;
95
- content: string;
96
- createdAt: number;
97
- }>;
98
-
99
- if (lastExtractedMessageId) {
100
- // Get the createdAt of the last extracted message so we can find messages after it
101
- const lastMsg = db
102
- .select({ id: messages.id, createdAt: messages.createdAt })
103
- .from(messages)
104
- .where(eq(messages.id, lastExtractedMessageId))
105
- .get();
106
-
107
- if (lastMsg) {
108
- // Compound cursor: createdAt > X OR (createdAt = X AND id > Y)
109
- // Avoids skipping messages that share the same timestamp as the checkpoint
110
- unextractedMessages = db
111
- .select({
112
- id: messages.id,
113
- role: messages.role,
114
- content: messages.content,
115
- createdAt: messages.createdAt,
116
- })
117
- .from(messages)
118
- .where(
119
- and(
120
- eq(messages.conversationId, conversationId),
121
- or(
122
- gt(messages.createdAt, lastMsg.createdAt),
123
- and(
124
- eq(messages.createdAt, lastMsg.createdAt),
125
- gt(messages.id, lastMsg.id),
126
- ),
127
- ),
128
- ),
129
- )
130
- .orderBy(asc(messages.createdAt), asc(messages.id))
131
- .all();
132
- } else {
133
- // Checkpoint references a deleted message — fetch all
134
- unextractedMessages = db
135
- .select({
136
- id: messages.id,
137
- role: messages.role,
138
- content: messages.content,
139
- createdAt: messages.createdAt,
140
- })
141
- .from(messages)
142
- .where(eq(messages.conversationId, conversationId))
143
- .orderBy(asc(messages.createdAt), asc(messages.id))
144
- .all();
145
- }
146
- } else {
147
- // No checkpoint — process all messages in the conversation
148
- unextractedMessages = db
149
- .select({
150
- id: messages.id,
151
- role: messages.role,
152
- content: messages.content,
153
- createdAt: messages.createdAt,
154
- })
155
- .from(messages)
156
- .where(eq(messages.conversationId, conversationId))
157
- .orderBy(asc(messages.createdAt), asc(messages.id))
158
- .all();
159
- }
160
-
161
- if (unextractedMessages.length === 0) {
162
- log.debug({ conversationId }, "No unextracted messages for batch extraction");
163
- // Reset pending count since there's nothing to extract
164
- setMemoryCheckpoint(
165
- `batch_extract:${conversationId}:pending_count`,
166
- "0",
167
- );
168
- return;
169
- }
170
-
171
- // ── Load running extraction summary ─────────────────────────────────
172
- const existingExtractionSummary = db
173
- .select({ summary: memorySummaries.summary })
174
- .from(memorySummaries)
175
- .where(
176
- and(
177
- eq(memorySummaries.scope, "extraction_context"),
178
- eq(memorySummaries.scopeKey, conversationId),
179
- ),
180
- )
181
- .get();
182
-
183
- // ── Load existing global items for supersession ─────────────────────
184
- // When fullReextract is set (re-extraction pass), load all active items
185
- // so the LLM can supersede as many old flat-fact memories as possible.
186
- const fullReextract = Boolean(job.payload.fullReextract);
187
- const existingItemsLimit = fullReextract ? 200 : 20;
188
-
189
- const existingItems = db
190
- .select({
191
- id: memoryItems.id,
192
- kind: memoryItems.kind,
193
- subject: memoryItems.subject,
194
- statement: memoryItems.statement,
195
- })
196
- .from(memoryItems)
197
- .where(
198
- and(
199
- eq(memoryItems.scopeId, scopeId),
200
- eq(memoryItems.status, "active"),
201
- ),
202
- )
203
- .orderBy(desc(memoryItems.lastSeenAt))
204
- .limit(existingItemsLimit)
205
- .all();
206
-
207
- // ── Build the batch extraction prompt ───────────────────────────────
208
- const userPersona = resolveGuardianPersona();
209
- const baseSystemPrompt = buildExtractionSystemPrompt(
210
- existingItems,
211
- "user", // batch processes both roles
212
- userPersona,
213
- );
214
-
215
- let systemPrompt = baseSystemPrompt;
216
- systemPrompt += `\n\nIMPORTANT: You are processing a batch of messages from a single conversation window, not individual messages. Extract items from the entire batch — look for cross-message patterns, evolving themes, and composite facts that only emerge from reading multiple messages together.`;
217
-
218
- if (existingExtractionSummary?.summary) {
219
- systemPrompt += `\n\nPreviously extracted from this conversation:\n${existingExtractionSummary.summary}`;
220
- }
221
-
222
- // ── Build user message from unextracted messages ────────────────────
223
- const assistantName = getAssistantName() ?? "the assistant";
224
- const messageParts: string[] = [];
225
- for (const msg of unextractedMessages) {
226
- const text = extractTextFromStoredMessageContent(msg.content);
227
- if (text.length === 0) continue;
228
- const roleLabel = msg.role === "assistant" ? assistantName : "user";
229
- const timestamp = new Date(msg.createdAt).toISOString();
230
- messageParts.push(`[${timestamp}] [${roleLabel}]: ${text}`);
231
- }
232
-
233
- if (messageParts.length === 0) {
234
- log.debug(
235
- { conversationId },
236
- "All unextracted messages have empty text content",
237
- );
238
- setMemoryCheckpoint(
239
- `batch_extract:${conversationId}:pending_count`,
240
- "0",
241
- );
242
- // Still update the last message ID checkpoint
243
- const lastMsg = unextractedMessages[unextractedMessages.length - 1];
244
- setMemoryCheckpoint(lastMessageIdKey, lastMsg.id);
245
- return;
246
- }
247
-
248
- const userContent = messageParts.join("\n\n");
249
-
250
- // ── Call LLM ────────────────────────────────────────────────────────
251
- const provider = await getConfiguredProvider();
252
- if (!provider) {
253
- throw new BackendUnavailableError(
254
- "Provider unavailable for batch memory extraction",
255
- );
256
- }
257
-
258
- const response = await provider.sendMessage(
259
- [userMessage(userContent)],
260
- [
261
- {
262
- name: "batch_extract_results",
263
- description:
264
- "Store extracted memory items and summaries from the conversation batch",
265
- input_schema: {
266
- type: "object" as const,
267
- properties: {
268
- items: {
269
- type: "array",
270
- items: {
271
- type: "object",
272
- properties: {
273
- kind: {
274
- type: "string",
275
- enum: EXTRACTION_KINDS,
276
- description: "Category of memory item",
277
- },
278
- subject: {
279
- type: "string",
280
- description:
281
- "Short label (2-8 words) for what this is about",
282
- },
283
- statement: {
284
- type: "string",
285
- description:
286
- "Relationship-rich factual statement to remember (1-2 sentences). Include relational context.",
287
- },
288
- confidence: {
289
- type: "number",
290
- description: "Confidence that this is accurate (0.0-1.0)",
291
- },
292
- importance: {
293
- type: "number",
294
- description: "How valuable this is to remember (0.0-1.0)",
295
- },
296
- supersedes: {
297
- type: ["string", "null"],
298
- description:
299
- "ID of the existing memory item this replaces, or null if not replacing anything",
300
- },
301
- overrideConfidence: {
302
- type: "string",
303
- enum: ["explicit", "tentative", "inferred"],
304
- description:
305
- "How confident you are that this overrides an existing item",
306
- },
307
- source_role: {
308
- type: "string",
309
- enum: ["user", "assistant"],
310
- description:
311
- "Which speaker's message this item was primarily extracted from",
312
- },
313
- },
314
- required: [
315
- "kind",
316
- "subject",
317
- "statement",
318
- "confidence",
319
- "importance",
320
- "supersedes",
321
- "overrideConfidence",
322
- "source_role",
323
- ],
324
- },
325
- },
326
- extraction_summary: {
327
- type: "string",
328
- description:
329
- "Updated summary of what has been extracted from this conversation so far",
330
- },
331
- conversation_summary: {
332
- type: "string",
333
- description: "Updated summary of the conversation so far",
334
- },
335
- },
336
- required: ["items", "extraction_summary", "conversation_summary"],
337
- },
338
- },
339
- ],
340
- systemPrompt,
341
- {
342
- config: {
343
- modelIntent: "quality-optimized" as const,
344
- tool_choice: {
345
- type: "tool" as const,
346
- name: "batch_extract_results",
347
- },
348
- },
349
- },
350
- );
351
-
352
- const toolBlock = extractToolUse(response);
353
- if (!toolBlock) {
354
- throw new ProviderError(
355
- "No tool_use block in batch extraction LLM response",
356
- "unknown",
357
- 502,
358
- );
359
- }
360
-
361
- const input = toolBlock.input as unknown as BatchExtractResult;
362
- if (!Array.isArray(input.items)) {
363
- throw new ProviderError(
364
- "Invalid items structure in batch extraction LLM response",
365
- "unknown",
366
- 502,
367
- );
368
- }
369
-
370
- // Guard: re-check after the async LLM call
371
- if (isConversationFailed(conversationId)) {
372
- log.info(
373
- { conversationId },
374
- "Skipping upsert — conversation marked failed during batch extraction",
375
- );
376
- return;
377
- }
378
-
379
- // ── Validate and process extracted items ────────────────────────────
380
- const existingItemIds = new Set(existingItems.map((e) => e.id));
381
-
382
- const validatedItems: ExtractedItem[] = [];
383
- for (const raw of input.items) {
384
- const resolvedKind = KIND_MIGRATION_MAP[raw.kind] ?? raw.kind;
385
- if (resolvedKind === "journal") continue;
386
- if (!VALID_KINDS.has(resolvedKind)) continue;
387
- if (!raw.subject || !raw.statement) continue;
388
- const subject = String(raw.subject).trim();
389
- const statement = String(raw.statement).trim();
390
- const confidence = clampUnitInterval(parseScore(raw.confidence, 0.5));
391
- const importance = clampUnitInterval(parseScore(raw.importance, 0.5));
392
- const fingerprint = computeMemoryFingerprint(
393
- scopeId,
394
- resolvedKind,
395
- subject,
396
- statement,
397
- );
398
-
399
- const rawSupersedes =
400
- typeof raw.supersedes === "string" && raw.supersedes.length > 0
401
- ? raw.supersedes
402
- : null;
403
- const supersedes =
404
- rawSupersedes && existingItemIds.has(rawSupersedes)
405
- ? rawSupersedes
406
- : null;
407
- const supersedesRejected = !!rawSupersedes && !supersedes;
408
- const overrideConfidence = VALID_OVERRIDE_CONFIDENCES.has(
409
- raw.overrideConfidence,
410
- )
411
- ? (raw.overrideConfidence as OverrideConfidence)
412
- : "inferred";
413
-
414
- const sourceRole =
415
- raw.source_role === "user" || raw.source_role === "assistant"
416
- ? raw.source_role
417
- : undefined;
418
-
419
- validatedItems.push({
420
- kind: resolvedKind as MemoryItemKind,
421
- subject,
422
- statement,
423
- confidence,
424
- importance,
425
- fingerprint,
426
- supersedes,
427
- overrideConfidence,
428
- supersedesRejected,
429
- sourceRole,
430
- });
431
- }
432
-
433
- const dedupedItems = deduplicateItems(validatedItems);
434
-
435
- // ── Upsert extracted items ──────────────────────────────────────────
436
- const lastMessage = unextractedMessages[unextractedMessages.length - 1];
437
- let upserted = 0;
438
-
439
- for (const item of dedupedItems) {
440
- const seenAt = lastMessage.createdAt;
441
- const itemRole = item.sourceRole ?? lastMessage.role;
442
- const existing = db
443
- .select()
444
- .from(memoryItems)
445
- .where(
446
- and(
447
- eq(memoryItems.fingerprint, item.fingerprint),
448
- eq(memoryItems.scopeId, scopeId),
449
- ),
450
- )
451
- .get();
452
-
453
- let memoryItemId: string;
454
- let effectiveStatus: string = "active";
455
- if (existing) {
456
- memoryItemId = existing.id;
457
- effectiveStatus = "active";
458
- const effectiveSourceType =
459
- existing.sourceType === "tool"
460
- ? "tool"
461
- : existing.sourceType === "journal_carry_forward"
462
- ? "journal_carry_forward"
463
- : "extraction";
464
- const effectiveVerificationState =
465
- existing.verificationState === "user_confirmed"
466
- ? "user_confirmed"
467
- : itemRole === "user" || existing.verificationState === "user_reported"
468
- ? "user_reported"
469
- : "assistant_inferred";
470
-
471
- db.update(memoryItems)
472
- .set({
473
- status: effectiveStatus,
474
- confidence: clampUnitInterval(
475
- Math.max(existing.confidence, item.confidence),
476
- ),
477
- importance: clampUnitInterval(
478
- fullReextract
479
- ? item.importance
480
- : Math.max(existing.importance ?? 0, item.importance),
481
- ),
482
- lastSeenAt: Math.max(existing.lastSeenAt, seenAt),
483
- sourceType: effectiveSourceType,
484
- sourceMessageRole: itemRole,
485
- verificationState: effectiveVerificationState,
486
- })
487
- .where(eq(memoryItems.id, existing.id))
488
- .run();
489
- } else {
490
- memoryItemId = uuid();
491
- db.insert(memoryItems)
492
- .values({
493
- id: memoryItemId,
494
- kind: item.kind,
495
- subject: item.subject,
496
- statement: item.statement,
497
- status: "active",
498
- confidence: item.confidence,
499
- importance: item.importance,
500
- fingerprint: item.fingerprint,
501
- sourceType: "extraction",
502
- verificationState:
503
- itemRole === "user" ? "user_reported" : "assistant_inferred",
504
- sourceMessageRole: itemRole,
505
- scopeId,
506
- firstSeenAt: lastMessage.createdAt,
507
- lastSeenAt: seenAt,
508
- lastUsedAt: null,
509
- supersedes: item.supersedes,
510
- overrideConfidence: item.overrideConfidence,
511
- })
512
- .run();
513
- upserted += 1;
514
- }
515
-
516
- // Handle LLM-directed supersession based on overrideConfidence
517
- if (
518
- item.supersedes &&
519
- item.supersedes !== memoryItemId &&
520
- item.overrideConfidence === "explicit" &&
521
- effectiveStatus === "active"
522
- ) {
523
- const oldItem = db
524
- .select({ id: memoryItems.id })
525
- .from(memoryItems)
526
- .where(
527
- and(
528
- eq(memoryItems.id, item.supersedes),
529
- eq(memoryItems.scopeId, scopeId),
530
- eq(memoryItems.status, "active"),
531
- ),
532
- )
533
- .get();
534
-
535
- if (oldItem) {
536
- db.update(memoryItems)
537
- .set({
538
- status: "superseded",
539
- supersededBy: memoryItemId,
540
- })
541
- .where(eq(memoryItems.id, oldItem.id))
542
- .run();
543
-
544
- db.update(memoryItems)
545
- .set({ supersedes: oldItem.id })
546
- .where(eq(memoryItems.id, memoryItemId))
547
- .run();
548
-
549
- try {
550
- const qdrant = getQdrantClient();
551
- await withQdrantBreaker(() =>
552
- qdrant.deleteByTarget("item", oldItem.id),
553
- );
554
- } catch (err) {
555
- const errMsg = err instanceof Error ? err.message : String(err);
556
- log.warn(
557
- { err: errMsg, oldItemId: oldItem.id },
558
- "Failed to remove superseded item from Qdrant — will be cleaned up by index maintenance",
559
- );
560
- }
561
-
562
- log.debug(
563
- { newItemId: memoryItemId, oldItemId: oldItem.id },
564
- "Explicitly superseded memory item (batch)",
565
- );
566
- }
567
- } else if (item.supersedes && item.overrideConfidence === "tentative") {
568
- log.debug(
569
- {
570
- newItemId: memoryItemId,
571
- supersedes: item.supersedes,
572
- overrideConfidence: "tentative",
573
- },
574
- "Tentative override — both items coexist (batch)",
575
- );
576
- } else if (item.supersedes && item.overrideConfidence === "inferred") {
577
- log.debug(
578
- {
579
- newItemId: memoryItemId,
580
- supersedes: item.supersedes,
581
- overrideConfidence: "inferred",
582
- },
583
- "Inferred override — both items coexist (batch)",
584
- );
585
- }
586
-
587
- // Fallback subject-match supersession
588
- if (
589
- !item.supersedes &&
590
- !item.supersedesRejected &&
591
- SUPERSEDE_KINDS.has(item.kind) &&
592
- effectiveStatus === "active"
593
- ) {
594
- db.update(memoryItems)
595
- .set({ status: "superseded" })
596
- .where(
597
- and(
598
- eq(memoryItems.kind, item.kind),
599
- eq(memoryItems.subject, item.subject),
600
- eq(memoryItems.status, "active"),
601
- eq(memoryItems.scopeId, scopeId),
602
- sql`${memoryItems.id} <> ${memoryItemId}`,
603
- ),
604
- )
605
- .run();
606
- }
607
-
608
- // Record source linkage for the last message in the batch
609
- db.insert(memoryItemSources)
610
- .values({
611
- memoryItemId,
612
- messageId: lastMessage.id,
613
- evidence: item.statement,
614
- createdAt: Date.now(),
615
- })
616
- .onConflictDoNothing()
617
- .run();
618
-
619
- enqueueMemoryJob("embed_item", { itemId: memoryItemId });
620
- }
621
-
622
- // ── Update running extraction summary ───────────────────────────────
623
- if (input.extraction_summary) {
624
- const now = Date.now();
625
- const summaryId =
626
- existingExtractionSummary
627
- ? db
628
- .select({ id: memorySummaries.id })
629
- .from(memorySummaries)
630
- .where(
631
- and(
632
- eq(memorySummaries.scope, "extraction_context"),
633
- eq(memorySummaries.scopeKey, conversationId),
634
- ),
635
- )
636
- .get()?.id ?? uuid()
637
- : uuid();
638
-
639
- db.insert(memorySummaries)
640
- .values({
641
- id: summaryId,
642
- scope: "extraction_context",
643
- scopeKey: conversationId,
644
- scopeId,
645
- summary: input.extraction_summary,
646
- tokenEstimate: estimateTextTokens(input.extraction_summary),
647
- version: 1,
648
- startAt: unextractedMessages[0].createdAt,
649
- endAt: lastMessage.createdAt,
650
- createdAt: now,
651
- updatedAt: now,
652
- })
653
- .onConflictDoUpdate({
654
- target: [memorySummaries.scope, memorySummaries.scopeKey],
655
- set: {
656
- summary: input.extraction_summary,
657
- tokenEstimate: estimateTextTokens(input.extraction_summary),
658
- version: sql`${memorySummaries.version} + 1`,
659
- scopeId,
660
- endAt: lastMessage.createdAt,
661
- updatedAt: now,
662
- },
663
- })
664
- .run();
665
- }
666
-
667
- // ── Update conversation summary ─────────────────────────────────────
668
- if (input.conversation_summary) {
669
- const now = Date.now();
670
- const existingConvSummary = db
671
- .select({ id: memorySummaries.id })
672
- .from(memorySummaries)
673
- .where(
674
- and(
675
- eq(memorySummaries.scope, "conversation"),
676
- eq(memorySummaries.scopeKey, conversationId),
677
- ),
678
- )
679
- .get();
680
-
681
- const convSummaryId = existingConvSummary?.id ?? uuid();
682
-
683
- db.insert(memorySummaries)
684
- .values({
685
- id: convSummaryId,
686
- scope: "conversation",
687
- scopeKey: conversationId,
688
- scopeId,
689
- summary: input.conversation_summary,
690
- tokenEstimate: estimateTextTokens(input.conversation_summary),
691
- version: 1,
692
- startAt: unextractedMessages[0].createdAt,
693
- endAt: lastMessage.createdAt,
694
- createdAt: now,
695
- updatedAt: now,
696
- })
697
- .onConflictDoUpdate({
698
- target: [memorySummaries.scope, memorySummaries.scopeKey],
699
- set: {
700
- summary: input.conversation_summary,
701
- tokenEstimate: estimateTextTokens(input.conversation_summary),
702
- version: sql`${memorySummaries.version} + 1`,
703
- scopeId,
704
- endAt: lastMessage.createdAt,
705
- updatedAt: now,
706
- },
707
- })
708
- .run();
709
-
710
- // Re-query to get the persisted row ID and enqueue embed job
711
- const actualRow = db
712
- .select({ id: memorySummaries.id })
713
- .from(memorySummaries)
714
- .where(
715
- and(
716
- eq(memorySummaries.scope, "conversation"),
717
- eq(memorySummaries.scopeKey, conversationId),
718
- ),
719
- )
720
- .get();
721
- if (actualRow) {
722
- enqueueMemoryJob("embed_summary", { summaryId: actualRow.id });
723
- }
724
- }
725
-
726
- // ── Update checkpoints ──────────────────────────────────────────────
727
- setMemoryCheckpoint(lastMessageIdKey, lastMessage.id);
728
- setMemoryCheckpoint(
729
- `batch_extract:${conversationId}:pending_count`,
730
- "0",
731
- );
732
-
733
- log.info(
734
- {
735
- conversationId,
736
- messagesProcessed: unextractedMessages.length,
737
- itemsExtracted: dedupedItems.length,
738
- itemsUpserted: upserted,
739
- },
740
- "Batch extraction completed",
741
- );
742
-
743
- if (upserted > 0) {
744
- try {
745
- maybeEnqueueConversationStartersJob(scopeId);
746
- } catch (err) {
747
- log.warn(
748
- { err: err instanceof Error ? err.message : String(err) },
749
- "Failed to check conversation starters cadence",
750
- );
751
- }
752
- }
753
- }