@sesamespace/hivemind 0.8.9 → 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/README.md +2 -0
- package/dist/{chunk-AC6ETJUC.js → chunk-A5LMEJIN.js} +2 -2
- package/dist/{chunk-DDFWLDFN.js → chunk-F6EMICWP.js} +3 -3
- package/dist/{chunk-CMPDA2UR.js → chunk-OG6GPQDK.js} +12 -1
- package/dist/chunk-OG6GPQDK.js.map +1 -0
- package/dist/{chunk-5LQLPBE2.js → chunk-OVRYYS5I.js} +2 -2
- package/dist/{chunk-JLLGBVBX.js → chunk-SMUG64N7.js} +2 -2
- package/dist/{chunk-MOCTUHCJ.js → chunk-XBJGLZ7V.js} +2 -2
- package/dist/commands/fleet.js +3 -3
- package/dist/commands/init.js +3 -3
- package/dist/commands/start.js +3 -3
- package/dist/commands/watchdog.js +3 -3
- package/dist/index.js +2 -2
- package/dist/main.js +6 -6
- package/dist/start.js +1 -1
- 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
- package/test-caitlin-access.md +11 -0
- package/dist/chunk-CMPDA2UR.js.map +0 -1
- /package/dist/{chunk-AC6ETJUC.js.map → chunk-A5LMEJIN.js.map} +0 -0
- /package/dist/{chunk-DDFWLDFN.js.map → chunk-F6EMICWP.js.map} +0 -0
- /package/dist/{chunk-5LQLPBE2.js.map → chunk-OVRYYS5I.js.map} +0 -0
- /package/dist/{chunk-JLLGBVBX.js.map → chunk-SMUG64N7.js.map} +0 -0
- /package/dist/{chunk-MOCTUHCJ.js.map → chunk-XBJGLZ7V.js.map} +0 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Learner - Tracks successful command patterns and builds a knowledge base
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { BackgroundProcessor } from './background-processor';
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
|
|
8
|
+
export interface CommandPattern {
|
|
9
|
+
pattern: string;
|
|
10
|
+
description: string;
|
|
11
|
+
examples: CommandExample[];
|
|
12
|
+
successRate: number;
|
|
13
|
+
category: string;
|
|
14
|
+
relatedCommands: string[];
|
|
15
|
+
lastUsed: Date;
|
|
16
|
+
usageCount: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CommandExample {
|
|
20
|
+
command: string;
|
|
21
|
+
context: string;
|
|
22
|
+
output?: string;
|
|
23
|
+
success: boolean;
|
|
24
|
+
timestamp: Date;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class CommandLearner extends BackgroundProcessor {
|
|
28
|
+
private patterns: Map<string, CommandPattern> = new Map();
|
|
29
|
+
private recentCommands: CommandExample[] = [];
|
|
30
|
+
private categoryIndex: Map<string, Set<string>> = new Map();
|
|
31
|
+
|
|
32
|
+
constructor() {
|
|
33
|
+
super('command-learner', 60000); // Process every minute
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async process(): Promise<void> {
|
|
37
|
+
// Analyze recent commands to extract patterns
|
|
38
|
+
if (this.recentCommands.length >= 5) {
|
|
39
|
+
await this.extractPatterns();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Update success rates
|
|
43
|
+
this.updateSuccessRates();
|
|
44
|
+
|
|
45
|
+
// Emit current state
|
|
46
|
+
this.emit('patterns-updated', {
|
|
47
|
+
totalPatterns: this.patterns.size,
|
|
48
|
+
categories: Array.from(this.categoryIndex.keys()),
|
|
49
|
+
recentCommands: this.recentCommands.length
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Track a command execution
|
|
55
|
+
*/
|
|
56
|
+
async trackCommand(command: string, context: string, output?: string, success: boolean = true): Promise<void> {
|
|
57
|
+
const example: CommandExample = {
|
|
58
|
+
command,
|
|
59
|
+
context,
|
|
60
|
+
output: output?.substring(0, 1000), // Limit output size
|
|
61
|
+
success,
|
|
62
|
+
timestamp: new Date()
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
this.recentCommands.push(example);
|
|
66
|
+
|
|
67
|
+
// Keep only last 100 commands
|
|
68
|
+
if (this.recentCommands.length > 100) {
|
|
69
|
+
this.recentCommands.shift();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check if this matches an existing pattern
|
|
73
|
+
await this.matchToPattern(example);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private async extractPatterns(): Promise<void> {
|
|
77
|
+
// Group commands by similarity
|
|
78
|
+
const groups = this.groupSimilarCommands(this.recentCommands.slice(-20));
|
|
79
|
+
|
|
80
|
+
for (const group of groups) {
|
|
81
|
+
if (group.length >= 2) {
|
|
82
|
+
const pattern = this.derivePattern(group);
|
|
83
|
+
if (pattern) {
|
|
84
|
+
this.patterns.set(pattern.pattern, pattern);
|
|
85
|
+
|
|
86
|
+
// Index by category
|
|
87
|
+
if (!this.categoryIndex.has(pattern.category)) {
|
|
88
|
+
this.categoryIndex.set(pattern.category, new Set());
|
|
89
|
+
}
|
|
90
|
+
this.categoryIndex.get(pattern.category)!.add(pattern.pattern);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private groupSimilarCommands(commands: CommandExample[]): CommandExample[][] {
|
|
97
|
+
const groups: CommandExample[][] = [];
|
|
98
|
+
const used = new Set<number>();
|
|
99
|
+
|
|
100
|
+
for (let i = 0; i < commands.length; i++) {
|
|
101
|
+
if (used.has(i)) continue;
|
|
102
|
+
|
|
103
|
+
const group = [commands[i]];
|
|
104
|
+
used.add(i);
|
|
105
|
+
|
|
106
|
+
for (let j = i + 1; j < commands.length; j++) {
|
|
107
|
+
if (used.has(j)) continue;
|
|
108
|
+
|
|
109
|
+
if (this.areSimilar(commands[i].command, commands[j].command)) {
|
|
110
|
+
group.push(commands[j]);
|
|
111
|
+
used.add(j);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (group.length > 1) {
|
|
116
|
+
groups.push(group);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return groups;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private areSimilar(cmd1: string, cmd2: string): boolean {
|
|
124
|
+
// Tokenize commands
|
|
125
|
+
const tokens1 = cmd1.split(/\s+/);
|
|
126
|
+
const tokens2 = cmd2.split(/\s+/);
|
|
127
|
+
|
|
128
|
+
// Check if base command is the same
|
|
129
|
+
if (tokens1[0] !== tokens2[0]) return false;
|
|
130
|
+
|
|
131
|
+
// Check structural similarity
|
|
132
|
+
if (Math.abs(tokens1.length - tokens2.length) > 2) return false;
|
|
133
|
+
|
|
134
|
+
// Count matching tokens (excluding file paths and specific values)
|
|
135
|
+
let matches = 0;
|
|
136
|
+
for (let i = 0; i < Math.min(tokens1.length, tokens2.length); i++) {
|
|
137
|
+
if (tokens1[i] === tokens2[i] ||
|
|
138
|
+
(this.isVariable(tokens1[i]) && this.isVariable(tokens2[i]))) {
|
|
139
|
+
matches++;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return matches / Math.max(tokens1.length, tokens2.length) > 0.6;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private isVariable(token: string): boolean {
|
|
147
|
+
// Detect tokens that are likely variables (paths, URLs, IDs, etc.)
|
|
148
|
+
return token.includes('/') ||
|
|
149
|
+
token.includes('.') ||
|
|
150
|
+
token.match(/^[a-f0-9]{8,}$/i) !== null ||
|
|
151
|
+
token.match(/^\d+$/) !== null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private derivePattern(examples: CommandExample[]): CommandPattern | null {
|
|
155
|
+
if (examples.length === 0) return null;
|
|
156
|
+
|
|
157
|
+
const baseCmd = examples[0].command.split(/\s+/)[0];
|
|
158
|
+
const category = this.categorizeCommand(baseCmd);
|
|
159
|
+
|
|
160
|
+
// Find common structure
|
|
161
|
+
const tokenizedExamples = examples.map(e => e.command.split(/\s+/));
|
|
162
|
+
const patternTokens: string[] = [];
|
|
163
|
+
|
|
164
|
+
for (let i = 0; i < tokenizedExamples[0].length; i++) {
|
|
165
|
+
const tokensAtPosition = tokenizedExamples.map(t => t[i]).filter(Boolean);
|
|
166
|
+
|
|
167
|
+
if (tokensAtPosition.every(t => t === tokensAtPosition[0])) {
|
|
168
|
+
patternTokens.push(tokensAtPosition[0]);
|
|
169
|
+
} else if (tokensAtPosition.every(t => this.isVariable(t))) {
|
|
170
|
+
patternTokens.push('<variable>');
|
|
171
|
+
} else {
|
|
172
|
+
patternTokens.push('<option>');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const pattern = patternTokens.join(' ');
|
|
177
|
+
const successCount = examples.filter(e => e.success).length;
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
pattern,
|
|
181
|
+
description: this.generateDescription(pattern, examples),
|
|
182
|
+
examples: examples.slice(0, 5), // Keep top 5 examples
|
|
183
|
+
successRate: successCount / examples.length,
|
|
184
|
+
category,
|
|
185
|
+
relatedCommands: this.findRelatedCommands(baseCmd),
|
|
186
|
+
lastUsed: new Date(Math.max(...examples.map(e => e.timestamp.getTime()))),
|
|
187
|
+
usageCount: examples.length
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private categorizeCommand(baseCmd: string): string {
|
|
192
|
+
const categories: Record<string, string[]> = {
|
|
193
|
+
'git': ['git', 'gh'],
|
|
194
|
+
'file': ['ls', 'cat', 'cp', 'mv', 'rm', 'find', 'grep'],
|
|
195
|
+
'process': ['ps', 'kill', 'top', 'htop'],
|
|
196
|
+
'network': ['curl', 'wget', 'ping', 'netstat'],
|
|
197
|
+
'package': ['npm', 'yarn', 'pip', 'brew'],
|
|
198
|
+
'docker': ['docker', 'docker-compose'],
|
|
199
|
+
'system': ['df', 'du', 'free', 'uname']
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
for (const [category, commands] of Object.entries(categories)) {
|
|
203
|
+
if (commands.includes(baseCmd)) {
|
|
204
|
+
return category;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return 'other';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private generateDescription(pattern: string, examples: CommandExample[]): string {
|
|
212
|
+
const baseCmd = pattern.split(' ')[0];
|
|
213
|
+
const contexts = [...new Set(examples.map(e => e.context))];
|
|
214
|
+
|
|
215
|
+
return `${baseCmd} command used for ${contexts.join(', ')}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private findRelatedCommands(baseCmd: string): string[] {
|
|
219
|
+
const related: Record<string, string[]> = {
|
|
220
|
+
'git': ['git status', 'git log', 'git diff'],
|
|
221
|
+
'npm': ['npm install', 'npm run', 'npm test'],
|
|
222
|
+
'docker': ['docker ps', 'docker logs', 'docker exec'],
|
|
223
|
+
'curl': ['wget', 'httpie', 'fetch']
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return related[baseCmd] || [];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private async matchToPattern(example: CommandExample): Promise<void> {
|
|
230
|
+
for (const [patternStr, pattern] of this.patterns) {
|
|
231
|
+
if (this.matchesPattern(example.command, patternStr)) {
|
|
232
|
+
pattern.examples.push(example);
|
|
233
|
+
pattern.lastUsed = example.timestamp;
|
|
234
|
+
pattern.usageCount++;
|
|
235
|
+
|
|
236
|
+
// Keep only recent examples
|
|
237
|
+
if (pattern.examples.length > 10) {
|
|
238
|
+
pattern.examples = pattern.examples.slice(-10);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private matchesPattern(command: string, pattern: string): boolean {
|
|
247
|
+
const cmdTokens = command.split(/\s+/);
|
|
248
|
+
const patternTokens = pattern.split(/\s+/);
|
|
249
|
+
|
|
250
|
+
if (cmdTokens.length !== patternTokens.length) return false;
|
|
251
|
+
|
|
252
|
+
for (let i = 0; i < cmdTokens.length; i++) {
|
|
253
|
+
if (patternTokens[i] === '<variable>' || patternTokens[i] === '<option>') {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (cmdTokens[i] !== patternTokens[i]) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private updateSuccessRates(): void {
|
|
265
|
+
for (const pattern of this.patterns.values()) {
|
|
266
|
+
const recentExamples = pattern.examples.slice(-10);
|
|
267
|
+
const successCount = recentExamples.filter(e => e.success).length;
|
|
268
|
+
pattern.successRate = successCount / recentExamples.length;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Get command suggestions for a given context
|
|
274
|
+
*/
|
|
275
|
+
async getSuggestions(context: string, category?: string): Promise<CommandPattern[]> {
|
|
276
|
+
let patterns = Array.from(this.patterns.values());
|
|
277
|
+
|
|
278
|
+
// Filter by category if specified
|
|
279
|
+
if (category && this.categoryIndex.has(category)) {
|
|
280
|
+
const categoryPatterns = this.categoryIndex.get(category)!;
|
|
281
|
+
patterns = patterns.filter(p => categoryPatterns.has(p.pattern));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Filter by context relevance
|
|
285
|
+
patterns = patterns.filter(p =>
|
|
286
|
+
p.examples.some(e => e.context.toLowerCase().includes(context.toLowerCase()))
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// Sort by success rate and recency
|
|
290
|
+
return patterns
|
|
291
|
+
.sort((a, b) => {
|
|
292
|
+
const scoreA = a.successRate * 0.7 + (Date.now() - a.lastUsed.getTime() < 3600000 ? 0.3 : 0);
|
|
293
|
+
const scoreB = b.successRate * 0.7 + (Date.now() - b.lastUsed.getTime() < 3600000 ? 0.3 : 0);
|
|
294
|
+
return scoreB - scoreA;
|
|
295
|
+
})
|
|
296
|
+
.slice(0, 5);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for interacting with the Hivemind memory daemon API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
|
|
8
|
+
export interface MemorySearchResult {
|
|
9
|
+
content: string;
|
|
10
|
+
similarity: number;
|
|
11
|
+
metadata?: Record<string, any>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface L3Knowledge {
|
|
15
|
+
insight: string;
|
|
16
|
+
confidence: number;
|
|
17
|
+
sources: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MemoryStats {
|
|
21
|
+
episodes: number;
|
|
22
|
+
contexts: string[];
|
|
23
|
+
l3Entries: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class MemoryAPIClient extends EventEmitter {
|
|
27
|
+
private baseURL: string;
|
|
28
|
+
private context: string;
|
|
29
|
+
|
|
30
|
+
constructor(baseURL: string = 'http://localhost:3434', context: string = 'global') {
|
|
31
|
+
super();
|
|
32
|
+
this.baseURL = baseURL;
|
|
33
|
+
this.context = context;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async health(): Promise<boolean> {
|
|
37
|
+
try {
|
|
38
|
+
const response = await axios.get(`${this.baseURL}/health`);
|
|
39
|
+
return response.status === 200;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
this.emit('error', error);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async search(query: string, limit: number = 10): Promise<MemorySearchResult[]> {
|
|
47
|
+
try {
|
|
48
|
+
const response = await axios.get(`${this.baseURL}/search`, {
|
|
49
|
+
params: { q: query, context: this.context, limit }
|
|
50
|
+
});
|
|
51
|
+
return response.data.results || [];
|
|
52
|
+
} catch (error) {
|
|
53
|
+
this.emit('error', error);
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async crossSearch(query: string, limit: number = 10): Promise<MemorySearchResult[]> {
|
|
59
|
+
try {
|
|
60
|
+
const response = await axios.get(`${this.baseURL}/cross-search`, {
|
|
61
|
+
params: { q: query, limit }
|
|
62
|
+
});
|
|
63
|
+
return response.data.results || [];
|
|
64
|
+
} catch (error) {
|
|
65
|
+
this.emit('error', error);
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async getL3Knowledge(): Promise<L3Knowledge[]> {
|
|
71
|
+
try {
|
|
72
|
+
const response = await axios.get(`${this.baseURL}/promotion/l3`, {
|
|
73
|
+
params: { context: this.context }
|
|
74
|
+
});
|
|
75
|
+
return response.data.knowledge || [];
|
|
76
|
+
} catch (error) {
|
|
77
|
+
this.emit('error', error);
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async getStats(): Promise<MemoryStats | null> {
|
|
83
|
+
try {
|
|
84
|
+
const response = await axios.get(`${this.baseURL}/stats`);
|
|
85
|
+
return response.data;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
this.emit('error', error);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async store(content: string, metadata?: Record<string, any>): Promise<boolean> {
|
|
93
|
+
try {
|
|
94
|
+
const response = await axios.post(`${this.baseURL}/store`, {
|
|
95
|
+
context: this.context,
|
|
96
|
+
content,
|
|
97
|
+
metadata
|
|
98
|
+
});
|
|
99
|
+
return response.status === 200;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
this.emit('error', error);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integrates memory processors with the agent's message flow
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { EventEmitter } from 'events';
|
|
6
|
+
import { MemoryAPIClient } from './memory-api-client';
|
|
7
|
+
import { ProcessManager } from './background-processor';
|
|
8
|
+
import { CodeIndexer } from './code-indexer';
|
|
9
|
+
import { TaskTracker } from './task-tracker';
|
|
10
|
+
import { ContextManager } from './context-manager';
|
|
11
|
+
|
|
12
|
+
export interface AgentMessage {
|
|
13
|
+
role: 'user' | 'assistant' | 'system';
|
|
14
|
+
content: string;
|
|
15
|
+
timestamp: Date;
|
|
16
|
+
context?: string;
|
|
17
|
+
metadata?: Record<string, any>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MessageFlowConfig {
|
|
21
|
+
memoryURL?: string;
|
|
22
|
+
context?: string;
|
|
23
|
+
workspaceRoot: string;
|
|
24
|
+
maxContextTokens?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class MessageFlowIntegration extends EventEmitter {
|
|
28
|
+
private memoryClient: MemoryAPIClient;
|
|
29
|
+
private processManager: ProcessManager;
|
|
30
|
+
private contextManager: ContextManager;
|
|
31
|
+
private taskTracker: TaskTracker;
|
|
32
|
+
private codeIndexer: CodeIndexer;
|
|
33
|
+
|
|
34
|
+
constructor(config: MessageFlowConfig) {
|
|
35
|
+
super();
|
|
36
|
+
|
|
37
|
+
// Initialize memory client
|
|
38
|
+
this.memoryClient = new MemoryAPIClient(
|
|
39
|
+
config.memoryURL || 'http://localhost:3434',
|
|
40
|
+
config.context || 'global'
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Initialize processors
|
|
44
|
+
this.processManager = new ProcessManager();
|
|
45
|
+
this.codeIndexer = new CodeIndexer(config.workspaceRoot);
|
|
46
|
+
this.taskTracker = new TaskTracker();
|
|
47
|
+
|
|
48
|
+
// Initialize context manager
|
|
49
|
+
this.contextManager = new ContextManager(
|
|
50
|
+
this.memoryClient,
|
|
51
|
+
this.processManager,
|
|
52
|
+
config.maxContextTokens || 8000
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Register processors
|
|
56
|
+
this.processManager.register('code-indexer', this.codeIndexer);
|
|
57
|
+
this.processManager.register('task-tracker', this.taskTracker);
|
|
58
|
+
|
|
59
|
+
// Set up event listeners
|
|
60
|
+
this.setupEventListeners();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private setupEventListeners(): void {
|
|
64
|
+
// Forward errors
|
|
65
|
+
this.memoryClient.on('error', (error) => {
|
|
66
|
+
this.emit('error', { source: 'memory-client', error });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
this.processManager.on('processor-error', (data) => {
|
|
70
|
+
this.emit('error', { source: 'processor', ...data });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Track file access from messages
|
|
74
|
+
this.on('message', (message: AgentMessage) => {
|
|
75
|
+
if (message.role === 'assistant') {
|
|
76
|
+
this.extractFileReferences(message.content);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private extractFileReferences(content: string): void {
|
|
82
|
+
// Extract file paths from message content
|
|
83
|
+
const filePathRegex = /(?:\/[\w.-]+)+(?:\.[\w]+)?/g;
|
|
84
|
+
const matches = content.match(filePathRegex) || [];
|
|
85
|
+
|
|
86
|
+
for (const match of matches) {
|
|
87
|
+
if (match.includes('.') && !match.includes('http')) {
|
|
88
|
+
this.codeIndexer.trackAccess(match);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async start(): Promise<void> {
|
|
94
|
+
// Check memory daemon health
|
|
95
|
+
const isHealthy = await this.memoryClient.health();
|
|
96
|
+
if (!isHealthy) {
|
|
97
|
+
throw new Error('Memory daemon is not responding');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Start background processors
|
|
101
|
+
await this.processManager.startAll();
|
|
102
|
+
|
|
103
|
+
this.emit('started');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async stop(): Promise<void> {
|
|
107
|
+
await this.processManager.stopAll();
|
|
108
|
+
this.emit('stopped');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Process an incoming message and return enriched context
|
|
113
|
+
*/
|
|
114
|
+
async processMessage(message: AgentMessage): Promise<string> {
|
|
115
|
+
// Store the message in memory
|
|
116
|
+
await this.memoryClient.store(
|
|
117
|
+
`${message.role}: ${message.content}`,
|
|
118
|
+
{
|
|
119
|
+
timestamp: message.timestamp.toISOString(),
|
|
120
|
+
context: message.context,
|
|
121
|
+
...message.metadata
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Update task tracker
|
|
126
|
+
if (message.role === 'user') {
|
|
127
|
+
await this.taskTracker.processMessage(message.content, 'user');
|
|
128
|
+
} else if (message.role === 'assistant') {
|
|
129
|
+
await this.taskTracker.processMessage(message.content, 'assistant');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Build context for the next response
|
|
133
|
+
const context = await this.contextManager.buildContext(message.content);
|
|
134
|
+
|
|
135
|
+
this.emit('context-built', {
|
|
136
|
+
message,
|
|
137
|
+
contextLength: context.length,
|
|
138
|
+
sections: this.contextManager['contextSections'].length // Track what was included
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return context;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get current state of all processors
|
|
146
|
+
*/
|
|
147
|
+
async getState(): Promise<Record<string, any>> {
|
|
148
|
+
const stats = await this.memoryClient.getStats();
|
|
149
|
+
const tasks = await this.taskTracker.getActiveTasks();
|
|
150
|
+
const workingSet = await this.codeIndexer.getWorkingSet();
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
memory: stats,
|
|
154
|
+
tasks: tasks.map(t => ({
|
|
155
|
+
description: t.description,
|
|
156
|
+
state: t.state,
|
|
157
|
+
startTime: t.startTime,
|
|
158
|
+
lastUpdate: t.lastUpdate
|
|
159
|
+
})),
|
|
160
|
+
workingSet: workingSet.map(f => ({
|
|
161
|
+
path: f.path,
|
|
162
|
+
accessCount: f.accessCount,
|
|
163
|
+
lastAccessed: f.lastAccessed
|
|
164
|
+
})),
|
|
165
|
+
processors: this.processManager['processors'].size
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|