wolbarg 0.5.2 → 0.5.4

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/dist/index.cjs CHANGED
@@ -689,6 +689,45 @@ function withEmbeddingCache(provider, store, config) {
689
689
  };
690
690
  }
691
691
 
692
+ // src/utils/vector.ts
693
+ function cosineDistance(a, b) {
694
+ if (a.length !== b.length) {
695
+ throw new Error(
696
+ `cosineDistance dimension mismatch: a.length=${a.length}, b.length=${b.length}`
697
+ );
698
+ }
699
+ const len = a.length;
700
+ let dot = 0;
701
+ let normA = 0;
702
+ let normB = 0;
703
+ for (let i = 0; i < len; i += 1) {
704
+ const av = a[i] ?? 0;
705
+ const bv = b[i] ?? 0;
706
+ dot += av * bv;
707
+ normA += av * av;
708
+ normB += bv * bv;
709
+ }
710
+ if (normA === 0 || normB === 0) {
711
+ return 1;
712
+ }
713
+ const similarity = dot / (Math.sqrt(normA) * Math.sqrt(normB));
714
+ return 1 - similarity;
715
+ }
716
+ function bufferToEmbedding(data) {
717
+ const byteOffset = data.byteOffset ?? 0;
718
+ const byteLength = data.byteLength;
719
+ if (byteOffset % Float32Array.BYTES_PER_ELEMENT === 0) {
720
+ return new Float32Array(
721
+ data.buffer,
722
+ byteOffset,
723
+ byteLength / Float32Array.BYTES_PER_ELEMENT
724
+ );
725
+ }
726
+ const copy = new Uint8Array(byteLength);
727
+ copy.set(data);
728
+ return new Float32Array(copy.buffer);
729
+ }
730
+
692
731
  // src/embedding/cache-store.ts
693
732
  var TOUCH_FLUSH_THRESHOLD = 64;
694
733
  var L1_DEFAULT_MAX = 5e4;
@@ -773,7 +812,9 @@ var SqliteEmbeddingCacheStore = class {
773
812
  }
774
813
  this.stmts = {
775
814
  get: db.prepare(
776
- `SELECT vector, last_used_at, created_at FROM embedding_cache WHERE cache_key = ?`
815
+ `SELECT model, vector, last_used_at, created_at
816
+ FROM embedding_cache
817
+ WHERE cache_key = ?`
777
818
  ),
778
819
  delete: db.prepare(`DELETE FROM embedding_cache WHERE cache_key = ?`),
779
820
  set: db.prepare(
@@ -808,7 +849,29 @@ var SqliteEmbeddingCacheStore = class {
808
849
  this.l1.set(cacheKey, pending.model, pending.vector);
809
850
  return pending.vector;
810
851
  }
811
- return null;
852
+ const db = this.requireDb();
853
+ if (!db) return null;
854
+ try {
855
+ const stmts = this.ensureStatements(db);
856
+ const row = stmts.get.get(cacheKey);
857
+ if (!row) return null;
858
+ if (this.ttlMs !== null) {
859
+ const createdMs = row.created_at ? Date.parse(row.created_at) : 0;
860
+ if (createdMs && Date.now() - createdMs > this.ttlMs) {
861
+ stmts.delete.run(cacheKey);
862
+ return null;
863
+ }
864
+ }
865
+ const vector = bufferToEmbedding(row.vector);
866
+ this.l1.set(cacheKey, row.model, vector);
867
+ this.pendingTouches.add(cacheKey);
868
+ if (this.pendingTouches.size >= TOUCH_FLUSH_THRESHOLD) {
869
+ this.schedulePersist();
870
+ }
871
+ return vector;
872
+ } catch {
873
+ return null;
874
+ }
812
875
  }
813
876
  async set(cacheKey, model, vector) {
814
877
  this.l1.set(cacheKey, model, vector);
@@ -860,6 +923,16 @@ var SqliteEmbeddingCacheStore = class {
860
923
  this.pendingSets.clear();
861
924
  const touches = [...this.pendingTouches];
862
925
  this.pendingTouches.clear();
926
+ const requeue = () => {
927
+ for (const [key, value] of sets) {
928
+ this.pendingSets.set(key, value);
929
+ }
930
+ for (const key of touches) {
931
+ this.pendingTouches.add(key);
932
+ }
933
+ this.stmts = null;
934
+ this.schedulePersist();
935
+ };
863
936
  const now = (/* @__PURE__ */ new Date()).toISOString();
864
937
  try {
865
938
  const stmts = this.ensureStatements(db);
@@ -894,10 +967,10 @@ var SqliteEmbeddingCacheStore = class {
894
967
  db.exec("ROLLBACK");
895
968
  } catch {
896
969
  }
897
- this.stmts = null;
970
+ requeue();
898
971
  }
899
972
  } catch {
900
- this.stmts = null;
973
+ requeue();
901
974
  }
902
975
  }
903
976
  };
@@ -1206,12 +1279,27 @@ var DocxParser = class {
1206
1279
  name = "docx";
1207
1280
  extensions = [".docx"];
1208
1281
  async parse(input) {
1282
+ let mod;
1283
+ try {
1284
+ mod = await import('mammoth');
1285
+ } catch (error) {
1286
+ throw new ConfigurationError(
1287
+ 'DOCX ingest requires the optional peer package "mammoth". Install it with: npm install mammoth',
1288
+ {
1289
+ cause: error instanceof Error ? error : void 0,
1290
+ suggestion: "npm install mammoth",
1291
+ operation: "parse"
1292
+ }
1293
+ );
1294
+ }
1295
+ const extractRawText = mod.extractRawText ?? mod.default?.extractRawText;
1296
+ if (!extractRawText) {
1297
+ throw new ConfigurationError(
1298
+ "DOCX ingest could not find mammoth.extractRawText. Ensure your mammoth version is compatible.",
1299
+ { operation: "parse" }
1300
+ );
1301
+ }
1209
1302
  try {
1210
- const mod = await import('mammoth');
1211
- const extractRawText = mod.extractRawText ?? mod.default?.extractRawText;
1212
- if (!extractRawText) {
1213
- throw new Error("mammoth.extractRawText unavailable");
1214
- }
1215
1303
  const result = await extractRawText({ buffer: input.buffer });
1216
1304
  return {
1217
1305
  text: result.value ?? "",
@@ -1219,9 +1307,14 @@ var DocxParser = class {
1219
1307
  filename: input.filename,
1220
1308
  isImage: false
1221
1309
  };
1222
- } catch {
1310
+ } catch (error) {
1223
1311
  throw new ConfigurationError(
1224
- 'DOCX ingest requires the optional "mammoth" package. Install it with: npm install mammoth'
1312
+ "DOCX ingest failed to parse the document (it may be corrupt or an unsupported .docx).",
1313
+ {
1314
+ cause: error instanceof Error ? error : void 0,
1315
+ suggestion: "Try a different DOCX file or ensure it is not password-protected.",
1316
+ operation: "parse"
1317
+ }
1225
1318
  );
1226
1319
  }
1227
1320
  }
@@ -1368,6 +1461,12 @@ function compileComparison(field, op) {
1368
1461
  const extract = `json_extract(metadata_json, '${path8}')`;
1369
1462
  if ("eq" in op) {
1370
1463
  const value = op.eq;
1464
+ if (value === null) {
1465
+ return {
1466
+ expression: `json_type(metadata_json, '${path8}') = 'null'`,
1467
+ params: []
1468
+ };
1469
+ }
1371
1470
  if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
1372
1471
  return null;
1373
1472
  }
@@ -1448,6 +1547,59 @@ function compileMetadataFilterToSql(filter) {
1448
1547
  return compileComparison(filter.field, filter.op);
1449
1548
  }
1450
1549
 
1550
+ // src/telemetry/logger.ts
1551
+ var LEVEL_ORDER = {
1552
+ off: 100,
1553
+ error: 50,
1554
+ warn: 40,
1555
+ info: 30,
1556
+ debug: 20,
1557
+ trace: 10
1558
+ };
1559
+ var WolbargLogger = class {
1560
+ constructor(level = "info") {
1561
+ this.level = level;
1562
+ }
1563
+ level;
1564
+ setLevel(level) {
1565
+ this.level = level;
1566
+ }
1567
+ getLevel() {
1568
+ return this.level;
1569
+ }
1570
+ error(message, extra) {
1571
+ this.write("error", message, extra);
1572
+ }
1573
+ warn(message, extra) {
1574
+ this.write("warn", message, extra);
1575
+ }
1576
+ info(message, extra) {
1577
+ this.write("info", message, extra);
1578
+ }
1579
+ debug(message, extra) {
1580
+ this.write("debug", message, extra);
1581
+ }
1582
+ trace(message, extra) {
1583
+ this.write("trace", message, extra);
1584
+ }
1585
+ write(level, message, extra) {
1586
+ if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level]) {
1587
+ return;
1588
+ }
1589
+ if (this.level === "off") {
1590
+ return;
1591
+ }
1592
+ const line = `[wolbarg:${level}] ${message}`;
1593
+ if (level === "error") {
1594
+ console.error(line, extra ?? "");
1595
+ } else if (level === "warn") {
1596
+ console.warn(line, extra ?? "");
1597
+ } else {
1598
+ console.log(line, extra ?? "");
1599
+ }
1600
+ }
1601
+ };
1602
+
1451
1603
  // src/schema/index.ts
1452
1604
  var SCHEMA_VERSION = 4;
1453
1605
  var META_KEYS = {
@@ -1634,7 +1786,7 @@ var SQL = {
1634
1786
  AND k = ?
1635
1787
  `,
1636
1788
  insertEmbeddingBlob: `
1637
- INSERT INTO memory_embeddings_blob (memory_rowid, embedding) VALUES (?, ?)
1789
+ INSERT OR REPLACE INTO memory_embeddings_blob (memory_rowid, embedding) VALUES (?, ?)
1638
1790
  `,
1639
1791
  deleteEmbeddingBlob: `
1640
1792
  DELETE FROM memory_embeddings_blob WHERE memory_rowid = ?
@@ -1762,40 +1914,6 @@ var SQL = {
1762
1914
  `
1763
1915
  };
1764
1916
 
1765
- // src/utils/vector.ts
1766
- function cosineDistance(a, b) {
1767
- const len = Math.min(a.length, b.length);
1768
- let dot = 0;
1769
- let normA = 0;
1770
- let normB = 0;
1771
- for (let i = 0; i < len; i += 1) {
1772
- const av = a[i] ?? 0;
1773
- const bv = b[i] ?? 0;
1774
- dot += av * bv;
1775
- normA += av * av;
1776
- normB += bv * bv;
1777
- }
1778
- if (normA === 0 || normB === 0) {
1779
- return 1;
1780
- }
1781
- const similarity = dot / (Math.sqrt(normA) * Math.sqrt(normB));
1782
- return 1 - similarity;
1783
- }
1784
- function bufferToEmbedding(data) {
1785
- const byteOffset = data.byteOffset ?? 0;
1786
- const byteLength = data.byteLength;
1787
- if (byteOffset % Float32Array.BYTES_PER_ELEMENT === 0) {
1788
- return new Float32Array(
1789
- data.buffer,
1790
- byteOffset,
1791
- byteLength / Float32Array.BYTES_PER_ELEMENT
1792
- );
1793
- }
1794
- const copy = new Uint8Array(byteLength);
1795
- copy.set(data);
1796
- return new Float32Array(copy.buffer);
1797
- }
1798
-
1799
1917
  // src/utils/vector-index.ts
1800
1918
  var InMemoryVectorIndex = class {
1801
1919
  dims;
@@ -1888,24 +2006,33 @@ var InMemoryVectorIndex = class {
1888
2006
  dot += queryNormalized[d] * data[base + d];
1889
2007
  }
1890
2008
  const distance = 1 - dot;
2009
+ const rowid = rowids[i];
1891
2010
  if (heapSize < k) {
1892
2011
  heapDist[heapSize] = distance;
1893
- heapRow[heapSize] = rowids[i];
2012
+ heapRow[heapSize] = rowid;
1894
2013
  heapSize += 1;
1895
2014
  if (heapSize === k) {
1896
2015
  buildMaxHeap(heapDist, heapRow, k);
1897
2016
  }
1898
- } else if (distance < heapDist[0]) {
1899
- heapDist[0] = distance;
1900
- heapRow[0] = rowids[i];
1901
- siftDown(heapDist, heapRow, 0, k);
2017
+ } else {
2018
+ const worstDist = heapDist[0];
2019
+ const worstRow = heapRow[0];
2020
+ if (distance < worstDist || distance === worstDist && rowid < worstRow) {
2021
+ heapDist[0] = distance;
2022
+ heapRow[0] = rowid;
2023
+ siftDown(heapDist, heapRow, 0, k);
2024
+ }
1902
2025
  }
1903
2026
  }
1904
2027
  const hits = new Array(heapSize);
1905
2028
  for (let i = 0; i < heapSize; i += 1) {
1906
2029
  hits[i] = { memoryRowid: heapRow[i], distance: heapDist[i] };
1907
2030
  }
1908
- hits.sort((a, b) => a.distance - b.distance);
2031
+ hits.sort((a, b) => {
2032
+ const diff = a.distance - b.distance;
2033
+ if (diff !== 0) return diff;
2034
+ return a.memoryRowid - b.memoryRowid;
2035
+ });
1909
2036
  return hits;
1910
2037
  }
1911
2038
  ensureCapacity(needed) {
@@ -1951,8 +2078,8 @@ function siftDown(dist, rows, i, n) {
1951
2078
  let largest = i;
1952
2079
  const left = (i << 1) + 1;
1953
2080
  const right = left + 1;
1954
- if (left < n && dist[left] > dist[largest]) largest = left;
1955
- if (right < n && dist[right] > dist[largest]) largest = right;
2081
+ if (left < n && (dist[left] > dist[largest] || dist[left] === dist[largest] && rows[left] > rows[largest])) largest = left;
2082
+ if (right < n && (dist[right] > dist[largest] || dist[right] === dist[largest] && rows[right] > rows[largest])) largest = right;
1956
2083
  if (largest === i) return;
1957
2084
  const td = dist[i];
1958
2085
  dist[i] = dist[largest];
@@ -2094,6 +2221,9 @@ var BATCH_INSERT_CHUNK = 96;
2094
2221
  var MAX_ANN_OVERFETCH = 8192;
2095
2222
  var INSERT_COALESCE_THRESHOLD = 24;
2096
2223
  var INSERT_COALESCE_MAX = 96;
2224
+ var warnLogger = new WolbargLogger("warn");
2225
+ var warnedSqliteVecFallback = false;
2226
+ var warnedFtsKeywordSearch = false;
2097
2227
  var SqliteStorageProvider = class _SqliteStorageProvider {
2098
2228
  name = "sqlite";
2099
2229
  connectionString;
@@ -2103,6 +2233,10 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2103
2233
  vectorDimensions = null;
2104
2234
  vectorBackend = null;
2105
2235
  sqliteVecLoaded = false;
2236
+ /** Tracks nesting depth for {@link withTransaction} so we can use savepoints. */
2237
+ transactionDepth = 0;
2238
+ /** Incrementing counter for deterministic savepoint names. */
2239
+ savepointCounter = 0;
2106
2240
  /** Hot in-process ANN for blob backend (sqlite-vec unavailable platforms). */
2107
2241
  memoryIndex = null;
2108
2242
  memoryIndexDirty = false;
@@ -2169,6 +2303,12 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2169
2303
  } else if (this.sqliteVecLoaded) {
2170
2304
  this.vectorBackend = "sqlite-vec";
2171
2305
  } else {
2306
+ if (!warnedSqliteVecFallback) {
2307
+ warnedSqliteVecFallback = true;
2308
+ warnLogger.warn(
2309
+ "sqlite-vec unavailable on this platform; using blob ANN fallback."
2310
+ );
2311
+ }
2172
2312
  this.vectorBackend = "blob";
2173
2313
  }
2174
2314
  if (dims !== null) {
@@ -2412,6 +2552,12 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2412
2552
  async searchKeyword(query, organization, topK) {
2413
2553
  const stmts = this.requireStatements();
2414
2554
  if (!stmts.searchFts) {
2555
+ if (!warnedFtsKeywordSearch) {
2556
+ warnedFtsKeywordSearch = true;
2557
+ warnLogger.warn(
2558
+ "FTS keyword search is unavailable; returning empty keyword hits."
2559
+ );
2560
+ }
2415
2561
  return [];
2416
2562
  }
2417
2563
  try {
@@ -2425,7 +2571,16 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2425
2571
  // bm25 returns lower (more negative) for better matches — invert to [0, ∞)
2426
2572
  score: 1 / (1 + Math.abs(row.rank))
2427
2573
  }));
2428
- } catch {
2574
+ } catch (error) {
2575
+ if (!warnedFtsKeywordSearch) {
2576
+ warnedFtsKeywordSearch = true;
2577
+ warnLogger.warn(
2578
+ "FTS keyword search failed; returning empty keyword hits.",
2579
+ {
2580
+ cause: error instanceof Error ? error.message : String(error)
2581
+ }
2582
+ );
2583
+ }
2429
2584
  return [];
2430
2585
  }
2431
2586
  }
@@ -2590,6 +2745,13 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2590
2745
  this.deleteEmbeddingsForScope(organization);
2591
2746
  this.deleteFtsForScope(organization);
2592
2747
  const result = stmts.deleteMemoriesByOrg.run(organization);
2748
+ try {
2749
+ this.requireDb().exec(`
2750
+ DELETE FROM memory_embeddings_blob
2751
+ WHERE memory_rowid NOT IN (SELECT rowid FROM memories)
2752
+ `);
2753
+ } catch {
2754
+ }
2593
2755
  return Number(result.changes);
2594
2756
  });
2595
2757
  }
@@ -2649,16 +2811,39 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2649
2811
  }
2650
2812
  async withTransaction(fn) {
2651
2813
  const db = this.requireDb();
2652
- return withImmediateTransaction(
2653
- db,
2654
- this.concurrency,
2655
- fn,
2656
- (attempt, delayMs) => {
2657
- this.retryLog?.(
2658
- `SQLITE_BUSY retry attempt=${attempt} backoffMs=${Math.round(delayMs)}`
2659
- );
2814
+ if (this.transactionDepth > 0) {
2815
+ const savepointName = `wolbarg_sp_${this.savepointCounter++}`;
2816
+ db.exec(`SAVEPOINT ${savepointName}`);
2817
+ try {
2818
+ this.transactionDepth += 1;
2819
+ const result = await fn();
2820
+ db.exec(`RELEASE SAVEPOINT ${savepointName}`);
2821
+ return result;
2822
+ } catch (error) {
2823
+ try {
2824
+ db.exec(`ROLLBACK TO SAVEPOINT ${savepointName}`);
2825
+ } catch {
2826
+ }
2827
+ throw error;
2828
+ } finally {
2829
+ this.transactionDepth -= 1;
2660
2830
  }
2661
- );
2831
+ }
2832
+ this.transactionDepth = 1;
2833
+ try {
2834
+ return await withImmediateTransaction(
2835
+ db,
2836
+ this.concurrency,
2837
+ fn,
2838
+ (attempt, delayMs) => {
2839
+ this.retryLog?.(
2840
+ `SQLITE_BUSY retry attempt=${attempt} backoffMs=${Math.round(delayMs)}`
2841
+ );
2842
+ }
2843
+ );
2844
+ } finally {
2845
+ this.transactionDepth = 0;
2846
+ }
2662
2847
  }
2663
2848
  // ─── internals ───────────────────────────────────────────────────────────
2664
2849
  tryLoadSqliteVec(db) {
@@ -3104,7 +3289,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
3104
3289
  if (!stmt) {
3105
3290
  const values = Array.from({ length: n }, () => "(?, ?)").join(", ");
3106
3291
  stmt = this.requireDb().prepare(
3107
- `INSERT INTO memory_embeddings_blob (memory_rowid, embedding) VALUES ${values}`
3292
+ `INSERT OR REPLACE INTO memory_embeddings_blob (memory_rowid, embedding) VALUES ${values}`
3108
3293
  );
3109
3294
  this.batchBlobEmbStatements.set(n, stmt);
3110
3295
  }
@@ -3812,9 +3997,13 @@ function createPostgresListenerFromPool(pool, onError) {
3812
3997
 
3813
3998
  // src/storage/providers/postgres.ts
3814
3999
  var txStore = new async_hooks.AsyncLocalStorage();
4000
+ var warnLogger2 = new WolbargLogger("warn");
4001
+ var warnedPgvectorFallback = false;
4002
+ var warnedFtsKeywordSearch2 = false;
4003
+ var warnedHnswSoftFail = false;
3815
4004
  var STMT = {
3816
4005
  insertOne: "Wolbarg_insert_one_v5",
3817
- insertBatch: "Wolbarg_insert_batch_v5"
4006
+ insertBatch: "Wolbarg_insert_batch_v6"
3818
4007
  };
3819
4008
  function toFloat4Param(embedding) {
3820
4009
  const out = new Array(embedding.length);
@@ -3885,13 +4074,13 @@ var INSERT_ONE_SQL = `WITH mem AS (
3885
4074
  var INSERT_BATCH_SQL = `WITH mem AS (
3886
4075
  INSERT INTO memories (
3887
4076
  id, organization, agent, content_text, metadata_json,
3888
- archived, compressed_into, created_at, updated_at
4077
+ archived, compressed_into, content_hash, created_at, updated_at
3889
4078
  )
3890
- SELECT id, org, agent, txt, meta::jsonb, false, NULL, c, u
4079
+ SELECT id, org, agent, txt, meta::jsonb, false, NULL, h, c, u
3891
4080
  FROM unnest(
3892
4081
  $1::text[], $2::text[], $3::text[], $4::text[],
3893
- $5::text[], $6::timestamptz[], $7::timestamptz[]
3894
- ) AS t(id, org, agent, txt, meta, c, u)
4082
+ $5::text[], $6::timestamptz[], $7::timestamptz[], $8::text[]
4083
+ ) AS t(id, org, agent, txt, meta, c, u, h)
3895
4084
  RETURNING id
3896
4085
  ),
3897
4086
  hist AS (
@@ -3907,7 +4096,7 @@ var INSERT_BATCH_SQL = `WITH mem AS (
3907
4096
  emb AS (
3908
4097
  INSERT INTO memory_embeddings (memory_id, embedding, organization, agent, archived)
3909
4098
  SELECT id, emb::vector, org, agent, false
3910
- FROM unnest($1::text[], $8::text[], $2::text[], $3::text[]) AS t(id, emb, org, agent)
4099
+ FROM unnest($1::text[], $9::text[], $2::text[], $3::text[]) AS t(id, emb, org, agent)
3911
4100
  )
3912
4101
  SELECT id FROM mem`;
3913
4102
  var COALESCE_FLUSH_MAX = 128;
@@ -4176,6 +4365,12 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4176
4365
  return;
4177
4366
  }
4178
4367
  this.hnswCreateFailures += 1;
4368
+ if (this.hnswCreateFailures === 1 && !warnedHnswSoftFail) {
4369
+ warnedHnswSoftFail = true;
4370
+ warnLogger2.warn(
4371
+ "HNSW index creation failed; ANN will fall back to sequential scan."
4372
+ );
4373
+ }
4179
4374
  if (this.hnswCreateFailures >= 3) {
4180
4375
  throw new DatabaseError(
4181
4376
  `Failed to create HNSW index after ${this.hnswCreateFailures} attempts: ${this.describe(error)}`,
@@ -4320,6 +4515,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4320
4515
  const metas = new Array(inputs.length);
4321
4516
  const created = new Array(inputs.length);
4322
4517
  const updated = new Array(inputs.length);
4518
+ const contentHashes = new Array(inputs.length);
4323
4519
  const vectors = new Array(inputs.length);
4324
4520
  for (let i = 0; i < inputs.length; i += 1) {
4325
4521
  const input = inputs[i];
@@ -4330,6 +4526,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4330
4526
  metas[i] = serializeMetadata(input.metadata);
4331
4527
  created[i] = input.createdAt;
4332
4528
  updated[i] = input.updatedAt;
4529
+ contentHashes[i] = input.contentHash !== void 0 ? input.contentHash : hashMemoryContent(input.contentText);
4333
4530
  vectors[i] = toVectorLiteral(input.embedding);
4334
4531
  }
4335
4532
  await this.queryNamed(STMT.insertBatch, INSERT_BATCH_SQL, [
@@ -4340,9 +4537,10 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4340
4537
  metas,
4341
4538
  created,
4342
4539
  updated,
4540
+ contentHashes,
4343
4541
  vectors
4344
4542
  ]);
4345
- return inputs.map((input, i) => ({
4543
+ return inputs.map((_, i) => ({
4346
4544
  id: ids[i],
4347
4545
  organization: orgs[i],
4348
4546
  agent: agents[i],
@@ -4350,7 +4548,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4350
4548
  metadata_json: metas[i],
4351
4549
  archived: 0,
4352
4550
  compressed_into: null,
4353
- content_hash: input.contentHash !== void 0 ? input.contentHash : null,
4551
+ content_hash: contentHashes[i] ?? null,
4354
4552
  created_at: created[i],
4355
4553
  updated_at: updated[i]
4356
4554
  }));
@@ -4391,80 +4589,84 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4391
4589
  return out;
4392
4590
  }
4393
4591
  async insertOneBlob(input) {
4394
- const buf = Buffer.from(
4395
- input.embedding.buffer,
4396
- input.embedding.byteOffset,
4397
- input.embedding.byteLength
4398
- );
4399
- const contentHash = input.contentHash !== void 0 ? input.contentHash : hashMemoryContent(input.contentText);
4400
- const inserted = await this.query(
4401
- `INSERT INTO memories (
4402
- id, organization, agent, content_text, metadata_json,
4403
- archived, compressed_into, content_hash, created_at, updated_at
4404
- ) VALUES ($1,$2,$3,$4,$5::jsonb,false,NULL,$8,$6,$7)
4405
- RETURNING id, organization, agent, content_text, metadata_json,
4406
- archived::int AS archived, compressed_into, content_hash, created_at, updated_at`,
4407
- [
4408
- input.id,
4409
- input.organization,
4410
- input.agent,
4411
- input.contentText,
4412
- serializeMetadata(input.metadata),
4413
- input.createdAt,
4414
- input.updatedAt,
4415
- contentHash
4416
- ]
4417
- );
4418
- const row = this.mapRow(inserted.rows[0]);
4419
- await this.query(
4420
- `WITH mapped AS (
4421
- INSERT INTO memory_row_map (memory_id) VALUES ($1)
4422
- ON CONFLICT (memory_id) DO NOTHING
4423
- )
4424
- INSERT INTO memory_embeddings_blob (memory_id, embedding)
4425
- VALUES ($1, $2)
4426
- ON CONFLICT (memory_id) DO UPDATE SET embedding = EXCLUDED.embedding`,
4427
- [input.id, buf]
4428
- );
4429
- await this.query(
4430
- `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4431
- VALUES ($1,$2,'created',NULL,$3)`,
4432
- [crypto.randomUUID(), input.id, input.createdAt]
4433
- );
4434
- return row;
4592
+ return this.withTransaction(async () => {
4593
+ const buf = Buffer.from(
4594
+ input.embedding.buffer,
4595
+ input.embedding.byteOffset,
4596
+ input.embedding.byteLength
4597
+ );
4598
+ const contentHash = input.contentHash !== void 0 ? input.contentHash : hashMemoryContent(input.contentText);
4599
+ const inserted = await this.query(
4600
+ `INSERT INTO memories (
4601
+ id, organization, agent, content_text, metadata_json,
4602
+ archived, compressed_into, content_hash, created_at, updated_at
4603
+ ) VALUES ($1,$2,$3,$4,$5::jsonb,false,NULL,$8,$6,$7)
4604
+ RETURNING id, organization, agent, content_text, metadata_json,
4605
+ archived::int AS archived, compressed_into, content_hash, created_at, updated_at`,
4606
+ [
4607
+ input.id,
4608
+ input.organization,
4609
+ input.agent,
4610
+ input.contentText,
4611
+ serializeMetadata(input.metadata),
4612
+ input.createdAt,
4613
+ input.updatedAt,
4614
+ contentHash
4615
+ ]
4616
+ );
4617
+ const row = this.mapRow(inserted.rows[0]);
4618
+ await this.query(
4619
+ `WITH mapped AS (
4620
+ INSERT INTO memory_row_map (memory_id) VALUES ($1)
4621
+ ON CONFLICT (memory_id) DO NOTHING
4622
+ )
4623
+ INSERT INTO memory_embeddings_blob (memory_id, embedding)
4624
+ VALUES ($1, $2)
4625
+ ON CONFLICT (memory_id) DO UPDATE SET embedding = EXCLUDED.embedding`,
4626
+ [input.id, buf]
4627
+ );
4628
+ await this.query(
4629
+ `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4630
+ VALUES ($1,$2,'created',NULL,$3)`,
4631
+ [crypto.randomUUID(), input.id, input.createdAt]
4632
+ );
4633
+ return row;
4634
+ });
4435
4635
  }
4436
4636
  async updateMemory(input) {
4437
- const existing = await this.getMemoryById(input.id, input.organization);
4438
- if (!existing) {
4439
- return null;
4440
- }
4441
- const contentHash = input.contentHash !== void 0 ? input.contentHash : input.contentText !== void 0 ? hashMemoryContent(input.contentText) : existing.content_hash ?? null;
4442
- await this.query(
4443
- `UPDATE memories SET
4444
- content_text = COALESCE($1, content_text),
4445
- metadata_json = COALESCE($2::jsonb, metadata_json),
4446
- content_hash = COALESCE($3, content_hash),
4447
- updated_at = $4
4448
- WHERE id = $5 AND organization = $6`,
4449
- [
4450
- input.contentText ?? null,
4451
- input.metadata !== void 0 ? serializeMetadata(input.metadata) : null,
4452
- contentHash,
4453
- input.updatedAt,
4454
- input.id,
4455
- input.organization
4456
- ]
4457
- );
4458
- if (input.embedding) {
4459
- await this.deleteEmbedding(input.id);
4460
- await this.insertEmbedding(input.id, input.embedding);
4461
- }
4462
- await this.query(
4463
- `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4464
- VALUES ($1,$2,'updated',NULL,$3)`,
4465
- [crypto.randomUUID(), input.id, input.updatedAt]
4466
- );
4467
- return this.getMemoryById(input.id, input.organization);
4637
+ return this.withTransaction(async () => {
4638
+ const existing = await this.getMemoryById(input.id, input.organization);
4639
+ if (!existing) {
4640
+ return null;
4641
+ }
4642
+ const contentHash = input.contentHash !== void 0 ? input.contentHash : input.contentText !== void 0 ? hashMemoryContent(input.contentText) : existing.content_hash ?? null;
4643
+ await this.query(
4644
+ `UPDATE memories SET
4645
+ content_text = COALESCE($1, content_text),
4646
+ metadata_json = COALESCE($2::jsonb, metadata_json),
4647
+ content_hash = COALESCE($3, content_hash),
4648
+ updated_at = $4
4649
+ WHERE id = $5 AND organization = $6`,
4650
+ [
4651
+ input.contentText ?? null,
4652
+ input.metadata !== void 0 ? serializeMetadata(input.metadata) : null,
4653
+ contentHash,
4654
+ input.updatedAt,
4655
+ input.id,
4656
+ input.organization
4657
+ ]
4658
+ );
4659
+ if (input.embedding) {
4660
+ await this.deleteEmbedding(input.id);
4661
+ await this.insertEmbedding(input.id, input.embedding);
4662
+ }
4663
+ await this.query(
4664
+ `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4665
+ VALUES ($1,$2,'updated',NULL,$3)`,
4666
+ [crypto.randomUUID(), input.id, input.updatedAt]
4667
+ );
4668
+ return this.getMemoryById(input.id, input.organization);
4669
+ });
4468
4670
  }
4469
4671
  async findActiveByContentHash(organization, agent, contentHash) {
4470
4672
  const result = await this.query(
@@ -4613,7 +4815,16 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4613
4815
  memoryId: String(row.memory_id),
4614
4816
  score: Number(row.rank)
4615
4817
  }));
4616
- } catch {
4818
+ } catch (error) {
4819
+ if (!warnedFtsKeywordSearch2) {
4820
+ warnedFtsKeywordSearch2 = true;
4821
+ warnLogger2.warn(
4822
+ "FTS keyword search failed; returning empty keyword hits.",
4823
+ {
4824
+ cause: error instanceof Error ? error.message : String(error)
4825
+ }
4826
+ );
4827
+ }
4617
4828
  return [];
4618
4829
  }
4619
4830
  }
@@ -4731,44 +4942,46 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4731
4942
  return mapHits(result.rows);
4732
4943
  }
4733
4944
  async archiveMemories(ids, organization, compressedIntoId, archivedAt) {
4734
- if (ids.length === 0) {
4735
- return [];
4736
- }
4737
- const result = await this.query(
4738
- `UPDATE memories
4739
- SET archived = true, compressed_into = $1, updated_at = $2
4740
- WHERE organization = $3 AND archived = false AND id = ANY($4::text[])
4741
- RETURNING id`,
4742
- [compressedIntoId, archivedAt, organization, ids]
4743
- );
4744
- const archived = result.rows.map((r) => String(r.id));
4745
- if (archived.length === 0) {
4746
- return [];
4747
- }
4748
- await this.query(
4749
- `UPDATE memory_embeddings
4750
- SET archived = true
4751
- WHERE memory_id = ANY($1::text[])`,
4752
- [archived]
4753
- ).catch(() => void 0);
4754
- const histIds = [];
4755
- const memIds = [];
4756
- const types = [];
4757
- const related = [];
4758
- const times = [];
4759
- for (const id of archived) {
4760
- histIds.push(crypto.randomUUID(), crypto.randomUUID());
4761
- memIds.push(id, compressedIntoId);
4762
- types.push("archived", "compressed");
4763
- related.push(compressedIntoId, id);
4764
- times.push(archivedAt, archivedAt);
4765
- }
4766
- await this.query(
4767
- `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4768
- SELECT * FROM unnest($1::text[], $2::text[], $3::text[], $4::text[], $5::timestamptz[])`,
4769
- [histIds, memIds, types, related, times]
4770
- );
4771
- return archived;
4945
+ return this.withTransaction(async () => {
4946
+ if (ids.length === 0) {
4947
+ return [];
4948
+ }
4949
+ const result = await this.query(
4950
+ `UPDATE memories
4951
+ SET archived = true, compressed_into = $1, updated_at = $2
4952
+ WHERE organization = $3 AND archived = false AND id = ANY($4::text[])
4953
+ RETURNING id`,
4954
+ [compressedIntoId, archivedAt, organization, ids]
4955
+ );
4956
+ const archived = result.rows.map((r) => String(r.id));
4957
+ if (archived.length === 0) {
4958
+ return [];
4959
+ }
4960
+ await this.query(
4961
+ `UPDATE memory_embeddings
4962
+ SET archived = true
4963
+ WHERE memory_id = ANY($1::text[])`,
4964
+ [archived]
4965
+ );
4966
+ const histIds = [];
4967
+ const memIds = [];
4968
+ const types = [];
4969
+ const related = [];
4970
+ const times = [];
4971
+ for (const id of archived) {
4972
+ histIds.push(crypto.randomUUID(), crypto.randomUUID());
4973
+ memIds.push(id, compressedIntoId);
4974
+ types.push("archived", "compressed");
4975
+ related.push(compressedIntoId, id);
4976
+ times.push(archivedAt, archivedAt);
4977
+ }
4978
+ await this.query(
4979
+ `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4980
+ SELECT * FROM unnest($1::text[], $2::text[], $3::text[], $4::text[], $5::timestamptz[])`,
4981
+ [histIds, memIds, types, related, times]
4982
+ );
4983
+ return archived;
4984
+ });
4772
4985
  }
4773
4986
  async deleteMemoryById(id, organization) {
4774
4987
  const result = await this.query(
@@ -4879,6 +5092,11 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4879
5092
  [META_KEYS.schemaVersion]
4880
5093
  ).catch(() => ({ rows: [] }));
4881
5094
  const current = versionRow.rows[0]?.value !== void 0 ? Number(versionRow.rows[0].value) : null;
5095
+ if (current !== null && current > SCHEMA_VERSION) {
5096
+ throw new InitializationError(
5097
+ `Database schema version ${current} is newer than this Wolbarg SDK (${SCHEMA_VERSION}). Please upgrade Wolbarg to open this database.`
5098
+ );
5099
+ }
4882
5100
  if (current === SCHEMA_VERSION) {
4883
5101
  const tsvProbe2 = await this.query(
4884
5102
  `SELECT 1 FROM information_schema.columns
@@ -5029,8 +5247,17 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
5029
5247
  await this.query(`CREATE EXTENSION IF NOT EXISTS vector`);
5030
5248
  _PostgresStorageProvider.pgvectorByConn.set(this.connectionString, true);
5031
5249
  return true;
5032
- } catch {
5250
+ } catch (error) {
5033
5251
  _PostgresStorageProvider.pgvectorByConn.set(this.connectionString, false);
5252
+ if (!warnedPgvectorFallback) {
5253
+ warnedPgvectorFallback = true;
5254
+ warnLogger2.warn(
5255
+ "pgvector extension is unavailable; using blob cosine search fallback.",
5256
+ {
5257
+ cause: error instanceof Error ? error.message : `unknown error: ${String(error)}`
5258
+ }
5259
+ );
5260
+ }
5034
5261
  return false;
5035
5262
  }
5036
5263
  }
@@ -5171,11 +5398,12 @@ function createDatabaseProvider(config) {
5171
5398
  }
5172
5399
 
5173
5400
  // src/version.ts
5174
- var SDK_VERSION = "0.5.2";
5401
+ var SDK_VERSION = "0.5.4";
5175
5402
 
5176
5403
  // src/memory/transfer.ts
5404
+ var warnedMissingExportManifest = false;
5177
5405
  var SqliteMemoryTransferProvider = class {
5178
- async exportTo(exportPath, sourcePath, organization) {
5406
+ async exportTo(exportPath, sourcePath, organization, embeddingModel, embeddingDimensions) {
5179
5407
  const resolvedSource = resolvePath(sourcePath);
5180
5408
  if (resolvedSource === ":memory:") {
5181
5409
  throw new ConfigurationError(
@@ -5202,7 +5430,9 @@ Initialize Wolbarg and verify the database path.`
5202
5430
  sdkVersion: SDK_VERSION,
5203
5431
  provider: "sqlite",
5204
5432
  sourcePath: resolvedSource,
5205
- organization
5433
+ organization,
5434
+ ...embeddingModel ? { embeddingModel } : {},
5435
+ ...embeddingDimensions ? { embeddingDimensions } : {}
5206
5436
  };
5207
5437
  fs6__default.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
5208
5438
  return {
@@ -5211,7 +5441,7 @@ Initialize Wolbarg and verify the database path.`
5211
5441
  sizeBytes: fs6__default.default.statSync(dbExportPath).size
5212
5442
  };
5213
5443
  }
5214
- async importFrom(exportPath, targetPath) {
5444
+ async importFrom(exportPath, targetPath, expected) {
5215
5445
  const resolvedExport = resolvePath(exportPath);
5216
5446
  const dbExportPath = resolvedExport.endsWith(".db") ? resolvedExport : fs6__default.default.existsSync(`${resolvedExport}.db`) ? `${resolvedExport}.db` : resolvedExport;
5217
5447
  if (!fs6__default.default.existsSync(dbExportPath)) {
@@ -5229,30 +5459,61 @@ Pass the path returned by export().`
5229
5459
  manifest = JSON.parse(fs6__default.default.readFileSync(manifestPath, "utf8"));
5230
5460
  if (manifest.format !== "wolbarg-export-v1") {
5231
5461
  throw new ValidationError(
5232
- `Unsupported export format: ${String(manifest.format)}`
5462
+ `Unsupported export format: ${String(manifest.format)}`
5463
+ );
5464
+ }
5465
+ } else {
5466
+ if (!warnedMissingExportManifest) {
5467
+ warnedMissingExportManifest = true;
5468
+ console.warn(
5469
+ "[wolbarg:warn] export manifest is missing; skipping manifest-based import validation."
5470
+ );
5471
+ }
5472
+ }
5473
+ if (manifest) {
5474
+ if (expected?.organization && manifest.organization && expected.organization !== manifest.organization) {
5475
+ throw new ValidationError(
5476
+ "Import failed: export organization does not match the current Wolbarg organization."
5477
+ );
5478
+ }
5479
+ if (expected?.embeddingDimensions != null && manifest.embeddingDimensions != null && expected.embeddingDimensions !== manifest.embeddingDimensions) {
5480
+ throw new ValidationError(
5481
+ `Import failed: embedding dimension mismatch (expected ${expected.embeddingDimensions}, got ${manifest.embeddingDimensions}).`
5482
+ );
5483
+ }
5484
+ if (expected?.embeddingModel && manifest.embeddingModel && expected.embeddingModel !== manifest.embeddingModel) {
5485
+ throw new ValidationError(
5486
+ `Import failed: embedding model mismatch (expected ${expected.embeddingModel}, got ${manifest.embeddingModel}).`
5233
5487
  );
5234
5488
  }
5235
- } else {
5236
- manifest = {
5237
- format: "wolbarg-export-v1",
5238
- exportedAt: nowIso(),
5239
- sdkVersion: SDK_VERSION,
5240
- provider: "sqlite",
5241
- sourcePath: dbExportPath
5242
- };
5243
5489
  }
5244
5490
  const resolvedTarget = resolvePath(targetPath);
5245
5491
  if (resolvedTarget === ":memory:") {
5246
5492
  throw new ConfigurationError("Cannot import into an in-memory database.");
5247
5493
  }
5494
+ const tmpTarget = `${resolvedTarget}.tmp-import-${Date.now()}`;
5495
+ for (const suffix of ["", "-wal", "-shm"]) {
5496
+ const side = `${tmpTarget}${suffix}`;
5497
+ if (fs6__default.default.existsSync(side)) {
5498
+ fs6__default.default.rmSync(side, { force: true });
5499
+ }
5500
+ }
5501
+ await copySqlite(dbExportPath, tmpTarget);
5248
5502
  for (const suffix of ["", "-wal", "-shm"]) {
5249
5503
  const side = `${resolvedTarget}${suffix}`;
5250
5504
  if (fs6__default.default.existsSync(side)) {
5251
5505
  fs6__default.default.rmSync(side, { force: true });
5252
5506
  }
5253
5507
  }
5254
- await copySqlite(dbExportPath, resolvedTarget);
5255
- return { path: resolvedTarget, manifest };
5508
+ fs6__default.default.renameSync(tmpTarget, resolvedTarget);
5509
+ for (const suffix of ["-wal", "-shm"]) {
5510
+ const from = `${tmpTarget}${suffix}`;
5511
+ const to = `${resolvedTarget}${suffix}`;
5512
+ if (fs6__default.default.existsSync(from)) {
5513
+ fs6__default.default.renameSync(from, to);
5514
+ }
5515
+ }
5516
+ return { path: resolvedTarget, manifest: manifest ?? void 0 };
5256
5517
  }
5257
5518
  };
5258
5519
  async function copySqlite(sourcePath, destPath) {
@@ -5481,7 +5742,8 @@ function applyMmr(candidates, topK, lambda) {
5481
5742
  );
5482
5743
  }
5483
5744
  const score = lambda * relevance - (1 - lambda) * maxSim;
5484
- if (score > bestScore) {
5745
+ const best = remaining[bestIdx];
5746
+ if (score > bestScore || score === bestScore && candidate.id < best.id) {
5485
5747
  bestScore = score;
5486
5748
  bestIdx = i;
5487
5749
  }
@@ -6041,24 +6303,26 @@ var Neo4jGraphProvider = class {
6041
6303
  }
6042
6304
  }
6043
6305
  async run(cypher, params = {}) {
6044
- return this.withSession(async (session) => {
6045
- const result = await session.run(cypher, params);
6046
- return result.records.map((rec) => {
6047
- try {
6048
- return rec.toObject();
6049
- } catch {
6050
- const obj = {};
6051
- for (const key of rec.keys) {
6052
- obj[key] = unwrapNeo4jValue(rec.get(key));
6053
- }
6054
- return obj;
6306
+ return this.withSession(
6307
+ (session) => this.runOnSession(session, cypher, params)
6308
+ );
6309
+ }
6310
+ async runOnSession(session, cypher, params = {}) {
6311
+ const result = await session.run(cypher, params);
6312
+ return result.records.map((rec) => {
6313
+ try {
6314
+ return rec.toObject();
6315
+ } catch {
6316
+ const obj = {};
6317
+ for (const key of rec.keys) {
6318
+ obj[key] = unwrapNeo4jValue(rec.get(key));
6055
6319
  }
6056
- });
6320
+ return obj;
6321
+ }
6057
6322
  });
6058
6323
  }
6059
- async ensureMemoryNode(id) {
6060
- await this.run(
6061
- `MERGE (m:Memory { id: $id })
6324
+ async ensureMemoryNode(id, session) {
6325
+ const cypher = `MERGE (m:Memory { id: $id })
6062
6326
  ON CREATE SET
6063
6327
  m.organization = '',
6064
6328
  m.agent = '',
@@ -6067,24 +6331,30 @@ var Neo4jGraphProvider = class {
6067
6331
  m.archived = false,
6068
6332
  m.compressed_into = '',
6069
6333
  m.created_at = '',
6070
- m.updated_at = ''`,
6071
- { id }
6072
- );
6334
+ m.updated_at = ''`;
6335
+ if (session) {
6336
+ await this.runOnSession(session, cypher, { id });
6337
+ return;
6338
+ }
6339
+ await this.run(cypher, { id });
6073
6340
  }
6074
6341
  async linkMemories(fromId, toId, relation, metadata) {
6075
- await this.ensureMemoryNode(fromId);
6076
- await this.ensureMemoryNode(toId);
6077
- await this.run(
6078
- `MATCH (a:Memory { id: $fromId }), (b:Memory { id: $toId })
6079
- MERGE (a)-[r:RELATED { relation: $relation }]->(b)
6080
- SET r.metadata_json = $metadata`,
6081
- {
6082
- fromId,
6083
- toId,
6084
- relation,
6085
- metadata: serializeMetadata2(metadata)
6086
- }
6087
- );
6342
+ await this.withSession(async (session) => {
6343
+ await this.ensureMemoryNode(fromId, session);
6344
+ await this.ensureMemoryNode(toId, session);
6345
+ await this.runOnSession(
6346
+ session,
6347
+ `MATCH (a:Memory { id: $fromId }), (b:Memory { id: $toId })
6348
+ MERGE (a)-[r:RELATED { relation: $relation }]->(b)
6349
+ SET r.metadata_json = $metadata`,
6350
+ {
6351
+ fromId,
6352
+ toId,
6353
+ relation,
6354
+ metadata: serializeMetadata2(metadata)
6355
+ }
6356
+ );
6357
+ });
6088
6358
  }
6089
6359
  async unlinkMemories(fromId, toId, relation) {
6090
6360
  if (relation !== void 0) {
@@ -6146,22 +6416,26 @@ var Neo4jGraphProvider = class {
6146
6416
  return id;
6147
6417
  }
6148
6418
  async linkEntityToMemory(entityId, memoryId, role) {
6149
- await this.ensureMemoryNode(memoryId);
6150
- const found = await this.run(
6151
- `MATCH (e:Entity { id: $id }) RETURN e.id AS id`,
6152
- { id: entityId }
6153
- );
6154
- if (found.length === 0) {
6155
- throw new DatabaseError(`Entity not found: ${entityId}`, {
6156
- operation: "linkEntityToMemory"
6157
- });
6158
- }
6159
- await this.run(
6160
- `MATCH (e:Entity { id: $entityId }), (m:Memory { id: $memoryId })
6161
- MERGE (e)-[r:MENTIONS]->(m)
6162
- SET r.role = $role`,
6163
- { entityId, memoryId, role: role ?? "" }
6164
- );
6419
+ await this.withSession(async (session) => {
6420
+ await this.ensureMemoryNode(memoryId, session);
6421
+ const found = await this.runOnSession(
6422
+ session,
6423
+ `MATCH (e:Entity { id: $id }) RETURN e.id AS id`,
6424
+ { id: entityId }
6425
+ );
6426
+ if (found.length === 0) {
6427
+ throw new DatabaseError(`Entity not found: ${entityId}`, {
6428
+ operation: "linkEntityToMemory"
6429
+ });
6430
+ }
6431
+ await this.runOnSession(
6432
+ session,
6433
+ `MATCH (e:Entity { id: $entityId }), (m:Memory { id: $memoryId })
6434
+ MERGE (e)-[r:MENTIONS]->(m)
6435
+ SET r.role = $role`,
6436
+ { entityId, memoryId, role: role ?? "" }
6437
+ );
6438
+ });
6165
6439
  }
6166
6440
  async deleteMemory(memoryId) {
6167
6441
  await this.run(
@@ -6234,6 +6508,21 @@ function assertNonEmpty(value, fieldName) {
6234
6508
  throw new ConfigurationError(`${fieldName} must be a non-empty string`);
6235
6509
  }
6236
6510
  }
6511
+ function assertFiniteNumber2(value, fieldName) {
6512
+ if (typeof value !== "number" || !Number.isFinite(value)) {
6513
+ throw new ConfigurationError(`${fieldName} must be a finite number`);
6514
+ }
6515
+ }
6516
+ function assertNumberMin(value, fieldName, min) {
6517
+ if (!Number.isFinite(value) || value < min) {
6518
+ throw new ConfigurationError(`${fieldName} must be >= ${min}`);
6519
+ }
6520
+ }
6521
+ function assertNumberInRange(value, fieldName, min, max) {
6522
+ if (!Number.isFinite(value) || value < min || value > max) {
6523
+ throw new ConfigurationError(`${fieldName} must be between ${min} and ${max}`);
6524
+ }
6525
+ }
6237
6526
  function assertUrl(value, fieldName) {
6238
6527
  assertNonEmpty(value, fieldName);
6239
6528
  try {
@@ -6280,6 +6569,135 @@ function validateLlmConfig(config) {
6280
6569
  ...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {}
6281
6570
  };
6282
6571
  }
6572
+ function validateRetrievalConfig(config) {
6573
+ if (config === null || typeof config !== "object") {
6574
+ throw new ConfigurationError("retrieval must be an object");
6575
+ }
6576
+ const out = {};
6577
+ if (config.overFetchFactor !== void 0) {
6578
+ assertFiniteNumber2(config.overFetchFactor, "retrieval.overFetchFactor");
6579
+ assertNumberMin(config.overFetchFactor, "retrieval.overFetchFactor", 0);
6580
+ if (config.overFetchFactor <= 0) {
6581
+ throw new ConfigurationError("retrieval.overFetchFactor must be > 0");
6582
+ }
6583
+ out.overFetchFactor = config.overFetchFactor;
6584
+ }
6585
+ if (config.hybrid !== void 0) {
6586
+ if (config.hybrid === null || typeof config.hybrid !== "object") {
6587
+ throw new ConfigurationError("retrieval.hybrid must be an object");
6588
+ }
6589
+ const hybrid = config.hybrid;
6590
+ const outHybrid = {};
6591
+ if (hybrid.semanticWeight !== void 0) {
6592
+ assertFiniteNumber2(hybrid.semanticWeight, "retrieval.hybrid.semanticWeight");
6593
+ if (hybrid.semanticWeight < 0) {
6594
+ throw new ConfigurationError(
6595
+ "retrieval.hybrid.semanticWeight must be >= 0"
6596
+ );
6597
+ }
6598
+ outHybrid.semanticWeight = hybrid.semanticWeight;
6599
+ }
6600
+ if (hybrid.keywordWeight !== void 0) {
6601
+ assertFiniteNumber2(hybrid.keywordWeight, "retrieval.hybrid.keywordWeight");
6602
+ if (hybrid.keywordWeight < 0) {
6603
+ throw new ConfigurationError(
6604
+ "retrieval.hybrid.keywordWeight must be >= 0"
6605
+ );
6606
+ }
6607
+ outHybrid.keywordWeight = hybrid.keywordWeight;
6608
+ }
6609
+ if (Object.keys(outHybrid).length > 0) {
6610
+ out.hybrid = outHybrid;
6611
+ }
6612
+ }
6613
+ if (config.mmr !== void 0) {
6614
+ if (config.mmr === null || typeof config.mmr !== "object") {
6615
+ throw new ConfigurationError("retrieval.mmr must be an object");
6616
+ }
6617
+ const mmr = config.mmr;
6618
+ const outMmr = {};
6619
+ if (mmr.lambda !== void 0) {
6620
+ assertFiniteNumber2(mmr.lambda, "retrieval.mmr.lambda");
6621
+ assertNumberInRange(mmr.lambda, "retrieval.mmr.lambda", 0, 1);
6622
+ outMmr.lambda = mmr.lambda;
6623
+ }
6624
+ if (Object.keys(outMmr).length > 0) {
6625
+ out.mmr = outMmr;
6626
+ }
6627
+ }
6628
+ return out;
6629
+ }
6630
+ function validateMemoryDedupeConfig(config) {
6631
+ if (config === null || typeof config !== "object") {
6632
+ throw new ConfigurationError("memory.dedupe must be an object");
6633
+ }
6634
+ const out = {};
6635
+ if (config.enabled !== void 0) {
6636
+ if (typeof config.enabled !== "boolean") {
6637
+ throw new ConfigurationError("memory.dedupe.enabled must be a boolean");
6638
+ }
6639
+ out.enabled = config.enabled;
6640
+ }
6641
+ if (config.strategy !== void 0) {
6642
+ const allowed = /* @__PURE__ */ new Set(["exact", "near", "exact-or-near"]);
6643
+ if (!allowed.has(config.strategy)) {
6644
+ throw new ConfigurationError(
6645
+ `memory.dedupe.strategy must be one of ${Array.from(allowed).join(", ")}`
6646
+ );
6647
+ }
6648
+ out.strategy = config.strategy;
6649
+ }
6650
+ if (config.nearThreshold !== void 0) {
6651
+ assertFiniteNumber2(config.nearThreshold, "memory.dedupe.nearThreshold");
6652
+ assertNumberInRange(
6653
+ config.nearThreshold,
6654
+ "memory.dedupe.nearThreshold",
6655
+ 0,
6656
+ 1
6657
+ );
6658
+ out.nearThreshold = config.nearThreshold;
6659
+ }
6660
+ if (config.nearCandidateLimit !== void 0) {
6661
+ assertFiniteNumber2(
6662
+ config.nearCandidateLimit,
6663
+ "memory.dedupe.nearCandidateLimit"
6664
+ );
6665
+ assertNumberMin(
6666
+ config.nearCandidateLimit,
6667
+ "memory.dedupe.nearCandidateLimit",
6668
+ 1
6669
+ );
6670
+ out.nearCandidateLimit = config.nearCandidateLimit;
6671
+ }
6672
+ return out;
6673
+ }
6674
+ function validateEmbeddingCacheConfig(config) {
6675
+ if (config === null || typeof config !== "object") {
6676
+ throw new ConfigurationError("embeddingCache must be an object");
6677
+ }
6678
+ const out = {};
6679
+ if (config.enabled !== void 0) {
6680
+ if (typeof config.enabled !== "boolean") {
6681
+ throw new ConfigurationError("embeddingCache.enabled must be a boolean");
6682
+ }
6683
+ out.enabled = config.enabled;
6684
+ }
6685
+ if (config.ttlMs !== void 0) {
6686
+ assertFiniteNumber2(config.ttlMs, "embeddingCache.ttlMs");
6687
+ if (config.ttlMs < 0) {
6688
+ throw new ConfigurationError("embeddingCache.ttlMs must be >= 0");
6689
+ }
6690
+ out.ttlMs = config.ttlMs;
6691
+ }
6692
+ if (config.maxEntries !== void 0) {
6693
+ assertFiniteNumber2(config.maxEntries, "embeddingCache.maxEntries");
6694
+ if (config.maxEntries < 0) {
6695
+ throw new ConfigurationError("embeddingCache.maxEntries must be >= 0");
6696
+ }
6697
+ out.maxEntries = config.maxEntries;
6698
+ }
6699
+ return out;
6700
+ }
6283
6701
  function normalizeDatabaseConfig(config) {
6284
6702
  const provider = config.provider;
6285
6703
  if (provider !== "sqlite" && provider !== "postgres") {
@@ -6389,11 +6807,22 @@ function validateWolbargOptions(options) {
6389
6807
  throw new ConfigurationError("Wolbarg options must be an object");
6390
6808
  }
6391
6809
  assertNonEmpty(options.organization, "organization");
6810
+ const checkpointDirectory = options.checkpointDirectory !== void 0 ? (() => {
6811
+ assertNonEmpty(options.checkpointDirectory, "checkpointDirectory");
6812
+ return options.checkpointDirectory.trim();
6813
+ })() : void 0;
6392
6814
  const storageInput = resolveStorageInput(options);
6393
6815
  let storage = storageInput;
6394
6816
  if (!isStorageProvider(storageInput)) {
6395
6817
  storage = normalizeDatabaseConfig(storageInput);
6396
6818
  }
6819
+ if (!isStorageProvider(storageInput) && storageInput.provider === "postgres" && storageInput.maxPoolSize !== void 0) {
6820
+ assertFiniteNumber2(
6821
+ storageInput.maxPoolSize,
6822
+ "database.maxPoolSize"
6823
+ );
6824
+ assertNumberMin(storageInput.maxPoolSize, "database.maxPoolSize", 1);
6825
+ }
6397
6826
  if (!options.embedding) {
6398
6827
  throw new ConfigurationError("embedding is required");
6399
6828
  }
@@ -6411,13 +6840,20 @@ function validateWolbargOptions(options) {
6411
6840
  if (graph !== void 0) {
6412
6841
  graph = resolveGraphInput(graph);
6413
6842
  }
6843
+ const retrieval = options.retrieval !== void 0 ? validateRetrievalConfig(options.retrieval) : void 0;
6844
+ const embeddingCache = options.embeddingCache !== void 0 ? validateEmbeddingCacheConfig(options.embeddingCache) : void 0;
6845
+ const memory = options.memory !== void 0 && options.memory.dedupe !== void 0 ? { ...options.memory, dedupe: validateMemoryDedupeConfig(options.memory.dedupe) } : options.memory;
6414
6846
  return {
6415
6847
  ...options,
6416
6848
  organization: options.organization.trim(),
6417
6849
  storage,
6418
6850
  database: void 0,
6419
6851
  ...telemetry ? { telemetry } : {},
6420
- ...graph !== void 0 ? { graph } : {}
6852
+ ...graph !== void 0 ? { graph } : {},
6853
+ ...retrieval !== void 0 ? { retrieval } : {},
6854
+ ...embeddingCache !== void 0 ? { embeddingCache } : {},
6855
+ ...checkpointDirectory !== void 0 ? { checkpointDirectory } : {},
6856
+ ...memory !== void 0 ? { memory } : {}
6421
6857
  };
6422
6858
  }
6423
6859
  function resolveGraphInput(input) {
@@ -6476,59 +6912,6 @@ function resolveGraphInput(input) {
6476
6912
  );
6477
6913
  }
6478
6914
 
6479
- // src/telemetry/logger.ts
6480
- var LEVEL_ORDER = {
6481
- off: 100,
6482
- error: 50,
6483
- warn: 40,
6484
- info: 30,
6485
- debug: 20,
6486
- trace: 10
6487
- };
6488
- var WolbargLogger = class {
6489
- constructor(level = "info") {
6490
- this.level = level;
6491
- }
6492
- level;
6493
- setLevel(level) {
6494
- this.level = level;
6495
- }
6496
- getLevel() {
6497
- return this.level;
6498
- }
6499
- error(message, extra) {
6500
- this.write("error", message, extra);
6501
- }
6502
- warn(message, extra) {
6503
- this.write("warn", message, extra);
6504
- }
6505
- info(message, extra) {
6506
- this.write("info", message, extra);
6507
- }
6508
- debug(message, extra) {
6509
- this.write("debug", message, extra);
6510
- }
6511
- trace(message, extra) {
6512
- this.write("trace", message, extra);
6513
- }
6514
- write(level, message, extra) {
6515
- if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level]) {
6516
- return;
6517
- }
6518
- if (this.level === "off") {
6519
- return;
6520
- }
6521
- const line = `[wolbarg:${level}] ${message}`;
6522
- if (level === "error") {
6523
- console.error(line, extra ?? "");
6524
- } else if (level === "warn") {
6525
- console.warn(line, extra ?? "");
6526
- } else {
6527
- console.log(line, extra ?? "");
6528
- }
6529
- }
6530
- };
6531
-
6532
6915
  // src/telemetry/trace.ts
6533
6916
  function createSessionId() {
6534
6917
  return createId();
@@ -6613,14 +6996,14 @@ var TelemetryEmitter = class {
6613
6996
  const totalMs = performance.now() - startedAt;
6614
6997
  const errMessage = error instanceof Error ? error.message : error ? String(error) : fields?.error ?? null;
6615
6998
  const errStack = error instanceof Error ? error.stack ?? null : fields?.errorStack ?? null;
6999
+ if (!self.config.enabled) {
7000
+ return;
7001
+ }
6616
7002
  if (status === "error") {
6617
7003
  self.logger.error(`${operation} failed: ${errMessage ?? "unknown"}`);
6618
7004
  } else {
6619
7005
  self.logger.debug(`${operation} completed in ${totalMs.toFixed(2)}ms`);
6620
7006
  }
6621
- if (!self.config.enabled) {
6622
- return;
6623
- }
6624
7007
  const event = {
6625
7008
  id: createId(),
6626
7009
  timestamp: nowIso(),
@@ -6922,8 +7305,10 @@ var SqliteEventDatabase = class {
6922
7305
  }
6923
7306
  }
6924
7307
  if (options.memoryId) {
6925
- clauses.push(`memory_ids_json LIKE ?`);
6926
- params.push(`%${options.memoryId}%`);
7308
+ clauses.push(
7309
+ `EXISTS (SELECT 1 FROM json_each(memory_ids_json) WHERE json_each.value = ?)`
7310
+ );
7311
+ params.push(options.memoryId);
6927
7312
  }
6928
7313
  if (options.queryText) {
6929
7314
  clauses.push(`query LIKE ?`);
@@ -7100,6 +7485,7 @@ function describe2(error) {
7100
7485
  }
7101
7486
 
7102
7487
  // src/providers/sqlite/sqliteTelemetryProvider.ts
7488
+ var MAX_QUEUE_SIZE = 1e4;
7103
7489
  var SqliteTelemetryProvider = class {
7104
7490
  name = "sqlite";
7105
7491
  db;
@@ -7107,6 +7493,8 @@ var SqliteTelemetryProvider = class {
7107
7493
  flushing = null;
7108
7494
  closed = false;
7109
7495
  openPromise = null;
7496
+ warnedQueueDrop = false;
7497
+ warnedFlushFailure = false;
7110
7498
  constructor(options) {
7111
7499
  this.db = new SqliteEventDatabase({ url: options.url });
7112
7500
  }
@@ -7127,6 +7515,15 @@ var SqliteTelemetryProvider = class {
7127
7515
  if (this.closed) {
7128
7516
  return;
7129
7517
  }
7518
+ if (this.queue.length >= MAX_QUEUE_SIZE) {
7519
+ this.queue.shift();
7520
+ if (!this.warnedQueueDrop) {
7521
+ this.warnedQueueDrop = true;
7522
+ console.warn(
7523
+ `[wolbarg telemetry] event queue capped at ${MAX_QUEUE_SIZE}; dropping oldest events`
7524
+ );
7525
+ }
7526
+ }
7130
7527
  this.queue.push(event);
7131
7528
  void this.scheduleFlush();
7132
7529
  }
@@ -7169,7 +7566,13 @@ var SqliteTelemetryProvider = class {
7169
7566
  await this.db.insertEvent(event);
7170
7567
  }
7171
7568
  }
7172
- } catch {
7569
+ } catch (error) {
7570
+ if (!this.warnedFlushFailure) {
7571
+ this.warnedFlushFailure = true;
7572
+ console.warn(
7573
+ `[wolbarg telemetry] flush failed; dropping batch: ${error instanceof Error ? error.message : String(error)}`
7574
+ );
7575
+ }
7173
7576
  }
7174
7577
  }
7175
7578
  };
@@ -7212,8 +7615,20 @@ Ensure Wolbarg has been initialized and the database path is correct.`
7212
7615
  );
7213
7616
  }
7214
7617
  const snapshotPath = this.snapshotPath(name);
7215
- await safeSqliteBackup(resolvedSource, snapshotPath);
7216
- const stats = fs6__default.default.statSync(snapshotPath);
7618
+ const tmpSnapshotPath = `${snapshotPath}.tmp-${Date.now()}`;
7619
+ try {
7620
+ await safeSqliteBackup(resolvedSource, tmpSnapshotPath);
7621
+ } catch (error) {
7622
+ if (fs6__default.default.existsSync(tmpSnapshotPath)) {
7623
+ fs6__default.default.rmSync(tmpSnapshotPath, { force: true });
7624
+ }
7625
+ throw error;
7626
+ }
7627
+ const stats = fs6__default.default.statSync(tmpSnapshotPath);
7628
+ if (fs6__default.default.existsSync(snapshotPath)) {
7629
+ fs6__default.default.rmSync(snapshotPath, { force: true });
7630
+ }
7631
+ fs6__default.default.renameSync(tmpSnapshotPath, snapshotPath);
7217
7632
  const meta2 = {
7218
7633
  name,
7219
7634
  description: options?.description ?? null,
@@ -7224,7 +7639,9 @@ Ensure Wolbarg has been initialized and the database path is correct.`
7224
7639
  sourcePath: resolvedSource,
7225
7640
  sizeBytes: stats.size
7226
7641
  };
7227
- fs6__default.default.writeFileSync(metaPath, JSON.stringify(meta2, null, 2), "utf8");
7642
+ const tmpMetaPath = `${metaPath}.tmp-${Date.now()}`;
7643
+ fs6__default.default.writeFileSync(tmpMetaPath, JSON.stringify(meta2, null, 2), "utf8");
7644
+ fs6__default.default.renameSync(tmpMetaPath, metaPath);
7228
7645
  return meta2;
7229
7646
  }
7230
7647
  async rollback(name, targetPath) {
@@ -7488,6 +7905,9 @@ var SqliteSubscribeEmitter = class {
7488
7905
  };
7489
7906
 
7490
7907
  // src/core/wolbarg.ts
7908
+ var warnLogger3 = new WolbargLogger("warn");
7909
+ var warnedHybridNoKeyword = false;
7910
+ var warnedRerankNoProvider = false;
7491
7911
  var Wolbarg = class {
7492
7912
  initialized = false;
7493
7913
  booting = null;
@@ -7593,6 +8013,7 @@ var Wolbarg = class {
7593
8013
  this.storage = createStorageProvider(validated.database);
7594
8014
  this.memoryDbPath = resolveDatabaseUrl(validated.database);
7595
8015
  this.embedding = createEmbeddingProvider(validated.embedding);
8016
+ this.rawEmbedding = this.embedding;
7596
8017
  if (validated.llm) {
7597
8018
  this.llm = createLlmProvider(validated.llm);
7598
8019
  this.compression = createCompressionProvider(this.llm);
@@ -7914,7 +8335,7 @@ var Wolbarg = class {
7914
8335
  * Update an existing memory by id (re-embeds when content changes).
7915
8336
  */
7916
8337
  async update(options) {
7917
- const trace = this.telemetry.start("remember");
8338
+ const trace = this.telemetry.start("update");
7918
8339
  try {
7919
8340
  const { storage, embedding, organization } = await this.requireReady();
7920
8341
  assertNonEmptyString(options.id, "id");
@@ -8440,17 +8861,37 @@ var Wolbarg = class {
8440
8861
  }
8441
8862
  }
8442
8863
  }
8864
+ if (hybridWeights && keywordSignal === "disabled" && !warnedHybridNoKeyword) {
8865
+ warnedHybridNoKeyword = true;
8866
+ warnLogger3.warn(
8867
+ "hybrid search requested but no keyword channel is available; using semantic-only results."
8868
+ );
8869
+ }
8443
8870
  const tRank = performance.now();
8444
- let results = [...byId.values()].sort(
8445
- (a, b) => b.similarity - a.similarity
8446
- );
8871
+ let results = [...byId.values()].sort((a, b) => {
8872
+ const diff = b.similarity - a.similarity;
8873
+ if (diff !== 0) return diff;
8874
+ return a.id.localeCompare(b.id);
8875
+ });
8447
8876
  const mmrLambda = resolveMmr(options.mmr) ?? resolveMmr(
8448
8877
  this.retrievalConfig.mmr ? { lambda: this.retrievalConfig.mmr.lambda } : void 0
8449
8878
  );
8450
8879
  let rankingReason = "cosine similarity";
8880
+ let mmrApplied = false;
8451
8881
  if (mmrLambda !== null) {
8452
- results = applyMmr(results, Math.max(topK * 2, topK), mmrLambda);
8453
- rankingReason = `MMR(lambda=${mmrLambda})`;
8882
+ mmrApplied = results.length > topK;
8883
+ if (mmrApplied) {
8884
+ results = applyMmr(results, topK, mmrLambda);
8885
+ rankingReason = `MMR(lambda=${mmrLambda})`;
8886
+ } else {
8887
+ rankingReason = `MMR(lambda=${mmrLambda})(skipped:insufficient_candidates)`;
8888
+ }
8889
+ }
8890
+ if (options.rerank === true && !this.reranker && !warnedRerankNoProvider) {
8891
+ warnedRerankNoProvider = true;
8892
+ warnLogger3.warn(
8893
+ "rerank is enabled but no reranker provider is configured; using identity order."
8894
+ );
8454
8895
  }
8455
8896
  if (options.rerank && this.reranker) {
8456
8897
  const reranked = await this.reranker.rerank(
@@ -8485,14 +8926,16 @@ var Wolbarg = class {
8485
8926
  agentId: options.filter?.agent ?? commonAgent(serialized.map((result) => result.agent)),
8486
8927
  tags: commonTags(serialized.map((result) => result.metadata))
8487
8928
  };
8488
- if (!explain) {
8489
- if (options.includeGraph === true && this.graph) {
8490
- const tGraph = performance.now();
8491
- for (const hit of serialized) {
8929
+ if (options.includeGraph === true && this.graph) {
8930
+ const tGraph = performance.now();
8931
+ await Promise.all(
8932
+ serialized.map(async (hit) => {
8492
8933
  hit.related = await this.hydrateRelated(hit.id);
8493
- }
8494
- trace.mark("databaseReadMs", performance.now() - tGraph);
8495
- }
8934
+ })
8935
+ );
8936
+ trace.mark("databaseReadMs", performance.now() - tGraph);
8937
+ }
8938
+ if (!explain) {
8496
8939
  trace.success(telemetryFields);
8497
8940
  return serialized;
8498
8941
  }
@@ -8521,7 +8964,7 @@ var Wolbarg = class {
8521
8964
  semantic: "enabled",
8522
8965
  keyword: keywordSignal,
8523
8966
  reranker: options.rerank === true && this.reranker ? "enabled" : "disabled",
8524
- mmr: mmrLambda === null ? "disabled" : "enabled",
8967
+ mmr: mmrLambda === null ? "disabled" : mmrApplied ? "enabled" : "disabled",
8525
8968
  recency: "disabled"
8526
8969
  },
8527
8970
  results: explanations.map((hit) => ({
@@ -8630,31 +9073,34 @@ var Wolbarg = class {
8630
9073
  const timestamp = nowIso();
8631
9074
  const result = await this.withWriteLock(async () => {
8632
9075
  const tWrite = performance.now();
8633
- const summaryRow = await storage.insertMemory({
8634
- id: summaryId,
8635
- organization,
8636
- agent: options.agent.trim(),
8637
- contentText: summaryText,
8638
- metadata: {
8639
- compressed: true,
8640
- sourceCount: records.length,
8641
- sourceIds: records.map((r) => r.id)
8642
- },
8643
- embedding: vector,
8644
- createdAt: timestamp,
8645
- updatedAt: timestamp
9076
+ const txResult = await storage.withTransaction(async () => {
9077
+ const summaryRow = await storage.insertMemory({
9078
+ id: summaryId,
9079
+ organization,
9080
+ agent: options.agent.trim(),
9081
+ contentText: summaryText,
9082
+ metadata: {
9083
+ compressed: true,
9084
+ sourceCount: records.length,
9085
+ sourceIds: records.map((r) => r.id)
9086
+ },
9087
+ embedding: vector,
9088
+ createdAt: timestamp,
9089
+ updatedAt: timestamp
9090
+ });
9091
+ const archivedIds = await storage.archiveMemories(
9092
+ records.map((r) => r.id),
9093
+ organization,
9094
+ summaryId,
9095
+ timestamp
9096
+ );
9097
+ return {
9098
+ summary: toMemoryRecord(summaryRow),
9099
+ archivedIds
9100
+ };
8646
9101
  });
8647
- const archivedIds = await storage.archiveMemories(
8648
- records.map((r) => r.id),
8649
- organization,
8650
- summaryId,
8651
- timestamp
8652
- );
8653
9102
  trace.mark("databaseWriteMs", performance.now() - tWrite);
8654
- return {
8655
- summary: toMemoryRecord(summaryRow),
8656
- archivedIds
8657
- };
9103
+ return txResult;
8658
9104
  });
8659
9105
  trace.success({
8660
9106
  provider: storage.name,
@@ -8886,8 +9332,23 @@ var Wolbarg = class {
8886
9332
  await this.requireReady();
8887
9333
  const graph = this.requireGraph("getRelated");
8888
9334
  assertNonEmptyString(memoryId, "memoryId");
9335
+ let validatedOptions = options;
9336
+ if (options?.depth !== void 0) {
9337
+ if (!Number.isFinite(options.depth)) {
9338
+ throw new ConfigurationError("graph.depth must be a finite number");
9339
+ }
9340
+ if (options.depth < 1) {
9341
+ throw new ConfigurationError("graph.depth must be >= 1");
9342
+ }
9343
+ if (options.depth > 16) {
9344
+ validatedOptions = { ...options, depth: 16 };
9345
+ }
9346
+ }
8889
9347
  const tGraph = performance.now();
8890
- const related = await this.hydrateRelated(memoryId.trim(), options);
9348
+ const related = await this.hydrateRelated(
9349
+ memoryId.trim(),
9350
+ validatedOptions
9351
+ );
8891
9352
  trace.mark("databaseReadMs", performance.now() - tGraph);
8892
9353
  trace.success({
8893
9354
  provider: graph.name,
@@ -9056,7 +9517,15 @@ var Wolbarg = class {
9056
9517
  this.assertGraphCheckpointSupported("checkpoint");
9057
9518
  const tCheckpoint = performance.now();
9058
9519
  const meta2 = await provider.checkpoint(name, source, options);
9059
- await this.snapshotGraphAlongside(meta2.snapshotPath, "checkpoint");
9520
+ try {
9521
+ await this.snapshotGraphAlongside(meta2.snapshotPath, "checkpoint");
9522
+ } catch (error) {
9523
+ const sidecar = this.graphSnapshotDir(meta2.snapshotPath);
9524
+ if (fs6__default.default.existsSync(sidecar)) {
9525
+ fs6__default.default.rmSync(sidecar, { recursive: true, force: true });
9526
+ }
9527
+ throw error;
9528
+ }
9060
9529
  trace.mark("databaseWriteMs", performance.now() - tCheckpoint);
9061
9530
  trace.success({
9062
9531
  provider: provider.name,
@@ -9120,8 +9589,15 @@ var Wolbarg = class {
9120
9589
  try {
9121
9590
  await this.requireReady();
9122
9591
  const provider = this.requireCheckpointProvider();
9592
+ const existing = await provider.getCheckpoint(name);
9123
9593
  const tDelete = performance.now();
9124
9594
  const removed = await provider.deleteCheckpoint(name);
9595
+ if (removed && existing) {
9596
+ const sidecar = this.graphSnapshotDir(existing.snapshotPath);
9597
+ if (fs6__default.default.existsSync(sidecar)) {
9598
+ fs6__default.default.rmSync(sidecar, { recursive: true, force: true });
9599
+ }
9600
+ }
9125
9601
  trace.mark("databaseWriteMs", performance.now() - tDelete);
9126
9602
  trace.success({
9127
9603
  provider: provider.name,
@@ -9183,7 +9659,9 @@ var Wolbarg = class {
9183
9659
  const result = await this.transfer.exportTo(
9184
9660
  exportPath,
9185
9661
  source,
9186
- this.organization ?? void 0
9662
+ this.organization ?? void 0,
9663
+ this.embedding?.model,
9664
+ this.embeddingDimensions ?? void 0
9187
9665
  );
9188
9666
  await this.snapshotGraphAlongside(result.path, "export");
9189
9667
  trace.success({
@@ -9206,16 +9684,21 @@ var Wolbarg = class {
9206
9684
  let storageClosed = false;
9207
9685
  try {
9208
9686
  await this.requireReady();
9687
+ this.assertGraphCheckpointSupported("import");
9209
9688
  const target = this.requireMemoryDbPath();
9210
9689
  if (this.storage) {
9211
9690
  await this.storage.close();
9212
9691
  storageClosed = true;
9213
9692
  }
9214
9693
  const graphClosed = await this.closeGraphForSnapshot();
9215
- this.assertGraphCheckpointSupported("import");
9216
9694
  const result = await this.transfer.importFrom(
9217
9695
  exportPath,
9218
- target
9696
+ target,
9697
+ {
9698
+ organization: this.organization ?? void 0,
9699
+ embeddingModel: this.embedding?.model,
9700
+ embeddingDimensions: this.embeddingDimensions ?? void 0
9701
+ }
9219
9702
  );
9220
9703
  await this.restoreGraphAlongside(result.path, "import");
9221
9704
  if (graphClosed) {
@@ -9410,18 +9893,26 @@ var Wolbarg = class {
9410
9893
  const graph = this.requireGraph("getRelated");
9411
9894
  const related = await graph.getRelated(memoryId, options);
9412
9895
  const { storage, organization } = await this.requireReady();
9896
+ const rows = await Promise.all(
9897
+ related.map((stub) => storage.getMemoryById(stub.id, organization))
9898
+ );
9413
9899
  const out = [];
9414
- for (const stub of related) {
9415
- const row = await storage.getMemoryById(stub.id, organization);
9416
- if (row) {
9417
- out.push(toMemoryRecord(row));
9418
- } else {
9419
- out.push(stub);
9420
- }
9900
+ for (let i = 0; i < related.length; i += 1) {
9901
+ const stub = related[i];
9902
+ const row = rows[i];
9903
+ out.push(row ? toMemoryRecord(row) : stub);
9421
9904
  }
9422
9905
  return out;
9423
9906
  }
9424
9907
  requireMemoryDbPath() {
9908
+ if (this.storage && !(this.storage instanceof SqliteStorageProvider)) {
9909
+ throw new ConfigurationError(
9910
+ "This operation requires a file-backed SQLite database (checkpoints / export-import are not supported for PostgreSQL).",
9911
+ {
9912
+ suggestion: 'Use database: { provider: "sqlite", url: "./memory.db" }'
9913
+ }
9914
+ );
9915
+ }
9425
9916
  if (!this.memoryDbPath || this.memoryDbPath === ":memory:") {
9426
9917
  throw new ConfigurationError(
9427
9918
  "This operation requires a file-backed SQLite memory database (not :memory:).",
@@ -9650,7 +10141,14 @@ var HttpRerankerProvider = class {
9650
10141
  }));
9651
10142
  }
9652
10143
  const parsed = this.options.parseResults(body, documents);
9653
- return parsed.slice(0, topK);
10144
+ const hits = parsed.slice(0, topK);
10145
+ if (hits.length === 0) {
10146
+ return documents.slice(0, topK).map((d, i) => ({
10147
+ id: d.id,
10148
+ score: 1 - i / Math.max(documents.length, 1)
10149
+ }));
10150
+ }
10151
+ return hits;
9654
10152
  } catch {
9655
10153
  return documents.slice(0, topK).map((d, i) => ({
9656
10154
  id: d.id,
@@ -9835,21 +10333,31 @@ function tesseract() {
9835
10333
  return {
9836
10334
  name: "tesseract",
9837
10335
  async recognize(image) {
10336
+ let mod;
9838
10337
  try {
9839
- const mod = await import('tesseract.js');
9840
- const createWorker = mod.createWorker ?? mod.default?.createWorker;
9841
- if (!createWorker) {
9842
- return { text: "" };
9843
- }
9844
- const worker = await createWorker("eng");
9845
- try {
9846
- const result = await worker.recognize(image);
9847
- return { text: result.data.text.trim() };
9848
- } finally {
9849
- await worker.terminate();
9850
- }
9851
- } catch {
9852
- return { text: "" };
10338
+ mod = await import('tesseract.js');
10339
+ } catch (error) {
10340
+ throw new ConfigurationError(
10341
+ 'OCR provider "tesseract" requires the optional peer package "tesseract.js". Install it with: npm install tesseract.js',
10342
+ {
10343
+ cause: error instanceof Error ? error : void 0,
10344
+ suggestion: "npm install tesseract.js",
10345
+ operation: "recognize"
10346
+ }
10347
+ );
10348
+ }
10349
+ const createWorker = mod.createWorker ?? mod.default?.createWorker;
10350
+ if (!createWorker) {
10351
+ throw new ConfigurationError(
10352
+ 'OCR provider "tesseract" could not find tesseract.js.createWorker. Ensure your tesseract.js version is compatible.'
10353
+ );
10354
+ }
10355
+ const worker = await createWorker("eng");
10356
+ try {
10357
+ const result = await worker.recognize(image);
10358
+ return { text: result.data.text.trim() };
10359
+ } finally {
10360
+ await worker.terminate();
9853
10361
  }
9854
10362
  }
9855
10363
  };