claude-memory-layer 1.0.27 → 1.0.28
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/.env.example +7 -0
- package/AGENTS.md +11 -0
- package/README.md +184 -41
- package/benchmarks/replay/anonymized-real-sessions.json +48 -0
- package/dist/cli/index.js +10097 -6003
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +9745 -5587
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/post-tool-use.js +6545 -5270
- package/dist/hooks/post-tool-use.js.map +4 -4
- package/dist/hooks/semantic-daemon.js +6646 -5354
- package/dist/hooks/semantic-daemon.js.map +4 -4
- package/dist/hooks/session-end.js +6618 -5347
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +6619 -5354
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +6614 -5325
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +6702 -5356
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/index.js +13537 -0
- package/dist/index.js.map +7 -0
- package/dist/mcp/index.js +20770 -0
- package/dist/mcp/index.js.map +7 -0
- package/dist/server/api/index.js +6632 -5319
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +6667 -5340
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +6568 -5350
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/assets/js/bootstrap.js +244 -0
- package/dist/ui/assets/js/chat.js +373 -0
- package/dist/ui/assets/js/disclosure.js +232 -0
- package/dist/ui/assets/js/modals.js +298 -0
- package/dist/ui/assets/js/overview.js +655 -0
- package/dist/ui/assets/js/state.js +72 -0
- package/dist/ui/assets/js/views.js +468 -0
- package/dist/ui/index.html +43 -1
- package/dist/ui/index.ts +3 -0
- package/dist/ui/style.css +222 -0
- package/docs/ARCHITECTURE_COMPARISON_AND_RECOMMENDATIONS.md +627 -0
- package/docs/HERMES_MEMORY_INGESTION_ANALYSIS.md +440 -0
- package/docs/MEMORY_USEFULNESS_AUDIT.md +371 -0
- package/docs/MEMORY_USEFULNESS_AUDIT_RAW.json +80 -0
- package/docs/MEMSEARCH_PROJECT_STRUCTURE_ANALYSIS.md +333 -0
- package/docs/PRODUCT_VALIDATION_MATRIX.md +82 -0
- package/docs/PROJECT_STRUCTURE_ANALYSIS.md +421 -0
- package/docs/REFACTORING_MILESTONES_AND_ISSUES.md +501 -0
- package/docs/REFACTORING_PLAN_THIN_CORE.md +414 -0
- package/docs/REFERENCE_PROJECT_ANALYSES.md +25 -0
- package/docs/SUPERLOCALMEMORY_PROJECT_STRUCTURE_ANALYSIS.md +452 -0
- package/docs/TARGET_ARCHITECTURE_AND_FOLDER_STRUCTURE.md +446 -0
- package/docs/architecture/comparison-index.md +47 -0
- package/docs/reports/codex-real-data-validation-20260505T040447Z.md +46 -0
- package/package.json +9 -5
- package/scripts/build.ts +25 -8
- package/scripts/generate-session-qrels.ts +126 -0
- package/scripts/replay-retrieval-benchmark.ts +69 -0
- package/specs/thin-core-refactor/context.md +275 -0
- package/specs/thin-core-refactor/plan.md +536 -0
- package/specs/thin-core-refactor/spec.md +465 -0
- package/src/adapters/claude/capture/index.ts +3 -0
- package/src/adapters/claude/context/index.ts +3 -0
- package/src/adapters/claude/hooks/index.ts +21 -0
- package/src/adapters/claude/hooks/post-tool-use.ts +239 -0
- package/src/adapters/claude/hooks/prompt-injection-policy.ts +104 -0
- package/src/adapters/claude/hooks/semantic-daemon-client.ts +209 -0
- package/src/adapters/claude/hooks/semantic-daemon.ts +283 -0
- package/src/adapters/claude/hooks/session-end.ts +59 -0
- package/src/adapters/claude/hooks/session-start.ts +73 -0
- package/src/adapters/claude/hooks/stop.ts +128 -0
- package/src/adapters/claude/hooks/user-prompt-submit.ts +361 -0
- package/src/adapters/claude/index.ts +4 -0
- package/src/adapters/claude/transcript/index.ts +4 -0
- package/src/adapters/claude/transcript/transcript-reader.ts +57 -0
- package/src/adapters/claude/transcript/turn-reconstructor.ts +65 -0
- package/src/apps/cli/claude-settings-hooks.ts +138 -0
- package/src/apps/cli/codex-import-runner.ts +125 -0
- package/src/apps/cli/codex-validation-output.ts +95 -0
- package/src/apps/cli/hermes-import-runner.ts +130 -0
- package/src/apps/cli/hermes-validation-output.ts +91 -0
- package/src/apps/cli/index.ts +1731 -0
- package/src/apps/cli/mcp-install.ts +106 -0
- package/src/apps/cli/retrieval-disclosure-output.ts +196 -0
- package/src/apps/dashboard/assets/js/bootstrap.js +244 -0
- package/src/apps/dashboard/assets/js/chat.js +373 -0
- package/src/apps/dashboard/assets/js/disclosure.js +232 -0
- package/src/apps/dashboard/assets/js/modals.js +298 -0
- package/src/apps/dashboard/assets/js/overview.js +655 -0
- package/src/apps/dashboard/assets/js/state.js +72 -0
- package/src/apps/dashboard/assets/js/views.js +468 -0
- package/src/{ui → apps/dashboard}/index.html +43 -1
- package/src/apps/dashboard/index.ts +3 -0
- package/src/{ui → apps/dashboard}/style.css +222 -0
- package/src/apps/index.ts +5 -0
- package/src/apps/server/api/chat.ts +244 -0
- package/src/apps/server/api/citations.ts +105 -0
- package/src/apps/server/api/events.ts +137 -0
- package/src/apps/server/api/health.ts +53 -0
- package/src/apps/server/api/index.ts +26 -0
- package/src/apps/server/api/projects.ts +74 -0
- package/src/apps/server/api/search.ts +184 -0
- package/src/apps/server/api/sessions.ts +115 -0
- package/src/apps/server/api/stats.ts +723 -0
- package/src/apps/server/api/turns.ts +143 -0
- package/src/apps/server/api/utils.ts +65 -0
- package/src/apps/server/index.ts +111 -0
- package/src/cli/index.ts +2 -1311
- package/src/cli/retrieval-disclosure-output.ts +2 -0
- package/src/compat/index.ts +5 -0
- package/src/core/derive/fact-deriver.ts +170 -0
- package/src/core/derive/index.ts +2 -0
- package/src/core/derive/summary-deriver.ts +76 -0
- package/src/core/embedder.ts +4 -152
- package/src/core/engine/embedding-maintenance-service.ts +187 -0
- package/src/core/engine/endless-memory-services.ts +4 -0
- package/src/core/engine/index.ts +19 -0
- package/src/core/engine/memory-engine-services.ts +170 -0
- package/src/core/engine/memory-ingest-service.ts +317 -0
- package/src/core/engine/memory-query-service.ts +173 -0
- package/src/core/engine/memory-runtime-service.ts +162 -0
- package/src/core/engine/memory-service-composition.ts +231 -0
- package/src/core/engine/retrieval-analytics-service.ts +181 -0
- package/src/core/engine/retrieval-disclosure-service.ts +420 -0
- package/src/core/engine/retrieval-orchestrator.ts +377 -0
- package/src/core/engine/retrieval-services.ts +176 -0
- package/src/core/engine/shared-memory-services.ts +4 -0
- package/src/core/entity-repo.ts +1 -3
- package/src/core/event-store.ts +3 -3
- package/src/core/evidence-aligner.ts +2 -2
- package/src/core/external-market-context.ts +582 -0
- package/src/core/graduation.ts +2 -3
- package/src/core/index.ts +21 -0
- package/src/core/matcher.ts +2 -4
- package/src/core/model/memory-fact.ts +30 -0
- package/src/core/model/memory-rule.ts +14 -0
- package/src/core/model/memory-summary.ts +21 -0
- package/src/core/model/raw-event.ts +28 -0
- package/src/core/model/retrieval-result.ts +35 -0
- package/src/core/privacy/filter.ts +21 -10
- package/src/core/product-validation-matrix.ts +314 -0
- package/src/core/progressive-retriever.ts +1 -2
- package/src/core/registry/project-path.ts +54 -0
- package/src/core/registry/session-registry.ts +69 -0
- package/src/core/replay-evaluator.ts +625 -0
- package/src/core/retrieval-benchmark.ts +117 -0
- package/src/core/retrieval-quality.ts +109 -0
- package/src/core/retriever.ts +53 -15
- package/src/core/session-qrels.ts +360 -0
- package/src/core/shared-event-store.ts +1 -1
- package/src/core/sqlite-event-store.ts +35 -11
- package/src/core/task/blocker-resolver.ts +2 -2
- package/src/core/task/task-resolver.ts +0 -1
- package/src/core/vector-outbox.ts +1 -10
- package/src/core/vector-worker.ts +1 -1
- package/src/extensions/endless-memory/endless-memory-services.ts +350 -0
- package/src/extensions/endless-memory/index.ts +1 -0
- package/src/extensions/index.ts +5 -0
- package/src/extensions/mcp/handlers.ts +960 -0
- package/src/extensions/mcp/index.ts +48 -0
- package/src/extensions/mcp/tools.ts +252 -0
- package/src/extensions/shared-memory/index.ts +1 -0
- package/src/extensions/shared-memory/shared-memory-services.ts +211 -0
- package/src/extensions/vector/embedder.ts +197 -0
- package/src/extensions/vector/index.ts +1 -0
- package/src/hooks/post-tool-use.ts +3 -236
- package/src/hooks/semantic-daemon-client.ts +1 -208
- package/src/hooks/semantic-daemon.ts +6 -271
- package/src/hooks/session-end.ts +4 -79
- package/src/hooks/session-start.ts +4 -73
- package/src/hooks/stop.ts +3 -173
- package/src/hooks/user-prompt-submit.ts +3 -338
- package/src/index.ts +13 -0
- package/src/mcp/handlers.ts +2 -212
- package/src/mcp/index.ts +3 -46
- package/src/mcp/tools.ts +2 -78
- package/src/server/api/chat.ts +2 -244
- package/src/server/api/citations.ts +2 -105
- package/src/server/api/events.ts +2 -137
- package/src/server/api/health.ts +2 -53
- package/src/server/api/index.ts +2 -26
- package/src/server/api/projects.ts +2 -74
- package/src/server/api/search.ts +2 -102
- package/src/server/api/sessions.ts +2 -115
- package/src/server/api/stats.ts +2 -724
- package/src/server/api/turns.ts +2 -143
- package/src/server/api/utils.ts +2 -46
- package/src/server/index.ts +2 -100
- package/src/services/bootstrap-organizer.ts +46 -26
- package/src/services/codex-session-history-importer.ts +521 -29
- package/src/services/hermes-session-history-importer.ts +733 -0
- package/src/services/memory-service-config.ts +36 -0
- package/src/services/memory-service-registry.ts +150 -0
- package/src/services/memory-service.ts +211 -1325
- package/src/services/session-history-importer.ts +58 -14
- package/tests/README.md +23 -0
- package/tests/adapters/claude/claude-semantic-daemon-adapter.test.ts +54 -0
- package/tests/adapters/claude/claude-transcript-reconstructor.test.ts +98 -0
- package/tests/adapters/claude-hook-prompt-injection-policy.test.ts +99 -0
- package/tests/apps/app-layer-boundary.test.ts +48 -0
- package/tests/apps/claude-settings-hooks.test.ts +107 -0
- package/tests/apps/cli-disclosure-output.test.ts +212 -0
- package/tests/apps/codex-import-runner.test.ts +99 -0
- package/tests/apps/codex-validation-output.test.ts +100 -0
- package/tests/apps/hermes-import-runner.test.ts +99 -0
- package/tests/apps/mcp-install-command.test.ts +59 -0
- package/tests/apps/package-build-entrypoints.test.ts +30 -0
- package/tests/apps/search-api-disclosure.test.ts +162 -0
- package/tests/apps/stats-api-lightweight.test.ts +67 -0
- package/tests/apps/ui-disclosure-output.test.ts +140 -0
- package/tests/{bootstrap-organizer.test.ts → core/bootstrap-organizer.test.ts} +1 -1
- package/tests/{canonical-key.test.ts → core/canonical-key.test.ts} +1 -1
- package/tests/core/codex-session-history-importer-validation.test.ts +185 -0
- package/tests/{consolidation-worker.test.ts → core/consolidation-worker.test.ts} +2 -2
- package/tests/core/embedding-maintenance-service.test.ts +282 -0
- package/tests/{evidence-aligner.test.ts → core/evidence-aligner.test.ts} +1 -1
- package/tests/core/external-market-context.test.ts +209 -0
- package/tests/core/fact-deriver.test.ts +79 -0
- package/tests/core/hermes-session-history-importer-validation.test.ts +609 -0
- package/tests/{ingest-interceptor.test.ts → core/ingest-interceptor.test.ts} +1 -1
- package/tests/{markdown-mirror.test.ts → core/markdown-mirror.test.ts} +2 -2
- package/tests/{matcher.test.ts → core/matcher.test.ts} +1 -1
- package/tests/{md-mirror.test.ts → core/md-mirror.test.ts} +2 -2
- package/tests/core/memory-engine-services.test.ts +240 -0
- package/tests/core/memory-ingest-service.test.ts +296 -0
- package/tests/core/memory-query-service.test.ts +129 -0
- package/tests/core/memory-runtime-service.test.ts +201 -0
- package/tests/core/memory-service-composition.test.ts +192 -0
- package/tests/core/memory-service-config.test.ts +41 -0
- package/tests/core/memory-service-facade.test.ts +30 -0
- package/tests/core/memory-service-registry.test.ts +206 -0
- package/tests/core/product-validation-matrix.test.ts +61 -0
- package/tests/core/project-registry.test.ts +78 -0
- package/tests/core/replay-evaluator.test.ts +181 -0
- package/tests/core/retrieval-analytics-service.test.ts +210 -0
- package/tests/core/retrieval-benchmark.test.ts +93 -0
- package/tests/core/retrieval-disclosure-service.test.ts +264 -0
- package/tests/core/retrieval-orchestrator.test.ts +403 -0
- package/tests/core/retrieval-quality.test.ts +31 -0
- package/tests/core/retrieval-services.test.ts +185 -0
- package/tests/{retriever-fallback-chain.test.ts → core/retriever-fallback-chain.test.ts} +3 -3
- package/tests/{retriever-strategy-scope.test.ts → core/retriever-strategy-scope.test.ts} +70 -3
- package/tests/{retriever.memu-adoption.test.ts → core/retriever.memu-adoption.test.ts} +3 -3
- package/tests/core/session-history-importer-filter.test.ts +78 -0
- package/tests/core/session-qrels.test.ts +250 -0
- package/tests/{sqlite-event-store-replication.test.ts → core/sqlite-event-store-replication.test.ts} +36 -1
- package/tests/core/summary-deriver.test.ts +66 -0
- package/tests/extensions/embedder-warning-suppression.test.ts +53 -0
- package/tests/extensions/endless-memory-extension-boundary.test.ts +17 -0
- package/tests/extensions/endless-memory-services.test.ts +325 -0
- package/tests/extensions/mcp-context-tools.test.ts +905 -0
- package/tests/extensions/mcp-extension-boundary.test.ts +21 -0
- package/tests/extensions/mcp-package-build.test.ts +22 -0
- package/tests/extensions/mcp-project-aware-tools.test.ts +102 -0
- package/tests/extensions/shared-memory-extension-boundary.test.ts +24 -0
- package/tests/extensions/shared-memory-services.test.ts +309 -0
- package/tests/extensions/vector-extension-boundary.test.ts +21 -0
- package/.claude/settings.local.json +0 -25
- package/.npm-cache/_cacache/content-v2/sha512/04/76/c098f88dfe584a2b80870bff7421b05d17d3d9ee1027f77772332a22d3f93a9a57101a2855107f6ad82077a818bba912b2bc317f2361b5ddb09ad284d9ce +0 -0
- package/.npm-cache/_cacache/content-v2/sha512/60/25/d2ecd39cfc7cab58351162814be77f935c6d6491c10c3745d456da7ddb2117ffd90c10e53fe3c0f1ed16b403307841543634504398b16ee4e6b6dd8e0c45 +0 -0
- package/.npm-cache/_cacache/index-v5/2b/9a/7f8f40206ed8a2e0a84efaa953ccaed1f5d001e14b931083f2e7a0738007 +0 -2
- package/.npm-cache/_cacache/index-v5/2e/d9/fcfa5c6a6abdc2a3644ab84a95936047298c465a2f47ee03db8f7fe1e946 +0 -3
- package/.npm-cache/_cacache/index-v5/a9/42/e519633356d12d3d2f19da66a8301016d496c8f5c3e0554124aaa62dc043 +0 -2
- package/.npm-cache/_logs/2026-02-26T12_04_52_729Z-debug-0.log +0 -256
- package/.npm-cache/_logs/2026-02-26T12_05_36_835Z-debug-0.log +0 -18
- package/.npm-cache/_logs/2026-02-26T12_05_45_982Z-debug-0.log +0 -32
- package/.npm-cache/_logs/2026-02-26T12_05_48_515Z-debug-0.log +0 -260
- package/.npm-cache/_logs/2026-02-26T12_05_53_567Z-debug-0.log +0 -69
- package/.npm-cache/_update-notifier-last-checked +0 -0
- package/bootstrap-kb/decisions/decisions.md +0 -244
- package/bootstrap-kb/glossary/glossary.md +0 -46
- package/bootstrap-kb/modules/.claude-plugin.md +0 -22
- package/bootstrap-kb/modules/agents.md.md +0 -15
- package/bootstrap-kb/modules/claude.md.md +0 -15
- package/bootstrap-kb/modules/context.md.md +0 -15
- package/bootstrap-kb/modules/docs.md +0 -18
- package/bootstrap-kb/modules/handoff.md.md +0 -15
- package/bootstrap-kb/modules/package-lock.json.md +0 -15
- package/bootstrap-kb/modules/package.json.md +0 -15
- package/bootstrap-kb/modules/plan.md.md +0 -15
- package/bootstrap-kb/modules/readme.md.md +0 -15
- package/bootstrap-kb/modules/scripts.md +0 -26
- package/bootstrap-kb/modules/spec.md.md +0 -15
- package/bootstrap-kb/modules/specs.md +0 -20
- package/bootstrap-kb/modules/src.md +0 -51
- package/bootstrap-kb/modules/tests.md +0 -42
- package/bootstrap-kb/modules/tsconfig.json.md +0 -15
- package/bootstrap-kb/modules/vitest.config.ts.md +0 -15
- package/bootstrap-kb/overview/overview.md +0 -40
- package/bootstrap-kb/sources/manifest.json +0 -950
- package/bootstrap-kb/sources/manifest.md +0 -227
- package/bootstrap-kb/timeline/timeline.md +0 -57
- package/claude-memory-layer-1.0.14.tgz +0 -0
- package/d.sh +0 -3
- package/deploy.sh +0 -3
- package/dist/ui/app.js +0 -2101
- package/memory/.claude-plugin/commands/2026-02-25.md +0 -263
- package/memory/_index.md +0 -419
- package/memory/agent_response/uncategorized/2026-02-26.md +0 -176
- package/memory/agent_response/uncategorized/2026-03-03.md +0 -14
- package/memory/agent_response/uncategorized/2026-03-04.md +0 -1421
- package/memory/agent_response/uncategorized/2026-03-05.md +0 -157
- package/memory/default/uncategorized/2026-02-25.md +0 -4839
- package/memory/session_summary/uncategorized/2026-02-26.md +0 -13
- package/memory/session_summary/uncategorized/2026-03-03.md +0 -5
- package/memory/session_summary/uncategorized/2026-03-04.md +0 -50
- package/memory/specs/20260207-dashboard-upgrade/2026-02-25.md +0 -142
- package/memory/specs/citations-system/2026-02-25.md +0 -1121
- package/memory/specs/endless-mode/2026-02-25.md +0 -1392
- package/memory/specs/entity-edge-model/2026-02-25.md +0 -1263
- package/memory/specs/evidence-aligner-v2/2026-02-25.md +0 -1028
- package/memory/specs/mcp-desktop-integration/2026-02-25.md +0 -1334
- package/memory/specs/post-tool-use-hook/2026-02-25.md +0 -1164
- package/memory/specs/private-tags/2026-02-25.md +0 -1057
- package/memory/specs/progressive-disclosure/2026-02-25.md +0 -1436
- package/memory/specs/task-entity-system/2026-02-25.md +0 -924
- package/memory/specs/vector-outbox-v2/2026-02-25.md +0 -1510
- package/memory/specs/web-viewer-ui/2026-02-25.md +0 -1709
- package/memory/tool_observation/uncategorized/2026-02-26.md +0 -209
- package/memory/tool_observation/uncategorized/2026-03-03.md +0 -21
- package/memory/tool_observation/uncategorized/2026-03-04.md +0 -1033
- package/memory/tool_observation/uncategorized/2026-03-05.md +0 -33
- package/memory/user_prompt/uncategorized/2026-02-26.md +0 -25
- package/memory/user_prompt/uncategorized/2026-03-04.md +0 -634
- package/memory/user_prompt/uncategorized/2026-03-05.md +0 -6
- package/specs/optional-duckdb/context.md +0 -77
- package/specs/optional-duckdb/plan.md +0 -142
- package/specs/optional-duckdb/spec.md +0 -35
- package/src/ui/app.js +0 -2101
|
@@ -3,479 +3,170 @@
|
|
|
3
3
|
* Coordinates EventStore, VectorStore, Retriever, and Graduation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import * as path from 'path';
|
|
7
6
|
import * as os from 'os';
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
import { SQLiteEventStore } from '../core/sqlite-event-store.js';
|
|
13
|
-
import { VectorStore } from '../core/vector-store.js';
|
|
14
|
-
import { Embedder, getDefaultEmbedder } from '../core/embedder.js';
|
|
15
|
-
import { VectorWorker, createVectorWorker } from '../core/vector-worker.js';
|
|
16
|
-
import { Matcher, getDefaultMatcher } from '../core/matcher.js';
|
|
17
|
-
import { Retriever, createRetriever, RetrievalResult, UnifiedRetrievalResult } from '../core/retriever.js';
|
|
18
|
-
import { GraduationPipeline, createGraduationPipeline } from '../core/graduation.js';
|
|
19
|
-
import { SharedEventStore, createSharedEventStore } from '../core/shared-event-store.js';
|
|
20
|
-
import { SharedStore, createSharedStore } from '../core/shared-store.js';
|
|
21
|
-
import { SharedVectorStore, createSharedVectorStore } from '../core/shared-vector-store.js';
|
|
22
|
-
import { SharedPromoter, createSharedPromoter, PromotionResult } from '../core/shared-promoter.js';
|
|
7
|
+
|
|
8
|
+
import type { RetrievalResult, UnifiedRetrievalResult } from '../core/retriever.js';
|
|
9
|
+
import type { PromotionResult } from '../core/shared-promoter.js';
|
|
10
|
+
import type { SharedMemoryServices } from '../extensions/shared-memory/index.js';
|
|
23
11
|
import type {
|
|
24
|
-
MemoryEventInput,
|
|
25
12
|
AppendResult,
|
|
26
13
|
MemoryEvent,
|
|
27
|
-
Config,
|
|
28
|
-
ConfigSchema,
|
|
29
14
|
ToolObservationPayload,
|
|
30
15
|
MemoryMode,
|
|
31
16
|
EndlessModeConfig,
|
|
32
|
-
EndlessModeConfigSchema,
|
|
33
17
|
WorkingSet,
|
|
34
18
|
ConsolidatedMemory,
|
|
35
19
|
EndlessModeStatus,
|
|
36
|
-
ContextSnapshot,
|
|
37
20
|
ContinuityScore,
|
|
38
21
|
SharedStoreConfig,
|
|
39
22
|
Entry
|
|
40
23
|
} from '../core/types.js';
|
|
41
|
-
import {
|
|
42
|
-
import { WorkingSetStore, createWorkingSetStore } from '../core/working-set-store.js';
|
|
43
|
-
import { ConsolidatedStore, createConsolidatedStore } from '../core/consolidated-store.js';
|
|
44
|
-
import { ConsolidationWorker, createConsolidationWorker } from '../core/consolidation-worker.js';
|
|
45
|
-
import { ContinuityManager, createContinuityManager } from '../core/continuity-manager.js';
|
|
46
|
-
import { GraduationWorker, createGraduationWorker, GraduationRunResult } from '../core/graduation-worker.js';
|
|
47
|
-
import { MarkdownMirror } from '../core/md-mirror.js';
|
|
24
|
+
import type { EndlessMemoryServices } from '../extensions/endless-memory/index.js';
|
|
48
25
|
import {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
} from '../core/
|
|
53
|
-
import {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const hash = hashProjectPath(projectPath);
|
|
104
|
-
return path.join(os.homedir(), '.claude-code', 'memory', 'projects', hash);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ============================================================
|
|
108
|
-
// Session Registry
|
|
109
|
-
// ============================================================
|
|
110
|
-
|
|
111
|
-
const REGISTRY_PATH = path.join(os.homedir(), '.claude-code', 'memory', 'session-registry.json');
|
|
112
|
-
const SHARED_STORAGE_PATH = path.join(os.homedir(), '.claude-code', 'memory', 'shared');
|
|
113
|
-
|
|
114
|
-
export interface SessionRegistryEntry {
|
|
115
|
-
projectPath: string;
|
|
116
|
-
projectHash: string;
|
|
117
|
-
registeredAt: string;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export interface SessionRegistry {
|
|
121
|
-
version: number;
|
|
122
|
-
sessions: Record<string, SessionRegistryEntry>;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function loadSessionRegistry(): SessionRegistry {
|
|
126
|
-
try {
|
|
127
|
-
if (fs.existsSync(REGISTRY_PATH)) {
|
|
128
|
-
const data = fs.readFileSync(REGISTRY_PATH, 'utf-8');
|
|
129
|
-
return JSON.parse(data);
|
|
130
|
-
}
|
|
131
|
-
} catch (error) {
|
|
132
|
-
console.error('Failed to load session registry:', error);
|
|
133
|
-
}
|
|
134
|
-
return { version: 1, sessions: {} };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function saveSessionRegistry(registry: SessionRegistry): void {
|
|
138
|
-
const dir = path.dirname(REGISTRY_PATH);
|
|
139
|
-
if (!fs.existsSync(dir)) {
|
|
140
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Atomic write using temp file
|
|
144
|
-
const tempPath = REGISTRY_PATH + '.tmp';
|
|
145
|
-
fs.writeFileSync(tempPath, JSON.stringify(registry, null, 2));
|
|
146
|
-
fs.renameSync(tempPath, REGISTRY_PATH);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Register a session with its project path
|
|
151
|
-
*/
|
|
152
|
-
export function registerSession(sessionId: string, projectPath: string): void {
|
|
153
|
-
const registry = loadSessionRegistry();
|
|
154
|
-
|
|
155
|
-
registry.sessions[sessionId] = {
|
|
156
|
-
projectPath: normalizePath(projectPath),
|
|
157
|
-
projectHash: hashProjectPath(projectPath),
|
|
158
|
-
registeredAt: new Date().toISOString()
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
// Clean up old sessions (keep last 1000)
|
|
162
|
-
const entries = Object.entries(registry.sessions);
|
|
163
|
-
if (entries.length > 1000) {
|
|
164
|
-
const sorted = entries.sort((a, b) =>
|
|
165
|
-
new Date(b[1].registeredAt).getTime() - new Date(a[1].registeredAt).getTime()
|
|
166
|
-
);
|
|
167
|
-
registry.sessions = Object.fromEntries(sorted.slice(0, 1000));
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
saveSessionRegistry(registry);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Get the project path for a session
|
|
175
|
-
*/
|
|
176
|
-
export function getSessionProject(sessionId: string): SessionRegistryEntry | null {
|
|
177
|
-
const registry = loadSessionRegistry();
|
|
178
|
-
return registry.sessions[sessionId] || null;
|
|
179
|
-
}
|
|
26
|
+
type EmbeddingMaintenanceService,
|
|
27
|
+
type EmbeddingModelMaintenanceOptions,
|
|
28
|
+
type EmbeddingModelMaintenanceResult
|
|
29
|
+
} from '../core/engine/embedding-maintenance-service.js';
|
|
30
|
+
import type { MemoryRuntimeService } from '../core/engine/memory-runtime-service.js';
|
|
31
|
+
import type { GraduationRunResult } from '../core/graduation-worker.js';
|
|
32
|
+
import type { IngestInterceptor } from '../core/ingest-interceptor.js';
|
|
33
|
+
import type { MemoryIngestService } from '../core/engine/memory-ingest-service.js';
|
|
34
|
+
import type { MemoryQueryService } from '../core/engine/memory-query-service.js';
|
|
35
|
+
import { createMemoryServiceComposition } from '../core/engine/memory-service-composition.js';
|
|
36
|
+
import {
|
|
37
|
+
getProjectStoragePath as defaultGetProjectStoragePath,
|
|
38
|
+
hashProjectPath as defaultHashProjectPath
|
|
39
|
+
} from '../core/registry/project-path.js';
|
|
40
|
+
import { getSessionProject as defaultGetSessionProject } from '../core/registry/session-registry.js';
|
|
41
|
+
import {
|
|
42
|
+
DEFAULT_ENABLED_SHARED_STORE_CONFIG,
|
|
43
|
+
DEFAULT_SHARED_STORAGE_PATH,
|
|
44
|
+
DISABLED_SHARED_STORE_CONFIG,
|
|
45
|
+
type MemoryServiceConfig
|
|
46
|
+
} from './memory-service-config.js';
|
|
47
|
+
import { createMemoryServiceRegistry } from './memory-service-registry.js';
|
|
48
|
+
import {
|
|
49
|
+
type AccessedMemory,
|
|
50
|
+
type HelpfulMemory,
|
|
51
|
+
type HelpfulnessStats,
|
|
52
|
+
type RecordQueryTraceInput,
|
|
53
|
+
type RetrievalAnalyticsService,
|
|
54
|
+
type RetrievalDisclosureExpansion,
|
|
55
|
+
type RetrievalDisclosureExpandOptions,
|
|
56
|
+
type RetrievalDisclosureSearchOptions,
|
|
57
|
+
type RetrievalDisclosureSearchResponse,
|
|
58
|
+
type RetrievalDisclosureService,
|
|
59
|
+
type RetrievalDisclosureSource,
|
|
60
|
+
type RetrievalOrchestrator,
|
|
61
|
+
type RetrievalTrace,
|
|
62
|
+
type RetrievalTraceStats,
|
|
63
|
+
type RetrieveMemoriesOptions
|
|
64
|
+
} from '../core/engine/retrieval-services.js';
|
|
65
|
+
export { getProjectStoragePath, hashProjectPath } from '../core/registry/project-path.js';
|
|
66
|
+
export {
|
|
67
|
+
getSessionProject,
|
|
68
|
+
registerSession,
|
|
69
|
+
type SessionRegistry,
|
|
70
|
+
type SessionRegistryEntry,
|
|
71
|
+
loadSessionRegistry
|
|
72
|
+
} from '../core/registry/session-registry.js';
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
DEFAULT_ENABLED_SHARED_STORE_CONFIG,
|
|
76
|
+
DEFAULT_SHARED_STORAGE_PATH,
|
|
77
|
+
DISABLED_SHARED_STORE_CONFIG,
|
|
78
|
+
type MemoryServiceConfig
|
|
79
|
+
} from './memory-service-config.js';
|
|
180
80
|
|
|
181
81
|
export class MemoryService {
|
|
182
|
-
|
|
183
|
-
private readonly
|
|
184
|
-
|
|
185
|
-
private readonly
|
|
186
|
-
private readonly
|
|
187
|
-
private readonly matcher: Matcher;
|
|
188
|
-
private readonly retriever: Retriever;
|
|
189
|
-
private readonly graduation: GraduationPipeline;
|
|
190
|
-
private vectorWorker: VectorWorker | null = null;
|
|
191
|
-
private graduationWorker: GraduationWorker | null = null;
|
|
192
|
-
private initialized = false;
|
|
193
|
-
private readonly ingestInterceptors = new IngestInterceptorRegistry();
|
|
82
|
+
private readonly retrievalOrchestrator: RetrievalOrchestrator;
|
|
83
|
+
private readonly retrievalDisclosureService: RetrievalDisclosureService;
|
|
84
|
+
private readonly retrievalAnalyticsService: RetrievalAnalyticsService;
|
|
85
|
+
private readonly embeddingMaintenanceService: EmbeddingMaintenanceService;
|
|
86
|
+
private readonly runtimeService: MemoryRuntimeService;
|
|
194
87
|
|
|
195
88
|
// Endless Mode components
|
|
196
|
-
private
|
|
197
|
-
private consolidatedStore: ConsolidatedStore | null = null;
|
|
198
|
-
private consolidationWorker: ConsolidationWorker | null = null;
|
|
199
|
-
private continuityManager: ContinuityManager | null = null;
|
|
200
|
-
private endlessMode: MemoryMode = 'session';
|
|
89
|
+
private readonly endlessMemoryServices: EndlessMemoryServices;
|
|
201
90
|
|
|
202
91
|
// Shared Store components (cross-project knowledge)
|
|
203
|
-
private
|
|
204
|
-
private sharedStore: SharedStore | null = null;
|
|
205
|
-
private sharedVectorStore: SharedVectorStore | null = null;
|
|
206
|
-
private sharedPromoter: SharedPromoter | null = null;
|
|
207
|
-
private sharedStoreConfig: SharedStoreConfig | null = null;
|
|
92
|
+
private sharedMemoryServices!: SharedMemoryServices;
|
|
208
93
|
private projectHash: string | null = null;
|
|
209
94
|
private projectPath: string | null = null;
|
|
210
95
|
|
|
211
96
|
private readonly readOnly: boolean;
|
|
212
97
|
private readonly lightweightMode: boolean;
|
|
213
98
|
private readonly embeddingOnly: boolean;
|
|
214
|
-
private readonly
|
|
215
|
-
private readonly
|
|
99
|
+
private readonly ingestService: MemoryIngestService;
|
|
100
|
+
private readonly queryService: MemoryQueryService;
|
|
216
101
|
|
|
217
102
|
constructor(config: MemoryServiceConfig & { projectHash?: string; projectPath?: string; sharedStoreConfig?: SharedStoreConfig }) {
|
|
218
|
-
const storagePath = this.expandPath(config.storagePath);
|
|
219
|
-
this.storagePath = storagePath;
|
|
220
103
|
this.readOnly = config.readOnly ?? false;
|
|
221
104
|
this.lightweightMode = config.lightweightMode ?? false;
|
|
222
105
|
this.embeddingOnly = config.embeddingOnly ?? false;
|
|
223
|
-
this.mdMirror = new MarkdownMirror(process.cwd());
|
|
224
|
-
|
|
225
|
-
// Ensure storage directory exists (only if not read-only)
|
|
226
|
-
if (!this.readOnly && !fs.existsSync(storagePath)) {
|
|
227
|
-
fs.mkdirSync(storagePath, { recursive: true });
|
|
228
|
-
}
|
|
229
106
|
|
|
230
107
|
// Store project hash for shared store operations
|
|
231
108
|
this.projectHash = config.projectHash || null;
|
|
232
109
|
this.projectPath = config.projectPath || null;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
this.
|
|
252
|
-
|
|
253
|
-
this.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
this.
|
|
260
|
-
this.graduation = createGraduationPipeline(this.sqliteStore as unknown as EventStore);
|
|
110
|
+
const sharedStoreConfig = config.sharedStoreConfig ?? DEFAULT_ENABLED_SHARED_STORE_CONFIG;
|
|
111
|
+
|
|
112
|
+
const composition = createMemoryServiceComposition({
|
|
113
|
+
config: {
|
|
114
|
+
...config,
|
|
115
|
+
storagePath: config.storagePath,
|
|
116
|
+
readOnly: this.readOnly,
|
|
117
|
+
lightweightMode: this.lightweightMode,
|
|
118
|
+
embeddingOnly: this.embeddingOnly,
|
|
119
|
+
sharedStoreConfig
|
|
120
|
+
},
|
|
121
|
+
defaultSharedStoragePath: DEFAULT_SHARED_STORAGE_PATH,
|
|
122
|
+
defaultSharedStoreConfig: DEFAULT_ENABLED_SHARED_STORE_CONFIG,
|
|
123
|
+
initialize: () => this.initialize(),
|
|
124
|
+
getProjectHash: () => this.projectHash,
|
|
125
|
+
getProjectPath: () => this.projectPath
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
this.retrievalOrchestrator = composition.retrievalOrchestrator;
|
|
129
|
+
this.retrievalDisclosureService = composition.retrievalDisclosureService;
|
|
130
|
+
this.retrievalAnalyticsService = composition.retrievalAnalyticsService;
|
|
131
|
+
this.ingestService = composition.ingestService;
|
|
132
|
+
this.queryService = composition.queryService;
|
|
133
|
+
this.endlessMemoryServices = composition.endlessMemoryServices;
|
|
134
|
+
this.sharedMemoryServices = composition.sharedMemoryServices;
|
|
135
|
+
this.runtimeService = composition.runtimeService;
|
|
136
|
+
this.embeddingMaintenanceService = composition.embeddingMaintenanceService;
|
|
261
137
|
}
|
|
262
138
|
|
|
263
139
|
/**
|
|
264
140
|
* Initialize all components
|
|
265
141
|
*/
|
|
266
142
|
async initialize(): Promise<void> {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
// Initialize PRIMARY store: SQLite (always)
|
|
270
|
-
await this.sqliteStore.initialize();
|
|
271
|
-
|
|
272
|
-
// Lightweight mode: only SQLite, no embedder/vector/workers
|
|
273
|
-
// Used for hooks that just need to store data quickly
|
|
274
|
-
if (this.lightweightMode) {
|
|
275
|
-
this.initialized = true;
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
await this.vectorStore.initialize();
|
|
280
|
-
await this.embedder.initialize();
|
|
281
|
-
|
|
282
|
-
// Skip write-related workers in read-only mode
|
|
283
|
-
if (!this.readOnly) {
|
|
284
|
-
// Start vector worker (uses SQLite as source)
|
|
285
|
-
this.vectorWorker = createVectorWorker(
|
|
286
|
-
this.sqliteStore as unknown as EventStore,
|
|
287
|
-
this.vectorStore,
|
|
288
|
-
this.embedder
|
|
289
|
-
);
|
|
290
|
-
this.vectorWorker.start();
|
|
291
|
-
|
|
292
|
-
if (!this.embeddingOnly) {
|
|
293
|
-
// Connect graduation pipeline to retriever for access tracking
|
|
294
|
-
this.retriever.setGraduationPipeline(this.graduation);
|
|
295
|
-
|
|
296
|
-
// Start graduation worker for automatic level promotion
|
|
297
|
-
this.graduationWorker = createGraduationWorker(
|
|
298
|
-
this.sqliteStore as unknown as EventStore,
|
|
299
|
-
this.graduation
|
|
300
|
-
);
|
|
301
|
-
this.graduationWorker.start();
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Load endless mode setting
|
|
306
|
-
const savedMode = await this.sqliteStore.getEndlessConfig('mode') as MemoryMode | null;
|
|
307
|
-
if (savedMode === 'endless') {
|
|
308
|
-
this.endlessMode = 'endless';
|
|
309
|
-
await this.initializeEndlessMode();
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Initialize shared store (enabled by default)
|
|
313
|
-
if (this.sharedStoreConfig?.enabled !== false) {
|
|
314
|
-
await this.initializeSharedStore();
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
this.initialized = true;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Initialize Shared Store components
|
|
323
|
-
*/
|
|
324
|
-
private async initializeSharedStore(): Promise<void> {
|
|
325
|
-
const sharedPath = this.sharedStoreConfig?.sharedStoragePath
|
|
326
|
-
? this.expandPath(this.sharedStoreConfig.sharedStoragePath)
|
|
327
|
-
: SHARED_STORAGE_PATH;
|
|
328
|
-
|
|
329
|
-
// Ensure shared directory exists
|
|
330
|
-
if (!fs.existsSync(sharedPath)) {
|
|
331
|
-
fs.mkdirSync(sharedPath, { recursive: true });
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
this.sharedEventStore = createSharedEventStore(
|
|
335
|
-
path.join(sharedPath, 'shared.duckdb')
|
|
336
|
-
);
|
|
337
|
-
await this.sharedEventStore.initialize();
|
|
338
|
-
|
|
339
|
-
this.sharedStore = createSharedStore(this.sharedEventStore);
|
|
340
|
-
this.sharedVectorStore = createSharedVectorStore(
|
|
341
|
-
path.join(sharedPath, 'vectors')
|
|
342
|
-
);
|
|
343
|
-
await this.sharedVectorStore.initialize();
|
|
344
|
-
|
|
345
|
-
this.sharedPromoter = createSharedPromoter(
|
|
346
|
-
this.sharedStore,
|
|
347
|
-
this.sharedVectorStore,
|
|
348
|
-
this.embedder,
|
|
349
|
-
this.sharedStoreConfig || undefined
|
|
350
|
-
);
|
|
351
|
-
|
|
352
|
-
// Connect shared stores to retriever
|
|
353
|
-
this.retriever.setSharedStores(this.sharedStore, this.sharedVectorStore);
|
|
143
|
+
await this.runtimeService.initialize();
|
|
354
144
|
}
|
|
355
145
|
|
|
356
146
|
registerIngestBefore(interceptor: IngestInterceptor): () => void {
|
|
357
|
-
return this.
|
|
147
|
+
return this.ingestService.registerIngestBefore(interceptor);
|
|
358
148
|
}
|
|
359
149
|
|
|
360
150
|
registerIngestAfter(interceptor: IngestInterceptor): () => void {
|
|
361
|
-
return this.
|
|
151
|
+
return this.ingestService.registerIngestAfter(interceptor);
|
|
362
152
|
}
|
|
363
153
|
|
|
364
154
|
registerIngestOnError(interceptor: IngestInterceptor): () => void {
|
|
365
|
-
return this.
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
private async ingestWithInterceptors(
|
|
369
|
-
operation: 'user_prompt' | 'agent_response' | 'session_summary' | 'tool_observation',
|
|
370
|
-
input: MemoryEventInput,
|
|
371
|
-
onSuccess?: (eventId: string) => Promise<void>
|
|
372
|
-
): Promise<AppendResult> {
|
|
373
|
-
const normalizedInput: MemoryEventInput = {
|
|
374
|
-
...input,
|
|
375
|
-
metadata: mergeHierarchicalMetadata(
|
|
376
|
-
{
|
|
377
|
-
ingest: {
|
|
378
|
-
operation,
|
|
379
|
-
pipeline: 'default',
|
|
380
|
-
ts: new Date().toISOString()
|
|
381
|
-
},
|
|
382
|
-
...(this.projectHash
|
|
383
|
-
? {
|
|
384
|
-
scope: {
|
|
385
|
-
project: {
|
|
386
|
-
hash: this.projectHash,
|
|
387
|
-
...(this.projectPath ? { path: this.projectPath } : {})
|
|
388
|
-
}
|
|
389
|
-
},
|
|
390
|
-
tags: [`proj:${this.projectHash}`]
|
|
391
|
-
}
|
|
392
|
-
: {})
|
|
393
|
-
},
|
|
394
|
-
input.metadata
|
|
395
|
-
)
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
if (this.projectHash && normalizedInput.metadata) {
|
|
399
|
-
const meta = normalizedInput.metadata as Record<string, unknown>;
|
|
400
|
-
const currentTags = Array.isArray(meta.tags)
|
|
401
|
-
? meta.tags.filter((x): x is string => typeof x === 'string')
|
|
402
|
-
: [];
|
|
403
|
-
const projectTag = `proj:${this.projectHash}`;
|
|
404
|
-
if (!currentTags.includes(projectTag)) {
|
|
405
|
-
meta.tags = [...currentTags, projectTag];
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
if (normalizedInput.metadata) {
|
|
410
|
-
const meta = normalizedInput.metadata as Record<string, unknown>;
|
|
411
|
-
const normalizedTags = normalizeTags(meta.tags);
|
|
412
|
-
if (normalizedTags.length > 0) {
|
|
413
|
-
meta.tags = normalizedTags;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
await this.ingestInterceptors.run('before', {
|
|
418
|
-
operation,
|
|
419
|
-
sessionId: normalizedInput.sessionId,
|
|
420
|
-
event: normalizedInput
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
try {
|
|
424
|
-
const result = await this.sqliteStore.append(normalizedInput);
|
|
425
|
-
if (result.success && !result.isDuplicate) {
|
|
426
|
-
if (onSuccess) {
|
|
427
|
-
await onSuccess(result.eventId);
|
|
428
|
-
}
|
|
429
|
-
try {
|
|
430
|
-
await this.mdMirror.append(normalizedInput, result.eventId);
|
|
431
|
-
} catch {
|
|
432
|
-
// non-breaking markdown mirror write
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
await this.ingestInterceptors.run('after', {
|
|
437
|
-
operation,
|
|
438
|
-
sessionId: normalizedInput.sessionId,
|
|
439
|
-
event: normalizedInput
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
return result;
|
|
443
|
-
} catch (error) {
|
|
444
|
-
const normalizedError = error instanceof Error ? error : new Error(String(error));
|
|
445
|
-
await this.ingestInterceptors.run('error', {
|
|
446
|
-
operation,
|
|
447
|
-
sessionId: normalizedInput.sessionId,
|
|
448
|
-
event: normalizedInput,
|
|
449
|
-
error: normalizedError
|
|
450
|
-
});
|
|
451
|
-
throw error;
|
|
452
|
-
}
|
|
155
|
+
return this.ingestService.registerIngestOnError(interceptor);
|
|
453
156
|
}
|
|
454
157
|
|
|
455
158
|
/**
|
|
456
159
|
* Start a new session
|
|
457
160
|
*/
|
|
458
161
|
async startSession(sessionId: string, projectPath?: string): Promise<void> {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
await this.sqliteStore.upsertSession({
|
|
462
|
-
id: sessionId,
|
|
463
|
-
startedAt: new Date(),
|
|
464
|
-
projectPath
|
|
465
|
-
});
|
|
162
|
+
return this.ingestService.startSession(sessionId, projectPath);
|
|
466
163
|
}
|
|
467
164
|
|
|
468
165
|
/**
|
|
469
166
|
* End a session
|
|
470
167
|
*/
|
|
471
168
|
async endSession(sessionId: string, summary?: string): Promise<void> {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
await this.sqliteStore.upsertSession({
|
|
475
|
-
id: sessionId,
|
|
476
|
-
endedAt: new Date(),
|
|
477
|
-
summary
|
|
478
|
-
});
|
|
169
|
+
return this.ingestService.endSession(sessionId, summary);
|
|
479
170
|
}
|
|
480
171
|
|
|
481
172
|
/**
|
|
@@ -486,21 +177,7 @@ export class MemoryService {
|
|
|
486
177
|
content: string,
|
|
487
178
|
metadata?: Record<string, unknown>
|
|
488
179
|
): Promise<AppendResult> {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
return this.ingestWithInterceptors(
|
|
492
|
-
'user_prompt',
|
|
493
|
-
{
|
|
494
|
-
eventType: 'user_prompt',
|
|
495
|
-
sessionId,
|
|
496
|
-
timestamp: new Date(),
|
|
497
|
-
content,
|
|
498
|
-
metadata
|
|
499
|
-
},
|
|
500
|
-
async (eventId) => {
|
|
501
|
-
await this.sqliteStore.enqueueForEmbedding(eventId, content);
|
|
502
|
-
}
|
|
503
|
-
);
|
|
180
|
+
return this.ingestService.storeUserPrompt(sessionId, content, metadata);
|
|
504
181
|
}
|
|
505
182
|
|
|
506
183
|
/**
|
|
@@ -511,21 +188,7 @@ export class MemoryService {
|
|
|
511
188
|
content: string,
|
|
512
189
|
metadata?: Record<string, unknown>
|
|
513
190
|
): Promise<AppendResult> {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
return this.ingestWithInterceptors(
|
|
517
|
-
'agent_response',
|
|
518
|
-
{
|
|
519
|
-
eventType: 'agent_response',
|
|
520
|
-
sessionId,
|
|
521
|
-
timestamp: new Date(),
|
|
522
|
-
content,
|
|
523
|
-
metadata
|
|
524
|
-
},
|
|
525
|
-
async (eventId) => {
|
|
526
|
-
await this.sqliteStore.enqueueForEmbedding(eventId, content);
|
|
527
|
-
}
|
|
528
|
-
);
|
|
191
|
+
return this.ingestService.storeAgentResponse(sessionId, content, metadata);
|
|
529
192
|
}
|
|
530
193
|
|
|
531
194
|
/**
|
|
@@ -536,21 +199,7 @@ export class MemoryService {
|
|
|
536
199
|
summary: string,
|
|
537
200
|
metadata?: Record<string, unknown>
|
|
538
201
|
): Promise<AppendResult> {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
return this.ingestWithInterceptors(
|
|
542
|
-
'session_summary',
|
|
543
|
-
{
|
|
544
|
-
eventType: 'session_summary',
|
|
545
|
-
sessionId,
|
|
546
|
-
timestamp: new Date(),
|
|
547
|
-
content: summary,
|
|
548
|
-
metadata
|
|
549
|
-
},
|
|
550
|
-
async (eventId) => {
|
|
551
|
-
await this.sqliteStore.enqueueForEmbedding(eventId, summary);
|
|
552
|
-
}
|
|
553
|
-
);
|
|
202
|
+
return this.ingestService.storeSessionSummary(sessionId, summary, metadata);
|
|
554
203
|
}
|
|
555
204
|
|
|
556
205
|
/**
|
|
@@ -558,17 +207,7 @@ export class MemoryService {
|
|
|
558
207
|
* Called from session-start hook to catch sessions that ended without Stop hook.
|
|
559
208
|
*/
|
|
560
209
|
async backfillMissingSummaries(currentSessionId: string, limit = 5): Promise<void> {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
// Get recent sessions that don't have a summary event
|
|
564
|
-
const recentSessionIds = await this.sqliteStore.getSessionsWithoutSummary(currentSessionId, limit);
|
|
565
|
-
for (const sid of recentSessionIds) {
|
|
566
|
-
try {
|
|
567
|
-
await this.generateSessionSummary(sid);
|
|
568
|
-
} catch {
|
|
569
|
-
// non-critical
|
|
570
|
-
}
|
|
571
|
-
}
|
|
210
|
+
return this.ingestService.backfillMissingSummaries(currentSessionId, limit);
|
|
572
211
|
}
|
|
573
212
|
|
|
574
213
|
/**
|
|
@@ -577,41 +216,7 @@ export class MemoryService {
|
|
|
577
216
|
* Skips if a summary already exists for this session.
|
|
578
217
|
*/
|
|
579
218
|
async generateSessionSummary(sessionId: string): Promise<void> {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
const events = await this.sqliteStore.getSessionEvents(sessionId);
|
|
583
|
-
if (events.length < 3) return; // Too short to summarize
|
|
584
|
-
|
|
585
|
-
// Skip if summary already exists
|
|
586
|
-
const hasSummary = events.some((e) => e.eventType === 'session_summary');
|
|
587
|
-
if (hasSummary) return;
|
|
588
|
-
|
|
589
|
-
const prompts = events.filter((e) => e.eventType === 'user_prompt');
|
|
590
|
-
const toolObs = events.filter((e) => e.eventType === 'tool_observation');
|
|
591
|
-
const toolNames = [...new Set(
|
|
592
|
-
toolObs.map((e) => (e.metadata as Record<string, unknown>)?.toolName as string).filter(Boolean)
|
|
593
|
-
)];
|
|
594
|
-
const errorObs = toolObs.filter((e) => {
|
|
595
|
-
const meta = e.metadata as Record<string, unknown>;
|
|
596
|
-
return meta?.exitCode !== undefined && meta.exitCode !== 0;
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
const datePart = events[0].timestamp.toISOString().split('T')[0];
|
|
600
|
-
const parts: string[] = [`[${datePart}] ${prompts.length}턴 세션.`];
|
|
601
|
-
|
|
602
|
-
if (prompts.length > 0) {
|
|
603
|
-
const firstPrompt = prompts[0].content.slice(0, 120).replace(/\n/g, ' ');
|
|
604
|
-
parts.push(`주요 작업: ${firstPrompt}`);
|
|
605
|
-
}
|
|
606
|
-
if (toolNames.length > 0) {
|
|
607
|
-
parts.push(`사용 툴: ${toolNames.slice(0, 6).join(', ')}`);
|
|
608
|
-
}
|
|
609
|
-
if (errorObs.length > 0) {
|
|
610
|
-
parts.push(`오류 ${errorObs.length}건 발생`);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
const summary = parts.join('. ');
|
|
614
|
-
await this.storeSessionSummary(sessionId, summary, { generated: 'rule-based', eventCount: events.length });
|
|
219
|
+
return this.ingestService.generateSessionSummary(sessionId);
|
|
615
220
|
}
|
|
616
221
|
|
|
617
222
|
/**
|
|
@@ -621,36 +226,7 @@ export class MemoryService {
|
|
|
621
226
|
sessionId: string,
|
|
622
227
|
payload: ToolObservationPayload
|
|
623
228
|
): Promise<AppendResult> {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
// Create content for storage (JSON stringified payload)
|
|
627
|
-
const content = JSON.stringify(payload);
|
|
628
|
-
|
|
629
|
-
// Extract turnId from payload metadata if present (set by PostToolUse hook)
|
|
630
|
-
const turnId = payload.metadata?.turnId;
|
|
631
|
-
|
|
632
|
-
return this.ingestWithInterceptors(
|
|
633
|
-
'tool_observation',
|
|
634
|
-
{
|
|
635
|
-
eventType: 'tool_observation',
|
|
636
|
-
sessionId,
|
|
637
|
-
timestamp: new Date(),
|
|
638
|
-
content,
|
|
639
|
-
metadata: {
|
|
640
|
-
toolName: payload.toolName,
|
|
641
|
-
success: payload.success,
|
|
642
|
-
...(turnId ? { turnId } : {})
|
|
643
|
-
}
|
|
644
|
-
},
|
|
645
|
-
async (eventId) => {
|
|
646
|
-
const embeddingContent = createToolObservationEmbedding(
|
|
647
|
-
payload.toolName,
|
|
648
|
-
payload.metadata || {},
|
|
649
|
-
payload.success
|
|
650
|
-
);
|
|
651
|
-
await this.sqliteStore.enqueueForEmbedding(eventId, embeddingContent);
|
|
652
|
-
}
|
|
653
|
-
);
|
|
229
|
+
return this.ingestService.storeToolObservation(sessionId, payload);
|
|
654
230
|
}
|
|
655
231
|
|
|
656
232
|
/**
|
|
@@ -658,193 +234,36 @@ export class MemoryService {
|
|
|
658
234
|
*/
|
|
659
235
|
async retrieveMemories(
|
|
660
236
|
query: string,
|
|
661
|
-
options?:
|
|
662
|
-
topK?: number;
|
|
663
|
-
minScore?: number;
|
|
664
|
-
sessionId?: string;
|
|
665
|
-
includeShared?: boolean;
|
|
666
|
-
adaptiveRerank?: boolean;
|
|
667
|
-
intentRewrite?: boolean;
|
|
668
|
-
projectScopeMode?: 'strict' | 'prefer' | 'global';
|
|
669
|
-
allowedProjectHashes?: string[];
|
|
670
|
-
}
|
|
237
|
+
options?: RetrieveMemoriesOptions
|
|
671
238
|
): Promise<UnifiedRetrievalResult> {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
try {
|
|
704
|
-
const selectedEventIds = result.memories.map((m) => m.event.id);
|
|
705
|
-
const selectedDetails = (result.selectedDebug || []).map((d) => ({
|
|
706
|
-
eventId: d.eventId,
|
|
707
|
-
score: d.score,
|
|
708
|
-
semanticScore: d.semanticScore,
|
|
709
|
-
lexicalScore: d.lexicalScore,
|
|
710
|
-
recencyScore: d.recencyScore,
|
|
711
|
-
}));
|
|
712
|
-
const candidateDetails = (result.candidateDebug || []).map((d) => ({
|
|
713
|
-
eventId: d.eventId,
|
|
714
|
-
score: d.score,
|
|
715
|
-
semanticScore: d.semanticScore,
|
|
716
|
-
lexicalScore: d.lexicalScore,
|
|
717
|
-
recencyScore: d.recencyScore,
|
|
718
|
-
}));
|
|
719
|
-
const candidateEventIds = candidateDetails.length > 0
|
|
720
|
-
? candidateDetails.map((d) => d.eventId)
|
|
721
|
-
: selectedEventIds;
|
|
722
|
-
await this.sqliteStore.recordRetrievalTrace({
|
|
723
|
-
sessionId: options?.sessionId,
|
|
724
|
-
projectHash: this.projectHash || undefined,
|
|
725
|
-
queryText: query,
|
|
726
|
-
strategy: options?.strategy || 'auto',
|
|
727
|
-
candidateEventIds,
|
|
728
|
-
selectedEventIds,
|
|
729
|
-
candidateDetails,
|
|
730
|
-
selectedDetails,
|
|
731
|
-
confidence: result.matchResult.confidence,
|
|
732
|
-
fallbackTrace: result.fallbackTrace || []
|
|
733
|
-
});
|
|
734
|
-
} catch {
|
|
735
|
-
// non-blocking telemetry
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
return result;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
private getConfiguredRerankWeights(): { semantic: number; lexical: number; recency: number } | undefined {
|
|
742
|
-
const semantic = Number(process.env.MEMORY_RERANK_WEIGHT_SEMANTIC ?? '');
|
|
743
|
-
const lexical = Number(process.env.MEMORY_RERANK_WEIGHT_LEXICAL ?? '');
|
|
744
|
-
const recency = Number(process.env.MEMORY_RERANK_WEIGHT_RECENCY ?? '');
|
|
745
|
-
|
|
746
|
-
const allFinite = [semantic, lexical, recency].every((v) => Number.isFinite(v));
|
|
747
|
-
if (!allFinite) return undefined;
|
|
748
|
-
|
|
749
|
-
const nonNegative = [semantic, lexical, recency].every((v) => v >= 0);
|
|
750
|
-
const total = semantic + lexical + recency;
|
|
751
|
-
if (!nonNegative || total <= 0) return undefined;
|
|
752
|
-
|
|
753
|
-
return {
|
|
754
|
-
semantic: semantic / total,
|
|
755
|
-
lexical: lexical / total,
|
|
756
|
-
recency: recency / total,
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
private async getRerankWeights(adaptive: boolean): Promise<{ semantic: number; lexical: number; recency: number } | undefined> {
|
|
761
|
-
const configured = this.getConfiguredRerankWeights();
|
|
762
|
-
if (configured) return configured;
|
|
763
|
-
if (adaptive) return this.getAdaptiveRerankWeights();
|
|
764
|
-
return undefined;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
private async rewriteQueryIntent(query: string): Promise<string | null> {
|
|
768
|
-
if (process.env.MEMORY_INTENT_REWRITE_ENABLED !== '1') return null;
|
|
769
|
-
|
|
770
|
-
const apiUrl = process.env.COMPANY_STOCK_API_URL || process.env.COMPANY_INT_API_URL;
|
|
771
|
-
if (!apiUrl) return null;
|
|
772
|
-
|
|
773
|
-
const controller = new AbortController();
|
|
774
|
-
const timeoutMs = Number(process.env.MEMORY_INTENT_REWRITE_TIMEOUT_MS || 5000);
|
|
775
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
776
|
-
|
|
777
|
-
try {
|
|
778
|
-
const prompt = [
|
|
779
|
-
'Rewrite user query for memory retrieval intent expansion.',
|
|
780
|
-
'Return plain text only, one line, no markdown.',
|
|
781
|
-
`Query: ${query}`,
|
|
782
|
-
].join('\n');
|
|
783
|
-
|
|
784
|
-
const res = await fetch(apiUrl, {
|
|
785
|
-
method: 'POST',
|
|
786
|
-
headers: {
|
|
787
|
-
'Content-Type': 'application/json',
|
|
788
|
-
Accept: '*/*',
|
|
789
|
-
Origin: process.env.COMPANY_INT_ORIGIN || 'http://company-int.aplusai.ai',
|
|
790
|
-
Referer: process.env.COMPANY_INT_REFERER || 'http://company-int.aplusai.ai/',
|
|
791
|
-
},
|
|
792
|
-
body: JSON.stringify({
|
|
793
|
-
question: prompt,
|
|
794
|
-
company_name: null,
|
|
795
|
-
conversation_id: null,
|
|
796
|
-
}),
|
|
797
|
-
signal: controller.signal,
|
|
798
|
-
});
|
|
799
|
-
|
|
800
|
-
const text = (await res.text()).trim();
|
|
801
|
-
if (!text) return null;
|
|
802
|
-
|
|
803
|
-
const oneLine = text
|
|
804
|
-
.replace(/^data:\s*/gm, '')
|
|
805
|
-
.split(/\r?\n/)
|
|
806
|
-
.map((x) => x.trim())
|
|
807
|
-
.filter(Boolean)
|
|
808
|
-
.join(' ')
|
|
809
|
-
.slice(0, 240);
|
|
810
|
-
|
|
811
|
-
if (!oneLine || oneLine.toLowerCase() === query.toLowerCase()) return null;
|
|
812
|
-
return oneLine;
|
|
813
|
-
} catch {
|
|
814
|
-
return null;
|
|
815
|
-
} finally {
|
|
816
|
-
clearTimeout(timeout);
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
private async getAdaptiveRerankWeights(): Promise<{ semantic: number; lexical: number; recency: number } | undefined> {
|
|
821
|
-
try {
|
|
822
|
-
const s = await this.sqliteStore.getHelpfulnessStats();
|
|
823
|
-
if (s.totalEvaluated < 20) return undefined;
|
|
824
|
-
|
|
825
|
-
// base weights
|
|
826
|
-
let semantic = 0.7;
|
|
827
|
-
let lexical = 0.2;
|
|
828
|
-
let recency = 0.1;
|
|
829
|
-
|
|
830
|
-
if (s.avgScore < 0.45) {
|
|
831
|
-
semantic -= 0.1;
|
|
832
|
-
lexical += 0.1;
|
|
833
|
-
} else if (s.avgScore > 0.75) {
|
|
834
|
-
semantic += 0.05;
|
|
835
|
-
lexical -= 0.05;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
if (s.unhelpful > s.helpful) {
|
|
839
|
-
recency += 0.05;
|
|
840
|
-
semantic -= 0.03;
|
|
841
|
-
lexical -= 0.02;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
return { semantic, lexical, recency };
|
|
845
|
-
} catch {
|
|
846
|
-
return undefined;
|
|
847
|
-
}
|
|
239
|
+
return this.retrievalOrchestrator.retrieveMemories(query, options);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Layer 1 retrieval disclosure: lightweight search envelopes for UI/API/agent use.
|
|
244
|
+
*/
|
|
245
|
+
async searchDisclosure(
|
|
246
|
+
query: string,
|
|
247
|
+
options?: RetrievalDisclosureSearchOptions
|
|
248
|
+
): Promise<RetrievalDisclosureSearchResponse> {
|
|
249
|
+
return this.retrievalDisclosureService.search(query, options);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Layer 2 retrieval disclosure: expand a search result into surrounding timeline context.
|
|
254
|
+
*/
|
|
255
|
+
async expandDisclosure(
|
|
256
|
+
resultId: string,
|
|
257
|
+
options?: RetrievalDisclosureExpandOptions
|
|
258
|
+
): Promise<RetrievalDisclosureExpansion | null> {
|
|
259
|
+
return this.retrievalDisclosureService.expand(resultId, options);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Layer 3 retrieval disclosure: resolve a search result to its raw source event.
|
|
264
|
+
*/
|
|
265
|
+
async sourceDisclosure(resultId: string): Promise<RetrievalDisclosureSource | null> {
|
|
266
|
+
return this.retrievalDisclosureService.source(resultId);
|
|
848
267
|
}
|
|
849
268
|
|
|
850
269
|
/**
|
|
@@ -855,44 +274,28 @@ export class MemoryService {
|
|
|
855
274
|
query: string,
|
|
856
275
|
options?: { topK?: number; minScore?: number }
|
|
857
276
|
): Promise<Array<{event: MemoryEvent; score: number}>> {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
const results = await this.sqliteStore.keywordSearch(query, options?.topK ?? 10);
|
|
861
|
-
|
|
862
|
-
// Normalize FTS5 rank to a score (0-1 range)
|
|
863
|
-
// FTS5 rank is negative (higher is worse), so we convert it
|
|
864
|
-
const maxRank = Math.min(...results.map(r => r.rank), -0.001);
|
|
865
|
-
const minRank = Math.max(...results.map(r => r.rank), -1000);
|
|
866
|
-
const rankRange = maxRank - minRank || 1;
|
|
867
|
-
|
|
868
|
-
return results.map(r => ({
|
|
869
|
-
event: r.event,
|
|
870
|
-
score: 1 - (r.rank - minRank) / rankRange // Normalize to 0-1
|
|
871
|
-
})).filter(r => !options?.minScore || r.score >= options.minScore);
|
|
277
|
+
return this.queryService.keywordSearch(query, options);
|
|
872
278
|
}
|
|
873
279
|
|
|
874
280
|
/**
|
|
875
281
|
* Rebuild FTS index (call after database upgrade)
|
|
876
282
|
*/
|
|
877
283
|
async rebuildFtsIndex(): Promise<number> {
|
|
878
|
-
|
|
879
|
-
return this.sqliteStore.rebuildFtsIndex();
|
|
284
|
+
return this.queryService.rebuildFtsIndex();
|
|
880
285
|
}
|
|
881
286
|
|
|
882
287
|
/**
|
|
883
288
|
* Get session history
|
|
884
289
|
*/
|
|
885
290
|
async getSessionHistory(sessionId: string): Promise<MemoryEvent[]> {
|
|
886
|
-
|
|
887
|
-
return this.sqliteStore.getSessionEvents(sessionId);
|
|
291
|
+
return this.queryService.getSessionHistory(sessionId);
|
|
888
292
|
}
|
|
889
293
|
|
|
890
294
|
/**
|
|
891
295
|
* Get recent events
|
|
892
296
|
*/
|
|
893
297
|
async getRecentEvents(limit: number = 100): Promise<MemoryEvent[]> {
|
|
894
|
-
|
|
895
|
-
return this.sqliteStore.getRecentEvents(limit);
|
|
298
|
+
return this.queryService.getRecentEvents(limit);
|
|
896
299
|
}
|
|
897
300
|
|
|
898
301
|
/**
|
|
@@ -903,23 +306,15 @@ export class MemoryService {
|
|
|
903
306
|
embedding: { pending: number; processing: number; failed: number; total: number };
|
|
904
307
|
vector: { pending: number; processing: number; failed: number; total: number };
|
|
905
308
|
}> {
|
|
906
|
-
|
|
907
|
-
return this.sqliteStore.getOutboxStats();
|
|
309
|
+
return this.queryService.getOutboxStats();
|
|
908
310
|
}
|
|
909
311
|
|
|
910
|
-
async getRetrievalTraceStats(): Promise<{
|
|
911
|
-
|
|
912
|
-
avgCandidateCount: number;
|
|
913
|
-
avgSelectedCount: number;
|
|
914
|
-
selectionRate: number;
|
|
915
|
-
}> {
|
|
916
|
-
await this.initialize();
|
|
917
|
-
return this.sqliteStore.getRetrievalTraceStats();
|
|
312
|
+
async getRetrievalTraceStats(): Promise<RetrievalTraceStats> {
|
|
313
|
+
return this.retrievalAnalyticsService.getRetrievalTraceStats();
|
|
918
314
|
}
|
|
919
315
|
|
|
920
|
-
async getRecentRetrievalTraces(limit: number = 50) {
|
|
921
|
-
|
|
922
|
-
return this.sqliteStore.getRecentRetrievalTraces(limit);
|
|
316
|
+
async getRecentRetrievalTraces(limit: number = 50): Promise<RetrievalTrace[]> {
|
|
317
|
+
return this.retrievalAnalyticsService.getRecentRetrievalTraces(limit);
|
|
923
318
|
}
|
|
924
319
|
|
|
925
320
|
async getStats(): Promise<{
|
|
@@ -927,63 +322,35 @@ export class MemoryService {
|
|
|
927
322
|
vectorCount: number;
|
|
928
323
|
levelStats: Array<{ level: string; count: number }>;
|
|
929
324
|
}> {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
const recentEvents = await this.sqliteStore.getRecentEvents(10000);
|
|
933
|
-
const vectorCount = await this.vectorStore.count();
|
|
934
|
-
const levelStats = await this.graduation.getStats();
|
|
935
|
-
|
|
936
|
-
return {
|
|
937
|
-
totalEvents: recentEvents.length,
|
|
938
|
-
vectorCount,
|
|
939
|
-
levelStats
|
|
940
|
-
};
|
|
325
|
+
return this.queryService.getStats();
|
|
941
326
|
}
|
|
942
327
|
|
|
943
328
|
/**
|
|
944
329
|
* Process pending embeddings
|
|
945
330
|
*/
|
|
946
331
|
async processPendingEmbeddings(): Promise<number> {
|
|
947
|
-
|
|
948
|
-
return this.vectorWorker.processAll();
|
|
949
|
-
}
|
|
950
|
-
return 0;
|
|
332
|
+
return this.runtimeService.processPendingEmbeddings();
|
|
951
333
|
}
|
|
952
334
|
|
|
953
335
|
/**
|
|
954
336
|
* Get events by memory level
|
|
955
337
|
*/
|
|
956
338
|
async getEventsByLevel(level: string, options?: { limit?: number; offset?: number }): Promise<MemoryEvent[]> {
|
|
957
|
-
|
|
958
|
-
return this.sqliteStore.getEventsByLevel(level, options);
|
|
339
|
+
return this.queryService.getEventsByLevel(level, options);
|
|
959
340
|
}
|
|
960
341
|
|
|
961
342
|
/**
|
|
962
343
|
* Get memory level for a specific event
|
|
963
344
|
*/
|
|
964
345
|
async getEventLevel(eventId: string): Promise<string | null> {
|
|
965
|
-
|
|
966
|
-
return this.sqliteStore.getEventLevel(eventId);
|
|
346
|
+
return this.queryService.getEventLevel(eventId);
|
|
967
347
|
}
|
|
968
348
|
|
|
969
349
|
/**
|
|
970
350
|
* Format retrieval results as context for Claude
|
|
971
351
|
*/
|
|
972
352
|
formatAsContext(result: RetrievalResult): string {
|
|
973
|
-
|
|
974
|
-
return '';
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
const confidence = result.matchResult.confidence;
|
|
978
|
-
let header = '';
|
|
979
|
-
|
|
980
|
-
if (confidence === 'high') {
|
|
981
|
-
header = '🎯 **High-confidence memory match found:**\n\n';
|
|
982
|
-
} else if (confidence === 'suggested') {
|
|
983
|
-
header = '💡 **Suggested memories (may be relevant):**\n\n';
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
return header + result.context;
|
|
353
|
+
return this.retrievalOrchestrator.formatAsContext(result);
|
|
987
354
|
}
|
|
988
355
|
|
|
989
356
|
// ============================================================
|
|
@@ -994,21 +361,14 @@ export class MemoryService {
|
|
|
994
361
|
* Check if shared store is enabled and initialized
|
|
995
362
|
*/
|
|
996
363
|
isSharedStoreEnabled(): boolean {
|
|
997
|
-
return this.
|
|
364
|
+
return this.sharedMemoryServices.isEnabled();
|
|
998
365
|
}
|
|
999
366
|
|
|
1000
367
|
/**
|
|
1001
368
|
* Promote an entry to shared storage
|
|
1002
369
|
*/
|
|
1003
370
|
async promoteToShared(entry: Entry): Promise<PromotionResult> {
|
|
1004
|
-
|
|
1005
|
-
return {
|
|
1006
|
-
success: false,
|
|
1007
|
-
error: 'Shared store not initialized or project hash not set'
|
|
1008
|
-
};
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
return this.sharedPromoter.promoteEntry(entry, this.projectHash);
|
|
371
|
+
return this.sharedMemoryServices.promoteToShared(entry, this.projectHash);
|
|
1012
372
|
}
|
|
1013
373
|
|
|
1014
374
|
/**
|
|
@@ -1020,8 +380,7 @@ export class MemoryService {
|
|
|
1020
380
|
topTopics: Array<{ topic: string; count: number }>;
|
|
1021
381
|
totalUsageCount: number;
|
|
1022
382
|
} | null> {
|
|
1023
|
-
|
|
1024
|
-
return this.sharedStore.getStats();
|
|
383
|
+
return this.sharedMemoryServices.getStats();
|
|
1025
384
|
}
|
|
1026
385
|
|
|
1027
386
|
/**
|
|
@@ -1031,8 +390,7 @@ export class MemoryService {
|
|
|
1031
390
|
query: string,
|
|
1032
391
|
options?: { topK?: number; minConfidence?: number }
|
|
1033
392
|
) {
|
|
1034
|
-
|
|
1035
|
-
return this.sharedStore.search(query, options);
|
|
393
|
+
return this.sharedMemoryServices.search(query, options);
|
|
1036
394
|
}
|
|
1037
395
|
|
|
1038
396
|
/**
|
|
@@ -1046,119 +404,60 @@ export class MemoryService {
|
|
|
1046
404
|
// Endless Mode Methods
|
|
1047
405
|
// ============================================================
|
|
1048
406
|
|
|
1049
|
-
/**
|
|
1050
|
-
* Get the default endless mode config
|
|
1051
|
-
*/
|
|
1052
|
-
private getDefaultEndlessConfig(): EndlessModeConfig {
|
|
1053
|
-
return {
|
|
1054
|
-
enabled: true,
|
|
1055
|
-
workingSet: {
|
|
1056
|
-
maxEvents: 100,
|
|
1057
|
-
timeWindowHours: 24,
|
|
1058
|
-
minRelevanceScore: 0.5
|
|
1059
|
-
},
|
|
1060
|
-
consolidation: {
|
|
1061
|
-
triggerIntervalMs: 3600000, // 1 hour
|
|
1062
|
-
triggerEventCount: 100,
|
|
1063
|
-
triggerIdleMs: 1800000, // 30 minutes
|
|
1064
|
-
useLLMSummarization: false
|
|
1065
|
-
},
|
|
1066
|
-
continuity: {
|
|
1067
|
-
minScoreForSeamless: 0.7,
|
|
1068
|
-
topicDecayHours: 48
|
|
1069
|
-
}
|
|
1070
|
-
};
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
407
|
/**
|
|
1074
408
|
* Initialize Endless Mode components
|
|
1075
409
|
*/
|
|
1076
410
|
async initializeEndlessMode(): Promise<void> {
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
this.workingSetStore = createWorkingSetStore(this.sqliteStore, config);
|
|
1080
|
-
this.consolidatedStore = createConsolidatedStore(this.sqliteStore);
|
|
1081
|
-
this.consolidationWorker = createConsolidationWorker(
|
|
1082
|
-
this.workingSetStore,
|
|
1083
|
-
this.consolidatedStore,
|
|
1084
|
-
config
|
|
1085
|
-
);
|
|
1086
|
-
this.continuityManager = createContinuityManager(this.sqliteStore, config);
|
|
1087
|
-
|
|
1088
|
-
// Start consolidation worker
|
|
1089
|
-
this.consolidationWorker.start();
|
|
411
|
+
return this.endlessMemoryServices.initializeEndlessMode();
|
|
1090
412
|
}
|
|
1091
413
|
|
|
1092
414
|
/**
|
|
1093
415
|
* Get Endless Mode configuration
|
|
1094
416
|
*/
|
|
1095
417
|
async getEndlessConfig(): Promise<EndlessModeConfig> {
|
|
1096
|
-
|
|
1097
|
-
return savedConfig || this.getDefaultEndlessConfig();
|
|
418
|
+
return this.endlessMemoryServices.getEndlessConfig();
|
|
1098
419
|
}
|
|
1099
420
|
|
|
1100
421
|
/**
|
|
1101
422
|
* Set Endless Mode configuration
|
|
1102
423
|
*/
|
|
1103
424
|
async setEndlessConfig(config: Partial<EndlessModeConfig>): Promise<void> {
|
|
1104
|
-
|
|
1105
|
-
const merged = { ...current, ...config };
|
|
1106
|
-
await this.sqliteStore.setEndlessConfig('config', merged);
|
|
425
|
+
return this.endlessMemoryServices.setEndlessConfig(config);
|
|
1107
426
|
}
|
|
1108
427
|
|
|
1109
428
|
/**
|
|
1110
429
|
* Set memory mode (session or endless)
|
|
1111
430
|
*/
|
|
1112
431
|
async setMode(mode: MemoryMode): Promise<void> {
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
if (mode === this.endlessMode) return;
|
|
1116
|
-
|
|
1117
|
-
this.endlessMode = mode;
|
|
1118
|
-
await this.sqliteStore.setEndlessConfig('mode', mode);
|
|
1119
|
-
|
|
1120
|
-
if (mode === 'endless') {
|
|
1121
|
-
await this.initializeEndlessMode();
|
|
1122
|
-
} else {
|
|
1123
|
-
// Stop endless mode components
|
|
1124
|
-
if (this.consolidationWorker) {
|
|
1125
|
-
this.consolidationWorker.stop();
|
|
1126
|
-
this.consolidationWorker = null;
|
|
1127
|
-
}
|
|
1128
|
-
this.workingSetStore = null;
|
|
1129
|
-
this.consolidatedStore = null;
|
|
1130
|
-
this.continuityManager = null;
|
|
1131
|
-
}
|
|
432
|
+
return this.endlessMemoryServices.setMode(mode);
|
|
1132
433
|
}
|
|
1133
434
|
|
|
1134
435
|
/**
|
|
1135
436
|
* Get current memory mode
|
|
1136
437
|
*/
|
|
1137
438
|
getMode(): MemoryMode {
|
|
1138
|
-
return this.
|
|
439
|
+
return this.endlessMemoryServices.getMode();
|
|
1139
440
|
}
|
|
1140
441
|
|
|
1141
442
|
/**
|
|
1142
443
|
* Check if endless mode is active
|
|
1143
444
|
*/
|
|
1144
445
|
isEndlessModeActive(): boolean {
|
|
1145
|
-
return this.
|
|
446
|
+
return this.endlessMemoryServices.isEndlessModeActive();
|
|
1146
447
|
}
|
|
1147
448
|
|
|
1148
449
|
/**
|
|
1149
450
|
* Add event to Working Set (Endless Mode)
|
|
1150
451
|
*/
|
|
1151
452
|
async addToWorkingSet(eventId: string, relevanceScore?: number): Promise<void> {
|
|
1152
|
-
|
|
1153
|
-
await this.workingSetStore.add(eventId, relevanceScore);
|
|
453
|
+
return this.endlessMemoryServices.addToWorkingSet(eventId, relevanceScore);
|
|
1154
454
|
}
|
|
1155
455
|
|
|
1156
456
|
/**
|
|
1157
457
|
* Get the current Working Set
|
|
1158
458
|
*/
|
|
1159
459
|
async getWorkingSet(): Promise<WorkingSet | null> {
|
|
1160
|
-
|
|
1161
|
-
return this.workingSetStore.get();
|
|
460
|
+
return this.endlessMemoryServices.getWorkingSet();
|
|
1162
461
|
}
|
|
1163
462
|
|
|
1164
463
|
/**
|
|
@@ -1168,138 +467,50 @@ export class MemoryService {
|
|
|
1168
467
|
query: string,
|
|
1169
468
|
options?: { topK?: number }
|
|
1170
469
|
): Promise<ConsolidatedMemory[]> {
|
|
1171
|
-
|
|
1172
|
-
return this.consolidatedStore.search(query, options);
|
|
470
|
+
return this.endlessMemoryServices.searchConsolidated(query, options);
|
|
1173
471
|
}
|
|
1174
472
|
|
|
1175
473
|
/**
|
|
1176
474
|
* Get all consolidated memories
|
|
1177
475
|
*/
|
|
1178
476
|
async getConsolidatedMemories(limit?: number): Promise<ConsolidatedMemory[]> {
|
|
1179
|
-
|
|
1180
|
-
return this.consolidatedStore.getAll({ limit });
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
/**
|
|
1184
|
-
* Extract topic keywords from event content (markdown headings and key terms)
|
|
1185
|
-
*/
|
|
1186
|
-
private extractTopicsFromContent(content: string): string[] {
|
|
1187
|
-
const topics: Set<string> = new Set();
|
|
1188
|
-
|
|
1189
|
-
// Extract markdown headings (## heading)
|
|
1190
|
-
const headings = content.match(/^#{1,3}\s+(.+)$/gm);
|
|
1191
|
-
if (headings) {
|
|
1192
|
-
for (const h of headings.slice(0, 5)) {
|
|
1193
|
-
const text = h.replace(/^#+\s+/, '').replace(/[*_`#]/g, '').trim();
|
|
1194
|
-
if (text.length > 2 && text.length < 50) {
|
|
1195
|
-
topics.add(text);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
// Extract bold terms (**term**)
|
|
1201
|
-
const boldTerms = content.match(/\*\*([^*]+)\*\*/g);
|
|
1202
|
-
if (boldTerms) {
|
|
1203
|
-
for (const b of boldTerms.slice(0, 5)) {
|
|
1204
|
-
const text = b.replace(/\*\*/g, '').trim();
|
|
1205
|
-
if (text.length > 2 && text.length < 30) {
|
|
1206
|
-
topics.add(text);
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
return Array.from(topics).slice(0, 5);
|
|
477
|
+
return this.endlessMemoryServices.getConsolidatedMemories(limit);
|
|
1212
478
|
}
|
|
1213
479
|
|
|
1214
480
|
/**
|
|
1215
481
|
* Increment access count for memories that were used in prompts
|
|
1216
482
|
*/
|
|
1217
483
|
async incrementMemoryAccess(eventIds: string[]): Promise<void> {
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
// Use SQLite event store if available
|
|
1221
|
-
if (this.sqliteStore) {
|
|
1222
|
-
await this.sqliteStore.incrementAccessCount(eventIds);
|
|
1223
|
-
} else if (this.eventStore) {
|
|
1224
|
-
// Fallback to regular event store (which has a stub implementation)
|
|
1225
|
-
await this.eventStore.incrementAccessCount(eventIds);
|
|
1226
|
-
}
|
|
484
|
+
return this.retrievalOrchestrator.incrementMemoryAccess(eventIds);
|
|
1227
485
|
}
|
|
1228
486
|
|
|
1229
487
|
/**
|
|
1230
488
|
* Get most accessed memories from events
|
|
1231
489
|
*/
|
|
1232
|
-
async getMostAccessedMemories(limit: number = 10): Promise<
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
// Try to get from SQLite event store if available
|
|
1236
|
-
if (this.sqliteStore) {
|
|
1237
|
-
const events = await this.sqliteStore.getMostAccessed(limit);
|
|
1238
|
-
console.log('[getMostAccessedMemories] Got events from SQLite:', events.length);
|
|
1239
|
-
return events.map(event => ({
|
|
1240
|
-
memoryId: event.id,
|
|
1241
|
-
summary: event.content.substring(0, 200) + (event.content.length > 200 ? '...' : ''),
|
|
1242
|
-
topics: this.extractTopicsFromContent(event.content),
|
|
1243
|
-
accessCount: (event as any).access_count || 0,
|
|
1244
|
-
lastAccessed: (event as any).last_accessed_at || null,
|
|
1245
|
-
confidence: 1.0,
|
|
1246
|
-
createdAt: event.timestamp
|
|
1247
|
-
}));
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
// Fallback to consolidated store if available
|
|
1251
|
-
if (this.consolidatedStore) {
|
|
1252
|
-
const consolidated = await this.consolidatedStore.getMostAccessed(limit);
|
|
1253
|
-
return consolidated.map(m => ({
|
|
1254
|
-
memoryId: m.memoryId,
|
|
1255
|
-
summary: m.summary,
|
|
1256
|
-
topics: m.topics,
|
|
1257
|
-
accessCount: m.accessCount,
|
|
1258
|
-
lastAccessed: m.accessedAt,
|
|
1259
|
-
confidence: m.confidence,
|
|
1260
|
-
createdAt: m.createdAt
|
|
1261
|
-
}));
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
return [];
|
|
490
|
+
async getMostAccessedMemories(limit: number = 10): Promise<AccessedMemory[]> {
|
|
491
|
+
return this.retrievalAnalyticsService.getMostAccessedMemories(limit);
|
|
1265
492
|
}
|
|
1266
493
|
|
|
1267
494
|
/**
|
|
1268
495
|
* Record a memory retrieval for helpfulness tracking
|
|
1269
496
|
*/
|
|
1270
497
|
async recordRetrieval(eventId: string, sessionId: string, score: number, query: string): Promise<void> {
|
|
1271
|
-
|
|
1272
|
-
await this.sqliteStore.recordRetrieval(eventId, sessionId, score, query);
|
|
498
|
+
return this.retrievalOrchestrator.recordRetrieval(eventId, sessionId, score, query);
|
|
1273
499
|
}
|
|
1274
500
|
|
|
1275
501
|
/**
|
|
1276
502
|
* Record a query-level retrieval trace (used by user-prompt-submit hook).
|
|
1277
503
|
* Feeds the retrieval_traces table that powers dashboard stats.
|
|
1278
504
|
*/
|
|
1279
|
-
async recordQueryTrace(input: {
|
|
1280
|
-
|
|
1281
|
-
queryText: string;
|
|
1282
|
-
strategy: string;
|
|
1283
|
-
candidateEventIds: string[];
|
|
1284
|
-
selectedEventIds: string[];
|
|
1285
|
-
confidence: string;
|
|
1286
|
-
}): Promise<void> {
|
|
1287
|
-
await this.initialize();
|
|
1288
|
-
await this.sqliteStore.recordRetrievalTrace({
|
|
1289
|
-
...input,
|
|
1290
|
-
projectHash: this.projectHash || undefined,
|
|
1291
|
-
candidateDetails: [],
|
|
1292
|
-
selectedDetails: [],
|
|
1293
|
-
fallbackTrace: [],
|
|
1294
|
-
});
|
|
505
|
+
async recordQueryTrace(input: RecordQueryTraceInput): Promise<void> {
|
|
506
|
+
return this.retrievalOrchestrator.recordQueryTrace(input);
|
|
1295
507
|
}
|
|
1296
508
|
|
|
1297
509
|
/**
|
|
1298
510
|
* Evaluate helpfulness of retrievals in a session (called at session end)
|
|
1299
511
|
*/
|
|
1300
512
|
async evaluateSessionHelpfulness(sessionId: string): Promise<void> {
|
|
1301
|
-
await this.
|
|
1302
|
-
await this.sqliteStore.evaluateSessionHelpfulness(sessionId);
|
|
513
|
+
await this.retrievalAnalyticsService.evaluateSessionHelpfulness(sessionId);
|
|
1303
514
|
}
|
|
1304
515
|
|
|
1305
516
|
/**
|
|
@@ -1307,52 +518,28 @@ export class MemoryService {
|
|
|
1307
518
|
* Call on first turn of a new session to catch missed evaluations.
|
|
1308
519
|
*/
|
|
1309
520
|
async evaluatePendingSessions(currentSessionId: string): Promise<void> {
|
|
1310
|
-
await this.
|
|
1311
|
-
const sessions = await this.sqliteStore.getUnevaluatedSessions(currentSessionId, 5);
|
|
1312
|
-
for (const sid of sessions) {
|
|
1313
|
-
try {
|
|
1314
|
-
await this.sqliteStore.evaluateSessionHelpfulness(sid);
|
|
1315
|
-
} catch {
|
|
1316
|
-
// non-critical, skip failed
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
521
|
+
await this.retrievalAnalyticsService.evaluatePendingSessions(currentSessionId);
|
|
1319
522
|
}
|
|
1320
523
|
|
|
1321
524
|
/**
|
|
1322
525
|
* Get most helpful memories ranked by helpfulness score
|
|
1323
526
|
*/
|
|
1324
|
-
async getHelpfulMemories(limit: number = 10): Promise<
|
|
1325
|
-
|
|
1326
|
-
summary: string;
|
|
1327
|
-
helpfulnessScore: number;
|
|
1328
|
-
accessCount: number;
|
|
1329
|
-
evaluationCount: number;
|
|
1330
|
-
}>> {
|
|
1331
|
-
await this.initialize();
|
|
1332
|
-
return this.sqliteStore.getHelpfulMemories(limit);
|
|
527
|
+
async getHelpfulMemories(limit: number = 10): Promise<HelpfulMemory[]> {
|
|
528
|
+
return this.retrievalAnalyticsService.getHelpfulMemories(limit);
|
|
1333
529
|
}
|
|
1334
530
|
|
|
1335
531
|
/**
|
|
1336
532
|
* Get helpfulness statistics for dashboard
|
|
1337
533
|
*/
|
|
1338
|
-
async getHelpfulnessStats(): Promise<{
|
|
1339
|
-
|
|
1340
|
-
totalEvaluated: number;
|
|
1341
|
-
totalRetrievals: number;
|
|
1342
|
-
helpful: number;
|
|
1343
|
-
neutral: number;
|
|
1344
|
-
unhelpful: number;
|
|
1345
|
-
}> {
|
|
1346
|
-
await this.initialize();
|
|
1347
|
-
return this.sqliteStore.getHelpfulnessStats();
|
|
534
|
+
async getHelpfulnessStats(): Promise<HelpfulnessStats> {
|
|
535
|
+
return this.retrievalAnalyticsService.getHelpfulnessStats();
|
|
1348
536
|
}
|
|
1349
537
|
|
|
1350
538
|
/**
|
|
1351
539
|
* Mark a consolidated memory as accessed
|
|
1352
540
|
*/
|
|
1353
541
|
async markMemoryAccessed(memoryId: string): Promise<void> {
|
|
1354
|
-
|
|
1355
|
-
await this.consolidatedStore.markAccessed(memoryId);
|
|
542
|
+
return this.endlessMemoryServices.markMemoryAccessed(memoryId);
|
|
1356
543
|
}
|
|
1357
544
|
|
|
1358
545
|
/**
|
|
@@ -1362,63 +549,28 @@ export class MemoryService {
|
|
|
1362
549
|
content: string,
|
|
1363
550
|
metadata?: { files?: string[]; entities?: string[] }
|
|
1364
551
|
): Promise<ContinuityScore | null> {
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
const snapshot = this.continuityManager.createSnapshot(
|
|
1368
|
-
crypto.randomUUID(),
|
|
1369
|
-
content,
|
|
1370
|
-
metadata
|
|
1371
|
-
);
|
|
1372
|
-
|
|
1373
|
-
return this.continuityManager.calculateScore(snapshot);
|
|
552
|
+
return this.endlessMemoryServices.calculateContinuity(content, metadata);
|
|
1374
553
|
}
|
|
1375
554
|
|
|
1376
555
|
/**
|
|
1377
556
|
* Record activity (for consolidation idle trigger)
|
|
1378
557
|
*/
|
|
1379
558
|
recordActivity(): void {
|
|
1380
|
-
|
|
1381
|
-
this.consolidationWorker.recordActivity();
|
|
1382
|
-
}
|
|
559
|
+
this.endlessMemoryServices.recordActivity();
|
|
1383
560
|
}
|
|
1384
561
|
|
|
1385
562
|
/**
|
|
1386
563
|
* Force a consolidation run
|
|
1387
564
|
*/
|
|
1388
565
|
async forceConsolidation(): Promise<number> {
|
|
1389
|
-
|
|
1390
|
-
return this.consolidationWorker.forceRun();
|
|
566
|
+
return this.endlessMemoryServices.forceConsolidation();
|
|
1391
567
|
}
|
|
1392
568
|
|
|
1393
569
|
/**
|
|
1394
570
|
* Get Endless Mode status
|
|
1395
571
|
*/
|
|
1396
572
|
async getEndlessModeStatus(): Promise<EndlessModeStatus> {
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
let workingSetSize = 0;
|
|
1400
|
-
let continuityScore = 0.5;
|
|
1401
|
-
let consolidatedCount = 0;
|
|
1402
|
-
let lastConsolidation: Date | null = null;
|
|
1403
|
-
|
|
1404
|
-
if (this.workingSetStore) {
|
|
1405
|
-
workingSetSize = await this.workingSetStore.count();
|
|
1406
|
-
const workingSet = await this.workingSetStore.get();
|
|
1407
|
-
continuityScore = workingSet.continuityScore;
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
if (this.consolidatedStore) {
|
|
1411
|
-
consolidatedCount = await this.consolidatedStore.count();
|
|
1412
|
-
lastConsolidation = await this.consolidatedStore.getLastConsolidationTime();
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
return {
|
|
1416
|
-
mode: this.endlessMode,
|
|
1417
|
-
workingSetSize,
|
|
1418
|
-
continuityScore,
|
|
1419
|
-
consolidatedCount,
|
|
1420
|
-
lastConsolidation
|
|
1421
|
-
};
|
|
573
|
+
return this.endlessMemoryServices.getEndlessModeStatus();
|
|
1422
574
|
}
|
|
1423
575
|
|
|
1424
576
|
// ============================================================
|
|
@@ -1437,207 +589,70 @@ export class MemoryService {
|
|
|
1437
589
|
toolCount: number;
|
|
1438
590
|
hasResponse: boolean;
|
|
1439
591
|
}>> {
|
|
1440
|
-
|
|
1441
|
-
return this.sqliteStore.getSessionTurns(sessionId, options);
|
|
592
|
+
return this.queryService.getSessionTurns(sessionId, options);
|
|
1442
593
|
}
|
|
1443
594
|
|
|
1444
595
|
/**
|
|
1445
596
|
* Get all events for a specific turn
|
|
1446
597
|
*/
|
|
1447
598
|
async getEventsByTurn(turnId: string): Promise<MemoryEvent[]> {
|
|
1448
|
-
|
|
1449
|
-
return this.sqliteStore.getEventsByTurn(turnId);
|
|
599
|
+
return this.queryService.getEventsByTurn(turnId);
|
|
1450
600
|
}
|
|
1451
601
|
|
|
1452
602
|
/**
|
|
1453
603
|
* Count total turns for a session
|
|
1454
604
|
*/
|
|
1455
605
|
async countSessionTurns(sessionId: string): Promise<number> {
|
|
1456
|
-
|
|
1457
|
-
return this.sqliteStore.countSessionTurns(sessionId);
|
|
606
|
+
return this.queryService.countSessionTurns(sessionId);
|
|
1458
607
|
}
|
|
1459
608
|
|
|
1460
609
|
/**
|
|
1461
610
|
* Backfill turn_ids from metadata for events stored before the migration
|
|
1462
611
|
*/
|
|
1463
612
|
async backfillTurnIds(): Promise<number> {
|
|
1464
|
-
|
|
1465
|
-
return this.sqliteStore.backfillTurnIds();
|
|
613
|
+
return this.queryService.backfillTurnIds();
|
|
1466
614
|
}
|
|
1467
615
|
|
|
1468
616
|
/**
|
|
1469
617
|
* Delete all events for a session (for force reimport)
|
|
1470
618
|
*/
|
|
1471
619
|
async deleteSessionEvents(sessionId: string): Promise<number> {
|
|
1472
|
-
|
|
1473
|
-
return this.sqliteStore.deleteSessionEvents(sessionId);
|
|
620
|
+
return this.queryService.deleteSessionEvents(sessionId);
|
|
1474
621
|
}
|
|
1475
622
|
|
|
1476
623
|
/**
|
|
1477
624
|
* Format Endless Mode context for Claude
|
|
1478
625
|
*/
|
|
1479
626
|
async formatEndlessContext(query: string): Promise<string> {
|
|
1480
|
-
|
|
1481
|
-
return '';
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
const workingSet = await this.getWorkingSet();
|
|
1485
|
-
const consolidated = await this.searchConsolidated(query, { topK: 3 });
|
|
1486
|
-
const continuity = await this.calculateContinuity(query);
|
|
1487
|
-
|
|
1488
|
-
const parts: string[] = [];
|
|
1489
|
-
|
|
1490
|
-
// Continuity status
|
|
1491
|
-
if (continuity) {
|
|
1492
|
-
const statusEmoji = continuity.transitionType === 'seamless' ? '🔗' :
|
|
1493
|
-
continuity.transitionType === 'topic_shift' ? '↪️' : '🆕';
|
|
1494
|
-
parts.push(`${statusEmoji} Context: ${continuity.transitionType} (score: ${continuity.score.toFixed(2)})`);
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
// Working set summary
|
|
1498
|
-
if (workingSet && workingSet.recentEvents.length > 0) {
|
|
1499
|
-
parts.push('\n## Recent Context (Working Set)');
|
|
1500
|
-
const recent = workingSet.recentEvents.slice(0, 5);
|
|
1501
|
-
for (const event of recent) {
|
|
1502
|
-
const preview = event.content.slice(0, 80) + (event.content.length > 80 ? '...' : '');
|
|
1503
|
-
const time = event.timestamp.toLocaleTimeString();
|
|
1504
|
-
parts.push(`- ${time} [${event.eventType}] ${preview}`);
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
// Consolidated memories
|
|
1509
|
-
if (consolidated.length > 0) {
|
|
1510
|
-
parts.push('\n## Related Knowledge (Consolidated)');
|
|
1511
|
-
for (const memory of consolidated) {
|
|
1512
|
-
parts.push(`- ${memory.topics.slice(0, 3).join(', ')}: ${memory.summary.slice(0, 100)}...`);
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
return parts.join('\n');
|
|
627
|
+
return this.endlessMemoryServices.formatEndlessContext(query);
|
|
1517
628
|
}
|
|
1518
629
|
|
|
1519
630
|
/**
|
|
1520
631
|
* Force a graduation evaluation run
|
|
1521
632
|
*/
|
|
1522
633
|
async forceGraduation(): Promise<GraduationRunResult> {
|
|
1523
|
-
|
|
1524
|
-
return { evaluated: 0, graduated: 0, byLevel: {} };
|
|
1525
|
-
}
|
|
1526
|
-
return this.graduationWorker.forceRun();
|
|
634
|
+
return this.runtimeService.forceGraduation();
|
|
1527
635
|
}
|
|
1528
636
|
|
|
1529
637
|
/**
|
|
1530
638
|
* Record access to a memory event (for graduation scoring)
|
|
1531
639
|
*/
|
|
1532
640
|
recordMemoryAccess(eventId: string, sessionId: string, confidence: number = 1.0): void {
|
|
1533
|
-
this.
|
|
641
|
+
this.runtimeService.recordMemoryAccess(eventId, sessionId, confidence);
|
|
1534
642
|
}
|
|
1535
643
|
|
|
1536
644
|
getEmbeddingModelName(): string {
|
|
1537
|
-
return this.
|
|
645
|
+
return this.embeddingMaintenanceService.getEmbeddingModelName();
|
|
1538
646
|
}
|
|
1539
647
|
|
|
1540
648
|
/**
|
|
1541
649
|
* Ensure embedding model metadata is in sync and optionally migrate vectors.
|
|
1542
650
|
* Migration strategy: clear vector index + clear embedding outbox + re-enqueue all events.
|
|
1543
651
|
*/
|
|
1544
|
-
async ensureEmbeddingModelForImport(
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
enqueued: number;
|
|
1549
|
-
reason?: string;
|
|
1550
|
-
}> {
|
|
1551
|
-
await this.initialize();
|
|
1552
|
-
|
|
1553
|
-
const currentModel = this.getEmbeddingModelName();
|
|
1554
|
-
const metaPath = path.join(this.storagePath, 'embedding-meta.json');
|
|
1555
|
-
|
|
1556
|
-
let previousModel: string | null = null;
|
|
1557
|
-
try {
|
|
1558
|
-
if (fs.existsSync(metaPath)) {
|
|
1559
|
-
const parsed = JSON.parse(fs.readFileSync(metaPath, 'utf-8')) as { model?: string };
|
|
1560
|
-
previousModel = parsed?.model || null;
|
|
1561
|
-
}
|
|
1562
|
-
} catch {
|
|
1563
|
-
previousModel = null;
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
const stats = await this.getStats();
|
|
1567
|
-
const hasExistingVectors = (stats.vectorCount || 0) > 0;
|
|
1568
|
-
|
|
1569
|
-
// First-time metadata write (no migration needed unless legacy vectors exist)
|
|
1570
|
-
if (!previousModel && !hasExistingVectors) {
|
|
1571
|
-
fs.writeFileSync(metaPath, JSON.stringify({ model: currentModel, updatedAt: new Date().toISOString() }, null, 2));
|
|
1572
|
-
return { changed: false, previousModel: null, currentModel, enqueued: 0, reason: 'initialized-meta' };
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
const modelChanged = previousModel !== currentModel;
|
|
1576
|
-
const legacyUnknownButVectorsExist = !previousModel && hasExistingVectors;
|
|
1577
|
-
|
|
1578
|
-
if (!modelChanged && !legacyUnknownButVectorsExist) {
|
|
1579
|
-
return { changed: false, previousModel, currentModel, enqueued: 0 };
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
if (options?.autoMigrate === false) {
|
|
1583
|
-
return {
|
|
1584
|
-
changed: true,
|
|
1585
|
-
previousModel,
|
|
1586
|
-
currentModel,
|
|
1587
|
-
enqueued: 0,
|
|
1588
|
-
reason: legacyUnknownButVectorsExist ? 'legacy-vectors-without-meta' : 'model-mismatch'
|
|
1589
|
-
};
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
// Pause background vector processing while preparing migration
|
|
1593
|
-
const wasRunning = this.vectorWorker?.isRunning() || false;
|
|
1594
|
-
if (wasRunning) this.vectorWorker?.stop();
|
|
1595
|
-
|
|
1596
|
-
// Reset vector and outbox state
|
|
1597
|
-
await this.vectorStore.clearAll();
|
|
1598
|
-
await this.sqliteStore.clearEmbeddingOutbox();
|
|
1599
|
-
|
|
1600
|
-
// Re-enqueue all events for new embeddings
|
|
1601
|
-
const pageSize = 1000;
|
|
1602
|
-
let offset = 0;
|
|
1603
|
-
let enqueued = 0;
|
|
1604
|
-
|
|
1605
|
-
while (true) {
|
|
1606
|
-
const page = await this.sqliteStore.getEventsPage(pageSize, offset);
|
|
1607
|
-
if (page.length === 0) break;
|
|
1608
|
-
|
|
1609
|
-
for (const event of page) {
|
|
1610
|
-
await this.sqliteStore.enqueueForEmbedding(event.id, event.content);
|
|
1611
|
-
enqueued += 1;
|
|
1612
|
-
}
|
|
1613
|
-
|
|
1614
|
-
offset += page.length;
|
|
1615
|
-
if (page.length < pageSize) break;
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
fs.writeFileSync(
|
|
1619
|
-
metaPath,
|
|
1620
|
-
JSON.stringify(
|
|
1621
|
-
{
|
|
1622
|
-
model: currentModel,
|
|
1623
|
-
previousModel,
|
|
1624
|
-
migratedAt: new Date().toISOString(),
|
|
1625
|
-
enqueued
|
|
1626
|
-
},
|
|
1627
|
-
null,
|
|
1628
|
-
2
|
|
1629
|
-
)
|
|
1630
|
-
);
|
|
1631
|
-
|
|
1632
|
-
if (wasRunning) this.vectorWorker?.start();
|
|
1633
|
-
|
|
1634
|
-
return {
|
|
1635
|
-
changed: true,
|
|
1636
|
-
previousModel,
|
|
1637
|
-
currentModel,
|
|
1638
|
-
enqueued,
|
|
1639
|
-
reason: legacyUnknownButVectorsExist ? 'legacy-vectors-without-meta' : 'model-mismatch'
|
|
1640
|
-
};
|
|
652
|
+
async ensureEmbeddingModelForImport(
|
|
653
|
+
options?: EmbeddingModelMaintenanceOptions
|
|
654
|
+
): Promise<EmbeddingModelMaintenanceResult> {
|
|
655
|
+
return this.embeddingMaintenanceService.ensureEmbeddingModelForImport(options);
|
|
1641
656
|
}
|
|
1642
657
|
|
|
1643
658
|
/**
|
|
@@ -1651,152 +666,23 @@ export class MemoryService {
|
|
|
1651
666
|
* Shutdown service
|
|
1652
667
|
*/
|
|
1653
668
|
async shutdown(): Promise<void> {
|
|
1654
|
-
|
|
1655
|
-
if (this.graduationWorker) {
|
|
1656
|
-
this.graduationWorker.stop();
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
// Stop endless mode components
|
|
1660
|
-
if (this.consolidationWorker) {
|
|
1661
|
-
this.consolidationWorker.stop();
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
if (this.vectorWorker) {
|
|
1665
|
-
this.vectorWorker.stop();
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
// Close shared store
|
|
1669
|
-
if (this.sharedEventStore) {
|
|
1670
|
-
await this.sharedEventStore.close();
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
|
-
// Close primary store (SQLite)
|
|
1674
|
-
await this.sqliteStore.close();
|
|
1675
|
-
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
/**
|
|
1679
|
-
* Expand ~ to home directory
|
|
1680
|
-
*/
|
|
1681
|
-
private expandPath(p: string): string {
|
|
1682
|
-
if (p.startsWith('~')) {
|
|
1683
|
-
return path.join(os.homedir(), p.slice(1));
|
|
1684
|
-
}
|
|
1685
|
-
return p;
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1689
|
-
// ============================================================
|
|
1690
|
-
// Service Instance Management
|
|
1691
|
-
// ============================================================
|
|
1692
|
-
|
|
1693
|
-
// Instance cache: Map from project hash (or '__global__') to MemoryService
|
|
1694
|
-
const serviceCache = new Map<string, MemoryService>();
|
|
1695
|
-
const GLOBAL_KEY = '__global__';
|
|
1696
|
-
const GLOBAL_READONLY_KEY = '__global_readonly__';
|
|
1697
|
-
|
|
1698
|
-
/**
|
|
1699
|
-
* Get the global memory service (backward compatibility)
|
|
1700
|
-
* Use this for operations not tied to a specific project
|
|
1701
|
-
* Note: analyticsEnabled=false and sharedStore disabled to avoid DuckDB lock conflicts
|
|
1702
|
-
*/
|
|
1703
|
-
export function getDefaultMemoryService(): MemoryService {
|
|
1704
|
-
if (!serviceCache.has(GLOBAL_KEY)) {
|
|
1705
|
-
serviceCache.set(GLOBAL_KEY, new MemoryService({
|
|
1706
|
-
storagePath: '~/.claude-code/memory',
|
|
1707
|
-
analyticsEnabled: false, // Hooks don't need DuckDB
|
|
1708
|
-
sharedStoreConfig: { enabled: false } // Shared store uses DuckDB too
|
|
1709
|
-
}));
|
|
1710
|
-
}
|
|
1711
|
-
return serviceCache.get(GLOBAL_KEY)!;
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
|
-
/**
|
|
1715
|
-
* Get a read-only global memory service
|
|
1716
|
-
* Use this for web server/dashboard that only needs to read data
|
|
1717
|
-
* Creates a fresh connection each time to avoid blocking the main writer process
|
|
1718
|
-
* Uses SQLite (WAL mode) which supports concurrent readers
|
|
1719
|
-
*/
|
|
1720
|
-
export function getReadOnlyMemoryService(): MemoryService {
|
|
1721
|
-
// Don't cache - create fresh instance each time to avoid holding locks
|
|
1722
|
-
// The connection will be closed when the request completes
|
|
1723
|
-
// Uses SQLite which supports concurrent readers via WAL mode
|
|
1724
|
-
return new MemoryService({
|
|
1725
|
-
storagePath: '~/.claude-code/memory',
|
|
1726
|
-
readOnly: true,
|
|
1727
|
-
analyticsEnabled: false, // Use SQLite for reads (WAL supports concurrent readers)
|
|
1728
|
-
sharedStoreConfig: { enabled: false } // Skip shared store for now
|
|
1729
|
-
});
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
/**
|
|
1733
|
-
* Get memory service for a specific project path
|
|
1734
|
-
* Creates isolated storage at ~/.claude-code/memory/projects/{hash}/
|
|
1735
|
-
* Note: analyticsEnabled=false and sharedStore disabled to avoid DuckDB lock conflicts
|
|
1736
|
-
*/
|
|
1737
|
-
export function getMemoryServiceForProject(
|
|
1738
|
-
projectPath: string,
|
|
1739
|
-
sharedStoreConfig?: SharedStoreConfig
|
|
1740
|
-
): MemoryService {
|
|
1741
|
-
const hash = hashProjectPath(projectPath);
|
|
1742
|
-
|
|
1743
|
-
if (!serviceCache.has(hash)) {
|
|
1744
|
-
const storagePath = getProjectStoragePath(projectPath);
|
|
1745
|
-
serviceCache.set(hash, new MemoryService({
|
|
1746
|
-
storagePath,
|
|
1747
|
-
projectHash: hash,
|
|
1748
|
-
projectPath,
|
|
1749
|
-
// Override shared store config - hooks don't need DuckDB
|
|
1750
|
-
sharedStoreConfig: sharedStoreConfig ?? { enabled: false },
|
|
1751
|
-
analyticsEnabled: false // Hooks don't need DuckDB
|
|
1752
|
-
}));
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
return serviceCache.get(hash)!;
|
|
1756
|
-
}
|
|
1757
|
-
|
|
1758
|
-
/**
|
|
1759
|
-
* Get memory service for a session by looking up its project
|
|
1760
|
-
* Falls back to global storage if session not found in registry
|
|
1761
|
-
*/
|
|
1762
|
-
export function getMemoryServiceForSession(sessionId: string): MemoryService {
|
|
1763
|
-
const projectInfo = getSessionProject(sessionId);
|
|
1764
|
-
|
|
1765
|
-
if (projectInfo) {
|
|
1766
|
-
return getMemoryServiceForProject(projectInfo.projectPath);
|
|
669
|
+
await this.runtimeService.shutdown();
|
|
1767
670
|
}
|
|
1768
|
-
|
|
1769
|
-
// Fallback to global storage for unknown sessions (backward compat)
|
|
1770
|
-
return getDefaultMemoryService();
|
|
1771
671
|
}
|
|
1772
672
|
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
projectHash: projectInfo?.projectHash,
|
|
1790
|
-
projectPath: projectInfo?.projectPath,
|
|
1791
|
-
lightweightMode: true, // Skip embedder/vector/workers
|
|
1792
|
-
analyticsEnabled: false,
|
|
1793
|
-
sharedStoreConfig: { enabled: false }
|
|
1794
|
-
}));
|
|
1795
|
-
}
|
|
1796
|
-
|
|
1797
|
-
return serviceCache.get(key)!;
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
export function createMemoryService(config: MemoryServiceConfig): MemoryService {
|
|
1801
|
-
return new MemoryService(config);
|
|
1802
|
-
}
|
|
673
|
+
const defaultRegistry = createMemoryServiceRegistry<MemoryService>({
|
|
674
|
+
createService: (config) => new MemoryService(config),
|
|
675
|
+
hashProjectPath: defaultHashProjectPath,
|
|
676
|
+
getProjectStoragePath: defaultGetProjectStoragePath,
|
|
677
|
+
getSessionProject: defaultGetSessionProject,
|
|
678
|
+
homedir: os.homedir,
|
|
679
|
+
disabledSharedStoreConfig: DISABLED_SHARED_STORE_CONFIG
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
export const getDefaultMemoryService = defaultRegistry.getDefaultMemoryService;
|
|
683
|
+
export const getReadOnlyMemoryService = defaultRegistry.getReadOnlyMemoryService;
|
|
684
|
+
export const getMemoryServiceForProject = defaultRegistry.getMemoryServiceForProject;
|
|
685
|
+
export const getMemoryServiceForSession = defaultRegistry.getMemoryServiceForSession;
|
|
686
|
+
export const getLightweightMemoryService = defaultRegistry.getLightweightMemoryService;
|
|
687
|
+
export const getLightweightMemoryServiceForProject = defaultRegistry.getLightweightMemoryServiceForProject;
|
|
688
|
+
export const createMemoryService = defaultRegistry.createMemoryService;
|