@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
|
@@ -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>,
|
|
@@ -133,20 +200,6 @@ function buildConversationTitleMap(
|
|
|
133
200
|
// Semantic search constants
|
|
134
201
|
// ---------------------------------------------------------------------------
|
|
135
202
|
|
|
136
|
-
/**
|
|
137
|
-
* Maximum number of candidate IDs fetched from Qdrant for semantic search.
|
|
138
|
-
*
|
|
139
|
-
* Kind-filtering and pagination happen *after* this fetch, so if a broad query
|
|
140
|
-
* matches more items than this ceiling, results beyond it are silently excluded
|
|
141
|
-
* — kind-filtered counts may be under-reported and offsets past this value are
|
|
142
|
-
* unreachable. The limit exists to bound Qdrant query cost and the size of the
|
|
143
|
-
* subsequent `IN (...)` SQL clauses (SQLite's SQLITE_MAX_VARIABLE_NUMBER is
|
|
144
|
-
* 32,766 in Bun's bundled build, so 10k stays well within that).
|
|
145
|
-
*
|
|
146
|
-
* If this becomes a real bottleneck for large memory stores, consider pushing
|
|
147
|
-
* kind/status filters into Qdrant payload filtering so the fetch limit only
|
|
148
|
-
* needs to cover the final result set.
|
|
149
|
-
*/
|
|
150
203
|
const SEMANTIC_SEARCH_FETCH_CEILING = 10_000;
|
|
151
204
|
|
|
152
205
|
// ---------------------------------------------------------------------------
|
|
@@ -154,15 +207,14 @@ const SEMANTIC_SEARCH_FETCH_CEILING = 10_000;
|
|
|
154
207
|
// ---------------------------------------------------------------------------
|
|
155
208
|
|
|
156
209
|
/**
|
|
157
|
-
*
|
|
158
|
-
* Returns ordered
|
|
210
|
+
* Hybrid semantic search for graph nodes via Qdrant.
|
|
211
|
+
* Returns ordered node IDs + total count on success, or `null` when
|
|
159
212
|
* the embedding backend / Qdrant is unavailable (caller falls back to SQL).
|
|
160
213
|
*/
|
|
161
|
-
async function
|
|
214
|
+
async function searchNodesSemantic(
|
|
162
215
|
query: string,
|
|
163
216
|
fetchLimit: number,
|
|
164
217
|
kindFilter: string | null,
|
|
165
|
-
statusFilter: string,
|
|
166
218
|
): Promise<{ ids: string[]; total: number } | null> {
|
|
167
219
|
try {
|
|
168
220
|
const config = getConfig();
|
|
@@ -176,13 +228,10 @@ async function searchItemsSemantic(
|
|
|
176
228
|
const sparse = generateSparseEmbedding(query);
|
|
177
229
|
const sparseVector = { indices: sparse.indices, values: sparse.values };
|
|
178
230
|
|
|
179
|
-
//
|
|
231
|
+
// Filter to graph_node target_type, exclude gone nodes
|
|
180
232
|
const mustConditions: Array<Record<string, unknown>> = [
|
|
181
|
-
{ key: "target_type", match: { value: "
|
|
233
|
+
{ key: "target_type", match: { value: "graph_node" } },
|
|
182
234
|
];
|
|
183
|
-
if (statusFilter && statusFilter !== "all") {
|
|
184
|
-
mustConditions.push({ key: "status", match: { value: statusFilter } });
|
|
185
|
-
}
|
|
186
235
|
if (kindFilter) {
|
|
187
236
|
mustConditions.push({ key: "kind", match: { value: kindFilter } });
|
|
188
237
|
}
|
|
@@ -204,11 +253,6 @@ async function searchItemsSemantic(
|
|
|
204
253
|
);
|
|
205
254
|
|
|
206
255
|
const ids = results.map((r) => r.payload.target_id);
|
|
207
|
-
|
|
208
|
-
// Use the vector search result count as the pagination total.
|
|
209
|
-
// A DB-wide COUNT would include items with no embedding yet (lagging) and
|
|
210
|
-
// items irrelevant to the search query, inflating the total and causing
|
|
211
|
-
// clients to paginate into empty pages.
|
|
212
256
|
return { ids, total: ids.length };
|
|
213
257
|
} catch (err) {
|
|
214
258
|
log.warn({ err }, "Semantic memory search failed, falling back to SQL");
|
|
@@ -229,10 +273,10 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
|
|
|
229
273
|
const limitParam = Number(url.searchParams.get("limit") ?? 100);
|
|
230
274
|
const offsetParam = Number(url.searchParams.get("offset") ?? 0);
|
|
231
275
|
|
|
232
|
-
if (kindParam && !
|
|
276
|
+
if (kindParam && !isValidType(kindParam)) {
|
|
233
277
|
return httpError(
|
|
234
278
|
"BAD_REQUEST",
|
|
235
|
-
`Invalid kind "${kindParam}". Must be one of: ${
|
|
279
|
+
`Invalid kind "${kindParam}". Must be one of: ${VALID_TYPES.join(", ")}`,
|
|
236
280
|
400,
|
|
237
281
|
);
|
|
238
282
|
}
|
|
@@ -255,57 +299,62 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
|
|
|
255
299
|
|
|
256
300
|
const db = getDb();
|
|
257
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
|
+
|
|
258
310
|
// ── Semantic search path ────────────────────────────────────────────
|
|
259
|
-
// When a search query is present, try Qdrant hybrid search first.
|
|
260
|
-
// Falls back to SQL LIKE when embeddings / Qdrant are unavailable.
|
|
261
311
|
if (searchParam) {
|
|
262
|
-
|
|
263
|
-
// Kind filtering is applied post-hoc while preserving relevance order.
|
|
264
|
-
const semanticResult = await searchItemsSemantic(
|
|
312
|
+
const semanticResult = await searchNodesSemantic(
|
|
265
313
|
searchParam,
|
|
266
314
|
SEMANTIC_SEARCH_FETCH_CEILING,
|
|
267
315
|
null,
|
|
268
|
-
statusParam,
|
|
269
316
|
);
|
|
270
317
|
|
|
271
318
|
if (semanticResult && semanticResult.ids.length > 0) {
|
|
272
|
-
// Compute kindCounts from all semantic matches
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
319
|
+
// Compute kindCounts from all semantic matches
|
|
320
|
+
const kindCountConditions = [
|
|
321
|
+
inArray(memoryGraphNodes.id, semanticResult.ids),
|
|
322
|
+
];
|
|
323
|
+
if (fidelityFilter) kindCountConditions.push(fidelityFilter);
|
|
324
|
+
|
|
278
325
|
const kindCountRows = db
|
|
279
|
-
.select({ kind:
|
|
280
|
-
.from(
|
|
326
|
+
.select({ kind: memoryGraphNodes.type, count: count() })
|
|
327
|
+
.from(memoryGraphNodes)
|
|
281
328
|
.where(and(...kindCountConditions))
|
|
282
|
-
.groupBy(
|
|
329
|
+
.groupBy(memoryGraphNodes.type)
|
|
283
330
|
.all();
|
|
284
331
|
const semanticKindCounts: Record<string, number> = {};
|
|
285
332
|
for (const row of kindCountRows) {
|
|
286
333
|
semanticKindCounts[row.kind] = row.count;
|
|
287
334
|
}
|
|
288
335
|
|
|
289
|
-
// Apply kind filter while preserving semantic relevance ordering
|
|
290
|
-
// Status filter is applied here too for defense-in-depth consistency.
|
|
336
|
+
// Apply kind + fidelity filter while preserving semantic relevance ordering
|
|
291
337
|
let filteredIds = semanticResult.ids;
|
|
292
|
-
|
|
293
|
-
const
|
|
294
|
-
inArray(
|
|
295
|
-
eq(memoryItems.kind, kindParam),
|
|
338
|
+
{
|
|
339
|
+
const filterConditions = [
|
|
340
|
+
inArray(memoryGraphNodes.id, semanticResult.ids),
|
|
296
341
|
];
|
|
297
|
-
if (
|
|
298
|
-
|
|
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));
|
|
299
357
|
}
|
|
300
|
-
const kindIdSet = new Set(
|
|
301
|
-
db
|
|
302
|
-
.select({ id: memoryItems.id })
|
|
303
|
-
.from(memoryItems)
|
|
304
|
-
.where(and(...kindFilterConditions))
|
|
305
|
-
.all()
|
|
306
|
-
.map((r) => r.id),
|
|
307
|
-
);
|
|
308
|
-
filteredIds = semanticResult.ids.filter((id) => kindIdSet.has(id));
|
|
309
358
|
}
|
|
310
359
|
|
|
311
360
|
const total = filteredIds.length;
|
|
@@ -319,19 +368,15 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
|
|
|
319
368
|
});
|
|
320
369
|
}
|
|
321
370
|
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
if (
|
|
326
|
-
hydrationConditions.push(eq(
|
|
327
|
-
}
|
|
328
|
-
if (kindParam) {
|
|
329
|
-
hydrationConditions.push(eq(memoryItems.kind, kindParam));
|
|
330
|
-
}
|
|
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));
|
|
331
376
|
|
|
332
377
|
const rows = db
|
|
333
378
|
.select()
|
|
334
|
-
.from(
|
|
379
|
+
.from(memoryGraphNodes)
|
|
335
380
|
.where(and(...hydrationConditions))
|
|
336
381
|
.all();
|
|
337
382
|
|
|
@@ -341,44 +386,34 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
|
|
|
341
386
|
|
|
342
387
|
const titleMap = buildConversationTitleMap(
|
|
343
388
|
db,
|
|
344
|
-
rows.map((
|
|
389
|
+
rows.map((r) => r.scopeId),
|
|
345
390
|
);
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}));
|
|
350
|
-
|
|
351
|
-
return Response.json({
|
|
352
|
-
items: enrichedItems,
|
|
353
|
-
total,
|
|
354
|
-
kindCounts: semanticKindCounts,
|
|
391
|
+
const items = rows.map((row) => {
|
|
392
|
+
const node = rowToNode(row);
|
|
393
|
+
return nodeToPayload(node, resolveScopeLabel(node.scopeId, titleMap));
|
|
355
394
|
});
|
|
395
|
+
|
|
396
|
+
return Response.json({ items, total, kindCounts: semanticKindCounts });
|
|
356
397
|
}
|
|
357
|
-
//
|
|
398
|
+
// Fall through to SQL path
|
|
358
399
|
}
|
|
359
400
|
|
|
360
401
|
// ── Kind counts for SQL path ───────────────────────────────────────
|
|
361
|
-
// Respects status/search filters but NOT kind filter, so the sidebar
|
|
362
|
-
// can show totals for every kind simultaneously.
|
|
363
402
|
const kindCountConditions = [];
|
|
364
|
-
if (
|
|
365
|
-
kindCountConditions.push(eq(memoryItems.status, statusParam));
|
|
366
|
-
}
|
|
403
|
+
if (fidelityFilter) kindCountConditions.push(fidelityFilter);
|
|
367
404
|
if (searchParam) {
|
|
368
405
|
kindCountConditions.push(
|
|
369
|
-
|
|
370
|
-
like(memoryItems.subject, `%${searchParam}%`),
|
|
371
|
-
like(memoryItems.statement, `%${searchParam}%`),
|
|
372
|
-
)!,
|
|
406
|
+
like(memoryGraphNodes.content, `%${searchParam}%`),
|
|
373
407
|
);
|
|
374
408
|
}
|
|
375
409
|
const kindCountWhere =
|
|
376
410
|
kindCountConditions.length > 0 ? and(...kindCountConditions) : undefined;
|
|
411
|
+
|
|
377
412
|
const sqlKindCountRows = db
|
|
378
|
-
.select({ kind:
|
|
379
|
-
.from(
|
|
413
|
+
.select({ kind: memoryGraphNodes.type, count: count() })
|
|
414
|
+
.from(memoryGraphNodes)
|
|
380
415
|
.where(kindCountWhere)
|
|
381
|
-
.groupBy(
|
|
416
|
+
.groupBy(memoryGraphNodes.type)
|
|
382
417
|
.all();
|
|
383
418
|
const kindCounts: Record<string, number> = {};
|
|
384
419
|
for (const row of sqlKindCountRows) {
|
|
@@ -387,19 +422,10 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
|
|
|
387
422
|
|
|
388
423
|
// ── SQL path (default or fallback) ──────────────────────────────────
|
|
389
424
|
const conditions = [];
|
|
390
|
-
if (
|
|
391
|
-
|
|
392
|
-
}
|
|
393
|
-
if (kindParam) {
|
|
394
|
-
conditions.push(eq(memoryItems.kind, kindParam));
|
|
395
|
-
}
|
|
425
|
+
if (fidelityFilter) conditions.push(fidelityFilter);
|
|
426
|
+
if (kindParam) conditions.push(eq(memoryGraphNodes.type, kindParam));
|
|
396
427
|
if (searchParam) {
|
|
397
|
-
conditions.push(
|
|
398
|
-
or(
|
|
399
|
-
like(memoryItems.subject, `%${searchParam}%`),
|
|
400
|
-
like(memoryItems.statement, `%${searchParam}%`),
|
|
401
|
-
)!,
|
|
402
|
-
);
|
|
428
|
+
conditions.push(like(memoryGraphNodes.content, `%${searchParam}%`));
|
|
403
429
|
}
|
|
404
430
|
|
|
405
431
|
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
|
|
@@ -407,7 +433,7 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
|
|
|
407
433
|
// Count query
|
|
408
434
|
const countResult = db
|
|
409
435
|
.select({ count: count() })
|
|
410
|
-
.from(
|
|
436
|
+
.from(memoryGraphNodes)
|
|
411
437
|
.where(whereClause)
|
|
412
438
|
.get();
|
|
413
439
|
const total = countResult?.count ?? 0;
|
|
@@ -416,26 +442,25 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
|
|
|
416
442
|
const sortColumn = SORT_COLUMN_MAP[sortParam];
|
|
417
443
|
const orderFn = orderParam === "asc" ? asc : desc;
|
|
418
444
|
|
|
419
|
-
const
|
|
445
|
+
const rows = db
|
|
420
446
|
.select()
|
|
421
|
-
.from(
|
|
447
|
+
.from(memoryGraphNodes)
|
|
422
448
|
.where(whereClause)
|
|
423
449
|
.orderBy(orderFn(sortColumn))
|
|
424
450
|
.limit(limitParam)
|
|
425
451
|
.offset(offsetParam)
|
|
426
452
|
.all();
|
|
427
453
|
|
|
428
|
-
// Resolve scope labels for private-scoped items
|
|
429
454
|
const titleMap = buildConversationTitleMap(
|
|
430
455
|
db,
|
|
431
|
-
|
|
456
|
+
rows.map((r) => r.scopeId),
|
|
432
457
|
);
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
})
|
|
458
|
+
const items = rows.map((row) => {
|
|
459
|
+
const node = rowToNode(row);
|
|
460
|
+
return nodeToPayload(node, resolveScopeLabel(node.scopeId, titleMap));
|
|
461
|
+
});
|
|
437
462
|
|
|
438
|
-
return Response.json({ items
|
|
463
|
+
return Response.json({ items, total, kindCounts });
|
|
439
464
|
}
|
|
440
465
|
|
|
441
466
|
// ---------------------------------------------------------------------------
|
|
@@ -444,51 +469,17 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
|
|
|
444
469
|
|
|
445
470
|
export function handleGetMemoryItem(ctx: RouteContext): Response {
|
|
446
471
|
const { id } = ctx.params;
|
|
447
|
-
const db = getDb();
|
|
448
472
|
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
.from(memoryItems)
|
|
452
|
-
.where(eq(memoryItems.id, id))
|
|
453
|
-
.get();
|
|
454
|
-
|
|
455
|
-
if (!item) {
|
|
473
|
+
const node = getNode(id);
|
|
474
|
+
if (!node) {
|
|
456
475
|
return httpError("NOT_FOUND", "Memory item not found", 404);
|
|
457
476
|
}
|
|
458
477
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
if (item.supersedes) {
|
|
463
|
-
const superseded = db
|
|
464
|
-
.select({ subject: memoryItems.subject })
|
|
465
|
-
.from(memoryItems)
|
|
466
|
-
.where(eq(memoryItems.id, item.supersedes))
|
|
467
|
-
.get();
|
|
468
|
-
supersedesSubject = superseded?.subject;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (item.supersededBy) {
|
|
472
|
-
const superseding = db
|
|
473
|
-
.select({ subject: memoryItems.subject })
|
|
474
|
-
.from(memoryItems)
|
|
475
|
-
.where(eq(memoryItems.id, item.supersededBy))
|
|
476
|
-
.get();
|
|
477
|
-
supersededBySubject = superseding?.subject;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Resolve scope label
|
|
481
|
-
const titleMap = buildConversationTitleMap(db, [item.scopeId]);
|
|
482
|
-
const scopeLabel = resolveScopeLabel(item.scopeId, titleMap);
|
|
478
|
+
const db = getDb();
|
|
479
|
+
const titleMap = buildConversationTitleMap(db, [node.scopeId]);
|
|
480
|
+
const scopeLabel = resolveScopeLabel(node.scopeId, titleMap);
|
|
483
481
|
|
|
484
|
-
return Response.json({
|
|
485
|
-
item: {
|
|
486
|
-
...item,
|
|
487
|
-
scopeLabel,
|
|
488
|
-
...(supersedesSubject !== undefined ? { supersedesSubject } : {}),
|
|
489
|
-
...(supersededBySubject !== undefined ? { supersededBySubject } : {}),
|
|
490
|
-
},
|
|
491
|
-
});
|
|
482
|
+
return Response.json({ item: nodeToPayload(node, scopeLabel) });
|
|
492
483
|
}
|
|
493
484
|
|
|
494
485
|
// ---------------------------------------------------------------------------
|
|
@@ -507,25 +498,14 @@ export async function handleCreateMemoryItem(
|
|
|
507
498
|
|
|
508
499
|
const { kind, subject, statement, importance } = body;
|
|
509
500
|
|
|
510
|
-
|
|
511
|
-
if (typeof kind !== "string" || !isValidKind(kind)) {
|
|
501
|
+
if (typeof kind !== "string" || !isValidType(kind)) {
|
|
512
502
|
return httpError(
|
|
513
503
|
"BAD_REQUEST",
|
|
514
|
-
`kind is required and must be one of: ${
|
|
504
|
+
`kind is required and must be one of: ${VALID_TYPES.join(", ")}`,
|
|
515
505
|
400,
|
|
516
506
|
);
|
|
517
507
|
}
|
|
518
508
|
|
|
519
|
-
// Validate subject
|
|
520
|
-
if (typeof subject !== "string" || subject.trim().length === 0) {
|
|
521
|
-
return httpError(
|
|
522
|
-
"BAD_REQUEST",
|
|
523
|
-
"subject is required and must be a non-empty string",
|
|
524
|
-
400,
|
|
525
|
-
);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// Validate statement
|
|
529
509
|
if (typeof statement !== "string" || statement.trim().length === 0) {
|
|
530
510
|
return httpError(
|
|
531
511
|
"BAD_REQUEST",
|
|
@@ -534,27 +514,21 @@ export async function handleCreateMemoryItem(
|
|
|
534
514
|
);
|
|
535
515
|
}
|
|
536
516
|
|
|
537
|
-
const trimmedSubject = subject.trim();
|
|
517
|
+
const trimmedSubject = typeof subject === "string" ? subject.trim() : "";
|
|
538
518
|
const trimmedStatement = statement.trim();
|
|
519
|
+
const content = trimmedSubject
|
|
520
|
+
? `${trimmedSubject}\n${trimmedStatement}`
|
|
521
|
+
: trimmedStatement;
|
|
539
522
|
|
|
540
|
-
|
|
541
|
-
const fingerprint = computeMemoryFingerprint(
|
|
542
|
-
scopeId,
|
|
543
|
-
kind,
|
|
544
|
-
trimmedSubject,
|
|
545
|
-
trimmedStatement,
|
|
546
|
-
);
|
|
547
|
-
|
|
523
|
+
// Check for duplicate content
|
|
548
524
|
const db = getDb();
|
|
549
|
-
|
|
550
|
-
// Check for existing item with same fingerprint + scopeId
|
|
551
525
|
const existing = db
|
|
552
|
-
.select()
|
|
553
|
-
.from(
|
|
526
|
+
.select({ id: memoryGraphNodes.id })
|
|
527
|
+
.from(memoryGraphNodes)
|
|
554
528
|
.where(
|
|
555
529
|
and(
|
|
556
|
-
eq(
|
|
557
|
-
|
|
530
|
+
eq(memoryGraphNodes.content, content),
|
|
531
|
+
ne(memoryGraphNodes.fidelity, "gone"),
|
|
558
532
|
),
|
|
559
533
|
)
|
|
560
534
|
.get();
|
|
@@ -567,44 +541,43 @@ export async function handleCreateMemoryItem(
|
|
|
567
541
|
);
|
|
568
542
|
}
|
|
569
543
|
|
|
570
|
-
const id = uuid();
|
|
571
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
|
+
};
|
|
572
572
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
id,
|
|
576
|
-
kind,
|
|
577
|
-
subject: trimmedSubject,
|
|
578
|
-
statement: trimmedStatement,
|
|
579
|
-
status: "active",
|
|
580
|
-
confidence: 0.95,
|
|
581
|
-
importance: importance ?? 0.8,
|
|
582
|
-
fingerprint,
|
|
583
|
-
sourceType: "tool",
|
|
584
|
-
verificationState: "user_confirmed",
|
|
585
|
-
scopeId,
|
|
586
|
-
firstSeenAt: now,
|
|
587
|
-
lastSeenAt: now,
|
|
588
|
-
lastUsedAt: null,
|
|
589
|
-
overrideConfidence: "explicit",
|
|
590
|
-
})
|
|
591
|
-
.run();
|
|
592
|
-
|
|
593
|
-
enqueueMemoryJob("embed_item", { itemId: id });
|
|
594
|
-
|
|
595
|
-
// Fetch the inserted row to return it
|
|
596
|
-
const insertedRow = db
|
|
597
|
-
.select()
|
|
598
|
-
.from(memoryItems)
|
|
599
|
-
.where(eq(memoryItems.id, id))
|
|
600
|
-
.get();
|
|
573
|
+
const created = createNode(newNode);
|
|
574
|
+
enqueueMemoryJob("embed_graph_node", { nodeId: created.id });
|
|
601
575
|
|
|
602
|
-
|
|
603
|
-
const
|
|
604
|
-
const scopeLabel = resolveScopeLabel(scopeId, titleMap);
|
|
576
|
+
const titleMap = buildConversationTitleMap(db, [created.scopeId]);
|
|
577
|
+
const scopeLabel = resolveScopeLabel(created.scopeId, titleMap);
|
|
605
578
|
|
|
606
579
|
return Response.json(
|
|
607
|
-
{ item:
|
|
580
|
+
{ item: nodeToPayload(created, scopeLabel) },
|
|
608
581
|
{ status: 201 },
|
|
609
582
|
);
|
|
610
583
|
}
|
|
@@ -617,114 +590,82 @@ export async function handleUpdateMemoryItem(
|
|
|
617
590
|
ctx: RouteContext,
|
|
618
591
|
): Promise<Response> {
|
|
619
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
|
+
|
|
620
599
|
const body = (await ctx.req.json()) as {
|
|
621
600
|
subject?: string;
|
|
622
601
|
statement?: string;
|
|
623
602
|
kind?: string;
|
|
624
603
|
status?: string;
|
|
625
604
|
importance?: number;
|
|
626
|
-
sourceType?: string;
|
|
627
|
-
verificationState?: string;
|
|
628
605
|
};
|
|
629
606
|
|
|
630
|
-
const
|
|
631
|
-
|
|
632
|
-
const existing = db
|
|
633
|
-
.select()
|
|
634
|
-
.from(memoryItems)
|
|
635
|
-
.where(eq(memoryItems.id, id))
|
|
636
|
-
.get();
|
|
637
|
-
|
|
638
|
-
if (!existing) {
|
|
639
|
-
return httpError("NOT_FOUND", "Memory item not found", 404);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// Build the update set with only provided fields
|
|
643
|
-
const set: Record<string, unknown> = {
|
|
644
|
-
lastSeenAt: Date.now(),
|
|
607
|
+
const changes: Partial<Omit<MemoryNode, "id">> = {
|
|
608
|
+
lastAccessed: Date.now(),
|
|
645
609
|
};
|
|
646
610
|
|
|
647
|
-
if
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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;
|
|
656
627
|
}
|
|
657
|
-
set.statement = body.statement.trim();
|
|
658
628
|
}
|
|
629
|
+
|
|
659
630
|
if (body.kind !== undefined) {
|
|
660
|
-
if (!
|
|
631
|
+
if (!isValidType(body.kind)) {
|
|
661
632
|
return httpError(
|
|
662
633
|
"BAD_REQUEST",
|
|
663
|
-
`Invalid kind "${body.kind}". Must be one of: ${
|
|
634
|
+
`Invalid kind "${body.kind}". Must be one of: ${VALID_TYPES.join(", ")}`,
|
|
664
635
|
400,
|
|
665
636
|
);
|
|
666
637
|
}
|
|
667
|
-
|
|
668
|
-
}
|
|
669
|
-
if (body.status !== undefined) {
|
|
670
|
-
set.status = body.status;
|
|
671
|
-
}
|
|
672
|
-
if (body.importance !== undefined) {
|
|
673
|
-
set.importance = body.importance;
|
|
674
|
-
}
|
|
675
|
-
if (body.sourceType !== undefined) {
|
|
676
|
-
set.sourceType = body.sourceType;
|
|
638
|
+
changes.type = body.kind as MemoryType;
|
|
677
639
|
}
|
|
678
640
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
set.sourceType =
|
|
686
|
-
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";
|
|
687
647
|
}
|
|
688
648
|
}
|
|
689
|
-
// If sourceType was set (either directly or via mapping), also write verificationState
|
|
690
|
-
if (body.sourceType !== undefined && body.verificationState === undefined) {
|
|
691
|
-
set.verificationState =
|
|
692
|
-
body.sourceType === "tool"
|
|
693
|
-
? "user_confirmed"
|
|
694
|
-
: existing.verificationState === "user_reported"
|
|
695
|
-
? "user_reported"
|
|
696
|
-
: "assistant_inferred";
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// If subject, statement, or kind changed, recompute fingerprint
|
|
700
|
-
const contentChanged =
|
|
701
|
-
body.subject !== undefined ||
|
|
702
|
-
body.statement !== undefined ||
|
|
703
|
-
body.kind !== undefined;
|
|
704
649
|
|
|
705
|
-
if (
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
(set.statement as string | undefined) ?? existing.statement;
|
|
709
|
-
const newKind = (set.kind as string | undefined) ?? existing.kind;
|
|
710
|
-
const scopeId = existing.scopeId;
|
|
711
|
-
|
|
712
|
-
const fingerprint = computeMemoryFingerprint(
|
|
713
|
-
scopeId,
|
|
714
|
-
newKind,
|
|
715
|
-
newSubject,
|
|
716
|
-
newStatement,
|
|
717
|
-
);
|
|
650
|
+
if (body.importance !== undefined) {
|
|
651
|
+
changes.significance = body.importance;
|
|
652
|
+
}
|
|
718
653
|
|
|
719
|
-
|
|
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();
|
|
720
661
|
const collision = db
|
|
721
|
-
.select({ id:
|
|
722
|
-
.from(
|
|
662
|
+
.select({ id: memoryGraphNodes.id })
|
|
663
|
+
.from(memoryGraphNodes)
|
|
723
664
|
.where(
|
|
724
665
|
and(
|
|
725
|
-
eq(
|
|
726
|
-
|
|
727
|
-
ne(
|
|
666
|
+
eq(memoryGraphNodes.content, contentToCheck),
|
|
667
|
+
ne(memoryGraphNodes.id, id),
|
|
668
|
+
ne(memoryGraphNodes.fidelity, "gone"),
|
|
728
669
|
),
|
|
729
670
|
)
|
|
730
671
|
.get();
|
|
@@ -736,36 +677,25 @@ export async function handleUpdateMemoryItem(
|
|
|
736
677
|
409,
|
|
737
678
|
);
|
|
738
679
|
}
|
|
739
|
-
|
|
740
|
-
set.fingerprint = fingerprint;
|
|
741
680
|
}
|
|
742
681
|
|
|
743
|
-
|
|
682
|
+
updateNode(id, changes);
|
|
744
683
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
enqueueMemoryJob("embed_item", { itemId: id });
|
|
684
|
+
if (contentChanged) {
|
|
685
|
+
enqueueMemoryJob("embed_graph_node", { nodeId: id });
|
|
748
686
|
}
|
|
749
687
|
|
|
750
|
-
// Fetch
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
.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
|
+
}
|
|
756
693
|
|
|
757
|
-
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
]);
|
|
761
|
-
const patchScopeLabel = resolveScopeLabel(
|
|
762
|
-
updatedRow?.scopeId ?? existing.scopeId,
|
|
763
|
-
patchTitleMap,
|
|
764
|
-
);
|
|
694
|
+
const db = getDb();
|
|
695
|
+
const titleMap = buildConversationTitleMap(db, [updated.scopeId]);
|
|
696
|
+
const scopeLabel = resolveScopeLabel(updated.scopeId, titleMap);
|
|
765
697
|
|
|
766
|
-
return Response.json({
|
|
767
|
-
item: { ...updatedRow, scopeLabel: patchScopeLabel },
|
|
768
|
-
});
|
|
698
|
+
return Response.json({ item: nodeToPayload(updated, scopeLabel) });
|
|
769
699
|
}
|
|
770
700
|
|
|
771
701
|
// ---------------------------------------------------------------------------
|
|
@@ -776,34 +706,57 @@ export async function handleDeleteMemoryItem(
|
|
|
776
706
|
ctx: RouteContext,
|
|
777
707
|
): Promise<Response> {
|
|
778
708
|
const { id } = ctx.params;
|
|
779
|
-
const db = getDb();
|
|
780
|
-
|
|
781
|
-
const existing = db
|
|
782
|
-
.select()
|
|
783
|
-
.from(memoryItems)
|
|
784
|
-
.where(eq(memoryItems.id, id))
|
|
785
|
-
.get();
|
|
786
709
|
|
|
710
|
+
const existing = getNode(id);
|
|
787
711
|
if (!existing) {
|
|
788
712
|
return httpError("NOT_FOUND", "Memory item not found", 404);
|
|
789
713
|
}
|
|
790
714
|
|
|
791
|
-
//
|
|
792
|
-
|
|
793
|
-
.where(
|
|
794
|
-
and(
|
|
795
|
-
eq(memoryEmbeddings.targetType, "item"),
|
|
796
|
-
eq(memoryEmbeddings.targetId, id),
|
|
797
|
-
),
|
|
798
|
-
)
|
|
799
|
-
.run();
|
|
715
|
+
// Hard-delete the node (cascades to edges and triggers via FK)
|
|
716
|
+
deleteNode(id);
|
|
800
717
|
|
|
801
|
-
//
|
|
802
|
-
|
|
718
|
+
// Clean up Qdrant vectors asynchronously
|
|
719
|
+
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
720
|
+
targetType: "graph_node",
|
|
721
|
+
targetId: id,
|
|
722
|
+
});
|
|
803
723
|
|
|
804
724
|
return new Response(null, { status: 204 });
|
|
805
725
|
}
|
|
806
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
|
+
|
|
807
760
|
// ---------------------------------------------------------------------------
|
|
808
761
|
// Route definitions
|
|
809
762
|
// ---------------------------------------------------------------------------
|
|
@@ -865,14 +818,13 @@ export function memoryItemRouteDefinitions(): RouteDefinition[] {
|
|
|
865
818
|
method: "GET",
|
|
866
819
|
policyKey: "memory-items",
|
|
867
820
|
summary: "Get a memory item",
|
|
868
|
-
description:
|
|
869
|
-
"Return a single memory item by ID with supersession metadata.",
|
|
821
|
+
description: "Return a single memory item by ID with graph metadata.",
|
|
870
822
|
tags: ["memory"],
|
|
871
823
|
responseBody: z.object({
|
|
872
824
|
item: z
|
|
873
825
|
.object({})
|
|
874
826
|
.passthrough()
|
|
875
|
-
.describe("Memory item with scopeLabel and
|
|
827
|
+
.describe("Memory item with scopeLabel and graph metadata"),
|
|
876
828
|
}),
|
|
877
829
|
handler: (ctx) => handleGetMemoryItem(ctx),
|
|
878
830
|
},
|
|
@@ -880,13 +832,16 @@ export function memoryItemRouteDefinitions(): RouteDefinition[] {
|
|
|
880
832
|
endpoint: "memory-items",
|
|
881
833
|
method: "POST",
|
|
882
834
|
summary: "Create a memory item",
|
|
883
|
-
description: "Create a new memory
|
|
835
|
+
description: "Create a new memory graph node and enqueue embedding.",
|
|
884
836
|
tags: ["memory"],
|
|
885
837
|
requestBody: z.object({
|
|
886
838
|
kind: z
|
|
887
839
|
.string()
|
|
888
|
-
.describe("Memory
|
|
889
|
-
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(),
|
|
890
845
|
statement: z.string().describe("Statement content"),
|
|
891
846
|
importance: z
|
|
892
847
|
.number()
|
|
@@ -903,16 +858,14 @@ export function memoryItemRouteDefinitions(): RouteDefinition[] {
|
|
|
903
858
|
method: "PATCH",
|
|
904
859
|
policyKey: "memory-items",
|
|
905
860
|
summary: "Update a memory item",
|
|
906
|
-
description: "Partially update fields on an existing memory
|
|
861
|
+
description: "Partially update fields on an existing memory graph node.",
|
|
907
862
|
tags: ["memory"],
|
|
908
863
|
requestBody: z.object({
|
|
909
|
-
subject: z.string(),
|
|
910
|
-
statement: z.string(),
|
|
911
|
-
kind: z.string(),
|
|
912
|
-
status: z.string(),
|
|
913
|
-
importance: z.number(),
|
|
914
|
-
sourceType: z.string(),
|
|
915
|
-
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(),
|
|
916
869
|
}),
|
|
917
870
|
responseBody: z.object({
|
|
918
871
|
item: z.object({}).passthrough().describe("Updated memory item"),
|
|
@@ -924,7 +877,7 @@ export function memoryItemRouteDefinitions(): RouteDefinition[] {
|
|
|
924
877
|
method: "DELETE",
|
|
925
878
|
policyKey: "memory-items",
|
|
926
879
|
summary: "Delete a memory item",
|
|
927
|
-
description: "Delete a memory
|
|
880
|
+
description: "Delete a memory graph node and its embeddings.",
|
|
928
881
|
tags: ["memory"],
|
|
929
882
|
responseBody: z.object({
|
|
930
883
|
ok: z.boolean(),
|