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,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base interface for memory backends
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { MemoryEntry, MemoryQuery } from '../../utils/types.ts';
|
|
6
|
+
|
|
7
|
+
export interface IMemoryBackend {
|
|
8
|
+
initialize(): Promise<void>;
|
|
9
|
+
shutdown(): Promise<void>;
|
|
10
|
+
store(entry: MemoryEntry): Promise<void>;
|
|
11
|
+
retrieve(id: string): Promise<MemoryEntry | undefined>;
|
|
12
|
+
update(id: string, entry: MemoryEntry): Promise<void>;
|
|
13
|
+
delete(id: string): Promise<void>;
|
|
14
|
+
query(query: MemoryQuery): Promise<MemoryEntry[]>;
|
|
15
|
+
getAllEntries(): Promise<MemoryEntry[]>;
|
|
16
|
+
getHealthStatus(): Promise<{
|
|
17
|
+
healthy: boolean;
|
|
18
|
+
error?: string;
|
|
19
|
+
metrics?: Record<string, number>;
|
|
20
|
+
}>;
|
|
21
|
+
performMaintenance?(): Promise<void>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown backend implementation for human-readable memory storage
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IMemoryBackend } from './base.ts';
|
|
6
|
+
import { MemoryEntry, MemoryQuery } from '../../utils/types.ts';
|
|
7
|
+
import { ILogger } from '../../core/logger.ts';
|
|
8
|
+
import { MemoryBackendError } from '../../utils/errors.ts';
|
|
9
|
+
import { ensureDir } from 'https://deno.land/std@0.208.0/fs/mod.ts';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Markdown-based memory backend
|
|
13
|
+
*/
|
|
14
|
+
export class MarkdownBackend implements IMemoryBackend {
|
|
15
|
+
private entries = new Map<string, MemoryEntry>();
|
|
16
|
+
private indexPath: string;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
private baseDir: string,
|
|
20
|
+
private logger: ILogger,
|
|
21
|
+
) {
|
|
22
|
+
this.indexPath = `${this.baseDir}/index.json`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async initialize(): Promise<void> {
|
|
26
|
+
this.logger.info('Initializing Markdown backend', { baseDir: this.baseDir });
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// Ensure directories exist
|
|
30
|
+
await ensureDir(this.baseDir);
|
|
31
|
+
await ensureDir(`${this.baseDir}/agents`);
|
|
32
|
+
await ensureDir(`${this.baseDir}/sessions`);
|
|
33
|
+
|
|
34
|
+
// Load index
|
|
35
|
+
await this.loadIndex();
|
|
36
|
+
|
|
37
|
+
this.logger.info('Markdown backend initialized');
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw new MemoryBackendError('Failed to initialize Markdown backend', { error });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async shutdown(): Promise<void> {
|
|
44
|
+
this.logger.info('Shutting down Markdown backend');
|
|
45
|
+
|
|
46
|
+
// Save index before shutdown
|
|
47
|
+
await this.saveIndex();
|
|
48
|
+
this.entries.clear();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async store(entry: MemoryEntry): Promise<void> {
|
|
52
|
+
try {
|
|
53
|
+
// Store in memory
|
|
54
|
+
this.entries.set(entry.id, entry);
|
|
55
|
+
|
|
56
|
+
// Write to markdown file
|
|
57
|
+
await this.writeEntryToFile(entry);
|
|
58
|
+
|
|
59
|
+
// Update index
|
|
60
|
+
await this.saveIndex();
|
|
61
|
+
} catch (error) {
|
|
62
|
+
throw new MemoryBackendError('Failed to store entry', { error });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async retrieve(id: string): Promise<MemoryEntry | undefined> {
|
|
67
|
+
return this.entries.get(id);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async update(id: string, entry: MemoryEntry): Promise<void> {
|
|
71
|
+
if (!this.entries.has(id)) {
|
|
72
|
+
throw new MemoryBackendError(`Entry not found: ${id}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
await this.store(entry);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async delete(id: string): Promise<void> {
|
|
79
|
+
const entry = this.entries.get(id);
|
|
80
|
+
if (!entry) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
// Delete from memory
|
|
86
|
+
this.entries.delete(id);
|
|
87
|
+
|
|
88
|
+
// Delete file
|
|
89
|
+
const filePath = this.getEntryFilePath(entry);
|
|
90
|
+
await Deno.remove(filePath);
|
|
91
|
+
|
|
92
|
+
// Update index
|
|
93
|
+
await this.saveIndex();
|
|
94
|
+
} catch (error) {
|
|
95
|
+
throw new MemoryBackendError('Failed to delete entry', { error });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async query(query: MemoryQuery): Promise<MemoryEntry[]> {
|
|
100
|
+
let results = Array.from(this.entries.values());
|
|
101
|
+
|
|
102
|
+
// Apply filters
|
|
103
|
+
if (query.agentId) {
|
|
104
|
+
results = results.filter(e => e.agentId === query.agentId);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (query.sessionId) {
|
|
108
|
+
results = results.filter(e => e.sessionId === query.sessionId);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (query.type) {
|
|
112
|
+
results = results.filter(e => e.type === query.type);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (query.tags && query.tags.length > 0) {
|
|
116
|
+
results = results.filter(e =>
|
|
117
|
+
query.tags!.some(tag => e.tags.includes(tag)),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (query.startTime) {
|
|
122
|
+
results = results.filter(e =>
|
|
123
|
+
e.timestamp.getTime() >= query.startTime!.getTime(),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (query.endTime) {
|
|
128
|
+
results = results.filter(e =>
|
|
129
|
+
e.timestamp.getTime() <= query.endTime!.getTime(),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (query.search) {
|
|
134
|
+
const searchLower = query.search.toLowerCase();
|
|
135
|
+
results = results.filter(e =>
|
|
136
|
+
e.content.toLowerCase().includes(searchLower) ||
|
|
137
|
+
e.tags.some(tag => tag.toLowerCase().includes(searchLower)),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Sort by timestamp (newest first)
|
|
142
|
+
results.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
143
|
+
|
|
144
|
+
// Apply pagination
|
|
145
|
+
const start = query.offset || 0;
|
|
146
|
+
const limit = query.limit || results.length;
|
|
147
|
+
results = results.slice(start, start + limit);
|
|
148
|
+
|
|
149
|
+
return results;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async getAllEntries(): Promise<MemoryEntry[]> {
|
|
153
|
+
return Array.from(this.entries.values());
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async getHealthStatus(): Promise<{
|
|
157
|
+
healthy: boolean;
|
|
158
|
+
error?: string;
|
|
159
|
+
metrics?: Record<string, number>;
|
|
160
|
+
}> {
|
|
161
|
+
try {
|
|
162
|
+
// Check if directory is accessible
|
|
163
|
+
await Deno.stat(this.baseDir);
|
|
164
|
+
|
|
165
|
+
const entryCount = this.entries.size;
|
|
166
|
+
let totalSizeBytes = 0;
|
|
167
|
+
|
|
168
|
+
// Calculate total size
|
|
169
|
+
for (const entry of this.entries.values()) {
|
|
170
|
+
const filePath = this.getEntryFilePath(entry);
|
|
171
|
+
try {
|
|
172
|
+
const stat = await Deno.stat(filePath);
|
|
173
|
+
totalSizeBytes += stat.size;
|
|
174
|
+
} catch {
|
|
175
|
+
// File might not exist yet
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
healthy: true,
|
|
181
|
+
metrics: {
|
|
182
|
+
entryCount,
|
|
183
|
+
totalSizeBytes,
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
} catch (error) {
|
|
187
|
+
return {
|
|
188
|
+
healthy: false,
|
|
189
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private async loadIndex(): Promise<void> {
|
|
195
|
+
try {
|
|
196
|
+
const content = await Deno.readTextFile(this.indexPath);
|
|
197
|
+
const index = JSON.parse(content) as Record<string, MemoryEntry>;
|
|
198
|
+
|
|
199
|
+
// Convert and validate entries
|
|
200
|
+
for (const [id, entry] of Object.entries(index)) {
|
|
201
|
+
// Reconstruct dates
|
|
202
|
+
entry.timestamp = new Date(entry.timestamp);
|
|
203
|
+
this.entries.set(id, entry);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.logger.info('Loaded memory index', { entries: this.entries.size });
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (!(error instanceof Deno.errors.NotFound)) {
|
|
209
|
+
this.logger.warn('Failed to load index', { error });
|
|
210
|
+
}
|
|
211
|
+
// Start with empty index if file doesn't exist
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private async saveIndex(): Promise<void> {
|
|
216
|
+
const index: Record<string, MemoryEntry> = {};
|
|
217
|
+
|
|
218
|
+
for (const [id, entry] of this.entries) {
|
|
219
|
+
index[id] = entry;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const content = JSON.stringify(index, null, 2);
|
|
223
|
+
await Deno.writeTextFile(this.indexPath, content);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private async writeEntryToFile(entry: MemoryEntry): Promise<void> {
|
|
227
|
+
const filePath = this.getEntryFilePath(entry);
|
|
228
|
+
const dirPath = filePath.substring(0, filePath.lastIndexOf('/'));
|
|
229
|
+
|
|
230
|
+
// Ensure directory exists
|
|
231
|
+
await ensureDir(dirPath);
|
|
232
|
+
|
|
233
|
+
// Generate markdown content
|
|
234
|
+
const content = this.entryToMarkdown(entry);
|
|
235
|
+
|
|
236
|
+
// Write file
|
|
237
|
+
await Deno.writeTextFile(filePath, content);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private getEntryFilePath(entry: MemoryEntry): string {
|
|
241
|
+
const date = entry.timestamp.toISOString().split('T')[0];
|
|
242
|
+
const time = entry.timestamp.toISOString().split('T')[1].replace(/:/g, '-').split('.')[0];
|
|
243
|
+
|
|
244
|
+
return `${this.baseDir}/agents/${entry.agentId}/${date}/${time}_${entry.id}.md`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private entryToMarkdown(entry: MemoryEntry): string {
|
|
248
|
+
const lines: string[] = [
|
|
249
|
+
`# Memory Entry: ${entry.id}`,
|
|
250
|
+
'',
|
|
251
|
+
`**Agent**: ${entry.agentId}`,
|
|
252
|
+
`**Session**: ${entry.sessionId}`,
|
|
253
|
+
`**Type**: ${entry.type}`,
|
|
254
|
+
`**Timestamp**: ${entry.timestamp.toISOString()}`,
|
|
255
|
+
`**Version**: ${entry.version}`,
|
|
256
|
+
'',
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
if (entry.parentId) {
|
|
260
|
+
lines.push(`**Parent**: ${entry.parentId}`, '');
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (entry.tags.length > 0) {
|
|
264
|
+
lines.push(`**Tags**: ${entry.tags.join(', ')}`, '');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
lines.push('## Content', '', entry.content, '');
|
|
268
|
+
|
|
269
|
+
if (Object.keys(entry.context).length > 0) {
|
|
270
|
+
lines.push('## Context', '', '```json');
|
|
271
|
+
lines.push(JSON.stringify(entry.context, null, 2));
|
|
272
|
+
lines.push('```', '');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (entry.metadata && Object.keys(entry.metadata).length > 0) {
|
|
276
|
+
lines.push('## Metadata', '', '```json');
|
|
277
|
+
lines.push(JSON.stringify(entry.metadata, null, 2));
|
|
278
|
+
lines.push('```', '');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return lines.join('\n');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite backend implementation for memory storage
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { IMemoryBackend } from './base.ts';
|
|
6
|
+
import { MemoryEntry, MemoryQuery } from '../../utils/types.ts';
|
|
7
|
+
import { ILogger } from '../../core/logger.ts';
|
|
8
|
+
import { MemoryBackendError } from '../../utils/errors.ts';
|
|
9
|
+
|
|
10
|
+
// SQLite bindings placeholder - in real implementation, use a proper SQLite library
|
|
11
|
+
interface Database {
|
|
12
|
+
execute(sql: string, params?: unknown[]): Promise<unknown[]>;
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* SQLite-based memory backend
|
|
18
|
+
*/
|
|
19
|
+
export class SQLiteBackend implements IMemoryBackend {
|
|
20
|
+
private db?: Database;
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
private dbPath: string,
|
|
24
|
+
private logger: ILogger,
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
async initialize(): Promise<void> {
|
|
28
|
+
this.logger.info('Initializing SQLite backend', { dbPath: this.dbPath });
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// In real implementation, open SQLite connection
|
|
32
|
+
// this.db = await openDatabase(this.dbPath);
|
|
33
|
+
|
|
34
|
+
// Create tables
|
|
35
|
+
await this.createTables();
|
|
36
|
+
|
|
37
|
+
// Create indexes
|
|
38
|
+
await this.createIndexes();
|
|
39
|
+
|
|
40
|
+
this.logger.info('SQLite backend initialized');
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new MemoryBackendError('Failed to initialize SQLite backend', { error });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async shutdown(): Promise<void> {
|
|
47
|
+
this.logger.info('Shutting down SQLite backend');
|
|
48
|
+
|
|
49
|
+
if (this.db) {
|
|
50
|
+
await this.db.close();
|
|
51
|
+
delete this.db;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async store(entry: MemoryEntry): Promise<void> {
|
|
56
|
+
if (!this.db) {
|
|
57
|
+
throw new MemoryBackendError('Database not initialized');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const sql = `
|
|
61
|
+
INSERT OR REPLACE INTO memory_entries (
|
|
62
|
+
id, agent_id, session_id, type, content,
|
|
63
|
+
context, timestamp, tags, version, parent_id, metadata
|
|
64
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
const params = [
|
|
68
|
+
entry.id,
|
|
69
|
+
entry.agentId,
|
|
70
|
+
entry.sessionId,
|
|
71
|
+
entry.type,
|
|
72
|
+
entry.content,
|
|
73
|
+
JSON.stringify(entry.context),
|
|
74
|
+
entry.timestamp.toISOString(),
|
|
75
|
+
JSON.stringify(entry.tags),
|
|
76
|
+
entry.version,
|
|
77
|
+
entry.parentId || null,
|
|
78
|
+
entry.metadata ? JSON.stringify(entry.metadata) : null,
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
await this.db.execute(sql, params);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throw new MemoryBackendError('Failed to store entry', { error });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async retrieve(id: string): Promise<MemoryEntry | undefined> {
|
|
89
|
+
if (!this.db) {
|
|
90
|
+
throw new MemoryBackendError('Database not initialized');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const sql = 'SELECT * FROM memory_entries WHERE id = ?';
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const rows = await this.db.execute(sql, [id]);
|
|
97
|
+
|
|
98
|
+
if (rows.length === 0) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return this.rowToEntry(rows[0] as Record<string, unknown>);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
throw new MemoryBackendError('Failed to retrieve entry', { error });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async update(id: string, entry: MemoryEntry): Promise<void> {
|
|
109
|
+
// SQLite INSERT OR REPLACE handles updates
|
|
110
|
+
await this.store(entry);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async delete(id: string): Promise<void> {
|
|
114
|
+
if (!this.db) {
|
|
115
|
+
throw new MemoryBackendError('Database not initialized');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const sql = 'DELETE FROM memory_entries WHERE id = ?';
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
await this.db.execute(sql, [id]);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
throw new MemoryBackendError('Failed to delete entry', { error });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async query(query: MemoryQuery): Promise<MemoryEntry[]> {
|
|
128
|
+
if (!this.db) {
|
|
129
|
+
throw new MemoryBackendError('Database not initialized');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const conditions: string[] = [];
|
|
133
|
+
const params: unknown[] = [];
|
|
134
|
+
|
|
135
|
+
if (query.agentId) {
|
|
136
|
+
conditions.push('agent_id = ?');
|
|
137
|
+
params.push(query.agentId);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (query.sessionId) {
|
|
141
|
+
conditions.push('session_id = ?');
|
|
142
|
+
params.push(query.sessionId);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (query.type) {
|
|
146
|
+
conditions.push('type = ?');
|
|
147
|
+
params.push(query.type);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (query.startTime) {
|
|
151
|
+
conditions.push('timestamp >= ?');
|
|
152
|
+
params.push(query.startTime.toISOString());
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (query.endTime) {
|
|
156
|
+
conditions.push('timestamp <= ?');
|
|
157
|
+
params.push(query.endTime.toISOString());
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (query.search) {
|
|
161
|
+
conditions.push('(content LIKE ? OR tags LIKE ?)');
|
|
162
|
+
params.push(`%${query.search}%`, `%${query.search}%`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (query.tags && query.tags.length > 0) {
|
|
166
|
+
const tagConditions = query.tags.map(() => 'tags LIKE ?');
|
|
167
|
+
conditions.push(`(${tagConditions.join(' OR ')})`);
|
|
168
|
+
query.tags.forEach(tag => params.push(`%"${tag}"%`));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let sql = 'SELECT * FROM memory_entries';
|
|
172
|
+
if (conditions.length > 0) {
|
|
173
|
+
sql += ' WHERE ' + conditions.join(' AND ');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
sql += ' ORDER BY timestamp DESC';
|
|
177
|
+
|
|
178
|
+
if (query.limit) {
|
|
179
|
+
sql += ' LIMIT ?';
|
|
180
|
+
params.push(query.limit);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (query.offset) {
|
|
184
|
+
sql += ' OFFSET ?';
|
|
185
|
+
params.push(query.offset);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const rows = await this.db.execute(sql, params);
|
|
190
|
+
return rows.map(row => this.rowToEntry(row as Record<string, unknown>));
|
|
191
|
+
} catch (error) {
|
|
192
|
+
throw new MemoryBackendError('Failed to query entries', { error });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async getAllEntries(): Promise<MemoryEntry[]> {
|
|
197
|
+
if (!this.db) {
|
|
198
|
+
throw new MemoryBackendError('Database not initialized');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const sql = 'SELECT * FROM memory_entries ORDER BY timestamp DESC';
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const rows = await this.db.execute(sql);
|
|
205
|
+
return rows.map(row => this.rowToEntry(row as Record<string, unknown>));
|
|
206
|
+
} catch (error) {
|
|
207
|
+
throw new MemoryBackendError('Failed to get all entries', { error });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async getHealthStatus(): Promise<{
|
|
212
|
+
healthy: boolean;
|
|
213
|
+
error?: string;
|
|
214
|
+
metrics?: Record<string, number>;
|
|
215
|
+
}> {
|
|
216
|
+
if (!this.db) {
|
|
217
|
+
return {
|
|
218
|
+
healthy: false,
|
|
219
|
+
error: 'Database not initialized',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
// Check database connectivity
|
|
225
|
+
await this.db.execute('SELECT 1');
|
|
226
|
+
|
|
227
|
+
// Get metrics
|
|
228
|
+
const [countResult] = await this.db.execute(
|
|
229
|
+
'SELECT COUNT(*) as count FROM memory_entries',
|
|
230
|
+
);
|
|
231
|
+
const entryCount = (countResult as any).count;
|
|
232
|
+
|
|
233
|
+
const [sizeResult] = await this.db.execute(
|
|
234
|
+
'SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size()',
|
|
235
|
+
);
|
|
236
|
+
const dbSize = (sizeResult as any).size;
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
healthy: true,
|
|
240
|
+
metrics: {
|
|
241
|
+
entryCount,
|
|
242
|
+
dbSizeBytes: dbSize,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
} catch (error) {
|
|
246
|
+
return {
|
|
247
|
+
healthy: false,
|
|
248
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private async createTables(): Promise<void> {
|
|
254
|
+
const sql = `
|
|
255
|
+
CREATE TABLE IF NOT EXISTS memory_entries (
|
|
256
|
+
id TEXT PRIMARY KEY,
|
|
257
|
+
agent_id TEXT NOT NULL,
|
|
258
|
+
session_id TEXT NOT NULL,
|
|
259
|
+
type TEXT NOT NULL,
|
|
260
|
+
content TEXT NOT NULL,
|
|
261
|
+
context TEXT NOT NULL,
|
|
262
|
+
timestamp TEXT NOT NULL,
|
|
263
|
+
tags TEXT NOT NULL,
|
|
264
|
+
version INTEGER NOT NULL,
|
|
265
|
+
parent_id TEXT,
|
|
266
|
+
metadata TEXT,
|
|
267
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
268
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
269
|
+
)
|
|
270
|
+
`;
|
|
271
|
+
|
|
272
|
+
await this.db!.execute(sql);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private async createIndexes(): Promise<void> {
|
|
276
|
+
const indexes = [
|
|
277
|
+
'CREATE INDEX IF NOT EXISTS idx_agent_id ON memory_entries(agent_id)',
|
|
278
|
+
'CREATE INDEX IF NOT EXISTS idx_session_id ON memory_entries(session_id)',
|
|
279
|
+
'CREATE INDEX IF NOT EXISTS idx_type ON memory_entries(type)',
|
|
280
|
+
'CREATE INDEX IF NOT EXISTS idx_timestamp ON memory_entries(timestamp)',
|
|
281
|
+
'CREATE INDEX IF NOT EXISTS idx_parent_id ON memory_entries(parent_id)',
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
for (const sql of indexes) {
|
|
285
|
+
await this.db!.execute(sql);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private rowToEntry(row: Record<string, unknown>): MemoryEntry {
|
|
290
|
+
const entry: MemoryEntry = {
|
|
291
|
+
id: row.id as string,
|
|
292
|
+
agentId: row.agent_id as string,
|
|
293
|
+
sessionId: row.session_id as string,
|
|
294
|
+
type: row.type as MemoryEntry['type'],
|
|
295
|
+
content: row.content as string,
|
|
296
|
+
context: JSON.parse(row.context as string),
|
|
297
|
+
timestamp: new Date(row.timestamp as string),
|
|
298
|
+
tags: JSON.parse(row.tags as string),
|
|
299
|
+
version: row.version as number,
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
if (row.parent_id) {
|
|
303
|
+
entry.parentId = row.parent_id as string;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (row.metadata) {
|
|
307
|
+
entry.metadata = JSON.parse(row.metadata as string);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return entry;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Placeholder implementation - replace with actual SQLite library
|
|
315
|
+
async function openDatabase(path: string): Promise<Database> {
|
|
316
|
+
// In real implementation, use a proper SQLite library like:
|
|
317
|
+
// - https://deno.land/x/sqlite
|
|
318
|
+
// - https://deno.land/x/sqlite3
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
async execute(sql: string, params?: unknown[]): Promise<unknown[]> {
|
|
322
|
+
// Placeholder
|
|
323
|
+
return [];
|
|
324
|
+
},
|
|
325
|
+
async close(): Promise<void> {
|
|
326
|
+
// Placeholder
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
}
|