@hasna/mementos 0.14.58 → 0.14.59
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/dist/cli/commands/info-report.d.ts.map +1 -1
- package/dist/cli/index.js +266 -113
- package/dist/db/api-mode.d.ts.map +1 -1
- package/dist/db/memories.d.ts +32 -0
- package/dist/db/memories.d.ts.map +1 -1
- package/dist/index.js +110 -65
- package/dist/lib/asmr/recall.d.ts +3 -0
- package/dist/lib/asmr/recall.d.ts.map +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +239 -267
- package/dist/mcp/tools/graph-utils.d.ts.map +1 -1
- package/dist/mcp/tools/memory-crud.d.ts.map +1 -1
- package/dist/mcp/tools/memory-search.d.ts.map +1 -1
- package/dist/mcp/tools/storage-tools.d.ts.map +1 -1
- package/dist/mcp/tools/system-tools-memory-admin.d.ts +1 -1
- package/dist/mcp/tools/system-tools-memory-admin.d.ts.map +1 -1
- package/dist/mcp/tools/system-tools-shared.d.ts.map +1 -1
- package/dist/mcp/tools/utility-tools.d.ts.map +1 -1
- package/dist/server/index.js +722 -53
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/lib/remote-sync.d.ts +0 -49
- package/dist/lib/remote-sync.d.ts.map +0 -1
- package/dist/mcp/tools/memory-sync.d.ts +0 -3
- package/dist/mcp/tools/memory-sync.d.ts.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -467,6 +467,10 @@ var init_storage = __esm(() => {
|
|
|
467
467
|
});
|
|
468
468
|
|
|
469
469
|
// src/db/api-mode.ts
|
|
470
|
+
import { tmpdir } from "os";
|
|
471
|
+
import { join as join3 } from "path";
|
|
472
|
+
import { writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
473
|
+
import { randomUUID } from "crypto";
|
|
470
474
|
function firstEnv(...keys) {
|
|
471
475
|
for (const k of keys) {
|
|
472
476
|
const v = process.env[k]?.trim();
|
|
@@ -502,33 +506,60 @@ function apiRequestRaw(method, path, body) {
|
|
|
502
506
|
throw new Error("api-mode: not configured (HASNA_MEMENTOS_API_URL / HASNA_MEMENTOS_API_KEY)");
|
|
503
507
|
const url = `${cfg.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
504
508
|
const hasBody = body !== undefined && body !== null;
|
|
505
|
-
const
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
509
|
+
const timeout = process.env["HASNA_MEMENTOS_API_TIMEOUT"] || DEFAULT_TIMEOUT_S;
|
|
510
|
+
const headerLines = `Authorization: Bearer ${cfg.apiKey}
|
|
511
|
+
x-api-key: ${cfg.apiKey}
|
|
512
|
+
`;
|
|
513
|
+
const args = [
|
|
514
|
+
"curl",
|
|
515
|
+
"-sS",
|
|
516
|
+
"--fail-with-body",
|
|
517
|
+
"-m",
|
|
518
|
+
timeout,
|
|
519
|
+
"-X",
|
|
520
|
+
method,
|
|
521
|
+
"-H",
|
|
522
|
+
"@-",
|
|
523
|
+
"-H",
|
|
524
|
+
"Content-Type: application/json",
|
|
525
|
+
"-H",
|
|
526
|
+
"Accept: application/json",
|
|
527
|
+
"-w",
|
|
528
|
+
"\\n%{http_code}"
|
|
529
|
+
];
|
|
530
|
+
let bodyFile;
|
|
531
|
+
if (hasBody) {
|
|
532
|
+
bodyFile = join3(tmpdir(), `mem-req-${process.pid}-${randomUUID()}.json`);
|
|
533
|
+
writeFileSync3(bodyFile, JSON.stringify(body), { mode: 384 });
|
|
534
|
+
args.push("--data-binary", `@${bodyFile}`);
|
|
535
|
+
}
|
|
536
|
+
args.push(url);
|
|
537
|
+
const childEnv = {};
|
|
538
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
539
|
+
if (v === undefined)
|
|
540
|
+
continue;
|
|
541
|
+
if (k === "HASNA_MEMENTOS_API_KEY" || k === "MEMENTOS_API_KEY")
|
|
542
|
+
continue;
|
|
543
|
+
childEnv[k] = v;
|
|
544
|
+
}
|
|
545
|
+
let out = "";
|
|
546
|
+
let err = "";
|
|
547
|
+
try {
|
|
548
|
+
const proc = Bun.spawnSync(args, {
|
|
549
|
+
stdin: Buffer.from(headerLines),
|
|
550
|
+
stdout: "pipe",
|
|
551
|
+
stderr: "pipe",
|
|
552
|
+
env: childEnv
|
|
553
|
+
});
|
|
554
|
+
out = proc.stdout ? new TextDecoder().decode(proc.stdout) : "";
|
|
555
|
+
err = proc.stderr ? new TextDecoder().decode(proc.stderr) : "";
|
|
556
|
+
} finally {
|
|
557
|
+
if (bodyFile) {
|
|
558
|
+
try {
|
|
559
|
+
unlinkSync2(bodyFile);
|
|
560
|
+
} catch {}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
532
563
|
const nl = out.lastIndexOf(`
|
|
533
564
|
`);
|
|
534
565
|
const codeStr = nl >= 0 ? out.slice(nl + 1).trim() : "";
|
|
@@ -1507,7 +1538,7 @@ __export(exports_database, {
|
|
|
1507
1538
|
closeDatabase: () => closeDatabase
|
|
1508
1539
|
});
|
|
1509
1540
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, cpSync as cpSync2 } from "fs";
|
|
1510
|
-
import { dirname as dirname2, join as
|
|
1541
|
+
import { dirname as dirname2, join as join4, resolve as resolve2 } from "path";
|
|
1511
1542
|
function isInMemoryDb2(path) {
|
|
1512
1543
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
1513
1544
|
}
|
|
@@ -1516,7 +1547,7 @@ function findNearestMementosDb(startDir) {
|
|
|
1516
1547
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
1517
1548
|
const legacyHomeDb = resolve2(home, ".mementos", "mementos.db");
|
|
1518
1549
|
while (true) {
|
|
1519
|
-
const candidate =
|
|
1550
|
+
const candidate = join4(dir, ".mementos", "mementos.db");
|
|
1520
1551
|
if (existsSync3(candidate) && resolve2(candidate) !== legacyHomeDb)
|
|
1521
1552
|
return candidate;
|
|
1522
1553
|
const parent = dirname2(dir);
|
|
@@ -1529,7 +1560,7 @@ function findNearestMementosDb(startDir) {
|
|
|
1529
1560
|
function findGitRoot2(startDir) {
|
|
1530
1561
|
let dir = resolve2(startDir);
|
|
1531
1562
|
while (true) {
|
|
1532
|
-
if (existsSync3(
|
|
1563
|
+
if (existsSync3(join4(dir, ".git")))
|
|
1533
1564
|
return dir;
|
|
1534
1565
|
const parent = dirname2(dir);
|
|
1535
1566
|
if (parent === dir)
|
|
@@ -1540,10 +1571,10 @@ function findGitRoot2(startDir) {
|
|
|
1540
1571
|
}
|
|
1541
1572
|
function migrateGlobalDir() {
|
|
1542
1573
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
1543
|
-
const newDir =
|
|
1544
|
-
const oldDir =
|
|
1574
|
+
const newDir = join4(home, ".hasna", "mementos");
|
|
1575
|
+
const oldDir = join4(home, ".mementos");
|
|
1545
1576
|
if (!existsSync3(newDir) && existsSync3(oldDir)) {
|
|
1546
|
-
mkdirSync3(
|
|
1577
|
+
mkdirSync3(join4(home, ".hasna"), { recursive: true });
|
|
1547
1578
|
cpSync2(oldDir, newDir, { recursive: true });
|
|
1548
1579
|
}
|
|
1549
1580
|
}
|
|
@@ -1559,12 +1590,12 @@ function getDbPath2() {
|
|
|
1559
1590
|
if (process.env["MEMENTOS_DB_SCOPE"] === "project") {
|
|
1560
1591
|
const gitRoot = findGitRoot2(cwd);
|
|
1561
1592
|
if (gitRoot) {
|
|
1562
|
-
return
|
|
1593
|
+
return join4(gitRoot, ".mementos", "mementos.db");
|
|
1563
1594
|
}
|
|
1564
1595
|
}
|
|
1565
1596
|
migrateGlobalDir();
|
|
1566
1597
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
1567
|
-
return
|
|
1598
|
+
return join4(home, ".hasna", "mementos", "mementos.db");
|
|
1568
1599
|
}
|
|
1569
1600
|
function ensureDir2(filePath) {
|
|
1570
1601
|
if (isInMemoryDb2(filePath))
|
|
@@ -2029,6 +2060,18 @@ var init_poisoning = __esm(() => {
|
|
|
2029
2060
|
});
|
|
2030
2061
|
|
|
2031
2062
|
// src/db/entity-memories.ts
|
|
2063
|
+
function parseEntityRow(row) {
|
|
2064
|
+
return {
|
|
2065
|
+
id: row["id"],
|
|
2066
|
+
name: row["name"],
|
|
2067
|
+
type: row["type"],
|
|
2068
|
+
description: row["description"] || null,
|
|
2069
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
2070
|
+
project_id: row["project_id"] || null,
|
|
2071
|
+
created_at: row["created_at"],
|
|
2072
|
+
updated_at: row["updated_at"]
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2032
2075
|
function parseEntityMemoryRow(row) {
|
|
2033
2076
|
return {
|
|
2034
2077
|
entity_id: row["entity_id"],
|
|
@@ -2072,6 +2115,14 @@ function getMemoriesForEntity(entityId, db) {
|
|
|
2072
2115
|
ORDER BY m.importance DESC, m.created_at DESC`).all(entityId);
|
|
2073
2116
|
return rows.map(parseMemoryRow);
|
|
2074
2117
|
}
|
|
2118
|
+
function getEntitiesForMemory(memoryId, db) {
|
|
2119
|
+
const d = db || getDatabase();
|
|
2120
|
+
const rows = d.query(`SELECT e.* FROM entities e
|
|
2121
|
+
INNER JOIN entity_memories em ON em.entity_id = e.id
|
|
2122
|
+
WHERE em.memory_id = ?
|
|
2123
|
+
ORDER BY e.name ASC`).all(memoryId);
|
|
2124
|
+
return rows.map(parseEntityRow);
|
|
2125
|
+
}
|
|
2075
2126
|
function getEntityMemoryLinks(entityId, memoryId, db) {
|
|
2076
2127
|
const d = db || getDatabase();
|
|
2077
2128
|
const conditions = [];
|
|
@@ -2107,12 +2158,14 @@ __export(exports_memories, {
|
|
|
2107
2158
|
parseMemoryRow: () => parseMemoryRow,
|
|
2108
2159
|
listMemoryHistory: () => listMemoryHistory,
|
|
2109
2160
|
listMemories: () => listMemories,
|
|
2161
|
+
listLowTrustMemories: () => listLowTrustMemories,
|
|
2110
2162
|
indexMemoryEmbedding: () => indexMemoryEmbedding,
|
|
2111
2163
|
incrementRecallCount: () => incrementRecallCount,
|
|
2112
2164
|
getMemoryVersions: () => getMemoryVersions,
|
|
2113
2165
|
getMemoryEmbeddings: () => getMemoryEmbeddings,
|
|
2114
2166
|
getMemoryChain: () => getMemoryChain,
|
|
2115
2167
|
getMemoryByKey: () => getMemoryByKey,
|
|
2168
|
+
getMemoryBriefing: () => getMemoryBriefing,
|
|
2116
2169
|
getMemory: () => getMemory,
|
|
2117
2170
|
getMemoriesByKey: () => getMemoriesByKey,
|
|
2118
2171
|
deleteMemory: () => deleteMemory,
|
|
@@ -2122,6 +2175,14 @@ __export(exports_memories, {
|
|
|
2122
2175
|
bulkDeleteMemories: () => bulkDeleteMemories
|
|
2123
2176
|
});
|
|
2124
2177
|
function runEntityExtraction(_memory, _projectId, _d) {}
|
|
2178
|
+
function applyContentType(d, id, memory, contentType) {
|
|
2179
|
+
if (!contentType || contentType === "text")
|
|
2180
|
+
return;
|
|
2181
|
+
try {
|
|
2182
|
+
d.run("UPDATE memories SET content_type = ? WHERE id = ?", [contentType, id]);
|
|
2183
|
+
memory.content_type = contentType;
|
|
2184
|
+
} catch {}
|
|
2185
|
+
}
|
|
2125
2186
|
function parseMemoryRow(row) {
|
|
2126
2187
|
return {
|
|
2127
2188
|
id: row["id"],
|
|
@@ -2226,6 +2287,7 @@ function createMemory(input, dedupeMode = "merge", db) {
|
|
|
2226
2287
|
insertTag2.run(existing.id, tag);
|
|
2227
2288
|
}
|
|
2228
2289
|
const merged = getMemory(existing.id, d);
|
|
2290
|
+
applyContentType(d, existing.id, merged, input.content_type);
|
|
2229
2291
|
try {
|
|
2230
2292
|
const existingMemories = listMemoriesByKey(input.key, d);
|
|
2231
2293
|
const trustScore = computeTrustScore(safeValue, input.key, existingMemories, input.importance);
|
|
@@ -2275,6 +2337,7 @@ function createMemory(input, dedupeMode = "merge", db) {
|
|
|
2275
2337
|
insertTag.run(id, tag);
|
|
2276
2338
|
}
|
|
2277
2339
|
const memory = getMemory(id, d);
|
|
2340
|
+
applyContentType(d, id, memory, input.content_type);
|
|
2278
2341
|
try {
|
|
2279
2342
|
const existingMemories = listMemoriesByKey(input.key, d);
|
|
2280
2343
|
const trustScore = computeTrustScore(safeValue, input.key, existingMemories, input.importance);
|
|
@@ -2609,6 +2672,66 @@ function listMemories(filter, db) {
|
|
|
2609
2672
|
const rows = d.query(sql).all(...params);
|
|
2610
2673
|
return rows.map(parseMemoryRow);
|
|
2611
2674
|
}
|
|
2675
|
+
function getMemoryBriefing(opts, db) {
|
|
2676
|
+
const limit = opts.limit ?? 20;
|
|
2677
|
+
if (!db && isApiMode()) {
|
|
2678
|
+
const q = toQuery({
|
|
2679
|
+
since: opts.since,
|
|
2680
|
+
scope: opts.scope,
|
|
2681
|
+
project_id: opts.project_id,
|
|
2682
|
+
machine_agnostic: opts.visible_machine_id === null || opts.visible_machine_id === undefined ? true : undefined,
|
|
2683
|
+
visible_machine_id: typeof opts.visible_machine_id === "string" ? opts.visible_machine_id : undefined,
|
|
2684
|
+
limit
|
|
2685
|
+
});
|
|
2686
|
+
const { data } = apiJson("GET", `/memories/briefing${q}`);
|
|
2687
|
+
return data ?? { new: [], updated: [], expired: [] };
|
|
2688
|
+
}
|
|
2689
|
+
const d = db || getDatabase();
|
|
2690
|
+
const visibleMachineId = opts.visible_machine_id;
|
|
2691
|
+
const scopeClause = opts.scope ? "AND scope = ?" : "";
|
|
2692
|
+
const projectClause = opts.project_id ? "AND project_id = ?" : "";
|
|
2693
|
+
const machineClause = typeof visibleMachineId === "string" ? "AND (machine_id IS NULL OR machine_id = ?)" : "AND machine_id IS NULL";
|
|
2694
|
+
const extraParams = [
|
|
2695
|
+
...opts.scope ? [opts.scope] : [],
|
|
2696
|
+
...opts.project_id ? [opts.project_id] : [],
|
|
2697
|
+
...typeof visibleMachineId === "string" ? [visibleMachineId] : []
|
|
2698
|
+
];
|
|
2699
|
+
const newRows = d.prepare(`SELECT * FROM memories
|
|
2700
|
+
WHERE status = 'active' AND created_at > ? ${scopeClause} ${projectClause} ${machineClause}
|
|
2701
|
+
ORDER BY importance DESC, created_at DESC LIMIT ?`).all(opts.since, ...extraParams, limit);
|
|
2702
|
+
const updatedRows = d.prepare(`SELECT * FROM memories
|
|
2703
|
+
WHERE status = 'active' AND updated_at > ? AND created_at <= ? ${scopeClause} ${projectClause} ${machineClause}
|
|
2704
|
+
ORDER BY importance DESC, updated_at DESC LIMIT ?`).all(opts.since, opts.since, ...extraParams, limit);
|
|
2705
|
+
const expiredRows = d.prepare(`SELECT * FROM memories
|
|
2706
|
+
WHERE status != 'active' AND updated_at > ? ${scopeClause} ${projectClause} ${machineClause}
|
|
2707
|
+
ORDER BY updated_at DESC LIMIT ?`).all(opts.since, ...extraParams, Math.min(limit, 10));
|
|
2708
|
+
return {
|
|
2709
|
+
new: newRows.map(parseMemoryRow),
|
|
2710
|
+
updated: updatedRows.map(parseMemoryRow),
|
|
2711
|
+
expired: expiredRows.map(parseMemoryRow)
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2714
|
+
function listLowTrustMemories(opts = {}, db) {
|
|
2715
|
+
const threshold = opts.threshold ?? 0.8;
|
|
2716
|
+
const limit = opts.limit ?? 20;
|
|
2717
|
+
const offset = opts.offset ?? 0;
|
|
2718
|
+
if (!db && isApiMode()) {
|
|
2719
|
+
const q = toQuery({ threshold, project_id: opts.project_id, limit, offset });
|
|
2720
|
+
const { data } = apiJson("GET", `/memories/audit${q}`);
|
|
2721
|
+
return data?.memories ?? [];
|
|
2722
|
+
}
|
|
2723
|
+
const d = db || getDatabase();
|
|
2724
|
+
const conditions = ["trust_score < ?", "status = 'active'"];
|
|
2725
|
+
const params = [threshold];
|
|
2726
|
+
if (opts.project_id) {
|
|
2727
|
+
const resolved = resolvePartialId(d, "projects", opts.project_id);
|
|
2728
|
+
conditions.push("project_id = ?");
|
|
2729
|
+
params.push(resolved ?? opts.project_id);
|
|
2730
|
+
}
|
|
2731
|
+
params.push(limit, offset);
|
|
2732
|
+
const rows = d.prepare(`SELECT * FROM memories WHERE ${conditions.join(" AND ")} ORDER BY trust_score ASC LIMIT ? OFFSET ?`).all(...params);
|
|
2733
|
+
return rows.map(parseMemoryRow);
|
|
2734
|
+
}
|
|
2612
2735
|
function listMemoryHistory(opts = {}, db) {
|
|
2613
2736
|
const limit = opts.limit ?? 20;
|
|
2614
2737
|
const offset = opts.offset ?? 0;
|
|
@@ -2820,6 +2943,10 @@ function incrementRecallCount(id, db) {
|
|
|
2820
2943
|
} catch {}
|
|
2821
2944
|
}
|
|
2822
2945
|
function cleanExpiredMemories(db) {
|
|
2946
|
+
if (!db && isApiMode()) {
|
|
2947
|
+
const { data } = apiJson("POST", "/memories/clean");
|
|
2948
|
+
return data?.cleaned ?? 0;
|
|
2949
|
+
}
|
|
2823
2950
|
const d = db || getDatabase();
|
|
2824
2951
|
const timestamp = now();
|
|
2825
2952
|
const countRow = d.query("SELECT COUNT(*) as c FROM memories WHERE expires_at IS NOT NULL AND expires_at < ?").get(timestamp);
|
|
@@ -2872,6 +2999,12 @@ async function semanticSearch(queryText, options = {}, db) {
|
|
|
2872
2999
|
}
|
|
2873
3000
|
const d = db || getDatabase();
|
|
2874
3001
|
const { threshold = 0.5, limit = 10, scope, agent_id, project_id } = options;
|
|
3002
|
+
if (options.index_missing) {
|
|
3003
|
+
const unindexed = d.prepare(`SELECT id, value, summary, when_to_use FROM memories
|
|
3004
|
+
WHERE status = 'active' AND id NOT IN (SELECT memory_id FROM memory_embeddings)
|
|
3005
|
+
LIMIT 100`).all();
|
|
3006
|
+
await Promise.all(unindexed.map((m) => indexMemoryEmbedding(m.id, m.when_to_use || [m.value, m.summary].filter(Boolean).join(" "), d)));
|
|
3007
|
+
}
|
|
2875
3008
|
const { embedding: queryEmbedding } = await generateEmbedding(queryText);
|
|
2876
3009
|
const conditions = ["m.status = 'active'", "e.embedding IS NOT NULL"];
|
|
2877
3010
|
const params = [];
|
|
@@ -2917,7 +3050,7 @@ var init_memories = __esm(() => {
|
|
|
2917
3050
|
});
|
|
2918
3051
|
|
|
2919
3052
|
// src/db/entities.ts
|
|
2920
|
-
function
|
|
3053
|
+
function parseEntityRow2(row) {
|
|
2921
3054
|
return {
|
|
2922
3055
|
id: row["id"],
|
|
2923
3056
|
name: row["name"],
|
|
@@ -2987,7 +3120,7 @@ function getEntity(id, db) {
|
|
|
2987
3120
|
const row = d.query("SELECT * FROM entities WHERE id = ?").get(id);
|
|
2988
3121
|
if (!row)
|
|
2989
3122
|
throw new EntityNotFoundError(id);
|
|
2990
|
-
return
|
|
3123
|
+
return parseEntityRow2(row);
|
|
2991
3124
|
}
|
|
2992
3125
|
function getEntityByName(name, type, projectId, db) {
|
|
2993
3126
|
if (!db && isApiMode()) {
|
|
@@ -3012,7 +3145,7 @@ function getEntityByName(name, type, projectId, db) {
|
|
|
3012
3145
|
const row = d.query(sql).get(...params);
|
|
3013
3146
|
if (!row)
|
|
3014
3147
|
return null;
|
|
3015
|
-
return
|
|
3148
|
+
return parseEntityRow2(row);
|
|
3016
3149
|
}
|
|
3017
3150
|
function listEntities(filter = {}, db) {
|
|
3018
3151
|
if (!db && isApiMode()) {
|
|
@@ -3056,7 +3189,7 @@ function listEntities(filter = {}, db) {
|
|
|
3056
3189
|
params.push(filter.offset);
|
|
3057
3190
|
}
|
|
3058
3191
|
const rows = d.query(sql).all(...params);
|
|
3059
|
-
return rows.map(
|
|
3192
|
+
return rows.map(parseEntityRow2);
|
|
3060
3193
|
}
|
|
3061
3194
|
function updateEntity(id, input, db) {
|
|
3062
3195
|
if (!db && isApiMode()) {
|
|
@@ -3961,7 +4094,7 @@ function parseRelationRow(row) {
|
|
|
3961
4094
|
created_at: row["created_at"]
|
|
3962
4095
|
};
|
|
3963
4096
|
}
|
|
3964
|
-
function
|
|
4097
|
+
function parseEntityRow3(row) {
|
|
3965
4098
|
return {
|
|
3966
4099
|
id: row["id"],
|
|
3967
4100
|
name: row["name"],
|
|
@@ -4071,7 +4204,7 @@ function getEntityGraph(entityId, depth = 2, db) {
|
|
|
4071
4204
|
WHERE g.depth < ?
|
|
4072
4205
|
)
|
|
4073
4206
|
SELECT DISTINCT e.* FROM entities e JOIN graph g ON e.id = g.id`).all(entityId, depth);
|
|
4074
|
-
const entities = entityRows.map(
|
|
4207
|
+
const entities = entityRows.map(parseEntityRow3);
|
|
4075
4208
|
const entityIds = new Set(entities.map((e) => e.id));
|
|
4076
4209
|
if (entityIds.size === 0) {
|
|
4077
4210
|
return { entities: [], relations: [] };
|
|
@@ -4111,7 +4244,7 @@ function findPath(fromEntityId, toEntityId, maxDepth = 5, db) {
|
|
|
4111
4244
|
for (const id of ids) {
|
|
4112
4245
|
const row = d.query("SELECT * FROM entities WHERE id = ?").get(id);
|
|
4113
4246
|
if (row)
|
|
4114
|
-
entities.push(
|
|
4247
|
+
entities.push(parseEntityRow3(row));
|
|
4115
4248
|
}
|
|
4116
4249
|
return entities.length > 0 ? entities : null;
|
|
4117
4250
|
}
|
|
@@ -52199,7 +52332,7 @@ var init_openapi = __esm(() => {
|
|
|
52199
52332
|
// src/server/index.ts
|
|
52200
52333
|
import { existsSync as existsSync5 } from "fs";
|
|
52201
52334
|
import { createRequire } from "module";
|
|
52202
|
-
import { join as
|
|
52335
|
+
import { join as join6, resolve as resolve4, sep } from "path";
|
|
52203
52336
|
|
|
52204
52337
|
// src/lib/config.ts
|
|
52205
52338
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, unlinkSync, cpSync } from "fs";
|
|
@@ -53820,7 +53953,7 @@ init_router();
|
|
|
53820
53953
|
|
|
53821
53954
|
// src/server/helpers.ts
|
|
53822
53955
|
import { existsSync as existsSync4 } from "fs";
|
|
53823
|
-
import { dirname as dirname3, extname, join as
|
|
53956
|
+
import { dirname as dirname3, extname, join as join5 } from "path";
|
|
53824
53957
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
53825
53958
|
var CORS_HEADERS = {
|
|
53826
53959
|
"Access-Control-Allow-Origin": process.env["MEMENTOS_CORS_ORIGIN"] ?? "http://localhost:19428",
|
|
@@ -53905,20 +54038,20 @@ function resolveDashboardDir() {
|
|
|
53905
54038
|
const candidates = [];
|
|
53906
54039
|
try {
|
|
53907
54040
|
const scriptDir = dirname3(fileURLToPath2(import.meta.url));
|
|
53908
|
-
candidates.push(
|
|
53909
|
-
candidates.push(
|
|
54041
|
+
candidates.push(join5(scriptDir, "..", "dashboard", "dist"));
|
|
54042
|
+
candidates.push(join5(scriptDir, "..", "..", "dashboard", "dist"));
|
|
53910
54043
|
} catch {}
|
|
53911
54044
|
if (process.argv[1]) {
|
|
53912
54045
|
const mainDir = dirname3(process.argv[1]);
|
|
53913
|
-
candidates.push(
|
|
53914
|
-
candidates.push(
|
|
54046
|
+
candidates.push(join5(mainDir, "..", "dashboard", "dist"));
|
|
54047
|
+
candidates.push(join5(mainDir, "..", "..", "dashboard", "dist"));
|
|
53915
54048
|
}
|
|
53916
|
-
candidates.push(
|
|
54049
|
+
candidates.push(join5(process.cwd(), "dashboard", "dist"));
|
|
53917
54050
|
for (const c of candidates) {
|
|
53918
54051
|
if (existsSync4(c))
|
|
53919
54052
|
return c;
|
|
53920
54053
|
}
|
|
53921
|
-
return
|
|
54054
|
+
return join5(process.cwd(), "dashboard", "dist");
|
|
53922
54055
|
}
|
|
53923
54056
|
function serveStaticFile(filePath) {
|
|
53924
54057
|
if (!existsSync4(filePath))
|
|
@@ -54468,6 +54601,500 @@ addRoute("GET", "/api/report", (_req, url) => {
|
|
|
54468
54601
|
// src/server/routes/memories-search.ts
|
|
54469
54602
|
init_search();
|
|
54470
54603
|
init_memories();
|
|
54604
|
+
|
|
54605
|
+
// src/lib/asmr/fact-agent.ts
|
|
54606
|
+
init_memories();
|
|
54607
|
+
function escapeFts5Token(token) {
|
|
54608
|
+
return `"${token.replace(/"/g, '""')}"`;
|
|
54609
|
+
}
|
|
54610
|
+
function hasFts5Table2(db) {
|
|
54611
|
+
try {
|
|
54612
|
+
const row = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='memories_fts'").get();
|
|
54613
|
+
return !!row;
|
|
54614
|
+
} catch {
|
|
54615
|
+
return false;
|
|
54616
|
+
}
|
|
54617
|
+
}
|
|
54618
|
+
function buildScopeFilter(opts) {
|
|
54619
|
+
const conditions = [
|
|
54620
|
+
"m.status = 'active'",
|
|
54621
|
+
"(m.expires_at IS NULL OR m.expires_at >= datetime('now'))",
|
|
54622
|
+
"m.category IN ('preference', 'fact', 'knowledge')"
|
|
54623
|
+
];
|
|
54624
|
+
const params = [];
|
|
54625
|
+
if (opts.project_id) {
|
|
54626
|
+
conditions.push("m.project_id = ?");
|
|
54627
|
+
params.push(opts.project_id);
|
|
54628
|
+
}
|
|
54629
|
+
if (opts.agent_id) {
|
|
54630
|
+
conditions.push("m.agent_id = ?");
|
|
54631
|
+
params.push(opts.agent_id);
|
|
54632
|
+
}
|
|
54633
|
+
return { conditions, params };
|
|
54634
|
+
}
|
|
54635
|
+
function scoreFactMemory(memory, queryLower) {
|
|
54636
|
+
const keyLower = memory.key.toLowerCase();
|
|
54637
|
+
const valueLower = memory.value.toLowerCase();
|
|
54638
|
+
let score = 0;
|
|
54639
|
+
let matchField = "value";
|
|
54640
|
+
if (keyLower === queryLower) {
|
|
54641
|
+
score = 10;
|
|
54642
|
+
matchField = "key (exact)";
|
|
54643
|
+
} else if (keyLower.includes(queryLower)) {
|
|
54644
|
+
score = 7;
|
|
54645
|
+
matchField = "key (partial)";
|
|
54646
|
+
} else if (valueLower.includes(queryLower)) {
|
|
54647
|
+
score = 3;
|
|
54648
|
+
matchField = "value";
|
|
54649
|
+
}
|
|
54650
|
+
if (score === 0) {
|
|
54651
|
+
const tokens = queryLower.split(/\s+/).filter(Boolean);
|
|
54652
|
+
if (tokens.length > 1) {
|
|
54653
|
+
let tokenHits = 0;
|
|
54654
|
+
for (const token of tokens) {
|
|
54655
|
+
if (keyLower.includes(token))
|
|
54656
|
+
tokenHits += 2;
|
|
54657
|
+
else if (valueLower.includes(token))
|
|
54658
|
+
tokenHits += 1;
|
|
54659
|
+
}
|
|
54660
|
+
if (tokenHits > 0) {
|
|
54661
|
+
score = tokenHits / tokens.length * 5;
|
|
54662
|
+
matchField = "tokens";
|
|
54663
|
+
}
|
|
54664
|
+
}
|
|
54665
|
+
}
|
|
54666
|
+
if (memory.pinned)
|
|
54667
|
+
score *= 1.3;
|
|
54668
|
+
const effectiveImportance = computeDecayScore(memory);
|
|
54669
|
+
score = score * effectiveImportance / 10;
|
|
54670
|
+
if (memory.importance >= 7)
|
|
54671
|
+
score *= 1.2;
|
|
54672
|
+
return { score, matchField };
|
|
54673
|
+
}
|
|
54674
|
+
async function runFactAgent(db, query, opts) {
|
|
54675
|
+
const queryLower = query.toLowerCase().trim();
|
|
54676
|
+
if (!queryLower)
|
|
54677
|
+
return { memories: [], reasoning: "Empty query" };
|
|
54678
|
+
const { conditions, params } = buildScopeFilter(opts);
|
|
54679
|
+
const limit = (opts.max_results ?? 20) * 2;
|
|
54680
|
+
let rows;
|
|
54681
|
+
if (hasFts5Table2(db)) {
|
|
54682
|
+
const tokens = queryLower.split(/\s+/).filter(Boolean);
|
|
54683
|
+
const ftsQuery = tokens.map(escapeFts5Token).join(" ");
|
|
54684
|
+
try {
|
|
54685
|
+
const ftsCondition = `m.rowid IN (SELECT f.rowid FROM memories_fts f WHERE memories_fts MATCH ?)`;
|
|
54686
|
+
const sql = `SELECT m.* FROM memories m WHERE ${ftsCondition} AND ${conditions.join(" AND ")} LIMIT ?`;
|
|
54687
|
+
rows = db.query(sql).all(ftsQuery, ...params, limit);
|
|
54688
|
+
} catch {
|
|
54689
|
+
rows = [];
|
|
54690
|
+
}
|
|
54691
|
+
} else {
|
|
54692
|
+
rows = [];
|
|
54693
|
+
}
|
|
54694
|
+
if (rows.length === 0) {
|
|
54695
|
+
const likePattern = `%${queryLower}%`;
|
|
54696
|
+
const likeSql = `SELECT m.* FROM memories m WHERE (m.key LIKE ? OR m.value LIKE ?) AND ${conditions.join(" AND ")} LIMIT ?`;
|
|
54697
|
+
rows = db.query(likeSql).all(likePattern, likePattern, ...params, limit);
|
|
54698
|
+
}
|
|
54699
|
+
const results = [];
|
|
54700
|
+
for (const row of rows) {
|
|
54701
|
+
const memory = parseMemoryRow(row);
|
|
54702
|
+
const { score, matchField } = scoreFactMemory(memory, queryLower);
|
|
54703
|
+
if (score <= 0)
|
|
54704
|
+
continue;
|
|
54705
|
+
results.push({
|
|
54706
|
+
memory,
|
|
54707
|
+
score,
|
|
54708
|
+
source_agent: "facts",
|
|
54709
|
+
reasoning: `Direct fact match on ${matchField}`,
|
|
54710
|
+
verbatim_excerpt: memory.value
|
|
54711
|
+
});
|
|
54712
|
+
}
|
|
54713
|
+
results.sort((a, b) => b.score - a.score);
|
|
54714
|
+
const trimmed = results.slice(0, opts.max_results ?? 20);
|
|
54715
|
+
return {
|
|
54716
|
+
memories: trimmed,
|
|
54717
|
+
reasoning: `Fact agent searched ${rows.length} candidate memories, returned ${trimmed.length} factual matches`
|
|
54718
|
+
};
|
|
54719
|
+
}
|
|
54720
|
+
|
|
54721
|
+
// src/lib/asmr/context-agent.ts
|
|
54722
|
+
init_memories();
|
|
54723
|
+
init_entities();
|
|
54724
|
+
init_entity_memories();
|
|
54725
|
+
function deduplicateCandidates(candidates) {
|
|
54726
|
+
const seen = new Map;
|
|
54727
|
+
for (const c of candidates) {
|
|
54728
|
+
const existing = seen.get(c.memory.id);
|
|
54729
|
+
if (!existing) {
|
|
54730
|
+
seen.set(c.memory.id, c);
|
|
54731
|
+
} else {
|
|
54732
|
+
const existingTotal = existing.semanticScore + existing.entityLinkStrength;
|
|
54733
|
+
const currentTotal = c.semanticScore + c.entityLinkStrength;
|
|
54734
|
+
if (currentTotal > existingTotal) {
|
|
54735
|
+
seen.set(c.memory.id, c);
|
|
54736
|
+
}
|
|
54737
|
+
}
|
|
54738
|
+
}
|
|
54739
|
+
return Array.from(seen.values());
|
|
54740
|
+
}
|
|
54741
|
+
async function runContextAgent(db, query, opts) {
|
|
54742
|
+
const queryLower = query.toLowerCase().trim();
|
|
54743
|
+
if (!queryLower)
|
|
54744
|
+
return { memories: [], reasoning: "Empty query" };
|
|
54745
|
+
const maxResults = opts.max_results ?? 20;
|
|
54746
|
+
const candidates = [];
|
|
54747
|
+
let semanticCount = 0;
|
|
54748
|
+
let entityQueryCount = 0;
|
|
54749
|
+
try {
|
|
54750
|
+
const semanticResults = await semanticSearch(query, {
|
|
54751
|
+
threshold: 0.3,
|
|
54752
|
+
limit: maxResults * 2,
|
|
54753
|
+
project_id: opts.project_id,
|
|
54754
|
+
agent_id: opts.agent_id
|
|
54755
|
+
}, db);
|
|
54756
|
+
for (const sr of semanticResults) {
|
|
54757
|
+
candidates.push({
|
|
54758
|
+
memory: sr.memory,
|
|
54759
|
+
semanticScore: sr.score,
|
|
54760
|
+
entityLinkStrength: 0,
|
|
54761
|
+
entityName: null,
|
|
54762
|
+
entityRelation: null
|
|
54763
|
+
});
|
|
54764
|
+
}
|
|
54765
|
+
semanticCount = semanticResults.length;
|
|
54766
|
+
} catch {}
|
|
54767
|
+
const topSemantic = candidates.slice(0, 10);
|
|
54768
|
+
const entityMemoryIds = new Set;
|
|
54769
|
+
for (const candidate of topSemantic) {
|
|
54770
|
+
try {
|
|
54771
|
+
const entities = getEntitiesForMemory(candidate.memory.id, db);
|
|
54772
|
+
for (const entity of entities) {
|
|
54773
|
+
const linkedMemories = getMemoriesForEntity(entity.id, db);
|
|
54774
|
+
for (const mem of linkedMemories) {
|
|
54775
|
+
if (entityMemoryIds.has(mem.id))
|
|
54776
|
+
continue;
|
|
54777
|
+
entityMemoryIds.add(mem.id);
|
|
54778
|
+
candidates.push({
|
|
54779
|
+
memory: mem,
|
|
54780
|
+
semanticScore: 0,
|
|
54781
|
+
entityLinkStrength: 1,
|
|
54782
|
+
entityName: entity.name,
|
|
54783
|
+
entityRelation: entity.type
|
|
54784
|
+
});
|
|
54785
|
+
}
|
|
54786
|
+
}
|
|
54787
|
+
} catch {}
|
|
54788
|
+
}
|
|
54789
|
+
try {
|
|
54790
|
+
const matchingEntities = listEntities({ search: query, limit: 10, project_id: opts.project_id }, db);
|
|
54791
|
+
const exactEntity = getEntityByName(query, undefined, opts.project_id, db);
|
|
54792
|
+
if (exactEntity && !matchingEntities.find((e) => e.id === exactEntity.id)) {
|
|
54793
|
+
matchingEntities.unshift(exactEntity);
|
|
54794
|
+
}
|
|
54795
|
+
for (const entity of matchingEntities) {
|
|
54796
|
+
entityQueryCount++;
|
|
54797
|
+
const linkedMemories = getMemoriesForEntity(entity.id, db);
|
|
54798
|
+
for (const mem of linkedMemories) {
|
|
54799
|
+
if (entityMemoryIds.has(mem.id))
|
|
54800
|
+
continue;
|
|
54801
|
+
entityMemoryIds.add(mem.id);
|
|
54802
|
+
const nameMatch = entity.name.toLowerCase() === queryLower ? 1 : 0.7;
|
|
54803
|
+
candidates.push({
|
|
54804
|
+
memory: mem,
|
|
54805
|
+
semanticScore: 0,
|
|
54806
|
+
entityLinkStrength: nameMatch,
|
|
54807
|
+
entityName: entity.name,
|
|
54808
|
+
entityRelation: entity.type
|
|
54809
|
+
});
|
|
54810
|
+
}
|
|
54811
|
+
}
|
|
54812
|
+
} catch {}
|
|
54813
|
+
const unique = deduplicateCandidates(candidates);
|
|
54814
|
+
const results = [];
|
|
54815
|
+
for (const c of unique) {
|
|
54816
|
+
if (opts.project_id && c.memory.project_id && c.memory.project_id !== opts.project_id)
|
|
54817
|
+
continue;
|
|
54818
|
+
if (c.memory.status !== "active")
|
|
54819
|
+
continue;
|
|
54820
|
+
const effectiveImportance = computeDecayScore(c.memory) / 10;
|
|
54821
|
+
const score = c.semanticScore * 0.4 + c.entityLinkStrength * 0.3 + effectiveImportance * 0.3;
|
|
54822
|
+
let reasoning;
|
|
54823
|
+
if (c.entityName) {
|
|
54824
|
+
reasoning = `Found via entity ${c.entityName} (relation: ${c.entityRelation})`;
|
|
54825
|
+
} else {
|
|
54826
|
+
reasoning = `Semantic similarity match (score: ${c.semanticScore.toFixed(3)})`;
|
|
54827
|
+
}
|
|
54828
|
+
results.push({
|
|
54829
|
+
memory: c.memory,
|
|
54830
|
+
score,
|
|
54831
|
+
source_agent: "context",
|
|
54832
|
+
reasoning,
|
|
54833
|
+
verbatim_excerpt: c.memory.value
|
|
54834
|
+
});
|
|
54835
|
+
}
|
|
54836
|
+
results.sort((a, b) => b.score - a.score);
|
|
54837
|
+
const trimmed = results.slice(0, maxResults);
|
|
54838
|
+
return {
|
|
54839
|
+
memories: trimmed,
|
|
54840
|
+
reasoning: `Context agent found ${semanticCount} semantic matches, traversed ${entityQueryCount} entities, returned ${trimmed.length} contextual results`
|
|
54841
|
+
};
|
|
54842
|
+
}
|
|
54843
|
+
|
|
54844
|
+
// src/lib/asmr/temporal-agent.ts
|
|
54845
|
+
init_memories();
|
|
54846
|
+
function hasFts5Table3(db) {
|
|
54847
|
+
try {
|
|
54848
|
+
const row = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='memories_fts'").get();
|
|
54849
|
+
return !!row;
|
|
54850
|
+
} catch {
|
|
54851
|
+
return false;
|
|
54852
|
+
}
|
|
54853
|
+
}
|
|
54854
|
+
function escapeFts5Token2(token) {
|
|
54855
|
+
return `"${token.replace(/"/g, '""')}"`;
|
|
54856
|
+
}
|
|
54857
|
+
function queryRelevance(memory, queryLower) {
|
|
54858
|
+
const keyLower = memory.key.toLowerCase();
|
|
54859
|
+
const valueLower = memory.value.toLowerCase();
|
|
54860
|
+
if (keyLower === queryLower)
|
|
54861
|
+
return 1;
|
|
54862
|
+
if (keyLower.includes(queryLower))
|
|
54863
|
+
return 0.8;
|
|
54864
|
+
if (valueLower.includes(queryLower))
|
|
54865
|
+
return 0.5;
|
|
54866
|
+
const tokens = queryLower.split(/\s+/).filter(Boolean);
|
|
54867
|
+
if (tokens.length > 1) {
|
|
54868
|
+
let hits = 0;
|
|
54869
|
+
for (const t of tokens) {
|
|
54870
|
+
if (keyLower.includes(t) || valueLower.includes(t))
|
|
54871
|
+
hits++;
|
|
54872
|
+
}
|
|
54873
|
+
return hits / tokens.length * 0.6;
|
|
54874
|
+
}
|
|
54875
|
+
return 0.1;
|
|
54876
|
+
}
|
|
54877
|
+
function formatDate(iso) {
|
|
54878
|
+
if (!iso)
|
|
54879
|
+
return "unknown";
|
|
54880
|
+
return iso.slice(0, 10);
|
|
54881
|
+
}
|
|
54882
|
+
async function runTemporalAgent(db, query, opts) {
|
|
54883
|
+
const queryLower = query.toLowerCase().trim();
|
|
54884
|
+
if (!queryLower)
|
|
54885
|
+
return { memories: [], reasoning: "Empty query" };
|
|
54886
|
+
const maxResults = opts.max_results ?? 20;
|
|
54887
|
+
const limit = maxResults * 3;
|
|
54888
|
+
const conditions = [
|
|
54889
|
+
"(m.expires_at IS NULL OR m.expires_at >= datetime('now'))"
|
|
54890
|
+
];
|
|
54891
|
+
const params = [];
|
|
54892
|
+
if (opts.project_id) {
|
|
54893
|
+
conditions.push("m.project_id = ?");
|
|
54894
|
+
params.push(opts.project_id);
|
|
54895
|
+
}
|
|
54896
|
+
if (opts.agent_id) {
|
|
54897
|
+
conditions.push("m.agent_id = ?");
|
|
54898
|
+
params.push(opts.agent_id);
|
|
54899
|
+
}
|
|
54900
|
+
let temporalRows = [];
|
|
54901
|
+
const temporalCondition = "(m.valid_from IS NOT NULL OR m.valid_until IS NOT NULL)";
|
|
54902
|
+
if (hasFts5Table3(db)) {
|
|
54903
|
+
const tokens = queryLower.split(/\s+/).filter(Boolean);
|
|
54904
|
+
const ftsQuery = tokens.map(escapeFts5Token2).join(" ");
|
|
54905
|
+
try {
|
|
54906
|
+
const ftsCondition = `m.rowid IN (SELECT f.rowid FROM memories_fts f WHERE memories_fts MATCH ?)`;
|
|
54907
|
+
const sql = `SELECT m.* FROM memories m WHERE ${ftsCondition} AND ${temporalCondition} AND ${conditions.join(" AND ")} ORDER BY m.valid_from DESC NULLS LAST LIMIT ?`;
|
|
54908
|
+
temporalRows = db.query(sql).all(ftsQuery, ...params, limit);
|
|
54909
|
+
} catch {
|
|
54910
|
+
temporalRows = [];
|
|
54911
|
+
}
|
|
54912
|
+
}
|
|
54913
|
+
if (temporalRows.length === 0) {
|
|
54914
|
+
const likePattern = `%${queryLower}%`;
|
|
54915
|
+
const sql = `SELECT m.* FROM memories m WHERE (m.key LIKE ? OR m.value LIKE ?) AND ${temporalCondition} AND ${conditions.join(" AND ")} ORDER BY m.valid_from DESC NULLS LAST LIMIT ?`;
|
|
54916
|
+
temporalRows = db.query(sql).all(likePattern, likePattern, ...params, limit);
|
|
54917
|
+
}
|
|
54918
|
+
let supersededRows = [];
|
|
54919
|
+
try {
|
|
54920
|
+
const likePattern = `%${queryLower}%`;
|
|
54921
|
+
const sql = `SELECT m.* FROM memories m WHERE (m.key LIKE ? OR m.value LIKE ?) AND (m.status = 'archived' OR m.valid_until IS NOT NULL) ${conditions.length > 0 ? "AND " + conditions.join(" AND ") : ""} ORDER BY m.updated_at DESC LIMIT ?`;
|
|
54922
|
+
supersededRows = db.query(sql).all(likePattern, likePattern, ...params, limit);
|
|
54923
|
+
} catch {}
|
|
54924
|
+
const seen = new Map;
|
|
54925
|
+
const allRows = [...temporalRows, ...supersededRows];
|
|
54926
|
+
for (const row of allRows) {
|
|
54927
|
+
const memory = parseMemoryRow(row);
|
|
54928
|
+
if (!seen.has(memory.id)) {
|
|
54929
|
+
seen.set(memory.id, memory);
|
|
54930
|
+
}
|
|
54931
|
+
}
|
|
54932
|
+
const memories = Array.from(seen.values());
|
|
54933
|
+
const timeline = [];
|
|
54934
|
+
const results = [];
|
|
54935
|
+
for (const memory of memories) {
|
|
54936
|
+
const hasTemporal = memory.valid_from !== null || memory.valid_until !== null;
|
|
54937
|
+
const isSuperseded = memory.status === "archived";
|
|
54938
|
+
const relevance = queryRelevance(memory, queryLower);
|
|
54939
|
+
let versions = [];
|
|
54940
|
+
let newerVersionNote = "";
|
|
54941
|
+
try {
|
|
54942
|
+
versions = getMemoryVersions(memory.id, db);
|
|
54943
|
+
if (versions.length > 1) {
|
|
54944
|
+
const latest = versions[versions.length - 1];
|
|
54945
|
+
if (latest.version > memory.version) {
|
|
54946
|
+
newerVersionNote = `, superseded at version ${latest.version}`;
|
|
54947
|
+
}
|
|
54948
|
+
}
|
|
54949
|
+
} catch {}
|
|
54950
|
+
if (hasTemporal || isSuperseded) {
|
|
54951
|
+
const from = formatDate(memory.valid_from);
|
|
54952
|
+
const until = formatDate(memory.valid_until);
|
|
54953
|
+
const status = isSuperseded ? " [superseded]" : "";
|
|
54954
|
+
timeline.push(`${from} - ${until}: ${memory.key}${status}`);
|
|
54955
|
+
}
|
|
54956
|
+
const recencyMs = memory.valid_from ? Date.now() - new Date(memory.valid_from).getTime() : Date.now() - new Date(memory.created_at).getTime();
|
|
54957
|
+
const daysSince = recencyMs / (1000 * 60 * 60 * 24);
|
|
54958
|
+
const recencyScore = Math.max(0, 1 - daysSince / 365);
|
|
54959
|
+
const temporalBonus = hasTemporal ? 1 : 0.3;
|
|
54960
|
+
const effectiveImportance = computeDecayScore(memory) / 10;
|
|
54961
|
+
const score = recencyScore * 0.4 + relevance * 0.3 + temporalBonus * 0.3;
|
|
54962
|
+
const finalScore = score * effectiveImportance;
|
|
54963
|
+
let reasoning;
|
|
54964
|
+
if (isSuperseded) {
|
|
54965
|
+
reasoning = `Superseded memory from ${formatDate(memory.valid_from)}${newerVersionNote}`;
|
|
54966
|
+
} else if (memory.valid_until && new Date(memory.valid_until) < new Date) {
|
|
54967
|
+
reasoning = `Expired temporal fact (valid ${formatDate(memory.valid_from)} to ${formatDate(memory.valid_until)})${newerVersionNote}`;
|
|
54968
|
+
} else if (hasTemporal) {
|
|
54969
|
+
reasoning = `Current as of ${formatDate(memory.valid_from)}${newerVersionNote}`;
|
|
54970
|
+
} else {
|
|
54971
|
+
reasoning = `Historical record from ${formatDate(memory.created_at)}${newerVersionNote}`;
|
|
54972
|
+
}
|
|
54973
|
+
results.push({
|
|
54974
|
+
memory,
|
|
54975
|
+
score: finalScore,
|
|
54976
|
+
source_agent: "temporal",
|
|
54977
|
+
reasoning,
|
|
54978
|
+
verbatim_excerpt: memory.value
|
|
54979
|
+
});
|
|
54980
|
+
}
|
|
54981
|
+
timeline.sort();
|
|
54982
|
+
results.sort((a, b) => b.score - a.score);
|
|
54983
|
+
const trimmed = results.slice(0, maxResults);
|
|
54984
|
+
return {
|
|
54985
|
+
memories: trimmed,
|
|
54986
|
+
reasoning: `Temporal agent found ${temporalRows.length} temporal memories and ${supersededRows.length} superseded records, built ${timeline.length}-entry timeline`
|
|
54987
|
+
};
|
|
54988
|
+
}
|
|
54989
|
+
|
|
54990
|
+
// src/lib/asmr/orchestrator.ts
|
|
54991
|
+
var DEFAULT_MAX_RESULTS = 20;
|
|
54992
|
+
function mergeResults(agentResults) {
|
|
54993
|
+
const byId = new Map;
|
|
54994
|
+
for (const { agent, result } of agentResults) {
|
|
54995
|
+
for (const mem of result.memories) {
|
|
54996
|
+
const existing = byId.get(mem.memory.id);
|
|
54997
|
+
if (!existing) {
|
|
54998
|
+
byId.set(mem.memory.id, {
|
|
54999
|
+
best: mem,
|
|
55000
|
+
sources: new Set([agent]),
|
|
55001
|
+
maxScore: mem.score
|
|
55002
|
+
});
|
|
55003
|
+
} else {
|
|
55004
|
+
existing.sources.add(agent);
|
|
55005
|
+
if (mem.score > existing.maxScore) {
|
|
55006
|
+
existing.best = mem;
|
|
55007
|
+
existing.maxScore = mem.score;
|
|
55008
|
+
}
|
|
55009
|
+
}
|
|
55010
|
+
}
|
|
55011
|
+
}
|
|
55012
|
+
const merged = [];
|
|
55013
|
+
for (const [, entry] of byId) {
|
|
55014
|
+
const multiAgentBoost = entry.sources.size > 1 ? 1.5 : 1;
|
|
55015
|
+
merged.push({
|
|
55016
|
+
...entry.best,
|
|
55017
|
+
score: entry.maxScore * multiAgentBoost
|
|
55018
|
+
});
|
|
55019
|
+
}
|
|
55020
|
+
merged.sort((a, b) => b.score - a.score);
|
|
55021
|
+
return merged;
|
|
55022
|
+
}
|
|
55023
|
+
function extractFacts(factResult) {
|
|
55024
|
+
const facts = [];
|
|
55025
|
+
const seen = new Set;
|
|
55026
|
+
for (const mem of factResult.memories) {
|
|
55027
|
+
const statement = `${mem.memory.key}: ${mem.memory.value}`;
|
|
55028
|
+
if (!seen.has(mem.memory.id)) {
|
|
55029
|
+
seen.add(mem.memory.id);
|
|
55030
|
+
facts.push(statement);
|
|
55031
|
+
}
|
|
55032
|
+
}
|
|
55033
|
+
return facts;
|
|
55034
|
+
}
|
|
55035
|
+
function extractTimeline(temporalResult) {
|
|
55036
|
+
const entries = [];
|
|
55037
|
+
const seen = new Set;
|
|
55038
|
+
for (const mem of temporalResult.memories) {
|
|
55039
|
+
if (seen.has(mem.memory.id))
|
|
55040
|
+
continue;
|
|
55041
|
+
seen.add(mem.memory.id);
|
|
55042
|
+
const date = mem.memory.valid_from ?? mem.memory.created_at;
|
|
55043
|
+
const status = mem.memory.status === "archived" ? " [superseded]" : "";
|
|
55044
|
+
entries.push({
|
|
55045
|
+
date,
|
|
55046
|
+
label: `${date.slice(0, 10)}: ${mem.memory.key}${status}`
|
|
55047
|
+
});
|
|
55048
|
+
}
|
|
55049
|
+
entries.sort((a, b) => a.date.localeCompare(b.date));
|
|
55050
|
+
return entries.map((e) => e.label);
|
|
55051
|
+
}
|
|
55052
|
+
async function asmrRecall(db, query, opts) {
|
|
55053
|
+
const options = {
|
|
55054
|
+
max_results: DEFAULT_MAX_RESULTS,
|
|
55055
|
+
include_reasoning: true,
|
|
55056
|
+
...opts
|
|
55057
|
+
};
|
|
55058
|
+
const start = performance.now();
|
|
55059
|
+
const [factResult, contextResult, temporalResult] = await Promise.all([
|
|
55060
|
+
runFactAgent(db, query, options),
|
|
55061
|
+
runContextAgent(db, query, options),
|
|
55062
|
+
runTemporalAgent(db, query, options)
|
|
55063
|
+
]);
|
|
55064
|
+
const agentResults = [
|
|
55065
|
+
{ agent: "facts", result: factResult },
|
|
55066
|
+
{ agent: "context", result: contextResult },
|
|
55067
|
+
{ agent: "temporal", result: temporalResult }
|
|
55068
|
+
];
|
|
55069
|
+
const merged = mergeResults(agentResults);
|
|
55070
|
+
const trimmed = merged.slice(0, options.max_results ?? DEFAULT_MAX_RESULTS);
|
|
55071
|
+
const facts = extractFacts(factResult);
|
|
55072
|
+
const timeline = extractTimeline(temporalResult);
|
|
55073
|
+
const agentsUsed = [];
|
|
55074
|
+
if (factResult.memories.length > 0)
|
|
55075
|
+
agentsUsed.push("facts");
|
|
55076
|
+
if (contextResult.memories.length > 0)
|
|
55077
|
+
agentsUsed.push("context");
|
|
55078
|
+
if (temporalResult.memories.length > 0)
|
|
55079
|
+
agentsUsed.push("temporal");
|
|
55080
|
+
const reasoningParts = [
|
|
55081
|
+
factResult.reasoning,
|
|
55082
|
+
contextResult.reasoning,
|
|
55083
|
+
temporalResult.reasoning
|
|
55084
|
+
].filter(Boolean);
|
|
55085
|
+
const duration = Math.round(performance.now() - start);
|
|
55086
|
+
return {
|
|
55087
|
+
memories: trimmed,
|
|
55088
|
+
facts,
|
|
55089
|
+
timeline,
|
|
55090
|
+
reasoning: reasoningParts.join(". "),
|
|
55091
|
+
agents_used: agentsUsed,
|
|
55092
|
+
duration_ms: duration
|
|
55093
|
+
};
|
|
55094
|
+
}
|
|
55095
|
+
|
|
55096
|
+
// src/server/routes/memories-search.ts
|
|
55097
|
+
init_database();
|
|
54471
55098
|
init_router();
|
|
54472
55099
|
addRoute("POST", "/api/memories/search/semantic", async (req) => {
|
|
54473
55100
|
const body = await readJson(req);
|
|
@@ -54479,10 +55106,23 @@ addRoute("POST", "/api/memories/search/semantic", async (req) => {
|
|
|
54479
55106
|
limit: body["limit"] ?? undefined,
|
|
54480
55107
|
scope: body["scope"] ?? undefined,
|
|
54481
55108
|
agent_id: body["agent_id"] ?? undefined,
|
|
54482
|
-
project_id: body["project_id"] ?? undefined
|
|
55109
|
+
project_id: body["project_id"] ?? undefined,
|
|
55110
|
+
index_missing: body["index_missing"] === true || body["index_missing"] === "true"
|
|
54483
55111
|
});
|
|
54484
55112
|
return json({ results, count: results.length });
|
|
54485
55113
|
});
|
|
55114
|
+
addRoute("POST", "/api/memories/recall/deep", async (req) => {
|
|
55115
|
+
const body = await readJson(req);
|
|
55116
|
+
if (!body || typeof body["query"] !== "string") {
|
|
55117
|
+
return errorResponse("Missing required field: query", 400);
|
|
55118
|
+
}
|
|
55119
|
+
const result = await asmrRecall(getDatabase(), body["query"], {
|
|
55120
|
+
max_results: body["max_results"] ?? undefined,
|
|
55121
|
+
project_id: body["project_id"] ?? undefined,
|
|
55122
|
+
agent_id: body["agent_id"] ?? undefined
|
|
55123
|
+
});
|
|
55124
|
+
return json(result);
|
|
55125
|
+
});
|
|
54486
55126
|
addRoute("POST", "/api/memories/search", async (req) => {
|
|
54487
55127
|
const body = await readJson(req);
|
|
54488
55128
|
if (!body || typeof body["query"] !== "string") {
|
|
@@ -54678,6 +55318,35 @@ init_router();
|
|
|
54678
55318
|
addRoute("GET", "/api/health", () => {
|
|
54679
55319
|
return json({ ok: true, version: "1", db: getDbPath() });
|
|
54680
55320
|
});
|
|
55321
|
+
addRoute("GET", "/api/memories/briefing", (_req, url) => {
|
|
55322
|
+
const q = getSearchParams(url);
|
|
55323
|
+
const since = q["since"];
|
|
55324
|
+
if (!since)
|
|
55325
|
+
return errorResponse("Missing required field: since", 400);
|
|
55326
|
+
let visibleMachineId;
|
|
55327
|
+
if (q["machine_agnostic"] === "true")
|
|
55328
|
+
visibleMachineId = null;
|
|
55329
|
+
else if (q["visible_machine_id"])
|
|
55330
|
+
visibleMachineId = q["visible_machine_id"];
|
|
55331
|
+
const result = getMemoryBriefing({
|
|
55332
|
+
since,
|
|
55333
|
+
scope: q["scope"] || undefined,
|
|
55334
|
+
project_id: q["project_id"] || undefined,
|
|
55335
|
+
visible_machine_id: visibleMachineId,
|
|
55336
|
+
limit: q["limit"] ? parseInt(q["limit"], 10) : undefined
|
|
55337
|
+
});
|
|
55338
|
+
return json(result);
|
|
55339
|
+
});
|
|
55340
|
+
addRoute("GET", "/api/memories/audit", (_req, url) => {
|
|
55341
|
+
const q = getSearchParams(url);
|
|
55342
|
+
const memories = listLowTrustMemories({
|
|
55343
|
+
threshold: q["threshold"] ? parseFloat(q["threshold"]) : undefined,
|
|
55344
|
+
project_id: q["project_id"] || undefined,
|
|
55345
|
+
limit: q["limit"] ? parseInt(q["limit"], 10) : undefined,
|
|
55346
|
+
offset: q["offset"] ? parseInt(q["offset"], 10) : undefined
|
|
55347
|
+
});
|
|
55348
|
+
return json({ memories, count: memories.length });
|
|
55349
|
+
});
|
|
54681
55350
|
addRoute("POST", "/api/memories/extract", async (req) => {
|
|
54682
55351
|
const body = await readJson(req);
|
|
54683
55352
|
if (!body)
|
|
@@ -58214,14 +58883,14 @@ function startServer(port) {
|
|
|
58214
58883
|
if (existsSync5(dashDir) && (req.method === "GET" || req.method === "HEAD")) {
|
|
58215
58884
|
if (pathname !== "/") {
|
|
58216
58885
|
const resolvedDash = resolve4(dashDir) + sep;
|
|
58217
|
-
const requestedPath = resolve4(
|
|
58886
|
+
const requestedPath = resolve4(join6(dashDir, pathname));
|
|
58218
58887
|
if (requestedPath.startsWith(resolvedDash)) {
|
|
58219
58888
|
const staticRes = serveStaticFile(requestedPath);
|
|
58220
58889
|
if (staticRes)
|
|
58221
58890
|
return staticRes;
|
|
58222
58891
|
}
|
|
58223
58892
|
}
|
|
58224
|
-
const indexRes = serveStaticFile(
|
|
58893
|
+
const indexRes = serveStaticFile(join6(dashDir, "index.html"));
|
|
58225
58894
|
if (indexRes)
|
|
58226
58895
|
return indexRes;
|
|
58227
58896
|
}
|