@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,725 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Memory Graph — LLM-based consolidation engine
3
+ //
4
+ // Runs daily (or on demand). Processes nodes in partitions:
5
+ // 1. Recency: nodes from last 7 days — merge duplicates, initial narrative
6
+ // 2. Significance: top N by significance — update narrative arcs
7
+ // 3. Decay: nodes at faded/gist fidelity — candidates for merging or marking gone
8
+ // 4. Random sample: cross-pollination and pattern detection
9
+ //
10
+ // Each partition is a separate LLM call. The LLM produces a MemoryDiff
11
+ // (same format as extraction) that is applied to the graph.
12
+ // ---------------------------------------------------------------------------
13
+
14
+ import type { AssistantConfig } from "../../config/types.js";
15
+ import {
16
+ extractToolUse,
17
+ getConfiguredProvider,
18
+ userMessage,
19
+ } from "../../providers/provider-send-message.js";
20
+ import { BackendUnavailableError } from "../../util/errors.js";
21
+ import { getLogger } from "../../util/logger.js";
22
+ import { getDb } from "../db.js";
23
+ import { parseEpochMs } from "./extraction.js";
24
+ import {
25
+ createTrigger,
26
+ deduplicateParagraphs,
27
+ deleteNode,
28
+ getEdgesForNode,
29
+ getTriggersForNode,
30
+ queryNodes,
31
+ recordNodeEdit,
32
+ updateNode,
33
+ } from "./store.js";
34
+ import type { MemoryNode } from "./types.js";
35
+ import { isCapabilityNode } from "./types.js";
36
+
37
+ const log = getLogger("graph-consolidation");
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Consolidation prompt
41
+ // ---------------------------------------------------------------------------
42
+
43
+ function buildConsolidationPrompt(
44
+ partitionName: string,
45
+ nodes: Array<{
46
+ id: string;
47
+ type: string;
48
+ content: string;
49
+ significance: number;
50
+ fidelity: string;
51
+ reinforcementCount: number;
52
+ created: number;
53
+ eventDate: number | null;
54
+ hasImage: boolean;
55
+ }>,
56
+ edges: Array<{ sourceId: string; targetId: string; relationship: string }>,
57
+ ): string {
58
+ const nodeList = nodes
59
+ .map((n) => {
60
+ const age = Math.floor((Date.now() - n.created) / (1000 * 60 * 60 * 24));
61
+ const eventStr =
62
+ n.eventDate != null
63
+ ? ` eventDate=${new Date(n.eventDate).toISOString().split("T")[0]}`
64
+ : "";
65
+ const imageStr = n.hasImage ? " [has_image]" : "";
66
+ return ` [${n.id}] type=${n.type} sig=${n.significance.toFixed(
67
+ 2,
68
+ )} fidelity=${n.fidelity} reinforced=${
69
+ n.reinforcementCount
70
+ }x age=${age}d${eventStr}${imageStr}\n ${n.content}`;
71
+ })
72
+ .join("\n\n");
73
+
74
+ const edgeList =
75
+ edges.length > 0
76
+ ? edges
77
+ .map((e) => ` ${e.sourceId} --${e.relationship}--> ${e.targetId}`)
78
+ .join("\n")
79
+ : " (none)";
80
+
81
+ const today = new Date().toLocaleDateString("en-US", {
82
+ weekday: "long",
83
+ year: "numeric",
84
+ month: "long",
85
+ day: "numeric",
86
+ });
87
+
88
+ return `You are consolidating the "${partitionName}" partition of a memory graph. These are memories stored by an AI assistant about its conversations and relationship with a user.
89
+
90
+ Today is ${today}.
91
+
92
+ ## Your Tasks
93
+
94
+ 1. **Merge duplicates**: If two or more nodes describe the same event or fact, merge them into one by:
95
+ - Keeping the richer/more complete version (UPDATE it to incorporate details from duplicates)
96
+ - DELETE the duplicates
97
+ - Preserve the highest significance, reinforcement count, and stability from the merged nodes
98
+ - Create a "supersedes" edge from the surviving node to each deleted node (the store handles deletion, but log the relationship)
99
+
100
+ 2. **Downgrade faded memories**: For nodes at "faded" or "gist" fidelity, rewrite their content to be shorter and more abstract — like how a real memory fades. A "faded" memory should be 1-2 sentences. A "gist" memory should be one sentence capturing only the essence.
101
+
102
+ 3. **Update narrative roles**: If a node is clearly a turning point, inciting incident, or thesis in a larger story arc, set its narrativeRole and partOfStory.
103
+
104
+ 4. **Mark gone**: If a node is at "gist" fidelity and has very low significance (< 0.2), mark it for deletion — it's faded beyond usefulness.
105
+
106
+ 5. **Resolve stale prospective nodes**: If a prospective node (type=prospective) is older than 7 days and has no "resolved-by" edge, it's likely a stale commitment. Downgrade its fidelity to "gist" and rewrite it as a past observation (e.g. "Had planned to X" instead of "Need to X"). If it's already at gist with significance < 0.2, mark it for deletion. If the node has an event_date in the past, clear it by setting event_date to null.
107
+
108
+ ## Constraints
109
+
110
+ - Do NOT create new nodes — consolidation only merges, updates, and prunes
111
+ - Do NOT change a node's type
112
+ - Do NOT increase fidelity (memories only fade, never sharpen)
113
+ - Preserve first-person prose style in content rewrites
114
+ - When merging, keep the node with higher reinforcementCount as the survivor
115
+
116
+ ## Current Nodes (${partitionName})
117
+
118
+ ${nodeList}
119
+
120
+ ## Current Edges
121
+
122
+ ${edgeList}
123
+
124
+ Use the consolidate_diff tool to output your changes.`;
125
+ }
126
+
127
+ const CONSOLIDATE_TOOL_SCHEMA = {
128
+ name: "consolidate_diff",
129
+ description: "Output consolidation changes to the memory graph",
130
+ input_schema: {
131
+ type: "object" as const,
132
+ properties: {
133
+ updates: {
134
+ type: "array" as const,
135
+ description:
136
+ "Nodes to update (content rewrites, narrative roles, fidelity downgrades)",
137
+ items: {
138
+ type: "object" as const,
139
+ properties: {
140
+ id: { type: "string" as const },
141
+ content: {
142
+ type: "string" as const,
143
+ description: "New content (if rewriting)",
144
+ },
145
+ fidelity: {
146
+ type: "string" as const,
147
+ enum: ["vivid", "clear", "faded", "gist", "gone"],
148
+ },
149
+ narrativeRole: { type: "string" as const },
150
+ partOfStory: { type: "string" as const },
151
+ event_date: {
152
+ type: ["number", "null"] as const,
153
+ description:
154
+ "Epoch ms of the event this memory describes. Preserve from merged duplicates when the survivor lacks one. Set to null to clear a stale event date.",
155
+ },
156
+ },
157
+ required: ["id"] as const,
158
+ },
159
+ },
160
+ delete_ids: {
161
+ type: "array" as const,
162
+ description: "Node IDs to delete (merged duplicates, gone memories)",
163
+ items: { type: "string" as const },
164
+ },
165
+ merge_edges: {
166
+ type: "array" as const,
167
+ description: "Supersedes edges for merged nodes (survivor → deleted)",
168
+ items: {
169
+ type: "object" as const,
170
+ properties: {
171
+ survivor_id: { type: "string" as const },
172
+ deleted_id: { type: "string" as const },
173
+ },
174
+ required: ["survivor_id", "deleted_id"] as const,
175
+ },
176
+ },
177
+ },
178
+ required: ["updates", "delete_ids", "merge_edges"] as const,
179
+ },
180
+ };
181
+
182
+ // ---------------------------------------------------------------------------
183
+ // Partition builders
184
+ // ---------------------------------------------------------------------------
185
+
186
+ function getRecentNodes(scopeId: string, days: number = 7): MemoryNode[] {
187
+ const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
188
+ return queryNodes({
189
+ scopeId,
190
+ fidelityNot: ["gone"],
191
+ createdAfter: cutoff,
192
+ limit: 10000,
193
+ }).filter((n) => !isCapabilityNode(n));
194
+ }
195
+
196
+ function getTopSignificanceNodes(
197
+ scopeId: string,
198
+ n: number = 50,
199
+ ): MemoryNode[] {
200
+ return queryNodes({
201
+ scopeId,
202
+ fidelityNot: ["gone"],
203
+ minSignificance: 0.6,
204
+ })
205
+ .filter((n) => !isCapabilityNode(n))
206
+ .slice(0, n);
207
+ }
208
+
209
+ function getDecayedNodes(scopeId: string): MemoryNode[] {
210
+ const all = queryNodes({
211
+ scopeId,
212
+ limit: 10000,
213
+ });
214
+ return all.filter(
215
+ (n) =>
216
+ (n.fidelity === "faded" || n.fidelity === "gist") && !isCapabilityNode(n),
217
+ );
218
+ }
219
+
220
+ function getRandomSample(scopeId: string, n: number = 30): MemoryNode[] {
221
+ const all = queryNodes({
222
+ scopeId,
223
+ fidelityNot: ["gone"],
224
+ limit: 10000,
225
+ }).filter((n) => !isCapabilityNode(n));
226
+ // Fisher-Yates shuffle, take first n
227
+ for (let i = all.length - 1; i > 0; i--) {
228
+ const j = Math.floor(Math.random() * (i + 1));
229
+ [all[i], all[j]] = [all[j], all[i]];
230
+ }
231
+ return all.slice(0, n);
232
+ }
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // Run consolidation on a partition
236
+ // ---------------------------------------------------------------------------
237
+
238
+ const CHUNK_SIZE = 25;
239
+
240
+ // ---------------------------------------------------------------------------
241
+ // Duplicate detection — fast LLM call on compact listings
242
+ // ---------------------------------------------------------------------------
243
+
244
+ const DUPE_DETECT_TOOL = {
245
+ name: "report_duplicate_groups",
246
+ description:
247
+ "Report groups of nodes that describe the same event, fact, or topic and should be merged",
248
+ input_schema: {
249
+ type: "object" as const,
250
+ properties: {
251
+ groups: {
252
+ type: "array" as const,
253
+ description:
254
+ "Each group is a list of node IDs that are duplicates of each other",
255
+ items: {
256
+ type: "array" as const,
257
+ items: { type: "string" as const },
258
+ },
259
+ },
260
+ },
261
+ required: ["groups"] as const,
262
+ },
263
+ };
264
+
265
+ /**
266
+ * Fast LLM pass to identify duplicate groups from a compact node listing.
267
+ * Uses a latency-optimized model since it only needs to compare one-line
268
+ * summaries, not reason about full prose. Returns groups of MemoryNodes
269
+ * that should be consolidated together.
270
+ */
271
+ async function identifyDuplicateGroups(
272
+ nodes: MemoryNode[],
273
+ _config: AssistantConfig,
274
+ ): Promise<MemoryNode[][]> {
275
+ if (nodes.length < 2) return [];
276
+
277
+ const provider = await getConfiguredProvider();
278
+ if (!provider) return [];
279
+
280
+ // Compact listing: ID + first 100 chars of content
281
+ const listing = nodes
282
+ .map((n) => {
283
+ const preview =
284
+ n.content.length > 100 ? n.content.slice(0, 100) + "…" : n.content;
285
+ return `[${n.id}] ${preview}`;
286
+ })
287
+ .join("\n");
288
+
289
+ const systemPrompt = `You are scanning a list of memory nodes for DUPLICATES — nodes that describe the same event, fact, or topic. Group duplicates together. Two nodes are duplicates if they describe the same underlying thing, even if worded differently. Be aggressive — if in doubt, group them. Only include nodes that have at least one duplicate.`;
290
+
291
+ const response = await provider.sendMessage(
292
+ [userMessage(listing)],
293
+ [DUPE_DETECT_TOOL],
294
+ systemPrompt,
295
+ {
296
+ config: {
297
+ modelIntent: "quality-optimized" as const,
298
+ tool_choice: { type: "tool" as const, name: "report_duplicate_groups" },
299
+ },
300
+ },
301
+ );
302
+
303
+ const toolBlock = extractToolUse(response);
304
+ if (!toolBlock) return [];
305
+
306
+ const input = toolBlock.input as { groups?: string[][] };
307
+ if (!input.groups) return [];
308
+
309
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
310
+
311
+ return (input.groups ?? [])
312
+ .map((ids) =>
313
+ ids.filter((id) => nodeMap.has(id)).map((id) => nodeMap.get(id)!),
314
+ )
315
+ .filter((group) => group.length >= 2);
316
+ }
317
+
318
+ // ---------------------------------------------------------------------------
319
+ // Consolidation partition processing
320
+ // ---------------------------------------------------------------------------
321
+
322
+ interface ConsolidationPartitionResult {
323
+ nodesUpdated: number;
324
+ nodesDeleted: number;
325
+ mergeEdgesCreated: number;
326
+ }
327
+
328
+ async function consolidatePartition(
329
+ partitionName: string,
330
+ nodes: MemoryNode[],
331
+ config: AssistantConfig,
332
+ ): Promise<ConsolidationPartitionResult> {
333
+ const result: ConsolidationPartitionResult = {
334
+ nodesUpdated: 0,
335
+ nodesDeleted: 0,
336
+ mergeEdgesCreated: 0,
337
+ };
338
+
339
+ if (nodes.length === 0) return result;
340
+
341
+ // Step 1: Fast LLM call to identify duplicate groups from compact listing
342
+ const dupeGroups = await identifyDuplicateGroups(nodes, config);
343
+ // Collect all nodes that appear in a duplicate group
344
+ const inGroup = new Set(dupeGroups.flat().map((n) => n.id));
345
+ // Singletons: nodes not in any duplicate group
346
+ const singletons = nodes.filter((n) => !inGroup.has(n.id));
347
+
348
+ log.info(
349
+ {
350
+ partition: partitionName,
351
+ nodeCount: nodes.length,
352
+ dupeGroups: dupeGroups.length,
353
+ singletons: singletons.length,
354
+ },
355
+ "Identified duplicate groups for consolidation",
356
+ );
357
+
358
+ // Step 2: Run full consolidation on each duplicate group
359
+ const deleted = new Set<string>();
360
+ for (let i = 0; i < dupeGroups.length; i++) {
361
+ const chunk = dupeGroups[i].filter((n) => !deleted.has(n.id));
362
+ if (chunk.length < 2) continue;
363
+
364
+ const chunkResult = await consolidateChunk(
365
+ `${partitionName} dupes (${i + 1}/${dupeGroups.length})`,
366
+ chunk,
367
+ config,
368
+ );
369
+ result.nodesUpdated += chunkResult.nodesUpdated;
370
+ result.nodesDeleted += chunkResult.nodesDeleted;
371
+ result.mergeEdgesCreated += chunkResult.mergeEdgesCreated;
372
+ for (const id of chunkResult.deletedIds) deleted.add(id);
373
+ }
374
+
375
+ // Step 3: Run consolidation on singletons in chunks (for fidelity/narrative updates)
376
+ const remainingSingletons = singletons.filter((n) => !deleted.has(n.id));
377
+ if (remainingSingletons.length >= 2) {
378
+ for (let i = 0; i < remainingSingletons.length; i += CHUNK_SIZE) {
379
+ const chunk = remainingSingletons.slice(i, i + CHUNK_SIZE);
380
+ if (chunk.length < 2) continue;
381
+
382
+ const chunkResult = await consolidateChunk(
383
+ `${partitionName} singles (${Math.floor(i / CHUNK_SIZE) + 1})`,
384
+ chunk,
385
+ config,
386
+ );
387
+ result.nodesUpdated += chunkResult.nodesUpdated;
388
+ result.nodesDeleted += chunkResult.nodesDeleted;
389
+ result.mergeEdgesCreated += chunkResult.mergeEdgesCreated;
390
+ for (const id of chunkResult.deletedIds) deleted.add(id);
391
+ }
392
+ }
393
+
394
+ return result;
395
+ }
396
+
397
+ interface ChunkResult extends ConsolidationPartitionResult {
398
+ deletedIds: string[];
399
+ }
400
+
401
+ async function consolidateChunk(
402
+ chunkName: string,
403
+ nodes: MemoryNode[],
404
+ _config: AssistantConfig,
405
+ ): Promise<ChunkResult> {
406
+ const result: ChunkResult = {
407
+ nodesUpdated: 0,
408
+ nodesDeleted: 0,
409
+ mergeEdgesCreated: 0,
410
+ deletedIds: [],
411
+ };
412
+
413
+ if (nodes.length === 0) return result;
414
+
415
+ // Collect edges between partition nodes
416
+ const nodeIds = new Set(nodes.map((n) => n.id));
417
+ const edges: Array<{
418
+ sourceId: string;
419
+ targetId: string;
420
+ relationship: string;
421
+ }> = [];
422
+ for (const node of nodes) {
423
+ for (const edge of getEdgesForNode(node.id)) {
424
+ if (nodeIds.has(edge.sourceNodeId) && nodeIds.has(edge.targetNodeId)) {
425
+ edges.push({
426
+ sourceId: edge.sourceNodeId,
427
+ targetId: edge.targetNodeId,
428
+ relationship: edge.relationship,
429
+ });
430
+ }
431
+ }
432
+ }
433
+
434
+ // Deduplicate edges
435
+ const edgeKeys = new Set<string>();
436
+ const dedupedEdges = edges.filter((e) => {
437
+ const key = `${e.sourceId}-${e.relationship}-${e.targetId}`;
438
+ if (edgeKeys.has(key)) return false;
439
+ edgeKeys.add(key);
440
+ return true;
441
+ });
442
+
443
+ const provider = await getConfiguredProvider();
444
+ if (!provider) {
445
+ throw new BackendUnavailableError("Provider unavailable for consolidation");
446
+ }
447
+
448
+ const systemPrompt = buildConsolidationPrompt(
449
+ chunkName,
450
+ nodes.map((n) => ({
451
+ id: n.id,
452
+ type: n.type,
453
+ content: n.content,
454
+ significance: n.significance,
455
+ fidelity: n.fidelity,
456
+ reinforcementCount: n.reinforcementCount,
457
+ created: n.created,
458
+ eventDate: n.eventDate,
459
+ hasImage: n.imageRefs != null && n.imageRefs.length > 0,
460
+ })),
461
+ dedupedEdges,
462
+ );
463
+
464
+ const response = await provider.sendMessage(
465
+ [
466
+ userMessage(
467
+ "Consolidate this partition. Focus on merging duplicates and fading old memories.",
468
+ ),
469
+ ],
470
+ [CONSOLIDATE_TOOL_SCHEMA],
471
+ systemPrompt,
472
+ {
473
+ config: {
474
+ modelIntent: "quality-optimized" as const,
475
+ tool_choice: { type: "tool" as const, name: "consolidate_diff" },
476
+ },
477
+ },
478
+ );
479
+
480
+ const toolBlock = extractToolUse(response);
481
+ if (!toolBlock) {
482
+ log.warn("No tool_use block in consolidation response");
483
+ return result;
484
+ }
485
+
486
+ const input = toolBlock.input as {
487
+ updates?: Array<{
488
+ id: string;
489
+ content?: string;
490
+ fidelity?: string;
491
+ narrativeRole?: string;
492
+ partOfStory?: string;
493
+ event_date?: number | null;
494
+ }>;
495
+ delete_ids?: string[];
496
+ merge_edges?: Array<{ survivor_id: string; deleted_id: string }>;
497
+ };
498
+
499
+ // Build nodeMap once upfront; patch entries after each updateNode() so
500
+ // later iterations always read fresh in-memory state.
501
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
502
+
503
+ // Apply updates
504
+ for (const update of input.updates ?? []) {
505
+ if (!nodeIds.has(update.id)) continue; // safety: only update nodes in this partition
506
+
507
+ const changes: Partial<MemoryNode> = {};
508
+ if (update.content) changes.content = update.content;
509
+ if (update.fidelity)
510
+ changes.fidelity = update.fidelity as MemoryNode["fidelity"];
511
+ if (update.narrativeRole !== undefined)
512
+ changes.narrativeRole = update.narrativeRole || null;
513
+ if (update.partOfStory !== undefined)
514
+ changes.partOfStory = update.partOfStory || null;
515
+ if (update.event_date !== undefined)
516
+ changes.eventDate = parseEpochMs(update.event_date);
517
+ changes.lastConsolidated = Date.now();
518
+
519
+ if (Object.keys(changes).length > 1) {
520
+ // more than just lastConsolidated
521
+
522
+ // Wrap edit recording + node update in a transaction so they are atomic:
523
+ // if updateNode fails, the edit record is rolled back.
524
+ getDb().transaction(() => {
525
+ if (changes.content) {
526
+ const cleanContent = deduplicateParagraphs(changes.content);
527
+ const node = nodeMap.get(update.id);
528
+ if (node && node.content !== cleanContent) {
529
+ recordNodeEdit({
530
+ nodeId: update.id,
531
+ previousContent: node.content,
532
+ newContent: cleanContent,
533
+ source: "consolidation",
534
+ });
535
+ }
536
+ }
537
+
538
+ updateNode(update.id, changes);
539
+ });
540
+ result.nodesUpdated++;
541
+ // Sync in-memory state with what updateNode actually wrote to the DB
542
+ // (updateNode deduplicates content before persisting)
543
+ if (changes.content)
544
+ changes.content = deduplicateParagraphs(changes.content);
545
+ const node = nodeMap.get(update.id);
546
+ if (node) Object.assign(node, changes);
547
+ }
548
+ }
549
+
550
+ // Apply merge edges (before deletion so the edge can reference the node)
551
+ const { createEdge } = await import("./store.js");
552
+ for (const merge of input.merge_edges ?? []) {
553
+ if (!nodeIds.has(merge.survivor_id) || !nodeIds.has(merge.deleted_id))
554
+ continue;
555
+ try {
556
+ createEdge({
557
+ sourceNodeId: merge.survivor_id,
558
+ targetNodeId: merge.deleted_id,
559
+ relationship: "supersedes",
560
+ weight: 1.0,
561
+ created: Date.now(),
562
+ });
563
+ result.mergeEdgesCreated++;
564
+
565
+ // Preserve eventDate from deleted node if survivor doesn't have one
566
+ const survivor = nodeMap.get(merge.survivor_id);
567
+ const deleted = nodeMap.get(merge.deleted_id);
568
+ if (
569
+ survivor &&
570
+ deleted?.eventDate != null &&
571
+ survivor.eventDate == null
572
+ ) {
573
+ updateNode(merge.survivor_id, { eventDate: deleted.eventDate });
574
+ survivor.eventDate = deleted.eventDate;
575
+
576
+ // The deleted node's triggers will be cascade-deleted when the node
577
+ // is removed. Ensure the survivor has an event trigger for the
578
+ // inherited eventDate (updateNode only syncs existing triggers).
579
+ const survivorTriggers = getTriggersForNode(merge.survivor_id);
580
+ if (!survivorTriggers.some((t) => t.type === "event")) {
581
+ createTrigger({
582
+ nodeId: merge.survivor_id,
583
+ type: "event",
584
+ schedule: null,
585
+ condition: null,
586
+ conditionEmbedding: null,
587
+ threshold: null,
588
+ eventDate: deleted.eventDate,
589
+ rampDays: 7,
590
+ followUpDays: 2,
591
+ recurring: false,
592
+ consumed: false,
593
+ cooldownMs: null,
594
+ lastFired: null,
595
+ });
596
+ }
597
+ }
598
+
599
+ // Preserve imageRefs from deleted node if survivor doesn't have any
600
+ if (
601
+ survivor &&
602
+ deleted?.imageRefs != null &&
603
+ deleted.imageRefs.length > 0 &&
604
+ (survivor.imageRefs == null || survivor.imageRefs.length === 0)
605
+ ) {
606
+ updateNode(merge.survivor_id, { imageRefs: deleted.imageRefs });
607
+ survivor.imageRefs = deleted.imageRefs;
608
+ }
609
+ } catch (err) {
610
+ log.warn({ err }, "Failed to create merge edge");
611
+ }
612
+ }
613
+
614
+ // Apply deletions
615
+ for (const id of input.delete_ids ?? []) {
616
+ if (!nodeIds.has(id)) continue; // safety
617
+ try {
618
+ log.info({ nodeId: id }, "Consolidation deleting node");
619
+ deleteNode(id);
620
+ result.nodesDeleted++;
621
+ result.deletedIds.push(id);
622
+ } catch (err) {
623
+ log.warn({ id, err }, "Failed to delete node during consolidation");
624
+ }
625
+ }
626
+
627
+ return result;
628
+ }
629
+
630
+ // ---------------------------------------------------------------------------
631
+ // Full consolidation run
632
+ // ---------------------------------------------------------------------------
633
+
634
+ export interface ConsolidationResult {
635
+ partitions: Record<string, ConsolidationPartitionResult>;
636
+ totalUpdated: number;
637
+ totalDeleted: number;
638
+ totalMergeEdges: number;
639
+ latencyMs: number;
640
+ }
641
+
642
+ export async function runConsolidation(
643
+ scopeId: string = "default",
644
+ config: AssistantConfig,
645
+ ): Promise<ConsolidationResult> {
646
+ const start = Date.now();
647
+
648
+ const result: ConsolidationResult = {
649
+ partitions: {},
650
+ totalUpdated: 0,
651
+ totalDeleted: 0,
652
+ totalMergeEdges: 0,
653
+ latencyMs: 0,
654
+ };
655
+
656
+ // Define partitions
657
+ const partitions: Array<{ name: string; nodes: MemoryNode[] }> = [
658
+ { name: "recent", nodes: getRecentNodes(scopeId) },
659
+ { name: "significant", nodes: getTopSignificanceNodes(scopeId) },
660
+ { name: "decayed", nodes: getDecayedNodes(scopeId) },
661
+ { name: "random", nodes: getRandomSample(scopeId) },
662
+ ];
663
+
664
+ for (const partition of partitions) {
665
+ if (partition.nodes.length === 0) {
666
+ log.info({ partition: partition.name }, "Empty partition, skipping");
667
+ continue;
668
+ }
669
+
670
+ log.info(
671
+ { partition: partition.name, nodeCount: partition.nodes.length },
672
+ "Consolidating partition",
673
+ );
674
+
675
+ try {
676
+ const partitionResult = await consolidatePartition(
677
+ partition.name,
678
+ partition.nodes,
679
+ config,
680
+ );
681
+
682
+ result.partitions[partition.name] = partitionResult;
683
+ result.totalUpdated += partitionResult.nodesUpdated;
684
+ result.totalDeleted += partitionResult.nodesDeleted;
685
+ result.totalMergeEdges += partitionResult.mergeEdgesCreated;
686
+
687
+ log.info(
688
+ {
689
+ partition: partition.name,
690
+ updated: partitionResult.nodesUpdated,
691
+ deleted: partitionResult.nodesDeleted,
692
+ mergeEdges: partitionResult.mergeEdgesCreated,
693
+ },
694
+ "Partition consolidation complete",
695
+ );
696
+ } catch (err) {
697
+ log.warn(
698
+ {
699
+ partition: partition.name,
700
+ err: err instanceof Error ? err.message : String(err),
701
+ },
702
+ "Partition consolidation failed",
703
+ );
704
+ result.partitions[partition.name] = {
705
+ nodesUpdated: 0,
706
+ nodesDeleted: 0,
707
+ mergeEdgesCreated: 0,
708
+ };
709
+ }
710
+ }
711
+
712
+ result.latencyMs = Date.now() - start;
713
+
714
+ log.info(
715
+ {
716
+ totalUpdated: result.totalUpdated,
717
+ totalDeleted: result.totalDeleted,
718
+ totalMergeEdges: result.totalMergeEdges,
719
+ latencyMs: result.latencyMs,
720
+ },
721
+ "Full consolidation complete",
722
+ );
723
+
724
+ return result;
725
+ }