@bip-krishna/agentmind 0.1.0
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/.agentmind/memory.db +0 -0
- package/.context-memory/memory.db +0 -0
- package/README.md +176 -0
- package/apps/dashboard/src/server.ts +369 -0
- package/cli/src/index.ts +163 -0
- package/dist/apps/dashboard/src/server.d.ts +6 -0
- package/dist/apps/dashboard/src/server.d.ts.map +1 -0
- package/dist/apps/dashboard/src/server.js +360 -0
- package/dist/apps/dashboard/src/server.js.map +1 -0
- package/dist/cli/src/index.d.ts +3 -0
- package/dist/cli/src/index.d.ts.map +1 -0
- package/dist/cli/src/index.js +162 -0
- package/dist/cli/src/index.js.map +1 -0
- package/dist/examples/advanced.d.ts +2 -0
- package/dist/examples/advanced.d.ts.map +1 -0
- package/dist/examples/advanced.js +41 -0
- package/dist/examples/advanced.js.map +1 -0
- package/dist/examples/basic.d.ts +2 -0
- package/dist/examples/basic.d.ts.map +1 -0
- package/dist/examples/basic.js +22 -0
- package/dist/examples/basic.js.map +1 -0
- package/dist/packages/core/src/consolidation.d.ts +27 -0
- package/dist/packages/core/src/consolidation.d.ts.map +1 -0
- package/dist/packages/core/src/consolidation.js +97 -0
- package/dist/packages/core/src/consolidation.js.map +1 -0
- package/dist/packages/core/src/events.d.ts +18 -0
- package/dist/packages/core/src/events.d.ts.map +1 -0
- package/dist/packages/core/src/events.js +13 -0
- package/dist/packages/core/src/events.js.map +1 -0
- package/dist/packages/core/src/extraction.d.ts +32 -0
- package/dist/packages/core/src/extraction.d.ts.map +1 -0
- package/dist/packages/core/src/extraction.js +65 -0
- package/dist/packages/core/src/extraction.js.map +1 -0
- package/dist/packages/core/src/index.d.ts +10 -0
- package/dist/packages/core/src/index.d.ts.map +1 -0
- package/dist/packages/core/src/index.js +7 -0
- package/dist/packages/core/src/index.js.map +1 -0
- package/dist/packages/core/src/memory-store.d.ts +36 -0
- package/dist/packages/core/src/memory-store.d.ts.map +1 -0
- package/dist/packages/core/src/memory-store.js +2 -0
- package/dist/packages/core/src/memory-store.js.map +1 -0
- package/dist/packages/core/src/memory.d.ts +75 -0
- package/dist/packages/core/src/memory.d.ts.map +1 -0
- package/dist/packages/core/src/memory.js +402 -0
- package/dist/packages/core/src/memory.js.map +1 -0
- package/dist/packages/core/src/policy.d.ts +17 -0
- package/dist/packages/core/src/policy.d.ts.map +1 -0
- package/dist/packages/core/src/policy.js +22 -0
- package/dist/packages/core/src/policy.js.map +1 -0
- package/dist/packages/core/src/types.d.ts +102 -0
- package/dist/packages/core/src/types.d.ts.map +1 -0
- package/dist/packages/core/src/types.js +23 -0
- package/dist/packages/core/src/types.js.map +1 -0
- package/dist/packages/embeddings/src/index.d.ts +4 -0
- package/dist/packages/embeddings/src/index.d.ts.map +1 -0
- package/dist/packages/embeddings/src/index.js +4 -0
- package/dist/packages/embeddings/src/index.js.map +1 -0
- package/dist/packages/embeddings/src/local.d.ts +15 -0
- package/dist/packages/embeddings/src/local.d.ts.map +1 -0
- package/dist/packages/embeddings/src/local.js +45 -0
- package/dist/packages/embeddings/src/local.js.map +1 -0
- package/dist/packages/embeddings/src/openai.d.ts +17 -0
- package/dist/packages/embeddings/src/openai.d.ts.map +1 -0
- package/dist/packages/embeddings/src/openai.js +41 -0
- package/dist/packages/embeddings/src/openai.js.map +1 -0
- package/dist/packages/embeddings/src/provider.d.ts +8 -0
- package/dist/packages/embeddings/src/provider.d.ts.map +1 -0
- package/dist/packages/embeddings/src/provider.js +12 -0
- package/dist/packages/embeddings/src/provider.js.map +1 -0
- package/dist/packages/integrations/src/anthropic.d.ts +15 -0
- package/dist/packages/integrations/src/anthropic.d.ts.map +1 -0
- package/dist/packages/integrations/src/anthropic.js +25 -0
- package/dist/packages/integrations/src/anthropic.js.map +1 -0
- package/dist/packages/integrations/src/index.d.ts +4 -0
- package/dist/packages/integrations/src/index.d.ts.map +1 -0
- package/dist/packages/integrations/src/index.js +4 -0
- package/dist/packages/integrations/src/index.js.map +1 -0
- package/dist/packages/integrations/src/openai.d.ts +27 -0
- package/dist/packages/integrations/src/openai.d.ts.map +1 -0
- package/dist/packages/integrations/src/openai.js +26 -0
- package/dist/packages/integrations/src/openai.js.map +1 -0
- package/dist/packages/integrations/src/vercel.d.ts +24 -0
- package/dist/packages/integrations/src/vercel.d.ts.map +1 -0
- package/dist/packages/integrations/src/vercel.js +20 -0
- package/dist/packages/integrations/src/vercel.js.map +1 -0
- package/dist/packages/retrieval/src/index.d.ts +3 -0
- package/dist/packages/retrieval/src/index.d.ts.map +1 -0
- package/dist/packages/retrieval/src/index.js +3 -0
- package/dist/packages/retrieval/src/index.js.map +1 -0
- package/dist/packages/retrieval/src/ranker.d.ts +21 -0
- package/dist/packages/retrieval/src/ranker.d.ts.map +1 -0
- package/dist/packages/retrieval/src/ranker.js +54 -0
- package/dist/packages/retrieval/src/ranker.js.map +1 -0
- package/dist/packages/retrieval/src/token.d.ts +3 -0
- package/dist/packages/retrieval/src/token.d.ts.map +1 -0
- package/dist/packages/retrieval/src/token.js +7 -0
- package/dist/packages/retrieval/src/token.js.map +1 -0
- package/dist/packages/storage/src/config.d.ts +14 -0
- package/dist/packages/storage/src/config.d.ts.map +1 -0
- package/dist/packages/storage/src/config.js +41 -0
- package/dist/packages/storage/src/config.js.map +1 -0
- package/dist/packages/storage/src/index.d.ts +4 -0
- package/dist/packages/storage/src/index.d.ts.map +1 -0
- package/dist/packages/storage/src/index.js +4 -0
- package/dist/packages/storage/src/index.js.map +1 -0
- package/dist/packages/storage/src/memory-store.d.ts +37 -0
- package/dist/packages/storage/src/memory-store.d.ts.map +1 -0
- package/dist/packages/storage/src/memory-store.js +107 -0
- package/dist/packages/storage/src/memory-store.js.map +1 -0
- package/dist/packages/storage/src/migrations.d.ts +12 -0
- package/dist/packages/storage/src/migrations.d.ts.map +1 -0
- package/dist/packages/storage/src/migrations.js +89 -0
- package/dist/packages/storage/src/migrations.js.map +1 -0
- package/dist/packages/storage/src/postgres-store.d.ts +45 -0
- package/dist/packages/storage/src/postgres-store.d.ts.map +1 -0
- package/dist/packages/storage/src/postgres-store.js +282 -0
- package/dist/packages/storage/src/postgres-store.js.map +1 -0
- package/dist/packages/storage/src/sqlite-store.d.ts +36 -0
- package/dist/packages/storage/src/sqlite-store.d.ts.map +1 -0
- package/dist/packages/storage/src/sqlite-store.js +243 -0
- package/dist/packages/storage/src/sqlite-store.js.map +1 -0
- package/examples/advanced.ts +46 -0
- package/examples/basic.ts +25 -0
- package/package.json +52 -0
- package/packages/core/package.json +13 -0
- package/packages/core/src/consolidation.ts +104 -0
- package/packages/core/src/events.ts +25 -0
- package/packages/core/src/extraction.ts +81 -0
- package/packages/core/src/index.ts +22 -0
- package/packages/core/src/memory-store.ts +50 -0
- package/packages/core/src/memory.ts +447 -0
- package/packages/core/src/policy.ts +36 -0
- package/packages/core/src/types.ts +119 -0
- package/packages/core/tsconfig.json +6 -0
- package/packages/embeddings/package.json +9 -0
- package/packages/embeddings/src/index.ts +3 -0
- package/packages/embeddings/src/local.ts +47 -0
- package/packages/embeddings/src/openai.ts +50 -0
- package/packages/embeddings/src/provider.d.ts.map +1 -0
- package/packages/embeddings/src/provider.js.map +1 -0
- package/packages/embeddings/src/provider.ts +17 -0
- package/packages/embeddings/tsconfig.json +6 -0
- package/packages/integrations/package.json +7 -0
- package/packages/integrations/src/anthropic.ts +34 -0
- package/packages/integrations/src/index.ts +3 -0
- package/packages/integrations/src/openai.ts +52 -0
- package/packages/integrations/src/vercel.ts +26 -0
- package/packages/retrieval/package.json +8 -0
- package/packages/retrieval/src/index.ts +2 -0
- package/packages/retrieval/src/ranker.d.ts.map +1 -0
- package/packages/retrieval/src/ranker.js.map +1 -0
- package/packages/retrieval/src/ranker.ts +77 -0
- package/packages/retrieval/src/token.d.ts.map +1 -0
- package/packages/retrieval/src/token.js.map +1 -0
- package/packages/retrieval/src/token.ts +5 -0
- package/packages/retrieval/tsconfig.json +6 -0
- package/packages/storage/package.json +11 -0
- package/packages/storage/src/config.ts +44 -0
- package/packages/storage/src/index.ts +3 -0
- package/packages/storage/src/memory-store.ts +100 -0
- package/packages/storage/src/migrations.ts +96 -0
- package/packages/storage/src/postgres-store.ts +299 -0
- package/packages/storage/src/sqlite-store.ts +298 -0
- package/packages/storage/tsconfig.json +6 -0
- package/test-mvp.mjs +82 -0
- package/tests/consolidation.test.ts +45 -0
- package/tests/eval.test.ts +102 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import type { MemoryStore } from "../../core/src/memory-store.js";
|
|
2
|
+
import type { Memory, Project, Source, SearchQuery, SearchResult, RetrievalEvent } from "../../core/src/types.js";
|
|
3
|
+
import { MIGRATIONS } from "./migrations.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Postgres + pgvector adapter — same MemoryStore contract as SqliteMemoryStore.
|
|
7
|
+
* Requires: CREATE EXTENSION vector;
|
|
8
|
+
* Embeddings stored dual: JSON text `vector` + native `vec` column for pgvector search.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
type PgPool = {
|
|
12
|
+
query: (text: string, params?: any[]) => Promise<{ rows: any[] }>;
|
|
13
|
+
end: () => Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface PostgresStoreOptions {
|
|
17
|
+
connectionString: string;
|
|
18
|
+
maxPool?: number;
|
|
19
|
+
vectorDimensions?: number; // default 1536
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class PostgresMemoryStore implements MemoryStore {
|
|
23
|
+
private pool: PgPool;
|
|
24
|
+
private dimensions: number;
|
|
25
|
+
private ready: Promise<void>;
|
|
26
|
+
|
|
27
|
+
constructor(opts: string | PostgresStoreOptions) {
|
|
28
|
+
const connectionString = typeof opts === "string" ? opts : opts.connectionString;
|
|
29
|
+
this.dimensions = typeof opts === "object" ? (opts.vectorDimensions ?? 1536) : 1536;
|
|
30
|
+
// dynamic import to avoid hard dep if not used
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
|
+
const { Pool } = require("pg");
|
|
33
|
+
this.pool = new Pool({ connectionString, max: typeof opts === "object" ? (opts.maxPool ?? 10) : 10 });
|
|
34
|
+
this.ready = this.migrate();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private async migrate() {
|
|
38
|
+
// schema_migrations + core tables
|
|
39
|
+
for (const m of MIGRATIONS) {
|
|
40
|
+
if (m.version === 2) {
|
|
41
|
+
// pgvector extension + vec column
|
|
42
|
+
try {
|
|
43
|
+
await this.pool.query(`CREATE EXTENSION IF NOT EXISTS vector`);
|
|
44
|
+
await this.pool.query(`ALTER TABLE embeddings ADD COLUMN IF NOT EXISTS vec vector(${this.dimensions})`);
|
|
45
|
+
await this.pool.query(`CREATE INDEX IF NOT EXISTS idx_embeddings_vec ON embeddings USING hnsw (vec vector_cosine_ops)`);
|
|
46
|
+
} catch {
|
|
47
|
+
// ignore if extension not available
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
// For version 1, run each statement separately for pg
|
|
52
|
+
const stmts = m.up.split(";").map((s) => s.trim()).filter(Boolean);
|
|
53
|
+
for (const stmt of stmts) {
|
|
54
|
+
try { await this.pool.query(stmt); } catch {}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Ensure tables exist with pg-compatible types (TEXT -> VARCHAR etc handled)
|
|
58
|
+
await this.pool.query(`
|
|
59
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
60
|
+
id TEXT PRIMARY KEY,
|
|
61
|
+
name TEXT UNIQUE NOT NULL,
|
|
62
|
+
description TEXT,
|
|
63
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
64
|
+
updated_at TIMESTAMPTZ NOT NULL
|
|
65
|
+
);
|
|
66
|
+
CREATE TABLE IF NOT EXISTS sources (
|
|
67
|
+
id TEXT PRIMARY KEY,
|
|
68
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
69
|
+
type TEXT NOT NULL,
|
|
70
|
+
content TEXT,
|
|
71
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
72
|
+
metadata JSONB NOT NULL
|
|
73
|
+
);
|
|
74
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
75
|
+
id TEXT PRIMARY KEY,
|
|
76
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
77
|
+
type TEXT NOT NULL,
|
|
78
|
+
content TEXT NOT NULL,
|
|
79
|
+
importance REAL NOT NULL,
|
|
80
|
+
confidence REAL NOT NULL,
|
|
81
|
+
status TEXT NOT NULL,
|
|
82
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
83
|
+
updated_at TIMESTAMPTZ NOT NULL,
|
|
84
|
+
last_accessed_at TIMESTAMPTZ,
|
|
85
|
+
access_count INTEGER NOT NULL,
|
|
86
|
+
source_id TEXT REFERENCES sources(id) ON DELETE SET NULL,
|
|
87
|
+
metadata JSONB NOT NULL,
|
|
88
|
+
archived INTEGER NOT NULL DEFAULT 0
|
|
89
|
+
);
|
|
90
|
+
CREATE TABLE IF NOT EXISTS embeddings (
|
|
91
|
+
memory_id TEXT PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
|
|
92
|
+
vector TEXT NOT NULL,
|
|
93
|
+
model TEXT NOT NULL,
|
|
94
|
+
dimensions INTEGER NOT NULL,
|
|
95
|
+
vec vector(${this.dimensions})
|
|
96
|
+
);
|
|
97
|
+
CREATE TABLE IF NOT EXISTS retrieval_events (
|
|
98
|
+
id TEXT PRIMARY KEY,
|
|
99
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
100
|
+
query TEXT NOT NULL,
|
|
101
|
+
retrieved_memory_ids JSONB NOT NULL,
|
|
102
|
+
scores JSONB NOT NULL,
|
|
103
|
+
final_context_tokens INTEGER NOT NULL,
|
|
104
|
+
created_at TIMESTAMPTZ NOT NULL
|
|
105
|
+
);
|
|
106
|
+
CREATE TABLE IF NOT EXISTS consolidations (
|
|
107
|
+
id TEXT PRIMARY KEY,
|
|
108
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
109
|
+
summary_memory_id TEXT REFERENCES memories(id) ON DELETE SET NULL,
|
|
110
|
+
source_memory_ids JSONB NOT NULL,
|
|
111
|
+
created_at TIMESTAMPTZ NOT NULL
|
|
112
|
+
);
|
|
113
|
+
`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private async ensureReady() { await this.ready; }
|
|
117
|
+
|
|
118
|
+
// ---- Projects ----
|
|
119
|
+
async createProject(project: Project): Promise<Project> {
|
|
120
|
+
await this.ensureReady();
|
|
121
|
+
await this.pool.query(`INSERT INTO projects (id, name, description, created_at, updated_at) VALUES ($1,$2,$3,$4,$5)`, [project.id, project.name, project.description ?? null, project.createdAt, project.updatedAt]);
|
|
122
|
+
return project;
|
|
123
|
+
}
|
|
124
|
+
async getProject(id: string): Promise<Project | null> {
|
|
125
|
+
await this.ensureReady();
|
|
126
|
+
const { rows } = await this.pool.query(`SELECT * FROM projects WHERE id=$1`, [id]);
|
|
127
|
+
if (!rows[0]) return null;
|
|
128
|
+
return this.mapProject(rows[0]);
|
|
129
|
+
}
|
|
130
|
+
async getProjectByName(name: string): Promise<Project | null> {
|
|
131
|
+
await this.ensureReady();
|
|
132
|
+
const { rows } = await this.pool.query(`SELECT * FROM projects WHERE name=$1`, [name]);
|
|
133
|
+
if (!rows[0]) return null;
|
|
134
|
+
return this.mapProject(rows[0]);
|
|
135
|
+
}
|
|
136
|
+
async listProjects(): Promise<Project[]> {
|
|
137
|
+
await this.ensureReady();
|
|
138
|
+
const { rows } = await this.pool.query(`SELECT * FROM projects`);
|
|
139
|
+
return rows.map(this.mapProject);
|
|
140
|
+
}
|
|
141
|
+
async deleteProject(projectId: string): Promise<void> {
|
|
142
|
+
await this.ensureReady();
|
|
143
|
+
await this.pool.query(`DELETE FROM projects WHERE id=$1`, [projectId]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ---- Sources ----
|
|
147
|
+
async createSource(source: Source): Promise<Source> {
|
|
148
|
+
await this.ensureReady();
|
|
149
|
+
await this.pool.query(`INSERT INTO sources (id, project_id, type, content, created_at, metadata) VALUES ($1,$2,$3,$4,$5,$6)`, [source.id, source.projectId, source.type, source.content ?? null, source.createdAt, JSON.stringify(source.metadata)]);
|
|
150
|
+
return source;
|
|
151
|
+
}
|
|
152
|
+
async getSource(id: string): Promise<Source | null> {
|
|
153
|
+
await this.ensureReady();
|
|
154
|
+
const { rows } = await this.pool.query(`SELECT * FROM sources WHERE id=$1`, [id]);
|
|
155
|
+
if (!rows[0]) return null;
|
|
156
|
+
const r = rows[0];
|
|
157
|
+
return { id: r.id, projectId: r.project_id, type: r.type, content: r.content ?? undefined, createdAt: new Date(r.created_at), metadata: typeof r.metadata === "string" ? JSON.parse(r.metadata) : r.metadata };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ---- Memories ----
|
|
161
|
+
async createMemory(memory: Memory): Promise<void> {
|
|
162
|
+
await this.ensureReady();
|
|
163
|
+
await this.pool.query(`INSERT INTO memories (id, project_id, type, content, importance, confidence, status, created_at, updated_at, last_accessed_at, access_count, source_id, metadata, archived) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)`, [memory.id, memory.projectId, memory.type, memory.content, memory.importance, memory.confidence ?? 1, memory.status ?? "active", memory.createdAt, memory.updatedAt, memory.lastAccessedAt ?? null, memory.accessCount, memory.sourceId ?? null, JSON.stringify(memory.metadata), memory.archived ? 1 : 0]);
|
|
164
|
+
}
|
|
165
|
+
async getMemory(id: string): Promise<Memory | null> {
|
|
166
|
+
await this.ensureReady();
|
|
167
|
+
const { rows } = await this.pool.query(`SELECT * FROM memories WHERE id=$1`, [id]);
|
|
168
|
+
if (!rows[0]) return null;
|
|
169
|
+
return this.mapMemory(rows[0]);
|
|
170
|
+
}
|
|
171
|
+
async updateMemory(id: string, update: Partial<Memory>): Promise<void> {
|
|
172
|
+
await this.ensureReady();
|
|
173
|
+
const existing = await this.getMemory(id);
|
|
174
|
+
if (!existing) return;
|
|
175
|
+
const merged: Memory = { ...existing, ...update, id, updatedAt: update.updatedAt ?? new Date() } as Memory;
|
|
176
|
+
await this.pool.query(`UPDATE memories SET type=$1, content=$2, importance=$3, confidence=$4, status=$5, created_at=$6, updated_at=$7, last_accessed_at=$8, access_count=$9, source_id=$10, metadata=$11, archived=$12 WHERE id=$13`, [merged.type, merged.content, merged.importance, merged.confidence, merged.status ?? "active", merged.createdAt, merged.updatedAt, merged.lastAccessedAt ?? null, merged.accessCount, merged.sourceId ?? null, JSON.stringify(merged.metadata), merged.archived ? 1 : 0, id]);
|
|
177
|
+
}
|
|
178
|
+
async deleteMemory(id: string): Promise<void> {
|
|
179
|
+
await this.ensureReady();
|
|
180
|
+
// Deleting embedding automatically via cascade, but also ensure vector cleanup
|
|
181
|
+
await this.pool.query(`DELETE FROM memories WHERE id=$1`, [id]);
|
|
182
|
+
}
|
|
183
|
+
async archiveMemory(id: string): Promise<void> {
|
|
184
|
+
await this.ensureReady();
|
|
185
|
+
await this.pool.query(`UPDATE memories SET archived=1, status='archived', updated_at=$1 WHERE id=$2`, [new Date(), id]);
|
|
186
|
+
}
|
|
187
|
+
async listMemories(projectId: string): Promise<Memory[]> {
|
|
188
|
+
await this.ensureReady();
|
|
189
|
+
const { rows } = await this.pool.query(`SELECT * FROM memories WHERE project_id=$1 ORDER BY updated_at DESC`, [projectId]);
|
|
190
|
+
return rows.map(this.mapMemory);
|
|
191
|
+
}
|
|
192
|
+
async search(query: SearchQuery): Promise<SearchResult[]> {
|
|
193
|
+
await this.ensureReady();
|
|
194
|
+
// Enforce project isolation first
|
|
195
|
+
let sql = `SELECT * FROM memories WHERE project_id=$1`;
|
|
196
|
+
const params: any[] = [query.projectId];
|
|
197
|
+
let idx = 2;
|
|
198
|
+
if (query.types?.length) {
|
|
199
|
+
sql += ` AND type IN (${query.types.map(() => `$${idx++}`).join(",")})`;
|
|
200
|
+
params.push(...query.types);
|
|
201
|
+
}
|
|
202
|
+
if (query.minImportance !== undefined) {
|
|
203
|
+
sql += ` AND importance >= $${idx++}`;
|
|
204
|
+
params.push(query.minImportance);
|
|
205
|
+
}
|
|
206
|
+
if (!query.includeArchived) sql += ` AND archived=0 AND status != 'archived'`;
|
|
207
|
+
const { rows } = await this.pool.query(sql, params);
|
|
208
|
+
const memories = rows.map(this.mapMemory);
|
|
209
|
+
// Keyword fallback scoring (vector search is done at MemoryEngine level with pgvector via separate query if needed)
|
|
210
|
+
const qLower = query.query.toLowerCase();
|
|
211
|
+
const qTokens = new Set(qLower.split(/\W+/).filter(Boolean));
|
|
212
|
+
const scored = memories.map((m) => {
|
|
213
|
+
const cLower = m.content.toLowerCase();
|
|
214
|
+
const cTokens = cLower.split(/\W+/).filter(Boolean);
|
|
215
|
+
let hits = 0;
|
|
216
|
+
for (const t of cTokens) if (qTokens.has(t)) hits++;
|
|
217
|
+
const phraseBoost = cLower.includes(qLower) ? 0.5 : 0;
|
|
218
|
+
const keywordScore = qTokens.size ? Math.min(1, hits / qTokens.size + phraseBoost) : 0;
|
|
219
|
+
const recency = Math.exp(-(Date.now() - m.updatedAt.getTime()) / (1000 * 60 * 60 * 24 * 30));
|
|
220
|
+
const final = keywordScore * 0.7 + m.importance * 0.15 + recency * 0.15;
|
|
221
|
+
return { memory: m, score: final, semanticScore: 0, keywordScore };
|
|
222
|
+
});
|
|
223
|
+
scored.sort((a, b) => b.score - a.score);
|
|
224
|
+
return scored.slice(0, query.limit ?? 5);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ---- Embeddings ----
|
|
228
|
+
async upsertEmbedding(memoryId: string, vector: number[], model: string): Promise<void> {
|
|
229
|
+
await this.ensureReady();
|
|
230
|
+
const vecStr = `[${vector.join(",")}]`;
|
|
231
|
+
await this.pool.query(`INSERT INTO embeddings (memory_id, vector, model, dimensions, vec) VALUES ($1,$2,$3,$4,$5::vector) ON CONFLICT (memory_id) DO UPDATE SET vector=EXCLUDED.vector, model=EXCLUDED.model, dimensions=EXCLUDED.dimensions, vec=EXCLUDED.vec`, [memoryId, JSON.stringify(vector), model, vector.length, vecStr]);
|
|
232
|
+
}
|
|
233
|
+
async getEmbedding(memoryId: string): Promise<{ vector: number[]; model: string } | null> {
|
|
234
|
+
await this.ensureReady();
|
|
235
|
+
const { rows } = await this.pool.query(`SELECT vector, model FROM embeddings WHERE memory_id=$1`, [memoryId]);
|
|
236
|
+
if (!rows[0]) return null;
|
|
237
|
+
return { vector: JSON.parse(rows[0].vector), model: rows[0].model };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** pgvector semantic search — use when MemoryEngine wants native vector ranking */
|
|
241
|
+
async vectorSearch(projectId: string, queryVector: number[], limit = 10): Promise<Array<{ memory: Memory; distance: number }>> {
|
|
242
|
+
await this.ensureReady();
|
|
243
|
+
const vecStr = `[${queryVector.join(",")}]`;
|
|
244
|
+
const { rows } = await this.pool.query(`
|
|
245
|
+
SELECT m.*, e.vec <=> $1::vector as distance
|
|
246
|
+
FROM memories m
|
|
247
|
+
JOIN embeddings e ON e.memory_id = m.id
|
|
248
|
+
WHERE m.project_id = $2 AND m.archived=0 AND m.status != 'archived'
|
|
249
|
+
ORDER BY distance ASC
|
|
250
|
+
LIMIT $3
|
|
251
|
+
`, [vecStr, projectId, limit]);
|
|
252
|
+
return rows.map((r) => ({ memory: this.mapMemory(r), distance: Number(r.distance) }));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ---- Retrieval logs ----
|
|
256
|
+
async logRetrieval(event: RetrievalEvent): Promise<void> {
|
|
257
|
+
await this.ensureReady();
|
|
258
|
+
await this.pool.query(`INSERT INTO retrieval_events (id, project_id, query, retrieved_memory_ids, scores, final_context_tokens, created_at) VALUES ($1,$2,$3,$4,$5,$6,$7)`, [event.id, event.projectId, event.query, JSON.stringify(event.retrievedMemoryIds), JSON.stringify(event.scores), event.finalContextTokens, event.createdAt]);
|
|
259
|
+
}
|
|
260
|
+
async listRetrievals(projectId: string, limit = 20): Promise<RetrievalEvent[]> {
|
|
261
|
+
await this.ensureReady();
|
|
262
|
+
const { rows } = await this.pool.query(`SELECT * FROM retrieval_events WHERE project_id=$1 ORDER BY created_at DESC LIMIT $2`, [projectId, limit]);
|
|
263
|
+
return rows.map((r) => ({
|
|
264
|
+
id: r.id,
|
|
265
|
+
projectId: r.project_id,
|
|
266
|
+
query: r.query,
|
|
267
|
+
retrievedMemoryIds: typeof r.retrieved_memory_ids === "string" ? JSON.parse(r.retrieved_memory_ids) : r.retrieved_memory_ids,
|
|
268
|
+
scores: typeof r.scores === "string" ? JSON.parse(r.scores) : r.scores,
|
|
269
|
+
finalContextTokens: r.final_context_tokens,
|
|
270
|
+
createdAt: new Date(r.created_at),
|
|
271
|
+
}));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async close(): Promise<void> { await this.pool.end(); }
|
|
275
|
+
|
|
276
|
+
private mapProject = (row: any): Project => ({
|
|
277
|
+
id: row.id,
|
|
278
|
+
name: row.name,
|
|
279
|
+
description: row.description ?? undefined,
|
|
280
|
+
createdAt: new Date(row.created_at),
|
|
281
|
+
updatedAt: new Date(row.updated_at),
|
|
282
|
+
});
|
|
283
|
+
private mapMemory = (row: any): Memory => ({
|
|
284
|
+
id: row.id,
|
|
285
|
+
projectId: row.project_id,
|
|
286
|
+
type: row.type,
|
|
287
|
+
content: row.content,
|
|
288
|
+
importance: Number(row.importance),
|
|
289
|
+
confidence: Number(row.confidence),
|
|
290
|
+
status: row.status,
|
|
291
|
+
createdAt: new Date(row.created_at),
|
|
292
|
+
updatedAt: new Date(row.updated_at),
|
|
293
|
+
lastAccessedAt: row.last_accessed_at ? new Date(row.last_accessed_at) : undefined,
|
|
294
|
+
accessCount: Number(row.access_count),
|
|
295
|
+
sourceId: row.source_id ?? undefined,
|
|
296
|
+
metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata,
|
|
297
|
+
archived: !!row.archived,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import type { MemoryStore } from "../../core/src/memory-store.js";
|
|
5
|
+
import type { Memory, Project, Source, SearchQuery, SearchResult, RetrievalEvent } from "../../core/src/types.js";
|
|
6
|
+
|
|
7
|
+
export interface SqliteStoreOptions {
|
|
8
|
+
path?: string; // file path or ":memory:"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class SqliteMemoryStore implements MemoryStore {
|
|
12
|
+
private db: DatabaseSync;
|
|
13
|
+
|
|
14
|
+
constructor(opts: SqliteStoreOptions = {}) {
|
|
15
|
+
const dbPath = opts.path ?? ":memory:";
|
|
16
|
+
if (dbPath !== ":memory:") {
|
|
17
|
+
const dir = path.dirname(dbPath);
|
|
18
|
+
if (dir && dir !== "." && dir !== "") fs.mkdirSync(dir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
this.db = new DatabaseSync(dbPath);
|
|
21
|
+
this.migrate();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private migrate() {
|
|
25
|
+
this.db.exec(`
|
|
26
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
name TEXT UNIQUE NOT NULL,
|
|
29
|
+
description TEXT,
|
|
30
|
+
created_at TEXT NOT NULL,
|
|
31
|
+
updated_at TEXT NOT NULL
|
|
32
|
+
);
|
|
33
|
+
CREATE TABLE IF NOT EXISTS sources (
|
|
34
|
+
id TEXT PRIMARY KEY,
|
|
35
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
36
|
+
type TEXT NOT NULL,
|
|
37
|
+
content TEXT,
|
|
38
|
+
created_at TEXT NOT NULL,
|
|
39
|
+
metadata TEXT NOT NULL
|
|
40
|
+
);
|
|
41
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
42
|
+
id TEXT PRIMARY KEY,
|
|
43
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
44
|
+
type TEXT NOT NULL,
|
|
45
|
+
content TEXT NOT NULL,
|
|
46
|
+
importance REAL NOT NULL,
|
|
47
|
+
confidence REAL NOT NULL,
|
|
48
|
+
status TEXT NOT NULL,
|
|
49
|
+
created_at TEXT NOT NULL,
|
|
50
|
+
updated_at TEXT NOT NULL,
|
|
51
|
+
last_accessed_at TEXT,
|
|
52
|
+
access_count INTEGER NOT NULL,
|
|
53
|
+
source_id TEXT REFERENCES sources(id) ON DELETE SET NULL,
|
|
54
|
+
metadata TEXT NOT NULL,
|
|
55
|
+
archived INTEGER NOT NULL DEFAULT 0
|
|
56
|
+
);
|
|
57
|
+
CREATE TABLE IF NOT EXISTS embeddings (
|
|
58
|
+
memory_id TEXT PRIMARY KEY REFERENCES memories(id) ON DELETE CASCADE,
|
|
59
|
+
vector TEXT NOT NULL,
|
|
60
|
+
model TEXT NOT NULL,
|
|
61
|
+
dimensions INTEGER NOT NULL
|
|
62
|
+
);
|
|
63
|
+
CREATE TABLE IF NOT EXISTS retrieval_events (
|
|
64
|
+
id TEXT PRIMARY KEY,
|
|
65
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
66
|
+
query TEXT NOT NULL,
|
|
67
|
+
retrieved_memory_ids TEXT NOT NULL,
|
|
68
|
+
scores TEXT NOT NULL,
|
|
69
|
+
final_context_tokens INTEGER NOT NULL,
|
|
70
|
+
created_at TEXT NOT NULL
|
|
71
|
+
);
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_id);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
|
|
74
|
+
CREATE TABLE IF NOT EXISTS consolidations (
|
|
75
|
+
id TEXT PRIMARY KEY,
|
|
76
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
77
|
+
summary_memory_id TEXT REFERENCES memories(id) ON DELETE SET NULL,
|
|
78
|
+
source_memory_ids TEXT NOT NULL,
|
|
79
|
+
created_at TEXT NOT NULL
|
|
80
|
+
);
|
|
81
|
+
CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status);
|
|
82
|
+
`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---- Projects ----
|
|
86
|
+
async createProject(project: Project): Promise<Project> {
|
|
87
|
+
this.db.prepare(`INSERT INTO projects (id, name, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`).run(project.id, project.name, project.description ?? null, project.createdAt.toISOString(), project.updatedAt.toISOString());
|
|
88
|
+
return project;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async getProject(id: string): Promise<Project | null> {
|
|
92
|
+
const row = this.db.prepare(`SELECT * FROM projects WHERE id = ?`).get(id) as any;
|
|
93
|
+
if (!row) return null;
|
|
94
|
+
return this.mapProject(row);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async getProjectByName(name: string): Promise<Project | null> {
|
|
98
|
+
const row = this.db.prepare(`SELECT * FROM projects WHERE name = ?`).get(name) as any;
|
|
99
|
+
if (!row) return null;
|
|
100
|
+
return this.mapProject(row);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async listProjects(): Promise<Project[]> {
|
|
104
|
+
const rows = this.db.prepare(`SELECT * FROM projects`).all() as any[];
|
|
105
|
+
return rows.map(this.mapProject);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async deleteProject(projectId: string): Promise<void> {
|
|
109
|
+
this.db.prepare(`DELETE FROM projects WHERE id = ?`).run(projectId);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---- Sources ----
|
|
113
|
+
async createSource(source: Source): Promise<Source> {
|
|
114
|
+
this.db.prepare(`INSERT INTO sources (id, project_id, type, content, created_at, metadata) VALUES (?, ?, ?, ?, ?, ?)`).run(
|
|
115
|
+
source.id, source.projectId, source.type, source.content ?? null, source.createdAt.toISOString(), JSON.stringify(source.metadata)
|
|
116
|
+
);
|
|
117
|
+
return source;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async getSource(id: string): Promise<Source | null> {
|
|
121
|
+
const row = this.db.prepare(`SELECT * FROM sources WHERE id = ?`).get(id) as any;
|
|
122
|
+
if (!row) return null;
|
|
123
|
+
return {
|
|
124
|
+
id: row.id,
|
|
125
|
+
projectId: row.project_id,
|
|
126
|
+
type: row.type,
|
|
127
|
+
content: row.content ?? undefined,
|
|
128
|
+
createdAt: new Date(row.created_at),
|
|
129
|
+
metadata: JSON.parse(row.metadata),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ---- Memories ----
|
|
134
|
+
async createMemory(memory: Memory): Promise<void> {
|
|
135
|
+
this.db.prepare(`
|
|
136
|
+
INSERT INTO memories (id, project_id, type, content, importance, confidence, status, created_at, updated_at, last_accessed_at, access_count, source_id, metadata, archived)
|
|
137
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
138
|
+
`).run(
|
|
139
|
+
memory.id,
|
|
140
|
+
memory.projectId,
|
|
141
|
+
memory.type,
|
|
142
|
+
memory.content,
|
|
143
|
+
memory.importance,
|
|
144
|
+
memory.confidence ?? 1,
|
|
145
|
+
memory.status ?? "active",
|
|
146
|
+
memory.createdAt.toISOString(),
|
|
147
|
+
memory.updatedAt.toISOString(),
|
|
148
|
+
memory.lastAccessedAt ? memory.lastAccessedAt.toISOString() : null,
|
|
149
|
+
memory.accessCount,
|
|
150
|
+
memory.sourceId ?? null,
|
|
151
|
+
JSON.stringify(memory.metadata),
|
|
152
|
+
memory.archived ? 1 : 0
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async getMemory(id: string): Promise<Memory | null> {
|
|
157
|
+
const row = this.db.prepare(`SELECT * FROM memories WHERE id = ?`).get(id) as any;
|
|
158
|
+
if (!row) return null;
|
|
159
|
+
return this.mapMemory(row);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async updateMemory(id: string, update: Partial<Memory>): Promise<void> {
|
|
163
|
+
const existing = await this.getMemory(id);
|
|
164
|
+
if (!existing) return;
|
|
165
|
+
const merged: Memory = { ...existing, ...update, id, updatedAt: update.updatedAt ?? new Date() } as Memory;
|
|
166
|
+
this.db.prepare(`
|
|
167
|
+
UPDATE memories SET type=?, content=?, importance=?, confidence=?, status=?, created_at=?, updated_at=?, last_accessed_at=?, access_count=?, source_id=?, metadata=?, archived=?
|
|
168
|
+
WHERE id=?
|
|
169
|
+
`).run(
|
|
170
|
+
merged.type,
|
|
171
|
+
merged.content,
|
|
172
|
+
merged.importance,
|
|
173
|
+
merged.confidence,
|
|
174
|
+
(merged.status ?? "active"),
|
|
175
|
+
merged.createdAt.toISOString(),
|
|
176
|
+
merged.updatedAt.toISOString(),
|
|
177
|
+
merged.lastAccessedAt ? merged.lastAccessedAt.toISOString() : null,
|
|
178
|
+
merged.accessCount,
|
|
179
|
+
merged.sourceId ?? null,
|
|
180
|
+
JSON.stringify(merged.metadata),
|
|
181
|
+
merged.archived ? 1 : 0,
|
|
182
|
+
id
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async deleteMemory(id: string): Promise<void> {
|
|
187
|
+
this.db.prepare(`DELETE FROM memories WHERE id = ?`).run(id);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async archiveMemory(id: string): Promise<void> {
|
|
191
|
+
this.db.prepare(`UPDATE memories SET archived=1, status='archived', updated_at=? WHERE id=?`).run(new Date().toISOString(), id);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async listMemories(projectId: string): Promise<Memory[]> {
|
|
195
|
+
const rows = this.db.prepare(`SELECT * FROM memories WHERE project_id = ? ORDER BY updated_at DESC`).all(projectId) as any[];
|
|
196
|
+
return rows.map(this.mapMemory);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async search(query: SearchQuery): Promise<SearchResult[]> {
|
|
200
|
+
let sql = `SELECT * FROM memories WHERE project_id = ?`;
|
|
201
|
+
const params: any[] = [query.projectId];
|
|
202
|
+
if (query.types && query.types.length) {
|
|
203
|
+
sql += ` AND type IN (${query.types.map(() => "?").join(",")})`;
|
|
204
|
+
params.push(...query.types);
|
|
205
|
+
}
|
|
206
|
+
if (query.minImportance !== undefined) {
|
|
207
|
+
sql += ` AND importance >= ?`;
|
|
208
|
+
params.push(query.minImportance);
|
|
209
|
+
}
|
|
210
|
+
if (!query.includeArchived) {
|
|
211
|
+
sql += ` AND archived = 0 AND status != 'archived'`;
|
|
212
|
+
}
|
|
213
|
+
const rows = this.db.prepare(sql).all(...params) as any[];
|
|
214
|
+
const memories = rows.map(this.mapMemory);
|
|
215
|
+
|
|
216
|
+
const qLower = query.query.toLowerCase();
|
|
217
|
+
const qTokens = new Set(qLower.split(/\W+/).filter(Boolean));
|
|
218
|
+
const scored = memories.map((m) => {
|
|
219
|
+
const cLower = m.content.toLowerCase();
|
|
220
|
+
const cTokens = cLower.split(/\W+/).filter(Boolean);
|
|
221
|
+
let hits = 0;
|
|
222
|
+
for (const t of cTokens) if (qTokens.has(t)) hits++;
|
|
223
|
+
const phraseBoost = cLower.includes(qLower) ? 0.5 : 0;
|
|
224
|
+
const keywordScore = qTokens.size ? Math.min(1, hits / qTokens.size + phraseBoost) : 0;
|
|
225
|
+
const recency = Math.exp(-(Date.now() - m.updatedAt.getTime()) / (1000 * 60 * 60 * 24 * 30));
|
|
226
|
+
const final = keywordScore * 0.7 + m.importance * 0.15 + recency * 0.15;
|
|
227
|
+
return { memory: m, score: final, semanticScore: 0, keywordScore };
|
|
228
|
+
});
|
|
229
|
+
scored.sort((a, b) => b.score - a.score);
|
|
230
|
+
return scored.slice(0, query.limit ?? 5);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ---- Embeddings ----
|
|
234
|
+
async upsertEmbedding(memoryId: string, vector: number[], model: string): Promise<void> {
|
|
235
|
+
this.db.prepare(`INSERT INTO embeddings (memory_id, vector, model, dimensions) VALUES (?, ?, ?, ?)
|
|
236
|
+
ON CONFLICT(memory_id) DO UPDATE SET vector=excluded.vector, model=excluded.model, dimensions=excluded.dimensions
|
|
237
|
+
`).run(memoryId, JSON.stringify(vector), model, vector.length);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async getEmbedding(memoryId: string): Promise<{ vector: number[]; model: string } | null> {
|
|
241
|
+
const row = this.db.prepare(`SELECT * FROM embeddings WHERE memory_id = ?`).get(memoryId) as any;
|
|
242
|
+
if (!row) return null;
|
|
243
|
+
return { vector: JSON.parse(row.vector), model: row.model };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async deleteEmbedding(memoryId: string): Promise<void> {
|
|
247
|
+
this.db.prepare(`DELETE FROM embeddings WHERE memory_id = ?`).run(memoryId);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ---- Retrieval logs ----
|
|
251
|
+
async logRetrieval(event: RetrievalEvent): Promise<void> {
|
|
252
|
+
this.db.prepare(`INSERT INTO retrieval_events (id, project_id, query, retrieved_memory_ids, scores, final_context_tokens, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`).run(
|
|
253
|
+
event.id, event.projectId, event.query, JSON.stringify(event.retrievedMemoryIds), JSON.stringify(event.scores), event.finalContextTokens, event.createdAt.toISOString()
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async listRetrievals(projectId: string, limit = 20): Promise<RetrievalEvent[]> {
|
|
258
|
+
const rows = this.db.prepare(`SELECT * FROM retrieval_events WHERE project_id = ? ORDER BY created_at DESC LIMIT ?`).all(projectId, limit) as any[];
|
|
259
|
+
return rows.map((r) => ({
|
|
260
|
+
id: r.id,
|
|
261
|
+
projectId: r.project_id,
|
|
262
|
+
query: r.query,
|
|
263
|
+
retrievedMemoryIds: JSON.parse(r.retrieved_memory_ids),
|
|
264
|
+
scores: JSON.parse(r.scores),
|
|
265
|
+
finalContextTokens: r.final_context_tokens,
|
|
266
|
+
createdAt: new Date(r.created_at),
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async close(): Promise<void> {
|
|
271
|
+
this.db.close();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private mapProject = (row: any): Project => ({
|
|
275
|
+
id: row.id,
|
|
276
|
+
name: row.name,
|
|
277
|
+
description: row.description ?? undefined,
|
|
278
|
+
createdAt: new Date(row.created_at),
|
|
279
|
+
updatedAt: new Date(row.updated_at),
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
private mapMemory = (row: any): Memory => ({
|
|
283
|
+
id: row.id,
|
|
284
|
+
projectId: row.project_id,
|
|
285
|
+
type: row.type,
|
|
286
|
+
content: row.content,
|
|
287
|
+
importance: row.importance,
|
|
288
|
+
confidence: row.confidence,
|
|
289
|
+
status: row.status,
|
|
290
|
+
createdAt: new Date(row.created_at),
|
|
291
|
+
updatedAt: new Date(row.updated_at),
|
|
292
|
+
lastAccessedAt: row.last_accessed_at ? new Date(row.last_accessed_at) : undefined,
|
|
293
|
+
accessCount: row.access_count,
|
|
294
|
+
sourceId: row.source_id ?? undefined,
|
|
295
|
+
metadata: JSON.parse(row.metadata),
|
|
296
|
+
archived: !!row.archived,
|
|
297
|
+
});
|
|
298
|
+
}
|
package/test-mvp.mjs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Memory } from "./dist/packages/core/src/memory.js";
|
|
2
|
+
import { InMemoryStore } from "./dist/packages/storage/src/memory-store.js";
|
|
3
|
+
import { SqliteMemoryStore } from "./dist/packages/storage/src/sqlite-store.js";
|
|
4
|
+
import { LocalEmbeddingProvider } from "./dist/packages/embeddings/src/local.js";
|
|
5
|
+
|
|
6
|
+
async function runTests() {
|
|
7
|
+
console.log("=== MVP Tests ===");
|
|
8
|
+
|
|
9
|
+
// Test 1: InMemoryStore + Local embeddings + basic remember/search
|
|
10
|
+
const store = new InMemoryStore();
|
|
11
|
+
const embed = new LocalEmbeddingProvider(64);
|
|
12
|
+
const memory = await Memory.create({ project: "research-mesh", store, embeddingProvider: embed });
|
|
13
|
+
|
|
14
|
+
await memory.remember({ type: "decision", content: "We decided to use PostgreSQL with pgvector.", importance: 0.9 });
|
|
15
|
+
await memory.remember({ type: "fact", content: "API-4821 is about payment gateway timeout.", importance: 0.8 });
|
|
16
|
+
await memory.remember({ type: "knowledge", content: "Backend uses Node.js and Fastify.", importance: 0.6 });
|
|
17
|
+
|
|
18
|
+
// Test search
|
|
19
|
+
const result = await memory.search("What database did we decide to use?");
|
|
20
|
+
console.log("Test 1 - Semantic search:", result.length > 0 ? "PASS" : "FAIL", "->", result[0]?.memory.content);
|
|
21
|
+
if (!result[0]?.memory.content.includes("PostgreSQL")) throw new Error("Search failed to retrieve PostgreSQL memory");
|
|
22
|
+
|
|
23
|
+
// Test hybrid keyword: API-4821
|
|
24
|
+
const kwResult = await memory.search("What's the status of API-4821?");
|
|
25
|
+
console.log("Test 2 - Hybrid keyword:", kwResult[0]?.memory.content.includes("API-4821") ? "PASS" : "FAIL", "->", kwResult[0]?.memory.content);
|
|
26
|
+
|
|
27
|
+
// Test context budgeting
|
|
28
|
+
const ctx = await memory.context({ query: "What database did we decide to use?", maxTokens: 6000 });
|
|
29
|
+
console.log("Test 3 - Context:", ctx.text.includes("PostgreSQL") ? "PASS" : "FAIL", `tokens=${ctx.tokenCount}`);
|
|
30
|
+
console.log(" text:", ctx.text.slice(0, 200));
|
|
31
|
+
|
|
32
|
+
// Test project isolation
|
|
33
|
+
const memoryB = await Memory.create({ project: "personal", store, embeddingProvider: embed });
|
|
34
|
+
await memoryB.remember({ type: "fact", content: "We use MongoDB for personal project.", importance: 0.9 });
|
|
35
|
+
const isolated = await memory.search("What database do we use?");
|
|
36
|
+
console.log("Test 4 - Project isolation:", isolated[0]?.memory.content.includes("PostgreSQL") && !isolated.some(r => r.memory.content.includes("MongoDB")) ? "PASS" : "FAIL", "->", isolated[0]?.memory.content);
|
|
37
|
+
const crossLeak = isolated.some(r => r.memory.projectId !== memory.project.id);
|
|
38
|
+
console.log(" cross-project leak:", crossLeak ? "FAIL" : "PASS");
|
|
39
|
+
|
|
40
|
+
// Test deduplication
|
|
41
|
+
const beforeCount = (await store.listMemories(memory.project.id)).length;
|
|
42
|
+
await memory.remember({ type: "decision", content: "We decided to use PostgreSQL with pgvector.", importance: 0.9 });
|
|
43
|
+
const afterCount = (await store.listMemories(memory.project.id)).length;
|
|
44
|
+
console.log("Test 5 - Deduplication:", beforeCount === afterCount ? "PASS" : "FAIL", `before=${beforeCount} after=${afterCount}`);
|
|
45
|
+
|
|
46
|
+
// Test conflict resolution
|
|
47
|
+
await memory.remember({ type: "decision", content: "We've switched to PostgreSQL with pgvector instead of MongoDB for production storage.", importance: 0.95 });
|
|
48
|
+
// should supersede any conflicting? Check statuses
|
|
49
|
+
const all = await store.listMemories(memory.project.id);
|
|
50
|
+
console.log("Test 6 - Conflict handling: memories", all.map(m => `${m.content.slice(0,30)} [${m.status}]`).join(" | "));
|
|
51
|
+
|
|
52
|
+
// Test token budgeting with many memories
|
|
53
|
+
for (let i = 0; i < 20; i++) {
|
|
54
|
+
await memory.remember({ content: `Fact ${i}: Lorem ipsum dolor sit amet consectetur adipiscing elit.`.repeat(5), importance: 0.5 });
|
|
55
|
+
}
|
|
56
|
+
const bigCtx = await memory.context({ query: "Fact", maxTokens: 100 });
|
|
57
|
+
console.log("Test 7 - Token budgeting:", bigCtx.tokenCount <= 100 ? "PASS" : "FAIL", `tokens=${bigCtx.tokenCount} limit=100`);
|
|
58
|
+
|
|
59
|
+
// Test Sqlite store (node:sqlite)
|
|
60
|
+
console.log("\n=== SQLite Store Test ===");
|
|
61
|
+
const sqliteStore = new SqliteMemoryStore({ path: ":memory:" });
|
|
62
|
+
const sqliteMemory = await Memory.create({ project: "sqlite-test", store: sqliteStore, embeddingProvider: embed });
|
|
63
|
+
await sqliteMemory.remember({ content: "SQLite test memory with PostgreSQL pgvector", importance: 0.9 });
|
|
64
|
+
const sqliteSearch = await sqliteMemory.search("PostgreSQL");
|
|
65
|
+
console.log("SQLite search:", sqliteSearch[0]?.memory.content.includes("PostgreSQL") ? "PASS" : "FAIL");
|
|
66
|
+
await sqliteStore.close();
|
|
67
|
+
|
|
68
|
+
// Test goldfish & cross-project leakage eval
|
|
69
|
+
console.log("\n=== Evaluation Tests ===");
|
|
70
|
+
const evalStore = new InMemoryStore();
|
|
71
|
+
const projA = await Memory.create({ project: "project-a", store: evalStore, embeddingProvider: embed });
|
|
72
|
+
const projB = await Memory.create({ project: "project-b", store: evalStore, embeddingProvider: embed });
|
|
73
|
+
await projA.remember({ content: "We use PostgreSQL.", importance: 0.9 });
|
|
74
|
+
await projB.remember({ content: "We use MongoDB.", importance: 0.9 });
|
|
75
|
+
const queryA = await projA.search("What database do we use?");
|
|
76
|
+
console.log("Goldfish cross-project:", queryA[0]?.memory.content.includes("PostgreSQL") ? "PASS" : "FAIL", "->", queryA[0]?.memory.content);
|
|
77
|
+
console.log("Cross-project leakage (should be 0):", queryA.some(r => r.memory.content.includes("MongoDB")) ? "FAIL" : "PASS");
|
|
78
|
+
|
|
79
|
+
console.log("\n=== All MVP tests completed ===");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
runTests().catch(e => { console.error(e); process.exit(1); });
|