@vellumai/assistant 0.5.15 → 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 (503) hide show
  1. package/ARCHITECTURE.md +3 -3
  2. package/Dockerfile +0 -3
  3. package/docs/architecture/integrations.md +15 -14
  4. package/knip.json +4 -1
  5. package/openapi.yaml +670 -122
  6. package/package.json +1 -1
  7. package/src/__tests__/actor-token-service.test.ts +68 -0
  8. package/src/__tests__/agent-loop.test.ts +0 -32
  9. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  10. package/src/__tests__/anthropic-provider.test.ts +57 -3
  11. package/src/__tests__/app-compiler.test.ts +120 -0
  12. package/src/__tests__/assistant-feature-flags-integration.test.ts +5 -377
  13. package/src/__tests__/call-conversation-messages.test.ts +2 -6
  14. package/src/__tests__/call-domain.test.ts +2 -6
  15. package/src/__tests__/call-pointer-messages.test.ts +2 -14
  16. package/src/__tests__/call-recovery.test.ts +2 -6
  17. package/src/__tests__/call-routes-http.test.ts +2 -6
  18. package/src/__tests__/call-store.test.ts +2 -6
  19. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
  20. package/src/__tests__/canonical-guardian-store.test.ts +2 -6
  21. package/src/__tests__/ces-rpc-credential-backend.test.ts +4 -1
  22. package/src/__tests__/channel-delivery-store.test.ts +2 -6
  23. package/src/__tests__/channel-retry-sweep.test.ts +2 -6
  24. package/src/__tests__/checker.test.ts +84 -3
  25. package/src/__tests__/clawhub.test.ts +54 -24
  26. package/src/__tests__/cli-command-risk-guard.test.ts +108 -6
  27. package/src/__tests__/cli-memory.test.ts +377 -0
  28. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +12 -2
  29. package/src/__tests__/config-schema.test.ts +1 -3
  30. package/src/__tests__/config-set-platform-guard.test.ts +302 -0
  31. package/src/__tests__/config-watcher-feature-flags.test.ts +211 -0
  32. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
  33. package/src/__tests__/contacts-tools.test.ts +31 -0
  34. package/src/__tests__/context-overflow-reducer.test.ts +86 -0
  35. package/src/__tests__/context-token-estimator.test.ts +175 -10
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
  37. package/src/__tests__/conversation-agent-loop.test.ts +9 -0
  38. package/src/__tests__/conversation-attachments.test.ts +2 -6
  39. package/src/__tests__/conversation-attention-store.test.ts +2 -6
  40. package/src/__tests__/conversation-clear-safety.test.ts +2 -6
  41. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
  42. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
  43. package/src/__tests__/conversation-disk-view.test.ts +2 -6
  44. package/src/__tests__/conversation-error.test.ts +33 -2
  45. package/src/__tests__/conversation-fork-crud.test.ts +2 -6
  46. package/src/__tests__/conversation-history-web-search.test.ts +5 -0
  47. package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
  48. package/src/__tests__/conversation-media-retry.test.ts +91 -0
  49. package/src/__tests__/conversation-runtime-assembly.test.ts +7 -4
  50. package/src/__tests__/conversation-slash-commands.test.ts +2 -6
  51. package/src/__tests__/conversation-starter-routes.test.ts +20 -11
  52. package/src/__tests__/conversation-store.test.ts +2 -6
  53. package/src/__tests__/conversation-usage.test.ts +3 -6
  54. package/src/__tests__/conversation-wipe.test.ts +11 -408
  55. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  56. package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
  57. package/src/__tests__/credential-security-e2e.test.ts +6 -1
  58. package/src/__tests__/docker-signing-key-bootstrap.test.ts +7 -73
  59. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -7
  60. package/src/__tests__/followup-tools.test.ts +2 -6
  61. package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
  62. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
  63. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
  64. package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
  65. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
  66. package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
  67. package/src/__tests__/guardian-action-store.test.ts +2 -6
  68. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
  69. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
  70. package/src/__tests__/guardian-dispatch.test.ts +2 -6
  71. package/src/__tests__/guardian-grant-minting.test.ts +2 -14
  72. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
  73. package/src/__tests__/guardian-routing-invariants.test.ts +343 -6
  74. package/src/__tests__/guardian-routing-state.test.ts +2 -6
  75. package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
  76. package/src/__tests__/heartbeat-service.test.ts +1 -3
  77. package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
  78. package/src/__tests__/injection-block.test.ts +154 -0
  79. package/src/__tests__/install-meta.test.ts +506 -0
  80. package/src/__tests__/install-skill-routing.test.ts +292 -0
  81. package/src/__tests__/intent-routing.test.ts +6 -18
  82. package/src/__tests__/invite-redemption-service.test.ts +2 -6
  83. package/src/__tests__/invite-routes-http.test.ts +2 -6
  84. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
  85. package/src/__tests__/list-messages-attachments.test.ts +2 -6
  86. package/src/__tests__/llm-context-route-provider.test.ts +2 -6
  87. package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
  88. package/src/__tests__/llm-usage-store.test.ts +2 -6
  89. package/src/__tests__/log-export-workspace.test.ts +4 -34
  90. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -37
  91. package/src/__tests__/managed-store.test.ts +40 -21
  92. package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
  93. package/src/__tests__/memory-recall-log-store.test.ts +2 -6
  94. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
  95. package/src/__tests__/messaging-send-tool.test.ts +6 -6
  96. package/src/__tests__/migration-cross-version-compatibility.test.ts +1 -29
  97. package/src/__tests__/migration-export-http.test.ts +3 -34
  98. package/src/__tests__/migration-import-commit-http.test.ts +1 -29
  99. package/src/__tests__/migration-import-preflight-http.test.ts +3 -34
  100. package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +2 -1
  101. package/src/__tests__/non-member-access-request.test.ts +2 -6
  102. package/src/__tests__/notification-guardian-path.test.ts +2 -6
  103. package/src/__tests__/oauth-apps-routes.test.ts +120 -10
  104. package/src/__tests__/oauth-cli.test.ts +364 -2
  105. package/src/__tests__/oauth-connect-orchestrator.test.ts +709 -0
  106. package/src/__tests__/oauth-provider-serializer.test.ts +2 -1
  107. package/src/__tests__/oauth-provider-visibility.test.ts +149 -0
  108. package/src/__tests__/oauth-providers-routes.test.ts +5 -2
  109. package/src/__tests__/oauth-store.test.ts +0 -5
  110. package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
  111. package/src/__tests__/outlook-attachments.test.ts +301 -0
  112. package/src/__tests__/outlook-automation-tools.test.ts +425 -0
  113. package/src/__tests__/outlook-categories.test.ts +212 -0
  114. package/src/__tests__/outlook-client-automation.test.ts +246 -0
  115. package/src/__tests__/outlook-compose-tools.test.ts +325 -0
  116. package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
  117. package/src/__tests__/outlook-email-watcher.test.ts +322 -0
  118. package/src/__tests__/outlook-follow-up.test.ts +196 -0
  119. package/src/__tests__/outlook-messaging-provider.test.ts +1071 -0
  120. package/src/__tests__/outlook-trash.test.ts +77 -0
  121. package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
  122. package/src/__tests__/path-policy.test.ts +2 -17
  123. package/src/__tests__/permission-types.test.ts +0 -1
  124. package/src/__tests__/platform-callback-registration.test.ts +7 -11
  125. package/src/__tests__/playbook-execution.test.ts +76 -80
  126. package/src/__tests__/playbook-tools.test.ts +5 -7
  127. package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
  128. package/src/__tests__/provider-error-scenarios.test.ts +21 -2
  129. package/src/__tests__/qdrant-manager.test.ts +68 -21
  130. package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
  131. package/src/__tests__/registry.test.ts +2 -2
  132. package/src/__tests__/require-fresh-approval.test.ts +64 -3
  133. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
  134. package/src/__tests__/runtime-events-sse.test.ts +2 -6
  135. package/src/__tests__/sandbox-diagnostics.test.ts +20 -29
  136. package/src/__tests__/scaffold-managed-skill-tool.test.ts +2 -10
  137. package/src/__tests__/schedule-store.test.ts +2 -6
  138. package/src/__tests__/schedule-tools.test.ts +2 -6
  139. package/src/__tests__/scheduler-recurrence.test.ts +1 -5
  140. package/src/__tests__/scoped-approval-grants.test.ts +2 -6
  141. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
  142. package/src/__tests__/search-skills-unified.test.ts +421 -0
  143. package/src/__tests__/secret-allowlist.test.ts +20 -35
  144. package/src/__tests__/secret-onetime-send.test.ts +2 -0
  145. package/src/__tests__/send-endpoint-busy.test.ts +2 -6
  146. package/src/__tests__/sequence-store.test.ts +2 -6
  147. package/src/__tests__/server-history-render.test.ts +2 -6
  148. package/src/__tests__/shell-credential-ref.test.ts +0 -5
  149. package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
  150. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  151. package/src/__tests__/skill-load-feature-flag.test.ts +13 -54
  152. package/src/__tests__/skill-load-inline-command.test.ts +3 -65
  153. package/src/__tests__/skill-load-inline-includes.test.ts +3 -65
  154. package/src/__tests__/skill-load-tool.test.ts +3 -67
  155. package/src/__tests__/skill-memory.test.ts +480 -195
  156. package/src/__tests__/skills-uninstall.test.ts +2 -2
  157. package/src/__tests__/skills.test.ts +23 -50
  158. package/src/__tests__/slack-channel-config.test.ts +2 -21
  159. package/src/__tests__/slack-inbound-verification.test.ts +2 -6
  160. package/src/__tests__/starter-bundle.test.ts +2 -8
  161. package/src/__tests__/stt-hints.test.ts +7 -2
  162. package/src/__tests__/system-prompt.test.ts +25 -45
  163. package/src/__tests__/task-compiler.test.ts +2 -27
  164. package/src/__tests__/task-management-tools.test.ts +2 -27
  165. package/src/__tests__/task-memory-cleanup.test.ts +173 -250
  166. package/src/__tests__/task-runner.test.ts +2 -27
  167. package/src/__tests__/task-scheduler.test.ts +2 -27
  168. package/src/__tests__/terminal-tools.test.ts +1 -17
  169. package/src/__tests__/test-preload.ts +3 -0
  170. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +0 -79
  171. package/src/__tests__/tool-approval-handler.test.ts +4 -27
  172. package/src/__tests__/tool-execution-abort-cleanup.test.ts +2 -11
  173. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -25
  174. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  175. package/src/__tests__/tool-executor.test.ts +0 -1
  176. package/src/__tests__/tool-grant-request-escalation.test.ts +4 -27
  177. package/src/__tests__/tool-preview-lifecycle.test.ts +0 -20
  178. package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
  179. package/src/__tests__/trust-store.test.ts +10 -42
  180. package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -30
  181. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +3 -27
  182. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -28
  183. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -28
  184. package/src/__tests__/trusted-contact-verification.test.ts +2 -28
  185. package/src/__tests__/turn-boundary-resolution.test.ts +2 -34
  186. package/src/__tests__/twilio-provider.test.ts +0 -16
  187. package/src/__tests__/twilio-routes-twiml.test.ts +7 -12
  188. package/src/__tests__/twilio-routes.test.ts +0 -24
  189. package/src/__tests__/update-bulletin.test.ts +17 -89
  190. package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -26
  191. package/src/__tests__/usage-routes.test.ts +2 -27
  192. package/src/__tests__/user-reference.test.ts +1 -5
  193. package/src/__tests__/vbundle-pax-and-symlink.test.ts +4 -34
  194. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +2 -53
  195. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  196. package/src/__tests__/voice-invite-redemption.test.ts +2 -27
  197. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -30
  198. package/src/__tests__/voice-session-bridge.test.ts +2 -27
  199. package/src/__tests__/volume-security-guard.test.ts +2 -0
  200. package/src/__tests__/workspace-lifecycle.test.ts +29 -1
  201. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -29
  202. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +2 -2
  203. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +4 -29
  204. package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
  205. package/src/__tests__/workspace-migration-down-functions.test.ts +0 -6
  206. package/src/__tests__/workspace-policy.test.ts +1 -1
  207. package/src/acp/client-handler.ts +1 -2
  208. package/src/agent/attachments.ts +7 -2
  209. package/src/agent/image-optimize.ts +165 -0
  210. package/src/agent/loop.ts +1 -15
  211. package/src/bundler/app-compiler.ts +179 -2
  212. package/src/bundler/package-resolver.ts +3 -5
  213. package/src/cli/__tests__/notifications.test.ts +1 -24
  214. package/src/cli/cli-memory.ts +179 -0
  215. package/src/cli/commands/avatar.ts +3 -3
  216. package/src/cli/commands/config.ts +26 -13
  217. package/src/cli/commands/doctor.ts +2 -2
  218. package/src/cli/commands/memory.ts +41 -55
  219. package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
  220. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
  221. package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
  222. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
  223. package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
  224. package/src/cli/commands/oauth/connect.ts +26 -6
  225. package/src/cli/commands/oauth/mode.ts +7 -0
  226. package/src/cli/commands/oauth/providers.ts +49 -42
  227. package/src/cli/commands/oauth/shared.ts +39 -3
  228. package/src/cli/commands/platform/__tests__/connect.test.ts +3 -49
  229. package/src/cli/commands/platform/__tests__/disconnect.test.ts +3 -49
  230. package/src/cli/commands/platform/__tests__/status.test.ts +5 -55
  231. package/src/cli/commands/platform/index.ts +16 -16
  232. package/src/cli/commands/skills.ts +88 -16
  233. package/src/cli/commands/trust.ts +2 -2
  234. package/src/cli/lib/daemon-credential-client.ts +2 -3
  235. package/src/config/bundled-skills/acp/TOOLS.json +1 -1
  236. package/src/config/bundled-skills/computer-use/TOOLS.json +7 -7
  237. package/src/config/bundled-skills/contacts/SKILL.md +0 -1
  238. package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
  239. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
  240. package/src/config/bundled-skills/gmail/SKILL.md +2 -10
  241. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
  242. package/src/config/bundled-skills/messaging/SKILL.md +26 -19
  243. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
  244. package/src/config/bundled-skills/outlook/SKILL.md +189 -0
  245. package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
  246. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
  247. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
  248. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
  249. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
  250. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
  251. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
  252. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
  253. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
  254. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
  255. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
  256. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
  257. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
  258. package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
  259. package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
  260. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
  261. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
  262. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
  263. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
  264. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
  265. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
  266. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
  267. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
  268. package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
  269. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
  270. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
  271. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
  272. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
  273. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  274. package/src/config/bundled-skills/slack/SKILL.md +1 -7
  275. package/src/config/bundled-tool-registry.ts +56 -4
  276. package/src/config/env-registry.ts +15 -8
  277. package/src/config/feature-flag-registry.json +29 -116
  278. package/src/config/loader.ts +4 -0
  279. package/src/config/schemas/platform.ts +8 -0
  280. package/src/config/schemas/security.ts +0 -6
  281. package/src/config/schemas/services.ts +8 -0
  282. package/src/config/schemas/timeouts.ts +1 -1
  283. package/src/config/skills.ts +18 -7
  284. package/src/context/token-estimator.ts +25 -18
  285. package/src/context/window-manager.ts +32 -9
  286. package/src/credential-execution/approval-bridge.ts +0 -1
  287. package/src/credential-execution/process-manager.ts +3 -1
  288. package/src/daemon/config-watcher.ts +51 -0
  289. package/src/daemon/context-overflow-reducer.ts +46 -2
  290. package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
  291. package/src/daemon/conversation-agent-loop.ts +99 -63
  292. package/src/daemon/conversation-error.ts +31 -8
  293. package/src/daemon/conversation-lifecycle.ts +33 -0
  294. package/src/daemon/conversation-media-retry.ts +85 -7
  295. package/src/daemon/conversation-notifiers.ts +4 -1
  296. package/src/daemon/conversation-process.ts +1 -0
  297. package/src/daemon/conversation-runtime-assembly.ts +5 -0
  298. package/src/daemon/conversation-usage.ts +1 -0
  299. package/src/daemon/conversation.ts +41 -2
  300. package/src/daemon/daemon-control.ts +8 -2
  301. package/src/daemon/handlers/shared.ts +22 -12
  302. package/src/daemon/handlers/skills.ts +423 -201
  303. package/src/daemon/lifecycle.ts +52 -4
  304. package/src/daemon/main.ts +5 -1
  305. package/src/daemon/message-types/conversations.ts +5 -1
  306. package/src/daemon/message-types/messages.ts +3 -1
  307. package/src/daemon/message-types/skills.ts +97 -36
  308. package/src/daemon/providers-setup.ts +7 -0
  309. package/src/daemon/server.ts +35 -22
  310. package/src/daemon/tool-side-effects.ts +27 -5
  311. package/src/events/domain-events.ts +1 -2
  312. package/src/heartbeat/heartbeat-service.ts +1 -0
  313. package/src/hooks/cli.ts +2 -2
  314. package/src/hooks/runner.ts +15 -38
  315. package/src/inbound/platform-callback-registration.ts +14 -14
  316. package/src/memory/admin.ts +11 -45
  317. package/src/memory/conversation-bootstrap.ts +2 -0
  318. package/src/memory/conversation-crud.ts +242 -348
  319. package/src/memory/conversation-group-migration.ts +157 -0
  320. package/src/memory/conversation-queries.ts +4 -2
  321. package/src/memory/db-init.ts +39 -3
  322. package/src/memory/embed.ts +73 -0
  323. package/src/memory/embedding-backend.ts +8 -14
  324. package/src/memory/embedding-runtime-manager.ts +12 -114
  325. package/src/memory/fingerprint.ts +2 -2
  326. package/src/memory/graph/bootstrap.ts +512 -0
  327. package/src/memory/graph/capability-seed.ts +297 -0
  328. package/src/memory/graph/consolidation.ts +691 -0
  329. package/src/memory/graph/conversation-graph-memory.ts +630 -0
  330. package/src/memory/graph/decay.test.ts +208 -0
  331. package/src/memory/graph/decay.ts +195 -0
  332. package/src/memory/graph/extraction-job.ts +69 -0
  333. package/src/memory/graph/extraction.test.ts +936 -0
  334. package/src/memory/graph/extraction.ts +1254 -0
  335. package/src/memory/graph/graph-search.ts +266 -0
  336. package/src/memory/graph/image-ref-utils.ts +29 -0
  337. package/src/memory/graph/injection.test.ts +513 -0
  338. package/src/memory/graph/injection.ts +439 -0
  339. package/src/memory/graph/inspect.ts +534 -0
  340. package/src/memory/graph/narrative.ts +267 -0
  341. package/src/memory/graph/pattern-scan.ts +269 -0
  342. package/src/memory/graph/retriever.ts +1008 -0
  343. package/src/memory/graph/scoring.test.ts +548 -0
  344. package/src/memory/graph/scoring.ts +232 -0
  345. package/src/memory/graph/serendipity.ts +65 -0
  346. package/src/memory/graph/store.test.ts +1050 -0
  347. package/src/memory/graph/store.ts +699 -0
  348. package/src/memory/graph/tool-handlers.ts +426 -0
  349. package/src/memory/graph/tools.ts +141 -0
  350. package/src/memory/graph/triggers.test.ts +487 -0
  351. package/src/memory/graph/triggers.ts +223 -0
  352. package/src/memory/graph/types.ts +271 -0
  353. package/src/memory/group-crud.ts +191 -0
  354. package/src/memory/indexer.ts +37 -19
  355. package/src/memory/job-handlers/cleanup.ts +0 -53
  356. package/src/memory/job-handlers/conversation-starters.ts +91 -53
  357. package/src/memory/job-handlers/embedding.test.ts +3 -27
  358. package/src/memory/job-handlers/embedding.ts +5 -31
  359. package/src/memory/job-handlers/index-maintenance.ts +23 -11
  360. package/src/memory/job-handlers/summarization.ts +32 -17
  361. package/src/memory/job-utils.ts +1 -1
  362. package/src/memory/jobs-store.ts +50 -70
  363. package/src/memory/jobs-worker.ts +147 -112
  364. package/src/memory/llm-usage-store.ts +35 -2
  365. package/src/memory/message-content.ts +1 -0
  366. package/src/memory/migrations/201-oauth-providers-feature-flag.ts +11 -0
  367. package/src/memory/migrations/202-drop-callback-transport-column.ts +13 -0
  368. package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
  369. package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
  370. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
  371. package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
  372. package/src/memory/migrations/index.ts +6 -0
  373. package/src/memory/migrations/registry.ts +8 -0
  374. package/src/memory/qdrant-client.ts +44 -17
  375. package/src/memory/qdrant-manager.ts +26 -5
  376. package/src/memory/schema/index.ts +1 -0
  377. package/src/memory/schema/memory-graph.ts +139 -0
  378. package/src/memory/schema/oauth.ts +1 -1
  379. package/src/memory/search/semantic.ts +47 -91
  380. package/src/memory/slack-thread-store.ts +17 -0
  381. package/src/memory/task-memory-cleanup.ts +28 -50
  382. package/src/messaging/providers/outlook/adapter.ts +200 -0
  383. package/src/messaging/providers/outlook/client.ts +610 -0
  384. package/src/messaging/providers/outlook/types.ts +201 -0
  385. package/src/notifications/adapters/macos.ts +1 -0
  386. package/src/notifications/adapters/slack.ts +1 -1
  387. package/src/notifications/copy-composer.ts +9 -0
  388. package/src/notifications/signal.ts +16 -0
  389. package/src/oauth/__tests__/identity-verifier.test.ts +1 -1
  390. package/src/oauth/connect-orchestrator.ts +10 -3
  391. package/src/oauth/oauth-store.ts +10 -11
  392. package/src/oauth/provider-serializer.ts +3 -0
  393. package/src/oauth/provider-visibility.ts +16 -0
  394. package/src/oauth/seed-providers.ts +50 -17
  395. package/src/permissions/checker.ts +62 -9
  396. package/src/permissions/defaults.ts +4 -4
  397. package/src/permissions/types.ts +2 -4
  398. package/src/permissions/workspace-policy.ts +1 -1
  399. package/src/playbooks/playbook-compiler.ts +19 -18
  400. package/src/playbooks/types.ts +4 -3
  401. package/src/prompts/system-prompt.ts +6 -93
  402. package/src/prompts/templates/UPDATES.md +6 -0
  403. package/src/providers/anthropic/client.ts +47 -19
  404. package/src/providers/gemini/client.ts +1 -1
  405. package/src/providers/openai/client.ts +1 -1
  406. package/src/providers/registry.ts +1 -1
  407. package/src/providers/retry.ts +19 -3
  408. package/src/runtime/actor-trust-resolver.ts +5 -1
  409. package/src/runtime/auth/__tests__/credential-service.test.ts +1 -27
  410. package/src/runtime/auth/__tests__/token-service.test.ts +1 -25
  411. package/src/runtime/auth/route-policy.ts +7 -4
  412. package/src/runtime/guardian-reply-router.ts +10 -2
  413. package/src/runtime/http-server.ts +23 -3
  414. package/src/runtime/middleware/auth.ts +20 -0
  415. package/src/runtime/routes/attachment-routes.test.ts +106 -0
  416. package/src/runtime/routes/attachment-routes.ts +106 -16
  417. package/src/runtime/routes/brain-graph-routes.ts +21 -22
  418. package/src/runtime/routes/btw-routes.ts +8 -0
  419. package/src/runtime/routes/conversation-management-routes.ts +2 -0
  420. package/src/runtime/routes/conversation-query-routes.ts +2 -58
  421. package/src/runtime/routes/conversation-starter-routes.ts +2 -2
  422. package/src/runtime/routes/debug-routes.ts +1 -1
  423. package/src/runtime/routes/global-search-routes.ts +21 -19
  424. package/src/runtime/routes/group-routes.ts +207 -0
  425. package/src/runtime/routes/guardian-action-routes.ts +21 -10
  426. package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
  427. package/src/runtime/routes/inbound-message-handler.ts +19 -0
  428. package/src/runtime/routes/inbound-stages/background-dispatch.ts +43 -2
  429. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
  430. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
  431. package/src/runtime/routes/memory-item-routes.test.ts +2 -31
  432. package/src/runtime/routes/memory-item-routes.ts +385 -341
  433. package/src/runtime/routes/oauth-apps.ts +18 -1
  434. package/src/runtime/routes/oauth-providers.ts +13 -1
  435. package/src/runtime/routes/schedule-routes.ts +2 -0
  436. package/src/runtime/routes/settings-routes.ts +1 -0
  437. package/src/runtime/routes/skills-routes.ts +103 -37
  438. package/src/runtime/routes/usage-routes.ts +19 -2
  439. package/src/runtime/routes/work-items-routes.test.ts +2 -27
  440. package/src/runtime/routes/workspace-routes.test.ts +3 -27
  441. package/src/schedule/scheduler.ts +8 -1
  442. package/src/security/oauth2.ts +1 -1
  443. package/src/security/secret-allowlist.ts +4 -4
  444. package/src/security/secure-keys.ts +4 -8
  445. package/src/shared/provider-env-vars.ts +19 -0
  446. package/src/skills/catalog-cache.ts +5 -0
  447. package/src/skills/catalog-install.ts +15 -14
  448. package/src/skills/clawhub.ts +134 -154
  449. package/src/skills/install-meta.ts +208 -0
  450. package/src/skills/managed-store.ts +27 -16
  451. package/src/skills/skill-memory.ts +210 -96
  452. package/src/skills/skillssh-registry.ts +19 -17
  453. package/src/tasks/task-runner.ts +3 -1
  454. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
  455. package/src/tools/browser/runtime-check.ts +3 -1
  456. package/src/tools/memory/register.ts +63 -46
  457. package/src/tools/permission-checker.ts +7 -19
  458. package/src/tools/shared/filesystem/image-read.ts +22 -85
  459. package/src/tools/skills/skill-script-runner.ts +1 -1
  460. package/src/tools/terminal/safe-env.ts +1 -0
  461. package/src/tools/tool-manifest.ts +3 -3
  462. package/src/util/browser.ts +25 -10
  463. package/src/util/bun-runtime.ts +172 -0
  464. package/src/util/device-id.ts +3 -65
  465. package/src/watcher/providers/outlook-calendar.ts +343 -0
  466. package/src/watcher/providers/outlook.ts +198 -0
  467. package/src/workspace/git-service.ts +27 -6
  468. package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
  469. package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
  470. package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
  471. package/src/workspace/migrations/registry.ts +6 -0
  472. package/src/__tests__/context-memory-e2e.test.ts +0 -415
  473. package/src/__tests__/journal-context.test.ts +0 -268
  474. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
  475. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
  476. package/src/__tests__/memory-query-builder.test.ts +0 -59
  477. package/src/__tests__/memory-recall-quality.test.ts +0 -1046
  478. package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
  479. package/src/__tests__/memory-regressions.test.ts +0 -3696
  480. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
  481. package/src/daemon/conversation-memory.ts +0 -207
  482. package/src/memory/conversation-starters-cadence.ts +0 -74
  483. package/src/memory/items-extractor.ts +0 -860
  484. package/src/memory/job-handlers/batch-extraction.ts +0 -741
  485. package/src/memory/job-handlers/extraction.ts +0 -40
  486. package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -383
  487. package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
  488. package/src/memory/journal-memory.ts +0 -224
  489. package/src/memory/query-builder.ts +0 -47
  490. package/src/memory/query-expansion.ts +0 -83
  491. package/src/memory/retriever.test.ts +0 -1590
  492. package/src/memory/retriever.ts +0 -1323
  493. package/src/memory/search/formatting.test.ts +0 -140
  494. package/src/memory/search/formatting.ts +0 -262
  495. package/src/memory/search/mmr.ts +0 -136
  496. package/src/memory/search/ranking.ts +0 -15
  497. package/src/memory/search/staleness.ts +0 -40
  498. package/src/memory/search/tier-classifier.ts +0 -18
  499. package/src/memory/search/types.ts +0 -121
  500. package/src/prompts/journal-context.ts +0 -156
  501. package/src/tools/memory/definitions.ts +0 -69
  502. package/src/tools/memory/handlers.test.ts +0 -590
  503. package/src/tools/memory/handlers.ts +0 -434
@@ -0,0 +1,630 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Memory Graph — Conversation-level memory integration
3
+ //
4
+ // Replaces the old `prepareMemoryContext` from conversation-memory.ts.
5
+ // Manages the InContextTracker lifecycle and dispatches to the correct
6
+ // retrieval mode based on conversation state.
7
+ // ---------------------------------------------------------------------------
8
+
9
+ import { and, desc, eq, ne } from "drizzle-orm";
10
+
11
+ import type { AssistantConfig } from "../../config/types.js";
12
+ import { estimateTextTokens } from "../../context/token-estimator.js";
13
+ import type { ServerMessage } from "../../daemon/message-protocol.js";
14
+ import type {
15
+ ContentBlock,
16
+ ImageContent,
17
+ Message,
18
+ } from "../../providers/types.js";
19
+ import { getLogger } from "../../util/logger.js";
20
+ import { getDb } from "../db.js";
21
+ import { memorySummaries } from "../schema.js";
22
+ import { conversations } from "../schema/conversations.js";
23
+ import {
24
+ assembleContextBlock,
25
+ assembleInjectionBlock,
26
+ InContextTracker,
27
+ MAX_CONTEXT_LOAD_IMAGES,
28
+ MAX_PER_TURN_IMAGES,
29
+ MAX_REFRESH_IMAGES,
30
+ type ResolvedImage,
31
+ resolveInjectionImages,
32
+ } from "./injection.js";
33
+ import {
34
+ loadContextMemory,
35
+ REFRESH_INTERVAL_TURNS,
36
+ refreshContextMemory,
37
+ retrieveForTurn,
38
+ } from "./retriever.js";
39
+
40
+ const log = getLogger("graph-conversation-memory");
41
+
42
+ const ESTIMATED_IMAGE_TOKENS = 1000;
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Per-conversation state
46
+ // ---------------------------------------------------------------------------
47
+
48
+ /**
49
+ * Manages memory graph state for a single conversation.
50
+ * Create one per Conversation instance. Persists across turns.
51
+ */
52
+ export class ConversationGraphMemory {
53
+ readonly tracker = new InContextTracker();
54
+ private turnCount = 0;
55
+ private initialized = false;
56
+ private lastCompactedAt: number | null = null;
57
+ private needsReload = false;
58
+ private scopeId: string;
59
+ private conversationId: string;
60
+ private lastInjectedBlock: string | null = null;
61
+ private lastInjectedNodeIds: string[] = [];
62
+ private lastInjectedImages: Map<string, ResolvedImage> = new Map();
63
+
64
+ constructor(scopeId: string, conversationId: string) {
65
+ this.scopeId = scopeId;
66
+ this.conversationId = conversationId;
67
+ }
68
+
69
+ /**
70
+ * Fetch the most recent conversation summaries (excluding the current
71
+ * conversation, which won't have one yet at context-load time).
72
+ *
73
+ * Prioritizes user conversations (conversationType != "background"),
74
+ * allowing at most 1 background conversation summary so the retrieval
75
+ * signal is mostly from direct interactions.
76
+ *
77
+ * Returns up to 3 summary texts, most recent first.
78
+ */
79
+ private fetchRecentSummaries(): string[] {
80
+ try {
81
+ const db = getDb();
82
+ const baseWhere = and(
83
+ eq(memorySummaries.scope, "conversation"),
84
+ eq(memorySummaries.scopeId, this.scopeId),
85
+ ne(memorySummaries.scopeKey, this.conversationId),
86
+ );
87
+
88
+ // Fetch user conversations first (up to 3)
89
+ const userRows = db
90
+ .select({ summary: memorySummaries.summary })
91
+ .from(memorySummaries)
92
+ .innerJoin(
93
+ conversations,
94
+ eq(memorySummaries.scopeKey, conversations.id),
95
+ )
96
+ .where(and(baseWhere, ne(conversations.conversationType, "background")))
97
+ .orderBy(desc(memorySummaries.updatedAt))
98
+ .limit(3)
99
+ .all();
100
+
101
+ if (userRows.length >= 3) {
102
+ return userRows.map((r) => r.summary);
103
+ }
104
+
105
+ // Fill remaining slots with at most 1 background conversation
106
+ const remaining = Math.min(1, 3 - userRows.length);
107
+ const bgRows = db
108
+ .select({ summary: memorySummaries.summary })
109
+ .from(memorySummaries)
110
+ .innerJoin(
111
+ conversations,
112
+ eq(memorySummaries.scopeKey, conversations.id),
113
+ )
114
+ .where(and(baseWhere, eq(conversations.conversationType, "background")))
115
+ .orderBy(desc(memorySummaries.updatedAt))
116
+ .limit(remaining)
117
+ .all();
118
+
119
+ return [...userRows, ...bgRows].map((r) => r.summary);
120
+ } catch (err) {
121
+ log.warn({ err }, "Failed to fetch recent conversation summaries");
122
+ return [];
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Notify that context compaction just happened.
128
+ * On the next turn, we'll re-run full context load.
129
+ */
130
+ onCompacted(compactedMessageCount: number): void {
131
+ // Evict everything — compaction summarized all prior turns.
132
+ // The tracker can't know exactly which turns were compacted,
133
+ // so we conservatively clear everything and reload.
134
+ this.tracker.evictCompactedTurns(this.tracker.getTurn());
135
+ this.needsReload = true;
136
+ this.lastCompactedAt = Date.now();
137
+ log.info(
138
+ { compactedMessageCount },
139
+ "Compaction detected — will reload context on next turn",
140
+ );
141
+ }
142
+
143
+ /**
144
+ * Re-inject the most recent memory block after context compaction.
145
+ * Synchronous — reuses the cached block from the last successful retrieval.
146
+ * Does NOT advance turn count or run new retrieval.
147
+ */
148
+ reinjectCachedMemory(messages: Message[]): {
149
+ runMessages: Message[];
150
+ injectedTokens: number;
151
+ } {
152
+ if (!this.lastInjectedBlock) {
153
+ return { runMessages: messages, injectedTokens: 0 };
154
+ }
155
+ // Re-track node IDs since onCompacted evicted them
156
+ this.tracker.add(this.lastInjectedNodeIds);
157
+ // Strip any existing <memory __injected> blocks from the last user message
158
+ // before re-injecting, so compaction sites don't end up with duplicates.
159
+ const cleaned = stripExistingMemoryInjections(messages);
160
+
161
+ const injectedTokens =
162
+ estimateTextTokens(this.lastInjectedBlock) +
163
+ this.lastInjectedImages.size * ESTIMATED_IMAGE_TOKENS;
164
+
165
+ if (this.lastInjectedImages.size > 0) {
166
+ return {
167
+ runMessages: injectMemoryBlock(
168
+ cleaned,
169
+ this.lastInjectedBlock,
170
+ this.lastInjectedImages,
171
+ ),
172
+ injectedTokens,
173
+ };
174
+ }
175
+
176
+ return {
177
+ runMessages: injectTextBlock(cleaned, this.lastInjectedBlock),
178
+ injectedTokens,
179
+ };
180
+ }
181
+
182
+ /**
183
+ * Main entry point — called on every turn before the LLM sees the messages.
184
+ *
185
+ * Dispatches to the appropriate retrieval mode:
186
+ * - Turn 1 (or after compaction): full context load
187
+ * - Every 5 turns: periodic refresh
188
+ * - Every other turn: per-turn injection
189
+ *
190
+ * Returns augmented messages with memory context prepended to the last
191
+ * user message, following the same injection pattern as the old system.
192
+ */
193
+ async prepareMemory(
194
+ messages: Message[],
195
+ config: AssistantConfig,
196
+ abortSignal: AbortSignal,
197
+ onEvent: (msg: ServerMessage) => void,
198
+ ): Promise<{
199
+ runMessages: Message[];
200
+ injectedTokens: number;
201
+ latencyMs: number;
202
+ mode: "context-load" | "refresh" | "per-turn" | "none";
203
+ /** The raw text content of the injected block (without XML wrapper), or null if nothing was injected. */
204
+ injectedBlockText: string | null;
205
+ }> {
206
+ this.turnCount++;
207
+ this.tracker.advanceTurn();
208
+
209
+ const noopResult = {
210
+ runMessages: messages,
211
+ injectedTokens: 0,
212
+ latencyMs: 0,
213
+ mode: "none" as const,
214
+ injectedBlockText: null as string | null,
215
+ };
216
+
217
+ // Gate: skip for empty/tool-result-only messages — unless we need to
218
+ // reload after compaction (needsReload) or haven't initialized yet.
219
+ const lastMessage = messages[messages.length - 1];
220
+ if (!lastMessage || lastMessage.role !== "user") return noopResult;
221
+ const hasUserContent = lastMessage.content.some(
222
+ (block) => block.type === "text" && block.text.trim().length > 0,
223
+ );
224
+ if (!hasUserContent && this.initialized && !this.needsReload)
225
+ return noopResult;
226
+
227
+ try {
228
+ // Decide which retrieval mode to use
229
+ if (!this.initialized || this.needsReload) {
230
+ const recentSummaries = this.fetchRecentSummaries();
231
+
232
+ // Extract the first user message as an additional retrieval signal
233
+ // so context-load biases toward what the user is asking about
234
+ const firstUserText = extractUserText(lastMessage);
235
+ if (firstUserText) {
236
+ recentSummaries.unshift(firstUserText);
237
+ }
238
+
239
+ return await this.runContextLoad(
240
+ messages,
241
+ config,
242
+ recentSummaries,
243
+ abortSignal,
244
+ onEvent,
245
+ );
246
+ }
247
+
248
+ if (this.turnCount % REFRESH_INTERVAL_TURNS === 0) {
249
+ return await this.runRefresh(messages, config, abortSignal);
250
+ }
251
+
252
+ return await this.runPerTurn(messages, config, abortSignal);
253
+ } catch (err) {
254
+ log.warn(
255
+ { err: err instanceof Error ? err.message : String(err) },
256
+ "Memory retrieval failed (non-fatal)",
257
+ );
258
+ return noopResult;
259
+ }
260
+ }
261
+
262
+ // ---------------------------------------------------------------------------
263
+ // Retrieval modes
264
+ // ---------------------------------------------------------------------------
265
+
266
+ private async runContextLoad(
267
+ messages: Message[],
268
+ config: AssistantConfig,
269
+ recentSummaries: string[],
270
+ signal: AbortSignal,
271
+ onEvent: (msg: ServerMessage) => void,
272
+ ) {
273
+ const result = await loadContextMemory({
274
+ scopeId: this.scopeId,
275
+ recentSummaries,
276
+ config,
277
+ signal,
278
+ });
279
+
280
+ this.initialized = true;
281
+ this.needsReload = false;
282
+
283
+ if (result.nodes.length === 0) {
284
+ this.lastInjectedBlock = null;
285
+ this.lastInjectedNodeIds = [];
286
+ this.lastInjectedImages = new Map();
287
+ return {
288
+ runMessages: messages,
289
+ injectedTokens: 0,
290
+ latencyMs: result.latencyMs,
291
+ mode: "context-load" as const,
292
+ injectedBlockText: null,
293
+ };
294
+ }
295
+
296
+ // Track loaded nodes (including serendipity)
297
+ this.tracker.add(result.nodes.map((n) => n.node.id));
298
+ this.tracker.add(result.serendipityNodes.map((n) => n.node.id));
299
+
300
+ // Assemble context block
301
+ const contextBlock = assembleContextBlock(result.nodes, {
302
+ serendipityNodes: result.serendipityNodes,
303
+ });
304
+ if (!contextBlock) {
305
+ return {
306
+ runMessages: messages,
307
+ injectedTokens: 0,
308
+ latencyMs: result.latencyMs,
309
+ mode: "context-load" as const,
310
+ injectedBlockText: null,
311
+ };
312
+ }
313
+
314
+ // Resolve images from scored nodes
315
+ const images = await resolveInjectionImages(
316
+ [...result.nodes, ...result.serendipityNodes],
317
+ MAX_CONTEXT_LOAD_IMAGES,
318
+ );
319
+
320
+ const injectedTokens =
321
+ estimateTextTokens(contextBlock) + images.size * ESTIMATED_IMAGE_TOKENS;
322
+
323
+ onEvent({
324
+ type: "memory_status",
325
+ enabled: true,
326
+ degraded: false,
327
+ } as ServerMessage);
328
+
329
+ this.lastInjectedBlock = contextBlock;
330
+ this.lastInjectedNodeIds = [
331
+ ...result.nodes.map((n) => n.node.id),
332
+ ...result.serendipityNodes.map((n) => n.node.id),
333
+ ];
334
+ this.lastInjectedImages = images;
335
+
336
+ return {
337
+ runMessages: injectMemoryBlock(messages, contextBlock, images),
338
+ injectedTokens,
339
+ latencyMs: result.latencyMs,
340
+ mode: "context-load" as const,
341
+ injectedBlockText: contextBlock,
342
+ };
343
+ }
344
+
345
+ private async runRefresh(
346
+ messages: Message[],
347
+ config: AssistantConfig,
348
+ signal: AbortSignal,
349
+ ) {
350
+ // Build recent turns text from the last ~6 messages
351
+ const recentTurns = messages
352
+ .slice(-6)
353
+ .map((m) => {
354
+ const textBlocks = m.content.filter(
355
+ (b): b is Extract<typeof b, { type: "text" }> => b.type === "text",
356
+ );
357
+ if (textBlocks.length === 0) return "";
358
+ return `[${m.role}]: ${textBlocks.map((b) => b.text).join(" ")}`;
359
+ })
360
+ .filter((t) => t.length > 0)
361
+ .join("\n\n");
362
+
363
+ const result = await refreshContextMemory({
364
+ recentTurnsText: recentTurns,
365
+ scopeId: this.scopeId,
366
+ config,
367
+ tracker: this.tracker,
368
+ signal,
369
+ });
370
+
371
+ if (result.nodes.length === 0) {
372
+ this.lastInjectedBlock = null;
373
+ this.lastInjectedNodeIds = [];
374
+ this.lastInjectedImages = new Map();
375
+ return {
376
+ runMessages: messages,
377
+ injectedTokens: 0,
378
+ latencyMs: result.latencyMs,
379
+ mode: "refresh" as const,
380
+ injectedBlockText: null,
381
+ };
382
+ }
383
+
384
+ // Track new nodes
385
+ this.tracker.add(result.nodes.map((n) => n.node.id));
386
+
387
+ const injectionBlock = assembleInjectionBlock(result.nodes);
388
+ if (!injectionBlock) {
389
+ return {
390
+ runMessages: messages,
391
+ injectedTokens: 0,
392
+ latencyMs: result.latencyMs,
393
+ mode: "refresh" as const,
394
+ injectedBlockText: null,
395
+ };
396
+ }
397
+
398
+ // Resolve images from scored nodes
399
+ const images = await resolveInjectionImages(
400
+ result.nodes,
401
+ MAX_REFRESH_IMAGES,
402
+ );
403
+
404
+ this.lastInjectedBlock = injectionBlock;
405
+ this.lastInjectedNodeIds = result.nodes.map((n) => n.node.id);
406
+ this.lastInjectedImages = images;
407
+
408
+ return {
409
+ runMessages: injectMemoryBlock(messages, injectionBlock, images),
410
+ injectedTokens:
411
+ estimateTextTokens(injectionBlock) +
412
+ images.size * ESTIMATED_IMAGE_TOKENS,
413
+ latencyMs: result.latencyMs,
414
+ mode: "refresh" as const,
415
+ injectedBlockText: injectionBlock,
416
+ };
417
+ }
418
+
419
+ private async runPerTurn(
420
+ messages: Message[],
421
+ config: AssistantConfig,
422
+ signal: AbortSignal,
423
+ ) {
424
+ // Extract last assistant and user messages as text
425
+ let assistantLast = "";
426
+ let userLast = "";
427
+ let userLastBlocks: ContentBlock[] = [];
428
+
429
+ for (let i = messages.length - 1; i >= 0; i--) {
430
+ const msg = messages[i];
431
+ const text = msg.content
432
+ .filter(
433
+ (b): b is Extract<typeof b, { type: "text" }> => b.type === "text",
434
+ )
435
+ .map((b) => b.text)
436
+ .join(" ");
437
+
438
+ if (msg.role === "user") {
439
+ if (userLastBlocks.length === 0) {
440
+ userLastBlocks = msg.content;
441
+ userLast = text;
442
+ }
443
+ } else if (msg.role === "assistant" && !assistantLast) {
444
+ assistantLast = text;
445
+ }
446
+ if (userLastBlocks.length > 0 && assistantLast) break;
447
+ }
448
+
449
+ const result = await retrieveForTurn({
450
+ assistantLastMessage: assistantLast,
451
+ userLastMessage: userLast,
452
+ userLastMessageBlocks: userLastBlocks,
453
+ scopeId: this.scopeId,
454
+ config,
455
+ tracker: this.tracker,
456
+ signal,
457
+ });
458
+
459
+ if (result.nodes.length === 0) {
460
+ this.lastInjectedBlock = null;
461
+ this.lastInjectedNodeIds = [];
462
+ this.lastInjectedImages = new Map();
463
+ return {
464
+ runMessages: messages,
465
+ injectedTokens: 0,
466
+ latencyMs: result.latencyMs,
467
+ mode: "per-turn" as const,
468
+ injectedBlockText: null,
469
+ };
470
+ }
471
+
472
+ // Track new nodes
473
+ this.tracker.add(result.nodes.map((n) => n.node.id));
474
+
475
+ const injectionBlock = assembleInjectionBlock(result.nodes);
476
+ if (!injectionBlock) {
477
+ return {
478
+ runMessages: messages,
479
+ injectedTokens: 0,
480
+ latencyMs: result.latencyMs,
481
+ mode: "per-turn" as const,
482
+ injectedBlockText: null,
483
+ };
484
+ }
485
+
486
+ // Resolve images from scored nodes
487
+ const images = await resolveInjectionImages(
488
+ result.nodes,
489
+ MAX_PER_TURN_IMAGES,
490
+ );
491
+
492
+ this.lastInjectedBlock = injectionBlock;
493
+ this.lastInjectedNodeIds = result.nodes.map((n) => n.node.id);
494
+ this.lastInjectedImages = images;
495
+
496
+ return {
497
+ runMessages: injectMemoryBlock(messages, injectionBlock, images),
498
+ injectedTokens:
499
+ estimateTextTokens(injectionBlock) +
500
+ images.size * ESTIMATED_IMAGE_TOKENS,
501
+ latencyMs: result.latencyMs,
502
+ mode: "per-turn" as const,
503
+ injectedBlockText: injectionBlock,
504
+ };
505
+ }
506
+ }
507
+
508
+ // ---------------------------------------------------------------------------
509
+ // Injection helper — same pattern as old injectMemoryRecallAsUserBlock
510
+ // ---------------------------------------------------------------------------
511
+
512
+ /**
513
+ * Remove all memory-injected blocks from the last user message.
514
+ *
515
+ * `injectMemoryBlock` always prepends blocks in this order:
516
+ * 1. `<memory __injected>…</memory>` text block
517
+ * 2. For each image: `<memory_image>…</memory_image>` text + `image` block
518
+ *
519
+ * We strip all leading blocks that match this pattern so that
520
+ * `reinjectCachedMemory` is idempotent — no duplicate images after compaction.
521
+ */
522
+ function stripExistingMemoryInjections(messages: Message[]): Message[] {
523
+ if (messages.length === 0) return messages;
524
+ const last = messages[messages.length - 1];
525
+ if (!last || last.role !== "user") return messages;
526
+
527
+ // Walk from the front and skip all memory-injected blocks.
528
+ // The injection prefix is always contiguous at the start of content.
529
+ let firstNonMemory = 0;
530
+ const content = last.content;
531
+ while (firstNonMemory < content.length) {
532
+ const block = content[firstNonMemory];
533
+ if (
534
+ block.type === "text" &&
535
+ block.text.startsWith("<memory __injected>\n")
536
+ ) {
537
+ firstNonMemory++;
538
+ } else if (
539
+ block.type === "text" &&
540
+ block.text.startsWith("<memory_image>")
541
+ ) {
542
+ firstNonMemory++;
543
+ } else if (block.type === "image") {
544
+ firstNonMemory++;
545
+ } else {
546
+ break;
547
+ }
548
+ }
549
+
550
+ // Nothing to strip
551
+ if (firstNonMemory === 0) return messages;
552
+
553
+ return [
554
+ ...messages.slice(0, -1),
555
+ { ...last, content: content.slice(firstNonMemory) },
556
+ ];
557
+ }
558
+
559
+ function injectTextBlock(messages: Message[], text: string): Message[] {
560
+ if (text.trim().length === 0) return messages;
561
+ if (messages.length === 0) return messages;
562
+ // Strip existing memory blocks from the last user message first to prevent
563
+ // duplicates when the message was loaded from DB with a persisted block.
564
+ const cleaned = stripExistingMemoryInjections(messages);
565
+ const userTail = cleaned[cleaned.length - 1];
566
+ if (!userTail || userTail.role !== "user") return messages;
567
+ return [
568
+ ...cleaned.slice(0, -1),
569
+ {
570
+ ...userTail,
571
+ content: [
572
+ {
573
+ type: "text" as const,
574
+ text: `<memory __injected>\n${text}\n</memory>`,
575
+ },
576
+ ...userTail.content,
577
+ ],
578
+ },
579
+ ];
580
+ }
581
+
582
+ function injectMemoryBlock(
583
+ messages: Message[],
584
+ text: string,
585
+ images: Map<string, ResolvedImage>,
586
+ ): Message[] {
587
+ if (text.trim().length === 0 && images.size === 0) return messages;
588
+ if (messages.length === 0) return messages;
589
+ // Strip existing memory blocks from the last user message first to prevent
590
+ // duplicates when the message was loaded from DB with a persisted block.
591
+ const cleaned = stripExistingMemoryInjections(messages);
592
+ const userTail = cleaned[cleaned.length - 1];
593
+ if (!userTail || userTail.role !== "user") return messages;
594
+
595
+ const blocks: ContentBlock[] = [
596
+ { type: "text" as const, text: `<memory __injected>\n${text}\n</memory>` },
597
+ ];
598
+
599
+ for (const [_nodeId, img] of images) {
600
+ blocks.push({
601
+ type: "text" as const,
602
+ text: `<memory_image>${img.description}</memory_image>`,
603
+ });
604
+ blocks.push({
605
+ type: "image" as const,
606
+ source: {
607
+ type: "base64" as const,
608
+ media_type: img.mediaType,
609
+ data: img.base64Data,
610
+ },
611
+ } as ImageContent);
612
+ }
613
+
614
+ return [
615
+ ...cleaned.slice(0, -1),
616
+ { ...userTail, content: [...blocks, ...userTail.content] },
617
+ ];
618
+ }
619
+
620
+ /** Extract text content from a user message. */
621
+ function extractUserText(message: Message): string | null {
622
+ const texts = message.content
623
+ .filter((b): b is Extract<typeof b, { type: "text" }> => b.type === "text")
624
+ .map((b) => b.text.trim())
625
+ .filter((t) => t.length > 0);
626
+ if (texts.length === 0) return null;
627
+ const joined = texts.join(" ");
628
+ // Skip very short messages ("hi", "yes") — they produce vague embeddings
629
+ return joined.length > 10 ? joined : null;
630
+ }