claude-memory-layer 1.0.8 → 1.0.9
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/.claude/settings.local.json +7 -1
- package/.claude-memory/test.sqlite +0 -0
- package/.history/package_20260202114053.json +49 -0
- package/HANDOFF.md +92 -0
- package/dist/cli/index.js +1150 -71
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +1033 -47
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/post-tool-use.js +5589 -0
- package/dist/hooks/post-tool-use.js.map +7 -0
- package/dist/hooks/session-end.js +1117 -64
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +1112 -63
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +1117 -64
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +1151 -67
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +1145 -70
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +1145 -70
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +1122 -65
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/app.js +304 -0
- package/dist/ui/index.html +195 -1188
- package/dist/ui/style.css +595 -0
- package/package.json +3 -1
- package/scripts/build.ts +2 -0
- package/src/core/event-store.ts +18 -0
- package/src/core/index.ts +3 -0
- package/src/core/retriever.ts +4 -1
- package/src/core/sqlite-event-store.ts +849 -0
- package/src/core/sqlite-wrapper.ts +108 -0
- package/src/core/sync-worker.ts +228 -0
- package/src/core/vector-worker.ts +44 -14
- package/src/hooks/user-prompt-submit.ts +53 -4
- package/src/server/api/stats.ts +37 -7
- package/src/services/memory-service.ts +168 -39
- package/src/ui/app.js +304 -0
- package/src/ui/index.html +195 -1188
- package/src/ui/style.css +595 -0
- package/test_access.js +49 -0
|
@@ -700,26 +700,975 @@ var EventStore = class {
|
|
|
700
700
|
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
701
701
|
}));
|
|
702
702
|
}
|
|
703
|
+
/**
|
|
704
|
+
* Increment access count for events (stub for compatibility)
|
|
705
|
+
*/
|
|
706
|
+
async incrementAccessCount(eventIds) {
|
|
707
|
+
return Promise.resolve();
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Get most accessed memories (stub for compatibility)
|
|
711
|
+
*/
|
|
712
|
+
async getMostAccessed(limit = 10) {
|
|
713
|
+
return [];
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Close database connection
|
|
717
|
+
*/
|
|
718
|
+
async close() {
|
|
719
|
+
await dbClose(this.db);
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Convert database row to MemoryEvent
|
|
723
|
+
*/
|
|
724
|
+
rowToEvent(row) {
|
|
725
|
+
return {
|
|
726
|
+
id: row.id,
|
|
727
|
+
eventType: row.event_type,
|
|
728
|
+
sessionId: row.session_id,
|
|
729
|
+
timestamp: toDate(row.timestamp),
|
|
730
|
+
content: row.content,
|
|
731
|
+
canonicalKey: row.canonical_key,
|
|
732
|
+
dedupeKey: row.dedupe_key,
|
|
733
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
// src/core/sqlite-event-store.ts
|
|
739
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
740
|
+
|
|
741
|
+
// src/core/sqlite-wrapper.ts
|
|
742
|
+
import Database from "better-sqlite3";
|
|
743
|
+
function createSQLiteDatabase(path2, options) {
|
|
744
|
+
const db = new Database(path2, {
|
|
745
|
+
readonly: options?.readonly ?? false
|
|
746
|
+
});
|
|
747
|
+
if (!options?.readonly && (options?.walMode ?? true)) {
|
|
748
|
+
db.pragma("journal_mode = WAL");
|
|
749
|
+
db.pragma("synchronous = NORMAL");
|
|
750
|
+
db.pragma("busy_timeout = 5000");
|
|
751
|
+
}
|
|
752
|
+
return db;
|
|
753
|
+
}
|
|
754
|
+
function sqliteRun(db, sql, params = []) {
|
|
755
|
+
const stmt = db.prepare(sql);
|
|
756
|
+
return stmt.run(...params);
|
|
757
|
+
}
|
|
758
|
+
function sqliteAll(db, sql, params = []) {
|
|
759
|
+
const stmt = db.prepare(sql);
|
|
760
|
+
return stmt.all(...params);
|
|
761
|
+
}
|
|
762
|
+
function sqliteGet(db, sql, params = []) {
|
|
763
|
+
const stmt = db.prepare(sql);
|
|
764
|
+
return stmt.get(...params);
|
|
765
|
+
}
|
|
766
|
+
function sqliteExec(db, sql) {
|
|
767
|
+
db.exec(sql);
|
|
768
|
+
}
|
|
769
|
+
function sqliteClose(db) {
|
|
770
|
+
db.close();
|
|
771
|
+
}
|
|
772
|
+
function toDateFromSQLite(value) {
|
|
773
|
+
if (value instanceof Date)
|
|
774
|
+
return value;
|
|
775
|
+
if (typeof value === "string")
|
|
776
|
+
return new Date(value);
|
|
777
|
+
if (typeof value === "number")
|
|
778
|
+
return new Date(value);
|
|
779
|
+
return new Date(String(value));
|
|
780
|
+
}
|
|
781
|
+
function toSQLiteTimestamp(date) {
|
|
782
|
+
return date.toISOString();
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// src/core/sqlite-event-store.ts
|
|
786
|
+
var SQLiteEventStore = class {
|
|
787
|
+
constructor(dbPath, options) {
|
|
788
|
+
this.dbPath = dbPath;
|
|
789
|
+
this.readOnly = options?.readonly ?? false;
|
|
790
|
+
this.db = createSQLiteDatabase(dbPath, {
|
|
791
|
+
readonly: this.readOnly,
|
|
792
|
+
walMode: !this.readOnly
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
db;
|
|
796
|
+
initialized = false;
|
|
797
|
+
readOnly;
|
|
798
|
+
/**
|
|
799
|
+
* Initialize database schema
|
|
800
|
+
*/
|
|
801
|
+
async initialize() {
|
|
802
|
+
if (this.initialized)
|
|
803
|
+
return;
|
|
804
|
+
if (this.readOnly) {
|
|
805
|
+
this.initialized = true;
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
sqliteExec(this.db, `
|
|
809
|
+
-- L0 EventStore: Single Source of Truth (immutable, append-only)
|
|
810
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
811
|
+
id TEXT PRIMARY KEY,
|
|
812
|
+
event_type TEXT NOT NULL,
|
|
813
|
+
session_id TEXT NOT NULL,
|
|
814
|
+
timestamp TEXT NOT NULL,
|
|
815
|
+
content TEXT NOT NULL,
|
|
816
|
+
canonical_key TEXT NOT NULL,
|
|
817
|
+
dedupe_key TEXT UNIQUE,
|
|
818
|
+
metadata TEXT,
|
|
819
|
+
access_count INTEGER DEFAULT 0,
|
|
820
|
+
last_accessed_at TEXT
|
|
821
|
+
);
|
|
822
|
+
|
|
823
|
+
-- Dedup table for idempotency
|
|
824
|
+
CREATE TABLE IF NOT EXISTS event_dedup (
|
|
825
|
+
dedupe_key TEXT PRIMARY KEY,
|
|
826
|
+
event_id TEXT NOT NULL,
|
|
827
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
828
|
+
);
|
|
829
|
+
|
|
830
|
+
-- Session metadata
|
|
831
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
832
|
+
id TEXT PRIMARY KEY,
|
|
833
|
+
started_at TEXT NOT NULL,
|
|
834
|
+
ended_at TEXT,
|
|
835
|
+
project_path TEXT,
|
|
836
|
+
summary TEXT,
|
|
837
|
+
tags TEXT
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
-- Insights (derived data, rebuildable)
|
|
841
|
+
CREATE TABLE IF NOT EXISTS insights (
|
|
842
|
+
id TEXT PRIMARY KEY,
|
|
843
|
+
insight_type TEXT NOT NULL,
|
|
844
|
+
content TEXT NOT NULL,
|
|
845
|
+
canonical_key TEXT NOT NULL,
|
|
846
|
+
confidence REAL,
|
|
847
|
+
source_events TEXT,
|
|
848
|
+
created_at TEXT,
|
|
849
|
+
last_updated TEXT
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
-- Embedding Outbox (Single-Writer Pattern)
|
|
853
|
+
CREATE TABLE IF NOT EXISTS embedding_outbox (
|
|
854
|
+
id TEXT PRIMARY KEY,
|
|
855
|
+
event_id TEXT NOT NULL,
|
|
856
|
+
content TEXT NOT NULL,
|
|
857
|
+
status TEXT DEFAULT 'pending',
|
|
858
|
+
retry_count INTEGER DEFAULT 0,
|
|
859
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
860
|
+
processed_at TEXT,
|
|
861
|
+
error_message TEXT
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
-- Projection offset tracking
|
|
865
|
+
CREATE TABLE IF NOT EXISTS projection_offsets (
|
|
866
|
+
projection_name TEXT PRIMARY KEY,
|
|
867
|
+
last_event_id TEXT,
|
|
868
|
+
last_timestamp TEXT,
|
|
869
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
870
|
+
);
|
|
871
|
+
|
|
872
|
+
-- Memory level tracking
|
|
873
|
+
CREATE TABLE IF NOT EXISTS memory_levels (
|
|
874
|
+
event_id TEXT PRIMARY KEY,
|
|
875
|
+
level TEXT NOT NULL DEFAULT 'L0',
|
|
876
|
+
promoted_at TEXT DEFAULT (datetime('now'))
|
|
877
|
+
);
|
|
878
|
+
|
|
879
|
+
-- Entries (immutable memory units)
|
|
880
|
+
CREATE TABLE IF NOT EXISTS entries (
|
|
881
|
+
entry_id TEXT PRIMARY KEY,
|
|
882
|
+
created_ts TEXT NOT NULL,
|
|
883
|
+
entry_type TEXT NOT NULL,
|
|
884
|
+
title TEXT NOT NULL,
|
|
885
|
+
content_json TEXT NOT NULL,
|
|
886
|
+
stage TEXT NOT NULL DEFAULT 'raw',
|
|
887
|
+
status TEXT DEFAULT 'active',
|
|
888
|
+
superseded_by TEXT,
|
|
889
|
+
build_id TEXT,
|
|
890
|
+
evidence_json TEXT,
|
|
891
|
+
canonical_key TEXT,
|
|
892
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
893
|
+
);
|
|
894
|
+
|
|
895
|
+
-- Entities (task/condition/artifact)
|
|
896
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
897
|
+
entity_id TEXT PRIMARY KEY,
|
|
898
|
+
entity_type TEXT NOT NULL,
|
|
899
|
+
canonical_key TEXT NOT NULL,
|
|
900
|
+
title TEXT NOT NULL,
|
|
901
|
+
stage TEXT NOT NULL DEFAULT 'raw',
|
|
902
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
903
|
+
current_json TEXT NOT NULL,
|
|
904
|
+
title_norm TEXT,
|
|
905
|
+
search_text TEXT,
|
|
906
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
907
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
908
|
+
);
|
|
909
|
+
|
|
910
|
+
-- Entity aliases for canonical key lookup
|
|
911
|
+
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
912
|
+
entity_type TEXT NOT NULL,
|
|
913
|
+
canonical_key TEXT NOT NULL,
|
|
914
|
+
entity_id TEXT NOT NULL,
|
|
915
|
+
is_primary INTEGER DEFAULT 0,
|
|
916
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
917
|
+
PRIMARY KEY(entity_type, canonical_key)
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
-- Edges (relationships between entries/entities)
|
|
921
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
922
|
+
edge_id TEXT PRIMARY KEY,
|
|
923
|
+
src_type TEXT NOT NULL,
|
|
924
|
+
src_id TEXT NOT NULL,
|
|
925
|
+
rel_type TEXT NOT NULL,
|
|
926
|
+
dst_type TEXT NOT NULL,
|
|
927
|
+
dst_id TEXT NOT NULL,
|
|
928
|
+
meta_json TEXT,
|
|
929
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
930
|
+
);
|
|
931
|
+
|
|
932
|
+
-- Vector Outbox V2 Table
|
|
933
|
+
CREATE TABLE IF NOT EXISTS vector_outbox (
|
|
934
|
+
job_id TEXT PRIMARY KEY,
|
|
935
|
+
item_kind TEXT NOT NULL,
|
|
936
|
+
item_id TEXT NOT NULL,
|
|
937
|
+
embedding_version TEXT NOT NULL,
|
|
938
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
939
|
+
retry_count INTEGER DEFAULT 0,
|
|
940
|
+
error TEXT,
|
|
941
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
942
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
943
|
+
UNIQUE(item_kind, item_id, embedding_version)
|
|
944
|
+
);
|
|
945
|
+
|
|
946
|
+
-- Build Runs
|
|
947
|
+
CREATE TABLE IF NOT EXISTS build_runs (
|
|
948
|
+
build_id TEXT PRIMARY KEY,
|
|
949
|
+
started_at TEXT NOT NULL,
|
|
950
|
+
finished_at TEXT,
|
|
951
|
+
extractor_model TEXT NOT NULL,
|
|
952
|
+
extractor_prompt_hash TEXT NOT NULL,
|
|
953
|
+
embedder_model TEXT NOT NULL,
|
|
954
|
+
embedding_version TEXT NOT NULL,
|
|
955
|
+
idris_version TEXT NOT NULL,
|
|
956
|
+
schema_version TEXT NOT NULL,
|
|
957
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
958
|
+
error TEXT
|
|
959
|
+
);
|
|
960
|
+
|
|
961
|
+
-- Pipeline Metrics
|
|
962
|
+
CREATE TABLE IF NOT EXISTS pipeline_metrics (
|
|
963
|
+
id TEXT PRIMARY KEY,
|
|
964
|
+
ts TEXT NOT NULL,
|
|
965
|
+
stage TEXT NOT NULL,
|
|
966
|
+
latency_ms REAL NOT NULL,
|
|
967
|
+
success INTEGER NOT NULL,
|
|
968
|
+
error TEXT,
|
|
969
|
+
session_id TEXT
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
-- Working Set table (active memory window)
|
|
973
|
+
CREATE TABLE IF NOT EXISTS working_set (
|
|
974
|
+
id TEXT PRIMARY KEY,
|
|
975
|
+
event_id TEXT NOT NULL,
|
|
976
|
+
added_at TEXT DEFAULT (datetime('now')),
|
|
977
|
+
relevance_score REAL DEFAULT 1.0,
|
|
978
|
+
topics TEXT,
|
|
979
|
+
expires_at TEXT
|
|
980
|
+
);
|
|
981
|
+
|
|
982
|
+
-- Consolidated Memories table (long-term integrated memories)
|
|
983
|
+
CREATE TABLE IF NOT EXISTS consolidated_memories (
|
|
984
|
+
memory_id TEXT PRIMARY KEY,
|
|
985
|
+
summary TEXT NOT NULL,
|
|
986
|
+
topics TEXT,
|
|
987
|
+
source_events TEXT,
|
|
988
|
+
confidence REAL DEFAULT 0.5,
|
|
989
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
990
|
+
accessed_at TEXT,
|
|
991
|
+
access_count INTEGER DEFAULT 0
|
|
992
|
+
);
|
|
993
|
+
|
|
994
|
+
-- Continuity Log table (tracks context transitions)
|
|
995
|
+
CREATE TABLE IF NOT EXISTS continuity_log (
|
|
996
|
+
log_id TEXT PRIMARY KEY,
|
|
997
|
+
from_context_id TEXT,
|
|
998
|
+
to_context_id TEXT,
|
|
999
|
+
continuity_score REAL,
|
|
1000
|
+
transition_type TEXT,
|
|
1001
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
1002
|
+
);
|
|
1003
|
+
|
|
1004
|
+
-- Endless Mode Config table
|
|
1005
|
+
CREATE TABLE IF NOT EXISTS endless_config (
|
|
1006
|
+
key TEXT PRIMARY KEY,
|
|
1007
|
+
value TEXT,
|
|
1008
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
1009
|
+
);
|
|
1010
|
+
|
|
1011
|
+
-- Sync position tracking (for SQLite -> DuckDB sync)
|
|
1012
|
+
CREATE TABLE IF NOT EXISTS sync_positions (
|
|
1013
|
+
target_name TEXT PRIMARY KEY,
|
|
1014
|
+
last_event_id TEXT,
|
|
1015
|
+
last_timestamp TEXT,
|
|
1016
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
1017
|
+
);
|
|
1018
|
+
|
|
1019
|
+
-- Create indexes
|
|
1020
|
+
CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
|
|
1021
|
+
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
|
|
1022
|
+
CREATE INDEX IF NOT EXISTS idx_entries_type ON entries(entry_type);
|
|
1023
|
+
CREATE INDEX IF NOT EXISTS idx_entries_stage ON entries(stage);
|
|
1024
|
+
CREATE INDEX IF NOT EXISTS idx_entries_canonical ON entries(canonical_key);
|
|
1025
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type_key ON entities(entity_type, canonical_key);
|
|
1026
|
+
CREATE INDEX IF NOT EXISTS idx_entities_status ON entities(status);
|
|
1027
|
+
CREATE INDEX IF NOT EXISTS idx_edges_src ON edges(src_id, rel_type);
|
|
1028
|
+
CREATE INDEX IF NOT EXISTS idx_edges_dst ON edges(dst_id, rel_type);
|
|
1029
|
+
CREATE INDEX IF NOT EXISTS idx_edges_rel ON edges(rel_type);
|
|
1030
|
+
CREATE INDEX IF NOT EXISTS idx_outbox_status ON vector_outbox(status);
|
|
1031
|
+
CREATE INDEX IF NOT EXISTS idx_working_set_expires ON working_set(expires_at);
|
|
1032
|
+
CREATE INDEX IF NOT EXISTS idx_working_set_relevance ON working_set(relevance_score);
|
|
1033
|
+
CREATE INDEX IF NOT EXISTS idx_consolidated_confidence ON consolidated_memories(confidence);
|
|
1034
|
+
CREATE INDEX IF NOT EXISTS idx_continuity_created ON continuity_log(created_at);
|
|
1035
|
+
CREATE INDEX IF NOT EXISTS idx_embedding_outbox_status ON embedding_outbox(status);
|
|
1036
|
+
`);
|
|
1037
|
+
const tableInfo = sqliteAll(this.db, "PRAGMA table_info(events)", []);
|
|
1038
|
+
const columnNames = tableInfo.map((col) => col.name);
|
|
1039
|
+
if (!columnNames.includes("access_count")) {
|
|
1040
|
+
try {
|
|
1041
|
+
sqliteExec(this.db, `
|
|
1042
|
+
ALTER TABLE events ADD COLUMN access_count INTEGER DEFAULT 0;
|
|
1043
|
+
`);
|
|
1044
|
+
} catch (err) {
|
|
1045
|
+
console.error("Error adding access_count column:", err);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
if (!columnNames.includes("last_accessed_at")) {
|
|
1049
|
+
try {
|
|
1050
|
+
sqliteExec(this.db, `
|
|
1051
|
+
ALTER TABLE events ADD COLUMN last_accessed_at TEXT;
|
|
1052
|
+
`);
|
|
1053
|
+
} catch (err) {
|
|
1054
|
+
console.error("Error adding last_accessed_at column:", err);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
try {
|
|
1058
|
+
sqliteExec(this.db, `
|
|
1059
|
+
CREATE INDEX IF NOT EXISTS idx_events_access_count ON events(access_count DESC);
|
|
1060
|
+
`);
|
|
1061
|
+
} catch (err) {
|
|
1062
|
+
}
|
|
1063
|
+
try {
|
|
1064
|
+
sqliteExec(this.db, `
|
|
1065
|
+
CREATE INDEX IF NOT EXISTS idx_events_last_accessed ON events(last_accessed_at DESC);
|
|
1066
|
+
`);
|
|
1067
|
+
} catch (err) {
|
|
1068
|
+
}
|
|
1069
|
+
this.initialized = true;
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Append event to store (Append-only, Idempotent)
|
|
1073
|
+
*/
|
|
1074
|
+
async append(input) {
|
|
1075
|
+
await this.initialize();
|
|
1076
|
+
const canonicalKey = makeCanonicalKey(input.content);
|
|
1077
|
+
const dedupeKey = makeDedupeKey(input.content, input.sessionId);
|
|
1078
|
+
const existing = sqliteGet(
|
|
1079
|
+
this.db,
|
|
1080
|
+
`SELECT event_id FROM event_dedup WHERE dedupe_key = ?`,
|
|
1081
|
+
[dedupeKey]
|
|
1082
|
+
);
|
|
1083
|
+
if (existing) {
|
|
1084
|
+
return {
|
|
1085
|
+
success: true,
|
|
1086
|
+
eventId: existing.event_id,
|
|
1087
|
+
isDuplicate: true
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
const id = randomUUID2();
|
|
1091
|
+
const timestamp = toSQLiteTimestamp(input.timestamp);
|
|
1092
|
+
try {
|
|
1093
|
+
const insertEvent = this.db.prepare(`
|
|
1094
|
+
INSERT INTO events (id, event_type, session_id, timestamp, content, canonical_key, dedupe_key, metadata)
|
|
1095
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1096
|
+
`);
|
|
1097
|
+
const insertDedup = this.db.prepare(`
|
|
1098
|
+
INSERT INTO event_dedup (dedupe_key, event_id) VALUES (?, ?)
|
|
1099
|
+
`);
|
|
1100
|
+
const insertLevel = this.db.prepare(`
|
|
1101
|
+
INSERT INTO memory_levels (event_id, level) VALUES (?, 'L0')
|
|
1102
|
+
`);
|
|
1103
|
+
const transaction = this.db.transaction(() => {
|
|
1104
|
+
insertEvent.run(
|
|
1105
|
+
id,
|
|
1106
|
+
input.eventType,
|
|
1107
|
+
input.sessionId,
|
|
1108
|
+
timestamp,
|
|
1109
|
+
input.content,
|
|
1110
|
+
canonicalKey,
|
|
1111
|
+
dedupeKey,
|
|
1112
|
+
JSON.stringify(input.metadata || {})
|
|
1113
|
+
);
|
|
1114
|
+
insertDedup.run(dedupeKey, id);
|
|
1115
|
+
insertLevel.run(id);
|
|
1116
|
+
});
|
|
1117
|
+
transaction();
|
|
1118
|
+
return { success: true, eventId: id, isDuplicate: false };
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
return {
|
|
1121
|
+
success: false,
|
|
1122
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Get events by session ID
|
|
1128
|
+
*/
|
|
1129
|
+
async getSessionEvents(sessionId) {
|
|
1130
|
+
await this.initialize();
|
|
1131
|
+
const rows = sqliteAll(
|
|
1132
|
+
this.db,
|
|
1133
|
+
`SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
|
|
1134
|
+
[sessionId]
|
|
1135
|
+
);
|
|
1136
|
+
return rows.map(this.rowToEvent);
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Get recent events
|
|
1140
|
+
*/
|
|
1141
|
+
async getRecentEvents(limit = 100) {
|
|
1142
|
+
await this.initialize();
|
|
1143
|
+
const rows = sqliteAll(
|
|
1144
|
+
this.db,
|
|
1145
|
+
`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
|
|
1146
|
+
[limit]
|
|
1147
|
+
);
|
|
1148
|
+
return rows.map(this.rowToEvent);
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Get event by ID
|
|
1152
|
+
*/
|
|
1153
|
+
async getEvent(id) {
|
|
1154
|
+
await this.initialize();
|
|
1155
|
+
const row = sqliteGet(
|
|
1156
|
+
this.db,
|
|
1157
|
+
`SELECT * FROM events WHERE id = ?`,
|
|
1158
|
+
[id]
|
|
1159
|
+
);
|
|
1160
|
+
if (!row)
|
|
1161
|
+
return null;
|
|
1162
|
+
return this.rowToEvent(row);
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Get events since a timestamp (for sync)
|
|
1166
|
+
*/
|
|
1167
|
+
async getEventsSince(timestamp, limit = 1e3) {
|
|
1168
|
+
await this.initialize();
|
|
1169
|
+
const rows = sqliteAll(
|
|
1170
|
+
this.db,
|
|
1171
|
+
`SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
|
|
1172
|
+
[timestamp, limit]
|
|
1173
|
+
);
|
|
1174
|
+
return rows.map(this.rowToEvent);
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Create or update session
|
|
1178
|
+
*/
|
|
1179
|
+
async upsertSession(session) {
|
|
1180
|
+
await this.initialize();
|
|
1181
|
+
const existing = sqliteGet(
|
|
1182
|
+
this.db,
|
|
1183
|
+
`SELECT id FROM sessions WHERE id = ?`,
|
|
1184
|
+
[session.id]
|
|
1185
|
+
);
|
|
1186
|
+
if (!existing) {
|
|
1187
|
+
sqliteRun(
|
|
1188
|
+
this.db,
|
|
1189
|
+
`INSERT INTO sessions (id, started_at, project_path, tags)
|
|
1190
|
+
VALUES (?, ?, ?, ?)`,
|
|
1191
|
+
[
|
|
1192
|
+
session.id,
|
|
1193
|
+
toSQLiteTimestamp(session.startedAt || /* @__PURE__ */ new Date()),
|
|
1194
|
+
session.projectPath || null,
|
|
1195
|
+
JSON.stringify(session.tags || [])
|
|
1196
|
+
]
|
|
1197
|
+
);
|
|
1198
|
+
} else {
|
|
1199
|
+
const updates = [];
|
|
1200
|
+
const values = [];
|
|
1201
|
+
if (session.endedAt) {
|
|
1202
|
+
updates.push("ended_at = ?");
|
|
1203
|
+
values.push(toSQLiteTimestamp(session.endedAt));
|
|
1204
|
+
}
|
|
1205
|
+
if (session.summary) {
|
|
1206
|
+
updates.push("summary = ?");
|
|
1207
|
+
values.push(session.summary);
|
|
1208
|
+
}
|
|
1209
|
+
if (session.tags) {
|
|
1210
|
+
updates.push("tags = ?");
|
|
1211
|
+
values.push(JSON.stringify(session.tags));
|
|
1212
|
+
}
|
|
1213
|
+
if (updates.length > 0) {
|
|
1214
|
+
values.push(session.id);
|
|
1215
|
+
sqliteRun(
|
|
1216
|
+
this.db,
|
|
1217
|
+
`UPDATE sessions SET ${updates.join(", ")} WHERE id = ?`,
|
|
1218
|
+
values
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Get session by ID
|
|
1225
|
+
*/
|
|
1226
|
+
async getSession(id) {
|
|
1227
|
+
await this.initialize();
|
|
1228
|
+
const row = sqliteGet(
|
|
1229
|
+
this.db,
|
|
1230
|
+
`SELECT * FROM sessions WHERE id = ?`,
|
|
1231
|
+
[id]
|
|
1232
|
+
);
|
|
1233
|
+
if (!row)
|
|
1234
|
+
return null;
|
|
1235
|
+
return {
|
|
1236
|
+
id: row.id,
|
|
1237
|
+
startedAt: toDateFromSQLite(row.started_at),
|
|
1238
|
+
endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
|
|
1239
|
+
projectPath: row.project_path,
|
|
1240
|
+
summary: row.summary,
|
|
1241
|
+
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Get all sessions
|
|
1246
|
+
*/
|
|
1247
|
+
async getAllSessions() {
|
|
1248
|
+
await this.initialize();
|
|
1249
|
+
const rows = sqliteAll(
|
|
1250
|
+
this.db,
|
|
1251
|
+
`SELECT * FROM sessions ORDER BY started_at DESC`
|
|
1252
|
+
);
|
|
1253
|
+
return rows.map((row) => ({
|
|
1254
|
+
id: row.id,
|
|
1255
|
+
startedAt: toDateFromSQLite(row.started_at),
|
|
1256
|
+
endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
|
|
1257
|
+
projectPath: row.project_path,
|
|
1258
|
+
summary: row.summary,
|
|
1259
|
+
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
1260
|
+
}));
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Add to embedding outbox
|
|
1264
|
+
*/
|
|
1265
|
+
async enqueueForEmbedding(eventId, content) {
|
|
1266
|
+
await this.initialize();
|
|
1267
|
+
const id = randomUUID2();
|
|
1268
|
+
sqliteRun(
|
|
1269
|
+
this.db,
|
|
1270
|
+
`INSERT INTO embedding_outbox (id, event_id, content, status, retry_count)
|
|
1271
|
+
VALUES (?, ?, ?, 'pending', 0)`,
|
|
1272
|
+
[id, eventId, content]
|
|
1273
|
+
);
|
|
1274
|
+
return id;
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Get pending outbox items
|
|
1278
|
+
*/
|
|
1279
|
+
async getPendingOutboxItems(limit = 32) {
|
|
1280
|
+
await this.initialize();
|
|
1281
|
+
const pending = sqliteAll(
|
|
1282
|
+
this.db,
|
|
1283
|
+
`SELECT * FROM embedding_outbox
|
|
1284
|
+
WHERE status = 'pending'
|
|
1285
|
+
ORDER BY created_at
|
|
1286
|
+
LIMIT ?`,
|
|
1287
|
+
[limit]
|
|
1288
|
+
);
|
|
1289
|
+
if (pending.length === 0)
|
|
1290
|
+
return [];
|
|
1291
|
+
const ids = pending.map((r) => r.id);
|
|
1292
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1293
|
+
sqliteRun(
|
|
1294
|
+
this.db,
|
|
1295
|
+
`UPDATE embedding_outbox SET status = 'processing' WHERE id IN (${placeholders})`,
|
|
1296
|
+
ids
|
|
1297
|
+
);
|
|
1298
|
+
return pending.map((row) => ({
|
|
1299
|
+
id: row.id,
|
|
1300
|
+
eventId: row.event_id,
|
|
1301
|
+
content: row.content,
|
|
1302
|
+
status: "processing",
|
|
1303
|
+
retryCount: row.retry_count,
|
|
1304
|
+
createdAt: toDateFromSQLite(row.created_at),
|
|
1305
|
+
errorMessage: row.error_message
|
|
1306
|
+
}));
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Mark outbox items as done
|
|
1310
|
+
*/
|
|
1311
|
+
async completeOutboxItems(ids) {
|
|
1312
|
+
if (ids.length === 0)
|
|
1313
|
+
return;
|
|
1314
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1315
|
+
sqliteRun(
|
|
1316
|
+
this.db,
|
|
1317
|
+
`DELETE FROM embedding_outbox WHERE id IN (${placeholders})`,
|
|
1318
|
+
ids
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Mark outbox items as failed
|
|
1323
|
+
*/
|
|
1324
|
+
async failOutboxItems(ids, error) {
|
|
1325
|
+
if (ids.length === 0)
|
|
1326
|
+
return;
|
|
1327
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1328
|
+
sqliteRun(
|
|
1329
|
+
this.db,
|
|
1330
|
+
`UPDATE embedding_outbox
|
|
1331
|
+
SET status = CASE WHEN retry_count >= 3 THEN 'failed' ELSE 'pending' END,
|
|
1332
|
+
retry_count = retry_count + 1,
|
|
1333
|
+
error_message = ?
|
|
1334
|
+
WHERE id IN (${placeholders})`,
|
|
1335
|
+
[error, ...ids]
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Update memory level
|
|
1340
|
+
*/
|
|
1341
|
+
async updateMemoryLevel(eventId, level) {
|
|
1342
|
+
await this.initialize();
|
|
1343
|
+
sqliteRun(
|
|
1344
|
+
this.db,
|
|
1345
|
+
`UPDATE memory_levels SET level = ?, promoted_at = datetime('now') WHERE event_id = ?`,
|
|
1346
|
+
[level, eventId]
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Get memory level statistics
|
|
1351
|
+
*/
|
|
1352
|
+
async getLevelStats() {
|
|
1353
|
+
await this.initialize();
|
|
1354
|
+
const rows = sqliteAll(
|
|
1355
|
+
this.db,
|
|
1356
|
+
`SELECT level, COUNT(*) as count FROM memory_levels GROUP BY level`
|
|
1357
|
+
);
|
|
1358
|
+
return rows;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Get events by memory level
|
|
1362
|
+
*/
|
|
1363
|
+
async getEventsByLevel(level, options) {
|
|
1364
|
+
await this.initialize();
|
|
1365
|
+
const limit = options?.limit || 50;
|
|
1366
|
+
const offset = options?.offset || 0;
|
|
1367
|
+
const rows = sqliteAll(
|
|
1368
|
+
this.db,
|
|
1369
|
+
`SELECT e.* FROM events e
|
|
1370
|
+
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
1371
|
+
WHERE ml.level = ?
|
|
1372
|
+
ORDER BY e.timestamp DESC
|
|
1373
|
+
LIMIT ? OFFSET ?`,
|
|
1374
|
+
[level, limit, offset]
|
|
1375
|
+
);
|
|
1376
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Get memory level for a specific event
|
|
1380
|
+
*/
|
|
1381
|
+
async getEventLevel(eventId) {
|
|
1382
|
+
await this.initialize();
|
|
1383
|
+
const row = sqliteGet(
|
|
1384
|
+
this.db,
|
|
1385
|
+
`SELECT level FROM memory_levels WHERE event_id = ?`,
|
|
1386
|
+
[eventId]
|
|
1387
|
+
);
|
|
1388
|
+
return row ? row.level : null;
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Get sync position for a target
|
|
1392
|
+
*/
|
|
1393
|
+
async getSyncPosition(targetName) {
|
|
1394
|
+
await this.initialize();
|
|
1395
|
+
const row = sqliteGet(
|
|
1396
|
+
this.db,
|
|
1397
|
+
`SELECT last_event_id, last_timestamp FROM sync_positions WHERE target_name = ?`,
|
|
1398
|
+
[targetName]
|
|
1399
|
+
);
|
|
1400
|
+
return {
|
|
1401
|
+
lastEventId: row?.last_event_id ?? null,
|
|
1402
|
+
lastTimestamp: row?.last_timestamp ?? null
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Update sync position for a target
|
|
1407
|
+
*/
|
|
1408
|
+
async updateSyncPosition(targetName, lastEventId, lastTimestamp) {
|
|
1409
|
+
await this.initialize();
|
|
1410
|
+
sqliteRun(
|
|
1411
|
+
this.db,
|
|
1412
|
+
`INSERT OR REPLACE INTO sync_positions (target_name, last_event_id, last_timestamp, updated_at)
|
|
1413
|
+
VALUES (?, ?, ?, datetime('now'))`,
|
|
1414
|
+
[targetName, lastEventId, lastTimestamp]
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Get config value for endless mode
|
|
1419
|
+
*/
|
|
1420
|
+
async getEndlessConfig(key) {
|
|
1421
|
+
await this.initialize();
|
|
1422
|
+
const row = sqliteGet(
|
|
1423
|
+
this.db,
|
|
1424
|
+
`SELECT value FROM endless_config WHERE key = ?`,
|
|
1425
|
+
[key]
|
|
1426
|
+
);
|
|
1427
|
+
if (!row)
|
|
1428
|
+
return null;
|
|
1429
|
+
return JSON.parse(row.value);
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Set config value for endless mode
|
|
1433
|
+
*/
|
|
1434
|
+
async setEndlessConfig(key, value) {
|
|
1435
|
+
await this.initialize();
|
|
1436
|
+
sqliteRun(
|
|
1437
|
+
this.db,
|
|
1438
|
+
`INSERT OR REPLACE INTO endless_config (key, value, updated_at)
|
|
1439
|
+
VALUES (?, ?, datetime('now'))`,
|
|
1440
|
+
[key, JSON.stringify(value)]
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Increment access count for events
|
|
1445
|
+
*/
|
|
1446
|
+
async incrementAccessCount(eventIds) {
|
|
1447
|
+
if (eventIds.length === 0 || this.readOnly)
|
|
1448
|
+
return;
|
|
1449
|
+
await this.initialize();
|
|
1450
|
+
const placeholders = eventIds.map(() => "?").join(",");
|
|
1451
|
+
const currentTime = toSQLiteTimestamp(/* @__PURE__ */ new Date());
|
|
1452
|
+
sqliteRun(
|
|
1453
|
+
this.db,
|
|
1454
|
+
`UPDATE events
|
|
1455
|
+
SET access_count = access_count + 1,
|
|
1456
|
+
last_accessed_at = ?
|
|
1457
|
+
WHERE id IN (${placeholders})`,
|
|
1458
|
+
[currentTime, ...eventIds]
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Get most accessed memories
|
|
1463
|
+
*/
|
|
1464
|
+
async getMostAccessed(limit = 10) {
|
|
1465
|
+
await this.initialize();
|
|
1466
|
+
const rows = sqliteAll(
|
|
1467
|
+
this.db,
|
|
1468
|
+
`SELECT * FROM events
|
|
1469
|
+
WHERE access_count > 0
|
|
1470
|
+
ORDER BY access_count DESC, last_accessed_at DESC
|
|
1471
|
+
LIMIT ?`,
|
|
1472
|
+
[limit]
|
|
1473
|
+
);
|
|
1474
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Get database instance for direct access
|
|
1478
|
+
*/
|
|
1479
|
+
getDatabase() {
|
|
1480
|
+
return this.db;
|
|
1481
|
+
}
|
|
703
1482
|
/**
|
|
704
1483
|
* Close database connection
|
|
705
1484
|
*/
|
|
706
1485
|
async close() {
|
|
707
|
-
|
|
1486
|
+
sqliteClose(this.db);
|
|
708
1487
|
}
|
|
709
1488
|
/**
|
|
710
1489
|
* Convert database row to MemoryEvent
|
|
711
1490
|
*/
|
|
712
1491
|
rowToEvent(row) {
|
|
713
|
-
|
|
1492
|
+
const event = {
|
|
714
1493
|
id: row.id,
|
|
715
1494
|
eventType: row.event_type,
|
|
716
1495
|
sessionId: row.session_id,
|
|
717
|
-
timestamp:
|
|
1496
|
+
timestamp: toDateFromSQLite(row.timestamp),
|
|
718
1497
|
content: row.content,
|
|
719
1498
|
canonicalKey: row.canonical_key,
|
|
720
1499
|
dedupeKey: row.dedupe_key,
|
|
721
1500
|
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
722
1501
|
};
|
|
1502
|
+
if (row.access_count !== void 0) {
|
|
1503
|
+
event.access_count = row.access_count;
|
|
1504
|
+
}
|
|
1505
|
+
if (row.last_accessed_at !== void 0) {
|
|
1506
|
+
event.last_accessed_at = row.last_accessed_at;
|
|
1507
|
+
}
|
|
1508
|
+
return event;
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
|
|
1512
|
+
// src/core/sync-worker.ts
|
|
1513
|
+
var DEFAULT_CONFIG = {
|
|
1514
|
+
intervalMs: 3e4,
|
|
1515
|
+
batchSize: 500,
|
|
1516
|
+
maxRetries: 3,
|
|
1517
|
+
retryDelayMs: 5e3
|
|
1518
|
+
};
|
|
1519
|
+
var SyncWorker = class {
|
|
1520
|
+
constructor(sqliteStore, duckdbStore, config) {
|
|
1521
|
+
this.sqliteStore = sqliteStore;
|
|
1522
|
+
this.duckdbStore = duckdbStore;
|
|
1523
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
1524
|
+
}
|
|
1525
|
+
config;
|
|
1526
|
+
intervalHandle = null;
|
|
1527
|
+
running = false;
|
|
1528
|
+
stats = {
|
|
1529
|
+
lastSyncAt: null,
|
|
1530
|
+
eventsSynced: 0,
|
|
1531
|
+
sessionsSynced: 0,
|
|
1532
|
+
errors: 0,
|
|
1533
|
+
status: "idle"
|
|
1534
|
+
};
|
|
1535
|
+
/**
|
|
1536
|
+
* Start the sync worker
|
|
1537
|
+
*/
|
|
1538
|
+
start() {
|
|
1539
|
+
if (this.running)
|
|
1540
|
+
return;
|
|
1541
|
+
this.running = true;
|
|
1542
|
+
this.stats.status = "idle";
|
|
1543
|
+
this.syncNow().catch((err) => {
|
|
1544
|
+
console.error("[SyncWorker] Initial sync failed:", err);
|
|
1545
|
+
});
|
|
1546
|
+
this.intervalHandle = setInterval(() => {
|
|
1547
|
+
this.syncNow().catch((err) => {
|
|
1548
|
+
console.error("[SyncWorker] Periodic sync failed:", err);
|
|
1549
|
+
});
|
|
1550
|
+
}, this.config.intervalMs);
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Stop the sync worker
|
|
1554
|
+
*/
|
|
1555
|
+
stop() {
|
|
1556
|
+
this.running = false;
|
|
1557
|
+
this.stats.status = "stopped";
|
|
1558
|
+
if (this.intervalHandle) {
|
|
1559
|
+
clearInterval(this.intervalHandle);
|
|
1560
|
+
this.intervalHandle = null;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* Trigger immediate sync
|
|
1565
|
+
*/
|
|
1566
|
+
async syncNow() {
|
|
1567
|
+
if (this.stats.status === "syncing") {
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
this.stats.status = "syncing";
|
|
1571
|
+
try {
|
|
1572
|
+
await this.syncEvents();
|
|
1573
|
+
await this.syncSessions();
|
|
1574
|
+
this.stats.lastSyncAt = /* @__PURE__ */ new Date();
|
|
1575
|
+
this.stats.status = "idle";
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
this.stats.errors++;
|
|
1578
|
+
this.stats.status = "error";
|
|
1579
|
+
throw error;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Sync events from SQLite to DuckDB
|
|
1584
|
+
*/
|
|
1585
|
+
async syncEvents() {
|
|
1586
|
+
const targetName = "duckdb_analytics";
|
|
1587
|
+
const position = await this.sqliteStore.getSyncPosition(targetName);
|
|
1588
|
+
const lastTimestamp = position.lastTimestamp || "1970-01-01T00:00:00.000Z";
|
|
1589
|
+
let hasMore = true;
|
|
1590
|
+
let totalSynced = 0;
|
|
1591
|
+
while (hasMore) {
|
|
1592
|
+
const events = await this.sqliteStore.getEventsSince(lastTimestamp, this.config.batchSize);
|
|
1593
|
+
if (events.length === 0) {
|
|
1594
|
+
hasMore = false;
|
|
1595
|
+
break;
|
|
1596
|
+
}
|
|
1597
|
+
await this.retryWithBackoff(async () => {
|
|
1598
|
+
for (const event of events) {
|
|
1599
|
+
await this.insertEventToDuckDB(event);
|
|
1600
|
+
}
|
|
1601
|
+
});
|
|
1602
|
+
totalSynced += events.length;
|
|
1603
|
+
const lastEvent = events[events.length - 1];
|
|
1604
|
+
await this.sqliteStore.updateSyncPosition(
|
|
1605
|
+
targetName,
|
|
1606
|
+
lastEvent.id,
|
|
1607
|
+
lastEvent.timestamp.toISOString()
|
|
1608
|
+
);
|
|
1609
|
+
hasMore = events.length === this.config.batchSize;
|
|
1610
|
+
}
|
|
1611
|
+
this.stats.eventsSynced += totalSynced;
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Sync sessions from SQLite to DuckDB
|
|
1615
|
+
*/
|
|
1616
|
+
async syncSessions() {
|
|
1617
|
+
const sessions = await this.sqliteStore.getAllSessions();
|
|
1618
|
+
for (const session of sessions) {
|
|
1619
|
+
await this.retryWithBackoff(async () => {
|
|
1620
|
+
await this.duckdbStore.upsertSession(session);
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
this.stats.sessionsSynced = sessions.length;
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Insert a single event into DuckDB
|
|
1627
|
+
*/
|
|
1628
|
+
async insertEventToDuckDB(event) {
|
|
1629
|
+
await this.duckdbStore.append({
|
|
1630
|
+
eventType: event.eventType,
|
|
1631
|
+
sessionId: event.sessionId,
|
|
1632
|
+
timestamp: event.timestamp,
|
|
1633
|
+
content: event.content,
|
|
1634
|
+
metadata: event.metadata
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Retry operation with exponential backoff
|
|
1639
|
+
*/
|
|
1640
|
+
async retryWithBackoff(fn) {
|
|
1641
|
+
let lastError = null;
|
|
1642
|
+
for (let attempt = 0; attempt < this.config.maxRetries; attempt++) {
|
|
1643
|
+
try {
|
|
1644
|
+
return await fn();
|
|
1645
|
+
} catch (error) {
|
|
1646
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1647
|
+
if (attempt < this.config.maxRetries - 1) {
|
|
1648
|
+
const delay = this.config.retryDelayMs * Math.pow(2, attempt);
|
|
1649
|
+
await this.sleep(delay);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
throw lastError;
|
|
1654
|
+
}
|
|
1655
|
+
/**
|
|
1656
|
+
* Sleep utility
|
|
1657
|
+
*/
|
|
1658
|
+
sleep(ms) {
|
|
1659
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Get sync statistics
|
|
1663
|
+
*/
|
|
1664
|
+
getStats() {
|
|
1665
|
+
return { ...this.stats };
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Check if worker is running
|
|
1669
|
+
*/
|
|
1670
|
+
isRunning() {
|
|
1671
|
+
return this.running;
|
|
723
1672
|
}
|
|
724
1673
|
};
|
|
725
1674
|
|
|
@@ -951,7 +1900,7 @@ function getDefaultEmbedder() {
|
|
|
951
1900
|
}
|
|
952
1901
|
|
|
953
1902
|
// src/core/vector-outbox.ts
|
|
954
|
-
var
|
|
1903
|
+
var DEFAULT_CONFIG2 = {
|
|
955
1904
|
embeddingVersion: "v1",
|
|
956
1905
|
maxRetries: 3,
|
|
957
1906
|
stuckThresholdMs: 5 * 60 * 1e3,
|
|
@@ -960,7 +1909,7 @@ var DEFAULT_CONFIG = {
|
|
|
960
1909
|
};
|
|
961
1910
|
|
|
962
1911
|
// src/core/vector-worker.ts
|
|
963
|
-
var
|
|
1912
|
+
var DEFAULT_CONFIG3 = {
|
|
964
1913
|
batchSize: 32,
|
|
965
1914
|
pollIntervalMs: 1e3,
|
|
966
1915
|
maxRetries: 3
|
|
@@ -971,12 +1920,13 @@ var VectorWorker = class {
|
|
|
971
1920
|
embedder;
|
|
972
1921
|
config;
|
|
973
1922
|
running = false;
|
|
1923
|
+
stopping = false;
|
|
974
1924
|
pollTimeout = null;
|
|
975
1925
|
constructor(eventStore, vectorStore, embedder, config = {}) {
|
|
976
1926
|
this.eventStore = eventStore;
|
|
977
1927
|
this.vectorStore = vectorStore;
|
|
978
1928
|
this.embedder = embedder;
|
|
979
|
-
this.config = { ...
|
|
1929
|
+
this.config = { ...DEFAULT_CONFIG3, ...config };
|
|
980
1930
|
}
|
|
981
1931
|
/**
|
|
982
1932
|
* Start the worker polling loop
|
|
@@ -985,6 +1935,7 @@ var VectorWorker = class {
|
|
|
985
1935
|
if (this.running)
|
|
986
1936
|
return;
|
|
987
1937
|
this.running = true;
|
|
1938
|
+
this.stopping = false;
|
|
988
1939
|
this.poll();
|
|
989
1940
|
}
|
|
990
1941
|
/**
|
|
@@ -992,6 +1943,7 @@ var VectorWorker = class {
|
|
|
992
1943
|
*/
|
|
993
1944
|
stop() {
|
|
994
1945
|
this.running = false;
|
|
1946
|
+
this.stopping = true;
|
|
995
1947
|
if (this.pollTimeout) {
|
|
996
1948
|
clearTimeout(this.pollTimeout);
|
|
997
1949
|
this.pollTimeout = null;
|
|
@@ -1041,9 +1993,15 @@ var VectorWorker = class {
|
|
|
1041
1993
|
}
|
|
1042
1994
|
return successful.length;
|
|
1043
1995
|
} catch (error) {
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1996
|
+
if (!this.stopping) {
|
|
1997
|
+
try {
|
|
1998
|
+
const allIds = items.map((i) => i.id);
|
|
1999
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2000
|
+
await this.eventStore.failOutboxItems(allIds, errorMessage);
|
|
2001
|
+
} catch (failError) {
|
|
2002
|
+
console.warn("Could not mark outbox items as failed (database may be closed)");
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
1047
2005
|
throw error;
|
|
1048
2006
|
}
|
|
1049
2007
|
}
|
|
@@ -1051,14 +2009,18 @@ var VectorWorker = class {
|
|
|
1051
2009
|
* Poll for new items
|
|
1052
2010
|
*/
|
|
1053
2011
|
async poll() {
|
|
1054
|
-
if (!this.running)
|
|
2012
|
+
if (!this.running || this.stopping)
|
|
1055
2013
|
return;
|
|
1056
2014
|
try {
|
|
1057
2015
|
await this.processBatch();
|
|
1058
2016
|
} catch (error) {
|
|
1059
|
-
|
|
2017
|
+
if (!this.stopping) {
|
|
2018
|
+
console.error("Vector worker error:", error);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
if (this.running && !this.stopping) {
|
|
2022
|
+
this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
|
|
1060
2023
|
}
|
|
1061
|
-
this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
|
|
1062
2024
|
}
|
|
1063
2025
|
/**
|
|
1064
2026
|
* Process all pending items (blocking)
|
|
@@ -1085,7 +2047,7 @@ function createVectorWorker(eventStore, vectorStore, embedder, config) {
|
|
|
1085
2047
|
}
|
|
1086
2048
|
|
|
1087
2049
|
// src/core/matcher.ts
|
|
1088
|
-
var
|
|
2050
|
+
var DEFAULT_CONFIG4 = {
|
|
1089
2051
|
weights: {
|
|
1090
2052
|
semanticSimilarity: 0.4,
|
|
1091
2053
|
ftsScore: 0.25,
|
|
@@ -1100,9 +2062,9 @@ var Matcher = class {
|
|
|
1100
2062
|
config;
|
|
1101
2063
|
constructor(config = {}) {
|
|
1102
2064
|
this.config = {
|
|
1103
|
-
...
|
|
2065
|
+
...DEFAULT_CONFIG4,
|
|
1104
2066
|
...config,
|
|
1105
|
-
weights: { ...
|
|
2067
|
+
weights: { ...DEFAULT_CONFIG4.weights, ...config.weights }
|
|
1106
2068
|
};
|
|
1107
2069
|
}
|
|
1108
2070
|
/**
|
|
@@ -1810,7 +2772,7 @@ function createSharedEventStore(dbPath) {
|
|
|
1810
2772
|
}
|
|
1811
2773
|
|
|
1812
2774
|
// src/core/shared-store.ts
|
|
1813
|
-
import { randomUUID as
|
|
2775
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1814
2776
|
var SharedStore = class {
|
|
1815
2777
|
constructor(sharedEventStore) {
|
|
1816
2778
|
this.sharedEventStore = sharedEventStore;
|
|
@@ -1822,7 +2784,7 @@ var SharedStore = class {
|
|
|
1822
2784
|
* Promote a verified troubleshooting entry to shared storage
|
|
1823
2785
|
*/
|
|
1824
2786
|
async promoteEntry(input) {
|
|
1825
|
-
const entryId =
|
|
2787
|
+
const entryId = randomUUID3();
|
|
1826
2788
|
await dbRun(
|
|
1827
2789
|
this.db,
|
|
1828
2790
|
`INSERT INTO shared_troubleshooting (
|
|
@@ -2187,7 +3149,7 @@ function createSharedVectorStore(dbPath) {
|
|
|
2187
3149
|
}
|
|
2188
3150
|
|
|
2189
3151
|
// src/core/shared-promoter.ts
|
|
2190
|
-
import { randomUUID as
|
|
3152
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
2191
3153
|
var SharedPromoter = class {
|
|
2192
3154
|
constructor(sharedStore, sharedVectorStore, embedder, config) {
|
|
2193
3155
|
this.sharedStore = sharedStore;
|
|
@@ -2255,7 +3217,7 @@ var SharedPromoter = class {
|
|
|
2255
3217
|
const embeddingContent = this.createEmbeddingContent(input);
|
|
2256
3218
|
const embedding = await this.embedder.embed(embeddingContent);
|
|
2257
3219
|
await this.sharedVectorStore.upsert({
|
|
2258
|
-
id:
|
|
3220
|
+
id: randomUUID4(),
|
|
2259
3221
|
entryId,
|
|
2260
3222
|
entryType: "troubleshooting",
|
|
2261
3223
|
content: embeddingContent,
|
|
@@ -2395,7 +3357,7 @@ function createToolObservationEmbedding(toolName, metadata, success) {
|
|
|
2395
3357
|
}
|
|
2396
3358
|
|
|
2397
3359
|
// src/core/working-set-store.ts
|
|
2398
|
-
import { randomUUID as
|
|
3360
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
2399
3361
|
var WorkingSetStore = class {
|
|
2400
3362
|
constructor(eventStore, config) {
|
|
2401
3363
|
this.eventStore = eventStore;
|
|
@@ -2416,7 +3378,7 @@ var WorkingSetStore = class {
|
|
|
2416
3378
|
`INSERT OR REPLACE INTO working_set (id, event_id, added_at, relevance_score, topics, expires_at)
|
|
2417
3379
|
VALUES (?, ?, CURRENT_TIMESTAMP, ?, ?, ?)`,
|
|
2418
3380
|
[
|
|
2419
|
-
|
|
3381
|
+
randomUUID5(),
|
|
2420
3382
|
eventId,
|
|
2421
3383
|
relevanceScore,
|
|
2422
3384
|
JSON.stringify(topics || []),
|
|
@@ -2600,7 +3562,7 @@ function createWorkingSetStore(eventStore, config) {
|
|
|
2600
3562
|
}
|
|
2601
3563
|
|
|
2602
3564
|
// src/core/consolidated-store.ts
|
|
2603
|
-
import { randomUUID as
|
|
3565
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
2604
3566
|
var ConsolidatedStore = class {
|
|
2605
3567
|
constructor(eventStore) {
|
|
2606
3568
|
this.eventStore = eventStore;
|
|
@@ -2612,7 +3574,7 @@ var ConsolidatedStore = class {
|
|
|
2612
3574
|
* Create a new consolidated memory
|
|
2613
3575
|
*/
|
|
2614
3576
|
async create(input) {
|
|
2615
|
-
const memoryId =
|
|
3577
|
+
const memoryId = randomUUID6();
|
|
2616
3578
|
await dbRun(
|
|
2617
3579
|
this.db,
|
|
2618
3580
|
`INSERT INTO consolidated_memories
|
|
@@ -3139,7 +4101,7 @@ function createConsolidationWorker(workingSetStore, consolidatedStore, config) {
|
|
|
3139
4101
|
}
|
|
3140
4102
|
|
|
3141
4103
|
// src/core/continuity-manager.ts
|
|
3142
|
-
import { randomUUID as
|
|
4104
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
3143
4105
|
var ContinuityManager = class {
|
|
3144
4106
|
constructor(eventStore, config) {
|
|
3145
4107
|
this.eventStore = eventStore;
|
|
@@ -3293,7 +4255,7 @@ var ContinuityManager = class {
|
|
|
3293
4255
|
`INSERT INTO continuity_log
|
|
3294
4256
|
(log_id, from_context_id, to_context_id, continuity_score, transition_type, created_at)
|
|
3295
4257
|
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
|
|
3296
|
-
[
|
|
4258
|
+
[randomUUID7(), previous.id, current.id, score, type]
|
|
3297
4259
|
);
|
|
3298
4260
|
}
|
|
3299
4261
|
/**
|
|
@@ -3402,7 +4364,7 @@ function createContinuityManager(eventStore, config) {
|
|
|
3402
4364
|
}
|
|
3403
4365
|
|
|
3404
4366
|
// src/core/graduation-worker.ts
|
|
3405
|
-
var
|
|
4367
|
+
var DEFAULT_CONFIG5 = {
|
|
3406
4368
|
evaluationIntervalMs: 3e5,
|
|
3407
4369
|
// 5 minutes
|
|
3408
4370
|
batchSize: 50,
|
|
@@ -3410,7 +4372,7 @@ var DEFAULT_CONFIG4 = {
|
|
|
3410
4372
|
// 1 hour cooldown between evaluations
|
|
3411
4373
|
};
|
|
3412
4374
|
var GraduationWorker = class {
|
|
3413
|
-
constructor(eventStore, graduation, config =
|
|
4375
|
+
constructor(eventStore, graduation, config = DEFAULT_CONFIG5) {
|
|
3414
4376
|
this.eventStore = eventStore;
|
|
3415
4377
|
this.graduation = graduation;
|
|
3416
4378
|
this.config = config;
|
|
@@ -3518,7 +4480,7 @@ function createGraduationWorker(eventStore, graduation, config) {
|
|
|
3518
4480
|
return new GraduationWorker(
|
|
3519
4481
|
eventStore,
|
|
3520
4482
|
graduation,
|
|
3521
|
-
{ ...
|
|
4483
|
+
{ ...DEFAULT_CONFIG5, ...config }
|
|
3522
4484
|
);
|
|
3523
4485
|
}
|
|
3524
4486
|
|
|
@@ -3582,7 +4544,11 @@ function getSessionProject(sessionId) {
|
|
|
3582
4544
|
return registry.sessions[sessionId] || null;
|
|
3583
4545
|
}
|
|
3584
4546
|
var MemoryService = class {
|
|
3585
|
-
|
|
4547
|
+
// Primary store: SQLite (WAL mode) - for hooks, always available
|
|
4548
|
+
sqliteStore;
|
|
4549
|
+
// Analytics store: DuckDB - for server reads (optional, synced from SQLite)
|
|
4550
|
+
analyticsStore;
|
|
4551
|
+
syncWorker = null;
|
|
3586
4552
|
vectorStore;
|
|
3587
4553
|
embedder;
|
|
3588
4554
|
matcher;
|
|
@@ -3613,17 +4579,39 @@ var MemoryService = class {
|
|
|
3613
4579
|
}
|
|
3614
4580
|
this.projectHash = config.projectHash || null;
|
|
3615
4581
|
this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
|
|
3616
|
-
this.
|
|
4582
|
+
this.sqliteStore = new SQLiteEventStore(
|
|
4583
|
+
path.join(storagePath, "events.sqlite"),
|
|
4584
|
+
{ readonly: this.readOnly }
|
|
4585
|
+
);
|
|
4586
|
+
const analyticsEnabled = config.analyticsEnabled ?? this.readOnly;
|
|
4587
|
+
if (!analyticsEnabled) {
|
|
4588
|
+
this.analyticsStore = null;
|
|
4589
|
+
} else if (this.readOnly) {
|
|
4590
|
+
try {
|
|
4591
|
+
this.analyticsStore = new EventStore(
|
|
4592
|
+
path.join(storagePath, "analytics.duckdb"),
|
|
4593
|
+
{ readOnly: true }
|
|
4594
|
+
);
|
|
4595
|
+
} catch {
|
|
4596
|
+
this.analyticsStore = null;
|
|
4597
|
+
}
|
|
4598
|
+
} else {
|
|
4599
|
+
this.analyticsStore = new EventStore(
|
|
4600
|
+
path.join(storagePath, "analytics.duckdb"),
|
|
4601
|
+
{ readOnly: false }
|
|
4602
|
+
);
|
|
4603
|
+
}
|
|
3617
4604
|
this.vectorStore = new VectorStore(path.join(storagePath, "vectors"));
|
|
3618
4605
|
this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
|
|
3619
4606
|
this.matcher = getDefaultMatcher();
|
|
3620
4607
|
this.retriever = createRetriever(
|
|
3621
|
-
this.
|
|
4608
|
+
this.sqliteStore,
|
|
4609
|
+
// Interface compatible
|
|
3622
4610
|
this.vectorStore,
|
|
3623
4611
|
this.embedder,
|
|
3624
4612
|
this.matcher
|
|
3625
4613
|
);
|
|
3626
|
-
this.graduation = createGraduationPipeline(this.
|
|
4614
|
+
this.graduation = createGraduationPipeline(this.sqliteStore);
|
|
3627
4615
|
}
|
|
3628
4616
|
/**
|
|
3629
4617
|
* Initialize all components
|
|
@@ -3631,23 +4619,38 @@ var MemoryService = class {
|
|
|
3631
4619
|
async initialize() {
|
|
3632
4620
|
if (this.initialized)
|
|
3633
4621
|
return;
|
|
3634
|
-
await this.
|
|
4622
|
+
await this.sqliteStore.initialize();
|
|
4623
|
+
if (this.analyticsStore) {
|
|
4624
|
+
try {
|
|
4625
|
+
await this.analyticsStore.initialize();
|
|
4626
|
+
} catch (error) {
|
|
4627
|
+
console.warn("[MemoryService] Analytics store (DuckDB) initialization failed, using SQLite for reads:", error);
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
3635
4630
|
await this.vectorStore.initialize();
|
|
3636
4631
|
await this.embedder.initialize();
|
|
3637
4632
|
if (!this.readOnly) {
|
|
3638
4633
|
this.vectorWorker = createVectorWorker(
|
|
3639
|
-
this.
|
|
4634
|
+
this.sqliteStore,
|
|
3640
4635
|
this.vectorStore,
|
|
3641
4636
|
this.embedder
|
|
3642
4637
|
);
|
|
3643
4638
|
this.vectorWorker.start();
|
|
3644
4639
|
this.retriever.setGraduationPipeline(this.graduation);
|
|
3645
4640
|
this.graduationWorker = createGraduationWorker(
|
|
3646
|
-
this.
|
|
4641
|
+
this.sqliteStore,
|
|
3647
4642
|
this.graduation
|
|
3648
4643
|
);
|
|
3649
4644
|
this.graduationWorker.start();
|
|
3650
|
-
|
|
4645
|
+
if (this.analyticsStore) {
|
|
4646
|
+
this.syncWorker = new SyncWorker(
|
|
4647
|
+
this.sqliteStore,
|
|
4648
|
+
this.analyticsStore,
|
|
4649
|
+
{ intervalMs: 3e4, batchSize: 500 }
|
|
4650
|
+
);
|
|
4651
|
+
this.syncWorker.start();
|
|
4652
|
+
}
|
|
4653
|
+
const savedMode = await this.sqliteStore.getEndlessConfig("mode");
|
|
3651
4654
|
if (savedMode === "endless") {
|
|
3652
4655
|
this.endlessMode = "endless";
|
|
3653
4656
|
await this.initializeEndlessMode();
|
|
@@ -3688,7 +4691,7 @@ var MemoryService = class {
|
|
|
3688
4691
|
*/
|
|
3689
4692
|
async startSession(sessionId, projectPath) {
|
|
3690
4693
|
await this.initialize();
|
|
3691
|
-
await this.
|
|
4694
|
+
await this.sqliteStore.upsertSession({
|
|
3692
4695
|
id: sessionId,
|
|
3693
4696
|
startedAt: /* @__PURE__ */ new Date(),
|
|
3694
4697
|
projectPath
|
|
@@ -3699,7 +4702,7 @@ var MemoryService = class {
|
|
|
3699
4702
|
*/
|
|
3700
4703
|
async endSession(sessionId, summary) {
|
|
3701
4704
|
await this.initialize();
|
|
3702
|
-
await this.
|
|
4705
|
+
await this.sqliteStore.upsertSession({
|
|
3703
4706
|
id: sessionId,
|
|
3704
4707
|
endedAt: /* @__PURE__ */ new Date(),
|
|
3705
4708
|
summary
|
|
@@ -3710,7 +4713,7 @@ var MemoryService = class {
|
|
|
3710
4713
|
*/
|
|
3711
4714
|
async storeUserPrompt(sessionId, content, metadata) {
|
|
3712
4715
|
await this.initialize();
|
|
3713
|
-
const result = await this.
|
|
4716
|
+
const result = await this.sqliteStore.append({
|
|
3714
4717
|
eventType: "user_prompt",
|
|
3715
4718
|
sessionId,
|
|
3716
4719
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -3718,7 +4721,7 @@ var MemoryService = class {
|
|
|
3718
4721
|
metadata
|
|
3719
4722
|
});
|
|
3720
4723
|
if (result.success && !result.isDuplicate) {
|
|
3721
|
-
await this.
|
|
4724
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
|
|
3722
4725
|
}
|
|
3723
4726
|
return result;
|
|
3724
4727
|
}
|
|
@@ -3727,7 +4730,7 @@ var MemoryService = class {
|
|
|
3727
4730
|
*/
|
|
3728
4731
|
async storeAgentResponse(sessionId, content, metadata) {
|
|
3729
4732
|
await this.initialize();
|
|
3730
|
-
const result = await this.
|
|
4733
|
+
const result = await this.sqliteStore.append({
|
|
3731
4734
|
eventType: "agent_response",
|
|
3732
4735
|
sessionId,
|
|
3733
4736
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -3735,7 +4738,7 @@ var MemoryService = class {
|
|
|
3735
4738
|
metadata
|
|
3736
4739
|
});
|
|
3737
4740
|
if (result.success && !result.isDuplicate) {
|
|
3738
|
-
await this.
|
|
4741
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
|
|
3739
4742
|
}
|
|
3740
4743
|
return result;
|
|
3741
4744
|
}
|
|
@@ -3744,14 +4747,14 @@ var MemoryService = class {
|
|
|
3744
4747
|
*/
|
|
3745
4748
|
async storeSessionSummary(sessionId, summary) {
|
|
3746
4749
|
await this.initialize();
|
|
3747
|
-
const result = await this.
|
|
4750
|
+
const result = await this.sqliteStore.append({
|
|
3748
4751
|
eventType: "session_summary",
|
|
3749
4752
|
sessionId,
|
|
3750
4753
|
timestamp: /* @__PURE__ */ new Date(),
|
|
3751
4754
|
content: summary
|
|
3752
4755
|
});
|
|
3753
4756
|
if (result.success && !result.isDuplicate) {
|
|
3754
|
-
await this.
|
|
4757
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, summary);
|
|
3755
4758
|
}
|
|
3756
4759
|
return result;
|
|
3757
4760
|
}
|
|
@@ -3761,7 +4764,7 @@ var MemoryService = class {
|
|
|
3761
4764
|
async storeToolObservation(sessionId, payload) {
|
|
3762
4765
|
await this.initialize();
|
|
3763
4766
|
const content = JSON.stringify(payload);
|
|
3764
|
-
const result = await this.
|
|
4767
|
+
const result = await this.sqliteStore.append({
|
|
3765
4768
|
eventType: "tool_observation",
|
|
3766
4769
|
sessionId,
|
|
3767
4770
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -3777,7 +4780,7 @@ var MemoryService = class {
|
|
|
3777
4780
|
payload.metadata || {},
|
|
3778
4781
|
payload.success
|
|
3779
4782
|
);
|
|
3780
|
-
await this.
|
|
4783
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, embeddingContent);
|
|
3781
4784
|
}
|
|
3782
4785
|
return result;
|
|
3783
4786
|
}
|
|
@@ -3803,21 +4806,21 @@ var MemoryService = class {
|
|
|
3803
4806
|
*/
|
|
3804
4807
|
async getSessionHistory(sessionId) {
|
|
3805
4808
|
await this.initialize();
|
|
3806
|
-
return this.
|
|
4809
|
+
return this.sqliteStore.getSessionEvents(sessionId);
|
|
3807
4810
|
}
|
|
3808
4811
|
/**
|
|
3809
4812
|
* Get recent events
|
|
3810
4813
|
*/
|
|
3811
4814
|
async getRecentEvents(limit = 100) {
|
|
3812
4815
|
await this.initialize();
|
|
3813
|
-
return this.
|
|
4816
|
+
return this.sqliteStore.getRecentEvents(limit);
|
|
3814
4817
|
}
|
|
3815
4818
|
/**
|
|
3816
4819
|
* Get memory statistics
|
|
3817
4820
|
*/
|
|
3818
4821
|
async getStats() {
|
|
3819
4822
|
await this.initialize();
|
|
3820
|
-
const recentEvents = await this.
|
|
4823
|
+
const recentEvents = await this.sqliteStore.getRecentEvents(1e4);
|
|
3821
4824
|
const vectorCount = await this.vectorStore.count();
|
|
3822
4825
|
const levelStats = await this.graduation.getStats();
|
|
3823
4826
|
return {
|
|
@@ -3840,14 +4843,14 @@ var MemoryService = class {
|
|
|
3840
4843
|
*/
|
|
3841
4844
|
async getEventsByLevel(level, options) {
|
|
3842
4845
|
await this.initialize();
|
|
3843
|
-
return this.
|
|
4846
|
+
return this.sqliteStore.getEventsByLevel(level, options);
|
|
3844
4847
|
}
|
|
3845
4848
|
/**
|
|
3846
4849
|
* Get memory level for a specific event
|
|
3847
4850
|
*/
|
|
3848
4851
|
async getEventLevel(eventId) {
|
|
3849
4852
|
await this.initialize();
|
|
3850
|
-
return this.
|
|
4853
|
+
return this.sqliteStore.getEventLevel(eventId);
|
|
3851
4854
|
}
|
|
3852
4855
|
/**
|
|
3853
4856
|
* Format retrieval results as context for Claude
|
|
@@ -3941,21 +4944,21 @@ var MemoryService = class {
|
|
|
3941
4944
|
*/
|
|
3942
4945
|
async initializeEndlessMode() {
|
|
3943
4946
|
const config = await this.getEndlessConfig();
|
|
3944
|
-
this.workingSetStore = createWorkingSetStore(this.
|
|
3945
|
-
this.consolidatedStore = createConsolidatedStore(this.
|
|
4947
|
+
this.workingSetStore = createWorkingSetStore(this.sqliteStore, config);
|
|
4948
|
+
this.consolidatedStore = createConsolidatedStore(this.sqliteStore);
|
|
3946
4949
|
this.consolidationWorker = createConsolidationWorker(
|
|
3947
4950
|
this.workingSetStore,
|
|
3948
4951
|
this.consolidatedStore,
|
|
3949
4952
|
config
|
|
3950
4953
|
);
|
|
3951
|
-
this.continuityManager = createContinuityManager(this.
|
|
4954
|
+
this.continuityManager = createContinuityManager(this.sqliteStore, config);
|
|
3952
4955
|
this.consolidationWorker.start();
|
|
3953
4956
|
}
|
|
3954
4957
|
/**
|
|
3955
4958
|
* Get Endless Mode configuration
|
|
3956
4959
|
*/
|
|
3957
4960
|
async getEndlessConfig() {
|
|
3958
|
-
const savedConfig = await this.
|
|
4961
|
+
const savedConfig = await this.sqliteStore.getEndlessConfig("config");
|
|
3959
4962
|
return savedConfig || this.getDefaultEndlessConfig();
|
|
3960
4963
|
}
|
|
3961
4964
|
/**
|
|
@@ -3964,7 +4967,7 @@ var MemoryService = class {
|
|
|
3964
4967
|
async setEndlessConfig(config) {
|
|
3965
4968
|
const current = await this.getEndlessConfig();
|
|
3966
4969
|
const merged = { ...current, ...config };
|
|
3967
|
-
await this.
|
|
4970
|
+
await this.sqliteStore.setEndlessConfig("config", merged);
|
|
3968
4971
|
}
|
|
3969
4972
|
/**
|
|
3970
4973
|
* Set memory mode (session or endless)
|
|
@@ -3974,7 +4977,7 @@ var MemoryService = class {
|
|
|
3974
4977
|
if (mode === this.endlessMode)
|
|
3975
4978
|
return;
|
|
3976
4979
|
this.endlessMode = mode;
|
|
3977
|
-
await this.
|
|
4980
|
+
await this.sqliteStore.setEndlessConfig("mode", mode);
|
|
3978
4981
|
if (mode === "endless") {
|
|
3979
4982
|
await this.initializeEndlessMode();
|
|
3980
4983
|
} else {
|
|
@@ -4032,12 +5035,49 @@ var MemoryService = class {
|
|
|
4032
5035
|
return this.consolidatedStore.getAll({ limit });
|
|
4033
5036
|
}
|
|
4034
5037
|
/**
|
|
4035
|
-
*
|
|
5038
|
+
* Increment access count for memories that were used in prompts
|
|
5039
|
+
*/
|
|
5040
|
+
async incrementMemoryAccess(eventIds) {
|
|
5041
|
+
if (eventIds.length === 0)
|
|
5042
|
+
return;
|
|
5043
|
+
if (this.sqliteStore) {
|
|
5044
|
+
await this.sqliteStore.incrementAccessCount(eventIds);
|
|
5045
|
+
} else if (this.eventStore) {
|
|
5046
|
+
await this.eventStore.incrementAccessCount(eventIds);
|
|
5047
|
+
}
|
|
5048
|
+
}
|
|
5049
|
+
/**
|
|
5050
|
+
* Get most accessed memories from events
|
|
4036
5051
|
*/
|
|
4037
5052
|
async getMostAccessedMemories(limit = 10) {
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
5053
|
+
console.log("[getMostAccessedMemories] sqliteStore available:", !!this.sqliteStore);
|
|
5054
|
+
if (this.sqliteStore) {
|
|
5055
|
+
const events = await this.sqliteStore.getMostAccessed(limit);
|
|
5056
|
+
console.log("[getMostAccessedMemories] Got events from SQLite:", events.length);
|
|
5057
|
+
return events.map((event) => ({
|
|
5058
|
+
memoryId: event.id,
|
|
5059
|
+
summary: event.content.substring(0, 200) + (event.content.length > 200 ? "..." : ""),
|
|
5060
|
+
topics: [],
|
|
5061
|
+
// Could extract topics from content if needed
|
|
5062
|
+
accessCount: event.access_count || 0,
|
|
5063
|
+
lastAccessed: event.last_accessed_at || null,
|
|
5064
|
+
confidence: 1,
|
|
5065
|
+
createdAt: event.timestamp
|
|
5066
|
+
}));
|
|
5067
|
+
}
|
|
5068
|
+
if (this.consolidatedStore) {
|
|
5069
|
+
const consolidated = await this.consolidatedStore.getMostAccessed(limit);
|
|
5070
|
+
return consolidated.map((m) => ({
|
|
5071
|
+
memoryId: m.memoryId,
|
|
5072
|
+
summary: m.summary,
|
|
5073
|
+
topics: m.topics,
|
|
5074
|
+
accessCount: m.accessCount,
|
|
5075
|
+
lastAccessed: m.accessedAt,
|
|
5076
|
+
confidence: m.confidence,
|
|
5077
|
+
createdAt: m.createdAt
|
|
5078
|
+
}));
|
|
5079
|
+
}
|
|
5080
|
+
return [];
|
|
4041
5081
|
}
|
|
4042
5082
|
/**
|
|
4043
5083
|
* Mark a consolidated memory as accessed
|
|
@@ -4162,10 +5202,16 @@ var MemoryService = class {
|
|
|
4162
5202
|
if (this.vectorWorker) {
|
|
4163
5203
|
this.vectorWorker.stop();
|
|
4164
5204
|
}
|
|
5205
|
+
if (this.syncWorker) {
|
|
5206
|
+
this.syncWorker.stop();
|
|
5207
|
+
}
|
|
4165
5208
|
if (this.sharedEventStore) {
|
|
4166
5209
|
await this.sharedEventStore.close();
|
|
4167
5210
|
}
|
|
4168
|
-
await this.
|
|
5211
|
+
await this.sqliteStore.close();
|
|
5212
|
+
if (this.analyticsStore) {
|
|
5213
|
+
await this.analyticsStore.close();
|
|
5214
|
+
}
|
|
4169
5215
|
}
|
|
4170
5216
|
/**
|
|
4171
5217
|
* Expand ~ to home directory
|
|
@@ -4182,7 +5228,11 @@ var GLOBAL_KEY = "__global__";
|
|
|
4182
5228
|
function getDefaultMemoryService() {
|
|
4183
5229
|
if (!serviceCache.has(GLOBAL_KEY)) {
|
|
4184
5230
|
serviceCache.set(GLOBAL_KEY, new MemoryService({
|
|
4185
|
-
storagePath: "~/.claude-code/memory"
|
|
5231
|
+
storagePath: "~/.claude-code/memory",
|
|
5232
|
+
analyticsEnabled: false,
|
|
5233
|
+
// Hooks don't need DuckDB
|
|
5234
|
+
sharedStoreConfig: { enabled: false }
|
|
5235
|
+
// Shared store uses DuckDB too
|
|
4186
5236
|
}));
|
|
4187
5237
|
}
|
|
4188
5238
|
return serviceCache.get(GLOBAL_KEY);
|
|
@@ -4190,7 +5240,11 @@ function getDefaultMemoryService() {
|
|
|
4190
5240
|
function getReadOnlyMemoryService() {
|
|
4191
5241
|
return new MemoryService({
|
|
4192
5242
|
storagePath: "~/.claude-code/memory",
|
|
4193
|
-
readOnly: true
|
|
5243
|
+
readOnly: true,
|
|
5244
|
+
analyticsEnabled: false,
|
|
5245
|
+
// Use SQLite for reads (WAL supports concurrent readers)
|
|
5246
|
+
sharedStoreConfig: { enabled: false }
|
|
5247
|
+
// Skip shared store for now
|
|
4194
5248
|
});
|
|
4195
5249
|
}
|
|
4196
5250
|
function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
|
|
@@ -4200,7 +5254,10 @@ function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
|
|
|
4200
5254
|
serviceCache.set(hash, new MemoryService({
|
|
4201
5255
|
storagePath,
|
|
4202
5256
|
projectHash: hash,
|
|
4203
|
-
|
|
5257
|
+
// Override shared store config - hooks don't need DuckDB
|
|
5258
|
+
sharedStoreConfig: sharedStoreConfig ?? { enabled: false },
|
|
5259
|
+
analyticsEnabled: false
|
|
5260
|
+
// Hooks don't need DuckDB
|
|
4204
5261
|
}));
|
|
4205
5262
|
}
|
|
4206
5263
|
return serviceCache.get(hash);
|