@sesamespace/hivemind 0.8.10 → 0.8.11
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/MEMORY-ENHANCEMENT-PLAN.md +211 -0
- package/package.json +4 -2
- package/src/memory/dashboard-integration.ts +295 -0
- package/src/memory/index.ts +187 -0
- package/src/memory/performance-test.ts +208 -0
- package/src/memory/processors/agent-sync.ts +312 -0
- package/src/memory/processors/command-learner.ts +298 -0
- package/src/memory/processors/memory-api-client.ts +105 -0
- package/src/memory/processors/message-flow-integration.ts +168 -0
- package/src/memory/processors/research-digester.ts +204 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Research Digester - Extracts and indexes key information from web pages and documents
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { BackgroundProcessor } from './background-processor';
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
import * as crypto from 'crypto';
|
|
8
|
+
|
|
9
|
+
export interface ResearchEntry {
|
|
10
|
+
id: string;
|
|
11
|
+
url?: string;
|
|
12
|
+
title: string;
|
|
13
|
+
summary: string;
|
|
14
|
+
keyPoints: string[];
|
|
15
|
+
relatedTopics: string[];
|
|
16
|
+
timestamp: Date;
|
|
17
|
+
sourceType: 'web' | 'pdf' | 'markdown' | 'other';
|
|
18
|
+
content?: string; // Full content for reference
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class ResearchDigester extends BackgroundProcessor {
|
|
22
|
+
private research: Map<string, ResearchEntry> = new Map();
|
|
23
|
+
private topicIndex: Map<string, Set<string>> = new Map(); // topic -> research IDs
|
|
24
|
+
private recentQueue: string[] = []; // URLs/content to process
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
super('research-digester', 30000); // Process every 30 seconds
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async process(): Promise<void> {
|
|
31
|
+
// Process any queued research
|
|
32
|
+
while (this.recentQueue.length > 0) {
|
|
33
|
+
const item = this.recentQueue.shift();
|
|
34
|
+
if (item) {
|
|
35
|
+
await this.digestContent(item);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Emit current research state
|
|
40
|
+
this.emit('research-updated', {
|
|
41
|
+
totalEntries: this.research.size,
|
|
42
|
+
topics: Array.from(this.topicIndex.keys())
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Queue content for digestion
|
|
48
|
+
*/
|
|
49
|
+
async addContent(content: string, metadata: {
|
|
50
|
+
url?: string;
|
|
51
|
+
title?: string;
|
|
52
|
+
sourceType?: ResearchEntry['sourceType'];
|
|
53
|
+
} = {}): Promise<void> {
|
|
54
|
+
this.recentQueue.push(JSON.stringify({ content, metadata }));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private async digestContent(item: string): Promise<void> {
|
|
58
|
+
try {
|
|
59
|
+
const { content, metadata } = JSON.parse(item);
|
|
60
|
+
|
|
61
|
+
// Extract key information
|
|
62
|
+
const entry: ResearchEntry = {
|
|
63
|
+
id: crypto.randomBytes(8).toString('hex'),
|
|
64
|
+
url: metadata.url,
|
|
65
|
+
title: metadata.title || this.extractTitle(content),
|
|
66
|
+
summary: this.extractSummary(content),
|
|
67
|
+
keyPoints: this.extractKeyPoints(content),
|
|
68
|
+
relatedTopics: this.extractTopics(content),
|
|
69
|
+
timestamp: new Date(),
|
|
70
|
+
sourceType: metadata.sourceType || 'other',
|
|
71
|
+
content: content.substring(0, 5000) // Store first 5k chars
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Store the entry
|
|
75
|
+
this.research.set(entry.id, entry);
|
|
76
|
+
|
|
77
|
+
// Index by topics
|
|
78
|
+
for (const topic of entry.relatedTopics) {
|
|
79
|
+
if (!this.topicIndex.has(topic)) {
|
|
80
|
+
this.topicIndex.set(topic, new Set());
|
|
81
|
+
}
|
|
82
|
+
this.topicIndex.get(topic)!.add(entry.id);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.emit('research-digested', entry);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
this.emit('error', { error, item });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private extractTitle(content: string): string {
|
|
92
|
+
// Look for markdown headers or first significant line
|
|
93
|
+
const lines = content.split('\n');
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
const trimmed = line.trim();
|
|
96
|
+
if (trimmed.startsWith('#')) {
|
|
97
|
+
return trimmed.replace(/^#+\s*/, '');
|
|
98
|
+
}
|
|
99
|
+
if (trimmed.length > 10 && trimmed.length < 100) {
|
|
100
|
+
return trimmed;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return 'Untitled Research';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private extractSummary(content: string): string {
|
|
107
|
+
// Get first meaningful paragraph
|
|
108
|
+
const paragraphs = content.split(/\n\n+/);
|
|
109
|
+
for (const para of paragraphs) {
|
|
110
|
+
const trimmed = para.trim();
|
|
111
|
+
if (trimmed.length > 50 && !trimmed.startsWith('#')) {
|
|
112
|
+
return trimmed.substring(0, 200) + '...';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return content.substring(0, 200) + '...';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private extractKeyPoints(content: string): string[] {
|
|
119
|
+
const keyPoints: string[] = [];
|
|
120
|
+
const lines = content.split('\n');
|
|
121
|
+
|
|
122
|
+
for (const line of lines) {
|
|
123
|
+
const trimmed = line.trim();
|
|
124
|
+
// Look for bullet points, numbered lists, or key phrases
|
|
125
|
+
if (trimmed.match(/^[-*•]\s+/) || trimmed.match(/^\d+\.\s+/)) {
|
|
126
|
+
keyPoints.push(trimmed.replace(/^[-*•\d.]\s+/, ''));
|
|
127
|
+
}
|
|
128
|
+
// Look for sentences with key indicator words
|
|
129
|
+
if (trimmed.match(/\b(important|key|critical|essential|must|should|note)\b/i)) {
|
|
130
|
+
keyPoints.push(trimmed);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Limit to top 10 key points
|
|
135
|
+
return keyPoints.slice(0, 10);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private extractTopics(content: string): string[] {
|
|
139
|
+
const topics = new Set<string>();
|
|
140
|
+
|
|
141
|
+
// Common technical topics
|
|
142
|
+
const topicPatterns = [
|
|
143
|
+
/\b(typescript|javascript|python|rust|go)\b/gi,
|
|
144
|
+
/\b(react|vue|angular|svelte)\b/gi,
|
|
145
|
+
/\b(docker|kubernetes|k8s)\b/gi,
|
|
146
|
+
/\b(api|rest|graphql|grpc)\b/gi,
|
|
147
|
+
/\b(database|sql|nosql|postgres|mysql|mongodb)\b/gi,
|
|
148
|
+
/\b(ai|ml|machine learning|llm|gpt)\b/gi,
|
|
149
|
+
/\b(memory|context|embedding|vector)\b/gi,
|
|
150
|
+
/\b(git|github|version control)\b/gi,
|
|
151
|
+
/\b(testing|ci|cd|deployment)\b/gi,
|
|
152
|
+
/\b(security|auth|authentication|authorization)\b/gi
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
for (const pattern of topicPatterns) {
|
|
156
|
+
const matches = content.match(pattern);
|
|
157
|
+
if (matches) {
|
|
158
|
+
matches.forEach(match => topics.add(match.toLowerCase()));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return Array.from(topics).slice(0, 5); // Top 5 topics
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Search research by topic or content
|
|
167
|
+
*/
|
|
168
|
+
async search(query: string): Promise<ResearchEntry[]> {
|
|
169
|
+
const results: ResearchEntry[] = [];
|
|
170
|
+
const queryLower = query.toLowerCase();
|
|
171
|
+
|
|
172
|
+
// Search by topic
|
|
173
|
+
if (this.topicIndex.has(queryLower)) {
|
|
174
|
+
const ids = this.topicIndex.get(queryLower)!;
|
|
175
|
+
for (const id of ids) {
|
|
176
|
+
const entry = this.research.get(id);
|
|
177
|
+
if (entry) results.push(entry);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Search in content
|
|
182
|
+
for (const entry of this.research.values()) {
|
|
183
|
+
if (entry.title.toLowerCase().includes(queryLower) ||
|
|
184
|
+
entry.summary.toLowerCase().includes(queryLower) ||
|
|
185
|
+
entry.keyPoints.some(kp => kp.toLowerCase().includes(queryLower))) {
|
|
186
|
+
if (!results.includes(entry)) {
|
|
187
|
+
results.push(entry);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Sort by recency
|
|
193
|
+
return results.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get recent research entries
|
|
198
|
+
*/
|
|
199
|
+
async getRecent(limit: number = 10): Promise<ResearchEntry[]> {
|
|
200
|
+
return Array.from(this.research.values())
|
|
201
|
+
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
|
|
202
|
+
.slice(0, limit);
|
|
203
|
+
}
|
|
204
|
+
}
|