@vellumai/assistant 0.5.16 → 0.6.1

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