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/index.js
CHANGED
|
@@ -9,7 +9,8 @@ const __dirname = dirname(__filename);
|
|
|
9
9
|
import { Hono as Hono7 } from "hono";
|
|
10
10
|
import { cors } from "hono/cors";
|
|
11
11
|
import { logger } from "hono/logger";
|
|
12
|
-
import {
|
|
12
|
+
import { serve } from "@hono/node-server";
|
|
13
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
13
14
|
import * as path2 from "path";
|
|
14
15
|
import * as fs2 from "fs";
|
|
15
16
|
|
|
@@ -80,7 +81,10 @@ function toDate(value) {
|
|
|
80
81
|
return new Date(value);
|
|
81
82
|
return new Date(String(value));
|
|
82
83
|
}
|
|
83
|
-
function createDatabase(path3) {
|
|
84
|
+
function createDatabase(path3, options) {
|
|
85
|
+
if (options?.readOnly) {
|
|
86
|
+
return new duckdb.Database(path3, { access_mode: "READ_ONLY" });
|
|
87
|
+
}
|
|
84
88
|
return new duckdb.Database(path3);
|
|
85
89
|
}
|
|
86
90
|
function dbRun(db, sql, params = []) {
|
|
@@ -134,18 +138,24 @@ function dbClose(db) {
|
|
|
134
138
|
|
|
135
139
|
// src/core/event-store.ts
|
|
136
140
|
var EventStore = class {
|
|
137
|
-
constructor(dbPath) {
|
|
141
|
+
constructor(dbPath, options) {
|
|
138
142
|
this.dbPath = dbPath;
|
|
139
|
-
this.
|
|
143
|
+
this.readOnly = options?.readOnly ?? false;
|
|
144
|
+
this.db = createDatabase(dbPath, { readOnly: this.readOnly });
|
|
140
145
|
}
|
|
141
146
|
db;
|
|
142
147
|
initialized = false;
|
|
148
|
+
readOnly;
|
|
143
149
|
/**
|
|
144
150
|
* Initialize database schema
|
|
145
151
|
*/
|
|
146
152
|
async initialize() {
|
|
147
153
|
if (this.initialized)
|
|
148
154
|
return;
|
|
155
|
+
if (this.readOnly) {
|
|
156
|
+
this.initialized = true;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
149
159
|
await dbRun(this.db, `
|
|
150
160
|
CREATE TABLE IF NOT EXISTS events (
|
|
151
161
|
id VARCHAR PRIMARY KEY,
|
|
@@ -622,6 +632,36 @@ var EventStore = class {
|
|
|
622
632
|
);
|
|
623
633
|
return rows;
|
|
624
634
|
}
|
|
635
|
+
/**
|
|
636
|
+
* Get events by memory level
|
|
637
|
+
*/
|
|
638
|
+
async getEventsByLevel(level, options) {
|
|
639
|
+
await this.initialize();
|
|
640
|
+
const limit = options?.limit || 50;
|
|
641
|
+
const offset = options?.offset || 0;
|
|
642
|
+
const rows = await dbAll(
|
|
643
|
+
this.db,
|
|
644
|
+
`SELECT e.* FROM events e
|
|
645
|
+
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
646
|
+
WHERE ml.level = ?
|
|
647
|
+
ORDER BY e.timestamp DESC
|
|
648
|
+
LIMIT ? OFFSET ?`,
|
|
649
|
+
[level, limit, offset]
|
|
650
|
+
);
|
|
651
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Get memory level for a specific event
|
|
655
|
+
*/
|
|
656
|
+
async getEventLevel(eventId) {
|
|
657
|
+
await this.initialize();
|
|
658
|
+
const rows = await dbAll(
|
|
659
|
+
this.db,
|
|
660
|
+
`SELECT level FROM memory_levels WHERE event_id = ?`,
|
|
661
|
+
[eventId]
|
|
662
|
+
);
|
|
663
|
+
return rows.length > 0 ? rows[0].level : null;
|
|
664
|
+
}
|
|
625
665
|
// ============================================================
|
|
626
666
|
// Endless Mode Helper Methods
|
|
627
667
|
// ============================================================
|
|
@@ -632,69 +672,1018 @@ var EventStore = class {
|
|
|
632
672
|
return this.db;
|
|
633
673
|
}
|
|
634
674
|
/**
|
|
635
|
-
* Get config value for endless mode
|
|
675
|
+
* Get config value for endless mode
|
|
676
|
+
*/
|
|
677
|
+
async getEndlessConfig(key) {
|
|
678
|
+
await this.initialize();
|
|
679
|
+
const rows = await dbAll(
|
|
680
|
+
this.db,
|
|
681
|
+
`SELECT value FROM endless_config WHERE key = ?`,
|
|
682
|
+
[key]
|
|
683
|
+
);
|
|
684
|
+
if (rows.length === 0)
|
|
685
|
+
return null;
|
|
686
|
+
return JSON.parse(rows[0].value);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Set config value for endless mode
|
|
690
|
+
*/
|
|
691
|
+
async setEndlessConfig(key, value) {
|
|
692
|
+
await this.initialize();
|
|
693
|
+
await dbRun(
|
|
694
|
+
this.db,
|
|
695
|
+
`INSERT OR REPLACE INTO endless_config (key, value, updated_at)
|
|
696
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)`,
|
|
697
|
+
[key, JSON.stringify(value)]
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Get all sessions
|
|
702
|
+
*/
|
|
703
|
+
async getAllSessions() {
|
|
704
|
+
await this.initialize();
|
|
705
|
+
const rows = await dbAll(
|
|
706
|
+
this.db,
|
|
707
|
+
`SELECT * FROM sessions ORDER BY started_at DESC`
|
|
708
|
+
);
|
|
709
|
+
return rows.map((row) => ({
|
|
710
|
+
id: row.id,
|
|
711
|
+
startedAt: toDate(row.started_at),
|
|
712
|
+
endedAt: row.ended_at ? toDate(row.ended_at) : void 0,
|
|
713
|
+
projectPath: row.project_path,
|
|
714
|
+
summary: row.summary,
|
|
715
|
+
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
716
|
+
}));
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Increment access count for events (stub for compatibility)
|
|
720
|
+
*/
|
|
721
|
+
async incrementAccessCount(eventIds) {
|
|
722
|
+
return Promise.resolve();
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Get most accessed memories (stub for compatibility)
|
|
726
|
+
*/
|
|
727
|
+
async getMostAccessed(limit = 10) {
|
|
728
|
+
return [];
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Close database connection
|
|
732
|
+
*/
|
|
733
|
+
async close() {
|
|
734
|
+
await dbClose(this.db);
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Convert database row to MemoryEvent
|
|
738
|
+
*/
|
|
739
|
+
rowToEvent(row) {
|
|
740
|
+
return {
|
|
741
|
+
id: row.id,
|
|
742
|
+
eventType: row.event_type,
|
|
743
|
+
sessionId: row.session_id,
|
|
744
|
+
timestamp: toDate(row.timestamp),
|
|
745
|
+
content: row.content,
|
|
746
|
+
canonicalKey: row.canonical_key,
|
|
747
|
+
dedupeKey: row.dedupe_key,
|
|
748
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
// src/core/sqlite-event-store.ts
|
|
754
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
755
|
+
|
|
756
|
+
// src/core/sqlite-wrapper.ts
|
|
757
|
+
import Database from "better-sqlite3";
|
|
758
|
+
function createSQLiteDatabase(path3, options) {
|
|
759
|
+
const db = new Database(path3, {
|
|
760
|
+
readonly: options?.readonly ?? false
|
|
761
|
+
});
|
|
762
|
+
if (!options?.readonly && (options?.walMode ?? true)) {
|
|
763
|
+
db.pragma("journal_mode = WAL");
|
|
764
|
+
db.pragma("synchronous = NORMAL");
|
|
765
|
+
db.pragma("busy_timeout = 5000");
|
|
766
|
+
}
|
|
767
|
+
return db;
|
|
768
|
+
}
|
|
769
|
+
function sqliteRun(db, sql, params = []) {
|
|
770
|
+
const stmt = db.prepare(sql);
|
|
771
|
+
return stmt.run(...params);
|
|
772
|
+
}
|
|
773
|
+
function sqliteAll(db, sql, params = []) {
|
|
774
|
+
const stmt = db.prepare(sql);
|
|
775
|
+
return stmt.all(...params);
|
|
776
|
+
}
|
|
777
|
+
function sqliteGet(db, sql, params = []) {
|
|
778
|
+
const stmt = db.prepare(sql);
|
|
779
|
+
return stmt.get(...params);
|
|
780
|
+
}
|
|
781
|
+
function sqliteExec(db, sql) {
|
|
782
|
+
db.exec(sql);
|
|
783
|
+
}
|
|
784
|
+
function sqliteClose(db) {
|
|
785
|
+
db.close();
|
|
786
|
+
}
|
|
787
|
+
function toDateFromSQLite(value) {
|
|
788
|
+
if (value instanceof Date)
|
|
789
|
+
return value;
|
|
790
|
+
if (typeof value === "string")
|
|
791
|
+
return new Date(value);
|
|
792
|
+
if (typeof value === "number")
|
|
793
|
+
return new Date(value);
|
|
794
|
+
return new Date(String(value));
|
|
795
|
+
}
|
|
796
|
+
function toSQLiteTimestamp(date) {
|
|
797
|
+
return date.toISOString();
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// src/core/sqlite-event-store.ts
|
|
801
|
+
var SQLiteEventStore = class {
|
|
802
|
+
constructor(dbPath, options) {
|
|
803
|
+
this.dbPath = dbPath;
|
|
804
|
+
this.readOnly = options?.readonly ?? false;
|
|
805
|
+
this.db = createSQLiteDatabase(dbPath, {
|
|
806
|
+
readonly: this.readOnly,
|
|
807
|
+
walMode: !this.readOnly
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
db;
|
|
811
|
+
initialized = false;
|
|
812
|
+
readOnly;
|
|
813
|
+
/**
|
|
814
|
+
* Initialize database schema
|
|
815
|
+
*/
|
|
816
|
+
async initialize() {
|
|
817
|
+
if (this.initialized)
|
|
818
|
+
return;
|
|
819
|
+
if (this.readOnly) {
|
|
820
|
+
this.initialized = true;
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
sqliteExec(this.db, `
|
|
824
|
+
-- L0 EventStore: Single Source of Truth (immutable, append-only)
|
|
825
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
826
|
+
id TEXT PRIMARY KEY,
|
|
827
|
+
event_type TEXT NOT NULL,
|
|
828
|
+
session_id TEXT NOT NULL,
|
|
829
|
+
timestamp TEXT NOT NULL,
|
|
830
|
+
content TEXT NOT NULL,
|
|
831
|
+
canonical_key TEXT NOT NULL,
|
|
832
|
+
dedupe_key TEXT UNIQUE,
|
|
833
|
+
metadata TEXT,
|
|
834
|
+
access_count INTEGER DEFAULT 0,
|
|
835
|
+
last_accessed_at TEXT
|
|
836
|
+
);
|
|
837
|
+
|
|
838
|
+
-- Dedup table for idempotency
|
|
839
|
+
CREATE TABLE IF NOT EXISTS event_dedup (
|
|
840
|
+
dedupe_key TEXT PRIMARY KEY,
|
|
841
|
+
event_id TEXT NOT NULL,
|
|
842
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
843
|
+
);
|
|
844
|
+
|
|
845
|
+
-- Session metadata
|
|
846
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
847
|
+
id TEXT PRIMARY KEY,
|
|
848
|
+
started_at TEXT NOT NULL,
|
|
849
|
+
ended_at TEXT,
|
|
850
|
+
project_path TEXT,
|
|
851
|
+
summary TEXT,
|
|
852
|
+
tags TEXT
|
|
853
|
+
);
|
|
854
|
+
|
|
855
|
+
-- Insights (derived data, rebuildable)
|
|
856
|
+
CREATE TABLE IF NOT EXISTS insights (
|
|
857
|
+
id TEXT PRIMARY KEY,
|
|
858
|
+
insight_type TEXT NOT NULL,
|
|
859
|
+
content TEXT NOT NULL,
|
|
860
|
+
canonical_key TEXT NOT NULL,
|
|
861
|
+
confidence REAL,
|
|
862
|
+
source_events TEXT,
|
|
863
|
+
created_at TEXT,
|
|
864
|
+
last_updated TEXT
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
-- Embedding Outbox (Single-Writer Pattern)
|
|
868
|
+
CREATE TABLE IF NOT EXISTS embedding_outbox (
|
|
869
|
+
id TEXT PRIMARY KEY,
|
|
870
|
+
event_id TEXT NOT NULL,
|
|
871
|
+
content TEXT NOT NULL,
|
|
872
|
+
status TEXT DEFAULT 'pending',
|
|
873
|
+
retry_count INTEGER DEFAULT 0,
|
|
874
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
875
|
+
processed_at TEXT,
|
|
876
|
+
error_message TEXT
|
|
877
|
+
);
|
|
878
|
+
|
|
879
|
+
-- Projection offset tracking
|
|
880
|
+
CREATE TABLE IF NOT EXISTS projection_offsets (
|
|
881
|
+
projection_name TEXT PRIMARY KEY,
|
|
882
|
+
last_event_id TEXT,
|
|
883
|
+
last_timestamp TEXT,
|
|
884
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
885
|
+
);
|
|
886
|
+
|
|
887
|
+
-- Memory level tracking
|
|
888
|
+
CREATE TABLE IF NOT EXISTS memory_levels (
|
|
889
|
+
event_id TEXT PRIMARY KEY,
|
|
890
|
+
level TEXT NOT NULL DEFAULT 'L0',
|
|
891
|
+
promoted_at TEXT DEFAULT (datetime('now'))
|
|
892
|
+
);
|
|
893
|
+
|
|
894
|
+
-- Entries (immutable memory units)
|
|
895
|
+
CREATE TABLE IF NOT EXISTS entries (
|
|
896
|
+
entry_id TEXT PRIMARY KEY,
|
|
897
|
+
created_ts TEXT NOT NULL,
|
|
898
|
+
entry_type TEXT NOT NULL,
|
|
899
|
+
title TEXT NOT NULL,
|
|
900
|
+
content_json TEXT NOT NULL,
|
|
901
|
+
stage TEXT NOT NULL DEFAULT 'raw',
|
|
902
|
+
status TEXT DEFAULT 'active',
|
|
903
|
+
superseded_by TEXT,
|
|
904
|
+
build_id TEXT,
|
|
905
|
+
evidence_json TEXT,
|
|
906
|
+
canonical_key TEXT,
|
|
907
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
908
|
+
);
|
|
909
|
+
|
|
910
|
+
-- Entities (task/condition/artifact)
|
|
911
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
912
|
+
entity_id TEXT PRIMARY KEY,
|
|
913
|
+
entity_type TEXT NOT NULL,
|
|
914
|
+
canonical_key TEXT NOT NULL,
|
|
915
|
+
title TEXT NOT NULL,
|
|
916
|
+
stage TEXT NOT NULL DEFAULT 'raw',
|
|
917
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
918
|
+
current_json TEXT NOT NULL,
|
|
919
|
+
title_norm TEXT,
|
|
920
|
+
search_text TEXT,
|
|
921
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
922
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
-- Entity aliases for canonical key lookup
|
|
926
|
+
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
927
|
+
entity_type TEXT NOT NULL,
|
|
928
|
+
canonical_key TEXT NOT NULL,
|
|
929
|
+
entity_id TEXT NOT NULL,
|
|
930
|
+
is_primary INTEGER DEFAULT 0,
|
|
931
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
932
|
+
PRIMARY KEY(entity_type, canonical_key)
|
|
933
|
+
);
|
|
934
|
+
|
|
935
|
+
-- Edges (relationships between entries/entities)
|
|
936
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
937
|
+
edge_id TEXT PRIMARY KEY,
|
|
938
|
+
src_type TEXT NOT NULL,
|
|
939
|
+
src_id TEXT NOT NULL,
|
|
940
|
+
rel_type TEXT NOT NULL,
|
|
941
|
+
dst_type TEXT NOT NULL,
|
|
942
|
+
dst_id TEXT NOT NULL,
|
|
943
|
+
meta_json TEXT,
|
|
944
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
945
|
+
);
|
|
946
|
+
|
|
947
|
+
-- Vector Outbox V2 Table
|
|
948
|
+
CREATE TABLE IF NOT EXISTS vector_outbox (
|
|
949
|
+
job_id TEXT PRIMARY KEY,
|
|
950
|
+
item_kind TEXT NOT NULL,
|
|
951
|
+
item_id TEXT NOT NULL,
|
|
952
|
+
embedding_version TEXT NOT NULL,
|
|
953
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
954
|
+
retry_count INTEGER DEFAULT 0,
|
|
955
|
+
error TEXT,
|
|
956
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
957
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
958
|
+
UNIQUE(item_kind, item_id, embedding_version)
|
|
959
|
+
);
|
|
960
|
+
|
|
961
|
+
-- Build Runs
|
|
962
|
+
CREATE TABLE IF NOT EXISTS build_runs (
|
|
963
|
+
build_id TEXT PRIMARY KEY,
|
|
964
|
+
started_at TEXT NOT NULL,
|
|
965
|
+
finished_at TEXT,
|
|
966
|
+
extractor_model TEXT NOT NULL,
|
|
967
|
+
extractor_prompt_hash TEXT NOT NULL,
|
|
968
|
+
embedder_model TEXT NOT NULL,
|
|
969
|
+
embedding_version TEXT NOT NULL,
|
|
970
|
+
idris_version TEXT NOT NULL,
|
|
971
|
+
schema_version TEXT NOT NULL,
|
|
972
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
973
|
+
error TEXT
|
|
974
|
+
);
|
|
975
|
+
|
|
976
|
+
-- Pipeline Metrics
|
|
977
|
+
CREATE TABLE IF NOT EXISTS pipeline_metrics (
|
|
978
|
+
id TEXT PRIMARY KEY,
|
|
979
|
+
ts TEXT NOT NULL,
|
|
980
|
+
stage TEXT NOT NULL,
|
|
981
|
+
latency_ms REAL NOT NULL,
|
|
982
|
+
success INTEGER NOT NULL,
|
|
983
|
+
error TEXT,
|
|
984
|
+
session_id TEXT
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
-- Working Set table (active memory window)
|
|
988
|
+
CREATE TABLE IF NOT EXISTS working_set (
|
|
989
|
+
id TEXT PRIMARY KEY,
|
|
990
|
+
event_id TEXT NOT NULL,
|
|
991
|
+
added_at TEXT DEFAULT (datetime('now')),
|
|
992
|
+
relevance_score REAL DEFAULT 1.0,
|
|
993
|
+
topics TEXT,
|
|
994
|
+
expires_at TEXT
|
|
995
|
+
);
|
|
996
|
+
|
|
997
|
+
-- Consolidated Memories table (long-term integrated memories)
|
|
998
|
+
CREATE TABLE IF NOT EXISTS consolidated_memories (
|
|
999
|
+
memory_id TEXT PRIMARY KEY,
|
|
1000
|
+
summary TEXT NOT NULL,
|
|
1001
|
+
topics TEXT,
|
|
1002
|
+
source_events TEXT,
|
|
1003
|
+
confidence REAL DEFAULT 0.5,
|
|
1004
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
1005
|
+
accessed_at TEXT,
|
|
1006
|
+
access_count INTEGER DEFAULT 0
|
|
1007
|
+
);
|
|
1008
|
+
|
|
1009
|
+
-- Continuity Log table (tracks context transitions)
|
|
1010
|
+
CREATE TABLE IF NOT EXISTS continuity_log (
|
|
1011
|
+
log_id TEXT PRIMARY KEY,
|
|
1012
|
+
from_context_id TEXT,
|
|
1013
|
+
to_context_id TEXT,
|
|
1014
|
+
continuity_score REAL,
|
|
1015
|
+
transition_type TEXT,
|
|
1016
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
1017
|
+
);
|
|
1018
|
+
|
|
1019
|
+
-- Endless Mode Config table
|
|
1020
|
+
CREATE TABLE IF NOT EXISTS endless_config (
|
|
1021
|
+
key TEXT PRIMARY KEY,
|
|
1022
|
+
value TEXT,
|
|
1023
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
1024
|
+
);
|
|
1025
|
+
|
|
1026
|
+
-- Sync position tracking (for SQLite -> DuckDB sync)
|
|
1027
|
+
CREATE TABLE IF NOT EXISTS sync_positions (
|
|
1028
|
+
target_name TEXT PRIMARY KEY,
|
|
1029
|
+
last_event_id TEXT,
|
|
1030
|
+
last_timestamp TEXT,
|
|
1031
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
1032
|
+
);
|
|
1033
|
+
|
|
1034
|
+
-- Create indexes
|
|
1035
|
+
CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
|
|
1036
|
+
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
|
|
1037
|
+
CREATE INDEX IF NOT EXISTS idx_entries_type ON entries(entry_type);
|
|
1038
|
+
CREATE INDEX IF NOT EXISTS idx_entries_stage ON entries(stage);
|
|
1039
|
+
CREATE INDEX IF NOT EXISTS idx_entries_canonical ON entries(canonical_key);
|
|
1040
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type_key ON entities(entity_type, canonical_key);
|
|
1041
|
+
CREATE INDEX IF NOT EXISTS idx_entities_status ON entities(status);
|
|
1042
|
+
CREATE INDEX IF NOT EXISTS idx_edges_src ON edges(src_id, rel_type);
|
|
1043
|
+
CREATE INDEX IF NOT EXISTS idx_edges_dst ON edges(dst_id, rel_type);
|
|
1044
|
+
CREATE INDEX IF NOT EXISTS idx_edges_rel ON edges(rel_type);
|
|
1045
|
+
CREATE INDEX IF NOT EXISTS idx_outbox_status ON vector_outbox(status);
|
|
1046
|
+
CREATE INDEX IF NOT EXISTS idx_working_set_expires ON working_set(expires_at);
|
|
1047
|
+
CREATE INDEX IF NOT EXISTS idx_working_set_relevance ON working_set(relevance_score);
|
|
1048
|
+
CREATE INDEX IF NOT EXISTS idx_consolidated_confidence ON consolidated_memories(confidence);
|
|
1049
|
+
CREATE INDEX IF NOT EXISTS idx_continuity_created ON continuity_log(created_at);
|
|
1050
|
+
CREATE INDEX IF NOT EXISTS idx_embedding_outbox_status ON embedding_outbox(status);
|
|
1051
|
+
`);
|
|
1052
|
+
const tableInfo = sqliteAll(this.db, "PRAGMA table_info(events)", []);
|
|
1053
|
+
const columnNames = tableInfo.map((col) => col.name);
|
|
1054
|
+
if (!columnNames.includes("access_count")) {
|
|
1055
|
+
try {
|
|
1056
|
+
sqliteExec(this.db, `
|
|
1057
|
+
ALTER TABLE events ADD COLUMN access_count INTEGER DEFAULT 0;
|
|
1058
|
+
`);
|
|
1059
|
+
} catch (err) {
|
|
1060
|
+
console.error("Error adding access_count column:", err);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
if (!columnNames.includes("last_accessed_at")) {
|
|
1064
|
+
try {
|
|
1065
|
+
sqliteExec(this.db, `
|
|
1066
|
+
ALTER TABLE events ADD COLUMN last_accessed_at TEXT;
|
|
1067
|
+
`);
|
|
1068
|
+
} catch (err) {
|
|
1069
|
+
console.error("Error adding last_accessed_at column:", err);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
try {
|
|
1073
|
+
sqliteExec(this.db, `
|
|
1074
|
+
CREATE INDEX IF NOT EXISTS idx_events_access_count ON events(access_count DESC);
|
|
1075
|
+
`);
|
|
1076
|
+
} catch (err) {
|
|
1077
|
+
}
|
|
1078
|
+
try {
|
|
1079
|
+
sqliteExec(this.db, `
|
|
1080
|
+
CREATE INDEX IF NOT EXISTS idx_events_last_accessed ON events(last_accessed_at DESC);
|
|
1081
|
+
`);
|
|
1082
|
+
} catch (err) {
|
|
1083
|
+
}
|
|
1084
|
+
this.initialized = true;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Append event to store (Append-only, Idempotent)
|
|
1088
|
+
*/
|
|
1089
|
+
async append(input) {
|
|
1090
|
+
await this.initialize();
|
|
1091
|
+
const canonicalKey = makeCanonicalKey(input.content);
|
|
1092
|
+
const dedupeKey = makeDedupeKey(input.content, input.sessionId);
|
|
1093
|
+
const existing = sqliteGet(
|
|
1094
|
+
this.db,
|
|
1095
|
+
`SELECT event_id FROM event_dedup WHERE dedupe_key = ?`,
|
|
1096
|
+
[dedupeKey]
|
|
1097
|
+
);
|
|
1098
|
+
if (existing) {
|
|
1099
|
+
return {
|
|
1100
|
+
success: true,
|
|
1101
|
+
eventId: existing.event_id,
|
|
1102
|
+
isDuplicate: true
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
const id = randomUUID2();
|
|
1106
|
+
const timestamp = toSQLiteTimestamp(input.timestamp);
|
|
1107
|
+
try {
|
|
1108
|
+
const insertEvent = this.db.prepare(`
|
|
1109
|
+
INSERT INTO events (id, event_type, session_id, timestamp, content, canonical_key, dedupe_key, metadata)
|
|
1110
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1111
|
+
`);
|
|
1112
|
+
const insertDedup = this.db.prepare(`
|
|
1113
|
+
INSERT INTO event_dedup (dedupe_key, event_id) VALUES (?, ?)
|
|
1114
|
+
`);
|
|
1115
|
+
const insertLevel = this.db.prepare(`
|
|
1116
|
+
INSERT INTO memory_levels (event_id, level) VALUES (?, 'L0')
|
|
1117
|
+
`);
|
|
1118
|
+
const transaction = this.db.transaction(() => {
|
|
1119
|
+
insertEvent.run(
|
|
1120
|
+
id,
|
|
1121
|
+
input.eventType,
|
|
1122
|
+
input.sessionId,
|
|
1123
|
+
timestamp,
|
|
1124
|
+
input.content,
|
|
1125
|
+
canonicalKey,
|
|
1126
|
+
dedupeKey,
|
|
1127
|
+
JSON.stringify(input.metadata || {})
|
|
1128
|
+
);
|
|
1129
|
+
insertDedup.run(dedupeKey, id);
|
|
1130
|
+
insertLevel.run(id);
|
|
1131
|
+
});
|
|
1132
|
+
transaction();
|
|
1133
|
+
return { success: true, eventId: id, isDuplicate: false };
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
return {
|
|
1136
|
+
success: false,
|
|
1137
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Get events by session ID
|
|
1143
|
+
*/
|
|
1144
|
+
async getSessionEvents(sessionId) {
|
|
1145
|
+
await this.initialize();
|
|
1146
|
+
const rows = sqliteAll(
|
|
1147
|
+
this.db,
|
|
1148
|
+
`SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
|
|
1149
|
+
[sessionId]
|
|
1150
|
+
);
|
|
1151
|
+
return rows.map(this.rowToEvent);
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Get recent events
|
|
1155
|
+
*/
|
|
1156
|
+
async getRecentEvents(limit = 100) {
|
|
1157
|
+
await this.initialize();
|
|
1158
|
+
const rows = sqliteAll(
|
|
1159
|
+
this.db,
|
|
1160
|
+
`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
|
|
1161
|
+
[limit]
|
|
1162
|
+
);
|
|
1163
|
+
return rows.map(this.rowToEvent);
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Get event by ID
|
|
1167
|
+
*/
|
|
1168
|
+
async getEvent(id) {
|
|
1169
|
+
await this.initialize();
|
|
1170
|
+
const row = sqliteGet(
|
|
1171
|
+
this.db,
|
|
1172
|
+
`SELECT * FROM events WHERE id = ?`,
|
|
1173
|
+
[id]
|
|
1174
|
+
);
|
|
1175
|
+
if (!row)
|
|
1176
|
+
return null;
|
|
1177
|
+
return this.rowToEvent(row);
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Get events since a timestamp (for sync)
|
|
1181
|
+
*/
|
|
1182
|
+
async getEventsSince(timestamp, limit = 1e3) {
|
|
1183
|
+
await this.initialize();
|
|
1184
|
+
const rows = sqliteAll(
|
|
1185
|
+
this.db,
|
|
1186
|
+
`SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
|
|
1187
|
+
[timestamp, limit]
|
|
1188
|
+
);
|
|
1189
|
+
return rows.map(this.rowToEvent);
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Create or update session
|
|
1193
|
+
*/
|
|
1194
|
+
async upsertSession(session) {
|
|
1195
|
+
await this.initialize();
|
|
1196
|
+
const existing = sqliteGet(
|
|
1197
|
+
this.db,
|
|
1198
|
+
`SELECT id FROM sessions WHERE id = ?`,
|
|
1199
|
+
[session.id]
|
|
1200
|
+
);
|
|
1201
|
+
if (!existing) {
|
|
1202
|
+
sqliteRun(
|
|
1203
|
+
this.db,
|
|
1204
|
+
`INSERT INTO sessions (id, started_at, project_path, tags)
|
|
1205
|
+
VALUES (?, ?, ?, ?)`,
|
|
1206
|
+
[
|
|
1207
|
+
session.id,
|
|
1208
|
+
toSQLiteTimestamp(session.startedAt || /* @__PURE__ */ new Date()),
|
|
1209
|
+
session.projectPath || null,
|
|
1210
|
+
JSON.stringify(session.tags || [])
|
|
1211
|
+
]
|
|
1212
|
+
);
|
|
1213
|
+
} else {
|
|
1214
|
+
const updates = [];
|
|
1215
|
+
const values = [];
|
|
1216
|
+
if (session.endedAt) {
|
|
1217
|
+
updates.push("ended_at = ?");
|
|
1218
|
+
values.push(toSQLiteTimestamp(session.endedAt));
|
|
1219
|
+
}
|
|
1220
|
+
if (session.summary) {
|
|
1221
|
+
updates.push("summary = ?");
|
|
1222
|
+
values.push(session.summary);
|
|
1223
|
+
}
|
|
1224
|
+
if (session.tags) {
|
|
1225
|
+
updates.push("tags = ?");
|
|
1226
|
+
values.push(JSON.stringify(session.tags));
|
|
1227
|
+
}
|
|
1228
|
+
if (updates.length > 0) {
|
|
1229
|
+
values.push(session.id);
|
|
1230
|
+
sqliteRun(
|
|
1231
|
+
this.db,
|
|
1232
|
+
`UPDATE sessions SET ${updates.join(", ")} WHERE id = ?`,
|
|
1233
|
+
values
|
|
1234
|
+
);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Get session by ID
|
|
1240
|
+
*/
|
|
1241
|
+
async getSession(id) {
|
|
1242
|
+
await this.initialize();
|
|
1243
|
+
const row = sqliteGet(
|
|
1244
|
+
this.db,
|
|
1245
|
+
`SELECT * FROM sessions WHERE id = ?`,
|
|
1246
|
+
[id]
|
|
1247
|
+
);
|
|
1248
|
+
if (!row)
|
|
1249
|
+
return null;
|
|
1250
|
+
return {
|
|
1251
|
+
id: row.id,
|
|
1252
|
+
startedAt: toDateFromSQLite(row.started_at),
|
|
1253
|
+
endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
|
|
1254
|
+
projectPath: row.project_path,
|
|
1255
|
+
summary: row.summary,
|
|
1256
|
+
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Get all sessions
|
|
1261
|
+
*/
|
|
1262
|
+
async getAllSessions() {
|
|
1263
|
+
await this.initialize();
|
|
1264
|
+
const rows = sqliteAll(
|
|
1265
|
+
this.db,
|
|
1266
|
+
`SELECT * FROM sessions ORDER BY started_at DESC`
|
|
1267
|
+
);
|
|
1268
|
+
return rows.map((row) => ({
|
|
1269
|
+
id: row.id,
|
|
1270
|
+
startedAt: toDateFromSQLite(row.started_at),
|
|
1271
|
+
endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
|
|
1272
|
+
projectPath: row.project_path,
|
|
1273
|
+
summary: row.summary,
|
|
1274
|
+
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
1275
|
+
}));
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Add to embedding outbox
|
|
1279
|
+
*/
|
|
1280
|
+
async enqueueForEmbedding(eventId, content) {
|
|
1281
|
+
await this.initialize();
|
|
1282
|
+
const id = randomUUID2();
|
|
1283
|
+
sqliteRun(
|
|
1284
|
+
this.db,
|
|
1285
|
+
`INSERT INTO embedding_outbox (id, event_id, content, status, retry_count)
|
|
1286
|
+
VALUES (?, ?, ?, 'pending', 0)`,
|
|
1287
|
+
[id, eventId, content]
|
|
1288
|
+
);
|
|
1289
|
+
return id;
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Get pending outbox items
|
|
1293
|
+
*/
|
|
1294
|
+
async getPendingOutboxItems(limit = 32) {
|
|
1295
|
+
await this.initialize();
|
|
1296
|
+
const pending = sqliteAll(
|
|
1297
|
+
this.db,
|
|
1298
|
+
`SELECT * FROM embedding_outbox
|
|
1299
|
+
WHERE status = 'pending'
|
|
1300
|
+
ORDER BY created_at
|
|
1301
|
+
LIMIT ?`,
|
|
1302
|
+
[limit]
|
|
1303
|
+
);
|
|
1304
|
+
if (pending.length === 0)
|
|
1305
|
+
return [];
|
|
1306
|
+
const ids = pending.map((r) => r.id);
|
|
1307
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1308
|
+
sqliteRun(
|
|
1309
|
+
this.db,
|
|
1310
|
+
`UPDATE embedding_outbox SET status = 'processing' WHERE id IN (${placeholders})`,
|
|
1311
|
+
ids
|
|
1312
|
+
);
|
|
1313
|
+
return pending.map((row) => ({
|
|
1314
|
+
id: row.id,
|
|
1315
|
+
eventId: row.event_id,
|
|
1316
|
+
content: row.content,
|
|
1317
|
+
status: "processing",
|
|
1318
|
+
retryCount: row.retry_count,
|
|
1319
|
+
createdAt: toDateFromSQLite(row.created_at),
|
|
1320
|
+
errorMessage: row.error_message
|
|
1321
|
+
}));
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Mark outbox items as done
|
|
1325
|
+
*/
|
|
1326
|
+
async completeOutboxItems(ids) {
|
|
1327
|
+
if (ids.length === 0)
|
|
1328
|
+
return;
|
|
1329
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1330
|
+
sqliteRun(
|
|
1331
|
+
this.db,
|
|
1332
|
+
`DELETE FROM embedding_outbox WHERE id IN (${placeholders})`,
|
|
1333
|
+
ids
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Mark outbox items as failed
|
|
1338
|
+
*/
|
|
1339
|
+
async failOutboxItems(ids, error) {
|
|
1340
|
+
if (ids.length === 0)
|
|
1341
|
+
return;
|
|
1342
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1343
|
+
sqliteRun(
|
|
1344
|
+
this.db,
|
|
1345
|
+
`UPDATE embedding_outbox
|
|
1346
|
+
SET status = CASE WHEN retry_count >= 3 THEN 'failed' ELSE 'pending' END,
|
|
1347
|
+
retry_count = retry_count + 1,
|
|
1348
|
+
error_message = ?
|
|
1349
|
+
WHERE id IN (${placeholders})`,
|
|
1350
|
+
[error, ...ids]
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Update memory level
|
|
1355
|
+
*/
|
|
1356
|
+
async updateMemoryLevel(eventId, level) {
|
|
1357
|
+
await this.initialize();
|
|
1358
|
+
sqliteRun(
|
|
1359
|
+
this.db,
|
|
1360
|
+
`UPDATE memory_levels SET level = ?, promoted_at = datetime('now') WHERE event_id = ?`,
|
|
1361
|
+
[level, eventId]
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
/**
|
|
1365
|
+
* Get memory level statistics
|
|
1366
|
+
*/
|
|
1367
|
+
async getLevelStats() {
|
|
1368
|
+
await this.initialize();
|
|
1369
|
+
const rows = sqliteAll(
|
|
1370
|
+
this.db,
|
|
1371
|
+
`SELECT level, COUNT(*) as count FROM memory_levels GROUP BY level`
|
|
1372
|
+
);
|
|
1373
|
+
return rows;
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Get events by memory level
|
|
1377
|
+
*/
|
|
1378
|
+
async getEventsByLevel(level, options) {
|
|
1379
|
+
await this.initialize();
|
|
1380
|
+
const limit = options?.limit || 50;
|
|
1381
|
+
const offset = options?.offset || 0;
|
|
1382
|
+
const rows = sqliteAll(
|
|
1383
|
+
this.db,
|
|
1384
|
+
`SELECT e.* FROM events e
|
|
1385
|
+
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
1386
|
+
WHERE ml.level = ?
|
|
1387
|
+
ORDER BY e.timestamp DESC
|
|
1388
|
+
LIMIT ? OFFSET ?`,
|
|
1389
|
+
[level, limit, offset]
|
|
1390
|
+
);
|
|
1391
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Get memory level for a specific event
|
|
1395
|
+
*/
|
|
1396
|
+
async getEventLevel(eventId) {
|
|
1397
|
+
await this.initialize();
|
|
1398
|
+
const row = sqliteGet(
|
|
1399
|
+
this.db,
|
|
1400
|
+
`SELECT level FROM memory_levels WHERE event_id = ?`,
|
|
1401
|
+
[eventId]
|
|
1402
|
+
);
|
|
1403
|
+
return row ? row.level : null;
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Get sync position for a target
|
|
1407
|
+
*/
|
|
1408
|
+
async getSyncPosition(targetName) {
|
|
1409
|
+
await this.initialize();
|
|
1410
|
+
const row = sqliteGet(
|
|
1411
|
+
this.db,
|
|
1412
|
+
`SELECT last_event_id, last_timestamp FROM sync_positions WHERE target_name = ?`,
|
|
1413
|
+
[targetName]
|
|
1414
|
+
);
|
|
1415
|
+
return {
|
|
1416
|
+
lastEventId: row?.last_event_id ?? null,
|
|
1417
|
+
lastTimestamp: row?.last_timestamp ?? null
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Update sync position for a target
|
|
1422
|
+
*/
|
|
1423
|
+
async updateSyncPosition(targetName, lastEventId, lastTimestamp) {
|
|
1424
|
+
await this.initialize();
|
|
1425
|
+
sqliteRun(
|
|
1426
|
+
this.db,
|
|
1427
|
+
`INSERT OR REPLACE INTO sync_positions (target_name, last_event_id, last_timestamp, updated_at)
|
|
1428
|
+
VALUES (?, ?, ?, datetime('now'))`,
|
|
1429
|
+
[targetName, lastEventId, lastTimestamp]
|
|
1430
|
+
);
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Get config value for endless mode
|
|
1434
|
+
*/
|
|
1435
|
+
async getEndlessConfig(key) {
|
|
1436
|
+
await this.initialize();
|
|
1437
|
+
const row = sqliteGet(
|
|
1438
|
+
this.db,
|
|
1439
|
+
`SELECT value FROM endless_config WHERE key = ?`,
|
|
1440
|
+
[key]
|
|
1441
|
+
);
|
|
1442
|
+
if (!row)
|
|
1443
|
+
return null;
|
|
1444
|
+
return JSON.parse(row.value);
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Set config value for endless mode
|
|
1448
|
+
*/
|
|
1449
|
+
async setEndlessConfig(key, value) {
|
|
1450
|
+
await this.initialize();
|
|
1451
|
+
sqliteRun(
|
|
1452
|
+
this.db,
|
|
1453
|
+
`INSERT OR REPLACE INTO endless_config (key, value, updated_at)
|
|
1454
|
+
VALUES (?, ?, datetime('now'))`,
|
|
1455
|
+
[key, JSON.stringify(value)]
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Increment access count for events
|
|
1460
|
+
*/
|
|
1461
|
+
async incrementAccessCount(eventIds) {
|
|
1462
|
+
if (eventIds.length === 0 || this.readOnly)
|
|
1463
|
+
return;
|
|
1464
|
+
await this.initialize();
|
|
1465
|
+
const placeholders = eventIds.map(() => "?").join(",");
|
|
1466
|
+
const currentTime = toSQLiteTimestamp(/* @__PURE__ */ new Date());
|
|
1467
|
+
sqliteRun(
|
|
1468
|
+
this.db,
|
|
1469
|
+
`UPDATE events
|
|
1470
|
+
SET access_count = access_count + 1,
|
|
1471
|
+
last_accessed_at = ?
|
|
1472
|
+
WHERE id IN (${placeholders})`,
|
|
1473
|
+
[currentTime, ...eventIds]
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Get most accessed memories
|
|
1478
|
+
*/
|
|
1479
|
+
async getMostAccessed(limit = 10) {
|
|
1480
|
+
await this.initialize();
|
|
1481
|
+
const rows = sqliteAll(
|
|
1482
|
+
this.db,
|
|
1483
|
+
`SELECT * FROM events
|
|
1484
|
+
WHERE access_count > 0
|
|
1485
|
+
ORDER BY access_count DESC, last_accessed_at DESC
|
|
1486
|
+
LIMIT ?`,
|
|
1487
|
+
[limit]
|
|
1488
|
+
);
|
|
1489
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Get database instance for direct access
|
|
1493
|
+
*/
|
|
1494
|
+
getDatabase() {
|
|
1495
|
+
return this.db;
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Close database connection
|
|
1499
|
+
*/
|
|
1500
|
+
async close() {
|
|
1501
|
+
sqliteClose(this.db);
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Convert database row to MemoryEvent
|
|
1505
|
+
*/
|
|
1506
|
+
rowToEvent(row) {
|
|
1507
|
+
const event = {
|
|
1508
|
+
id: row.id,
|
|
1509
|
+
eventType: row.event_type,
|
|
1510
|
+
sessionId: row.session_id,
|
|
1511
|
+
timestamp: toDateFromSQLite(row.timestamp),
|
|
1512
|
+
content: row.content,
|
|
1513
|
+
canonicalKey: row.canonical_key,
|
|
1514
|
+
dedupeKey: row.dedupe_key,
|
|
1515
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
1516
|
+
};
|
|
1517
|
+
if (row.access_count !== void 0) {
|
|
1518
|
+
event.access_count = row.access_count;
|
|
1519
|
+
}
|
|
1520
|
+
if (row.last_accessed_at !== void 0) {
|
|
1521
|
+
event.last_accessed_at = row.last_accessed_at;
|
|
1522
|
+
}
|
|
1523
|
+
return event;
|
|
1524
|
+
}
|
|
1525
|
+
};
|
|
1526
|
+
|
|
1527
|
+
// src/core/sync-worker.ts
|
|
1528
|
+
var DEFAULT_CONFIG = {
|
|
1529
|
+
intervalMs: 3e4,
|
|
1530
|
+
batchSize: 500,
|
|
1531
|
+
maxRetries: 3,
|
|
1532
|
+
retryDelayMs: 5e3
|
|
1533
|
+
};
|
|
1534
|
+
var SyncWorker = class {
|
|
1535
|
+
constructor(sqliteStore, duckdbStore, config) {
|
|
1536
|
+
this.sqliteStore = sqliteStore;
|
|
1537
|
+
this.duckdbStore = duckdbStore;
|
|
1538
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
1539
|
+
}
|
|
1540
|
+
config;
|
|
1541
|
+
intervalHandle = null;
|
|
1542
|
+
running = false;
|
|
1543
|
+
stats = {
|
|
1544
|
+
lastSyncAt: null,
|
|
1545
|
+
eventsSynced: 0,
|
|
1546
|
+
sessionsSynced: 0,
|
|
1547
|
+
errors: 0,
|
|
1548
|
+
status: "idle"
|
|
1549
|
+
};
|
|
1550
|
+
/**
|
|
1551
|
+
* Start the sync worker
|
|
1552
|
+
*/
|
|
1553
|
+
start() {
|
|
1554
|
+
if (this.running)
|
|
1555
|
+
return;
|
|
1556
|
+
this.running = true;
|
|
1557
|
+
this.stats.status = "idle";
|
|
1558
|
+
this.syncNow().catch((err) => {
|
|
1559
|
+
console.error("[SyncWorker] Initial sync failed:", err);
|
|
1560
|
+
});
|
|
1561
|
+
this.intervalHandle = setInterval(() => {
|
|
1562
|
+
this.syncNow().catch((err) => {
|
|
1563
|
+
console.error("[SyncWorker] Periodic sync failed:", err);
|
|
1564
|
+
});
|
|
1565
|
+
}, this.config.intervalMs);
|
|
1566
|
+
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Stop the sync worker
|
|
1569
|
+
*/
|
|
1570
|
+
stop() {
|
|
1571
|
+
this.running = false;
|
|
1572
|
+
this.stats.status = "stopped";
|
|
1573
|
+
if (this.intervalHandle) {
|
|
1574
|
+
clearInterval(this.intervalHandle);
|
|
1575
|
+
this.intervalHandle = null;
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Trigger immediate sync
|
|
1580
|
+
*/
|
|
1581
|
+
async syncNow() {
|
|
1582
|
+
if (this.stats.status === "syncing") {
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
this.stats.status = "syncing";
|
|
1586
|
+
try {
|
|
1587
|
+
await this.syncEvents();
|
|
1588
|
+
await this.syncSessions();
|
|
1589
|
+
this.stats.lastSyncAt = /* @__PURE__ */ new Date();
|
|
1590
|
+
this.stats.status = "idle";
|
|
1591
|
+
} catch (error) {
|
|
1592
|
+
this.stats.errors++;
|
|
1593
|
+
this.stats.status = "error";
|
|
1594
|
+
throw error;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Sync events from SQLite to DuckDB
|
|
1599
|
+
*/
|
|
1600
|
+
async syncEvents() {
|
|
1601
|
+
const targetName = "duckdb_analytics";
|
|
1602
|
+
const position = await this.sqliteStore.getSyncPosition(targetName);
|
|
1603
|
+
const lastTimestamp = position.lastTimestamp || "1970-01-01T00:00:00.000Z";
|
|
1604
|
+
let hasMore = true;
|
|
1605
|
+
let totalSynced = 0;
|
|
1606
|
+
while (hasMore) {
|
|
1607
|
+
const events = await this.sqliteStore.getEventsSince(lastTimestamp, this.config.batchSize);
|
|
1608
|
+
if (events.length === 0) {
|
|
1609
|
+
hasMore = false;
|
|
1610
|
+
break;
|
|
1611
|
+
}
|
|
1612
|
+
await this.retryWithBackoff(async () => {
|
|
1613
|
+
for (const event of events) {
|
|
1614
|
+
await this.insertEventToDuckDB(event);
|
|
1615
|
+
}
|
|
1616
|
+
});
|
|
1617
|
+
totalSynced += events.length;
|
|
1618
|
+
const lastEvent = events[events.length - 1];
|
|
1619
|
+
await this.sqliteStore.updateSyncPosition(
|
|
1620
|
+
targetName,
|
|
1621
|
+
lastEvent.id,
|
|
1622
|
+
lastEvent.timestamp.toISOString()
|
|
1623
|
+
);
|
|
1624
|
+
hasMore = events.length === this.config.batchSize;
|
|
1625
|
+
}
|
|
1626
|
+
this.stats.eventsSynced += totalSynced;
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Sync sessions from SQLite to DuckDB
|
|
1630
|
+
*/
|
|
1631
|
+
async syncSessions() {
|
|
1632
|
+
const sessions = await this.sqliteStore.getAllSessions();
|
|
1633
|
+
for (const session of sessions) {
|
|
1634
|
+
await this.retryWithBackoff(async () => {
|
|
1635
|
+
await this.duckdbStore.upsertSession(session);
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
this.stats.sessionsSynced = sessions.length;
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Insert a single event into DuckDB
|
|
636
1642
|
*/
|
|
637
|
-
async
|
|
638
|
-
await this.
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
return null;
|
|
646
|
-
return JSON.parse(rows[0].value);
|
|
1643
|
+
async insertEventToDuckDB(event) {
|
|
1644
|
+
await this.duckdbStore.append({
|
|
1645
|
+
eventType: event.eventType,
|
|
1646
|
+
sessionId: event.sessionId,
|
|
1647
|
+
timestamp: event.timestamp,
|
|
1648
|
+
content: event.content,
|
|
1649
|
+
metadata: event.metadata
|
|
1650
|
+
});
|
|
647
1651
|
}
|
|
648
1652
|
/**
|
|
649
|
-
*
|
|
1653
|
+
* Retry operation with exponential backoff
|
|
650
1654
|
*/
|
|
651
|
-
async
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1655
|
+
async retryWithBackoff(fn) {
|
|
1656
|
+
let lastError = null;
|
|
1657
|
+
for (let attempt = 0; attempt < this.config.maxRetries; attempt++) {
|
|
1658
|
+
try {
|
|
1659
|
+
return await fn();
|
|
1660
|
+
} catch (error) {
|
|
1661
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1662
|
+
if (attempt < this.config.maxRetries - 1) {
|
|
1663
|
+
const delay = this.config.retryDelayMs * Math.pow(2, attempt);
|
|
1664
|
+
await this.sleep(delay);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
throw lastError;
|
|
659
1669
|
}
|
|
660
1670
|
/**
|
|
661
|
-
*
|
|
1671
|
+
* Sleep utility
|
|
662
1672
|
*/
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const rows = await dbAll(
|
|
666
|
-
this.db,
|
|
667
|
-
`SELECT * FROM sessions ORDER BY started_at DESC`
|
|
668
|
-
);
|
|
669
|
-
return rows.map((row) => ({
|
|
670
|
-
id: row.id,
|
|
671
|
-
startedAt: toDate(row.started_at),
|
|
672
|
-
endedAt: row.ended_at ? toDate(row.ended_at) : void 0,
|
|
673
|
-
projectPath: row.project_path,
|
|
674
|
-
summary: row.summary,
|
|
675
|
-
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
676
|
-
}));
|
|
1673
|
+
sleep(ms) {
|
|
1674
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
677
1675
|
}
|
|
678
1676
|
/**
|
|
679
|
-
*
|
|
1677
|
+
* Get sync statistics
|
|
680
1678
|
*/
|
|
681
|
-
|
|
682
|
-
|
|
1679
|
+
getStats() {
|
|
1680
|
+
return { ...this.stats };
|
|
683
1681
|
}
|
|
684
1682
|
/**
|
|
685
|
-
*
|
|
1683
|
+
* Check if worker is running
|
|
686
1684
|
*/
|
|
687
|
-
|
|
688
|
-
return
|
|
689
|
-
id: row.id,
|
|
690
|
-
eventType: row.event_type,
|
|
691
|
-
sessionId: row.session_id,
|
|
692
|
-
timestamp: toDate(row.timestamp),
|
|
693
|
-
content: row.content,
|
|
694
|
-
canonicalKey: row.canonical_key,
|
|
695
|
-
dedupeKey: row.dedupe_key,
|
|
696
|
-
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
697
|
-
};
|
|
1685
|
+
isRunning() {
|
|
1686
|
+
return this.running;
|
|
698
1687
|
}
|
|
699
1688
|
};
|
|
700
1689
|
|
|
@@ -926,7 +1915,7 @@ function getDefaultEmbedder() {
|
|
|
926
1915
|
}
|
|
927
1916
|
|
|
928
1917
|
// src/core/vector-outbox.ts
|
|
929
|
-
var
|
|
1918
|
+
var DEFAULT_CONFIG2 = {
|
|
930
1919
|
embeddingVersion: "v1",
|
|
931
1920
|
maxRetries: 3,
|
|
932
1921
|
stuckThresholdMs: 5 * 60 * 1e3,
|
|
@@ -935,7 +1924,7 @@ var DEFAULT_CONFIG = {
|
|
|
935
1924
|
};
|
|
936
1925
|
|
|
937
1926
|
// src/core/vector-worker.ts
|
|
938
|
-
var
|
|
1927
|
+
var DEFAULT_CONFIG3 = {
|
|
939
1928
|
batchSize: 32,
|
|
940
1929
|
pollIntervalMs: 1e3,
|
|
941
1930
|
maxRetries: 3
|
|
@@ -946,12 +1935,13 @@ var VectorWorker = class {
|
|
|
946
1935
|
embedder;
|
|
947
1936
|
config;
|
|
948
1937
|
running = false;
|
|
1938
|
+
stopping = false;
|
|
949
1939
|
pollTimeout = null;
|
|
950
1940
|
constructor(eventStore, vectorStore, embedder, config = {}) {
|
|
951
1941
|
this.eventStore = eventStore;
|
|
952
1942
|
this.vectorStore = vectorStore;
|
|
953
1943
|
this.embedder = embedder;
|
|
954
|
-
this.config = { ...
|
|
1944
|
+
this.config = { ...DEFAULT_CONFIG3, ...config };
|
|
955
1945
|
}
|
|
956
1946
|
/**
|
|
957
1947
|
* Start the worker polling loop
|
|
@@ -960,6 +1950,7 @@ var VectorWorker = class {
|
|
|
960
1950
|
if (this.running)
|
|
961
1951
|
return;
|
|
962
1952
|
this.running = true;
|
|
1953
|
+
this.stopping = false;
|
|
963
1954
|
this.poll();
|
|
964
1955
|
}
|
|
965
1956
|
/**
|
|
@@ -967,6 +1958,7 @@ var VectorWorker = class {
|
|
|
967
1958
|
*/
|
|
968
1959
|
stop() {
|
|
969
1960
|
this.running = false;
|
|
1961
|
+
this.stopping = true;
|
|
970
1962
|
if (this.pollTimeout) {
|
|
971
1963
|
clearTimeout(this.pollTimeout);
|
|
972
1964
|
this.pollTimeout = null;
|
|
@@ -1016,9 +2008,15 @@ var VectorWorker = class {
|
|
|
1016
2008
|
}
|
|
1017
2009
|
return successful.length;
|
|
1018
2010
|
} catch (error) {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
2011
|
+
if (!this.stopping) {
|
|
2012
|
+
try {
|
|
2013
|
+
const allIds = items.map((i) => i.id);
|
|
2014
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2015
|
+
await this.eventStore.failOutboxItems(allIds, errorMessage);
|
|
2016
|
+
} catch (failError) {
|
|
2017
|
+
console.warn("Could not mark outbox items as failed (database may be closed)");
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
1022
2020
|
throw error;
|
|
1023
2021
|
}
|
|
1024
2022
|
}
|
|
@@ -1026,14 +2024,18 @@ var VectorWorker = class {
|
|
|
1026
2024
|
* Poll for new items
|
|
1027
2025
|
*/
|
|
1028
2026
|
async poll() {
|
|
1029
|
-
if (!this.running)
|
|
2027
|
+
if (!this.running || this.stopping)
|
|
1030
2028
|
return;
|
|
1031
2029
|
try {
|
|
1032
2030
|
await this.processBatch();
|
|
1033
2031
|
} catch (error) {
|
|
1034
|
-
|
|
2032
|
+
if (!this.stopping) {
|
|
2033
|
+
console.error("Vector worker error:", error);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
if (this.running && !this.stopping) {
|
|
2037
|
+
this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
|
|
1035
2038
|
}
|
|
1036
|
-
this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
|
|
1037
2039
|
}
|
|
1038
2040
|
/**
|
|
1039
2041
|
* Process all pending items (blocking)
|
|
@@ -1060,7 +2062,7 @@ function createVectorWorker(eventStore, vectorStore, embedder, config) {
|
|
|
1060
2062
|
}
|
|
1061
2063
|
|
|
1062
2064
|
// src/core/matcher.ts
|
|
1063
|
-
var
|
|
2065
|
+
var DEFAULT_CONFIG4 = {
|
|
1064
2066
|
weights: {
|
|
1065
2067
|
semanticSimilarity: 0.4,
|
|
1066
2068
|
ftsScore: 0.25,
|
|
@@ -1075,9 +2077,9 @@ var Matcher = class {
|
|
|
1075
2077
|
config;
|
|
1076
2078
|
constructor(config = {}) {
|
|
1077
2079
|
this.config = {
|
|
1078
|
-
...
|
|
2080
|
+
...DEFAULT_CONFIG4,
|
|
1079
2081
|
...config,
|
|
1080
|
-
weights: { ...
|
|
2082
|
+
weights: { ...DEFAULT_CONFIG4.weights, ...config.weights }
|
|
1081
2083
|
};
|
|
1082
2084
|
}
|
|
1083
2085
|
/**
|
|
@@ -1204,6 +2206,7 @@ var Retriever = class {
|
|
|
1204
2206
|
matcher;
|
|
1205
2207
|
sharedStore;
|
|
1206
2208
|
sharedVectorStore;
|
|
2209
|
+
graduation;
|
|
1207
2210
|
constructor(eventStore, vectorStore, embedder, matcher, sharedOptions) {
|
|
1208
2211
|
this.eventStore = eventStore;
|
|
1209
2212
|
this.vectorStore = vectorStore;
|
|
@@ -1212,6 +2215,12 @@ var Retriever = class {
|
|
|
1212
2215
|
this.sharedStore = sharedOptions?.sharedStore;
|
|
1213
2216
|
this.sharedVectorStore = sharedOptions?.sharedVectorStore;
|
|
1214
2217
|
}
|
|
2218
|
+
/**
|
|
2219
|
+
* Set graduation pipeline for access tracking
|
|
2220
|
+
*/
|
|
2221
|
+
setGraduationPipeline(graduation) {
|
|
2222
|
+
this.graduation = graduation;
|
|
2223
|
+
}
|
|
1215
2224
|
/**
|
|
1216
2225
|
* Set shared stores after construction
|
|
1217
2226
|
*/
|
|
@@ -1334,6 +2343,13 @@ var Retriever = class {
|
|
|
1334
2343
|
const event = await this.eventStore.getEvent(result.eventId);
|
|
1335
2344
|
if (!event)
|
|
1336
2345
|
continue;
|
|
2346
|
+
if (this.graduation) {
|
|
2347
|
+
this.graduation.recordAccess(
|
|
2348
|
+
event.id,
|
|
2349
|
+
options.sessionId || "unknown",
|
|
2350
|
+
result.score
|
|
2351
|
+
);
|
|
2352
|
+
}
|
|
1337
2353
|
let sessionContext;
|
|
1338
2354
|
if (options.includeSessionContext) {
|
|
1339
2355
|
sessionContext = await this.getSessionContext(event.sessionId, event.id);
|
|
@@ -1455,15 +2471,26 @@ var GraduationPipeline = class {
|
|
|
1455
2471
|
L3toL4: { ...DEFAULT_CRITERIA.L3toL4, ...criteria.L3toL4 }
|
|
1456
2472
|
};
|
|
1457
2473
|
}
|
|
2474
|
+
// Track which sessions have accessed each event
|
|
2475
|
+
sessionAccesses = /* @__PURE__ */ new Map();
|
|
1458
2476
|
/**
|
|
1459
2477
|
* Record an access to an event (used for graduation scoring)
|
|
1460
2478
|
*/
|
|
1461
2479
|
recordAccess(eventId, fromSessionId, confidence = 1) {
|
|
1462
2480
|
const existing = this.metrics.get(eventId);
|
|
2481
|
+
if (!this.sessionAccesses.has(eventId)) {
|
|
2482
|
+
this.sessionAccesses.set(eventId, /* @__PURE__ */ new Set());
|
|
2483
|
+
}
|
|
2484
|
+
const sessions = this.sessionAccesses.get(eventId);
|
|
2485
|
+
const isNewSession = !sessions.has(fromSessionId);
|
|
2486
|
+
sessions.add(fromSessionId);
|
|
1463
2487
|
if (existing) {
|
|
1464
2488
|
existing.accessCount++;
|
|
1465
2489
|
existing.lastAccessed = /* @__PURE__ */ new Date();
|
|
1466
2490
|
existing.confidence = Math.max(existing.confidence, confidence);
|
|
2491
|
+
if (isNewSession && sessions.size > 1) {
|
|
2492
|
+
existing.crossSessionRefs = sessions.size - 1;
|
|
2493
|
+
}
|
|
1467
2494
|
} else {
|
|
1468
2495
|
this.metrics.set(eventId, {
|
|
1469
2496
|
eventId,
|
|
@@ -1760,7 +2787,7 @@ function createSharedEventStore(dbPath) {
|
|
|
1760
2787
|
}
|
|
1761
2788
|
|
|
1762
2789
|
// src/core/shared-store.ts
|
|
1763
|
-
import { randomUUID as
|
|
2790
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1764
2791
|
var SharedStore = class {
|
|
1765
2792
|
constructor(sharedEventStore) {
|
|
1766
2793
|
this.sharedEventStore = sharedEventStore;
|
|
@@ -1772,7 +2799,7 @@ var SharedStore = class {
|
|
|
1772
2799
|
* Promote a verified troubleshooting entry to shared storage
|
|
1773
2800
|
*/
|
|
1774
2801
|
async promoteEntry(input) {
|
|
1775
|
-
const entryId =
|
|
2802
|
+
const entryId = randomUUID3();
|
|
1776
2803
|
await dbRun(
|
|
1777
2804
|
this.db,
|
|
1778
2805
|
`INSERT INTO shared_troubleshooting (
|
|
@@ -2137,7 +3164,7 @@ function createSharedVectorStore(dbPath) {
|
|
|
2137
3164
|
}
|
|
2138
3165
|
|
|
2139
3166
|
// src/core/shared-promoter.ts
|
|
2140
|
-
import { randomUUID as
|
|
3167
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
2141
3168
|
var SharedPromoter = class {
|
|
2142
3169
|
constructor(sharedStore, sharedVectorStore, embedder, config) {
|
|
2143
3170
|
this.sharedStore = sharedStore;
|
|
@@ -2205,7 +3232,7 @@ var SharedPromoter = class {
|
|
|
2205
3232
|
const embeddingContent = this.createEmbeddingContent(input);
|
|
2206
3233
|
const embedding = await this.embedder.embed(embeddingContent);
|
|
2207
3234
|
await this.sharedVectorStore.upsert({
|
|
2208
|
-
id:
|
|
3235
|
+
id: randomUUID4(),
|
|
2209
3236
|
entryId,
|
|
2210
3237
|
entryType: "troubleshooting",
|
|
2211
3238
|
content: embeddingContent,
|
|
@@ -2345,7 +3372,7 @@ function createToolObservationEmbedding(toolName, metadata, success) {
|
|
|
2345
3372
|
}
|
|
2346
3373
|
|
|
2347
3374
|
// src/core/working-set-store.ts
|
|
2348
|
-
import { randomUUID as
|
|
3375
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
2349
3376
|
var WorkingSetStore = class {
|
|
2350
3377
|
constructor(eventStore, config) {
|
|
2351
3378
|
this.eventStore = eventStore;
|
|
@@ -2366,7 +3393,7 @@ var WorkingSetStore = class {
|
|
|
2366
3393
|
`INSERT OR REPLACE INTO working_set (id, event_id, added_at, relevance_score, topics, expires_at)
|
|
2367
3394
|
VALUES (?, ?, CURRENT_TIMESTAMP, ?, ?, ?)`,
|
|
2368
3395
|
[
|
|
2369
|
-
|
|
3396
|
+
randomUUID5(),
|
|
2370
3397
|
eventId,
|
|
2371
3398
|
relevanceScore,
|
|
2372
3399
|
JSON.stringify(topics || []),
|
|
@@ -2550,7 +3577,7 @@ function createWorkingSetStore(eventStore, config) {
|
|
|
2550
3577
|
}
|
|
2551
3578
|
|
|
2552
3579
|
// src/core/consolidated-store.ts
|
|
2553
|
-
import { randomUUID as
|
|
3580
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
2554
3581
|
var ConsolidatedStore = class {
|
|
2555
3582
|
constructor(eventStore) {
|
|
2556
3583
|
this.eventStore = eventStore;
|
|
@@ -2562,7 +3589,7 @@ var ConsolidatedStore = class {
|
|
|
2562
3589
|
* Create a new consolidated memory
|
|
2563
3590
|
*/
|
|
2564
3591
|
async create(input) {
|
|
2565
|
-
const memoryId =
|
|
3592
|
+
const memoryId = randomUUID6();
|
|
2566
3593
|
await dbRun(
|
|
2567
3594
|
this.db,
|
|
2568
3595
|
`INSERT INTO consolidated_memories
|
|
@@ -3089,7 +4116,7 @@ function createConsolidationWorker(workingSetStore, consolidatedStore, config) {
|
|
|
3089
4116
|
}
|
|
3090
4117
|
|
|
3091
4118
|
// src/core/continuity-manager.ts
|
|
3092
|
-
import { randomUUID as
|
|
4119
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
3093
4120
|
var ContinuityManager = class {
|
|
3094
4121
|
constructor(eventStore, config) {
|
|
3095
4122
|
this.eventStore = eventStore;
|
|
@@ -3243,7 +4270,7 @@ var ContinuityManager = class {
|
|
|
3243
4270
|
`INSERT INTO continuity_log
|
|
3244
4271
|
(log_id, from_context_id, to_context_id, continuity_score, transition_type, created_at)
|
|
3245
4272
|
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`,
|
|
3246
|
-
[
|
|
4273
|
+
[randomUUID7(), previous.id, current.id, score, type]
|
|
3247
4274
|
);
|
|
3248
4275
|
}
|
|
3249
4276
|
/**
|
|
@@ -3351,6 +4378,127 @@ function createContinuityManager(eventStore, config) {
|
|
|
3351
4378
|
return new ContinuityManager(eventStore, config);
|
|
3352
4379
|
}
|
|
3353
4380
|
|
|
4381
|
+
// src/core/graduation-worker.ts
|
|
4382
|
+
var DEFAULT_CONFIG5 = {
|
|
4383
|
+
evaluationIntervalMs: 3e5,
|
|
4384
|
+
// 5 minutes
|
|
4385
|
+
batchSize: 50,
|
|
4386
|
+
cooldownMs: 36e5
|
|
4387
|
+
// 1 hour cooldown between evaluations
|
|
4388
|
+
};
|
|
4389
|
+
var GraduationWorker = class {
|
|
4390
|
+
constructor(eventStore, graduation, config = DEFAULT_CONFIG5) {
|
|
4391
|
+
this.eventStore = eventStore;
|
|
4392
|
+
this.graduation = graduation;
|
|
4393
|
+
this.config = config;
|
|
4394
|
+
}
|
|
4395
|
+
running = false;
|
|
4396
|
+
timeout = null;
|
|
4397
|
+
lastEvaluated = /* @__PURE__ */ new Map();
|
|
4398
|
+
/**
|
|
4399
|
+
* Start the graduation worker
|
|
4400
|
+
*/
|
|
4401
|
+
start() {
|
|
4402
|
+
if (this.running)
|
|
4403
|
+
return;
|
|
4404
|
+
this.running = true;
|
|
4405
|
+
this.scheduleNext();
|
|
4406
|
+
}
|
|
4407
|
+
/**
|
|
4408
|
+
* Stop the graduation worker
|
|
4409
|
+
*/
|
|
4410
|
+
stop() {
|
|
4411
|
+
this.running = false;
|
|
4412
|
+
if (this.timeout) {
|
|
4413
|
+
clearTimeout(this.timeout);
|
|
4414
|
+
this.timeout = null;
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
/**
|
|
4418
|
+
* Check if currently running
|
|
4419
|
+
*/
|
|
4420
|
+
isRunning() {
|
|
4421
|
+
return this.running;
|
|
4422
|
+
}
|
|
4423
|
+
/**
|
|
4424
|
+
* Force a graduation evaluation run
|
|
4425
|
+
*/
|
|
4426
|
+
async forceRun() {
|
|
4427
|
+
return await this.runGraduation();
|
|
4428
|
+
}
|
|
4429
|
+
/**
|
|
4430
|
+
* Schedule the next graduation check
|
|
4431
|
+
*/
|
|
4432
|
+
scheduleNext() {
|
|
4433
|
+
if (!this.running)
|
|
4434
|
+
return;
|
|
4435
|
+
this.timeout = setTimeout(
|
|
4436
|
+
() => this.run(),
|
|
4437
|
+
this.config.evaluationIntervalMs
|
|
4438
|
+
);
|
|
4439
|
+
}
|
|
4440
|
+
/**
|
|
4441
|
+
* Run graduation evaluation
|
|
4442
|
+
*/
|
|
4443
|
+
async run() {
|
|
4444
|
+
if (!this.running)
|
|
4445
|
+
return;
|
|
4446
|
+
try {
|
|
4447
|
+
await this.runGraduation();
|
|
4448
|
+
} catch (error) {
|
|
4449
|
+
console.error("Graduation error:", error);
|
|
4450
|
+
}
|
|
4451
|
+
this.scheduleNext();
|
|
4452
|
+
}
|
|
4453
|
+
/**
|
|
4454
|
+
* Perform graduation evaluation across all levels
|
|
4455
|
+
*/
|
|
4456
|
+
async runGraduation() {
|
|
4457
|
+
const result = {
|
|
4458
|
+
evaluated: 0,
|
|
4459
|
+
graduated: 0,
|
|
4460
|
+
byLevel: {}
|
|
4461
|
+
};
|
|
4462
|
+
const levels = ["L0", "L1", "L2", "L3"];
|
|
4463
|
+
const now = Date.now();
|
|
4464
|
+
for (const level of levels) {
|
|
4465
|
+
const events = await this.eventStore.getEventsByLevel(level, {
|
|
4466
|
+
limit: this.config.batchSize
|
|
4467
|
+
});
|
|
4468
|
+
let levelGraduated = 0;
|
|
4469
|
+
for (const event of events) {
|
|
4470
|
+
const lastEval = this.lastEvaluated.get(event.id);
|
|
4471
|
+
if (lastEval && now - lastEval < this.config.cooldownMs) {
|
|
4472
|
+
continue;
|
|
4473
|
+
}
|
|
4474
|
+
result.evaluated++;
|
|
4475
|
+
this.lastEvaluated.set(event.id, now);
|
|
4476
|
+
const gradResult = await this.graduation.evaluateGraduation(event.id, level);
|
|
4477
|
+
if (gradResult.success) {
|
|
4478
|
+
result.graduated++;
|
|
4479
|
+
levelGraduated++;
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
if (levelGraduated > 0) {
|
|
4483
|
+
result.byLevel[level] = levelGraduated;
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
4486
|
+
if (this.lastEvaluated.size > 1e3) {
|
|
4487
|
+
const entries = Array.from(this.lastEvaluated.entries());
|
|
4488
|
+
entries.sort((a, b) => b[1] - a[1]);
|
|
4489
|
+
this.lastEvaluated = new Map(entries.slice(0, 1e3));
|
|
4490
|
+
}
|
|
4491
|
+
return result;
|
|
4492
|
+
}
|
|
4493
|
+
};
|
|
4494
|
+
function createGraduationWorker(eventStore, graduation, config) {
|
|
4495
|
+
return new GraduationWorker(
|
|
4496
|
+
eventStore,
|
|
4497
|
+
graduation,
|
|
4498
|
+
{ ...DEFAULT_CONFIG5, ...config }
|
|
4499
|
+
);
|
|
4500
|
+
}
|
|
4501
|
+
|
|
3354
4502
|
// src/services/memory-service.ts
|
|
3355
4503
|
function normalizePath(projectPath) {
|
|
3356
4504
|
const expanded = projectPath.startsWith("~") ? path.join(os.homedir(), projectPath.slice(1)) : projectPath;
|
|
@@ -3371,13 +4519,18 @@ function getProjectStoragePath(projectPath) {
|
|
|
3371
4519
|
var REGISTRY_PATH = path.join(os.homedir(), ".claude-code", "memory", "session-registry.json");
|
|
3372
4520
|
var SHARED_STORAGE_PATH = path.join(os.homedir(), ".claude-code", "memory", "shared");
|
|
3373
4521
|
var MemoryService = class {
|
|
3374
|
-
|
|
4522
|
+
// Primary store: SQLite (WAL mode) - for hooks, always available
|
|
4523
|
+
sqliteStore;
|
|
4524
|
+
// Analytics store: DuckDB - for server reads (optional, synced from SQLite)
|
|
4525
|
+
analyticsStore;
|
|
4526
|
+
syncWorker = null;
|
|
3375
4527
|
vectorStore;
|
|
3376
4528
|
embedder;
|
|
3377
4529
|
matcher;
|
|
3378
4530
|
retriever;
|
|
3379
4531
|
graduation;
|
|
3380
4532
|
vectorWorker = null;
|
|
4533
|
+
graduationWorker = null;
|
|
3381
4534
|
initialized = false;
|
|
3382
4535
|
// Endless Mode components
|
|
3383
4536
|
workingSetStore = null;
|
|
@@ -3392,24 +4545,48 @@ var MemoryService = class {
|
|
|
3392
4545
|
sharedPromoter = null;
|
|
3393
4546
|
sharedStoreConfig = null;
|
|
3394
4547
|
projectHash = null;
|
|
4548
|
+
readOnly;
|
|
3395
4549
|
constructor(config) {
|
|
3396
4550
|
const storagePath = this.expandPath(config.storagePath);
|
|
3397
|
-
|
|
4551
|
+
this.readOnly = config.readOnly ?? false;
|
|
4552
|
+
if (!this.readOnly && !fs.existsSync(storagePath)) {
|
|
3398
4553
|
fs.mkdirSync(storagePath, { recursive: true });
|
|
3399
4554
|
}
|
|
3400
4555
|
this.projectHash = config.projectHash || null;
|
|
3401
4556
|
this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
|
|
3402
|
-
this.
|
|
4557
|
+
this.sqliteStore = new SQLiteEventStore(
|
|
4558
|
+
path.join(storagePath, "events.sqlite"),
|
|
4559
|
+
{ readonly: this.readOnly }
|
|
4560
|
+
);
|
|
4561
|
+
const analyticsEnabled = config.analyticsEnabled ?? this.readOnly;
|
|
4562
|
+
if (!analyticsEnabled) {
|
|
4563
|
+
this.analyticsStore = null;
|
|
4564
|
+
} else if (this.readOnly) {
|
|
4565
|
+
try {
|
|
4566
|
+
this.analyticsStore = new EventStore(
|
|
4567
|
+
path.join(storagePath, "analytics.duckdb"),
|
|
4568
|
+
{ readOnly: true }
|
|
4569
|
+
);
|
|
4570
|
+
} catch {
|
|
4571
|
+
this.analyticsStore = null;
|
|
4572
|
+
}
|
|
4573
|
+
} else {
|
|
4574
|
+
this.analyticsStore = new EventStore(
|
|
4575
|
+
path.join(storagePath, "analytics.duckdb"),
|
|
4576
|
+
{ readOnly: false }
|
|
4577
|
+
);
|
|
4578
|
+
}
|
|
3403
4579
|
this.vectorStore = new VectorStore(path.join(storagePath, "vectors"));
|
|
3404
4580
|
this.embedder = config.embeddingModel ? new Embedder(config.embeddingModel) : getDefaultEmbedder();
|
|
3405
4581
|
this.matcher = getDefaultMatcher();
|
|
3406
4582
|
this.retriever = createRetriever(
|
|
3407
|
-
this.
|
|
4583
|
+
this.sqliteStore,
|
|
4584
|
+
// Interface compatible
|
|
3408
4585
|
this.vectorStore,
|
|
3409
4586
|
this.embedder,
|
|
3410
4587
|
this.matcher
|
|
3411
4588
|
);
|
|
3412
|
-
this.graduation = createGraduationPipeline(this.
|
|
4589
|
+
this.graduation = createGraduationPipeline(this.sqliteStore);
|
|
3413
4590
|
}
|
|
3414
4591
|
/**
|
|
3415
4592
|
* Initialize all components
|
|
@@ -3417,22 +4594,45 @@ var MemoryService = class {
|
|
|
3417
4594
|
async initialize() {
|
|
3418
4595
|
if (this.initialized)
|
|
3419
4596
|
return;
|
|
3420
|
-
await this.
|
|
4597
|
+
await this.sqliteStore.initialize();
|
|
4598
|
+
if (this.analyticsStore) {
|
|
4599
|
+
try {
|
|
4600
|
+
await this.analyticsStore.initialize();
|
|
4601
|
+
} catch (error) {
|
|
4602
|
+
console.warn("[MemoryService] Analytics store (DuckDB) initialization failed, using SQLite for reads:", error);
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
3421
4605
|
await this.vectorStore.initialize();
|
|
3422
4606
|
await this.embedder.initialize();
|
|
3423
|
-
this.
|
|
3424
|
-
this.
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
this.
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
4607
|
+
if (!this.readOnly) {
|
|
4608
|
+
this.vectorWorker = createVectorWorker(
|
|
4609
|
+
this.sqliteStore,
|
|
4610
|
+
this.vectorStore,
|
|
4611
|
+
this.embedder
|
|
4612
|
+
);
|
|
4613
|
+
this.vectorWorker.start();
|
|
4614
|
+
this.retriever.setGraduationPipeline(this.graduation);
|
|
4615
|
+
this.graduationWorker = createGraduationWorker(
|
|
4616
|
+
this.sqliteStore,
|
|
4617
|
+
this.graduation
|
|
4618
|
+
);
|
|
4619
|
+
this.graduationWorker.start();
|
|
4620
|
+
if (this.analyticsStore) {
|
|
4621
|
+
this.syncWorker = new SyncWorker(
|
|
4622
|
+
this.sqliteStore,
|
|
4623
|
+
this.analyticsStore,
|
|
4624
|
+
{ intervalMs: 3e4, batchSize: 500 }
|
|
4625
|
+
);
|
|
4626
|
+
this.syncWorker.start();
|
|
4627
|
+
}
|
|
4628
|
+
const savedMode = await this.sqliteStore.getEndlessConfig("mode");
|
|
4629
|
+
if (savedMode === "endless") {
|
|
4630
|
+
this.endlessMode = "endless";
|
|
4631
|
+
await this.initializeEndlessMode();
|
|
4632
|
+
}
|
|
4633
|
+
if (this.sharedStoreConfig?.enabled !== false) {
|
|
4634
|
+
await this.initializeSharedStore();
|
|
4635
|
+
}
|
|
3436
4636
|
}
|
|
3437
4637
|
this.initialized = true;
|
|
3438
4638
|
}
|
|
@@ -3466,7 +4666,7 @@ var MemoryService = class {
|
|
|
3466
4666
|
*/
|
|
3467
4667
|
async startSession(sessionId, projectPath) {
|
|
3468
4668
|
await this.initialize();
|
|
3469
|
-
await this.
|
|
4669
|
+
await this.sqliteStore.upsertSession({
|
|
3470
4670
|
id: sessionId,
|
|
3471
4671
|
startedAt: /* @__PURE__ */ new Date(),
|
|
3472
4672
|
projectPath
|
|
@@ -3477,7 +4677,7 @@ var MemoryService = class {
|
|
|
3477
4677
|
*/
|
|
3478
4678
|
async endSession(sessionId, summary) {
|
|
3479
4679
|
await this.initialize();
|
|
3480
|
-
await this.
|
|
4680
|
+
await this.sqliteStore.upsertSession({
|
|
3481
4681
|
id: sessionId,
|
|
3482
4682
|
endedAt: /* @__PURE__ */ new Date(),
|
|
3483
4683
|
summary
|
|
@@ -3488,7 +4688,7 @@ var MemoryService = class {
|
|
|
3488
4688
|
*/
|
|
3489
4689
|
async storeUserPrompt(sessionId, content, metadata) {
|
|
3490
4690
|
await this.initialize();
|
|
3491
|
-
const result = await this.
|
|
4691
|
+
const result = await this.sqliteStore.append({
|
|
3492
4692
|
eventType: "user_prompt",
|
|
3493
4693
|
sessionId,
|
|
3494
4694
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -3496,7 +4696,7 @@ var MemoryService = class {
|
|
|
3496
4696
|
metadata
|
|
3497
4697
|
});
|
|
3498
4698
|
if (result.success && !result.isDuplicate) {
|
|
3499
|
-
await this.
|
|
4699
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
|
|
3500
4700
|
}
|
|
3501
4701
|
return result;
|
|
3502
4702
|
}
|
|
@@ -3505,7 +4705,7 @@ var MemoryService = class {
|
|
|
3505
4705
|
*/
|
|
3506
4706
|
async storeAgentResponse(sessionId, content, metadata) {
|
|
3507
4707
|
await this.initialize();
|
|
3508
|
-
const result = await this.
|
|
4708
|
+
const result = await this.sqliteStore.append({
|
|
3509
4709
|
eventType: "agent_response",
|
|
3510
4710
|
sessionId,
|
|
3511
4711
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -3513,7 +4713,7 @@ var MemoryService = class {
|
|
|
3513
4713
|
metadata
|
|
3514
4714
|
});
|
|
3515
4715
|
if (result.success && !result.isDuplicate) {
|
|
3516
|
-
await this.
|
|
4716
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, content);
|
|
3517
4717
|
}
|
|
3518
4718
|
return result;
|
|
3519
4719
|
}
|
|
@@ -3522,14 +4722,14 @@ var MemoryService = class {
|
|
|
3522
4722
|
*/
|
|
3523
4723
|
async storeSessionSummary(sessionId, summary) {
|
|
3524
4724
|
await this.initialize();
|
|
3525
|
-
const result = await this.
|
|
4725
|
+
const result = await this.sqliteStore.append({
|
|
3526
4726
|
eventType: "session_summary",
|
|
3527
4727
|
sessionId,
|
|
3528
4728
|
timestamp: /* @__PURE__ */ new Date(),
|
|
3529
4729
|
content: summary
|
|
3530
4730
|
});
|
|
3531
4731
|
if (result.success && !result.isDuplicate) {
|
|
3532
|
-
await this.
|
|
4732
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, summary);
|
|
3533
4733
|
}
|
|
3534
4734
|
return result;
|
|
3535
4735
|
}
|
|
@@ -3539,7 +4739,7 @@ var MemoryService = class {
|
|
|
3539
4739
|
async storeToolObservation(sessionId, payload) {
|
|
3540
4740
|
await this.initialize();
|
|
3541
4741
|
const content = JSON.stringify(payload);
|
|
3542
|
-
const result = await this.
|
|
4742
|
+
const result = await this.sqliteStore.append({
|
|
3543
4743
|
eventType: "tool_observation",
|
|
3544
4744
|
sessionId,
|
|
3545
4745
|
timestamp: /* @__PURE__ */ new Date(),
|
|
@@ -3555,7 +4755,7 @@ var MemoryService = class {
|
|
|
3555
4755
|
payload.metadata || {},
|
|
3556
4756
|
payload.success
|
|
3557
4757
|
);
|
|
3558
|
-
await this.
|
|
4758
|
+
await this.sqliteStore.enqueueForEmbedding(result.eventId, embeddingContent);
|
|
3559
4759
|
}
|
|
3560
4760
|
return result;
|
|
3561
4761
|
}
|
|
@@ -3581,21 +4781,21 @@ var MemoryService = class {
|
|
|
3581
4781
|
*/
|
|
3582
4782
|
async getSessionHistory(sessionId) {
|
|
3583
4783
|
await this.initialize();
|
|
3584
|
-
return this.
|
|
4784
|
+
return this.sqliteStore.getSessionEvents(sessionId);
|
|
3585
4785
|
}
|
|
3586
4786
|
/**
|
|
3587
4787
|
* Get recent events
|
|
3588
4788
|
*/
|
|
3589
4789
|
async getRecentEvents(limit = 100) {
|
|
3590
4790
|
await this.initialize();
|
|
3591
|
-
return this.
|
|
4791
|
+
return this.sqliteStore.getRecentEvents(limit);
|
|
3592
4792
|
}
|
|
3593
4793
|
/**
|
|
3594
4794
|
* Get memory statistics
|
|
3595
4795
|
*/
|
|
3596
4796
|
async getStats() {
|
|
3597
4797
|
await this.initialize();
|
|
3598
|
-
const recentEvents = await this.
|
|
4798
|
+
const recentEvents = await this.sqliteStore.getRecentEvents(1e4);
|
|
3599
4799
|
const vectorCount = await this.vectorStore.count();
|
|
3600
4800
|
const levelStats = await this.graduation.getStats();
|
|
3601
4801
|
return {
|
|
@@ -3613,6 +4813,20 @@ var MemoryService = class {
|
|
|
3613
4813
|
}
|
|
3614
4814
|
return 0;
|
|
3615
4815
|
}
|
|
4816
|
+
/**
|
|
4817
|
+
* Get events by memory level
|
|
4818
|
+
*/
|
|
4819
|
+
async getEventsByLevel(level, options) {
|
|
4820
|
+
await this.initialize();
|
|
4821
|
+
return this.sqliteStore.getEventsByLevel(level, options);
|
|
4822
|
+
}
|
|
4823
|
+
/**
|
|
4824
|
+
* Get memory level for a specific event
|
|
4825
|
+
*/
|
|
4826
|
+
async getEventLevel(eventId) {
|
|
4827
|
+
await this.initialize();
|
|
4828
|
+
return this.sqliteStore.getEventLevel(eventId);
|
|
4829
|
+
}
|
|
3616
4830
|
/**
|
|
3617
4831
|
* Format retrieval results as context for Claude
|
|
3618
4832
|
*/
|
|
@@ -3705,21 +4919,21 @@ var MemoryService = class {
|
|
|
3705
4919
|
*/
|
|
3706
4920
|
async initializeEndlessMode() {
|
|
3707
4921
|
const config = await this.getEndlessConfig();
|
|
3708
|
-
this.workingSetStore = createWorkingSetStore(this.
|
|
3709
|
-
this.consolidatedStore = createConsolidatedStore(this.
|
|
4922
|
+
this.workingSetStore = createWorkingSetStore(this.sqliteStore, config);
|
|
4923
|
+
this.consolidatedStore = createConsolidatedStore(this.sqliteStore);
|
|
3710
4924
|
this.consolidationWorker = createConsolidationWorker(
|
|
3711
4925
|
this.workingSetStore,
|
|
3712
4926
|
this.consolidatedStore,
|
|
3713
4927
|
config
|
|
3714
4928
|
);
|
|
3715
|
-
this.continuityManager = createContinuityManager(this.
|
|
4929
|
+
this.continuityManager = createContinuityManager(this.sqliteStore, config);
|
|
3716
4930
|
this.consolidationWorker.start();
|
|
3717
4931
|
}
|
|
3718
4932
|
/**
|
|
3719
4933
|
* Get Endless Mode configuration
|
|
3720
4934
|
*/
|
|
3721
4935
|
async getEndlessConfig() {
|
|
3722
|
-
const savedConfig = await this.
|
|
4936
|
+
const savedConfig = await this.sqliteStore.getEndlessConfig("config");
|
|
3723
4937
|
return savedConfig || this.getDefaultEndlessConfig();
|
|
3724
4938
|
}
|
|
3725
4939
|
/**
|
|
@@ -3728,7 +4942,7 @@ var MemoryService = class {
|
|
|
3728
4942
|
async setEndlessConfig(config) {
|
|
3729
4943
|
const current = await this.getEndlessConfig();
|
|
3730
4944
|
const merged = { ...current, ...config };
|
|
3731
|
-
await this.
|
|
4945
|
+
await this.sqliteStore.setEndlessConfig("config", merged);
|
|
3732
4946
|
}
|
|
3733
4947
|
/**
|
|
3734
4948
|
* Set memory mode (session or endless)
|
|
@@ -3738,7 +4952,7 @@ var MemoryService = class {
|
|
|
3738
4952
|
if (mode === this.endlessMode)
|
|
3739
4953
|
return;
|
|
3740
4954
|
this.endlessMode = mode;
|
|
3741
|
-
await this.
|
|
4955
|
+
await this.sqliteStore.setEndlessConfig("mode", mode);
|
|
3742
4956
|
if (mode === "endless") {
|
|
3743
4957
|
await this.initializeEndlessMode();
|
|
3744
4958
|
} else {
|
|
@@ -3795,6 +5009,59 @@ var MemoryService = class {
|
|
|
3795
5009
|
return [];
|
|
3796
5010
|
return this.consolidatedStore.getAll({ limit });
|
|
3797
5011
|
}
|
|
5012
|
+
/**
|
|
5013
|
+
* Increment access count for memories that were used in prompts
|
|
5014
|
+
*/
|
|
5015
|
+
async incrementMemoryAccess(eventIds) {
|
|
5016
|
+
if (eventIds.length === 0)
|
|
5017
|
+
return;
|
|
5018
|
+
if (this.sqliteStore) {
|
|
5019
|
+
await this.sqliteStore.incrementAccessCount(eventIds);
|
|
5020
|
+
} else if (this.eventStore) {
|
|
5021
|
+
await this.eventStore.incrementAccessCount(eventIds);
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
/**
|
|
5025
|
+
* Get most accessed memories from events
|
|
5026
|
+
*/
|
|
5027
|
+
async getMostAccessedMemories(limit = 10) {
|
|
5028
|
+
console.log("[getMostAccessedMemories] sqliteStore available:", !!this.sqliteStore);
|
|
5029
|
+
if (this.sqliteStore) {
|
|
5030
|
+
const events = await this.sqliteStore.getMostAccessed(limit);
|
|
5031
|
+
console.log("[getMostAccessedMemories] Got events from SQLite:", events.length);
|
|
5032
|
+
return events.map((event) => ({
|
|
5033
|
+
memoryId: event.id,
|
|
5034
|
+
summary: event.content.substring(0, 200) + (event.content.length > 200 ? "..." : ""),
|
|
5035
|
+
topics: [],
|
|
5036
|
+
// Could extract topics from content if needed
|
|
5037
|
+
accessCount: event.access_count || 0,
|
|
5038
|
+
lastAccessed: event.last_accessed_at || null,
|
|
5039
|
+
confidence: 1,
|
|
5040
|
+
createdAt: event.timestamp
|
|
5041
|
+
}));
|
|
5042
|
+
}
|
|
5043
|
+
if (this.consolidatedStore) {
|
|
5044
|
+
const consolidated = await this.consolidatedStore.getMostAccessed(limit);
|
|
5045
|
+
return consolidated.map((m) => ({
|
|
5046
|
+
memoryId: m.memoryId,
|
|
5047
|
+
summary: m.summary,
|
|
5048
|
+
topics: m.topics,
|
|
5049
|
+
accessCount: m.accessCount,
|
|
5050
|
+
lastAccessed: m.accessedAt,
|
|
5051
|
+
confidence: m.confidence,
|
|
5052
|
+
createdAt: m.createdAt
|
|
5053
|
+
}));
|
|
5054
|
+
}
|
|
5055
|
+
return [];
|
|
5056
|
+
}
|
|
5057
|
+
/**
|
|
5058
|
+
* Mark a consolidated memory as accessed
|
|
5059
|
+
*/
|
|
5060
|
+
async markMemoryAccessed(memoryId) {
|
|
5061
|
+
if (!this.consolidatedStore)
|
|
5062
|
+
return;
|
|
5063
|
+
await this.consolidatedStore.markAccessed(memoryId);
|
|
5064
|
+
}
|
|
3798
5065
|
/**
|
|
3799
5066
|
* Calculate continuity score for current context
|
|
3800
5067
|
*/
|
|
@@ -3882,20 +5149,44 @@ var MemoryService = class {
|
|
|
3882
5149
|
}
|
|
3883
5150
|
return parts.join("\n");
|
|
3884
5151
|
}
|
|
5152
|
+
/**
|
|
5153
|
+
* Force a graduation evaluation run
|
|
5154
|
+
*/
|
|
5155
|
+
async forceGraduation() {
|
|
5156
|
+
if (!this.graduationWorker) {
|
|
5157
|
+
return { evaluated: 0, graduated: 0, byLevel: {} };
|
|
5158
|
+
}
|
|
5159
|
+
return this.graduationWorker.forceRun();
|
|
5160
|
+
}
|
|
5161
|
+
/**
|
|
5162
|
+
* Record access to a memory event (for graduation scoring)
|
|
5163
|
+
*/
|
|
5164
|
+
recordMemoryAccess(eventId, sessionId, confidence = 1) {
|
|
5165
|
+
this.graduation.recordAccess(eventId, sessionId, confidence);
|
|
5166
|
+
}
|
|
3885
5167
|
/**
|
|
3886
5168
|
* Shutdown service
|
|
3887
5169
|
*/
|
|
3888
5170
|
async shutdown() {
|
|
5171
|
+
if (this.graduationWorker) {
|
|
5172
|
+
this.graduationWorker.stop();
|
|
5173
|
+
}
|
|
3889
5174
|
if (this.consolidationWorker) {
|
|
3890
5175
|
this.consolidationWorker.stop();
|
|
3891
5176
|
}
|
|
3892
5177
|
if (this.vectorWorker) {
|
|
3893
5178
|
this.vectorWorker.stop();
|
|
3894
5179
|
}
|
|
5180
|
+
if (this.syncWorker) {
|
|
5181
|
+
this.syncWorker.stop();
|
|
5182
|
+
}
|
|
3895
5183
|
if (this.sharedEventStore) {
|
|
3896
5184
|
await this.sharedEventStore.close();
|
|
3897
5185
|
}
|
|
3898
|
-
await this.
|
|
5186
|
+
await this.sqliteStore.close();
|
|
5187
|
+
if (this.analyticsStore) {
|
|
5188
|
+
await this.analyticsStore.close();
|
|
5189
|
+
}
|
|
3899
5190
|
}
|
|
3900
5191
|
/**
|
|
3901
5192
|
* Expand ~ to home directory
|
|
@@ -3908,14 +5199,15 @@ var MemoryService = class {
|
|
|
3908
5199
|
}
|
|
3909
5200
|
};
|
|
3910
5201
|
var serviceCache = /* @__PURE__ */ new Map();
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
5202
|
+
function getReadOnlyMemoryService() {
|
|
5203
|
+
return new MemoryService({
|
|
5204
|
+
storagePath: "~/.claude-code/memory",
|
|
5205
|
+
readOnly: true,
|
|
5206
|
+
analyticsEnabled: false,
|
|
5207
|
+
// Use SQLite for reads (WAL supports concurrent readers)
|
|
5208
|
+
sharedStoreConfig: { enabled: false }
|
|
5209
|
+
// Skip shared store for now
|
|
5210
|
+
});
|
|
3919
5211
|
}
|
|
3920
5212
|
function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
|
|
3921
5213
|
const hash = hashProjectPath(projectPath);
|
|
@@ -3924,7 +5216,10 @@ function getMemoryServiceForProject(projectPath, sharedStoreConfig) {
|
|
|
3924
5216
|
serviceCache.set(hash, new MemoryService({
|
|
3925
5217
|
storagePath,
|
|
3926
5218
|
projectHash: hash,
|
|
3927
|
-
|
|
5219
|
+
// Override shared store config - hooks don't need DuckDB
|
|
5220
|
+
sharedStoreConfig: sharedStoreConfig ?? { enabled: false },
|
|
5221
|
+
analyticsEnabled: false
|
|
5222
|
+
// Hooks don't need DuckDB
|
|
3928
5223
|
}));
|
|
3929
5224
|
}
|
|
3930
5225
|
return serviceCache.get(hash);
|
|
@@ -3935,8 +5230,8 @@ var sessionsRouter = new Hono();
|
|
|
3935
5230
|
sessionsRouter.get("/", async (c) => {
|
|
3936
5231
|
const page = parseInt(c.req.query("page") || "1", 10);
|
|
3937
5232
|
const pageSize = parseInt(c.req.query("pageSize") || "20", 10);
|
|
5233
|
+
const memoryService = getReadOnlyMemoryService();
|
|
3938
5234
|
try {
|
|
3939
|
-
const memoryService = getDefaultMemoryService();
|
|
3940
5235
|
await memoryService.initialize();
|
|
3941
5236
|
const recentEvents = await memoryService.getRecentEvents(1e3);
|
|
3942
5237
|
const sessionMap = /* @__PURE__ */ new Map();
|
|
@@ -3973,12 +5268,14 @@ sessionsRouter.get("/", async (c) => {
|
|
|
3973
5268
|
});
|
|
3974
5269
|
} catch (error) {
|
|
3975
5270
|
return c.json({ error: error.message }, 500);
|
|
5271
|
+
} finally {
|
|
5272
|
+
await memoryService.shutdown();
|
|
3976
5273
|
}
|
|
3977
5274
|
});
|
|
3978
5275
|
sessionsRouter.get("/:id", async (c) => {
|
|
3979
5276
|
const { id } = c.req.param();
|
|
5277
|
+
const memoryService = getReadOnlyMemoryService();
|
|
3980
5278
|
try {
|
|
3981
|
-
const memoryService = getDefaultMemoryService();
|
|
3982
5279
|
await memoryService.initialize();
|
|
3983
5280
|
const events = await memoryService.getSessionHistory(id);
|
|
3984
5281
|
if (events.length === 0) {
|
|
@@ -4007,6 +5304,8 @@ sessionsRouter.get("/:id", async (c) => {
|
|
|
4007
5304
|
});
|
|
4008
5305
|
} catch (error) {
|
|
4009
5306
|
return c.json({ error: error.message }, 500);
|
|
5307
|
+
} finally {
|
|
5308
|
+
await memoryService.shutdown();
|
|
4010
5309
|
}
|
|
4011
5310
|
});
|
|
4012
5311
|
|
|
@@ -4018,8 +5317,8 @@ eventsRouter.get("/", async (c) => {
|
|
|
4018
5317
|
const eventType = c.req.query("type");
|
|
4019
5318
|
const limit = parseInt(c.req.query("limit") || "100", 10);
|
|
4020
5319
|
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
5320
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4021
5321
|
try {
|
|
4022
|
-
const memoryService = getDefaultMemoryService();
|
|
4023
5322
|
await memoryService.initialize();
|
|
4024
5323
|
let events = await memoryService.getRecentEvents(limit + offset + 1e3);
|
|
4025
5324
|
if (sessionId) {
|
|
@@ -4046,12 +5345,14 @@ eventsRouter.get("/", async (c) => {
|
|
|
4046
5345
|
});
|
|
4047
5346
|
} catch (error) {
|
|
4048
5347
|
return c.json({ error: error.message }, 500);
|
|
5348
|
+
} finally {
|
|
5349
|
+
await memoryService.shutdown();
|
|
4049
5350
|
}
|
|
4050
5351
|
});
|
|
4051
5352
|
eventsRouter.get("/:id", async (c) => {
|
|
4052
5353
|
const { id } = c.req.param();
|
|
5354
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4053
5355
|
try {
|
|
4054
|
-
const memoryService = getDefaultMemoryService();
|
|
4055
5356
|
await memoryService.initialize();
|
|
4056
5357
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4057
5358
|
const event = recentEvents.find((e) => e.id === id);
|
|
@@ -4081,6 +5382,8 @@ eventsRouter.get("/:id", async (c) => {
|
|
|
4081
5382
|
});
|
|
4082
5383
|
} catch (error) {
|
|
4083
5384
|
return c.json({ error: error.message }, 500);
|
|
5385
|
+
} finally {
|
|
5386
|
+
await memoryService.shutdown();
|
|
4084
5387
|
}
|
|
4085
5388
|
});
|
|
4086
5389
|
|
|
@@ -4088,12 +5391,12 @@ eventsRouter.get("/:id", async (c) => {
|
|
|
4088
5391
|
import { Hono as Hono3 } from "hono";
|
|
4089
5392
|
var searchRouter = new Hono3();
|
|
4090
5393
|
searchRouter.post("/", async (c) => {
|
|
5394
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4091
5395
|
try {
|
|
4092
5396
|
const body = await c.req.json();
|
|
4093
5397
|
if (!body.query) {
|
|
4094
5398
|
return c.json({ error: "Query is required" }, 400);
|
|
4095
5399
|
}
|
|
4096
|
-
const memoryService = getDefaultMemoryService();
|
|
4097
5400
|
await memoryService.initialize();
|
|
4098
5401
|
const startTime = Date.now();
|
|
4099
5402
|
const result = await memoryService.retrieveMemories(body.query, {
|
|
@@ -4122,6 +5425,8 @@ searchRouter.post("/", async (c) => {
|
|
|
4122
5425
|
});
|
|
4123
5426
|
} catch (error) {
|
|
4124
5427
|
return c.json({ error: error.message }, 500);
|
|
5428
|
+
} finally {
|
|
5429
|
+
await memoryService.shutdown();
|
|
4125
5430
|
}
|
|
4126
5431
|
});
|
|
4127
5432
|
searchRouter.get("/", async (c) => {
|
|
@@ -4130,8 +5435,8 @@ searchRouter.get("/", async (c) => {
|
|
|
4130
5435
|
return c.json({ error: 'Query parameter "q" is required' }, 400);
|
|
4131
5436
|
}
|
|
4132
5437
|
const topK = parseInt(c.req.query("topK") || "5", 10);
|
|
5438
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4133
5439
|
try {
|
|
4134
|
-
const memoryService = getDefaultMemoryService();
|
|
4135
5440
|
await memoryService.initialize();
|
|
4136
5441
|
const result = await memoryService.retrieveMemories(query, { topK });
|
|
4137
5442
|
return c.json({
|
|
@@ -4149,6 +5454,8 @@ searchRouter.get("/", async (c) => {
|
|
|
4149
5454
|
});
|
|
4150
5455
|
} catch (error) {
|
|
4151
5456
|
return c.json({ error: error.message }, 500);
|
|
5457
|
+
} finally {
|
|
5458
|
+
await memoryService.shutdown();
|
|
4152
5459
|
}
|
|
4153
5460
|
});
|
|
4154
5461
|
|
|
@@ -4156,8 +5463,8 @@ searchRouter.get("/", async (c) => {
|
|
|
4156
5463
|
import { Hono as Hono4 } from "hono";
|
|
4157
5464
|
var statsRouter = new Hono4();
|
|
4158
5465
|
statsRouter.get("/shared", async (c) => {
|
|
5466
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4159
5467
|
try {
|
|
4160
|
-
const memoryService = getDefaultMemoryService();
|
|
4161
5468
|
await memoryService.initialize();
|
|
4162
5469
|
const sharedStats = await memoryService.getSharedStoreStats();
|
|
4163
5470
|
return c.json({
|
|
@@ -4175,12 +5482,14 @@ statsRouter.get("/shared", async (c) => {
|
|
|
4175
5482
|
totalUsageCount: 0,
|
|
4176
5483
|
lastUpdated: null
|
|
4177
5484
|
});
|
|
5485
|
+
} finally {
|
|
5486
|
+
await memoryService.shutdown();
|
|
4178
5487
|
}
|
|
4179
5488
|
});
|
|
4180
5489
|
statsRouter.get("/endless", async (c) => {
|
|
5490
|
+
const projectPath = c.req.query("project") || process.cwd();
|
|
5491
|
+
const memoryService = getMemoryServiceForProject(projectPath);
|
|
4181
5492
|
try {
|
|
4182
|
-
const projectPath = c.req.query("project") || process.cwd();
|
|
4183
|
-
const memoryService = getMemoryServiceForProject(projectPath);
|
|
4184
5493
|
await memoryService.initialize();
|
|
4185
5494
|
const status = await memoryService.getEndlessModeStatus();
|
|
4186
5495
|
return c.json({
|
|
@@ -4198,11 +5507,68 @@ statsRouter.get("/endless", async (c) => {
|
|
|
4198
5507
|
consolidatedCount: 0,
|
|
4199
5508
|
lastConsolidation: null
|
|
4200
5509
|
});
|
|
5510
|
+
} finally {
|
|
5511
|
+
await memoryService.shutdown();
|
|
5512
|
+
}
|
|
5513
|
+
});
|
|
5514
|
+
statsRouter.get("/levels/:level", async (c) => {
|
|
5515
|
+
const { level } = c.req.param();
|
|
5516
|
+
const limit = parseInt(c.req.query("limit") || "20", 10);
|
|
5517
|
+
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
5518
|
+
const sort = c.req.query("sort") || "recent";
|
|
5519
|
+
const validLevels = ["L0", "L1", "L2", "L3", "L4"];
|
|
5520
|
+
if (!validLevels.includes(level)) {
|
|
5521
|
+
return c.json({ error: `Invalid level. Must be one of: ${validLevels.join(", ")}` }, 400);
|
|
5522
|
+
}
|
|
5523
|
+
const memoryService = getReadOnlyMemoryService();
|
|
5524
|
+
try {
|
|
5525
|
+
await memoryService.initialize();
|
|
5526
|
+
let events = await memoryService.getEventsByLevel(level, { limit: limit * 2, offset });
|
|
5527
|
+
const stats = await memoryService.getStats();
|
|
5528
|
+
const levelStat = stats.levelStats.find((s) => s.level === level);
|
|
5529
|
+
if (sort === "accessed") {
|
|
5530
|
+
const sqliteStore = memoryService.sqliteEventStore;
|
|
5531
|
+
if (sqliteStore) {
|
|
5532
|
+
const eventIds = events.map((e) => e.id);
|
|
5533
|
+
const accessedEvents = await sqliteStore.getMostAccessed(1e3);
|
|
5534
|
+
const accessMap = new Map(accessedEvents.map((e) => [e.id, e.access_count || 0]));
|
|
5535
|
+
events = events.map((e) => ({
|
|
5536
|
+
...e,
|
|
5537
|
+
accessCount: accessMap.get(e.id) || 0
|
|
5538
|
+
}));
|
|
5539
|
+
events.sort((a, b) => b.accessCount - a.accessCount);
|
|
5540
|
+
}
|
|
5541
|
+
} else if (sort === "oldest") {
|
|
5542
|
+
events.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
5543
|
+
} else {
|
|
5544
|
+
events.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
5545
|
+
}
|
|
5546
|
+
events = events.slice(0, limit);
|
|
5547
|
+
return c.json({
|
|
5548
|
+
level,
|
|
5549
|
+
events: events.map((e) => ({
|
|
5550
|
+
id: e.id,
|
|
5551
|
+
eventType: e.eventType,
|
|
5552
|
+
sessionId: e.sessionId,
|
|
5553
|
+
timestamp: e.timestamp.toISOString(),
|
|
5554
|
+
content: e.content.slice(0, 500) + (e.content.length > 500 ? "..." : ""),
|
|
5555
|
+
metadata: e.metadata,
|
|
5556
|
+
accessCount: e.accessCount || 0
|
|
5557
|
+
})),
|
|
5558
|
+
total: levelStat?.count || 0,
|
|
5559
|
+
limit,
|
|
5560
|
+
offset,
|
|
5561
|
+
hasMore: events.length === limit
|
|
5562
|
+
});
|
|
5563
|
+
} catch (error) {
|
|
5564
|
+
return c.json({ error: error.message }, 500);
|
|
5565
|
+
} finally {
|
|
5566
|
+
await memoryService.shutdown();
|
|
4201
5567
|
}
|
|
4202
5568
|
});
|
|
4203
5569
|
statsRouter.get("/", async (c) => {
|
|
5570
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4204
5571
|
try {
|
|
4205
|
-
const memoryService = getDefaultMemoryService();
|
|
4206
5572
|
await memoryService.initialize();
|
|
4207
5573
|
const stats = await memoryService.getStats();
|
|
4208
5574
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
@@ -4239,12 +5605,45 @@ statsRouter.get("/", async (c) => {
|
|
|
4239
5605
|
});
|
|
4240
5606
|
} catch (error) {
|
|
4241
5607
|
return c.json({ error: error.message }, 500);
|
|
5608
|
+
} finally {
|
|
5609
|
+
await memoryService.shutdown();
|
|
5610
|
+
}
|
|
5611
|
+
});
|
|
5612
|
+
statsRouter.get("/most-accessed", async (c) => {
|
|
5613
|
+
const limit = parseInt(c.req.query("limit") || "10", 10);
|
|
5614
|
+
const memoryService = getReadOnlyMemoryService();
|
|
5615
|
+
try {
|
|
5616
|
+
await memoryService.initialize();
|
|
5617
|
+
console.log("[most-accessed] Fetching most accessed memories, limit:", limit);
|
|
5618
|
+
const memories = await memoryService.getMostAccessedMemories(limit);
|
|
5619
|
+
console.log("[most-accessed] Got memories:", memories.length);
|
|
5620
|
+
return c.json({
|
|
5621
|
+
memories: memories.map((m) => ({
|
|
5622
|
+
memoryId: m.memoryId,
|
|
5623
|
+
summary: m.summary,
|
|
5624
|
+
topics: m.topics,
|
|
5625
|
+
accessCount: m.accessCount,
|
|
5626
|
+
lastAccessed: m.lastAccessed || null,
|
|
5627
|
+
confidence: m.confidence,
|
|
5628
|
+
createdAt: m.createdAt instanceof Date ? m.createdAt.toISOString() : m.createdAt
|
|
5629
|
+
})),
|
|
5630
|
+
total: memories.length
|
|
5631
|
+
});
|
|
5632
|
+
} catch (error) {
|
|
5633
|
+
console.error("[most-accessed] Error:", error);
|
|
5634
|
+
return c.json({
|
|
5635
|
+
memories: [],
|
|
5636
|
+
total: 0,
|
|
5637
|
+
error: error.message
|
|
5638
|
+
});
|
|
5639
|
+
} finally {
|
|
5640
|
+
await memoryService.shutdown();
|
|
4242
5641
|
}
|
|
4243
5642
|
});
|
|
4244
5643
|
statsRouter.get("/timeline", async (c) => {
|
|
4245
5644
|
const days = parseInt(c.req.query("days") || "7", 10);
|
|
5645
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4246
5646
|
try {
|
|
4247
|
-
const memoryService = getDefaultMemoryService();
|
|
4248
5647
|
await memoryService.initialize();
|
|
4249
5648
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4250
5649
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
@@ -4269,8 +5668,46 @@ statsRouter.get("/timeline", async (c) => {
|
|
|
4269
5668
|
});
|
|
4270
5669
|
} catch (error) {
|
|
4271
5670
|
return c.json({ error: error.message }, 500);
|
|
5671
|
+
} finally {
|
|
5672
|
+
await memoryService.shutdown();
|
|
5673
|
+
}
|
|
5674
|
+
});
|
|
5675
|
+
statsRouter.post("/graduation/run", async (c) => {
|
|
5676
|
+
const memoryService = getReadOnlyMemoryService();
|
|
5677
|
+
try {
|
|
5678
|
+
await memoryService.initialize();
|
|
5679
|
+
const result = await memoryService.forceGraduation();
|
|
5680
|
+
return c.json({
|
|
5681
|
+
success: true,
|
|
5682
|
+
evaluated: result.evaluated,
|
|
5683
|
+
graduated: result.graduated,
|
|
5684
|
+
byLevel: result.byLevel
|
|
5685
|
+
});
|
|
5686
|
+
} catch (error) {
|
|
5687
|
+
return c.json({
|
|
5688
|
+
success: false,
|
|
5689
|
+
error: error.message
|
|
5690
|
+
}, 500);
|
|
5691
|
+
} finally {
|
|
5692
|
+
await memoryService.shutdown();
|
|
4272
5693
|
}
|
|
4273
5694
|
});
|
|
5695
|
+
statsRouter.get("/graduation", async (c) => {
|
|
5696
|
+
return c.json({
|
|
5697
|
+
criteria: {
|
|
5698
|
+
L0toL1: { minAccessCount: 1, minConfidence: 0.5, minCrossSessionRefs: 0, maxAgeDays: 30 },
|
|
5699
|
+
L1toL2: { minAccessCount: 3, minConfidence: 0.7, minCrossSessionRefs: 1, maxAgeDays: 60 },
|
|
5700
|
+
L2toL3: { minAccessCount: 5, minConfidence: 0.85, minCrossSessionRefs: 2, maxAgeDays: 90 },
|
|
5701
|
+
L3toL4: { minAccessCount: 10, minConfidence: 0.92, minCrossSessionRefs: 3, maxAgeDays: 180 }
|
|
5702
|
+
},
|
|
5703
|
+
description: {
|
|
5704
|
+
accessCount: "Number of times the memory was retrieved/referenced",
|
|
5705
|
+
confidence: "Match confidence score when retrieved (0.0-1.0)",
|
|
5706
|
+
crossSessionRefs: "Number of different sessions that referenced this memory",
|
|
5707
|
+
maxAgeDays: "Maximum days since last access (prevents stale promotion)"
|
|
5708
|
+
}
|
|
5709
|
+
});
|
|
5710
|
+
});
|
|
4274
5711
|
|
|
4275
5712
|
// src/server/api/citations.ts
|
|
4276
5713
|
import { Hono as Hono5 } from "hono";
|
|
@@ -4297,8 +5734,8 @@ var citationsRouter = new Hono5();
|
|
|
4297
5734
|
citationsRouter.get("/:id", async (c) => {
|
|
4298
5735
|
const { id } = c.req.param();
|
|
4299
5736
|
const citationId = parseCitationId(id) || id;
|
|
5737
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4300
5738
|
try {
|
|
4301
|
-
const memoryService = getDefaultMemoryService();
|
|
4302
5739
|
await memoryService.initialize();
|
|
4303
5740
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4304
5741
|
const event = recentEvents.find((e) => {
|
|
@@ -4324,13 +5761,15 @@ citationsRouter.get("/:id", async (c) => {
|
|
|
4324
5761
|
});
|
|
4325
5762
|
} catch (error) {
|
|
4326
5763
|
return c.json({ error: error.message }, 500);
|
|
5764
|
+
} finally {
|
|
5765
|
+
await memoryService.shutdown();
|
|
4327
5766
|
}
|
|
4328
5767
|
});
|
|
4329
5768
|
citationsRouter.get("/:id/related", async (c) => {
|
|
4330
5769
|
const { id } = c.req.param();
|
|
4331
5770
|
const citationId = parseCitationId(id) || id;
|
|
5771
|
+
const memoryService = getReadOnlyMemoryService();
|
|
4332
5772
|
try {
|
|
4333
|
-
const memoryService = getDefaultMemoryService();
|
|
4334
5773
|
await memoryService.initialize();
|
|
4335
5774
|
const recentEvents = await memoryService.getRecentEvents(1e4);
|
|
4336
5775
|
const event = recentEvents.find((e) => {
|
|
@@ -4360,6 +5799,8 @@ citationsRouter.get("/:id/related", async (c) => {
|
|
|
4360
5799
|
});
|
|
4361
5800
|
} catch (error) {
|
|
4362
5801
|
return c.json({ error: error.message }, 500);
|
|
5802
|
+
} finally {
|
|
5803
|
+
await memoryService.shutdown();
|
|
4363
5804
|
}
|
|
4364
5805
|
});
|
|
4365
5806
|
|
|
@@ -4372,7 +5813,7 @@ app.use("/*", cors());
|
|
|
4372
5813
|
app.use("/*", logger());
|
|
4373
5814
|
app.route("/api", apiRouter);
|
|
4374
5815
|
app.get("/health", (c) => c.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
4375
|
-
var uiPath = path2.join(
|
|
5816
|
+
var uiPath = path2.join(__dirname, "../../dist/ui");
|
|
4376
5817
|
if (fs2.existsSync(uiPath)) {
|
|
4377
5818
|
app.use("/*", serveStatic({ root: uiPath }));
|
|
4378
5819
|
}
|
|
@@ -4388,17 +5829,17 @@ function startServer(port = 37777) {
|
|
|
4388
5829
|
if (serverInstance) {
|
|
4389
5830
|
return serverInstance;
|
|
4390
5831
|
}
|
|
4391
|
-
serverInstance =
|
|
4392
|
-
|
|
5832
|
+
serverInstance = serve({
|
|
5833
|
+
fetch: app.fetch,
|
|
4393
5834
|
port,
|
|
4394
|
-
|
|
5835
|
+
hostname: "127.0.0.1"
|
|
4395
5836
|
});
|
|
4396
5837
|
console.log(`\u{1F9E0} Code Memory viewer started at http://localhost:${port}`);
|
|
4397
5838
|
return serverInstance;
|
|
4398
5839
|
}
|
|
4399
5840
|
function stopServer() {
|
|
4400
5841
|
if (serverInstance) {
|
|
4401
|
-
serverInstance.
|
|
5842
|
+
serverInstance.close();
|
|
4402
5843
|
serverInstance = null;
|
|
4403
5844
|
}
|
|
4404
5845
|
}
|
|
@@ -4410,7 +5851,8 @@ async function isServerRunning(port = 37777) {
|
|
|
4410
5851
|
return false;
|
|
4411
5852
|
}
|
|
4412
5853
|
}
|
|
4413
|
-
|
|
5854
|
+
var isMainModule = process.argv[1]?.includes("server/index") || process.argv[1]?.endsWith("server.js");
|
|
5855
|
+
if (isMainModule) {
|
|
4414
5856
|
const port = parseInt(process.env.PORT || "37777", 10);
|
|
4415
5857
|
startServer(port);
|
|
4416
5858
|
}
|