memo-grafter 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -0
- package/README.md +187 -95
- package/USER_GUIDE.md +226 -9
- package/dist/MemoGrafter.d.ts +1 -1
- package/dist/MemoGrafter.d.ts.map +1 -1
- package/dist/MemoGrafter.js +16 -5
- package/dist/MemoGrafter.js.map +1 -1
- package/dist/MemoGrafterAgent.d.ts +7 -1
- package/dist/MemoGrafterAgent.d.ts.map +1 -1
- package/dist/MemoGrafterAgent.js +45 -8
- package/dist/MemoGrafterAgent.js.map +1 -1
- package/dist/adapters/AnthropicAdapter.d.ts.map +1 -1
- package/dist/adapters/AnthropicAdapter.js +8 -2
- package/dist/adapters/AnthropicAdapter.js.map +1 -1
- package/dist/adapters/GeminiAdapter.d.ts +15 -0
- package/dist/adapters/GeminiAdapter.d.ts.map +1 -0
- package/dist/adapters/GeminiAdapter.js +48 -0
- package/dist/adapters/GeminiAdapter.js.map +1 -0
- package/dist/fleet/FleetStore.d.ts +1 -1
- package/dist/fleet/FleetStore.d.ts.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/GrafterPipeline.d.ts +1 -2
- package/dist/pipeline/GrafterPipeline.d.ts.map +1 -1
- package/dist/pipeline/GrafterPipeline.js +3 -5
- package/dist/pipeline/GrafterPipeline.js.map +1 -1
- package/dist/pipeline/IngestPipeline.d.ts +7 -3
- package/dist/pipeline/IngestPipeline.d.ts.map +1 -1
- package/dist/pipeline/IngestPipeline.js +31 -5
- package/dist/pipeline/IngestPipeline.js.map +1 -1
- package/dist/pipeline/RetrieverPipeline.d.ts +11 -0
- package/dist/pipeline/RetrieverPipeline.d.ts.map +1 -0
- package/dist/pipeline/RetrieverPipeline.js +73 -0
- package/dist/pipeline/RetrieverPipeline.js.map +1 -0
- package/dist/pipeline/SegmentProcessor.d.ts +3 -2
- package/dist/pipeline/SegmentProcessor.d.ts.map +1 -1
- package/dist/pipeline/SegmentProcessor.js +50 -18
- package/dist/pipeline/SegmentProcessor.js.map +1 -1
- package/dist/pipeline/TopicDriftDetector.d.ts +21 -3
- package/dist/pipeline/TopicDriftDetector.d.ts.map +1 -1
- package/dist/pipeline/TopicDriftDetector.js +143 -17
- package/dist/pipeline/TopicDriftDetector.js.map +1 -1
- package/dist/prompts/factRetrievalPrompt.d.ts +4 -0
- package/dist/prompts/factRetrievalPrompt.d.ts.map +1 -0
- package/dist/prompts/factRetrievalPrompt.js +25 -0
- package/dist/prompts/factRetrievalPrompt.js.map +1 -0
- package/dist/prompts/historyCompressionPrompt.d.ts +3 -0
- package/dist/prompts/historyCompressionPrompt.d.ts.map +1 -0
- package/dist/prompts/historyCompressionPrompt.js +4 -0
- package/dist/prompts/historyCompressionPrompt.js.map +1 -0
- package/dist/prompts/intentShiftPrompt.d.ts +3 -0
- package/dist/prompts/intentShiftPrompt.d.ts.map +1 -0
- package/dist/prompts/intentShiftPrompt.js +11 -0
- package/dist/prompts/intentShiftPrompt.js.map +1 -0
- package/dist/prompts/segmentExtractionPrompt.d.ts.map +1 -1
- package/dist/prompts/segmentExtractionPrompt.js +68 -12
- package/dist/prompts/segmentExtractionPrompt.js.map +1 -1
- package/dist/store/GraphStore.d.ts +16 -19
- package/dist/store/GraphStore.d.ts.map +1 -1
- package/dist/store/GraphStore.js +1 -576
- package/dist/store/GraphStore.js.map +1 -1
- package/dist/store/index.d.ts +3 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +2 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/postgres-pgvector/GraphStore.d.ts +75 -0
- package/dist/store/postgres-pgvector/GraphStore.d.ts.map +1 -0
- package/dist/store/postgres-pgvector/GraphStore.js +782 -0
- package/dist/store/postgres-pgvector/GraphStore.js.map +1 -0
- package/dist/types.d.ts +69 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/drift/driftMarkers.d.ts +4 -0
- package/dist/utils/drift/driftMarkers.d.ts.map +1 -0
- package/dist/utils/drift/driftMarkers.js +31 -0
- package/dist/utils/drift/driftMarkers.js.map +1 -0
- package/dist/utils/drift/driftScore.d.ts +3 -0
- package/dist/utils/drift/driftScore.d.ts.map +1 -0
- package/dist/utils/drift/driftScore.js +11 -0
- package/dist/utils/drift/driftScore.js.map +1 -0
- package/dist/utils/drift/driftThreshold.d.ts +7 -0
- package/dist/utils/drift/driftThreshold.d.ts.map +1 -0
- package/dist/utils/drift/driftThreshold.js +21 -0
- package/dist/utils/drift/driftThreshold.js.map +1 -0
- package/dist/utils/drift/reentryMatch.d.ts +3 -0
- package/dist/utils/drift/reentryMatch.d.ts.map +1 -0
- package/dist/utils/drift/reentryMatch.js +10 -0
- package/dist/utils/drift/reentryMatch.js.map +1 -0
- package/dist/utils/extraction/segmentExtraction.d.ts +5 -0
- package/dist/utils/extraction/segmentExtraction.d.ts.map +1 -0
- package/dist/utils/extraction/segmentExtraction.js +82 -0
- package/dist/utils/extraction/segmentExtraction.js.map +1 -0
- package/dist/utils/reentry/reentryCues.d.ts +3 -0
- package/dist/utils/reentry/reentryCues.d.ts.map +1 -0
- package/dist/utils/reentry/reentryCues.js +14 -0
- package/dist/utils/reentry/reentryCues.js.map +1 -0
- package/dist/utils/reentry/reentryEdges.d.ts +16 -0
- package/dist/utils/reentry/reentryEdges.d.ts.map +1 -0
- package/dist/utils/reentry/reentryEdges.js +80 -0
- package/dist/utils/reentry/reentryEdges.js.map +1 -0
- package/dist/utils/reentry/reentrySimilarity.d.ts +5 -0
- package/dist/utils/reentry/reentrySimilarity.d.ts.map +1 -0
- package/dist/utils/reentry/reentrySimilarity.js +26 -0
- package/dist/utils/reentry/reentrySimilarity.js.map +1 -0
- package/dist/utils/reentry/reentryText.d.ts +6 -0
- package/dist/utils/reentry/reentryText.d.ts.map +1 -0
- package/dist/utils/reentry/reentryText.js +29 -0
- package/dist/utils/reentry/reentryText.js.map +1 -0
- package/dist/utils/reentry/types.d.ts +6 -0
- package/dist/utils/reentry/types.d.ts.map +1 -0
- package/dist/utils/reentry/types.js +2 -0
- package/dist/utils/reentry/types.js.map +1 -0
- package/dist/utils/text/normalizeText.d.ts +2 -0
- package/dist/utils/text/normalizeText.d.ts.map +1 -0
- package/dist/utils/text/normalizeText.js +5 -0
- package/dist/utils/text/normalizeText.js.map +1 -0
- package/dist/utils/text/terms.d.ts +3 -0
- package/dist/utils/text/terms.d.ts.map +1 -0
- package/dist/utils/text/terms.js +51 -0
- package/dist/utils/text/terms.js.map +1 -0
- package/dist/utils/text/tokenCount.d.ts +2 -0
- package/dist/utils/text/tokenCount.d.ts.map +1 -0
- package/dist/utils/text/tokenCount.js +4 -0
- package/dist/utils/text/tokenCount.js.map +1 -0
- package/dist/utils/vector/vectorLiteral.d.ts +3 -0
- package/dist/utils/vector/vectorLiteral.d.ts.map +1 -0
- package/dist/utils/vector/vectorLiteral.js +19 -0
- package/dist/utils/vector/vectorLiteral.js.map +1 -0
- package/package.json +11 -3
|
@@ -0,0 +1,782 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import postgres, {} from "postgres";
|
|
3
|
+
import { cosineSimilarity } from "../../utils/drift/cosineSimilarity.js";
|
|
4
|
+
import { parseVector, toVectorLiteral } from "../../utils/vector/vectorLiteral.js";
|
|
5
|
+
export class PostgresGraphStore {
|
|
6
|
+
sql;
|
|
7
|
+
constructor(connectionString) {
|
|
8
|
+
this.sql = postgres(connectionString, {
|
|
9
|
+
max: 10,
|
|
10
|
+
idle_timeout: 30,
|
|
11
|
+
connect_timeout: 10,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
async initialize() {
|
|
15
|
+
await this.sql `CREATE EXTENSION IF NOT EXISTS vector`;
|
|
16
|
+
await this.sql `CREATE EXTENSION IF NOT EXISTS pgcrypto`;
|
|
17
|
+
await this.sql `
|
|
18
|
+
CREATE TABLE IF NOT EXISTS mg_message_buffer (
|
|
19
|
+
session_id TEXT NOT NULL,
|
|
20
|
+
message_index INT NOT NULL,
|
|
21
|
+
role TEXT NOT NULL,
|
|
22
|
+
content TEXT NOT NULL,
|
|
23
|
+
PRIMARY KEY (session_id, message_index)
|
|
24
|
+
)
|
|
25
|
+
`;
|
|
26
|
+
await this.sql `
|
|
27
|
+
CREATE TABLE IF NOT EXISTS mg_segments (
|
|
28
|
+
id TEXT PRIMARY KEY,
|
|
29
|
+
session_id TEXT NOT NULL,
|
|
30
|
+
start_index INT NOT NULL,
|
|
31
|
+
end_index INT NOT NULL,
|
|
32
|
+
topic_order INT NOT NULL,
|
|
33
|
+
drift_score FLOAT NOT NULL,
|
|
34
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
35
|
+
UNIQUE (session_id, start_index, end_index)
|
|
36
|
+
)
|
|
37
|
+
`;
|
|
38
|
+
await this.sql `
|
|
39
|
+
CREATE TABLE IF NOT EXISTS mg_topic_nodes (
|
|
40
|
+
id TEXT PRIMARY KEY,
|
|
41
|
+
session_id TEXT NOT NULL,
|
|
42
|
+
segment_id TEXT NOT NULL REFERENCES mg_segments(id) ON DELETE CASCADE,
|
|
43
|
+
label TEXT,
|
|
44
|
+
summary TEXT,
|
|
45
|
+
embedding vector(1536),
|
|
46
|
+
message_range INT[],
|
|
47
|
+
topic_order INT NOT NULL DEFAULT 0,
|
|
48
|
+
drift_score FLOAT NOT NULL DEFAULT 0,
|
|
49
|
+
agent_color TEXT,
|
|
50
|
+
fleet_id TEXT,
|
|
51
|
+
agent_id TEXT,
|
|
52
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
53
|
+
UNIQUE (segment_id)
|
|
54
|
+
)
|
|
55
|
+
`;
|
|
56
|
+
await this.sql `
|
|
57
|
+
CREATE TABLE IF NOT EXISTS mg_topic_edges (
|
|
58
|
+
src_id TEXT NOT NULL,
|
|
59
|
+
dst_id TEXT NOT NULL,
|
|
60
|
+
weight FLOAT NOT NULL,
|
|
61
|
+
type TEXT NOT NULL,
|
|
62
|
+
PRIMARY KEY (src_id, dst_id)
|
|
63
|
+
)
|
|
64
|
+
`;
|
|
65
|
+
await this.sql `
|
|
66
|
+
CREATE TABLE IF NOT EXISTS mg_memory_nodes (
|
|
67
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
68
|
+
segment_id TEXT NOT NULL REFERENCES mg_segments(id) ON DELETE CASCADE,
|
|
69
|
+
topic_node_id TEXT NOT NULL REFERENCES mg_topic_nodes(id) ON DELETE CASCADE,
|
|
70
|
+
agent_id TEXT,
|
|
71
|
+
session_id TEXT NOT NULL,
|
|
72
|
+
memory_type TEXT NOT NULL CHECK (memory_type IN ('fact','insight','question','task','reference')),
|
|
73
|
+
source_type TEXT NOT NULL DEFAULT 'conversation' CHECK (source_type IN ('conversation','note','document','code')),
|
|
74
|
+
subject TEXT NOT NULL,
|
|
75
|
+
predicate TEXT NOT NULL,
|
|
76
|
+
value TEXT NOT NULL,
|
|
77
|
+
confidence FLOAT NOT NULL DEFAULT 1.0,
|
|
78
|
+
embedding vector(1536),
|
|
79
|
+
source_url TEXT,
|
|
80
|
+
source_title TEXT,
|
|
81
|
+
superseded_by UUID REFERENCES mg_memory_nodes(id),
|
|
82
|
+
decayed BOOLEAN NOT NULL DEFAULT FALSE,
|
|
83
|
+
agent_color TEXT,
|
|
84
|
+
fleet_id TEXT,
|
|
85
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
86
|
+
)
|
|
87
|
+
`;
|
|
88
|
+
await this.sql `
|
|
89
|
+
CREATE TABLE IF NOT EXISTS mg_memory_edges (
|
|
90
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
91
|
+
source_id UUID NOT NULL REFERENCES mg_memory_nodes(id) ON DELETE CASCADE,
|
|
92
|
+
target_id UUID NOT NULL REFERENCES mg_memory_nodes(id) ON DELETE CASCADE,
|
|
93
|
+
edge_type TEXT NOT NULL CHECK (edge_type IN ('semantic','conflicts','updates','related')),
|
|
94
|
+
weight FLOAT,
|
|
95
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
96
|
+
)
|
|
97
|
+
`;
|
|
98
|
+
await this.sql `
|
|
99
|
+
CREATE TABLE IF NOT EXISTS mg_fleets (
|
|
100
|
+
id TEXT PRIMARY KEY,
|
|
101
|
+
name TEXT,
|
|
102
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
103
|
+
)
|
|
104
|
+
`;
|
|
105
|
+
await this.sql `
|
|
106
|
+
CREATE TABLE IF NOT EXISTS mg_fleet_agents (
|
|
107
|
+
id TEXT PRIMARY KEY,
|
|
108
|
+
fleet_id TEXT NOT NULL REFERENCES mg_fleets(id) ON DELETE CASCADE,
|
|
109
|
+
session_id TEXT NOT NULL,
|
|
110
|
+
agent_color TEXT NOT NULL,
|
|
111
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
112
|
+
UNIQUE (fleet_id, agent_color)
|
|
113
|
+
)
|
|
114
|
+
`;
|
|
115
|
+
await this.migrateExistingNodeTable();
|
|
116
|
+
await this.createIndexes();
|
|
117
|
+
}
|
|
118
|
+
async saveMessages(sessionId, messages) {
|
|
119
|
+
await this.saveMessagesAt(sessionId, 0, messages);
|
|
120
|
+
}
|
|
121
|
+
async saveMessagesAt(sessionId, startIndex, messages) {
|
|
122
|
+
for (const [index, message] of messages.entries()) {
|
|
123
|
+
await this.sql `
|
|
124
|
+
INSERT INTO mg_message_buffer (session_id, message_index, role, content)
|
|
125
|
+
VALUES (${sessionId}, ${startIndex + index}, ${message.role}, ${message.content})
|
|
126
|
+
ON CONFLICT (session_id, message_index)
|
|
127
|
+
DO UPDATE SET role = EXCLUDED.role, content = EXCLUDED.content
|
|
128
|
+
`;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async saveSegment(segment) {
|
|
132
|
+
const rows = await this.sql `
|
|
133
|
+
INSERT INTO mg_segments (id, session_id, start_index, end_index, topic_order, drift_score, created_at)
|
|
134
|
+
VALUES (
|
|
135
|
+
${segment.id},
|
|
136
|
+
${segment.sessionId},
|
|
137
|
+
${segment.startIndex},
|
|
138
|
+
${segment.endIndex},
|
|
139
|
+
${segment.topicOrder},
|
|
140
|
+
${segment.driftScore},
|
|
141
|
+
${segment.createdAt}
|
|
142
|
+
)
|
|
143
|
+
ON CONFLICT (session_id, start_index, end_index)
|
|
144
|
+
DO UPDATE SET
|
|
145
|
+
topic_order = EXCLUDED.topic_order,
|
|
146
|
+
drift_score = EXCLUDED.drift_score
|
|
147
|
+
RETURNING *
|
|
148
|
+
`;
|
|
149
|
+
return this.rowToSegment(rows[0]);
|
|
150
|
+
}
|
|
151
|
+
async saveNode(node) {
|
|
152
|
+
await this.sql `
|
|
153
|
+
INSERT INTO mg_topic_nodes (
|
|
154
|
+
id,
|
|
155
|
+
session_id,
|
|
156
|
+
segment_id,
|
|
157
|
+
label,
|
|
158
|
+
summary,
|
|
159
|
+
embedding,
|
|
160
|
+
message_range,
|
|
161
|
+
topic_order,
|
|
162
|
+
drift_score,
|
|
163
|
+
agent_color,
|
|
164
|
+
fleet_id,
|
|
165
|
+
agent_id,
|
|
166
|
+
created_at
|
|
167
|
+
)
|
|
168
|
+
VALUES (
|
|
169
|
+
${node.id},
|
|
170
|
+
${node.sessionId},
|
|
171
|
+
${node.segmentId},
|
|
172
|
+
${node.label},
|
|
173
|
+
${node.summary},
|
|
174
|
+
${toVectorLiteral(node.embedding)}::vector,
|
|
175
|
+
${node.messageRange},
|
|
176
|
+
${node.topicOrder},
|
|
177
|
+
${node.driftScore},
|
|
178
|
+
${node.agentColor},
|
|
179
|
+
${node.fleetId},
|
|
180
|
+
${node.agentId},
|
|
181
|
+
${node.createdAt}
|
|
182
|
+
)
|
|
183
|
+
ON CONFLICT (segment_id)
|
|
184
|
+
DO UPDATE SET
|
|
185
|
+
id = EXCLUDED.id,
|
|
186
|
+
session_id = EXCLUDED.session_id,
|
|
187
|
+
label = EXCLUDED.label,
|
|
188
|
+
summary = EXCLUDED.summary,
|
|
189
|
+
embedding = EXCLUDED.embedding,
|
|
190
|
+
message_range = EXCLUDED.message_range,
|
|
191
|
+
topic_order = EXCLUDED.topic_order,
|
|
192
|
+
drift_score = EXCLUDED.drift_score,
|
|
193
|
+
agent_color = EXCLUDED.agent_color,
|
|
194
|
+
fleet_id = EXCLUDED.fleet_id,
|
|
195
|
+
agent_id = EXCLUDED.agent_id,
|
|
196
|
+
created_at = EXCLUDED.created_at
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
199
|
+
async saveEdge(edge) {
|
|
200
|
+
await this.sql `
|
|
201
|
+
INSERT INTO mg_topic_edges (src_id, dst_id, weight, type)
|
|
202
|
+
VALUES (${edge.srcId}, ${edge.dstId}, ${edge.weight}, ${edge.type})
|
|
203
|
+
ON CONFLICT (src_id, dst_id)
|
|
204
|
+
DO UPDATE SET weight = EXCLUDED.weight, type = EXCLUDED.type
|
|
205
|
+
`;
|
|
206
|
+
}
|
|
207
|
+
async getEdgesByType(sessionId, type) {
|
|
208
|
+
const rows = await this.sql `
|
|
209
|
+
SELECT e.*
|
|
210
|
+
FROM mg_topic_edges e
|
|
211
|
+
JOIN mg_topic_nodes n ON n.id = e.src_id
|
|
212
|
+
WHERE n.session_id = ${sessionId}
|
|
213
|
+
AND e.type = ${type}
|
|
214
|
+
`;
|
|
215
|
+
return rows.map((row) => ({
|
|
216
|
+
srcId: row.src_id,
|
|
217
|
+
dstId: row.dst_id,
|
|
218
|
+
weight: row.weight,
|
|
219
|
+
type: row.type,
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
async clearSession(sessionId) {
|
|
223
|
+
const nodeRows = await this.sql `
|
|
224
|
+
SELECT id FROM mg_topic_nodes
|
|
225
|
+
WHERE session_id = ${sessionId}
|
|
226
|
+
`;
|
|
227
|
+
const nodeIds = nodeRows.map((row) => row.id);
|
|
228
|
+
if (nodeIds.length > 0) {
|
|
229
|
+
await this.sql `
|
|
230
|
+
DELETE FROM mg_topic_edges
|
|
231
|
+
WHERE src_id = ANY(${this.sql.array(nodeIds)})
|
|
232
|
+
OR dst_id = ANY(${this.sql.array(nodeIds)})
|
|
233
|
+
`;
|
|
234
|
+
}
|
|
235
|
+
await this.sql `
|
|
236
|
+
DELETE FROM mg_topic_nodes
|
|
237
|
+
WHERE session_id = ${sessionId}
|
|
238
|
+
`;
|
|
239
|
+
await this.sql `
|
|
240
|
+
DELETE FROM mg_segments
|
|
241
|
+
WHERE session_id = ${sessionId}
|
|
242
|
+
`;
|
|
243
|
+
}
|
|
244
|
+
async clearSessionGraph(sessionId) {
|
|
245
|
+
await this.clearSession(sessionId);
|
|
246
|
+
}
|
|
247
|
+
async getTopicNode(topicNodeId, sessionId) {
|
|
248
|
+
const rows = await this.sql `
|
|
249
|
+
SELECT * FROM mg_topic_nodes
|
|
250
|
+
WHERE id = ${topicNodeId}
|
|
251
|
+
${sessionId ? this.sql `AND session_id = ${sessionId}` : this.sql ``}
|
|
252
|
+
LIMIT 1
|
|
253
|
+
`;
|
|
254
|
+
return rows[0] ? this.rowToNode(rows[0]) : null;
|
|
255
|
+
}
|
|
256
|
+
async getNodeBySegment(segmentId) {
|
|
257
|
+
const rows = await this.sql `
|
|
258
|
+
SELECT * FROM mg_topic_nodes
|
|
259
|
+
WHERE segment_id = ${segmentId}
|
|
260
|
+
LIMIT 1
|
|
261
|
+
`;
|
|
262
|
+
return rows[0] ? this.rowToNode(rows[0]) : null;
|
|
263
|
+
}
|
|
264
|
+
async getNodesBySession(sessionId) {
|
|
265
|
+
const rows = await this.sql `
|
|
266
|
+
SELECT * FROM mg_topic_nodes
|
|
267
|
+
WHERE session_id = ${sessionId}
|
|
268
|
+
ORDER BY topic_order ASC, created_at ASC
|
|
269
|
+
`;
|
|
270
|
+
return rows.map((row) => this.rowToNode(row));
|
|
271
|
+
}
|
|
272
|
+
async getSegmentsBySession(sessionId) {
|
|
273
|
+
const rows = await this.sql `
|
|
274
|
+
SELECT * FROM mg_segments
|
|
275
|
+
WHERE session_id = ${sessionId}
|
|
276
|
+
ORDER BY topic_order ASC, created_at ASC
|
|
277
|
+
`;
|
|
278
|
+
return rows.map((row) => this.rowToSegment(row));
|
|
279
|
+
}
|
|
280
|
+
async insertMemories(nodes) {
|
|
281
|
+
if (nodes.length === 0)
|
|
282
|
+
return;
|
|
283
|
+
const rows = nodes.map((node) => ({
|
|
284
|
+
id: node.id,
|
|
285
|
+
segment_id: node.segmentId,
|
|
286
|
+
topic_node_id: node.topicNodeId,
|
|
287
|
+
agent_id: node.agentId,
|
|
288
|
+
session_id: node.sessionId,
|
|
289
|
+
memory_type: node.memoryType,
|
|
290
|
+
source_type: node.sourceType,
|
|
291
|
+
subject: node.subject,
|
|
292
|
+
predicate: node.predicate,
|
|
293
|
+
value: node.value,
|
|
294
|
+
confidence: node.confidence,
|
|
295
|
+
embedding: toVectorLiteral(node.embedding),
|
|
296
|
+
source_url: node.sourceUrl,
|
|
297
|
+
source_title: node.sourceTitle,
|
|
298
|
+
superseded_by: node.supersededBy,
|
|
299
|
+
decayed: node.decayed,
|
|
300
|
+
agent_color: node.agentColor,
|
|
301
|
+
fleet_id: node.fleetId,
|
|
302
|
+
}));
|
|
303
|
+
await this.sql `
|
|
304
|
+
INSERT INTO mg_memory_nodes ${this.sql(rows, "id", "segment_id", "topic_node_id", "agent_id", "session_id", "memory_type", "source_type", "subject", "predicate", "value", "confidence", "embedding", "source_url", "source_title", "superseded_by", "decayed", "agent_color", "fleet_id")}
|
|
305
|
+
`;
|
|
306
|
+
}
|
|
307
|
+
async getMemoriesBySegment(segmentId) {
|
|
308
|
+
const rows = await this.sql `
|
|
309
|
+
SELECT * FROM mg_memory_nodes
|
|
310
|
+
WHERE segment_id = ${segmentId}
|
|
311
|
+
ORDER BY created_at ASC
|
|
312
|
+
`;
|
|
313
|
+
return rows.map((row) => this.rowToMemoryNode(row));
|
|
314
|
+
}
|
|
315
|
+
async getMemoriesByTopic(topicNodeId) {
|
|
316
|
+
const rows = await this.sql `
|
|
317
|
+
SELECT * FROM mg_memory_nodes
|
|
318
|
+
WHERE topic_node_id = ${topicNodeId}
|
|
319
|
+
AND decayed = false
|
|
320
|
+
AND superseded_by IS NULL
|
|
321
|
+
ORDER BY created_at ASC
|
|
322
|
+
`;
|
|
323
|
+
return rows.map((row) => this.rowToMemoryNode(row));
|
|
324
|
+
}
|
|
325
|
+
async searchMemories(embedding, sessionId, limit, minSimilarity) {
|
|
326
|
+
const rows = await this.sql `
|
|
327
|
+
SELECT *, 1 - (embedding <=> ${toVectorLiteral(embedding)}::vector) AS similarity
|
|
328
|
+
FROM mg_memory_nodes
|
|
329
|
+
WHERE session_id = ${sessionId}
|
|
330
|
+
AND decayed = false
|
|
331
|
+
AND superseded_by IS NULL
|
|
332
|
+
AND 1 - (embedding <=> ${toVectorLiteral(embedding)}::vector) >= ${minSimilarity}
|
|
333
|
+
ORDER BY similarity DESC
|
|
334
|
+
LIMIT ${limit}
|
|
335
|
+
`;
|
|
336
|
+
return rows.map((row) => ({
|
|
337
|
+
...this.rowToMemoryNode(row),
|
|
338
|
+
similarity: row.similarity,
|
|
339
|
+
}));
|
|
340
|
+
}
|
|
341
|
+
async buildMemoryEdges(topicNodeId, sessionId, threshold) {
|
|
342
|
+
try {
|
|
343
|
+
const memories = (await this.getMemoriesByTopic(topicNodeId))
|
|
344
|
+
.filter((memory) => memory.sessionId === sessionId);
|
|
345
|
+
if (memories.length < 2)
|
|
346
|
+
return;
|
|
347
|
+
for (let sourceIndex = 0; sourceIndex < memories.length; sourceIndex += 1) {
|
|
348
|
+
const source = memories[sourceIndex];
|
|
349
|
+
if (!source)
|
|
350
|
+
continue;
|
|
351
|
+
for (let targetIndex = sourceIndex + 1; targetIndex < memories.length; targetIndex += 1) {
|
|
352
|
+
const target = memories[targetIndex];
|
|
353
|
+
if (!target || source.id === target.id)
|
|
354
|
+
continue;
|
|
355
|
+
const similarity = cosineSimilarity(source.embedding, target.embedding);
|
|
356
|
+
if (!Number.isFinite(similarity) || similarity < threshold)
|
|
357
|
+
continue;
|
|
358
|
+
const existing = await this.sql `
|
|
359
|
+
SELECT id FROM mg_memory_edges
|
|
360
|
+
WHERE (source_id = ${source.id}::uuid AND target_id = ${target.id}::uuid)
|
|
361
|
+
OR (source_id = ${target.id}::uuid AND target_id = ${source.id}::uuid)
|
|
362
|
+
LIMIT 1
|
|
363
|
+
`;
|
|
364
|
+
if (existing.length > 0)
|
|
365
|
+
continue;
|
|
366
|
+
await this.sql `
|
|
367
|
+
INSERT INTO mg_memory_edges (source_id, target_id, edge_type, weight)
|
|
368
|
+
VALUES (${source.id}::uuid, ${target.id}::uuid, 'semantic', ${similarity})
|
|
369
|
+
`;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
console.warn("GraphStore memory edge build warning:", error);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
async getTopKSimilar(nodeId, embedding, sessionId, k) {
|
|
378
|
+
return this.getSimilarNodes(embedding, sessionId, { k, excludeNodeId: nodeId });
|
|
379
|
+
}
|
|
380
|
+
async getSimilarNodes(embedding, sessionId, options = {}) {
|
|
381
|
+
const rows = await this.sql `
|
|
382
|
+
SELECT *, 1 - (embedding <=> ${toVectorLiteral(embedding)}::vector) AS similarity
|
|
383
|
+
FROM mg_topic_nodes
|
|
384
|
+
WHERE session_id = ${sessionId}
|
|
385
|
+
${options.excludeNodeId ? this.sql `AND id != ${options.excludeNodeId}` : this.sql ``}
|
|
386
|
+
${options.minSimilarity === undefined ? this.sql `` : this.sql `AND 1 - (embedding <=> ${toVectorLiteral(embedding)}::vector) >= ${options.minSimilarity}`}
|
|
387
|
+
ORDER BY embedding <=> ${toVectorLiteral(embedding)}::vector ASC
|
|
388
|
+
LIMIT ${options.k ?? 5}
|
|
389
|
+
`;
|
|
390
|
+
return rows.map((row) => this.rowToNode(row));
|
|
391
|
+
}
|
|
392
|
+
async getSimilarNodesAcrossFleet(fleetId, embedding, options = {}) {
|
|
393
|
+
const rows = await this.sql `
|
|
394
|
+
SELECT *, 1 - (embedding <=> ${toVectorLiteral(embedding)}::vector) AS similarity
|
|
395
|
+
FROM mg_topic_nodes
|
|
396
|
+
WHERE fleet_id = ${fleetId}
|
|
397
|
+
${options.agentColor ? this.sql `AND agent_color = ${options.agentColor}` : this.sql ``}
|
|
398
|
+
${options.excludeNodeId ? this.sql `AND id != ${options.excludeNodeId}` : this.sql ``}
|
|
399
|
+
${options.minSimilarity === undefined ? this.sql `` : this.sql `AND 1 - (embedding <=> ${toVectorLiteral(embedding)}::vector) >= ${options.minSimilarity}`}
|
|
400
|
+
ORDER BY embedding <=> ${toVectorLiteral(embedding)}::vector ASC
|
|
401
|
+
LIMIT ${options.k ?? 5}
|
|
402
|
+
`;
|
|
403
|
+
return rows.map((row) => this.rowToNode(row));
|
|
404
|
+
}
|
|
405
|
+
async getNodesByColor(fleetId, agentColor) {
|
|
406
|
+
const rows = await this.sql `
|
|
407
|
+
SELECT * FROM mg_topic_nodes
|
|
408
|
+
WHERE fleet_id = ${fleetId}
|
|
409
|
+
AND agent_color = ${agentColor}
|
|
410
|
+
ORDER BY topic_order ASC, created_at ASC
|
|
411
|
+
`;
|
|
412
|
+
return rows.map((row) => this.rowToNode(row));
|
|
413
|
+
}
|
|
414
|
+
async saveFleet(fleetId, name) {
|
|
415
|
+
await this.sql `
|
|
416
|
+
INSERT INTO mg_fleets (id, name)
|
|
417
|
+
VALUES (${fleetId}, ${name ?? null})
|
|
418
|
+
ON CONFLICT (id)
|
|
419
|
+
DO UPDATE SET name = COALESCE(EXCLUDED.name, mg_fleets.name)
|
|
420
|
+
`;
|
|
421
|
+
}
|
|
422
|
+
async saveFleetAgent(agent) {
|
|
423
|
+
await this.sql `
|
|
424
|
+
INSERT INTO mg_fleet_agents (id, fleet_id, session_id, agent_color)
|
|
425
|
+
VALUES (${agent.id}, ${agent.fleetId}, ${agent.sessionId}, ${agent.agentColor})
|
|
426
|
+
ON CONFLICT (fleet_id, agent_color)
|
|
427
|
+
DO UPDATE SET
|
|
428
|
+
id = EXCLUDED.id,
|
|
429
|
+
session_id = EXCLUDED.session_id
|
|
430
|
+
`;
|
|
431
|
+
}
|
|
432
|
+
async getFleetAgents(fleetId) {
|
|
433
|
+
const rows = await this.sql `
|
|
434
|
+
SELECT * FROM mg_fleet_agents
|
|
435
|
+
WHERE fleet_id = ${fleetId}
|
|
436
|
+
ORDER BY created_at ASC
|
|
437
|
+
`;
|
|
438
|
+
return rows.map((row) => ({
|
|
439
|
+
id: row.id,
|
|
440
|
+
fleetId: row.fleet_id,
|
|
441
|
+
sessionId: row.session_id,
|
|
442
|
+
agentColor: row.agent_color,
|
|
443
|
+
createdAt: row.created_at,
|
|
444
|
+
}));
|
|
445
|
+
}
|
|
446
|
+
async tagSessionNodes(sessionId, metadata) {
|
|
447
|
+
await this.sql `
|
|
448
|
+
UPDATE mg_topic_nodes
|
|
449
|
+
SET
|
|
450
|
+
fleet_id = ${metadata.fleetId},
|
|
451
|
+
agent_id = ${metadata.agentId},
|
|
452
|
+
agent_color = ${metadata.agentColor}
|
|
453
|
+
WHERE session_id = ${sessionId}
|
|
454
|
+
`;
|
|
455
|
+
}
|
|
456
|
+
async getPreviousNode(sessionId, topicOrder) {
|
|
457
|
+
const rows = await this.sql `
|
|
458
|
+
SELECT *
|
|
459
|
+
FROM mg_topic_nodes
|
|
460
|
+
WHERE session_id = ${sessionId}
|
|
461
|
+
AND topic_order = ${topicOrder - 1}
|
|
462
|
+
LIMIT 1
|
|
463
|
+
`;
|
|
464
|
+
return rows[0] ? this.rowToNode(rows[0]) : null;
|
|
465
|
+
}
|
|
466
|
+
async nodeSimilarity(nodeAId, nodeBId) {
|
|
467
|
+
const rows = await this.sql `
|
|
468
|
+
SELECT 1 - (n1.embedding <=> n2.embedding) AS similarity
|
|
469
|
+
FROM mg_topic_nodes n1, mg_topic_nodes n2
|
|
470
|
+
WHERE n1.id = ${nodeAId}
|
|
471
|
+
AND n2.id = ${nodeBId}
|
|
472
|
+
`;
|
|
473
|
+
return rows[0]?.similarity ?? 0;
|
|
474
|
+
}
|
|
475
|
+
async getNeighbours(nodeIds, hopDepth, sessionId) {
|
|
476
|
+
const visited = new Set(nodeIds);
|
|
477
|
+
let frontier = [...nodeIds];
|
|
478
|
+
for (let hop = 0; hop < hopDepth; hop += 1) {
|
|
479
|
+
if (frontier.length === 0)
|
|
480
|
+
break;
|
|
481
|
+
const edges = await this.sql `
|
|
482
|
+
SELECT * FROM mg_topic_edges
|
|
483
|
+
WHERE src_id = ANY(${this.sql.array(frontier)})
|
|
484
|
+
OR dst_id = ANY(${this.sql.array(frontier)})
|
|
485
|
+
`;
|
|
486
|
+
const nextFrontier = [];
|
|
487
|
+
for (const edge of edges) {
|
|
488
|
+
const candidates = [edge.src_id, edge.dst_id];
|
|
489
|
+
for (const candidate of candidates) {
|
|
490
|
+
if (!visited.has(candidate)) {
|
|
491
|
+
visited.add(candidate);
|
|
492
|
+
nextFrontier.push(candidate);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
frontier = nextFrontier;
|
|
497
|
+
}
|
|
498
|
+
const ids = Array.from(visited);
|
|
499
|
+
if (ids.length === 0)
|
|
500
|
+
return [];
|
|
501
|
+
const rows = await this.sql `
|
|
502
|
+
SELECT * FROM mg_topic_nodes
|
|
503
|
+
WHERE id = ANY(${this.sql.array(ids)})
|
|
504
|
+
${sessionId ? this.sql `AND session_id = ${sessionId}` : this.sql ``}
|
|
505
|
+
ORDER BY topic_order ASC, created_at ASC
|
|
506
|
+
`;
|
|
507
|
+
return rows.map((row) => this.rowToNode(row));
|
|
508
|
+
}
|
|
509
|
+
async getBufferMessages(sessionId, start, end, maxChars = 600) {
|
|
510
|
+
const rows = await this.sql `
|
|
511
|
+
SELECT * FROM mg_message_buffer
|
|
512
|
+
WHERE session_id = ${sessionId}
|
|
513
|
+
AND message_index >= ${start}
|
|
514
|
+
AND message_index <= ${end}
|
|
515
|
+
ORDER BY message_index ASC
|
|
516
|
+
`;
|
|
517
|
+
return rows.map((row) => ({
|
|
518
|
+
role: row.role,
|
|
519
|
+
content: row.role === "assistant" ? this.cleanAssistantMessage(row.content, maxChars) : row.content,
|
|
520
|
+
}));
|
|
521
|
+
}
|
|
522
|
+
async absorbNodes(nodes, targetSessionId, options = {}) {
|
|
523
|
+
const copiedNodes = [];
|
|
524
|
+
const nextMessageIndex = await this.getNextMessageIndex(targetSessionId);
|
|
525
|
+
const nextTopicOrder = await this.getNextTopicOrder(targetSessionId);
|
|
526
|
+
for (const [index, node] of nodes.entries()) {
|
|
527
|
+
const messageIndex = nextMessageIndex + index;
|
|
528
|
+
await this.saveMessagesAt(targetSessionId, messageIndex, [{
|
|
529
|
+
role: "assistant",
|
|
530
|
+
content: this.formatGraftedMemoryMessage(node),
|
|
531
|
+
}]);
|
|
532
|
+
const segment = await this.saveSegment({
|
|
533
|
+
id: randomUUID(),
|
|
534
|
+
sessionId: targetSessionId,
|
|
535
|
+
startIndex: messageIndex,
|
|
536
|
+
endIndex: messageIndex,
|
|
537
|
+
topicOrder: nextTopicOrder + index,
|
|
538
|
+
driftScore: node.driftScore,
|
|
539
|
+
createdAt: new Date(),
|
|
540
|
+
});
|
|
541
|
+
const copy = {
|
|
542
|
+
...node,
|
|
543
|
+
id: randomUUID(),
|
|
544
|
+
sessionId: targetSessionId,
|
|
545
|
+
segmentId: segment.id,
|
|
546
|
+
messageRange: [segment.startIndex, segment.endIndex],
|
|
547
|
+
topicOrder: segment.topicOrder,
|
|
548
|
+
driftScore: segment.driftScore,
|
|
549
|
+
agentColor: options.agentColor ?? node.agentColor,
|
|
550
|
+
fleetId: options.fleetId ?? node.fleetId,
|
|
551
|
+
agentId: options.agentId ?? node.agentId,
|
|
552
|
+
createdAt: new Date(),
|
|
553
|
+
};
|
|
554
|
+
await this.saveNode(copy);
|
|
555
|
+
await this.saveEdge({
|
|
556
|
+
srcId: copy.id,
|
|
557
|
+
dstId: node.id,
|
|
558
|
+
weight: 1,
|
|
559
|
+
type: "grafted",
|
|
560
|
+
});
|
|
561
|
+
copiedNodes.push(copy);
|
|
562
|
+
}
|
|
563
|
+
return copiedNodes;
|
|
564
|
+
}
|
|
565
|
+
async rebuildEdgesForSession(sessionId, semanticTopK = 5, semanticThreshold = 0.6) {
|
|
566
|
+
const nodes = await this.getNodesBySession(sessionId);
|
|
567
|
+
const nodeIds = nodes.map((node) => node.id);
|
|
568
|
+
if (nodeIds.length > 0) {
|
|
569
|
+
await this.sql `
|
|
570
|
+
DELETE FROM mg_topic_edges
|
|
571
|
+
WHERE type != 'grafted'
|
|
572
|
+
AND (
|
|
573
|
+
src_id = ANY(${this.sql.array(nodeIds)})
|
|
574
|
+
OR dst_id = ANY(${this.sql.array(nodeIds)})
|
|
575
|
+
)
|
|
576
|
+
`;
|
|
577
|
+
}
|
|
578
|
+
for (let index = 1; index < nodes.length; index += 1) {
|
|
579
|
+
const current = nodes[index];
|
|
580
|
+
const previous = nodes[index - 1];
|
|
581
|
+
if (!current || !previous)
|
|
582
|
+
continue;
|
|
583
|
+
await this.saveEdge({
|
|
584
|
+
srcId: current.id,
|
|
585
|
+
dstId: previous.id,
|
|
586
|
+
weight: await this.nodeSimilarity(current.id, previous.id),
|
|
587
|
+
type: "temporal",
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
for (const node of nodes) {
|
|
591
|
+
const similarNodes = await this.getSimilarNodes(node.embedding, sessionId, {
|
|
592
|
+
k: semanticTopK,
|
|
593
|
+
excludeNodeId: node.id,
|
|
594
|
+
minSimilarity: semanticThreshold,
|
|
595
|
+
});
|
|
596
|
+
for (const similarNode of similarNodes) {
|
|
597
|
+
await this.saveEdge({
|
|
598
|
+
srcId: node.id,
|
|
599
|
+
dstId: similarNode.id,
|
|
600
|
+
weight: await this.nodeSimilarity(node.id, similarNode.id),
|
|
601
|
+
type: "semantic",
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
async close() {
|
|
607
|
+
await this.sql.end();
|
|
608
|
+
}
|
|
609
|
+
async migrateExistingNodeTable() {
|
|
610
|
+
await this.sql `ALTER TABLE mg_segments DROP COLUMN IF EXISTS node_id`;
|
|
611
|
+
await this.sql `ALTER TABLE mg_topic_nodes ADD COLUMN IF NOT EXISTS segment_id TEXT`;
|
|
612
|
+
await this.sql `ALTER TABLE mg_topic_nodes ADD COLUMN IF NOT EXISTS topic_order INT NOT NULL DEFAULT 0`;
|
|
613
|
+
await this.sql `ALTER TABLE mg_topic_nodes ADD COLUMN IF NOT EXISTS drift_score FLOAT NOT NULL DEFAULT 0`;
|
|
614
|
+
await this.sql `ALTER TABLE mg_topic_nodes ADD COLUMN IF NOT EXISTS agent_color TEXT`;
|
|
615
|
+
await this.sql `ALTER TABLE mg_topic_nodes ADD COLUMN IF NOT EXISTS fleet_id TEXT`;
|
|
616
|
+
await this.sql `ALTER TABLE mg_topic_nodes ADD COLUMN IF NOT EXISTS agent_id TEXT`;
|
|
617
|
+
await this.sql `
|
|
618
|
+
UPDATE mg_topic_nodes n
|
|
619
|
+
SET
|
|
620
|
+
topic_order = s.topic_order,
|
|
621
|
+
drift_score = s.drift_score
|
|
622
|
+
FROM mg_segments s
|
|
623
|
+
WHERE s.id = n.segment_id
|
|
624
|
+
`;
|
|
625
|
+
await this.sql `
|
|
626
|
+
DO $$
|
|
627
|
+
BEGIN
|
|
628
|
+
IF NOT EXISTS (
|
|
629
|
+
SELECT 1
|
|
630
|
+
FROM pg_constraint
|
|
631
|
+
WHERE conname = 'mg_topic_nodes_segment_id_key'
|
|
632
|
+
) THEN
|
|
633
|
+
ALTER TABLE mg_topic_nodes
|
|
634
|
+
ADD CONSTRAINT mg_topic_nodes_segment_id_key UNIQUE (segment_id);
|
|
635
|
+
END IF;
|
|
636
|
+
END $$;
|
|
637
|
+
`;
|
|
638
|
+
}
|
|
639
|
+
async createIndexes() {
|
|
640
|
+
await this.sql `
|
|
641
|
+
CREATE INDEX IF NOT EXISTS mg_message_buffer_session_idx
|
|
642
|
+
ON mg_message_buffer(session_id, message_index)
|
|
643
|
+
`;
|
|
644
|
+
await this.sql `
|
|
645
|
+
CREATE INDEX IF NOT EXISTS mg_segments_session_idx
|
|
646
|
+
ON mg_segments(session_id, topic_order)
|
|
647
|
+
`;
|
|
648
|
+
await this.sql `
|
|
649
|
+
CREATE INDEX IF NOT EXISTS mg_nodes_session_idx
|
|
650
|
+
ON mg_topic_nodes(session_id, topic_order)
|
|
651
|
+
`;
|
|
652
|
+
await this.sql `
|
|
653
|
+
CREATE INDEX IF NOT EXISTS mg_nodes_fleet_idx
|
|
654
|
+
ON mg_topic_nodes(fleet_id, agent_color)
|
|
655
|
+
`;
|
|
656
|
+
await this.sql `
|
|
657
|
+
CREATE INDEX IF NOT EXISTS mg_fleet_agents_fleet_idx
|
|
658
|
+
ON mg_fleet_agents(fleet_id, agent_color)
|
|
659
|
+
`;
|
|
660
|
+
await this.sql `
|
|
661
|
+
CREATE INDEX IF NOT EXISTS mg_nodes_embedding_idx
|
|
662
|
+
ON mg_topic_nodes
|
|
663
|
+
USING ivfflat (embedding vector_cosine_ops)
|
|
664
|
+
WITH (lists = 100)
|
|
665
|
+
`;
|
|
666
|
+
await this.sql `
|
|
667
|
+
CREATE INDEX IF NOT EXISTS idx_memory_nodes_topic
|
|
668
|
+
ON mg_memory_nodes(topic_node_id)
|
|
669
|
+
`;
|
|
670
|
+
await this.sql `
|
|
671
|
+
CREATE INDEX IF NOT EXISTS idx_memory_nodes_segment
|
|
672
|
+
ON mg_memory_nodes(segment_id)
|
|
673
|
+
`;
|
|
674
|
+
await this.sql `
|
|
675
|
+
CREATE INDEX IF NOT EXISTS idx_memory_nodes_session
|
|
676
|
+
ON mg_memory_nodes(session_id)
|
|
677
|
+
`;
|
|
678
|
+
await this.sql `
|
|
679
|
+
CREATE INDEX IF NOT EXISTS idx_memory_nodes_embedding
|
|
680
|
+
ON mg_memory_nodes
|
|
681
|
+
USING ivfflat (embedding vector_cosine_ops)
|
|
682
|
+
WITH (lists = 100)
|
|
683
|
+
`;
|
|
684
|
+
await this.sql `
|
|
685
|
+
CREATE INDEX IF NOT EXISTS idx_memory_edges_source
|
|
686
|
+
ON mg_memory_edges(source_id)
|
|
687
|
+
`;
|
|
688
|
+
await this.sql `
|
|
689
|
+
CREATE INDEX IF NOT EXISTS idx_memory_edges_target
|
|
690
|
+
ON mg_memory_edges(target_id)
|
|
691
|
+
`;
|
|
692
|
+
}
|
|
693
|
+
cleanAssistantMessage(content, maxChars) {
|
|
694
|
+
const withoutCodeBlocks = content.replace(/```[\s\S]*?```/g, "[code omitted]");
|
|
695
|
+
if (withoutCodeBlocks.length <= maxChars)
|
|
696
|
+
return withoutCodeBlocks;
|
|
697
|
+
const truncated = withoutCodeBlocks.slice(0, maxChars);
|
|
698
|
+
const sentenceEnd = Math.max(truncated.lastIndexOf("."), truncated.lastIndexOf("!"), truncated.lastIndexOf("?"));
|
|
699
|
+
if (sentenceEnd >= Math.floor(maxChars * 0.5)) {
|
|
700
|
+
return `${truncated.slice(0, sentenceEnd + 1)} [truncated]`;
|
|
701
|
+
}
|
|
702
|
+
return `${truncated.trimEnd()} [truncated]`;
|
|
703
|
+
}
|
|
704
|
+
async getNextMessageIndex(sessionId) {
|
|
705
|
+
const rows = await this.sql `
|
|
706
|
+
SELECT COALESCE(MAX(message_index) + 1, 0) AS next_index
|
|
707
|
+
FROM mg_message_buffer
|
|
708
|
+
WHERE session_id = ${sessionId}
|
|
709
|
+
`;
|
|
710
|
+
return rows[0]?.next_index ?? 0;
|
|
711
|
+
}
|
|
712
|
+
async getNextTopicOrder(sessionId) {
|
|
713
|
+
const rows = await this.sql `
|
|
714
|
+
SELECT COALESCE(MAX(topic_order) + 1, 1) AS next_order
|
|
715
|
+
FROM mg_topic_nodes
|
|
716
|
+
WHERE session_id = ${sessionId}
|
|
717
|
+
`;
|
|
718
|
+
return rows[0]?.next_order ?? 1;
|
|
719
|
+
}
|
|
720
|
+
formatGraftedMemoryMessage(node) {
|
|
721
|
+
return [
|
|
722
|
+
`Grafted memory: ${node.label}`,
|
|
723
|
+
node.summary,
|
|
724
|
+
].filter(Boolean).join("\n");
|
|
725
|
+
}
|
|
726
|
+
rowToNode(row) {
|
|
727
|
+
const [start = 0, end = start] = row.message_range ?? [];
|
|
728
|
+
return {
|
|
729
|
+
id: row.id,
|
|
730
|
+
sessionId: row.session_id,
|
|
731
|
+
segmentId: row.segment_id,
|
|
732
|
+
label: row.label ?? "Untitled topic",
|
|
733
|
+
summary: row.summary ?? "",
|
|
734
|
+
embedding: parseVector(row.embedding),
|
|
735
|
+
messageRange: [start, end],
|
|
736
|
+
topicOrder: row.topic_order ?? 0,
|
|
737
|
+
driftScore: row.drift_score ?? 0,
|
|
738
|
+
agentColor: row.agent_color,
|
|
739
|
+
fleetId: row.fleet_id,
|
|
740
|
+
agentId: row.agent_id,
|
|
741
|
+
createdAt: row.created_at,
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
rowToMemoryNode(row) {
|
|
745
|
+
return {
|
|
746
|
+
id: row.id,
|
|
747
|
+
segmentId: row.segment_id,
|
|
748
|
+
topicNodeId: row.topic_node_id,
|
|
749
|
+
agentId: row.agent_id,
|
|
750
|
+
sessionId: row.session_id,
|
|
751
|
+
memoryType: row.memory_type,
|
|
752
|
+
sourceType: row.source_type,
|
|
753
|
+
subject: row.subject,
|
|
754
|
+
predicate: row.predicate,
|
|
755
|
+
value: row.value,
|
|
756
|
+
confidence: row.confidence,
|
|
757
|
+
embedding: parseVector(row.embedding),
|
|
758
|
+
sourceUrl: row.source_url,
|
|
759
|
+
sourceTitle: row.source_title,
|
|
760
|
+
supersededBy: row.superseded_by,
|
|
761
|
+
decayed: row.decayed,
|
|
762
|
+
agentColor: row.agent_color,
|
|
763
|
+
fleetId: row.fleet_id,
|
|
764
|
+
createdAt: row.created_at,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
rowToSegment(row) {
|
|
768
|
+
if (!row) {
|
|
769
|
+
throw new Error("Expected segment row.");
|
|
770
|
+
}
|
|
771
|
+
return {
|
|
772
|
+
id: row.id,
|
|
773
|
+
sessionId: row.session_id,
|
|
774
|
+
startIndex: row.start_index,
|
|
775
|
+
endIndex: row.end_index,
|
|
776
|
+
topicOrder: row.topic_order,
|
|
777
|
+
driftScore: row.drift_score,
|
|
778
|
+
createdAt: row.created_at,
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
//# sourceMappingURL=GraphStore.js.map
|