claude-memory-layer 1.0.8 → 1.0.10
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/.claude/settings.local.json +7 -1
- package/.claude-memory/test.sqlite +0 -0
- package/.history/package_20260202114053.json +49 -0
- package/.history/package_20260202121115.json +49 -0
- package/HANDOFF.md +92 -0
- package/dist/cli/index.js +1257 -74
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +1111 -47
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/post-tool-use.js +5693 -0
- package/dist/hooks/post-tool-use.js.map +7 -0
- package/dist/hooks/session-end.js +1224 -67
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +1219 -66
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +1224 -67
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +1252 -98
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +1252 -73
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +1252 -73
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +1246 -68
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/app.js +304 -0
- package/dist/ui/index.html +195 -1188
- package/dist/ui/style.css +595 -0
- package/package.json +3 -1
- package/scripts/build.ts +2 -0
- package/src/core/event-store.ts +18 -0
- package/src/core/index.ts +3 -0
- package/src/core/retriever.ts +4 -1
- package/src/core/sqlite-event-store.ts +947 -0
- package/src/core/sqlite-wrapper.ts +108 -0
- package/src/core/sync-worker.ts +228 -0
- package/src/core/vector-worker.ts +44 -14
- package/src/hooks/user-prompt-submit.ts +40 -17
- package/src/server/api/stats.ts +37 -7
- package/src/services/memory-service.ts +239 -43
- package/src/ui/app.js +304 -0
- package/src/ui/index.html +195 -1188
- package/src/ui/style.css +595 -0
- package/test_access.js +49 -0
package/dist/core/index.js
CHANGED
|
@@ -1273,31 +1273,1059 @@ var EventStore = class {
|
|
|
1273
1273
|
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
1274
1274
|
}));
|
|
1275
1275
|
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Increment access count for events (stub for compatibility)
|
|
1278
|
+
*/
|
|
1279
|
+
async incrementAccessCount(eventIds) {
|
|
1280
|
+
return Promise.resolve();
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Get most accessed memories (stub for compatibility)
|
|
1284
|
+
*/
|
|
1285
|
+
async getMostAccessed(limit = 10) {
|
|
1286
|
+
return [];
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Close database connection
|
|
1290
|
+
*/
|
|
1291
|
+
async close() {
|
|
1292
|
+
await dbClose(this.db);
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Convert database row to MemoryEvent
|
|
1296
|
+
*/
|
|
1297
|
+
rowToEvent(row) {
|
|
1298
|
+
return {
|
|
1299
|
+
id: row.id,
|
|
1300
|
+
eventType: row.event_type,
|
|
1301
|
+
sessionId: row.session_id,
|
|
1302
|
+
timestamp: toDate(row.timestamp),
|
|
1303
|
+
content: row.content,
|
|
1304
|
+
canonicalKey: row.canonical_key,
|
|
1305
|
+
dedupeKey: row.dedupe_key,
|
|
1306
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
};
|
|
1310
|
+
|
|
1311
|
+
// src/core/sqlite-wrapper.ts
|
|
1312
|
+
import Database from "better-sqlite3";
|
|
1313
|
+
function createSQLiteDatabase(path, options) {
|
|
1314
|
+
const db = new Database(path, {
|
|
1315
|
+
readonly: options?.readonly ?? false
|
|
1316
|
+
});
|
|
1317
|
+
if (!options?.readonly && (options?.walMode ?? true)) {
|
|
1318
|
+
db.pragma("journal_mode = WAL");
|
|
1319
|
+
db.pragma("synchronous = NORMAL");
|
|
1320
|
+
db.pragma("busy_timeout = 5000");
|
|
1321
|
+
}
|
|
1322
|
+
return db;
|
|
1323
|
+
}
|
|
1324
|
+
function sqliteRun(db, sql, params = []) {
|
|
1325
|
+
const stmt = db.prepare(sql);
|
|
1326
|
+
return stmt.run(...params);
|
|
1327
|
+
}
|
|
1328
|
+
function sqliteAll(db, sql, params = []) {
|
|
1329
|
+
const stmt = db.prepare(sql);
|
|
1330
|
+
return stmt.all(...params);
|
|
1331
|
+
}
|
|
1332
|
+
function sqliteGet(db, sql, params = []) {
|
|
1333
|
+
const stmt = db.prepare(sql);
|
|
1334
|
+
return stmt.get(...params);
|
|
1335
|
+
}
|
|
1336
|
+
function sqliteExec(db, sql) {
|
|
1337
|
+
db.exec(sql);
|
|
1338
|
+
}
|
|
1339
|
+
function sqliteClose(db) {
|
|
1340
|
+
db.close();
|
|
1341
|
+
}
|
|
1342
|
+
function sqliteTransaction(db, fn) {
|
|
1343
|
+
return db.transaction(fn)();
|
|
1344
|
+
}
|
|
1345
|
+
function toDateFromSQLite(value) {
|
|
1346
|
+
if (value instanceof Date)
|
|
1347
|
+
return value;
|
|
1348
|
+
if (typeof value === "string")
|
|
1349
|
+
return new Date(value);
|
|
1350
|
+
if (typeof value === "number")
|
|
1351
|
+
return new Date(value);
|
|
1352
|
+
return new Date(String(value));
|
|
1353
|
+
}
|
|
1354
|
+
function toSQLiteTimestamp(date) {
|
|
1355
|
+
return date.toISOString();
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// src/core/sqlite-event-store.ts
|
|
1359
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1360
|
+
var SQLiteEventStore = class {
|
|
1361
|
+
constructor(dbPath, options) {
|
|
1362
|
+
this.dbPath = dbPath;
|
|
1363
|
+
this.readOnly = options?.readonly ?? false;
|
|
1364
|
+
this.db = createSQLiteDatabase(dbPath, {
|
|
1365
|
+
readonly: this.readOnly,
|
|
1366
|
+
walMode: !this.readOnly
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
db;
|
|
1370
|
+
initialized = false;
|
|
1371
|
+
readOnly;
|
|
1372
|
+
/**
|
|
1373
|
+
* Initialize database schema
|
|
1374
|
+
*/
|
|
1375
|
+
async initialize() {
|
|
1376
|
+
if (this.initialized)
|
|
1377
|
+
return;
|
|
1378
|
+
if (this.readOnly) {
|
|
1379
|
+
this.initialized = true;
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
sqliteExec(this.db, `
|
|
1383
|
+
-- L0 EventStore: Single Source of Truth (immutable, append-only)
|
|
1384
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
1385
|
+
id TEXT PRIMARY KEY,
|
|
1386
|
+
event_type TEXT NOT NULL,
|
|
1387
|
+
session_id TEXT NOT NULL,
|
|
1388
|
+
timestamp TEXT NOT NULL,
|
|
1389
|
+
content TEXT NOT NULL,
|
|
1390
|
+
canonical_key TEXT NOT NULL,
|
|
1391
|
+
dedupe_key TEXT UNIQUE,
|
|
1392
|
+
metadata TEXT,
|
|
1393
|
+
access_count INTEGER DEFAULT 0,
|
|
1394
|
+
last_accessed_at TEXT
|
|
1395
|
+
);
|
|
1396
|
+
|
|
1397
|
+
-- Dedup table for idempotency
|
|
1398
|
+
CREATE TABLE IF NOT EXISTS event_dedup (
|
|
1399
|
+
dedupe_key TEXT PRIMARY KEY,
|
|
1400
|
+
event_id TEXT NOT NULL,
|
|
1401
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
1402
|
+
);
|
|
1403
|
+
|
|
1404
|
+
-- Session metadata
|
|
1405
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
1406
|
+
id TEXT PRIMARY KEY,
|
|
1407
|
+
started_at TEXT NOT NULL,
|
|
1408
|
+
ended_at TEXT,
|
|
1409
|
+
project_path TEXT,
|
|
1410
|
+
summary TEXT,
|
|
1411
|
+
tags TEXT
|
|
1412
|
+
);
|
|
1413
|
+
|
|
1414
|
+
-- Insights (derived data, rebuildable)
|
|
1415
|
+
CREATE TABLE IF NOT EXISTS insights (
|
|
1416
|
+
id TEXT PRIMARY KEY,
|
|
1417
|
+
insight_type TEXT NOT NULL,
|
|
1418
|
+
content TEXT NOT NULL,
|
|
1419
|
+
canonical_key TEXT NOT NULL,
|
|
1420
|
+
confidence REAL,
|
|
1421
|
+
source_events TEXT,
|
|
1422
|
+
created_at TEXT,
|
|
1423
|
+
last_updated TEXT
|
|
1424
|
+
);
|
|
1425
|
+
|
|
1426
|
+
-- Embedding Outbox (Single-Writer Pattern)
|
|
1427
|
+
CREATE TABLE IF NOT EXISTS embedding_outbox (
|
|
1428
|
+
id TEXT PRIMARY KEY,
|
|
1429
|
+
event_id TEXT NOT NULL,
|
|
1430
|
+
content TEXT NOT NULL,
|
|
1431
|
+
status TEXT DEFAULT 'pending',
|
|
1432
|
+
retry_count INTEGER DEFAULT 0,
|
|
1433
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
1434
|
+
processed_at TEXT,
|
|
1435
|
+
error_message TEXT
|
|
1436
|
+
);
|
|
1437
|
+
|
|
1438
|
+
-- Projection offset tracking
|
|
1439
|
+
CREATE TABLE IF NOT EXISTS projection_offsets (
|
|
1440
|
+
projection_name TEXT PRIMARY KEY,
|
|
1441
|
+
last_event_id TEXT,
|
|
1442
|
+
last_timestamp TEXT,
|
|
1443
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
1444
|
+
);
|
|
1445
|
+
|
|
1446
|
+
-- Memory level tracking
|
|
1447
|
+
CREATE TABLE IF NOT EXISTS memory_levels (
|
|
1448
|
+
event_id TEXT PRIMARY KEY,
|
|
1449
|
+
level TEXT NOT NULL DEFAULT 'L0',
|
|
1450
|
+
promoted_at TEXT DEFAULT (datetime('now'))
|
|
1451
|
+
);
|
|
1452
|
+
|
|
1453
|
+
-- Entries (immutable memory units)
|
|
1454
|
+
CREATE TABLE IF NOT EXISTS entries (
|
|
1455
|
+
entry_id TEXT PRIMARY KEY,
|
|
1456
|
+
created_ts TEXT NOT NULL,
|
|
1457
|
+
entry_type TEXT NOT NULL,
|
|
1458
|
+
title TEXT NOT NULL,
|
|
1459
|
+
content_json TEXT NOT NULL,
|
|
1460
|
+
stage TEXT NOT NULL DEFAULT 'raw',
|
|
1461
|
+
status TEXT DEFAULT 'active',
|
|
1462
|
+
superseded_by TEXT,
|
|
1463
|
+
build_id TEXT,
|
|
1464
|
+
evidence_json TEXT,
|
|
1465
|
+
canonical_key TEXT,
|
|
1466
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
1467
|
+
);
|
|
1468
|
+
|
|
1469
|
+
-- Entities (task/condition/artifact)
|
|
1470
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
1471
|
+
entity_id TEXT PRIMARY KEY,
|
|
1472
|
+
entity_type TEXT NOT NULL,
|
|
1473
|
+
canonical_key TEXT NOT NULL,
|
|
1474
|
+
title TEXT NOT NULL,
|
|
1475
|
+
stage TEXT NOT NULL DEFAULT 'raw',
|
|
1476
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
1477
|
+
current_json TEXT NOT NULL,
|
|
1478
|
+
title_norm TEXT,
|
|
1479
|
+
search_text TEXT,
|
|
1480
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
1481
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
1482
|
+
);
|
|
1483
|
+
|
|
1484
|
+
-- Entity aliases for canonical key lookup
|
|
1485
|
+
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1486
|
+
entity_type TEXT NOT NULL,
|
|
1487
|
+
canonical_key TEXT NOT NULL,
|
|
1488
|
+
entity_id TEXT NOT NULL,
|
|
1489
|
+
is_primary INTEGER DEFAULT 0,
|
|
1490
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
1491
|
+
PRIMARY KEY(entity_type, canonical_key)
|
|
1492
|
+
);
|
|
1493
|
+
|
|
1494
|
+
-- Edges (relationships between entries/entities)
|
|
1495
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
1496
|
+
edge_id TEXT PRIMARY KEY,
|
|
1497
|
+
src_type TEXT NOT NULL,
|
|
1498
|
+
src_id TEXT NOT NULL,
|
|
1499
|
+
rel_type TEXT NOT NULL,
|
|
1500
|
+
dst_type TEXT NOT NULL,
|
|
1501
|
+
dst_id TEXT NOT NULL,
|
|
1502
|
+
meta_json TEXT,
|
|
1503
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
1504
|
+
);
|
|
1505
|
+
|
|
1506
|
+
-- Vector Outbox V2 Table
|
|
1507
|
+
CREATE TABLE IF NOT EXISTS vector_outbox (
|
|
1508
|
+
job_id TEXT PRIMARY KEY,
|
|
1509
|
+
item_kind TEXT NOT NULL,
|
|
1510
|
+
item_id TEXT NOT NULL,
|
|
1511
|
+
embedding_version TEXT NOT NULL,
|
|
1512
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
1513
|
+
retry_count INTEGER DEFAULT 0,
|
|
1514
|
+
error TEXT,
|
|
1515
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
1516
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
1517
|
+
UNIQUE(item_kind, item_id, embedding_version)
|
|
1518
|
+
);
|
|
1519
|
+
|
|
1520
|
+
-- Build Runs
|
|
1521
|
+
CREATE TABLE IF NOT EXISTS build_runs (
|
|
1522
|
+
build_id TEXT PRIMARY KEY,
|
|
1523
|
+
started_at TEXT NOT NULL,
|
|
1524
|
+
finished_at TEXT,
|
|
1525
|
+
extractor_model TEXT NOT NULL,
|
|
1526
|
+
extractor_prompt_hash TEXT NOT NULL,
|
|
1527
|
+
embedder_model TEXT NOT NULL,
|
|
1528
|
+
embedding_version TEXT NOT NULL,
|
|
1529
|
+
idris_version TEXT NOT NULL,
|
|
1530
|
+
schema_version TEXT NOT NULL,
|
|
1531
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
1532
|
+
error TEXT
|
|
1533
|
+
);
|
|
1534
|
+
|
|
1535
|
+
-- Pipeline Metrics
|
|
1536
|
+
CREATE TABLE IF NOT EXISTS pipeline_metrics (
|
|
1537
|
+
id TEXT PRIMARY KEY,
|
|
1538
|
+
ts TEXT NOT NULL,
|
|
1539
|
+
stage TEXT NOT NULL,
|
|
1540
|
+
latency_ms REAL NOT NULL,
|
|
1541
|
+
success INTEGER NOT NULL,
|
|
1542
|
+
error TEXT,
|
|
1543
|
+
session_id TEXT
|
|
1544
|
+
);
|
|
1545
|
+
|
|
1546
|
+
-- Working Set table (active memory window)
|
|
1547
|
+
CREATE TABLE IF NOT EXISTS working_set (
|
|
1548
|
+
id TEXT PRIMARY KEY,
|
|
1549
|
+
event_id TEXT NOT NULL,
|
|
1550
|
+
added_at TEXT DEFAULT (datetime('now')),
|
|
1551
|
+
relevance_score REAL DEFAULT 1.0,
|
|
1552
|
+
topics TEXT,
|
|
1553
|
+
expires_at TEXT
|
|
1554
|
+
);
|
|
1555
|
+
|
|
1556
|
+
-- Consolidated Memories table (long-term integrated memories)
|
|
1557
|
+
CREATE TABLE IF NOT EXISTS consolidated_memories (
|
|
1558
|
+
memory_id TEXT PRIMARY KEY,
|
|
1559
|
+
summary TEXT NOT NULL,
|
|
1560
|
+
topics TEXT,
|
|
1561
|
+
source_events TEXT,
|
|
1562
|
+
confidence REAL DEFAULT 0.5,
|
|
1563
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
1564
|
+
accessed_at TEXT,
|
|
1565
|
+
access_count INTEGER DEFAULT 0
|
|
1566
|
+
);
|
|
1567
|
+
|
|
1568
|
+
-- Continuity Log table (tracks context transitions)
|
|
1569
|
+
CREATE TABLE IF NOT EXISTS continuity_log (
|
|
1570
|
+
log_id TEXT PRIMARY KEY,
|
|
1571
|
+
from_context_id TEXT,
|
|
1572
|
+
to_context_id TEXT,
|
|
1573
|
+
continuity_score REAL,
|
|
1574
|
+
transition_type TEXT,
|
|
1575
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
1576
|
+
);
|
|
1577
|
+
|
|
1578
|
+
-- Endless Mode Config table
|
|
1579
|
+
CREATE TABLE IF NOT EXISTS endless_config (
|
|
1580
|
+
key TEXT PRIMARY KEY,
|
|
1581
|
+
value TEXT,
|
|
1582
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
1583
|
+
);
|
|
1584
|
+
|
|
1585
|
+
-- Sync position tracking (for SQLite -> DuckDB sync)
|
|
1586
|
+
CREATE TABLE IF NOT EXISTS sync_positions (
|
|
1587
|
+
target_name TEXT PRIMARY KEY,
|
|
1588
|
+
last_event_id TEXT,
|
|
1589
|
+
last_timestamp TEXT,
|
|
1590
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
1591
|
+
);
|
|
1592
|
+
|
|
1593
|
+
-- Create indexes
|
|
1594
|
+
CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
|
|
1595
|
+
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
|
|
1596
|
+
CREATE INDEX IF NOT EXISTS idx_entries_type ON entries(entry_type);
|
|
1597
|
+
CREATE INDEX IF NOT EXISTS idx_entries_stage ON entries(stage);
|
|
1598
|
+
CREATE INDEX IF NOT EXISTS idx_entries_canonical ON entries(canonical_key);
|
|
1599
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type_key ON entities(entity_type, canonical_key);
|
|
1600
|
+
CREATE INDEX IF NOT EXISTS idx_entities_status ON entities(status);
|
|
1601
|
+
CREATE INDEX IF NOT EXISTS idx_edges_src ON edges(src_id, rel_type);
|
|
1602
|
+
CREATE INDEX IF NOT EXISTS idx_edges_dst ON edges(dst_id, rel_type);
|
|
1603
|
+
CREATE INDEX IF NOT EXISTS idx_edges_rel ON edges(rel_type);
|
|
1604
|
+
CREATE INDEX IF NOT EXISTS idx_outbox_status ON vector_outbox(status);
|
|
1605
|
+
CREATE INDEX IF NOT EXISTS idx_working_set_expires ON working_set(expires_at);
|
|
1606
|
+
CREATE INDEX IF NOT EXISTS idx_working_set_relevance ON working_set(relevance_score);
|
|
1607
|
+
CREATE INDEX IF NOT EXISTS idx_consolidated_confidence ON consolidated_memories(confidence);
|
|
1608
|
+
CREATE INDEX IF NOT EXISTS idx_continuity_created ON continuity_log(created_at);
|
|
1609
|
+
CREATE INDEX IF NOT EXISTS idx_embedding_outbox_status ON embedding_outbox(status);
|
|
1610
|
+
|
|
1611
|
+
-- FTS5 Full-Text Search for fast keyword search
|
|
1612
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS events_fts USING fts5(
|
|
1613
|
+
content,
|
|
1614
|
+
event_id UNINDEXED,
|
|
1615
|
+
content='events',
|
|
1616
|
+
content_rowid='rowid'
|
|
1617
|
+
);
|
|
1618
|
+
|
|
1619
|
+
-- Triggers to keep FTS in sync with events table
|
|
1620
|
+
CREATE TRIGGER IF NOT EXISTS events_fts_insert AFTER INSERT ON events BEGIN
|
|
1621
|
+
INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
|
|
1622
|
+
END;
|
|
1623
|
+
|
|
1624
|
+
CREATE TRIGGER IF NOT EXISTS events_fts_delete AFTER DELETE ON events BEGIN
|
|
1625
|
+
INSERT INTO events_fts(events_fts, rowid, content, event_id) VALUES('delete', OLD.rowid, OLD.content, OLD.id);
|
|
1626
|
+
END;
|
|
1627
|
+
|
|
1628
|
+
CREATE TRIGGER IF NOT EXISTS events_fts_update AFTER UPDATE ON events BEGIN
|
|
1629
|
+
INSERT INTO events_fts(events_fts, rowid, content, event_id) VALUES('delete', OLD.rowid, OLD.content, OLD.id);
|
|
1630
|
+
INSERT INTO events_fts(rowid, content, event_id) VALUES (NEW.rowid, NEW.content, NEW.id);
|
|
1631
|
+
END;
|
|
1632
|
+
`);
|
|
1633
|
+
const tableInfo = sqliteAll(this.db, "PRAGMA table_info(events)", []);
|
|
1634
|
+
const columnNames = tableInfo.map((col) => col.name);
|
|
1635
|
+
if (!columnNames.includes("access_count")) {
|
|
1636
|
+
try {
|
|
1637
|
+
sqliteExec(this.db, `
|
|
1638
|
+
ALTER TABLE events ADD COLUMN access_count INTEGER DEFAULT 0;
|
|
1639
|
+
`);
|
|
1640
|
+
} catch (err) {
|
|
1641
|
+
console.error("Error adding access_count column:", err);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
if (!columnNames.includes("last_accessed_at")) {
|
|
1645
|
+
try {
|
|
1646
|
+
sqliteExec(this.db, `
|
|
1647
|
+
ALTER TABLE events ADD COLUMN last_accessed_at TEXT;
|
|
1648
|
+
`);
|
|
1649
|
+
} catch (err) {
|
|
1650
|
+
console.error("Error adding last_accessed_at column:", err);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
try {
|
|
1654
|
+
sqliteExec(this.db, `
|
|
1655
|
+
CREATE INDEX IF NOT EXISTS idx_events_access_count ON events(access_count DESC);
|
|
1656
|
+
`);
|
|
1657
|
+
} catch (err) {
|
|
1658
|
+
}
|
|
1659
|
+
try {
|
|
1660
|
+
sqliteExec(this.db, `
|
|
1661
|
+
CREATE INDEX IF NOT EXISTS idx_events_last_accessed ON events(last_accessed_at DESC);
|
|
1662
|
+
`);
|
|
1663
|
+
} catch (err) {
|
|
1664
|
+
}
|
|
1665
|
+
this.initialized = true;
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Append event to store (Append-only, Idempotent)
|
|
1669
|
+
*/
|
|
1670
|
+
async append(input) {
|
|
1671
|
+
await this.initialize();
|
|
1672
|
+
const canonicalKey = makeCanonicalKey(input.content);
|
|
1673
|
+
const dedupeKey = makeDedupeKey(input.content, input.sessionId);
|
|
1674
|
+
const existing = sqliteGet(
|
|
1675
|
+
this.db,
|
|
1676
|
+
`SELECT event_id FROM event_dedup WHERE dedupe_key = ?`,
|
|
1677
|
+
[dedupeKey]
|
|
1678
|
+
);
|
|
1679
|
+
if (existing) {
|
|
1680
|
+
return {
|
|
1681
|
+
success: true,
|
|
1682
|
+
eventId: existing.event_id,
|
|
1683
|
+
isDuplicate: true
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
const id = randomUUID2();
|
|
1687
|
+
const timestamp = toSQLiteTimestamp(input.timestamp);
|
|
1688
|
+
try {
|
|
1689
|
+
const insertEvent = this.db.prepare(`
|
|
1690
|
+
INSERT INTO events (id, event_type, session_id, timestamp, content, canonical_key, dedupe_key, metadata)
|
|
1691
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1692
|
+
`);
|
|
1693
|
+
const insertDedup = this.db.prepare(`
|
|
1694
|
+
INSERT INTO event_dedup (dedupe_key, event_id) VALUES (?, ?)
|
|
1695
|
+
`);
|
|
1696
|
+
const insertLevel = this.db.prepare(`
|
|
1697
|
+
INSERT INTO memory_levels (event_id, level) VALUES (?, 'L0')
|
|
1698
|
+
`);
|
|
1699
|
+
const transaction = this.db.transaction(() => {
|
|
1700
|
+
insertEvent.run(
|
|
1701
|
+
id,
|
|
1702
|
+
input.eventType,
|
|
1703
|
+
input.sessionId,
|
|
1704
|
+
timestamp,
|
|
1705
|
+
input.content,
|
|
1706
|
+
canonicalKey,
|
|
1707
|
+
dedupeKey,
|
|
1708
|
+
JSON.stringify(input.metadata || {})
|
|
1709
|
+
);
|
|
1710
|
+
insertDedup.run(dedupeKey, id);
|
|
1711
|
+
insertLevel.run(id);
|
|
1712
|
+
});
|
|
1713
|
+
transaction();
|
|
1714
|
+
return { success: true, eventId: id, isDuplicate: false };
|
|
1715
|
+
} catch (error) {
|
|
1716
|
+
return {
|
|
1717
|
+
success: false,
|
|
1718
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Get events by session ID
|
|
1724
|
+
*/
|
|
1725
|
+
async getSessionEvents(sessionId) {
|
|
1726
|
+
await this.initialize();
|
|
1727
|
+
const rows = sqliteAll(
|
|
1728
|
+
this.db,
|
|
1729
|
+
`SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
|
|
1730
|
+
[sessionId]
|
|
1731
|
+
);
|
|
1732
|
+
return rows.map(this.rowToEvent);
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Get recent events
|
|
1736
|
+
*/
|
|
1737
|
+
async getRecentEvents(limit = 100) {
|
|
1738
|
+
await this.initialize();
|
|
1739
|
+
const rows = sqliteAll(
|
|
1740
|
+
this.db,
|
|
1741
|
+
`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
|
|
1742
|
+
[limit]
|
|
1743
|
+
);
|
|
1744
|
+
return rows.map(this.rowToEvent);
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Get event by ID
|
|
1748
|
+
*/
|
|
1749
|
+
async getEvent(id) {
|
|
1750
|
+
await this.initialize();
|
|
1751
|
+
const row = sqliteGet(
|
|
1752
|
+
this.db,
|
|
1753
|
+
`SELECT * FROM events WHERE id = ?`,
|
|
1754
|
+
[id]
|
|
1755
|
+
);
|
|
1756
|
+
if (!row)
|
|
1757
|
+
return null;
|
|
1758
|
+
return this.rowToEvent(row);
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Get events since a timestamp (for sync)
|
|
1762
|
+
*/
|
|
1763
|
+
async getEventsSince(timestamp, limit = 1e3) {
|
|
1764
|
+
await this.initialize();
|
|
1765
|
+
const rows = sqliteAll(
|
|
1766
|
+
this.db,
|
|
1767
|
+
`SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
|
|
1768
|
+
[timestamp, limit]
|
|
1769
|
+
);
|
|
1770
|
+
return rows.map(this.rowToEvent);
|
|
1771
|
+
}
|
|
1772
|
+
/**
|
|
1773
|
+
* Create or update session
|
|
1774
|
+
*/
|
|
1775
|
+
async upsertSession(session) {
|
|
1776
|
+
await this.initialize();
|
|
1777
|
+
const existing = sqliteGet(
|
|
1778
|
+
this.db,
|
|
1779
|
+
`SELECT id FROM sessions WHERE id = ?`,
|
|
1780
|
+
[session.id]
|
|
1781
|
+
);
|
|
1782
|
+
if (!existing) {
|
|
1783
|
+
sqliteRun(
|
|
1784
|
+
this.db,
|
|
1785
|
+
`INSERT INTO sessions (id, started_at, project_path, tags)
|
|
1786
|
+
VALUES (?, ?, ?, ?)`,
|
|
1787
|
+
[
|
|
1788
|
+
session.id,
|
|
1789
|
+
toSQLiteTimestamp(session.startedAt || /* @__PURE__ */ new Date()),
|
|
1790
|
+
session.projectPath || null,
|
|
1791
|
+
JSON.stringify(session.tags || [])
|
|
1792
|
+
]
|
|
1793
|
+
);
|
|
1794
|
+
} else {
|
|
1795
|
+
const updates = [];
|
|
1796
|
+
const values = [];
|
|
1797
|
+
if (session.endedAt) {
|
|
1798
|
+
updates.push("ended_at = ?");
|
|
1799
|
+
values.push(toSQLiteTimestamp(session.endedAt));
|
|
1800
|
+
}
|
|
1801
|
+
if (session.summary) {
|
|
1802
|
+
updates.push("summary = ?");
|
|
1803
|
+
values.push(session.summary);
|
|
1804
|
+
}
|
|
1805
|
+
if (session.tags) {
|
|
1806
|
+
updates.push("tags = ?");
|
|
1807
|
+
values.push(JSON.stringify(session.tags));
|
|
1808
|
+
}
|
|
1809
|
+
if (updates.length > 0) {
|
|
1810
|
+
values.push(session.id);
|
|
1811
|
+
sqliteRun(
|
|
1812
|
+
this.db,
|
|
1813
|
+
`UPDATE sessions SET ${updates.join(", ")} WHERE id = ?`,
|
|
1814
|
+
values
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Get session by ID
|
|
1821
|
+
*/
|
|
1822
|
+
async getSession(id) {
|
|
1823
|
+
await this.initialize();
|
|
1824
|
+
const row = sqliteGet(
|
|
1825
|
+
this.db,
|
|
1826
|
+
`SELECT * FROM sessions WHERE id = ?`,
|
|
1827
|
+
[id]
|
|
1828
|
+
);
|
|
1829
|
+
if (!row)
|
|
1830
|
+
return null;
|
|
1831
|
+
return {
|
|
1832
|
+
id: row.id,
|
|
1833
|
+
startedAt: toDateFromSQLite(row.started_at),
|
|
1834
|
+
endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
|
|
1835
|
+
projectPath: row.project_path,
|
|
1836
|
+
summary: row.summary,
|
|
1837
|
+
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Get all sessions
|
|
1842
|
+
*/
|
|
1843
|
+
async getAllSessions() {
|
|
1844
|
+
await this.initialize();
|
|
1845
|
+
const rows = sqliteAll(
|
|
1846
|
+
this.db,
|
|
1847
|
+
`SELECT * FROM sessions ORDER BY started_at DESC`
|
|
1848
|
+
);
|
|
1849
|
+
return rows.map((row) => ({
|
|
1850
|
+
id: row.id,
|
|
1851
|
+
startedAt: toDateFromSQLite(row.started_at),
|
|
1852
|
+
endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
|
|
1853
|
+
projectPath: row.project_path,
|
|
1854
|
+
summary: row.summary,
|
|
1855
|
+
tags: row.tags ? JSON.parse(row.tags) : void 0
|
|
1856
|
+
}));
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Add to embedding outbox
|
|
1860
|
+
*/
|
|
1861
|
+
async enqueueForEmbedding(eventId, content) {
|
|
1862
|
+
await this.initialize();
|
|
1863
|
+
const id = randomUUID2();
|
|
1864
|
+
sqliteRun(
|
|
1865
|
+
this.db,
|
|
1866
|
+
`INSERT INTO embedding_outbox (id, event_id, content, status, retry_count)
|
|
1867
|
+
VALUES (?, ?, ?, 'pending', 0)`,
|
|
1868
|
+
[id, eventId, content]
|
|
1869
|
+
);
|
|
1870
|
+
return id;
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Get pending outbox items
|
|
1874
|
+
*/
|
|
1875
|
+
async getPendingOutboxItems(limit = 32) {
|
|
1876
|
+
await this.initialize();
|
|
1877
|
+
const pending = sqliteAll(
|
|
1878
|
+
this.db,
|
|
1879
|
+
`SELECT * FROM embedding_outbox
|
|
1880
|
+
WHERE status = 'pending'
|
|
1881
|
+
ORDER BY created_at
|
|
1882
|
+
LIMIT ?`,
|
|
1883
|
+
[limit]
|
|
1884
|
+
);
|
|
1885
|
+
if (pending.length === 0)
|
|
1886
|
+
return [];
|
|
1887
|
+
const ids = pending.map((r) => r.id);
|
|
1888
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1889
|
+
sqliteRun(
|
|
1890
|
+
this.db,
|
|
1891
|
+
`UPDATE embedding_outbox SET status = 'processing' WHERE id IN (${placeholders})`,
|
|
1892
|
+
ids
|
|
1893
|
+
);
|
|
1894
|
+
return pending.map((row) => ({
|
|
1895
|
+
id: row.id,
|
|
1896
|
+
eventId: row.event_id,
|
|
1897
|
+
content: row.content,
|
|
1898
|
+
status: "processing",
|
|
1899
|
+
retryCount: row.retry_count,
|
|
1900
|
+
createdAt: toDateFromSQLite(row.created_at),
|
|
1901
|
+
errorMessage: row.error_message
|
|
1902
|
+
}));
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Mark outbox items as done
|
|
1906
|
+
*/
|
|
1907
|
+
async completeOutboxItems(ids) {
|
|
1908
|
+
if (ids.length === 0)
|
|
1909
|
+
return;
|
|
1910
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1911
|
+
sqliteRun(
|
|
1912
|
+
this.db,
|
|
1913
|
+
`DELETE FROM embedding_outbox WHERE id IN (${placeholders})`,
|
|
1914
|
+
ids
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Mark outbox items as failed
|
|
1919
|
+
*/
|
|
1920
|
+
async failOutboxItems(ids, error) {
|
|
1921
|
+
if (ids.length === 0)
|
|
1922
|
+
return;
|
|
1923
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1924
|
+
sqliteRun(
|
|
1925
|
+
this.db,
|
|
1926
|
+
`UPDATE embedding_outbox
|
|
1927
|
+
SET status = CASE WHEN retry_count >= 3 THEN 'failed' ELSE 'pending' END,
|
|
1928
|
+
retry_count = retry_count + 1,
|
|
1929
|
+
error_message = ?
|
|
1930
|
+
WHERE id IN (${placeholders})`,
|
|
1931
|
+
[error, ...ids]
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
/**
|
|
1935
|
+
* Update memory level
|
|
1936
|
+
*/
|
|
1937
|
+
async updateMemoryLevel(eventId, level) {
|
|
1938
|
+
await this.initialize();
|
|
1939
|
+
sqliteRun(
|
|
1940
|
+
this.db,
|
|
1941
|
+
`UPDATE memory_levels SET level = ?, promoted_at = datetime('now') WHERE event_id = ?`,
|
|
1942
|
+
[level, eventId]
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Get memory level statistics
|
|
1947
|
+
*/
|
|
1948
|
+
async getLevelStats() {
|
|
1949
|
+
await this.initialize();
|
|
1950
|
+
const rows = sqliteAll(
|
|
1951
|
+
this.db,
|
|
1952
|
+
`SELECT level, COUNT(*) as count FROM memory_levels GROUP BY level`
|
|
1953
|
+
);
|
|
1954
|
+
return rows;
|
|
1955
|
+
}
|
|
1956
|
+
/**
|
|
1957
|
+
* Get events by memory level
|
|
1958
|
+
*/
|
|
1959
|
+
async getEventsByLevel(level, options) {
|
|
1960
|
+
await this.initialize();
|
|
1961
|
+
const limit = options?.limit || 50;
|
|
1962
|
+
const offset = options?.offset || 0;
|
|
1963
|
+
const rows = sqliteAll(
|
|
1964
|
+
this.db,
|
|
1965
|
+
`SELECT e.* FROM events e
|
|
1966
|
+
INNER JOIN memory_levels ml ON e.id = ml.event_id
|
|
1967
|
+
WHERE ml.level = ?
|
|
1968
|
+
ORDER BY e.timestamp DESC
|
|
1969
|
+
LIMIT ? OFFSET ?`,
|
|
1970
|
+
[level, limit, offset]
|
|
1971
|
+
);
|
|
1972
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Get memory level for a specific event
|
|
1976
|
+
*/
|
|
1977
|
+
async getEventLevel(eventId) {
|
|
1978
|
+
await this.initialize();
|
|
1979
|
+
const row = sqliteGet(
|
|
1980
|
+
this.db,
|
|
1981
|
+
`SELECT level FROM memory_levels WHERE event_id = ?`,
|
|
1982
|
+
[eventId]
|
|
1983
|
+
);
|
|
1984
|
+
return row ? row.level : null;
|
|
1985
|
+
}
|
|
1986
|
+
/**
|
|
1987
|
+
* Get sync position for a target
|
|
1988
|
+
*/
|
|
1989
|
+
async getSyncPosition(targetName) {
|
|
1990
|
+
await this.initialize();
|
|
1991
|
+
const row = sqliteGet(
|
|
1992
|
+
this.db,
|
|
1993
|
+
`SELECT last_event_id, last_timestamp FROM sync_positions WHERE target_name = ?`,
|
|
1994
|
+
[targetName]
|
|
1995
|
+
);
|
|
1996
|
+
return {
|
|
1997
|
+
lastEventId: row?.last_event_id ?? null,
|
|
1998
|
+
lastTimestamp: row?.last_timestamp ?? null
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
/**
|
|
2002
|
+
* Update sync position for a target
|
|
2003
|
+
*/
|
|
2004
|
+
async updateSyncPosition(targetName, lastEventId, lastTimestamp) {
|
|
2005
|
+
await this.initialize();
|
|
2006
|
+
sqliteRun(
|
|
2007
|
+
this.db,
|
|
2008
|
+
`INSERT OR REPLACE INTO sync_positions (target_name, last_event_id, last_timestamp, updated_at)
|
|
2009
|
+
VALUES (?, ?, ?, datetime('now'))`,
|
|
2010
|
+
[targetName, lastEventId, lastTimestamp]
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
/**
|
|
2014
|
+
* Get config value for endless mode
|
|
2015
|
+
*/
|
|
2016
|
+
async getEndlessConfig(key) {
|
|
2017
|
+
await this.initialize();
|
|
2018
|
+
const row = sqliteGet(
|
|
2019
|
+
this.db,
|
|
2020
|
+
`SELECT value FROM endless_config WHERE key = ?`,
|
|
2021
|
+
[key]
|
|
2022
|
+
);
|
|
2023
|
+
if (!row)
|
|
2024
|
+
return null;
|
|
2025
|
+
return JSON.parse(row.value);
|
|
2026
|
+
}
|
|
2027
|
+
/**
|
|
2028
|
+
* Set config value for endless mode
|
|
2029
|
+
*/
|
|
2030
|
+
async setEndlessConfig(key, value) {
|
|
2031
|
+
await this.initialize();
|
|
2032
|
+
sqliteRun(
|
|
2033
|
+
this.db,
|
|
2034
|
+
`INSERT OR REPLACE INTO endless_config (key, value, updated_at)
|
|
2035
|
+
VALUES (?, ?, datetime('now'))`,
|
|
2036
|
+
[key, JSON.stringify(value)]
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
/**
|
|
2040
|
+
* Increment access count for events
|
|
2041
|
+
*/
|
|
2042
|
+
async incrementAccessCount(eventIds) {
|
|
2043
|
+
if (eventIds.length === 0 || this.readOnly)
|
|
2044
|
+
return;
|
|
2045
|
+
await this.initialize();
|
|
2046
|
+
const placeholders = eventIds.map(() => "?").join(",");
|
|
2047
|
+
const currentTime = toSQLiteTimestamp(/* @__PURE__ */ new Date());
|
|
2048
|
+
sqliteRun(
|
|
2049
|
+
this.db,
|
|
2050
|
+
`UPDATE events
|
|
2051
|
+
SET access_count = access_count + 1,
|
|
2052
|
+
last_accessed_at = ?
|
|
2053
|
+
WHERE id IN (${placeholders})`,
|
|
2054
|
+
[currentTime, ...eventIds]
|
|
2055
|
+
);
|
|
2056
|
+
}
|
|
2057
|
+
/**
|
|
2058
|
+
* Get most accessed memories
|
|
2059
|
+
*/
|
|
2060
|
+
async getMostAccessed(limit = 10) {
|
|
2061
|
+
await this.initialize();
|
|
2062
|
+
const rows = sqliteAll(
|
|
2063
|
+
this.db,
|
|
2064
|
+
`SELECT * FROM events
|
|
2065
|
+
WHERE access_count > 0
|
|
2066
|
+
ORDER BY access_count DESC, last_accessed_at DESC
|
|
2067
|
+
LIMIT ?`,
|
|
2068
|
+
[limit]
|
|
2069
|
+
);
|
|
2070
|
+
return rows.map((row) => this.rowToEvent(row));
|
|
2071
|
+
}
|
|
2072
|
+
/**
|
|
2073
|
+
* Fast keyword search using FTS5
|
|
2074
|
+
* Returns events matching the search query, ranked by relevance
|
|
2075
|
+
*/
|
|
2076
|
+
async keywordSearch(query, limit = 10) {
|
|
2077
|
+
await this.initialize();
|
|
2078
|
+
const searchTerms = query.replace(/['"(){}[\]^~*?:\\/-]/g, " ").split(/\s+/).filter((term) => term.length > 1).map((term) => `"${term}"*`).join(" OR ");
|
|
2079
|
+
if (!searchTerms) {
|
|
2080
|
+
return [];
|
|
2081
|
+
}
|
|
2082
|
+
try {
|
|
2083
|
+
const rows = sqliteAll(
|
|
2084
|
+
this.db,
|
|
2085
|
+
`SELECT e.*, fts.rank
|
|
2086
|
+
FROM events_fts fts
|
|
2087
|
+
JOIN events e ON e.id = fts.event_id
|
|
2088
|
+
WHERE events_fts MATCH ?
|
|
2089
|
+
ORDER BY fts.rank
|
|
2090
|
+
LIMIT ?`,
|
|
2091
|
+
[searchTerms, limit]
|
|
2092
|
+
);
|
|
2093
|
+
return rows.map((row) => ({
|
|
2094
|
+
event: this.rowToEvent(row),
|
|
2095
|
+
rank: row.rank
|
|
2096
|
+
}));
|
|
2097
|
+
} catch (error) {
|
|
2098
|
+
const likePattern = `%${query}%`;
|
|
2099
|
+
const rows = sqliteAll(
|
|
2100
|
+
this.db,
|
|
2101
|
+
`SELECT *, 0 as rank FROM events
|
|
2102
|
+
WHERE content LIKE ?
|
|
2103
|
+
ORDER BY timestamp DESC
|
|
2104
|
+
LIMIT ?`,
|
|
2105
|
+
[likePattern, limit]
|
|
2106
|
+
);
|
|
2107
|
+
return rows.map((row) => ({
|
|
2108
|
+
event: this.rowToEvent(row),
|
|
2109
|
+
rank: 0
|
|
2110
|
+
}));
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Rebuild FTS index from existing events
|
|
2115
|
+
* Call this once after upgrading to FTS5
|
|
2116
|
+
*/
|
|
2117
|
+
async rebuildFtsIndex() {
|
|
2118
|
+
await this.initialize();
|
|
2119
|
+
const countRow = sqliteGet(this.db, "SELECT COUNT(*) as count FROM events", []);
|
|
2120
|
+
const totalEvents = countRow?.count ?? 0;
|
|
2121
|
+
sqliteExec(this.db, `
|
|
2122
|
+
DELETE FROM events_fts;
|
|
2123
|
+
INSERT INTO events_fts(rowid, content, event_id)
|
|
2124
|
+
SELECT rowid, content, id FROM events;
|
|
2125
|
+
`);
|
|
2126
|
+
return totalEvents;
|
|
2127
|
+
}
|
|
2128
|
+
/**
|
|
2129
|
+
* Get database instance for direct access
|
|
2130
|
+
*/
|
|
2131
|
+
getDatabase() {
|
|
2132
|
+
return this.db;
|
|
2133
|
+
}
|
|
1276
2134
|
/**
|
|
1277
2135
|
* Close database connection
|
|
1278
2136
|
*/
|
|
1279
2137
|
async close() {
|
|
1280
|
-
|
|
2138
|
+
sqliteClose(this.db);
|
|
1281
2139
|
}
|
|
1282
2140
|
/**
|
|
1283
2141
|
* Convert database row to MemoryEvent
|
|
1284
2142
|
*/
|
|
1285
2143
|
rowToEvent(row) {
|
|
1286
|
-
|
|
2144
|
+
const event = {
|
|
1287
2145
|
id: row.id,
|
|
1288
2146
|
eventType: row.event_type,
|
|
1289
2147
|
sessionId: row.session_id,
|
|
1290
|
-
timestamp:
|
|
2148
|
+
timestamp: toDateFromSQLite(row.timestamp),
|
|
1291
2149
|
content: row.content,
|
|
1292
2150
|
canonicalKey: row.canonical_key,
|
|
1293
2151
|
dedupeKey: row.dedupe_key,
|
|
1294
2152
|
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
1295
2153
|
};
|
|
2154
|
+
if (row.access_count !== void 0) {
|
|
2155
|
+
event.access_count = row.access_count;
|
|
2156
|
+
}
|
|
2157
|
+
if (row.last_accessed_at !== void 0) {
|
|
2158
|
+
event.last_accessed_at = row.last_accessed_at;
|
|
2159
|
+
}
|
|
2160
|
+
return event;
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2163
|
+
|
|
2164
|
+
// src/core/sync-worker.ts
|
|
2165
|
+
var DEFAULT_CONFIG = {
|
|
2166
|
+
intervalMs: 3e4,
|
|
2167
|
+
batchSize: 500,
|
|
2168
|
+
maxRetries: 3,
|
|
2169
|
+
retryDelayMs: 5e3
|
|
2170
|
+
};
|
|
2171
|
+
var SyncWorker = class {
|
|
2172
|
+
constructor(sqliteStore, duckdbStore, config) {
|
|
2173
|
+
this.sqliteStore = sqliteStore;
|
|
2174
|
+
this.duckdbStore = duckdbStore;
|
|
2175
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
2176
|
+
}
|
|
2177
|
+
config;
|
|
2178
|
+
intervalHandle = null;
|
|
2179
|
+
running = false;
|
|
2180
|
+
stats = {
|
|
2181
|
+
lastSyncAt: null,
|
|
2182
|
+
eventsSynced: 0,
|
|
2183
|
+
sessionsSynced: 0,
|
|
2184
|
+
errors: 0,
|
|
2185
|
+
status: "idle"
|
|
2186
|
+
};
|
|
2187
|
+
/**
|
|
2188
|
+
* Start the sync worker
|
|
2189
|
+
*/
|
|
2190
|
+
start() {
|
|
2191
|
+
if (this.running)
|
|
2192
|
+
return;
|
|
2193
|
+
this.running = true;
|
|
2194
|
+
this.stats.status = "idle";
|
|
2195
|
+
this.syncNow().catch((err) => {
|
|
2196
|
+
console.error("[SyncWorker] Initial sync failed:", err);
|
|
2197
|
+
});
|
|
2198
|
+
this.intervalHandle = setInterval(() => {
|
|
2199
|
+
this.syncNow().catch((err) => {
|
|
2200
|
+
console.error("[SyncWorker] Periodic sync failed:", err);
|
|
2201
|
+
});
|
|
2202
|
+
}, this.config.intervalMs);
|
|
2203
|
+
}
|
|
2204
|
+
/**
|
|
2205
|
+
* Stop the sync worker
|
|
2206
|
+
*/
|
|
2207
|
+
stop() {
|
|
2208
|
+
this.running = false;
|
|
2209
|
+
this.stats.status = "stopped";
|
|
2210
|
+
if (this.intervalHandle) {
|
|
2211
|
+
clearInterval(this.intervalHandle);
|
|
2212
|
+
this.intervalHandle = null;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
/**
|
|
2216
|
+
* Trigger immediate sync
|
|
2217
|
+
*/
|
|
2218
|
+
async syncNow() {
|
|
2219
|
+
if (this.stats.status === "syncing") {
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
this.stats.status = "syncing";
|
|
2223
|
+
try {
|
|
2224
|
+
await this.syncEvents();
|
|
2225
|
+
await this.syncSessions();
|
|
2226
|
+
this.stats.lastSyncAt = /* @__PURE__ */ new Date();
|
|
2227
|
+
this.stats.status = "idle";
|
|
2228
|
+
} catch (error) {
|
|
2229
|
+
this.stats.errors++;
|
|
2230
|
+
this.stats.status = "error";
|
|
2231
|
+
throw error;
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
/**
|
|
2235
|
+
* Sync events from SQLite to DuckDB
|
|
2236
|
+
*/
|
|
2237
|
+
async syncEvents() {
|
|
2238
|
+
const targetName = "duckdb_analytics";
|
|
2239
|
+
const position = await this.sqliteStore.getSyncPosition(targetName);
|
|
2240
|
+
const lastTimestamp = position.lastTimestamp || "1970-01-01T00:00:00.000Z";
|
|
2241
|
+
let hasMore = true;
|
|
2242
|
+
let totalSynced = 0;
|
|
2243
|
+
while (hasMore) {
|
|
2244
|
+
const events = await this.sqliteStore.getEventsSince(lastTimestamp, this.config.batchSize);
|
|
2245
|
+
if (events.length === 0) {
|
|
2246
|
+
hasMore = false;
|
|
2247
|
+
break;
|
|
2248
|
+
}
|
|
2249
|
+
await this.retryWithBackoff(async () => {
|
|
2250
|
+
for (const event of events) {
|
|
2251
|
+
await this.insertEventToDuckDB(event);
|
|
2252
|
+
}
|
|
2253
|
+
});
|
|
2254
|
+
totalSynced += events.length;
|
|
2255
|
+
const lastEvent = events[events.length - 1];
|
|
2256
|
+
await this.sqliteStore.updateSyncPosition(
|
|
2257
|
+
targetName,
|
|
2258
|
+
lastEvent.id,
|
|
2259
|
+
lastEvent.timestamp.toISOString()
|
|
2260
|
+
);
|
|
2261
|
+
hasMore = events.length === this.config.batchSize;
|
|
2262
|
+
}
|
|
2263
|
+
this.stats.eventsSynced += totalSynced;
|
|
2264
|
+
}
|
|
2265
|
+
/**
|
|
2266
|
+
* Sync sessions from SQLite to DuckDB
|
|
2267
|
+
*/
|
|
2268
|
+
async syncSessions() {
|
|
2269
|
+
const sessions = await this.sqliteStore.getAllSessions();
|
|
2270
|
+
for (const session of sessions) {
|
|
2271
|
+
await this.retryWithBackoff(async () => {
|
|
2272
|
+
await this.duckdbStore.upsertSession(session);
|
|
2273
|
+
});
|
|
2274
|
+
}
|
|
2275
|
+
this.stats.sessionsSynced = sessions.length;
|
|
2276
|
+
}
|
|
2277
|
+
/**
|
|
2278
|
+
* Insert a single event into DuckDB
|
|
2279
|
+
*/
|
|
2280
|
+
async insertEventToDuckDB(event) {
|
|
2281
|
+
await this.duckdbStore.append({
|
|
2282
|
+
eventType: event.eventType,
|
|
2283
|
+
sessionId: event.sessionId,
|
|
2284
|
+
timestamp: event.timestamp,
|
|
2285
|
+
content: event.content,
|
|
2286
|
+
metadata: event.metadata
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
/**
|
|
2290
|
+
* Retry operation with exponential backoff
|
|
2291
|
+
*/
|
|
2292
|
+
async retryWithBackoff(fn) {
|
|
2293
|
+
let lastError = null;
|
|
2294
|
+
for (let attempt = 0; attempt < this.config.maxRetries; attempt++) {
|
|
2295
|
+
try {
|
|
2296
|
+
return await fn();
|
|
2297
|
+
} catch (error) {
|
|
2298
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
2299
|
+
if (attempt < this.config.maxRetries - 1) {
|
|
2300
|
+
const delay = this.config.retryDelayMs * Math.pow(2, attempt);
|
|
2301
|
+
await this.sleep(delay);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
throw lastError;
|
|
2306
|
+
}
|
|
2307
|
+
/**
|
|
2308
|
+
* Sleep utility
|
|
2309
|
+
*/
|
|
2310
|
+
sleep(ms) {
|
|
2311
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2312
|
+
}
|
|
2313
|
+
/**
|
|
2314
|
+
* Get sync statistics
|
|
2315
|
+
*/
|
|
2316
|
+
getStats() {
|
|
2317
|
+
return { ...this.stats };
|
|
2318
|
+
}
|
|
2319
|
+
/**
|
|
2320
|
+
* Check if worker is running
|
|
2321
|
+
*/
|
|
2322
|
+
isRunning() {
|
|
2323
|
+
return this.running;
|
|
1296
2324
|
}
|
|
1297
2325
|
};
|
|
1298
2326
|
|
|
1299
2327
|
// src/core/entity-repo.ts
|
|
1300
|
-
import { randomUUID as
|
|
2328
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
1301
2329
|
var EntityRepo = class {
|
|
1302
2330
|
constructor(db) {
|
|
1303
2331
|
this.db = db;
|
|
@@ -1306,7 +2334,7 @@ var EntityRepo = class {
|
|
|
1306
2334
|
* Create a new entity
|
|
1307
2335
|
*/
|
|
1308
2336
|
async create(input) {
|
|
1309
|
-
const entityId =
|
|
2337
|
+
const entityId = randomUUID3();
|
|
1310
2338
|
const canonicalKey = makeEntityCanonicalKey(input.entityType, input.title, {
|
|
1311
2339
|
project: input.project
|
|
1312
2340
|
});
|
|
@@ -1560,7 +2588,7 @@ var EntityRepo = class {
|
|
|
1560
2588
|
};
|
|
1561
2589
|
|
|
1562
2590
|
// src/core/edge-repo.ts
|
|
1563
|
-
import { randomUUID as
|
|
2591
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
1564
2592
|
var EdgeRepo = class {
|
|
1565
2593
|
constructor(db) {
|
|
1566
2594
|
this.db = db;
|
|
@@ -1569,7 +2597,7 @@ var EdgeRepo = class {
|
|
|
1569
2597
|
* Create a new edge (idempotent - ignores duplicates)
|
|
1570
2598
|
*/
|
|
1571
2599
|
async create(input) {
|
|
1572
|
-
const edgeId =
|
|
2600
|
+
const edgeId = randomUUID4();
|
|
1573
2601
|
const now = /* @__PURE__ */ new Date();
|
|
1574
2602
|
await dbRun(
|
|
1575
2603
|
this.db,
|
|
@@ -2042,8 +3070,8 @@ function getDefaultEmbedder() {
|
|
|
2042
3070
|
}
|
|
2043
3071
|
|
|
2044
3072
|
// src/core/vector-outbox.ts
|
|
2045
|
-
import { randomUUID as
|
|
2046
|
-
var
|
|
3073
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
3074
|
+
var DEFAULT_CONFIG2 = {
|
|
2047
3075
|
embeddingVersion: "v1",
|
|
2048
3076
|
maxRetries: 3,
|
|
2049
3077
|
stuckThresholdMs: 5 * 60 * 1e3,
|
|
@@ -2053,7 +3081,7 @@ var DEFAULT_CONFIG = {
|
|
|
2053
3081
|
var VectorOutbox = class {
|
|
2054
3082
|
constructor(db, config) {
|
|
2055
3083
|
this.db = db;
|
|
2056
|
-
this.config = { ...
|
|
3084
|
+
this.config = { ...DEFAULT_CONFIG2, ...config };
|
|
2057
3085
|
}
|
|
2058
3086
|
config;
|
|
2059
3087
|
/**
|
|
@@ -2061,7 +3089,7 @@ var VectorOutbox = class {
|
|
|
2061
3089
|
*/
|
|
2062
3090
|
async enqueue(itemKind, itemId, embeddingVersion) {
|
|
2063
3091
|
const version = embeddingVersion ?? this.config.embeddingVersion;
|
|
2064
|
-
const jobId =
|
|
3092
|
+
const jobId = randomUUID5();
|
|
2065
3093
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2066
3094
|
await dbRun(
|
|
2067
3095
|
this.db,
|
|
@@ -2281,7 +3309,7 @@ var VectorOutbox = class {
|
|
|
2281
3309
|
};
|
|
2282
3310
|
|
|
2283
3311
|
// src/core/vector-worker.ts
|
|
2284
|
-
var
|
|
3312
|
+
var DEFAULT_CONFIG3 = {
|
|
2285
3313
|
batchSize: 32,
|
|
2286
3314
|
pollIntervalMs: 1e3,
|
|
2287
3315
|
maxRetries: 3
|
|
@@ -2292,12 +3320,13 @@ var VectorWorker = class {
|
|
|
2292
3320
|
embedder;
|
|
2293
3321
|
config;
|
|
2294
3322
|
running = false;
|
|
3323
|
+
stopping = false;
|
|
2295
3324
|
pollTimeout = null;
|
|
2296
3325
|
constructor(eventStore, vectorStore, embedder, config = {}) {
|
|
2297
3326
|
this.eventStore = eventStore;
|
|
2298
3327
|
this.vectorStore = vectorStore;
|
|
2299
3328
|
this.embedder = embedder;
|
|
2300
|
-
this.config = { ...
|
|
3329
|
+
this.config = { ...DEFAULT_CONFIG3, ...config };
|
|
2301
3330
|
}
|
|
2302
3331
|
/**
|
|
2303
3332
|
* Start the worker polling loop
|
|
@@ -2306,6 +3335,7 @@ var VectorWorker = class {
|
|
|
2306
3335
|
if (this.running)
|
|
2307
3336
|
return;
|
|
2308
3337
|
this.running = true;
|
|
3338
|
+
this.stopping = false;
|
|
2309
3339
|
this.poll();
|
|
2310
3340
|
}
|
|
2311
3341
|
/**
|
|
@@ -2313,6 +3343,7 @@ var VectorWorker = class {
|
|
|
2313
3343
|
*/
|
|
2314
3344
|
stop() {
|
|
2315
3345
|
this.running = false;
|
|
3346
|
+
this.stopping = true;
|
|
2316
3347
|
if (this.pollTimeout) {
|
|
2317
3348
|
clearTimeout(this.pollTimeout);
|
|
2318
3349
|
this.pollTimeout = null;
|
|
@@ -2362,9 +3393,15 @@ var VectorWorker = class {
|
|
|
2362
3393
|
}
|
|
2363
3394
|
return successful.length;
|
|
2364
3395
|
} catch (error) {
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
3396
|
+
if (!this.stopping) {
|
|
3397
|
+
try {
|
|
3398
|
+
const allIds = items.map((i) => i.id);
|
|
3399
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3400
|
+
await this.eventStore.failOutboxItems(allIds, errorMessage);
|
|
3401
|
+
} catch (failError) {
|
|
3402
|
+
console.warn("Could not mark outbox items as failed (database may be closed)");
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
2368
3405
|
throw error;
|
|
2369
3406
|
}
|
|
2370
3407
|
}
|
|
@@ -2372,14 +3409,18 @@ var VectorWorker = class {
|
|
|
2372
3409
|
* Poll for new items
|
|
2373
3410
|
*/
|
|
2374
3411
|
async poll() {
|
|
2375
|
-
if (!this.running)
|
|
3412
|
+
if (!this.running || this.stopping)
|
|
2376
3413
|
return;
|
|
2377
3414
|
try {
|
|
2378
3415
|
await this.processBatch();
|
|
2379
3416
|
} catch (error) {
|
|
2380
|
-
|
|
3417
|
+
if (!this.stopping) {
|
|
3418
|
+
console.error("Vector worker error:", error);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
if (this.running && !this.stopping) {
|
|
3422
|
+
this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
|
|
2381
3423
|
}
|
|
2382
|
-
this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
|
|
2383
3424
|
}
|
|
2384
3425
|
/**
|
|
2385
3426
|
* Process all pending items (blocking)
|
|
@@ -2489,6 +3530,7 @@ var VectorWorkerV2 = class {
|
|
|
2489
3530
|
contentProvider;
|
|
2490
3531
|
config;
|
|
2491
3532
|
running = false;
|
|
3533
|
+
stopping = false;
|
|
2492
3534
|
pollTimeout = null;
|
|
2493
3535
|
constructor(db, vectorStore, embedder, config = {}, contentProvider) {
|
|
2494
3536
|
this.outbox = new VectorOutbox(db, {
|
|
@@ -2507,6 +3549,7 @@ var VectorWorkerV2 = class {
|
|
|
2507
3549
|
if (this.running)
|
|
2508
3550
|
return;
|
|
2509
3551
|
this.running = true;
|
|
3552
|
+
this.stopping = false;
|
|
2510
3553
|
this.poll();
|
|
2511
3554
|
}
|
|
2512
3555
|
/**
|
|
@@ -2514,6 +3557,7 @@ var VectorWorkerV2 = class {
|
|
|
2514
3557
|
*/
|
|
2515
3558
|
stop() {
|
|
2516
3559
|
this.running = false;
|
|
3560
|
+
this.stopping = true;
|
|
2517
3561
|
if (this.pollTimeout) {
|
|
2518
3562
|
clearTimeout(this.pollTimeout);
|
|
2519
3563
|
this.pollTimeout = null;
|
|
@@ -2534,8 +3578,13 @@ var VectorWorkerV2 = class {
|
|
|
2534
3578
|
await this.outbox.markDone(job.jobId);
|
|
2535
3579
|
successCount++;
|
|
2536
3580
|
} catch (error) {
|
|
2537
|
-
|
|
2538
|
-
|
|
3581
|
+
if (!this.stopping) {
|
|
3582
|
+
try {
|
|
3583
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3584
|
+
await this.outbox.markFailed(job.jobId, errorMessage);
|
|
3585
|
+
} catch {
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
2539
3588
|
}
|
|
2540
3589
|
}
|
|
2541
3590
|
return successCount;
|
|
@@ -2568,14 +3617,18 @@ var VectorWorkerV2 = class {
|
|
|
2568
3617
|
* Poll for new jobs
|
|
2569
3618
|
*/
|
|
2570
3619
|
async poll() {
|
|
2571
|
-
if (!this.running)
|
|
3620
|
+
if (!this.running || this.stopping)
|
|
2572
3621
|
return;
|
|
2573
3622
|
try {
|
|
2574
3623
|
await this.processBatch();
|
|
2575
3624
|
} catch (error) {
|
|
2576
|
-
|
|
3625
|
+
if (!this.stopping) {
|
|
3626
|
+
console.error("Vector worker V2 error:", error);
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
if (this.running && !this.stopping) {
|
|
3630
|
+
this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
|
|
2577
3631
|
}
|
|
2578
|
-
this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
|
|
2579
3632
|
}
|
|
2580
3633
|
/**
|
|
2581
3634
|
* Process all pending jobs (blocking)
|
|
@@ -2619,7 +3672,7 @@ function createVectorWorkerV2(db, vectorStore, embedder, config) {
|
|
|
2619
3672
|
}
|
|
2620
3673
|
|
|
2621
3674
|
// src/core/matcher.ts
|
|
2622
|
-
var
|
|
3675
|
+
var DEFAULT_CONFIG4 = {
|
|
2623
3676
|
weights: {
|
|
2624
3677
|
semanticSimilarity: 0.4,
|
|
2625
3678
|
ftsScore: 0.25,
|
|
@@ -2634,9 +3687,9 @@ var Matcher = class {
|
|
|
2634
3687
|
config;
|
|
2635
3688
|
constructor(config = {}) {
|
|
2636
3689
|
this.config = {
|
|
2637
|
-
...
|
|
3690
|
+
...DEFAULT_CONFIG4,
|
|
2638
3691
|
...config,
|
|
2639
|
-
weights: { ...
|
|
3692
|
+
weights: { ...DEFAULT_CONFIG4.weights, ...config.weights }
|
|
2640
3693
|
};
|
|
2641
3694
|
}
|
|
2642
3695
|
/**
|
|
@@ -3696,7 +4749,7 @@ function createGraduationPipeline(eventStore) {
|
|
|
3696
4749
|
}
|
|
3697
4750
|
|
|
3698
4751
|
// src/core/graduation-worker.ts
|
|
3699
|
-
var
|
|
4752
|
+
var DEFAULT_CONFIG5 = {
|
|
3700
4753
|
evaluationIntervalMs: 3e5,
|
|
3701
4754
|
// 5 minutes
|
|
3702
4755
|
batchSize: 50,
|
|
@@ -3704,7 +4757,7 @@ var DEFAULT_CONFIG4 = {
|
|
|
3704
4757
|
// 1 hour cooldown between evaluations
|
|
3705
4758
|
};
|
|
3706
4759
|
var GraduationWorker = class {
|
|
3707
|
-
constructor(eventStore, graduation, config =
|
|
4760
|
+
constructor(eventStore, graduation, config = DEFAULT_CONFIG5) {
|
|
3708
4761
|
this.eventStore = eventStore;
|
|
3709
4762
|
this.graduation = graduation;
|
|
3710
4763
|
this.config = config;
|
|
@@ -3812,12 +4865,12 @@ function createGraduationWorker(eventStore, graduation, config) {
|
|
|
3812
4865
|
return new GraduationWorker(
|
|
3813
4866
|
eventStore,
|
|
3814
4867
|
graduation,
|
|
3815
|
-
{ ...
|
|
4868
|
+
{ ...DEFAULT_CONFIG5, ...config }
|
|
3816
4869
|
);
|
|
3817
4870
|
}
|
|
3818
4871
|
|
|
3819
4872
|
// src/core/task/task-matcher.ts
|
|
3820
|
-
var
|
|
4873
|
+
var DEFAULT_CONFIG6 = {
|
|
3821
4874
|
minCombinedScore: MATCH_THRESHOLDS.minCombinedScore,
|
|
3822
4875
|
minGap: MATCH_THRESHOLDS.minGap,
|
|
3823
4876
|
suggestionThreshold: MATCH_THRESHOLDS.suggestionThreshold,
|
|
@@ -3826,7 +4879,7 @@ var DEFAULT_CONFIG5 = {
|
|
|
3826
4879
|
var TaskMatcher = class {
|
|
3827
4880
|
constructor(db, config) {
|
|
3828
4881
|
this.db = db;
|
|
3829
|
-
this.config = { ...
|
|
4882
|
+
this.config = { ...DEFAULT_CONFIG6, ...config };
|
|
3830
4883
|
}
|
|
3831
4884
|
config;
|
|
3832
4885
|
/**
|
|
@@ -3990,7 +5043,7 @@ var TaskMatcher = class {
|
|
|
3990
5043
|
};
|
|
3991
5044
|
|
|
3992
5045
|
// src/core/task/blocker-resolver.ts
|
|
3993
|
-
import { randomUUID as
|
|
5046
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
3994
5047
|
var URL_PATTERN = /^https?:\/\/.+/;
|
|
3995
5048
|
var JIRA_PATTERN = /^[A-Z]+-\d+$/;
|
|
3996
5049
|
var GITHUB_ISSUE_PATTERN = /^[^\/]+\/[^#]+#\d+$/;
|
|
@@ -4127,7 +5180,7 @@ var BlockerResolver = class {
|
|
|
4127
5180
|
* Declare a new condition entity
|
|
4128
5181
|
*/
|
|
4129
5182
|
async declareCondition(text, canonicalKey, candidates) {
|
|
4130
|
-
const entityId =
|
|
5183
|
+
const entityId = randomUUID6();
|
|
4131
5184
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4132
5185
|
const currentJson = {
|
|
4133
5186
|
text,
|
|
@@ -4170,7 +5223,7 @@ var BlockerResolver = class {
|
|
|
4170
5223
|
* Declare a new artifact entity
|
|
4171
5224
|
*/
|
|
4172
5225
|
async declareArtifact(identifier, canonicalKey) {
|
|
4173
|
-
const entityId =
|
|
5226
|
+
const entityId = randomUUID6();
|
|
4174
5227
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4175
5228
|
let artifactType = "generic";
|
|
4176
5229
|
if (URL_PATTERN.test(identifier)) {
|
|
@@ -4235,7 +5288,7 @@ var BlockerResolver = class {
|
|
|
4235
5288
|
};
|
|
4236
5289
|
|
|
4237
5290
|
// src/core/task/task-resolver.ts
|
|
4238
|
-
import { randomUUID as
|
|
5291
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
4239
5292
|
var VALID_TRANSITIONS = {
|
|
4240
5293
|
pending: ["in_progress", "cancelled"],
|
|
4241
5294
|
in_progress: ["blocked", "done", "cancelled"],
|
|
@@ -4310,7 +5363,7 @@ var TaskResolver = class {
|
|
|
4310
5363
|
isNew: false
|
|
4311
5364
|
};
|
|
4312
5365
|
}
|
|
4313
|
-
const taskId =
|
|
5366
|
+
const taskId = randomUUID7();
|
|
4314
5367
|
const canonicalKey = makeEntityCanonicalKey("task", extracted.title, {
|
|
4315
5368
|
project: extracted.project
|
|
4316
5369
|
});
|
|
@@ -4463,7 +5516,7 @@ var TaskResolver = class {
|
|
|
4463
5516
|
* Emit task event to events table
|
|
4464
5517
|
*/
|
|
4465
5518
|
async emitTaskEvent(eventType, payload) {
|
|
4466
|
-
const eventId =
|
|
5519
|
+
const eventId = randomUUID7();
|
|
4467
5520
|
const now = /* @__PURE__ */ new Date();
|
|
4468
5521
|
const dedupeKey = makeTaskEventDedupeKey(
|
|
4469
5522
|
eventType,
|
|
@@ -4521,14 +5574,14 @@ var TaskResolver = class {
|
|
|
4521
5574
|
`INSERT INTO edges (edge_id, src_type, src_id, rel_type, dst_type, dst_id, meta_json)
|
|
4522
5575
|
VALUES (?, 'entity', ?, 'resolves_to', 'entity', ?, ?)
|
|
4523
5576
|
ON CONFLICT DO NOTHING`,
|
|
4524
|
-
[
|
|
5577
|
+
[randomUUID7(), conditionId, taskId, JSON.stringify({ resolved_at: (/* @__PURE__ */ new Date()).toISOString() })]
|
|
4525
5578
|
);
|
|
4526
5579
|
return eventId;
|
|
4527
5580
|
}
|
|
4528
5581
|
};
|
|
4529
5582
|
|
|
4530
5583
|
// src/core/task/task-projector.ts
|
|
4531
|
-
import { randomUUID as
|
|
5584
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
4532
5585
|
var PROJECTOR_NAME = "task_projector";
|
|
4533
5586
|
var TASK_EVENT_TYPES = [
|
|
4534
5587
|
"task_created",
|
|
@@ -4724,7 +5777,7 @@ var TaskProjector = class {
|
|
|
4724
5777
|
* Create blocker edge
|
|
4725
5778
|
*/
|
|
4726
5779
|
async createBlockerEdge(taskId, blocker, relType) {
|
|
4727
|
-
const edgeId =
|
|
5780
|
+
const edgeId = randomUUID8();
|
|
4728
5781
|
await dbRun(
|
|
4729
5782
|
this.db,
|
|
4730
5783
|
`INSERT INTO edges (edge_id, src_type, src_id, rel_type, dst_type, dst_id, meta_json, created_at)
|
|
@@ -4762,7 +5815,7 @@ var TaskProjector = class {
|
|
|
4762
5815
|
* Enqueue entity for vectorization
|
|
4763
5816
|
*/
|
|
4764
5817
|
async enqueueForVectorization(itemId, itemKind) {
|
|
4765
|
-
const jobId =
|
|
5818
|
+
const jobId = randomUUID8();
|
|
4766
5819
|
const embeddingVersion = "v1";
|
|
4767
5820
|
await dbRun(
|
|
4768
5821
|
this.db,
|
|
@@ -4882,7 +5935,7 @@ function createSharedEventStore(dbPath) {
|
|
|
4882
5935
|
}
|
|
4883
5936
|
|
|
4884
5937
|
// src/core/shared-store.ts
|
|
4885
|
-
import { randomUUID as
|
|
5938
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
4886
5939
|
var SharedStore = class {
|
|
4887
5940
|
constructor(sharedEventStore) {
|
|
4888
5941
|
this.sharedEventStore = sharedEventStore;
|
|
@@ -4894,7 +5947,7 @@ var SharedStore = class {
|
|
|
4894
5947
|
* Promote a verified troubleshooting entry to shared storage
|
|
4895
5948
|
*/
|
|
4896
5949
|
async promoteEntry(input) {
|
|
4897
|
-
const entryId =
|
|
5950
|
+
const entryId = randomUUID9();
|
|
4898
5951
|
await dbRun(
|
|
4899
5952
|
this.db,
|
|
4900
5953
|
`INSERT INTO shared_troubleshooting (
|
|
@@ -5259,7 +6312,7 @@ function createSharedVectorStore(dbPath) {
|
|
|
5259
6312
|
}
|
|
5260
6313
|
|
|
5261
6314
|
// src/core/shared-promoter.ts
|
|
5262
|
-
import { randomUUID as
|
|
6315
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
5263
6316
|
var SharedPromoter = class {
|
|
5264
6317
|
constructor(sharedStore, sharedVectorStore, embedder, config) {
|
|
5265
6318
|
this.sharedStore = sharedStore;
|
|
@@ -5327,7 +6380,7 @@ var SharedPromoter = class {
|
|
|
5327
6380
|
const embeddingContent = this.createEmbeddingContent(input);
|
|
5328
6381
|
const embedding = await this.embedder.embed(embeddingContent);
|
|
5329
6382
|
await this.sharedVectorStore.upsert({
|
|
5330
|
-
id:
|
|
6383
|
+
id: randomUUID10(),
|
|
5331
6384
|
entryId,
|
|
5332
6385
|
entryType: "troubleshooting",
|
|
5333
6386
|
content: embeddingContent,
|
|
@@ -5497,6 +6550,7 @@ export {
|
|
|
5497
6550
|
ProgressiveSearchResultSchema,
|
|
5498
6551
|
RelationTypeSchema,
|
|
5499
6552
|
Retriever,
|
|
6553
|
+
SQLiteEventStore,
|
|
5500
6554
|
SearchIndexItemSchema,
|
|
5501
6555
|
SessionSchema,
|
|
5502
6556
|
SharedEntryTypeSchema,
|
|
@@ -5506,6 +6560,7 @@ export {
|
|
|
5506
6560
|
SharedStoreConfigSchema,
|
|
5507
6561
|
SharedTroubleshootingEntrySchema,
|
|
5508
6562
|
SharedVectorStore,
|
|
6563
|
+
SyncWorker,
|
|
5509
6564
|
TaskBlockersSetPayloadSchema,
|
|
5510
6565
|
TaskCreatedPayloadSchema,
|
|
5511
6566
|
TaskCurrentJsonSchema,
|
|
@@ -5529,6 +6584,7 @@ export {
|
|
|
5529
6584
|
createGraduationPipeline,
|
|
5530
6585
|
createGraduationWorker,
|
|
5531
6586
|
createRetriever,
|
|
6587
|
+
createSQLiteDatabase,
|
|
5532
6588
|
createSharedEventStore,
|
|
5533
6589
|
createSharedPromoter,
|
|
5534
6590
|
createSharedStore,
|
|
@@ -5545,6 +6601,14 @@ export {
|
|
|
5545
6601
|
makeDedupeKey,
|
|
5546
6602
|
makeEntityCanonicalKey,
|
|
5547
6603
|
makeTaskEventDedupeKey,
|
|
5548
|
-
parseEntityCanonicalKey
|
|
6604
|
+
parseEntityCanonicalKey,
|
|
6605
|
+
sqliteAll,
|
|
6606
|
+
sqliteClose,
|
|
6607
|
+
sqliteExec,
|
|
6608
|
+
sqliteGet,
|
|
6609
|
+
sqliteRun,
|
|
6610
|
+
sqliteTransaction,
|
|
6611
|
+
toDateFromSQLite,
|
|
6612
|
+
toSQLiteTimestamp
|
|
5549
6613
|
};
|
|
5550
6614
|
//# sourceMappingURL=index.js.map
|