wolbarg 0.5.3 → 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.js CHANGED
@@ -662,6 +662,45 @@ function withEmbeddingCache(provider, store, config) {
662
662
  };
663
663
  }
664
664
 
665
+ // src/utils/vector.ts
666
+ function cosineDistance(a, b) {
667
+ if (a.length !== b.length) {
668
+ throw new Error(
669
+ `cosineDistance dimension mismatch: a.length=${a.length}, b.length=${b.length}`
670
+ );
671
+ }
672
+ const len = a.length;
673
+ let dot = 0;
674
+ let normA = 0;
675
+ let normB = 0;
676
+ for (let i = 0; i < len; i += 1) {
677
+ const av = a[i] ?? 0;
678
+ const bv = b[i] ?? 0;
679
+ dot += av * bv;
680
+ normA += av * av;
681
+ normB += bv * bv;
682
+ }
683
+ if (normA === 0 || normB === 0) {
684
+ return 1;
685
+ }
686
+ const similarity = dot / (Math.sqrt(normA) * Math.sqrt(normB));
687
+ return 1 - similarity;
688
+ }
689
+ function bufferToEmbedding(data) {
690
+ const byteOffset = data.byteOffset ?? 0;
691
+ const byteLength = data.byteLength;
692
+ if (byteOffset % Float32Array.BYTES_PER_ELEMENT === 0) {
693
+ return new Float32Array(
694
+ data.buffer,
695
+ byteOffset,
696
+ byteLength / Float32Array.BYTES_PER_ELEMENT
697
+ );
698
+ }
699
+ const copy = new Uint8Array(byteLength);
700
+ copy.set(data);
701
+ return new Float32Array(copy.buffer);
702
+ }
703
+
665
704
  // src/embedding/cache-store.ts
666
705
  var TOUCH_FLUSH_THRESHOLD = 64;
667
706
  var L1_DEFAULT_MAX = 5e4;
@@ -746,7 +785,9 @@ var SqliteEmbeddingCacheStore = class {
746
785
  }
747
786
  this.stmts = {
748
787
  get: db.prepare(
749
- `SELECT vector, last_used_at, created_at FROM embedding_cache WHERE cache_key = ?`
788
+ `SELECT model, vector, last_used_at, created_at
789
+ FROM embedding_cache
790
+ WHERE cache_key = ?`
750
791
  ),
751
792
  delete: db.prepare(`DELETE FROM embedding_cache WHERE cache_key = ?`),
752
793
  set: db.prepare(
@@ -781,7 +822,29 @@ var SqliteEmbeddingCacheStore = class {
781
822
  this.l1.set(cacheKey, pending.model, pending.vector);
782
823
  return pending.vector;
783
824
  }
784
- return null;
825
+ const db = this.requireDb();
826
+ if (!db) return null;
827
+ try {
828
+ const stmts = this.ensureStatements(db);
829
+ const row = stmts.get.get(cacheKey);
830
+ if (!row) return null;
831
+ if (this.ttlMs !== null) {
832
+ const createdMs = row.created_at ? Date.parse(row.created_at) : 0;
833
+ if (createdMs && Date.now() - createdMs > this.ttlMs) {
834
+ stmts.delete.run(cacheKey);
835
+ return null;
836
+ }
837
+ }
838
+ const vector = bufferToEmbedding(row.vector);
839
+ this.l1.set(cacheKey, row.model, vector);
840
+ this.pendingTouches.add(cacheKey);
841
+ if (this.pendingTouches.size >= TOUCH_FLUSH_THRESHOLD) {
842
+ this.schedulePersist();
843
+ }
844
+ return vector;
845
+ } catch {
846
+ return null;
847
+ }
785
848
  }
786
849
  async set(cacheKey, model, vector) {
787
850
  this.l1.set(cacheKey, model, vector);
@@ -833,6 +896,16 @@ var SqliteEmbeddingCacheStore = class {
833
896
  this.pendingSets.clear();
834
897
  const touches = [...this.pendingTouches];
835
898
  this.pendingTouches.clear();
899
+ const requeue = () => {
900
+ for (const [key, value] of sets) {
901
+ this.pendingSets.set(key, value);
902
+ }
903
+ for (const key of touches) {
904
+ this.pendingTouches.add(key);
905
+ }
906
+ this.stmts = null;
907
+ this.schedulePersist();
908
+ };
836
909
  const now = (/* @__PURE__ */ new Date()).toISOString();
837
910
  try {
838
911
  const stmts = this.ensureStatements(db);
@@ -867,10 +940,10 @@ var SqliteEmbeddingCacheStore = class {
867
940
  db.exec("ROLLBACK");
868
941
  } catch {
869
942
  }
870
- this.stmts = null;
943
+ requeue();
871
944
  }
872
945
  } catch {
873
- this.stmts = null;
946
+ requeue();
874
947
  }
875
948
  }
876
949
  };
@@ -1179,12 +1252,27 @@ var DocxParser = class {
1179
1252
  name = "docx";
1180
1253
  extensions = [".docx"];
1181
1254
  async parse(input) {
1255
+ let mod;
1256
+ try {
1257
+ mod = await import('mammoth');
1258
+ } catch (error) {
1259
+ throw new ConfigurationError(
1260
+ 'DOCX ingest requires the optional peer package "mammoth". Install it with: npm install mammoth',
1261
+ {
1262
+ cause: error instanceof Error ? error : void 0,
1263
+ suggestion: "npm install mammoth",
1264
+ operation: "parse"
1265
+ }
1266
+ );
1267
+ }
1268
+ const extractRawText = mod.extractRawText ?? mod.default?.extractRawText;
1269
+ if (!extractRawText) {
1270
+ throw new ConfigurationError(
1271
+ "DOCX ingest could not find mammoth.extractRawText. Ensure your mammoth version is compatible.",
1272
+ { operation: "parse" }
1273
+ );
1274
+ }
1182
1275
  try {
1183
- const mod = await import('mammoth');
1184
- const extractRawText = mod.extractRawText ?? mod.default?.extractRawText;
1185
- if (!extractRawText) {
1186
- throw new Error("mammoth.extractRawText unavailable");
1187
- }
1188
1276
  const result = await extractRawText({ buffer: input.buffer });
1189
1277
  return {
1190
1278
  text: result.value ?? "",
@@ -1192,9 +1280,14 @@ var DocxParser = class {
1192
1280
  filename: input.filename,
1193
1281
  isImage: false
1194
1282
  };
1195
- } catch {
1283
+ } catch (error) {
1196
1284
  throw new ConfigurationError(
1197
- 'DOCX ingest requires the optional "mammoth" package. Install it with: npm install mammoth'
1285
+ "DOCX ingest failed to parse the document (it may be corrupt or an unsupported .docx).",
1286
+ {
1287
+ cause: error instanceof Error ? error : void 0,
1288
+ suggestion: "Try a different DOCX file or ensure it is not password-protected.",
1289
+ operation: "parse"
1290
+ }
1198
1291
  );
1199
1292
  }
1200
1293
  }
@@ -1341,6 +1434,12 @@ function compileComparison(field, op) {
1341
1434
  const extract = `json_extract(metadata_json, '${path8}')`;
1342
1435
  if ("eq" in op) {
1343
1436
  const value = op.eq;
1437
+ if (value === null) {
1438
+ return {
1439
+ expression: `json_type(metadata_json, '${path8}') = 'null'`,
1440
+ params: []
1441
+ };
1442
+ }
1344
1443
  if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
1345
1444
  return null;
1346
1445
  }
@@ -1421,6 +1520,59 @@ function compileMetadataFilterToSql(filter) {
1421
1520
  return compileComparison(filter.field, filter.op);
1422
1521
  }
1423
1522
 
1523
+ // src/telemetry/logger.ts
1524
+ var LEVEL_ORDER = {
1525
+ off: 100,
1526
+ error: 50,
1527
+ warn: 40,
1528
+ info: 30,
1529
+ debug: 20,
1530
+ trace: 10
1531
+ };
1532
+ var WolbargLogger = class {
1533
+ constructor(level = "info") {
1534
+ this.level = level;
1535
+ }
1536
+ level;
1537
+ setLevel(level) {
1538
+ this.level = level;
1539
+ }
1540
+ getLevel() {
1541
+ return this.level;
1542
+ }
1543
+ error(message, extra) {
1544
+ this.write("error", message, extra);
1545
+ }
1546
+ warn(message, extra) {
1547
+ this.write("warn", message, extra);
1548
+ }
1549
+ info(message, extra) {
1550
+ this.write("info", message, extra);
1551
+ }
1552
+ debug(message, extra) {
1553
+ this.write("debug", message, extra);
1554
+ }
1555
+ trace(message, extra) {
1556
+ this.write("trace", message, extra);
1557
+ }
1558
+ write(level, message, extra) {
1559
+ if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level]) {
1560
+ return;
1561
+ }
1562
+ if (this.level === "off") {
1563
+ return;
1564
+ }
1565
+ const line = `[wolbarg:${level}] ${message}`;
1566
+ if (level === "error") {
1567
+ console.error(line, extra ?? "");
1568
+ } else if (level === "warn") {
1569
+ console.warn(line, extra ?? "");
1570
+ } else {
1571
+ console.log(line, extra ?? "");
1572
+ }
1573
+ }
1574
+ };
1575
+
1424
1576
  // src/schema/index.ts
1425
1577
  var SCHEMA_VERSION = 4;
1426
1578
  var META_KEYS = {
@@ -1735,40 +1887,6 @@ var SQL = {
1735
1887
  `
1736
1888
  };
1737
1889
 
1738
- // src/utils/vector.ts
1739
- function cosineDistance(a, b) {
1740
- const len = Math.min(a.length, b.length);
1741
- let dot = 0;
1742
- let normA = 0;
1743
- let normB = 0;
1744
- for (let i = 0; i < len; i += 1) {
1745
- const av = a[i] ?? 0;
1746
- const bv = b[i] ?? 0;
1747
- dot += av * bv;
1748
- normA += av * av;
1749
- normB += bv * bv;
1750
- }
1751
- if (normA === 0 || normB === 0) {
1752
- return 1;
1753
- }
1754
- const similarity = dot / (Math.sqrt(normA) * Math.sqrt(normB));
1755
- return 1 - similarity;
1756
- }
1757
- function bufferToEmbedding(data) {
1758
- const byteOffset = data.byteOffset ?? 0;
1759
- const byteLength = data.byteLength;
1760
- if (byteOffset % Float32Array.BYTES_PER_ELEMENT === 0) {
1761
- return new Float32Array(
1762
- data.buffer,
1763
- byteOffset,
1764
- byteLength / Float32Array.BYTES_PER_ELEMENT
1765
- );
1766
- }
1767
- const copy = new Uint8Array(byteLength);
1768
- copy.set(data);
1769
- return new Float32Array(copy.buffer);
1770
- }
1771
-
1772
1890
  // src/utils/vector-index.ts
1773
1891
  var InMemoryVectorIndex = class {
1774
1892
  dims;
@@ -1861,24 +1979,33 @@ var InMemoryVectorIndex = class {
1861
1979
  dot += queryNormalized[d] * data[base + d];
1862
1980
  }
1863
1981
  const distance = 1 - dot;
1982
+ const rowid = rowids[i];
1864
1983
  if (heapSize < k) {
1865
1984
  heapDist[heapSize] = distance;
1866
- heapRow[heapSize] = rowids[i];
1985
+ heapRow[heapSize] = rowid;
1867
1986
  heapSize += 1;
1868
1987
  if (heapSize === k) {
1869
1988
  buildMaxHeap(heapDist, heapRow, k);
1870
1989
  }
1871
- } else if (distance < heapDist[0]) {
1872
- heapDist[0] = distance;
1873
- heapRow[0] = rowids[i];
1874
- siftDown(heapDist, heapRow, 0, k);
1990
+ } else {
1991
+ const worstDist = heapDist[0];
1992
+ const worstRow = heapRow[0];
1993
+ if (distance < worstDist || distance === worstDist && rowid < worstRow) {
1994
+ heapDist[0] = distance;
1995
+ heapRow[0] = rowid;
1996
+ siftDown(heapDist, heapRow, 0, k);
1997
+ }
1875
1998
  }
1876
1999
  }
1877
2000
  const hits = new Array(heapSize);
1878
2001
  for (let i = 0; i < heapSize; i += 1) {
1879
2002
  hits[i] = { memoryRowid: heapRow[i], distance: heapDist[i] };
1880
2003
  }
1881
- hits.sort((a, b) => a.distance - b.distance);
2004
+ hits.sort((a, b) => {
2005
+ const diff = a.distance - b.distance;
2006
+ if (diff !== 0) return diff;
2007
+ return a.memoryRowid - b.memoryRowid;
2008
+ });
1882
2009
  return hits;
1883
2010
  }
1884
2011
  ensureCapacity(needed) {
@@ -1924,8 +2051,8 @@ function siftDown(dist, rows, i, n) {
1924
2051
  let largest = i;
1925
2052
  const left = (i << 1) + 1;
1926
2053
  const right = left + 1;
1927
- if (left < n && dist[left] > dist[largest]) largest = left;
1928
- if (right < n && dist[right] > dist[largest]) largest = right;
2054
+ if (left < n && (dist[left] > dist[largest] || dist[left] === dist[largest] && rows[left] > rows[largest])) largest = left;
2055
+ if (right < n && (dist[right] > dist[largest] || dist[right] === dist[largest] && rows[right] > rows[largest])) largest = right;
1929
2056
  if (largest === i) return;
1930
2057
  const td = dist[i];
1931
2058
  dist[i] = dist[largest];
@@ -2067,6 +2194,9 @@ var BATCH_INSERT_CHUNK = 96;
2067
2194
  var MAX_ANN_OVERFETCH = 8192;
2068
2195
  var INSERT_COALESCE_THRESHOLD = 24;
2069
2196
  var INSERT_COALESCE_MAX = 96;
2197
+ var warnLogger = new WolbargLogger("warn");
2198
+ var warnedSqliteVecFallback = false;
2199
+ var warnedFtsKeywordSearch = false;
2070
2200
  var SqliteStorageProvider = class _SqliteStorageProvider {
2071
2201
  name = "sqlite";
2072
2202
  connectionString;
@@ -2076,6 +2206,10 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2076
2206
  vectorDimensions = null;
2077
2207
  vectorBackend = null;
2078
2208
  sqliteVecLoaded = false;
2209
+ /** Tracks nesting depth for {@link withTransaction} so we can use savepoints. */
2210
+ transactionDepth = 0;
2211
+ /** Incrementing counter for deterministic savepoint names. */
2212
+ savepointCounter = 0;
2079
2213
  /** Hot in-process ANN for blob backend (sqlite-vec unavailable platforms). */
2080
2214
  memoryIndex = null;
2081
2215
  memoryIndexDirty = false;
@@ -2142,6 +2276,12 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2142
2276
  } else if (this.sqliteVecLoaded) {
2143
2277
  this.vectorBackend = "sqlite-vec";
2144
2278
  } else {
2279
+ if (!warnedSqliteVecFallback) {
2280
+ warnedSqliteVecFallback = true;
2281
+ warnLogger.warn(
2282
+ "sqlite-vec unavailable on this platform; using blob ANN fallback."
2283
+ );
2284
+ }
2145
2285
  this.vectorBackend = "blob";
2146
2286
  }
2147
2287
  if (dims !== null) {
@@ -2385,6 +2525,12 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2385
2525
  async searchKeyword(query, organization, topK) {
2386
2526
  const stmts = this.requireStatements();
2387
2527
  if (!stmts.searchFts) {
2528
+ if (!warnedFtsKeywordSearch) {
2529
+ warnedFtsKeywordSearch = true;
2530
+ warnLogger.warn(
2531
+ "FTS keyword search is unavailable; returning empty keyword hits."
2532
+ );
2533
+ }
2388
2534
  return [];
2389
2535
  }
2390
2536
  try {
@@ -2398,7 +2544,16 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2398
2544
  // bm25 returns lower (more negative) for better matches — invert to [0, ∞)
2399
2545
  score: 1 / (1 + Math.abs(row.rank))
2400
2546
  }));
2401
- } catch {
2547
+ } catch (error) {
2548
+ if (!warnedFtsKeywordSearch) {
2549
+ warnedFtsKeywordSearch = true;
2550
+ warnLogger.warn(
2551
+ "FTS keyword search failed; returning empty keyword hits.",
2552
+ {
2553
+ cause: error instanceof Error ? error.message : String(error)
2554
+ }
2555
+ );
2556
+ }
2402
2557
  return [];
2403
2558
  }
2404
2559
  }
@@ -2629,16 +2784,39 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2629
2784
  }
2630
2785
  async withTransaction(fn) {
2631
2786
  const db = this.requireDb();
2632
- return withImmediateTransaction(
2633
- db,
2634
- this.concurrency,
2635
- fn,
2636
- (attempt, delayMs) => {
2637
- this.retryLog?.(
2638
- `SQLITE_BUSY retry attempt=${attempt} backoffMs=${Math.round(delayMs)}`
2639
- );
2787
+ if (this.transactionDepth > 0) {
2788
+ const savepointName = `wolbarg_sp_${this.savepointCounter++}`;
2789
+ db.exec(`SAVEPOINT ${savepointName}`);
2790
+ try {
2791
+ this.transactionDepth += 1;
2792
+ const result = await fn();
2793
+ db.exec(`RELEASE SAVEPOINT ${savepointName}`);
2794
+ return result;
2795
+ } catch (error) {
2796
+ try {
2797
+ db.exec(`ROLLBACK TO SAVEPOINT ${savepointName}`);
2798
+ } catch {
2799
+ }
2800
+ throw error;
2801
+ } finally {
2802
+ this.transactionDepth -= 1;
2640
2803
  }
2641
- );
2804
+ }
2805
+ this.transactionDepth = 1;
2806
+ try {
2807
+ return await withImmediateTransaction(
2808
+ db,
2809
+ this.concurrency,
2810
+ fn,
2811
+ (attempt, delayMs) => {
2812
+ this.retryLog?.(
2813
+ `SQLITE_BUSY retry attempt=${attempt} backoffMs=${Math.round(delayMs)}`
2814
+ );
2815
+ }
2816
+ );
2817
+ } finally {
2818
+ this.transactionDepth = 0;
2819
+ }
2642
2820
  }
2643
2821
  // ─── internals ───────────────────────────────────────────────────────────
2644
2822
  tryLoadSqliteVec(db) {
@@ -3792,9 +3970,13 @@ function createPostgresListenerFromPool(pool, onError) {
3792
3970
 
3793
3971
  // src/storage/providers/postgres.ts
3794
3972
  var txStore = new AsyncLocalStorage();
3973
+ var warnLogger2 = new WolbargLogger("warn");
3974
+ var warnedPgvectorFallback = false;
3975
+ var warnedFtsKeywordSearch2 = false;
3976
+ var warnedHnswSoftFail = false;
3795
3977
  var STMT = {
3796
3978
  insertOne: "Wolbarg_insert_one_v5",
3797
- insertBatch: "Wolbarg_insert_batch_v5"
3979
+ insertBatch: "Wolbarg_insert_batch_v6"
3798
3980
  };
3799
3981
  function toFloat4Param(embedding) {
3800
3982
  const out = new Array(embedding.length);
@@ -3865,13 +4047,13 @@ var INSERT_ONE_SQL = `WITH mem AS (
3865
4047
  var INSERT_BATCH_SQL = `WITH mem AS (
3866
4048
  INSERT INTO memories (
3867
4049
  id, organization, agent, content_text, metadata_json,
3868
- archived, compressed_into, created_at, updated_at
4050
+ archived, compressed_into, content_hash, created_at, updated_at
3869
4051
  )
3870
- SELECT id, org, agent, txt, meta::jsonb, false, NULL, c, u
4052
+ SELECT id, org, agent, txt, meta::jsonb, false, NULL, h, c, u
3871
4053
  FROM unnest(
3872
4054
  $1::text[], $2::text[], $3::text[], $4::text[],
3873
- $5::text[], $6::timestamptz[], $7::timestamptz[]
3874
- ) AS t(id, org, agent, txt, meta, c, u)
4055
+ $5::text[], $6::timestamptz[], $7::timestamptz[], $8::text[]
4056
+ ) AS t(id, org, agent, txt, meta, c, u, h)
3875
4057
  RETURNING id
3876
4058
  ),
3877
4059
  hist AS (
@@ -3887,7 +4069,7 @@ var INSERT_BATCH_SQL = `WITH mem AS (
3887
4069
  emb AS (
3888
4070
  INSERT INTO memory_embeddings (memory_id, embedding, organization, agent, archived)
3889
4071
  SELECT id, emb::vector, org, agent, false
3890
- FROM unnest($1::text[], $8::text[], $2::text[], $3::text[]) AS t(id, emb, org, agent)
4072
+ FROM unnest($1::text[], $9::text[], $2::text[], $3::text[]) AS t(id, emb, org, agent)
3891
4073
  )
3892
4074
  SELECT id FROM mem`;
3893
4075
  var COALESCE_FLUSH_MAX = 128;
@@ -4156,6 +4338,12 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4156
4338
  return;
4157
4339
  }
4158
4340
  this.hnswCreateFailures += 1;
4341
+ if (this.hnswCreateFailures === 1 && !warnedHnswSoftFail) {
4342
+ warnedHnswSoftFail = true;
4343
+ warnLogger2.warn(
4344
+ "HNSW index creation failed; ANN will fall back to sequential scan."
4345
+ );
4346
+ }
4159
4347
  if (this.hnswCreateFailures >= 3) {
4160
4348
  throw new DatabaseError(
4161
4349
  `Failed to create HNSW index after ${this.hnswCreateFailures} attempts: ${this.describe(error)}`,
@@ -4300,6 +4488,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4300
4488
  const metas = new Array(inputs.length);
4301
4489
  const created = new Array(inputs.length);
4302
4490
  const updated = new Array(inputs.length);
4491
+ const contentHashes = new Array(inputs.length);
4303
4492
  const vectors = new Array(inputs.length);
4304
4493
  for (let i = 0; i < inputs.length; i += 1) {
4305
4494
  const input = inputs[i];
@@ -4310,6 +4499,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4310
4499
  metas[i] = serializeMetadata(input.metadata);
4311
4500
  created[i] = input.createdAt;
4312
4501
  updated[i] = input.updatedAt;
4502
+ contentHashes[i] = input.contentHash !== void 0 ? input.contentHash : hashMemoryContent(input.contentText);
4313
4503
  vectors[i] = toVectorLiteral(input.embedding);
4314
4504
  }
4315
4505
  await this.queryNamed(STMT.insertBatch, INSERT_BATCH_SQL, [
@@ -4320,9 +4510,10 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4320
4510
  metas,
4321
4511
  created,
4322
4512
  updated,
4513
+ contentHashes,
4323
4514
  vectors
4324
4515
  ]);
4325
- return inputs.map((input, i) => ({
4516
+ return inputs.map((_, i) => ({
4326
4517
  id: ids[i],
4327
4518
  organization: orgs[i],
4328
4519
  agent: agents[i],
@@ -4330,7 +4521,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4330
4521
  metadata_json: metas[i],
4331
4522
  archived: 0,
4332
4523
  compressed_into: null,
4333
- content_hash: input.contentHash !== void 0 ? input.contentHash : null,
4524
+ content_hash: contentHashes[i] ?? null,
4334
4525
  created_at: created[i],
4335
4526
  updated_at: updated[i]
4336
4527
  }));
@@ -4371,80 +4562,84 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4371
4562
  return out;
4372
4563
  }
4373
4564
  async insertOneBlob(input) {
4374
- const buf = Buffer.from(
4375
- input.embedding.buffer,
4376
- input.embedding.byteOffset,
4377
- input.embedding.byteLength
4378
- );
4379
- const contentHash = input.contentHash !== void 0 ? input.contentHash : hashMemoryContent(input.contentText);
4380
- const inserted = await this.query(
4381
- `INSERT INTO memories (
4382
- id, organization, agent, content_text, metadata_json,
4383
- archived, compressed_into, content_hash, created_at, updated_at
4384
- ) VALUES ($1,$2,$3,$4,$5::jsonb,false,NULL,$8,$6,$7)
4385
- RETURNING id, organization, agent, content_text, metadata_json,
4386
- archived::int AS archived, compressed_into, content_hash, created_at, updated_at`,
4387
- [
4388
- input.id,
4389
- input.organization,
4390
- input.agent,
4391
- input.contentText,
4392
- serializeMetadata(input.metadata),
4393
- input.createdAt,
4394
- input.updatedAt,
4395
- contentHash
4396
- ]
4397
- );
4398
- const row = this.mapRow(inserted.rows[0]);
4399
- await this.query(
4400
- `WITH mapped AS (
4401
- INSERT INTO memory_row_map (memory_id) VALUES ($1)
4402
- ON CONFLICT (memory_id) DO NOTHING
4403
- )
4404
- INSERT INTO memory_embeddings_blob (memory_id, embedding)
4405
- VALUES ($1, $2)
4406
- ON CONFLICT (memory_id) DO UPDATE SET embedding = EXCLUDED.embedding`,
4407
- [input.id, buf]
4408
- );
4409
- await this.query(
4410
- `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4411
- VALUES ($1,$2,'created',NULL,$3)`,
4412
- [crypto.randomUUID(), input.id, input.createdAt]
4413
- );
4414
- return row;
4565
+ return this.withTransaction(async () => {
4566
+ const buf = Buffer.from(
4567
+ input.embedding.buffer,
4568
+ input.embedding.byteOffset,
4569
+ input.embedding.byteLength
4570
+ );
4571
+ const contentHash = input.contentHash !== void 0 ? input.contentHash : hashMemoryContent(input.contentText);
4572
+ const inserted = await this.query(
4573
+ `INSERT INTO memories (
4574
+ id, organization, agent, content_text, metadata_json,
4575
+ archived, compressed_into, content_hash, created_at, updated_at
4576
+ ) VALUES ($1,$2,$3,$4,$5::jsonb,false,NULL,$8,$6,$7)
4577
+ RETURNING id, organization, agent, content_text, metadata_json,
4578
+ archived::int AS archived, compressed_into, content_hash, created_at, updated_at`,
4579
+ [
4580
+ input.id,
4581
+ input.organization,
4582
+ input.agent,
4583
+ input.contentText,
4584
+ serializeMetadata(input.metadata),
4585
+ input.createdAt,
4586
+ input.updatedAt,
4587
+ contentHash
4588
+ ]
4589
+ );
4590
+ const row = this.mapRow(inserted.rows[0]);
4591
+ await this.query(
4592
+ `WITH mapped AS (
4593
+ INSERT INTO memory_row_map (memory_id) VALUES ($1)
4594
+ ON CONFLICT (memory_id) DO NOTHING
4595
+ )
4596
+ INSERT INTO memory_embeddings_blob (memory_id, embedding)
4597
+ VALUES ($1, $2)
4598
+ ON CONFLICT (memory_id) DO UPDATE SET embedding = EXCLUDED.embedding`,
4599
+ [input.id, buf]
4600
+ );
4601
+ await this.query(
4602
+ `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4603
+ VALUES ($1,$2,'created',NULL,$3)`,
4604
+ [crypto.randomUUID(), input.id, input.createdAt]
4605
+ );
4606
+ return row;
4607
+ });
4415
4608
  }
4416
4609
  async updateMemory(input) {
4417
- const existing = await this.getMemoryById(input.id, input.organization);
4418
- if (!existing) {
4419
- return null;
4420
- }
4421
- const contentHash = input.contentHash !== void 0 ? input.contentHash : input.contentText !== void 0 ? hashMemoryContent(input.contentText) : existing.content_hash ?? null;
4422
- await this.query(
4423
- `UPDATE memories SET
4424
- content_text = COALESCE($1, content_text),
4425
- metadata_json = COALESCE($2::jsonb, metadata_json),
4426
- content_hash = COALESCE($3, content_hash),
4427
- updated_at = $4
4428
- WHERE id = $5 AND organization = $6`,
4429
- [
4430
- input.contentText ?? null,
4431
- input.metadata !== void 0 ? serializeMetadata(input.metadata) : null,
4432
- contentHash,
4433
- input.updatedAt,
4434
- input.id,
4435
- input.organization
4436
- ]
4437
- );
4438
- if (input.embedding) {
4439
- await this.deleteEmbedding(input.id);
4440
- await this.insertEmbedding(input.id, input.embedding);
4441
- }
4442
- await this.query(
4443
- `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4444
- VALUES ($1,$2,'updated',NULL,$3)`,
4445
- [crypto.randomUUID(), input.id, input.updatedAt]
4446
- );
4447
- return this.getMemoryById(input.id, input.organization);
4610
+ return this.withTransaction(async () => {
4611
+ const existing = await this.getMemoryById(input.id, input.organization);
4612
+ if (!existing) {
4613
+ return null;
4614
+ }
4615
+ const contentHash = input.contentHash !== void 0 ? input.contentHash : input.contentText !== void 0 ? hashMemoryContent(input.contentText) : existing.content_hash ?? null;
4616
+ await this.query(
4617
+ `UPDATE memories SET
4618
+ content_text = COALESCE($1, content_text),
4619
+ metadata_json = COALESCE($2::jsonb, metadata_json),
4620
+ content_hash = COALESCE($3, content_hash),
4621
+ updated_at = $4
4622
+ WHERE id = $5 AND organization = $6`,
4623
+ [
4624
+ input.contentText ?? null,
4625
+ input.metadata !== void 0 ? serializeMetadata(input.metadata) : null,
4626
+ contentHash,
4627
+ input.updatedAt,
4628
+ input.id,
4629
+ input.organization
4630
+ ]
4631
+ );
4632
+ if (input.embedding) {
4633
+ await this.deleteEmbedding(input.id);
4634
+ await this.insertEmbedding(input.id, input.embedding);
4635
+ }
4636
+ await this.query(
4637
+ `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4638
+ VALUES ($1,$2,'updated',NULL,$3)`,
4639
+ [crypto.randomUUID(), input.id, input.updatedAt]
4640
+ );
4641
+ return this.getMemoryById(input.id, input.organization);
4642
+ });
4448
4643
  }
4449
4644
  async findActiveByContentHash(organization, agent, contentHash) {
4450
4645
  const result = await this.query(
@@ -4593,7 +4788,16 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4593
4788
  memoryId: String(row.memory_id),
4594
4789
  score: Number(row.rank)
4595
4790
  }));
4596
- } catch {
4791
+ } catch (error) {
4792
+ if (!warnedFtsKeywordSearch2) {
4793
+ warnedFtsKeywordSearch2 = true;
4794
+ warnLogger2.warn(
4795
+ "FTS keyword search failed; returning empty keyword hits.",
4796
+ {
4797
+ cause: error instanceof Error ? error.message : String(error)
4798
+ }
4799
+ );
4800
+ }
4597
4801
  return [];
4598
4802
  }
4599
4803
  }
@@ -4711,44 +4915,46 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4711
4915
  return mapHits(result.rows);
4712
4916
  }
4713
4917
  async archiveMemories(ids, organization, compressedIntoId, archivedAt) {
4714
- if (ids.length === 0) {
4715
- return [];
4716
- }
4717
- const result = await this.query(
4718
- `UPDATE memories
4719
- SET archived = true, compressed_into = $1, updated_at = $2
4720
- WHERE organization = $3 AND archived = false AND id = ANY($4::text[])
4721
- RETURNING id`,
4722
- [compressedIntoId, archivedAt, organization, ids]
4723
- );
4724
- const archived = result.rows.map((r) => String(r.id));
4725
- if (archived.length === 0) {
4726
- return [];
4727
- }
4728
- await this.query(
4729
- `UPDATE memory_embeddings
4730
- SET archived = true
4731
- WHERE memory_id = ANY($1::text[])`,
4732
- [archived]
4733
- ).catch(() => void 0);
4734
- const histIds = [];
4735
- const memIds = [];
4736
- const types = [];
4737
- const related = [];
4738
- const times = [];
4739
- for (const id of archived) {
4740
- histIds.push(crypto.randomUUID(), crypto.randomUUID());
4741
- memIds.push(id, compressedIntoId);
4742
- types.push("archived", "compressed");
4743
- related.push(compressedIntoId, id);
4744
- times.push(archivedAt, archivedAt);
4745
- }
4746
- await this.query(
4747
- `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4748
- SELECT * FROM unnest($1::text[], $2::text[], $3::text[], $4::text[], $5::timestamptz[])`,
4749
- [histIds, memIds, types, related, times]
4750
- );
4751
- return archived;
4918
+ return this.withTransaction(async () => {
4919
+ if (ids.length === 0) {
4920
+ return [];
4921
+ }
4922
+ const result = await this.query(
4923
+ `UPDATE memories
4924
+ SET archived = true, compressed_into = $1, updated_at = $2
4925
+ WHERE organization = $3 AND archived = false AND id = ANY($4::text[])
4926
+ RETURNING id`,
4927
+ [compressedIntoId, archivedAt, organization, ids]
4928
+ );
4929
+ const archived = result.rows.map((r) => String(r.id));
4930
+ if (archived.length === 0) {
4931
+ return [];
4932
+ }
4933
+ await this.query(
4934
+ `UPDATE memory_embeddings
4935
+ SET archived = true
4936
+ WHERE memory_id = ANY($1::text[])`,
4937
+ [archived]
4938
+ );
4939
+ const histIds = [];
4940
+ const memIds = [];
4941
+ const types = [];
4942
+ const related = [];
4943
+ const times = [];
4944
+ for (const id of archived) {
4945
+ histIds.push(crypto.randomUUID(), crypto.randomUUID());
4946
+ memIds.push(id, compressedIntoId);
4947
+ types.push("archived", "compressed");
4948
+ related.push(compressedIntoId, id);
4949
+ times.push(archivedAt, archivedAt);
4950
+ }
4951
+ await this.query(
4952
+ `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4953
+ SELECT * FROM unnest($1::text[], $2::text[], $3::text[], $4::text[], $5::timestamptz[])`,
4954
+ [histIds, memIds, types, related, times]
4955
+ );
4956
+ return archived;
4957
+ });
4752
4958
  }
4753
4959
  async deleteMemoryById(id, organization) {
4754
4960
  const result = await this.query(
@@ -4859,6 +5065,11 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4859
5065
  [META_KEYS.schemaVersion]
4860
5066
  ).catch(() => ({ rows: [] }));
4861
5067
  const current = versionRow.rows[0]?.value !== void 0 ? Number(versionRow.rows[0].value) : null;
5068
+ if (current !== null && current > SCHEMA_VERSION) {
5069
+ throw new InitializationError(
5070
+ `Database schema version ${current} is newer than this Wolbarg SDK (${SCHEMA_VERSION}). Please upgrade Wolbarg to open this database.`
5071
+ );
5072
+ }
4862
5073
  if (current === SCHEMA_VERSION) {
4863
5074
  const tsvProbe2 = await this.query(
4864
5075
  `SELECT 1 FROM information_schema.columns
@@ -5009,8 +5220,17 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
5009
5220
  await this.query(`CREATE EXTENSION IF NOT EXISTS vector`);
5010
5221
  _PostgresStorageProvider.pgvectorByConn.set(this.connectionString, true);
5011
5222
  return true;
5012
- } catch {
5223
+ } catch (error) {
5013
5224
  _PostgresStorageProvider.pgvectorByConn.set(this.connectionString, false);
5225
+ if (!warnedPgvectorFallback) {
5226
+ warnedPgvectorFallback = true;
5227
+ warnLogger2.warn(
5228
+ "pgvector extension is unavailable; using blob cosine search fallback.",
5229
+ {
5230
+ cause: error instanceof Error ? error.message : `unknown error: ${String(error)}`
5231
+ }
5232
+ );
5233
+ }
5014
5234
  return false;
5015
5235
  }
5016
5236
  }
@@ -5151,11 +5371,12 @@ function createDatabaseProvider(config) {
5151
5371
  }
5152
5372
 
5153
5373
  // src/version.ts
5154
- var SDK_VERSION = "0.5.3";
5374
+ var SDK_VERSION = "0.5.4";
5155
5375
 
5156
5376
  // src/memory/transfer.ts
5377
+ var warnedMissingExportManifest = false;
5157
5378
  var SqliteMemoryTransferProvider = class {
5158
- async exportTo(exportPath, sourcePath, organization) {
5379
+ async exportTo(exportPath, sourcePath, organization, embeddingModel, embeddingDimensions) {
5159
5380
  const resolvedSource = resolvePath(sourcePath);
5160
5381
  if (resolvedSource === ":memory:") {
5161
5382
  throw new ConfigurationError(
@@ -5182,7 +5403,9 @@ Initialize Wolbarg and verify the database path.`
5182
5403
  sdkVersion: SDK_VERSION,
5183
5404
  provider: "sqlite",
5184
5405
  sourcePath: resolvedSource,
5185
- organization
5406
+ organization,
5407
+ ...embeddingModel ? { embeddingModel } : {},
5408
+ ...embeddingDimensions ? { embeddingDimensions } : {}
5186
5409
  };
5187
5410
  fs6.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
5188
5411
  return {
@@ -5191,7 +5414,7 @@ Initialize Wolbarg and verify the database path.`
5191
5414
  sizeBytes: fs6.statSync(dbExportPath).size
5192
5415
  };
5193
5416
  }
5194
- async importFrom(exportPath, targetPath) {
5417
+ async importFrom(exportPath, targetPath, expected) {
5195
5418
  const resolvedExport = resolvePath(exportPath);
5196
5419
  const dbExportPath = resolvedExport.endsWith(".db") ? resolvedExport : fs6.existsSync(`${resolvedExport}.db`) ? `${resolvedExport}.db` : resolvedExport;
5197
5420
  if (!fs6.existsSync(dbExportPath)) {
@@ -5209,30 +5432,61 @@ Pass the path returned by export().`
5209
5432
  manifest = JSON.parse(fs6.readFileSync(manifestPath, "utf8"));
5210
5433
  if (manifest.format !== "wolbarg-export-v1") {
5211
5434
  throw new ValidationError(
5212
- `Unsupported export format: ${String(manifest.format)}`
5435
+ `Unsupported export format: ${String(manifest.format)}`
5436
+ );
5437
+ }
5438
+ } else {
5439
+ if (!warnedMissingExportManifest) {
5440
+ warnedMissingExportManifest = true;
5441
+ console.warn(
5442
+ "[wolbarg:warn] export manifest is missing; skipping manifest-based import validation."
5443
+ );
5444
+ }
5445
+ }
5446
+ if (manifest) {
5447
+ if (expected?.organization && manifest.organization && expected.organization !== manifest.organization) {
5448
+ throw new ValidationError(
5449
+ "Import failed: export organization does not match the current Wolbarg organization."
5450
+ );
5451
+ }
5452
+ if (expected?.embeddingDimensions != null && manifest.embeddingDimensions != null && expected.embeddingDimensions !== manifest.embeddingDimensions) {
5453
+ throw new ValidationError(
5454
+ `Import failed: embedding dimension mismatch (expected ${expected.embeddingDimensions}, got ${manifest.embeddingDimensions}).`
5455
+ );
5456
+ }
5457
+ if (expected?.embeddingModel && manifest.embeddingModel && expected.embeddingModel !== manifest.embeddingModel) {
5458
+ throw new ValidationError(
5459
+ `Import failed: embedding model mismatch (expected ${expected.embeddingModel}, got ${manifest.embeddingModel}).`
5213
5460
  );
5214
5461
  }
5215
- } else {
5216
- manifest = {
5217
- format: "wolbarg-export-v1",
5218
- exportedAt: nowIso(),
5219
- sdkVersion: SDK_VERSION,
5220
- provider: "sqlite",
5221
- sourcePath: dbExportPath
5222
- };
5223
5462
  }
5224
5463
  const resolvedTarget = resolvePath(targetPath);
5225
5464
  if (resolvedTarget === ":memory:") {
5226
5465
  throw new ConfigurationError("Cannot import into an in-memory database.");
5227
5466
  }
5467
+ const tmpTarget = `${resolvedTarget}.tmp-import-${Date.now()}`;
5468
+ for (const suffix of ["", "-wal", "-shm"]) {
5469
+ const side = `${tmpTarget}${suffix}`;
5470
+ if (fs6.existsSync(side)) {
5471
+ fs6.rmSync(side, { force: true });
5472
+ }
5473
+ }
5474
+ await copySqlite(dbExportPath, tmpTarget);
5228
5475
  for (const suffix of ["", "-wal", "-shm"]) {
5229
5476
  const side = `${resolvedTarget}${suffix}`;
5230
5477
  if (fs6.existsSync(side)) {
5231
5478
  fs6.rmSync(side, { force: true });
5232
5479
  }
5233
5480
  }
5234
- await copySqlite(dbExportPath, resolvedTarget);
5235
- return { path: resolvedTarget, manifest };
5481
+ fs6.renameSync(tmpTarget, resolvedTarget);
5482
+ for (const suffix of ["-wal", "-shm"]) {
5483
+ const from = `${tmpTarget}${suffix}`;
5484
+ const to = `${resolvedTarget}${suffix}`;
5485
+ if (fs6.existsSync(from)) {
5486
+ fs6.renameSync(from, to);
5487
+ }
5488
+ }
5489
+ return { path: resolvedTarget, manifest: manifest ?? void 0 };
5236
5490
  }
5237
5491
  };
5238
5492
  async function copySqlite(sourcePath, destPath) {
@@ -5461,7 +5715,8 @@ function applyMmr(candidates, topK, lambda) {
5461
5715
  );
5462
5716
  }
5463
5717
  const score = lambda * relevance - (1 - lambda) * maxSim;
5464
- if (score > bestScore) {
5718
+ const best = remaining[bestIdx];
5719
+ if (score > bestScore || score === bestScore && candidate.id < best.id) {
5465
5720
  bestScore = score;
5466
5721
  bestIdx = i;
5467
5722
  }
@@ -6021,24 +6276,26 @@ var Neo4jGraphProvider = class {
6021
6276
  }
6022
6277
  }
6023
6278
  async run(cypher, params = {}) {
6024
- return this.withSession(async (session) => {
6025
- const result = await session.run(cypher, params);
6026
- return result.records.map((rec) => {
6027
- try {
6028
- return rec.toObject();
6029
- } catch {
6030
- const obj = {};
6031
- for (const key of rec.keys) {
6032
- obj[key] = unwrapNeo4jValue(rec.get(key));
6033
- }
6034
- return obj;
6279
+ return this.withSession(
6280
+ (session) => this.runOnSession(session, cypher, params)
6281
+ );
6282
+ }
6283
+ async runOnSession(session, cypher, params = {}) {
6284
+ const result = await session.run(cypher, params);
6285
+ return result.records.map((rec) => {
6286
+ try {
6287
+ return rec.toObject();
6288
+ } catch {
6289
+ const obj = {};
6290
+ for (const key of rec.keys) {
6291
+ obj[key] = unwrapNeo4jValue(rec.get(key));
6035
6292
  }
6036
- });
6293
+ return obj;
6294
+ }
6037
6295
  });
6038
6296
  }
6039
- async ensureMemoryNode(id) {
6040
- await this.run(
6041
- `MERGE (m:Memory { id: $id })
6297
+ async ensureMemoryNode(id, session) {
6298
+ const cypher = `MERGE (m:Memory { id: $id })
6042
6299
  ON CREATE SET
6043
6300
  m.organization = '',
6044
6301
  m.agent = '',
@@ -6047,24 +6304,30 @@ var Neo4jGraphProvider = class {
6047
6304
  m.archived = false,
6048
6305
  m.compressed_into = '',
6049
6306
  m.created_at = '',
6050
- m.updated_at = ''`,
6051
- { id }
6052
- );
6307
+ m.updated_at = ''`;
6308
+ if (session) {
6309
+ await this.runOnSession(session, cypher, { id });
6310
+ return;
6311
+ }
6312
+ await this.run(cypher, { id });
6053
6313
  }
6054
6314
  async linkMemories(fromId, toId, relation, metadata) {
6055
- await this.ensureMemoryNode(fromId);
6056
- await this.ensureMemoryNode(toId);
6057
- await this.run(
6058
- `MATCH (a:Memory { id: $fromId }), (b:Memory { id: $toId })
6059
- MERGE (a)-[r:RELATED { relation: $relation }]->(b)
6060
- SET r.metadata_json = $metadata`,
6061
- {
6062
- fromId,
6063
- toId,
6064
- relation,
6065
- metadata: serializeMetadata2(metadata)
6066
- }
6067
- );
6315
+ await this.withSession(async (session) => {
6316
+ await this.ensureMemoryNode(fromId, session);
6317
+ await this.ensureMemoryNode(toId, session);
6318
+ await this.runOnSession(
6319
+ session,
6320
+ `MATCH (a:Memory { id: $fromId }), (b:Memory { id: $toId })
6321
+ MERGE (a)-[r:RELATED { relation: $relation }]->(b)
6322
+ SET r.metadata_json = $metadata`,
6323
+ {
6324
+ fromId,
6325
+ toId,
6326
+ relation,
6327
+ metadata: serializeMetadata2(metadata)
6328
+ }
6329
+ );
6330
+ });
6068
6331
  }
6069
6332
  async unlinkMemories(fromId, toId, relation) {
6070
6333
  if (relation !== void 0) {
@@ -6126,22 +6389,26 @@ var Neo4jGraphProvider = class {
6126
6389
  return id;
6127
6390
  }
6128
6391
  async linkEntityToMemory(entityId, memoryId, role) {
6129
- await this.ensureMemoryNode(memoryId);
6130
- const found = await this.run(
6131
- `MATCH (e:Entity { id: $id }) RETURN e.id AS id`,
6132
- { id: entityId }
6133
- );
6134
- if (found.length === 0) {
6135
- throw new DatabaseError(`Entity not found: ${entityId}`, {
6136
- operation: "linkEntityToMemory"
6137
- });
6138
- }
6139
- await this.run(
6140
- `MATCH (e:Entity { id: $entityId }), (m:Memory { id: $memoryId })
6141
- MERGE (e)-[r:MENTIONS]->(m)
6142
- SET r.role = $role`,
6143
- { entityId, memoryId, role: role ?? "" }
6144
- );
6392
+ await this.withSession(async (session) => {
6393
+ await this.ensureMemoryNode(memoryId, session);
6394
+ const found = await this.runOnSession(
6395
+ session,
6396
+ `MATCH (e:Entity { id: $id }) RETURN e.id AS id`,
6397
+ { id: entityId }
6398
+ );
6399
+ if (found.length === 0) {
6400
+ throw new DatabaseError(`Entity not found: ${entityId}`, {
6401
+ operation: "linkEntityToMemory"
6402
+ });
6403
+ }
6404
+ await this.runOnSession(
6405
+ session,
6406
+ `MATCH (e:Entity { id: $entityId }), (m:Memory { id: $memoryId })
6407
+ MERGE (e)-[r:MENTIONS]->(m)
6408
+ SET r.role = $role`,
6409
+ { entityId, memoryId, role: role ?? "" }
6410
+ );
6411
+ });
6145
6412
  }
6146
6413
  async deleteMemory(memoryId) {
6147
6414
  await this.run(
@@ -6214,6 +6481,21 @@ function assertNonEmpty(value, fieldName) {
6214
6481
  throw new ConfigurationError(`${fieldName} must be a non-empty string`);
6215
6482
  }
6216
6483
  }
6484
+ function assertFiniteNumber2(value, fieldName) {
6485
+ if (typeof value !== "number" || !Number.isFinite(value)) {
6486
+ throw new ConfigurationError(`${fieldName} must be a finite number`);
6487
+ }
6488
+ }
6489
+ function assertNumberMin(value, fieldName, min) {
6490
+ if (!Number.isFinite(value) || value < min) {
6491
+ throw new ConfigurationError(`${fieldName} must be >= ${min}`);
6492
+ }
6493
+ }
6494
+ function assertNumberInRange(value, fieldName, min, max) {
6495
+ if (!Number.isFinite(value) || value < min || value > max) {
6496
+ throw new ConfigurationError(`${fieldName} must be between ${min} and ${max}`);
6497
+ }
6498
+ }
6217
6499
  function assertUrl(value, fieldName) {
6218
6500
  assertNonEmpty(value, fieldName);
6219
6501
  try {
@@ -6260,6 +6542,135 @@ function validateLlmConfig(config) {
6260
6542
  ...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {}
6261
6543
  };
6262
6544
  }
6545
+ function validateRetrievalConfig(config) {
6546
+ if (config === null || typeof config !== "object") {
6547
+ throw new ConfigurationError("retrieval must be an object");
6548
+ }
6549
+ const out = {};
6550
+ if (config.overFetchFactor !== void 0) {
6551
+ assertFiniteNumber2(config.overFetchFactor, "retrieval.overFetchFactor");
6552
+ assertNumberMin(config.overFetchFactor, "retrieval.overFetchFactor", 0);
6553
+ if (config.overFetchFactor <= 0) {
6554
+ throw new ConfigurationError("retrieval.overFetchFactor must be > 0");
6555
+ }
6556
+ out.overFetchFactor = config.overFetchFactor;
6557
+ }
6558
+ if (config.hybrid !== void 0) {
6559
+ if (config.hybrid === null || typeof config.hybrid !== "object") {
6560
+ throw new ConfigurationError("retrieval.hybrid must be an object");
6561
+ }
6562
+ const hybrid = config.hybrid;
6563
+ const outHybrid = {};
6564
+ if (hybrid.semanticWeight !== void 0) {
6565
+ assertFiniteNumber2(hybrid.semanticWeight, "retrieval.hybrid.semanticWeight");
6566
+ if (hybrid.semanticWeight < 0) {
6567
+ throw new ConfigurationError(
6568
+ "retrieval.hybrid.semanticWeight must be >= 0"
6569
+ );
6570
+ }
6571
+ outHybrid.semanticWeight = hybrid.semanticWeight;
6572
+ }
6573
+ if (hybrid.keywordWeight !== void 0) {
6574
+ assertFiniteNumber2(hybrid.keywordWeight, "retrieval.hybrid.keywordWeight");
6575
+ if (hybrid.keywordWeight < 0) {
6576
+ throw new ConfigurationError(
6577
+ "retrieval.hybrid.keywordWeight must be >= 0"
6578
+ );
6579
+ }
6580
+ outHybrid.keywordWeight = hybrid.keywordWeight;
6581
+ }
6582
+ if (Object.keys(outHybrid).length > 0) {
6583
+ out.hybrid = outHybrid;
6584
+ }
6585
+ }
6586
+ if (config.mmr !== void 0) {
6587
+ if (config.mmr === null || typeof config.mmr !== "object") {
6588
+ throw new ConfigurationError("retrieval.mmr must be an object");
6589
+ }
6590
+ const mmr = config.mmr;
6591
+ const outMmr = {};
6592
+ if (mmr.lambda !== void 0) {
6593
+ assertFiniteNumber2(mmr.lambda, "retrieval.mmr.lambda");
6594
+ assertNumberInRange(mmr.lambda, "retrieval.mmr.lambda", 0, 1);
6595
+ outMmr.lambda = mmr.lambda;
6596
+ }
6597
+ if (Object.keys(outMmr).length > 0) {
6598
+ out.mmr = outMmr;
6599
+ }
6600
+ }
6601
+ return out;
6602
+ }
6603
+ function validateMemoryDedupeConfig(config) {
6604
+ if (config === null || typeof config !== "object") {
6605
+ throw new ConfigurationError("memory.dedupe must be an object");
6606
+ }
6607
+ const out = {};
6608
+ if (config.enabled !== void 0) {
6609
+ if (typeof config.enabled !== "boolean") {
6610
+ throw new ConfigurationError("memory.dedupe.enabled must be a boolean");
6611
+ }
6612
+ out.enabled = config.enabled;
6613
+ }
6614
+ if (config.strategy !== void 0) {
6615
+ const allowed = /* @__PURE__ */ new Set(["exact", "near", "exact-or-near"]);
6616
+ if (!allowed.has(config.strategy)) {
6617
+ throw new ConfigurationError(
6618
+ `memory.dedupe.strategy must be one of ${Array.from(allowed).join(", ")}`
6619
+ );
6620
+ }
6621
+ out.strategy = config.strategy;
6622
+ }
6623
+ if (config.nearThreshold !== void 0) {
6624
+ assertFiniteNumber2(config.nearThreshold, "memory.dedupe.nearThreshold");
6625
+ assertNumberInRange(
6626
+ config.nearThreshold,
6627
+ "memory.dedupe.nearThreshold",
6628
+ 0,
6629
+ 1
6630
+ );
6631
+ out.nearThreshold = config.nearThreshold;
6632
+ }
6633
+ if (config.nearCandidateLimit !== void 0) {
6634
+ assertFiniteNumber2(
6635
+ config.nearCandidateLimit,
6636
+ "memory.dedupe.nearCandidateLimit"
6637
+ );
6638
+ assertNumberMin(
6639
+ config.nearCandidateLimit,
6640
+ "memory.dedupe.nearCandidateLimit",
6641
+ 1
6642
+ );
6643
+ out.nearCandidateLimit = config.nearCandidateLimit;
6644
+ }
6645
+ return out;
6646
+ }
6647
+ function validateEmbeddingCacheConfig(config) {
6648
+ if (config === null || typeof config !== "object") {
6649
+ throw new ConfigurationError("embeddingCache must be an object");
6650
+ }
6651
+ const out = {};
6652
+ if (config.enabled !== void 0) {
6653
+ if (typeof config.enabled !== "boolean") {
6654
+ throw new ConfigurationError("embeddingCache.enabled must be a boolean");
6655
+ }
6656
+ out.enabled = config.enabled;
6657
+ }
6658
+ if (config.ttlMs !== void 0) {
6659
+ assertFiniteNumber2(config.ttlMs, "embeddingCache.ttlMs");
6660
+ if (config.ttlMs < 0) {
6661
+ throw new ConfigurationError("embeddingCache.ttlMs must be >= 0");
6662
+ }
6663
+ out.ttlMs = config.ttlMs;
6664
+ }
6665
+ if (config.maxEntries !== void 0) {
6666
+ assertFiniteNumber2(config.maxEntries, "embeddingCache.maxEntries");
6667
+ if (config.maxEntries < 0) {
6668
+ throw new ConfigurationError("embeddingCache.maxEntries must be >= 0");
6669
+ }
6670
+ out.maxEntries = config.maxEntries;
6671
+ }
6672
+ return out;
6673
+ }
6263
6674
  function normalizeDatabaseConfig(config) {
6264
6675
  const provider = config.provider;
6265
6676
  if (provider !== "sqlite" && provider !== "postgres") {
@@ -6369,11 +6780,22 @@ function validateWolbargOptions(options) {
6369
6780
  throw new ConfigurationError("Wolbarg options must be an object");
6370
6781
  }
6371
6782
  assertNonEmpty(options.organization, "organization");
6783
+ const checkpointDirectory = options.checkpointDirectory !== void 0 ? (() => {
6784
+ assertNonEmpty(options.checkpointDirectory, "checkpointDirectory");
6785
+ return options.checkpointDirectory.trim();
6786
+ })() : void 0;
6372
6787
  const storageInput = resolveStorageInput(options);
6373
6788
  let storage = storageInput;
6374
6789
  if (!isStorageProvider(storageInput)) {
6375
6790
  storage = normalizeDatabaseConfig(storageInput);
6376
6791
  }
6792
+ if (!isStorageProvider(storageInput) && storageInput.provider === "postgres" && storageInput.maxPoolSize !== void 0) {
6793
+ assertFiniteNumber2(
6794
+ storageInput.maxPoolSize,
6795
+ "database.maxPoolSize"
6796
+ );
6797
+ assertNumberMin(storageInput.maxPoolSize, "database.maxPoolSize", 1);
6798
+ }
6377
6799
  if (!options.embedding) {
6378
6800
  throw new ConfigurationError("embedding is required");
6379
6801
  }
@@ -6391,13 +6813,20 @@ function validateWolbargOptions(options) {
6391
6813
  if (graph !== void 0) {
6392
6814
  graph = resolveGraphInput(graph);
6393
6815
  }
6816
+ const retrieval = options.retrieval !== void 0 ? validateRetrievalConfig(options.retrieval) : void 0;
6817
+ const embeddingCache = options.embeddingCache !== void 0 ? validateEmbeddingCacheConfig(options.embeddingCache) : void 0;
6818
+ const memory = options.memory !== void 0 && options.memory.dedupe !== void 0 ? { ...options.memory, dedupe: validateMemoryDedupeConfig(options.memory.dedupe) } : options.memory;
6394
6819
  return {
6395
6820
  ...options,
6396
6821
  organization: options.organization.trim(),
6397
6822
  storage,
6398
6823
  database: void 0,
6399
6824
  ...telemetry ? { telemetry } : {},
6400
- ...graph !== void 0 ? { graph } : {}
6825
+ ...graph !== void 0 ? { graph } : {},
6826
+ ...retrieval !== void 0 ? { retrieval } : {},
6827
+ ...embeddingCache !== void 0 ? { embeddingCache } : {},
6828
+ ...checkpointDirectory !== void 0 ? { checkpointDirectory } : {},
6829
+ ...memory !== void 0 ? { memory } : {}
6401
6830
  };
6402
6831
  }
6403
6832
  function resolveGraphInput(input) {
@@ -6456,59 +6885,6 @@ function resolveGraphInput(input) {
6456
6885
  );
6457
6886
  }
6458
6887
 
6459
- // src/telemetry/logger.ts
6460
- var LEVEL_ORDER = {
6461
- off: 100,
6462
- error: 50,
6463
- warn: 40,
6464
- info: 30,
6465
- debug: 20,
6466
- trace: 10
6467
- };
6468
- var WolbargLogger = class {
6469
- constructor(level = "info") {
6470
- this.level = level;
6471
- }
6472
- level;
6473
- setLevel(level) {
6474
- this.level = level;
6475
- }
6476
- getLevel() {
6477
- return this.level;
6478
- }
6479
- error(message, extra) {
6480
- this.write("error", message, extra);
6481
- }
6482
- warn(message, extra) {
6483
- this.write("warn", message, extra);
6484
- }
6485
- info(message, extra) {
6486
- this.write("info", message, extra);
6487
- }
6488
- debug(message, extra) {
6489
- this.write("debug", message, extra);
6490
- }
6491
- trace(message, extra) {
6492
- this.write("trace", message, extra);
6493
- }
6494
- write(level, message, extra) {
6495
- if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level]) {
6496
- return;
6497
- }
6498
- if (this.level === "off") {
6499
- return;
6500
- }
6501
- const line = `[wolbarg:${level}] ${message}`;
6502
- if (level === "error") {
6503
- console.error(line, extra ?? "");
6504
- } else if (level === "warn") {
6505
- console.warn(line, extra ?? "");
6506
- } else {
6507
- console.log(line, extra ?? "");
6508
- }
6509
- }
6510
- };
6511
-
6512
6888
  // src/telemetry/trace.ts
6513
6889
  function createSessionId() {
6514
6890
  return createId();
@@ -6593,14 +6969,14 @@ var TelemetryEmitter = class {
6593
6969
  const totalMs = performance.now() - startedAt;
6594
6970
  const errMessage = error instanceof Error ? error.message : error ? String(error) : fields?.error ?? null;
6595
6971
  const errStack = error instanceof Error ? error.stack ?? null : fields?.errorStack ?? null;
6972
+ if (!self.config.enabled) {
6973
+ return;
6974
+ }
6596
6975
  if (status === "error") {
6597
6976
  self.logger.error(`${operation} failed: ${errMessage ?? "unknown"}`);
6598
6977
  } else {
6599
6978
  self.logger.debug(`${operation} completed in ${totalMs.toFixed(2)}ms`);
6600
6979
  }
6601
- if (!self.config.enabled) {
6602
- return;
6603
- }
6604
6980
  const event = {
6605
6981
  id: createId(),
6606
6982
  timestamp: nowIso(),
@@ -6902,8 +7278,10 @@ var SqliteEventDatabase = class {
6902
7278
  }
6903
7279
  }
6904
7280
  if (options.memoryId) {
6905
- clauses.push(`memory_ids_json LIKE ?`);
6906
- params.push(`%${options.memoryId}%`);
7281
+ clauses.push(
7282
+ `EXISTS (SELECT 1 FROM json_each(memory_ids_json) WHERE json_each.value = ?)`
7283
+ );
7284
+ params.push(options.memoryId);
6907
7285
  }
6908
7286
  if (options.queryText) {
6909
7287
  clauses.push(`query LIKE ?`);
@@ -7080,6 +7458,7 @@ function describe2(error) {
7080
7458
  }
7081
7459
 
7082
7460
  // src/providers/sqlite/sqliteTelemetryProvider.ts
7461
+ var MAX_QUEUE_SIZE = 1e4;
7083
7462
  var SqliteTelemetryProvider = class {
7084
7463
  name = "sqlite";
7085
7464
  db;
@@ -7087,6 +7466,8 @@ var SqliteTelemetryProvider = class {
7087
7466
  flushing = null;
7088
7467
  closed = false;
7089
7468
  openPromise = null;
7469
+ warnedQueueDrop = false;
7470
+ warnedFlushFailure = false;
7090
7471
  constructor(options) {
7091
7472
  this.db = new SqliteEventDatabase({ url: options.url });
7092
7473
  }
@@ -7107,6 +7488,15 @@ var SqliteTelemetryProvider = class {
7107
7488
  if (this.closed) {
7108
7489
  return;
7109
7490
  }
7491
+ if (this.queue.length >= MAX_QUEUE_SIZE) {
7492
+ this.queue.shift();
7493
+ if (!this.warnedQueueDrop) {
7494
+ this.warnedQueueDrop = true;
7495
+ console.warn(
7496
+ `[wolbarg telemetry] event queue capped at ${MAX_QUEUE_SIZE}; dropping oldest events`
7497
+ );
7498
+ }
7499
+ }
7110
7500
  this.queue.push(event);
7111
7501
  void this.scheduleFlush();
7112
7502
  }
@@ -7149,7 +7539,13 @@ var SqliteTelemetryProvider = class {
7149
7539
  await this.db.insertEvent(event);
7150
7540
  }
7151
7541
  }
7152
- } catch {
7542
+ } catch (error) {
7543
+ if (!this.warnedFlushFailure) {
7544
+ this.warnedFlushFailure = true;
7545
+ console.warn(
7546
+ `[wolbarg telemetry] flush failed; dropping batch: ${error instanceof Error ? error.message : String(error)}`
7547
+ );
7548
+ }
7153
7549
  }
7154
7550
  }
7155
7551
  };
@@ -7192,8 +7588,20 @@ Ensure Wolbarg has been initialized and the database path is correct.`
7192
7588
  );
7193
7589
  }
7194
7590
  const snapshotPath = this.snapshotPath(name);
7195
- await safeSqliteBackup(resolvedSource, snapshotPath);
7196
- const stats = fs6.statSync(snapshotPath);
7591
+ const tmpSnapshotPath = `${snapshotPath}.tmp-${Date.now()}`;
7592
+ try {
7593
+ await safeSqliteBackup(resolvedSource, tmpSnapshotPath);
7594
+ } catch (error) {
7595
+ if (fs6.existsSync(tmpSnapshotPath)) {
7596
+ fs6.rmSync(tmpSnapshotPath, { force: true });
7597
+ }
7598
+ throw error;
7599
+ }
7600
+ const stats = fs6.statSync(tmpSnapshotPath);
7601
+ if (fs6.existsSync(snapshotPath)) {
7602
+ fs6.rmSync(snapshotPath, { force: true });
7603
+ }
7604
+ fs6.renameSync(tmpSnapshotPath, snapshotPath);
7197
7605
  const meta2 = {
7198
7606
  name,
7199
7607
  description: options?.description ?? null,
@@ -7204,7 +7612,9 @@ Ensure Wolbarg has been initialized and the database path is correct.`
7204
7612
  sourcePath: resolvedSource,
7205
7613
  sizeBytes: stats.size
7206
7614
  };
7207
- fs6.writeFileSync(metaPath, JSON.stringify(meta2, null, 2), "utf8");
7615
+ const tmpMetaPath = `${metaPath}.tmp-${Date.now()}`;
7616
+ fs6.writeFileSync(tmpMetaPath, JSON.stringify(meta2, null, 2), "utf8");
7617
+ fs6.renameSync(tmpMetaPath, metaPath);
7208
7618
  return meta2;
7209
7619
  }
7210
7620
  async rollback(name, targetPath) {
@@ -7468,6 +7878,9 @@ var SqliteSubscribeEmitter = class {
7468
7878
  };
7469
7879
 
7470
7880
  // src/core/wolbarg.ts
7881
+ var warnLogger3 = new WolbargLogger("warn");
7882
+ var warnedHybridNoKeyword = false;
7883
+ var warnedRerankNoProvider = false;
7471
7884
  var Wolbarg = class {
7472
7885
  initialized = false;
7473
7886
  booting = null;
@@ -7573,6 +7986,7 @@ var Wolbarg = class {
7573
7986
  this.storage = createStorageProvider(validated.database);
7574
7987
  this.memoryDbPath = resolveDatabaseUrl(validated.database);
7575
7988
  this.embedding = createEmbeddingProvider(validated.embedding);
7989
+ this.rawEmbedding = this.embedding;
7576
7990
  if (validated.llm) {
7577
7991
  this.llm = createLlmProvider(validated.llm);
7578
7992
  this.compression = createCompressionProvider(this.llm);
@@ -7894,7 +8308,7 @@ var Wolbarg = class {
7894
8308
  * Update an existing memory by id (re-embeds when content changes).
7895
8309
  */
7896
8310
  async update(options) {
7897
- const trace = this.telemetry.start("remember");
8311
+ const trace = this.telemetry.start("update");
7898
8312
  try {
7899
8313
  const { storage, embedding, organization } = await this.requireReady();
7900
8314
  assertNonEmptyString(options.id, "id");
@@ -8420,17 +8834,37 @@ var Wolbarg = class {
8420
8834
  }
8421
8835
  }
8422
8836
  }
8837
+ if (hybridWeights && keywordSignal === "disabled" && !warnedHybridNoKeyword) {
8838
+ warnedHybridNoKeyword = true;
8839
+ warnLogger3.warn(
8840
+ "hybrid search requested but no keyword channel is available; using semantic-only results."
8841
+ );
8842
+ }
8423
8843
  const tRank = performance.now();
8424
- let results = [...byId.values()].sort(
8425
- (a, b) => b.similarity - a.similarity
8426
- );
8844
+ let results = [...byId.values()].sort((a, b) => {
8845
+ const diff = b.similarity - a.similarity;
8846
+ if (diff !== 0) return diff;
8847
+ return a.id.localeCompare(b.id);
8848
+ });
8427
8849
  const mmrLambda = resolveMmr(options.mmr) ?? resolveMmr(
8428
8850
  this.retrievalConfig.mmr ? { lambda: this.retrievalConfig.mmr.lambda } : void 0
8429
8851
  );
8430
8852
  let rankingReason = "cosine similarity";
8853
+ let mmrApplied = false;
8431
8854
  if (mmrLambda !== null) {
8432
- results = applyMmr(results, Math.max(topK * 2, topK), mmrLambda);
8433
- rankingReason = `MMR(lambda=${mmrLambda})`;
8855
+ mmrApplied = results.length > topK;
8856
+ if (mmrApplied) {
8857
+ results = applyMmr(results, topK, mmrLambda);
8858
+ rankingReason = `MMR(lambda=${mmrLambda})`;
8859
+ } else {
8860
+ rankingReason = `MMR(lambda=${mmrLambda})(skipped:insufficient_candidates)`;
8861
+ }
8862
+ }
8863
+ if (options.rerank === true && !this.reranker && !warnedRerankNoProvider) {
8864
+ warnedRerankNoProvider = true;
8865
+ warnLogger3.warn(
8866
+ "rerank is enabled but no reranker provider is configured; using identity order."
8867
+ );
8434
8868
  }
8435
8869
  if (options.rerank && this.reranker) {
8436
8870
  const reranked = await this.reranker.rerank(
@@ -8465,14 +8899,16 @@ var Wolbarg = class {
8465
8899
  agentId: options.filter?.agent ?? commonAgent(serialized.map((result) => result.agent)),
8466
8900
  tags: commonTags(serialized.map((result) => result.metadata))
8467
8901
  };
8468
- if (!explain) {
8469
- if (options.includeGraph === true && this.graph) {
8470
- const tGraph = performance.now();
8471
- for (const hit of serialized) {
8902
+ if (options.includeGraph === true && this.graph) {
8903
+ const tGraph = performance.now();
8904
+ await Promise.all(
8905
+ serialized.map(async (hit) => {
8472
8906
  hit.related = await this.hydrateRelated(hit.id);
8473
- }
8474
- trace.mark("databaseReadMs", performance.now() - tGraph);
8475
- }
8907
+ })
8908
+ );
8909
+ trace.mark("databaseReadMs", performance.now() - tGraph);
8910
+ }
8911
+ if (!explain) {
8476
8912
  trace.success(telemetryFields);
8477
8913
  return serialized;
8478
8914
  }
@@ -8501,7 +8937,7 @@ var Wolbarg = class {
8501
8937
  semantic: "enabled",
8502
8938
  keyword: keywordSignal,
8503
8939
  reranker: options.rerank === true && this.reranker ? "enabled" : "disabled",
8504
- mmr: mmrLambda === null ? "disabled" : "enabled",
8940
+ mmr: mmrLambda === null ? "disabled" : mmrApplied ? "enabled" : "disabled",
8505
8941
  recency: "disabled"
8506
8942
  },
8507
8943
  results: explanations.map((hit) => ({
@@ -8610,31 +9046,34 @@ var Wolbarg = class {
8610
9046
  const timestamp = nowIso();
8611
9047
  const result = await this.withWriteLock(async () => {
8612
9048
  const tWrite = performance.now();
8613
- const summaryRow = await storage.insertMemory({
8614
- id: summaryId,
8615
- organization,
8616
- agent: options.agent.trim(),
8617
- contentText: summaryText,
8618
- metadata: {
8619
- compressed: true,
8620
- sourceCount: records.length,
8621
- sourceIds: records.map((r) => r.id)
8622
- },
8623
- embedding: vector,
8624
- createdAt: timestamp,
8625
- updatedAt: timestamp
9049
+ const txResult = await storage.withTransaction(async () => {
9050
+ const summaryRow = await storage.insertMemory({
9051
+ id: summaryId,
9052
+ organization,
9053
+ agent: options.agent.trim(),
9054
+ contentText: summaryText,
9055
+ metadata: {
9056
+ compressed: true,
9057
+ sourceCount: records.length,
9058
+ sourceIds: records.map((r) => r.id)
9059
+ },
9060
+ embedding: vector,
9061
+ createdAt: timestamp,
9062
+ updatedAt: timestamp
9063
+ });
9064
+ const archivedIds = await storage.archiveMemories(
9065
+ records.map((r) => r.id),
9066
+ organization,
9067
+ summaryId,
9068
+ timestamp
9069
+ );
9070
+ return {
9071
+ summary: toMemoryRecord(summaryRow),
9072
+ archivedIds
9073
+ };
8626
9074
  });
8627
- const archivedIds = await storage.archiveMemories(
8628
- records.map((r) => r.id),
8629
- organization,
8630
- summaryId,
8631
- timestamp
8632
- );
8633
9075
  trace.mark("databaseWriteMs", performance.now() - tWrite);
8634
- return {
8635
- summary: toMemoryRecord(summaryRow),
8636
- archivedIds
8637
- };
9076
+ return txResult;
8638
9077
  });
8639
9078
  trace.success({
8640
9079
  provider: storage.name,
@@ -8866,8 +9305,23 @@ var Wolbarg = class {
8866
9305
  await this.requireReady();
8867
9306
  const graph = this.requireGraph("getRelated");
8868
9307
  assertNonEmptyString(memoryId, "memoryId");
9308
+ let validatedOptions = options;
9309
+ if (options?.depth !== void 0) {
9310
+ if (!Number.isFinite(options.depth)) {
9311
+ throw new ConfigurationError("graph.depth must be a finite number");
9312
+ }
9313
+ if (options.depth < 1) {
9314
+ throw new ConfigurationError("graph.depth must be >= 1");
9315
+ }
9316
+ if (options.depth > 16) {
9317
+ validatedOptions = { ...options, depth: 16 };
9318
+ }
9319
+ }
8869
9320
  const tGraph = performance.now();
8870
- const related = await this.hydrateRelated(memoryId.trim(), options);
9321
+ const related = await this.hydrateRelated(
9322
+ memoryId.trim(),
9323
+ validatedOptions
9324
+ );
8871
9325
  trace.mark("databaseReadMs", performance.now() - tGraph);
8872
9326
  trace.success({
8873
9327
  provider: graph.name,
@@ -9036,7 +9490,15 @@ var Wolbarg = class {
9036
9490
  this.assertGraphCheckpointSupported("checkpoint");
9037
9491
  const tCheckpoint = performance.now();
9038
9492
  const meta2 = await provider.checkpoint(name, source, options);
9039
- await this.snapshotGraphAlongside(meta2.snapshotPath, "checkpoint");
9493
+ try {
9494
+ await this.snapshotGraphAlongside(meta2.snapshotPath, "checkpoint");
9495
+ } catch (error) {
9496
+ const sidecar = this.graphSnapshotDir(meta2.snapshotPath);
9497
+ if (fs6.existsSync(sidecar)) {
9498
+ fs6.rmSync(sidecar, { recursive: true, force: true });
9499
+ }
9500
+ throw error;
9501
+ }
9040
9502
  trace.mark("databaseWriteMs", performance.now() - tCheckpoint);
9041
9503
  trace.success({
9042
9504
  provider: provider.name,
@@ -9100,8 +9562,15 @@ var Wolbarg = class {
9100
9562
  try {
9101
9563
  await this.requireReady();
9102
9564
  const provider = this.requireCheckpointProvider();
9565
+ const existing = await provider.getCheckpoint(name);
9103
9566
  const tDelete = performance.now();
9104
9567
  const removed = await provider.deleteCheckpoint(name);
9568
+ if (removed && existing) {
9569
+ const sidecar = this.graphSnapshotDir(existing.snapshotPath);
9570
+ if (fs6.existsSync(sidecar)) {
9571
+ fs6.rmSync(sidecar, { recursive: true, force: true });
9572
+ }
9573
+ }
9105
9574
  trace.mark("databaseWriteMs", performance.now() - tDelete);
9106
9575
  trace.success({
9107
9576
  provider: provider.name,
@@ -9163,7 +9632,9 @@ var Wolbarg = class {
9163
9632
  const result = await this.transfer.exportTo(
9164
9633
  exportPath,
9165
9634
  source,
9166
- this.organization ?? void 0
9635
+ this.organization ?? void 0,
9636
+ this.embedding?.model,
9637
+ this.embeddingDimensions ?? void 0
9167
9638
  );
9168
9639
  await this.snapshotGraphAlongside(result.path, "export");
9169
9640
  trace.success({
@@ -9186,16 +9657,21 @@ var Wolbarg = class {
9186
9657
  let storageClosed = false;
9187
9658
  try {
9188
9659
  await this.requireReady();
9660
+ this.assertGraphCheckpointSupported("import");
9189
9661
  const target = this.requireMemoryDbPath();
9190
9662
  if (this.storage) {
9191
9663
  await this.storage.close();
9192
9664
  storageClosed = true;
9193
9665
  }
9194
9666
  const graphClosed = await this.closeGraphForSnapshot();
9195
- this.assertGraphCheckpointSupported("import");
9196
9667
  const result = await this.transfer.importFrom(
9197
9668
  exportPath,
9198
- target
9669
+ target,
9670
+ {
9671
+ organization: this.organization ?? void 0,
9672
+ embeddingModel: this.embedding?.model,
9673
+ embeddingDimensions: this.embeddingDimensions ?? void 0
9674
+ }
9199
9675
  );
9200
9676
  await this.restoreGraphAlongside(result.path, "import");
9201
9677
  if (graphClosed) {
@@ -9390,18 +9866,26 @@ var Wolbarg = class {
9390
9866
  const graph = this.requireGraph("getRelated");
9391
9867
  const related = await graph.getRelated(memoryId, options);
9392
9868
  const { storage, organization } = await this.requireReady();
9869
+ const rows = await Promise.all(
9870
+ related.map((stub) => storage.getMemoryById(stub.id, organization))
9871
+ );
9393
9872
  const out = [];
9394
- for (const stub of related) {
9395
- const row = await storage.getMemoryById(stub.id, organization);
9396
- if (row) {
9397
- out.push(toMemoryRecord(row));
9398
- } else {
9399
- out.push(stub);
9400
- }
9873
+ for (let i = 0; i < related.length; i += 1) {
9874
+ const stub = related[i];
9875
+ const row = rows[i];
9876
+ out.push(row ? toMemoryRecord(row) : stub);
9401
9877
  }
9402
9878
  return out;
9403
9879
  }
9404
9880
  requireMemoryDbPath() {
9881
+ if (this.storage && !(this.storage instanceof SqliteStorageProvider)) {
9882
+ throw new ConfigurationError(
9883
+ "This operation requires a file-backed SQLite database (checkpoints / export-import are not supported for PostgreSQL).",
9884
+ {
9885
+ suggestion: 'Use database: { provider: "sqlite", url: "./memory.db" }'
9886
+ }
9887
+ );
9888
+ }
9405
9889
  if (!this.memoryDbPath || this.memoryDbPath === ":memory:") {
9406
9890
  throw new ConfigurationError(
9407
9891
  "This operation requires a file-backed SQLite memory database (not :memory:).",
@@ -9630,7 +10114,14 @@ var HttpRerankerProvider = class {
9630
10114
  }));
9631
10115
  }
9632
10116
  const parsed = this.options.parseResults(body, documents);
9633
- return parsed.slice(0, topK);
10117
+ const hits = parsed.slice(0, topK);
10118
+ if (hits.length === 0) {
10119
+ return documents.slice(0, topK).map((d, i) => ({
10120
+ id: d.id,
10121
+ score: 1 - i / Math.max(documents.length, 1)
10122
+ }));
10123
+ }
10124
+ return hits;
9634
10125
  } catch {
9635
10126
  return documents.slice(0, topK).map((d, i) => ({
9636
10127
  id: d.id,
@@ -9815,21 +10306,31 @@ function tesseract() {
9815
10306
  return {
9816
10307
  name: "tesseract",
9817
10308
  async recognize(image) {
10309
+ let mod;
9818
10310
  try {
9819
- const mod = await import('tesseract.js');
9820
- const createWorker = mod.createWorker ?? mod.default?.createWorker;
9821
- if (!createWorker) {
9822
- return { text: "" };
9823
- }
9824
- const worker = await createWorker("eng");
9825
- try {
9826
- const result = await worker.recognize(image);
9827
- return { text: result.data.text.trim() };
9828
- } finally {
9829
- await worker.terminate();
9830
- }
9831
- } catch {
9832
- return { text: "" };
10311
+ mod = await import('tesseract.js');
10312
+ } catch (error) {
10313
+ throw new ConfigurationError(
10314
+ 'OCR provider "tesseract" requires the optional peer package "tesseract.js". Install it with: npm install tesseract.js',
10315
+ {
10316
+ cause: error instanceof Error ? error : void 0,
10317
+ suggestion: "npm install tesseract.js",
10318
+ operation: "recognize"
10319
+ }
10320
+ );
10321
+ }
10322
+ const createWorker = mod.createWorker ?? mod.default?.createWorker;
10323
+ if (!createWorker) {
10324
+ throw new ConfigurationError(
10325
+ 'OCR provider "tesseract" could not find tesseract.js.createWorker. Ensure your tesseract.js version is compatible.'
10326
+ );
10327
+ }
10328
+ const worker = await createWorker("eng");
10329
+ try {
10330
+ const result = await worker.recognize(image);
10331
+ return { text: result.data.text.trim() };
10332
+ } finally {
10333
+ await worker.terminate();
9833
10334
  }
9834
10335
  }
9835
10336
  };