@hasna/mementos 0.3.9 → 0.4.2
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/dashboard/dist/assets/index-B1yiOEw3.js +290 -0
- package/dashboard/dist/assets/index-DnpbasSl.css +1 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/cli/index.js +4191 -1688
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/entities.d.ts +17 -0
- package/dist/db/entities.d.ts.map +1 -0
- package/dist/db/entity-memories.d.ts +34 -0
- package/dist/db/entity-memories.d.ts.map +1 -0
- package/dist/db/memories.d.ts +7 -1
- package/dist/db/memories.d.ts.map +1 -1
- package/dist/db/relations.d.ts +29 -0
- package/dist/db/relations.d.ts.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1563 -467
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/duration.d.ts +28 -0
- package/dist/lib/duration.d.ts.map +1 -0
- package/dist/lib/extractor.d.ts +9 -0
- package/dist/lib/extractor.d.ts.map +1 -0
- package/dist/lib/retention.d.ts.map +1 -1
- package/dist/lib/search.d.ts +11 -0
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/mcp/index.js +1948 -246
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1539 -195
- package/dist/types/index.d.ts +77 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/dashboard/dist/assets/index-DqyMbv89.js +0 -270
- package/dashboard/dist/assets/index-UBCddFo_.css +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/types/index.ts
|
|
3
|
+
class EntityNotFoundError extends Error {
|
|
4
|
+
constructor(id) {
|
|
5
|
+
super(`Entity not found: ${id}`);
|
|
6
|
+
this.name = "EntityNotFoundError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
class MemoryNotFoundError extends Error {
|
|
4
11
|
constructor(id) {
|
|
5
12
|
super(`Memory not found: ${id}`);
|
|
@@ -182,6 +189,113 @@ var MIGRATIONS = [
|
|
|
182
189
|
);
|
|
183
190
|
|
|
184
191
|
INSERT OR IGNORE INTO _migrations (id) VALUES (1);
|
|
192
|
+
`,
|
|
193
|
+
`
|
|
194
|
+
CREATE TABLE IF NOT EXISTS memory_versions (
|
|
195
|
+
id TEXT PRIMARY KEY,
|
|
196
|
+
memory_id TEXT NOT NULL,
|
|
197
|
+
version INTEGER NOT NULL,
|
|
198
|
+
value TEXT NOT NULL,
|
|
199
|
+
importance INTEGER NOT NULL,
|
|
200
|
+
scope TEXT NOT NULL,
|
|
201
|
+
category TEXT NOT NULL,
|
|
202
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
203
|
+
summary TEXT,
|
|
204
|
+
pinned INTEGER NOT NULL DEFAULT 0,
|
|
205
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
206
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
207
|
+
UNIQUE(memory_id, version)
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
CREATE INDEX IF NOT EXISTS idx_memory_versions_memory ON memory_versions(memory_id);
|
|
211
|
+
CREATE INDEX IF NOT EXISTS idx_memory_versions_version ON memory_versions(memory_id, version);
|
|
212
|
+
|
|
213
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (2);
|
|
214
|
+
`,
|
|
215
|
+
`
|
|
216
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
217
|
+
key, value, summary,
|
|
218
|
+
content='memories',
|
|
219
|
+
content_rowid='rowid'
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
223
|
+
INSERT INTO memories_fts(rowid, key, value, summary) VALUES (new.rowid, new.key, new.value, new.summary);
|
|
224
|
+
END;
|
|
225
|
+
|
|
226
|
+
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
227
|
+
INSERT INTO memories_fts(memories_fts, rowid, key, value, summary) VALUES('delete', old.rowid, old.key, old.value, old.summary);
|
|
228
|
+
END;
|
|
229
|
+
|
|
230
|
+
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
|
|
231
|
+
INSERT INTO memories_fts(memories_fts, rowid, key, value, summary) VALUES('delete', old.rowid, old.key, old.value, old.summary);
|
|
232
|
+
INSERT INTO memories_fts(rowid, key, value, summary) VALUES (new.rowid, new.key, new.value, new.summary);
|
|
233
|
+
END;
|
|
234
|
+
|
|
235
|
+
INSERT INTO memories_fts(memories_fts) VALUES('rebuild');
|
|
236
|
+
|
|
237
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (3);
|
|
238
|
+
`,
|
|
239
|
+
`
|
|
240
|
+
CREATE TABLE IF NOT EXISTS search_history (
|
|
241
|
+
id TEXT PRIMARY KEY,
|
|
242
|
+
query TEXT NOT NULL,
|
|
243
|
+
result_count INTEGER NOT NULL DEFAULT 0,
|
|
244
|
+
agent_id TEXT,
|
|
245
|
+
project_id TEXT,
|
|
246
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
247
|
+
);
|
|
248
|
+
CREATE INDEX IF NOT EXISTS idx_search_history_query ON search_history(query);
|
|
249
|
+
CREATE INDEX IF NOT EXISTS idx_search_history_created ON search_history(created_at);
|
|
250
|
+
|
|
251
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (4);
|
|
252
|
+
`,
|
|
253
|
+
`
|
|
254
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
255
|
+
id TEXT PRIMARY KEY,
|
|
256
|
+
name TEXT NOT NULL,
|
|
257
|
+
type TEXT NOT NULL CHECK (type IN ('person','project','tool','concept','file','api','pattern','organization')),
|
|
258
|
+
description TEXT,
|
|
259
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
260
|
+
project_id TEXT,
|
|
261
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
262
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
263
|
+
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE SET NULL
|
|
264
|
+
);
|
|
265
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_entities_unique_name_type_project
|
|
266
|
+
ON entities(name, type, COALESCE(project_id, ''));
|
|
267
|
+
CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
|
|
268
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
|
|
269
|
+
CREATE INDEX IF NOT EXISTS idx_entities_project ON entities(project_id);
|
|
270
|
+
|
|
271
|
+
CREATE TABLE IF NOT EXISTS relations (
|
|
272
|
+
id TEXT PRIMARY KEY,
|
|
273
|
+
source_entity_id TEXT NOT NULL,
|
|
274
|
+
target_entity_id TEXT NOT NULL,
|
|
275
|
+
relation_type TEXT NOT NULL CHECK (relation_type IN ('uses','knows','depends_on','created_by','related_to','contradicts','part_of','implements')),
|
|
276
|
+
weight REAL NOT NULL DEFAULT 1.0,
|
|
277
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
278
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
279
|
+
UNIQUE(source_entity_id, target_entity_id, relation_type),
|
|
280
|
+
FOREIGN KEY (source_entity_id) REFERENCES entities(id) ON DELETE CASCADE,
|
|
281
|
+
FOREIGN KEY (target_entity_id) REFERENCES entities(id) ON DELETE CASCADE
|
|
282
|
+
);
|
|
283
|
+
CREATE INDEX IF NOT EXISTS idx_relations_source ON relations(source_entity_id);
|
|
284
|
+
CREATE INDEX IF NOT EXISTS idx_relations_target ON relations(target_entity_id);
|
|
285
|
+
CREATE INDEX IF NOT EXISTS idx_relations_type ON relations(relation_type);
|
|
286
|
+
|
|
287
|
+
CREATE TABLE IF NOT EXISTS entity_memories (
|
|
288
|
+
entity_id TEXT NOT NULL,
|
|
289
|
+
memory_id TEXT NOT NULL,
|
|
290
|
+
role TEXT NOT NULL DEFAULT 'context' CHECK (role IN ('subject','object','context')),
|
|
291
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
292
|
+
PRIMARY KEY (entity_id, memory_id),
|
|
293
|
+
FOREIGN KEY (entity_id) REFERENCES entities(id) ON DELETE CASCADE,
|
|
294
|
+
FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE
|
|
295
|
+
);
|
|
296
|
+
CREATE INDEX IF NOT EXISTS idx_entity_memories_memory ON entity_memories(memory_id);
|
|
297
|
+
|
|
298
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (5);
|
|
185
299
|
`
|
|
186
300
|
];
|
|
187
301
|
var _db = null;
|
|
@@ -279,176 +393,924 @@ function containsSecrets(text) {
|
|
|
279
393
|
return false;
|
|
280
394
|
}
|
|
281
395
|
|
|
282
|
-
// src/db/
|
|
283
|
-
function
|
|
396
|
+
// src/db/agents.ts
|
|
397
|
+
function parseAgentRow(row) {
|
|
284
398
|
return {
|
|
285
399
|
id: row["id"],
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
scope: row["scope"],
|
|
290
|
-
summary: row["summary"] || null,
|
|
291
|
-
tags: JSON.parse(row["tags"] || "[]"),
|
|
292
|
-
importance: row["importance"],
|
|
293
|
-
source: row["source"],
|
|
294
|
-
status: row["status"],
|
|
295
|
-
pinned: !!row["pinned"],
|
|
296
|
-
agent_id: row["agent_id"] || null,
|
|
297
|
-
project_id: row["project_id"] || null,
|
|
298
|
-
session_id: row["session_id"] || null,
|
|
400
|
+
name: row["name"],
|
|
401
|
+
description: row["description"] || null,
|
|
402
|
+
role: row["role"] || null,
|
|
299
403
|
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
300
|
-
access_count: row["access_count"],
|
|
301
|
-
version: row["version"],
|
|
302
|
-
expires_at: row["expires_at"] || null,
|
|
303
404
|
created_at: row["created_at"],
|
|
304
|
-
|
|
305
|
-
accessed_at: row["accessed_at"] || null
|
|
405
|
+
last_seen_at: row["last_seen_at"]
|
|
306
406
|
};
|
|
307
407
|
}
|
|
308
|
-
function
|
|
408
|
+
function registerAgent(name, description, role, db) {
|
|
309
409
|
const d = db || getDatabase();
|
|
310
410
|
const timestamp = now();
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
WHERE key = ? AND scope = ?
|
|
324
|
-
AND COALESCE(agent_id, '') = ?
|
|
325
|
-
AND COALESCE(project_id, '') = ?
|
|
326
|
-
AND COALESCE(session_id, '') = ?`).get(input.key, input.scope || "private", input.agent_id || "", input.project_id || "", input.session_id || "");
|
|
327
|
-
if (existing) {
|
|
328
|
-
d.run(`UPDATE memories SET
|
|
329
|
-
value = ?, category = ?, summary = ?, tags = ?,
|
|
330
|
-
importance = ?, metadata = ?, expires_at = ?,
|
|
331
|
-
pinned = COALESCE(pinned, 0),
|
|
332
|
-
version = version + 1, updated_at = ?
|
|
333
|
-
WHERE id = ?`, [
|
|
334
|
-
safeValue,
|
|
335
|
-
input.category || "knowledge",
|
|
336
|
-
safeSummary,
|
|
337
|
-
tagsJson,
|
|
338
|
-
input.importance ?? 5,
|
|
339
|
-
metadataJson,
|
|
340
|
-
expiresAt,
|
|
341
|
-
timestamp,
|
|
342
|
-
existing.id
|
|
411
|
+
const normalizedName = name.trim().toLowerCase();
|
|
412
|
+
const existing = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(normalizedName);
|
|
413
|
+
if (existing) {
|
|
414
|
+
const existingId = existing["id"];
|
|
415
|
+
d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [
|
|
416
|
+
timestamp,
|
|
417
|
+
existingId
|
|
418
|
+
]);
|
|
419
|
+
if (description) {
|
|
420
|
+
d.run("UPDATE agents SET description = ? WHERE id = ?", [
|
|
421
|
+
description,
|
|
422
|
+
existingId
|
|
343
423
|
]);
|
|
344
|
-
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [existing.id]);
|
|
345
|
-
const insertTag2 = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
346
|
-
for (const tag of tags) {
|
|
347
|
-
insertTag2.run(existing.id, tag);
|
|
348
|
-
}
|
|
349
|
-
return getMemory(existing.id, d);
|
|
350
424
|
}
|
|
425
|
+
if (role) {
|
|
426
|
+
d.run("UPDATE agents SET role = ? WHERE id = ?", [
|
|
427
|
+
role,
|
|
428
|
+
existingId
|
|
429
|
+
]);
|
|
430
|
+
}
|
|
431
|
+
return getAgent(existingId, d);
|
|
351
432
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
input.key,
|
|
356
|
-
input.value,
|
|
357
|
-
input.category || "knowledge",
|
|
358
|
-
input.scope || "private",
|
|
359
|
-
input.summary || null,
|
|
360
|
-
tagsJson,
|
|
361
|
-
input.importance ?? 5,
|
|
362
|
-
input.source || "agent",
|
|
363
|
-
input.agent_id || null,
|
|
364
|
-
input.project_id || null,
|
|
365
|
-
input.session_id || null,
|
|
366
|
-
metadataJson,
|
|
367
|
-
expiresAt,
|
|
368
|
-
timestamp,
|
|
369
|
-
timestamp
|
|
370
|
-
]);
|
|
371
|
-
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
372
|
-
for (const tag of tags) {
|
|
373
|
-
insertTag.run(id, tag);
|
|
374
|
-
}
|
|
375
|
-
return getMemory(id, d);
|
|
433
|
+
const id = shortUuid();
|
|
434
|
+
d.run("INSERT INTO agents (id, name, description, role, created_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?)", [id, normalizedName, description || null, role || "agent", timestamp, timestamp]);
|
|
435
|
+
return getAgent(id, d);
|
|
376
436
|
}
|
|
377
|
-
function
|
|
437
|
+
function getAgent(idOrName, db) {
|
|
378
438
|
const d = db || getDatabase();
|
|
379
|
-
|
|
380
|
-
if (
|
|
381
|
-
return
|
|
382
|
-
|
|
439
|
+
let row = d.query("SELECT * FROM agents WHERE id = ?").get(idOrName);
|
|
440
|
+
if (row)
|
|
441
|
+
return parseAgentRow(row);
|
|
442
|
+
row = d.query("SELECT * FROM agents WHERE LOWER(name) = ?").get(idOrName.trim().toLowerCase());
|
|
443
|
+
if (row)
|
|
444
|
+
return parseAgentRow(row);
|
|
445
|
+
const rows = d.query("SELECT * FROM agents WHERE id LIKE ?").all(`${idOrName}%`);
|
|
446
|
+
if (rows.length === 1)
|
|
447
|
+
return parseAgentRow(rows[0]);
|
|
448
|
+
return null;
|
|
383
449
|
}
|
|
384
|
-
function
|
|
450
|
+
function listAgents(db) {
|
|
385
451
|
const d = db || getDatabase();
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
452
|
+
const rows = d.query("SELECT * FROM agents ORDER BY last_seen_at DESC").all();
|
|
453
|
+
return rows.map(parseAgentRow);
|
|
454
|
+
}
|
|
455
|
+
function updateAgent(id, updates, db) {
|
|
456
|
+
const d = db || getDatabase();
|
|
457
|
+
const agent = getAgent(id, d);
|
|
458
|
+
if (!agent)
|
|
459
|
+
return null;
|
|
460
|
+
const timestamp = now();
|
|
461
|
+
if (updates.name) {
|
|
462
|
+
const normalizedNewName = updates.name.trim().toLowerCase();
|
|
463
|
+
if (normalizedNewName !== agent.name) {
|
|
464
|
+
const existing = d.query("SELECT id FROM agents WHERE LOWER(name) = ? AND id != ?").get(normalizedNewName, agent.id);
|
|
465
|
+
if (existing) {
|
|
466
|
+
throw new Error(`Agent name already taken: ${normalizedNewName}`);
|
|
467
|
+
}
|
|
468
|
+
d.run("UPDATE agents SET name = ? WHERE id = ?", [normalizedNewName, agent.id]);
|
|
469
|
+
}
|
|
391
470
|
}
|
|
392
|
-
if (
|
|
393
|
-
|
|
394
|
-
params.push(agentId);
|
|
471
|
+
if (updates.description !== undefined) {
|
|
472
|
+
d.run("UPDATE agents SET description = ? WHERE id = ?", [updates.description, agent.id]);
|
|
395
473
|
}
|
|
396
|
-
if (
|
|
397
|
-
|
|
398
|
-
params.push(projectId);
|
|
474
|
+
if (updates.role !== undefined) {
|
|
475
|
+
d.run("UPDATE agents SET role = ? WHERE id = ?", [updates.role, agent.id]);
|
|
399
476
|
}
|
|
400
|
-
if (
|
|
401
|
-
|
|
402
|
-
params.push(sessionId);
|
|
477
|
+
if (updates.metadata !== undefined) {
|
|
478
|
+
d.run("UPDATE agents SET metadata = ? WHERE id = ?", [JSON.stringify(updates.metadata), agent.id]);
|
|
403
479
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
if (!row)
|
|
407
|
-
return null;
|
|
408
|
-
return parseMemoryRow(row);
|
|
480
|
+
d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [timestamp, agent.id]);
|
|
481
|
+
return getAgent(agent.id, d);
|
|
409
482
|
}
|
|
410
|
-
|
|
483
|
+
|
|
484
|
+
// src/db/projects.ts
|
|
485
|
+
function parseProjectRow(row) {
|
|
486
|
+
return {
|
|
487
|
+
id: row["id"],
|
|
488
|
+
name: row["name"],
|
|
489
|
+
path: row["path"],
|
|
490
|
+
description: row["description"] || null,
|
|
491
|
+
memory_prefix: row["memory_prefix"] || null,
|
|
492
|
+
created_at: row["created_at"],
|
|
493
|
+
updated_at: row["updated_at"]
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
function registerProject(name, path, description, memoryPrefix, db) {
|
|
411
497
|
const d = db || getDatabase();
|
|
412
|
-
const
|
|
413
|
-
const
|
|
414
|
-
if (
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
498
|
+
const timestamp = now();
|
|
499
|
+
const existing = d.query("SELECT * FROM projects WHERE path = ?").get(path);
|
|
500
|
+
if (existing) {
|
|
501
|
+
const existingId = existing["id"];
|
|
502
|
+
d.run("UPDATE projects SET updated_at = ? WHERE id = ?", [
|
|
503
|
+
timestamp,
|
|
504
|
+
existingId
|
|
505
|
+
]);
|
|
506
|
+
return parseProjectRow(existing);
|
|
507
|
+
}
|
|
508
|
+
const id = uuid();
|
|
509
|
+
d.run("INSERT INTO projects (id, name, path, description, memory_prefix, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)", [id, name, path, description || null, memoryPrefix || null, timestamp, timestamp]);
|
|
510
|
+
return getProject(id, d);
|
|
511
|
+
}
|
|
512
|
+
function getProject(idOrPath, db) {
|
|
513
|
+
const d = db || getDatabase();
|
|
514
|
+
let row = d.query("SELECT * FROM projects WHERE id = ?").get(idOrPath);
|
|
515
|
+
if (row)
|
|
516
|
+
return parseProjectRow(row);
|
|
517
|
+
row = d.query("SELECT * FROM projects WHERE path = ?").get(idOrPath);
|
|
518
|
+
if (row)
|
|
519
|
+
return parseProjectRow(row);
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
function listProjects(db) {
|
|
523
|
+
const d = db || getDatabase();
|
|
524
|
+
const rows = d.query("SELECT * FROM projects ORDER BY updated_at DESC").all();
|
|
525
|
+
return rows.map(parseProjectRow);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/lib/extractor.ts
|
|
529
|
+
var TECH_KEYWORDS = new Set([
|
|
530
|
+
"typescript",
|
|
531
|
+
"javascript",
|
|
532
|
+
"python",
|
|
533
|
+
"rust",
|
|
534
|
+
"go",
|
|
535
|
+
"java",
|
|
536
|
+
"ruby",
|
|
537
|
+
"swift",
|
|
538
|
+
"kotlin",
|
|
539
|
+
"react",
|
|
540
|
+
"vue",
|
|
541
|
+
"angular",
|
|
542
|
+
"svelte",
|
|
543
|
+
"nextjs",
|
|
544
|
+
"bun",
|
|
545
|
+
"node",
|
|
546
|
+
"deno",
|
|
547
|
+
"sqlite",
|
|
548
|
+
"postgres",
|
|
549
|
+
"mysql",
|
|
550
|
+
"redis",
|
|
551
|
+
"docker",
|
|
552
|
+
"kubernetes",
|
|
553
|
+
"git",
|
|
554
|
+
"npm",
|
|
555
|
+
"yarn",
|
|
556
|
+
"pnpm",
|
|
557
|
+
"webpack",
|
|
558
|
+
"vite",
|
|
559
|
+
"tailwind",
|
|
560
|
+
"prisma",
|
|
561
|
+
"drizzle",
|
|
562
|
+
"zod",
|
|
563
|
+
"commander",
|
|
564
|
+
"express",
|
|
565
|
+
"fastify",
|
|
566
|
+
"hono"
|
|
567
|
+
]);
|
|
568
|
+
var FILE_PATH_RE = /(?:^|\s)((?:\/|\.\/|~\/)?(?:[\w.-]+\/)+[\w.-]+\.\w+)/g;
|
|
569
|
+
var URL_RE = /https?:\/\/[^\s)]+/g;
|
|
570
|
+
var NPM_PACKAGE_RE = /@[\w-]+\/[\w.-]+/g;
|
|
571
|
+
var PASCAL_CASE_RE = /\b([A-Z][a-z]+(?:[A-Z][a-z]+)+)\b/g;
|
|
572
|
+
function getSearchText(memory) {
|
|
573
|
+
const parts = [memory.key, memory.value];
|
|
574
|
+
if (memory.summary)
|
|
575
|
+
parts.push(memory.summary);
|
|
576
|
+
return parts.join(" ");
|
|
577
|
+
}
|
|
578
|
+
function extractEntities(memory, db) {
|
|
579
|
+
const text = getSearchText(memory);
|
|
580
|
+
const entityMap = new Map;
|
|
581
|
+
function add(name, type, confidence) {
|
|
582
|
+
const normalized = name.toLowerCase();
|
|
583
|
+
if (normalized.length < 3)
|
|
584
|
+
return;
|
|
585
|
+
const existing = entityMap.get(normalized);
|
|
586
|
+
if (!existing || existing.confidence < confidence) {
|
|
587
|
+
entityMap.set(normalized, { name: normalized, type, confidence });
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
for (const match of text.matchAll(FILE_PATH_RE)) {
|
|
591
|
+
add(match[1].trim(), "file", 0.9);
|
|
592
|
+
}
|
|
593
|
+
for (const match of text.matchAll(URL_RE)) {
|
|
594
|
+
add(match[0], "api", 0.8);
|
|
595
|
+
}
|
|
596
|
+
for (const match of text.matchAll(NPM_PACKAGE_RE)) {
|
|
597
|
+
add(match[0], "tool", 0.85);
|
|
598
|
+
}
|
|
599
|
+
try {
|
|
600
|
+
const d = db || getDatabase();
|
|
601
|
+
const agents = listAgents(d);
|
|
602
|
+
const textLower2 = text.toLowerCase();
|
|
603
|
+
for (const agent of agents) {
|
|
604
|
+
const nameLower = agent.name.toLowerCase();
|
|
605
|
+
if (nameLower.length >= 3 && textLower2.includes(nameLower)) {
|
|
606
|
+
add(agent.name, "person", 0.95);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
} catch {}
|
|
610
|
+
try {
|
|
611
|
+
const d = db || getDatabase();
|
|
612
|
+
const projects = listProjects(d);
|
|
613
|
+
const textLower2 = text.toLowerCase();
|
|
614
|
+
for (const project of projects) {
|
|
615
|
+
const nameLower = project.name.toLowerCase();
|
|
616
|
+
if (nameLower.length >= 3 && textLower2.includes(nameLower)) {
|
|
617
|
+
add(project.name, "project", 0.95);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
} catch {}
|
|
621
|
+
const textLower = text.toLowerCase();
|
|
622
|
+
for (const keyword of TECH_KEYWORDS) {
|
|
623
|
+
const re = new RegExp(`\\b${keyword}\\b`, "i");
|
|
624
|
+
if (re.test(textLower)) {
|
|
625
|
+
add(keyword, "tool", 0.7);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
for (const match of text.matchAll(PASCAL_CASE_RE)) {
|
|
629
|
+
add(match[1], "concept", 0.5);
|
|
630
|
+
}
|
|
631
|
+
return Array.from(entityMap.values()).sort((a, b) => b.confidence - a.confidence);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/db/entities.ts
|
|
635
|
+
function parseEntityRow(row) {
|
|
636
|
+
return {
|
|
637
|
+
id: row["id"],
|
|
638
|
+
name: row["name"],
|
|
639
|
+
type: row["type"],
|
|
640
|
+
description: row["description"] || null,
|
|
641
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
642
|
+
project_id: row["project_id"] || null,
|
|
643
|
+
created_at: row["created_at"],
|
|
644
|
+
updated_at: row["updated_at"]
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function createEntity(input, db) {
|
|
648
|
+
const d = db || getDatabase();
|
|
649
|
+
const timestamp = now();
|
|
650
|
+
const metadataJson = JSON.stringify(input.metadata || {});
|
|
651
|
+
const existing = d.query(`SELECT * FROM entities
|
|
652
|
+
WHERE name = ? AND type = ? AND COALESCE(project_id, '') = ?`).get(input.name, input.type, input.project_id || "");
|
|
653
|
+
if (existing) {
|
|
654
|
+
const sets = ["updated_at = ?"];
|
|
655
|
+
const params = [timestamp];
|
|
656
|
+
if (input.description !== undefined) {
|
|
657
|
+
sets.push("description = ?");
|
|
658
|
+
params.push(input.description);
|
|
659
|
+
}
|
|
660
|
+
if (input.metadata !== undefined) {
|
|
661
|
+
sets.push("metadata = ?");
|
|
662
|
+
params.push(metadataJson);
|
|
663
|
+
}
|
|
664
|
+
const existingId = existing["id"];
|
|
665
|
+
params.push(existingId);
|
|
666
|
+
d.run(`UPDATE entities SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
667
|
+
return getEntity(existingId, d);
|
|
668
|
+
}
|
|
669
|
+
const id = shortUuid();
|
|
670
|
+
d.run(`INSERT INTO entities (id, name, type, description, metadata, project_id, created_at, updated_at)
|
|
671
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
672
|
+
id,
|
|
673
|
+
input.name,
|
|
674
|
+
input.type,
|
|
675
|
+
input.description || null,
|
|
676
|
+
metadataJson,
|
|
677
|
+
input.project_id || null,
|
|
678
|
+
timestamp,
|
|
679
|
+
timestamp
|
|
680
|
+
]);
|
|
681
|
+
return getEntity(id, d);
|
|
682
|
+
}
|
|
683
|
+
function getEntity(id, db) {
|
|
684
|
+
const d = db || getDatabase();
|
|
685
|
+
const row = d.query("SELECT * FROM entities WHERE id = ?").get(id);
|
|
686
|
+
if (!row)
|
|
687
|
+
throw new EntityNotFoundError(id);
|
|
688
|
+
return parseEntityRow(row);
|
|
689
|
+
}
|
|
690
|
+
function getEntityByName(name, type, projectId, db) {
|
|
691
|
+
const d = db || getDatabase();
|
|
692
|
+
let sql = "SELECT * FROM entities WHERE name = ?";
|
|
693
|
+
const params = [name];
|
|
694
|
+
if (type) {
|
|
695
|
+
sql += " AND type = ?";
|
|
696
|
+
params.push(type);
|
|
697
|
+
}
|
|
698
|
+
if (projectId !== undefined) {
|
|
699
|
+
sql += " AND project_id = ?";
|
|
700
|
+
params.push(projectId);
|
|
701
|
+
}
|
|
702
|
+
sql += " LIMIT 1";
|
|
703
|
+
const row = d.query(sql).get(...params);
|
|
704
|
+
if (!row)
|
|
705
|
+
return null;
|
|
706
|
+
return parseEntityRow(row);
|
|
707
|
+
}
|
|
708
|
+
function listEntities(filter = {}, db) {
|
|
709
|
+
const d = db || getDatabase();
|
|
710
|
+
const conditions = [];
|
|
711
|
+
const params = [];
|
|
712
|
+
if (filter.type) {
|
|
713
|
+
conditions.push("type = ?");
|
|
714
|
+
params.push(filter.type);
|
|
715
|
+
}
|
|
716
|
+
if (filter.project_id) {
|
|
717
|
+
conditions.push("project_id = ?");
|
|
718
|
+
params.push(filter.project_id);
|
|
719
|
+
}
|
|
720
|
+
if (filter.search) {
|
|
721
|
+
conditions.push("(name LIKE ? OR description LIKE ?)");
|
|
722
|
+
const term = `%${filter.search}%`;
|
|
723
|
+
params.push(term, term);
|
|
724
|
+
}
|
|
725
|
+
let sql = "SELECT * FROM entities";
|
|
726
|
+
if (conditions.length > 0) {
|
|
727
|
+
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
728
|
+
}
|
|
729
|
+
sql += " ORDER BY updated_at DESC";
|
|
730
|
+
if (filter.limit) {
|
|
731
|
+
sql += " LIMIT ?";
|
|
732
|
+
params.push(filter.limit);
|
|
733
|
+
}
|
|
734
|
+
if (filter.offset) {
|
|
735
|
+
sql += " OFFSET ?";
|
|
736
|
+
params.push(filter.offset);
|
|
737
|
+
}
|
|
738
|
+
const rows = d.query(sql).all(...params);
|
|
739
|
+
return rows.map(parseEntityRow);
|
|
740
|
+
}
|
|
741
|
+
function updateEntity(id, input, db) {
|
|
742
|
+
const d = db || getDatabase();
|
|
743
|
+
const existing = d.query("SELECT id FROM entities WHERE id = ?").get(id);
|
|
744
|
+
if (!existing)
|
|
745
|
+
throw new EntityNotFoundError(id);
|
|
746
|
+
const sets = ["updated_at = ?"];
|
|
747
|
+
const params = [now()];
|
|
748
|
+
if (input.name !== undefined) {
|
|
749
|
+
sets.push("name = ?");
|
|
750
|
+
params.push(input.name);
|
|
751
|
+
}
|
|
752
|
+
if (input.type !== undefined) {
|
|
753
|
+
sets.push("type = ?");
|
|
754
|
+
params.push(input.type);
|
|
755
|
+
}
|
|
756
|
+
if (input.description !== undefined) {
|
|
757
|
+
sets.push("description = ?");
|
|
758
|
+
params.push(input.description);
|
|
759
|
+
}
|
|
760
|
+
if (input.metadata !== undefined) {
|
|
761
|
+
sets.push("metadata = ?");
|
|
762
|
+
params.push(JSON.stringify(input.metadata));
|
|
763
|
+
}
|
|
764
|
+
params.push(id);
|
|
765
|
+
d.run(`UPDATE entities SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
766
|
+
return getEntity(id, d);
|
|
767
|
+
}
|
|
768
|
+
function deleteEntity(id, db) {
|
|
769
|
+
const d = db || getDatabase();
|
|
770
|
+
const result = d.run("DELETE FROM entities WHERE id = ?", [id]);
|
|
771
|
+
if (result.changes === 0)
|
|
772
|
+
throw new EntityNotFoundError(id);
|
|
773
|
+
}
|
|
774
|
+
function mergeEntities(sourceId, targetId, db) {
|
|
775
|
+
const d = db || getDatabase();
|
|
776
|
+
getEntity(sourceId, d);
|
|
777
|
+
getEntity(targetId, d);
|
|
778
|
+
d.run(`UPDATE OR IGNORE relations SET source_entity_id = ? WHERE source_entity_id = ?`, [targetId, sourceId]);
|
|
779
|
+
d.run(`UPDATE OR IGNORE relations SET target_entity_id = ? WHERE target_entity_id = ?`, [targetId, sourceId]);
|
|
780
|
+
d.run("DELETE FROM relations WHERE source_entity_id = ? OR target_entity_id = ?", [
|
|
781
|
+
sourceId,
|
|
782
|
+
sourceId
|
|
783
|
+
]);
|
|
784
|
+
d.run(`UPDATE OR IGNORE entity_memories SET entity_id = ? WHERE entity_id = ?`, [targetId, sourceId]);
|
|
785
|
+
d.run("DELETE FROM entity_memories WHERE entity_id = ?", [sourceId]);
|
|
786
|
+
d.run("DELETE FROM entities WHERE id = ?", [sourceId]);
|
|
787
|
+
d.run("UPDATE entities SET updated_at = ? WHERE id = ?", [now(), targetId]);
|
|
788
|
+
return getEntity(targetId, d);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// src/db/entity-memories.ts
|
|
792
|
+
function parseEntityRow2(row) {
|
|
793
|
+
return {
|
|
794
|
+
id: row["id"],
|
|
795
|
+
name: row["name"],
|
|
796
|
+
type: row["type"],
|
|
797
|
+
description: row["description"] || null,
|
|
798
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
799
|
+
project_id: row["project_id"] || null,
|
|
800
|
+
created_at: row["created_at"],
|
|
801
|
+
updated_at: row["updated_at"]
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
function parseEntityMemoryRow(row) {
|
|
805
|
+
return {
|
|
806
|
+
entity_id: row["entity_id"],
|
|
807
|
+
memory_id: row["memory_id"],
|
|
808
|
+
role: row["role"],
|
|
809
|
+
created_at: row["created_at"]
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
function linkEntityToMemory(entityId, memoryId, role = "context", db) {
|
|
813
|
+
const d = db || getDatabase();
|
|
814
|
+
const timestamp = now();
|
|
815
|
+
d.run(`INSERT OR IGNORE INTO entity_memories (entity_id, memory_id, role, created_at)
|
|
816
|
+
VALUES (?, ?, ?, ?)`, [entityId, memoryId, role, timestamp]);
|
|
817
|
+
const row = d.query("SELECT * FROM entity_memories WHERE entity_id = ? AND memory_id = ?").get(entityId, memoryId);
|
|
818
|
+
return parseEntityMemoryRow(row);
|
|
819
|
+
}
|
|
820
|
+
function unlinkEntityFromMemory(entityId, memoryId, db) {
|
|
821
|
+
const d = db || getDatabase();
|
|
822
|
+
d.run("DELETE FROM entity_memories WHERE entity_id = ? AND memory_id = ?", [entityId, memoryId]);
|
|
823
|
+
}
|
|
824
|
+
function getMemoriesForEntity(entityId, db) {
|
|
825
|
+
const d = db || getDatabase();
|
|
826
|
+
const rows = d.query(`SELECT m.* FROM memories m
|
|
827
|
+
INNER JOIN entity_memories em ON em.memory_id = m.id
|
|
828
|
+
WHERE em.entity_id = ?
|
|
829
|
+
ORDER BY m.importance DESC, m.created_at DESC`).all(entityId);
|
|
830
|
+
return rows.map(parseMemoryRow);
|
|
831
|
+
}
|
|
832
|
+
function getEntitiesForMemory(memoryId, db) {
|
|
833
|
+
const d = db || getDatabase();
|
|
834
|
+
const rows = d.query(`SELECT e.* FROM entities e
|
|
835
|
+
INNER JOIN entity_memories em ON em.entity_id = e.id
|
|
836
|
+
WHERE em.memory_id = ?
|
|
837
|
+
ORDER BY e.name ASC`).all(memoryId);
|
|
838
|
+
return rows.map(parseEntityRow2);
|
|
839
|
+
}
|
|
840
|
+
function bulkLinkEntities(entityIds, memoryId, role = "context", db) {
|
|
841
|
+
const d = db || getDatabase();
|
|
842
|
+
const timestamp = now();
|
|
843
|
+
const tx = d.transaction(() => {
|
|
844
|
+
const stmt = d.prepare(`INSERT OR IGNORE INTO entity_memories (entity_id, memory_id, role, created_at)
|
|
845
|
+
VALUES (?, ?, ?, ?)`);
|
|
846
|
+
for (const entityId of entityIds) {
|
|
847
|
+
stmt.run(entityId, memoryId, role, timestamp);
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
tx();
|
|
851
|
+
}
|
|
852
|
+
function getEntityMemoryLinks(entityId, memoryId, db) {
|
|
853
|
+
const d = db || getDatabase();
|
|
854
|
+
const conditions = [];
|
|
855
|
+
const params = [];
|
|
856
|
+
if (entityId) {
|
|
857
|
+
conditions.push("entity_id = ?");
|
|
858
|
+
params.push(entityId);
|
|
859
|
+
}
|
|
860
|
+
if (memoryId) {
|
|
861
|
+
conditions.push("memory_id = ?");
|
|
862
|
+
params.push(memoryId);
|
|
863
|
+
}
|
|
864
|
+
let sql = "SELECT * FROM entity_memories";
|
|
865
|
+
if (conditions.length > 0) {
|
|
866
|
+
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
867
|
+
}
|
|
868
|
+
sql += " ORDER BY created_at DESC";
|
|
869
|
+
const rows = d.query(sql).all(...params);
|
|
870
|
+
return rows.map(parseEntityMemoryRow);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// src/db/relations.ts
|
|
874
|
+
function parseRelationRow(row) {
|
|
875
|
+
return {
|
|
876
|
+
id: row["id"],
|
|
877
|
+
source_entity_id: row["source_entity_id"],
|
|
878
|
+
target_entity_id: row["target_entity_id"],
|
|
879
|
+
relation_type: row["relation_type"],
|
|
880
|
+
weight: row["weight"],
|
|
881
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
882
|
+
created_at: row["created_at"]
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
function parseEntityRow3(row) {
|
|
886
|
+
return {
|
|
887
|
+
id: row["id"],
|
|
888
|
+
name: row["name"],
|
|
889
|
+
type: row["type"],
|
|
890
|
+
description: row["description"] || null,
|
|
891
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
892
|
+
project_id: row["project_id"] || null,
|
|
893
|
+
created_at: row["created_at"],
|
|
894
|
+
updated_at: row["updated_at"]
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
function createRelation(input, db) {
|
|
898
|
+
const d = db || getDatabase();
|
|
899
|
+
const id = shortUuid();
|
|
900
|
+
const timestamp = now();
|
|
901
|
+
const weight = input.weight ?? 1;
|
|
902
|
+
const metadata = JSON.stringify(input.metadata ?? {});
|
|
903
|
+
d.run(`INSERT INTO relations (id, source_entity_id, target_entity_id, relation_type, weight, metadata, created_at)
|
|
904
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
905
|
+
ON CONFLICT(source_entity_id, target_entity_id, relation_type)
|
|
906
|
+
DO UPDATE SET weight = excluded.weight, metadata = excluded.metadata`, [id, input.source_entity_id, input.target_entity_id, input.relation_type, weight, metadata, timestamp]);
|
|
907
|
+
const row = d.query(`SELECT * FROM relations
|
|
908
|
+
WHERE source_entity_id = ? AND target_entity_id = ? AND relation_type = ?`).get(input.source_entity_id, input.target_entity_id, input.relation_type);
|
|
909
|
+
return parseRelationRow(row);
|
|
910
|
+
}
|
|
911
|
+
function getRelation(id, db) {
|
|
912
|
+
const d = db || getDatabase();
|
|
913
|
+
const row = d.query("SELECT * FROM relations WHERE id = ?").get(id);
|
|
914
|
+
if (!row)
|
|
915
|
+
throw new Error(`Relation not found: ${id}`);
|
|
916
|
+
return parseRelationRow(row);
|
|
917
|
+
}
|
|
918
|
+
function listRelations(filter, db) {
|
|
919
|
+
const d = db || getDatabase();
|
|
920
|
+
const conditions = [];
|
|
921
|
+
const params = [];
|
|
922
|
+
if (filter.entity_id) {
|
|
923
|
+
const dir = filter.direction || "both";
|
|
924
|
+
if (dir === "outgoing") {
|
|
925
|
+
conditions.push("source_entity_id = ?");
|
|
926
|
+
params.push(filter.entity_id);
|
|
927
|
+
} else if (dir === "incoming") {
|
|
928
|
+
conditions.push("target_entity_id = ?");
|
|
929
|
+
params.push(filter.entity_id);
|
|
930
|
+
} else {
|
|
931
|
+
conditions.push("(source_entity_id = ? OR target_entity_id = ?)");
|
|
932
|
+
params.push(filter.entity_id, filter.entity_id);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (filter.relation_type) {
|
|
936
|
+
conditions.push("relation_type = ?");
|
|
937
|
+
params.push(filter.relation_type);
|
|
938
|
+
}
|
|
939
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
940
|
+
const rows = d.query(`SELECT * FROM relations ${where} ORDER BY created_at DESC`).all(...params);
|
|
941
|
+
return rows.map(parseRelationRow);
|
|
942
|
+
}
|
|
943
|
+
function deleteRelation(id, db) {
|
|
944
|
+
const d = db || getDatabase();
|
|
945
|
+
const result = d.run("DELETE FROM relations WHERE id = ?", [id]);
|
|
946
|
+
if (result.changes === 0)
|
|
947
|
+
throw new Error(`Relation not found: ${id}`);
|
|
948
|
+
}
|
|
949
|
+
function getRelatedEntities(entityId, relationType, db) {
|
|
950
|
+
const d = db || getDatabase();
|
|
951
|
+
let sql;
|
|
952
|
+
const params = [];
|
|
953
|
+
if (relationType) {
|
|
954
|
+
sql = `
|
|
955
|
+
SELECT DISTINCT e.* FROM entities e
|
|
956
|
+
JOIN relations r ON (
|
|
957
|
+
(r.source_entity_id = ? AND r.target_entity_id = e.id)
|
|
958
|
+
OR (r.target_entity_id = ? AND r.source_entity_id = e.id)
|
|
959
|
+
)
|
|
960
|
+
WHERE r.relation_type = ?
|
|
961
|
+
`;
|
|
962
|
+
params.push(entityId, entityId, relationType);
|
|
963
|
+
} else {
|
|
964
|
+
sql = `
|
|
965
|
+
SELECT DISTINCT e.* FROM entities e
|
|
966
|
+
JOIN relations r ON (
|
|
967
|
+
(r.source_entity_id = ? AND r.target_entity_id = e.id)
|
|
968
|
+
OR (r.target_entity_id = ? AND r.source_entity_id = e.id)
|
|
969
|
+
)
|
|
970
|
+
`;
|
|
971
|
+
params.push(entityId, entityId);
|
|
972
|
+
}
|
|
973
|
+
const rows = d.query(sql).all(...params);
|
|
974
|
+
return rows.map(parseEntityRow3);
|
|
975
|
+
}
|
|
976
|
+
function getEntityGraph(entityId, depth = 2, db) {
|
|
977
|
+
const d = db || getDatabase();
|
|
978
|
+
const entityRows = d.query(`WITH RECURSIVE graph(id, depth) AS (
|
|
979
|
+
VALUES(?, 0)
|
|
980
|
+
UNION
|
|
981
|
+
SELECT CASE WHEN r.source_entity_id = g.id THEN r.target_entity_id ELSE r.source_entity_id END, g.depth + 1
|
|
982
|
+
FROM relations r JOIN graph g ON (r.source_entity_id = g.id OR r.target_entity_id = g.id)
|
|
983
|
+
WHERE g.depth < ?
|
|
984
|
+
)
|
|
985
|
+
SELECT DISTINCT e.* FROM entities e JOIN graph g ON e.id = g.id`).all(entityId, depth);
|
|
986
|
+
const entities = entityRows.map(parseEntityRow3);
|
|
987
|
+
const entityIds = new Set(entities.map((e) => e.id));
|
|
988
|
+
if (entityIds.size === 0) {
|
|
989
|
+
return { entities: [], relations: [] };
|
|
990
|
+
}
|
|
991
|
+
const placeholders = Array.from(entityIds).map(() => "?").join(",");
|
|
992
|
+
const relationRows = d.query(`SELECT * FROM relations
|
|
993
|
+
WHERE source_entity_id IN (${placeholders})
|
|
994
|
+
AND target_entity_id IN (${placeholders})`).all(...Array.from(entityIds), ...Array.from(entityIds));
|
|
995
|
+
const relations = relationRows.map(parseRelationRow);
|
|
996
|
+
return { entities, relations };
|
|
997
|
+
}
|
|
998
|
+
function findPath(fromEntityId, toEntityId, maxDepth = 5, db) {
|
|
999
|
+
const d = db || getDatabase();
|
|
1000
|
+
const rows = d.query(`WITH RECURSIVE path(id, trail, depth) AS (
|
|
1001
|
+
SELECT ?, ?, 0
|
|
1002
|
+
UNION
|
|
1003
|
+
SELECT
|
|
1004
|
+
CASE WHEN r.source_entity_id = p.id THEN r.target_entity_id ELSE r.source_entity_id END,
|
|
1005
|
+
p.trail || ',' || CASE WHEN r.source_entity_id = p.id THEN r.target_entity_id ELSE r.source_entity_id END,
|
|
1006
|
+
p.depth + 1
|
|
1007
|
+
FROM relations r JOIN path p ON (r.source_entity_id = p.id OR r.target_entity_id = p.id)
|
|
1008
|
+
WHERE p.depth < ?
|
|
1009
|
+
AND INSTR(p.trail, CASE WHEN r.source_entity_id = p.id THEN r.target_entity_id ELSE r.source_entity_id END) = 0
|
|
1010
|
+
)
|
|
1011
|
+
SELECT trail FROM path WHERE id = ? ORDER BY depth ASC LIMIT 1`).get(fromEntityId, fromEntityId, maxDepth, toEntityId);
|
|
1012
|
+
if (!rows)
|
|
1013
|
+
return null;
|
|
1014
|
+
const ids = rows.trail.split(",");
|
|
1015
|
+
const entities = [];
|
|
1016
|
+
for (const id of ids) {
|
|
1017
|
+
const row = d.query("SELECT * FROM entities WHERE id = ?").get(id);
|
|
1018
|
+
if (row)
|
|
1019
|
+
entities.push(parseEntityRow3(row));
|
|
1020
|
+
}
|
|
1021
|
+
return entities.length > 0 ? entities : null;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// src/lib/config.ts
|
|
1025
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync } from "fs";
|
|
1026
|
+
import { homedir } from "os";
|
|
1027
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
1028
|
+
var DEFAULT_CONFIG = {
|
|
1029
|
+
default_scope: "private",
|
|
1030
|
+
default_category: "knowledge",
|
|
1031
|
+
default_importance: 5,
|
|
1032
|
+
max_entries: 1000,
|
|
1033
|
+
max_entries_per_scope: {
|
|
1034
|
+
global: 500,
|
|
1035
|
+
shared: 300,
|
|
1036
|
+
private: 200
|
|
1037
|
+
},
|
|
1038
|
+
injection: {
|
|
1039
|
+
max_tokens: 500,
|
|
1040
|
+
min_importance: 5,
|
|
1041
|
+
categories: ["preference", "fact"],
|
|
1042
|
+
refresh_interval: 5
|
|
1043
|
+
},
|
|
1044
|
+
extraction: {
|
|
1045
|
+
enabled: true,
|
|
1046
|
+
min_confidence: 0.5
|
|
1047
|
+
},
|
|
1048
|
+
sync_agents: ["claude", "codex", "gemini"],
|
|
1049
|
+
auto_cleanup: {
|
|
1050
|
+
enabled: true,
|
|
1051
|
+
expired_check_interval: 3600,
|
|
1052
|
+
unused_archive_days: 7,
|
|
1053
|
+
stale_deprioritize_days: 14
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
function deepMerge(target, source) {
|
|
1057
|
+
const result = { ...target };
|
|
1058
|
+
for (const key of Object.keys(source)) {
|
|
1059
|
+
const sourceVal = source[key];
|
|
1060
|
+
const targetVal = result[key];
|
|
1061
|
+
if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) {
|
|
1062
|
+
result[key] = deepMerge(targetVal, sourceVal);
|
|
1063
|
+
} else {
|
|
1064
|
+
result[key] = sourceVal;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return result;
|
|
1068
|
+
}
|
|
1069
|
+
var VALID_SCOPES = ["global", "shared", "private"];
|
|
1070
|
+
var VALID_CATEGORIES = [
|
|
1071
|
+
"preference",
|
|
1072
|
+
"fact",
|
|
1073
|
+
"knowledge",
|
|
1074
|
+
"history"
|
|
1075
|
+
];
|
|
1076
|
+
function isValidScope(value) {
|
|
1077
|
+
return VALID_SCOPES.includes(value);
|
|
1078
|
+
}
|
|
1079
|
+
function isValidCategory(value) {
|
|
1080
|
+
return VALID_CATEGORIES.includes(value);
|
|
1081
|
+
}
|
|
1082
|
+
function loadConfig() {
|
|
1083
|
+
const configPath = join2(homedir(), ".mementos", "config.json");
|
|
1084
|
+
let fileConfig = {};
|
|
1085
|
+
if (existsSync2(configPath)) {
|
|
1086
|
+
try {
|
|
1087
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
1088
|
+
fileConfig = JSON.parse(raw);
|
|
1089
|
+
} catch {}
|
|
1090
|
+
}
|
|
1091
|
+
const merged = deepMerge(DEFAULT_CONFIG, fileConfig);
|
|
1092
|
+
const envScope = process.env["MEMENTOS_DEFAULT_SCOPE"];
|
|
1093
|
+
if (envScope && isValidScope(envScope)) {
|
|
1094
|
+
merged.default_scope = envScope;
|
|
1095
|
+
}
|
|
1096
|
+
const envCategory = process.env["MEMENTOS_DEFAULT_CATEGORY"];
|
|
1097
|
+
if (envCategory && isValidCategory(envCategory)) {
|
|
1098
|
+
merged.default_category = envCategory;
|
|
1099
|
+
}
|
|
1100
|
+
const envImportance = process.env["MEMENTOS_DEFAULT_IMPORTANCE"];
|
|
1101
|
+
if (envImportance) {
|
|
1102
|
+
const parsed = parseInt(envImportance, 10);
|
|
1103
|
+
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 10) {
|
|
1104
|
+
merged.default_importance = parsed;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
return merged;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// src/db/memories.ts
|
|
1111
|
+
function runEntityExtraction(memory, projectId, d) {
|
|
1112
|
+
const config = loadConfig();
|
|
1113
|
+
if (config.extraction?.enabled === false)
|
|
1114
|
+
return;
|
|
1115
|
+
const extracted = extractEntities(memory, d);
|
|
1116
|
+
const minConfidence = config.extraction?.min_confidence ?? 0.5;
|
|
1117
|
+
const entityIds = [];
|
|
1118
|
+
for (const ext of extracted) {
|
|
1119
|
+
if (ext.confidence >= minConfidence) {
|
|
1120
|
+
const entity = createEntity({ name: ext.name, type: ext.type, project_id: projectId }, d);
|
|
1121
|
+
linkEntityToMemory(entity.id, memory.id, "context", d);
|
|
1122
|
+
entityIds.push(entity.id);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
for (let i = 0;i < entityIds.length; i++) {
|
|
1126
|
+
for (let j = i + 1;j < entityIds.length; j++) {
|
|
1127
|
+
try {
|
|
1128
|
+
createRelation({ source_entity_id: entityIds[i], target_entity_id: entityIds[j], relation_type: "related_to" }, d);
|
|
1129
|
+
} catch {}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
function parseMemoryRow(row) {
|
|
1134
|
+
return {
|
|
1135
|
+
id: row["id"],
|
|
1136
|
+
key: row["key"],
|
|
1137
|
+
value: row["value"],
|
|
1138
|
+
category: row["category"],
|
|
1139
|
+
scope: row["scope"],
|
|
1140
|
+
summary: row["summary"] || null,
|
|
1141
|
+
tags: JSON.parse(row["tags"] || "[]"),
|
|
1142
|
+
importance: row["importance"],
|
|
1143
|
+
source: row["source"],
|
|
1144
|
+
status: row["status"],
|
|
1145
|
+
pinned: !!row["pinned"],
|
|
1146
|
+
agent_id: row["agent_id"] || null,
|
|
1147
|
+
project_id: row["project_id"] || null,
|
|
1148
|
+
session_id: row["session_id"] || null,
|
|
1149
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
1150
|
+
access_count: row["access_count"],
|
|
1151
|
+
version: row["version"],
|
|
1152
|
+
expires_at: row["expires_at"] || null,
|
|
1153
|
+
created_at: row["created_at"],
|
|
1154
|
+
updated_at: row["updated_at"],
|
|
1155
|
+
accessed_at: row["accessed_at"] || null
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
function createMemory(input, dedupeMode = "merge", db) {
|
|
1159
|
+
const d = db || getDatabase();
|
|
1160
|
+
const timestamp = now();
|
|
1161
|
+
let expiresAt = input.expires_at || null;
|
|
1162
|
+
if (input.ttl_ms && !expiresAt) {
|
|
1163
|
+
expiresAt = new Date(Date.now() + input.ttl_ms).toISOString();
|
|
1164
|
+
}
|
|
1165
|
+
const id = uuid();
|
|
1166
|
+
const tags = input.tags || [];
|
|
1167
|
+
const tagsJson = JSON.stringify(tags);
|
|
1168
|
+
const metadataJson = JSON.stringify(input.metadata || {});
|
|
1169
|
+
const safeValue = redactSecrets(input.value);
|
|
1170
|
+
const safeSummary = input.summary ? redactSecrets(input.summary) : null;
|
|
1171
|
+
if (dedupeMode === "merge") {
|
|
1172
|
+
const existing = d.query(`SELECT id, version FROM memories
|
|
1173
|
+
WHERE key = ? AND scope = ?
|
|
1174
|
+
AND COALESCE(agent_id, '') = ?
|
|
1175
|
+
AND COALESCE(project_id, '') = ?
|
|
1176
|
+
AND COALESCE(session_id, '') = ?`).get(input.key, input.scope || "private", input.agent_id || "", input.project_id || "", input.session_id || "");
|
|
1177
|
+
if (existing) {
|
|
1178
|
+
d.run(`UPDATE memories SET
|
|
1179
|
+
value = ?, category = ?, summary = ?, tags = ?,
|
|
1180
|
+
importance = ?, metadata = ?, expires_at = ?,
|
|
1181
|
+
pinned = COALESCE(pinned, 0),
|
|
1182
|
+
version = version + 1, updated_at = ?
|
|
1183
|
+
WHERE id = ?`, [
|
|
1184
|
+
safeValue,
|
|
1185
|
+
input.category || "knowledge",
|
|
1186
|
+
safeSummary,
|
|
1187
|
+
tagsJson,
|
|
1188
|
+
input.importance ?? 5,
|
|
1189
|
+
metadataJson,
|
|
1190
|
+
expiresAt,
|
|
1191
|
+
timestamp,
|
|
1192
|
+
existing.id
|
|
1193
|
+
]);
|
|
1194
|
+
d.run("DELETE FROM memory_tags WHERE memory_id = ?", [existing.id]);
|
|
1195
|
+
const insertTag2 = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
1196
|
+
for (const tag of tags) {
|
|
1197
|
+
insertTag2.run(existing.id, tag);
|
|
1198
|
+
}
|
|
1199
|
+
const merged = getMemory(existing.id, d);
|
|
1200
|
+
try {
|
|
1201
|
+
const oldLinks = getEntityMemoryLinks(undefined, merged.id, d);
|
|
1202
|
+
for (const link of oldLinks) {
|
|
1203
|
+
unlinkEntityFromMemory(link.entity_id, merged.id, d);
|
|
1204
|
+
}
|
|
1205
|
+
runEntityExtraction(merged, input.project_id, d);
|
|
1206
|
+
} catch {}
|
|
1207
|
+
return merged;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
d.run(`INSERT INTO memories (id, key, value, category, scope, summary, tags, importance, source, status, pinned, agent_id, project_id, session_id, metadata, access_count, version, expires_at, created_at, updated_at)
|
|
1211
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', 0, ?, ?, ?, ?, 0, 1, ?, ?, ?)`, [
|
|
1212
|
+
id,
|
|
1213
|
+
input.key,
|
|
1214
|
+
input.value,
|
|
1215
|
+
input.category || "knowledge",
|
|
1216
|
+
input.scope || "private",
|
|
1217
|
+
input.summary || null,
|
|
1218
|
+
tagsJson,
|
|
1219
|
+
input.importance ?? 5,
|
|
1220
|
+
input.source || "agent",
|
|
1221
|
+
input.agent_id || null,
|
|
1222
|
+
input.project_id || null,
|
|
1223
|
+
input.session_id || null,
|
|
1224
|
+
metadataJson,
|
|
1225
|
+
expiresAt,
|
|
1226
|
+
timestamp,
|
|
1227
|
+
timestamp
|
|
1228
|
+
]);
|
|
1229
|
+
const insertTag = d.prepare("INSERT OR IGNORE INTO memory_tags (memory_id, tag) VALUES (?, ?)");
|
|
1230
|
+
for (const tag of tags) {
|
|
1231
|
+
insertTag.run(id, tag);
|
|
1232
|
+
}
|
|
1233
|
+
const memory = getMemory(id, d);
|
|
1234
|
+
try {
|
|
1235
|
+
runEntityExtraction(memory, input.project_id, d);
|
|
1236
|
+
} catch {}
|
|
1237
|
+
return memory;
|
|
1238
|
+
}
|
|
1239
|
+
function getMemory(id, db) {
|
|
1240
|
+
const d = db || getDatabase();
|
|
1241
|
+
const row = d.query("SELECT * FROM memories WHERE id = ?").get(id);
|
|
1242
|
+
if (!row)
|
|
1243
|
+
return null;
|
|
1244
|
+
return parseMemoryRow(row);
|
|
1245
|
+
}
|
|
1246
|
+
function getMemoryByKey(key, scope, agentId, projectId, sessionId, db) {
|
|
1247
|
+
const d = db || getDatabase();
|
|
1248
|
+
let sql = "SELECT * FROM memories WHERE key = ?";
|
|
1249
|
+
const params = [key];
|
|
1250
|
+
if (scope) {
|
|
1251
|
+
sql += " AND scope = ?";
|
|
1252
|
+
params.push(scope);
|
|
1253
|
+
}
|
|
1254
|
+
if (agentId) {
|
|
1255
|
+
sql += " AND agent_id = ?";
|
|
1256
|
+
params.push(agentId);
|
|
1257
|
+
}
|
|
1258
|
+
if (projectId) {
|
|
1259
|
+
sql += " AND project_id = ?";
|
|
1260
|
+
params.push(projectId);
|
|
1261
|
+
}
|
|
1262
|
+
if (sessionId) {
|
|
1263
|
+
sql += " AND session_id = ?";
|
|
1264
|
+
params.push(sessionId);
|
|
1265
|
+
}
|
|
1266
|
+
sql += " AND status = 'active' ORDER BY importance DESC LIMIT 1";
|
|
1267
|
+
const row = d.query(sql).get(...params);
|
|
1268
|
+
if (!row)
|
|
1269
|
+
return null;
|
|
1270
|
+
return parseMemoryRow(row);
|
|
1271
|
+
}
|
|
1272
|
+
function listMemories(filter, db) {
|
|
1273
|
+
const d = db || getDatabase();
|
|
1274
|
+
const conditions = [];
|
|
1275
|
+
const params = [];
|
|
1276
|
+
if (filter) {
|
|
1277
|
+
if (filter.scope) {
|
|
1278
|
+
if (Array.isArray(filter.scope)) {
|
|
1279
|
+
conditions.push(`scope IN (${filter.scope.map(() => "?").join(",")})`);
|
|
1280
|
+
params.push(...filter.scope);
|
|
1281
|
+
} else {
|
|
1282
|
+
conditions.push("scope = ?");
|
|
1283
|
+
params.push(filter.scope);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
if (filter.category) {
|
|
1287
|
+
if (Array.isArray(filter.category)) {
|
|
1288
|
+
conditions.push(`category IN (${filter.category.map(() => "?").join(",")})`);
|
|
1289
|
+
params.push(...filter.category);
|
|
1290
|
+
} else {
|
|
1291
|
+
conditions.push("category = ?");
|
|
1292
|
+
params.push(filter.category);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
if (filter.source) {
|
|
1296
|
+
if (Array.isArray(filter.source)) {
|
|
1297
|
+
conditions.push(`source IN (${filter.source.map(() => "?").join(",")})`);
|
|
1298
|
+
params.push(...filter.source);
|
|
1299
|
+
} else {
|
|
1300
|
+
conditions.push("source = ?");
|
|
1301
|
+
params.push(filter.source);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
if (filter.status) {
|
|
1305
|
+
if (Array.isArray(filter.status)) {
|
|
1306
|
+
conditions.push(`status IN (${filter.status.map(() => "?").join(",")})`);
|
|
1307
|
+
params.push(...filter.status);
|
|
1308
|
+
} else {
|
|
1309
|
+
conditions.push("status = ?");
|
|
1310
|
+
params.push(filter.status);
|
|
1311
|
+
}
|
|
1312
|
+
} else {
|
|
1313
|
+
conditions.push("status = 'active'");
|
|
452
1314
|
}
|
|
453
1315
|
if (filter.project_id) {
|
|
454
1316
|
conditions.push("project_id = ?");
|
|
@@ -508,6 +1370,23 @@ function updateMemory(id, input, db) {
|
|
|
508
1370
|
if (existing.version !== input.version) {
|
|
509
1371
|
throw new VersionConflictError(id, input.version, existing.version);
|
|
510
1372
|
}
|
|
1373
|
+
try {
|
|
1374
|
+
d.run(`INSERT OR IGNORE INTO memory_versions (id, memory_id, version, value, importance, scope, category, tags, summary, pinned, status, created_at)
|
|
1375
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
1376
|
+
uuid(),
|
|
1377
|
+
existing.id,
|
|
1378
|
+
existing.version,
|
|
1379
|
+
existing.value,
|
|
1380
|
+
existing.importance,
|
|
1381
|
+
existing.scope,
|
|
1382
|
+
existing.category,
|
|
1383
|
+
JSON.stringify(existing.tags),
|
|
1384
|
+
existing.summary,
|
|
1385
|
+
existing.pinned ? 1 : 0,
|
|
1386
|
+
existing.status,
|
|
1387
|
+
existing.updated_at
|
|
1388
|
+
]);
|
|
1389
|
+
} catch {}
|
|
511
1390
|
const sets = ["version = version + 1", "updated_at = ?"];
|
|
512
1391
|
const params = [now()];
|
|
513
1392
|
if (input.value !== undefined) {
|
|
@@ -557,156 +1436,48 @@ function updateMemory(id, input, db) {
|
|
|
557
1436
|
}
|
|
558
1437
|
params.push(id);
|
|
559
1438
|
d.run(`UPDATE memories SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
const placeholders = ids.map(() => "?").join(",");
|
|
572
|
-
const result = d.run(`DELETE FROM memories WHERE id IN (${placeholders})`, ids);
|
|
573
|
-
return result.changes;
|
|
574
|
-
}
|
|
575
|
-
function touchMemory(id, db) {
|
|
576
|
-
const d = db || getDatabase();
|
|
577
|
-
d.run("UPDATE memories SET access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now(), id]);
|
|
578
|
-
}
|
|
579
|
-
function cleanExpiredMemories(db) {
|
|
580
|
-
const d = db || getDatabase();
|
|
581
|
-
const timestamp = now();
|
|
582
|
-
const result = d.run("DELETE FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?", [timestamp]);
|
|
583
|
-
return result.changes;
|
|
584
|
-
}
|
|
585
|
-
// src/db/agents.ts
|
|
586
|
-
function parseAgentRow(row) {
|
|
587
|
-
return {
|
|
588
|
-
id: row["id"],
|
|
589
|
-
name: row["name"],
|
|
590
|
-
description: row["description"] || null,
|
|
591
|
-
role: row["role"] || null,
|
|
592
|
-
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
593
|
-
created_at: row["created_at"],
|
|
594
|
-
last_seen_at: row["last_seen_at"]
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
function registerAgent(name, description, role, db) {
|
|
598
|
-
const d = db || getDatabase();
|
|
599
|
-
const timestamp = now();
|
|
600
|
-
const existing = d.query("SELECT * FROM agents WHERE name = ?").get(name);
|
|
601
|
-
if (existing) {
|
|
602
|
-
const existingId = existing["id"];
|
|
603
|
-
d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [
|
|
604
|
-
timestamp,
|
|
605
|
-
existingId
|
|
606
|
-
]);
|
|
607
|
-
if (description) {
|
|
608
|
-
d.run("UPDATE agents SET description = ? WHERE id = ?", [
|
|
609
|
-
description,
|
|
610
|
-
existingId
|
|
611
|
-
]);
|
|
612
|
-
}
|
|
613
|
-
if (role) {
|
|
614
|
-
d.run("UPDATE agents SET role = ? WHERE id = ?", [
|
|
615
|
-
role,
|
|
616
|
-
existingId
|
|
617
|
-
]);
|
|
618
|
-
}
|
|
619
|
-
return getAgent(existingId, d);
|
|
620
|
-
}
|
|
621
|
-
const id = shortUuid();
|
|
622
|
-
d.run("INSERT INTO agents (id, name, description, role, created_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?)", [id, name, description || null, role || "agent", timestamp, timestamp]);
|
|
623
|
-
return getAgent(id, d);
|
|
624
|
-
}
|
|
625
|
-
function getAgent(idOrName, db) {
|
|
626
|
-
const d = db || getDatabase();
|
|
627
|
-
let row = d.query("SELECT * FROM agents WHERE id = ?").get(idOrName);
|
|
628
|
-
if (row)
|
|
629
|
-
return parseAgentRow(row);
|
|
630
|
-
row = d.query("SELECT * FROM agents WHERE name = ?").get(idOrName);
|
|
631
|
-
if (row)
|
|
632
|
-
return parseAgentRow(row);
|
|
633
|
-
const rows = d.query("SELECT * FROM agents WHERE id LIKE ?").all(`${idOrName}%`);
|
|
634
|
-
if (rows.length === 1)
|
|
635
|
-
return parseAgentRow(rows[0]);
|
|
636
|
-
return null;
|
|
637
|
-
}
|
|
638
|
-
function listAgents(db) {
|
|
639
|
-
const d = db || getDatabase();
|
|
640
|
-
const rows = d.query("SELECT * FROM agents ORDER BY last_seen_at DESC").all();
|
|
641
|
-
return rows.map(parseAgentRow);
|
|
642
|
-
}
|
|
643
|
-
function updateAgent(id, updates, db) {
|
|
644
|
-
const d = db || getDatabase();
|
|
645
|
-
const agent = getAgent(id, d);
|
|
646
|
-
if (!agent)
|
|
647
|
-
return null;
|
|
648
|
-
const timestamp = now();
|
|
649
|
-
if (updates.name && updates.name !== agent.name) {
|
|
650
|
-
const existing = d.query("SELECT id FROM agents WHERE name = ? AND id != ?").get(updates.name, agent.id);
|
|
651
|
-
if (existing) {
|
|
652
|
-
throw new Error(`Agent name already taken: ${updates.name}`);
|
|
653
|
-
}
|
|
654
|
-
d.run("UPDATE agents SET name = ? WHERE id = ?", [updates.name, agent.id]);
|
|
655
|
-
}
|
|
656
|
-
if (updates.description !== undefined) {
|
|
657
|
-
d.run("UPDATE agents SET description = ? WHERE id = ?", [updates.description, agent.id]);
|
|
658
|
-
}
|
|
659
|
-
if (updates.role !== undefined) {
|
|
660
|
-
d.run("UPDATE agents SET role = ? WHERE id = ?", [updates.role, agent.id]);
|
|
661
|
-
}
|
|
662
|
-
if (updates.metadata !== undefined) {
|
|
663
|
-
d.run("UPDATE agents SET metadata = ? WHERE id = ?", [JSON.stringify(updates.metadata), agent.id]);
|
|
664
|
-
}
|
|
665
|
-
d.run("UPDATE agents SET last_seen_at = ? WHERE id = ?", [timestamp, agent.id]);
|
|
666
|
-
return getAgent(agent.id, d);
|
|
1439
|
+
const updated = getMemory(id, d);
|
|
1440
|
+
try {
|
|
1441
|
+
if (input.value !== undefined) {
|
|
1442
|
+
const oldLinks = getEntityMemoryLinks(undefined, updated.id, d);
|
|
1443
|
+
for (const link of oldLinks) {
|
|
1444
|
+
unlinkEntityFromMemory(link.entity_id, updated.id, d);
|
|
1445
|
+
}
|
|
1446
|
+
runEntityExtraction(updated, existing.project_id || undefined, d);
|
|
1447
|
+
}
|
|
1448
|
+
} catch {}
|
|
1449
|
+
return updated;
|
|
667
1450
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
name: row["name"],
|
|
673
|
-
path: row["path"],
|
|
674
|
-
description: row["description"] || null,
|
|
675
|
-
memory_prefix: row["memory_prefix"] || null,
|
|
676
|
-
created_at: row["created_at"],
|
|
677
|
-
updated_at: row["updated_at"]
|
|
678
|
-
};
|
|
1451
|
+
function deleteMemory(id, db) {
|
|
1452
|
+
const d = db || getDatabase();
|
|
1453
|
+
const result = d.run("DELETE FROM memories WHERE id = ?", [id]);
|
|
1454
|
+
return result.changes > 0;
|
|
679
1455
|
}
|
|
680
|
-
function
|
|
1456
|
+
function bulkDeleteMemories(ids, db) {
|
|
681
1457
|
const d = db || getDatabase();
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
]);
|
|
690
|
-
return parseProjectRow(existing);
|
|
1458
|
+
if (ids.length === 0)
|
|
1459
|
+
return 0;
|
|
1460
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1461
|
+
const countRow = d.query(`SELECT COUNT(*) as c FROM memories WHERE id IN (${placeholders})`).get(...ids);
|
|
1462
|
+
const count = countRow.c;
|
|
1463
|
+
if (count > 0) {
|
|
1464
|
+
d.run(`DELETE FROM memories WHERE id IN (${placeholders})`, ids);
|
|
691
1465
|
}
|
|
692
|
-
|
|
693
|
-
d.run("INSERT INTO projects (id, name, path, description, memory_prefix, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)", [id, name, path, description || null, memoryPrefix || null, timestamp, timestamp]);
|
|
694
|
-
return getProject(id, d);
|
|
1466
|
+
return count;
|
|
695
1467
|
}
|
|
696
|
-
function
|
|
1468
|
+
function touchMemory(id, db) {
|
|
697
1469
|
const d = db || getDatabase();
|
|
698
|
-
|
|
699
|
-
if (row)
|
|
700
|
-
return parseProjectRow(row);
|
|
701
|
-
row = d.query("SELECT * FROM projects WHERE path = ?").get(idOrPath);
|
|
702
|
-
if (row)
|
|
703
|
-
return parseProjectRow(row);
|
|
704
|
-
return null;
|
|
1470
|
+
d.run("UPDATE memories SET access_count = access_count + 1, accessed_at = ? WHERE id = ?", [now(), id]);
|
|
705
1471
|
}
|
|
706
|
-
function
|
|
1472
|
+
function cleanExpiredMemories(db) {
|
|
707
1473
|
const d = db || getDatabase();
|
|
708
|
-
const
|
|
709
|
-
|
|
1474
|
+
const timestamp = now();
|
|
1475
|
+
const countRow = d.query("SELECT COUNT(*) as c FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?").get(timestamp);
|
|
1476
|
+
const count = countRow.c;
|
|
1477
|
+
if (count > 0) {
|
|
1478
|
+
d.run("DELETE FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?", [timestamp]);
|
|
1479
|
+
}
|
|
1480
|
+
return count;
|
|
710
1481
|
}
|
|
711
1482
|
// src/lib/search.ts
|
|
712
1483
|
function parseMemoryRow2(row) {
|
|
@@ -734,109 +1505,442 @@ function parseMemoryRow2(row) {
|
|
|
734
1505
|
accessed_at: row["accessed_at"] || null
|
|
735
1506
|
};
|
|
736
1507
|
}
|
|
1508
|
+
function preprocessQuery(query) {
|
|
1509
|
+
let q = query.trim();
|
|
1510
|
+
q = q.replace(/\s+/g, " ");
|
|
1511
|
+
q = q.normalize("NFC");
|
|
1512
|
+
return q;
|
|
1513
|
+
}
|
|
1514
|
+
function escapeLikePattern(s) {
|
|
1515
|
+
return s.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
1516
|
+
}
|
|
1517
|
+
var STOP_WORDS = new Set([
|
|
1518
|
+
"a",
|
|
1519
|
+
"an",
|
|
1520
|
+
"the",
|
|
1521
|
+
"is",
|
|
1522
|
+
"are",
|
|
1523
|
+
"was",
|
|
1524
|
+
"were",
|
|
1525
|
+
"be",
|
|
1526
|
+
"been",
|
|
1527
|
+
"being",
|
|
1528
|
+
"have",
|
|
1529
|
+
"has",
|
|
1530
|
+
"had",
|
|
1531
|
+
"do",
|
|
1532
|
+
"does",
|
|
1533
|
+
"did",
|
|
1534
|
+
"will",
|
|
1535
|
+
"would",
|
|
1536
|
+
"could",
|
|
1537
|
+
"should",
|
|
1538
|
+
"may",
|
|
1539
|
+
"might",
|
|
1540
|
+
"shall",
|
|
1541
|
+
"can",
|
|
1542
|
+
"need",
|
|
1543
|
+
"dare",
|
|
1544
|
+
"ought",
|
|
1545
|
+
"used",
|
|
1546
|
+
"to",
|
|
1547
|
+
"of",
|
|
1548
|
+
"in",
|
|
1549
|
+
"for",
|
|
1550
|
+
"on",
|
|
1551
|
+
"with",
|
|
1552
|
+
"at",
|
|
1553
|
+
"by",
|
|
1554
|
+
"from",
|
|
1555
|
+
"as",
|
|
1556
|
+
"into",
|
|
1557
|
+
"through",
|
|
1558
|
+
"during",
|
|
1559
|
+
"before",
|
|
1560
|
+
"after",
|
|
1561
|
+
"above",
|
|
1562
|
+
"below",
|
|
1563
|
+
"between",
|
|
1564
|
+
"out",
|
|
1565
|
+
"off",
|
|
1566
|
+
"over",
|
|
1567
|
+
"under",
|
|
1568
|
+
"again",
|
|
1569
|
+
"further",
|
|
1570
|
+
"then",
|
|
1571
|
+
"once",
|
|
1572
|
+
"here",
|
|
1573
|
+
"there",
|
|
1574
|
+
"when",
|
|
1575
|
+
"where",
|
|
1576
|
+
"why",
|
|
1577
|
+
"how",
|
|
1578
|
+
"all",
|
|
1579
|
+
"each",
|
|
1580
|
+
"every",
|
|
1581
|
+
"both",
|
|
1582
|
+
"few",
|
|
1583
|
+
"more",
|
|
1584
|
+
"most",
|
|
1585
|
+
"other",
|
|
1586
|
+
"some",
|
|
1587
|
+
"such",
|
|
1588
|
+
"no",
|
|
1589
|
+
"not",
|
|
1590
|
+
"only",
|
|
1591
|
+
"own",
|
|
1592
|
+
"same",
|
|
1593
|
+
"so",
|
|
1594
|
+
"than",
|
|
1595
|
+
"too",
|
|
1596
|
+
"very",
|
|
1597
|
+
"just",
|
|
1598
|
+
"because",
|
|
1599
|
+
"but",
|
|
1600
|
+
"and",
|
|
1601
|
+
"or",
|
|
1602
|
+
"if",
|
|
1603
|
+
"while",
|
|
1604
|
+
"that",
|
|
1605
|
+
"this",
|
|
1606
|
+
"it"
|
|
1607
|
+
]);
|
|
1608
|
+
function removeStopWords(tokens) {
|
|
1609
|
+
if (tokens.length <= 1)
|
|
1610
|
+
return tokens;
|
|
1611
|
+
const filtered = tokens.filter((t) => !STOP_WORDS.has(t.toLowerCase()));
|
|
1612
|
+
return filtered.length > 0 ? filtered : tokens;
|
|
1613
|
+
}
|
|
1614
|
+
function extractHighlights(memory, queryLower) {
|
|
1615
|
+
const highlights = [];
|
|
1616
|
+
const tokens = queryLower.split(/\s+/).filter(Boolean);
|
|
1617
|
+
for (const field of ["key", "value", "summary"]) {
|
|
1618
|
+
const text = field === "summary" ? memory.summary : memory[field];
|
|
1619
|
+
if (!text)
|
|
1620
|
+
continue;
|
|
1621
|
+
const textLower = text.toLowerCase();
|
|
1622
|
+
const searchTerms = [queryLower, ...tokens].filter(Boolean);
|
|
1623
|
+
for (const term of searchTerms) {
|
|
1624
|
+
const idx = textLower.indexOf(term);
|
|
1625
|
+
if (idx !== -1) {
|
|
1626
|
+
const start = Math.max(0, idx - 30);
|
|
1627
|
+
const end = Math.min(text.length, idx + term.length + 30);
|
|
1628
|
+
const prefix = start > 0 ? "..." : "";
|
|
1629
|
+
const suffix = end < text.length ? "..." : "";
|
|
1630
|
+
highlights.push({
|
|
1631
|
+
field,
|
|
1632
|
+
snippet: prefix + text.slice(start, end) + suffix
|
|
1633
|
+
});
|
|
1634
|
+
break;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
for (const tag of memory.tags) {
|
|
1639
|
+
if (tag.toLowerCase().includes(queryLower) || tokens.some((t) => tag.toLowerCase().includes(t))) {
|
|
1640
|
+
highlights.push({ field: "tag", snippet: tag });
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
return highlights;
|
|
1644
|
+
}
|
|
737
1645
|
function determineMatchType(memory, queryLower) {
|
|
738
1646
|
if (memory.key.toLowerCase() === queryLower)
|
|
739
1647
|
return "exact";
|
|
740
1648
|
if (memory.tags.some((t) => t.toLowerCase() === queryLower))
|
|
741
1649
|
return "tag";
|
|
1650
|
+
if (memory.tags.some((t) => t.toLowerCase().includes(queryLower)))
|
|
1651
|
+
return "tag";
|
|
742
1652
|
return "fuzzy";
|
|
743
1653
|
}
|
|
744
1654
|
function computeScore(memory, queryLower) {
|
|
745
|
-
|
|
1655
|
+
const fieldScores = [];
|
|
746
1656
|
const keyLower = memory.key.toLowerCase();
|
|
747
1657
|
if (keyLower === queryLower) {
|
|
748
|
-
|
|
1658
|
+
fieldScores.push(10);
|
|
749
1659
|
} else if (keyLower.includes(queryLower)) {
|
|
750
|
-
|
|
1660
|
+
fieldScores.push(7);
|
|
751
1661
|
}
|
|
752
1662
|
if (memory.tags.some((t) => t.toLowerCase() === queryLower)) {
|
|
753
|
-
|
|
1663
|
+
fieldScores.push(6);
|
|
1664
|
+
} else if (memory.tags.some((t) => t.toLowerCase().includes(queryLower))) {
|
|
1665
|
+
fieldScores.push(3);
|
|
754
1666
|
}
|
|
755
1667
|
if (memory.summary && memory.summary.toLowerCase().includes(queryLower)) {
|
|
756
|
-
|
|
1668
|
+
fieldScores.push(4);
|
|
757
1669
|
}
|
|
758
1670
|
if (memory.value.toLowerCase().includes(queryLower)) {
|
|
759
|
-
|
|
1671
|
+
fieldScores.push(3);
|
|
1672
|
+
}
|
|
1673
|
+
const metadataStr = JSON.stringify(memory.metadata).toLowerCase();
|
|
1674
|
+
if (metadataStr !== "{}" && metadataStr.includes(queryLower)) {
|
|
1675
|
+
fieldScores.push(2);
|
|
1676
|
+
}
|
|
1677
|
+
fieldScores.sort((a, b) => b - a);
|
|
1678
|
+
const diminishingMultipliers = [1, 0.5, 0.25, 0.15, 0.15];
|
|
1679
|
+
let score = 0;
|
|
1680
|
+
for (let i = 0;i < fieldScores.length; i++) {
|
|
1681
|
+
score += fieldScores[i] * (diminishingMultipliers[i] ?? 0.15);
|
|
1682
|
+
}
|
|
1683
|
+
const { phrases } = extractQuotedPhrases(queryLower);
|
|
1684
|
+
for (const phrase of phrases) {
|
|
1685
|
+
if (keyLower.includes(phrase))
|
|
1686
|
+
score += 8;
|
|
1687
|
+
if (memory.value.toLowerCase().includes(phrase))
|
|
1688
|
+
score += 5;
|
|
1689
|
+
if (memory.summary && memory.summary.toLowerCase().includes(phrase))
|
|
1690
|
+
score += 4;
|
|
1691
|
+
}
|
|
1692
|
+
const { remainder } = extractQuotedPhrases(queryLower);
|
|
1693
|
+
const tokens = removeStopWords(remainder.split(/\s+/).filter(Boolean));
|
|
1694
|
+
if (tokens.length > 1) {
|
|
1695
|
+
let tokenScore = 0;
|
|
1696
|
+
for (const token of tokens) {
|
|
1697
|
+
if (keyLower === token) {
|
|
1698
|
+
tokenScore += 10 / tokens.length;
|
|
1699
|
+
} else if (keyLower.includes(token)) {
|
|
1700
|
+
tokenScore += 7 / tokens.length;
|
|
1701
|
+
}
|
|
1702
|
+
if (memory.tags.some((t) => t.toLowerCase() === token)) {
|
|
1703
|
+
tokenScore += 6 / tokens.length;
|
|
1704
|
+
} else if (memory.tags.some((t) => t.toLowerCase().includes(token))) {
|
|
1705
|
+
tokenScore += 3 / tokens.length;
|
|
1706
|
+
}
|
|
1707
|
+
if (memory.summary && memory.summary.toLowerCase().includes(token)) {
|
|
1708
|
+
tokenScore += 4 / tokens.length;
|
|
1709
|
+
}
|
|
1710
|
+
if (memory.value.toLowerCase().includes(token)) {
|
|
1711
|
+
tokenScore += 3 / tokens.length;
|
|
1712
|
+
}
|
|
1713
|
+
if (metadataStr !== "{}" && metadataStr.includes(token)) {
|
|
1714
|
+
tokenScore += 2 / tokens.length;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
if (score > 0) {
|
|
1718
|
+
score += tokenScore * 0.3;
|
|
1719
|
+
} else {
|
|
1720
|
+
score += tokenScore;
|
|
1721
|
+
}
|
|
760
1722
|
}
|
|
761
1723
|
return score;
|
|
762
1724
|
}
|
|
763
|
-
function
|
|
764
|
-
const
|
|
765
|
-
const
|
|
766
|
-
|
|
1725
|
+
function extractQuotedPhrases(query) {
|
|
1726
|
+
const phrases = [];
|
|
1727
|
+
const remainder = query.replace(/"([^"]+)"/g, (_match, phrase) => {
|
|
1728
|
+
phrases.push(phrase);
|
|
1729
|
+
return "";
|
|
1730
|
+
});
|
|
1731
|
+
return { phrases, remainder: remainder.trim() };
|
|
1732
|
+
}
|
|
1733
|
+
function escapeFts5Query(query) {
|
|
1734
|
+
const { phrases, remainder } = extractQuotedPhrases(query);
|
|
1735
|
+
const parts = [];
|
|
1736
|
+
for (const phrase of phrases) {
|
|
1737
|
+
parts.push(`"${phrase.replace(/"/g, '""')}"`);
|
|
1738
|
+
}
|
|
1739
|
+
const tokens = removeStopWords(remainder.split(/\s+/).filter(Boolean));
|
|
1740
|
+
for (const t of tokens) {
|
|
1741
|
+
parts.push(`"${t.replace(/"/g, '""')}"`);
|
|
1742
|
+
}
|
|
1743
|
+
return parts.join(" ");
|
|
1744
|
+
}
|
|
1745
|
+
function hasFts5Table(d) {
|
|
1746
|
+
try {
|
|
1747
|
+
const row = d.query("SELECT name FROM sqlite_master WHERE type='table' AND name='memories_fts'").get();
|
|
1748
|
+
return !!row;
|
|
1749
|
+
} catch {
|
|
1750
|
+
return false;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
function buildFilterConditions(filter) {
|
|
767
1754
|
const conditions = [];
|
|
768
1755
|
const params = [];
|
|
769
1756
|
conditions.push("m.status = 'active'");
|
|
770
1757
|
conditions.push("(m.expires_at IS NULL OR m.expires_at >= datetime('now'))");
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
if (filter) {
|
|
774
|
-
if (filter.scope) {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
params.push(filter.scope);
|
|
781
|
-
}
|
|
1758
|
+
if (!filter)
|
|
1759
|
+
return { conditions, params };
|
|
1760
|
+
if (filter.scope) {
|
|
1761
|
+
if (Array.isArray(filter.scope)) {
|
|
1762
|
+
conditions.push(`m.scope IN (${filter.scope.map(() => "?").join(",")})`);
|
|
1763
|
+
params.push(...filter.scope);
|
|
1764
|
+
} else {
|
|
1765
|
+
conditions.push("m.scope = ?");
|
|
1766
|
+
params.push(filter.scope);
|
|
782
1767
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1768
|
+
}
|
|
1769
|
+
if (filter.category) {
|
|
1770
|
+
if (Array.isArray(filter.category)) {
|
|
1771
|
+
conditions.push(`m.category IN (${filter.category.map(() => "?").join(",")})`);
|
|
1772
|
+
params.push(...filter.category);
|
|
1773
|
+
} else {
|
|
1774
|
+
conditions.push("m.category = ?");
|
|
1775
|
+
params.push(filter.category);
|
|
791
1776
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1777
|
+
}
|
|
1778
|
+
if (filter.source) {
|
|
1779
|
+
if (Array.isArray(filter.source)) {
|
|
1780
|
+
conditions.push(`m.source IN (${filter.source.map(() => "?").join(",")})`);
|
|
1781
|
+
params.push(...filter.source);
|
|
1782
|
+
} else {
|
|
1783
|
+
conditions.push("m.source = ?");
|
|
1784
|
+
params.push(filter.source);
|
|
800
1785
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1786
|
+
}
|
|
1787
|
+
if (filter.status) {
|
|
1788
|
+
conditions.shift();
|
|
1789
|
+
if (Array.isArray(filter.status)) {
|
|
1790
|
+
conditions.push(`m.status IN (${filter.status.map(() => "?").join(",")})`);
|
|
1791
|
+
params.push(...filter.status);
|
|
1792
|
+
} else {
|
|
1793
|
+
conditions.push("m.status = ?");
|
|
1794
|
+
params.push(filter.status);
|
|
810
1795
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1796
|
+
}
|
|
1797
|
+
if (filter.project_id) {
|
|
1798
|
+
conditions.push("m.project_id = ?");
|
|
1799
|
+
params.push(filter.project_id);
|
|
1800
|
+
}
|
|
1801
|
+
if (filter.agent_id) {
|
|
1802
|
+
conditions.push("m.agent_id = ?");
|
|
1803
|
+
params.push(filter.agent_id);
|
|
1804
|
+
}
|
|
1805
|
+
if (filter.session_id) {
|
|
1806
|
+
conditions.push("m.session_id = ?");
|
|
1807
|
+
params.push(filter.session_id);
|
|
1808
|
+
}
|
|
1809
|
+
if (filter.min_importance) {
|
|
1810
|
+
conditions.push("m.importance >= ?");
|
|
1811
|
+
params.push(filter.min_importance);
|
|
1812
|
+
}
|
|
1813
|
+
if (filter.pinned !== undefined) {
|
|
1814
|
+
conditions.push("m.pinned = ?");
|
|
1815
|
+
params.push(filter.pinned ? 1 : 0);
|
|
1816
|
+
}
|
|
1817
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
1818
|
+
for (const tag of filter.tags) {
|
|
1819
|
+
conditions.push("m.id IN (SELECT memory_id FROM memory_tags WHERE tag = ?)");
|
|
1820
|
+
params.push(tag);
|
|
814
1821
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
1822
|
+
}
|
|
1823
|
+
return { conditions, params };
|
|
1824
|
+
}
|
|
1825
|
+
function searchWithFts5(d, query, queryLower, filter, graphBoostedIds) {
|
|
1826
|
+
const ftsQuery = escapeFts5Query(query);
|
|
1827
|
+
if (!ftsQuery)
|
|
1828
|
+
return null;
|
|
1829
|
+
try {
|
|
1830
|
+
const { conditions, params } = buildFilterConditions(filter);
|
|
1831
|
+
const queryParam = `%${query}%`;
|
|
1832
|
+
const ftsCondition = `(m.rowid IN (SELECT f.rowid FROM memories_fts f WHERE memories_fts MATCH ?) ` + `OR m.id IN (SELECT memory_id FROM memory_tags WHERE tag LIKE ?) ` + `OR m.metadata LIKE ?)`;
|
|
1833
|
+
const allConditions = [ftsCondition, ...conditions];
|
|
1834
|
+
const allParams = [ftsQuery, queryParam, queryParam, ...params];
|
|
1835
|
+
const candidateSql = `SELECT m.* FROM memories m WHERE ${allConditions.join(" AND ")}`;
|
|
1836
|
+
const rows = d.query(candidateSql).all(...allParams);
|
|
1837
|
+
return scoreResults(rows, queryLower, graphBoostedIds);
|
|
1838
|
+
} catch {
|
|
1839
|
+
return null;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
function searchWithLike(d, query, queryLower, filter, graphBoostedIds) {
|
|
1843
|
+
const { conditions, params } = buildFilterConditions(filter);
|
|
1844
|
+
const rawTokens = query.trim().split(/\s+/).filter(Boolean);
|
|
1845
|
+
const tokens = removeStopWords(rawTokens);
|
|
1846
|
+
const escapedQuery = escapeLikePattern(query);
|
|
1847
|
+
const likePatterns = [`%${escapedQuery}%`];
|
|
1848
|
+
if (tokens.length > 1) {
|
|
1849
|
+
for (const t of tokens)
|
|
1850
|
+
likePatterns.push(`%${escapeLikePattern(t)}%`);
|
|
1851
|
+
}
|
|
1852
|
+
const fieldClauses = [];
|
|
1853
|
+
for (const pattern of likePatterns) {
|
|
1854
|
+
fieldClauses.push("m.key LIKE ? ESCAPE '\\'");
|
|
1855
|
+
params.push(pattern);
|
|
1856
|
+
fieldClauses.push("m.value LIKE ? ESCAPE '\\'");
|
|
1857
|
+
params.push(pattern);
|
|
1858
|
+
fieldClauses.push("m.summary LIKE ? ESCAPE '\\'");
|
|
1859
|
+
params.push(pattern);
|
|
1860
|
+
fieldClauses.push("m.metadata LIKE ? ESCAPE '\\'");
|
|
1861
|
+
params.push(pattern);
|
|
1862
|
+
fieldClauses.push("m.id IN (SELECT memory_id FROM memory_tags WHERE tag LIKE ? ESCAPE '\\')");
|
|
1863
|
+
params.push(pattern);
|
|
1864
|
+
}
|
|
1865
|
+
conditions.push(`(${fieldClauses.join(" OR ")})`);
|
|
1866
|
+
const sql = `SELECT DISTINCT m.* FROM memories m WHERE ${conditions.join(" AND ")}`;
|
|
1867
|
+
const rows = d.query(sql).all(...params);
|
|
1868
|
+
return scoreResults(rows, queryLower, graphBoostedIds);
|
|
1869
|
+
}
|
|
1870
|
+
function generateTrigrams(s) {
|
|
1871
|
+
const lower = s.toLowerCase();
|
|
1872
|
+
const trigrams = new Set;
|
|
1873
|
+
for (let i = 0;i <= lower.length - 3; i++) {
|
|
1874
|
+
trigrams.add(lower.slice(i, i + 3));
|
|
1875
|
+
}
|
|
1876
|
+
return trigrams;
|
|
1877
|
+
}
|
|
1878
|
+
function trigramSimilarity(a, b) {
|
|
1879
|
+
const triA = generateTrigrams(a);
|
|
1880
|
+
const triB = generateTrigrams(b);
|
|
1881
|
+
if (triA.size === 0 || triB.size === 0)
|
|
1882
|
+
return 0;
|
|
1883
|
+
let intersection = 0;
|
|
1884
|
+
for (const t of triA) {
|
|
1885
|
+
if (triB.has(t))
|
|
1886
|
+
intersection++;
|
|
1887
|
+
}
|
|
1888
|
+
const union = triA.size + triB.size - intersection;
|
|
1889
|
+
return union === 0 ? 0 : intersection / union;
|
|
1890
|
+
}
|
|
1891
|
+
function searchWithFuzzy(d, query, filter, graphBoostedIds) {
|
|
1892
|
+
const { conditions, params } = buildFilterConditions(filter);
|
|
1893
|
+
const sql = `SELECT m.* FROM memories m WHERE ${conditions.join(" AND ")}`;
|
|
1894
|
+
const rows = d.query(sql).all(...params);
|
|
1895
|
+
const MIN_SIMILARITY = 0.3;
|
|
1896
|
+
const results = [];
|
|
1897
|
+
for (const row of rows) {
|
|
1898
|
+
const memory = parseMemoryRow2(row);
|
|
1899
|
+
let bestSimilarity = 0;
|
|
1900
|
+
bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, memory.key));
|
|
1901
|
+
bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, memory.value.slice(0, 200)));
|
|
1902
|
+
if (memory.summary) {
|
|
1903
|
+
bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, memory.summary));
|
|
818
1904
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
params.push(filter.session_id);
|
|
1905
|
+
for (const tag of memory.tags) {
|
|
1906
|
+
bestSimilarity = Math.max(bestSimilarity, trigramSimilarity(query, tag));
|
|
822
1907
|
}
|
|
823
|
-
if (
|
|
824
|
-
|
|
825
|
-
|
|
1908
|
+
if (bestSimilarity >= MIN_SIMILARITY) {
|
|
1909
|
+
const graphBoost = graphBoostedIds?.has(memory.id) ? 2 : 0;
|
|
1910
|
+
const score = bestSimilarity * 5 * memory.importance / 10 + graphBoost;
|
|
1911
|
+
results.push({ memory, score, match_type: "fuzzy" });
|
|
826
1912
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1913
|
+
}
|
|
1914
|
+
results.sort((a, b) => b.score - a.score);
|
|
1915
|
+
return results;
|
|
1916
|
+
}
|
|
1917
|
+
function getGraphBoostedMemoryIds(query, d) {
|
|
1918
|
+
const boostedIds = new Set;
|
|
1919
|
+
try {
|
|
1920
|
+
const matchingEntities = listEntities({ search: query, limit: 10 }, d);
|
|
1921
|
+
const exactMatch = getEntityByName(query, undefined, undefined, d);
|
|
1922
|
+
if (exactMatch && !matchingEntities.find((e) => e.id === exactMatch.id)) {
|
|
1923
|
+
matchingEntities.push(exactMatch);
|
|
830
1924
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1925
|
+
for (const entity of matchingEntities) {
|
|
1926
|
+
const memories = getMemoriesForEntity(entity.id, d);
|
|
1927
|
+
for (const mem of memories) {
|
|
1928
|
+
boostedIds.add(mem.id);
|
|
835
1929
|
}
|
|
836
1930
|
}
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
|
|
1931
|
+
} catch {}
|
|
1932
|
+
return boostedIds;
|
|
1933
|
+
}
|
|
1934
|
+
function computeRecencyBoost(memory) {
|
|
1935
|
+
if (memory.pinned)
|
|
1936
|
+
return 1;
|
|
1937
|
+
const mostRecent = memory.accessed_at || memory.updated_at;
|
|
1938
|
+
if (!mostRecent)
|
|
1939
|
+
return 0;
|
|
1940
|
+
const daysSinceAccess = (Date.now() - Date.parse(mostRecent)) / (1000 * 60 * 60 * 24);
|
|
1941
|
+
return Math.max(0, 1 - daysSinceAccess / 30);
|
|
1942
|
+
}
|
|
1943
|
+
function scoreResults(rows, queryLower, graphBoostedIds) {
|
|
840
1944
|
const scored = [];
|
|
841
1945
|
for (const row of rows) {
|
|
842
1946
|
const memory = parseMemoryRow2(row);
|
|
@@ -844,11 +1948,16 @@ function searchMemories(query, filter, db) {
|
|
|
844
1948
|
if (rawScore === 0)
|
|
845
1949
|
continue;
|
|
846
1950
|
const weightedScore = rawScore * memory.importance / 10;
|
|
1951
|
+
const recencyBoost = computeRecencyBoost(memory);
|
|
1952
|
+
const accessBoost = Math.min(memory.access_count / 20, 0.2);
|
|
1953
|
+
const graphBoost = graphBoostedIds?.has(memory.id) ? 2 : 0;
|
|
1954
|
+
const finalScore = (weightedScore + graphBoost) * (1 + recencyBoost * 0.3) * (1 + accessBoost);
|
|
847
1955
|
const matchType = determineMatchType(memory, queryLower);
|
|
848
1956
|
scored.push({
|
|
849
1957
|
memory,
|
|
850
|
-
score:
|
|
851
|
-
match_type: matchType
|
|
1958
|
+
score: finalScore,
|
|
1959
|
+
match_type: matchType,
|
|
1960
|
+
highlights: extractHighlights(memory, queryLower)
|
|
852
1961
|
});
|
|
853
1962
|
}
|
|
854
1963
|
scored.sort((a, b) => {
|
|
@@ -856,90 +1965,53 @@ function searchMemories(query, filter, db) {
|
|
|
856
1965
|
return b.score - a.score;
|
|
857
1966
|
return b.memory.importance - a.memory.importance;
|
|
858
1967
|
});
|
|
859
|
-
|
|
860
|
-
const limit = filter?.limit ?? scored.length;
|
|
861
|
-
return scored.slice(offset, offset + limit);
|
|
1968
|
+
return scored;
|
|
862
1969
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
private: 200
|
|
876
|
-
},
|
|
877
|
-
injection: {
|
|
878
|
-
max_tokens: 500,
|
|
879
|
-
min_importance: 5,
|
|
880
|
-
categories: ["preference", "fact"],
|
|
881
|
-
refresh_interval: 5
|
|
882
|
-
},
|
|
883
|
-
sync_agents: ["claude", "codex", "gemini"],
|
|
884
|
-
auto_cleanup: {
|
|
885
|
-
enabled: true,
|
|
886
|
-
expired_check_interval: 3600,
|
|
887
|
-
unused_archive_days: 7,
|
|
888
|
-
stale_deprioritize_days: 14
|
|
889
|
-
}
|
|
890
|
-
};
|
|
891
|
-
function deepMerge(target, source) {
|
|
892
|
-
const result = { ...target };
|
|
893
|
-
for (const key of Object.keys(source)) {
|
|
894
|
-
const sourceVal = source[key];
|
|
895
|
-
const targetVal = result[key];
|
|
896
|
-
if (sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal !== null && typeof targetVal === "object" && !Array.isArray(targetVal)) {
|
|
897
|
-
result[key] = deepMerge(targetVal, sourceVal);
|
|
1970
|
+
function searchMemories(query, filter, db) {
|
|
1971
|
+
const d = db || getDatabase();
|
|
1972
|
+
query = preprocessQuery(query);
|
|
1973
|
+
if (!query)
|
|
1974
|
+
return [];
|
|
1975
|
+
const queryLower = query.toLowerCase();
|
|
1976
|
+
const graphBoostedIds = getGraphBoostedMemoryIds(query, d);
|
|
1977
|
+
let scored;
|
|
1978
|
+
if (hasFts5Table(d)) {
|
|
1979
|
+
const ftsResult = searchWithFts5(d, query, queryLower, filter, graphBoostedIds);
|
|
1980
|
+
if (ftsResult !== null) {
|
|
1981
|
+
scored = ftsResult;
|
|
898
1982
|
} else {
|
|
899
|
-
|
|
1983
|
+
scored = searchWithLike(d, query, queryLower, filter, graphBoostedIds);
|
|
900
1984
|
}
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
return VALID_SCOPES.includes(value);
|
|
913
|
-
}
|
|
914
|
-
function isValidCategory(value) {
|
|
915
|
-
return VALID_CATEGORIES.includes(value);
|
|
916
|
-
}
|
|
917
|
-
function loadConfig() {
|
|
918
|
-
const configPath = join2(homedir(), ".mementos", "config.json");
|
|
919
|
-
let fileConfig = {};
|
|
920
|
-
if (existsSync2(configPath)) {
|
|
921
|
-
try {
|
|
922
|
-
const raw = readFileSync(configPath, "utf-8");
|
|
923
|
-
fileConfig = JSON.parse(raw);
|
|
924
|
-
} catch {}
|
|
925
|
-
}
|
|
926
|
-
const merged = deepMerge(DEFAULT_CONFIG, fileConfig);
|
|
927
|
-
const envScope = process.env["MEMENTOS_DEFAULT_SCOPE"];
|
|
928
|
-
if (envScope && isValidScope(envScope)) {
|
|
929
|
-
merged.default_scope = envScope;
|
|
930
|
-
}
|
|
931
|
-
const envCategory = process.env["MEMENTOS_DEFAULT_CATEGORY"];
|
|
932
|
-
if (envCategory && isValidCategory(envCategory)) {
|
|
933
|
-
merged.default_category = envCategory;
|
|
934
|
-
}
|
|
935
|
-
const envImportance = process.env["MEMENTOS_DEFAULT_IMPORTANCE"];
|
|
936
|
-
if (envImportance) {
|
|
937
|
-
const parsed = parseInt(envImportance, 10);
|
|
938
|
-
if (!Number.isNaN(parsed) && parsed >= 1 && parsed <= 10) {
|
|
939
|
-
merged.default_importance = parsed;
|
|
1985
|
+
} else {
|
|
1986
|
+
scored = searchWithLike(d, query, queryLower, filter, graphBoostedIds);
|
|
1987
|
+
}
|
|
1988
|
+
if (scored.length < 3) {
|
|
1989
|
+
const fuzzyResults = searchWithFuzzy(d, query, filter, graphBoostedIds);
|
|
1990
|
+
const seenIds = new Set(scored.map((r) => r.memory.id));
|
|
1991
|
+
for (const fr of fuzzyResults) {
|
|
1992
|
+
if (!seenIds.has(fr.memory.id)) {
|
|
1993
|
+
scored.push(fr);
|
|
1994
|
+
seenIds.add(fr.memory.id);
|
|
1995
|
+
}
|
|
940
1996
|
}
|
|
1997
|
+
scored.sort((a, b) => {
|
|
1998
|
+
if (b.score !== a.score)
|
|
1999
|
+
return b.score - a.score;
|
|
2000
|
+
return b.memory.importance - a.memory.importance;
|
|
2001
|
+
});
|
|
941
2002
|
}
|
|
942
|
-
|
|
2003
|
+
const offset = filter?.offset ?? 0;
|
|
2004
|
+
const limit = filter?.limit ?? scored.length;
|
|
2005
|
+
const finalResults = scored.slice(offset, offset + limit);
|
|
2006
|
+
logSearchQuery(query, scored.length, filter?.agent_id, filter?.project_id, d);
|
|
2007
|
+
return finalResults;
|
|
2008
|
+
}
|
|
2009
|
+
function logSearchQuery(query, resultCount, agentId, projectId, db) {
|
|
2010
|
+
try {
|
|
2011
|
+
const d = db || getDatabase();
|
|
2012
|
+
const id = crypto.randomUUID().slice(0, 8);
|
|
2013
|
+
d.run("INSERT INTO search_history (id, query, result_count, agent_id, project_id) VALUES (?, ?, ?, ?, ?)", [id, query, resultCount, agentId || null, projectId || null]);
|
|
2014
|
+
} catch {}
|
|
943
2015
|
}
|
|
944
2016
|
// src/lib/injector.ts
|
|
945
2017
|
class MemoryInjector {
|
|
@@ -1100,13 +2172,13 @@ function enforceQuotas(config, db) {
|
|
|
1100
2172
|
if (count <= limit)
|
|
1101
2173
|
continue;
|
|
1102
2174
|
const excess = count - limit;
|
|
1103
|
-
const
|
|
1104
|
-
SELECT id FROM memories
|
|
2175
|
+
const subquery = `SELECT id FROM memories
|
|
1105
2176
|
WHERE scope = ? AND status = 'active' AND pinned = 0
|
|
1106
2177
|
ORDER BY importance ASC, created_at ASC
|
|
1107
|
-
LIMIT
|
|
1108
|
-
|
|
1109
|
-
|
|
2178
|
+
LIMIT ?`;
|
|
2179
|
+
const delCount = d.query(`SELECT COUNT(*) as c FROM (${subquery})`).get(scope, excess).c;
|
|
2180
|
+
d.run(`DELETE FROM memories WHERE id IN (${subquery})`, [scope, excess]);
|
|
2181
|
+
totalEvicted += delCount;
|
|
1110
2182
|
}
|
|
1111
2183
|
return totalEvicted;
|
|
1112
2184
|
}
|
|
@@ -1114,38 +2186,38 @@ function archiveStale(staleDays, db) {
|
|
|
1114
2186
|
const d = db || getDatabase();
|
|
1115
2187
|
const timestamp = now();
|
|
1116
2188
|
const cutoff = new Date(Date.now() - staleDays * 24 * 60 * 60 * 1000).toISOString();
|
|
1117
|
-
const
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
return
|
|
2189
|
+
const archiveWhere = `status = 'active' AND pinned = 0 AND COALESCE(accessed_at, created_at) < ?`;
|
|
2190
|
+
const count = d.query(`SELECT COUNT(*) as c FROM memories WHERE ${archiveWhere}`).get(cutoff).c;
|
|
2191
|
+
if (count > 0) {
|
|
2192
|
+
d.run(`UPDATE memories SET status = 'archived', updated_at = ? WHERE ${archiveWhere}`, [timestamp, cutoff]);
|
|
2193
|
+
}
|
|
2194
|
+
return count;
|
|
1123
2195
|
}
|
|
1124
2196
|
function archiveUnused(days, db) {
|
|
1125
2197
|
const d = db || getDatabase();
|
|
1126
2198
|
const timestamp = now();
|
|
1127
2199
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
return result.changes;
|
|
2200
|
+
const unusedWhere = `status = 'active' AND pinned = 0 AND access_count = 0 AND created_at < ?`;
|
|
2201
|
+
const count = d.query(`SELECT COUNT(*) as c FROM memories WHERE ${unusedWhere}`).get(cutoff).c;
|
|
2202
|
+
if (count > 0) {
|
|
2203
|
+
d.run(`UPDATE memories SET status = 'archived', updated_at = ? WHERE ${unusedWhere}`, [timestamp, cutoff]);
|
|
2204
|
+
}
|
|
2205
|
+
return count;
|
|
1135
2206
|
}
|
|
1136
2207
|
function deprioritizeStale(days, db) {
|
|
1137
2208
|
const d = db || getDatabase();
|
|
1138
2209
|
const timestamp = now();
|
|
1139
2210
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
2211
|
+
const deprioWhere = `status = 'active' AND pinned = 0 AND importance > 1 AND COALESCE(accessed_at, updated_at) < ?`;
|
|
2212
|
+
const count = d.query(`SELECT COUNT(*) as c FROM memories WHERE ${deprioWhere}`).get(cutoff).c;
|
|
2213
|
+
if (count > 0) {
|
|
2214
|
+
d.run(`UPDATE memories
|
|
2215
|
+
SET importance = MAX(importance - 1, 1),
|
|
2216
|
+
version = version + 1,
|
|
2217
|
+
updated_at = ?
|
|
2218
|
+
WHERE ${deprioWhere}`, [timestamp, cutoff]);
|
|
2219
|
+
}
|
|
2220
|
+
return count;
|
|
1149
2221
|
}
|
|
1150
2222
|
function runCleanup(config, db) {
|
|
1151
2223
|
const d = db || getDatabase();
|
|
@@ -1288,7 +2360,9 @@ var defaultSyncAgents = ["claude", "codex", "gemini"];
|
|
|
1288
2360
|
export {
|
|
1289
2361
|
uuid,
|
|
1290
2362
|
updateMemory,
|
|
2363
|
+
updateEntity,
|
|
1291
2364
|
updateAgent,
|
|
2365
|
+
unlinkEntityFromMemory,
|
|
1292
2366
|
touchMemory,
|
|
1293
2367
|
syncMemories,
|
|
1294
2368
|
shortUuid,
|
|
@@ -1299,25 +2373,46 @@ export {
|
|
|
1299
2373
|
registerProject,
|
|
1300
2374
|
registerAgent,
|
|
1301
2375
|
redactSecrets,
|
|
2376
|
+
parseRelationRow,
|
|
2377
|
+
parseEntityRow,
|
|
1302
2378
|
now,
|
|
2379
|
+
mergeEntities,
|
|
1303
2380
|
loadConfig,
|
|
2381
|
+
listRelations,
|
|
1304
2382
|
listProjects,
|
|
1305
2383
|
listMemories,
|
|
2384
|
+
listEntities,
|
|
1306
2385
|
listAgents,
|
|
2386
|
+
linkEntityToMemory,
|
|
2387
|
+
getRelation,
|
|
2388
|
+
getRelatedEntities,
|
|
1307
2389
|
getProject,
|
|
1308
2390
|
getMemoryByKey,
|
|
1309
2391
|
getMemory,
|
|
2392
|
+
getMemoriesForEntity,
|
|
2393
|
+
getEntityMemoryLinks,
|
|
2394
|
+
getEntityGraph,
|
|
2395
|
+
getEntityByName,
|
|
2396
|
+
getEntity,
|
|
2397
|
+
getEntitiesForMemory,
|
|
1310
2398
|
getDbPath,
|
|
1311
2399
|
getDatabase,
|
|
1312
2400
|
getAgent,
|
|
2401
|
+
findPath,
|
|
2402
|
+
extractEntities,
|
|
1313
2403
|
enforceQuotas,
|
|
1314
2404
|
deprioritizeStale,
|
|
2405
|
+
deleteRelation,
|
|
1315
2406
|
deleteMemory,
|
|
2407
|
+
deleteEntity,
|
|
1316
2408
|
defaultSyncAgents,
|
|
2409
|
+
createRelation,
|
|
1317
2410
|
createMemory,
|
|
2411
|
+
createEntity,
|
|
1318
2412
|
containsSecrets,
|
|
1319
2413
|
closeDatabase,
|
|
1320
2414
|
cleanExpiredMemories,
|
|
2415
|
+
bulkLinkEntities,
|
|
1321
2416
|
bulkDeleteMemories,
|
|
1322
2417
|
archiveUnused,
|
|
1323
2418
|
archiveStale,
|
|
@@ -1326,6 +2421,7 @@ export {
|
|
|
1326
2421
|
MemoryInjector,
|
|
1327
2422
|
MemoryExpiredError,
|
|
1328
2423
|
InvalidScopeError,
|
|
2424
|
+
EntityNotFoundError,
|
|
1329
2425
|
DuplicateMemoryError,
|
|
1330
2426
|
DEFAULT_CONFIG
|
|
1331
2427
|
};
|