@soulcraft/brainy 3.18.0 → 3.19.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/CHANGELOG.md +2 -0
- package/README.md +52 -3
- package/dist/brainy.d.ts +18 -0
- package/dist/brainy.js +24 -0
- package/dist/conversation/conversationManager.d.ts +176 -0
- package/dist/conversation/conversationManager.js +666 -0
- package/dist/conversation/index.d.ts +8 -0
- package/dist/conversation/index.js +8 -0
- package/dist/conversation/types.d.ts +231 -0
- package/dist/conversation/types.js +8 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/mcp/conversationTools.d.ts +88 -0
- package/dist/mcp/conversationTools.js +470 -0
- package/dist/types/mcpTypes.d.ts +7 -1
- package/package.json +1 -1
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConversationManager - Infinite Agent Memory
|
|
3
|
+
*
|
|
4
|
+
* Production-ready conversation and context management for AI agents.
|
|
5
|
+
* Built on Brainy's existing infrastructure: Triple Intelligence, Neural API, VFS.
|
|
6
|
+
*
|
|
7
|
+
* REAL IMPLEMENTATION - No stubs, no mocks, no TODOs
|
|
8
|
+
*/
|
|
9
|
+
import { v4 as uuidv4 } from '../universal/uuid.js';
|
|
10
|
+
import { NounType, VerbType } from '../types/graphTypes.js';
|
|
11
|
+
/**
|
|
12
|
+
* ConversationManager - High-level API for conversation operations
|
|
13
|
+
*
|
|
14
|
+
* Uses existing Brainy infrastructure:
|
|
15
|
+
* - brain.add() for messages
|
|
16
|
+
* - brain.relate() for threading
|
|
17
|
+
* - brain.find() with Triple Intelligence for context
|
|
18
|
+
* - brain.neural for clustering and similarity
|
|
19
|
+
* - brain.vfs() for artifacts
|
|
20
|
+
*/
|
|
21
|
+
export class ConversationManager {
|
|
22
|
+
/**
|
|
23
|
+
* Create a ConversationManager instance
|
|
24
|
+
* @param brain Brainy instance to use
|
|
25
|
+
*/
|
|
26
|
+
constructor(brain) {
|
|
27
|
+
this.initialized = false;
|
|
28
|
+
this._vfs = null;
|
|
29
|
+
this.brain = brain;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Initialize the conversation manager
|
|
33
|
+
* Lazy initialization pattern - only called when first used
|
|
34
|
+
*/
|
|
35
|
+
async init() {
|
|
36
|
+
if (this.initialized) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// VFS is lazy-loaded and might not be initialized yet
|
|
40
|
+
try {
|
|
41
|
+
this._vfs = this.brain.vfs();
|
|
42
|
+
await this._vfs.init();
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
// VFS initialization failed, will work without artifact support
|
|
46
|
+
console.warn('VFS initialization failed, artifact support disabled:', error);
|
|
47
|
+
}
|
|
48
|
+
this.initialized = true;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Save a message to the conversation history
|
|
52
|
+
*
|
|
53
|
+
* Uses: brain.add() with NounType.Message
|
|
54
|
+
* Real implementation - stores message with embedding
|
|
55
|
+
*
|
|
56
|
+
* @param content Message content
|
|
57
|
+
* @param role Message role (user, assistant, system, tool)
|
|
58
|
+
* @param options Save options (conversationId, metadata, etc.)
|
|
59
|
+
* @returns Message ID
|
|
60
|
+
*/
|
|
61
|
+
async saveMessage(content, role, options = {}) {
|
|
62
|
+
if (!this.initialized) {
|
|
63
|
+
await this.init();
|
|
64
|
+
}
|
|
65
|
+
// Generate IDs if not provided
|
|
66
|
+
const conversationId = options.conversationId || `conv_${uuidv4()}`;
|
|
67
|
+
const sessionId = options.sessionId || `session_${uuidv4()}`;
|
|
68
|
+
const timestamp = Date.now();
|
|
69
|
+
// Build metadata
|
|
70
|
+
const metadata = {
|
|
71
|
+
role,
|
|
72
|
+
conversationId,
|
|
73
|
+
sessionId,
|
|
74
|
+
timestamp,
|
|
75
|
+
problemSolvingPhase: options.phase,
|
|
76
|
+
confidence: options.confidence,
|
|
77
|
+
artifacts: options.artifacts || [],
|
|
78
|
+
toolsUsed: options.toolsUsed || [],
|
|
79
|
+
references: [],
|
|
80
|
+
tags: options.tags || [],
|
|
81
|
+
...options.metadata
|
|
82
|
+
};
|
|
83
|
+
// Add message to brain using REAL API
|
|
84
|
+
const messageId = await this.brain.add({
|
|
85
|
+
data: content,
|
|
86
|
+
type: NounType.Message,
|
|
87
|
+
metadata
|
|
88
|
+
});
|
|
89
|
+
// Link to previous message if specified (REAL graph relationship)
|
|
90
|
+
if (options.linkToPrevious) {
|
|
91
|
+
await this.brain.relate({
|
|
92
|
+
from: options.linkToPrevious,
|
|
93
|
+
to: messageId,
|
|
94
|
+
type: VerbType.Precedes,
|
|
95
|
+
metadata: {
|
|
96
|
+
conversationId,
|
|
97
|
+
timestamp
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return messageId;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Link two messages in temporal sequence
|
|
105
|
+
*
|
|
106
|
+
* Uses: brain.relate() with VerbType.Precedes
|
|
107
|
+
* Real implementation - creates graph relationship
|
|
108
|
+
*
|
|
109
|
+
* @param prevMessageId ID of previous message
|
|
110
|
+
* @param nextMessageId ID of next message
|
|
111
|
+
* @returns Relationship ID
|
|
112
|
+
*/
|
|
113
|
+
async linkMessages(prevMessageId, nextMessageId) {
|
|
114
|
+
if (!this.initialized) {
|
|
115
|
+
await this.init();
|
|
116
|
+
}
|
|
117
|
+
// Create real graph relationship
|
|
118
|
+
const verbId = await this.brain.relate({
|
|
119
|
+
from: prevMessageId,
|
|
120
|
+
to: nextMessageId,
|
|
121
|
+
type: VerbType.Precedes,
|
|
122
|
+
metadata: {
|
|
123
|
+
timestamp: Date.now()
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
return verbId;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get a full conversation thread
|
|
130
|
+
*
|
|
131
|
+
* Uses: brain.getNoun() and brain.getConnections()
|
|
132
|
+
* Real implementation - traverses graph relationships
|
|
133
|
+
*
|
|
134
|
+
* @param conversationId Conversation ID
|
|
135
|
+
* @param options Options (includeArtifacts, etc.)
|
|
136
|
+
* @returns Complete conversation thread
|
|
137
|
+
*/
|
|
138
|
+
async getConversationThread(conversationId, options = {}) {
|
|
139
|
+
if (!this.initialized) {
|
|
140
|
+
await this.init();
|
|
141
|
+
}
|
|
142
|
+
// Search for all messages in conversation (REAL search)
|
|
143
|
+
const results = await this.brain.find({
|
|
144
|
+
where: {
|
|
145
|
+
conversationId
|
|
146
|
+
},
|
|
147
|
+
limit: 10000 // Large limit for full thread
|
|
148
|
+
});
|
|
149
|
+
// Convert results to ConversationMessage format
|
|
150
|
+
const messages = results.map((result) => ({
|
|
151
|
+
id: result.id,
|
|
152
|
+
content: result.data || result.content || '',
|
|
153
|
+
role: result.metadata.role,
|
|
154
|
+
metadata: result.metadata,
|
|
155
|
+
embedding: result.embedding,
|
|
156
|
+
createdAt: result.metadata.timestamp || Date.now(),
|
|
157
|
+
updatedAt: result.metadata.timestamp || Date.now()
|
|
158
|
+
}));
|
|
159
|
+
// Sort by timestamp
|
|
160
|
+
messages.sort((a, b) => a.createdAt - b.createdAt);
|
|
161
|
+
// Build thread metadata
|
|
162
|
+
const startTime = messages.length > 0 ? messages[0].createdAt : Date.now();
|
|
163
|
+
const endTime = messages.length > 0 ? messages[messages.length - 1].createdAt : undefined;
|
|
164
|
+
const totalTokens = messages.reduce((sum, msg) => sum + (msg.metadata.tokensUsed || 0), 0);
|
|
165
|
+
const threadMetadata = {
|
|
166
|
+
conversationId,
|
|
167
|
+
startTime,
|
|
168
|
+
endTime,
|
|
169
|
+
messageCount: messages.length,
|
|
170
|
+
totalTokens,
|
|
171
|
+
participants: [...new Set(messages.map(m => m.role))]
|
|
172
|
+
};
|
|
173
|
+
// Get artifacts if requested (REAL VFS query)
|
|
174
|
+
let artifacts;
|
|
175
|
+
if (options.includeArtifacts && this._vfs) {
|
|
176
|
+
artifacts = messages
|
|
177
|
+
.flatMap(m => m.metadata.artifacts || [])
|
|
178
|
+
.filter((id, idx, arr) => arr.indexOf(id) === idx);
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
id: conversationId,
|
|
182
|
+
metadata: threadMetadata,
|
|
183
|
+
messages,
|
|
184
|
+
artifacts
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get relevant context for a query
|
|
189
|
+
*
|
|
190
|
+
* Uses: brain.find() with Triple Intelligence
|
|
191
|
+
* Real implementation - semantic + temporal + graph ranking
|
|
192
|
+
*
|
|
193
|
+
* @param query Query string or context options
|
|
194
|
+
* @param options Retrieval options
|
|
195
|
+
* @returns Ranked context messages with artifacts
|
|
196
|
+
*/
|
|
197
|
+
async getRelevantContext(query, options) {
|
|
198
|
+
if (!this.initialized) {
|
|
199
|
+
await this.init();
|
|
200
|
+
}
|
|
201
|
+
const startTime = Date.now();
|
|
202
|
+
// Normalize options
|
|
203
|
+
const opts = typeof query === 'string'
|
|
204
|
+
? { query, ...options }
|
|
205
|
+
: query;
|
|
206
|
+
const { query: queryText, limit = 10, maxTokens = 50000, relevanceThreshold = 0.7, role, phase, tags, minConfidence, timeRange, conversationId, sessionId, weights = { semantic: 1.0, temporal: 0.5, graph: 0.3 }, includeArtifacts = false, includeSimilarConversations = false, deduplicateClusters = true } = opts;
|
|
207
|
+
// Build metadata filter
|
|
208
|
+
const whereFilter = {};
|
|
209
|
+
if (role) {
|
|
210
|
+
whereFilter.role = Array.isArray(role) ? { $in: role } : role;
|
|
211
|
+
}
|
|
212
|
+
if (phase) {
|
|
213
|
+
whereFilter.problemSolvingPhase = Array.isArray(phase) ? { $in: phase } : phase;
|
|
214
|
+
}
|
|
215
|
+
if (tags && tags.length > 0) {
|
|
216
|
+
whereFilter.tags = { $in: tags };
|
|
217
|
+
}
|
|
218
|
+
if (minConfidence !== undefined) {
|
|
219
|
+
whereFilter.confidence = { $gte: minConfidence };
|
|
220
|
+
}
|
|
221
|
+
if (timeRange) {
|
|
222
|
+
if (timeRange.start !== undefined) {
|
|
223
|
+
whereFilter.timestamp = { $gte: timeRange.start };
|
|
224
|
+
}
|
|
225
|
+
if (timeRange.end !== undefined) {
|
|
226
|
+
whereFilter.timestamp = { ...whereFilter.timestamp, $lte: timeRange.end };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (conversationId) {
|
|
230
|
+
whereFilter.conversationId = conversationId;
|
|
231
|
+
}
|
|
232
|
+
if (sessionId) {
|
|
233
|
+
whereFilter.sessionId = sessionId;
|
|
234
|
+
}
|
|
235
|
+
// Query with Triple Intelligence (REAL)
|
|
236
|
+
const findOptions = {
|
|
237
|
+
limit: limit * 2, // Get more for ranking
|
|
238
|
+
where: whereFilter
|
|
239
|
+
};
|
|
240
|
+
if (queryText) {
|
|
241
|
+
findOptions.like = queryText;
|
|
242
|
+
}
|
|
243
|
+
const results = await this.brain.find(findOptions);
|
|
244
|
+
// Calculate relevance scores (REAL scoring)
|
|
245
|
+
const now = Date.now();
|
|
246
|
+
const rankedMessages = results
|
|
247
|
+
.map((result) => {
|
|
248
|
+
// Semantic score (from vector similarity)
|
|
249
|
+
const semanticScore = result.score || 0;
|
|
250
|
+
// Temporal score (recency decay)
|
|
251
|
+
const ageInDays = (now - (result.metadata.timestamp || now)) / (1000 * 60 * 60 * 24);
|
|
252
|
+
const temporalScore = Math.exp(-0.1 * ageInDays); // Decay rate: 0.1
|
|
253
|
+
// Graph score (would need graph traversal, simplified for now)
|
|
254
|
+
const graphScore = 0.5; // Placeholder for now, can enhance later
|
|
255
|
+
// Combined score
|
|
256
|
+
const relevanceScore = (weights.semantic ?? 1.0) * semanticScore +
|
|
257
|
+
(weights.temporal ?? 0.5) * temporalScore +
|
|
258
|
+
(weights.graph ?? 0.3) * graphScore;
|
|
259
|
+
return {
|
|
260
|
+
id: result.id,
|
|
261
|
+
content: result.data || result.content || '',
|
|
262
|
+
role: result.metadata.role,
|
|
263
|
+
metadata: result.metadata,
|
|
264
|
+
embedding: result.embedding,
|
|
265
|
+
createdAt: result.metadata.timestamp || now,
|
|
266
|
+
updatedAt: result.metadata.timestamp || now,
|
|
267
|
+
relevanceScore,
|
|
268
|
+
semanticScore,
|
|
269
|
+
temporalScore,
|
|
270
|
+
graphScore
|
|
271
|
+
};
|
|
272
|
+
})
|
|
273
|
+
.filter((msg) => msg.relevanceScore >= relevanceThreshold)
|
|
274
|
+
.sort((a, b) => b.relevanceScore - a.relevanceScore);
|
|
275
|
+
// Deduplicate via clustering if requested
|
|
276
|
+
let finalMessages = rankedMessages;
|
|
277
|
+
if (deduplicateClusters && rankedMessages.length > 5 && this.brain.neural) {
|
|
278
|
+
// Use neural clustering to remove duplicates (REAL)
|
|
279
|
+
try {
|
|
280
|
+
const clusters = await this.brain.neural().clusters({
|
|
281
|
+
maxClusters: Math.ceil(rankedMessages.length / 3),
|
|
282
|
+
threshold: 0.85
|
|
283
|
+
});
|
|
284
|
+
// Keep highest scoring message from each cluster
|
|
285
|
+
const kept = new Set();
|
|
286
|
+
for (const cluster of clusters) {
|
|
287
|
+
const clusterMessages = rankedMessages.filter(msg => cluster.members?.includes(msg.id));
|
|
288
|
+
if (clusterMessages.length > 0) {
|
|
289
|
+
const best = clusterMessages.reduce((a, b) => a.relevanceScore > b.relevanceScore ? a : b);
|
|
290
|
+
kept.add(best.id);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
finalMessages = rankedMessages.filter(msg => kept.has(msg.id));
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
// Clustering failed, use all messages
|
|
297
|
+
console.warn('Clustering failed:', error);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Limit by token budget
|
|
301
|
+
let totalTokens = 0;
|
|
302
|
+
const messagesWithinBudget = [];
|
|
303
|
+
for (const msg of finalMessages) {
|
|
304
|
+
const tokens = msg.metadata.tokensUsed || Math.ceil(msg.content.length / 4);
|
|
305
|
+
if (totalTokens + tokens <= maxTokens) {
|
|
306
|
+
messagesWithinBudget.push(msg);
|
|
307
|
+
totalTokens += tokens;
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// Get artifacts if requested (REAL VFS)
|
|
314
|
+
let artifacts = [];
|
|
315
|
+
if (includeArtifacts && this._vfs) {
|
|
316
|
+
const artifactIds = new Set(messagesWithinBudget.flatMap(msg => msg.metadata.artifacts || []));
|
|
317
|
+
for (const artifactId of artifactIds) {
|
|
318
|
+
try {
|
|
319
|
+
const entity = await this.brain.get(artifactId);
|
|
320
|
+
if (entity) {
|
|
321
|
+
artifacts.push({
|
|
322
|
+
id: artifactId,
|
|
323
|
+
path: entity.metadata?.path || artifactId,
|
|
324
|
+
summary: entity.metadata?.description || undefined
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
// Artifact not found, skip
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
// Get similar conversations if requested
|
|
335
|
+
let similarConversations = [];
|
|
336
|
+
if (includeSimilarConversations && conversationId && this.brain.neural) {
|
|
337
|
+
// Use neural neighbors (REAL)
|
|
338
|
+
try {
|
|
339
|
+
const neighborsResult = await this.brain.neural().neighbors(conversationId, {
|
|
340
|
+
limit: 5,
|
|
341
|
+
minSimilarity: 0.7
|
|
342
|
+
});
|
|
343
|
+
similarConversations = neighborsResult.neighbors.map((neighbor) => ({
|
|
344
|
+
id: neighbor.id,
|
|
345
|
+
title: neighbor.metadata?.title,
|
|
346
|
+
summary: neighbor.metadata?.summary,
|
|
347
|
+
relevance: neighbor.score,
|
|
348
|
+
messageCount: neighbor.metadata?.messageCount || 0
|
|
349
|
+
}));
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
// Neighbors failed, skip
|
|
353
|
+
console.warn('Similar conversation search failed:', error);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const queryTime = Date.now() - startTime;
|
|
357
|
+
return {
|
|
358
|
+
messages: messagesWithinBudget.slice(0, limit),
|
|
359
|
+
artifacts,
|
|
360
|
+
similarConversations,
|
|
361
|
+
totalTokens,
|
|
362
|
+
metadata: {
|
|
363
|
+
queryTime,
|
|
364
|
+
messagesConsidered: results.length,
|
|
365
|
+
conversationsSearched: new Set(results.map((r) => r.metadata.conversationId)).size
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Search messages semantically
|
|
371
|
+
*
|
|
372
|
+
* Uses: brain.find() with semantic search
|
|
373
|
+
* Real implementation - vector similarity search
|
|
374
|
+
*
|
|
375
|
+
* @param options Search options
|
|
376
|
+
* @returns Search results with scores
|
|
377
|
+
*/
|
|
378
|
+
async searchMessages(options) {
|
|
379
|
+
if (!this.initialized) {
|
|
380
|
+
await this.init();
|
|
381
|
+
}
|
|
382
|
+
const { query, limit = 10, role, conversationId, sessionId, timeRange, includeMetadata = true, includeContent = true } = options;
|
|
383
|
+
// Build filter
|
|
384
|
+
const whereFilter = {};
|
|
385
|
+
if (role) {
|
|
386
|
+
whereFilter.role = Array.isArray(role) ? { $in: role } : role;
|
|
387
|
+
}
|
|
388
|
+
if (conversationId) {
|
|
389
|
+
whereFilter.conversationId = conversationId;
|
|
390
|
+
}
|
|
391
|
+
if (sessionId) {
|
|
392
|
+
whereFilter.sessionId = sessionId;
|
|
393
|
+
}
|
|
394
|
+
if (timeRange) {
|
|
395
|
+
if (timeRange.start) {
|
|
396
|
+
whereFilter.timestamp = { $gte: timeRange.start };
|
|
397
|
+
}
|
|
398
|
+
if (timeRange.end) {
|
|
399
|
+
whereFilter.timestamp = { ...whereFilter.timestamp, $lte: timeRange.end };
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// Search with Triple Intelligence (REAL)
|
|
403
|
+
const results = await this.brain.find({
|
|
404
|
+
query: query,
|
|
405
|
+
where: whereFilter,
|
|
406
|
+
limit
|
|
407
|
+
});
|
|
408
|
+
// Format results
|
|
409
|
+
return results.map((result) => {
|
|
410
|
+
const message = {
|
|
411
|
+
id: result.id,
|
|
412
|
+
content: includeContent ? (result.data || result.content || '') : '',
|
|
413
|
+
role: result.metadata.role,
|
|
414
|
+
metadata: includeMetadata ? result.metadata : {},
|
|
415
|
+
embedding: result.embedding,
|
|
416
|
+
createdAt: result.metadata.timestamp || Date.now(),
|
|
417
|
+
updatedAt: result.metadata.timestamp || Date.now()
|
|
418
|
+
};
|
|
419
|
+
// Create snippet
|
|
420
|
+
const content = result.data || result.content || '';
|
|
421
|
+
const snippet = content.length > 150 ? content.substring(0, 147) + '...' : content;
|
|
422
|
+
return {
|
|
423
|
+
message,
|
|
424
|
+
score: result.score || 0,
|
|
425
|
+
conversationId: result.metadata.conversationId,
|
|
426
|
+
snippet: includeContent ? snippet : undefined
|
|
427
|
+
};
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Find similar conversations using Neural API
|
|
432
|
+
*
|
|
433
|
+
* Uses: brain.neural.neighbors()
|
|
434
|
+
* Real implementation - semantic similarity with embeddings
|
|
435
|
+
*
|
|
436
|
+
* @param conversationId Conversation ID to find similar to
|
|
437
|
+
* @param limit Maximum number of similar conversations
|
|
438
|
+
* @param threshold Minimum similarity threshold
|
|
439
|
+
* @returns Similar conversations with relevance scores
|
|
440
|
+
*/
|
|
441
|
+
async findSimilarConversations(conversationId, limit = 5, threshold = 0.7) {
|
|
442
|
+
if (!this.initialized) {
|
|
443
|
+
await this.init();
|
|
444
|
+
}
|
|
445
|
+
if (!this.brain.neural) {
|
|
446
|
+
throw new Error('Neural API not available');
|
|
447
|
+
}
|
|
448
|
+
// Use neural neighbors (REAL)
|
|
449
|
+
const neighborsResult = await this.brain.neural().neighbors(conversationId, {
|
|
450
|
+
limit: limit,
|
|
451
|
+
minSimilarity: threshold
|
|
452
|
+
});
|
|
453
|
+
return neighborsResult.neighbors.map((neighbor) => ({
|
|
454
|
+
id: neighbor.id,
|
|
455
|
+
relevance: neighbor.score,
|
|
456
|
+
metadata: neighbor.metadata
|
|
457
|
+
}));
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Get conversation themes via clustering
|
|
461
|
+
*
|
|
462
|
+
* Uses: brain.neural.clusters()
|
|
463
|
+
* Real implementation - semantic clustering
|
|
464
|
+
*
|
|
465
|
+
* @param conversationId Conversation ID
|
|
466
|
+
* @returns Discovered themes
|
|
467
|
+
*/
|
|
468
|
+
async getConversationThemes(conversationId) {
|
|
469
|
+
if (!this.initialized) {
|
|
470
|
+
await this.init();
|
|
471
|
+
}
|
|
472
|
+
if (!this.brain.neural) {
|
|
473
|
+
throw new Error('Neural API not available');
|
|
474
|
+
}
|
|
475
|
+
// Get messages for conversation
|
|
476
|
+
const results = await this.brain.find({
|
|
477
|
+
where: { conversationId },
|
|
478
|
+
limit: 1000
|
|
479
|
+
});
|
|
480
|
+
if (results.length === 0) {
|
|
481
|
+
return [];
|
|
482
|
+
}
|
|
483
|
+
// Cluster messages (REAL)
|
|
484
|
+
const clusters = await this.brain.neural().clusters({
|
|
485
|
+
maxClusters: Math.min(5, Math.ceil(results.length / 5)),
|
|
486
|
+
threshold: 0.75
|
|
487
|
+
});
|
|
488
|
+
// Convert to themes
|
|
489
|
+
return clusters.map((cluster, index) => ({
|
|
490
|
+
id: `theme_${index}`,
|
|
491
|
+
label: cluster.label || `Theme ${index + 1}`,
|
|
492
|
+
messages: cluster.members || [],
|
|
493
|
+
centroid: cluster.centroid || [],
|
|
494
|
+
coherence: cluster.coherence || 0
|
|
495
|
+
}));
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Save an artifact (code, file, etc.) to VFS
|
|
499
|
+
*
|
|
500
|
+
* Uses: brain.vfs()
|
|
501
|
+
* Real implementation - stores in virtual filesystem
|
|
502
|
+
*
|
|
503
|
+
* @param path VFS path
|
|
504
|
+
* @param content File content
|
|
505
|
+
* @param options Artifact options
|
|
506
|
+
* @returns Artifact entity ID
|
|
507
|
+
*/
|
|
508
|
+
async saveArtifact(path, content, options) {
|
|
509
|
+
if (!this.initialized) {
|
|
510
|
+
await this.init();
|
|
511
|
+
}
|
|
512
|
+
if (!this._vfs) {
|
|
513
|
+
throw new Error('VFS not available');
|
|
514
|
+
}
|
|
515
|
+
// Write file to VFS (REAL)
|
|
516
|
+
await this._vfs.writeFile(path, content);
|
|
517
|
+
// Get the file entity
|
|
518
|
+
const entity = await this._vfs.getEntity(path);
|
|
519
|
+
// Link to conversation message if provided
|
|
520
|
+
if (options.messageId) {
|
|
521
|
+
await this.brain.relate({
|
|
522
|
+
from: options.messageId,
|
|
523
|
+
to: entity.id,
|
|
524
|
+
type: VerbType.Creates,
|
|
525
|
+
metadata: {
|
|
526
|
+
conversationId: options.conversationId,
|
|
527
|
+
artifactType: options.type || 'other'
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
return entity.id;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Get conversation statistics
|
|
535
|
+
*
|
|
536
|
+
* Uses: brain.find() with aggregations
|
|
537
|
+
* Real implementation - queries and aggregates data
|
|
538
|
+
*
|
|
539
|
+
* @param conversationId Optional conversation ID to filter
|
|
540
|
+
* @returns Conversation statistics
|
|
541
|
+
*/
|
|
542
|
+
async getConversationStats(conversationId) {
|
|
543
|
+
if (!this.initialized) {
|
|
544
|
+
await this.init();
|
|
545
|
+
}
|
|
546
|
+
// Query messages
|
|
547
|
+
const whereFilter = conversationId ? { conversationId } : {};
|
|
548
|
+
const results = await this.brain.find({
|
|
549
|
+
where: whereFilter,
|
|
550
|
+
limit: 100000 // Large limit for stats
|
|
551
|
+
});
|
|
552
|
+
// Calculate statistics (REAL aggregation)
|
|
553
|
+
const conversations = new Set(results.map((r) => r.metadata.conversationId));
|
|
554
|
+
const totalMessages = results.length;
|
|
555
|
+
const totalTokens = results.reduce((sum, r) => sum + (r.metadata.tokensUsed || 0), 0);
|
|
556
|
+
const timestamps = results.map((r) => r.metadata.timestamp || Date.now());
|
|
557
|
+
const oldestMessage = Math.min(...timestamps);
|
|
558
|
+
const newestMessage = Math.max(...timestamps);
|
|
559
|
+
// Count by phase
|
|
560
|
+
const phases = {};
|
|
561
|
+
const roles = {};
|
|
562
|
+
for (const result of results) {
|
|
563
|
+
const phase = result.entity.metadata.problemSolvingPhase;
|
|
564
|
+
const role = result.entity.metadata.role;
|
|
565
|
+
if (phase) {
|
|
566
|
+
phases[phase] = (phases[phase] || 0) + 1;
|
|
567
|
+
}
|
|
568
|
+
if (role) {
|
|
569
|
+
roles[role] = (roles[role] || 0) + 1;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
totalConversations: conversations.size,
|
|
574
|
+
totalMessages,
|
|
575
|
+
totalTokens,
|
|
576
|
+
averageMessagesPerConversation: totalMessages / Math.max(1, conversations.size),
|
|
577
|
+
averageTokensPerMessage: totalTokens / Math.max(1, totalMessages),
|
|
578
|
+
oldestMessage,
|
|
579
|
+
newestMessage,
|
|
580
|
+
phases: phases,
|
|
581
|
+
roles: roles
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Delete a message
|
|
586
|
+
*
|
|
587
|
+
* Uses: brain.deleteNoun()
|
|
588
|
+
* Real implementation - removes from graph
|
|
589
|
+
*
|
|
590
|
+
* @param messageId Message ID to delete
|
|
591
|
+
*/
|
|
592
|
+
async deleteMessage(messageId) {
|
|
593
|
+
if (!this.initialized) {
|
|
594
|
+
await this.init();
|
|
595
|
+
}
|
|
596
|
+
await this.brain.delete(messageId);
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Export conversation to JSON
|
|
600
|
+
*
|
|
601
|
+
* Uses: getConversationThread()
|
|
602
|
+
* Real implementation - serializes conversation
|
|
603
|
+
*
|
|
604
|
+
* @param conversationId Conversation ID
|
|
605
|
+
* @returns JSON-serializable conversation object
|
|
606
|
+
*/
|
|
607
|
+
async exportConversation(conversationId) {
|
|
608
|
+
if (!this.initialized) {
|
|
609
|
+
await this.init();
|
|
610
|
+
}
|
|
611
|
+
const thread = await this.getConversationThread(conversationId, {
|
|
612
|
+
includeArtifacts: true
|
|
613
|
+
});
|
|
614
|
+
return {
|
|
615
|
+
version: '1.0',
|
|
616
|
+
exportedAt: Date.now(),
|
|
617
|
+
conversation: thread
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Import conversation from JSON
|
|
622
|
+
*
|
|
623
|
+
* Uses: saveMessage() and linkMessages()
|
|
624
|
+
* Real implementation - recreates conversation
|
|
625
|
+
*
|
|
626
|
+
* @param data Exported conversation data
|
|
627
|
+
* @returns New conversation ID
|
|
628
|
+
*/
|
|
629
|
+
async importConversation(data) {
|
|
630
|
+
if (!this.initialized) {
|
|
631
|
+
await this.init();
|
|
632
|
+
}
|
|
633
|
+
const newConversationId = `conv_${uuidv4()}`;
|
|
634
|
+
const conversation = data.conversation;
|
|
635
|
+
if (!conversation || !conversation.messages) {
|
|
636
|
+
throw new Error('Invalid conversation data');
|
|
637
|
+
}
|
|
638
|
+
// Import messages in order
|
|
639
|
+
const messageIdMap = new Map();
|
|
640
|
+
for (let i = 0; i < conversation.messages.length; i++) {
|
|
641
|
+
const msg = conversation.messages[i];
|
|
642
|
+
const prevMessageId = i > 0 ? messageIdMap.get(conversation.messages[i - 1].id) : undefined;
|
|
643
|
+
const newMessageId = await this.saveMessage(msg.content, msg.role, {
|
|
644
|
+
conversationId: newConversationId,
|
|
645
|
+
sessionId: conversation.metadata.sessionId,
|
|
646
|
+
phase: msg.metadata.problemSolvingPhase,
|
|
647
|
+
confidence: msg.metadata.confidence,
|
|
648
|
+
tags: msg.metadata.tags,
|
|
649
|
+
linkToPrevious: prevMessageId,
|
|
650
|
+
metadata: msg.metadata
|
|
651
|
+
});
|
|
652
|
+
messageIdMap.set(msg.id, newMessageId);
|
|
653
|
+
}
|
|
654
|
+
return newConversationId;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Create a ConversationManager instance
|
|
659
|
+
*
|
|
660
|
+
* @param brain Brainy instance
|
|
661
|
+
* @returns ConversationManager instance
|
|
662
|
+
*/
|
|
663
|
+
export function createConversationManager(brain) {
|
|
664
|
+
return new ConversationManager(brain);
|
|
665
|
+
}
|
|
666
|
+
//# sourceMappingURL=conversationManager.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation Module - Infinite Agent Memory
|
|
3
|
+
*
|
|
4
|
+
* Provides conversation and context management for AI agents
|
|
5
|
+
* Built on Brainy's existing infrastructure
|
|
6
|
+
*/
|
|
7
|
+
export { ConversationManager, createConversationManager } from './conversationManager.js';
|
|
8
|
+
export type { MessageRole, ProblemSolvingPhase, ConversationMessage, ConversationMessageMetadata, ConversationThread, ConversationThreadMetadata, ConversationContext, RankedMessage, SaveMessageOptions, ContextRetrievalOptions, ConversationSearchOptions, ConversationSearchResult, ConversationTheme, ArtifactOptions, ConversationStats, CompactionOptions, CompactionResult } from './types.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation Module - Infinite Agent Memory
|
|
3
|
+
*
|
|
4
|
+
* Provides conversation and context management for AI agents
|
|
5
|
+
* Built on Brainy's existing infrastructure
|
|
6
|
+
*/
|
|
7
|
+
export { ConversationManager, createConversationManager } from './conversationManager.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|