claude-flow 1.0.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/LICENSE +21 -0
- package/README.md +612 -0
- package/bin/claude-flow +0 -0
- package/bin/claude-flow-simple +0 -0
- package/bin/claude-flow-typecheck +0 -0
- package/deno.json +84 -0
- package/package.json +45 -0
- package/scripts/check-links.ts +274 -0
- package/scripts/check-performance-regression.ts +168 -0
- package/scripts/claude-sparc.sh +562 -0
- package/scripts/coverage-report.ts +692 -0
- package/scripts/demo-task-system.ts +224 -0
- package/scripts/install.js +72 -0
- package/scripts/test-batch-tasks.ts +29 -0
- package/scripts/test-coordination-features.ts +238 -0
- package/scripts/test-mcp.ts +251 -0
- package/scripts/test-runner.ts +571 -0
- package/scripts/validate-examples.ts +288 -0
- package/src/cli/cli-core.ts +273 -0
- package/src/cli/commands/agent.ts +83 -0
- package/src/cli/commands/config.ts +442 -0
- package/src/cli/commands/help.ts +765 -0
- package/src/cli/commands/index.ts +963 -0
- package/src/cli/commands/mcp.ts +191 -0
- package/src/cli/commands/memory.ts +74 -0
- package/src/cli/commands/monitor.ts +403 -0
- package/src/cli/commands/session.ts +595 -0
- package/src/cli/commands/start.ts +156 -0
- package/src/cli/commands/status.ts +345 -0
- package/src/cli/commands/task.ts +79 -0
- package/src/cli/commands/workflow.ts +763 -0
- package/src/cli/completion.ts +553 -0
- package/src/cli/formatter.ts +310 -0
- package/src/cli/index.ts +211 -0
- package/src/cli/main.ts +23 -0
- package/src/cli/repl.ts +1050 -0
- package/src/cli/simple-cli.js +211 -0
- package/src/cli/simple-cli.ts +211 -0
- package/src/coordination/README.md +400 -0
- package/src/coordination/advanced-scheduler.ts +487 -0
- package/src/coordination/circuit-breaker.ts +366 -0
- package/src/coordination/conflict-resolution.ts +490 -0
- package/src/coordination/dependency-graph.ts +475 -0
- package/src/coordination/index.ts +63 -0
- package/src/coordination/manager.ts +460 -0
- package/src/coordination/messaging.ts +290 -0
- package/src/coordination/metrics.ts +585 -0
- package/src/coordination/resources.ts +322 -0
- package/src/coordination/scheduler.ts +390 -0
- package/src/coordination/work-stealing.ts +224 -0
- package/src/core/config.ts +627 -0
- package/src/core/event-bus.ts +186 -0
- package/src/core/json-persistence.ts +183 -0
- package/src/core/logger.ts +262 -0
- package/src/core/orchestrator-fixed.ts +312 -0
- package/src/core/orchestrator.ts +1234 -0
- package/src/core/persistence.ts +276 -0
- package/src/mcp/auth.ts +438 -0
- package/src/mcp/claude-flow-tools.ts +1280 -0
- package/src/mcp/load-balancer.ts +510 -0
- package/src/mcp/router.ts +240 -0
- package/src/mcp/server.ts +548 -0
- package/src/mcp/session-manager.ts +418 -0
- package/src/mcp/tools.ts +180 -0
- package/src/mcp/transports/base.ts +21 -0
- package/src/mcp/transports/http.ts +457 -0
- package/src/mcp/transports/stdio.ts +254 -0
- package/src/memory/backends/base.ts +22 -0
- package/src/memory/backends/markdown.ts +283 -0
- package/src/memory/backends/sqlite.ts +329 -0
- package/src/memory/cache.ts +238 -0
- package/src/memory/indexer.ts +238 -0
- package/src/memory/manager.ts +572 -0
- package/src/terminal/adapters/base.ts +29 -0
- package/src/terminal/adapters/native.ts +504 -0
- package/src/terminal/adapters/vscode.ts +340 -0
- package/src/terminal/manager.ts +308 -0
- package/src/terminal/pool.ts +271 -0
- package/src/terminal/session.ts +250 -0
- package/src/terminal/vscode-bridge.ts +242 -0
- package/src/utils/errors.ts +231 -0
- package/src/utils/helpers.ts +476 -0
- package/src/utils/types.ts +493 -0
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory manager interface and implementation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { MemoryEntry, MemoryQuery, MemoryConfig } from '../utils/types.ts';
|
|
6
|
+
import { IEventBus } from '../core/event-bus.ts';
|
|
7
|
+
import { ILogger } from '../core/logger.ts';
|
|
8
|
+
import { MemoryError } from '../utils/errors.ts';
|
|
9
|
+
import { IMemoryBackend } from './backends/base.ts';
|
|
10
|
+
import { SQLiteBackend } from './backends/sqlite.ts';
|
|
11
|
+
import { MarkdownBackend } from './backends/markdown.ts';
|
|
12
|
+
import { MemoryCache } from './cache.ts';
|
|
13
|
+
import { MemoryIndexer } from './indexer.ts';
|
|
14
|
+
|
|
15
|
+
export interface IMemoryManager {
|
|
16
|
+
initialize(): Promise<void>;
|
|
17
|
+
shutdown(): Promise<void>;
|
|
18
|
+
createBank(agentId: string): Promise<string>;
|
|
19
|
+
closeBank(bankId: string): Promise<void>;
|
|
20
|
+
store(entry: MemoryEntry): Promise<void>;
|
|
21
|
+
retrieve(id: string): Promise<MemoryEntry | undefined>;
|
|
22
|
+
query(query: MemoryQuery): Promise<MemoryEntry[]>;
|
|
23
|
+
update(id: string, updates: Partial<MemoryEntry>): Promise<void>;
|
|
24
|
+
delete(id: string): Promise<void>;
|
|
25
|
+
getHealthStatus(): Promise<{ healthy: boolean; error?: string; metrics?: Record<string, number> }>;
|
|
26
|
+
performMaintenance(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Memory bank for agent-specific storage
|
|
31
|
+
*/
|
|
32
|
+
interface MemoryBank {
|
|
33
|
+
id: string;
|
|
34
|
+
agentId: string;
|
|
35
|
+
createdAt: Date;
|
|
36
|
+
lastAccessed: Date;
|
|
37
|
+
entryCount: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Memory manager implementation
|
|
42
|
+
*/
|
|
43
|
+
export class MemoryManager implements IMemoryManager {
|
|
44
|
+
private backend: IMemoryBackend;
|
|
45
|
+
private cache: MemoryCache;
|
|
46
|
+
private indexer: MemoryIndexer;
|
|
47
|
+
private banks = new Map<string, MemoryBank>();
|
|
48
|
+
private initialized = false;
|
|
49
|
+
private syncInterval?: number;
|
|
50
|
+
|
|
51
|
+
constructor(
|
|
52
|
+
private config: MemoryConfig,
|
|
53
|
+
private eventBus: IEventBus,
|
|
54
|
+
private logger: ILogger,
|
|
55
|
+
) {
|
|
56
|
+
// Initialize backend based on configuration
|
|
57
|
+
this.backend = this.createBackend();
|
|
58
|
+
|
|
59
|
+
// Initialize cache
|
|
60
|
+
this.cache = new MemoryCache(
|
|
61
|
+
this.config.cacheSizeMB * 1024 * 1024, // Convert MB to bytes
|
|
62
|
+
this.logger,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Initialize indexer
|
|
66
|
+
this.indexer = new MemoryIndexer(this.logger);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async initialize(): Promise<void> {
|
|
70
|
+
if (this.initialized) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.logger.info('Initializing memory manager...');
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
// Initialize backend
|
|
78
|
+
await this.backend.initialize();
|
|
79
|
+
|
|
80
|
+
// Initialize indexer with existing entries
|
|
81
|
+
const allEntries = await this.backend.getAllEntries();
|
|
82
|
+
await this.indexer.buildIndex(allEntries);
|
|
83
|
+
|
|
84
|
+
// Start sync interval
|
|
85
|
+
this.startSyncInterval();
|
|
86
|
+
|
|
87
|
+
this.initialized = true;
|
|
88
|
+
this.logger.info('Memory manager initialized');
|
|
89
|
+
} catch (error) {
|
|
90
|
+
this.logger.error('Failed to initialize memory manager', error);
|
|
91
|
+
throw new MemoryError('Memory manager initialization failed', { error });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async shutdown(): Promise<void> {
|
|
96
|
+
if (!this.initialized) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.logger.info('Shutting down memory manager...');
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
// Stop sync interval
|
|
104
|
+
if (this.syncInterval) {
|
|
105
|
+
clearInterval(this.syncInterval);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Flush cache
|
|
109
|
+
await this.flushCache();
|
|
110
|
+
|
|
111
|
+
// Close all banks
|
|
112
|
+
const bankIds = Array.from(this.banks.keys());
|
|
113
|
+
await Promise.all(bankIds.map(id => this.closeBank(id)));
|
|
114
|
+
|
|
115
|
+
// Shutdown backend
|
|
116
|
+
await this.backend.shutdown();
|
|
117
|
+
|
|
118
|
+
this.initialized = false;
|
|
119
|
+
this.logger.info('Memory manager shutdown complete');
|
|
120
|
+
} catch (error) {
|
|
121
|
+
this.logger.error('Error during memory manager shutdown', error);
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async createBank(agentId: string): Promise<string> {
|
|
127
|
+
if (!this.initialized) {
|
|
128
|
+
throw new MemoryError('Memory manager not initialized');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const bank: MemoryBank = {
|
|
132
|
+
id: `bank_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
133
|
+
agentId,
|
|
134
|
+
createdAt: new Date(),
|
|
135
|
+
lastAccessed: new Date(),
|
|
136
|
+
entryCount: 0,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
this.banks.set(bank.id, bank);
|
|
140
|
+
|
|
141
|
+
this.logger.info('Memory bank created', { bankId: bank.id, agentId });
|
|
142
|
+
|
|
143
|
+
return bank.id;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async closeBank(bankId: string): Promise<void> {
|
|
147
|
+
const bank = this.banks.get(bankId);
|
|
148
|
+
if (!bank) {
|
|
149
|
+
throw new MemoryError(`Memory bank not found: ${bankId}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Flush any cached entries for this bank
|
|
153
|
+
const bankEntries = this.cache.getByPrefix(`${bank.agentId}:`);
|
|
154
|
+
for (const entry of bankEntries) {
|
|
155
|
+
await this.backend.store(entry);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.banks.delete(bankId);
|
|
159
|
+
|
|
160
|
+
this.logger.info('Memory bank closed', { bankId });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async store(entry: MemoryEntry): Promise<void> {
|
|
164
|
+
if (!this.initialized) {
|
|
165
|
+
throw new MemoryError('Memory manager not initialized');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
this.logger.debug('Storing memory entry', {
|
|
169
|
+
id: entry.id,
|
|
170
|
+
type: entry.type,
|
|
171
|
+
agentId: entry.agentId,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
// Add to cache
|
|
176
|
+
this.cache.set(entry.id, entry);
|
|
177
|
+
|
|
178
|
+
// Add to index
|
|
179
|
+
this.indexer.addEntry(entry);
|
|
180
|
+
|
|
181
|
+
// Store in backend (async, don't wait)
|
|
182
|
+
this.backend.store(entry).catch(error => {
|
|
183
|
+
this.logger.error('Failed to store entry in backend', {
|
|
184
|
+
id: entry.id,
|
|
185
|
+
error,
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Update bank stats
|
|
190
|
+
const bank = Array.from(this.banks.values()).find(b => b.agentId === entry.agentId);
|
|
191
|
+
if (bank) {
|
|
192
|
+
bank.entryCount++;
|
|
193
|
+
bank.lastAccessed = new Date();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Emit event
|
|
197
|
+
this.eventBus.emit('memory:created', { entry });
|
|
198
|
+
} catch (error) {
|
|
199
|
+
this.logger.error('Failed to store memory entry', error);
|
|
200
|
+
throw new MemoryError('Failed to store memory entry', { error });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async retrieve(id: string): Promise<MemoryEntry | undefined> {
|
|
205
|
+
if (!this.initialized) {
|
|
206
|
+
throw new MemoryError('Memory manager not initialized');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Check cache first
|
|
210
|
+
const cached = this.cache.get(id);
|
|
211
|
+
if (cached) {
|
|
212
|
+
return cached;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Retrieve from backend
|
|
216
|
+
const entry = await this.backend.retrieve(id);
|
|
217
|
+
if (entry) {
|
|
218
|
+
// Add to cache
|
|
219
|
+
this.cache.set(id, entry);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return entry;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async query(query: MemoryQuery): Promise<MemoryEntry[]> {
|
|
226
|
+
if (!this.initialized) {
|
|
227
|
+
throw new MemoryError('Memory manager not initialized');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
this.logger.debug('Querying memory', query);
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
// Use index for fast querying
|
|
234
|
+
let results = this.indexer.search(query);
|
|
235
|
+
|
|
236
|
+
// Apply additional filters if needed
|
|
237
|
+
if (query.search) {
|
|
238
|
+
results = results.filter(entry =>
|
|
239
|
+
entry.content.toLowerCase().includes(query.search!.toLowerCase()) ||
|
|
240
|
+
entry.tags.some(tag => tag.toLowerCase().includes(query.search!.toLowerCase())),
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Apply time range filter
|
|
245
|
+
if (query.startTime || query.endTime) {
|
|
246
|
+
results = results.filter(entry => {
|
|
247
|
+
const timestamp = entry.timestamp.getTime();
|
|
248
|
+
if (query.startTime && timestamp < query.startTime.getTime()) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
if (query.endTime && timestamp > query.endTime.getTime()) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
return true;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Apply pagination
|
|
259
|
+
const start = query.offset || 0;
|
|
260
|
+
const limit = query.limit || 100;
|
|
261
|
+
results = results.slice(start, start + limit);
|
|
262
|
+
|
|
263
|
+
return results;
|
|
264
|
+
} catch (error) {
|
|
265
|
+
this.logger.error('Failed to query memory', error);
|
|
266
|
+
throw new MemoryError('Failed to query memory', { error });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async update(id: string, updates: Partial<MemoryEntry>): Promise<void> {
|
|
271
|
+
if (!this.initialized) {
|
|
272
|
+
throw new MemoryError('Memory manager not initialized');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const existing = await this.retrieve(id);
|
|
276
|
+
if (!existing) {
|
|
277
|
+
throw new MemoryError(`Memory entry not found: ${id}`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Create updated entry
|
|
281
|
+
const updated: MemoryEntry = {
|
|
282
|
+
...existing,
|
|
283
|
+
...updates,
|
|
284
|
+
id: existing.id, // Ensure ID doesn't change
|
|
285
|
+
version: existing.version + 1,
|
|
286
|
+
timestamp: new Date(),
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// Update in cache
|
|
290
|
+
this.cache.set(id, updated);
|
|
291
|
+
|
|
292
|
+
// Update in index
|
|
293
|
+
this.indexer.updateEntry(updated);
|
|
294
|
+
|
|
295
|
+
// Update in backend
|
|
296
|
+
await this.backend.update(id, updated);
|
|
297
|
+
|
|
298
|
+
// Emit event
|
|
299
|
+
this.eventBus.emit('memory:updated', {
|
|
300
|
+
entry: updated,
|
|
301
|
+
previousVersion: existing.version,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async delete(id: string): Promise<void> {
|
|
306
|
+
if (!this.initialized) {
|
|
307
|
+
throw new MemoryError('Memory manager not initialized');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Remove from cache
|
|
311
|
+
this.cache.delete(id);
|
|
312
|
+
|
|
313
|
+
// Remove from index
|
|
314
|
+
this.indexer.removeEntry(id);
|
|
315
|
+
|
|
316
|
+
// Delete from backend
|
|
317
|
+
await this.backend.delete(id);
|
|
318
|
+
|
|
319
|
+
// Emit event
|
|
320
|
+
this.eventBus.emit('memory:deleted', { entryId: id });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async getHealthStatus(): Promise<{
|
|
324
|
+
healthy: boolean;
|
|
325
|
+
error?: string;
|
|
326
|
+
metrics?: Record<string, number>;
|
|
327
|
+
}> {
|
|
328
|
+
try {
|
|
329
|
+
const backendHealth = await this.backend.getHealthStatus();
|
|
330
|
+
const cacheMetrics = this.cache.getMetrics();
|
|
331
|
+
const indexMetrics = this.indexer.getMetrics();
|
|
332
|
+
|
|
333
|
+
const metrics = {
|
|
334
|
+
totalEntries: indexMetrics.totalEntries,
|
|
335
|
+
cacheSize: cacheMetrics.size,
|
|
336
|
+
cacheHitRate: cacheMetrics.hitRate,
|
|
337
|
+
activeBanks: this.banks.size,
|
|
338
|
+
...backendHealth.metrics,
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
healthy: backendHealth.healthy,
|
|
343
|
+
metrics,
|
|
344
|
+
...(backendHealth.error && { error: backendHealth.error }),
|
|
345
|
+
};
|
|
346
|
+
} catch (error) {
|
|
347
|
+
return {
|
|
348
|
+
healthy: false,
|
|
349
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async performMaintenance(): Promise<void> {
|
|
355
|
+
if (!this.initialized) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
this.logger.debug('Performing memory manager maintenance');
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
// Clean up old entries based on retention policy
|
|
363
|
+
if (this.config.retentionDays > 0) {
|
|
364
|
+
const cutoffDate = new Date();
|
|
365
|
+
cutoffDate.setDate(cutoffDate.getDate() - this.config.retentionDays);
|
|
366
|
+
|
|
367
|
+
const oldEntries = await this.query({
|
|
368
|
+
endTime: cutoffDate,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
for (const entry of oldEntries) {
|
|
372
|
+
await this.delete(entry.id);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
this.logger.info(`Cleaned up ${oldEntries.length} old memory entries`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Perform cache maintenance
|
|
379
|
+
this.cache.performMaintenance();
|
|
380
|
+
|
|
381
|
+
// Perform backend maintenance
|
|
382
|
+
if (this.backend.performMaintenance) {
|
|
383
|
+
await this.backend.performMaintenance();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Update bank statistics
|
|
387
|
+
for (const bank of this.banks.values()) {
|
|
388
|
+
const entries = await this.query({ agentId: bank.agentId });
|
|
389
|
+
bank.entryCount = entries.length;
|
|
390
|
+
bank.lastAccessed = new Date();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
this.logger.debug('Memory manager maintenance completed');
|
|
394
|
+
} catch (error) {
|
|
395
|
+
this.logger.error('Error during memory manager maintenance', error);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
private createBackend(): IMemoryBackend {
|
|
400
|
+
switch (this.config.backend) {
|
|
401
|
+
case 'sqlite':
|
|
402
|
+
return new SQLiteBackend(
|
|
403
|
+
this.config.sqlitePath || './claude-flow.db',
|
|
404
|
+
this.logger,
|
|
405
|
+
);
|
|
406
|
+
case 'markdown':
|
|
407
|
+
return new MarkdownBackend(
|
|
408
|
+
this.config.markdownDir || './memory',
|
|
409
|
+
this.logger,
|
|
410
|
+
);
|
|
411
|
+
case 'hybrid':
|
|
412
|
+
// Use SQLite for structured data and Markdown for human-readable backup
|
|
413
|
+
return new HybridBackend(
|
|
414
|
+
new SQLiteBackend(
|
|
415
|
+
this.config.sqlitePath || './claude-flow.db',
|
|
416
|
+
this.logger,
|
|
417
|
+
),
|
|
418
|
+
new MarkdownBackend(
|
|
419
|
+
this.config.markdownDir || './memory',
|
|
420
|
+
this.logger,
|
|
421
|
+
),
|
|
422
|
+
this.logger,
|
|
423
|
+
);
|
|
424
|
+
default:
|
|
425
|
+
throw new MemoryError(`Unknown memory backend: ${this.config.backend}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
private startSyncInterval(): void {
|
|
430
|
+
this.syncInterval = setInterval(async () => {
|
|
431
|
+
try {
|
|
432
|
+
await this.syncCache();
|
|
433
|
+
} catch (error) {
|
|
434
|
+
this.logger.error('Cache sync error', error);
|
|
435
|
+
}
|
|
436
|
+
}, this.config.syncInterval);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private async syncCache(): Promise<void> {
|
|
440
|
+
const dirtyEntries = this.cache.getDirtyEntries();
|
|
441
|
+
|
|
442
|
+
if (dirtyEntries.length === 0) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
this.logger.debug('Syncing cache to backend', { count: dirtyEntries.length });
|
|
447
|
+
|
|
448
|
+
const promises = dirtyEntries.map(entry =>
|
|
449
|
+
this.backend.store(entry).catch(error => {
|
|
450
|
+
this.logger.error('Failed to sync entry', { id: entry.id, error });
|
|
451
|
+
}),
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
await Promise.all(promises);
|
|
455
|
+
this.cache.markClean(dirtyEntries.map(e => e.id));
|
|
456
|
+
|
|
457
|
+
// Emit sync event
|
|
458
|
+
this.eventBus.emit('memory:synced', { entries: dirtyEntries });
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private async flushCache(): Promise<void> {
|
|
462
|
+
const allEntries = this.cache.getAllEntries();
|
|
463
|
+
|
|
464
|
+
if (allEntries.length === 0) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
this.logger.info('Flushing cache to backend', { count: allEntries.length });
|
|
469
|
+
|
|
470
|
+
const promises = allEntries.map(entry =>
|
|
471
|
+
this.backend.store(entry).catch(error => {
|
|
472
|
+
this.logger.error('Failed to flush entry', { id: entry.id, error });
|
|
473
|
+
}),
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
await Promise.all(promises);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Hybrid backend that uses both SQLite and Markdown
|
|
482
|
+
*/
|
|
483
|
+
class HybridBackend implements IMemoryBackend {
|
|
484
|
+
constructor(
|
|
485
|
+
private primary: IMemoryBackend,
|
|
486
|
+
private secondary: IMemoryBackend,
|
|
487
|
+
private logger: ILogger,
|
|
488
|
+
) {}
|
|
489
|
+
|
|
490
|
+
async initialize(): Promise<void> {
|
|
491
|
+
await Promise.all([
|
|
492
|
+
this.primary.initialize(),
|
|
493
|
+
this.secondary.initialize(),
|
|
494
|
+
]);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async shutdown(): Promise<void> {
|
|
498
|
+
await Promise.all([
|
|
499
|
+
this.primary.shutdown(),
|
|
500
|
+
this.secondary.shutdown(),
|
|
501
|
+
]);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async store(entry: MemoryEntry): Promise<void> {
|
|
505
|
+
// Store in both backends
|
|
506
|
+
await Promise.all([
|
|
507
|
+
this.primary.store(entry),
|
|
508
|
+
this.secondary.store(entry).catch(error => {
|
|
509
|
+
this.logger.warn('Failed to store in secondary backend', { error });
|
|
510
|
+
}),
|
|
511
|
+
]);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async retrieve(id: string): Promise<MemoryEntry | undefined> {
|
|
515
|
+
// Try primary first
|
|
516
|
+
const entry = await this.primary.retrieve(id);
|
|
517
|
+
if (entry) {
|
|
518
|
+
return entry;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Fall back to secondary
|
|
522
|
+
return await this.secondary.retrieve(id);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async update(id: string, entry: MemoryEntry): Promise<void> {
|
|
526
|
+
await Promise.all([
|
|
527
|
+
this.primary.update(id, entry),
|
|
528
|
+
this.secondary.update(id, entry).catch(error => {
|
|
529
|
+
this.logger.warn('Failed to update in secondary backend', { error });
|
|
530
|
+
}),
|
|
531
|
+
]);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
async delete(id: string): Promise<void> {
|
|
535
|
+
await Promise.all([
|
|
536
|
+
this.primary.delete(id),
|
|
537
|
+
this.secondary.delete(id).catch(error => {
|
|
538
|
+
this.logger.warn('Failed to delete from secondary backend', { error });
|
|
539
|
+
}),
|
|
540
|
+
]);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
async query(query: MemoryQuery): Promise<MemoryEntry[]> {
|
|
544
|
+
// Use primary for querying (faster)
|
|
545
|
+
return await this.primary.query(query);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async getAllEntries(): Promise<MemoryEntry[]> {
|
|
549
|
+
return await this.primary.getAllEntries();
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
async getHealthStatus(): Promise<{
|
|
553
|
+
healthy: boolean;
|
|
554
|
+
error?: string;
|
|
555
|
+
metrics?: Record<string, number>;
|
|
556
|
+
}> {
|
|
557
|
+
const [primaryHealth, secondaryHealth] = await Promise.all([
|
|
558
|
+
this.primary.getHealthStatus(),
|
|
559
|
+
this.secondary.getHealthStatus(),
|
|
560
|
+
]);
|
|
561
|
+
|
|
562
|
+
const error = primaryHealth.error || secondaryHealth.error;
|
|
563
|
+
return {
|
|
564
|
+
healthy: primaryHealth.healthy && secondaryHealth.healthy,
|
|
565
|
+
...(error && { error }),
|
|
566
|
+
metrics: {
|
|
567
|
+
...primaryHealth.metrics,
|
|
568
|
+
...secondaryHealth.metrics,
|
|
569
|
+
},
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base terminal adapter interface
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface Terminal {
|
|
6
|
+
id: string;
|
|
7
|
+
pid?: number;
|
|
8
|
+
executeCommand(command: string): Promise<string>;
|
|
9
|
+
write(data: string): Promise<void>;
|
|
10
|
+
read(): Promise<string>;
|
|
11
|
+
isAlive(): boolean;
|
|
12
|
+
kill(): Promise<void>;
|
|
13
|
+
addOutputListener?(listener: (data: string) => void): void;
|
|
14
|
+
removeOutputListener?(listener: (data: string) => void): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ITerminalAdapter {
|
|
18
|
+
initialize(): Promise<void>;
|
|
19
|
+
shutdown(): Promise<void>;
|
|
20
|
+
createTerminal(): Promise<Terminal>;
|
|
21
|
+
destroyTerminal(terminal: Terminal): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TerminalEvents {
|
|
25
|
+
'terminal:created': { terminalId: string; pid?: number };
|
|
26
|
+
'terminal:closed': { terminalId: string; code?: number; signal?: string };
|
|
27
|
+
'terminal:error': { terminalId: string; error: Error };
|
|
28
|
+
'terminal:output': { terminalId: string; data: string };
|
|
29
|
+
}
|