claude-memory-layer 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.claude/settings.local.json +7 -1
  2. package/.claude-memory/test.sqlite +0 -0
  3. package/.history/package_20260202114053.json +49 -0
  4. package/HANDOFF.md +92 -0
  5. package/dist/cli/index.js +1150 -71
  6. package/dist/cli/index.js.map +4 -4
  7. package/dist/core/index.js +1033 -47
  8. package/dist/core/index.js.map +4 -4
  9. package/dist/hooks/post-tool-use.js +5589 -0
  10. package/dist/hooks/post-tool-use.js.map +7 -0
  11. package/dist/hooks/session-end.js +1117 -64
  12. package/dist/hooks/session-end.js.map +4 -4
  13. package/dist/hooks/session-start.js +1112 -63
  14. package/dist/hooks/session-start.js.map +4 -4
  15. package/dist/hooks/stop.js +1117 -64
  16. package/dist/hooks/stop.js.map +4 -4
  17. package/dist/hooks/user-prompt-submit.js +1151 -67
  18. package/dist/hooks/user-prompt-submit.js.map +4 -4
  19. package/dist/server/api/index.js +1145 -70
  20. package/dist/server/api/index.js.map +4 -4
  21. package/dist/server/index.js +1145 -70
  22. package/dist/server/index.js.map +4 -4
  23. package/dist/services/memory-service.js +1122 -65
  24. package/dist/services/memory-service.js.map +4 -4
  25. package/dist/ui/app.js +304 -0
  26. package/dist/ui/index.html +195 -1188
  27. package/dist/ui/style.css +595 -0
  28. package/package.json +3 -1
  29. package/scripts/build.ts +2 -0
  30. package/src/core/event-store.ts +18 -0
  31. package/src/core/index.ts +3 -0
  32. package/src/core/retriever.ts +4 -1
  33. package/src/core/sqlite-event-store.ts +849 -0
  34. package/src/core/sqlite-wrapper.ts +108 -0
  35. package/src/core/sync-worker.ts +228 -0
  36. package/src/core/vector-worker.ts +44 -14
  37. package/src/hooks/user-prompt-submit.ts +53 -4
  38. package/src/server/api/stats.ts +37 -7
  39. package/src/services/memory-service.ts +168 -39
  40. package/src/ui/app.js +304 -0
  41. package/src/ui/index.html +195 -1188
  42. package/src/ui/style.css +595 -0
  43. package/test_access.js +49 -0
@@ -1273,31 +1273,981 @@ 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
+ const tableInfo = sqliteAll(this.db, "PRAGMA table_info(events)", []);
1612
+ const columnNames = tableInfo.map((col) => col.name);
1613
+ if (!columnNames.includes("access_count")) {
1614
+ try {
1615
+ sqliteExec(this.db, `
1616
+ ALTER TABLE events ADD COLUMN access_count INTEGER DEFAULT 0;
1617
+ `);
1618
+ } catch (err) {
1619
+ console.error("Error adding access_count column:", err);
1620
+ }
1621
+ }
1622
+ if (!columnNames.includes("last_accessed_at")) {
1623
+ try {
1624
+ sqliteExec(this.db, `
1625
+ ALTER TABLE events ADD COLUMN last_accessed_at TEXT;
1626
+ `);
1627
+ } catch (err) {
1628
+ console.error("Error adding last_accessed_at column:", err);
1629
+ }
1630
+ }
1631
+ try {
1632
+ sqliteExec(this.db, `
1633
+ CREATE INDEX IF NOT EXISTS idx_events_access_count ON events(access_count DESC);
1634
+ `);
1635
+ } catch (err) {
1636
+ }
1637
+ try {
1638
+ sqliteExec(this.db, `
1639
+ CREATE INDEX IF NOT EXISTS idx_events_last_accessed ON events(last_accessed_at DESC);
1640
+ `);
1641
+ } catch (err) {
1642
+ }
1643
+ this.initialized = true;
1644
+ }
1645
+ /**
1646
+ * Append event to store (Append-only, Idempotent)
1647
+ */
1648
+ async append(input) {
1649
+ await this.initialize();
1650
+ const canonicalKey = makeCanonicalKey(input.content);
1651
+ const dedupeKey = makeDedupeKey(input.content, input.sessionId);
1652
+ const existing = sqliteGet(
1653
+ this.db,
1654
+ `SELECT event_id FROM event_dedup WHERE dedupe_key = ?`,
1655
+ [dedupeKey]
1656
+ );
1657
+ if (existing) {
1658
+ return {
1659
+ success: true,
1660
+ eventId: existing.event_id,
1661
+ isDuplicate: true
1662
+ };
1663
+ }
1664
+ const id = randomUUID2();
1665
+ const timestamp = toSQLiteTimestamp(input.timestamp);
1666
+ try {
1667
+ const insertEvent = this.db.prepare(`
1668
+ INSERT INTO events (id, event_type, session_id, timestamp, content, canonical_key, dedupe_key, metadata)
1669
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1670
+ `);
1671
+ const insertDedup = this.db.prepare(`
1672
+ INSERT INTO event_dedup (dedupe_key, event_id) VALUES (?, ?)
1673
+ `);
1674
+ const insertLevel = this.db.prepare(`
1675
+ INSERT INTO memory_levels (event_id, level) VALUES (?, 'L0')
1676
+ `);
1677
+ const transaction = this.db.transaction(() => {
1678
+ insertEvent.run(
1679
+ id,
1680
+ input.eventType,
1681
+ input.sessionId,
1682
+ timestamp,
1683
+ input.content,
1684
+ canonicalKey,
1685
+ dedupeKey,
1686
+ JSON.stringify(input.metadata || {})
1687
+ );
1688
+ insertDedup.run(dedupeKey, id);
1689
+ insertLevel.run(id);
1690
+ });
1691
+ transaction();
1692
+ return { success: true, eventId: id, isDuplicate: false };
1693
+ } catch (error) {
1694
+ return {
1695
+ success: false,
1696
+ error: error instanceof Error ? error.message : String(error)
1697
+ };
1698
+ }
1699
+ }
1700
+ /**
1701
+ * Get events by session ID
1702
+ */
1703
+ async getSessionEvents(sessionId) {
1704
+ await this.initialize();
1705
+ const rows = sqliteAll(
1706
+ this.db,
1707
+ `SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC`,
1708
+ [sessionId]
1709
+ );
1710
+ return rows.map(this.rowToEvent);
1711
+ }
1712
+ /**
1713
+ * Get recent events
1714
+ */
1715
+ async getRecentEvents(limit = 100) {
1716
+ await this.initialize();
1717
+ const rows = sqliteAll(
1718
+ this.db,
1719
+ `SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`,
1720
+ [limit]
1721
+ );
1722
+ return rows.map(this.rowToEvent);
1723
+ }
1724
+ /**
1725
+ * Get event by ID
1726
+ */
1727
+ async getEvent(id) {
1728
+ await this.initialize();
1729
+ const row = sqliteGet(
1730
+ this.db,
1731
+ `SELECT * FROM events WHERE id = ?`,
1732
+ [id]
1733
+ );
1734
+ if (!row)
1735
+ return null;
1736
+ return this.rowToEvent(row);
1737
+ }
1738
+ /**
1739
+ * Get events since a timestamp (for sync)
1740
+ */
1741
+ async getEventsSince(timestamp, limit = 1e3) {
1742
+ await this.initialize();
1743
+ const rows = sqliteAll(
1744
+ this.db,
1745
+ `SELECT * FROM events WHERE timestamp > ? ORDER BY timestamp ASC LIMIT ?`,
1746
+ [timestamp, limit]
1747
+ );
1748
+ return rows.map(this.rowToEvent);
1749
+ }
1750
+ /**
1751
+ * Create or update session
1752
+ */
1753
+ async upsertSession(session) {
1754
+ await this.initialize();
1755
+ const existing = sqliteGet(
1756
+ this.db,
1757
+ `SELECT id FROM sessions WHERE id = ?`,
1758
+ [session.id]
1759
+ );
1760
+ if (!existing) {
1761
+ sqliteRun(
1762
+ this.db,
1763
+ `INSERT INTO sessions (id, started_at, project_path, tags)
1764
+ VALUES (?, ?, ?, ?)`,
1765
+ [
1766
+ session.id,
1767
+ toSQLiteTimestamp(session.startedAt || /* @__PURE__ */ new Date()),
1768
+ session.projectPath || null,
1769
+ JSON.stringify(session.tags || [])
1770
+ ]
1771
+ );
1772
+ } else {
1773
+ const updates = [];
1774
+ const values = [];
1775
+ if (session.endedAt) {
1776
+ updates.push("ended_at = ?");
1777
+ values.push(toSQLiteTimestamp(session.endedAt));
1778
+ }
1779
+ if (session.summary) {
1780
+ updates.push("summary = ?");
1781
+ values.push(session.summary);
1782
+ }
1783
+ if (session.tags) {
1784
+ updates.push("tags = ?");
1785
+ values.push(JSON.stringify(session.tags));
1786
+ }
1787
+ if (updates.length > 0) {
1788
+ values.push(session.id);
1789
+ sqliteRun(
1790
+ this.db,
1791
+ `UPDATE sessions SET ${updates.join(", ")} WHERE id = ?`,
1792
+ values
1793
+ );
1794
+ }
1795
+ }
1796
+ }
1797
+ /**
1798
+ * Get session by ID
1799
+ */
1800
+ async getSession(id) {
1801
+ await this.initialize();
1802
+ const row = sqliteGet(
1803
+ this.db,
1804
+ `SELECT * FROM sessions WHERE id = ?`,
1805
+ [id]
1806
+ );
1807
+ if (!row)
1808
+ return null;
1809
+ return {
1810
+ id: row.id,
1811
+ startedAt: toDateFromSQLite(row.started_at),
1812
+ endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
1813
+ projectPath: row.project_path,
1814
+ summary: row.summary,
1815
+ tags: row.tags ? JSON.parse(row.tags) : void 0
1816
+ };
1817
+ }
1818
+ /**
1819
+ * Get all sessions
1820
+ */
1821
+ async getAllSessions() {
1822
+ await this.initialize();
1823
+ const rows = sqliteAll(
1824
+ this.db,
1825
+ `SELECT * FROM sessions ORDER BY started_at DESC`
1826
+ );
1827
+ return rows.map((row) => ({
1828
+ id: row.id,
1829
+ startedAt: toDateFromSQLite(row.started_at),
1830
+ endedAt: row.ended_at ? toDateFromSQLite(row.ended_at) : void 0,
1831
+ projectPath: row.project_path,
1832
+ summary: row.summary,
1833
+ tags: row.tags ? JSON.parse(row.tags) : void 0
1834
+ }));
1835
+ }
1836
+ /**
1837
+ * Add to embedding outbox
1838
+ */
1839
+ async enqueueForEmbedding(eventId, content) {
1840
+ await this.initialize();
1841
+ const id = randomUUID2();
1842
+ sqliteRun(
1843
+ this.db,
1844
+ `INSERT INTO embedding_outbox (id, event_id, content, status, retry_count)
1845
+ VALUES (?, ?, ?, 'pending', 0)`,
1846
+ [id, eventId, content]
1847
+ );
1848
+ return id;
1849
+ }
1850
+ /**
1851
+ * Get pending outbox items
1852
+ */
1853
+ async getPendingOutboxItems(limit = 32) {
1854
+ await this.initialize();
1855
+ const pending = sqliteAll(
1856
+ this.db,
1857
+ `SELECT * FROM embedding_outbox
1858
+ WHERE status = 'pending'
1859
+ ORDER BY created_at
1860
+ LIMIT ?`,
1861
+ [limit]
1862
+ );
1863
+ if (pending.length === 0)
1864
+ return [];
1865
+ const ids = pending.map((r) => r.id);
1866
+ const placeholders = ids.map(() => "?").join(",");
1867
+ sqliteRun(
1868
+ this.db,
1869
+ `UPDATE embedding_outbox SET status = 'processing' WHERE id IN (${placeholders})`,
1870
+ ids
1871
+ );
1872
+ return pending.map((row) => ({
1873
+ id: row.id,
1874
+ eventId: row.event_id,
1875
+ content: row.content,
1876
+ status: "processing",
1877
+ retryCount: row.retry_count,
1878
+ createdAt: toDateFromSQLite(row.created_at),
1879
+ errorMessage: row.error_message
1880
+ }));
1881
+ }
1882
+ /**
1883
+ * Mark outbox items as done
1884
+ */
1885
+ async completeOutboxItems(ids) {
1886
+ if (ids.length === 0)
1887
+ return;
1888
+ const placeholders = ids.map(() => "?").join(",");
1889
+ sqliteRun(
1890
+ this.db,
1891
+ `DELETE FROM embedding_outbox WHERE id IN (${placeholders})`,
1892
+ ids
1893
+ );
1894
+ }
1895
+ /**
1896
+ * Mark outbox items as failed
1897
+ */
1898
+ async failOutboxItems(ids, error) {
1899
+ if (ids.length === 0)
1900
+ return;
1901
+ const placeholders = ids.map(() => "?").join(",");
1902
+ sqliteRun(
1903
+ this.db,
1904
+ `UPDATE embedding_outbox
1905
+ SET status = CASE WHEN retry_count >= 3 THEN 'failed' ELSE 'pending' END,
1906
+ retry_count = retry_count + 1,
1907
+ error_message = ?
1908
+ WHERE id IN (${placeholders})`,
1909
+ [error, ...ids]
1910
+ );
1911
+ }
1912
+ /**
1913
+ * Update memory level
1914
+ */
1915
+ async updateMemoryLevel(eventId, level) {
1916
+ await this.initialize();
1917
+ sqliteRun(
1918
+ this.db,
1919
+ `UPDATE memory_levels SET level = ?, promoted_at = datetime('now') WHERE event_id = ?`,
1920
+ [level, eventId]
1921
+ );
1922
+ }
1923
+ /**
1924
+ * Get memory level statistics
1925
+ */
1926
+ async getLevelStats() {
1927
+ await this.initialize();
1928
+ const rows = sqliteAll(
1929
+ this.db,
1930
+ `SELECT level, COUNT(*) as count FROM memory_levels GROUP BY level`
1931
+ );
1932
+ return rows;
1933
+ }
1934
+ /**
1935
+ * Get events by memory level
1936
+ */
1937
+ async getEventsByLevel(level, options) {
1938
+ await this.initialize();
1939
+ const limit = options?.limit || 50;
1940
+ const offset = options?.offset || 0;
1941
+ const rows = sqliteAll(
1942
+ this.db,
1943
+ `SELECT e.* FROM events e
1944
+ INNER JOIN memory_levels ml ON e.id = ml.event_id
1945
+ WHERE ml.level = ?
1946
+ ORDER BY e.timestamp DESC
1947
+ LIMIT ? OFFSET ?`,
1948
+ [level, limit, offset]
1949
+ );
1950
+ return rows.map((row) => this.rowToEvent(row));
1951
+ }
1952
+ /**
1953
+ * Get memory level for a specific event
1954
+ */
1955
+ async getEventLevel(eventId) {
1956
+ await this.initialize();
1957
+ const row = sqliteGet(
1958
+ this.db,
1959
+ `SELECT level FROM memory_levels WHERE event_id = ?`,
1960
+ [eventId]
1961
+ );
1962
+ return row ? row.level : null;
1963
+ }
1964
+ /**
1965
+ * Get sync position for a target
1966
+ */
1967
+ async getSyncPosition(targetName) {
1968
+ await this.initialize();
1969
+ const row = sqliteGet(
1970
+ this.db,
1971
+ `SELECT last_event_id, last_timestamp FROM sync_positions WHERE target_name = ?`,
1972
+ [targetName]
1973
+ );
1974
+ return {
1975
+ lastEventId: row?.last_event_id ?? null,
1976
+ lastTimestamp: row?.last_timestamp ?? null
1977
+ };
1978
+ }
1979
+ /**
1980
+ * Update sync position for a target
1981
+ */
1982
+ async updateSyncPosition(targetName, lastEventId, lastTimestamp) {
1983
+ await this.initialize();
1984
+ sqliteRun(
1985
+ this.db,
1986
+ `INSERT OR REPLACE INTO sync_positions (target_name, last_event_id, last_timestamp, updated_at)
1987
+ VALUES (?, ?, ?, datetime('now'))`,
1988
+ [targetName, lastEventId, lastTimestamp]
1989
+ );
1990
+ }
1991
+ /**
1992
+ * Get config value for endless mode
1993
+ */
1994
+ async getEndlessConfig(key) {
1995
+ await this.initialize();
1996
+ const row = sqliteGet(
1997
+ this.db,
1998
+ `SELECT value FROM endless_config WHERE key = ?`,
1999
+ [key]
2000
+ );
2001
+ if (!row)
2002
+ return null;
2003
+ return JSON.parse(row.value);
2004
+ }
2005
+ /**
2006
+ * Set config value for endless mode
2007
+ */
2008
+ async setEndlessConfig(key, value) {
2009
+ await this.initialize();
2010
+ sqliteRun(
2011
+ this.db,
2012
+ `INSERT OR REPLACE INTO endless_config (key, value, updated_at)
2013
+ VALUES (?, ?, datetime('now'))`,
2014
+ [key, JSON.stringify(value)]
2015
+ );
2016
+ }
2017
+ /**
2018
+ * Increment access count for events
2019
+ */
2020
+ async incrementAccessCount(eventIds) {
2021
+ if (eventIds.length === 0 || this.readOnly)
2022
+ return;
2023
+ await this.initialize();
2024
+ const placeholders = eventIds.map(() => "?").join(",");
2025
+ const currentTime = toSQLiteTimestamp(/* @__PURE__ */ new Date());
2026
+ sqliteRun(
2027
+ this.db,
2028
+ `UPDATE events
2029
+ SET access_count = access_count + 1,
2030
+ last_accessed_at = ?
2031
+ WHERE id IN (${placeholders})`,
2032
+ [currentTime, ...eventIds]
2033
+ );
2034
+ }
2035
+ /**
2036
+ * Get most accessed memories
2037
+ */
2038
+ async getMostAccessed(limit = 10) {
2039
+ await this.initialize();
2040
+ const rows = sqliteAll(
2041
+ this.db,
2042
+ `SELECT * FROM events
2043
+ WHERE access_count > 0
2044
+ ORDER BY access_count DESC, last_accessed_at DESC
2045
+ LIMIT ?`,
2046
+ [limit]
2047
+ );
2048
+ return rows.map((row) => this.rowToEvent(row));
2049
+ }
2050
+ /**
2051
+ * Get database instance for direct access
2052
+ */
2053
+ getDatabase() {
2054
+ return this.db;
2055
+ }
1276
2056
  /**
1277
2057
  * Close database connection
1278
2058
  */
1279
2059
  async close() {
1280
- await dbClose(this.db);
2060
+ sqliteClose(this.db);
1281
2061
  }
1282
2062
  /**
1283
2063
  * Convert database row to MemoryEvent
1284
2064
  */
1285
2065
  rowToEvent(row) {
1286
- return {
2066
+ const event = {
1287
2067
  id: row.id,
1288
2068
  eventType: row.event_type,
1289
2069
  sessionId: row.session_id,
1290
- timestamp: toDate(row.timestamp),
2070
+ timestamp: toDateFromSQLite(row.timestamp),
1291
2071
  content: row.content,
1292
2072
  canonicalKey: row.canonical_key,
1293
2073
  dedupeKey: row.dedupe_key,
1294
2074
  metadata: row.metadata ? JSON.parse(row.metadata) : void 0
1295
2075
  };
2076
+ if (row.access_count !== void 0) {
2077
+ event.access_count = row.access_count;
2078
+ }
2079
+ if (row.last_accessed_at !== void 0) {
2080
+ event.last_accessed_at = row.last_accessed_at;
2081
+ }
2082
+ return event;
2083
+ }
2084
+ };
2085
+
2086
+ // src/core/sync-worker.ts
2087
+ var DEFAULT_CONFIG = {
2088
+ intervalMs: 3e4,
2089
+ batchSize: 500,
2090
+ maxRetries: 3,
2091
+ retryDelayMs: 5e3
2092
+ };
2093
+ var SyncWorker = class {
2094
+ constructor(sqliteStore, duckdbStore, config) {
2095
+ this.sqliteStore = sqliteStore;
2096
+ this.duckdbStore = duckdbStore;
2097
+ this.config = { ...DEFAULT_CONFIG, ...config };
2098
+ }
2099
+ config;
2100
+ intervalHandle = null;
2101
+ running = false;
2102
+ stats = {
2103
+ lastSyncAt: null,
2104
+ eventsSynced: 0,
2105
+ sessionsSynced: 0,
2106
+ errors: 0,
2107
+ status: "idle"
2108
+ };
2109
+ /**
2110
+ * Start the sync worker
2111
+ */
2112
+ start() {
2113
+ if (this.running)
2114
+ return;
2115
+ this.running = true;
2116
+ this.stats.status = "idle";
2117
+ this.syncNow().catch((err) => {
2118
+ console.error("[SyncWorker] Initial sync failed:", err);
2119
+ });
2120
+ this.intervalHandle = setInterval(() => {
2121
+ this.syncNow().catch((err) => {
2122
+ console.error("[SyncWorker] Periodic sync failed:", err);
2123
+ });
2124
+ }, this.config.intervalMs);
2125
+ }
2126
+ /**
2127
+ * Stop the sync worker
2128
+ */
2129
+ stop() {
2130
+ this.running = false;
2131
+ this.stats.status = "stopped";
2132
+ if (this.intervalHandle) {
2133
+ clearInterval(this.intervalHandle);
2134
+ this.intervalHandle = null;
2135
+ }
2136
+ }
2137
+ /**
2138
+ * Trigger immediate sync
2139
+ */
2140
+ async syncNow() {
2141
+ if (this.stats.status === "syncing") {
2142
+ return;
2143
+ }
2144
+ this.stats.status = "syncing";
2145
+ try {
2146
+ await this.syncEvents();
2147
+ await this.syncSessions();
2148
+ this.stats.lastSyncAt = /* @__PURE__ */ new Date();
2149
+ this.stats.status = "idle";
2150
+ } catch (error) {
2151
+ this.stats.errors++;
2152
+ this.stats.status = "error";
2153
+ throw error;
2154
+ }
2155
+ }
2156
+ /**
2157
+ * Sync events from SQLite to DuckDB
2158
+ */
2159
+ async syncEvents() {
2160
+ const targetName = "duckdb_analytics";
2161
+ const position = await this.sqliteStore.getSyncPosition(targetName);
2162
+ const lastTimestamp = position.lastTimestamp || "1970-01-01T00:00:00.000Z";
2163
+ let hasMore = true;
2164
+ let totalSynced = 0;
2165
+ while (hasMore) {
2166
+ const events = await this.sqliteStore.getEventsSince(lastTimestamp, this.config.batchSize);
2167
+ if (events.length === 0) {
2168
+ hasMore = false;
2169
+ break;
2170
+ }
2171
+ await this.retryWithBackoff(async () => {
2172
+ for (const event of events) {
2173
+ await this.insertEventToDuckDB(event);
2174
+ }
2175
+ });
2176
+ totalSynced += events.length;
2177
+ const lastEvent = events[events.length - 1];
2178
+ await this.sqliteStore.updateSyncPosition(
2179
+ targetName,
2180
+ lastEvent.id,
2181
+ lastEvent.timestamp.toISOString()
2182
+ );
2183
+ hasMore = events.length === this.config.batchSize;
2184
+ }
2185
+ this.stats.eventsSynced += totalSynced;
2186
+ }
2187
+ /**
2188
+ * Sync sessions from SQLite to DuckDB
2189
+ */
2190
+ async syncSessions() {
2191
+ const sessions = await this.sqliteStore.getAllSessions();
2192
+ for (const session of sessions) {
2193
+ await this.retryWithBackoff(async () => {
2194
+ await this.duckdbStore.upsertSession(session);
2195
+ });
2196
+ }
2197
+ this.stats.sessionsSynced = sessions.length;
2198
+ }
2199
+ /**
2200
+ * Insert a single event into DuckDB
2201
+ */
2202
+ async insertEventToDuckDB(event) {
2203
+ await this.duckdbStore.append({
2204
+ eventType: event.eventType,
2205
+ sessionId: event.sessionId,
2206
+ timestamp: event.timestamp,
2207
+ content: event.content,
2208
+ metadata: event.metadata
2209
+ });
2210
+ }
2211
+ /**
2212
+ * Retry operation with exponential backoff
2213
+ */
2214
+ async retryWithBackoff(fn) {
2215
+ let lastError = null;
2216
+ for (let attempt = 0; attempt < this.config.maxRetries; attempt++) {
2217
+ try {
2218
+ return await fn();
2219
+ } catch (error) {
2220
+ lastError = error instanceof Error ? error : new Error(String(error));
2221
+ if (attempt < this.config.maxRetries - 1) {
2222
+ const delay = this.config.retryDelayMs * Math.pow(2, attempt);
2223
+ await this.sleep(delay);
2224
+ }
2225
+ }
2226
+ }
2227
+ throw lastError;
2228
+ }
2229
+ /**
2230
+ * Sleep utility
2231
+ */
2232
+ sleep(ms) {
2233
+ return new Promise((resolve) => setTimeout(resolve, ms));
2234
+ }
2235
+ /**
2236
+ * Get sync statistics
2237
+ */
2238
+ getStats() {
2239
+ return { ...this.stats };
2240
+ }
2241
+ /**
2242
+ * Check if worker is running
2243
+ */
2244
+ isRunning() {
2245
+ return this.running;
1296
2246
  }
1297
2247
  };
1298
2248
 
1299
2249
  // src/core/entity-repo.ts
1300
- import { randomUUID as randomUUID2 } from "crypto";
2250
+ import { randomUUID as randomUUID3 } from "crypto";
1301
2251
  var EntityRepo = class {
1302
2252
  constructor(db) {
1303
2253
  this.db = db;
@@ -1306,7 +2256,7 @@ var EntityRepo = class {
1306
2256
  * Create a new entity
1307
2257
  */
1308
2258
  async create(input) {
1309
- const entityId = randomUUID2();
2259
+ const entityId = randomUUID3();
1310
2260
  const canonicalKey = makeEntityCanonicalKey(input.entityType, input.title, {
1311
2261
  project: input.project
1312
2262
  });
@@ -1560,7 +2510,7 @@ var EntityRepo = class {
1560
2510
  };
1561
2511
 
1562
2512
  // src/core/edge-repo.ts
1563
- import { randomUUID as randomUUID3 } from "crypto";
2513
+ import { randomUUID as randomUUID4 } from "crypto";
1564
2514
  var EdgeRepo = class {
1565
2515
  constructor(db) {
1566
2516
  this.db = db;
@@ -1569,7 +2519,7 @@ var EdgeRepo = class {
1569
2519
  * Create a new edge (idempotent - ignores duplicates)
1570
2520
  */
1571
2521
  async create(input) {
1572
- const edgeId = randomUUID3();
2522
+ const edgeId = randomUUID4();
1573
2523
  const now = /* @__PURE__ */ new Date();
1574
2524
  await dbRun(
1575
2525
  this.db,
@@ -2042,8 +2992,8 @@ function getDefaultEmbedder() {
2042
2992
  }
2043
2993
 
2044
2994
  // src/core/vector-outbox.ts
2045
- import { randomUUID as randomUUID4 } from "crypto";
2046
- var DEFAULT_CONFIG = {
2995
+ import { randomUUID as randomUUID5 } from "crypto";
2996
+ var DEFAULT_CONFIG2 = {
2047
2997
  embeddingVersion: "v1",
2048
2998
  maxRetries: 3,
2049
2999
  stuckThresholdMs: 5 * 60 * 1e3,
@@ -2053,7 +3003,7 @@ var DEFAULT_CONFIG = {
2053
3003
  var VectorOutbox = class {
2054
3004
  constructor(db, config) {
2055
3005
  this.db = db;
2056
- this.config = { ...DEFAULT_CONFIG, ...config };
3006
+ this.config = { ...DEFAULT_CONFIG2, ...config };
2057
3007
  }
2058
3008
  config;
2059
3009
  /**
@@ -2061,7 +3011,7 @@ var VectorOutbox = class {
2061
3011
  */
2062
3012
  async enqueue(itemKind, itemId, embeddingVersion) {
2063
3013
  const version = embeddingVersion ?? this.config.embeddingVersion;
2064
- const jobId = randomUUID4();
3014
+ const jobId = randomUUID5();
2065
3015
  const now = (/* @__PURE__ */ new Date()).toISOString();
2066
3016
  await dbRun(
2067
3017
  this.db,
@@ -2281,7 +3231,7 @@ var VectorOutbox = class {
2281
3231
  };
2282
3232
 
2283
3233
  // src/core/vector-worker.ts
2284
- var DEFAULT_CONFIG2 = {
3234
+ var DEFAULT_CONFIG3 = {
2285
3235
  batchSize: 32,
2286
3236
  pollIntervalMs: 1e3,
2287
3237
  maxRetries: 3
@@ -2292,12 +3242,13 @@ var VectorWorker = class {
2292
3242
  embedder;
2293
3243
  config;
2294
3244
  running = false;
3245
+ stopping = false;
2295
3246
  pollTimeout = null;
2296
3247
  constructor(eventStore, vectorStore, embedder, config = {}) {
2297
3248
  this.eventStore = eventStore;
2298
3249
  this.vectorStore = vectorStore;
2299
3250
  this.embedder = embedder;
2300
- this.config = { ...DEFAULT_CONFIG2, ...config };
3251
+ this.config = { ...DEFAULT_CONFIG3, ...config };
2301
3252
  }
2302
3253
  /**
2303
3254
  * Start the worker polling loop
@@ -2306,6 +3257,7 @@ var VectorWorker = class {
2306
3257
  if (this.running)
2307
3258
  return;
2308
3259
  this.running = true;
3260
+ this.stopping = false;
2309
3261
  this.poll();
2310
3262
  }
2311
3263
  /**
@@ -2313,6 +3265,7 @@ var VectorWorker = class {
2313
3265
  */
2314
3266
  stop() {
2315
3267
  this.running = false;
3268
+ this.stopping = true;
2316
3269
  if (this.pollTimeout) {
2317
3270
  clearTimeout(this.pollTimeout);
2318
3271
  this.pollTimeout = null;
@@ -2362,9 +3315,15 @@ var VectorWorker = class {
2362
3315
  }
2363
3316
  return successful.length;
2364
3317
  } catch (error) {
2365
- const allIds = items.map((i) => i.id);
2366
- const errorMessage = error instanceof Error ? error.message : String(error);
2367
- await this.eventStore.failOutboxItems(allIds, errorMessage);
3318
+ if (!this.stopping) {
3319
+ try {
3320
+ const allIds = items.map((i) => i.id);
3321
+ const errorMessage = error instanceof Error ? error.message : String(error);
3322
+ await this.eventStore.failOutboxItems(allIds, errorMessage);
3323
+ } catch (failError) {
3324
+ console.warn("Could not mark outbox items as failed (database may be closed)");
3325
+ }
3326
+ }
2368
3327
  throw error;
2369
3328
  }
2370
3329
  }
@@ -2372,14 +3331,18 @@ var VectorWorker = class {
2372
3331
  * Poll for new items
2373
3332
  */
2374
3333
  async poll() {
2375
- if (!this.running)
3334
+ if (!this.running || this.stopping)
2376
3335
  return;
2377
3336
  try {
2378
3337
  await this.processBatch();
2379
3338
  } catch (error) {
2380
- console.error("Vector worker error:", error);
3339
+ if (!this.stopping) {
3340
+ console.error("Vector worker error:", error);
3341
+ }
3342
+ }
3343
+ if (this.running && !this.stopping) {
3344
+ this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
2381
3345
  }
2382
- this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
2383
3346
  }
2384
3347
  /**
2385
3348
  * Process all pending items (blocking)
@@ -2489,6 +3452,7 @@ var VectorWorkerV2 = class {
2489
3452
  contentProvider;
2490
3453
  config;
2491
3454
  running = false;
3455
+ stopping = false;
2492
3456
  pollTimeout = null;
2493
3457
  constructor(db, vectorStore, embedder, config = {}, contentProvider) {
2494
3458
  this.outbox = new VectorOutbox(db, {
@@ -2507,6 +3471,7 @@ var VectorWorkerV2 = class {
2507
3471
  if (this.running)
2508
3472
  return;
2509
3473
  this.running = true;
3474
+ this.stopping = false;
2510
3475
  this.poll();
2511
3476
  }
2512
3477
  /**
@@ -2514,6 +3479,7 @@ var VectorWorkerV2 = class {
2514
3479
  */
2515
3480
  stop() {
2516
3481
  this.running = false;
3482
+ this.stopping = true;
2517
3483
  if (this.pollTimeout) {
2518
3484
  clearTimeout(this.pollTimeout);
2519
3485
  this.pollTimeout = null;
@@ -2534,8 +3500,13 @@ var VectorWorkerV2 = class {
2534
3500
  await this.outbox.markDone(job.jobId);
2535
3501
  successCount++;
2536
3502
  } catch (error) {
2537
- const errorMessage = error instanceof Error ? error.message : String(error);
2538
- await this.outbox.markFailed(job.jobId, errorMessage);
3503
+ if (!this.stopping) {
3504
+ try {
3505
+ const errorMessage = error instanceof Error ? error.message : String(error);
3506
+ await this.outbox.markFailed(job.jobId, errorMessage);
3507
+ } catch {
3508
+ }
3509
+ }
2539
3510
  }
2540
3511
  }
2541
3512
  return successCount;
@@ -2568,14 +3539,18 @@ var VectorWorkerV2 = class {
2568
3539
  * Poll for new jobs
2569
3540
  */
2570
3541
  async poll() {
2571
- if (!this.running)
3542
+ if (!this.running || this.stopping)
2572
3543
  return;
2573
3544
  try {
2574
3545
  await this.processBatch();
2575
3546
  } catch (error) {
2576
- console.error("Vector worker V2 error:", error);
3547
+ if (!this.stopping) {
3548
+ console.error("Vector worker V2 error:", error);
3549
+ }
3550
+ }
3551
+ if (this.running && !this.stopping) {
3552
+ this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
2577
3553
  }
2578
- this.pollTimeout = setTimeout(() => this.poll(), this.config.pollIntervalMs);
2579
3554
  }
2580
3555
  /**
2581
3556
  * Process all pending jobs (blocking)
@@ -2619,7 +3594,7 @@ function createVectorWorkerV2(db, vectorStore, embedder, config) {
2619
3594
  }
2620
3595
 
2621
3596
  // src/core/matcher.ts
2622
- var DEFAULT_CONFIG3 = {
3597
+ var DEFAULT_CONFIG4 = {
2623
3598
  weights: {
2624
3599
  semanticSimilarity: 0.4,
2625
3600
  ftsScore: 0.25,
@@ -2634,9 +3609,9 @@ var Matcher = class {
2634
3609
  config;
2635
3610
  constructor(config = {}) {
2636
3611
  this.config = {
2637
- ...DEFAULT_CONFIG3,
3612
+ ...DEFAULT_CONFIG4,
2638
3613
  ...config,
2639
- weights: { ...DEFAULT_CONFIG3.weights, ...config.weights }
3614
+ weights: { ...DEFAULT_CONFIG4.weights, ...config.weights }
2640
3615
  };
2641
3616
  }
2642
3617
  /**
@@ -3696,7 +4671,7 @@ function createGraduationPipeline(eventStore) {
3696
4671
  }
3697
4672
 
3698
4673
  // src/core/graduation-worker.ts
3699
- var DEFAULT_CONFIG4 = {
4674
+ var DEFAULT_CONFIG5 = {
3700
4675
  evaluationIntervalMs: 3e5,
3701
4676
  // 5 minutes
3702
4677
  batchSize: 50,
@@ -3704,7 +4679,7 @@ var DEFAULT_CONFIG4 = {
3704
4679
  // 1 hour cooldown between evaluations
3705
4680
  };
3706
4681
  var GraduationWorker = class {
3707
- constructor(eventStore, graduation, config = DEFAULT_CONFIG4) {
4682
+ constructor(eventStore, graduation, config = DEFAULT_CONFIG5) {
3708
4683
  this.eventStore = eventStore;
3709
4684
  this.graduation = graduation;
3710
4685
  this.config = config;
@@ -3812,12 +4787,12 @@ function createGraduationWorker(eventStore, graduation, config) {
3812
4787
  return new GraduationWorker(
3813
4788
  eventStore,
3814
4789
  graduation,
3815
- { ...DEFAULT_CONFIG4, ...config }
4790
+ { ...DEFAULT_CONFIG5, ...config }
3816
4791
  );
3817
4792
  }
3818
4793
 
3819
4794
  // src/core/task/task-matcher.ts
3820
- var DEFAULT_CONFIG5 = {
4795
+ var DEFAULT_CONFIG6 = {
3821
4796
  minCombinedScore: MATCH_THRESHOLDS.minCombinedScore,
3822
4797
  minGap: MATCH_THRESHOLDS.minGap,
3823
4798
  suggestionThreshold: MATCH_THRESHOLDS.suggestionThreshold,
@@ -3826,7 +4801,7 @@ var DEFAULT_CONFIG5 = {
3826
4801
  var TaskMatcher = class {
3827
4802
  constructor(db, config) {
3828
4803
  this.db = db;
3829
- this.config = { ...DEFAULT_CONFIG5, ...config };
4804
+ this.config = { ...DEFAULT_CONFIG6, ...config };
3830
4805
  }
3831
4806
  config;
3832
4807
  /**
@@ -3990,7 +4965,7 @@ var TaskMatcher = class {
3990
4965
  };
3991
4966
 
3992
4967
  // src/core/task/blocker-resolver.ts
3993
- import { randomUUID as randomUUID5 } from "crypto";
4968
+ import { randomUUID as randomUUID6 } from "crypto";
3994
4969
  var URL_PATTERN = /^https?:\/\/.+/;
3995
4970
  var JIRA_PATTERN = /^[A-Z]+-\d+$/;
3996
4971
  var GITHUB_ISSUE_PATTERN = /^[^\/]+\/[^#]+#\d+$/;
@@ -4127,7 +5102,7 @@ var BlockerResolver = class {
4127
5102
  * Declare a new condition entity
4128
5103
  */
4129
5104
  async declareCondition(text, canonicalKey, candidates) {
4130
- const entityId = randomUUID5();
5105
+ const entityId = randomUUID6();
4131
5106
  const now = (/* @__PURE__ */ new Date()).toISOString();
4132
5107
  const currentJson = {
4133
5108
  text,
@@ -4170,7 +5145,7 @@ var BlockerResolver = class {
4170
5145
  * Declare a new artifact entity
4171
5146
  */
4172
5147
  async declareArtifact(identifier, canonicalKey) {
4173
- const entityId = randomUUID5();
5148
+ const entityId = randomUUID6();
4174
5149
  const now = (/* @__PURE__ */ new Date()).toISOString();
4175
5150
  let artifactType = "generic";
4176
5151
  if (URL_PATTERN.test(identifier)) {
@@ -4235,7 +5210,7 @@ var BlockerResolver = class {
4235
5210
  };
4236
5211
 
4237
5212
  // src/core/task/task-resolver.ts
4238
- import { randomUUID as randomUUID6 } from "crypto";
5213
+ import { randomUUID as randomUUID7 } from "crypto";
4239
5214
  var VALID_TRANSITIONS = {
4240
5215
  pending: ["in_progress", "cancelled"],
4241
5216
  in_progress: ["blocked", "done", "cancelled"],
@@ -4310,7 +5285,7 @@ var TaskResolver = class {
4310
5285
  isNew: false
4311
5286
  };
4312
5287
  }
4313
- const taskId = randomUUID6();
5288
+ const taskId = randomUUID7();
4314
5289
  const canonicalKey = makeEntityCanonicalKey("task", extracted.title, {
4315
5290
  project: extracted.project
4316
5291
  });
@@ -4463,7 +5438,7 @@ var TaskResolver = class {
4463
5438
  * Emit task event to events table
4464
5439
  */
4465
5440
  async emitTaskEvent(eventType, payload) {
4466
- const eventId = randomUUID6();
5441
+ const eventId = randomUUID7();
4467
5442
  const now = /* @__PURE__ */ new Date();
4468
5443
  const dedupeKey = makeTaskEventDedupeKey(
4469
5444
  eventType,
@@ -4521,14 +5496,14 @@ var TaskResolver = class {
4521
5496
  `INSERT INTO edges (edge_id, src_type, src_id, rel_type, dst_type, dst_id, meta_json)
4522
5497
  VALUES (?, 'entity', ?, 'resolves_to', 'entity', ?, ?)
4523
5498
  ON CONFLICT DO NOTHING`,
4524
- [randomUUID6(), conditionId, taskId, JSON.stringify({ resolved_at: (/* @__PURE__ */ new Date()).toISOString() })]
5499
+ [randomUUID7(), conditionId, taskId, JSON.stringify({ resolved_at: (/* @__PURE__ */ new Date()).toISOString() })]
4525
5500
  );
4526
5501
  return eventId;
4527
5502
  }
4528
5503
  };
4529
5504
 
4530
5505
  // src/core/task/task-projector.ts
4531
- import { randomUUID as randomUUID7 } from "crypto";
5506
+ import { randomUUID as randomUUID8 } from "crypto";
4532
5507
  var PROJECTOR_NAME = "task_projector";
4533
5508
  var TASK_EVENT_TYPES = [
4534
5509
  "task_created",
@@ -4724,7 +5699,7 @@ var TaskProjector = class {
4724
5699
  * Create blocker edge
4725
5700
  */
4726
5701
  async createBlockerEdge(taskId, blocker, relType) {
4727
- const edgeId = randomUUID7();
5702
+ const edgeId = randomUUID8();
4728
5703
  await dbRun(
4729
5704
  this.db,
4730
5705
  `INSERT INTO edges (edge_id, src_type, src_id, rel_type, dst_type, dst_id, meta_json, created_at)
@@ -4762,7 +5737,7 @@ var TaskProjector = class {
4762
5737
  * Enqueue entity for vectorization
4763
5738
  */
4764
5739
  async enqueueForVectorization(itemId, itemKind) {
4765
- const jobId = randomUUID7();
5740
+ const jobId = randomUUID8();
4766
5741
  const embeddingVersion = "v1";
4767
5742
  await dbRun(
4768
5743
  this.db,
@@ -4882,7 +5857,7 @@ function createSharedEventStore(dbPath) {
4882
5857
  }
4883
5858
 
4884
5859
  // src/core/shared-store.ts
4885
- import { randomUUID as randomUUID8 } from "crypto";
5860
+ import { randomUUID as randomUUID9 } from "crypto";
4886
5861
  var SharedStore = class {
4887
5862
  constructor(sharedEventStore) {
4888
5863
  this.sharedEventStore = sharedEventStore;
@@ -4894,7 +5869,7 @@ var SharedStore = class {
4894
5869
  * Promote a verified troubleshooting entry to shared storage
4895
5870
  */
4896
5871
  async promoteEntry(input) {
4897
- const entryId = randomUUID8();
5872
+ const entryId = randomUUID9();
4898
5873
  await dbRun(
4899
5874
  this.db,
4900
5875
  `INSERT INTO shared_troubleshooting (
@@ -5259,7 +6234,7 @@ function createSharedVectorStore(dbPath) {
5259
6234
  }
5260
6235
 
5261
6236
  // src/core/shared-promoter.ts
5262
- import { randomUUID as randomUUID9 } from "crypto";
6237
+ import { randomUUID as randomUUID10 } from "crypto";
5263
6238
  var SharedPromoter = class {
5264
6239
  constructor(sharedStore, sharedVectorStore, embedder, config) {
5265
6240
  this.sharedStore = sharedStore;
@@ -5327,7 +6302,7 @@ var SharedPromoter = class {
5327
6302
  const embeddingContent = this.createEmbeddingContent(input);
5328
6303
  const embedding = await this.embedder.embed(embeddingContent);
5329
6304
  await this.sharedVectorStore.upsert({
5330
- id: randomUUID9(),
6305
+ id: randomUUID10(),
5331
6306
  entryId,
5332
6307
  entryType: "troubleshooting",
5333
6308
  content: embeddingContent,
@@ -5497,6 +6472,7 @@ export {
5497
6472
  ProgressiveSearchResultSchema,
5498
6473
  RelationTypeSchema,
5499
6474
  Retriever,
6475
+ SQLiteEventStore,
5500
6476
  SearchIndexItemSchema,
5501
6477
  SessionSchema,
5502
6478
  SharedEntryTypeSchema,
@@ -5506,6 +6482,7 @@ export {
5506
6482
  SharedStoreConfigSchema,
5507
6483
  SharedTroubleshootingEntrySchema,
5508
6484
  SharedVectorStore,
6485
+ SyncWorker,
5509
6486
  TaskBlockersSetPayloadSchema,
5510
6487
  TaskCreatedPayloadSchema,
5511
6488
  TaskCurrentJsonSchema,
@@ -5529,6 +6506,7 @@ export {
5529
6506
  createGraduationPipeline,
5530
6507
  createGraduationWorker,
5531
6508
  createRetriever,
6509
+ createSQLiteDatabase,
5532
6510
  createSharedEventStore,
5533
6511
  createSharedPromoter,
5534
6512
  createSharedStore,
@@ -5545,6 +6523,14 @@ export {
5545
6523
  makeDedupeKey,
5546
6524
  makeEntityCanonicalKey,
5547
6525
  makeTaskEventDedupeKey,
5548
- parseEntityCanonicalKey
6526
+ parseEntityCanonicalKey,
6527
+ sqliteAll,
6528
+ sqliteClose,
6529
+ sqliteExec,
6530
+ sqliteGet,
6531
+ sqliteRun,
6532
+ sqliteTransaction,
6533
+ toDateFromSQLite,
6534
+ toSQLiteTimestamp
5549
6535
  };
5550
6536
  //# sourceMappingURL=index.js.map