pi-mega-compact 0.11.7 → 0.11.8
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/extensions/dashboard-server/routes-memory-map.js +8 -15
- package/dist/extensions/mega-commands.js +65 -5
- package/dist/extensions/mega-config.js +6 -0
- package/dist/extensions/mega-events/context-handler.js +65 -0
- package/dist/src/config/turns.js +2 -1
- package/dist/src/memoryGraph/embedding.js +50 -0
- package/dist/src/memoryGraph/gates.js +379 -0
- package/dist/src/memoryGraph/sources.js +390 -0
- package/dist/src/memoryGraph.js +133 -259
- package/dist/src/store/sqlite/embedding-cache.js +71 -0
- package/dist/src/store/sqlite.js +1 -0
- package/dist/src/topics/cluster.js +10 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js → AchievementTiles-BESF1HT1.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-DR9cLGDL.js.map → AchievementTiles-BESF1HT1.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js → AchievementsTab-8RzNnz8u.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-svNUgJ1e.js.map → AchievementsTab-8RzNnz8u.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-ASwhOMjR.js → CacheTab-CUIiUgh3.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-ASwhOMjR.js.map → CacheTab-CUIiUgh3.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js → ConfigTab-D85fB9HP.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-CmmWheuW.js.map → ConfigTab-D85fB9HP.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js → EventsTab-TCLR81G5.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-CFE7jLsa.js.map → EventsTab-TCLR81G5.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js → GameTab-BqneNRYH.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{GameTab-CJAX_th_.js.map → GameTab-BqneNRYH.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-P4SUsvMf.js → MaintenanceTab-LblMg3NB.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-P4SUsvMf.js.map → MaintenanceTab-LblMg3NB.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-CpjMQwRr.js +2 -0
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-CpjMQwRr.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js → MetricsTab-LxNtNzLQ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CHQscixy.js.map → MetricsTab-LxNtNzLQ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js → OverviewTab-BXKUZbT6.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-DBs4DJdd.js.map → OverviewTab-BXKUZbT6.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js → ReposTab-DTooKeAi.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-DeFz76L5.js.map → ReposTab-DTooKeAi.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js → SessionsTab-Devulgm9.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-BWNsj1OB.js.map → SessionsTab-Devulgm9.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js → SetupTab-By9eqUoE.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-uS6A0xmw.js.map → SetupTab-By9eqUoE.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js → TimeSavedCard-z4M8TDLm.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CiqCOQuL.js.map → TimeSavedCard-z4M8TDLm.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/TopicsTab-bCGZSfLN.js +2 -0
- package/extensions/dashboard-client/dist/assets/TopicsTab-bCGZSfLN.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js → TurnsTab-m_XzE0H4.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-CVz4f4-o.js.map → TurnsTab-m_XzE0H4.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js → index-PRkilPXw.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-DrktNOoT.js.map → index-PRkilPXw.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js → useSSE-CGzgc2IK.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-Bf5LOLSi.js.map → useSSE-CGzgc2IK.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/memory-map-layout.ts +161 -0
- package/extensions/dashboard-client/src/memory-map-shapes.tsx +126 -0
- package/extensions/dashboard-client/src/tabs/MemoryMapTab.tsx +201 -165
- package/extensions/dashboard-client/src/tabs/ReposTab.tsx +21 -0
- package/extensions/dashboard-client/src/tabs/TopicsTab.tsx +1 -1
- package/extensions/dashboard-server/api-contracts/index.ts +2 -1
- package/extensions/dashboard-server/api-contracts/memory-map.ts +14 -6
- package/extensions/dashboard-server/routes-memory-map.ts +21 -29
- package/extensions/mega-commands.ts +63 -6
- package/extensions/mega-config.ts +18 -0
- package/extensions/mega-events/context-handler.ts +80 -0
- package/package.json +1 -1
- package/src/config/turns.ts +5 -2
- package/src/memoryGraph/embedding.ts +63 -0
- package/src/memoryGraph/gates.ts +451 -0
- package/src/memoryGraph/sources.ts +484 -0
- package/src/memoryGraph.ts +188 -350
- package/src/store/sqlite/embedding-cache.ts +81 -0
- package/src/store/sqlite.ts +1 -0
- package/src/topics/cluster.ts +11 -1
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js +0 -2
- package/extensions/dashboard-client/dist/assets/MemoryMapTab-BBiH76eF.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.js +0 -2
- package/extensions/dashboard-client/dist/assets/TopicsTab-BWWQxltv.js.map +0 -1
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sources.ts — D3 data-source builders for the memory graph.
|
|
3
|
+
*
|
|
4
|
+
* Four source builders: checkpoint, turn (structural), turn-content
|
|
5
|
+
* (raw_transcript join), and memory. Each reads from SQLite using
|
|
6
|
+
* parameterized queries (PREVENT-002) and pushes nodes+edges into
|
|
7
|
+
* caller-supplied arrays.
|
|
8
|
+
*
|
|
9
|
+
* RAPTOR annotation is also here since it enriches checkpoint nodes.
|
|
10
|
+
*/
|
|
11
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
12
|
+
import type { MemoryGraphNode, MemoryGraphEdge } from "../memoryGraph.js";
|
|
13
|
+
import { listRaptorNodes } from "../store/sqlite/raptor.js";
|
|
14
|
+
import { decodeEmbedding } from "../store/sqlite/utils.js";
|
|
15
|
+
import { getOrComputeEmbedding, cosineSimilarity } from "./embedding.js";
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Feature flags and env helpers
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
function flagEnabled(name: string, def: boolean): boolean {
|
|
22
|
+
const v = process.env[name];
|
|
23
|
+
if (v === undefined) return def;
|
|
24
|
+
return v === "true" || v === "1";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function areTurnsEnabled(): boolean {
|
|
28
|
+
return flagEnabled("MEGACOMPACT_MEMORY_GRAPH_SEED_TURNS", true);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isTurnContentEnabled(): boolean {
|
|
32
|
+
return flagEnabled("MEGACOMPACT_MEMORY_GRAPH_SEED_TURN_CONTENT", true) &&
|
|
33
|
+
flagEnabled("MEGACOMPACT_DB_MIRROR", false);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isTurnContentFlaggedOn(): boolean {
|
|
37
|
+
return flagEnabled("MEGACOMPACT_MEMORY_GRAPH_SEED_TURN_CONTENT", true);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function areMemoriesEnabled(): boolean {
|
|
41
|
+
return flagEnabled("MEGACOMPACT_MEMORY_GRAPH_SEED_MEMORIES", true);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Source: checkpoints (from context_chunks table)
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
export function buildCheckpointNodes(
|
|
49
|
+
db: DatabaseSync,
|
|
50
|
+
sessionId: string,
|
|
51
|
+
nodes: MemoryGraphNode[],
|
|
52
|
+
edges: MemoryGraphEdge[],
|
|
53
|
+
): void {
|
|
54
|
+
const rows = db
|
|
55
|
+
.prepare(
|
|
56
|
+
`SELECT id, session_id, summary, token_estimate, timestamp,
|
|
57
|
+
dedup_status, topic_summary, key_decisions,
|
|
58
|
+
normalized_text, embedding_blob
|
|
59
|
+
FROM context_chunks
|
|
60
|
+
WHERE session_id = ?
|
|
61
|
+
ORDER BY timestamp ASC`,
|
|
62
|
+
)
|
|
63
|
+
.all(sessionId) as Array<Record<string, unknown>>;
|
|
64
|
+
|
|
65
|
+
if (rows.length === 0) return;
|
|
66
|
+
|
|
67
|
+
let prevId: string | null = null;
|
|
68
|
+
for (const row of rows) {
|
|
69
|
+
const id = String(row.id);
|
|
70
|
+
const summary = String(row.summary ?? "");
|
|
71
|
+
const node: MemoryGraphNode = {
|
|
72
|
+
id,
|
|
73
|
+
sessionId,
|
|
74
|
+
label: id,
|
|
75
|
+
summaryTruncated: summary.length > 200 ? summary.slice(0, 197) + "..." : summary,
|
|
76
|
+
tokenEstimate: Number(row.token_estimate ?? 0),
|
|
77
|
+
timestamp: Number(row.timestamp ?? Date.now()),
|
|
78
|
+
dedupStatus: row.dedup_status ? String(row.dedup_status) : undefined,
|
|
79
|
+
raptorLevel: 0,
|
|
80
|
+
topicSummary: row.topic_summary ? String(row.topic_summary) : undefined,
|
|
81
|
+
decisionCount: row.key_decisions ? safeJsonCount(String(row.key_decisions)) : 0,
|
|
82
|
+
textSnippet: summary.length > 200 ? summary.slice(0, 197) + "..." : summary,
|
|
83
|
+
nodeType: "checkpoint",
|
|
84
|
+
};
|
|
85
|
+
nodes.push(node);
|
|
86
|
+
|
|
87
|
+
if (prevId !== null) {
|
|
88
|
+
edges.push({ source: prevId, target: id, weight: 1.0, type: "temporal" });
|
|
89
|
+
}
|
|
90
|
+
prevId = id;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
addCheckpointSemanticEdges(rows, edges);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function addCheckpointSemanticEdges(
|
|
97
|
+
rows: Array<Record<string, unknown>>,
|
|
98
|
+
edges: MemoryGraphEdge[],
|
|
99
|
+
): void {
|
|
100
|
+
const embeddings: Array<{ id: string; blob: Buffer | null }> = [];
|
|
101
|
+
for (const row of rows) {
|
|
102
|
+
const blob = row.embedding_blob;
|
|
103
|
+
embeddings.push({
|
|
104
|
+
id: String(row.id),
|
|
105
|
+
blob: blob instanceof Buffer ? blob : null,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (let i = 0; i < embeddings.length; i++) {
|
|
110
|
+
if (!embeddings[i].blob) continue;
|
|
111
|
+
const a = decodeEmbedding(embeddings[i].blob!);
|
|
112
|
+
for (let j = i + 1; j < embeddings.length; j++) {
|
|
113
|
+
if (!embeddings[j].blob) continue;
|
|
114
|
+
const b = decodeEmbedding(embeddings[j].blob!);
|
|
115
|
+
const sim = cosineSimilarity(a, b);
|
|
116
|
+
if (sim > 0) {
|
|
117
|
+
edges.push({
|
|
118
|
+
source: embeddings[i].id,
|
|
119
|
+
target: embeddings[j].id,
|
|
120
|
+
weight: sim,
|
|
121
|
+
type: "semantic",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Source A: turn structural nodes (from turns table, metadata-only)
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
export function buildTurnNodes(
|
|
133
|
+
db: DatabaseSync,
|
|
134
|
+
sessionId: string,
|
|
135
|
+
nodes: MemoryGraphNode[],
|
|
136
|
+
edges: MemoryGraphEdge[],
|
|
137
|
+
): void {
|
|
138
|
+
const existingIds = new Set(nodes.map((n) => n.id));
|
|
139
|
+
const rows = db
|
|
140
|
+
.prepare(
|
|
141
|
+
`SELECT turn_index, role, pressure_band, ctx_tokens, ctx_percent,
|
|
142
|
+
epoch_id, ended_at
|
|
143
|
+
FROM turns
|
|
144
|
+
WHERE session_id = ?
|
|
145
|
+
ORDER BY turn_index ASC`,
|
|
146
|
+
)
|
|
147
|
+
.all(sessionId) as Array<Record<string, unknown>>;
|
|
148
|
+
|
|
149
|
+
if (rows.length === 0) return;
|
|
150
|
+
|
|
151
|
+
let prevId: string | null = null;
|
|
152
|
+
for (const row of rows) {
|
|
153
|
+
const ti = Number(row.turn_index);
|
|
154
|
+
const nodeId = `turn:${sessionId}:${ti}`;
|
|
155
|
+
if (existingIds.has(nodeId)) continue;
|
|
156
|
+
existingIds.add(nodeId);
|
|
157
|
+
|
|
158
|
+
const role = row.role ? String(row.role) : "unknown";
|
|
159
|
+
const endedAt = row.ended_at ? Number(row.ended_at) : Date.now();
|
|
160
|
+
|
|
161
|
+
const node: MemoryGraphNode = {
|
|
162
|
+
id: nodeId,
|
|
163
|
+
sessionId,
|
|
164
|
+
label: `Turn ${ti}`,
|
|
165
|
+
summaryTruncated: `Turn ${ti}: ${role}${row.epoch_id ? ` (epoch: ${String(row.epoch_id)})` : ""}`,
|
|
166
|
+
tokenEstimate: row.ctx_tokens ? Number(row.ctx_tokens) : 0,
|
|
167
|
+
timestamp: endedAt,
|
|
168
|
+
dedupStatus: undefined,
|
|
169
|
+
raptorLevel: 0,
|
|
170
|
+
topicSummary: undefined,
|
|
171
|
+
decisionCount: 0,
|
|
172
|
+
textSnippet: `Turn ${ti}: ${role} — ${String(row.ctx_tokens ?? "?")} tokens`,
|
|
173
|
+
nodeType: "turn",
|
|
174
|
+
epochId: row.epoch_id ? String(row.epoch_id) : undefined,
|
|
175
|
+
};
|
|
176
|
+
nodes.push(node);
|
|
177
|
+
|
|
178
|
+
if (prevId !== null) {
|
|
179
|
+
edges.push({ source: prevId, target: nodeId, weight: 1.0, type: "temporal" });
|
|
180
|
+
}
|
|
181
|
+
prevId = nodeId;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Source B: turn-content nodes (from turns JOIN raw_transcript)
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
export function buildTurnContentNodes(
|
|
190
|
+
db: DatabaseSync,
|
|
191
|
+
sessionId: string,
|
|
192
|
+
nodes: MemoryGraphNode[],
|
|
193
|
+
edges: MemoryGraphEdge[],
|
|
194
|
+
): void {
|
|
195
|
+
// Local dedup only — DON'T skip ids from Source A (turn-structural). Source A
|
|
196
|
+
// and Source B share the same node id (turn:<session>:<idx>); Gate 2
|
|
197
|
+
// (identity_merge) merges them with the richest nodeType winning. Using the
|
|
198
|
+
// global nodes array here would skip every node (Source A already added them).
|
|
199
|
+
const seen = new Set<string>();
|
|
200
|
+
const rows = db
|
|
201
|
+
.prepare(
|
|
202
|
+
`SELECT t.turn_index, t.role, t.ended_at, r.content_bytes
|
|
203
|
+
FROM turns t
|
|
204
|
+
JOIN raw_transcript r ON r.session_id = t.session_id AND r.turn_index = t.turn_index
|
|
205
|
+
WHERE t.session_id = ?
|
|
206
|
+
ORDER BY t.turn_index ASC`,
|
|
207
|
+
)
|
|
208
|
+
.all(sessionId) as Array<Record<string, unknown>>;
|
|
209
|
+
|
|
210
|
+
if (rows.length === 0) return;
|
|
211
|
+
|
|
212
|
+
const turnContentNodes: MemoryGraphNode[] = [];
|
|
213
|
+
|
|
214
|
+
for (const row of rows) {
|
|
215
|
+
const ti = Number(row.turn_index);
|
|
216
|
+
const nodeId = `turn:${sessionId}:${ti}`;
|
|
217
|
+
if (seen.has(nodeId)) continue;
|
|
218
|
+
seen.add(nodeId);
|
|
219
|
+
|
|
220
|
+
const contentBytes = row.content_bytes ? String(row.content_bytes) : "";
|
|
221
|
+
const snippet = contentBytes.length > 200 ? contentBytes.slice(0, 197) + "..." : contentBytes;
|
|
222
|
+
|
|
223
|
+
const node: MemoryGraphNode = {
|
|
224
|
+
id: nodeId,
|
|
225
|
+
sessionId,
|
|
226
|
+
label: `Turn ${ti}`,
|
|
227
|
+
summaryTruncated: snippet,
|
|
228
|
+
tokenEstimate: contentBytes.length,
|
|
229
|
+
timestamp: row.ended_at ? Number(row.ended_at) : Date.now(),
|
|
230
|
+
dedupStatus: undefined,
|
|
231
|
+
raptorLevel: 0,
|
|
232
|
+
topicSummary: undefined,
|
|
233
|
+
decisionCount: 0,
|
|
234
|
+
textSnippet: snippet,
|
|
235
|
+
nodeType: "turn-content",
|
|
236
|
+
epochId: undefined,
|
|
237
|
+
};
|
|
238
|
+
turnContentNodes.push(node);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (turnContentNodes.length > 1) {
|
|
242
|
+
const embeddings = turnContentNodes.map((n) => {
|
|
243
|
+
try {
|
|
244
|
+
return { id: n.id, vec: getOrComputeEmbedding(db, n.textSnippet) };
|
|
245
|
+
} catch {
|
|
246
|
+
return { id: n.id, vec: null as number[] | null };
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
for (let i = 0; i < embeddings.length; i++) {
|
|
251
|
+
if (!embeddings[i].vec) continue;
|
|
252
|
+
for (let j = i + 1; j < embeddings.length; j++) {
|
|
253
|
+
if (!embeddings[j].vec) continue;
|
|
254
|
+
const sim = cosineSimilarity(embeddings[i].vec!, embeddings[j].vec!);
|
|
255
|
+
if (sim > 0) {
|
|
256
|
+
edges.push({
|
|
257
|
+
source: embeddings[i].id,
|
|
258
|
+
target: embeddings[j].id,
|
|
259
|
+
weight: sim,
|
|
260
|
+
type: "semantic",
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
linkTurnToCheckpointEdges(db, sessionId, turnContentNodes, edges);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
nodes.push(...turnContentNodes);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
// Source C: memory nodes
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
export function buildMemoryNodes(
|
|
277
|
+
db: DatabaseSync,
|
|
278
|
+
sessionId: string,
|
|
279
|
+
nodes: MemoryGraphNode[],
|
|
280
|
+
edges: MemoryGraphEdge[],
|
|
281
|
+
): void {
|
|
282
|
+
const existingIds = new Set(nodes.map((n) => n.id));
|
|
283
|
+
const rows = db
|
|
284
|
+
.prepare(
|
|
285
|
+
`SELECT id, content, source_turn, created_at
|
|
286
|
+
FROM memories
|
|
287
|
+
ORDER BY created_at ASC`,
|
|
288
|
+
)
|
|
289
|
+
.all() as Array<Record<string, unknown>>;
|
|
290
|
+
|
|
291
|
+
if (rows.length === 0) return;
|
|
292
|
+
|
|
293
|
+
const memoryNodes: MemoryGraphNode[] = [];
|
|
294
|
+
|
|
295
|
+
for (const row of rows) {
|
|
296
|
+
const id = `mem:${String(row.id)}`;
|
|
297
|
+
if (existingIds.has(id)) continue;
|
|
298
|
+
existingIds.add(id);
|
|
299
|
+
|
|
300
|
+
const content = row.content ? String(row.content) : "";
|
|
301
|
+
const snippet = content.length > 200 ? content.slice(0, 197) + "..." : content;
|
|
302
|
+
|
|
303
|
+
const node: MemoryGraphNode = {
|
|
304
|
+
id,
|
|
305
|
+
sessionId,
|
|
306
|
+
label: `Memory ${String(row.id)}`,
|
|
307
|
+
summaryTruncated: snippet,
|
|
308
|
+
tokenEstimate: content.length,
|
|
309
|
+
timestamp: row.created_at ? Number(row.created_at) : Date.now(),
|
|
310
|
+
dedupStatus: undefined,
|
|
311
|
+
raptorLevel: 0,
|
|
312
|
+
topicSummary: undefined,
|
|
313
|
+
decisionCount: 0,
|
|
314
|
+
textSnippet: snippet,
|
|
315
|
+
nodeType: "memory",
|
|
316
|
+
epochId: undefined,
|
|
317
|
+
};
|
|
318
|
+
memoryNodes.push(node);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (memoryNodes.length > 1) {
|
|
322
|
+
const embeddings = memoryNodes.map((n) => {
|
|
323
|
+
try {
|
|
324
|
+
return { id: n.id, vec: getOrComputeEmbedding(db, n.textSnippet) };
|
|
325
|
+
} catch {
|
|
326
|
+
return { id: n.id, vec: null as number[] | null };
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
for (let i = 0; i < embeddings.length; i++) {
|
|
331
|
+
if (!embeddings[i].vec) continue;
|
|
332
|
+
for (let j = i + 1; j < embeddings.length; j++) {
|
|
333
|
+
if (!embeddings[j].vec) continue;
|
|
334
|
+
const sim = cosineSimilarity(embeddings[i].vec!, embeddings[j].vec!);
|
|
335
|
+
if (sim > 0) {
|
|
336
|
+
edges.push({
|
|
337
|
+
source: embeddings[i].id,
|
|
338
|
+
target: embeddings[j].id,
|
|
339
|
+
weight: sim,
|
|
340
|
+
type: "semantic",
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
crossLinkMemoryToCheckpoints(memoryNodes, nodes, edges);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
nodes.push(...memoryNodes);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
// RAPTOR annotations
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
|
|
356
|
+
export function addRaptorAnnotations(
|
|
357
|
+
sessionId: string,
|
|
358
|
+
stateDir: string,
|
|
359
|
+
nodes: MemoryGraphNode[],
|
|
360
|
+
edges: MemoryGraphEdge[],
|
|
361
|
+
): void {
|
|
362
|
+
const raptorNodes = listRaptorNodes(sessionId, stateDir);
|
|
363
|
+
if (!raptorNodes || raptorNodes.length === 0) return;
|
|
364
|
+
|
|
365
|
+
const nodeIndex = new Map<string, number>();
|
|
366
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
367
|
+
nodeIndex.set(nodes[i].id, i);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
for (const rn of raptorNodes) {
|
|
371
|
+
const idx = nodeIndex.get(rn.id);
|
|
372
|
+
if (idx !== undefined) {
|
|
373
|
+
nodes[idx].raptorLevel = rn.level;
|
|
374
|
+
}
|
|
375
|
+
if (!rn.parentId) continue;
|
|
376
|
+
if (nodeIndex.has(rn.id) && nodeIndex.has(rn.parentId)) {
|
|
377
|
+
edges.push({
|
|
378
|
+
source: rn.id,
|
|
379
|
+
target: rn.parentId,
|
|
380
|
+
weight: Math.max(0.1, 1.0 - rn.level * 0.1),
|
|
381
|
+
type: "raptor_parent",
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ---------------------------------------------------------------------------
|
|
388
|
+
// Cross-source edge linking
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
|
|
391
|
+
function linkTurnToCheckpointEdges(
|
|
392
|
+
db: DatabaseSync,
|
|
393
|
+
sessionId: string,
|
|
394
|
+
turnNodes: MemoryGraphNode[],
|
|
395
|
+
edges: MemoryGraphEdge[],
|
|
396
|
+
): void {
|
|
397
|
+
const checkpoints = db
|
|
398
|
+
.prepare(
|
|
399
|
+
`SELECT id, timestamp FROM context_chunks WHERE session_id = ? ORDER BY timestamp ASC`,
|
|
400
|
+
)
|
|
401
|
+
.all(sessionId) as Array<{ id: string; timestamp: number }>;
|
|
402
|
+
|
|
403
|
+
for (const tn of turnNodes) {
|
|
404
|
+
let bestCp: { id: string; diff: number } | null = null;
|
|
405
|
+
for (const cp of checkpoints) {
|
|
406
|
+
const diff = Math.abs(tn.timestamp - cp.timestamp);
|
|
407
|
+
if (bestCp === null || diff < bestCp.diff) {
|
|
408
|
+
bestCp = { id: cp.id, diff };
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (bestCp) {
|
|
412
|
+
edges.push({
|
|
413
|
+
source: tn.id,
|
|
414
|
+
target: bestCp.id,
|
|
415
|
+
weight: 1.0,
|
|
416
|
+
type: "temporal",
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function crossLinkMemoryToCheckpoints(
|
|
423
|
+
memoryNodes: MemoryGraphNode[],
|
|
424
|
+
allNodes: MemoryGraphNode[],
|
|
425
|
+
edges: MemoryGraphEdge[],
|
|
426
|
+
): void {
|
|
427
|
+
for (const mn of memoryNodes) {
|
|
428
|
+
let bestNeighbor: { id: string; diff: number } | null = null;
|
|
429
|
+
for (const n of allNodes) {
|
|
430
|
+
if (n.nodeType !== "checkpoint" && n.nodeType !== "turn-content") continue;
|
|
431
|
+
if (n.id === mn.id) continue;
|
|
432
|
+
const diff = Math.abs(mn.timestamp - n.timestamp);
|
|
433
|
+
if (bestNeighbor === null || diff < bestNeighbor.diff) {
|
|
434
|
+
bestNeighbor = { id: n.id, diff };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (bestNeighbor) {
|
|
438
|
+
edges.push({
|
|
439
|
+
source: mn.id,
|
|
440
|
+
target: bestNeighbor.id,
|
|
441
|
+
weight: 0.85,
|
|
442
|
+
type: "semantic",
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ---------------------------------------------------------------------------
|
|
449
|
+
// Edge deduplication
|
|
450
|
+
// ---------------------------------------------------------------------------
|
|
451
|
+
|
|
452
|
+
export function deduplicateEdges(edges: MemoryGraphEdge[]): MemoryGraphEdge[] {
|
|
453
|
+
const seen = new Map<string, MemoryGraphEdge>();
|
|
454
|
+
|
|
455
|
+
for (const e of edges) {
|
|
456
|
+
const keyA = `${e.source}|${e.target}`;
|
|
457
|
+
const keyB = `${e.target}|${e.source}`;
|
|
458
|
+
const existing = seen.get(keyA) ?? seen.get(keyB);
|
|
459
|
+
if (existing) {
|
|
460
|
+
if (e.weight > existing.weight) {
|
|
461
|
+
seen.set(keyA, e);
|
|
462
|
+
}
|
|
463
|
+
} else {
|
|
464
|
+
seen.set(keyA, e);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return [...seen.values()];
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ---------------------------------------------------------------------------
|
|
472
|
+
// Helpers
|
|
473
|
+
// ---------------------------------------------------------------------------
|
|
474
|
+
|
|
475
|
+
function safeJsonCount(raw: string | null | undefined): number {
|
|
476
|
+
if (!raw || typeof raw !== "string") return 0;
|
|
477
|
+
try {
|
|
478
|
+
const arr = JSON.parse(raw);
|
|
479
|
+
return Array.isArray(arr) ? arr.length : 0;
|
|
480
|
+
} catch {
|
|
481
|
+
return 0;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|