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/server/api/index.js
CHANGED
|
@@ -72,7 +72,10 @@ function toDate(value) {
|
|
|
72
72
|
return new Date(value);
|
|
73
73
|
return new Date(String(value));
|
|
74
74
|
}
|
|
75
|
-
function createDatabase(path2) {
|
|
75
|
+
function createDatabase(path2, options) {
|
|
76
|
+
if (options?.readOnly) {
|
|
77
|
+
return new duckdb.Database(path2, { access_mode: "READ_ONLY" });
|
|
78
|
+
}
|
|
76
79
|
return new duckdb.Database(path2);
|
|
77
80
|
}
|
|
78
81
|
function dbRun(db, sql, params = []) {
|
|
@@ -126,18 +129,24 @@ function dbClose(db) {
|
|
|
126
129
|
|
|
127
130
|
// src/core/event-store.ts
|
|
128
131
|
var EventStore = class {
|
|
129
|
-
constructor(dbPath) {
|
|
132
|
+
constructor(dbPath, options) {
|
|
130
133
|
this.dbPath = dbPath;
|
|
131
|
-
this.
|
|
134
|
+
this.readOnly = options?.readOnly ?? false;
|
|
135
|
+
this.db = createDatabase(dbPath, { readOnly: this.readOnly });
|
|
132
136
|
}
|
|
133
137
|
db;
|
|
134
138
|
initialized = false;
|
|
139
|
+
readOnly;
|
|
135
140
|
/**
|
|
136
141
|
* Initialize database schema
|
|
137
142
|
*/
|
|
138
143
|
async initialize() {
|
|
139
144
|
if (this.initialized)
|
|
140
145
|
return;
|
|
146
|
+
if (this.readOnly) {
|
|
147
|
+
this.initialized = true;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
141
150
|
await dbRun(this.db, `
|
|
142
151
|
CREATE TABLE IF NOT EXISTS events (
|
|
143
152
|
id VARCHAR PRIMARY KEY,
|
|
@@ -614,6 +623,36 @@ var EventStore = class {
|
|
|
614
623
|
);
|
|
615
624
|
return rows;
|
|
616
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* Get events by memory level
|
|
628
|
+
*/
|
|
629
|
+
async getEventsByLevel(level, options) {
|
|
630
|
+
await this.initialize();
|
|
631
|
+
const limit = options?.limit || 50;
|
|
632
|
+
const offset = options?.offset || 0;
|
|
633
|
+
const rows = await dbAll(
|
|
634
|
+
this.db,
|
|
635
|
+
`SELECT e.* FROM events e
|
|
636
|
+
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
637
|
+
WHERE ml.level = ?
|
|
638
|
+
ORDER BY e.timestamp DESC
|
|
639
|
+
LIMIT ? OFFSET ?`,
|
|
640
|
+
[level, limit, offset]
|
|
641
|
+
);
|
|
642
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Get memory level for a specific event
|
|
646
|
+
*/
|
|
647
|
+
async getEventLevel(eventId) {
|
|
648
|
+
await this.initialize();
|
|
649
|
+
const rows = await dbAll(
|
|
650
|
+
this.db,
|
|
651
|
+
`SELECT level FROM memory_levels WHERE event_id = ?`,
|
|
652
|
+
[eventId]
|
|
653
|
+
);
|
|
654
|
+
return rows.length > 0 ? rows[0].level : null;
|
|
655
|
+
}
|
|
617
656
|
// ============================================================
|
|
618
657
|
// Endless Mode Helper Methods
|
|
619
658
|
// ============================================================
|
|
@@ -624,69 +663,1018 @@ var EventStore = class {
|
|
|
624
663
|
return this.db;
|
|
625
664
|
}
|
|
626
665
|
/**
|
|
627
|
-
* Get config value for endless mode
|
|
666
|
+
* Get config value for endless mode
|
|
667
|
+
*/
|
|
668
|
+
async getEndlessConfig(key) {
|
|
669
|
+
await this.initialize();
|
|
670
|
+
const rows = await dbAll(
|
|
671
|
+
this.db,
|
|
672
|
+
`SELECT value FROM endless_config WHERE key = ?`,
|
|
673
|
+
[key]
|
|
674
|
+
);
|
|
675
|
+
if (rows.length === 0)
|
|
676
|
+
return null;
|
|
677
|
+
return JSON.parse(rows[0].value);
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Set config value for endless mode
|
|
681
|
+
*/
|
|
682
|
+
async setEndlessConfig(key, value) {
|
|
683
|
+
await this.initialize();
|
|
684
|
+
await dbRun(
|
|
685
|
+
this.db,
|
|
686
|
+
`INSERT OR REPLACE INTO endless_config (key, value, updated_at)
|
|
687
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)`,
|
|
688
|
+
[key, JSON.stringify(value)]
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Get all sessions
|
|
693
|
+
*/
|
|
694
|
+
async getAllSessions() {
|
|
695
|
+
await this.initialize();
|
|
696
|
+
const rows = await dbAll(
|
|
697
|
+
this.db,
|
|
698
|
+
`SELECT * FROM sessions ORDER BY started_at DESC`
|
|
699
|
+
);
|
|
700
|
+
return rows.map((row) => ({
|
|
701
|
+
id: row.id,
|
|
702
|
+
startedAt: toDate(row.started_at),
|
|
703
|
+
endedAt: row.ended_at ? toDate(row.ended_at) : void 0,
|
|
704
|
+
projectPath: row.project_path,
|
|
705
|
+
summary: row.summary,
|
|
706
|
+
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
707
|
+
}));
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Increment access count for events (stub for compatibility)
|
|
711
|
+
*/
|
|
712
|
+
async incrementAccessCount(eventIds) {
|
|
713
|
+
return Promise.resolve();
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Get most accessed memories (stub for compatibility)
|
|
717
|
+
*/
|
|
718
|
+
async getMostAccessed(limit = 10) {
|
|
719
|
+
return [];
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Close database connection
|
|
723
|
+
*/
|
|
724
|
+
async close() {
|
|
725
|
+
await dbClose(this.db);
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Convert database row to MemoryEvent
|
|
729
|
+
*/
|
|
730
|
+
rowToEvent(row) {
|
|
731
|
+
return {
|
|
732
|
+
id: row.id,
|
|
733
|
+
eventType: row.event_type,
|
|
734
|
+
sessionId: row.session_id,
|
|
735
|
+
timestamp: toDate(row.timestamp),
|
|
736
|
+
content: row.content,
|
|
737
|
+
canonicalKey: row.canonical_key,
|
|
738
|
+
dedupeKey: row.dedupe_key,
|
|
739
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
// src/core/sqlite-event-store.ts
|
|
745
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
746
|
+
|
|
747
|
+
// src/core/sqlite-wrapper.ts
|
|
748
|
+
import Database from "better-sqlite3";
|
|
749
|
+
function createSQLiteDatabase(path2, options) {
|
|
750
|
+
const db = new Database(path2, {
|
|
751
|
+
readonly: options?.readonly ?? false
|
|
752
|
+
});
|
|
753
|
+
if (!options?.readonly && (options?.walMode ?? true)) {
|
|
754
|
+
db.pragma("journal_mode = WAL");
|
|
755
|
+
db.pragma("synchronous = NORMAL");
|
|
756
|
+
db.pragma("busy_timeout = 5000");
|
|
757
|
+
}
|
|
758
|
+
return db;
|
|
759
|
+
}
|
|
760
|
+
function sqliteRun(db, sql, params = []) {
|
|
761
|
+
const stmt = db.prepare(sql);
|
|
762
|
+
return stmt.run(...params);
|
|
763
|
+
}
|
|
764
|
+
function sqliteAll(db, sql, params = []) {
|
|
765
|
+
const stmt = db.prepare(sql);
|
|
766
|
+
return stmt.all(...params);
|
|
767
|
+
}
|
|
768
|
+
function sqliteGet(db, sql, params = []) {
|
|
769
|
+
const stmt = db.prepare(sql);
|
|
770
|
+
return stmt.get(...params);
|
|
771
|
+
}
|
|
772
|
+
function sqliteExec(db, sql) {
|
|
773
|
+
db.exec(sql);
|
|
774
|
+
}
|
|
775
|
+
function sqliteClose(db) {
|
|
776
|
+
db.close();
|
|
777
|
+
}
|
|
778
|
+
function toDateFromSQLite(value) {
|
|
779
|
+
if (value instanceof Date)
|
|
780
|
+
return value;
|
|
781
|
+
if (typeof value === "string")
|
|
782
|
+
return new Date(value);
|
|
783
|
+
if (typeof value === "number")
|
|
784
|
+
return new Date(value);
|
|
785
|
+
return new Date(String(value));
|
|
786
|
+
}
|
|
787
|
+
function toSQLiteTimestamp(date) {
|
|
788
|
+
return date.toISOString();
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// src/core/sqlite-event-store.ts
|
|
792
|
+
var SQLiteEventStore = class {
|
|
793
|
+
constructor(dbPath, options) {
|
|
794
|
+
this.dbPath = dbPath;
|
|
795
|
+
this.readOnly = options?.readonly ?? false;
|
|
796
|
+
this.db = createSQLiteDatabase(dbPath, {
|
|
797
|
+
readonly: this.readOnly,
|
|
798
|
+
walMode: !this.readOnly
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
db;
|
|
802
|
+
initialized = false;
|
|
803
|
+
readOnly;
|
|
804
|
+
/**
|
|
805
|
+
* Initialize database schema
|
|
806
|
+
*/
|
|
807
|
+
async initialize() {
|
|
808
|
+
if (this.initialized)
|
|
809
|
+
return;
|
|
810
|
+
if (this.readOnly) {
|
|
811
|
+
this.initialized = true;
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
sqliteExec(this.db, `
|
|
815
|
+
-- L0 EventStore: Single Source of Truth (immutable, append-only)
|
|
816
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
817
|
+
id TEXT PRIMARY KEY,
|
|
818
|
+
event_type TEXT NOT NULL,
|
|
819
|
+
session_id TEXT NOT NULL,
|
|
820
|
+
timestamp TEXT NOT NULL,
|
|
821
|
+
content TEXT NOT NULL,
|
|
822
|
+
canonical_key TEXT NOT NULL,
|
|
823
|
+
dedupe_key TEXT UNIQUE,
|
|
824
|
+
metadata TEXT,
|
|
825
|
+
access_count INTEGER DEFAULT 0,
|
|
826
|
+
last_accessed_at TEXT
|
|
827
|
+
);
|
|
828
|
+
|
|
829
|
+
-- Dedup table for idempotency
|
|
830
|
+
CREATE TABLE IF NOT EXISTS event_dedup (
|
|
831
|
+
dedupe_key TEXT PRIMARY KEY,
|
|
832
|
+
event_id TEXT NOT NULL,
|
|
833
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
834
|
+
);
|
|
835
|
+
|
|
836
|
+
-- Session metadata
|
|
837
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
838
|
+
id TEXT PRIMARY KEY,
|
|
839
|
+
started_at TEXT NOT NULL,
|
|
840
|
+
ended_at TEXT,
|
|
841
|
+
project_path TEXT,
|
|
842
|
+
summary TEXT,
|
|
843
|
+
tags TEXT
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
-- Insights (derived data, rebuildable)
|
|
847
|
+
CREATE TABLE IF NOT EXISTS insights (
|
|
848
|
+
id TEXT PRIMARY KEY,
|
|
849
|
+
insight_type TEXT NOT NULL,
|
|
850
|
+
content TEXT NOT NULL,
|
|
851
|
+
canonical_key TEXT NOT NULL,
|
|
852
|
+
confidence REAL,
|
|
853
|
+
source_events TEXT,
|
|
854
|
+
created_at TEXT,
|
|
855
|
+
last_updated TEXT
|
|
856
|
+
);
|
|
857
|
+
|
|
858
|
+
-- Embedding Outbox (Single-Writer Pattern)
|
|
859
|
+
CREATE TABLE IF NOT EXISTS embedding_outbox (
|
|
860
|
+
id TEXT PRIMARY KEY,
|
|
861
|
+
event_id TEXT NOT NULL,
|
|
862
|
+
content TEXT NOT NULL,
|
|
863
|
+
status TEXT DEFAULT 'pending',
|
|
864
|
+
retry_count INTEGER DEFAULT 0,
|
|
865
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
866
|
+
processed_at TEXT,
|
|
867
|
+
error_message TEXT
|
|
868
|
+
);
|
|
869
|
+
|
|
870
|
+
-- Projection offset tracking
|
|
871
|
+
CREATE TABLE IF NOT EXISTS projection_offsets (
|
|
872
|
+
projection_name TEXT PRIMARY KEY,
|
|
873
|
+
last_event_id TEXT,
|
|
874
|
+
last_timestamp TEXT,
|
|
875
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
876
|
+
);
|
|
877
|
+
|
|
878
|
+
-- Memory level tracking
|
|
879
|
+
CREATE TABLE IF NOT EXISTS memory_levels (
|
|
880
|
+
event_id TEXT PRIMARY KEY,
|
|
881
|
+
level TEXT NOT NULL DEFAULT 'L0',
|
|
882
|
+
promoted_at TEXT DEFAULT (datetime('now'))
|
|
883
|
+
);
|
|
884
|
+
|
|
885
|
+
-- Entries (immutable memory units)
|
|
886
|
+
CREATE TABLE IF NOT EXISTS entries (
|
|
887
|
+
entry_id TEXT PRIMARY KEY,
|
|
888
|
+
created_ts TEXT NOT NULL,
|
|
889
|
+
entry_type TEXT NOT NULL,
|
|
890
|
+
title TEXT NOT NULL,
|
|
891
|
+
content_json TEXT NOT NULL,
|
|
892
|
+
stage TEXT NOT NULL DEFAULT 'raw',
|
|
893
|
+
status TEXT DEFAULT 'active',
|
|
894
|
+
superseded_by TEXT,
|
|
895
|
+
build_id TEXT,
|
|
896
|
+
evidence_json TEXT,
|
|
897
|
+
canonical_key TEXT,
|
|
898
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
899
|
+
);
|
|
900
|
+
|
|
901
|
+
-- Entities (task/condition/artifact)
|
|
902
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
903
|
+
entity_id TEXT PRIMARY KEY,
|
|
904
|
+
entity_type TEXT NOT NULL,
|
|
905
|
+
canonical_key TEXT NOT NULL,
|
|
906
|
+
title TEXT NOT NULL,
|
|
907
|
+
stage TEXT NOT NULL DEFAULT 'raw',
|
|
908
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
909
|
+
current_json TEXT NOT NULL,
|
|
910
|
+
title_norm TEXT,
|
|
911
|
+
search_text TEXT,
|
|
912
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
913
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
914
|
+
);
|
|
915
|
+
|
|
916
|
+
-- Entity aliases for canonical key lookup
|
|
917
|
+
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
918
|
+
entity_type TEXT NOT NULL,
|
|
919
|
+
canonical_key TEXT NOT NULL,
|
|
920
|
+
entity_id TEXT NOT NULL,
|
|
921
|
+
is_primary INTEGER DEFAULT 0,
|
|
922
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
923
|
+
PRIMARY KEY(entity_type, canonical_key)
|
|
924
|
+
);
|
|
925
|
+
|
|
926
|
+
-- Edges (relationships between entries/entities)
|
|
927
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
928
|
+
edge_id TEXT PRIMARY KEY,
|
|
929
|
+
src_type TEXT NOT NULL,
|
|
930
|
+
src_id TEXT NOT NULL,
|
|
931
|
+
rel_type TEXT NOT NULL,
|
|
932
|
+
dst_type TEXT NOT NULL,
|
|
933
|
+
dst_id TEXT NOT NULL,
|
|
934
|
+
meta_json TEXT,
|
|
935
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
936
|
+
);
|
|
937
|
+
|
|
938
|
+
-- Vector Outbox V2 Table
|
|
939
|
+
CREATE TABLE IF NOT EXISTS vector_outbox (
|
|
940
|
+
job_id TEXT PRIMARY KEY,
|
|
941
|
+
item_kind TEXT NOT NULL,
|
|
942
|
+
item_id TEXT NOT NULL,
|
|
943
|
+
embedding_version TEXT NOT NULL,
|
|
944
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
945
|
+
retry_count INTEGER DEFAULT 0,
|
|
946
|
+
error TEXT,
|
|
947
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
948
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
949
|
+
UNIQUE(item_kind, item_id, embedding_version)
|
|
950
|
+
);
|
|
951
|
+
|
|
952
|
+
-- Build Runs
|
|
953
|
+
CREATE TABLE IF NOT EXISTS build_runs (
|
|
954
|
+
build_id TEXT PRIMARY KEY,
|
|
955
|
+
started_at TEXT NOT NULL,
|
|
956
|
+
finished_at TEXT,
|
|
957
|
+
extractor_model TEXT NOT NULL,
|
|
958
|
+
extractor_prompt_hash TEXT NOT NULL,
|
|
959
|
+
embedder_model TEXT NOT NULL,
|
|
960
|
+
embedding_version TEXT NOT NULL,
|
|
961
|
+
idris_version TEXT NOT NULL,
|
|
962
|
+
schema_version TEXT NOT NULL,
|
|
963
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
964
|
+
error TEXT
|
|
965
|
+
);
|
|
966
|
+
|
|
967
|
+
-- Pipeline Metrics
|
|
968
|
+
CREATE TABLE IF NOT EXISTS pipeline_metrics (
|
|
969
|
+
id TEXT PRIMARY KEY,
|
|
970
|
+
ts TEXT NOT NULL,
|
|
971
|
+
stage TEXT NOT NULL,
|
|
972
|
+
latency_ms REAL NOT NULL,
|
|
973
|
+
success INTEGER NOT NULL,
|
|
974
|
+
error TEXT,
|
|
975
|
+
session_id TEXT
|
|
976
|
+
);
|
|
977
|
+
|
|
978
|
+
-- Working Set table (active memory window)
|
|
979
|
+
CREATE TABLE IF NOT EXISTS working_set (
|
|
980
|
+
id TEXT PRIMARY KEY,
|
|
981
|
+
event_id TEXT NOT NULL,
|
|
982
|
+
added_at TEXT DEFAULT (datetime('now')),
|
|
983
|
+
relevance_score REAL DEFAULT 1.0,
|
|
984
|
+
topics TEXT,
|
|
985
|
+
expires_at TEXT
|
|
986
|
+
);
|
|
987
|
+
|
|
988
|
+
-- Consolidated Memories table (long-term integrated memories)
|
|
989
|
+
CREATE TABLE IF NOT EXISTS consolidated_memories (
|
|
990
|
+
memory_id TEXT PRIMARY KEY,
|
|
991
|
+
summary TEXT NOT NULL,
|
|
992
|
+
topics TEXT,
|
|
993
|
+
source_events TEXT,
|
|
994
|
+
confidence REAL DEFAULT 0.5,
|
|
995
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
996
|
+
accessed_at TEXT,
|
|
997
|
+
access_count INTEGER DEFAULT 0
|
|
998
|
+
);
|
|
999
|
+
|
|
1000
|
+
-- Continuity Log table (tracks context transitions)
|
|
1001
|
+
CREATE TABLE IF NOT EXISTS continuity_log (
|
|
1002
|
+
log_id TEXT PRIMARY KEY,
|
|
1003
|
+
from_context_id TEXT,
|
|
1004
|
+
to_context_id TEXT,
|
|
1005
|
+
continuity_score REAL,
|
|
1006
|
+
transition_type TEXT,
|
|
1007
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
1008
|
+
);
|
|
1009
|
+
|
|
1010
|
+
-- Endless Mode Config table
|
|
1011
|
+
CREATE TABLE IF NOT EXISTS endless_config (
|
|
1012
|
+
key TEXT PRIMARY KEY,
|
|
1013
|
+
value TEXT,
|
|
1014
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
1015
|
+
);
|
|
1016
|
+
|
|
1017
|
+
-- Sync position tracking (for SQLite -> DuckDB sync)
|
|
1018
|
+
CREATE TABLE IF NOT EXISTS sync_positions (
|
|
1019
|
+
target_name TEXT PRIMARY KEY,
|
|
1020
|
+
last_event_id TEXT,
|
|
1021
|
+
last_timestamp TEXT,
|
|
1022
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
1023
|
+
);
|
|
1024
|
+
|
|
1025
|
+
-- Create indexes
|
|
1026
|
+
CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
|
|
1027
|
+
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
|
|
1028
|
+
CREATE INDEX IF NOT EXISTS idx_entries_type ON entries(entry_type);
|
|
1029
|
+
CREATE INDEX IF NOT EXISTS idx_entries_stage ON entries(stage);
|
|
1030
|
+
CREATE INDEX IF NOT EXISTS idx_entries_canonical ON entries(canonical_key);
|
|
1031
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type_key ON entities(entity_type, canonical_key);
|
|
1032
|
+
CREATE INDEX IF NOT EXISTS idx_entities_status ON entities(status);
|
|
1033
|
+
CREATE INDEX IF NOT EXISTS idx_edges_src ON edges(src_id, rel_type);
|
|
1034
|
+
CREATE INDEX IF NOT EXISTS idx_edges_dst ON edges(dst_id, rel_type);
|
|
1035
|
+
CREATE INDEX IF NOT EXISTS idx_edges_rel ON edges(rel_type);
|
|
1036
|
+
CREATE INDEX IF NOT EXISTS idx_outbox_status ON vector_outbox(status);
|
|
1037
|
+
CREATE INDEX IF NOT EXISTS idx_working_set_expires ON working_set(expires_at);
|
|
1038
|
+
CREATE INDEX IF NOT EXISTS idx_working_set_relevance ON working_set(relevance_score);
|
|
1039
|
+
CREATE INDEX IF NOT EXISTS idx_consolidated_confidence ON consolidated_memories(confidence);
|
|
1040
|
+
CREATE INDEX IF NOT EXISTS idx_continuity_created ON continuity_log(created_at);
|
|
1041
|
+
CREATE INDEX IF NOT EXISTS idx_embedding_outbox_status ON embedding_outbox(status);
|
|
1042
|
+
`);
|
|
1043
|
+
const tableInfo = sqliteAll(this.db, "PRAGMA table_info(events)", []);
|
|
1044
|
+
const columnNames = tableInfo.map((col) => col.name);
|
|
1045
|
+
if (!columnNames.includes("access_count")) {
|
|
1046
|
+
try {
|
|
1047
|
+
sqliteExec(this.db, `
|
|
1048
|
+
ALTER TABLE events ADD COLUMN access_count INTEGER DEFAULT 0;
|
|
1049
|
+
`);
|
|
1050
|
+
} catch (err) {
|
|
1051
|
+
console.error("Error adding access_count column:", err);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
if (!columnNames.includes("last_accessed_at")) {
|
|
1055
|
+
try {
|
|
1056
|
+
sqliteExec(this.db, `
|
|
1057
|
+
ALTER TABLE events ADD COLUMN last_accessed_at TEXT;
|
|
1058
|
+
`);
|
|
1059
|
+
} catch (err) {
|
|
1060
|
+
console.error("Error adding last_accessed_at column:", err);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
try {
|
|
1064
|
+
sqliteExec(this.db, `
|
|
1065
|
+
CREATE INDEX IF NOT EXISTS idx_events_access_count ON events(access_count DESC);
|
|
1066
|
+
`);
|
|
1067
|
+
} catch (err) {
|
|
1068
|
+
}
|
|
1069
|
+
try {
|
|
1070
|
+
sqliteExec(this.db, `
|
|
1071
|
+
CREATE INDEX IF NOT EXISTS idx_events_last_accessed ON events(last_accessed_at DESC);
|
|
1072
|
+
`);
|
|
1073
|
+
} catch (err) {
|
|
1074
|
+
}
|
|
1075
|
+
this.initialized = true;
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Append event to store (Append-only, Idempotent)
|
|
1079
|
+
*/
|
|
1080
|
+
async append(input) {
|
|
1081
|
+
await this.initialize();
|
|
1082
|
+
const canonicalKey = makeCanonicalKey(input.content);
|
|
1083
|
+
const dedupeKey = makeDedupeKey(input.content, input.sessionId);
|
|
1084
|
+
const existing = sqliteGet(
|
|
1085
|
+
this.db,
|
|
1086
|
+
`SELECT event_id FROM event_dedup WHERE dedupe_key = ?`,
|
|
1087
|
+
[dedupeKey]
|
|
1088
|
+
);
|
|
1089
|
+
if (existing) {
|
|
1090
|
+
return {
|
|
1091
|
+
success: true,
|
|
1092
|
+
eventId: existing.event_id,
|
|
1093
|
+
isDuplicate: true
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
const id = randomUUID2();
|
|
1097
|
+
const timestamp = toSQLiteTimestamp(input.timestamp);
|
|
1098
|
+
try {
|
|
1099
|
+
const insertEvent = this.db.prepare(`
|
|
1100
|
+
INSERT INTO events (id, event_type, session_id, timestamp, content, canonical_key, dedupe_key, metadata)
|
|
1101
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1102
|
+
`);
|
|
1103
|
+
const insertDedup = this.db.prepare(`
|
|
1104
|
+
INSERT INTO event_dedup (dedupe_key, event_id) VALUES (?, ?)
|
|
1105
|
+
`);
|
|
1106
|
+
const insertLevel = this.db.prepare(`
|
|
1107
|
+
INSERT INTO memory_levels (event_id, level) VALUES (?, 'L0')
|
|
1108
|
+
`);
|
|
1109
|
+
const transaction = this.db.transaction(() => {
|
|
1110
|
+
insertEvent.run(
|
|
1111
|
+
id,
|
|
1112
|
+
input.eventType,
|
|
1113
|
+
input.sessionId,
|
|
1114
|
+
timestamp,
|
|
1115
|
+
input.content,
|
|
1116
|
+
canonicalKey,
|
|
1117
|
+
dedupeKey,
|
|
1118
|
+
JSON.stringify(input.metadata || {})
|
|
1119
|
+
);
|
|
1120
|
+
insertDedup.run(dedupeKey, id);
|
|
1121
|
+
insertLevel.run(id);
|
|
1122
|
+
});
|
|
1123
|
+
transaction();
|
|
1124
|
+
return { success: true, eventId: id, isDuplicate: false };
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
return {
|
|
1127
|
+
success: false,
|
|
1128
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Get events by session ID
|
|
1134
|
+
*/
|
|
1135
|
+
async getSessionEvents(sessionId) {
|
|
1136
|
+
await this.initialize();
|
|
1137
|
+
const rows = sqliteAll(
|
|
1138
|
+
this.db,
|
|
1139
|
+
`SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
|
|
1140
|
+
[sessionId]
|
|
1141
|
+
);
|
|
1142
|
+
return rows.map(this.rowToEvent);
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Get recent events
|
|
1146
|
+
*/
|
|
1147
|
+
async getRecentEvents(limit = 100) {
|
|
1148
|
+
await this.initialize();
|
|
1149
|
+
const rows = sqliteAll(
|
|
1150
|
+
this.db,
|
|
1151
|
+
`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
|
|
1152
|
+
[limit]
|
|
1153
|
+
);
|
|
1154
|
+
return rows.map(this.rowToEvent);
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Get event by ID
|
|
1158
|
+
*/
|
|
1159
|
+
async getEvent(id) {
|
|
1160
|
+
await this.initialize();
|
|
1161
|
+
const row = sqliteGet(
|
|
1162
|
+
this.db,
|
|
1163
|
+
`SELECT * FROM events WHERE id = ?`,
|
|
1164
|
+
[id]
|
|
1165
|
+
);
|
|
1166
|
+
if (!row)
|
|
1167
|
+
return null;
|
|
1168
|
+
return this.rowToEvent(row);
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Get events since a timestamp (for sync)
|
|
1172
|
+
*/
|
|
1173
|
+
async getEventsSince(timestamp, limit = 1e3) {
|
|
1174
|
+
await this.initialize();
|
|
1175
|
+
const rows = sqliteAll(
|
|
1176
|
+
this.db,
|
|
1177
|
+
`SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
|
|
1178
|
+
[timestamp, limit]
|
|
1179
|
+
);
|
|
1180
|
+
return rows.map(this.rowToEvent);
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Create or update session
|
|
1184
|
+
*/
|
|
1185
|
+
async upsertSession(session) {
|
|
1186
|
+
await this.initialize();
|
|
1187
|
+
const existing = sqliteGet(
|
|
1188
|
+
this.db,
|
|
1189
|
+
`SELECT id FROM sessions WHERE id = ?`,
|
|
1190
|
+
[session.id]
|
|
1191
|
+
);
|
|
1192
|
+
if (!existing) {
|
|
1193
|
+
sqliteRun(
|
|
1194
|
+
this.db,
|
|
1195
|
+
`INSERT INTO sessions (id, started_at, project_path, tags)
|
|
1196
|
+
VALUES (?, ?, ?, ?)`,
|
|
1197
|
+
[
|
|
1198
|
+
session.id,
|
|
1199
|
+
toSQLiteTimestamp(session.startedAt || /* @__PURE__ */ new Date()),
|
|
1200
|
+
session.projectPath || null,
|
|
1201
|
+
JSON.stringify(session.tags || [])
|
|
1202
|
+
]
|
|
1203
|
+
);
|
|
1204
|
+
} else {
|
|
1205
|
+
const updates = [];
|
|
1206
|
+
const values = [];
|
|
1207
|
+
if (session.endedAt) {
|
|
1208
|
+
updates.push("ended_at = ?");
|
|
1209
|
+
values.push(toSQLiteTimestamp(session.endedAt));
|
|
1210
|
+
}
|
|
1211
|
+
if (session.summary) {
|
|
1212
|
+
updates.push("summary = ?");
|
|
1213
|
+
values.push(session.summary);
|
|
1214
|
+
}
|
|
1215
|
+
if (session.tags) {
|
|
1216
|
+
updates.push("tags = ?");
|
|
1217
|
+
values.push(JSON.stringify(session.tags));
|
|
1218
|
+
}
|
|
1219
|
+
if (updates.length > 0) {
|
|
1220
|
+
values.push(session.id);
|
|
1221
|
+
sqliteRun(
|
|
1222
|
+
this.db,
|
|
1223
|
+
`UPDATE sessions SET ${updates.join(", ")} WHERE id = ?`,
|
|
1224
|
+
values
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Get session by ID
|
|
1231
|
+
*/
|
|
1232
|
+
async getSession(id) {
|
|
1233
|
+
await this.initialize();
|
|
1234
|
+
const row = sqliteGet(
|
|
1235
|
+
this.db,
|
|
1236
|
+
`SELECT * FROM sessions WHERE id = ?`,
|
|
1237
|
+
[id]
|
|
1238
|
+
);
|
|
1239
|
+
if (!row)
|
|
1240
|
+
return null;
|
|
1241
|
+
return {
|
|
1242
|
+
id: row.id,
|
|
1243
|
+
startedAt: toDateFromSQLite(row.started_at),
|
|
1244
|
+
endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
|
|
1245
|
+
projectPath: row.project_path,
|
|
1246
|
+
summary: row.summary,
|
|
1247
|
+
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Get all sessions
|
|
1252
|
+
*/
|
|
1253
|
+
async getAllSessions() {
|
|
1254
|
+
await this.initialize();
|
|
1255
|
+
const rows = sqliteAll(
|
|
1256
|
+
this.db,
|
|
1257
|
+
`SELECT * FROM sessions ORDER BY started_at DESC`
|
|
1258
|
+
);
|
|
1259
|
+
return rows.map((row) => ({
|
|
1260
|
+
id: row.id,
|
|
1261
|
+
startedAt: toDateFromSQLite(row.started_at),
|
|
1262
|
+
endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
|
|
1263
|
+
projectPath: row.project_path,
|
|
1264
|
+
summary: row.summary,
|
|
1265
|
+
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
1266
|
+
}));
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Add to embedding outbox
|
|
1270
|
+
*/
|
|
1271
|
+
async enqueueForEmbedding(eventId, content) {
|
|
1272
|
+
await this.initialize();
|
|
1273
|
+
const id = randomUUID2();
|
|
1274
|
+
sqliteRun(
|
|
1275
|
+
this.db,
|
|
1276
|
+
`INSERT INTO embedding_outbox (id, event_id, content, status, retry_count)
|
|
1277
|
+
VALUES (?, ?, ?, 'pending', 0)`,
|
|
1278
|
+
[id, eventId, content]
|
|
1279
|
+
);
|
|
1280
|
+
return id;
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Get pending outbox items
|
|
1284
|
+
*/
|
|
1285
|
+
async getPendingOutboxItems(limit = 32) {
|
|
1286
|
+
await this.initialize();
|
|
1287
|
+
const pending = sqliteAll(
|
|
1288
|
+
this.db,
|
|
1289
|
+
`SELECT * FROM embedding_outbox
|
|
1290
|
+
WHERE status = 'pending'
|
|
1291
|
+
ORDER BY created_at
|
|
1292
|
+
LIMIT ?`,
|
|
1293
|
+
[limit]
|
|
1294
|
+
);
|
|
1295
|
+
if (pending.length === 0)
|
|
1296
|
+
return [];
|
|
1297
|
+
const ids = pending.map((r) => r.id);
|
|
1298
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1299
|
+
sqliteRun(
|
|
1300
|
+
this.db,
|
|
1301
|
+
`UPDATE embedding_outbox SET status = 'processing' WHERE id IN (${placeholders})`,
|
|
1302
|
+
ids
|
|
1303
|
+
);
|
|
1304
|
+
return pending.map((row) => ({
|
|
1305
|
+
id: row.id,
|
|
1306
|
+
eventId: row.event_id,
|
|
1307
|
+
content: row.content,
|
|
1308
|
+
status: "processing",
|
|
1309
|
+
retryCount: row.retry_count,
|
|
1310
|
+
createdAt: toDateFromSQLite(row.created_at),
|
|
1311
|
+
errorMessage: row.error_message
|
|
1312
|
+
}));
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Mark outbox items as done
|
|
1316
|
+
*/
|
|
1317
|
+
async completeOutboxItems(ids) {
|
|
1318
|
+
if (ids.length === 0)
|
|
1319
|
+
return;
|
|
1320
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1321
|
+
sqliteRun(
|
|
1322
|
+
this.db,
|
|
1323
|
+
`DELETE FROM embedding_outbox WHERE id IN (${placeholders})`,
|
|
1324
|
+
ids
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Mark outbox items as failed
|
|
1329
|
+
*/
|
|
1330
|
+
async failOutboxItems(ids, error) {
|
|
1331
|
+
if (ids.length === 0)
|
|
1332
|
+
return;
|
|
1333
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1334
|
+
sqliteRun(
|
|
1335
|
+
this.db,
|
|
1336
|
+
`UPDATE embedding_outbox
|
|
1337
|
+
SET status = CASE WHEN retry_count >= 3 THEN 'failed' ELSE 'pending' END,
|
|
1338
|
+
retry_count = retry_count + 1,
|
|
1339
|
+
error_message = ?
|
|
1340
|
+
WHERE id IN (${placeholders})`,
|
|
1341
|
+
[error, ...ids]
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Update memory level
|
|
1346
|
+
*/
|
|
1347
|
+
async updateMemoryLevel(eventId, level) {
|
|
1348
|
+
await this.initialize();
|
|
1349
|
+
sqliteRun(
|
|
1350
|
+
this.db,
|
|
1351
|
+
`UPDATE memory_levels SET level = ?, promoted_at = datetime('now') WHERE event_id = ?`,
|
|
1352
|
+
[level, eventId]
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Get memory level statistics
|
|
1357
|
+
*/
|
|
1358
|
+
async getLevelStats() {
|
|
1359
|
+
await this.initialize();
|
|
1360
|
+
const rows = sqliteAll(
|
|
1361
|
+
this.db,
|
|
1362
|
+
`SELECT level, COUNT(*) as count FROM memory_levels GROUP BY level`
|
|
1363
|
+
);
|
|
1364
|
+
return rows;
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Get events by memory level
|
|
1368
|
+
*/
|
|
1369
|
+
async getEventsByLevel(level, options) {
|
|
1370
|
+
await this.initialize();
|
|
1371
|
+
const limit = options?.limit || 50;
|
|
1372
|
+
const offset = options?.offset || 0;
|
|
1373
|
+
const rows = sqliteAll(
|
|
1374
|
+
this.db,
|
|
1375
|
+
`SELECT e.* FROM events e
|
|
1376
|
+
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
1377
|
+
WHERE ml.level = ?
|
|
1378
|
+
ORDER BY e.timestamp DESC
|
|
1379
|
+
LIMIT ? OFFSET ?`,
|
|
1380
|
+
[level, limit, offset]
|
|
1381
|
+
);
|
|
1382
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Get memory level for a specific event
|
|
1386
|
+
*/
|
|
1387
|
+
async getEventLevel(eventId) {
|
|
1388
|
+
await this.initialize();
|
|
1389
|
+
const row = sqliteGet(
|
|
1390
|
+
this.db,
|
|
1391
|
+
`SELECT level FROM memory_levels WHERE event_id = ?`,
|
|
1392
|
+
[eventId]
|
|
1393
|
+
);
|
|
1394
|
+
return row ? row.level : null;
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Get sync position for a target
|
|
1398
|
+
*/
|
|
1399
|
+
async getSyncPosition(targetName) {
|
|
1400
|
+
await this.initialize();
|
|
1401
|
+
const row = sqliteGet(
|
|
1402
|
+
this.db,
|
|
1403
|
+
`SELECT last_event_id, last_timestamp FROM sync_positions WHERE target_name = ?`,
|
|
1404
|
+
[targetName]
|
|
1405
|
+
);
|
|
1406
|
+
return {
|
|
1407
|
+
lastEventId: row?.last_event_id ?? null,
|
|
1408
|
+
lastTimestamp: row?.last_timestamp ?? null
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Update sync position for a target
|
|
1413
|
+
*/
|
|
1414
|
+
async updateSyncPosition(targetName, lastEventId, lastTimestamp) {
|
|
1415
|
+
await this.initialize();
|
|
1416
|
+
sqliteRun(
|
|
1417
|
+
this.db,
|
|
1418
|
+
`INSERT OR REPLACE INTO sync_positions (target_name, last_event_id, last_timestamp, updated_at)
|
|
1419
|
+
VALUES (?, ?, ?, datetime('now'))`,
|
|
1420
|
+
[targetName, lastEventId, lastTimestamp]
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Get config value for endless mode
|
|
1425
|
+
*/
|
|
1426
|
+
async getEndlessConfig(key) {
|
|
1427
|
+
await this.initialize();
|
|
1428
|
+
const row = sqliteGet(
|
|
1429
|
+
this.db,
|
|
1430
|
+
`SELECT value FROM endless_config WHERE key = ?`,
|
|
1431
|
+
[key]
|
|
1432
|
+
);
|
|
1433
|
+
if (!row)
|
|
1434
|
+
return null;
|
|
1435
|
+
return JSON.parse(row.value);
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Set config value for endless mode
|
|
1439
|
+
*/
|
|
1440
|
+
async setEndlessConfig(key, value) {
|
|
1441
|
+
await this.initialize();
|
|
1442
|
+
sqliteRun(
|
|
1443
|
+
this.db,
|
|
1444
|
+
`INSERT OR REPLACE INTO endless_config (key, value, updated_at)
|
|
1445
|
+
VALUES (?, ?, datetime('now'))`,
|
|
1446
|
+
[key, JSON.stringify(value)]
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Increment access count for events
|
|
1451
|
+
*/
|
|
1452
|
+
async incrementAccessCount(eventIds) {
|
|
1453
|
+
if (eventIds.length === 0 || this.readOnly)
|
|
1454
|
+
return;
|
|
1455
|
+
await this.initialize();
|
|
1456
|
+
const placeholders = eventIds.map(() => "?").join(",");
|
|
1457
|
+
const currentTime = toSQLiteTimestamp(/* @__PURE__ */ new Date());
|
|
1458
|
+
sqliteRun(
|
|
1459
|
+
this.db,
|
|
1460
|
+
`UPDATE events
|
|
1461
|
+
SET access_count = access_count + 1,
|
|
1462
|
+
last_accessed_at = ?
|
|
1463
|
+
WHERE id IN (${placeholders})`,
|
|
1464
|
+
[currentTime, ...eventIds]
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Get most accessed memories
|
|
1469
|
+
*/
|
|
1470
|
+
async getMostAccessed(limit = 10) {
|
|
1471
|
+
await this.initialize();
|
|
1472
|
+
const rows = sqliteAll(
|
|
1473
|
+
this.db,
|
|
1474
|
+
`SELECT * FROM events
|
|
1475
|
+
WHERE access_count > 0
|
|
1476
|
+
ORDER BY access_count DESC, last_accessed_at DESC
|
|
1477
|
+
LIMIT ?`,
|
|
1478
|
+
[limit]
|
|
1479
|
+
);
|
|
1480
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Get database instance for direct access
|
|
1484
|
+
*/
|
|
1485
|
+
getDatabase() {
|
|
1486
|
+
return this.db;
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Close database connection
|
|
1490
|
+
*/
|
|
1491
|
+
async close() {
|
|
1492
|
+
sqliteClose(this.db);
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* Convert database row to MemoryEvent
|
|
1496
|
+
*/
|
|
1497
|
+
rowToEvent(row) {
|
|
1498
|
+
const event = {
|
|
1499
|
+
id: row.id,
|
|
1500
|
+
eventType: row.event_type,
|
|
1501
|
+
sessionId: row.session_id,
|
|
1502
|
+
timestamp: toDateFromSQLite(row.timestamp),
|
|
1503
|
+
content: row.content,
|
|
1504
|
+
canonicalKey: row.canonical_key,
|
|
1505
|
+
dedupeKey: row.dedupe_key,
|
|
1506
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
1507
|
+
};
|
|
1508
|
+
if (row.access_count !== void 0) {
|
|
1509
|
+
event.access_count = row.access_count;
|
|
1510
|
+
}
|
|
1511
|
+
if (row.last_accessed_at !== void 0) {
|
|
1512
|
+
event.last_accessed_at = row.last_accessed_at;
|
|
1513
|
+
}
|
|
1514
|
+
return event;
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
|
|
1518
|
+
// src/core/sync-worker.ts
|
|
1519
|
+
var DEFAULT_CONFIG = {
|
|
1520
|
+
intervalMs: 3e4,
|
|
1521
|
+
batchSize: 500,
|
|
1522
|
+
maxRetries: 3,
|
|
1523
|
+
retryDelayMs: 5e3
|
|
1524
|
+
};
|
|
1525
|
+
var SyncWorker = class {
|
|
1526
|
+
constructor(sqliteStore, duckdbStore, config) {
|
|
1527
|
+
this.sqliteStore = sqliteStore;
|
|
1528
|
+
this.duckdbStore = duckdbStore;
|
|
1529
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
1530
|
+
}
|
|
1531
|
+
config;
|
|
1532
|
+
intervalHandle = null;
|
|
1533
|
+
running = false;
|
|
1534
|
+
stats = {
|
|
1535
|
+
lastSyncAt: null,
|
|
1536
|
+
eventsSynced: 0,
|
|
1537
|
+
sessionsSynced: 0,
|
|
1538
|
+
errors: 0,
|
|
1539
|
+
status: "idle"
|
|
1540
|
+
};
|
|
1541
|
+
/**
|
|
1542
|
+
* Start the sync worker
|
|
1543
|
+
*/
|
|
1544
|
+
start() {
|
|
1545
|
+
if (this.running)
|
|
1546
|
+
return;
|
|
1547
|
+
this.running = true;
|
|
1548
|
+
this.stats.status = "idle";
|
|
1549
|
+
this.syncNow().catch((err) => {
|
|
1550
|
+
console.error("[SyncWorker] Initial sync failed:", err);
|
|
1551
|
+
});
|
|
1552
|
+
this.intervalHandle = setInterval(() => {
|
|
1553
|
+
this.syncNow().catch((err) => {
|
|
1554
|
+
console.error("[SyncWorker] Periodic sync failed:", err);
|
|
1555
|
+
});
|
|
1556
|
+
}, this.config.intervalMs);
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Stop the sync worker
|
|
1560
|
+
*/
|
|
1561
|
+
stop() {
|
|
1562
|
+
this.running = false;
|
|
1563
|
+
this.stats.status = "stopped";
|
|
1564
|
+
if (this.intervalHandle) {
|
|
1565
|
+
clearInterval(this.intervalHandle);
|
|
1566
|
+
this.intervalHandle = null;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Trigger immediate sync
|
|
1571
|
+
*/
|
|
1572
|
+
async syncNow() {
|
|
1573
|
+
if (this.stats.status === "syncing") {
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
this.stats.status = "syncing";
|
|
1577
|
+
try {
|
|
1578
|
+
await this.syncEvents();
|
|
1579
|
+
await this.syncSessions();
|
|
1580
|
+
this.stats.lastSyncAt = /* @__PURE__ */ new Date();
|
|
1581
|
+
this.stats.status = "idle";
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
this.stats.errors++;
|
|
1584
|
+
this.stats.status = "error";
|
|
1585
|
+
throw error;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Sync events from SQLite to DuckDB
|
|
1590
|
+
*/
|
|
1591
|
+
async syncEvents() {
|
|
1592
|
+
const targetName = "duckdb_analytics";
|
|
1593
|
+
const position = await this.sqliteStore.getSyncPosition(targetName);
|
|
1594
|
+
const lastTimestamp = position.lastTimestamp || "1970-01-01T00:00:00.000Z";
|
|
1595
|
+
let hasMore = true;
|
|
1596
|
+
let totalSynced = 0;
|
|
1597
|
+
while (hasMore) {
|
|
1598
|
+
const events = await this.sqliteStore.getEventsSince(lastTimestamp, this.config.batchSize);
|
|
1599
|
+
if (events.length === 0) {
|
|
1600
|
+
hasMore = false;
|
|
1601
|
+
break;
|
|
1602
|
+
}
|
|
1603
|
+
await this.retryWithBackoff(async () => {
|
|
1604
|
+
for (const event of events) {
|
|
1605
|
+
await this.insertEventToDuckDB(event);
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
totalSynced += events.length;
|
|
1609
|
+
const lastEvent = events[events.length - 1];
|
|
1610
|
+
await this.sqliteStore.updateSyncPosition(
|
|
1611
|
+
targetName,
|
|
1612
|
+
lastEvent.id,
|
|
1613
|
+
lastEvent.timestamp.toISOString()
|
|
1614
|
+
);
|
|
1615
|
+
hasMore = events.length === this.config.batchSize;
|
|
1616
|
+
}
|
|
1617
|
+
this.stats.eventsSynced += totalSynced;
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Sync sessions from SQLite to DuckDB
|
|
1621
|
+
*/
|
|
1622
|
+
async syncSessions() {
|
|
1623
|
+
const sessions = await this.sqliteStore.getAllSessions();
|
|
1624
|
+
for (const session of sessions) {
|
|
1625
|
+
await this.retryWithBackoff(async () => {
|
|
1626
|
+
await this.duckdbStore.upsertSession(session);
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
this.stats.sessionsSynced = sessions.length;
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* Insert a single event into DuckDB
|
|
628
1633
|
*/
|
|
629
|
-
async
|
|
630
|
-
await this.
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
return null;
|
|
638
|
-
return JSON.parse(rows[0].value);
|
|
1634
|
+
async insertEventToDuckDB(event) {
|
|
1635
|
+
await this.duckdbStore.append({
|
|
1636
|
+
eventType: event.eventType,
|
|
1637
|
+
sessionId: event.sessionId,
|
|
1638
|
+
timestamp: event.timestamp,
|
|
1639
|
+
content: event.content,
|
|
1640
|
+
metadata: event.metadata
|
|
1641
|
+
});
|
|
639
1642
|
}
|
|
640
1643
|
/**
|
|
641
|
-
*
|
|
1644
|
+
* Retry operation with exponential backoff
|
|
642
1645
|
*/
|
|
643
|
-
async
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
1646
|
+
async retryWithBackoff(fn) {
|
|
1647
|
+
let lastError = null;
|
|
1648
|
+
for (let attempt = 0; attempt < this.config.maxRetries; attempt++) {
|
|
1649
|
+
try {
|
|
1650
|
+
return await fn();
|
|
1651
|
+
} catch (error) {
|
|
1652
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1653
|
+
if (attempt < this.config.maxRetries - 1) {
|
|
1654
|
+
const delay = this.config.retryDelayMs * Math.pow(2, attempt);
|
|
1655
|
+
await this.sleep(delay);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
throw lastError;
|
|
651
1660
|
}
|
|
652
1661
|
/**
|
|
653
|
-
*
|
|
1662
|
+
* Sleep utility
|
|
654
1663
|
*/
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
const rows = await dbAll(
|
|
658
|
-
this.db,
|
|
659
|
-
`SELECT * FROM sessions ORDER BY started_at DESC`
|
|
660
|
-
);
|
|
661
|
-
return rows.map((row) => ({
|
|
662
|
-
id: row.id,
|
|
663
|
-
startedAt: toDate(row.started_at),
|
|
664
|
-
endedAt: row.ended_at ? toDate(row.ended_at) : void 0,
|
|
665
|
-
projectPath: row.project_path,
|
|
666
|
-
summary: row.summary,
|
|
667
|
-
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
668
|
-
}));
|
|
1664
|
+
sleep(ms) {
|
|
1665
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
669
1666
|
}
|
|
670
1667
|
/**
|
|
671
|
-
*
|
|
1668
|
+
* Get sync statistics
|
|
672
1669
|
*/
|
|
673
|
-
|
|
674
|
-
|
|
1670
|
+
getStats() {
|
|
1671
|
+
return { ...this.stats };
|
|
675
1672
|
}
|
|
676
1673
|
/**
|
|
677
|
-
*
|
|
1674
|
+
* Check if worker is running
|
|
678
1675
|
*/
|
|
679
|
-
|
|
680
|
-
return
|
|
681
|
-
id: row.id,
|
|
682
|
-
eventType: row.event_type,
|
|
683
|
-
sessionId: row.session_id,
|
|
684
|
-
timestamp: toDate(row.timestamp),
|
|
685
|
-
content: row.content,
|
|
686
|
-
canonicalKey: row.canonical_key,
|
|
687
|
-
dedupeKey: row.dedupe_key,
|
|
688
|
-
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
689
|
-
};
|
|
1676
|
+
isRunning() {
|
|
1677
|
+
return this.running;
|
|
690
1678
|
}
|
|
691
1679
|
};
|
|
692
1680
|
|
|
@@ -918,7 +1906,7 @@ function getDefaultEmbedder() {
|
|
|
918
1906
|
}
|
|
919
1907
|
|
|
920
1908
|
// src/core/vector-outbox.ts
|
|
921
|
-
var
|
|
1909
|
+
var DEFAULT_CONFIG2 = {
|
|
922
1910
|
embeddingVersion: "v1",
|
|
923
1911
|
maxRetries: 3,
|
|
924
1912
|
stuckThresholdMs: 5 * 60 * 1e3,
|
|
@@ -927,7 +1915,7 @@ var DEFAULT_CONFIG = {
|
|
|
927
1915
|
};
|
|
928
1916
|
|
|
929
1917
|
// src/core/vector-worker.ts
|
|
930
|
-
var
|
|
1918
|
+
var DEFAULT_CONFIG3 = {
|
|
931
1919
|
batchSize: 32,
|
|
932
1920
|
pollIntervalMs: 1e3,
|
|
933
1921
|
maxRetries: 3
|
|
@@ -938,12 +1926,13 @@ var VectorWorker = class {
|
|
|
938
1926
|
embedder;
|
|
939
1927
|
config;
|
|
940
1928
|
running = false;
|
|
1929
|
+
stopping = false;
|
|
941
1930
|
pollTimeout = null;
|
|
942
1931
|
constructor(eventStore, vectorStore, embedder, config = {}) {
|
|
943
1932
|
this.eventStore = eventStore;
|
|
944
1933
|
this.vectorStore = vectorStore;
|
|
945
1934
|
this.embedder = embedder;
|
|
946
|
-
this.config = { ...
|
|
1935
|
+
this.config = { ...DEFAULT_CONFIG3, ...config };
|
|
947
1936
|
}
|
|
948
1937
|
/**
|
|
949
1938
|
* Start the worker polling loop
|
|
@@ -952,6 +1941,7 @@ var VectorWorker = class {
|
|
|
952
1941
|
if (this.running)
|
|
953
1942
|
return;
|
|
954
1943
|
this.running = true;
|
|
1944
|
+
this.stopping = false;
|
|
955
1945
|
this.poll();
|
|
956
1946
|
}
|
|
957
1947
|
/**
|
|
@@ -959,6 +1949,7 @@ var VectorWorker = class {
|
|
|
959
1949
|
*/
|
|
960
1950
|
stop() {
|
|
961
1951
|
this.running = false;
|
|
1952
|
+
this.stopping = true;
|
|
962
1953
|
if (this.pollTimeout) {
|
|
963
1954
|
clearTimeout(this.pollTimeout);
|
|
964
1955
|
this.pollTimeout = null;
|
|
@@ -1008,9 +1999,15 @@ var VectorWorker = class {
|
|
|
1008
1999
|
}
|
|
1009
2000
|
return successful.length;
|
|
1010
2001
|
} catch (error) {
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
2002
|
+
if (!this.stopping) {
|
|
2003
|
+
try {
|
|
2004
|
+
const allIds = items.map((i) => i.id);
|
|
2005
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2006
|
+
await this.eventStore.failOutboxItems(allIds, errorMessage);
|
|
2007
|
+
} catch (failError) {
|
|
2008
|
+
console.warn("Could not mark outbox items as failed (database may be closed)");
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
1014
2011
|
throw error;
|
|
1015
2012
|
}
|
|
1016
2013
|
}
|
|
@@ -1018,14 +2015,18 @@ var VectorWorker = class {
|
|
|
1018
2015
|
* Poll for new items
|
|
1019
2016
|
*/
|
|
1020
2017
|
async poll() {
|
|
1021
|
-
if (!this.running)
|
|
2018
|
+
if (!this.running || this.stopping)
|
|
1022
2019
|
return;
|
|
1023
2020
|
try {
|
|
1024
2021
|
await this.processBatch();
|
|
1025
2022
|
} catch (error) {
|
|
1026
|
-
|
|
2023
|
+
if (!this.stopping) {
|
|
2024
|
+
console.error("Vector worker error:", error);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
if (this.running && !this.stopping) {
|
|
2028
|
+
this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
|
|
1027
2029
|
}
|
|
1028
|
-
this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
|
|
1029
2030
|
}
|
|
1030
2031
|
/**
|
|
1031
2032
|
* Process all pending items (blocking)
|
|
@@ -1052,7 +2053,7 @@ function createVectorWorker(eventStore, vectorStore, embedder, config) {
|
|
|
1052
2053
|
}
|
|
1053
2054
|
|
|
1054
2055
|
// src/core/matcher.ts
|
|
1055
|
-
var
|
|
2056
|
+
var DEFAULT_CONFIG4 = {
|
|
1056
2057
|
weights: {
|
|
1057
2058
|
semanticSimilarity: 0.4,
|
|
1058
2059
|
ftsScore: 0.25,
|
|
@@ -1067,9 +2068,9 @@ var Matcher = class {
|
|
|
1067
2068
|
config;
|
|
1068
2069
|
constructor(config = {}) {
|
|
1069
2070
|
this.config = {
|
|
1070
|
-
...
|
|
2071
|
+
...DEFAULT_CONFIG4,
|
|
1071
2072
|
...config,
|
|
1072
|
-
weights: { ...
|
|
2073
|
+
weights: { ...DEFAULT_CONFIG4.weights, ...config.weights }
|
|
1073
2074
|
};
|
|
1074
2075
|
}
|
|
1075
2076
|
/**
|
|
@@ -1196,6 +2197,7 @@ var Retriever = class {
|
|
|
1196
2197
|
matcher;
|
|
1197
2198
|
sharedStore;
|
|
1198
2199
|
sharedVectorStore;
|
|
2200
|
+
graduation;
|
|
1199
2201
|
constructor(eventStore, vectorStore, embedder, matcher, sharedOptions) {
|
|
1200
2202
|
this.eventStore = eventStore;
|
|
1201
2203
|
this.vectorStore = vectorStore;
|
|
@@ -1204,6 +2206,12 @@ var Retriever = class {
|
|
|
1204
2206
|
this.sharedStore = sharedOptions?.sharedStore;
|
|
1205
2207
|
this.sharedVectorStore = sharedOptions?.sharedVectorStore;
|
|
1206
2208
|
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Set graduation pipeline for access tracking
|
|
2211
|
+
*/
|
|
2212
|
+
setGraduationPipeline(graduation) {
|
|
2213
|
+
this.graduation = graduation;
|
|
2214
|
+
}
|
|
1207
2215
|
/**
|
|
1208
2216
|
* Set shared stores after construction
|
|
1209
2217
|
*/
|
|
@@ -1326,6 +2334,13 @@ var Retriever = class {
|
|
|
1326
2334
|
const event = await this.eventStore.getEvent(result.eventId);
|
|
1327
2335
|
if (!event)
|
|
1328
2336
|
continue;
|
|
2337
|
+
if (this.graduation) {
|
|
2338
|
+
this.graduation.recordAccess(
|
|
2339
|
+
event.id,
|
|
2340
|
+
options.sessionId || "unknown",
|
|
2341
|
+
result.score
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
1329
2344
|
let sessionContext;
|
|
1330
2345
|
if (options.includeSessionContext) {
|
|
1331
2346
|
sessionContext = await this.getSessionContext(event.sessionId, event.id);
|
|
@@ -1447,15 +2462,26 @@ var GraduationPipeline = class {
|
|
|
1447
2462
|
L3toL4: { ...DEFAULT_CRITERIA.L3toL4, ...criteria.L3toL4 }
|
|
1448
2463
|
};
|
|
1449
2464
|
}
|
|
2465
|
+
// Track which sessions have accessed each event
|
|
2466
|
+
sessionAccesses = /* @__PURE__ */ new Map();
|
|
1450
2467
|
/**
|
|
1451
2468
|
* Record an access to an event (used for graduation scoring)
|
|
1452
2469
|
*/
|
|
1453
2470
|
recordAccess(eventId, fromSessionId, confidence = 1) {
|
|
1454
2471
|
const existing = this.metrics.get(eventId);
|
|
2472
|
+
if (!this.sessionAccesses.has(eventId)) {
|
|
2473
|
+
this.sessionAccesses.set(eventId, /* @__PURE__ */ new Set());
|
|
2474
|
+
}
|
|
2475
|
+
const sessions = this.sessionAccesses.get(eventId);
|
|
2476
|
+
const isNewSession = !sessions.has(fromSessionId);
|
|
2477
|
+
sessions.add(fromSessionId);
|
|
1455
2478
|
if (existing) {
|
|
1456
2479
|
existing.accessCount++;
|
|
1457
2480
|
existing.lastAccessed = /* @__PURE__ */ new Date();
|
|
1458
2481
|
existing.confidence = Math.max(existing.confidence, confidence);
|
|
2482
|
+
if (isNewSession && sessions.size > 1) {
|
|
2483
|
+
existing.crossSessionRefs = sessions.size - 1;
|
|
2484
|
+
}
|
|
1459
2485
|
} else {
|
|
1460
2486
|
this.metrics.set(eventId, {
|
|
1461
2487
|
eventId,
|
|
@@ -1752,7 +2778,7 @@ function createSharedEventStore(dbPath) {
|
|
|
1752
2778
|
}
|
|
1753
2779
|
|
|
1754
2780
|
// src/core/shared-store.ts
|
|
1755
|
-
import { randomUUID as
|
|
2781
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1756
2782
|
var SharedStore = class {
|
|
1757
2783
|
constructor(sharedEventStore) {
|
|
1758
2784
|
this.sharedEventStore = sharedEventStore;
|
|
@@ -1764,7 +2790,7 @@ var SharedStore = class {
|
|
|
1764
2790
|
* Promote a verified troubleshooting entry to shared storage
|
|
1765
2791
|
*/
|
|
1766
2792
|
async promoteEntry(input) {
|
|
1767
|
-
const entryId =
|
|
2793
|
+
const entryId = randomUUID3();
|
|
1768
2794
|
await dbRun(
|
|
1769
2795
|
this.db,
|
|
1770
2796
|
`INSERT INTO shared_troubleshooting (
|
|
@@ -2129,7 +3155,7 @@ function createSharedVectorStore(dbPath) {
|
|
|
2129
3155
|
}
|
|
2130
3156
|
|
|
2131
3157
|
// src/core/shared-promoter.ts
|
|
2132
|
-
import { randomUUID as
|
|
3158
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
2133
3159
|
var SharedPromoter = class {
|
|
2134
3160
|
constructor(sharedStore, sharedVectorStore, embedder, config) {
|
|
2135
3161
|
this.sharedStore = sharedStore;
|
|
@@ -2197,7 +3223,7 @@ var SharedPromoter = class {
|
|
|
2197
3223
|
const embeddingContent = this.createEmbeddingContent(input);
|
|
2198
3224
|
const embedding = await this.embedder.embed(embeddingContent);
|
|
2199
3225
|
await this.sharedVectorStore.upsert({
|
|
2200
|
-
id:
|
|
3226
|
+
id: randomUUID4(),
|
|
2201
3227
|
entryId,
|
|
2202
3228
|
entryType: "troubleshooting",
|
|
2203
3229
|
content: embeddingContent,
|
|
@@ -2337,7 +3363,7 @@ function createToolObservationEmbedding(toolName, metadata, success) {
|
|
|
2337
3363
|
}
|
|
2338
3364
|
|
|
2339
3365
|
// src/core/working-set-store.ts
|
|
2340
|
-
import { randomUUID as
|
|
3366
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
2341
3367
|
var WorkingSetStore = class {
|
|
2342
3368
|
constructor(eventStore, config) {
|
|
2343
3369
|
this.eventStore = eventStore;
|
|
@@ -2358,7 +3384,7 @@ var WorkingSetStore = class {
|
|
|
2358
3384
|
`INSERT OR REPLACE INTO working_set (id, event_id, added_at, relevance_score, topics, expires_at)
|
|
2359
3385
|
VALUES (?, ?, CURRENT_TIMESTAMP, ?, ?, ?)`,
|
|
2360
3386
|
[
|
|
2361
|
-
|
|
3387
|
+
randomUUID5(),
|
|
2362
3388
|
eventId,
|
|
2363
3389
|
relevanceScore,
|
|
2364
3390
|
JSON.stringify(topics || []),
|
|
@@ -2542,7 +3568,7 @@ function createWorkingSetStore(eventStore, config) {
|
|
|
2542
3568
|
}
|
|
2543
3569
|
|
|
2544
3570
|
// src/core/consolidated-store.ts
|
|
2545
|
-
import { randomUUID as
|
|
3571
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
2546
3572
|
var ConsolidatedStore = class {
|
|
2547
3573
|
constructor(eventStore) {
|
|
2548
3574
|
this.eventStore = eventStore;
|
|
@@ -2554,7 +3580,7 @@ var ConsolidatedStore = class {
|
|
|
2554
3580
|
* Create a new consolidated memory
|
|
2555
3581
|
*/
|
|
2556
3582
|
async create(input) {
|
|
2557
|
-
const memoryId =
|
|
3583
|
+
const memoryId = randomUUID6();
|
|
2558
3584
|
await dbRun(
|
|
2559
3585
|
this.db,
|
|
2560
3586
|
`INSERT INTO consolidated_memories
|
|
@@ -3081,7 +4107,7 @@ function createConsolidationWorker(workingSetStore, consolidatedStore, config) {
|
|
|
3081
4107
|
}
|
|
3082
4108
|
|
|
3083
4109
|
// src/core/continuity-manager.ts
|
|
3084
|
-
import { randomUUID as
|
|
4110
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
3085
4111
|
var ContinuityManager = class {
|
|
3086
4112
|
constructor(eventStore, config) {
|
|
3087
4113
|
this.eventStore = eventStore;
|
|
@@ -3235,7 +4261,7 @@ var ContinuityManager = class {
|
|
|
3235
4261
|
`INSERT INTO continuity_log
|
|
3236
4262
|
(log_id, from_context_id, to_context_id, continuity_score, transition_type, created_at)
|
|
3237
4263
|
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
|
|
3238
|
-
[
|
|
4264
|
+
[randomUUID7(), previous.id, current.id, score, type]
|
|
3239
4265
|
);
|
|
3240
4266
|
}
|
|
3241
4267
|
/**
|
|
@@ -3343,6 +4369,127 @@ function createContinuityManager(eventStore, config) {
|
|
|
3343
4369
|
return new ContinuityManager(eventStore, config);
|
|
3344
4370
|
}
|
|
3345
4371
|
|
|
4372
|
+
// src/core/graduation-worker.ts
|
|
4373
|
+
var DEFAULT_CONFIG5 = {
|
|
4374
|
+
evaluationIntervalMs: 3e5,
|
|
4375
|
+
// 5 minutes
|
|
4376
|
+
batchSize: 50,
|
|
4377
|
+
cooldownMs: 36e5
|
|
4378
|
+
// 1 hour cooldown between evaluations
|
|
4379
|
+
};
|
|
4380
|
+
var GraduationWorker = class {
|
|
4381
|
+
constructor(eventStore, graduation, config = DEFAULT_CONFIG5) {
|
|
4382
|
+
this.eventStore = eventStore;
|
|
4383
|
+
this.graduation = graduation;
|
|
4384
|
+
this.config = config;
|
|
4385
|
+
}
|
|
4386
|
+
running = false;
|
|
4387
|
+
timeout = null;
|
|
4388
|
+
lastEvaluated = /* @__PURE__ */ new Map();
|
|
4389
|
+
/**
|
|
4390
|
+
* Start the graduation worker
|
|
4391
|
+
*/
|
|
4392
|
+
start() {
|
|
4393
|
+
if (this.running)
|
|
4394
|
+
return;
|
|
4395
|
+
this.running = true;
|
|
4396
|
+
this.scheduleNext();
|
|
4397
|
+
}
|
|
4398
|
+
/**
|
|
4399
|
+
* Stop the graduation worker
|
|
4400
|
+
*/
|
|
4401
|
+
stop() {
|
|
4402
|
+
this.running = false;
|
|
4403
|
+
if (this.timeout) {
|
|
4404
|
+
clearTimeout(this.timeout);
|
|
4405
|
+
this.timeout = null;
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
/**
|
|
4409
|
+
* Check if currently running
|
|
4410
|
+
*/
|
|
4411
|
+
isRunning() {
|
|
4412
|
+
return this.running;
|
|
4413
|
+
}
|
|
4414
|
+
/**
|
|
4415
|
+
* Force a graduation evaluation run
|
|
4416
|
+
*/
|
|
4417
|
+
async forceRun() {
|
|
4418
|
+
return await this.runGraduation();
|
|
4419
|
+
}
|
|
4420
|
+
/**
|
|
4421
|
+
* Schedule the next graduation check
|
|
4422
|
+
*/
|
|
4423
|
+
scheduleNext() {
|
|
4424
|
+
if (!this.running)
|
|
4425
|
+
return;
|
|
4426
|
+
this.timeout = setTimeout(
|
|
4427
|
+
() => this.run(),
|
|
4428
|
+
this.config.evaluationIntervalMs
|
|
4429
|
+
);
|
|
4430
|
+
}
|
|
4431
|
+
/**
|
|
4432
|
+
* Run graduation evaluation
|
|
4433
|
+
*/
|
|
4434
|
+
async run() {
|
|
4435
|
+
if (!this.running)
|
|
4436
|
+
return;
|
|
4437
|
+
try {
|
|
4438
|
+
await this.runGraduation();
|
|
4439
|
+
} catch (error) {
|
|
4440
|
+
console.error("Graduation error:", error);
|
|
4441
|
+
}
|
|
4442
|
+
this.scheduleNext();
|
|
4443
|
+
}
|
|
4444
|
+
/**
|
|
4445
|
+
* Perform graduation evaluation across all levels
|
|
4446
|
+
*/
|
|
4447
|
+
async runGraduation() {
|
|
4448
|
+
const result = {
|
|
4449
|
+
evaluated: 0,
|
|
4450
|
+
graduated: 0,
|
|
4451
|
+
byLevel: {}
|
|
4452
|
+
};
|
|
4453
|
+
const levels = ["L0", "L1", "L2", "L3"];
|
|
4454
|
+
const now = Date.now();
|
|
4455
|
+
for (const level of levels) {
|
|
4456
|
+
const events = await this.eventStore.getEventsByLevel(level, {
|
|
4457
|
+
limit: this.config.batchSize
|
|
4458
|
+
});
|
|
4459
|
+
let levelGraduated = 0;
|
|
4460
|
+
for (const event of events) {
|
|
4461
|
+
const lastEval = this.lastEvaluated.get(event.id);
|
|
4462
|
+
if (lastEval && now - lastEval < this.config.cooldownMs) {
|
|
4463
|
+
continue;
|
|
4464
|
+
}
|
|
4465
|
+
result.evaluated++;
|
|
4466
|
+
this.lastEvaluated.set(event.id, now);
|
|
4467
|
+
const gradResult = await this.graduation.evaluateGraduation(event.id, level);
|
|
4468
|
+
if (gradResult.success) {
|
|
4469
|
+
result.graduated++;
|
|
4470
|
+
levelGraduated++;
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
if (levelGraduated > 0) {
|
|
4474
|
+
result.byLevel[level] = levelGraduated;
|
|
4475
|
+
}
|
|
4476
|
+
}
|
|
4477
|
+
if (this.lastEvaluated.size > 1e3) {
|
|
4478
|
+
const entries = Array.from(this.lastEvaluated.entries());
|
|
4479
|
+
entries.sort((a, b) => b[1] - a[1]);
|
|
4480
|
+
this.lastEvaluated = new Map(entries.slice(0, 1e3));
|
|
4481
|
+
}
|
|
4482
|
+
return result;
|
|
4483
|
+
}
|
|
4484
|
+
};
|
|
4485
|
+
function createGraduationWorker(eventStore, graduation, config) {
|
|
4486
|
+
return new GraduationWorker(
|
|
4487
|
+
eventStore,
|
|
4488
|
+
graduation,
|
|
4489
|
+
{ ...DEFAULT_CONFIG5, ...config }
|
|
4490
|
+
);
|
|
4491
|
+
}
|
|
4492
|
+
|
|
3346
4493
|
// src/services/memory-service.ts
|
|
3347
4494
|
function normalizePath(projectPath) {
|
|
3348
4495
|
const expanded = projectPath.startsWith("~") ? path.join(os.homedir(), projectPath.slice(1)) : projectPath;
|
|
@@ -3363,13 +4510,18 @@ function getProjectStoragePath(projectPath) {
|
|
|
3363
4510
|
var REGISTRY_PATH = path.join(os.homedir(), ".claude-code", "memory", "session-registry.json");
|
|
3364
4511
|
var SHARED_STORAGE_PATH = path.join(os.homedir(), ".claude-code", "memory", "shared");
|
|
3365
4512
|
var MemoryService = class {
|
|
3366
|
-
|
|
4513
|
+
// Primary store: SQLite (WAL mode) - for hooks, always available
|
|
4514
|
+
sqliteStore;
|
|
4515
|
+
// Analytics store: DuckDB - for server reads (optional, synced from SQLite)
|
|
4516
|
+
analyticsStore;
|
|
4517
|
+
syncWorker = null;
|
|
3367
4518
|
vectorStore;
|
|
3368
4519
|
embedder;
|
|
3369
4520
|
matcher;
|
|
3370
4521
|
retriever;
|
|
3371
4522
|
graduation;
|
|
3372
4523
|
vectorWorker = null;
|
|
4524
|
+
graduationWorker = null;
|
|
3373
4525
|
initialized = false;
|
|
3374
4526
|
// Endless Mode components
|
|
3375
4527
|
workingSetStore = null;
|
|
@@ -3384,24 +4536,48 @@ var MemoryService = class {
|
|
|
3384
4536
|
sharedPromoter = null;
|
|
3385
4537
|
sharedStoreConfig = null;
|
|
3386
4538
|
projectHash = null;
|
|
4539
|
+
readOnly;
|
|
3387
4540
|
constructor(config) {
|
|
3388
4541
|
const storagePath = this.expandPath(config.storagePath);
|
|
3389
|
-
|
|
4542
|
+
this.readOnly = config.readOnly ?? false;
|
|
4543
|
+
if (!this.readOnly && !fs.existsSync(storagePath)) {
|
|
3390
4544
|
fs.mkdirSync(storagePath, { recursive: true });
|
|
3391
4545
|
}
|
|
3392
4546
|
this.projectHash = config.projectHash || null;
|
|
3393
4547
|
this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
|
|
3394
|
-
this.
|
|
4548
|
+
this.sqliteStore = new SQLiteEventStore(
|
|
4549
|
+
path.join(storagePath, "events.sqlite"),
|
|
4550
|
+
{ readonly: this.readOnly }
|
|
4551
|
+
);
|
|
4552
|
+
const analyticsEnabled = config.analyticsEnabled ?? this.readOnly;
|
|
4553
|
+
if (!analyticsEnabled) {
|
|
4554
|
+
this.analyticsStore = null;
|
|
4555
|
+
} else if (this.readOnly) {
|
|
4556
|
+
try {
|
|
4557
|
+
this.analyticsStore = new EventStore(
|
|
4558
|
+
path.join(storagePath, "analytics.duckdb"),
|
|
4559
|
+
{ readOnly: true }
|
|
4560
|
+
);
|
|
4561
|
+
} catch {
|
|
4562
|
+
this.analyticsStore = null;
|
|
4563
|
+
}
|
|
4564
|
+
} else {
|
|
4565
|
+
this.analyticsStore = new EventStore(
|
|
4566
|
+
path.join(storagePath, "analytics.duckdb"),
|
|
4567
|
+
{ readOnly: false }
|
|
4568
|
+
);
|
|
4569
|
+
}
|
|
3395
4570
|
this.vectorStore = new VectorStore(path.join(storagePath, "vectors"));
|
|
3396
4571
|
this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
|
|
3397
4572
|
this.matcher = getDefaultMatcher();
|
|
3398
4573
|
this.retriever = createRetriever(
|
|
3399
|
-
this.
|
|
4574
|
+
this.sqliteStore,
|
|
4575
|
+
// Interface compatible
|
|
3400
4576
|
this.vectorStore,
|
|
3401
4577
|
this.embedder,
|
|
3402
4578
|
this.matcher
|
|
3403
4579
|
);
|
|
3404
|
-
this.graduation = createGraduationPipeline(this.
|
|
4580
|
+
this.graduation = createGraduationPipeline(this.sqliteStore);
|
|
3405
4581
|
}
|
|
3406
4582
|
/**
|
|
3407
4583
|
* Initialize all components
|
|
@@ -3409,22 +4585,45 @@ var MemoryService = class {
|
|
|
3409
4585
|
async initialize() {
|
|
3410
4586
|
if (this.initialized)
|
|
3411
4587
|
return;
|
|
3412
|
-
await this.
|
|
4588
|
+
await this.sqliteStore.initialize();
|
|
4589
|
+
if (this.analyticsStore) {
|
|
4590
|
+
try {
|
|
4591
|
+
await this.analyticsStore.initialize();
|
|
4592
|
+
} catch (error) {
|
|
4593
|
+
console.warn("[MemoryService] Analytics store (DuckDB) initialization failed, using SQLite for reads:", error);
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
3413
4596
|
await this.vectorStore.initialize();
|
|
3414
4597
|
await this.embedder.initialize();
|
|
3415
|
-
this.
|
|
3416
|
-
this.
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
this.
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
4598
|
+
if (!this.readOnly) {
|
|
4599
|
+
this.vectorWorker = createVectorWorker(
|
|
4600
|
+
this.sqliteStore,
|
|
4601
|
+
this.vectorStore,
|
|
4602
|
+
this.embedder
|
|
4603
|
+
);
|
|
4604
|
+
this.vectorWorker.start();
|
|
4605
|
+
this.retriever.setGraduationPipeline(this.graduation);
|
|
4606
|
+
this.graduationWorker = createGraduationWorker(
|
|
4607
|
+
this.sqliteStore,
|
|
4608
|
+
this.graduation
|
|
4609
|
+
);
|
|
4610
|
+
this.graduationWorker.start();
|
|
4611
|
+
if (this.analyticsStore) {
|
|
4612
|
+
this.syncWorker = new SyncWorker(
|
|
4613
|
+
this.sqliteStore,
|
|
4614
|
+
this.analyticsStore,
|
|
4615
|
+
{ intervalMs: 3e4, batchSize: 500 }
|
|
4616
|
+
);
|
|
4617
|
+
this.syncWorker.start();
|
|
4618
|
+
}
|
|
4619
|
+
const savedMode = await this.sqliteStore.getEndlessConfig("mode");
|
|
4620
|
+
if (savedMode === "endless") {
|
|
4621
|
+
this.endlessMode = "endless";
|
|
4622
|
+
await this.initializeEndlessMode();
|
|
4623
|
+
}
|
|
4624
|
+
if (this.sharedStoreConfig?.enabled !== false) {
|
|
4625
|
+
await this.initializeSharedStore();
|
|
4626
|
+
}
|
|
3428
4627
|
}
|
|
3429
4628
|
this.initialized = true;
|
|
3430
4629
|
}
|
|
@@ -3458,7 +4657,7 @@ var MemoryService = class {
|
|
|
3458
4657
|
*/
|
|
3459
4658
|
async startSession(sessionId, projectPath) {
|
|
3460
4659
|
await this.initialize();
|
|
3461
|
-
await this.
|
|
4660
|
+
await this.sqliteStore.upsertSession({
|
|
3462
4661
|
id: sessionId,
|
|
3463
4662
|
startedAt: /* @__PURE__ */ new Date(),
|
|
3464
4663
|
projectPath
|
|
@@ -3469,7 +4668,7 @@ var MemoryService = class {
|
|
|
3469
4668
|
*/
|
|
3470
4669
|
async endSession(sessionId, summary) {
|
|
3471
4670
|
await this.initialize();
|
|
3472
|
-
await this.
|
|
4671
|
+
await this.sqliteStore.upsertSession({
|
|
3473
4672
|
id: sessionId,
|
|
3474
4673
|
endedAt: /* @__PURE__ */ new Date(),
|
|
3475
4674
|
summary
|
|
@@ -3480,7 +4679,7 @@ var MemoryService = class {
|
|
|
3480
4679
|
*/
|
|
3481
4680
|
async storeUserPrompt(sessionId, content, metadata) {
|
|
3482
4681
|
await this.initialize();
|
|
3483
|
-
const result = await this.
|
|
4682
|
+
const result = await this.sqliteStore.append({
|
|
3484
4683
|
eventType: "user_prompt",
|
|
3485
4684
|
sessionId,
|
|
3486
4685
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -3488,7 +4687,7 @@ var MemoryService = class {
|
|
|
3488
4687
|
metadata
|
|
3489
4688
|
});
|
|
3490
4689
|
if (result.success && !result.isDuplicate) {
|
|
3491
|
-
await this.
|
|
4690
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
|
|
3492
4691
|
}
|
|
3493
4692
|
return result;
|
|
3494
4693
|
}
|
|
@@ -3497,7 +4696,7 @@ var MemoryService = class {
|
|
|
3497
4696
|
*/
|
|
3498
4697
|
async storeAgentResponse(sessionId, content, metadata) {
|
|
3499
4698
|
await this.initialize();
|
|
3500
|
-
const result = await this.
|
|
4699
|
+
const result = await this.sqliteStore.append({
|
|
3501
4700
|
eventType: "agent_response",
|
|
3502
4701
|
sessionId,
|
|
3503
4702
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -3505,7 +4704,7 @@ var MemoryService = class {
|
|
|
3505
4704
|
metadata
|
|
3506
4705
|
});
|
|
3507
4706
|
if (result.success && !result.isDuplicate) {
|
|
3508
|
-
await this.
|
|
4707
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
|
|
3509
4708
|
}
|
|
3510
4709
|
return result;
|
|
3511
4710
|
}
|
|
@@ -3514,14 +4713,14 @@ var MemoryService = class {
|
|
|
3514
4713
|
*/
|
|
3515
4714
|
async storeSessionSummary(sessionId, summary) {
|
|
3516
4715
|
await this.initialize();
|
|
3517
|
-
const result = await this.
|
|
4716
|
+
const result = await this.sqliteStore.append({
|
|
3518
4717
|
eventType: "session_summary",
|
|
3519
4718
|
sessionId,
|
|
3520
4719
|
timestamp: /* @__PURE__ */ new Date(),
|
|
3521
4720
|
content: summary
|
|
3522
4721
|
});
|
|
3523
4722
|
if (result.success && !result.isDuplicate) {
|
|
3524
|
-
await this.
|
|
4723
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, summary);
|
|
3525
4724
|
}
|
|
3526
4725
|
return result;
|
|
3527
4726
|
}
|
|
@@ -3531,7 +4730,7 @@ var MemoryService = class {
|
|
|
3531
4730
|
async storeToolObservation(sessionId, payload) {
|
|
3532
4731
|
await this.initialize();
|
|
3533
4732
|
const content = JSON.stringify(payload);
|
|
3534
|
-
const result = await this.
|
|
4733
|
+
const result = await this.sqliteStore.append({
|
|
3535
4734
|
eventType: "tool_observation",
|
|
3536
4735
|
sessionId,
|
|
3537
4736
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -3547,7 +4746,7 @@ var MemoryService = class {
|
|
|
3547
4746
|
payload.metadata || {},
|
|
3548
4747
|
payload.success
|
|
3549
4748
|
);
|
|
3550
|
-
await this.
|
|
4749
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, embeddingContent);
|
|
3551
4750
|
}
|
|
3552
4751
|
return result;
|
|
3553
4752
|
}
|
|
@@ -3573,21 +4772,21 @@ var MemoryService = class {
|
|
|
3573
4772
|
*/
|
|
3574
4773
|
async getSessionHistory(sessionId) {
|
|
3575
4774
|
await this.initialize();
|
|
3576
|
-
return this.
|
|
4775
|
+
return this.sqliteStore.getSessionEvents(sessionId);
|
|
3577
4776
|
}
|
|
3578
4777
|
/**
|
|
3579
4778
|
* Get recent events
|
|
3580
4779
|
*/
|
|
3581
4780
|
async getRecentEvents(limit = 100) {
|
|
3582
4781
|
await this.initialize();
|
|
3583
|
-
return this.
|
|
4782
|
+
return this.sqliteStore.getRecentEvents(limit);
|
|
3584
4783
|
}
|
|
3585
4784
|
/**
|
|
3586
4785
|
* Get memory statistics
|
|
3587
4786
|
*/
|
|
3588
4787
|
async getStats() {
|
|
3589
4788
|
await this.initialize();
|
|
3590
|
-
const recentEvents = await this.
|
|
4789
|
+
const recentEvents = await this.sqliteStore.getRecentEvents(1e4);
|
|
3591
4790
|
const vectorCount = await this.vectorStore.count();
|
|
3592
4791
|
const levelStats = await this.graduation.getStats();
|
|
3593
4792
|
return {
|
|
@@ -3605,6 +4804,20 @@ var MemoryService = class {
|
|
|
3605
4804
|
}
|
|
3606
4805
|
return 0;
|
|
3607
4806
|
}
|
|
4807
|
+
/**
|
|
4808
|
+
* Get events by memory level
|
|
4809
|
+
*/
|
|
4810
|
+
async getEventsByLevel(level, options) {
|
|
4811
|
+
await this.initialize();
|
|
4812
|
+
return this.sqliteStore.getEventsByLevel(level, options);
|
|
4813
|
+
}
|
|
4814
|
+
/**
|
|
4815
|
+
* Get memory level for a specific event
|
|
4816
|
+
*/
|
|
4817
|
+
async getEventLevel(eventId) {
|
|
4818
|
+
await this.initialize();
|
|
4819
|
+
return this.sqliteStore.getEventLevel(eventId);
|
|
4820
|
+
}
|
|
3608
4821
|
/**
|
|
3609
4822
|
* Format retrieval results as context for Claude
|
|
3610
4823
|
*/
|
|
@@ -3697,21 +4910,21 @@ var MemoryService = class {
|
|
|
3697
4910
|
*/
|
|
3698
4911
|
async initializeEndlessMode() {
|
|
3699
4912
|
const config = await this.getEndlessConfig();
|
|
3700
|
-
this.workingSetStore = createWorkingSetStore(this.
|
|
3701
|
-
this.consolidatedStore = createConsolidatedStore(this.
|
|
4913
|
+
this.workingSetStore = createWorkingSetStore(this.sqliteStore, config);
|
|
4914
|
+
this.consolidatedStore = createConsolidatedStore(this.sqliteStore);
|
|
3702
4915
|
this.consolidationWorker = createConsolidationWorker(
|
|
3703
4916
|
this.workingSetStore,
|
|
3704
4917
|
this.consolidatedStore,
|
|
3705
4918
|
config
|
|
3706
4919
|
);
|
|
3707
|
-
this.continuityManager = createContinuityManager(this.
|
|
4920
|
+
this.continuityManager = createContinuityManager(this.sqliteStore, config);
|
|
3708
4921
|
this.consolidationWorker.start();
|
|
3709
4922
|
}
|
|
3710
4923
|
/**
|
|
3711
4924
|
* Get Endless Mode configuration
|
|
3712
4925
|
*/
|
|
3713
4926
|
async getEndlessConfig() {
|
|
3714
|
-
const savedConfig = await this.
|
|
4927
|
+
const savedConfig = await this.sqliteStore.getEndlessConfig("config");
|
|
3715
4928
|
return savedConfig || this.getDefaultEndlessConfig();
|
|
3716
4929
|
}
|
|
3717
4930
|
/**
|
|
@@ -3720,7 +4933,7 @@ var MemoryService = class {
|
|
|
3720
4933
|
async setEndlessConfig(config) {
|
|
3721
4934
|
const current = await this.getEndlessConfig();
|
|
3722
4935
|
const merged = { ...current, ...config };
|
|
3723
|
-
await this.
|
|
4936
|
+
await this.sqliteStore.setEndlessConfig("config", merged);
|
|
3724
4937
|
}
|
|
3725
4938
|
/**
|
|
3726
4939
|
* Set memory mode (session or endless)
|
|
@@ -3730,7 +4943,7 @@ var MemoryService = class {
|
|
|
3730
4943
|
if (mode === this.endlessMode)
|
|
3731
4944
|
return;
|
|
3732
4945
|
this.endlessMode = mode;
|
|
3733
|
-
await this.
|
|
4946
|
+
await this.sqliteStore.setEndlessConfig("mode", mode);
|
|
3734
4947
|
if (mode === "endless") {
|
|
3735
4948
|
await this.initializeEndlessMode();
|
|
3736
4949
|
} else {
|
|
@@ -3787,6 +5000,59 @@ var MemoryService = class {
|
|
|
3787
5000
|
return [];
|
|
3788
5001
|
return this.consolidatedStore.getAll({ limit });
|
|
3789
5002
|
}
|
|
5003
|
+
/**
|
|
5004
|
+
* Increment access count for memories that were used in prompts
|
|
5005
|
+
*/
|
|
5006
|
+
async incrementMemoryAccess(eventIds) {
|
|
5007
|
+
if (eventIds.length === 0)
|
|
5008
|
+
return;
|
|
5009
|
+
if (this.sqliteStore) {
|
|
5010
|
+
await this.sqliteStore.incrementAccessCount(eventIds);
|
|
5011
|
+
} else if (this.eventStore) {
|
|
5012
|
+
await this.eventStore.incrementAccessCount(eventIds);
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
/**
|
|
5016
|
+
* Get most accessed memories from events
|
|
5017
|
+
*/
|
|
5018
|
+
async getMostAccessedMemories(limit = 10) {
|
|
5019
|
+
console.log("[getMostAccessedMemories] sqliteStore available:", !!this.sqliteStore);
|
|
5020
|
+
if (this.sqliteStore) {
|
|
5021
|
+
const events = await this.sqliteStore.getMostAccessed(limit);
|
|
5022
|
+
console.log("[getMostAccessedMemories] Got events from SQLite:", events.length);
|
|
5023
|
+
return events.map((event) => ({
|
|
5024
|
+
memoryId: event.id,
|
|
5025
|
+
summary: event.content.substring(0, 200) + (event.content.length > 200 ? "..." : ""),
|
|
5026
|
+
topics: [],
|
|
5027
|
+
// Could extract topics from content if needed
|
|
5028
|
+
accessCount: event.access_count || 0,
|
|
5029
|
+
lastAccessed: event.last_accessed_at || null,
|
|
5030
|
+
confidence: 1,
|
|
5031
|
+
createdAt: event.timestamp
|
|
5032
|
+
}));
|
|
5033
|
+
}
|
|
5034
|
+
if (this.consolidatedStore) {
|
|
5035
|
+
const consolidated = await this.consolidatedStore.getMostAccessed(limit);
|
|
5036
|
+
return consolidated.map((m) => ({
|
|
5037
|
+
memoryId: m.memoryId,
|
|
5038
|
+
summary: m.summary,
|
|
5039
|
+
topics: m.topics,
|
|
5040
|
+
accessCount: m.accessCount,
|
|
5041
|
+
lastAccessed: m.accessedAt,
|
|
5042
|
+
confidence: m.confidence,
|
|
5043
|
+
createdAt: m.createdAt
|
|
5044
|
+
}));
|
|
5045
|
+
}
|
|
5046
|
+
return [];
|
|
5047
|
+
}
|
|
5048
|
+
/**
|
|
5049
|
+
* Mark a consolidated memory as accessed
|
|
5050
|
+
*/
|
|
5051
|
+
async markMemoryAccessed(memoryId) {
|
|
5052
|
+
if (!this.consolidatedStore)
|
|
5053
|
+
return;
|
|
5054
|
+
await this.consolidatedStore.markAccessed(memoryId);
|
|
5055
|
+
}
|
|
3790
5056
|
/**
|
|
3791
5057
|
* Calculate continuity score for current context
|
|
3792
5058
|
*/
|
|
@@ -3874,20 +5140,44 @@ var MemoryService = class {
|
|
|
3874
5140
|
}
|
|
3875
5141
|
return parts.join("\n");
|
|
3876
5142
|
}
|
|
5143
|
+
/**
|
|
5144
|
+
* Force a graduation evaluation run
|
|
5145
|
+
*/
|
|
5146
|
+
async forceGraduation() {
|
|
5147
|
+
if (!this.graduationWorker) {
|
|
5148
|
+
return { evaluated: 0, graduated: 0, byLevel: {} };
|
|
5149
|
+
}
|
|
5150
|
+
return this.graduationWorker.forceRun();
|
|
5151
|
+
}
|
|
5152
|
+
/**
|
|
5153
|
+
* Record access to a memory event (for graduation scoring)
|
|
5154
|
+
*/
|
|
5155
|
+
recordMemoryAccess(eventId, sessionId, confidence = 1) {
|
|
5156
|
+
this.graduation.recordAccess(eventId, sessionId, confidence);
|
|
5157
|
+
}
|
|
3877
5158
|
/**
|
|
3878
5159
|
* Shutdown service
|
|
3879
5160
|
*/
|
|
3880
5161
|
async shutdown() {
|
|
5162
|
+
if (this.graduationWorker) {
|
|
5163
|
+
this.graduationWorker.stop();
|
|
5164
|
+
}
|
|
3881
5165
|
if (this.consolidationWorker) {
|
|
3882
5166
|
this.consolidationWorker.stop();
|
|
3883
5167
|
}
|
|
3884
5168
|
if (this.vectorWorker) {
|
|
3885
5169
|
this.vectorWorker.stop();
|
|
3886
5170
|
}
|
|
5171
|
+
if (this.syncWorker) {
|
|
5172
|
+
this.syncWorker.stop();
|
|
5173
|
+
}
|
|
3887
5174
|
if (this.sharedEventStore) {
|
|
3888
5175
|
await this.sharedEventStore.close();
|
|
3889
5176
|
}
|
|
3890
|
-
await this.
|
|
5177
|
+
await this.sqliteStore.close();
|
|
5178
|
+
if (this.analyticsStore) {
|
|
5179
|
+
await this.analyticsStore.close();
|
|
5180
|
+
}
|
|
3891
5181
|
}
|
|
3892
5182
|
/**
|
|
3893
5183
|
* Expand ~ to home directory
|
|
@@ -3900,14 +5190,15 @@ var MemoryService = class {
|
|
|
3900
5190
|
}
|
|
3901
5191
|
};
|
|
3902
5192
|
var serviceCache = /* @__PURE__ */ new Map();
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
5193
|
+
function getReadOnlyMemoryService() {
|
|
5194
|
+
return new MemoryService({
|
|
5195
|
+
storagePath: "~/.claude-code/memory",
|
|
5196
|
+
readOnly: true,
|
|
5197
|
+
analyticsEnabled: false,
|
|
5198
|
+
// Use SQLite for reads (WAL supports concurrent readers)
|
|
5199
|
+
sharedStoreConfig: { enabled: false }
|
|
5200
|
+
// Skip shared store for now
|
|
5201
|
+
});
|
|
3911
5202
|
}
|
|
3912
5203
|
function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
|
|
3913
5204
|
const hash = hashProjectPath(projectPath);
|
|
@@ -3916,7 +5207,10 @@ function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
|
|
|
3916
5207
|
serviceCache.set(hash, new MemoryService({
|
|
3917
5208
|
storagePath,
|
|
3918
5209
|
projectHash: hash,
|
|
3919
|
-
|
|
5210
|
+
// Override shared store config - hooks don't need DuckDB
|
|
5211
|
+
sharedStoreConfig: sharedStoreConfig ?? { enabled: false },
|
|
5212
|
+
analyticsEnabled: false
|
|
5213
|
+
// Hooks don't need DuckDB
|
|
3920
5214
|
}));
|
|
3921
5215
|
}
|
|
3922
5216
|
return serviceCache.get(hash);
|
|
@@ -3927,8 +5221,8 @@ var sessionsRouter = new Hono();
|
|
|
3927
5221
|
sessionsRouter.get("/", async (c) => {
|
|
3928
5222
|
const page = parseInt(c.req.query("page") || "1", 10);
|
|
3929
5223
|
const pageSize = parseInt(c.req.query("pageSize") || "20", 10);
|
|
5224
|
+
const memoryService = getReadOnlyMemoryService();
|
|
3930
5225
|
try {
|
|
3931
|
-
const memoryService = getDefaultMemoryService();
|
|
3932
5226
|
await memoryService.initialize();
|
|
3933
5227
|
const recentEvents = await memoryService.getRecentEvents(1e3);
|
|
3934
5228
|
const sessionMap = /* @__PURE__ */ new Map();
|
|
@@ -3965,12 +5259,14 @@ sessionsRouter.get("/", async (c) => {
|
|
|
3965
5259
|
});
|
|
3966
5260
|
} catch (error) {
|
|
3967
5261
|
return c.json({ error: error.message }, 500);
|
|
5262
|
+
} finally {
|
|
5263
|
+
await memoryService.shutdown();
|
|
3968
5264
|
}
|
|
3969
5265
|
});
|
|
3970
5266
|
sessionsRouter.get("/:id", async (c) => {
|
|
3971
5267
|
const { id } = c.req.param();
|
|
5268
|
+
const memoryService = getReadOnlyMemoryService();
|
|
3972
5269
|
try {
|
|
3973
|
-
const memoryService = getDefaultMemoryService();
|
|
3974
5270
|
await memoryService.initialize();
|
|
3975
5271
|
const events = await memoryService.getSessionHistory(id);
|
|
3976
5272
|
if (events.length === 0) {
|
|
@@ -3999,6 +5295,8 @@ sessionsRouter.get("/:id", async (c) => {
|
|
|
3999
5295
|
});
|
|
4000
5296
|
} catch (error) {
|
|
4001
5297
|
return c.json({ error: error.message }, 500);
|
|
5298
|
+
} finally {
|
|
5299
|
+
await memoryService.shutdown();
|
|
4002
5300
|
}
|
|
4003
5301
|
});
|
|
4004
5302
|
|
|
@@ -4010,8 +5308,8 @@ eventsRouter.get("/", async (c) => {
|
|
|
4010
5308
|
const eventType = c.req.query("type");
|
|
4011
5309
|
const limit = parseInt(c.req.query("limit") || "100", 10);
|
|
4012
5310
|
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
5311
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4013
5312
|
try {
|
|
4014
|
-
const memoryService = getDefaultMemoryService();
|
|
4015
5313
|
await memoryService.initialize();
|
|
4016
5314
|
let events = await memoryService.getRecentEvents(limit + offset + 1e3);
|
|
4017
5315
|
if (sessionId) {
|
|
@@ -4038,12 +5336,14 @@ eventsRouter.get("/", async (c) => {
|
|
|
4038
5336
|
});
|
|
4039
5337
|
} catch (error) {
|
|
4040
5338
|
return c.json({ error: error.message }, 500);
|
|
5339
|
+
} finally {
|
|
5340
|
+
await memoryService.shutdown();
|
|
4041
5341
|
}
|
|
4042
5342
|
});
|
|
4043
5343
|
eventsRouter.get("/:id", async (c) => {
|
|
4044
5344
|
const { id } = c.req.param();
|
|
5345
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4045
5346
|
try {
|
|
4046
|
-
const memoryService = getDefaultMemoryService();
|
|
4047
5347
|
await memoryService.initialize();
|
|
4048
5348
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4049
5349
|
const event = recentEvents.find((e) => e.id === id);
|
|
@@ -4073,6 +5373,8 @@ eventsRouter.get("/:id", async (c) => {
|
|
|
4073
5373
|
});
|
|
4074
5374
|
} catch (error) {
|
|
4075
5375
|
return c.json({ error: error.message }, 500);
|
|
5376
|
+
} finally {
|
|
5377
|
+
await memoryService.shutdown();
|
|
4076
5378
|
}
|
|
4077
5379
|
});
|
|
4078
5380
|
|
|
@@ -4080,12 +5382,12 @@ eventsRouter.get("/:id", async (c) => {
|
|
|
4080
5382
|
import { Hono as Hono3 } from "hono";
|
|
4081
5383
|
var searchRouter = new Hono3();
|
|
4082
5384
|
searchRouter.post("/", async (c) => {
|
|
5385
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4083
5386
|
try {
|
|
4084
5387
|
const body = await c.req.json();
|
|
4085
5388
|
if (!body.query) {
|
|
4086
5389
|
return c.json({ error: "Query is required" }, 400);
|
|
4087
5390
|
}
|
|
4088
|
-
const memoryService = getDefaultMemoryService();
|
|
4089
5391
|
await memoryService.initialize();
|
|
4090
5392
|
const startTime = Date.now();
|
|
4091
5393
|
const result = await memoryService.retrieveMemories(body.query, {
|
|
@@ -4114,6 +5416,8 @@ searchRouter.post("/", async (c) => {
|
|
|
4114
5416
|
});
|
|
4115
5417
|
} catch (error) {
|
|
4116
5418
|
return c.json({ error: error.message }, 500);
|
|
5419
|
+
} finally {
|
|
5420
|
+
await memoryService.shutdown();
|
|
4117
5421
|
}
|
|
4118
5422
|
});
|
|
4119
5423
|
searchRouter.get("/", async (c) => {
|
|
@@ -4122,8 +5426,8 @@ searchRouter.get("/", async (c) => {
|
|
|
4122
5426
|
return c.json({ error: 'Query parameter "q" is required' }, 400);
|
|
4123
5427
|
}
|
|
4124
5428
|
const topK = parseInt(c.req.query("topK") || "5", 10);
|
|
5429
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4125
5430
|
try {
|
|
4126
|
-
const memoryService = getDefaultMemoryService();
|
|
4127
5431
|
await memoryService.initialize();
|
|
4128
5432
|
const result = await memoryService.retrieveMemories(query, { topK });
|
|
4129
5433
|
return c.json({
|
|
@@ -4141,6 +5445,8 @@ searchRouter.get("/", async (c) => {
|
|
|
4141
5445
|
});
|
|
4142
5446
|
} catch (error) {
|
|
4143
5447
|
return c.json({ error: error.message }, 500);
|
|
5448
|
+
} finally {
|
|
5449
|
+
await memoryService.shutdown();
|
|
4144
5450
|
}
|
|
4145
5451
|
});
|
|
4146
5452
|
|
|
@@ -4148,8 +5454,8 @@ searchRouter.get("/", async (c) => {
|
|
|
4148
5454
|
import { Hono as Hono4 } from "hono";
|
|
4149
5455
|
var statsRouter = new Hono4();
|
|
4150
5456
|
statsRouter.get("/shared", async (c) => {
|
|
5457
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4151
5458
|
try {
|
|
4152
|
-
const memoryService = getDefaultMemoryService();
|
|
4153
5459
|
await memoryService.initialize();
|
|
4154
5460
|
const sharedStats = await memoryService.getSharedStoreStats();
|
|
4155
5461
|
return c.json({
|
|
@@ -4167,12 +5473,14 @@ statsRouter.get("/shared", async (c) => {
|
|
|
4167
5473
|
totalUsageCount: 0,
|
|
4168
5474
|
lastUpdated: null
|
|
4169
5475
|
});
|
|
5476
|
+
} finally {
|
|
5477
|
+
await memoryService.shutdown();
|
|
4170
5478
|
}
|
|
4171
5479
|
});
|
|
4172
5480
|
statsRouter.get("/endless", async (c) => {
|
|
5481
|
+
const projectPath = c.req.query("project") || process.cwd();
|
|
5482
|
+
const memoryService = getMemoryServiceForProject(projectPath);
|
|
4173
5483
|
try {
|
|
4174
|
-
const projectPath = c.req.query("project") || process.cwd();
|
|
4175
|
-
const memoryService = getMemoryServiceForProject(projectPath);
|
|
4176
5484
|
await memoryService.initialize();
|
|
4177
5485
|
const status = await memoryService.getEndlessModeStatus();
|
|
4178
5486
|
return c.json({
|
|
@@ -4190,11 +5498,68 @@ statsRouter.get("/endless", async (c) => {
|
|
|
4190
5498
|
consolidatedCount: 0,
|
|
4191
5499
|
lastConsolidation: null
|
|
4192
5500
|
});
|
|
5501
|
+
} finally {
|
|
5502
|
+
await memoryService.shutdown();
|
|
5503
|
+
}
|
|
5504
|
+
});
|
|
5505
|
+
statsRouter.get("/levels/:level", async (c) => {
|
|
5506
|
+
const { level } = c.req.param();
|
|
5507
|
+
const limit = parseInt(c.req.query("limit") || "20", 10);
|
|
5508
|
+
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
5509
|
+
const sort = c.req.query("sort") || "recent";
|
|
5510
|
+
const validLevels = ["L0", "L1", "L2", "L3", "L4"];
|
|
5511
|
+
if (!validLevels.includes(level)) {
|
|
5512
|
+
return c.json({ error: `Invalid level. Must be one of: ${validLevels.join(", ")}` }, 400);
|
|
5513
|
+
}
|
|
5514
|
+
const memoryService = getReadOnlyMemoryService();
|
|
5515
|
+
try {
|
|
5516
|
+
await memoryService.initialize();
|
|
5517
|
+
let events = await memoryService.getEventsByLevel(level, { limit: limit * 2, offset });
|
|
5518
|
+
const stats = await memoryService.getStats();
|
|
5519
|
+
const levelStat = stats.levelStats.find((s) => s.level === level);
|
|
5520
|
+
if (sort === "accessed") {
|
|
5521
|
+
const sqliteStore = memoryService.sqliteEventStore;
|
|
5522
|
+
if (sqliteStore) {
|
|
5523
|
+
const eventIds = events.map((e) => e.id);
|
|
5524
|
+
const accessedEvents = await sqliteStore.getMostAccessed(1e3);
|
|
5525
|
+
const accessMap = new Map(accessedEvents.map((e) => [e.id, e.access_count || 0]));
|
|
5526
|
+
events = events.map((e) => ({
|
|
5527
|
+
...e,
|
|
5528
|
+
accessCount: accessMap.get(e.id) || 0
|
|
5529
|
+
}));
|
|
5530
|
+
events.sort((a, b) => b.accessCount - a.accessCount);
|
|
5531
|
+
}
|
|
5532
|
+
} else if (sort === "oldest") {
|
|
5533
|
+
events.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
5534
|
+
} else {
|
|
5535
|
+
events.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
5536
|
+
}
|
|
5537
|
+
events = events.slice(0, limit);
|
|
5538
|
+
return c.json({
|
|
5539
|
+
level,
|
|
5540
|
+
events: events.map((e) => ({
|
|
5541
|
+
id: e.id,
|
|
5542
|
+
eventType: e.eventType,
|
|
5543
|
+
sessionId: e.sessionId,
|
|
5544
|
+
timestamp: e.timestamp.toISOString(),
|
|
5545
|
+
content: e.content.slice(0, 500) + (e.content.length > 500 ? "..." : ""),
|
|
5546
|
+
metadata: e.metadata,
|
|
5547
|
+
accessCount: e.accessCount || 0
|
|
5548
|
+
})),
|
|
5549
|
+
total: levelStat?.count || 0,
|
|
5550
|
+
limit,
|
|
5551
|
+
offset,
|
|
5552
|
+
hasMore: events.length === limit
|
|
5553
|
+
});
|
|
5554
|
+
} catch (error) {
|
|
5555
|
+
return c.json({ error: error.message }, 500);
|
|
5556
|
+
} finally {
|
|
5557
|
+
await memoryService.shutdown();
|
|
4193
5558
|
}
|
|
4194
5559
|
});
|
|
4195
5560
|
statsRouter.get("/", async (c) => {
|
|
5561
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4196
5562
|
try {
|
|
4197
|
-
const memoryService = getDefaultMemoryService();
|
|
4198
5563
|
await memoryService.initialize();
|
|
4199
5564
|
const stats = await memoryService.getStats();
|
|
4200
5565
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
@@ -4231,12 +5596,45 @@ statsRouter.get("/", async (c) => {
|
|
|
4231
5596
|
});
|
|
4232
5597
|
} catch (error) {
|
|
4233
5598
|
return c.json({ error: error.message }, 500);
|
|
5599
|
+
} finally {
|
|
5600
|
+
await memoryService.shutdown();
|
|
5601
|
+
}
|
|
5602
|
+
});
|
|
5603
|
+
statsRouter.get("/most-accessed", async (c) => {
|
|
5604
|
+
const limit = parseInt(c.req.query("limit") || "10", 10);
|
|
5605
|
+
const memoryService = getReadOnlyMemoryService();
|
|
5606
|
+
try {
|
|
5607
|
+
await memoryService.initialize();
|
|
5608
|
+
console.log("[most-accessed] Fetching most accessed memories, limit:", limit);
|
|
5609
|
+
const memories = await memoryService.getMostAccessedMemories(limit);
|
|
5610
|
+
console.log("[most-accessed] Got memories:", memories.length);
|
|
5611
|
+
return c.json({
|
|
5612
|
+
memories: memories.map((m) => ({
|
|
5613
|
+
memoryId: m.memoryId,
|
|
5614
|
+
summary: m.summary,
|
|
5615
|
+
topics: m.topics,
|
|
5616
|
+
accessCount: m.accessCount,
|
|
5617
|
+
lastAccessed: m.lastAccessed || null,
|
|
5618
|
+
confidence: m.confidence,
|
|
5619
|
+
createdAt: m.createdAt instanceof Date ? m.createdAt.toISOString() : m.createdAt
|
|
5620
|
+
})),
|
|
5621
|
+
total: memories.length
|
|
5622
|
+
});
|
|
5623
|
+
} catch (error) {
|
|
5624
|
+
console.error("[most-accessed] Error:", error);
|
|
5625
|
+
return c.json({
|
|
5626
|
+
memories: [],
|
|
5627
|
+
total: 0,
|
|
5628
|
+
error: error.message
|
|
5629
|
+
});
|
|
5630
|
+
} finally {
|
|
5631
|
+
await memoryService.shutdown();
|
|
4234
5632
|
}
|
|
4235
5633
|
});
|
|
4236
5634
|
statsRouter.get("/timeline", async (c) => {
|
|
4237
5635
|
const days = parseInt(c.req.query("days") || "7", 10);
|
|
5636
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4238
5637
|
try {
|
|
4239
|
-
const memoryService = getDefaultMemoryService();
|
|
4240
5638
|
await memoryService.initialize();
|
|
4241
5639
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4242
5640
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
@@ -4261,8 +5659,46 @@ statsRouter.get("/timeline", async (c) => {
|
|
|
4261
5659
|
});
|
|
4262
5660
|
} catch (error) {
|
|
4263
5661
|
return c.json({ error: error.message }, 500);
|
|
5662
|
+
} finally {
|
|
5663
|
+
await memoryService.shutdown();
|
|
5664
|
+
}
|
|
5665
|
+
});
|
|
5666
|
+
statsRouter.post("/graduation/run", async (c) => {
|
|
5667
|
+
const memoryService = getReadOnlyMemoryService();
|
|
5668
|
+
try {
|
|
5669
|
+
await memoryService.initialize();
|
|
5670
|
+
const result = await memoryService.forceGraduation();
|
|
5671
|
+
return c.json({
|
|
5672
|
+
success: true,
|
|
5673
|
+
evaluated: result.evaluated,
|
|
5674
|
+
graduated: result.graduated,
|
|
5675
|
+
byLevel: result.byLevel
|
|
5676
|
+
});
|
|
5677
|
+
} catch (error) {
|
|
5678
|
+
return c.json({
|
|
5679
|
+
success: false,
|
|
5680
|
+
error: error.message
|
|
5681
|
+
}, 500);
|
|
5682
|
+
} finally {
|
|
5683
|
+
await memoryService.shutdown();
|
|
4264
5684
|
}
|
|
4265
5685
|
});
|
|
5686
|
+
statsRouter.get("/graduation", async (c) => {
|
|
5687
|
+
return c.json({
|
|
5688
|
+
criteria: {
|
|
5689
|
+
L0toL1: { minAccessCount: 1, minConfidence: 0.5, minCrossSessionRefs: 0, maxAgeDays: 30 },
|
|
5690
|
+
L1toL2: { minAccessCount: 3, minConfidence: 0.7, minCrossSessionRefs: 1, maxAgeDays: 60 },
|
|
5691
|
+
L2toL3: { minAccessCount: 5, minConfidence: 0.85, minCrossSessionRefs: 2, maxAgeDays: 90 },
|
|
5692
|
+
L3toL4: { minAccessCount: 10, minConfidence: 0.92, minCrossSessionRefs: 3, maxAgeDays: 180 }
|
|
5693
|
+
},
|
|
5694
|
+
description: {
|
|
5695
|
+
accessCount: "Number of times the memory was retrieved/referenced",
|
|
5696
|
+
confidence: "Match confidence score when retrieved (0.0-1.0)",
|
|
5697
|
+
crossSessionRefs: "Number of different sessions that referenced this memory",
|
|
5698
|
+
maxAgeDays: "Maximum days since last access (prevents stale promotion)"
|
|
5699
|
+
}
|
|
5700
|
+
});
|
|
5701
|
+
});
|
|
4266
5702
|
|
|
4267
5703
|
// src/server/api/citations.ts
|
|
4268
5704
|
import { Hono as Hono5 } from "hono";
|
|
@@ -4289,8 +5725,8 @@ var citationsRouter = new Hono5();
|
|
|
4289
5725
|
citationsRouter.get("/:id", async (c) => {
|
|
4290
5726
|
const { id } = c.req.param();
|
|
4291
5727
|
const citationId = parseCitationId(id) || id;
|
|
5728
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4292
5729
|
try {
|
|
4293
|
-
const memoryService = getDefaultMemoryService();
|
|
4294
5730
|
await memoryService.initialize();
|
|
4295
5731
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4296
5732
|
const event = recentEvents.find((e) => {
|
|
@@ -4316,13 +5752,15 @@ citationsRouter.get("/:id", async (c) => {
|
|
|
4316
5752
|
});
|
|
4317
5753
|
} catch (error) {
|
|
4318
5754
|
return c.json({ error: error.message }, 500);
|
|
5755
|
+
} finally {
|
|
5756
|
+
await memoryService.shutdown();
|
|
4319
5757
|
}
|
|
4320
5758
|
});
|
|
4321
5759
|
citationsRouter.get("/:id/related", async (c) => {
|
|
4322
5760
|
const { id } = c.req.param();
|
|
4323
5761
|
const citationId = parseCitationId(id) || id;
|
|
5762
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4324
5763
|
try {
|
|
4325
|
-
const memoryService = getDefaultMemoryService();
|
|
4326
5764
|
await memoryService.initialize();
|
|
4327
5765
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4328
5766
|
const event = recentEvents.find((e) => {
|
|
@@ -4352,6 +5790,8 @@ citationsRouter.get("/:id/related", async (c) => {
|
|
|
4352
5790
|
});
|
|
4353
5791
|
} catch (error) {
|
|
4354
5792
|
return c.json({ error: error.message }, 500);
|
|
5793
|
+
} finally {
|
|
5794
|
+
await memoryService.shutdown();
|
|
4355
5795
|
}
|
|
4356
5796
|
});
|
|
4357
5797
|
|