@vellumai/assistant 0.5.16 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +1 -1
- package/Dockerfile +0 -3
- package/knip.json +2 -1
- package/openapi.yaml +660 -80
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +68 -0
- package/src/__tests__/agent-loop.test.ts +0 -32
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
- package/src/__tests__/anthropic-provider.test.ts +57 -3
- package/src/__tests__/app-compiler.test.ts +120 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
- package/src/__tests__/call-conversation-messages.test.ts +2 -6
- package/src/__tests__/call-domain.test.ts +2 -6
- package/src/__tests__/call-pointer-messages.test.ts +2 -14
- package/src/__tests__/call-recovery.test.ts +2 -6
- package/src/__tests__/call-routes-http.test.ts +2 -6
- package/src/__tests__/call-store.test.ts +2 -6
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
- package/src/__tests__/canonical-guardian-store.test.ts +2 -6
- package/src/__tests__/channel-delivery-store.test.ts +2 -6
- package/src/__tests__/channel-retry-sweep.test.ts +2 -6
- package/src/__tests__/checker.test.ts +25 -3
- package/src/__tests__/clawhub.test.ts +54 -24
- package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
- package/src/__tests__/cli-memory.test.ts +74 -69
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/config-set-platform-guard.test.ts +302 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
- package/src/__tests__/contacts-tools.test.ts +31 -0
- package/src/__tests__/context-overflow-reducer.test.ts +86 -0
- package/src/__tests__/context-token-estimator.test.ts +175 -10
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
- package/src/__tests__/conversation-agent-loop.test.ts +9 -0
- package/src/__tests__/conversation-attachments.test.ts +2 -6
- package/src/__tests__/conversation-attention-store.test.ts +2 -6
- package/src/__tests__/conversation-clear-safety.test.ts +2 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
- package/src/__tests__/conversation-disk-view.test.ts +2 -6
- package/src/__tests__/conversation-error.test.ts +33 -2
- package/src/__tests__/conversation-fork-crud.test.ts +2 -6
- package/src/__tests__/conversation-history-web-search.test.ts +5 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
- package/src/__tests__/conversation-media-retry.test.ts +91 -0
- package/src/__tests__/conversation-starter-routes.test.ts +20 -11
- package/src/__tests__/conversation-store.test.ts +2 -6
- package/src/__tests__/conversation-usage.test.ts +2 -6
- package/src/__tests__/conversation-wipe.test.ts +11 -408
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
- package/src/__tests__/credential-security-e2e.test.ts +2 -0
- package/src/__tests__/followup-tools.test.ts +2 -6
- package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
- package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
- package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
- package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
- package/src/__tests__/guardian-action-store.test.ts +2 -6
- package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
- package/src/__tests__/guardian-dispatch.test.ts +2 -6
- package/src/__tests__/guardian-grant-minting.test.ts +2 -14
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
- package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
- package/src/__tests__/guardian-routing-state.test.ts +2 -6
- package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
- package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
- package/src/__tests__/injection-block.test.ts +154 -0
- package/src/__tests__/install-meta.test.ts +506 -0
- package/src/__tests__/install-skill-routing.test.ts +292 -0
- package/src/__tests__/invite-redemption-service.test.ts +2 -6
- package/src/__tests__/invite-routes-http.test.ts +2 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
- package/src/__tests__/list-messages-attachments.test.ts +2 -6
- package/src/__tests__/llm-context-route-provider.test.ts +2 -6
- package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
- package/src/__tests__/llm-usage-store.test.ts +2 -6
- package/src/__tests__/log-export-workspace.test.ts +2 -6
- package/src/__tests__/managed-store.test.ts +38 -11
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
- package/src/__tests__/memory-recall-log-store.test.ts +2 -6
- package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
- package/src/__tests__/non-member-access-request.test.ts +2 -6
- package/src/__tests__/notification-guardian-path.test.ts +2 -6
- package/src/__tests__/oauth-cli.test.ts +364 -2
- package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
- package/src/__tests__/outlook-attachments.test.ts +301 -0
- package/src/__tests__/outlook-automation-tools.test.ts +425 -0
- package/src/__tests__/outlook-categories.test.ts +212 -0
- package/src/__tests__/outlook-client-automation.test.ts +246 -0
- package/src/__tests__/outlook-compose-tools.test.ts +325 -0
- package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
- package/src/__tests__/outlook-email-watcher.test.ts +322 -0
- package/src/__tests__/outlook-follow-up.test.ts +196 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
- package/src/__tests__/outlook-trash.test.ts +77 -0
- package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
- package/src/__tests__/platform-callback-registration.test.ts +4 -4
- package/src/__tests__/playbook-execution.test.ts +76 -80
- package/src/__tests__/playbook-tools.test.ts +5 -7
- package/src/__tests__/provider-error-scenarios.test.ts +21 -0
- package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/require-fresh-approval.test.ts +64 -2
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
- package/src/__tests__/runtime-events-sse.test.ts +2 -6
- package/src/__tests__/schedule-store.test.ts +2 -6
- package/src/__tests__/schedule-tools.test.ts +2 -6
- package/src/__tests__/scheduler-recurrence.test.ts +1 -5
- package/src/__tests__/scoped-approval-grants.test.ts +2 -6
- package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
- package/src/__tests__/search-skills-unified.test.ts +421 -0
- package/src/__tests__/secret-onetime-send.test.ts +2 -0
- package/src/__tests__/send-endpoint-busy.test.ts +2 -6
- package/src/__tests__/sequence-store.test.ts +2 -6
- package/src/__tests__/server-history-render.test.ts +2 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
- package/src/__tests__/skill-feature-flags.test.ts +6 -6
- package/src/__tests__/skill-load-feature-flag.test.ts +11 -11
- package/src/__tests__/skill-memory.test.ts +140 -98
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/skills.test.ts +1 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -6
- package/src/__tests__/task-compiler.test.ts +2 -6
- package/src/__tests__/task-management-tools.test.ts +2 -6
- package/src/__tests__/task-memory-cleanup.test.ts +173 -229
- package/src/__tests__/task-runner.test.ts +2 -6
- package/src/__tests__/task-scheduler.test.ts +2 -6
- package/src/__tests__/test-preload.ts +3 -0
- package/src/__tests__/tool-approval-handler.test.ts +2 -6
- package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
- package/src/__tests__/trust-store.test.ts +1 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -6
- package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
- package/src/__tests__/trusted-contact-verification.test.ts +2 -6
- package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
- package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
- package/src/__tests__/usage-routes.test.ts +2 -6
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-invite-redemption.test.ts +2 -6
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
- package/src/__tests__/voice-session-bridge.test.ts +2 -6
- package/src/__tests__/volume-security-guard.test.ts +2 -0
- package/src/__tests__/workspace-lifecycle.test.ts +29 -1
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
- package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
- package/src/__tests__/workspace-policy.test.ts +1 -1
- package/src/agent/attachments.ts +7 -2
- package/src/agent/image-optimize.ts +165 -0
- package/src/agent/loop.ts +1 -15
- package/src/bundler/app-compiler.ts +179 -2
- package/src/bundler/package-resolver.ts +3 -5
- package/src/cli/__tests__/notifications.test.ts +1 -2
- package/src/cli/cli-memory.ts +67 -64
- package/src/cli/commands/avatar.ts +3 -3
- package/src/cli/commands/config.ts +26 -13
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/memory.ts +41 -55
- package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
- package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
- package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
- package/src/cli/commands/oauth/connect.ts +11 -6
- package/src/cli/commands/oauth/mode.ts +7 -0
- package/src/cli/commands/oauth/shared.ts +39 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
- package/src/cli/commands/platform/index.ts +16 -16
- package/src/cli/commands/skills.ts +88 -16
- package/src/cli/commands/trust.ts +2 -2
- package/src/cli/lib/daemon-credential-client.ts +2 -3
- package/src/config/bundled-skills/acp/TOOLS.json +1 -1
- package/src/config/bundled-skills/contacts/SKILL.md +0 -1
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
- package/src/config/bundled-skills/gmail/SKILL.md +2 -10
- package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
- package/src/config/bundled-skills/messaging/SKILL.md +10 -18
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
- package/src/config/bundled-skills/outlook/SKILL.md +189 -0
- package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
- package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
- package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
- package/src/config/bundled-skills/slack/SKILL.md +1 -7
- package/src/config/bundled-tool-registry.ts +56 -4
- package/src/config/env-registry.ts +15 -8
- package/src/config/feature-flag-registry.json +21 -124
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/timeouts.ts +1 -1
- package/src/config/skills.ts +18 -7
- package/src/context/token-estimator.ts +25 -18
- package/src/context/window-manager.ts +6 -2
- package/src/credential-execution/process-manager.ts +3 -1
- package/src/daemon/context-overflow-reducer.ts +46 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
- package/src/daemon/conversation-agent-loop.ts +96 -61
- package/src/daemon/conversation-error.ts +31 -8
- package/src/daemon/conversation-lifecycle.ts +33 -0
- package/src/daemon/conversation-media-retry.ts +85 -7
- package/src/daemon/conversation-notifiers.ts +4 -1
- package/src/daemon/conversation-runtime-assembly.ts +5 -0
- package/src/daemon/conversation.ts +41 -2
- package/src/daemon/daemon-control.ts +8 -2
- package/src/daemon/handlers/shared.ts +22 -12
- package/src/daemon/handlers/skills.ts +416 -202
- package/src/daemon/lifecycle.ts +40 -1
- package/src/daemon/main.ts +5 -1
- package/src/daemon/message-types/conversations.ts +4 -1
- package/src/daemon/message-types/messages.ts +3 -1
- package/src/daemon/message-types/skills.ts +97 -36
- package/src/daemon/providers-setup.ts +5 -0
- package/src/daemon/server.ts +11 -2
- package/src/daemon/tool-side-effects.ts +27 -5
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/hooks/cli.ts +2 -2
- package/src/hooks/runner.ts +15 -38
- package/src/inbound/platform-callback-registration.ts +14 -14
- package/src/memory/admin.ts +11 -45
- package/src/memory/conversation-bootstrap.ts +2 -0
- package/src/memory/conversation-crud.ts +242 -348
- package/src/memory/conversation-group-migration.ts +157 -0
- package/src/memory/conversation-queries.ts +4 -2
- package/src/memory/db-init.ts +30 -3
- package/src/memory/embed.ts +73 -0
- package/src/memory/embedding-backend.ts +8 -14
- package/src/memory/embedding-runtime-manager.ts +12 -114
- package/src/memory/fingerprint.ts +2 -2
- package/src/memory/graph/bootstrap.ts +512 -0
- package/src/memory/graph/capability-seed.ts +297 -0
- package/src/memory/graph/consolidation.ts +691 -0
- package/src/memory/graph/conversation-graph-memory.ts +630 -0
- package/src/memory/graph/decay.test.ts +208 -0
- package/src/memory/graph/decay.ts +195 -0
- package/src/memory/graph/extraction-job.ts +69 -0
- package/src/memory/graph/extraction.test.ts +936 -0
- package/src/memory/graph/extraction.ts +1254 -0
- package/src/memory/graph/graph-search.ts +266 -0
- package/src/memory/graph/image-ref-utils.ts +29 -0
- package/src/memory/graph/injection.test.ts +513 -0
- package/src/memory/graph/injection.ts +439 -0
- package/src/memory/graph/inspect.ts +534 -0
- package/src/memory/graph/narrative.ts +267 -0
- package/src/memory/graph/pattern-scan.ts +269 -0
- package/src/memory/graph/retriever.ts +1008 -0
- package/src/memory/graph/scoring.test.ts +548 -0
- package/src/memory/graph/scoring.ts +232 -0
- package/src/memory/graph/serendipity.ts +65 -0
- package/src/memory/graph/store.test.ts +1050 -0
- package/src/memory/graph/store.ts +699 -0
- package/src/memory/graph/tool-handlers.ts +426 -0
- package/src/memory/graph/tools.ts +141 -0
- package/src/memory/graph/triggers.test.ts +487 -0
- package/src/memory/graph/triggers.ts +223 -0
- package/src/memory/graph/types.ts +271 -0
- package/src/memory/group-crud.ts +191 -0
- package/src/memory/indexer.ts +37 -19
- package/src/memory/job-handlers/cleanup.ts +0 -53
- package/src/memory/job-handlers/conversation-starters.ts +91 -53
- package/src/memory/job-handlers/embedding.ts +5 -31
- package/src/memory/job-handlers/index-maintenance.ts +23 -11
- package/src/memory/job-handlers/summarization.ts +32 -17
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +50 -70
- package/src/memory/jobs-worker.ts +147 -112
- package/src/memory/message-content.ts +1 -0
- package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
- package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
- package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
- package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/qdrant-client.ts +44 -17
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/memory-graph.ts +139 -0
- package/src/memory/search/semantic.ts +47 -91
- package/src/memory/task-memory-cleanup.ts +28 -50
- package/src/messaging/providers/outlook/adapter.ts +8 -1
- package/src/messaging/providers/outlook/client.ts +299 -0
- package/src/messaging/providers/outlook/types.ts +118 -0
- package/src/notifications/adapters/macos.ts +1 -0
- package/src/notifications/copy-composer.ts +9 -0
- package/src/notifications/signal.ts +16 -0
- package/src/oauth/seed-providers.ts +2 -1
- package/src/permissions/checker.ts +24 -3
- package/src/permissions/defaults.ts +4 -4
- package/src/permissions/workspace-policy.ts +1 -1
- package/src/playbooks/playbook-compiler.ts +19 -18
- package/src/playbooks/types.ts +4 -3
- package/src/prompts/system-prompt.ts +3 -29
- package/src/providers/anthropic/client.ts +47 -19
- package/src/providers/gemini/client.ts +1 -1
- package/src/providers/openai/client.ts +1 -1
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +19 -3
- package/src/runtime/actor-trust-resolver.ts +5 -1
- package/src/runtime/auth/route-policy.ts +7 -0
- package/src/runtime/guardian-reply-router.ts +5 -1
- package/src/runtime/http-server.ts +23 -3
- package/src/runtime/middleware/auth.ts +20 -0
- package/src/runtime/routes/attachment-routes.test.ts +106 -0
- package/src/runtime/routes/attachment-routes.ts +106 -16
- package/src/runtime/routes/brain-graph-routes.ts +21 -22
- package/src/runtime/routes/btw-routes.ts +8 -0
- package/src/runtime/routes/conversation-management-routes.ts +2 -0
- package/src/runtime/routes/conversation-starter-routes.ts +2 -2
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/global-search-routes.ts +21 -19
- package/src/runtime/routes/group-routes.ts +207 -0
- package/src/runtime/routes/guardian-action-routes.ts +21 -10
- package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
- package/src/runtime/routes/inbound-message-handler.ts +19 -0
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
- package/src/runtime/routes/memory-item-routes.test.ts +2 -14
- package/src/runtime/routes/memory-item-routes.ts +341 -388
- package/src/runtime/routes/schedule-routes.ts +2 -0
- package/src/runtime/routes/skills-routes.ts +103 -37
- package/src/runtime/routes/work-items-routes.test.ts +2 -6
- package/src/schedule/scheduler.ts +8 -1
- package/src/security/oauth2.ts +1 -1
- package/src/security/secure-keys.ts +4 -8
- package/src/shared/provider-env-vars.ts +19 -0
- package/src/skills/catalog-cache.ts +5 -0
- package/src/skills/catalog-install.ts +15 -14
- package/src/skills/clawhub.ts +134 -154
- package/src/skills/install-meta.ts +208 -0
- package/src/skills/managed-store.ts +27 -16
- package/src/skills/skill-memory.ts +152 -77
- package/src/skills/skillssh-registry.ts +19 -17
- package/src/tasks/task-runner.ts +3 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
- package/src/tools/browser/runtime-check.ts +3 -1
- package/src/tools/memory/register.ts +63 -46
- package/src/tools/permission-checker.ts +7 -1
- package/src/tools/shared/filesystem/image-read.ts +22 -85
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/tool-manifest.ts +3 -3
- package/src/util/browser.ts +25 -10
- package/src/util/bun-runtime.ts +172 -0
- package/src/watcher/providers/outlook-calendar.ts +343 -0
- package/src/watcher/providers/outlook.ts +198 -0
- package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
- package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
- package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/context-memory-e2e.test.ts +0 -415
- package/src/__tests__/journal-context.test.ts +0 -268
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
- package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
- package/src/__tests__/memory-query-builder.test.ts +0 -59
- package/src/__tests__/memory-recall-quality.test.ts +0 -1046
- package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
- package/src/__tests__/memory-regressions.test.ts +0 -3696
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
- package/src/daemon/conversation-memory.ts +0 -207
- package/src/memory/conversation-starters-cadence.ts +0 -74
- package/src/memory/items-extractor.ts +0 -860
- package/src/memory/job-handlers/batch-extraction.ts +0 -753
- package/src/memory/job-handlers/extraction.ts +0 -40
- package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
- package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
- package/src/memory/journal-memory.ts +0 -224
- package/src/memory/query-builder.ts +0 -47
- package/src/memory/query-expansion.ts +0 -83
- package/src/memory/retriever.test.ts +0 -1592
- package/src/memory/retriever.ts +0 -1331
- package/src/memory/search/formatting.test.ts +0 -140
- package/src/memory/search/formatting.ts +0 -262
- package/src/memory/search/mmr.ts +0 -139
- package/src/memory/search/ranking.ts +0 -15
- package/src/memory/search/staleness.ts +0 -40
- package/src/memory/search/tier-classifier.ts +0 -18
- package/src/memory/search/types.ts +0 -121
- package/src/prompts/journal-context.ts +0 -154
- package/src/tools/memory/definitions.ts +0 -69
- package/src/tools/memory/handlers.test.ts +0 -562
- package/src/tools/memory/handlers.ts +0 -434
|
@@ -0,0 +1,691 @@
|
|
|
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 { parseEpochMs } from "./extraction.js";
|
|
23
|
+
import {
|
|
24
|
+
createTrigger,
|
|
25
|
+
deleteNode,
|
|
26
|
+
getEdgesForNode,
|
|
27
|
+
getTriggersForNode,
|
|
28
|
+
queryNodes,
|
|
29
|
+
updateNode,
|
|
30
|
+
} from "./store.js";
|
|
31
|
+
import type { MemoryNode } from "./types.js";
|
|
32
|
+
import { isCapabilityNode } from "./types.js";
|
|
33
|
+
|
|
34
|
+
const log = getLogger("graph-consolidation");
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Consolidation prompt
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
function buildConsolidationPrompt(
|
|
41
|
+
partitionName: string,
|
|
42
|
+
nodes: Array<{
|
|
43
|
+
id: string;
|
|
44
|
+
type: string;
|
|
45
|
+
content: string;
|
|
46
|
+
significance: number;
|
|
47
|
+
fidelity: string;
|
|
48
|
+
reinforcementCount: number;
|
|
49
|
+
created: number;
|
|
50
|
+
eventDate: number | null;
|
|
51
|
+
hasImage: boolean;
|
|
52
|
+
}>,
|
|
53
|
+
edges: Array<{ sourceId: string; targetId: string; relationship: string }>,
|
|
54
|
+
): string {
|
|
55
|
+
const nodeList = nodes
|
|
56
|
+
.map((n) => {
|
|
57
|
+
const age = Math.floor((Date.now() - n.created) / (1000 * 60 * 60 * 24));
|
|
58
|
+
const eventStr =
|
|
59
|
+
n.eventDate != null
|
|
60
|
+
? ` eventDate=${new Date(n.eventDate).toISOString().split("T")[0]}`
|
|
61
|
+
: "";
|
|
62
|
+
const imageStr = n.hasImage ? " [has_image]" : "";
|
|
63
|
+
return ` [${n.id}] type=${n.type} sig=${n.significance.toFixed(2)} fidelity=${n.fidelity} reinforced=${n.reinforcementCount}x age=${age}d${eventStr}${imageStr}\n ${n.content}`;
|
|
64
|
+
})
|
|
65
|
+
.join("\n\n");
|
|
66
|
+
|
|
67
|
+
const edgeList =
|
|
68
|
+
edges.length > 0
|
|
69
|
+
? edges
|
|
70
|
+
.map((e) => ` ${e.sourceId} --${e.relationship}--> ${e.targetId}`)
|
|
71
|
+
.join("\n")
|
|
72
|
+
: " (none)";
|
|
73
|
+
|
|
74
|
+
const today = new Date().toLocaleDateString("en-US", {
|
|
75
|
+
weekday: "long",
|
|
76
|
+
year: "numeric",
|
|
77
|
+
month: "long",
|
|
78
|
+
day: "numeric",
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
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.
|
|
82
|
+
|
|
83
|
+
Today is ${today}.
|
|
84
|
+
|
|
85
|
+
## Your Tasks
|
|
86
|
+
|
|
87
|
+
1. **Merge duplicates**: If two or more nodes describe the same event or fact, merge them into one by:
|
|
88
|
+
- Keeping the richer/more complete version (UPDATE it to incorporate details from duplicates)
|
|
89
|
+
- DELETE the duplicates
|
|
90
|
+
- Preserve the highest significance, reinforcement count, and stability from the merged nodes
|
|
91
|
+
- Create a "supersedes" edge from the surviving node to each deleted node (the store handles deletion, but log the relationship)
|
|
92
|
+
|
|
93
|
+
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.
|
|
94
|
+
|
|
95
|
+
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.
|
|
96
|
+
|
|
97
|
+
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.
|
|
98
|
+
|
|
99
|
+
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.
|
|
100
|
+
|
|
101
|
+
## Constraints
|
|
102
|
+
|
|
103
|
+
- Do NOT create new nodes — consolidation only merges, updates, and prunes
|
|
104
|
+
- Do NOT change a node's type
|
|
105
|
+
- Do NOT increase fidelity (memories only fade, never sharpen)
|
|
106
|
+
- Preserve first-person prose style in content rewrites
|
|
107
|
+
- When merging, keep the node with higher reinforcementCount as the survivor
|
|
108
|
+
|
|
109
|
+
## Current Nodes (${partitionName})
|
|
110
|
+
|
|
111
|
+
${nodeList}
|
|
112
|
+
|
|
113
|
+
## Current Edges
|
|
114
|
+
|
|
115
|
+
${edgeList}
|
|
116
|
+
|
|
117
|
+
Use the consolidate_diff tool to output your changes.`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const CONSOLIDATE_TOOL_SCHEMA = {
|
|
121
|
+
name: "consolidate_diff",
|
|
122
|
+
description: "Output consolidation changes to the memory graph",
|
|
123
|
+
input_schema: {
|
|
124
|
+
type: "object" as const,
|
|
125
|
+
properties: {
|
|
126
|
+
updates: {
|
|
127
|
+
type: "array" as const,
|
|
128
|
+
description:
|
|
129
|
+
"Nodes to update (content rewrites, narrative roles, fidelity downgrades)",
|
|
130
|
+
items: {
|
|
131
|
+
type: "object" as const,
|
|
132
|
+
properties: {
|
|
133
|
+
id: { type: "string" as const },
|
|
134
|
+
content: {
|
|
135
|
+
type: "string" as const,
|
|
136
|
+
description: "New content (if rewriting)",
|
|
137
|
+
},
|
|
138
|
+
fidelity: {
|
|
139
|
+
type: "string" as const,
|
|
140
|
+
enum: ["vivid", "clear", "faded", "gist", "gone"],
|
|
141
|
+
},
|
|
142
|
+
narrativeRole: { type: "string" as const },
|
|
143
|
+
partOfStory: { type: "string" as const },
|
|
144
|
+
event_date: {
|
|
145
|
+
type: ["number", "null"] as const,
|
|
146
|
+
description:
|
|
147
|
+
"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.",
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
required: ["id"] as const,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
delete_ids: {
|
|
154
|
+
type: "array" as const,
|
|
155
|
+
description: "Node IDs to delete (merged duplicates, gone memories)",
|
|
156
|
+
items: { type: "string" as const },
|
|
157
|
+
},
|
|
158
|
+
merge_edges: {
|
|
159
|
+
type: "array" as const,
|
|
160
|
+
description: "Supersedes edges for merged nodes (survivor → deleted)",
|
|
161
|
+
items: {
|
|
162
|
+
type: "object" as const,
|
|
163
|
+
properties: {
|
|
164
|
+
survivor_id: { type: "string" as const },
|
|
165
|
+
deleted_id: { type: "string" as const },
|
|
166
|
+
},
|
|
167
|
+
required: ["survivor_id", "deleted_id"] as const,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
required: ["updates", "delete_ids", "merge_edges"] as const,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Partition builders
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
function getRecentNodes(scopeId: string, days: number = 7): MemoryNode[] {
|
|
180
|
+
const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
|
|
181
|
+
return queryNodes({
|
|
182
|
+
scopeId,
|
|
183
|
+
fidelityNot: ["gone"],
|
|
184
|
+
createdAfter: cutoff,
|
|
185
|
+
limit: 10000,
|
|
186
|
+
}).filter((n) => !isCapabilityNode(n));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function getTopSignificanceNodes(
|
|
190
|
+
scopeId: string,
|
|
191
|
+
n: number = 50,
|
|
192
|
+
): MemoryNode[] {
|
|
193
|
+
return queryNodes({
|
|
194
|
+
scopeId,
|
|
195
|
+
fidelityNot: ["gone"],
|
|
196
|
+
minSignificance: 0.6,
|
|
197
|
+
}).filter((n) => !isCapabilityNode(n)).slice(0, n);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function getDecayedNodes(scopeId: string): MemoryNode[] {
|
|
201
|
+
const all = queryNodes({
|
|
202
|
+
scopeId,
|
|
203
|
+
limit: 10000,
|
|
204
|
+
});
|
|
205
|
+
return all.filter((n) => (n.fidelity === "faded" || n.fidelity === "gist") && !isCapabilityNode(n));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function getRandomSample(scopeId: string, n: number = 30): MemoryNode[] {
|
|
209
|
+
const all = queryNodes({
|
|
210
|
+
scopeId,
|
|
211
|
+
fidelityNot: ["gone"],
|
|
212
|
+
limit: 10000,
|
|
213
|
+
}).filter((n) => !isCapabilityNode(n));
|
|
214
|
+
// Fisher-Yates shuffle, take first n
|
|
215
|
+
for (let i = all.length - 1; i > 0; i--) {
|
|
216
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
217
|
+
[all[i], all[j]] = [all[j], all[i]];
|
|
218
|
+
}
|
|
219
|
+
return all.slice(0, n);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// Run consolidation on a partition
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
const CHUNK_SIZE = 25;
|
|
227
|
+
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
// Duplicate detection — fast LLM call on compact listings
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
const DUPE_DETECT_TOOL = {
|
|
233
|
+
name: "report_duplicate_groups",
|
|
234
|
+
description:
|
|
235
|
+
"Report groups of nodes that describe the same event, fact, or topic and should be merged",
|
|
236
|
+
input_schema: {
|
|
237
|
+
type: "object" as const,
|
|
238
|
+
properties: {
|
|
239
|
+
groups: {
|
|
240
|
+
type: "array" as const,
|
|
241
|
+
description:
|
|
242
|
+
"Each group is a list of node IDs that are duplicates of each other",
|
|
243
|
+
items: {
|
|
244
|
+
type: "array" as const,
|
|
245
|
+
items: { type: "string" as const },
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
required: ["groups"] as const,
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Fast LLM pass to identify duplicate groups from a compact node listing.
|
|
255
|
+
* Uses a latency-optimized model since it only needs to compare one-line
|
|
256
|
+
* summaries, not reason about full prose. Returns groups of MemoryNodes
|
|
257
|
+
* that should be consolidated together.
|
|
258
|
+
*/
|
|
259
|
+
async function identifyDuplicateGroups(
|
|
260
|
+
nodes: MemoryNode[],
|
|
261
|
+
_config: AssistantConfig,
|
|
262
|
+
): Promise<MemoryNode[][]> {
|
|
263
|
+
if (nodes.length < 2) return [];
|
|
264
|
+
|
|
265
|
+
const provider = await getConfiguredProvider();
|
|
266
|
+
if (!provider) return [];
|
|
267
|
+
|
|
268
|
+
// Compact listing: ID + first 100 chars of content
|
|
269
|
+
const listing = nodes
|
|
270
|
+
.map((n) => {
|
|
271
|
+
const preview =
|
|
272
|
+
n.content.length > 100 ? n.content.slice(0, 100) + "…" : n.content;
|
|
273
|
+
return `[${n.id}] ${preview}`;
|
|
274
|
+
})
|
|
275
|
+
.join("\n");
|
|
276
|
+
|
|
277
|
+
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.`;
|
|
278
|
+
|
|
279
|
+
const response = await provider.sendMessage(
|
|
280
|
+
[userMessage(listing)],
|
|
281
|
+
[DUPE_DETECT_TOOL],
|
|
282
|
+
systemPrompt,
|
|
283
|
+
{
|
|
284
|
+
config: {
|
|
285
|
+
modelIntent: "quality-optimized" as const,
|
|
286
|
+
tool_choice: { type: "tool" as const, name: "report_duplicate_groups" },
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const toolBlock = extractToolUse(response);
|
|
292
|
+
if (!toolBlock) return [];
|
|
293
|
+
|
|
294
|
+
const input = toolBlock.input as { groups?: string[][] };
|
|
295
|
+
if (!input.groups) return [];
|
|
296
|
+
|
|
297
|
+
const nodeMap = new Map(nodes.map((n) => [n.id, n]));
|
|
298
|
+
|
|
299
|
+
return (input.groups ?? [])
|
|
300
|
+
.map((ids) =>
|
|
301
|
+
ids.filter((id) => nodeMap.has(id)).map((id) => nodeMap.get(id)!),
|
|
302
|
+
)
|
|
303
|
+
.filter((group) => group.length >= 2);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ---------------------------------------------------------------------------
|
|
307
|
+
// Consolidation partition processing
|
|
308
|
+
// ---------------------------------------------------------------------------
|
|
309
|
+
|
|
310
|
+
interface ConsolidationPartitionResult {
|
|
311
|
+
nodesUpdated: number;
|
|
312
|
+
nodesDeleted: number;
|
|
313
|
+
mergeEdgesCreated: number;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function consolidatePartition(
|
|
317
|
+
partitionName: string,
|
|
318
|
+
nodes: MemoryNode[],
|
|
319
|
+
config: AssistantConfig,
|
|
320
|
+
): Promise<ConsolidationPartitionResult> {
|
|
321
|
+
const result: ConsolidationPartitionResult = {
|
|
322
|
+
nodesUpdated: 0,
|
|
323
|
+
nodesDeleted: 0,
|
|
324
|
+
mergeEdgesCreated: 0,
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
if (nodes.length === 0) return result;
|
|
328
|
+
|
|
329
|
+
// Step 1: Fast LLM call to identify duplicate groups from compact listing
|
|
330
|
+
const dupeGroups = await identifyDuplicateGroups(nodes, config);
|
|
331
|
+
// Collect all nodes that appear in a duplicate group
|
|
332
|
+
const inGroup = new Set(dupeGroups.flat().map((n) => n.id));
|
|
333
|
+
// Singletons: nodes not in any duplicate group
|
|
334
|
+
const singletons = nodes.filter((n) => !inGroup.has(n.id));
|
|
335
|
+
|
|
336
|
+
log.info(
|
|
337
|
+
{
|
|
338
|
+
partition: partitionName,
|
|
339
|
+
nodeCount: nodes.length,
|
|
340
|
+
dupeGroups: dupeGroups.length,
|
|
341
|
+
singletons: singletons.length,
|
|
342
|
+
},
|
|
343
|
+
"Identified duplicate groups for consolidation",
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// Step 2: Run full consolidation on each duplicate group
|
|
347
|
+
const deleted = new Set<string>();
|
|
348
|
+
for (let i = 0; i < dupeGroups.length; i++) {
|
|
349
|
+
const chunk = dupeGroups[i].filter((n) => !deleted.has(n.id));
|
|
350
|
+
if (chunk.length < 2) continue;
|
|
351
|
+
|
|
352
|
+
const chunkResult = await consolidateChunk(
|
|
353
|
+
`${partitionName} dupes (${i + 1}/${dupeGroups.length})`,
|
|
354
|
+
chunk,
|
|
355
|
+
config,
|
|
356
|
+
);
|
|
357
|
+
result.nodesUpdated += chunkResult.nodesUpdated;
|
|
358
|
+
result.nodesDeleted += chunkResult.nodesDeleted;
|
|
359
|
+
result.mergeEdgesCreated += chunkResult.mergeEdgesCreated;
|
|
360
|
+
for (const id of chunkResult.deletedIds) deleted.add(id);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Step 3: Run consolidation on singletons in chunks (for fidelity/narrative updates)
|
|
364
|
+
const remainingSingletons = singletons.filter((n) => !deleted.has(n.id));
|
|
365
|
+
if (remainingSingletons.length >= 2) {
|
|
366
|
+
for (let i = 0; i < remainingSingletons.length; i += CHUNK_SIZE) {
|
|
367
|
+
const chunk = remainingSingletons.slice(i, i + CHUNK_SIZE);
|
|
368
|
+
if (chunk.length < 2) continue;
|
|
369
|
+
|
|
370
|
+
const chunkResult = await consolidateChunk(
|
|
371
|
+
`${partitionName} singles (${Math.floor(i / CHUNK_SIZE) + 1})`,
|
|
372
|
+
chunk,
|
|
373
|
+
config,
|
|
374
|
+
);
|
|
375
|
+
result.nodesUpdated += chunkResult.nodesUpdated;
|
|
376
|
+
result.nodesDeleted += chunkResult.nodesDeleted;
|
|
377
|
+
result.mergeEdgesCreated += chunkResult.mergeEdgesCreated;
|
|
378
|
+
for (const id of chunkResult.deletedIds) deleted.add(id);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
interface ChunkResult extends ConsolidationPartitionResult {
|
|
386
|
+
deletedIds: string[];
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function consolidateChunk(
|
|
390
|
+
chunkName: string,
|
|
391
|
+
nodes: MemoryNode[],
|
|
392
|
+
_config: AssistantConfig,
|
|
393
|
+
): Promise<ChunkResult> {
|
|
394
|
+
const result: ChunkResult = {
|
|
395
|
+
nodesUpdated: 0,
|
|
396
|
+
nodesDeleted: 0,
|
|
397
|
+
mergeEdgesCreated: 0,
|
|
398
|
+
deletedIds: [],
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
if (nodes.length === 0) return result;
|
|
402
|
+
|
|
403
|
+
// Collect edges between partition nodes
|
|
404
|
+
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
405
|
+
const edges: Array<{
|
|
406
|
+
sourceId: string;
|
|
407
|
+
targetId: string;
|
|
408
|
+
relationship: string;
|
|
409
|
+
}> = [];
|
|
410
|
+
for (const node of nodes) {
|
|
411
|
+
for (const edge of getEdgesForNode(node.id)) {
|
|
412
|
+
if (nodeIds.has(edge.sourceNodeId) && nodeIds.has(edge.targetNodeId)) {
|
|
413
|
+
edges.push({
|
|
414
|
+
sourceId: edge.sourceNodeId,
|
|
415
|
+
targetId: edge.targetNodeId,
|
|
416
|
+
relationship: edge.relationship,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Deduplicate edges
|
|
423
|
+
const edgeKeys = new Set<string>();
|
|
424
|
+
const dedupedEdges = edges.filter((e) => {
|
|
425
|
+
const key = `${e.sourceId}-${e.relationship}-${e.targetId}`;
|
|
426
|
+
if (edgeKeys.has(key)) return false;
|
|
427
|
+
edgeKeys.add(key);
|
|
428
|
+
return true;
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const provider = await getConfiguredProvider();
|
|
432
|
+
if (!provider) {
|
|
433
|
+
throw new BackendUnavailableError("Provider unavailable for consolidation");
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const systemPrompt = buildConsolidationPrompt(
|
|
437
|
+
chunkName,
|
|
438
|
+
nodes.map((n) => ({
|
|
439
|
+
id: n.id,
|
|
440
|
+
type: n.type,
|
|
441
|
+
content: n.content,
|
|
442
|
+
significance: n.significance,
|
|
443
|
+
fidelity: n.fidelity,
|
|
444
|
+
reinforcementCount: n.reinforcementCount,
|
|
445
|
+
created: n.created,
|
|
446
|
+
eventDate: n.eventDate,
|
|
447
|
+
hasImage: n.imageRefs != null && n.imageRefs.length > 0,
|
|
448
|
+
})),
|
|
449
|
+
dedupedEdges,
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
const response = await provider.sendMessage(
|
|
453
|
+
[
|
|
454
|
+
userMessage(
|
|
455
|
+
"Consolidate this partition. Focus on merging duplicates and fading old memories.",
|
|
456
|
+
),
|
|
457
|
+
],
|
|
458
|
+
[CONSOLIDATE_TOOL_SCHEMA],
|
|
459
|
+
systemPrompt,
|
|
460
|
+
{
|
|
461
|
+
config: {
|
|
462
|
+
modelIntent: "quality-optimized" as const,
|
|
463
|
+
tool_choice: { type: "tool" as const, name: "consolidate_diff" },
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
const toolBlock = extractToolUse(response);
|
|
469
|
+
if (!toolBlock) {
|
|
470
|
+
log.warn("No tool_use block in consolidation response");
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const input = toolBlock.input as {
|
|
475
|
+
updates?: Array<{
|
|
476
|
+
id: string;
|
|
477
|
+
content?: string;
|
|
478
|
+
fidelity?: string;
|
|
479
|
+
narrativeRole?: string;
|
|
480
|
+
partOfStory?: string;
|
|
481
|
+
event_date?: number | null;
|
|
482
|
+
}>;
|
|
483
|
+
delete_ids?: string[];
|
|
484
|
+
merge_edges?: Array<{ survivor_id: string; deleted_id: string }>;
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// Build nodeMap once upfront; patch entries after each updateNode() so
|
|
488
|
+
// later iterations always read fresh in-memory state.
|
|
489
|
+
const nodeMap = new Map(nodes.map((n) => [n.id, n]));
|
|
490
|
+
|
|
491
|
+
// Apply updates
|
|
492
|
+
for (const update of input.updates ?? []) {
|
|
493
|
+
if (!nodeIds.has(update.id)) continue; // safety: only update nodes in this partition
|
|
494
|
+
|
|
495
|
+
const changes: Partial<MemoryNode> = {};
|
|
496
|
+
if (update.content) changes.content = update.content;
|
|
497
|
+
if (update.fidelity)
|
|
498
|
+
changes.fidelity = update.fidelity as MemoryNode["fidelity"];
|
|
499
|
+
if (update.narrativeRole !== undefined)
|
|
500
|
+
changes.narrativeRole = update.narrativeRole || null;
|
|
501
|
+
if (update.partOfStory !== undefined)
|
|
502
|
+
changes.partOfStory = update.partOfStory || null;
|
|
503
|
+
if (update.event_date !== undefined)
|
|
504
|
+
changes.eventDate = parseEpochMs(update.event_date);
|
|
505
|
+
changes.lastConsolidated = Date.now();
|
|
506
|
+
|
|
507
|
+
if (Object.keys(changes).length > 1) {
|
|
508
|
+
// more than just lastConsolidated
|
|
509
|
+
updateNode(update.id, changes);
|
|
510
|
+
result.nodesUpdated++;
|
|
511
|
+
const node = nodeMap.get(update.id);
|
|
512
|
+
if (node) Object.assign(node, changes);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Apply merge edges (before deletion so the edge can reference the node)
|
|
517
|
+
const { createEdge } = await import("./store.js");
|
|
518
|
+
for (const merge of input.merge_edges ?? []) {
|
|
519
|
+
if (!nodeIds.has(merge.survivor_id) || !nodeIds.has(merge.deleted_id))
|
|
520
|
+
continue;
|
|
521
|
+
try {
|
|
522
|
+
createEdge({
|
|
523
|
+
sourceNodeId: merge.survivor_id,
|
|
524
|
+
targetNodeId: merge.deleted_id,
|
|
525
|
+
relationship: "supersedes",
|
|
526
|
+
weight: 1.0,
|
|
527
|
+
created: Date.now(),
|
|
528
|
+
});
|
|
529
|
+
result.mergeEdgesCreated++;
|
|
530
|
+
|
|
531
|
+
// Preserve eventDate from deleted node if survivor doesn't have one
|
|
532
|
+
const survivor = nodeMap.get(merge.survivor_id);
|
|
533
|
+
const deleted = nodeMap.get(merge.deleted_id);
|
|
534
|
+
if (
|
|
535
|
+
survivor &&
|
|
536
|
+
deleted?.eventDate != null &&
|
|
537
|
+
survivor.eventDate == null
|
|
538
|
+
) {
|
|
539
|
+
updateNode(merge.survivor_id, { eventDate: deleted.eventDate });
|
|
540
|
+
survivor.eventDate = deleted.eventDate;
|
|
541
|
+
|
|
542
|
+
// The deleted node's triggers will be cascade-deleted when the node
|
|
543
|
+
// is removed. Ensure the survivor has an event trigger for the
|
|
544
|
+
// inherited eventDate (updateNode only syncs existing triggers).
|
|
545
|
+
const survivorTriggers = getTriggersForNode(merge.survivor_id);
|
|
546
|
+
if (!survivorTriggers.some((t) => t.type === "event")) {
|
|
547
|
+
createTrigger({
|
|
548
|
+
nodeId: merge.survivor_id,
|
|
549
|
+
type: "event",
|
|
550
|
+
schedule: null,
|
|
551
|
+
condition: null,
|
|
552
|
+
conditionEmbedding: null,
|
|
553
|
+
threshold: null,
|
|
554
|
+
eventDate: deleted.eventDate,
|
|
555
|
+
rampDays: 7,
|
|
556
|
+
followUpDays: 2,
|
|
557
|
+
recurring: false,
|
|
558
|
+
consumed: false,
|
|
559
|
+
cooldownMs: null,
|
|
560
|
+
lastFired: null,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Preserve imageRefs from deleted node if survivor doesn't have any
|
|
566
|
+
if (
|
|
567
|
+
survivor &&
|
|
568
|
+
deleted?.imageRefs != null &&
|
|
569
|
+
deleted.imageRefs.length > 0 &&
|
|
570
|
+
(survivor.imageRefs == null || survivor.imageRefs.length === 0)
|
|
571
|
+
) {
|
|
572
|
+
updateNode(merge.survivor_id, { imageRefs: deleted.imageRefs });
|
|
573
|
+
survivor.imageRefs = deleted.imageRefs;
|
|
574
|
+
}
|
|
575
|
+
} catch (err) {
|
|
576
|
+
log.warn({ err }, "Failed to create merge edge");
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Apply deletions
|
|
581
|
+
for (const id of input.delete_ids ?? []) {
|
|
582
|
+
if (!nodeIds.has(id)) continue; // safety
|
|
583
|
+
try {
|
|
584
|
+
log.info({ nodeId: id }, "Consolidation deleting node");
|
|
585
|
+
deleteNode(id);
|
|
586
|
+
result.nodesDeleted++;
|
|
587
|
+
result.deletedIds.push(id);
|
|
588
|
+
} catch (err) {
|
|
589
|
+
log.warn({ id, err }, "Failed to delete node during consolidation");
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return result;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// ---------------------------------------------------------------------------
|
|
597
|
+
// Full consolidation run
|
|
598
|
+
// ---------------------------------------------------------------------------
|
|
599
|
+
|
|
600
|
+
export interface ConsolidationResult {
|
|
601
|
+
partitions: Record<string, ConsolidationPartitionResult>;
|
|
602
|
+
totalUpdated: number;
|
|
603
|
+
totalDeleted: number;
|
|
604
|
+
totalMergeEdges: number;
|
|
605
|
+
latencyMs: number;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
export async function runConsolidation(
|
|
609
|
+
scopeId: string = "default",
|
|
610
|
+
config: AssistantConfig,
|
|
611
|
+
): Promise<ConsolidationResult> {
|
|
612
|
+
const start = Date.now();
|
|
613
|
+
|
|
614
|
+
const result: ConsolidationResult = {
|
|
615
|
+
partitions: {},
|
|
616
|
+
totalUpdated: 0,
|
|
617
|
+
totalDeleted: 0,
|
|
618
|
+
totalMergeEdges: 0,
|
|
619
|
+
latencyMs: 0,
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
// Define partitions
|
|
623
|
+
const partitions: Array<{ name: string; nodes: MemoryNode[] }> = [
|
|
624
|
+
{ name: "recent", nodes: getRecentNodes(scopeId) },
|
|
625
|
+
{ name: "significant", nodes: getTopSignificanceNodes(scopeId) },
|
|
626
|
+
{ name: "decayed", nodes: getDecayedNodes(scopeId) },
|
|
627
|
+
{ name: "random", nodes: getRandomSample(scopeId) },
|
|
628
|
+
];
|
|
629
|
+
|
|
630
|
+
for (const partition of partitions) {
|
|
631
|
+
if (partition.nodes.length === 0) {
|
|
632
|
+
log.info({ partition: partition.name }, "Empty partition, skipping");
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
log.info(
|
|
637
|
+
{ partition: partition.name, nodeCount: partition.nodes.length },
|
|
638
|
+
"Consolidating partition",
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
try {
|
|
642
|
+
const partitionResult = await consolidatePartition(
|
|
643
|
+
partition.name,
|
|
644
|
+
partition.nodes,
|
|
645
|
+
config,
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
result.partitions[partition.name] = partitionResult;
|
|
649
|
+
result.totalUpdated += partitionResult.nodesUpdated;
|
|
650
|
+
result.totalDeleted += partitionResult.nodesDeleted;
|
|
651
|
+
result.totalMergeEdges += partitionResult.mergeEdgesCreated;
|
|
652
|
+
|
|
653
|
+
log.info(
|
|
654
|
+
{
|
|
655
|
+
partition: partition.name,
|
|
656
|
+
updated: partitionResult.nodesUpdated,
|
|
657
|
+
deleted: partitionResult.nodesDeleted,
|
|
658
|
+
mergeEdges: partitionResult.mergeEdgesCreated,
|
|
659
|
+
},
|
|
660
|
+
"Partition consolidation complete",
|
|
661
|
+
);
|
|
662
|
+
} catch (err) {
|
|
663
|
+
log.warn(
|
|
664
|
+
{
|
|
665
|
+
partition: partition.name,
|
|
666
|
+
err: err instanceof Error ? err.message : String(err),
|
|
667
|
+
},
|
|
668
|
+
"Partition consolidation failed",
|
|
669
|
+
);
|
|
670
|
+
result.partitions[partition.name] = {
|
|
671
|
+
nodesUpdated: 0,
|
|
672
|
+
nodesDeleted: 0,
|
|
673
|
+
mergeEdgesCreated: 0,
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
result.latencyMs = Date.now() - start;
|
|
679
|
+
|
|
680
|
+
log.info(
|
|
681
|
+
{
|
|
682
|
+
totalUpdated: result.totalUpdated,
|
|
683
|
+
totalDeleted: result.totalDeleted,
|
|
684
|
+
totalMergeEdges: result.totalMergeEdges,
|
|
685
|
+
latencyMs: result.latencyMs,
|
|
686
|
+
},
|
|
687
|
+
"Full consolidation complete",
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
return result;
|
|
691
|
+
}
|