@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.
- package/README.md +152 -11
- package/dist/agents/definitions/adversarial.d.ts +6 -0
- package/dist/agents/definitions/adversarial.d.ts.map +1 -0
- package/dist/agents/definitions/adversarial.js +34 -0
- package/dist/agents/definitions/adversarial.js.map +1 -0
- package/dist/agents/definitions/devops.d.ts +6 -0
- package/dist/agents/definitions/devops.d.ts.map +1 -0
- package/dist/agents/definitions/devops.js +65 -0
- package/dist/agents/definitions/devops.js.map +1 -0
- package/dist/agents/definitions/documentation.d.ts +6 -0
- package/dist/agents/definitions/documentation.d.ts.map +1 -0
- package/dist/agents/definitions/documentation.js +72 -0
- package/dist/agents/definitions/documentation.js.map +1 -0
- package/dist/agents/definitions/index.d.ts +4 -0
- package/dist/agents/definitions/index.d.ts.map +1 -1
- package/dist/agents/definitions/index.js +4 -0
- package/dist/agents/definitions/index.js.map +1 -1
- package/dist/agents/definitions/security-auditor.d.ts +6 -0
- package/dist/agents/definitions/security-auditor.d.ts.map +1 -0
- package/dist/agents/definitions/security-auditor.js +100 -0
- package/dist/agents/definitions/security-auditor.js.map +1 -0
- package/dist/agents/registry.d.ts.map +1 -1
- package/dist/agents/registry.js +5 -1
- package/dist/agents/registry.js.map +1 -1
- package/dist/agents/spawner.d.ts +26 -1
- package/dist/agents/spawner.d.ts.map +1 -1
- package/dist/agents/spawner.js +94 -1
- package/dist/agents/spawner.js.map +1 -1
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +6 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/service.d.ts +79 -0
- package/dist/auth/service.d.ts.map +1 -0
- package/dist/auth/service.js +383 -0
- package/dist/auth/service.js.map +1 -0
- package/dist/auth/types.d.ts +48 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +10 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/coordination/index.d.ts +1 -0
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +1 -0
- package/dist/coordination/index.js.map +1 -1
- package/dist/coordination/review-loop.d.ts +89 -0
- package/dist/coordination/review-loop.d.ts.map +1 -0
- package/dist/coordination/review-loop.js +341 -0
- package/dist/coordination/review-loop.js.map +1 -0
- package/dist/integrations/slack-notifier.d.ts +63 -0
- package/dist/integrations/slack-notifier.d.ts.map +1 -0
- package/dist/integrations/slack-notifier.js +224 -0
- package/dist/integrations/slack-notifier.js.map +1 -0
- package/dist/integrations/slack.d.ts +50 -0
- package/dist/integrations/slack.d.ts.map +1 -0
- package/dist/integrations/slack.js +225 -0
- package/dist/integrations/slack.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +1 -0
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js +1 -0
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/review-loop-tools.d.ts +210 -0
- package/dist/mcp/tools/review-loop-tools.d.ts.map +1 -0
- package/dist/mcp/tools/review-loop-tools.js +213 -0
- package/dist/mcp/tools/review-loop-tools.js.map +1 -0
- package/dist/memory/index.d.ts +75 -0
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +101 -0
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/sqlite-store.d.ts +85 -1
- package/dist/memory/sqlite-store.d.ts.map +1 -1
- package/dist/memory/sqlite-store.js +647 -15
- package/dist/memory/sqlite-store.js.map +1 -1
- package/dist/monitoring/health.d.ts +68 -0
- package/dist/monitoring/health.d.ts.map +1 -0
- package/dist/monitoring/health.js +220 -0
- package/dist/monitoring/health.js.map +1 -0
- package/dist/monitoring/metrics.d.ts +31 -0
- package/dist/monitoring/metrics.d.ts.map +1 -0
- package/dist/monitoring/metrics.js +230 -0
- package/dist/monitoring/metrics.js.map +1 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +132 -94
- package/dist/providers/index.js.map +1 -1
- package/dist/types.d.ts +69 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +12 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.d.ts +24 -7
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +128 -20
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/retry.d.ts +49 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +160 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/semaphore.d.ts +75 -0
- package/dist/utils/semaphore.d.ts.map +1 -0
- package/dist/utils/semaphore.js +185 -0
- package/dist/utils/semaphore.js.map +1 -0
- package/dist/utils/validation.d.ts +2 -2
- package/dist/web/middleware/auth.d.ts +16 -10
- package/dist/web/middleware/auth.d.ts.map +1 -1
- package/dist/web/middleware/auth.js +152 -12
- package/dist/web/middleware/auth.js.map +1 -1
- package/dist/web/routes/auth.d.ts +50 -0
- package/dist/web/routes/auth.d.ts.map +1 -0
- package/dist/web/routes/auth.js +216 -0
- package/dist/web/routes/auth.js.map +1 -0
- package/dist/web/routes/index.d.ts +2 -0
- package/dist/web/routes/index.d.ts.map +1 -1
- package/dist/web/routes/index.js +2 -0
- package/dist/web/routes/index.js.map +1 -1
- package/dist/web/routes/memory.d.ts.map +1 -1
- package/dist/web/routes/memory.js +188 -0
- package/dist/web/routes/memory.js.map +1 -1
- package/dist/web/routes/review-loops.d.ts +12 -0
- package/dist/web/routes/review-loops.d.ts.map +1 -0
- package/dist/web/routes/review-loops.js +157 -0
- package/dist/web/routes/review-loops.js.map +1 -0
- package/dist/web/routes/sessions.d.ts.map +1 -1
- package/dist/web/routes/sessions.js +14 -0
- package/dist/web/routes/sessions.js.map +1 -1
- package/dist/web/routes/system.d.ts.map +1 -1
- package/dist/web/routes/system.js +34 -23
- package/dist/web/routes/system.js.map +1 -1
- package/dist/web/routes/workflows.d.ts.map +1 -1
- package/dist/web/routes/workflows.js +13 -1
- package/dist/web/routes/workflows.js.map +1 -1
- package/dist/web/server.d.ts +1 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +30 -2
- package/dist/web/server.js.map +1 -1
- package/dist/web/utils/request.d.ts +13 -0
- package/dist/web/utils/request.d.ts.map +1 -0
- package/dist/web/utils/request.js +49 -0
- package/dist/web/utils/request.js.map +1 -0
- package/dist/web/websocket/handler.d.ts +4 -0
- package/dist/web/websocket/handler.d.ts.map +1 -1
- package/dist/web/websocket/handler.js +59 -0
- package/dist/web/websocket/handler.js.map +1 -1
- package/dist/workflows/doc-sync.d.ts.map +1 -1
- package/dist/workflows/doc-sync.js +4 -0
- package/dist/workflows/doc-sync.js.map +1 -1
- package/dist/workflows/full-stack-feature.d.ts +74 -0
- package/dist/workflows/full-stack-feature.d.ts.map +1 -0
- package/dist/workflows/full-stack-feature.js +273 -0
- package/dist/workflows/full-stack-feature.js.map +1 -0
- package/dist/workflows/index.d.ts +1 -0
- package/dist/workflows/index.d.ts.map +1 -1
- package/dist/workflows/index.js +2 -0
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/runner.js.map +1 -1
- package/dist/workflows/types.d.ts +6 -5
- package/dist/workflows/types.d.ts.map +1 -1
- 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
|
-
//
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
this.
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
-
//
|
|
595
|
-
this.db
|
|
596
|
-
|
|
597
|
-
.prepare('DELETE FROM
|
|
598
|
-
|
|
599
|
-
|
|
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');
|