@vellumai/assistant 0.5.15 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +3 -3
- package/Dockerfile +0 -3
- package/docs/architecture/integrations.md +15 -14
- package/knip.json +4 -1
- package/openapi.yaml +670 -122
- 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 +5 -377
- 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__/ces-rpc-credential-backend.test.ts +4 -1
- 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 +84 -3
- package/src/__tests__/clawhub.test.ts +54 -24
- package/src/__tests__/cli-command-risk-guard.test.ts +108 -6
- package/src/__tests__/cli-memory.test.ts +377 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +12 -2
- package/src/__tests__/config-schema.test.ts +1 -3
- package/src/__tests__/config-set-platform-guard.test.ts +302 -0
- package/src/__tests__/config-watcher-feature-flags.test.ts +211 -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-runtime-assembly.test.ts +7 -4
- package/src/__tests__/conversation-slash-commands.test.ts +2 -6
- 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 +3 -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 +6 -1
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +7 -73
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -7
- 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 +343 -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__/heartbeat-service.test.ts +1 -3
- 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__/intent-routing.test.ts +6 -18
- 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 +4 -34
- package/src/__tests__/managed-skill-lifecycle.test.ts +7 -37
- package/src/__tests__/managed-store.test.ts +40 -21
- 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__/messaging-send-tool.test.ts +6 -6
- package/src/__tests__/migration-cross-version-compatibility.test.ts +1 -29
- package/src/__tests__/migration-export-http.test.ts +3 -34
- package/src/__tests__/migration-import-commit-http.test.ts +1 -29
- package/src/__tests__/migration-import-preflight-http.test.ts +3 -34
- package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +2 -1
- 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-apps-routes.test.ts +120 -10
- package/src/__tests__/oauth-cli.test.ts +364 -2
- package/src/__tests__/oauth-connect-orchestrator.test.ts +709 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +2 -1
- package/src/__tests__/oauth-provider-visibility.test.ts +149 -0
- package/src/__tests__/oauth-providers-routes.test.ts +5 -2
- package/src/__tests__/oauth-store.test.ts +0 -5
- 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 +1071 -0
- package/src/__tests__/outlook-trash.test.ts +77 -0
- package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
- package/src/__tests__/path-policy.test.ts +2 -17
- package/src/__tests__/permission-types.test.ts +0 -1
- package/src/__tests__/platform-callback-registration.test.ts +7 -11
- package/src/__tests__/playbook-execution.test.ts +76 -80
- package/src/__tests__/playbook-tools.test.ts +5 -7
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
- package/src/__tests__/provider-error-scenarios.test.ts +21 -2
- package/src/__tests__/qdrant-manager.test.ts +68 -21
- 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 -3
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
- package/src/__tests__/runtime-events-sse.test.ts +2 -6
- package/src/__tests__/sandbox-diagnostics.test.ts +20 -29
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +2 -10
- 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-allowlist.test.ts +20 -35
- 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__/shell-credential-ref.test.ts +0 -5
- 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 +13 -54
- package/src/__tests__/skill-load-inline-command.test.ts +3 -65
- package/src/__tests__/skill-load-inline-includes.test.ts +3 -65
- package/src/__tests__/skill-load-tool.test.ts +3 -67
- package/src/__tests__/skill-memory.test.ts +480 -195
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/skills.test.ts +23 -50
- package/src/__tests__/slack-channel-config.test.ts +2 -21
- package/src/__tests__/slack-inbound-verification.test.ts +2 -6
- package/src/__tests__/starter-bundle.test.ts +2 -8
- package/src/__tests__/stt-hints.test.ts +7 -2
- package/src/__tests__/system-prompt.test.ts +25 -45
- package/src/__tests__/task-compiler.test.ts +2 -27
- package/src/__tests__/task-management-tools.test.ts +2 -27
- package/src/__tests__/task-memory-cleanup.test.ts +173 -250
- package/src/__tests__/task-runner.test.ts +2 -27
- package/src/__tests__/task-scheduler.test.ts +2 -27
- package/src/__tests__/terminal-tools.test.ts +1 -17
- package/src/__tests__/test-preload.ts +3 -0
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +0 -79
- package/src/__tests__/tool-approval-handler.test.ts +4 -27
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +2 -11
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -25
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +4 -27
- package/src/__tests__/tool-preview-lifecycle.test.ts +0 -20
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
- package/src/__tests__/trust-store.test.ts +10 -42
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -30
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +3 -27
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -28
- package/src/__tests__/trusted-contact-multichannel.test.ts +2 -28
- package/src/__tests__/trusted-contact-verification.test.ts +2 -28
- package/src/__tests__/turn-boundary-resolution.test.ts +2 -34
- package/src/__tests__/twilio-provider.test.ts +0 -16
- package/src/__tests__/twilio-routes-twiml.test.ts +7 -12
- package/src/__tests__/twilio-routes.test.ts +0 -24
- package/src/__tests__/update-bulletin.test.ts +17 -89
- package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -26
- package/src/__tests__/usage-routes.test.ts +2 -27
- package/src/__tests__/user-reference.test.ts +1 -5
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +4 -34
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +2 -53
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-invite-redemption.test.ts +2 -27
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -30
- package/src/__tests__/voice-session-bridge.test.ts +2 -27
- 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 +4 -29
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +2 -2
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +4 -29
- package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +0 -6
- package/src/__tests__/workspace-policy.test.ts +1 -1
- package/src/acp/client-handler.ts +1 -2
- 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 -24
- package/src/cli/cli-memory.ts +179 -0
- 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__/providers-update.test.ts +1 -1
- package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
- package/src/cli/commands/oauth/connect.ts +26 -6
- package/src/cli/commands/oauth/mode.ts +7 -0
- package/src/cli/commands/oauth/providers.ts +49 -42
- package/src/cli/commands/oauth/shared.ts +39 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +3 -49
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +3 -49
- package/src/cli/commands/platform/__tests__/status.test.ts +5 -55
- 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/computer-use/TOOLS.json +7 -7
- 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 +26 -19
- 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/settings/TOOLS.json +3 -3
- 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 +29 -116
- package/src/config/loader.ts +4 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.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 +32 -9
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/process-manager.ts +3 -1
- package/src/daemon/config-watcher.ts +51 -0
- 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 +99 -63
- 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-process.ts +1 -0
- package/src/daemon/conversation-runtime-assembly.ts +5 -0
- package/src/daemon/conversation-usage.ts +1 -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 +423 -201
- package/src/daemon/lifecycle.ts +52 -4
- package/src/daemon/main.ts +5 -1
- package/src/daemon/message-types/conversations.ts +5 -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 +7 -0
- package/src/daemon/server.ts +35 -22
- package/src/daemon/tool-side-effects.ts +27 -5
- package/src/events/domain-events.ts +1 -2
- 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 +39 -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.test.ts +3 -27
- 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/llm-usage-store.ts +35 -2
- package/src/memory/message-content.ts +1 -0
- package/src/memory/migrations/201-oauth-providers-feature-flag.ts +11 -0
- package/src/memory/migrations/202-drop-callback-transport-column.ts +13 -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 +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/qdrant-client.ts +44 -17
- package/src/memory/qdrant-manager.ts +26 -5
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/memory-graph.ts +139 -0
- package/src/memory/schema/oauth.ts +1 -1
- package/src/memory/search/semantic.ts +47 -91
- package/src/memory/slack-thread-store.ts +17 -0
- package/src/memory/task-memory-cleanup.ts +28 -50
- package/src/messaging/providers/outlook/adapter.ts +200 -0
- package/src/messaging/providers/outlook/client.ts +610 -0
- package/src/messaging/providers/outlook/types.ts +201 -0
- package/src/notifications/adapters/macos.ts +1 -0
- package/src/notifications/adapters/slack.ts +1 -1
- package/src/notifications/copy-composer.ts +9 -0
- package/src/notifications/signal.ts +16 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +1 -1
- package/src/oauth/connect-orchestrator.ts +10 -3
- package/src/oauth/oauth-store.ts +10 -11
- package/src/oauth/provider-serializer.ts +3 -0
- package/src/oauth/provider-visibility.ts +16 -0
- package/src/oauth/seed-providers.ts +50 -17
- package/src/permissions/checker.ts +62 -9
- package/src/permissions/defaults.ts +4 -4
- package/src/permissions/types.ts +2 -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 +6 -93
- package/src/prompts/templates/UPDATES.md +6 -0
- 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/__tests__/credential-service.test.ts +1 -27
- package/src/runtime/auth/__tests__/token-service.test.ts +1 -25
- package/src/runtime/auth/route-policy.ts +7 -4
- package/src/runtime/guardian-reply-router.ts +10 -2
- 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-query-routes.ts +2 -58
- 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/background-dispatch.ts +43 -2
- 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 -31
- package/src/runtime/routes/memory-item-routes.ts +385 -341
- package/src/runtime/routes/oauth-apps.ts +18 -1
- package/src/runtime/routes/oauth-providers.ts +13 -1
- package/src/runtime/routes/schedule-routes.ts +2 -0
- package/src/runtime/routes/settings-routes.ts +1 -0
- package/src/runtime/routes/skills-routes.ts +103 -37
- package/src/runtime/routes/usage-routes.ts +19 -2
- package/src/runtime/routes/work-items-routes.test.ts +2 -27
- package/src/runtime/routes/workspace-routes.test.ts +3 -27
- package/src/schedule/scheduler.ts +8 -1
- package/src/security/oauth2.ts +1 -1
- package/src/security/secret-allowlist.ts +4 -4
- 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 +210 -96
- 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 -19
- package/src/tools/shared/filesystem/image-read.ts +22 -85
- package/src/tools/skills/skill-script-runner.ts +1 -1
- 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/util/device-id.ts +3 -65
- package/src/watcher/providers/outlook-calendar.ts +343 -0
- package/src/watcher/providers/outlook.ts +198 -0
- package/src/workspace/git-service.ts +27 -6
- 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 -741
- package/src/memory/job-handlers/extraction.ts +0 -40
- package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -383
- 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 -1590
- package/src/memory/retriever.ts +0 -1323
- package/src/memory/search/formatting.test.ts +0 -140
- package/src/memory/search/formatting.ts +0 -262
- package/src/memory/search/mmr.ts +0 -136
- 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 -156
- package/src/tools/memory/definitions.ts +0 -69
- package/src/tools/memory/handlers.test.ts +0 -590
- package/src/tools/memory/handlers.ts +0 -434
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Route handlers for memory item CRUD endpoints.
|
|
3
3
|
*
|
|
4
|
+
* Queries memory_graph_nodes and maps results to the client's
|
|
5
|
+
* MemoryItemPayload shape for backwards compatibility.
|
|
6
|
+
*
|
|
4
7
|
* GET /v1/memory-items — list memory items (with filtering, search, sort, pagination)
|
|
5
8
|
* GET /v1/memory-items/:id — get a single memory item
|
|
6
9
|
* POST /v1/memory-items — create a new memory item
|
|
@@ -8,8 +11,17 @@
|
|
|
8
11
|
* DELETE /v1/memory-items/:id — delete a memory item and its embeddings
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
|
-
import {
|
|
12
|
-
|
|
14
|
+
import {
|
|
15
|
+
and,
|
|
16
|
+
asc,
|
|
17
|
+
count,
|
|
18
|
+
desc,
|
|
19
|
+
eq,
|
|
20
|
+
inArray,
|
|
21
|
+
like,
|
|
22
|
+
ne,
|
|
23
|
+
notInArray,
|
|
24
|
+
} from "drizzle-orm";
|
|
13
25
|
import { z } from "zod";
|
|
14
26
|
|
|
15
27
|
import { getConfig } from "../../config/loader.js";
|
|
@@ -19,15 +31,23 @@ import {
|
|
|
19
31
|
generateSparseEmbedding,
|
|
20
32
|
getMemoryBackendStatus,
|
|
21
33
|
} from "../../memory/embedding-backend.js";
|
|
22
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
createNode,
|
|
36
|
+
deleteNode,
|
|
37
|
+
getNode,
|
|
38
|
+
updateNode,
|
|
39
|
+
} from "../../memory/graph/store.js";
|
|
40
|
+
import type {
|
|
41
|
+
Fidelity,
|
|
42
|
+
ImageRef,
|
|
43
|
+
MemoryNode,
|
|
44
|
+
MemoryType,
|
|
45
|
+
NewNode,
|
|
46
|
+
} from "../../memory/graph/types.js";
|
|
23
47
|
import { enqueueMemoryJob } from "../../memory/jobs-store.js";
|
|
24
48
|
import { withQdrantBreaker } from "../../memory/qdrant-circuit-breaker.js";
|
|
25
49
|
import { getQdrantClient } from "../../memory/qdrant-client.js";
|
|
26
|
-
import {
|
|
27
|
-
conversations,
|
|
28
|
-
memoryEmbeddings,
|
|
29
|
-
memoryItems,
|
|
30
|
-
} from "../../memory/schema.js";
|
|
50
|
+
import { conversations, memoryGraphNodes } from "../../memory/schema.js";
|
|
31
51
|
import { getLogger } from "../../util/logger.js";
|
|
32
52
|
import { httpError } from "../http-errors.js";
|
|
33
53
|
import type { RouteContext, RouteDefinition } from "../http-router.js";
|
|
@@ -38,23 +58,20 @@ const log = getLogger("memory-item-routes");
|
|
|
38
58
|
// Constants
|
|
39
59
|
// ---------------------------------------------------------------------------
|
|
40
60
|
|
|
41
|
-
const
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
type MemoryItemKind = (typeof VALID_KINDS)[number];
|
|
61
|
+
const VALID_TYPES: MemoryType[] = [
|
|
62
|
+
"episodic",
|
|
63
|
+
"semantic",
|
|
64
|
+
"procedural",
|
|
65
|
+
"emotional",
|
|
66
|
+
"prospective",
|
|
67
|
+
"behavioral",
|
|
68
|
+
"narrative",
|
|
69
|
+
"shared",
|
|
70
|
+
];
|
|
53
71
|
|
|
54
72
|
const VALID_SORT_FIELDS = [
|
|
55
73
|
"lastSeenAt",
|
|
56
74
|
"importance",
|
|
57
|
-
"accessCount",
|
|
58
75
|
"kind",
|
|
59
76
|
"firstSeenAt",
|
|
60
77
|
] as const;
|
|
@@ -62,31 +79,82 @@ const VALID_SORT_FIELDS = [
|
|
|
62
79
|
type SortField = (typeof VALID_SORT_FIELDS)[number];
|
|
63
80
|
|
|
64
81
|
const SORT_COLUMN_MAP = {
|
|
65
|
-
lastSeenAt:
|
|
66
|
-
importance:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
firstSeenAt: memoryItems.firstSeenAt,
|
|
82
|
+
lastSeenAt: memoryGraphNodes.lastAccessed,
|
|
83
|
+
importance: memoryGraphNodes.significance,
|
|
84
|
+
kind: memoryGraphNodes.type,
|
|
85
|
+
firstSeenAt: memoryGraphNodes.created,
|
|
70
86
|
} as const;
|
|
71
87
|
|
|
72
88
|
// ---------------------------------------------------------------------------
|
|
73
89
|
// Helpers
|
|
74
90
|
// ---------------------------------------------------------------------------
|
|
75
91
|
|
|
76
|
-
function
|
|
77
|
-
return (
|
|
92
|
+
function isValidType(value: string): value is MemoryType {
|
|
93
|
+
return (VALID_TYPES as string[]).includes(value);
|
|
78
94
|
}
|
|
79
95
|
|
|
80
96
|
function isValidSortField(value: string): value is SortField {
|
|
81
97
|
return (VALID_SORT_FIELDS as readonly string[]).includes(value);
|
|
82
98
|
}
|
|
83
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Split graph node content into subject (first line) and statement (rest).
|
|
102
|
+
* Playbooks store JSON in statement; other nodes use plain prose.
|
|
103
|
+
*/
|
|
104
|
+
function splitContent(content: string): { subject: string; statement: string } {
|
|
105
|
+
const newlineIdx = content.indexOf("\n");
|
|
106
|
+
if (newlineIdx === -1) {
|
|
107
|
+
return { subject: content, statement: content };
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
subject: content.slice(0, newlineIdx).trim(),
|
|
111
|
+
statement: content.slice(newlineIdx + 1).trim(),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Map a graph node to the client's MemoryItemPayload shape.
|
|
117
|
+
*/
|
|
118
|
+
function nodeToPayload(
|
|
119
|
+
node: MemoryNode,
|
|
120
|
+
scopeLabel: string | null = null,
|
|
121
|
+
): Record<string, unknown> {
|
|
122
|
+
const { subject, statement } = splitContent(node.content);
|
|
123
|
+
return {
|
|
124
|
+
id: node.id,
|
|
125
|
+
kind: node.type,
|
|
126
|
+
subject,
|
|
127
|
+
statement,
|
|
128
|
+
status: node.fidelity === "gone" ? "superseded" : "active",
|
|
129
|
+
confidence: node.confidence,
|
|
130
|
+
importance: node.significance,
|
|
131
|
+
eventDate: node.eventDate,
|
|
132
|
+
firstSeenAt: node.created,
|
|
133
|
+
lastSeenAt: node.lastAccessed,
|
|
134
|
+
|
|
135
|
+
// Graph-specific fields
|
|
136
|
+
fidelity: node.fidelity,
|
|
137
|
+
sourceType: node.sourceType,
|
|
138
|
+
narrativeRole: node.narrativeRole,
|
|
139
|
+
partOfStory: node.partOfStory,
|
|
140
|
+
reinforcementCount: node.reinforcementCount,
|
|
141
|
+
stability: node.stability,
|
|
142
|
+
emotionalCharge: node.emotionalCharge,
|
|
143
|
+
|
|
144
|
+
scopeId: node.scopeId,
|
|
145
|
+
scopeLabel,
|
|
146
|
+
|
|
147
|
+
// Legacy fields — not applicable to graph nodes
|
|
148
|
+
accessCount: null,
|
|
149
|
+
verificationState: null,
|
|
150
|
+
lastUsedAt: null,
|
|
151
|
+
supersedes: null,
|
|
152
|
+
supersededBy: null,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
84
156
|
/**
|
|
85
157
|
* Resolve a `scopeLabel` for a memory item based on its `scopeId`.
|
|
86
|
-
*
|
|
87
|
-
* - `"default"` → `null`
|
|
88
|
-
* - `"private:<conversationId>"` → `"Private · <title>"` when the conversation
|
|
89
|
-
* has a title, or `"Private"` when it doesn't (or the conversation was deleted).
|
|
90
158
|
*/
|
|
91
159
|
function resolveScopeLabel(
|
|
92
160
|
scopeId: string,
|
|
@@ -103,7 +171,6 @@ function resolveScopeLabel(
|
|
|
103
171
|
|
|
104
172
|
/**
|
|
105
173
|
* Batch-fetch conversation titles for a set of private-scoped memory items.
|
|
106
|
-
* Returns a Map from conversation ID → title (or null).
|
|
107
174
|
*/
|
|
108
175
|
function buildConversationTitleMap(
|
|
109
176
|
db: ReturnType<typeof getDb>,
|
|
@@ -129,20 +196,25 @@ function buildConversationTitleMap(
|
|
|
129
196
|
return map;
|
|
130
197
|
}
|
|
131
198
|
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// Semantic search constants
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
const SEMANTIC_SEARCH_FETCH_CEILING = 10_000;
|
|
204
|
+
|
|
132
205
|
// ---------------------------------------------------------------------------
|
|
133
206
|
// Semantic search helper
|
|
134
207
|
// ---------------------------------------------------------------------------
|
|
135
208
|
|
|
136
209
|
/**
|
|
137
|
-
*
|
|
138
|
-
* Returns ordered
|
|
210
|
+
* Hybrid semantic search for graph nodes via Qdrant.
|
|
211
|
+
* Returns ordered node IDs + total count on success, or `null` when
|
|
139
212
|
* the embedding backend / Qdrant is unavailable (caller falls back to SQL).
|
|
140
213
|
*/
|
|
141
|
-
async function
|
|
214
|
+
async function searchNodesSemantic(
|
|
142
215
|
query: string,
|
|
143
216
|
fetchLimit: number,
|
|
144
217
|
kindFilter: string | null,
|
|
145
|
-
statusFilter: string,
|
|
146
218
|
): Promise<{ ids: string[]; total: number } | null> {
|
|
147
219
|
try {
|
|
148
220
|
const config = getConfig();
|
|
@@ -156,13 +228,10 @@ async function searchItemsSemantic(
|
|
|
156
228
|
const sparse = generateSparseEmbedding(query);
|
|
157
229
|
const sparseVector = { indices: sparse.indices, values: sparse.values };
|
|
158
230
|
|
|
159
|
-
//
|
|
231
|
+
// Filter to graph_node target_type, exclude gone nodes
|
|
160
232
|
const mustConditions: Array<Record<string, unknown>> = [
|
|
161
|
-
{ key: "target_type", match: { value: "
|
|
233
|
+
{ key: "target_type", match: { value: "graph_node" } },
|
|
162
234
|
];
|
|
163
|
-
if (statusFilter && statusFilter !== "all") {
|
|
164
|
-
mustConditions.push({ key: "status", match: { value: statusFilter } });
|
|
165
|
-
}
|
|
166
235
|
if (kindFilter) {
|
|
167
236
|
mustConditions.push({ key: "kind", match: { value: kindFilter } });
|
|
168
237
|
}
|
|
@@ -184,11 +253,6 @@ async function searchItemsSemantic(
|
|
|
184
253
|
);
|
|
185
254
|
|
|
186
255
|
const ids = results.map((r) => r.payload.target_id);
|
|
187
|
-
|
|
188
|
-
// Use the vector search result count as the pagination total.
|
|
189
|
-
// A DB-wide COUNT would include items with no embedding yet (lagging) and
|
|
190
|
-
// items irrelevant to the search query, inflating the total and causing
|
|
191
|
-
// clients to paginate into empty pages.
|
|
192
256
|
return { ids, total: ids.length };
|
|
193
257
|
} catch (err) {
|
|
194
258
|
log.warn({ err }, "Semantic memory search failed, falling back to SQL");
|
|
@@ -209,10 +273,10 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
|
|
|
209
273
|
const limitParam = Number(url.searchParams.get("limit") ?? 100);
|
|
210
274
|
const offsetParam = Number(url.searchParams.get("offset") ?? 0);
|
|
211
275
|
|
|
212
|
-
if (kindParam && !
|
|
276
|
+
if (kindParam && !isValidType(kindParam)) {
|
|
213
277
|
return httpError(
|
|
214
278
|
"BAD_REQUEST",
|
|
215
|
-
`Invalid kind "${kindParam}". Must be one of: ${
|
|
279
|
+
`Invalid kind "${kindParam}". Must be one of: ${VALID_TYPES.join(", ")}`,
|
|
216
280
|
400,
|
|
217
281
|
);
|
|
218
282
|
}
|
|
@@ -235,41 +299,84 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
|
|
|
235
299
|
|
|
236
300
|
const db = getDb();
|
|
237
301
|
|
|
302
|
+
// Build fidelity filter based on status param
|
|
303
|
+
const fidelityFilter =
|
|
304
|
+
statusParam === "all"
|
|
305
|
+
? undefined
|
|
306
|
+
: statusParam === "inactive"
|
|
307
|
+
? eq(memoryGraphNodes.fidelity, "gone")
|
|
308
|
+
: notInArray(memoryGraphNodes.fidelity, ["gone"]);
|
|
309
|
+
|
|
238
310
|
// ── Semantic search path ────────────────────────────────────────────
|
|
239
|
-
// When a search query is present, try Qdrant hybrid search first.
|
|
240
|
-
// Falls back to SQL LIKE when embeddings / Qdrant are unavailable.
|
|
241
311
|
if (searchParam) {
|
|
242
|
-
const semanticResult = await
|
|
312
|
+
const semanticResult = await searchNodesSemantic(
|
|
243
313
|
searchParam,
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
statusParam,
|
|
314
|
+
SEMANTIC_SEARCH_FETCH_CEILING,
|
|
315
|
+
null,
|
|
247
316
|
);
|
|
248
317
|
|
|
249
318
|
if (semanticResult && semanticResult.ids.length > 0) {
|
|
250
|
-
//
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
319
|
+
// Compute kindCounts from all semantic matches
|
|
320
|
+
const kindCountConditions = [
|
|
321
|
+
inArray(memoryGraphNodes.id, semanticResult.ids),
|
|
322
|
+
];
|
|
323
|
+
if (fidelityFilter) kindCountConditions.push(fidelityFilter);
|
|
324
|
+
|
|
325
|
+
const kindCountRows = db
|
|
326
|
+
.select({ kind: memoryGraphNodes.type, count: count() })
|
|
327
|
+
.from(memoryGraphNodes)
|
|
328
|
+
.where(and(...kindCountConditions))
|
|
329
|
+
.groupBy(memoryGraphNodes.type)
|
|
330
|
+
.all();
|
|
331
|
+
const semanticKindCounts: Record<string, number> = {};
|
|
332
|
+
for (const row of kindCountRows) {
|
|
333
|
+
semanticKindCounts[row.kind] = row.count;
|
|
258
334
|
}
|
|
259
335
|
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
336
|
+
// Apply kind + fidelity filter while preserving semantic relevance ordering
|
|
337
|
+
let filteredIds = semanticResult.ids;
|
|
338
|
+
{
|
|
339
|
+
const filterConditions = [
|
|
340
|
+
inArray(memoryGraphNodes.id, semanticResult.ids),
|
|
341
|
+
];
|
|
342
|
+
if (kindParam) {
|
|
343
|
+
filterConditions.push(eq(memoryGraphNodes.type, kindParam));
|
|
344
|
+
}
|
|
345
|
+
if (fidelityFilter) filterConditions.push(fidelityFilter);
|
|
346
|
+
|
|
347
|
+
if (filterConditions.length > 1) {
|
|
348
|
+
const validIdSet = new Set(
|
|
349
|
+
db
|
|
350
|
+
.select({ id: memoryGraphNodes.id })
|
|
351
|
+
.from(memoryGraphNodes)
|
|
352
|
+
.where(and(...filterConditions))
|
|
353
|
+
.all()
|
|
354
|
+
.map((r) => r.id),
|
|
355
|
+
);
|
|
356
|
+
filteredIds = semanticResult.ids.filter((id) => validIdSet.has(id));
|
|
357
|
+
}
|
|
265
358
|
}
|
|
266
|
-
|
|
267
|
-
|
|
359
|
+
|
|
360
|
+
const total = filteredIds.length;
|
|
361
|
+
const pageIds = filteredIds.slice(offsetParam, offsetParam + limitParam);
|
|
362
|
+
|
|
363
|
+
if (pageIds.length === 0) {
|
|
364
|
+
return Response.json({
|
|
365
|
+
items: [],
|
|
366
|
+
total,
|
|
367
|
+
kindCounts: semanticKindCounts,
|
|
368
|
+
});
|
|
268
369
|
}
|
|
269
370
|
|
|
371
|
+
// Hydrate nodes from DB
|
|
372
|
+
const hydrationConditions = [inArray(memoryGraphNodes.id, pageIds)];
|
|
373
|
+
if (fidelityFilter) hydrationConditions.push(fidelityFilter);
|
|
374
|
+
if (kindParam)
|
|
375
|
+
hydrationConditions.push(eq(memoryGraphNodes.type, kindParam));
|
|
376
|
+
|
|
270
377
|
const rows = db
|
|
271
378
|
.select()
|
|
272
|
-
.from(
|
|
379
|
+
.from(memoryGraphNodes)
|
|
273
380
|
.where(and(...hydrationConditions))
|
|
274
381
|
.all();
|
|
275
382
|
|
|
@@ -279,36 +386,46 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
|
|
|
279
386
|
|
|
280
387
|
const titleMap = buildConversationTitleMap(
|
|
281
388
|
db,
|
|
282
|
-
rows.map((
|
|
389
|
+
rows.map((r) => r.scopeId),
|
|
283
390
|
);
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}));
|
|
288
|
-
|
|
289
|
-
return Response.json({
|
|
290
|
-
items: enrichedItems,
|
|
291
|
-
total: semanticResult.total,
|
|
391
|
+
const items = rows.map((row) => {
|
|
392
|
+
const node = rowToNode(row);
|
|
393
|
+
return nodeToPayload(node, resolveScopeLabel(node.scopeId, titleMap));
|
|
292
394
|
});
|
|
395
|
+
|
|
396
|
+
return Response.json({ items, total, kindCounts: semanticKindCounts });
|
|
293
397
|
}
|
|
294
|
-
//
|
|
398
|
+
// Fall through to SQL path
|
|
295
399
|
}
|
|
296
400
|
|
|
297
|
-
// ──
|
|
298
|
-
const
|
|
299
|
-
if (
|
|
300
|
-
|
|
401
|
+
// ── Kind counts for SQL path ───────────────────────────────────────
|
|
402
|
+
const kindCountConditions = [];
|
|
403
|
+
if (fidelityFilter) kindCountConditions.push(fidelityFilter);
|
|
404
|
+
if (searchParam) {
|
|
405
|
+
kindCountConditions.push(
|
|
406
|
+
like(memoryGraphNodes.content, `%${searchParam}%`),
|
|
407
|
+
);
|
|
301
408
|
}
|
|
302
|
-
|
|
303
|
-
|
|
409
|
+
const kindCountWhere =
|
|
410
|
+
kindCountConditions.length > 0 ? and(...kindCountConditions) : undefined;
|
|
411
|
+
|
|
412
|
+
const sqlKindCountRows = db
|
|
413
|
+
.select({ kind: memoryGraphNodes.type, count: count() })
|
|
414
|
+
.from(memoryGraphNodes)
|
|
415
|
+
.where(kindCountWhere)
|
|
416
|
+
.groupBy(memoryGraphNodes.type)
|
|
417
|
+
.all();
|
|
418
|
+
const kindCounts: Record<string, number> = {};
|
|
419
|
+
for (const row of sqlKindCountRows) {
|
|
420
|
+
kindCounts[row.kind] = row.count;
|
|
304
421
|
}
|
|
422
|
+
|
|
423
|
+
// ── SQL path (default or fallback) ──────────────────────────────────
|
|
424
|
+
const conditions = [];
|
|
425
|
+
if (fidelityFilter) conditions.push(fidelityFilter);
|
|
426
|
+
if (kindParam) conditions.push(eq(memoryGraphNodes.type, kindParam));
|
|
305
427
|
if (searchParam) {
|
|
306
|
-
conditions.push(
|
|
307
|
-
or(
|
|
308
|
-
like(memoryItems.subject, `%${searchParam}%`),
|
|
309
|
-
like(memoryItems.statement, `%${searchParam}%`),
|
|
310
|
-
)!,
|
|
311
|
-
);
|
|
428
|
+
conditions.push(like(memoryGraphNodes.content, `%${searchParam}%`));
|
|
312
429
|
}
|
|
313
430
|
|
|
314
431
|
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
|
|
@@ -316,7 +433,7 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
|
|
|
316
433
|
// Count query
|
|
317
434
|
const countResult = db
|
|
318
435
|
.select({ count: count() })
|
|
319
|
-
.from(
|
|
436
|
+
.from(memoryGraphNodes)
|
|
320
437
|
.where(whereClause)
|
|
321
438
|
.get();
|
|
322
439
|
const total = countResult?.count ?? 0;
|
|
@@ -325,26 +442,25 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
|
|
|
325
442
|
const sortColumn = SORT_COLUMN_MAP[sortParam];
|
|
326
443
|
const orderFn = orderParam === "asc" ? asc : desc;
|
|
327
444
|
|
|
328
|
-
const
|
|
445
|
+
const rows = db
|
|
329
446
|
.select()
|
|
330
|
-
.from(
|
|
447
|
+
.from(memoryGraphNodes)
|
|
331
448
|
.where(whereClause)
|
|
332
449
|
.orderBy(orderFn(sortColumn))
|
|
333
450
|
.limit(limitParam)
|
|
334
451
|
.offset(offsetParam)
|
|
335
452
|
.all();
|
|
336
453
|
|
|
337
|
-
// Resolve scope labels for private-scoped items
|
|
338
454
|
const titleMap = buildConversationTitleMap(
|
|
339
455
|
db,
|
|
340
|
-
|
|
456
|
+
rows.map((r) => r.scopeId),
|
|
341
457
|
);
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
})
|
|
458
|
+
const items = rows.map((row) => {
|
|
459
|
+
const node = rowToNode(row);
|
|
460
|
+
return nodeToPayload(node, resolveScopeLabel(node.scopeId, titleMap));
|
|
461
|
+
});
|
|
346
462
|
|
|
347
|
-
return Response.json({ items
|
|
463
|
+
return Response.json({ items, total, kindCounts });
|
|
348
464
|
}
|
|
349
465
|
|
|
350
466
|
// ---------------------------------------------------------------------------
|
|
@@ -353,51 +469,17 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
|
|
|
353
469
|
|
|
354
470
|
export function handleGetMemoryItem(ctx: RouteContext): Response {
|
|
355
471
|
const { id } = ctx.params;
|
|
356
|
-
const db = getDb();
|
|
357
|
-
|
|
358
|
-
const item = db
|
|
359
|
-
.select()
|
|
360
|
-
.from(memoryItems)
|
|
361
|
-
.where(eq(memoryItems.id, id))
|
|
362
|
-
.get();
|
|
363
472
|
|
|
364
|
-
|
|
473
|
+
const node = getNode(id);
|
|
474
|
+
if (!node) {
|
|
365
475
|
return httpError("NOT_FOUND", "Memory item not found", 404);
|
|
366
476
|
}
|
|
367
477
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if (item.supersedes) {
|
|
372
|
-
const superseded = db
|
|
373
|
-
.select({ subject: memoryItems.subject })
|
|
374
|
-
.from(memoryItems)
|
|
375
|
-
.where(eq(memoryItems.id, item.supersedes))
|
|
376
|
-
.get();
|
|
377
|
-
supersedesSubject = superseded?.subject;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (item.supersededBy) {
|
|
381
|
-
const superseding = db
|
|
382
|
-
.select({ subject: memoryItems.subject })
|
|
383
|
-
.from(memoryItems)
|
|
384
|
-
.where(eq(memoryItems.id, item.supersededBy))
|
|
385
|
-
.get();
|
|
386
|
-
supersededBySubject = superseding?.subject;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Resolve scope label
|
|
390
|
-
const titleMap = buildConversationTitleMap(db, [item.scopeId]);
|
|
391
|
-
const scopeLabel = resolveScopeLabel(item.scopeId, titleMap);
|
|
478
|
+
const db = getDb();
|
|
479
|
+
const titleMap = buildConversationTitleMap(db, [node.scopeId]);
|
|
480
|
+
const scopeLabel = resolveScopeLabel(node.scopeId, titleMap);
|
|
392
481
|
|
|
393
|
-
return Response.json({
|
|
394
|
-
item: {
|
|
395
|
-
...item,
|
|
396
|
-
scopeLabel,
|
|
397
|
-
...(supersedesSubject !== undefined ? { supersedesSubject } : {}),
|
|
398
|
-
...(supersededBySubject !== undefined ? { supersededBySubject } : {}),
|
|
399
|
-
},
|
|
400
|
-
});
|
|
482
|
+
return Response.json({ item: nodeToPayload(node, scopeLabel) });
|
|
401
483
|
}
|
|
402
484
|
|
|
403
485
|
// ---------------------------------------------------------------------------
|
|
@@ -416,25 +498,14 @@ export async function handleCreateMemoryItem(
|
|
|
416
498
|
|
|
417
499
|
const { kind, subject, statement, importance } = body;
|
|
418
500
|
|
|
419
|
-
|
|
420
|
-
if (typeof kind !== "string" || !isValidKind(kind)) {
|
|
421
|
-
return httpError(
|
|
422
|
-
"BAD_REQUEST",
|
|
423
|
-
`kind is required and must be one of: ${VALID_KINDS.join(", ")}`,
|
|
424
|
-
400,
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Validate subject
|
|
429
|
-
if (typeof subject !== "string" || subject.trim().length === 0) {
|
|
501
|
+
if (typeof kind !== "string" || !isValidType(kind)) {
|
|
430
502
|
return httpError(
|
|
431
503
|
"BAD_REQUEST",
|
|
432
|
-
|
|
504
|
+
`kind is required and must be one of: ${VALID_TYPES.join(", ")}`,
|
|
433
505
|
400,
|
|
434
506
|
);
|
|
435
507
|
}
|
|
436
508
|
|
|
437
|
-
// Validate statement
|
|
438
509
|
if (typeof statement !== "string" || statement.trim().length === 0) {
|
|
439
510
|
return httpError(
|
|
440
511
|
"BAD_REQUEST",
|
|
@@ -443,27 +514,21 @@ export async function handleCreateMemoryItem(
|
|
|
443
514
|
);
|
|
444
515
|
}
|
|
445
516
|
|
|
446
|
-
const trimmedSubject = subject.trim();
|
|
517
|
+
const trimmedSubject = typeof subject === "string" ? subject.trim() : "";
|
|
447
518
|
const trimmedStatement = statement.trim();
|
|
519
|
+
const content = trimmedSubject
|
|
520
|
+
? `${trimmedSubject}\n${trimmedStatement}`
|
|
521
|
+
: trimmedStatement;
|
|
448
522
|
|
|
449
|
-
|
|
450
|
-
const fingerprint = computeMemoryFingerprint(
|
|
451
|
-
scopeId,
|
|
452
|
-
kind,
|
|
453
|
-
trimmedSubject,
|
|
454
|
-
trimmedStatement,
|
|
455
|
-
);
|
|
456
|
-
|
|
523
|
+
// Check for duplicate content
|
|
457
524
|
const db = getDb();
|
|
458
|
-
|
|
459
|
-
// Check for existing item with same fingerprint + scopeId
|
|
460
525
|
const existing = db
|
|
461
|
-
.select()
|
|
462
|
-
.from(
|
|
526
|
+
.select({ id: memoryGraphNodes.id })
|
|
527
|
+
.from(memoryGraphNodes)
|
|
463
528
|
.where(
|
|
464
529
|
and(
|
|
465
|
-
eq(
|
|
466
|
-
|
|
530
|
+
eq(memoryGraphNodes.content, content),
|
|
531
|
+
ne(memoryGraphNodes.fidelity, "gone"),
|
|
467
532
|
),
|
|
468
533
|
)
|
|
469
534
|
.get();
|
|
@@ -476,44 +541,43 @@ export async function handleCreateMemoryItem(
|
|
|
476
541
|
);
|
|
477
542
|
}
|
|
478
543
|
|
|
479
|
-
const id = uuid();
|
|
480
544
|
const now = Date.now();
|
|
545
|
+
const newNode: NewNode = {
|
|
546
|
+
content,
|
|
547
|
+
type: kind as MemoryType,
|
|
548
|
+
created: now,
|
|
549
|
+
lastAccessed: now,
|
|
550
|
+
lastConsolidated: now,
|
|
551
|
+
eventDate: null,
|
|
552
|
+
emotionalCharge: {
|
|
553
|
+
valence: 0,
|
|
554
|
+
intensity: 0.1,
|
|
555
|
+
decayCurve: "linear",
|
|
556
|
+
decayRate: 0.05,
|
|
557
|
+
originalIntensity: 0.1,
|
|
558
|
+
},
|
|
559
|
+
fidelity: "vivid",
|
|
560
|
+
confidence: 0.95,
|
|
561
|
+
significance: importance ?? 0.8,
|
|
562
|
+
stability: 14,
|
|
563
|
+
reinforcementCount: 0,
|
|
564
|
+
lastReinforced: now,
|
|
565
|
+
sourceConversations: [],
|
|
566
|
+
sourceType: "direct",
|
|
567
|
+
narrativeRole: null,
|
|
568
|
+
partOfStory: null,
|
|
569
|
+
imageRefs: null,
|
|
570
|
+
scopeId: "default",
|
|
571
|
+
};
|
|
481
572
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
id,
|
|
485
|
-
kind,
|
|
486
|
-
subject: trimmedSubject,
|
|
487
|
-
statement: trimmedStatement,
|
|
488
|
-
status: "active",
|
|
489
|
-
confidence: 0.95,
|
|
490
|
-
importance: importance ?? 0.8,
|
|
491
|
-
fingerprint,
|
|
492
|
-
sourceType: "tool",
|
|
493
|
-
verificationState: "user_confirmed",
|
|
494
|
-
scopeId,
|
|
495
|
-
firstSeenAt: now,
|
|
496
|
-
lastSeenAt: now,
|
|
497
|
-
lastUsedAt: null,
|
|
498
|
-
overrideConfidence: "explicit",
|
|
499
|
-
})
|
|
500
|
-
.run();
|
|
501
|
-
|
|
502
|
-
enqueueMemoryJob("embed_item", { itemId: id });
|
|
503
|
-
|
|
504
|
-
// Fetch the inserted row to return it
|
|
505
|
-
const insertedRow = db
|
|
506
|
-
.select()
|
|
507
|
-
.from(memoryItems)
|
|
508
|
-
.where(eq(memoryItems.id, id))
|
|
509
|
-
.get();
|
|
573
|
+
const created = createNode(newNode);
|
|
574
|
+
enqueueMemoryJob("embed_graph_node", { nodeId: created.id });
|
|
510
575
|
|
|
511
|
-
|
|
512
|
-
const
|
|
513
|
-
const scopeLabel = resolveScopeLabel(scopeId, titleMap);
|
|
576
|
+
const titleMap = buildConversationTitleMap(db, [created.scopeId]);
|
|
577
|
+
const scopeLabel = resolveScopeLabel(created.scopeId, titleMap);
|
|
514
578
|
|
|
515
579
|
return Response.json(
|
|
516
|
-
{ item:
|
|
580
|
+
{ item: nodeToPayload(created, scopeLabel) },
|
|
517
581
|
{ status: 201 },
|
|
518
582
|
);
|
|
519
583
|
}
|
|
@@ -526,114 +590,82 @@ export async function handleUpdateMemoryItem(
|
|
|
526
590
|
ctx: RouteContext,
|
|
527
591
|
): Promise<Response> {
|
|
528
592
|
const { id } = ctx.params;
|
|
593
|
+
|
|
594
|
+
const existing = getNode(id);
|
|
595
|
+
if (!existing) {
|
|
596
|
+
return httpError("NOT_FOUND", "Memory item not found", 404);
|
|
597
|
+
}
|
|
598
|
+
|
|
529
599
|
const body = (await ctx.req.json()) as {
|
|
530
600
|
subject?: string;
|
|
531
601
|
statement?: string;
|
|
532
602
|
kind?: string;
|
|
533
603
|
status?: string;
|
|
534
604
|
importance?: number;
|
|
535
|
-
sourceType?: string;
|
|
536
|
-
verificationState?: string;
|
|
537
605
|
};
|
|
538
606
|
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
const existing = db
|
|
542
|
-
.select()
|
|
543
|
-
.from(memoryItems)
|
|
544
|
-
.where(eq(memoryItems.id, id))
|
|
545
|
-
.get();
|
|
546
|
-
|
|
547
|
-
if (!existing) {
|
|
548
|
-
return httpError("NOT_FOUND", "Memory item not found", 404);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// Build the update set with only provided fields
|
|
552
|
-
const set: Record<string, unknown> = {
|
|
553
|
-
lastSeenAt: Date.now(),
|
|
607
|
+
const changes: Partial<Omit<MemoryNode, "id">> = {
|
|
608
|
+
lastAccessed: Date.now(),
|
|
554
609
|
};
|
|
555
610
|
|
|
556
|
-
if
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
611
|
+
// Rebuild content if subject or statement changed
|
|
612
|
+
const { subject: existingSubject, statement: existingStatement } =
|
|
613
|
+
splitContent(existing.content);
|
|
614
|
+
const newSubject =
|
|
615
|
+
body.subject !== undefined ? body.subject.trim() : existingSubject;
|
|
616
|
+
const newStatement =
|
|
617
|
+
body.statement !== undefined ? body.statement.trim() : existingStatement;
|
|
618
|
+
|
|
619
|
+
let contentChanged = false;
|
|
620
|
+
if (body.subject !== undefined || body.statement !== undefined) {
|
|
621
|
+
const newContent = newSubject
|
|
622
|
+
? `${newSubject}\n${newStatement}`
|
|
623
|
+
: newStatement;
|
|
624
|
+
if (newContent !== existing.content) {
|
|
625
|
+
changes.content = newContent;
|
|
626
|
+
contentChanged = true;
|
|
565
627
|
}
|
|
566
|
-
set.statement = body.statement.trim();
|
|
567
628
|
}
|
|
629
|
+
|
|
568
630
|
if (body.kind !== undefined) {
|
|
569
|
-
if (!
|
|
631
|
+
if (!isValidType(body.kind)) {
|
|
570
632
|
return httpError(
|
|
571
633
|
"BAD_REQUEST",
|
|
572
|
-
`Invalid kind "${body.kind}". Must be one of: ${
|
|
634
|
+
`Invalid kind "${body.kind}". Must be one of: ${VALID_TYPES.join(", ")}`,
|
|
573
635
|
400,
|
|
574
636
|
);
|
|
575
637
|
}
|
|
576
|
-
|
|
577
|
-
}
|
|
578
|
-
if (body.status !== undefined) {
|
|
579
|
-
set.status = body.status;
|
|
580
|
-
}
|
|
581
|
-
if (body.importance !== undefined) {
|
|
582
|
-
set.importance = body.importance;
|
|
583
|
-
}
|
|
584
|
-
if (body.sourceType !== undefined) {
|
|
585
|
-
set.sourceType = body.sourceType;
|
|
638
|
+
changes.type = body.kind as MemoryType;
|
|
586
639
|
}
|
|
587
640
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
set.sourceType =
|
|
595
|
-
body.verificationState === "user_confirmed" ? "tool" : "extraction";
|
|
641
|
+
if (body.status !== undefined) {
|
|
642
|
+
// Map client status to fidelity
|
|
643
|
+
if (body.status === "superseded" || body.status === "inactive") {
|
|
644
|
+
changes.fidelity = "gone";
|
|
645
|
+
} else if (body.status === "active") {
|
|
646
|
+
changes.fidelity = "vivid";
|
|
596
647
|
}
|
|
597
648
|
}
|
|
598
|
-
// If sourceType was set (either directly or via mapping), also write verificationState
|
|
599
|
-
if (body.sourceType !== undefined && body.verificationState === undefined) {
|
|
600
|
-
set.verificationState =
|
|
601
|
-
body.sourceType === "tool"
|
|
602
|
-
? "user_confirmed"
|
|
603
|
-
: existing.verificationState === "user_reported"
|
|
604
|
-
? "user_reported"
|
|
605
|
-
: "assistant_inferred";
|
|
606
|
-
}
|
|
607
649
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
body.statement !== undefined ||
|
|
612
|
-
body.kind !== undefined;
|
|
613
|
-
|
|
614
|
-
if (contentChanged) {
|
|
615
|
-
const newSubject = (set.subject as string | undefined) ?? existing.subject;
|
|
616
|
-
const newStatement =
|
|
617
|
-
(set.statement as string | undefined) ?? existing.statement;
|
|
618
|
-
const newKind = (set.kind as string | undefined) ?? existing.kind;
|
|
619
|
-
const scopeId = existing.scopeId;
|
|
620
|
-
|
|
621
|
-
const fingerprint = computeMemoryFingerprint(
|
|
622
|
-
scopeId,
|
|
623
|
-
newKind,
|
|
624
|
-
newSubject,
|
|
625
|
-
newStatement,
|
|
626
|
-
);
|
|
650
|
+
if (body.importance !== undefined) {
|
|
651
|
+
changes.significance = body.importance;
|
|
652
|
+
}
|
|
627
653
|
|
|
628
|
-
|
|
654
|
+
// Check for content collision when content changed OR when reactivating a
|
|
655
|
+
// gone item (which could duplicate an existing active item's content).
|
|
656
|
+
const reactivating =
|
|
657
|
+
changes.fidelity === "vivid" && existing.fidelity === "gone";
|
|
658
|
+
if (contentChanged || reactivating) {
|
|
659
|
+
const contentToCheck = changes.content ?? existing.content;
|
|
660
|
+
const db = getDb();
|
|
629
661
|
const collision = db
|
|
630
|
-
.select({ id:
|
|
631
|
-
.from(
|
|
662
|
+
.select({ id: memoryGraphNodes.id })
|
|
663
|
+
.from(memoryGraphNodes)
|
|
632
664
|
.where(
|
|
633
665
|
and(
|
|
634
|
-
eq(
|
|
635
|
-
|
|
636
|
-
ne(
|
|
666
|
+
eq(memoryGraphNodes.content, contentToCheck),
|
|
667
|
+
ne(memoryGraphNodes.id, id),
|
|
668
|
+
ne(memoryGraphNodes.fidelity, "gone"),
|
|
637
669
|
),
|
|
638
670
|
)
|
|
639
671
|
.get();
|
|
@@ -645,36 +677,25 @@ export async function handleUpdateMemoryItem(
|
|
|
645
677
|
409,
|
|
646
678
|
);
|
|
647
679
|
}
|
|
648
|
-
|
|
649
|
-
set.fingerprint = fingerprint;
|
|
650
680
|
}
|
|
651
681
|
|
|
652
|
-
|
|
682
|
+
updateNode(id, changes);
|
|
653
683
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
enqueueMemoryJob("embed_item", { itemId: id });
|
|
684
|
+
if (contentChanged) {
|
|
685
|
+
enqueueMemoryJob("embed_graph_node", { nodeId: id });
|
|
657
686
|
}
|
|
658
687
|
|
|
659
|
-
// Fetch
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
.get();
|
|
688
|
+
// Fetch updated node
|
|
689
|
+
const updated = getNode(id);
|
|
690
|
+
if (!updated) {
|
|
691
|
+
return httpError("NOT_FOUND", "Memory item not found after update", 404);
|
|
692
|
+
}
|
|
665
693
|
|
|
666
|
-
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
]);
|
|
670
|
-
const patchScopeLabel = resolveScopeLabel(
|
|
671
|
-
updatedRow?.scopeId ?? existing.scopeId,
|
|
672
|
-
patchTitleMap,
|
|
673
|
-
);
|
|
694
|
+
const db = getDb();
|
|
695
|
+
const titleMap = buildConversationTitleMap(db, [updated.scopeId]);
|
|
696
|
+
const scopeLabel = resolveScopeLabel(updated.scopeId, titleMap);
|
|
674
697
|
|
|
675
|
-
return Response.json({
|
|
676
|
-
item: { ...updatedRow, scopeLabel: patchScopeLabel },
|
|
677
|
-
});
|
|
698
|
+
return Response.json({ item: nodeToPayload(updated, scopeLabel) });
|
|
678
699
|
}
|
|
679
700
|
|
|
680
701
|
// ---------------------------------------------------------------------------
|
|
@@ -685,34 +706,57 @@ export async function handleDeleteMemoryItem(
|
|
|
685
706
|
ctx: RouteContext,
|
|
686
707
|
): Promise<Response> {
|
|
687
708
|
const { id } = ctx.params;
|
|
688
|
-
const db = getDb();
|
|
689
|
-
|
|
690
|
-
const existing = db
|
|
691
|
-
.select()
|
|
692
|
-
.from(memoryItems)
|
|
693
|
-
.where(eq(memoryItems.id, id))
|
|
694
|
-
.get();
|
|
695
709
|
|
|
710
|
+
const existing = getNode(id);
|
|
696
711
|
if (!existing) {
|
|
697
712
|
return httpError("NOT_FOUND", "Memory item not found", 404);
|
|
698
713
|
}
|
|
699
714
|
|
|
700
|
-
//
|
|
701
|
-
|
|
702
|
-
.where(
|
|
703
|
-
and(
|
|
704
|
-
eq(memoryEmbeddings.targetType, "item"),
|
|
705
|
-
eq(memoryEmbeddings.targetId, id),
|
|
706
|
-
),
|
|
707
|
-
)
|
|
708
|
-
.run();
|
|
715
|
+
// Hard-delete the node (cascades to edges and triggers via FK)
|
|
716
|
+
deleteNode(id);
|
|
709
717
|
|
|
710
|
-
//
|
|
711
|
-
|
|
718
|
+
// Clean up Qdrant vectors asynchronously
|
|
719
|
+
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
720
|
+
targetType: "graph_node",
|
|
721
|
+
targetId: id,
|
|
722
|
+
});
|
|
712
723
|
|
|
713
724
|
return new Response(null, { status: 204 });
|
|
714
725
|
}
|
|
715
726
|
|
|
727
|
+
// ---------------------------------------------------------------------------
|
|
728
|
+
// Row → MemoryNode helper (inline version of store's rowToNode)
|
|
729
|
+
// ---------------------------------------------------------------------------
|
|
730
|
+
|
|
731
|
+
function rowToNode(row: typeof memoryGraphNodes.$inferSelect): MemoryNode {
|
|
732
|
+
return {
|
|
733
|
+
id: row.id,
|
|
734
|
+
content: row.content,
|
|
735
|
+
type: row.type as MemoryType,
|
|
736
|
+
created: row.created,
|
|
737
|
+
lastAccessed: row.lastAccessed,
|
|
738
|
+
lastConsolidated: row.lastConsolidated,
|
|
739
|
+
eventDate: row.eventDate ?? null,
|
|
740
|
+
emotionalCharge: JSON.parse(row.emotionalCharge),
|
|
741
|
+
fidelity: row.fidelity as Fidelity,
|
|
742
|
+
confidence: row.confidence,
|
|
743
|
+
significance: row.significance,
|
|
744
|
+
stability: row.stability,
|
|
745
|
+
reinforcementCount: row.reinforcementCount,
|
|
746
|
+
lastReinforced: row.lastReinforced,
|
|
747
|
+
sourceConversations: JSON.parse(row.sourceConversations) as string[],
|
|
748
|
+
sourceType: row.sourceType as
|
|
749
|
+
| "direct"
|
|
750
|
+
| "inferred"
|
|
751
|
+
| "observed"
|
|
752
|
+
| "told-by-other",
|
|
753
|
+
narrativeRole: row.narrativeRole,
|
|
754
|
+
partOfStory: row.partOfStory,
|
|
755
|
+
imageRefs: row.imageRefs ? (JSON.parse(row.imageRefs) as ImageRef[]) : null,
|
|
756
|
+
scopeId: row.scopeId,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
|
|
716
760
|
// ---------------------------------------------------------------------------
|
|
717
761
|
// Route definitions
|
|
718
762
|
// ---------------------------------------------------------------------------
|
|
@@ -774,14 +818,13 @@ export function memoryItemRouteDefinitions(): RouteDefinition[] {
|
|
|
774
818
|
method: "GET",
|
|
775
819
|
policyKey: "memory-items",
|
|
776
820
|
summary: "Get a memory item",
|
|
777
|
-
description:
|
|
778
|
-
"Return a single memory item by ID with supersession metadata.",
|
|
821
|
+
description: "Return a single memory item by ID with graph metadata.",
|
|
779
822
|
tags: ["memory"],
|
|
780
823
|
responseBody: z.object({
|
|
781
824
|
item: z
|
|
782
825
|
.object({})
|
|
783
826
|
.passthrough()
|
|
784
|
-
.describe("Memory item with scopeLabel and
|
|
827
|
+
.describe("Memory item with scopeLabel and graph metadata"),
|
|
785
828
|
}),
|
|
786
829
|
handler: (ctx) => handleGetMemoryItem(ctx),
|
|
787
830
|
},
|
|
@@ -789,13 +832,16 @@ export function memoryItemRouteDefinitions(): RouteDefinition[] {
|
|
|
789
832
|
endpoint: "memory-items",
|
|
790
833
|
method: "POST",
|
|
791
834
|
summary: "Create a memory item",
|
|
792
|
-
description: "Create a new memory
|
|
835
|
+
description: "Create a new memory graph node and enqueue embedding.",
|
|
793
836
|
tags: ["memory"],
|
|
794
837
|
requestBody: z.object({
|
|
795
838
|
kind: z
|
|
796
839
|
.string()
|
|
797
|
-
.describe("Memory
|
|
798
|
-
subject: z
|
|
840
|
+
.describe("Memory type (episodic, semantic, procedural, etc.)"),
|
|
841
|
+
subject: z
|
|
842
|
+
.string()
|
|
843
|
+
.describe("Subject line (first line of content)")
|
|
844
|
+
.optional(),
|
|
799
845
|
statement: z.string().describe("Statement content"),
|
|
800
846
|
importance: z
|
|
801
847
|
.number()
|
|
@@ -812,16 +858,14 @@ export function memoryItemRouteDefinitions(): RouteDefinition[] {
|
|
|
812
858
|
method: "PATCH",
|
|
813
859
|
policyKey: "memory-items",
|
|
814
860
|
summary: "Update a memory item",
|
|
815
|
-
description: "Partially update fields on an existing memory
|
|
861
|
+
description: "Partially update fields on an existing memory graph node.",
|
|
816
862
|
tags: ["memory"],
|
|
817
863
|
requestBody: z.object({
|
|
818
|
-
subject: z.string(),
|
|
819
|
-
statement: z.string(),
|
|
820
|
-
kind: z.string(),
|
|
821
|
-
status: z.string(),
|
|
822
|
-
importance: z.number(),
|
|
823
|
-
sourceType: z.string(),
|
|
824
|
-
verificationState: z.string(),
|
|
864
|
+
subject: z.string().optional(),
|
|
865
|
+
statement: z.string().optional(),
|
|
866
|
+
kind: z.string().optional(),
|
|
867
|
+
status: z.string().optional(),
|
|
868
|
+
importance: z.number().optional(),
|
|
825
869
|
}),
|
|
826
870
|
responseBody: z.object({
|
|
827
871
|
item: z.object({}).passthrough().describe("Updated memory item"),
|
|
@@ -833,7 +877,7 @@ export function memoryItemRouteDefinitions(): RouteDefinition[] {
|
|
|
833
877
|
method: "DELETE",
|
|
834
878
|
policyKey: "memory-items",
|
|
835
879
|
summary: "Delete a memory item",
|
|
836
|
-
description: "Delete a memory
|
|
880
|
+
description: "Delete a memory graph node and its embeddings.",
|
|
837
881
|
tags: ["memory"],
|
|
838
882
|
responseBody: z.object({
|
|
839
883
|
ok: z.boolean(),
|