claude-memory-layer 1.0.7 → 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 +10 -1
- package/.claude-memory/test.sqlite +0 -0
- package/.history/package_20260201192048.json +47 -0
- package/.history/package_20260202114053.json +49 -0
- package/HANDOFF.md +92 -0
- package/dist/cli/index.js +1711 -102
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +1257 -84
- 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 +1382 -85
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +1377 -84
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +1383 -86
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +1412 -84
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +1576 -136
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +1585 -143
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +1392 -84
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/app.js +304 -0
- package/dist/ui/index.html +202 -715
- package/dist/ui/style.css +595 -0
- package/package.json +4 -1
- package/scripts/build.ts +5 -2
- package/src/cli/index.ts +226 -0
- package/src/core/db-wrapper.ts +8 -1
- package/src/core/event-store.ts +70 -3
- package/src/core/graduation-worker.ts +171 -0
- package/src/core/graduation.ts +15 -2
- package/src/core/index.ts +4 -0
- package/src/core/retriever.ts +21 -0
- 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/citations.ts +7 -3
- package/src/server/api/events.ts +7 -3
- package/src/server/api/search.ts +7 -3
- package/src/server/api/sessions.ts +7 -3
- package/src/server/api/stats.ts +159 -12
- package/src/server/index.ts +18 -9
- package/src/services/memory-service.ts +263 -46
- package/src/ui/app.js +304 -0
- package/src/ui/index.html +202 -715
- package/src/ui/style.css +595 -0
- package/test_access.js +49 -0
package/dist/hooks/stop.js
CHANGED
|
@@ -67,7 +67,10 @@ function toDate(value) {
|
|
|
67
67
|
return new Date(value);
|
|
68
68
|
return new Date(String(value));
|
|
69
69
|
}
|
|
70
|
-
function createDatabase(path2) {
|
|
70
|
+
function createDatabase(path2, options) {
|
|
71
|
+
if (options?.readOnly) {
|
|
72
|
+
return new duckdb.Database(path2, { access_mode: "READ_ONLY" });
|
|
73
|
+
}
|
|
71
74
|
return new duckdb.Database(path2);
|
|
72
75
|
}
|
|
73
76
|
function dbRun(db, sql, params = []) {
|
|
@@ -121,18 +124,24 @@ function dbClose(db) {
|
|
|
121
124
|
|
|
122
125
|
// src/core/event-store.ts
|
|
123
126
|
var EventStore = class {
|
|
124
|
-
constructor(dbPath) {
|
|
127
|
+
constructor(dbPath, options) {
|
|
125
128
|
this.dbPath = dbPath;
|
|
126
|
-
this.
|
|
129
|
+
this.readOnly = options?.readOnly ?? false;
|
|
130
|
+
this.db = createDatabase(dbPath, { readOnly: this.readOnly });
|
|
127
131
|
}
|
|
128
132
|
db;
|
|
129
133
|
initialized = false;
|
|
134
|
+
readOnly;
|
|
130
135
|
/**
|
|
131
136
|
* Initialize database schema
|
|
132
137
|
*/
|
|
133
138
|
async initialize() {
|
|
134
139
|
if (this.initialized)
|
|
135
140
|
return;
|
|
141
|
+
if (this.readOnly) {
|
|
142
|
+
this.initialized = true;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
136
145
|
await dbRun(this.db, `
|
|
137
146
|
CREATE TABLE IF NOT EXISTS events (
|
|
138
147
|
id VARCHAR PRIMARY KEY,
|
|
@@ -609,6 +618,36 @@ var EventStore = class {
|
|
|
609
618
|
);
|
|
610
619
|
return rows;
|
|
611
620
|
}
|
|
621
|
+
/**
|
|
622
|
+
* Get events by memory level
|
|
623
|
+
*/
|
|
624
|
+
async getEventsByLevel(level, options) {
|
|
625
|
+
await this.initialize();
|
|
626
|
+
const limit = options?.limit || 50;
|
|
627
|
+
const offset = options?.offset || 0;
|
|
628
|
+
const rows = await dbAll(
|
|
629
|
+
this.db,
|
|
630
|
+
`SELECT e.* FROM events e
|
|
631
|
+
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
632
|
+
WHERE ml.level = ?
|
|
633
|
+
ORDER BY e.timestamp DESC
|
|
634
|
+
LIMIT ? OFFSET ?`,
|
|
635
|
+
[level, limit, offset]
|
|
636
|
+
);
|
|
637
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Get memory level for a specific event
|
|
641
|
+
*/
|
|
642
|
+
async getEventLevel(eventId) {
|
|
643
|
+
await this.initialize();
|
|
644
|
+
const rows = await dbAll(
|
|
645
|
+
this.db,
|
|
646
|
+
`SELECT level FROM memory_levels WHERE event_id = ?`,
|
|
647
|
+
[eventId]
|
|
648
|
+
);
|
|
649
|
+
return rows.length > 0 ? rows[0].level : null;
|
|
650
|
+
}
|
|
612
651
|
// ============================================================
|
|
613
652
|
// Endless Mode Helper Methods
|
|
614
653
|
// ============================================================
|
|
@@ -635,53 +674,1002 @@ var EventStore = class {
|
|
|
635
674
|
/**
|
|
636
675
|
* Set config value for endless mode
|
|
637
676
|
*/
|
|
638
|
-
async setEndlessConfig(key, value) {
|
|
677
|
+
async setEndlessConfig(key, value) {
|
|
678
|
+
await this.initialize();
|
|
679
|
+
await dbRun(
|
|
680
|
+
this.db,
|
|
681
|
+
`INSERT OR REPLACE INTO endless_config (key, value, updated_at)
|
|
682
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)`,
|
|
683
|
+
[key, JSON.stringify(value)]
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Get all sessions
|
|
688
|
+
*/
|
|
689
|
+
async getAllSessions() {
|
|
690
|
+
await this.initialize();
|
|
691
|
+
const rows = await dbAll(
|
|
692
|
+
this.db,
|
|
693
|
+
`SELECT * FROM sessions ORDER BY started_at DESC`
|
|
694
|
+
);
|
|
695
|
+
return rows.map((row) => ({
|
|
696
|
+
id: row.id,
|
|
697
|
+
startedAt: toDate(row.started_at),
|
|
698
|
+
endedAt: row.ended_at ? toDate(row.ended_at) : void 0,
|
|
699
|
+
projectPath: row.project_path,
|
|
700
|
+
summary: row.summary,
|
|
701
|
+
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
702
|
+
}));
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Increment access count for events (stub for compatibility)
|
|
706
|
+
*/
|
|
707
|
+
async incrementAccessCount(eventIds) {
|
|
708
|
+
return Promise.resolve();
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Get most accessed memories (stub for compatibility)
|
|
712
|
+
*/
|
|
713
|
+
async getMostAccessed(limit = 10) {
|
|
714
|
+
return [];
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Close database connection
|
|
718
|
+
*/
|
|
719
|
+
async close() {
|
|
720
|
+
await dbClose(this.db);
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Convert database row to MemoryEvent
|
|
724
|
+
*/
|
|
725
|
+
rowToEvent(row) {
|
|
726
|
+
return {
|
|
727
|
+
id: row.id,
|
|
728
|
+
eventType: row.event_type,
|
|
729
|
+
sessionId: row.session_id,
|
|
730
|
+
timestamp: toDate(row.timestamp),
|
|
731
|
+
content: row.content,
|
|
732
|
+
canonicalKey: row.canonical_key,
|
|
733
|
+
dedupeKey: row.dedupe_key,
|
|
734
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
// src/core/sqlite-event-store.ts
|
|
740
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
741
|
+
|
|
742
|
+
// src/core/sqlite-wrapper.ts
|
|
743
|
+
import Database from "better-sqlite3";
|
|
744
|
+
function createSQLiteDatabase(path2, options) {
|
|
745
|
+
const db = new Database(path2, {
|
|
746
|
+
readonly: options?.readonly ?? false
|
|
747
|
+
});
|
|
748
|
+
if (!options?.readonly && (options?.walMode ?? true)) {
|
|
749
|
+
db.pragma("journal_mode = WAL");
|
|
750
|
+
db.pragma("synchronous = NORMAL");
|
|
751
|
+
db.pragma("busy_timeout = 5000");
|
|
752
|
+
}
|
|
753
|
+
return db;
|
|
754
|
+
}
|
|
755
|
+
function sqliteRun(db, sql, params = []) {
|
|
756
|
+
const stmt = db.prepare(sql);
|
|
757
|
+
return stmt.run(...params);
|
|
758
|
+
}
|
|
759
|
+
function sqliteAll(db, sql, params = []) {
|
|
760
|
+
const stmt = db.prepare(sql);
|
|
761
|
+
return stmt.all(...params);
|
|
762
|
+
}
|
|
763
|
+
function sqliteGet(db, sql, params = []) {
|
|
764
|
+
const stmt = db.prepare(sql);
|
|
765
|
+
return stmt.get(...params);
|
|
766
|
+
}
|
|
767
|
+
function sqliteExec(db, sql) {
|
|
768
|
+
db.exec(sql);
|
|
769
|
+
}
|
|
770
|
+
function sqliteClose(db) {
|
|
771
|
+
db.close();
|
|
772
|
+
}
|
|
773
|
+
function toDateFromSQLite(value) {
|
|
774
|
+
if (value instanceof Date)
|
|
775
|
+
return value;
|
|
776
|
+
if (typeof value === "string")
|
|
777
|
+
return new Date(value);
|
|
778
|
+
if (typeof value === "number")
|
|
779
|
+
return new Date(value);
|
|
780
|
+
return new Date(String(value));
|
|
781
|
+
}
|
|
782
|
+
function toSQLiteTimestamp(date) {
|
|
783
|
+
return date.toISOString();
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/core/sqlite-event-store.ts
|
|
787
|
+
var SQLiteEventStore = class {
|
|
788
|
+
constructor(dbPath, options) {
|
|
789
|
+
this.dbPath = dbPath;
|
|
790
|
+
this.readOnly = options?.readonly ?? false;
|
|
791
|
+
this.db = createSQLiteDatabase(dbPath, {
|
|
792
|
+
readonly: this.readOnly,
|
|
793
|
+
walMode: !this.readOnly
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
db;
|
|
797
|
+
initialized = false;
|
|
798
|
+
readOnly;
|
|
799
|
+
/**
|
|
800
|
+
* Initialize database schema
|
|
801
|
+
*/
|
|
802
|
+
async initialize() {
|
|
803
|
+
if (this.initialized)
|
|
804
|
+
return;
|
|
805
|
+
if (this.readOnly) {
|
|
806
|
+
this.initialized = true;
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
sqliteExec(this.db, `
|
|
810
|
+
-- L0 EventStore: Single Source of Truth (immutable, append-only)
|
|
811
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
812
|
+
id TEXT PRIMARY KEY,
|
|
813
|
+
event_type TEXT NOT NULL,
|
|
814
|
+
session_id TEXT NOT NULL,
|
|
815
|
+
timestamp TEXT NOT NULL,
|
|
816
|
+
content TEXT NOT NULL,
|
|
817
|
+
canonical_key TEXT NOT NULL,
|
|
818
|
+
dedupe_key TEXT UNIQUE,
|
|
819
|
+
metadata TEXT,
|
|
820
|
+
access_count INTEGER DEFAULT 0,
|
|
821
|
+
last_accessed_at TEXT
|
|
822
|
+
);
|
|
823
|
+
|
|
824
|
+
-- Dedup table for idempotency
|
|
825
|
+
CREATE TABLE IF NOT EXISTS event_dedup (
|
|
826
|
+
dedupe_key TEXT PRIMARY KEY,
|
|
827
|
+
event_id TEXT NOT NULL,
|
|
828
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
-- Session metadata
|
|
832
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
833
|
+
id TEXT PRIMARY KEY,
|
|
834
|
+
started_at TEXT NOT NULL,
|
|
835
|
+
ended_at TEXT,
|
|
836
|
+
project_path TEXT,
|
|
837
|
+
summary TEXT,
|
|
838
|
+
tags TEXT
|
|
839
|
+
);
|
|
840
|
+
|
|
841
|
+
-- Insights (derived data, rebuildable)
|
|
842
|
+
CREATE TABLE IF NOT EXISTS insights (
|
|
843
|
+
id TEXT PRIMARY KEY,
|
|
844
|
+
insight_type TEXT NOT NULL,
|
|
845
|
+
content TEXT NOT NULL,
|
|
846
|
+
canonical_key TEXT NOT NULL,
|
|
847
|
+
confidence REAL,
|
|
848
|
+
source_events TEXT,
|
|
849
|
+
created_at TEXT,
|
|
850
|
+
last_updated TEXT
|
|
851
|
+
);
|
|
852
|
+
|
|
853
|
+
-- Embedding Outbox (Single-Writer Pattern)
|
|
854
|
+
CREATE TABLE IF NOT EXISTS embedding_outbox (
|
|
855
|
+
id TEXT PRIMARY KEY,
|
|
856
|
+
event_id TEXT NOT NULL,
|
|
857
|
+
content TEXT NOT NULL,
|
|
858
|
+
status TEXT DEFAULT 'pending',
|
|
859
|
+
retry_count INTEGER DEFAULT 0,
|
|
860
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
861
|
+
processed_at TEXT,
|
|
862
|
+
error_message TEXT
|
|
863
|
+
);
|
|
864
|
+
|
|
865
|
+
-- Projection offset tracking
|
|
866
|
+
CREATE TABLE IF NOT EXISTS projection_offsets (
|
|
867
|
+
projection_name TEXT PRIMARY KEY,
|
|
868
|
+
last_event_id TEXT,
|
|
869
|
+
last_timestamp TEXT,
|
|
870
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
-- Memory level tracking
|
|
874
|
+
CREATE TABLE IF NOT EXISTS memory_levels (
|
|
875
|
+
event_id TEXT PRIMARY KEY,
|
|
876
|
+
level TEXT NOT NULL DEFAULT 'L0',
|
|
877
|
+
promoted_at TEXT DEFAULT (datetime('now'))
|
|
878
|
+
);
|
|
879
|
+
|
|
880
|
+
-- Entries (immutable memory units)
|
|
881
|
+
CREATE TABLE IF NOT EXISTS entries (
|
|
882
|
+
entry_id TEXT PRIMARY KEY,
|
|
883
|
+
created_ts TEXT NOT NULL,
|
|
884
|
+
entry_type TEXT NOT NULL,
|
|
885
|
+
title TEXT NOT NULL,
|
|
886
|
+
content_json TEXT NOT NULL,
|
|
887
|
+
stage TEXT NOT NULL DEFAULT 'raw',
|
|
888
|
+
status TEXT DEFAULT 'active',
|
|
889
|
+
superseded_by TEXT,
|
|
890
|
+
build_id TEXT,
|
|
891
|
+
evidence_json TEXT,
|
|
892
|
+
canonical_key TEXT,
|
|
893
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
894
|
+
);
|
|
895
|
+
|
|
896
|
+
-- Entities (task/condition/artifact)
|
|
897
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
898
|
+
entity_id TEXT PRIMARY KEY,
|
|
899
|
+
entity_type TEXT NOT NULL,
|
|
900
|
+
canonical_key TEXT NOT NULL,
|
|
901
|
+
title TEXT NOT NULL,
|
|
902
|
+
stage TEXT NOT NULL DEFAULT 'raw',
|
|
903
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
904
|
+
current_json TEXT NOT NULL,
|
|
905
|
+
title_norm TEXT,
|
|
906
|
+
search_text TEXT,
|
|
907
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
908
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
909
|
+
);
|
|
910
|
+
|
|
911
|
+
-- Entity aliases for canonical key lookup
|
|
912
|
+
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
913
|
+
entity_type TEXT NOT NULL,
|
|
914
|
+
canonical_key TEXT NOT NULL,
|
|
915
|
+
entity_id TEXT NOT NULL,
|
|
916
|
+
is_primary INTEGER DEFAULT 0,
|
|
917
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
918
|
+
PRIMARY KEY(entity_type, canonical_key)
|
|
919
|
+
);
|
|
920
|
+
|
|
921
|
+
-- Edges (relationships between entries/entities)
|
|
922
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
923
|
+
edge_id TEXT PRIMARY KEY,
|
|
924
|
+
src_type TEXT NOT NULL,
|
|
925
|
+
src_id TEXT NOT NULL,
|
|
926
|
+
rel_type TEXT NOT NULL,
|
|
927
|
+
dst_type TEXT NOT NULL,
|
|
928
|
+
dst_id TEXT NOT NULL,
|
|
929
|
+
meta_json TEXT,
|
|
930
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
931
|
+
);
|
|
932
|
+
|
|
933
|
+
-- Vector Outbox V2 Table
|
|
934
|
+
CREATE TABLE IF NOT EXISTS vector_outbox (
|
|
935
|
+
job_id TEXT PRIMARY KEY,
|
|
936
|
+
item_kind TEXT NOT NULL,
|
|
937
|
+
item_id TEXT NOT NULL,
|
|
938
|
+
embedding_version TEXT NOT NULL,
|
|
939
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
940
|
+
retry_count INTEGER DEFAULT 0,
|
|
941
|
+
error TEXT,
|
|
942
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
943
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
944
|
+
UNIQUE(item_kind, item_id, embedding_version)
|
|
945
|
+
);
|
|
946
|
+
|
|
947
|
+
-- Build Runs
|
|
948
|
+
CREATE TABLE IF NOT EXISTS build_runs (
|
|
949
|
+
build_id TEXT PRIMARY KEY,
|
|
950
|
+
started_at TEXT NOT NULL,
|
|
951
|
+
finished_at TEXT,
|
|
952
|
+
extractor_model TEXT NOT NULL,
|
|
953
|
+
extractor_prompt_hash TEXT NOT NULL,
|
|
954
|
+
embedder_model TEXT NOT NULL,
|
|
955
|
+
embedding_version TEXT NOT NULL,
|
|
956
|
+
idris_version TEXT NOT NULL,
|
|
957
|
+
schema_version TEXT NOT NULL,
|
|
958
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
959
|
+
error TEXT
|
|
960
|
+
);
|
|
961
|
+
|
|
962
|
+
-- Pipeline Metrics
|
|
963
|
+
CREATE TABLE IF NOT EXISTS pipeline_metrics (
|
|
964
|
+
id TEXT PRIMARY KEY,
|
|
965
|
+
ts TEXT NOT NULL,
|
|
966
|
+
stage TEXT NOT NULL,
|
|
967
|
+
latency_ms REAL NOT NULL,
|
|
968
|
+
success INTEGER NOT NULL,
|
|
969
|
+
error TEXT,
|
|
970
|
+
session_id TEXT
|
|
971
|
+
);
|
|
972
|
+
|
|
973
|
+
-- Working Set table (active memory window)
|
|
974
|
+
CREATE TABLE IF NOT EXISTS working_set (
|
|
975
|
+
id TEXT PRIMARY KEY,
|
|
976
|
+
event_id TEXT NOT NULL,
|
|
977
|
+
added_at TEXT DEFAULT (datetime('now')),
|
|
978
|
+
relevance_score REAL DEFAULT 1.0,
|
|
979
|
+
topics TEXT,
|
|
980
|
+
expires_at TEXT
|
|
981
|
+
);
|
|
982
|
+
|
|
983
|
+
-- Consolidated Memories table (long-term integrated memories)
|
|
984
|
+
CREATE TABLE IF NOT EXISTS consolidated_memories (
|
|
985
|
+
memory_id TEXT PRIMARY KEY,
|
|
986
|
+
summary TEXT NOT NULL,
|
|
987
|
+
topics TEXT,
|
|
988
|
+
source_events TEXT,
|
|
989
|
+
confidence REAL DEFAULT 0.5,
|
|
990
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
991
|
+
accessed_at TEXT,
|
|
992
|
+
access_count INTEGER DEFAULT 0
|
|
993
|
+
);
|
|
994
|
+
|
|
995
|
+
-- Continuity Log table (tracks context transitions)
|
|
996
|
+
CREATE TABLE IF NOT EXISTS continuity_log (
|
|
997
|
+
log_id TEXT PRIMARY KEY,
|
|
998
|
+
from_context_id TEXT,
|
|
999
|
+
to_context_id TEXT,
|
|
1000
|
+
continuity_score REAL,
|
|
1001
|
+
transition_type TEXT,
|
|
1002
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
1003
|
+
);
|
|
1004
|
+
|
|
1005
|
+
-- Endless Mode Config table
|
|
1006
|
+
CREATE TABLE IF NOT EXISTS endless_config (
|
|
1007
|
+
key TEXT PRIMARY KEY,
|
|
1008
|
+
value TEXT,
|
|
1009
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
1010
|
+
);
|
|
1011
|
+
|
|
1012
|
+
-- Sync position tracking (for SQLite -> DuckDB sync)
|
|
1013
|
+
CREATE TABLE IF NOT EXISTS sync_positions (
|
|
1014
|
+
target_name TEXT PRIMARY KEY,
|
|
1015
|
+
last_event_id TEXT,
|
|
1016
|
+
last_timestamp TEXT,
|
|
1017
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
1018
|
+
);
|
|
1019
|
+
|
|
1020
|
+
-- Create indexes
|
|
1021
|
+
CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
|
|
1022
|
+
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
|
|
1023
|
+
CREATE INDEX IF NOT EXISTS idx_entries_type ON entries(entry_type);
|
|
1024
|
+
CREATE INDEX IF NOT EXISTS idx_entries_stage ON entries(stage);
|
|
1025
|
+
CREATE INDEX IF NOT EXISTS idx_entries_canonical ON entries(canonical_key);
|
|
1026
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type_key ON entities(entity_type, canonical_key);
|
|
1027
|
+
CREATE INDEX IF NOT EXISTS idx_entities_status ON entities(status);
|
|
1028
|
+
CREATE INDEX IF NOT EXISTS idx_edges_src ON edges(src_id, rel_type);
|
|
1029
|
+
CREATE INDEX IF NOT EXISTS idx_edges_dst ON edges(dst_id, rel_type);
|
|
1030
|
+
CREATE INDEX IF NOT EXISTS idx_edges_rel ON edges(rel_type);
|
|
1031
|
+
CREATE INDEX IF NOT EXISTS idx_outbox_status ON vector_outbox(status);
|
|
1032
|
+
CREATE INDEX IF NOT EXISTS idx_working_set_expires ON working_set(expires_at);
|
|
1033
|
+
CREATE INDEX IF NOT EXISTS idx_working_set_relevance ON working_set(relevance_score);
|
|
1034
|
+
CREATE INDEX IF NOT EXISTS idx_consolidated_confidence ON consolidated_memories(confidence);
|
|
1035
|
+
CREATE INDEX IF NOT EXISTS idx_continuity_created ON continuity_log(created_at);
|
|
1036
|
+
CREATE INDEX IF NOT EXISTS idx_embedding_outbox_status ON embedding_outbox(status);
|
|
1037
|
+
`);
|
|
1038
|
+
const tableInfo = sqliteAll(this.db, "PRAGMA table_info(events)", []);
|
|
1039
|
+
const columnNames = tableInfo.map((col) => col.name);
|
|
1040
|
+
if (!columnNames.includes("access_count")) {
|
|
1041
|
+
try {
|
|
1042
|
+
sqliteExec(this.db, `
|
|
1043
|
+
ALTER TABLE events ADD COLUMN access_count INTEGER DEFAULT 0;
|
|
1044
|
+
`);
|
|
1045
|
+
} catch (err) {
|
|
1046
|
+
console.error("Error adding access_count column:", err);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
if (!columnNames.includes("last_accessed_at")) {
|
|
1050
|
+
try {
|
|
1051
|
+
sqliteExec(this.db, `
|
|
1052
|
+
ALTER TABLE events ADD COLUMN last_accessed_at TEXT;
|
|
1053
|
+
`);
|
|
1054
|
+
} catch (err) {
|
|
1055
|
+
console.error("Error adding last_accessed_at column:", err);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
try {
|
|
1059
|
+
sqliteExec(this.db, `
|
|
1060
|
+
CREATE INDEX IF NOT EXISTS idx_events_access_count ON events(access_count DESC);
|
|
1061
|
+
`);
|
|
1062
|
+
} catch (err) {
|
|
1063
|
+
}
|
|
1064
|
+
try {
|
|
1065
|
+
sqliteExec(this.db, `
|
|
1066
|
+
CREATE INDEX IF NOT EXISTS idx_events_last_accessed ON events(last_accessed_at DESC);
|
|
1067
|
+
`);
|
|
1068
|
+
} catch (err) {
|
|
1069
|
+
}
|
|
1070
|
+
this.initialized = true;
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Append event to store (Append-only, Idempotent)
|
|
1074
|
+
*/
|
|
1075
|
+
async append(input) {
|
|
1076
|
+
await this.initialize();
|
|
1077
|
+
const canonicalKey = makeCanonicalKey(input.content);
|
|
1078
|
+
const dedupeKey = makeDedupeKey(input.content, input.sessionId);
|
|
1079
|
+
const existing = sqliteGet(
|
|
1080
|
+
this.db,
|
|
1081
|
+
`SELECT event_id FROM event_dedup WHERE dedupe_key = ?`,
|
|
1082
|
+
[dedupeKey]
|
|
1083
|
+
);
|
|
1084
|
+
if (existing) {
|
|
1085
|
+
return {
|
|
1086
|
+
success: true,
|
|
1087
|
+
eventId: existing.event_id,
|
|
1088
|
+
isDuplicate: true
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
const id = randomUUID2();
|
|
1092
|
+
const timestamp = toSQLiteTimestamp(input.timestamp);
|
|
1093
|
+
try {
|
|
1094
|
+
const insertEvent = this.db.prepare(`
|
|
1095
|
+
INSERT INTO events (id, event_type, session_id, timestamp, content, canonical_key, dedupe_key, metadata)
|
|
1096
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1097
|
+
`);
|
|
1098
|
+
const insertDedup = this.db.prepare(`
|
|
1099
|
+
INSERT INTO event_dedup (dedupe_key, event_id) VALUES (?, ?)
|
|
1100
|
+
`);
|
|
1101
|
+
const insertLevel = this.db.prepare(`
|
|
1102
|
+
INSERT INTO memory_levels (event_id, level) VALUES (?, 'L0')
|
|
1103
|
+
`);
|
|
1104
|
+
const transaction = this.db.transaction(() => {
|
|
1105
|
+
insertEvent.run(
|
|
1106
|
+
id,
|
|
1107
|
+
input.eventType,
|
|
1108
|
+
input.sessionId,
|
|
1109
|
+
timestamp,
|
|
1110
|
+
input.content,
|
|
1111
|
+
canonicalKey,
|
|
1112
|
+
dedupeKey,
|
|
1113
|
+
JSON.stringify(input.metadata || {})
|
|
1114
|
+
);
|
|
1115
|
+
insertDedup.run(dedupeKey, id);
|
|
1116
|
+
insertLevel.run(id);
|
|
1117
|
+
});
|
|
1118
|
+
transaction();
|
|
1119
|
+
return { success: true, eventId: id, isDuplicate: false };
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
return {
|
|
1122
|
+
success: false,
|
|
1123
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Get events by session ID
|
|
1129
|
+
*/
|
|
1130
|
+
async getSessionEvents(sessionId) {
|
|
1131
|
+
await this.initialize();
|
|
1132
|
+
const rows = sqliteAll(
|
|
1133
|
+
this.db,
|
|
1134
|
+
`SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
|
|
1135
|
+
[sessionId]
|
|
1136
|
+
);
|
|
1137
|
+
return rows.map(this.rowToEvent);
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Get recent events
|
|
1141
|
+
*/
|
|
1142
|
+
async getRecentEvents(limit = 100) {
|
|
1143
|
+
await this.initialize();
|
|
1144
|
+
const rows = sqliteAll(
|
|
1145
|
+
this.db,
|
|
1146
|
+
`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
|
|
1147
|
+
[limit]
|
|
1148
|
+
);
|
|
1149
|
+
return rows.map(this.rowToEvent);
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Get event by ID
|
|
1153
|
+
*/
|
|
1154
|
+
async getEvent(id) {
|
|
1155
|
+
await this.initialize();
|
|
1156
|
+
const row = sqliteGet(
|
|
1157
|
+
this.db,
|
|
1158
|
+
`SELECT * FROM events WHERE id = ?`,
|
|
1159
|
+
[id]
|
|
1160
|
+
);
|
|
1161
|
+
if (!row)
|
|
1162
|
+
return null;
|
|
1163
|
+
return this.rowToEvent(row);
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Get events since a timestamp (for sync)
|
|
1167
|
+
*/
|
|
1168
|
+
async getEventsSince(timestamp, limit = 1e3) {
|
|
1169
|
+
await this.initialize();
|
|
1170
|
+
const rows = sqliteAll(
|
|
1171
|
+
this.db,
|
|
1172
|
+
`SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
|
|
1173
|
+
[timestamp, limit]
|
|
1174
|
+
);
|
|
1175
|
+
return rows.map(this.rowToEvent);
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Create or update session
|
|
1179
|
+
*/
|
|
1180
|
+
async upsertSession(session) {
|
|
1181
|
+
await this.initialize();
|
|
1182
|
+
const existing = sqliteGet(
|
|
1183
|
+
this.db,
|
|
1184
|
+
`SELECT id FROM sessions WHERE id = ?`,
|
|
1185
|
+
[session.id]
|
|
1186
|
+
);
|
|
1187
|
+
if (!existing) {
|
|
1188
|
+
sqliteRun(
|
|
1189
|
+
this.db,
|
|
1190
|
+
`INSERT INTO sessions (id, started_at, project_path, tags)
|
|
1191
|
+
VALUES (?, ?, ?, ?)`,
|
|
1192
|
+
[
|
|
1193
|
+
session.id,
|
|
1194
|
+
toSQLiteTimestamp(session.startedAt || /* @__PURE__ */ new Date()),
|
|
1195
|
+
session.projectPath || null,
|
|
1196
|
+
JSON.stringify(session.tags || [])
|
|
1197
|
+
]
|
|
1198
|
+
);
|
|
1199
|
+
} else {
|
|
1200
|
+
const updates = [];
|
|
1201
|
+
const values = [];
|
|
1202
|
+
if (session.endedAt) {
|
|
1203
|
+
updates.push("ended_at = ?");
|
|
1204
|
+
values.push(toSQLiteTimestamp(session.endedAt));
|
|
1205
|
+
}
|
|
1206
|
+
if (session.summary) {
|
|
1207
|
+
updates.push("summary = ?");
|
|
1208
|
+
values.push(session.summary);
|
|
1209
|
+
}
|
|
1210
|
+
if (session.tags) {
|
|
1211
|
+
updates.push("tags = ?");
|
|
1212
|
+
values.push(JSON.stringify(session.tags));
|
|
1213
|
+
}
|
|
1214
|
+
if (updates.length > 0) {
|
|
1215
|
+
values.push(session.id);
|
|
1216
|
+
sqliteRun(
|
|
1217
|
+
this.db,
|
|
1218
|
+
`UPDATE sessions SET ${updates.join(", ")} WHERE id = ?`,
|
|
1219
|
+
values
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Get session by ID
|
|
1226
|
+
*/
|
|
1227
|
+
async getSession(id) {
|
|
1228
|
+
await this.initialize();
|
|
1229
|
+
const row = sqliteGet(
|
|
1230
|
+
this.db,
|
|
1231
|
+
`SELECT * FROM sessions WHERE id = ?`,
|
|
1232
|
+
[id]
|
|
1233
|
+
);
|
|
1234
|
+
if (!row)
|
|
1235
|
+
return null;
|
|
1236
|
+
return {
|
|
1237
|
+
id: row.id,
|
|
1238
|
+
startedAt: toDateFromSQLite(row.started_at),
|
|
1239
|
+
endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
|
|
1240
|
+
projectPath: row.project_path,
|
|
1241
|
+
summary: row.summary,
|
|
1242
|
+
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Get all sessions
|
|
1247
|
+
*/
|
|
1248
|
+
async getAllSessions() {
|
|
1249
|
+
await this.initialize();
|
|
1250
|
+
const rows = sqliteAll(
|
|
1251
|
+
this.db,
|
|
1252
|
+
`SELECT * FROM sessions ORDER BY started_at DESC`
|
|
1253
|
+
);
|
|
1254
|
+
return rows.map((row) => ({
|
|
1255
|
+
id: row.id,
|
|
1256
|
+
startedAt: toDateFromSQLite(row.started_at),
|
|
1257
|
+
endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
|
|
1258
|
+
projectPath: row.project_path,
|
|
1259
|
+
summary: row.summary,
|
|
1260
|
+
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
1261
|
+
}));
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Add to embedding outbox
|
|
1265
|
+
*/
|
|
1266
|
+
async enqueueForEmbedding(eventId, content) {
|
|
1267
|
+
await this.initialize();
|
|
1268
|
+
const id = randomUUID2();
|
|
1269
|
+
sqliteRun(
|
|
1270
|
+
this.db,
|
|
1271
|
+
`INSERT INTO embedding_outbox (id, event_id, content, status, retry_count)
|
|
1272
|
+
VALUES (?, ?, ?, 'pending', 0)`,
|
|
1273
|
+
[id, eventId, content]
|
|
1274
|
+
);
|
|
1275
|
+
return id;
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Get pending outbox items
|
|
1279
|
+
*/
|
|
1280
|
+
async getPendingOutboxItems(limit = 32) {
|
|
1281
|
+
await this.initialize();
|
|
1282
|
+
const pending = sqliteAll(
|
|
1283
|
+
this.db,
|
|
1284
|
+
`SELECT * FROM embedding_outbox
|
|
1285
|
+
WHERE status = 'pending'
|
|
1286
|
+
ORDER BY created_at
|
|
1287
|
+
LIMIT ?`,
|
|
1288
|
+
[limit]
|
|
1289
|
+
);
|
|
1290
|
+
if (pending.length === 0)
|
|
1291
|
+
return [];
|
|
1292
|
+
const ids = pending.map((r) => r.id);
|
|
1293
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1294
|
+
sqliteRun(
|
|
1295
|
+
this.db,
|
|
1296
|
+
`UPDATE embedding_outbox SET status = 'processing' WHERE id IN (${placeholders})`,
|
|
1297
|
+
ids
|
|
1298
|
+
);
|
|
1299
|
+
return pending.map((row) => ({
|
|
1300
|
+
id: row.id,
|
|
1301
|
+
eventId: row.event_id,
|
|
1302
|
+
content: row.content,
|
|
1303
|
+
status: "processing",
|
|
1304
|
+
retryCount: row.retry_count,
|
|
1305
|
+
createdAt: toDateFromSQLite(row.created_at),
|
|
1306
|
+
errorMessage: row.error_message
|
|
1307
|
+
}));
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Mark outbox items as done
|
|
1311
|
+
*/
|
|
1312
|
+
async completeOutboxItems(ids) {
|
|
1313
|
+
if (ids.length === 0)
|
|
1314
|
+
return;
|
|
1315
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1316
|
+
sqliteRun(
|
|
1317
|
+
this.db,
|
|
1318
|
+
`DELETE FROM embedding_outbox WHERE id IN (${placeholders})`,
|
|
1319
|
+
ids
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Mark outbox items as failed
|
|
1324
|
+
*/
|
|
1325
|
+
async failOutboxItems(ids, error) {
|
|
1326
|
+
if (ids.length === 0)
|
|
1327
|
+
return;
|
|
1328
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1329
|
+
sqliteRun(
|
|
1330
|
+
this.db,
|
|
1331
|
+
`UPDATE embedding_outbox
|
|
1332
|
+
SET status = CASE WHEN retry_count >= 3 THEN 'failed' ELSE 'pending' END,
|
|
1333
|
+
retry_count = retry_count + 1,
|
|
1334
|
+
error_message = ?
|
|
1335
|
+
WHERE id IN (${placeholders})`,
|
|
1336
|
+
[error, ...ids]
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Update memory level
|
|
1341
|
+
*/
|
|
1342
|
+
async updateMemoryLevel(eventId, level) {
|
|
1343
|
+
await this.initialize();
|
|
1344
|
+
sqliteRun(
|
|
1345
|
+
this.db,
|
|
1346
|
+
`UPDATE memory_levels SET level = ?, promoted_at = datetime('now') WHERE event_id = ?`,
|
|
1347
|
+
[level, eventId]
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
/**
|
|
1351
|
+
* Get memory level statistics
|
|
1352
|
+
*/
|
|
1353
|
+
async getLevelStats() {
|
|
1354
|
+
await this.initialize();
|
|
1355
|
+
const rows = sqliteAll(
|
|
1356
|
+
this.db,
|
|
1357
|
+
`SELECT level, COUNT(*) as count FROM memory_levels GROUP BY level`
|
|
1358
|
+
);
|
|
1359
|
+
return rows;
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* Get events by memory level
|
|
1363
|
+
*/
|
|
1364
|
+
async getEventsByLevel(level, options) {
|
|
1365
|
+
await this.initialize();
|
|
1366
|
+
const limit = options?.limit || 50;
|
|
1367
|
+
const offset = options?.offset || 0;
|
|
1368
|
+
const rows = sqliteAll(
|
|
1369
|
+
this.db,
|
|
1370
|
+
`SELECT e.* FROM events e
|
|
1371
|
+
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
1372
|
+
WHERE ml.level = ?
|
|
1373
|
+
ORDER BY e.timestamp DESC
|
|
1374
|
+
LIMIT ? OFFSET ?`,
|
|
1375
|
+
[level, limit, offset]
|
|
1376
|
+
);
|
|
1377
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Get memory level for a specific event
|
|
1381
|
+
*/
|
|
1382
|
+
async getEventLevel(eventId) {
|
|
1383
|
+
await this.initialize();
|
|
1384
|
+
const row = sqliteGet(
|
|
1385
|
+
this.db,
|
|
1386
|
+
`SELECT level FROM memory_levels WHERE event_id = ?`,
|
|
1387
|
+
[eventId]
|
|
1388
|
+
);
|
|
1389
|
+
return row ? row.level : null;
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Get sync position for a target
|
|
1393
|
+
*/
|
|
1394
|
+
async getSyncPosition(targetName) {
|
|
1395
|
+
await this.initialize();
|
|
1396
|
+
const row = sqliteGet(
|
|
1397
|
+
this.db,
|
|
1398
|
+
`SELECT last_event_id, last_timestamp FROM sync_positions WHERE target_name = ?`,
|
|
1399
|
+
[targetName]
|
|
1400
|
+
);
|
|
1401
|
+
return {
|
|
1402
|
+
lastEventId: row?.last_event_id ?? null,
|
|
1403
|
+
lastTimestamp: row?.last_timestamp ?? null
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
/**
|
|
1407
|
+
* Update sync position for a target
|
|
1408
|
+
*/
|
|
1409
|
+
async updateSyncPosition(targetName, lastEventId, lastTimestamp) {
|
|
1410
|
+
await this.initialize();
|
|
1411
|
+
sqliteRun(
|
|
1412
|
+
this.db,
|
|
1413
|
+
`INSERT OR REPLACE INTO sync_positions (target_name, last_event_id, last_timestamp, updated_at)
|
|
1414
|
+
VALUES (?, ?, ?, datetime('now'))`,
|
|
1415
|
+
[targetName, lastEventId, lastTimestamp]
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Get config value for endless mode
|
|
1420
|
+
*/
|
|
1421
|
+
async getEndlessConfig(key) {
|
|
1422
|
+
await this.initialize();
|
|
1423
|
+
const row = sqliteGet(
|
|
1424
|
+
this.db,
|
|
1425
|
+
`SELECT value FROM endless_config WHERE key = ?`,
|
|
1426
|
+
[key]
|
|
1427
|
+
);
|
|
1428
|
+
if (!row)
|
|
1429
|
+
return null;
|
|
1430
|
+
return JSON.parse(row.value);
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Set config value for endless mode
|
|
1434
|
+
*/
|
|
1435
|
+
async setEndlessConfig(key, value) {
|
|
1436
|
+
await this.initialize();
|
|
1437
|
+
sqliteRun(
|
|
1438
|
+
this.db,
|
|
1439
|
+
`INSERT OR REPLACE INTO endless_config (key, value, updated_at)
|
|
1440
|
+
VALUES (?, ?, datetime('now'))`,
|
|
1441
|
+
[key, JSON.stringify(value)]
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Increment access count for events
|
|
1446
|
+
*/
|
|
1447
|
+
async incrementAccessCount(eventIds) {
|
|
1448
|
+
if (eventIds.length === 0 || this.readOnly)
|
|
1449
|
+
return;
|
|
639
1450
|
await this.initialize();
|
|
640
|
-
|
|
1451
|
+
const placeholders = eventIds.map(() => "?").join(",");
|
|
1452
|
+
const currentTime = toSQLiteTimestamp(/* @__PURE__ */ new Date());
|
|
1453
|
+
sqliteRun(
|
|
641
1454
|
this.db,
|
|
642
|
-
`
|
|
643
|
-
|
|
644
|
-
|
|
1455
|
+
`UPDATE events
|
|
1456
|
+
SET access_count = access_count + 1,
|
|
1457
|
+
last_accessed_at = ?
|
|
1458
|
+
WHERE id IN (${placeholders})`,
|
|
1459
|
+
[currentTime, ...eventIds]
|
|
645
1460
|
);
|
|
646
1461
|
}
|
|
647
1462
|
/**
|
|
648
|
-
* Get
|
|
1463
|
+
* Get most accessed memories
|
|
649
1464
|
*/
|
|
650
|
-
async
|
|
1465
|
+
async getMostAccessed(limit = 10) {
|
|
651
1466
|
await this.initialize();
|
|
652
|
-
const rows =
|
|
1467
|
+
const rows = sqliteAll(
|
|
653
1468
|
this.db,
|
|
654
|
-
`SELECT * FROM
|
|
1469
|
+
`SELECT * FROM events
|
|
1470
|
+
WHERE access_count > 0
|
|
1471
|
+
ORDER BY access_count DESC, last_accessed_at DESC
|
|
1472
|
+
LIMIT ?`,
|
|
1473
|
+
[limit]
|
|
655
1474
|
);
|
|
656
|
-
return rows.map((row) => (
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
}));
|
|
1475
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
1476
|
+
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Get database instance for direct access
|
|
1479
|
+
*/
|
|
1480
|
+
getDatabase() {
|
|
1481
|
+
return this.db;
|
|
664
1482
|
}
|
|
665
1483
|
/**
|
|
666
1484
|
* Close database connection
|
|
667
1485
|
*/
|
|
668
1486
|
async close() {
|
|
669
|
-
|
|
1487
|
+
sqliteClose(this.db);
|
|
670
1488
|
}
|
|
671
1489
|
/**
|
|
672
1490
|
* Convert database row to MemoryEvent
|
|
673
1491
|
*/
|
|
674
1492
|
rowToEvent(row) {
|
|
675
|
-
|
|
1493
|
+
const event = {
|
|
676
1494
|
id: row.id,
|
|
677
1495
|
eventType: row.event_type,
|
|
678
1496
|
sessionId: row.session_id,
|
|
679
|
-
timestamp:
|
|
1497
|
+
timestamp: toDateFromSQLite(row.timestamp),
|
|
680
1498
|
content: row.content,
|
|
681
1499
|
canonicalKey: row.canonical_key,
|
|
682
1500
|
dedupeKey: row.dedupe_key,
|
|
683
1501
|
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
684
1502
|
};
|
|
1503
|
+
if (row.access_count !== void 0) {
|
|
1504
|
+
event.access_count = row.access_count;
|
|
1505
|
+
}
|
|
1506
|
+
if (row.last_accessed_at !== void 0) {
|
|
1507
|
+
event.last_accessed_at = row.last_accessed_at;
|
|
1508
|
+
}
|
|
1509
|
+
return event;
|
|
1510
|
+
}
|
|
1511
|
+
};
|
|
1512
|
+
|
|
1513
|
+
// src/core/sync-worker.ts
|
|
1514
|
+
var DEFAULT_CONFIG = {
|
|
1515
|
+
intervalMs: 3e4,
|
|
1516
|
+
batchSize: 500,
|
|
1517
|
+
maxRetries: 3,
|
|
1518
|
+
retryDelayMs: 5e3
|
|
1519
|
+
};
|
|
1520
|
+
var SyncWorker = class {
|
|
1521
|
+
constructor(sqliteStore, duckdbStore, config) {
|
|
1522
|
+
this.sqliteStore = sqliteStore;
|
|
1523
|
+
this.duckdbStore = duckdbStore;
|
|
1524
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
1525
|
+
}
|
|
1526
|
+
config;
|
|
1527
|
+
intervalHandle = null;
|
|
1528
|
+
running = false;
|
|
1529
|
+
stats = {
|
|
1530
|
+
lastSyncAt: null,
|
|
1531
|
+
eventsSynced: 0,
|
|
1532
|
+
sessionsSynced: 0,
|
|
1533
|
+
errors: 0,
|
|
1534
|
+
status: "idle"
|
|
1535
|
+
};
|
|
1536
|
+
/**
|
|
1537
|
+
* Start the sync worker
|
|
1538
|
+
*/
|
|
1539
|
+
start() {
|
|
1540
|
+
if (this.running)
|
|
1541
|
+
return;
|
|
1542
|
+
this.running = true;
|
|
1543
|
+
this.stats.status = "idle";
|
|
1544
|
+
this.syncNow().catch((err) => {
|
|
1545
|
+
console.error("[SyncWorker] Initial sync failed:", err);
|
|
1546
|
+
});
|
|
1547
|
+
this.intervalHandle = setInterval(() => {
|
|
1548
|
+
this.syncNow().catch((err) => {
|
|
1549
|
+
console.error("[SyncWorker] Periodic sync failed:", err);
|
|
1550
|
+
});
|
|
1551
|
+
}, this.config.intervalMs);
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Stop the sync worker
|
|
1555
|
+
*/
|
|
1556
|
+
stop() {
|
|
1557
|
+
this.running = false;
|
|
1558
|
+
this.stats.status = "stopped";
|
|
1559
|
+
if (this.intervalHandle) {
|
|
1560
|
+
clearInterval(this.intervalHandle);
|
|
1561
|
+
this.intervalHandle = null;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Trigger immediate sync
|
|
1566
|
+
*/
|
|
1567
|
+
async syncNow() {
|
|
1568
|
+
if (this.stats.status === "syncing") {
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
this.stats.status = "syncing";
|
|
1572
|
+
try {
|
|
1573
|
+
await this.syncEvents();
|
|
1574
|
+
await this.syncSessions();
|
|
1575
|
+
this.stats.lastSyncAt = /* @__PURE__ */ new Date();
|
|
1576
|
+
this.stats.status = "idle";
|
|
1577
|
+
} catch (error) {
|
|
1578
|
+
this.stats.errors++;
|
|
1579
|
+
this.stats.status = "error";
|
|
1580
|
+
throw error;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
/**
|
|
1584
|
+
* Sync events from SQLite to DuckDB
|
|
1585
|
+
*/
|
|
1586
|
+
async syncEvents() {
|
|
1587
|
+
const targetName = "duckdb_analytics";
|
|
1588
|
+
const position = await this.sqliteStore.getSyncPosition(targetName);
|
|
1589
|
+
const lastTimestamp = position.lastTimestamp || "1970-01-01T00:00:00.000Z";
|
|
1590
|
+
let hasMore = true;
|
|
1591
|
+
let totalSynced = 0;
|
|
1592
|
+
while (hasMore) {
|
|
1593
|
+
const events = await this.sqliteStore.getEventsSince(lastTimestamp, this.config.batchSize);
|
|
1594
|
+
if (events.length === 0) {
|
|
1595
|
+
hasMore = false;
|
|
1596
|
+
break;
|
|
1597
|
+
}
|
|
1598
|
+
await this.retryWithBackoff(async () => {
|
|
1599
|
+
for (const event of events) {
|
|
1600
|
+
await this.insertEventToDuckDB(event);
|
|
1601
|
+
}
|
|
1602
|
+
});
|
|
1603
|
+
totalSynced += events.length;
|
|
1604
|
+
const lastEvent = events[events.length - 1];
|
|
1605
|
+
await this.sqliteStore.updateSyncPosition(
|
|
1606
|
+
targetName,
|
|
1607
|
+
lastEvent.id,
|
|
1608
|
+
lastEvent.timestamp.toISOString()
|
|
1609
|
+
);
|
|
1610
|
+
hasMore = events.length === this.config.batchSize;
|
|
1611
|
+
}
|
|
1612
|
+
this.stats.eventsSynced += totalSynced;
|
|
1613
|
+
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Sync sessions from SQLite to DuckDB
|
|
1616
|
+
*/
|
|
1617
|
+
async syncSessions() {
|
|
1618
|
+
const sessions = await this.sqliteStore.getAllSessions();
|
|
1619
|
+
for (const session of sessions) {
|
|
1620
|
+
await this.retryWithBackoff(async () => {
|
|
1621
|
+
await this.duckdbStore.upsertSession(session);
|
|
1622
|
+
});
|
|
1623
|
+
}
|
|
1624
|
+
this.stats.sessionsSynced = sessions.length;
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Insert a single event into DuckDB
|
|
1628
|
+
*/
|
|
1629
|
+
async insertEventToDuckDB(event) {
|
|
1630
|
+
await this.duckdbStore.append({
|
|
1631
|
+
eventType: event.eventType,
|
|
1632
|
+
sessionId: event.sessionId,
|
|
1633
|
+
timestamp: event.timestamp,
|
|
1634
|
+
content: event.content,
|
|
1635
|
+
metadata: event.metadata
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Retry operation with exponential backoff
|
|
1640
|
+
*/
|
|
1641
|
+
async retryWithBackoff(fn) {
|
|
1642
|
+
let lastError = null;
|
|
1643
|
+
for (let attempt = 0; attempt < this.config.maxRetries; attempt++) {
|
|
1644
|
+
try {
|
|
1645
|
+
return await fn();
|
|
1646
|
+
} catch (error) {
|
|
1647
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1648
|
+
if (attempt < this.config.maxRetries - 1) {
|
|
1649
|
+
const delay = this.config.retryDelayMs * Math.pow(2, attempt);
|
|
1650
|
+
await this.sleep(delay);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
throw lastError;
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Sleep utility
|
|
1658
|
+
*/
|
|
1659
|
+
sleep(ms) {
|
|
1660
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Get sync statistics
|
|
1664
|
+
*/
|
|
1665
|
+
getStats() {
|
|
1666
|
+
return { ...this.stats };
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Check if worker is running
|
|
1670
|
+
*/
|
|
1671
|
+
isRunning() {
|
|
1672
|
+
return this.running;
|
|
685
1673
|
}
|
|
686
1674
|
};
|
|
687
1675
|
|
|
@@ -913,7 +1901,7 @@ function getDefaultEmbedder() {
|
|
|
913
1901
|
}
|
|
914
1902
|
|
|
915
1903
|
// src/core/vector-outbox.ts
|
|
916
|
-
var
|
|
1904
|
+
var DEFAULT_CONFIG2 = {
|
|
917
1905
|
embeddingVersion: "v1",
|
|
918
1906
|
maxRetries: 3,
|
|
919
1907
|
stuckThresholdMs: 5 * 60 * 1e3,
|
|
@@ -922,7 +1910,7 @@ var DEFAULT_CONFIG = {
|
|
|
922
1910
|
};
|
|
923
1911
|
|
|
924
1912
|
// src/core/vector-worker.ts
|
|
925
|
-
var
|
|
1913
|
+
var DEFAULT_CONFIG3 = {
|
|
926
1914
|
batchSize: 32,
|
|
927
1915
|
pollIntervalMs: 1e3,
|
|
928
1916
|
maxRetries: 3
|
|
@@ -933,12 +1921,13 @@ var VectorWorker = class {
|
|
|
933
1921
|
embedder;
|
|
934
1922
|
config;
|
|
935
1923
|
running = false;
|
|
1924
|
+
stopping = false;
|
|
936
1925
|
pollTimeout = null;
|
|
937
1926
|
constructor(eventStore, vectorStore, embedder, config = {}) {
|
|
938
1927
|
this.eventStore = eventStore;
|
|
939
1928
|
this.vectorStore = vectorStore;
|
|
940
1929
|
this.embedder = embedder;
|
|
941
|
-
this.config = { ...
|
|
1930
|
+
this.config = { ...DEFAULT_CONFIG3, ...config };
|
|
942
1931
|
}
|
|
943
1932
|
/**
|
|
944
1933
|
* Start the worker polling loop
|
|
@@ -947,6 +1936,7 @@ var VectorWorker = class {
|
|
|
947
1936
|
if (this.running)
|
|
948
1937
|
return;
|
|
949
1938
|
this.running = true;
|
|
1939
|
+
this.stopping = false;
|
|
950
1940
|
this.poll();
|
|
951
1941
|
}
|
|
952
1942
|
/**
|
|
@@ -954,6 +1944,7 @@ var VectorWorker = class {
|
|
|
954
1944
|
*/
|
|
955
1945
|
stop() {
|
|
956
1946
|
this.running = false;
|
|
1947
|
+
this.stopping = true;
|
|
957
1948
|
if (this.pollTimeout) {
|
|
958
1949
|
clearTimeout(this.pollTimeout);
|
|
959
1950
|
this.pollTimeout = null;
|
|
@@ -1003,9 +1994,15 @@ var VectorWorker = class {
|
|
|
1003
1994
|
}
|
|
1004
1995
|
return successful.length;
|
|
1005
1996
|
} catch (error) {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1997
|
+
if (!this.stopping) {
|
|
1998
|
+
try {
|
|
1999
|
+
const allIds = items.map((i) => i.id);
|
|
2000
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2001
|
+
await this.eventStore.failOutboxItems(allIds, errorMessage);
|
|
2002
|
+
} catch (failError) {
|
|
2003
|
+
console.warn("Could not mark outbox items as failed (database may be closed)");
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
1009
2006
|
throw error;
|
|
1010
2007
|
}
|
|
1011
2008
|
}
|
|
@@ -1013,14 +2010,18 @@ var VectorWorker = class {
|
|
|
1013
2010
|
* Poll for new items
|
|
1014
2011
|
*/
|
|
1015
2012
|
async poll() {
|
|
1016
|
-
if (!this.running)
|
|
2013
|
+
if (!this.running || this.stopping)
|
|
1017
2014
|
return;
|
|
1018
2015
|
try {
|
|
1019
2016
|
await this.processBatch();
|
|
1020
2017
|
} catch (error) {
|
|
1021
|
-
|
|
2018
|
+
if (!this.stopping) {
|
|
2019
|
+
console.error("Vector worker error:", error);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
if (this.running && !this.stopping) {
|
|
2023
|
+
this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
|
|
1022
2024
|
}
|
|
1023
|
-
this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
|
|
1024
2025
|
}
|
|
1025
2026
|
/**
|
|
1026
2027
|
* Process all pending items (blocking)
|
|
@@ -1047,7 +2048,7 @@ function createVectorWorker(eventStore, vectorStore, embedder, config) {
|
|
|
1047
2048
|
}
|
|
1048
2049
|
|
|
1049
2050
|
// src/core/matcher.ts
|
|
1050
|
-
var
|
|
2051
|
+
var DEFAULT_CONFIG4 = {
|
|
1051
2052
|
weights: {
|
|
1052
2053
|
semanticSimilarity: 0.4,
|
|
1053
2054
|
ftsScore: 0.25,
|
|
@@ -1062,9 +2063,9 @@ var Matcher = class {
|
|
|
1062
2063
|
config;
|
|
1063
2064
|
constructor(config = {}) {
|
|
1064
2065
|
this.config = {
|
|
1065
|
-
...
|
|
2066
|
+
...DEFAULT_CONFIG4,
|
|
1066
2067
|
...config,
|
|
1067
|
-
weights: { ...
|
|
2068
|
+
weights: { ...DEFAULT_CONFIG4.weights, ...config.weights }
|
|
1068
2069
|
};
|
|
1069
2070
|
}
|
|
1070
2071
|
/**
|
|
@@ -1191,6 +2192,7 @@ var Retriever = class {
|
|
|
1191
2192
|
matcher;
|
|
1192
2193
|
sharedStore;
|
|
1193
2194
|
sharedVectorStore;
|
|
2195
|
+
graduation;
|
|
1194
2196
|
constructor(eventStore, vectorStore, embedder, matcher, sharedOptions) {
|
|
1195
2197
|
this.eventStore = eventStore;
|
|
1196
2198
|
this.vectorStore = vectorStore;
|
|
@@ -1199,6 +2201,12 @@ var Retriever = class {
|
|
|
1199
2201
|
this.sharedStore = sharedOptions?.sharedStore;
|
|
1200
2202
|
this.sharedVectorStore = sharedOptions?.sharedVectorStore;
|
|
1201
2203
|
}
|
|
2204
|
+
/**
|
|
2205
|
+
* Set graduation pipeline for access tracking
|
|
2206
|
+
*/
|
|
2207
|
+
setGraduationPipeline(graduation) {
|
|
2208
|
+
this.graduation = graduation;
|
|
2209
|
+
}
|
|
1202
2210
|
/**
|
|
1203
2211
|
* Set shared stores after construction
|
|
1204
2212
|
*/
|
|
@@ -1321,6 +2329,13 @@ var Retriever = class {
|
|
|
1321
2329
|
const event = await this.eventStore.getEvent(result.eventId);
|
|
1322
2330
|
if (!event)
|
|
1323
2331
|
continue;
|
|
2332
|
+
if (this.graduation) {
|
|
2333
|
+
this.graduation.recordAccess(
|
|
2334
|
+
event.id,
|
|
2335
|
+
options.sessionId || "unknown",
|
|
2336
|
+
result.score
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
1324
2339
|
let sessionContext;
|
|
1325
2340
|
if (options.includeSessionContext) {
|
|
1326
2341
|
sessionContext = await this.getSessionContext(event.sessionId, event.id);
|
|
@@ -1442,15 +2457,26 @@ var GraduationPipeline = class {
|
|
|
1442
2457
|
L3toL4: { ...DEFAULT_CRITERIA.L3toL4, ...criteria.L3toL4 }
|
|
1443
2458
|
};
|
|
1444
2459
|
}
|
|
2460
|
+
// Track which sessions have accessed each event
|
|
2461
|
+
sessionAccesses = /* @__PURE__ */ new Map();
|
|
1445
2462
|
/**
|
|
1446
2463
|
* Record an access to an event (used for graduation scoring)
|
|
1447
2464
|
*/
|
|
1448
2465
|
recordAccess(eventId, fromSessionId, confidence = 1) {
|
|
1449
2466
|
const existing = this.metrics.get(eventId);
|
|
2467
|
+
if (!this.sessionAccesses.has(eventId)) {
|
|
2468
|
+
this.sessionAccesses.set(eventId, /* @__PURE__ */ new Set());
|
|
2469
|
+
}
|
|
2470
|
+
const sessions = this.sessionAccesses.get(eventId);
|
|
2471
|
+
const isNewSession = !sessions.has(fromSessionId);
|
|
2472
|
+
sessions.add(fromSessionId);
|
|
1450
2473
|
if (existing) {
|
|
1451
2474
|
existing.accessCount++;
|
|
1452
2475
|
existing.lastAccessed = /* @__PURE__ */ new Date();
|
|
1453
2476
|
existing.confidence = Math.max(existing.confidence, confidence);
|
|
2477
|
+
if (isNewSession && sessions.size > 1) {
|
|
2478
|
+
existing.crossSessionRefs = sessions.size - 1;
|
|
2479
|
+
}
|
|
1454
2480
|
} else {
|
|
1455
2481
|
this.metrics.set(eventId, {
|
|
1456
2482
|
eventId,
|
|
@@ -1747,7 +2773,7 @@ function createSharedEventStore(dbPath) {
|
|
|
1747
2773
|
}
|
|
1748
2774
|
|
|
1749
2775
|
// src/core/shared-store.ts
|
|
1750
|
-
import { randomUUID as
|
|
2776
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1751
2777
|
var SharedStore = class {
|
|
1752
2778
|
constructor(sharedEventStore) {
|
|
1753
2779
|
this.sharedEventStore = sharedEventStore;
|
|
@@ -1759,7 +2785,7 @@ var SharedStore = class {
|
|
|
1759
2785
|
* Promote a verified troubleshooting entry to shared storage
|
|
1760
2786
|
*/
|
|
1761
2787
|
async promoteEntry(input) {
|
|
1762
|
-
const entryId =
|
|
2788
|
+
const entryId = randomUUID3();
|
|
1763
2789
|
await dbRun(
|
|
1764
2790
|
this.db,
|
|
1765
2791
|
`INSERT INTO shared_troubleshooting (
|
|
@@ -2124,7 +3150,7 @@ function createSharedVectorStore(dbPath) {
|
|
|
2124
3150
|
}
|
|
2125
3151
|
|
|
2126
3152
|
// src/core/shared-promoter.ts
|
|
2127
|
-
import { randomUUID as
|
|
3153
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
2128
3154
|
var SharedPromoter = class {
|
|
2129
3155
|
constructor(sharedStore, sharedVectorStore, embedder, config) {
|
|
2130
3156
|
this.sharedStore = sharedStore;
|
|
@@ -2192,7 +3218,7 @@ var SharedPromoter = class {
|
|
|
2192
3218
|
const embeddingContent = this.createEmbeddingContent(input);
|
|
2193
3219
|
const embedding = await this.embedder.embed(embeddingContent);
|
|
2194
3220
|
await this.sharedVectorStore.upsert({
|
|
2195
|
-
id:
|
|
3221
|
+
id: randomUUID4(),
|
|
2196
3222
|
entryId,
|
|
2197
3223
|
entryType: "troubleshooting",
|
|
2198
3224
|
content: embeddingContent,
|
|
@@ -2332,7 +3358,7 @@ function createToolObservationEmbedding(toolName, metadata, success) {
|
|
|
2332
3358
|
}
|
|
2333
3359
|
|
|
2334
3360
|
// src/core/working-set-store.ts
|
|
2335
|
-
import { randomUUID as
|
|
3361
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
2336
3362
|
var WorkingSetStore = class {
|
|
2337
3363
|
constructor(eventStore, config) {
|
|
2338
3364
|
this.eventStore = eventStore;
|
|
@@ -2353,7 +3379,7 @@ var WorkingSetStore = class {
|
|
|
2353
3379
|
`INSERT OR REPLACE INTO working_set (id, event_id, added_at, relevance_score, topics, expires_at)
|
|
2354
3380
|
VALUES (?, ?, CURRENT_TIMESTAMP, ?, ?, ?)`,
|
|
2355
3381
|
[
|
|
2356
|
-
|
|
3382
|
+
randomUUID5(),
|
|
2357
3383
|
eventId,
|
|
2358
3384
|
relevanceScore,
|
|
2359
3385
|
JSON.stringify(topics || []),
|
|
@@ -2537,7 +3563,7 @@ function createWorkingSetStore(eventStore, config) {
|
|
|
2537
3563
|
}
|
|
2538
3564
|
|
|
2539
3565
|
// src/core/consolidated-store.ts
|
|
2540
|
-
import { randomUUID as
|
|
3566
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
2541
3567
|
var ConsolidatedStore = class {
|
|
2542
3568
|
constructor(eventStore) {
|
|
2543
3569
|
this.eventStore = eventStore;
|
|
@@ -2549,7 +3575,7 @@ var ConsolidatedStore = class {
|
|
|
2549
3575
|
* Create a new consolidated memory
|
|
2550
3576
|
*/
|
|
2551
3577
|
async create(input) {
|
|
2552
|
-
const memoryId =
|
|
3578
|
+
const memoryId = randomUUID6();
|
|
2553
3579
|
await dbRun(
|
|
2554
3580
|
this.db,
|
|
2555
3581
|
`INSERT INTO consolidated_memories
|
|
@@ -3076,7 +4102,7 @@ function createConsolidationWorker(workingSetStore, consolidatedStore, config) {
|
|
|
3076
4102
|
}
|
|
3077
4103
|
|
|
3078
4104
|
// src/core/continuity-manager.ts
|
|
3079
|
-
import { randomUUID as
|
|
4105
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
3080
4106
|
var ContinuityManager = class {
|
|
3081
4107
|
constructor(eventStore, config) {
|
|
3082
4108
|
this.eventStore = eventStore;
|
|
@@ -3230,7 +4256,7 @@ var ContinuityManager = class {
|
|
|
3230
4256
|
`INSERT INTO continuity_log
|
|
3231
4257
|
(log_id, from_context_id, to_context_id, continuity_score, transition_type, created_at)
|
|
3232
4258
|
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
|
|
3233
|
-
[
|
|
4259
|
+
[randomUUID7(), previous.id, current.id, score, type]
|
|
3234
4260
|
);
|
|
3235
4261
|
}
|
|
3236
4262
|
/**
|
|
@@ -3338,6 +4364,127 @@ function createContinuityManager(eventStore, config) {
|
|
|
3338
4364
|
return new ContinuityManager(eventStore, config);
|
|
3339
4365
|
}
|
|
3340
4366
|
|
|
4367
|
+
// src/core/graduation-worker.ts
|
|
4368
|
+
var DEFAULT_CONFIG5 = {
|
|
4369
|
+
evaluationIntervalMs: 3e5,
|
|
4370
|
+
// 5 minutes
|
|
4371
|
+
batchSize: 50,
|
|
4372
|
+
cooldownMs: 36e5
|
|
4373
|
+
// 1 hour cooldown between evaluations
|
|
4374
|
+
};
|
|
4375
|
+
var GraduationWorker = class {
|
|
4376
|
+
constructor(eventStore, graduation, config = DEFAULT_CONFIG5) {
|
|
4377
|
+
this.eventStore = eventStore;
|
|
4378
|
+
this.graduation = graduation;
|
|
4379
|
+
this.config = config;
|
|
4380
|
+
}
|
|
4381
|
+
running = false;
|
|
4382
|
+
timeout = null;
|
|
4383
|
+
lastEvaluated = /* @__PURE__ */ new Map();
|
|
4384
|
+
/**
|
|
4385
|
+
* Start the graduation worker
|
|
4386
|
+
*/
|
|
4387
|
+
start() {
|
|
4388
|
+
if (this.running)
|
|
4389
|
+
return;
|
|
4390
|
+
this.running = true;
|
|
4391
|
+
this.scheduleNext();
|
|
4392
|
+
}
|
|
4393
|
+
/**
|
|
4394
|
+
* Stop the graduation worker
|
|
4395
|
+
*/
|
|
4396
|
+
stop() {
|
|
4397
|
+
this.running = false;
|
|
4398
|
+
if (this.timeout) {
|
|
4399
|
+
clearTimeout(this.timeout);
|
|
4400
|
+
this.timeout = null;
|
|
4401
|
+
}
|
|
4402
|
+
}
|
|
4403
|
+
/**
|
|
4404
|
+
* Check if currently running
|
|
4405
|
+
*/
|
|
4406
|
+
isRunning() {
|
|
4407
|
+
return this.running;
|
|
4408
|
+
}
|
|
4409
|
+
/**
|
|
4410
|
+
* Force a graduation evaluation run
|
|
4411
|
+
*/
|
|
4412
|
+
async forceRun() {
|
|
4413
|
+
return await this.runGraduation();
|
|
4414
|
+
}
|
|
4415
|
+
/**
|
|
4416
|
+
* Schedule the next graduation check
|
|
4417
|
+
*/
|
|
4418
|
+
scheduleNext() {
|
|
4419
|
+
if (!this.running)
|
|
4420
|
+
return;
|
|
4421
|
+
this.timeout = setTimeout(
|
|
4422
|
+
() => this.run(),
|
|
4423
|
+
this.config.evaluationIntervalMs
|
|
4424
|
+
);
|
|
4425
|
+
}
|
|
4426
|
+
/**
|
|
4427
|
+
* Run graduation evaluation
|
|
4428
|
+
*/
|
|
4429
|
+
async run() {
|
|
4430
|
+
if (!this.running)
|
|
4431
|
+
return;
|
|
4432
|
+
try {
|
|
4433
|
+
await this.runGraduation();
|
|
4434
|
+
} catch (error) {
|
|
4435
|
+
console.error("Graduation error:", error);
|
|
4436
|
+
}
|
|
4437
|
+
this.scheduleNext();
|
|
4438
|
+
}
|
|
4439
|
+
/**
|
|
4440
|
+
* Perform graduation evaluation across all levels
|
|
4441
|
+
*/
|
|
4442
|
+
async runGraduation() {
|
|
4443
|
+
const result = {
|
|
4444
|
+
evaluated: 0,
|
|
4445
|
+
graduated: 0,
|
|
4446
|
+
byLevel: {}
|
|
4447
|
+
};
|
|
4448
|
+
const levels = ["L0", "L1", "L2", "L3"];
|
|
4449
|
+
const now = Date.now();
|
|
4450
|
+
for (const level of levels) {
|
|
4451
|
+
const events = await this.eventStore.getEventsByLevel(level, {
|
|
4452
|
+
limit: this.config.batchSize
|
|
4453
|
+
});
|
|
4454
|
+
let levelGraduated = 0;
|
|
4455
|
+
for (const event of events) {
|
|
4456
|
+
const lastEval = this.lastEvaluated.get(event.id);
|
|
4457
|
+
if (lastEval && now - lastEval < this.config.cooldownMs) {
|
|
4458
|
+
continue;
|
|
4459
|
+
}
|
|
4460
|
+
result.evaluated++;
|
|
4461
|
+
this.lastEvaluated.set(event.id, now);
|
|
4462
|
+
const gradResult = await this.graduation.evaluateGraduation(event.id, level);
|
|
4463
|
+
if (gradResult.success) {
|
|
4464
|
+
result.graduated++;
|
|
4465
|
+
levelGraduated++;
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
if (levelGraduated > 0) {
|
|
4469
|
+
result.byLevel[level] = levelGraduated;
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4472
|
+
if (this.lastEvaluated.size > 1e3) {
|
|
4473
|
+
const entries = Array.from(this.lastEvaluated.entries());
|
|
4474
|
+
entries.sort((a, b) => b[1] - a[1]);
|
|
4475
|
+
this.lastEvaluated = new Map(entries.slice(0, 1e3));
|
|
4476
|
+
}
|
|
4477
|
+
return result;
|
|
4478
|
+
}
|
|
4479
|
+
};
|
|
4480
|
+
function createGraduationWorker(eventStore, graduation, config) {
|
|
4481
|
+
return new GraduationWorker(
|
|
4482
|
+
eventStore,
|
|
4483
|
+
graduation,
|
|
4484
|
+
{ ...DEFAULT_CONFIG5, ...config }
|
|
4485
|
+
);
|
|
4486
|
+
}
|
|
4487
|
+
|
|
3341
4488
|
// src/services/memory-service.ts
|
|
3342
4489
|
function normalizePath(projectPath) {
|
|
3343
4490
|
const expanded = projectPath.startsWith("~") ? path.join(os.homedir(), projectPath.slice(1)) : projectPath;
|
|
@@ -3373,13 +4520,18 @@ function getSessionProject(sessionId) {
|
|
|
3373
4520
|
return registry.sessions[sessionId] || null;
|
|
3374
4521
|
}
|
|
3375
4522
|
var MemoryService = class {
|
|
3376
|
-
|
|
4523
|
+
// Primary store: SQLite (WAL mode) - for hooks, always available
|
|
4524
|
+
sqliteStore;
|
|
4525
|
+
// Analytics store: DuckDB - for server reads (optional, synced from SQLite)
|
|
4526
|
+
analyticsStore;
|
|
4527
|
+
syncWorker = null;
|
|
3377
4528
|
vectorStore;
|
|
3378
4529
|
embedder;
|
|
3379
4530
|
matcher;
|
|
3380
4531
|
retriever;
|
|
3381
4532
|
graduation;
|
|
3382
4533
|
vectorWorker = null;
|
|
4534
|
+
graduationWorker = null;
|
|
3383
4535
|
initialized = false;
|
|
3384
4536
|
// Endless Mode components
|
|
3385
4537
|
workingSetStore = null;
|
|
@@ -3394,24 +4546,48 @@ var MemoryService = class {
|
|
|
3394
4546
|
sharedPromoter = null;
|
|
3395
4547
|
sharedStoreConfig = null;
|
|
3396
4548
|
projectHash = null;
|
|
4549
|
+
readOnly;
|
|
3397
4550
|
constructor(config) {
|
|
3398
4551
|
const storagePath = this.expandPath(config.storagePath);
|
|
3399
|
-
|
|
4552
|
+
this.readOnly = config.readOnly ?? false;
|
|
4553
|
+
if (!this.readOnly && !fs.existsSync(storagePath)) {
|
|
3400
4554
|
fs.mkdirSync(storagePath, { recursive: true });
|
|
3401
4555
|
}
|
|
3402
4556
|
this.projectHash = config.projectHash || null;
|
|
3403
4557
|
this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
|
|
3404
|
-
this.
|
|
4558
|
+
this.sqliteStore = new SQLiteEventStore(
|
|
4559
|
+
path.join(storagePath, "events.sqlite"),
|
|
4560
|
+
{ readonly: this.readOnly }
|
|
4561
|
+
);
|
|
4562
|
+
const analyticsEnabled = config.analyticsEnabled ?? this.readOnly;
|
|
4563
|
+
if (!analyticsEnabled) {
|
|
4564
|
+
this.analyticsStore = null;
|
|
4565
|
+
} else if (this.readOnly) {
|
|
4566
|
+
try {
|
|
4567
|
+
this.analyticsStore = new EventStore(
|
|
4568
|
+
path.join(storagePath, "analytics.duckdb"),
|
|
4569
|
+
{ readOnly: true }
|
|
4570
|
+
);
|
|
4571
|
+
} catch {
|
|
4572
|
+
this.analyticsStore = null;
|
|
4573
|
+
}
|
|
4574
|
+
} else {
|
|
4575
|
+
this.analyticsStore = new EventStore(
|
|
4576
|
+
path.join(storagePath, "analytics.duckdb"),
|
|
4577
|
+
{ readOnly: false }
|
|
4578
|
+
);
|
|
4579
|
+
}
|
|
3405
4580
|
this.vectorStore = new VectorStore(path.join(storagePath, "vectors"));
|
|
3406
4581
|
this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
|
|
3407
4582
|
this.matcher = getDefaultMatcher();
|
|
3408
4583
|
this.retriever = createRetriever(
|
|
3409
|
-
this.
|
|
4584
|
+
this.sqliteStore,
|
|
4585
|
+
// Interface compatible
|
|
3410
4586
|
this.vectorStore,
|
|
3411
4587
|
this.embedder,
|
|
3412
4588
|
this.matcher
|
|
3413
4589
|
);
|
|
3414
|
-
this.graduation = createGraduationPipeline(this.
|
|
4590
|
+
this.graduation = createGraduationPipeline(this.sqliteStore);
|
|
3415
4591
|
}
|
|
3416
4592
|
/**
|
|
3417
4593
|
* Initialize all components
|
|
@@ -3419,22 +4595,45 @@ var MemoryService = class {
|
|
|
3419
4595
|
async initialize() {
|
|
3420
4596
|
if (this.initialized)
|
|
3421
4597
|
return;
|
|
3422
|
-
await this.
|
|
4598
|
+
await this.sqliteStore.initialize();
|
|
4599
|
+
if (this.analyticsStore) {
|
|
4600
|
+
try {
|
|
4601
|
+
await this.analyticsStore.initialize();
|
|
4602
|
+
} catch (error) {
|
|
4603
|
+
console.warn("[MemoryService] Analytics store (DuckDB) initialization failed, using SQLite for reads:", error);
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
3423
4606
|
await this.vectorStore.initialize();
|
|
3424
4607
|
await this.embedder.initialize();
|
|
3425
|
-
this.
|
|
3426
|
-
this.
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
this.
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
4608
|
+
if (!this.readOnly) {
|
|
4609
|
+
this.vectorWorker = createVectorWorker(
|
|
4610
|
+
this.sqliteStore,
|
|
4611
|
+
this.vectorStore,
|
|
4612
|
+
this.embedder
|
|
4613
|
+
);
|
|
4614
|
+
this.vectorWorker.start();
|
|
4615
|
+
this.retriever.setGraduationPipeline(this.graduation);
|
|
4616
|
+
this.graduationWorker = createGraduationWorker(
|
|
4617
|
+
this.sqliteStore,
|
|
4618
|
+
this.graduation
|
|
4619
|
+
);
|
|
4620
|
+
this.graduationWorker.start();
|
|
4621
|
+
if (this.analyticsStore) {
|
|
4622
|
+
this.syncWorker = new SyncWorker(
|
|
4623
|
+
this.sqliteStore,
|
|
4624
|
+
this.analyticsStore,
|
|
4625
|
+
{ intervalMs: 3e4, batchSize: 500 }
|
|
4626
|
+
);
|
|
4627
|
+
this.syncWorker.start();
|
|
4628
|
+
}
|
|
4629
|
+
const savedMode = await this.sqliteStore.getEndlessConfig("mode");
|
|
4630
|
+
if (savedMode === "endless") {
|
|
4631
|
+
this.endlessMode = "endless";
|
|
4632
|
+
await this.initializeEndlessMode();
|
|
4633
|
+
}
|
|
4634
|
+
if (this.sharedStoreConfig?.enabled !== false) {
|
|
4635
|
+
await this.initializeSharedStore();
|
|
4636
|
+
}
|
|
3438
4637
|
}
|
|
3439
4638
|
this.initialized = true;
|
|
3440
4639
|
}
|
|
@@ -3468,7 +4667,7 @@ var MemoryService = class {
|
|
|
3468
4667
|
*/
|
|
3469
4668
|
async startSession(sessionId, projectPath) {
|
|
3470
4669
|
await this.initialize();
|
|
3471
|
-
await this.
|
|
4670
|
+
await this.sqliteStore.upsertSession({
|
|
3472
4671
|
id: sessionId,
|
|
3473
4672
|
startedAt: /* @__PURE__ */ new Date(),
|
|
3474
4673
|
projectPath
|
|
@@ -3479,7 +4678,7 @@ var MemoryService = class {
|
|
|
3479
4678
|
*/
|
|
3480
4679
|
async endSession(sessionId, summary) {
|
|
3481
4680
|
await this.initialize();
|
|
3482
|
-
await this.
|
|
4681
|
+
await this.sqliteStore.upsertSession({
|
|
3483
4682
|
id: sessionId,
|
|
3484
4683
|
endedAt: /* @__PURE__ */ new Date(),
|
|
3485
4684
|
summary
|
|
@@ -3490,7 +4689,7 @@ var MemoryService = class {
|
|
|
3490
4689
|
*/
|
|
3491
4690
|
async storeUserPrompt(sessionId, content, metadata) {
|
|
3492
4691
|
await this.initialize();
|
|
3493
|
-
const result = await this.
|
|
4692
|
+
const result = await this.sqliteStore.append({
|
|
3494
4693
|
eventType: "user_prompt",
|
|
3495
4694
|
sessionId,
|
|
3496
4695
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -3498,7 +4697,7 @@ var MemoryService = class {
|
|
|
3498
4697
|
metadata
|
|
3499
4698
|
});
|
|
3500
4699
|
if (result.success && !result.isDuplicate) {
|
|
3501
|
-
await this.
|
|
4700
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
|
|
3502
4701
|
}
|
|
3503
4702
|
return result;
|
|
3504
4703
|
}
|
|
@@ -3507,7 +4706,7 @@ var MemoryService = class {
|
|
|
3507
4706
|
*/
|
|
3508
4707
|
async storeAgentResponse(sessionId, content, metadata) {
|
|
3509
4708
|
await this.initialize();
|
|
3510
|
-
const result = await this.
|
|
4709
|
+
const result = await this.sqliteStore.append({
|
|
3511
4710
|
eventType: "agent_response",
|
|
3512
4711
|
sessionId,
|
|
3513
4712
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -3515,7 +4714,7 @@ var MemoryService = class {
|
|
|
3515
4714
|
metadata
|
|
3516
4715
|
});
|
|
3517
4716
|
if (result.success && !result.isDuplicate) {
|
|
3518
|
-
await this.
|
|
4717
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
|
|
3519
4718
|
}
|
|
3520
4719
|
return result;
|
|
3521
4720
|
}
|
|
@@ -3524,14 +4723,14 @@ var MemoryService = class {
|
|
|
3524
4723
|
*/
|
|
3525
4724
|
async storeSessionSummary(sessionId, summary) {
|
|
3526
4725
|
await this.initialize();
|
|
3527
|
-
const result = await this.
|
|
4726
|
+
const result = await this.sqliteStore.append({
|
|
3528
4727
|
eventType: "session_summary",
|
|
3529
4728
|
sessionId,
|
|
3530
4729
|
timestamp: /* @__PURE__ */ new Date(),
|
|
3531
4730
|
content: summary
|
|
3532
4731
|
});
|
|
3533
4732
|
if (result.success && !result.isDuplicate) {
|
|
3534
|
-
await this.
|
|
4733
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, summary);
|
|
3535
4734
|
}
|
|
3536
4735
|
return result;
|
|
3537
4736
|
}
|
|
@@ -3541,7 +4740,7 @@ var MemoryService = class {
|
|
|
3541
4740
|
async storeToolObservation(sessionId, payload) {
|
|
3542
4741
|
await this.initialize();
|
|
3543
4742
|
const content = JSON.stringify(payload);
|
|
3544
|
-
const result = await this.
|
|
4743
|
+
const result = await this.sqliteStore.append({
|
|
3545
4744
|
eventType: "tool_observation",
|
|
3546
4745
|
sessionId,
|
|
3547
4746
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -3557,7 +4756,7 @@ var MemoryService = class {
|
|
|
3557
4756
|
payload.metadata || {},
|
|
3558
4757
|
payload.success
|
|
3559
4758
|
);
|
|
3560
|
-
await this.
|
|
4759
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, embeddingContent);
|
|
3561
4760
|
}
|
|
3562
4761
|
return result;
|
|
3563
4762
|
}
|
|
@@ -3583,21 +4782,21 @@ var MemoryService = class {
|
|
|
3583
4782
|
*/
|
|
3584
4783
|
async getSessionHistory(sessionId) {
|
|
3585
4784
|
await this.initialize();
|
|
3586
|
-
return this.
|
|
4785
|
+
return this.sqliteStore.getSessionEvents(sessionId);
|
|
3587
4786
|
}
|
|
3588
4787
|
/**
|
|
3589
4788
|
* Get recent events
|
|
3590
4789
|
*/
|
|
3591
4790
|
async getRecentEvents(limit = 100) {
|
|
3592
4791
|
await this.initialize();
|
|
3593
|
-
return this.
|
|
4792
|
+
return this.sqliteStore.getRecentEvents(limit);
|
|
3594
4793
|
}
|
|
3595
4794
|
/**
|
|
3596
4795
|
* Get memory statistics
|
|
3597
4796
|
*/
|
|
3598
4797
|
async getStats() {
|
|
3599
4798
|
await this.initialize();
|
|
3600
|
-
const recentEvents = await this.
|
|
4799
|
+
const recentEvents = await this.sqliteStore.getRecentEvents(1e4);
|
|
3601
4800
|
const vectorCount = await this.vectorStore.count();
|
|
3602
4801
|
const levelStats = await this.graduation.getStats();
|
|
3603
4802
|
return {
|
|
@@ -3615,6 +4814,20 @@ var MemoryService = class {
|
|
|
3615
4814
|
}
|
|
3616
4815
|
return 0;
|
|
3617
4816
|
}
|
|
4817
|
+
/**
|
|
4818
|
+
* Get events by memory level
|
|
4819
|
+
*/
|
|
4820
|
+
async getEventsByLevel(level, options) {
|
|
4821
|
+
await this.initialize();
|
|
4822
|
+
return this.sqliteStore.getEventsByLevel(level, options);
|
|
4823
|
+
}
|
|
4824
|
+
/**
|
|
4825
|
+
* Get memory level for a specific event
|
|
4826
|
+
*/
|
|
4827
|
+
async getEventLevel(eventId) {
|
|
4828
|
+
await this.initialize();
|
|
4829
|
+
return this.sqliteStore.getEventLevel(eventId);
|
|
4830
|
+
}
|
|
3618
4831
|
/**
|
|
3619
4832
|
* Format retrieval results as context for Claude
|
|
3620
4833
|
*/
|
|
@@ -3707,21 +4920,21 @@ var MemoryService = class {
|
|
|
3707
4920
|
*/
|
|
3708
4921
|
async initializeEndlessMode() {
|
|
3709
4922
|
const config = await this.getEndlessConfig();
|
|
3710
|
-
this.workingSetStore = createWorkingSetStore(this.
|
|
3711
|
-
this.consolidatedStore = createConsolidatedStore(this.
|
|
4923
|
+
this.workingSetStore = createWorkingSetStore(this.sqliteStore, config);
|
|
4924
|
+
this.consolidatedStore = createConsolidatedStore(this.sqliteStore);
|
|
3712
4925
|
this.consolidationWorker = createConsolidationWorker(
|
|
3713
4926
|
this.workingSetStore,
|
|
3714
4927
|
this.consolidatedStore,
|
|
3715
4928
|
config
|
|
3716
4929
|
);
|
|
3717
|
-
this.continuityManager = createContinuityManager(this.
|
|
4930
|
+
this.continuityManager = createContinuityManager(this.sqliteStore, config);
|
|
3718
4931
|
this.consolidationWorker.start();
|
|
3719
4932
|
}
|
|
3720
4933
|
/**
|
|
3721
4934
|
* Get Endless Mode configuration
|
|
3722
4935
|
*/
|
|
3723
4936
|
async getEndlessConfig() {
|
|
3724
|
-
const savedConfig = await this.
|
|
4937
|
+
const savedConfig = await this.sqliteStore.getEndlessConfig("config");
|
|
3725
4938
|
return savedConfig || this.getDefaultEndlessConfig();
|
|
3726
4939
|
}
|
|
3727
4940
|
/**
|
|
@@ -3730,7 +4943,7 @@ var MemoryService = class {
|
|
|
3730
4943
|
async setEndlessConfig(config) {
|
|
3731
4944
|
const current = await this.getEndlessConfig();
|
|
3732
4945
|
const merged = { ...current, ...config };
|
|
3733
|
-
await this.
|
|
4946
|
+
await this.sqliteStore.setEndlessConfig("config", merged);
|
|
3734
4947
|
}
|
|
3735
4948
|
/**
|
|
3736
4949
|
* Set memory mode (session or endless)
|
|
@@ -3740,7 +4953,7 @@ var MemoryService = class {
|
|
|
3740
4953
|
if (mode === this.endlessMode)
|
|
3741
4954
|
return;
|
|
3742
4955
|
this.endlessMode = mode;
|
|
3743
|
-
await this.
|
|
4956
|
+
await this.sqliteStore.setEndlessConfig("mode", mode);
|
|
3744
4957
|
if (mode === "endless") {
|
|
3745
4958
|
await this.initializeEndlessMode();
|
|
3746
4959
|
} else {
|
|
@@ -3797,6 +5010,59 @@ var MemoryService = class {
|
|
|
3797
5010
|
return [];
|
|
3798
5011
|
return this.consolidatedStore.getAll({ limit });
|
|
3799
5012
|
}
|
|
5013
|
+
/**
|
|
5014
|
+
* Increment access count for memories that were used in prompts
|
|
5015
|
+
*/
|
|
5016
|
+
async incrementMemoryAccess(eventIds) {
|
|
5017
|
+
if (eventIds.length === 0)
|
|
5018
|
+
return;
|
|
5019
|
+
if (this.sqliteStore) {
|
|
5020
|
+
await this.sqliteStore.incrementAccessCount(eventIds);
|
|
5021
|
+
} else if (this.eventStore) {
|
|
5022
|
+
await this.eventStore.incrementAccessCount(eventIds);
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
/**
|
|
5026
|
+
* Get most accessed memories from events
|
|
5027
|
+
*/
|
|
5028
|
+
async getMostAccessedMemories(limit = 10) {
|
|
5029
|
+
console.log("[getMostAccessedMemories] sqliteStore available:", !!this.sqliteStore);
|
|
5030
|
+
if (this.sqliteStore) {
|
|
5031
|
+
const events = await this.sqliteStore.getMostAccessed(limit);
|
|
5032
|
+
console.log("[getMostAccessedMemories] Got events from SQLite:", events.length);
|
|
5033
|
+
return events.map((event) => ({
|
|
5034
|
+
memoryId: event.id,
|
|
5035
|
+
summary: event.content.substring(0, 200) + (event.content.length > 200 ? "..." : ""),
|
|
5036
|
+
topics: [],
|
|
5037
|
+
// Could extract topics from content if needed
|
|
5038
|
+
accessCount: event.access_count || 0,
|
|
5039
|
+
lastAccessed: event.last_accessed_at || null,
|
|
5040
|
+
confidence: 1,
|
|
5041
|
+
createdAt: event.timestamp
|
|
5042
|
+
}));
|
|
5043
|
+
}
|
|
5044
|
+
if (this.consolidatedStore) {
|
|
5045
|
+
const consolidated = await this.consolidatedStore.getMostAccessed(limit);
|
|
5046
|
+
return consolidated.map((m) => ({
|
|
5047
|
+
memoryId: m.memoryId,
|
|
5048
|
+
summary: m.summary,
|
|
5049
|
+
topics: m.topics,
|
|
5050
|
+
accessCount: m.accessCount,
|
|
5051
|
+
lastAccessed: m.accessedAt,
|
|
5052
|
+
confidence: m.confidence,
|
|
5053
|
+
createdAt: m.createdAt
|
|
5054
|
+
}));
|
|
5055
|
+
}
|
|
5056
|
+
return [];
|
|
5057
|
+
}
|
|
5058
|
+
/**
|
|
5059
|
+
* Mark a consolidated memory as accessed
|
|
5060
|
+
*/
|
|
5061
|
+
async markMemoryAccessed(memoryId) {
|
|
5062
|
+
if (!this.consolidatedStore)
|
|
5063
|
+
return;
|
|
5064
|
+
await this.consolidatedStore.markAccessed(memoryId);
|
|
5065
|
+
}
|
|
3800
5066
|
/**
|
|
3801
5067
|
* Calculate continuity score for current context
|
|
3802
5068
|
*/
|
|
@@ -3884,20 +5150,44 @@ var MemoryService = class {
|
|
|
3884
5150
|
}
|
|
3885
5151
|
return parts.join("\n");
|
|
3886
5152
|
}
|
|
5153
|
+
/**
|
|
5154
|
+
* Force a graduation evaluation run
|
|
5155
|
+
*/
|
|
5156
|
+
async forceGraduation() {
|
|
5157
|
+
if (!this.graduationWorker) {
|
|
5158
|
+
return { evaluated: 0, graduated: 0, byLevel: {} };
|
|
5159
|
+
}
|
|
5160
|
+
return this.graduationWorker.forceRun();
|
|
5161
|
+
}
|
|
5162
|
+
/**
|
|
5163
|
+
* Record access to a memory event (for graduation scoring)
|
|
5164
|
+
*/
|
|
5165
|
+
recordMemoryAccess(eventId, sessionId, confidence = 1) {
|
|
5166
|
+
this.graduation.recordAccess(eventId, sessionId, confidence);
|
|
5167
|
+
}
|
|
3887
5168
|
/**
|
|
3888
5169
|
* Shutdown service
|
|
3889
5170
|
*/
|
|
3890
5171
|
async shutdown() {
|
|
5172
|
+
if (this.graduationWorker) {
|
|
5173
|
+
this.graduationWorker.stop();
|
|
5174
|
+
}
|
|
3891
5175
|
if (this.consolidationWorker) {
|
|
3892
5176
|
this.consolidationWorker.stop();
|
|
3893
5177
|
}
|
|
3894
5178
|
if (this.vectorWorker) {
|
|
3895
5179
|
this.vectorWorker.stop();
|
|
3896
5180
|
}
|
|
5181
|
+
if (this.syncWorker) {
|
|
5182
|
+
this.syncWorker.stop();
|
|
5183
|
+
}
|
|
3897
5184
|
if (this.sharedEventStore) {
|
|
3898
5185
|
await this.sharedEventStore.close();
|
|
3899
5186
|
}
|
|
3900
|
-
await this.
|
|
5187
|
+
await this.sqliteStore.close();
|
|
5188
|
+
if (this.analyticsStore) {
|
|
5189
|
+
await this.analyticsStore.close();
|
|
5190
|
+
}
|
|
3901
5191
|
}
|
|
3902
5192
|
/**
|
|
3903
5193
|
* Expand ~ to home directory
|
|
@@ -3914,7 +5204,11 @@ var GLOBAL_KEY = "__global__";
|
|
|
3914
5204
|
function getDefaultMemoryService() {
|
|
3915
5205
|
if (!serviceCache.has(GLOBAL_KEY)) {
|
|
3916
5206
|
serviceCache.set(GLOBAL_KEY, new MemoryService({
|
|
3917
|
-
storagePath: "~/.claude-code/memory"
|
|
5207
|
+
storagePath: "~/.claude-code/memory",
|
|
5208
|
+
analyticsEnabled: false,
|
|
5209
|
+
// Hooks don't need DuckDB
|
|
5210
|
+
sharedStoreConfig: { enabled: false }
|
|
5211
|
+
// Shared store uses DuckDB too
|
|
3918
5212
|
}));
|
|
3919
5213
|
}
|
|
3920
5214
|
return serviceCache.get(GLOBAL_KEY);
|
|
@@ -3926,7 +5220,10 @@ function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
|
|
|
3926
5220
|
serviceCache.set(hash, new MemoryService({
|
|
3927
5221
|
storagePath,
|
|
3928
5222
|
projectHash: hash,
|
|
3929
|
-
|
|
5223
|
+
// Override shared store config - hooks don't need DuckDB
|
|
5224
|
+
sharedStoreConfig: sharedStoreConfig ?? { enabled: false },
|
|
5225
|
+
analyticsEnabled: false
|
|
5226
|
+
// Hooks don't need DuckDB
|
|
3930
5227
|
}));
|
|
3931
5228
|
}
|
|
3932
5229
|
return serviceCache.get(hash);
|