@blackms/aistack 1.2.0 → 1.3.1

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.
Files changed (161) hide show
  1. package/README.md +152 -11
  2. package/dist/agents/definitions/adversarial.d.ts +6 -0
  3. package/dist/agents/definitions/adversarial.d.ts.map +1 -0
  4. package/dist/agents/definitions/adversarial.js +34 -0
  5. package/dist/agents/definitions/adversarial.js.map +1 -0
  6. package/dist/agents/definitions/devops.d.ts +6 -0
  7. package/dist/agents/definitions/devops.d.ts.map +1 -0
  8. package/dist/agents/definitions/devops.js +65 -0
  9. package/dist/agents/definitions/devops.js.map +1 -0
  10. package/dist/agents/definitions/documentation.d.ts +6 -0
  11. package/dist/agents/definitions/documentation.d.ts.map +1 -0
  12. package/dist/agents/definitions/documentation.js +72 -0
  13. package/dist/agents/definitions/documentation.js.map +1 -0
  14. package/dist/agents/definitions/index.d.ts +4 -0
  15. package/dist/agents/definitions/index.d.ts.map +1 -1
  16. package/dist/agents/definitions/index.js +4 -0
  17. package/dist/agents/definitions/index.js.map +1 -1
  18. package/dist/agents/definitions/security-auditor.d.ts +6 -0
  19. package/dist/agents/definitions/security-auditor.d.ts.map +1 -0
  20. package/dist/agents/definitions/security-auditor.js +100 -0
  21. package/dist/agents/definitions/security-auditor.js.map +1 -0
  22. package/dist/agents/registry.d.ts.map +1 -1
  23. package/dist/agents/registry.js +5 -1
  24. package/dist/agents/registry.js.map +1 -1
  25. package/dist/agents/spawner.d.ts +26 -1
  26. package/dist/agents/spawner.d.ts.map +1 -1
  27. package/dist/agents/spawner.js +94 -1
  28. package/dist/agents/spawner.js.map +1 -1
  29. package/dist/auth/index.d.ts +6 -0
  30. package/dist/auth/index.d.ts.map +1 -0
  31. package/dist/auth/index.js +6 -0
  32. package/dist/auth/index.js.map +1 -0
  33. package/dist/auth/service.d.ts +79 -0
  34. package/dist/auth/service.d.ts.map +1 -0
  35. package/dist/auth/service.js +383 -0
  36. package/dist/auth/service.js.map +1 -0
  37. package/dist/auth/types.d.ts +48 -0
  38. package/dist/auth/types.d.ts.map +1 -0
  39. package/dist/auth/types.js +10 -0
  40. package/dist/auth/types.js.map +1 -0
  41. package/dist/coordination/index.d.ts +1 -0
  42. package/dist/coordination/index.d.ts.map +1 -1
  43. package/dist/coordination/index.js +1 -0
  44. package/dist/coordination/index.js.map +1 -1
  45. package/dist/coordination/review-loop.d.ts +89 -0
  46. package/dist/coordination/review-loop.d.ts.map +1 -0
  47. package/dist/coordination/review-loop.js +341 -0
  48. package/dist/coordination/review-loop.js.map +1 -0
  49. package/dist/integrations/slack-notifier.d.ts +63 -0
  50. package/dist/integrations/slack-notifier.d.ts.map +1 -0
  51. package/dist/integrations/slack-notifier.js +224 -0
  52. package/dist/integrations/slack-notifier.js.map +1 -0
  53. package/dist/integrations/slack.d.ts +50 -0
  54. package/dist/integrations/slack.d.ts.map +1 -0
  55. package/dist/integrations/slack.js +225 -0
  56. package/dist/integrations/slack.js.map +1 -0
  57. package/dist/mcp/tools/index.d.ts +1 -0
  58. package/dist/mcp/tools/index.d.ts.map +1 -1
  59. package/dist/mcp/tools/index.js +1 -0
  60. package/dist/mcp/tools/index.js.map +1 -1
  61. package/dist/mcp/tools/review-loop-tools.d.ts +210 -0
  62. package/dist/mcp/tools/review-loop-tools.d.ts.map +1 -0
  63. package/dist/mcp/tools/review-loop-tools.js +213 -0
  64. package/dist/mcp/tools/review-loop-tools.js.map +1 -0
  65. package/dist/memory/index.d.ts +75 -0
  66. package/dist/memory/index.d.ts.map +1 -1
  67. package/dist/memory/index.js +101 -0
  68. package/dist/memory/index.js.map +1 -1
  69. package/dist/memory/sqlite-store.d.ts +85 -1
  70. package/dist/memory/sqlite-store.d.ts.map +1 -1
  71. package/dist/memory/sqlite-store.js +647 -15
  72. package/dist/memory/sqlite-store.js.map +1 -1
  73. package/dist/monitoring/health.d.ts +68 -0
  74. package/dist/monitoring/health.d.ts.map +1 -0
  75. package/dist/monitoring/health.js +220 -0
  76. package/dist/monitoring/health.js.map +1 -0
  77. package/dist/monitoring/metrics.d.ts +31 -0
  78. package/dist/monitoring/metrics.d.ts.map +1 -0
  79. package/dist/monitoring/metrics.js +230 -0
  80. package/dist/monitoring/metrics.js.map +1 -0
  81. package/dist/providers/index.d.ts.map +1 -1
  82. package/dist/providers/index.js +132 -94
  83. package/dist/providers/index.js.map +1 -1
  84. package/dist/types.d.ts +69 -1
  85. package/dist/types.d.ts.map +1 -1
  86. package/dist/types.js.map +1 -1
  87. package/dist/utils/config.d.ts.map +1 -1
  88. package/dist/utils/config.js +12 -0
  89. package/dist/utils/config.js.map +1 -1
  90. package/dist/utils/index.d.ts +1 -1
  91. package/dist/utils/index.d.ts.map +1 -1
  92. package/dist/utils/index.js +1 -1
  93. package/dist/utils/index.js.map +1 -1
  94. package/dist/utils/logger.d.ts +24 -7
  95. package/dist/utils/logger.d.ts.map +1 -1
  96. package/dist/utils/logger.js +128 -20
  97. package/dist/utils/logger.js.map +1 -1
  98. package/dist/utils/retry.d.ts +49 -0
  99. package/dist/utils/retry.d.ts.map +1 -0
  100. package/dist/utils/retry.js +160 -0
  101. package/dist/utils/retry.js.map +1 -0
  102. package/dist/utils/semaphore.d.ts +75 -0
  103. package/dist/utils/semaphore.d.ts.map +1 -0
  104. package/dist/utils/semaphore.js +185 -0
  105. package/dist/utils/semaphore.js.map +1 -0
  106. package/dist/utils/validation.d.ts +2 -2
  107. package/dist/web/middleware/auth.d.ts +16 -10
  108. package/dist/web/middleware/auth.d.ts.map +1 -1
  109. package/dist/web/middleware/auth.js +152 -12
  110. package/dist/web/middleware/auth.js.map +1 -1
  111. package/dist/web/routes/auth.d.ts +50 -0
  112. package/dist/web/routes/auth.d.ts.map +1 -0
  113. package/dist/web/routes/auth.js +216 -0
  114. package/dist/web/routes/auth.js.map +1 -0
  115. package/dist/web/routes/index.d.ts +2 -0
  116. package/dist/web/routes/index.d.ts.map +1 -1
  117. package/dist/web/routes/index.js +2 -0
  118. package/dist/web/routes/index.js.map +1 -1
  119. package/dist/web/routes/memory.d.ts.map +1 -1
  120. package/dist/web/routes/memory.js +188 -0
  121. package/dist/web/routes/memory.js.map +1 -1
  122. package/dist/web/routes/review-loops.d.ts +12 -0
  123. package/dist/web/routes/review-loops.d.ts.map +1 -0
  124. package/dist/web/routes/review-loops.js +157 -0
  125. package/dist/web/routes/review-loops.js.map +1 -0
  126. package/dist/web/routes/sessions.d.ts.map +1 -1
  127. package/dist/web/routes/sessions.js +14 -0
  128. package/dist/web/routes/sessions.js.map +1 -1
  129. package/dist/web/routes/system.d.ts.map +1 -1
  130. package/dist/web/routes/system.js +34 -23
  131. package/dist/web/routes/system.js.map +1 -1
  132. package/dist/web/routes/workflows.d.ts.map +1 -1
  133. package/dist/web/routes/workflows.js +13 -1
  134. package/dist/web/routes/workflows.js.map +1 -1
  135. package/dist/web/server.d.ts +1 -0
  136. package/dist/web/server.d.ts.map +1 -1
  137. package/dist/web/server.js +30 -2
  138. package/dist/web/server.js.map +1 -1
  139. package/dist/web/utils/request.d.ts +13 -0
  140. package/dist/web/utils/request.d.ts.map +1 -0
  141. package/dist/web/utils/request.js +49 -0
  142. package/dist/web/utils/request.js.map +1 -0
  143. package/dist/web/websocket/handler.d.ts +4 -0
  144. package/dist/web/websocket/handler.d.ts.map +1 -1
  145. package/dist/web/websocket/handler.js +59 -0
  146. package/dist/web/websocket/handler.js.map +1 -1
  147. package/dist/workflows/doc-sync.d.ts.map +1 -1
  148. package/dist/workflows/doc-sync.js +4 -0
  149. package/dist/workflows/doc-sync.js.map +1 -1
  150. package/dist/workflows/full-stack-feature.d.ts +74 -0
  151. package/dist/workflows/full-stack-feature.d.ts.map +1 -0
  152. package/dist/workflows/full-stack-feature.js +273 -0
  153. package/dist/workflows/full-stack-feature.js.map +1 -0
  154. package/dist/workflows/index.d.ts +1 -0
  155. package/dist/workflows/index.d.ts.map +1 -1
  156. package/dist/workflows/index.js +2 -0
  157. package/dist/workflows/index.js.map +1 -1
  158. package/dist/workflows/runner.js.map +1 -1
  159. package/dist/workflows/types.d.ts +6 -5
  160. package/dist/workflows/types.d.ts.map +1 -1
  161. package/package.json +12 -5
@@ -26,6 +26,62 @@ CREATE INDEX IF NOT EXISTS idx_memory_namespace ON memory(namespace);
26
26
  CREATE INDEX IF NOT EXISTS idx_memory_key ON memory(key);
27
27
  CREATE INDEX IF NOT EXISTS idx_memory_updated ON memory(updated_at DESC);
28
28
 
29
+ -- Tags table
30
+ CREATE TABLE IF NOT EXISTS tags (
31
+ id TEXT PRIMARY KEY,
32
+ name TEXT NOT NULL UNIQUE,
33
+ created_at INTEGER NOT NULL
34
+ );
35
+
36
+ CREATE INDEX IF NOT EXISTS idx_tags_name ON tags(name);
37
+
38
+ -- Memory-Tags junction table
39
+ CREATE TABLE IF NOT EXISTS memory_tags (
40
+ memory_id TEXT NOT NULL,
41
+ tag_id TEXT NOT NULL,
42
+ created_at INTEGER NOT NULL,
43
+ PRIMARY KEY (memory_id, tag_id),
44
+ FOREIGN KEY (memory_id) REFERENCES memory(id) ON DELETE CASCADE,
45
+ FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
46
+ );
47
+
48
+ CREATE INDEX IF NOT EXISTS idx_memory_tags_memory ON memory_tags(memory_id);
49
+ CREATE INDEX IF NOT EXISTS idx_memory_tags_tag ON memory_tags(tag_id);
50
+
51
+ -- Memory relationships table
52
+ CREATE TABLE IF NOT EXISTS memory_relationships (
53
+ id TEXT PRIMARY KEY,
54
+ from_id TEXT NOT NULL,
55
+ to_id TEXT NOT NULL,
56
+ relationship_type TEXT NOT NULL,
57
+ metadata TEXT,
58
+ created_at INTEGER NOT NULL,
59
+ FOREIGN KEY (from_id) REFERENCES memory(id) ON DELETE CASCADE,
60
+ FOREIGN KEY (to_id) REFERENCES memory(id) ON DELETE CASCADE,
61
+ UNIQUE(from_id, to_id, relationship_type)
62
+ );
63
+
64
+ CREATE INDEX IF NOT EXISTS idx_memory_relationships_from ON memory_relationships(from_id);
65
+ CREATE INDEX IF NOT EXISTS idx_memory_relationships_to ON memory_relationships(to_id);
66
+ CREATE INDEX IF NOT EXISTS idx_memory_relationships_type ON memory_relationships(relationship_type);
67
+
68
+ -- Memory versions table (for version history)
69
+ CREATE TABLE IF NOT EXISTS memory_versions (
70
+ id TEXT PRIMARY KEY,
71
+ memory_id TEXT NOT NULL,
72
+ version INTEGER NOT NULL,
73
+ key TEXT NOT NULL,
74
+ namespace TEXT NOT NULL,
75
+ content TEXT NOT NULL,
76
+ metadata TEXT,
77
+ created_at INTEGER NOT NULL,
78
+ FOREIGN KEY (memory_id) REFERENCES memory(id) ON DELETE CASCADE,
79
+ UNIQUE(memory_id, version)
80
+ );
81
+
82
+ CREATE INDEX IF NOT EXISTS idx_memory_versions_memory ON memory_versions(memory_id);
83
+ CREATE INDEX IF NOT EXISTS idx_memory_versions_created ON memory_versions(created_at DESC);
84
+
29
85
  -- Full-text search table
30
86
  CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
31
87
  key, content, namespace,
@@ -142,6 +198,45 @@ CREATE TABLE IF NOT EXISTS specifications (
142
198
 
143
199
  CREATE INDEX IF NOT EXISTS idx_specifications_task ON specifications(project_task_id);
144
200
  CREATE INDEX IF NOT EXISTS idx_specifications_status ON specifications(status);
201
+
202
+ -- Active Agents table for state persistence
203
+ CREATE TABLE IF NOT EXISTS active_agents (
204
+ id TEXT PRIMARY KEY,
205
+ type TEXT NOT NULL,
206
+ name TEXT NOT NULL UNIQUE,
207
+ status TEXT NOT NULL,
208
+ session_id TEXT,
209
+ created_at INTEGER NOT NULL,
210
+ updated_at INTEGER NOT NULL,
211
+ metadata TEXT,
212
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
213
+ );
214
+
215
+ CREATE INDEX IF NOT EXISTS idx_active_agents_status ON active_agents(status);
216
+ CREATE INDEX IF NOT EXISTS idx_active_agents_session ON active_agents(session_id);
217
+ CREATE INDEX IF NOT EXISTS idx_active_agents_name ON active_agents(name);
218
+
219
+ -- Review Loops table for state persistence
220
+ CREATE TABLE IF NOT EXISTS review_loops (
221
+ id TEXT PRIMARY KEY,
222
+ coder_id TEXT NOT NULL,
223
+ adversarial_id TEXT NOT NULL,
224
+ session_id TEXT,
225
+ status TEXT NOT NULL,
226
+ iteration INTEGER DEFAULT 0,
227
+ max_iterations INTEGER DEFAULT 3,
228
+ code_input TEXT NOT NULL,
229
+ current_code TEXT,
230
+ reviews TEXT,
231
+ final_verdict TEXT,
232
+ created_at INTEGER NOT NULL,
233
+ updated_at INTEGER NOT NULL,
234
+ completed_at INTEGER,
235
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
236
+ );
237
+
238
+ CREATE INDEX IF NOT EXISTS idx_review_loops_status ON review_loops(status);
239
+ CREATE INDEX IF NOT EXISTS idx_review_loops_session ON review_loops(session_id);
145
240
  `;
146
241
  export class SQLiteStore {
147
242
  db;
@@ -173,6 +268,11 @@ export class SQLiteStore {
173
268
  const id = existing?.id ?? randomUUID();
174
269
  const metadataJson = options.metadata ? JSON.stringify(options.metadata) : null;
175
270
  if (existing) {
271
+ // Save current version before updating
272
+ const currentEntry = this.getById(id);
273
+ if (currentEntry) {
274
+ this.saveVersion(currentEntry);
275
+ }
176
276
  // Update existing entry
177
277
  this.db
178
278
  .prepare(`
@@ -283,7 +383,335 @@ export class SQLiteStore {
283
383
  embedding: Array.from(new Float32Array(row.embedding.buffer)),
284
384
  }));
285
385
  }
386
+ // ==================== Tag Operations ====================
387
+ // Get tags for a specific memory entry
388
+ getEntryTags(entryId) {
389
+ const rows = this.db
390
+ .prepare(`
391
+ SELECT t.name
392
+ FROM tags t
393
+ INNER JOIN memory_tags mt ON t.id = mt.tag_id
394
+ WHERE mt.memory_id = ?
395
+ ORDER BY t.name
396
+ `)
397
+ .all(entryId);
398
+ return rows.map(row => row.name);
399
+ }
400
+ // Add a tag to a memory entry (creates tag if it doesn't exist)
401
+ addTag(entryId, tagName) {
402
+ const normalizedTag = tagName.trim().toLowerCase();
403
+ // Validate tag name
404
+ if (!normalizedTag) {
405
+ throw new Error('Tag name cannot be empty');
406
+ }
407
+ if (normalizedTag.length > 50) {
408
+ throw new Error('Tag name must be 50 characters or less');
409
+ }
410
+ if (!/^[a-z0-9_-]+$/.test(normalizedTag)) {
411
+ throw new Error('Tag name can only contain lowercase letters, numbers, hyphens, and underscores');
412
+ }
413
+ // Check if entry exists
414
+ const entry = this.get(entryId);
415
+ if (!entry) {
416
+ throw new Error(`Memory entry not found: ${entryId}`);
417
+ }
418
+ // Get or create tag
419
+ let tagRow = this.db
420
+ .prepare('SELECT id FROM tags WHERE name = ?')
421
+ .get(normalizedTag);
422
+ if (!tagRow) {
423
+ const tagId = randomUUID();
424
+ this.db
425
+ .prepare('INSERT INTO tags (id, name, created_at) VALUES (?, ?, ?)')
426
+ .run(tagId, normalizedTag, Date.now());
427
+ tagRow = { id: tagId };
428
+ }
429
+ // Add tag to entry (ignore if already exists)
430
+ try {
431
+ this.db
432
+ .prepare('INSERT INTO memory_tags (memory_id, tag_id, created_at) VALUES (?, ?, ?)')
433
+ .run(entryId, tagRow.id, Date.now());
434
+ }
435
+ catch (err) {
436
+ // Ignore duplicate key errors
437
+ if (!(err instanceof Error) || !err.message.includes('UNIQUE constraint failed')) {
438
+ throw err;
439
+ }
440
+ }
441
+ log.debug(`Tag "${normalizedTag}" added to entry ${entryId}`);
442
+ }
443
+ // Remove a tag from a memory entry
444
+ removeTag(entryId, tagName) {
445
+ const normalizedTag = tagName.trim().toLowerCase();
446
+ const result = this.db
447
+ .prepare(`
448
+ DELETE FROM memory_tags
449
+ WHERE memory_id = ?
450
+ AND tag_id = (SELECT id FROM tags WHERE name = ?)
451
+ `)
452
+ .run(entryId, normalizedTag);
453
+ log.debug(`Tag "${normalizedTag}" removed from entry ${entryId}`);
454
+ return result.changes > 0;
455
+ }
456
+ // Get all unique tags with usage counts
457
+ getAllTags() {
458
+ const rows = this.db
459
+ .prepare(`
460
+ SELECT t.name, COUNT(mt.memory_id) as count
461
+ FROM tags t
462
+ LEFT JOIN memory_tags mt ON t.id = mt.tag_id
463
+ GROUP BY t.id, t.name
464
+ ORDER BY count DESC, t.name
465
+ `)
466
+ .all();
467
+ return rows;
468
+ }
469
+ // Search entries by tags (AND logic - entry must have all specified tags)
470
+ searchByTags(tags, namespace) {
471
+ if (tags.length === 0) {
472
+ return [];
473
+ }
474
+ const normalizedTags = tags.map(t => t.trim().toLowerCase());
475
+ const placeholders = normalizedTags.map(() => '?').join(',');
476
+ let query = `
477
+ SELECT m.*
478
+ FROM memory m
479
+ WHERE m.id IN (
480
+ SELECT mt.memory_id
481
+ FROM memory_tags mt
482
+ INNER JOIN tags t ON mt.tag_id = t.id
483
+ WHERE t.name IN (${placeholders})
484
+ GROUP BY mt.memory_id
485
+ HAVING COUNT(DISTINCT t.name) = ?
486
+ )
487
+ `;
488
+ const params = [...normalizedTags, normalizedTags.length];
489
+ if (namespace) {
490
+ query += ' AND m.namespace = ?';
491
+ params.push(namespace);
492
+ }
493
+ query += ' ORDER BY m.updated_at DESC';
494
+ const rows = this.db.prepare(query).all(...params);
495
+ return rows.map(row => this.rowToEntry(row));
496
+ }
497
+ // ==================== Relationship Operations ====================
498
+ // Create a relationship between two memory entries
499
+ createRelationship(fromId, toId, relationshipType, metadata) {
500
+ // Validate input
501
+ if (!fromId || !toId) {
502
+ throw new Error('Both fromId and toId are required');
503
+ }
504
+ if (fromId === toId) {
505
+ throw new Error('Cannot create relationship to self');
506
+ }
507
+ // Validate relationship type
508
+ const validTypes = [
509
+ 'related_to',
510
+ 'derived_from',
511
+ 'references',
512
+ 'depends_on',
513
+ 'supersedes',
514
+ 'conflicts_with',
515
+ 'validates'
516
+ ];
517
+ if (!validTypes.includes(relationshipType)) {
518
+ throw new Error(`Invalid relationship type: ${relationshipType}. Valid types: ${validTypes.join(', ')}`);
519
+ }
520
+ // Validate that both entries exist
521
+ const fromEntry = this.getById(fromId);
522
+ const toEntry = this.getById(toId);
523
+ if (!fromEntry) {
524
+ throw new Error(`Source entry not found: ${fromId}`);
525
+ }
526
+ if (!toEntry) {
527
+ throw new Error(`Target entry not found: ${toId}`);
528
+ }
529
+ const id = randomUUID();
530
+ const metadataJson = metadata ? JSON.stringify(metadata) : null;
531
+ try {
532
+ this.db
533
+ .prepare(`
534
+ INSERT INTO memory_relationships (id, from_id, to_id, relationship_type, metadata, created_at)
535
+ VALUES (?, ?, ?, ?, ?, ?)
536
+ `)
537
+ .run(id, fromId, toId, relationshipType, metadataJson, Date.now());
538
+ log.debug(`Relationship created: ${fromId} --[${relationshipType}]--> ${toId}`);
539
+ return id;
540
+ }
541
+ catch (err) {
542
+ if (err instanceof Error && err.message.includes('UNIQUE constraint failed')) {
543
+ throw new Error('Relationship already exists');
544
+ }
545
+ throw err;
546
+ }
547
+ }
548
+ // Get relationships for an entry
549
+ getRelationships(entryId, direction = 'both') {
550
+ let query = 'SELECT * FROM memory_relationships WHERE ';
551
+ const params = [];
552
+ if (direction === 'outgoing') {
553
+ query += 'from_id = ?';
554
+ params.push(entryId);
555
+ }
556
+ else if (direction === 'incoming') {
557
+ query += 'to_id = ?';
558
+ params.push(entryId);
559
+ }
560
+ else {
561
+ query += '(from_id = ? OR to_id = ?)';
562
+ params.push(entryId, entryId);
563
+ }
564
+ query += ' ORDER BY created_at DESC';
565
+ const rows = this.db.prepare(query).all(...params);
566
+ return rows.map(row => ({
567
+ id: row.id,
568
+ fromId: row.from_id,
569
+ toId: row.to_id,
570
+ relationshipType: row.relationship_type,
571
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
572
+ createdAt: new Date(row.created_at),
573
+ }));
574
+ }
575
+ // Get related entries (follows relationships and fetches the connected entries)
576
+ getRelatedEntries(entryId, relationshipType) {
577
+ let query = `
578
+ SELECT m.*, r.id as rel_id, r.relationship_type, r.from_id, r.to_id
579
+ FROM memory_relationships r
580
+ INNER JOIN memory m ON (
581
+ CASE
582
+ WHEN r.from_id = ? THEN m.id = r.to_id
583
+ WHEN r.to_id = ? THEN m.id = r.from_id
584
+ END
585
+ )
586
+ WHERE (r.from_id = ? OR r.to_id = ?)
587
+ `;
588
+ const params = [entryId, entryId, entryId, entryId];
589
+ if (relationshipType) {
590
+ query += ' AND r.relationship_type = ?';
591
+ params.push(relationshipType);
592
+ }
593
+ query += ' ORDER BY r.created_at DESC';
594
+ const rows = this.db.prepare(query).all(...params);
595
+ return rows.map(row => ({
596
+ entry: this.rowToEntry(row),
597
+ relationship: {
598
+ id: row.rel_id,
599
+ type: row.relationship_type,
600
+ direction: row.from_id === entryId ? 'outgoing' : 'incoming',
601
+ },
602
+ }));
603
+ }
604
+ // Delete a relationship
605
+ deleteRelationship(relationshipId) {
606
+ const result = this.db
607
+ .prepare('DELETE FROM memory_relationships WHERE id = ?')
608
+ .run(relationshipId);
609
+ log.debug(`Relationship deleted: ${relationshipId}`);
610
+ return result.changes > 0;
611
+ }
612
+ // Delete all relationships for an entry
613
+ deleteAllRelationships(entryId) {
614
+ const result = this.db
615
+ .prepare('DELETE FROM memory_relationships WHERE from_id = ? OR to_id = ?')
616
+ .run(entryId, entryId);
617
+ log.debug(`All relationships deleted for entry: ${entryId}`);
618
+ return result.changes;
619
+ }
620
+ // ==================== Version Operations ====================
621
+ // Save current state as a version (called before updates)
622
+ saveVersion(entry) {
623
+ // Get the next version number
624
+ const versionRow = this.db
625
+ .prepare('SELECT COALESCE(MAX(version), 0) as max_version FROM memory_versions WHERE memory_id = ?')
626
+ .get(entry.id);
627
+ const nextVersion = versionRow.max_version + 1;
628
+ const versionId = randomUUID();
629
+ const metadataJson = entry.metadata ? JSON.stringify(entry.metadata) : null;
630
+ this.db
631
+ .prepare(`
632
+ INSERT INTO memory_versions (id, memory_id, version, key, namespace, content, metadata, created_at)
633
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
634
+ `)
635
+ .run(versionId, entry.id, nextVersion, entry.key, entry.namespace, entry.content, metadataJson, entry.updatedAt.getTime());
636
+ log.debug(`Version saved for entry ${entry.id}: v${nextVersion}`);
637
+ }
638
+ // Get version history for an entry
639
+ getVersionHistory(entryId) {
640
+ const rows = this.db
641
+ .prepare(`
642
+ SELECT id, memory_id, version, key, namespace, content, metadata, created_at
643
+ FROM memory_versions
644
+ WHERE memory_id = ?
645
+ ORDER BY version DESC
646
+ `)
647
+ .all(entryId);
648
+ return rows.map(row => ({
649
+ id: row.id,
650
+ memoryId: row.memory_id,
651
+ version: row.version,
652
+ key: row.key,
653
+ namespace: row.namespace,
654
+ content: row.content,
655
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
656
+ createdAt: new Date(row.created_at),
657
+ }));
658
+ }
659
+ // Get a specific version of an entry
660
+ getVersion(entryId, version) {
661
+ const row = this.db
662
+ .prepare(`
663
+ SELECT id, memory_id, version, key, namespace, content, metadata, created_at
664
+ FROM memory_versions
665
+ WHERE memory_id = ? AND version = ?
666
+ `)
667
+ .get(entryId, version);
668
+ if (!row)
669
+ return null;
670
+ return {
671
+ id: row.id,
672
+ memoryId: row.memory_id,
673
+ version: row.version,
674
+ key: row.key,
675
+ namespace: row.namespace,
676
+ content: row.content,
677
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
678
+ createdAt: new Date(row.created_at),
679
+ };
680
+ }
681
+ // Get the current version number for an entry
682
+ getCurrentVersion(entryId) {
683
+ const row = this.db
684
+ .prepare('SELECT COALESCE(MAX(version), 0) as version FROM memory_versions WHERE memory_id = ?')
685
+ .get(entryId);
686
+ return row.version;
687
+ }
688
+ // Restore a specific version (creates a new version with old content)
689
+ restoreVersion(entryId, version) {
690
+ const versionEntry = this.getVersion(entryId, version);
691
+ if (!versionEntry) {
692
+ return false;
693
+ }
694
+ const currentEntry = this.getById(entryId);
695
+ if (!currentEntry) {
696
+ return false;
697
+ }
698
+ // Save current state before restoring
699
+ this.saveVersion(currentEntry);
700
+ // Update with old content
701
+ const metadataJson = versionEntry.metadata ? JSON.stringify(versionEntry.metadata) : null;
702
+ this.db
703
+ .prepare(`
704
+ UPDATE memory
705
+ SET content = ?, metadata = ?, updated_at = ?
706
+ WHERE id = ?
707
+ `)
708
+ .run(versionEntry.content, metadataJson, Date.now(), entryId);
709
+ log.info(`Version ${version} restored for entry ${entryId}`);
710
+ return true;
711
+ }
286
712
  rowToEntry(row) {
713
+ // Fetch tags for this entry
714
+ const tags = this.getEntryTags(row.id);
287
715
  return {
288
716
  id: row.id,
289
717
  key: row.key,
@@ -293,6 +721,7 @@ export class SQLiteStore {
293
721
  ? new Float32Array(row.embedding.buffer)
294
722
  : undefined,
295
723
  metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
724
+ tags: tags.length > 0 ? tags : undefined,
296
725
  createdAt: new Date(row.created_at),
297
726
  updatedAt: new Date(row.updated_at),
298
727
  };
@@ -342,6 +771,18 @@ export class SQLiteStore {
342
771
  .get();
343
772
  return row ? this.rowToSession(row) : null;
344
773
  }
774
+ listSessions(status, limit = 50, offset = 0) {
775
+ let query = 'SELECT * FROM sessions';
776
+ const params = [];
777
+ if (status) {
778
+ query += ' WHERE status = ?';
779
+ params.push(status);
780
+ }
781
+ query += ' ORDER BY started_at DESC LIMIT ? OFFSET ?';
782
+ params.push(limit, offset);
783
+ const rows = this.db.prepare(query).all(...params);
784
+ return rows.map(row => this.rowToSession(row));
785
+ }
345
786
  rowToSession(row) {
346
787
  return {
347
788
  id: row.id,
@@ -483,15 +924,18 @@ export class SQLiteStore {
483
924
  return rows.map(row => this.rowToProject(row));
484
925
  }
485
926
  deleteProject(id) {
486
- // First delete related project tasks and specifications
487
- const tasks = this.listProjectTasks(id);
488
- for (const task of tasks) {
489
- this.deleteProjectTask(task.id);
490
- }
491
- const result = this.db
492
- .prepare('DELETE FROM projects WHERE id = ?')
493
- .run(id);
494
- return result.changes > 0;
927
+ // Use transaction to ensure atomic deletion of project and related data
928
+ return this.transaction((db) => {
929
+ // First delete specifications for each project task
930
+ const tasks = this.listProjectTasks(id);
931
+ for (const task of tasks) {
932
+ db.prepare('DELETE FROM specifications WHERE project_task_id = ?').run(task.id);
933
+ db.prepare('DELETE FROM project_tasks WHERE id = ?').run(task.id);
934
+ }
935
+ // Delete the project itself
936
+ const result = db.prepare('DELETE FROM projects WHERE id = ?').run(id);
937
+ return result.changes > 0;
938
+ });
495
939
  }
496
940
  rowToProject(row) {
497
941
  return {
@@ -591,12 +1035,14 @@ export class SQLiteStore {
591
1035
  return rows.map(row => this.rowToProjectTask(row));
592
1036
  }
593
1037
  deleteProjectTask(id) {
594
- // First delete related specifications
595
- this.db.prepare('DELETE FROM specifications WHERE project_task_id = ?').run(id);
596
- const result = this.db
597
- .prepare('DELETE FROM project_tasks WHERE id = ?')
598
- .run(id);
599
- return result.changes > 0;
1038
+ // Use transaction to ensure atomic deletion
1039
+ return this.transaction((db) => {
1040
+ // First delete related specifications
1041
+ db.prepare('DELETE FROM specifications WHERE project_task_id = ?').run(id);
1042
+ // Delete the task itself
1043
+ const result = db.prepare('DELETE FROM project_tasks WHERE id = ?').run(id);
1044
+ return result.changes > 0;
1045
+ });
600
1046
  }
601
1047
  rowToProjectTask(row) {
602
1048
  return {
@@ -714,7 +1160,193 @@ export class SQLiteStore {
714
1160
  comments: row.comments ? JSON.parse(row.comments) : undefined,
715
1161
  };
716
1162
  }
1163
+ // ==================== Active Agents Persistence ====================
1164
+ /**
1165
+ * Save active agent state
1166
+ */
1167
+ saveActiveAgent(agent) {
1168
+ const now = Date.now();
1169
+ const metadataJson = agent.metadata ? JSON.stringify(agent.metadata) : null;
1170
+ const existing = this.db
1171
+ .prepare('SELECT id FROM active_agents WHERE id = ?')
1172
+ .get(agent.id);
1173
+ if (existing) {
1174
+ this.db
1175
+ .prepare(`
1176
+ UPDATE active_agents
1177
+ SET type = ?, name = ?, status = ?, session_id = ?, updated_at = ?, metadata = ?
1178
+ WHERE id = ?
1179
+ `)
1180
+ .run(agent.type, agent.name, agent.status, agent.sessionId ?? null, now, metadataJson, agent.id);
1181
+ }
1182
+ else {
1183
+ this.db
1184
+ .prepare(`
1185
+ INSERT INTO active_agents (id, type, name, status, session_id, created_at, updated_at, metadata)
1186
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1187
+ `)
1188
+ .run(agent.id, agent.type, agent.name, agent.status, agent.sessionId ?? null, agent.createdAt.getTime(), now, metadataJson);
1189
+ }
1190
+ }
1191
+ /**
1192
+ * Load active agents
1193
+ */
1194
+ loadActiveAgents() {
1195
+ const rows = this.db
1196
+ .prepare('SELECT * FROM active_agents WHERE status IN (?, ?, ?)')
1197
+ .all('idle', 'running', 'stopped');
1198
+ return rows.map(row => ({
1199
+ id: row.id,
1200
+ type: row.type,
1201
+ name: row.name,
1202
+ status: row.status,
1203
+ sessionId: row.session_id ?? undefined,
1204
+ createdAt: new Date(row.created_at),
1205
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
1206
+ }));
1207
+ }
1208
+ /**
1209
+ * Delete active agent
1210
+ */
1211
+ deleteActiveAgent(agentId) {
1212
+ const result = this.db
1213
+ .prepare('DELETE FROM active_agents WHERE id = ?')
1214
+ .run(agentId);
1215
+ return result.changes > 0;
1216
+ }
1217
+ /**
1218
+ * Update agent status
1219
+ */
1220
+ updateAgentStatus(agentId, status) {
1221
+ const now = Date.now();
1222
+ const result = this.db
1223
+ .prepare('UPDATE active_agents SET status = ?, updated_at = ? WHERE id = ?')
1224
+ .run(status, now, agentId);
1225
+ return result.changes > 0;
1226
+ }
1227
+ /**
1228
+ * Clear completed/failed agents
1229
+ */
1230
+ clearInactiveAgents() {
1231
+ this.db
1232
+ .prepare('DELETE FROM active_agents WHERE status IN (?, ?)')
1233
+ .run('completed', 'failed');
1234
+ }
1235
+ // ==================== Review Loops Persistence ====================
1236
+ /**
1237
+ * Save review loop state
1238
+ */
1239
+ saveReviewLoop(loopId, state) {
1240
+ const now = Date.now();
1241
+ const reviewsJson = JSON.stringify(state.reviews);
1242
+ const finalVerdictJson = state.finalVerdict ? JSON.stringify(state.finalVerdict) : null;
1243
+ const existing = this.db
1244
+ .prepare('SELECT id FROM review_loops WHERE id = ?')
1245
+ .get(loopId);
1246
+ if (existing) {
1247
+ this.db
1248
+ .prepare(`
1249
+ UPDATE review_loops
1250
+ SET status = ?, iteration = ?, current_code = ?, reviews = ?,
1251
+ final_verdict = ?, updated_at = ?, completed_at = ?
1252
+ WHERE id = ?
1253
+ `)
1254
+ .run(state.status, state.iteration, state.currentCode ?? null, reviewsJson, finalVerdictJson, now, state.completedAt?.getTime() ?? null, loopId);
1255
+ }
1256
+ else {
1257
+ this.db
1258
+ .prepare(`
1259
+ INSERT INTO review_loops (
1260
+ id, coder_id, adversarial_id, session_id, status,
1261
+ iteration, max_iterations, code_input, current_code,
1262
+ reviews, final_verdict, created_at, updated_at, completed_at
1263
+ )
1264
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1265
+ `)
1266
+ .run(loopId, state.coderId, state.adversarialId, state.sessionId ?? null, state.status, state.iteration, state.maxIterations, state.codeInput, state.currentCode ?? null, reviewsJson, finalVerdictJson, state.startedAt.getTime(), now, state.completedAt?.getTime() ?? null);
1267
+ }
1268
+ }
1269
+ /**
1270
+ * Load review loop state
1271
+ */
1272
+ loadReviewLoop(loopId) {
1273
+ const row = this.db
1274
+ .prepare('SELECT * FROM review_loops WHERE id = ?')
1275
+ .get(loopId);
1276
+ if (!row)
1277
+ return null;
1278
+ return {
1279
+ id: row.id,
1280
+ coderId: row.coder_id,
1281
+ adversarialId: row.adversarial_id,
1282
+ sessionId: row.session_id ?? undefined,
1283
+ status: row.status,
1284
+ iteration: row.iteration,
1285
+ maxIterations: row.max_iterations,
1286
+ codeInput: row.code_input,
1287
+ currentCode: row.current_code ?? undefined,
1288
+ reviews: JSON.parse(row.reviews),
1289
+ finalVerdict: row.final_verdict ? JSON.parse(row.final_verdict) : undefined,
1290
+ startedAt: new Date(row.created_at),
1291
+ completedAt: row.completed_at ? new Date(row.completed_at) : undefined,
1292
+ };
1293
+ }
1294
+ /**
1295
+ * Load active review loops
1296
+ */
1297
+ loadActiveReviewLoops() {
1298
+ const rows = this.db
1299
+ .prepare('SELECT * FROM review_loops WHERE status IN (?, ?, ?, ?)')
1300
+ .all('pending', 'coding', 'reviewing', 'fixing');
1301
+ return rows.map(row => ({
1302
+ id: row.id,
1303
+ coderId: row.coder_id,
1304
+ adversarialId: row.adversarial_id,
1305
+ sessionId: row.session_id ?? undefined,
1306
+ status: row.status,
1307
+ iteration: row.iteration,
1308
+ maxIterations: row.max_iterations,
1309
+ codeInput: row.code_input,
1310
+ currentCode: row.current_code ?? undefined,
1311
+ reviews: JSON.parse(row.reviews),
1312
+ finalVerdict: row.final_verdict ? JSON.parse(row.final_verdict) : undefined,
1313
+ startedAt: new Date(row.created_at),
1314
+ completedAt: row.completed_at ? new Date(row.completed_at) : undefined,
1315
+ }));
1316
+ }
1317
+ /**
1318
+ * Delete review loop
1319
+ */
1320
+ deleteReviewLoop(loopId) {
1321
+ const result = this.db
1322
+ .prepare('DELETE FROM review_loops WHERE id = ?')
1323
+ .run(loopId);
1324
+ return result.changes > 0;
1325
+ }
1326
+ /**
1327
+ * Clear completed review loops
1328
+ */
1329
+ clearCompletedReviewLoops() {
1330
+ this.db
1331
+ .prepare('DELETE FROM review_loops WHERE status IN (?, ?)')
1332
+ .run('approved', 'failed');
1333
+ }
717
1334
  // ==================== Cleanup ====================
1335
+ /**
1336
+ * Get the underlying database instance
1337
+ * Used for authentication and other features that need direct DB access
1338
+ */
1339
+ getDatabase() {
1340
+ return this.db;
1341
+ }
1342
+ /**
1343
+ * Execute a function within a transaction
1344
+ * Automatically commits on success and rolls back on error
1345
+ */
1346
+ transaction(fn) {
1347
+ const transaction = this.db.transaction(fn);
1348
+ return transaction(this.db);
1349
+ }
718
1350
  close() {
719
1351
  this.db.close();
720
1352
  log.debug('SQLite store closed');