claude-memory-layer 1.0.31 → 1.0.32
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/README.md +9 -2
- package/dist/cli/index.js +1 -1
- package/package.json +11 -2
- package/scripts/postinstall-embedding-backend.cjs +16 -12
- package/AGENTS.md +0 -71
- package/CLAUDE.md +0 -30
- package/HANDOFF.md +0 -92
- package/Memo.txt +0 -558
- package/benchmarks/replay/anonymized-real-sessions.json +0 -48
- package/config/kpi-thresholds.json +0 -7
- package/context.md +0 -636
- package/docs/ARCHITECTURE_COMPARISON_AND_RECOMMENDATIONS.md +0 -627
- package/docs/HERMES_MEMORY_INGESTION_ANALYSIS.md +0 -440
- package/docs/MCP_MEMORY_SERVICE_COMPARATIVE_REVIEW.md +0 -271
- package/docs/MEMORY_USEFULNESS_AUDIT.md +0 -371
- package/docs/MEMORY_USEFULNESS_AUDIT_RAW.json +0 -80
- package/docs/MEMSEARCH_PROJECT_STRUCTURE_ANALYSIS.md +0 -333
- package/docs/MEMU_ADOPTION.md +0 -40
- package/docs/OPERATIONS.md +0 -18
- package/docs/PRODUCT_VALIDATION_MATRIX.md +0 -82
- package/docs/PROJECT_STRUCTURE_ANALYSIS.md +0 -421
- package/docs/REFACTORING_MILESTONES_AND_ISSUES.md +0 -501
- package/docs/REFACTORING_PLAN_THIN_CORE.md +0 -414
- package/docs/REFERENCE_PROJECT_ANALYSES.md +0 -25
- package/docs/SUPERLOCALMEMORY_PROJECT_STRUCTURE_ANALYSIS.md +0 -452
- package/docs/TARGET_ARCHITECTURE_AND_FOLDER_STRUCTURE.md +0 -446
- package/docs/architecture/comparison-index.md +0 -47
- package/docs/reports/codex-real-data-validation-20260505T040447Z.md +0 -46
- package/plan.md +0 -1642
- package/scripts/build.ts +0 -159
- package/scripts/bump-patch-version.sh +0 -18
- package/scripts/delete-unknown-projects.js +0 -154
- package/scripts/fix-sync-gap.js +0 -32
- package/scripts/generate-session-qrels.ts +0 -126
- package/scripts/heartbeat-memory-orchestrator.sh +0 -28
- package/scripts/replay-retrieval-benchmark.ts +0 -69
- package/scripts/report-sync-gap.js +0 -26
- package/scripts/review-queue-auto-resolve.js +0 -21
- package/scripts/sync-gap-auto-heal.sh +0 -17
- package/spec.md +0 -624
- package/specs/20260207-dashboard-upgrade/context.md +0 -38
- package/specs/20260207-dashboard-upgrade/spec.md +0 -96
- package/specs/citations-system/context.md +0 -243
- package/specs/citations-system/plan.md +0 -495
- package/specs/citations-system/spec.md +0 -371
- package/specs/endless-mode/context.md +0 -305
- package/specs/endless-mode/plan.md +0 -620
- package/specs/endless-mode/spec.md +0 -455
- package/specs/entity-edge-model/context.md +0 -401
- package/specs/entity-edge-model/plan.md +0 -459
- package/specs/entity-edge-model/spec.md +0 -391
- package/specs/evidence-aligner-v2/context.md +0 -401
- package/specs/evidence-aligner-v2/plan.md +0 -303
- package/specs/evidence-aligner-v2/spec.md +0 -312
- package/specs/mcp-desktop-integration/context.md +0 -278
- package/specs/mcp-desktop-integration/plan.md +0 -550
- package/specs/mcp-desktop-integration/spec.md +0 -494
- package/specs/memory-utilization-improvements/context.md +0 -145
- package/specs/memory-utilization-improvements/plan.md +0 -361
- package/specs/memory-utilization-improvements/spec.md +0 -361
- package/specs/post-tool-use-hook/context.md +0 -319
- package/specs/post-tool-use-hook/plan.md +0 -469
- package/specs/post-tool-use-hook/spec.md +0 -364
- package/specs/private-tags/context.md +0 -288
- package/specs/private-tags/plan.md +0 -412
- package/specs/private-tags/spec.md +0 -345
- package/specs/progressive-disclosure/context.md +0 -346
- package/specs/progressive-disclosure/plan.md +0 -663
- package/specs/progressive-disclosure/spec.md +0 -415
- package/specs/selective-tool-observation/context.md +0 -100
- package/specs/selective-tool-observation/plan.md +0 -158
- package/specs/selective-tool-observation/spec.md +0 -127
- package/specs/task-entity-system/context.md +0 -297
- package/specs/task-entity-system/plan.md +0 -301
- package/specs/task-entity-system/spec.md +0 -314
- package/specs/thin-core-refactor/context.md +0 -275
- package/specs/thin-core-refactor/plan.md +0 -536
- package/specs/thin-core-refactor/spec.md +0 -465
- package/specs/vector-outbox-v2/context.md +0 -470
- package/specs/vector-outbox-v2/plan.md +0 -562
- package/specs/vector-outbox-v2/spec.md +0 -466
- package/specs/web-viewer-ui/context.md +0 -384
- package/specs/web-viewer-ui/plan.md +0 -797
- package/specs/web-viewer-ui/spec.md +0 -516
- package/src/adapters/claude/capture/index.ts +0 -3
- package/src/adapters/claude/context/index.ts +0 -3
- package/src/adapters/claude/hooks/index.ts +0 -21
- package/src/adapters/claude/hooks/post-tool-use.ts +0 -239
- package/src/adapters/claude/hooks/prompt-injection-policy.ts +0 -104
- package/src/adapters/claude/hooks/semantic-daemon-client.ts +0 -209
- package/src/adapters/claude/hooks/semantic-daemon.ts +0 -283
- package/src/adapters/claude/hooks/session-end.ts +0 -59
- package/src/adapters/claude/hooks/session-start.ts +0 -73
- package/src/adapters/claude/hooks/stop.ts +0 -128
- package/src/adapters/claude/hooks/user-prompt-submit.ts +0 -361
- package/src/adapters/claude/index.ts +0 -4
- package/src/adapters/claude/transcript/index.ts +0 -4
- package/src/adapters/claude/transcript/transcript-reader.ts +0 -57
- package/src/adapters/claude/transcript/turn-reconstructor.ts +0 -65
- package/src/apps/cli/claude-settings-hooks.ts +0 -138
- package/src/apps/cli/codex-import-runner.ts +0 -125
- package/src/apps/cli/codex-validation-output.ts +0 -95
- package/src/apps/cli/hermes-import-runner.ts +0 -130
- package/src/apps/cli/hermes-validation-output.ts +0 -91
- package/src/apps/cli/index.ts +0 -1735
- package/src/apps/cli/mcp-install.ts +0 -106
- package/src/apps/cli/retrieval-disclosure-output.ts +0 -196
- package/src/apps/dashboard/assets/js/bootstrap.js +0 -244
- package/src/apps/dashboard/assets/js/chat.js +0 -373
- package/src/apps/dashboard/assets/js/disclosure.js +0 -232
- package/src/apps/dashboard/assets/js/modals.js +0 -298
- package/src/apps/dashboard/assets/js/overview.js +0 -655
- package/src/apps/dashboard/assets/js/state.js +0 -72
- package/src/apps/dashboard/assets/js/views.js +0 -468
- package/src/apps/dashboard/index.html +0 -543
- package/src/apps/dashboard/index.ts +0 -3
- package/src/apps/dashboard/style.css +0 -1750
- package/src/apps/index.ts +0 -5
- package/src/apps/server/api/chat.ts +0 -244
- package/src/apps/server/api/citations.ts +0 -105
- package/src/apps/server/api/events.ts +0 -137
- package/src/apps/server/api/health.ts +0 -53
- package/src/apps/server/api/index.ts +0 -26
- package/src/apps/server/api/projects.ts +0 -74
- package/src/apps/server/api/search.ts +0 -184
- package/src/apps/server/api/sessions.ts +0 -115
- package/src/apps/server/api/stats.ts +0 -723
- package/src/apps/server/api/turns.ts +0 -143
- package/src/apps/server/api/utils.ts +0 -65
- package/src/apps/server/index.ts +0 -111
- package/src/cli/index.ts +0 -3
- package/src/cli/retrieval-disclosure-output.ts +0 -2
- package/src/compat/index.ts +0 -5
- package/src/core/canonical-key.ts +0 -186
- package/src/core/citation-generator.ts +0 -63
- package/src/core/consolidated-store.ts +0 -356
- package/src/core/consolidation-worker.ts +0 -493
- package/src/core/context-formatter.ts +0 -276
- package/src/core/continuity-manager.ts +0 -341
- package/src/core/db-wrapper.ts +0 -64
- package/src/core/derive/fact-deriver.ts +0 -170
- package/src/core/derive/index.ts +0 -2
- package/src/core/derive/summary-deriver.ts +0 -76
- package/src/core/edge-repo.ts +0 -333
- package/src/core/embedder.ts +0 -4
- package/src/core/engine/embedding-maintenance-service.ts +0 -187
- package/src/core/engine/endless-memory-services.ts +0 -4
- package/src/core/engine/index.ts +0 -19
- package/src/core/engine/memory-engine-services.ts +0 -170
- package/src/core/engine/memory-ingest-service.ts +0 -317
- package/src/core/engine/memory-query-service.ts +0 -173
- package/src/core/engine/memory-runtime-service.ts +0 -162
- package/src/core/engine/memory-service-composition.ts +0 -231
- package/src/core/engine/retrieval-analytics-service.ts +0 -181
- package/src/core/engine/retrieval-disclosure-service.ts +0 -420
- package/src/core/engine/retrieval-orchestrator.ts +0 -377
- package/src/core/engine/retrieval-services.ts +0 -176
- package/src/core/engine/shared-memory-services.ts +0 -4
- package/src/core/entity-repo.ts +0 -349
- package/src/core/event-store.ts +0 -779
- package/src/core/evidence-aligner.ts +0 -635
- package/src/core/external-market-context.ts +0 -582
- package/src/core/graduation-worker.ts +0 -171
- package/src/core/graduation.ts +0 -377
- package/src/core/index.ts +0 -64
- package/src/core/ingest-interceptor.ts +0 -80
- package/src/core/markdown-mirror.ts +0 -70
- package/src/core/matcher.ts +0 -208
- package/src/core/md-mirror.ts +0 -92
- package/src/core/metadata-extractor.ts +0 -203
- package/src/core/model/memory-fact.ts +0 -30
- package/src/core/model/memory-rule.ts +0 -14
- package/src/core/model/memory-summary.ts +0 -21
- package/src/core/model/raw-event.ts +0 -28
- package/src/core/model/retrieval-result.ts +0 -35
- package/src/core/mongo-sync-config.ts +0 -165
- package/src/core/mongo-sync-worker.ts +0 -381
- package/src/core/privacy/filter.ts +0 -190
- package/src/core/privacy/index.ts +0 -20
- package/src/core/privacy/tag-parser.ts +0 -145
- package/src/core/product-validation-matrix.ts +0 -314
- package/src/core/progressive-retriever.ts +0 -414
- package/src/core/registry/project-path.ts +0 -54
- package/src/core/registry/session-registry.ts +0 -69
- package/src/core/replay-evaluator.ts +0 -625
- package/src/core/retrieval-benchmark.ts +0 -117
- package/src/core/retrieval-quality.ts +0 -109
- package/src/core/retriever.ts +0 -800
- package/src/core/session-qrels.ts +0 -360
- package/src/core/shared-event-store.ts +0 -114
- package/src/core/shared-promoter.ts +0 -249
- package/src/core/shared-store.ts +0 -289
- package/src/core/shared-vector-store.ts +0 -203
- package/src/core/sqlite-event-store.ts +0 -1846
- package/src/core/sqlite-wrapper.ts +0 -116
- package/src/core/sync-worker.ts +0 -228
- package/src/core/tag-taxonomy.ts +0 -51
- package/src/core/task/blocker-resolver.ts +0 -333
- package/src/core/task/index.ts +0 -9
- package/src/core/task/task-matcher.ts +0 -240
- package/src/core/task/task-projector.ts +0 -358
- package/src/core/task/task-resolver.ts +0 -421
- package/src/core/turn-state.ts +0 -207
- package/src/core/types.ts +0 -952
- package/src/core/vector-outbox.ts +0 -299
- package/src/core/vector-store.ts +0 -231
- package/src/core/vector-worker.ts +0 -521
- package/src/core/working-set-store.ts +0 -257
- package/src/extensions/endless-memory/endless-memory-services.ts +0 -350
- package/src/extensions/endless-memory/index.ts +0 -1
- package/src/extensions/index.ts +0 -5
- package/src/extensions/mcp/handlers.ts +0 -960
- package/src/extensions/mcp/index.ts +0 -48
- package/src/extensions/mcp/tools.ts +0 -252
- package/src/extensions/shared-memory/index.ts +0 -1
- package/src/extensions/shared-memory/shared-memory-services.ts +0 -211
- package/src/extensions/vector/embedder.ts +0 -234
- package/src/extensions/vector/index.ts +0 -1
- package/src/hooks/post-tool-use.ts +0 -9
- package/src/hooks/semantic-daemon-client.ts +0 -1
- package/src/hooks/semantic-daemon.ts +0 -11
- package/src/hooks/session-end.ts +0 -9
- package/src/hooks/session-start.ts +0 -9
- package/src/hooks/stop.ts +0 -9
- package/src/hooks/user-prompt-submit.ts +0 -9
- package/src/index.ts +0 -13
- package/src/mcp/handlers.ts +0 -2
- package/src/mcp/index.ts +0 -4
- package/src/mcp/tools.ts +0 -2
- package/src/server/api/chat.ts +0 -2
- package/src/server/api/citations.ts +0 -2
- package/src/server/api/events.ts +0 -2
- package/src/server/api/health.ts +0 -2
- package/src/server/api/index.ts +0 -2
- package/src/server/api/projects.ts +0 -2
- package/src/server/api/search.ts +0 -2
- package/src/server/api/sessions.ts +0 -2
- package/src/server/api/stats.ts +0 -2
- package/src/server/api/turns.ts +0 -2
- package/src/server/api/utils.ts +0 -2
- package/src/server/index.ts +0 -2
- package/src/services/bootstrap-organizer.ts +0 -463
- package/src/services/codex-session-history-importer.ts +0 -966
- package/src/services/hermes-session-history-importer.ts +0 -733
- package/src/services/memory-service-config.ts +0 -36
- package/src/services/memory-service-registry.ts +0 -150
- package/src/services/memory-service.ts +0 -688
- package/src/services/session-history-importer.ts +0 -629
- package/tests/README.md +0 -23
- package/tests/adapters/claude/claude-semantic-daemon-adapter.test.ts +0 -54
- package/tests/adapters/claude/claude-transcript-reconstructor.test.ts +0 -98
- package/tests/adapters/claude-hook-prompt-injection-policy.test.ts +0 -99
- package/tests/apps/app-layer-boundary.test.ts +0 -48
- package/tests/apps/claude-settings-hooks.test.ts +0 -107
- package/tests/apps/cli-disclosure-output.test.ts +0 -212
- package/tests/apps/codex-import-runner.test.ts +0 -99
- package/tests/apps/codex-validation-output.test.ts +0 -100
- package/tests/apps/hermes-import-runner.test.ts +0 -99
- package/tests/apps/mcp-install-command.test.ts +0 -59
- package/tests/apps/package-build-entrypoints.test.ts +0 -30
- package/tests/apps/postinstall-embedding-backend.test.ts +0 -185
- package/tests/apps/search-api-disclosure.test.ts +0 -162
- package/tests/apps/stats-api-lightweight.test.ts +0 -67
- package/tests/apps/ui-disclosure-output.test.ts +0 -140
- package/tests/core/bootstrap-organizer.test.ts +0 -111
- package/tests/core/canonical-key.test.ts +0 -101
- package/tests/core/codex-session-history-importer-validation.test.ts +0 -185
- package/tests/core/consolidation-worker.test.ts +0 -75
- package/tests/core/embedding-maintenance-service.test.ts +0 -282
- package/tests/core/evidence-aligner.test.ts +0 -152
- package/tests/core/external-market-context.test.ts +0 -209
- package/tests/core/fact-deriver.test.ts +0 -79
- package/tests/core/hermes-session-history-importer-validation.test.ts +0 -609
- package/tests/core/ingest-interceptor.test.ts +0 -38
- package/tests/core/markdown-mirror.test.ts +0 -85
- package/tests/core/matcher.test.ts +0 -112
- package/tests/core/md-mirror.test.ts +0 -50
- package/tests/core/memory-engine-services.test.ts +0 -240
- package/tests/core/memory-ingest-service.test.ts +0 -296
- package/tests/core/memory-query-service.test.ts +0 -129
- package/tests/core/memory-runtime-service.test.ts +0 -201
- package/tests/core/memory-service-composition.test.ts +0 -192
- package/tests/core/memory-service-config.test.ts +0 -41
- package/tests/core/memory-service-facade.test.ts +0 -30
- package/tests/core/memory-service-registry.test.ts +0 -206
- package/tests/core/product-validation-matrix.test.ts +0 -61
- package/tests/core/project-registry.test.ts +0 -78
- package/tests/core/replay-evaluator.test.ts +0 -181
- package/tests/core/retrieval-analytics-service.test.ts +0 -210
- package/tests/core/retrieval-benchmark.test.ts +0 -93
- package/tests/core/retrieval-disclosure-service.test.ts +0 -264
- package/tests/core/retrieval-orchestrator.test.ts +0 -403
- package/tests/core/retrieval-quality.test.ts +0 -31
- package/tests/core/retrieval-services.test.ts +0 -185
- package/tests/core/retriever-fallback-chain.test.ts +0 -223
- package/tests/core/retriever-strategy-scope.test.ts +0 -164
- package/tests/core/retriever.memu-adoption.test.ts +0 -122
- package/tests/core/session-history-importer-filter.test.ts +0 -78
- package/tests/core/session-qrels.test.ts +0 -250
- package/tests/core/sqlite-event-store-replication.test.ts +0 -127
- package/tests/core/summary-deriver.test.ts +0 -66
- package/tests/extensions/embedder-warning-suppression.test.ts +0 -84
- package/tests/extensions/endless-memory-extension-boundary.test.ts +0 -17
- package/tests/extensions/endless-memory-services.test.ts +0 -325
- package/tests/extensions/mcp-context-tools.test.ts +0 -905
- package/tests/extensions/mcp-extension-boundary.test.ts +0 -21
- package/tests/extensions/mcp-package-build.test.ts +0 -22
- package/tests/extensions/mcp-project-aware-tools.test.ts +0 -102
- package/tests/extensions/shared-memory-extension-boundary.test.ts +0 -24
- package/tests/extensions/shared-memory-services.test.ts +0 -309
- package/tests/extensions/vector-extension-boundary.test.ts +0 -21
- package/tsconfig.json +0 -24
- package/vitest.config.ts +0 -15
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mongo Sync Config
|
|
3
|
-
* Persistent per-project config for enabling auto-sync from hooks (e.g., SessionEnd).
|
|
4
|
-
*
|
|
5
|
-
* Stored as JSON under the project's storagePath next to events.sqlite.
|
|
6
|
-
* Note: This may include credentials in plaintext (MongoDB URI). Treat accordingly.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import * as fs from 'fs';
|
|
10
|
-
import * as path from 'path';
|
|
11
|
-
|
|
12
|
-
import type { MongoSyncDirection } from './mongo-sync-worker.js';
|
|
13
|
-
|
|
14
|
-
export type MongoSyncConfig = {
|
|
15
|
-
version: 1;
|
|
16
|
-
enabled: boolean;
|
|
17
|
-
/** MongoDB connection URI (may include credentials). */
|
|
18
|
-
uri: string;
|
|
19
|
-
/** MongoDB database name. */
|
|
20
|
-
dbName: string;
|
|
21
|
-
/** Remote project key (shared across machines). */
|
|
22
|
-
projectKey: string;
|
|
23
|
-
/** push|pull|both */
|
|
24
|
-
direction: MongoSyncDirection;
|
|
25
|
-
/** Batch size for push/pull loops. */
|
|
26
|
-
batchSize: number;
|
|
27
|
-
/** If true, hooks will run a sync at SessionEnd. */
|
|
28
|
-
autoSyncOnSessionEnd: boolean;
|
|
29
|
-
createdAt: string;
|
|
30
|
-
updatedAt: string;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
34
|
-
return typeof value === 'object' && value !== null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function asNonEmptyString(value: unknown): string | null {
|
|
38
|
-
if (typeof value !== 'string') return null;
|
|
39
|
-
const s = value.trim();
|
|
40
|
-
return s.length > 0 ? s : null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function asPositiveInt(value: unknown): number | null {
|
|
44
|
-
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
45
|
-
const n = Math.trunc(value);
|
|
46
|
-
return n > 0 ? n : null;
|
|
47
|
-
}
|
|
48
|
-
if (typeof value === 'string') {
|
|
49
|
-
const n = parseInt(value, 10);
|
|
50
|
-
return Number.isFinite(n) && n > 0 ? n : null;
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function normalizeDirection(value: unknown): MongoSyncDirection | null {
|
|
56
|
-
if (typeof value !== 'string') return null;
|
|
57
|
-
const v = value.toLowerCase();
|
|
58
|
-
if (v === 'push' || v === 'pull' || v === 'both') return v;
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function getMongoSyncConfigPath(storagePath: string): string {
|
|
63
|
-
return path.join(storagePath, 'mongo-sync.json');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function readMongoSyncConfig(storagePath: string): MongoSyncConfig | null {
|
|
67
|
-
const configPath = getMongoSyncConfigPath(storagePath);
|
|
68
|
-
if (!fs.existsSync(configPath)) return null;
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
const raw = JSON.parse(fs.readFileSync(configPath, 'utf8')) as unknown;
|
|
72
|
-
if (!isRecord(raw)) return null;
|
|
73
|
-
|
|
74
|
-
const version = raw.version;
|
|
75
|
-
if (version !== 1) return null;
|
|
76
|
-
|
|
77
|
-
const enabled = raw.enabled;
|
|
78
|
-
if (typeof enabled !== 'boolean') return null;
|
|
79
|
-
|
|
80
|
-
const uri = asNonEmptyString(raw.uri);
|
|
81
|
-
const dbName = asNonEmptyString(raw.dbName);
|
|
82
|
-
const projectKey = asNonEmptyString(raw.projectKey);
|
|
83
|
-
const direction = normalizeDirection(raw.direction);
|
|
84
|
-
const batchSize = asPositiveInt(raw.batchSize) ?? 500;
|
|
85
|
-
const autoSyncOnSessionEnd = typeof raw.autoSyncOnSessionEnd === 'boolean' ? raw.autoSyncOnSessionEnd : true;
|
|
86
|
-
const createdAt = asNonEmptyString(raw.createdAt) ?? new Date(0).toISOString();
|
|
87
|
-
const updatedAt = asNonEmptyString(raw.updatedAt) ?? new Date(0).toISOString();
|
|
88
|
-
|
|
89
|
-
if (!uri || !dbName || !projectKey || !direction) return null;
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
version: 1,
|
|
93
|
-
enabled,
|
|
94
|
-
uri,
|
|
95
|
-
dbName,
|
|
96
|
-
projectKey,
|
|
97
|
-
direction,
|
|
98
|
-
batchSize,
|
|
99
|
-
autoSyncOnSessionEnd,
|
|
100
|
-
createdAt,
|
|
101
|
-
updatedAt
|
|
102
|
-
};
|
|
103
|
-
} catch {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function writeMongoSyncConfig(storagePath: string, config: Omit<MongoSyncConfig, 'version' | 'createdAt' | 'updatedAt'> & {
|
|
109
|
-
createdAt?: string;
|
|
110
|
-
updatedAt?: string;
|
|
111
|
-
}): MongoSyncConfig {
|
|
112
|
-
if (!fs.existsSync(storagePath)) {
|
|
113
|
-
fs.mkdirSync(storagePath, { recursive: true });
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const now = new Date().toISOString();
|
|
117
|
-
const existing = readMongoSyncConfig(storagePath);
|
|
118
|
-
|
|
119
|
-
const normalized: MongoSyncConfig = {
|
|
120
|
-
version: 1,
|
|
121
|
-
enabled: config.enabled,
|
|
122
|
-
uri: config.uri,
|
|
123
|
-
dbName: config.dbName,
|
|
124
|
-
projectKey: config.projectKey,
|
|
125
|
-
direction: config.direction,
|
|
126
|
-
batchSize: config.batchSize,
|
|
127
|
-
autoSyncOnSessionEnd: config.autoSyncOnSessionEnd,
|
|
128
|
-
createdAt: config.createdAt ?? existing?.createdAt ?? now,
|
|
129
|
-
updatedAt: config.updatedAt ?? now
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const configPath = getMongoSyncConfigPath(storagePath);
|
|
133
|
-
const tmpPath = `${configPath}.tmp`;
|
|
134
|
-
|
|
135
|
-
// 0600: contains secrets (Mongo URI may embed credentials)
|
|
136
|
-
fs.writeFileSync(tmpPath, JSON.stringify(normalized, null, 2), { mode: 0o600 });
|
|
137
|
-
fs.renameSync(tmpPath, configPath);
|
|
138
|
-
|
|
139
|
-
return normalized;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function removeMongoSyncConfig(storagePath: string): boolean {
|
|
143
|
-
const configPath = getMongoSyncConfigPath(storagePath);
|
|
144
|
-
if (!fs.existsSync(configPath)) return false;
|
|
145
|
-
fs.unlinkSync(configPath);
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export function redactMongoUri(uri: string): string {
|
|
150
|
-
// mongodb://user:pass@host:port/ -> mongodb://user:***@host:port/
|
|
151
|
-
// mongodb+srv://user:pass@host/ -> mongodb+srv://user:***@host/
|
|
152
|
-
const schemeIdx = uri.indexOf('://');
|
|
153
|
-
if (schemeIdx === -1) return uri;
|
|
154
|
-
const atIdx = uri.indexOf('@', schemeIdx + 3);
|
|
155
|
-
if (atIdx === -1) return uri;
|
|
156
|
-
|
|
157
|
-
const creds = uri.slice(schemeIdx + 3, atIdx); // user:pass
|
|
158
|
-
const colonIdx = creds.indexOf(':');
|
|
159
|
-
if (colonIdx === -1) return uri;
|
|
160
|
-
|
|
161
|
-
const prefix = uri.slice(0, schemeIdx + 3 + colonIdx + 1);
|
|
162
|
-
const suffix = uri.slice(atIdx);
|
|
163
|
-
return `${prefix}***${suffix}`;
|
|
164
|
-
}
|
|
165
|
-
|
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mongo Sync Worker
|
|
3
|
-
* Optional: sync per-project SQLite events to a shared MongoDB database.
|
|
4
|
-
*
|
|
5
|
-
* Design goals:
|
|
6
|
-
* - Optional and decoupled (doesn't affect default local-only flow)
|
|
7
|
-
* - Idempotent (safe to retry)
|
|
8
|
-
* - Incremental push (SQLite rowid) and incremental pull (Mongo seq per project)
|
|
9
|
-
*
|
|
10
|
-
* NOTE:
|
|
11
|
-
* - We only sync immutable L0 events (events table). Derived tables can be rebuilt.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { randomUUID } from 'crypto';
|
|
15
|
-
import * as os from 'os';
|
|
16
|
-
import { MongoClient } from 'mongodb';
|
|
17
|
-
import type { Collection, Db } from 'mongodb';
|
|
18
|
-
|
|
19
|
-
import type { MemoryEvent } from './types.js';
|
|
20
|
-
import { SQLiteEventStore } from './sqlite-event-store.js';
|
|
21
|
-
|
|
22
|
-
export type MongoSyncDirection = 'push' | 'pull' | 'both';
|
|
23
|
-
|
|
24
|
-
export interface MongoSyncWorkerConfig {
|
|
25
|
-
uri: string;
|
|
26
|
-
dbName: string;
|
|
27
|
-
projectKey: string;
|
|
28
|
-
direction?: MongoSyncDirection;
|
|
29
|
-
intervalMs?: number;
|
|
30
|
-
batchSize?: number;
|
|
31
|
-
instanceId?: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface MongoSyncStats {
|
|
35
|
-
lastSyncAt: Date | null;
|
|
36
|
-
pushedEvents: number;
|
|
37
|
-
pulledEvents: number;
|
|
38
|
-
errors: number;
|
|
39
|
-
status: 'idle' | 'syncing' | 'error' | 'stopped';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
interface CounterDoc {
|
|
43
|
-
_id: string;
|
|
44
|
-
seq: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface RemoteEventDoc {
|
|
48
|
-
_id: string;
|
|
49
|
-
projectKey: string;
|
|
50
|
-
seq: number;
|
|
51
|
-
eventId: string;
|
|
52
|
-
eventType: string;
|
|
53
|
-
sessionId: string;
|
|
54
|
-
timestamp: Date;
|
|
55
|
-
content: string;
|
|
56
|
-
canonicalKey: string;
|
|
57
|
-
dedupeKey: string;
|
|
58
|
-
metadata?: Record<string, unknown> | null;
|
|
59
|
-
insertedAt: Date;
|
|
60
|
-
updatedAt: Date;
|
|
61
|
-
source?: {
|
|
62
|
-
hostname?: string;
|
|
63
|
-
instanceId?: string;
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function redactMongoUri(uri: string): string {
|
|
68
|
-
// mongodb://user:pass@host:port/ -> mongodb://user:***@host:port/
|
|
69
|
-
// mongodb+srv://user:pass@host/ -> mongodb+srv://user:***@host/
|
|
70
|
-
const schemeIdx = uri.indexOf('://');
|
|
71
|
-
if (schemeIdx === -1) return uri;
|
|
72
|
-
const atIdx = uri.indexOf('@', schemeIdx + 3);
|
|
73
|
-
if (atIdx === -1) return uri;
|
|
74
|
-
|
|
75
|
-
const creds = uri.slice(schemeIdx + 3, atIdx); // user:pass
|
|
76
|
-
const colonIdx = creds.indexOf(':');
|
|
77
|
-
if (colonIdx === -1) return uri;
|
|
78
|
-
|
|
79
|
-
const prefix = uri.slice(0, schemeIdx + 3 + colonIdx + 1);
|
|
80
|
-
const suffix = uri.slice(atIdx);
|
|
81
|
-
return `${prefix}***${suffix}`;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function parseIntOrZero(value: string | null | undefined): number {
|
|
85
|
-
if (!value) return 0;
|
|
86
|
-
const n = parseInt(value, 10);
|
|
87
|
-
return Number.isFinite(n) ? n : 0;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export class MongoSyncWorker {
|
|
91
|
-
private readonly config: Required<Omit<MongoSyncWorkerConfig, 'instanceId'>> & { instanceId: string };
|
|
92
|
-
private intervalHandle: NodeJS.Timeout | null = null;
|
|
93
|
-
private running = false;
|
|
94
|
-
|
|
95
|
-
private client: MongoClient | null = null;
|
|
96
|
-
private db: Db | null = null;
|
|
97
|
-
private counters: Collection<CounterDoc> | null = null;
|
|
98
|
-
private events: Collection<RemoteEventDoc> | null = null;
|
|
99
|
-
private indexesEnsured = false;
|
|
100
|
-
|
|
101
|
-
private stats: MongoSyncStats = {
|
|
102
|
-
lastSyncAt: null,
|
|
103
|
-
pushedEvents: 0,
|
|
104
|
-
pulledEvents: 0,
|
|
105
|
-
errors: 0,
|
|
106
|
-
status: 'idle'
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
constructor(
|
|
110
|
-
private readonly sqliteStore: SQLiteEventStore,
|
|
111
|
-
config: MongoSyncWorkerConfig
|
|
112
|
-
) {
|
|
113
|
-
this.config = {
|
|
114
|
-
uri: config.uri,
|
|
115
|
-
dbName: config.dbName,
|
|
116
|
-
projectKey: config.projectKey,
|
|
117
|
-
direction: config.direction ?? 'both',
|
|
118
|
-
intervalMs: config.intervalMs ?? 30000,
|
|
119
|
-
batchSize: config.batchSize ?? 500,
|
|
120
|
-
instanceId: config.instanceId ?? randomUUID()
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
start(): void {
|
|
125
|
-
if (this.running) return;
|
|
126
|
-
this.running = true;
|
|
127
|
-
this.stats.status = 'idle';
|
|
128
|
-
|
|
129
|
-
// Initial sync
|
|
130
|
-
this.syncNow().catch((err) => {
|
|
131
|
-
console.error('[MongoSyncWorker] Initial sync failed:', err);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// Periodic sync
|
|
135
|
-
this.intervalHandle = setInterval(() => {
|
|
136
|
-
this.syncNow().catch((err) => {
|
|
137
|
-
console.error('[MongoSyncWorker] Periodic sync failed:', err);
|
|
138
|
-
});
|
|
139
|
-
}, this.config.intervalMs);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
stop(): void {
|
|
143
|
-
this.running = false;
|
|
144
|
-
this.stats.status = 'stopped';
|
|
145
|
-
|
|
146
|
-
if (this.intervalHandle) {
|
|
147
|
-
clearInterval(this.intervalHandle);
|
|
148
|
-
this.intervalHandle = null;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async shutdown(): Promise<void> {
|
|
153
|
-
this.stop();
|
|
154
|
-
await this.disconnect();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
getStats(): MongoSyncStats {
|
|
158
|
-
return { ...this.stats };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
isRunning(): boolean {
|
|
162
|
-
return this.running;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async syncNow(): Promise<{ pushed: number; pulled: number }> {
|
|
166
|
-
if (this.stats.status === 'syncing') return { pushed: 0, pulled: 0 };
|
|
167
|
-
|
|
168
|
-
this.stats.status = 'syncing';
|
|
169
|
-
let pushed = 0;
|
|
170
|
-
let pulled = 0;
|
|
171
|
-
|
|
172
|
-
try {
|
|
173
|
-
await this.sqliteStore.initialize();
|
|
174
|
-
await this.ensureConnected();
|
|
175
|
-
await this.ensureIndexes();
|
|
176
|
-
|
|
177
|
-
if (this.config.direction === 'push' || this.config.direction === 'both') {
|
|
178
|
-
pushed = await this.pushEvents();
|
|
179
|
-
this.stats.pushedEvents += pushed;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (this.config.direction === 'pull' || this.config.direction === 'both') {
|
|
183
|
-
pulled = await this.pullEvents();
|
|
184
|
-
this.stats.pulledEvents += pulled;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
this.stats.lastSyncAt = new Date();
|
|
188
|
-
this.stats.status = 'idle';
|
|
189
|
-
return { pushed, pulled };
|
|
190
|
-
} catch (error) {
|
|
191
|
-
this.stats.errors++;
|
|
192
|
-
this.stats.status = 'error';
|
|
193
|
-
throw error;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
private async ensureConnected(): Promise<void> {
|
|
198
|
-
if (this.client && this.db && this.counters && this.events) return;
|
|
199
|
-
|
|
200
|
-
try {
|
|
201
|
-
this.client = new MongoClient(this.config.uri, {
|
|
202
|
-
appName: 'claude-memory-layer',
|
|
203
|
-
serverSelectionTimeoutMS: 5000
|
|
204
|
-
});
|
|
205
|
-
await this.client.connect();
|
|
206
|
-
this.db = this.client.db(this.config.dbName);
|
|
207
|
-
this.counters = this.db.collection<CounterDoc>('cml_counters');
|
|
208
|
-
this.events = this.db.collection<RemoteEventDoc>('cml_events');
|
|
209
|
-
} catch (err) {
|
|
210
|
-
// Avoid leaking credentials in logs
|
|
211
|
-
const safeUri = redactMongoUri(this.config.uri);
|
|
212
|
-
throw new Error(`MongoDB connection failed (${safeUri}, db=${this.config.dbName}): ${String(err)}`);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
private async disconnect(): Promise<void> {
|
|
217
|
-
try {
|
|
218
|
-
await this.client?.close();
|
|
219
|
-
} finally {
|
|
220
|
-
this.client = null;
|
|
221
|
-
this.db = null;
|
|
222
|
-
this.counters = null;
|
|
223
|
-
this.events = null;
|
|
224
|
-
this.indexesEnsured = false;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
private async ensureIndexes(): Promise<void> {
|
|
229
|
-
if (this.indexesEnsured) return;
|
|
230
|
-
if (!this.events || !this.counters) throw new Error('Mongo not connected');
|
|
231
|
-
|
|
232
|
-
// Best-effort: if the user lacks index privileges, sync can still work (slower)
|
|
233
|
-
try {
|
|
234
|
-
await this.events.createIndex({ projectKey: 1, seq: 1 }, { unique: true });
|
|
235
|
-
await this.events.createIndex({ projectKey: 1, eventId: 1 }, { unique: true });
|
|
236
|
-
await this.events.createIndex({ projectKey: 1, dedupeKey: 1 });
|
|
237
|
-
} catch (err) {
|
|
238
|
-
console.warn('[MongoSyncWorker] Failed to ensure indexes (continuing):', err);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
this.indexesEnsured = true;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
private counterKey(kind: 'events'): string {
|
|
245
|
-
return `${kind}:${this.config.projectKey}`;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
private async allocateSeqRange(kind: 'events', count: number): Promise<number> {
|
|
249
|
-
if (!this.counters) throw new Error('Mongo not connected');
|
|
250
|
-
if (count <= 0) return 1;
|
|
251
|
-
|
|
252
|
-
const key = this.counterKey(kind);
|
|
253
|
-
const doc = await this.counters.findOneAndUpdate(
|
|
254
|
-
{ _id: key },
|
|
255
|
-
{ $inc: { seq: count } },
|
|
256
|
-
{ upsert: true, returnDocument: 'after' }
|
|
257
|
-
);
|
|
258
|
-
|
|
259
|
-
const endSeq = doc?.seq;
|
|
260
|
-
if (typeof endSeq !== 'number') {
|
|
261
|
-
throw new Error(`Failed to allocate seq range for ${key}`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return endSeq - count + 1;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
private pushTargetName(): string {
|
|
268
|
-
return `mongo_push_events_rowid:${this.config.projectKey}`;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
private pullTargetName(): string {
|
|
272
|
-
return `mongo_pull_events_seq:${this.config.projectKey}`;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
private async pushEvents(): Promise<number> {
|
|
276
|
-
if (!this.events) throw new Error('Mongo not connected');
|
|
277
|
-
|
|
278
|
-
const position = await this.sqliteStore.getSyncPosition(this.pushTargetName());
|
|
279
|
-
let lastRowid = parseIntOrZero(position.lastEventId);
|
|
280
|
-
|
|
281
|
-
let pushed = 0;
|
|
282
|
-
|
|
283
|
-
while (true) {
|
|
284
|
-
const batch = await this.sqliteStore.getEventsSinceRowid(lastRowid, this.config.batchSize);
|
|
285
|
-
if (batch.length === 0) break;
|
|
286
|
-
|
|
287
|
-
const startSeq = await this.allocateSeqRange('events', batch.length);
|
|
288
|
-
const now = new Date();
|
|
289
|
-
const hostname = os.hostname();
|
|
290
|
-
|
|
291
|
-
const ops = batch.map((item, idx) => {
|
|
292
|
-
const event = item.event as unknown as MemoryEvent;
|
|
293
|
-
const seq = startSeq + idx;
|
|
294
|
-
const docId = `${this.config.projectKey}:${event.id}`;
|
|
295
|
-
|
|
296
|
-
return {
|
|
297
|
-
updateOne: {
|
|
298
|
-
filter: { _id: docId },
|
|
299
|
-
update: {
|
|
300
|
-
$setOnInsert: {
|
|
301
|
-
_id: docId,
|
|
302
|
-
projectKey: this.config.projectKey,
|
|
303
|
-
seq,
|
|
304
|
-
eventId: event.id,
|
|
305
|
-
eventType: event.eventType,
|
|
306
|
-
sessionId: event.sessionId,
|
|
307
|
-
timestamp: event.timestamp,
|
|
308
|
-
content: event.content,
|
|
309
|
-
canonicalKey: event.canonicalKey,
|
|
310
|
-
dedupeKey: event.dedupeKey,
|
|
311
|
-
metadata: event.metadata ?? null,
|
|
312
|
-
insertedAt: now,
|
|
313
|
-
updatedAt: now,
|
|
314
|
-
source: { hostname, instanceId: this.config.instanceId }
|
|
315
|
-
}
|
|
316
|
-
},
|
|
317
|
-
upsert: true
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
await this.events.bulkWrite(ops, { ordered: false });
|
|
323
|
-
|
|
324
|
-
const last = batch[batch.length - 1];
|
|
325
|
-
lastRowid = last.rowid;
|
|
326
|
-
await this.sqliteStore.updateSyncPosition(
|
|
327
|
-
this.pushTargetName(),
|
|
328
|
-
String(lastRowid),
|
|
329
|
-
last.event.timestamp.toISOString()
|
|
330
|
-
);
|
|
331
|
-
|
|
332
|
-
pushed += batch.length;
|
|
333
|
-
if (batch.length < this.config.batchSize) break;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
return pushed;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
private async pullEvents(): Promise<number> {
|
|
340
|
-
if (!this.events) throw new Error('Mongo not connected');
|
|
341
|
-
|
|
342
|
-
const position = await this.sqliteStore.getSyncPosition(this.pullTargetName());
|
|
343
|
-
let lastSeq = parseIntOrZero(position.lastEventId);
|
|
344
|
-
|
|
345
|
-
let pulled = 0;
|
|
346
|
-
|
|
347
|
-
while (true) {
|
|
348
|
-
const docs = await this.events.find(
|
|
349
|
-
{ projectKey: this.config.projectKey, seq: { $gt: lastSeq } },
|
|
350
|
-
{ sort: { seq: 1 }, limit: this.config.batchSize }
|
|
351
|
-
).toArray();
|
|
352
|
-
|
|
353
|
-
if (docs.length === 0) break;
|
|
354
|
-
|
|
355
|
-
const events: MemoryEvent[] = docs.map((d) => ({
|
|
356
|
-
id: d.eventId,
|
|
357
|
-
eventType: d.eventType as any,
|
|
358
|
-
sessionId: d.sessionId,
|
|
359
|
-
timestamp: d.timestamp instanceof Date ? d.timestamp : new Date(d.timestamp),
|
|
360
|
-
content: d.content,
|
|
361
|
-
canonicalKey: d.canonicalKey,
|
|
362
|
-
dedupeKey: d.dedupeKey,
|
|
363
|
-
metadata: d.metadata ?? undefined
|
|
364
|
-
}));
|
|
365
|
-
|
|
366
|
-
const result = await this.sqliteStore.importEvents(events);
|
|
367
|
-
pulled += result.inserted;
|
|
368
|
-
|
|
369
|
-
lastSeq = docs[docs.length - 1].seq;
|
|
370
|
-
await this.sqliteStore.updateSyncPosition(
|
|
371
|
-
this.pullTargetName(),
|
|
372
|
-
String(lastSeq),
|
|
373
|
-
new Date().toISOString()
|
|
374
|
-
);
|
|
375
|
-
|
|
376
|
-
if (docs.length < this.config.batchSize) break;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
return pulled;
|
|
380
|
-
}
|
|
381
|
-
}
|