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.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 = {
@@ -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
  }
@@ -2656,16 +2811,39 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2656
2811
  }
2657
2812
  async withTransaction(fn) {
2658
2813
  const db = this.requireDb();
2659
- return withImmediateTransaction(
2660
- db,
2661
- this.concurrency,
2662
- fn,
2663
- (attempt, delayMs) => {
2664
- this.retryLog?.(
2665
- `SQLITE_BUSY retry attempt=${attempt} backoffMs=${Math.round(delayMs)}`
2666
- );
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;
2667
2830
  }
2668
- );
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
+ }
2669
2847
  }
2670
2848
  // ─── internals ───────────────────────────────────────────────────────────
2671
2849
  tryLoadSqliteVec(db) {
@@ -3819,9 +3997,13 @@ function createPostgresListenerFromPool(pool, onError) {
3819
3997
 
3820
3998
  // src/storage/providers/postgres.ts
3821
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;
3822
4004
  var STMT = {
3823
4005
  insertOne: "Wolbarg_insert_one_v5",
3824
- insertBatch: "Wolbarg_insert_batch_v5"
4006
+ insertBatch: "Wolbarg_insert_batch_v6"
3825
4007
  };
3826
4008
  function toFloat4Param(embedding) {
3827
4009
  const out = new Array(embedding.length);
@@ -3892,13 +4074,13 @@ var INSERT_ONE_SQL = `WITH mem AS (
3892
4074
  var INSERT_BATCH_SQL = `WITH mem AS (
3893
4075
  INSERT INTO memories (
3894
4076
  id, organization, agent, content_text, metadata_json,
3895
- archived, compressed_into, created_at, updated_at
4077
+ archived, compressed_into, content_hash, created_at, updated_at
3896
4078
  )
3897
- SELECT id, org, agent, txt, meta::jsonb, false, NULL, c, u
4079
+ SELECT id, org, agent, txt, meta::jsonb, false, NULL, h, c, u
3898
4080
  FROM unnest(
3899
4081
  $1::text[], $2::text[], $3::text[], $4::text[],
3900
- $5::text[], $6::timestamptz[], $7::timestamptz[]
3901
- ) 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)
3902
4084
  RETURNING id
3903
4085
  ),
3904
4086
  hist AS (
@@ -3914,7 +4096,7 @@ var INSERT_BATCH_SQL = `WITH mem AS (
3914
4096
  emb AS (
3915
4097
  INSERT INTO memory_embeddings (memory_id, embedding, organization, agent, archived)
3916
4098
  SELECT id, emb::vector, org, agent, false
3917
- 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)
3918
4100
  )
3919
4101
  SELECT id FROM mem`;
3920
4102
  var COALESCE_FLUSH_MAX = 128;
@@ -4183,6 +4365,12 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4183
4365
  return;
4184
4366
  }
4185
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
+ }
4186
4374
  if (this.hnswCreateFailures >= 3) {
4187
4375
  throw new DatabaseError(
4188
4376
  `Failed to create HNSW index after ${this.hnswCreateFailures} attempts: ${this.describe(error)}`,
@@ -4327,6 +4515,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4327
4515
  const metas = new Array(inputs.length);
4328
4516
  const created = new Array(inputs.length);
4329
4517
  const updated = new Array(inputs.length);
4518
+ const contentHashes = new Array(inputs.length);
4330
4519
  const vectors = new Array(inputs.length);
4331
4520
  for (let i = 0; i < inputs.length; i += 1) {
4332
4521
  const input = inputs[i];
@@ -4337,6 +4526,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4337
4526
  metas[i] = serializeMetadata(input.metadata);
4338
4527
  created[i] = input.createdAt;
4339
4528
  updated[i] = input.updatedAt;
4529
+ contentHashes[i] = input.contentHash !== void 0 ? input.contentHash : hashMemoryContent(input.contentText);
4340
4530
  vectors[i] = toVectorLiteral(input.embedding);
4341
4531
  }
4342
4532
  await this.queryNamed(STMT.insertBatch, INSERT_BATCH_SQL, [
@@ -4347,9 +4537,10 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4347
4537
  metas,
4348
4538
  created,
4349
4539
  updated,
4540
+ contentHashes,
4350
4541
  vectors
4351
4542
  ]);
4352
- return inputs.map((input, i) => ({
4543
+ return inputs.map((_, i) => ({
4353
4544
  id: ids[i],
4354
4545
  organization: orgs[i],
4355
4546
  agent: agents[i],
@@ -4357,7 +4548,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4357
4548
  metadata_json: metas[i],
4358
4549
  archived: 0,
4359
4550
  compressed_into: null,
4360
- content_hash: input.contentHash !== void 0 ? input.contentHash : null,
4551
+ content_hash: contentHashes[i] ?? null,
4361
4552
  created_at: created[i],
4362
4553
  updated_at: updated[i]
4363
4554
  }));
@@ -4398,80 +4589,84 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4398
4589
  return out;
4399
4590
  }
4400
4591
  async insertOneBlob(input) {
4401
- const buf = Buffer.from(
4402
- input.embedding.buffer,
4403
- input.embedding.byteOffset,
4404
- input.embedding.byteLength
4405
- );
4406
- const contentHash = input.contentHash !== void 0 ? input.contentHash : hashMemoryContent(input.contentText);
4407
- const inserted = await this.query(
4408
- `INSERT INTO memories (
4409
- id, organization, agent, content_text, metadata_json,
4410
- archived, compressed_into, content_hash, created_at, updated_at
4411
- ) VALUES ($1,$2,$3,$4,$5::jsonb,false,NULL,$8,$6,$7)
4412
- RETURNING id, organization, agent, content_text, metadata_json,
4413
- archived::int AS archived, compressed_into, content_hash, created_at, updated_at`,
4414
- [
4415
- input.id,
4416
- input.organization,
4417
- input.agent,
4418
- input.contentText,
4419
- serializeMetadata(input.metadata),
4420
- input.createdAt,
4421
- input.updatedAt,
4422
- contentHash
4423
- ]
4424
- );
4425
- const row = this.mapRow(inserted.rows[0]);
4426
- await this.query(
4427
- `WITH mapped AS (
4428
- INSERT INTO memory_row_map (memory_id) VALUES ($1)
4429
- ON CONFLICT (memory_id) DO NOTHING
4430
- )
4431
- INSERT INTO memory_embeddings_blob (memory_id, embedding)
4432
- VALUES ($1, $2)
4433
- ON CONFLICT (memory_id) DO UPDATE SET embedding = EXCLUDED.embedding`,
4434
- [input.id, buf]
4435
- );
4436
- await this.query(
4437
- `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4438
- VALUES ($1,$2,'created',NULL,$3)`,
4439
- [crypto.randomUUID(), input.id, input.createdAt]
4440
- );
4441
- 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
+ });
4442
4635
  }
4443
4636
  async updateMemory(input) {
4444
- const existing = await this.getMemoryById(input.id, input.organization);
4445
- if (!existing) {
4446
- return null;
4447
- }
4448
- const contentHash = input.contentHash !== void 0 ? input.contentHash : input.contentText !== void 0 ? hashMemoryContent(input.contentText) : existing.content_hash ?? null;
4449
- await this.query(
4450
- `UPDATE memories SET
4451
- content_text = COALESCE($1, content_text),
4452
- metadata_json = COALESCE($2::jsonb, metadata_json),
4453
- content_hash = COALESCE($3, content_hash),
4454
- updated_at = $4
4455
- WHERE id = $5 AND organization = $6`,
4456
- [
4457
- input.contentText ?? null,
4458
- input.metadata !== void 0 ? serializeMetadata(input.metadata) : null,
4459
- contentHash,
4460
- input.updatedAt,
4461
- input.id,
4462
- input.organization
4463
- ]
4464
- );
4465
- if (input.embedding) {
4466
- await this.deleteEmbedding(input.id);
4467
- await this.insertEmbedding(input.id, input.embedding);
4468
- }
4469
- await this.query(
4470
- `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4471
- VALUES ($1,$2,'updated',NULL,$3)`,
4472
- [crypto.randomUUID(), input.id, input.updatedAt]
4473
- );
4474
- 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
+ });
4475
4670
  }
4476
4671
  async findActiveByContentHash(organization, agent, contentHash) {
4477
4672
  const result = await this.query(
@@ -4620,7 +4815,16 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4620
4815
  memoryId: String(row.memory_id),
4621
4816
  score: Number(row.rank)
4622
4817
  }));
4623
- } 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
+ }
4624
4828
  return [];
4625
4829
  }
4626
4830
  }
@@ -4738,44 +4942,46 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4738
4942
  return mapHits(result.rows);
4739
4943
  }
4740
4944
  async archiveMemories(ids, organization, compressedIntoId, archivedAt) {
4741
- if (ids.length === 0) {
4742
- return [];
4743
- }
4744
- const result = await this.query(
4745
- `UPDATE memories
4746
- SET archived = true, compressed_into = $1, updated_at = $2
4747
- WHERE organization = $3 AND archived = false AND id = ANY($4::text[])
4748
- RETURNING id`,
4749
- [compressedIntoId, archivedAt, organization, ids]
4750
- );
4751
- const archived = result.rows.map((r) => String(r.id));
4752
- if (archived.length === 0) {
4753
- return [];
4754
- }
4755
- await this.query(
4756
- `UPDATE memory_embeddings
4757
- SET archived = true
4758
- WHERE memory_id = ANY($1::text[])`,
4759
- [archived]
4760
- ).catch(() => void 0);
4761
- const histIds = [];
4762
- const memIds = [];
4763
- const types = [];
4764
- const related = [];
4765
- const times = [];
4766
- for (const id of archived) {
4767
- histIds.push(crypto.randomUUID(), crypto.randomUUID());
4768
- memIds.push(id, compressedIntoId);
4769
- types.push("archived", "compressed");
4770
- related.push(compressedIntoId, id);
4771
- times.push(archivedAt, archivedAt);
4772
- }
4773
- await this.query(
4774
- `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4775
- SELECT * FROM unnest($1::text[], $2::text[], $3::text[], $4::text[], $5::timestamptz[])`,
4776
- [histIds, memIds, types, related, times]
4777
- );
4778
- 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
+ });
4779
4985
  }
4780
4986
  async deleteMemoryById(id, organization) {
4781
4987
  const result = await this.query(
@@ -4886,6 +5092,11 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4886
5092
  [META_KEYS.schemaVersion]
4887
5093
  ).catch(() => ({ rows: [] }));
4888
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
+ }
4889
5100
  if (current === SCHEMA_VERSION) {
4890
5101
  const tsvProbe2 = await this.query(
4891
5102
  `SELECT 1 FROM information_schema.columns
@@ -5036,8 +5247,17 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
5036
5247
  await this.query(`CREATE EXTENSION IF NOT EXISTS vector`);
5037
5248
  _PostgresStorageProvider.pgvectorByConn.set(this.connectionString, true);
5038
5249
  return true;
5039
- } catch {
5250
+ } catch (error) {
5040
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
+ }
5041
5261
  return false;
5042
5262
  }
5043
5263
  }
@@ -5178,11 +5398,12 @@ function createDatabaseProvider(config) {
5178
5398
  }
5179
5399
 
5180
5400
  // src/version.ts
5181
- var SDK_VERSION = "0.5.3";
5401
+ var SDK_VERSION = "0.5.4";
5182
5402
 
5183
5403
  // src/memory/transfer.ts
5404
+ var warnedMissingExportManifest = false;
5184
5405
  var SqliteMemoryTransferProvider = class {
5185
- async exportTo(exportPath, sourcePath, organization) {
5406
+ async exportTo(exportPath, sourcePath, organization, embeddingModel, embeddingDimensions) {
5186
5407
  const resolvedSource = resolvePath(sourcePath);
5187
5408
  if (resolvedSource === ":memory:") {
5188
5409
  throw new ConfigurationError(
@@ -5209,7 +5430,9 @@ Initialize Wolbarg and verify the database path.`
5209
5430
  sdkVersion: SDK_VERSION,
5210
5431
  provider: "sqlite",
5211
5432
  sourcePath: resolvedSource,
5212
- organization
5433
+ organization,
5434
+ ...embeddingModel ? { embeddingModel } : {},
5435
+ ...embeddingDimensions ? { embeddingDimensions } : {}
5213
5436
  };
5214
5437
  fs6__default.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
5215
5438
  return {
@@ -5218,7 +5441,7 @@ Initialize Wolbarg and verify the database path.`
5218
5441
  sizeBytes: fs6__default.default.statSync(dbExportPath).size
5219
5442
  };
5220
5443
  }
5221
- async importFrom(exportPath, targetPath) {
5444
+ async importFrom(exportPath, targetPath, expected) {
5222
5445
  const resolvedExport = resolvePath(exportPath);
5223
5446
  const dbExportPath = resolvedExport.endsWith(".db") ? resolvedExport : fs6__default.default.existsSync(`${resolvedExport}.db`) ? `${resolvedExport}.db` : resolvedExport;
5224
5447
  if (!fs6__default.default.existsSync(dbExportPath)) {
@@ -5236,30 +5459,61 @@ Pass the path returned by export().`
5236
5459
  manifest = JSON.parse(fs6__default.default.readFileSync(manifestPath, "utf8"));
5237
5460
  if (manifest.format !== "wolbarg-export-v1") {
5238
5461
  throw new ValidationError(
5239
- `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}).`
5240
5487
  );
5241
5488
  }
5242
- } else {
5243
- manifest = {
5244
- format: "wolbarg-export-v1",
5245
- exportedAt: nowIso(),
5246
- sdkVersion: SDK_VERSION,
5247
- provider: "sqlite",
5248
- sourcePath: dbExportPath
5249
- };
5250
5489
  }
5251
5490
  const resolvedTarget = resolvePath(targetPath);
5252
5491
  if (resolvedTarget === ":memory:") {
5253
5492
  throw new ConfigurationError("Cannot import into an in-memory database.");
5254
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);
5255
5502
  for (const suffix of ["", "-wal", "-shm"]) {
5256
5503
  const side = `${resolvedTarget}${suffix}`;
5257
5504
  if (fs6__default.default.existsSync(side)) {
5258
5505
  fs6__default.default.rmSync(side, { force: true });
5259
5506
  }
5260
5507
  }
5261
- await copySqlite(dbExportPath, resolvedTarget);
5262
- 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 };
5263
5517
  }
5264
5518
  };
5265
5519
  async function copySqlite(sourcePath, destPath) {
@@ -5488,7 +5742,8 @@ function applyMmr(candidates, topK, lambda) {
5488
5742
  );
5489
5743
  }
5490
5744
  const score = lambda * relevance - (1 - lambda) * maxSim;
5491
- if (score > bestScore) {
5745
+ const best = remaining[bestIdx];
5746
+ if (score > bestScore || score === bestScore && candidate.id < best.id) {
5492
5747
  bestScore = score;
5493
5748
  bestIdx = i;
5494
5749
  }
@@ -6048,24 +6303,26 @@ var Neo4jGraphProvider = class {
6048
6303
  }
6049
6304
  }
6050
6305
  async run(cypher, params = {}) {
6051
- return this.withSession(async (session) => {
6052
- const result = await session.run(cypher, params);
6053
- return result.records.map((rec) => {
6054
- try {
6055
- return rec.toObject();
6056
- } catch {
6057
- const obj = {};
6058
- for (const key of rec.keys) {
6059
- obj[key] = unwrapNeo4jValue(rec.get(key));
6060
- }
6061
- 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));
6062
6319
  }
6063
- });
6320
+ return obj;
6321
+ }
6064
6322
  });
6065
6323
  }
6066
- async ensureMemoryNode(id) {
6067
- await this.run(
6068
- `MERGE (m:Memory { id: $id })
6324
+ async ensureMemoryNode(id, session) {
6325
+ const cypher = `MERGE (m:Memory { id: $id })
6069
6326
  ON CREATE SET
6070
6327
  m.organization = '',
6071
6328
  m.agent = '',
@@ -6074,24 +6331,30 @@ var Neo4jGraphProvider = class {
6074
6331
  m.archived = false,
6075
6332
  m.compressed_into = '',
6076
6333
  m.created_at = '',
6077
- m.updated_at = ''`,
6078
- { id }
6079
- );
6334
+ m.updated_at = ''`;
6335
+ if (session) {
6336
+ await this.runOnSession(session, cypher, { id });
6337
+ return;
6338
+ }
6339
+ await this.run(cypher, { id });
6080
6340
  }
6081
6341
  async linkMemories(fromId, toId, relation, metadata) {
6082
- await this.ensureMemoryNode(fromId);
6083
- await this.ensureMemoryNode(toId);
6084
- await this.run(
6085
- `MATCH (a:Memory { id: $fromId }), (b:Memory { id: $toId })
6086
- MERGE (a)-[r:RELATED { relation: $relation }]->(b)
6087
- SET r.metadata_json = $metadata`,
6088
- {
6089
- fromId,
6090
- toId,
6091
- relation,
6092
- metadata: serializeMetadata2(metadata)
6093
- }
6094
- );
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
+ });
6095
6358
  }
6096
6359
  async unlinkMemories(fromId, toId, relation) {
6097
6360
  if (relation !== void 0) {
@@ -6153,22 +6416,26 @@ var Neo4jGraphProvider = class {
6153
6416
  return id;
6154
6417
  }
6155
6418
  async linkEntityToMemory(entityId, memoryId, role) {
6156
- await this.ensureMemoryNode(memoryId);
6157
- const found = await this.run(
6158
- `MATCH (e:Entity { id: $id }) RETURN e.id AS id`,
6159
- { id: entityId }
6160
- );
6161
- if (found.length === 0) {
6162
- throw new DatabaseError(`Entity not found: ${entityId}`, {
6163
- operation: "linkEntityToMemory"
6164
- });
6165
- }
6166
- await this.run(
6167
- `MATCH (e:Entity { id: $entityId }), (m:Memory { id: $memoryId })
6168
- MERGE (e)-[r:MENTIONS]->(m)
6169
- SET r.role = $role`,
6170
- { entityId, memoryId, role: role ?? "" }
6171
- );
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
+ });
6172
6439
  }
6173
6440
  async deleteMemory(memoryId) {
6174
6441
  await this.run(
@@ -6241,6 +6508,21 @@ function assertNonEmpty(value, fieldName) {
6241
6508
  throw new ConfigurationError(`${fieldName} must be a non-empty string`);
6242
6509
  }
6243
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
+ }
6244
6526
  function assertUrl(value, fieldName) {
6245
6527
  assertNonEmpty(value, fieldName);
6246
6528
  try {
@@ -6287,6 +6569,135 @@ function validateLlmConfig(config) {
6287
6569
  ...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {}
6288
6570
  };
6289
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
+ }
6290
6701
  function normalizeDatabaseConfig(config) {
6291
6702
  const provider = config.provider;
6292
6703
  if (provider !== "sqlite" && provider !== "postgres") {
@@ -6396,11 +6807,22 @@ function validateWolbargOptions(options) {
6396
6807
  throw new ConfigurationError("Wolbarg options must be an object");
6397
6808
  }
6398
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;
6399
6814
  const storageInput = resolveStorageInput(options);
6400
6815
  let storage = storageInput;
6401
6816
  if (!isStorageProvider(storageInput)) {
6402
6817
  storage = normalizeDatabaseConfig(storageInput);
6403
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
+ }
6404
6826
  if (!options.embedding) {
6405
6827
  throw new ConfigurationError("embedding is required");
6406
6828
  }
@@ -6418,13 +6840,20 @@ function validateWolbargOptions(options) {
6418
6840
  if (graph !== void 0) {
6419
6841
  graph = resolveGraphInput(graph);
6420
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;
6421
6846
  return {
6422
6847
  ...options,
6423
6848
  organization: options.organization.trim(),
6424
6849
  storage,
6425
6850
  database: void 0,
6426
6851
  ...telemetry ? { telemetry } : {},
6427
- ...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 } : {}
6428
6857
  };
6429
6858
  }
6430
6859
  function resolveGraphInput(input) {
@@ -6483,59 +6912,6 @@ function resolveGraphInput(input) {
6483
6912
  );
6484
6913
  }
6485
6914
 
6486
- // src/telemetry/logger.ts
6487
- var LEVEL_ORDER = {
6488
- off: 100,
6489
- error: 50,
6490
- warn: 40,
6491
- info: 30,
6492
- debug: 20,
6493
- trace: 10
6494
- };
6495
- var WolbargLogger = class {
6496
- constructor(level = "info") {
6497
- this.level = level;
6498
- }
6499
- level;
6500
- setLevel(level) {
6501
- this.level = level;
6502
- }
6503
- getLevel() {
6504
- return this.level;
6505
- }
6506
- error(message, extra) {
6507
- this.write("error", message, extra);
6508
- }
6509
- warn(message, extra) {
6510
- this.write("warn", message, extra);
6511
- }
6512
- info(message, extra) {
6513
- this.write("info", message, extra);
6514
- }
6515
- debug(message, extra) {
6516
- this.write("debug", message, extra);
6517
- }
6518
- trace(message, extra) {
6519
- this.write("trace", message, extra);
6520
- }
6521
- write(level, message, extra) {
6522
- if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level]) {
6523
- return;
6524
- }
6525
- if (this.level === "off") {
6526
- return;
6527
- }
6528
- const line = `[wolbarg:${level}] ${message}`;
6529
- if (level === "error") {
6530
- console.error(line, extra ?? "");
6531
- } else if (level === "warn") {
6532
- console.warn(line, extra ?? "");
6533
- } else {
6534
- console.log(line, extra ?? "");
6535
- }
6536
- }
6537
- };
6538
-
6539
6915
  // src/telemetry/trace.ts
6540
6916
  function createSessionId() {
6541
6917
  return createId();
@@ -6620,14 +6996,14 @@ var TelemetryEmitter = class {
6620
6996
  const totalMs = performance.now() - startedAt;
6621
6997
  const errMessage = error instanceof Error ? error.message : error ? String(error) : fields?.error ?? null;
6622
6998
  const errStack = error instanceof Error ? error.stack ?? null : fields?.errorStack ?? null;
6999
+ if (!self.config.enabled) {
7000
+ return;
7001
+ }
6623
7002
  if (status === "error") {
6624
7003
  self.logger.error(`${operation} failed: ${errMessage ?? "unknown"}`);
6625
7004
  } else {
6626
7005
  self.logger.debug(`${operation} completed in ${totalMs.toFixed(2)}ms`);
6627
7006
  }
6628
- if (!self.config.enabled) {
6629
- return;
6630
- }
6631
7007
  const event = {
6632
7008
  id: createId(),
6633
7009
  timestamp: nowIso(),
@@ -6929,8 +7305,10 @@ var SqliteEventDatabase = class {
6929
7305
  }
6930
7306
  }
6931
7307
  if (options.memoryId) {
6932
- clauses.push(`memory_ids_json LIKE ?`);
6933
- 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);
6934
7312
  }
6935
7313
  if (options.queryText) {
6936
7314
  clauses.push(`query LIKE ?`);
@@ -7107,6 +7485,7 @@ function describe2(error) {
7107
7485
  }
7108
7486
 
7109
7487
  // src/providers/sqlite/sqliteTelemetryProvider.ts
7488
+ var MAX_QUEUE_SIZE = 1e4;
7110
7489
  var SqliteTelemetryProvider = class {
7111
7490
  name = "sqlite";
7112
7491
  db;
@@ -7114,6 +7493,8 @@ var SqliteTelemetryProvider = class {
7114
7493
  flushing = null;
7115
7494
  closed = false;
7116
7495
  openPromise = null;
7496
+ warnedQueueDrop = false;
7497
+ warnedFlushFailure = false;
7117
7498
  constructor(options) {
7118
7499
  this.db = new SqliteEventDatabase({ url: options.url });
7119
7500
  }
@@ -7134,6 +7515,15 @@ var SqliteTelemetryProvider = class {
7134
7515
  if (this.closed) {
7135
7516
  return;
7136
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
+ }
7137
7527
  this.queue.push(event);
7138
7528
  void this.scheduleFlush();
7139
7529
  }
@@ -7176,7 +7566,13 @@ var SqliteTelemetryProvider = class {
7176
7566
  await this.db.insertEvent(event);
7177
7567
  }
7178
7568
  }
7179
- } 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
+ }
7180
7576
  }
7181
7577
  }
7182
7578
  };
@@ -7219,8 +7615,20 @@ Ensure Wolbarg has been initialized and the database path is correct.`
7219
7615
  );
7220
7616
  }
7221
7617
  const snapshotPath = this.snapshotPath(name);
7222
- await safeSqliteBackup(resolvedSource, snapshotPath);
7223
- 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);
7224
7632
  const meta2 = {
7225
7633
  name,
7226
7634
  description: options?.description ?? null,
@@ -7231,7 +7639,9 @@ Ensure Wolbarg has been initialized and the database path is correct.`
7231
7639
  sourcePath: resolvedSource,
7232
7640
  sizeBytes: stats.size
7233
7641
  };
7234
- 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);
7235
7645
  return meta2;
7236
7646
  }
7237
7647
  async rollback(name, targetPath) {
@@ -7495,6 +7905,9 @@ var SqliteSubscribeEmitter = class {
7495
7905
  };
7496
7906
 
7497
7907
  // src/core/wolbarg.ts
7908
+ var warnLogger3 = new WolbargLogger("warn");
7909
+ var warnedHybridNoKeyword = false;
7910
+ var warnedRerankNoProvider = false;
7498
7911
  var Wolbarg = class {
7499
7912
  initialized = false;
7500
7913
  booting = null;
@@ -7600,6 +8013,7 @@ var Wolbarg = class {
7600
8013
  this.storage = createStorageProvider(validated.database);
7601
8014
  this.memoryDbPath = resolveDatabaseUrl(validated.database);
7602
8015
  this.embedding = createEmbeddingProvider(validated.embedding);
8016
+ this.rawEmbedding = this.embedding;
7603
8017
  if (validated.llm) {
7604
8018
  this.llm = createLlmProvider(validated.llm);
7605
8019
  this.compression = createCompressionProvider(this.llm);
@@ -7921,7 +8335,7 @@ var Wolbarg = class {
7921
8335
  * Update an existing memory by id (re-embeds when content changes).
7922
8336
  */
7923
8337
  async update(options) {
7924
- const trace = this.telemetry.start("remember");
8338
+ const trace = this.telemetry.start("update");
7925
8339
  try {
7926
8340
  const { storage, embedding, organization } = await this.requireReady();
7927
8341
  assertNonEmptyString(options.id, "id");
@@ -8447,17 +8861,37 @@ var Wolbarg = class {
8447
8861
  }
8448
8862
  }
8449
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
+ }
8450
8870
  const tRank = performance.now();
8451
- let results = [...byId.values()].sort(
8452
- (a, b) => b.similarity - a.similarity
8453
- );
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
+ });
8454
8876
  const mmrLambda = resolveMmr(options.mmr) ?? resolveMmr(
8455
8877
  this.retrievalConfig.mmr ? { lambda: this.retrievalConfig.mmr.lambda } : void 0
8456
8878
  );
8457
8879
  let rankingReason = "cosine similarity";
8880
+ let mmrApplied = false;
8458
8881
  if (mmrLambda !== null) {
8459
- results = applyMmr(results, Math.max(topK * 2, topK), mmrLambda);
8460
- 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
+ );
8461
8895
  }
8462
8896
  if (options.rerank && this.reranker) {
8463
8897
  const reranked = await this.reranker.rerank(
@@ -8492,14 +8926,16 @@ var Wolbarg = class {
8492
8926
  agentId: options.filter?.agent ?? commonAgent(serialized.map((result) => result.agent)),
8493
8927
  tags: commonTags(serialized.map((result) => result.metadata))
8494
8928
  };
8495
- if (!explain) {
8496
- if (options.includeGraph === true && this.graph) {
8497
- const tGraph = performance.now();
8498
- 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) => {
8499
8933
  hit.related = await this.hydrateRelated(hit.id);
8500
- }
8501
- trace.mark("databaseReadMs", performance.now() - tGraph);
8502
- }
8934
+ })
8935
+ );
8936
+ trace.mark("databaseReadMs", performance.now() - tGraph);
8937
+ }
8938
+ if (!explain) {
8503
8939
  trace.success(telemetryFields);
8504
8940
  return serialized;
8505
8941
  }
@@ -8528,7 +8964,7 @@ var Wolbarg = class {
8528
8964
  semantic: "enabled",
8529
8965
  keyword: keywordSignal,
8530
8966
  reranker: options.rerank === true && this.reranker ? "enabled" : "disabled",
8531
- mmr: mmrLambda === null ? "disabled" : "enabled",
8967
+ mmr: mmrLambda === null ? "disabled" : mmrApplied ? "enabled" : "disabled",
8532
8968
  recency: "disabled"
8533
8969
  },
8534
8970
  results: explanations.map((hit) => ({
@@ -8637,31 +9073,34 @@ var Wolbarg = class {
8637
9073
  const timestamp = nowIso();
8638
9074
  const result = await this.withWriteLock(async () => {
8639
9075
  const tWrite = performance.now();
8640
- const summaryRow = await storage.insertMemory({
8641
- id: summaryId,
8642
- organization,
8643
- agent: options.agent.trim(),
8644
- contentText: summaryText,
8645
- metadata: {
8646
- compressed: true,
8647
- sourceCount: records.length,
8648
- sourceIds: records.map((r) => r.id)
8649
- },
8650
- embedding: vector,
8651
- createdAt: timestamp,
8652
- 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
+ };
8653
9101
  });
8654
- const archivedIds = await storage.archiveMemories(
8655
- records.map((r) => r.id),
8656
- organization,
8657
- summaryId,
8658
- timestamp
8659
- );
8660
9102
  trace.mark("databaseWriteMs", performance.now() - tWrite);
8661
- return {
8662
- summary: toMemoryRecord(summaryRow),
8663
- archivedIds
8664
- };
9103
+ return txResult;
8665
9104
  });
8666
9105
  trace.success({
8667
9106
  provider: storage.name,
@@ -8893,8 +9332,23 @@ var Wolbarg = class {
8893
9332
  await this.requireReady();
8894
9333
  const graph = this.requireGraph("getRelated");
8895
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
+ }
8896
9347
  const tGraph = performance.now();
8897
- const related = await this.hydrateRelated(memoryId.trim(), options);
9348
+ const related = await this.hydrateRelated(
9349
+ memoryId.trim(),
9350
+ validatedOptions
9351
+ );
8898
9352
  trace.mark("databaseReadMs", performance.now() - tGraph);
8899
9353
  trace.success({
8900
9354
  provider: graph.name,
@@ -9063,7 +9517,15 @@ var Wolbarg = class {
9063
9517
  this.assertGraphCheckpointSupported("checkpoint");
9064
9518
  const tCheckpoint = performance.now();
9065
9519
  const meta2 = await provider.checkpoint(name, source, options);
9066
- 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
+ }
9067
9529
  trace.mark("databaseWriteMs", performance.now() - tCheckpoint);
9068
9530
  trace.success({
9069
9531
  provider: provider.name,
@@ -9127,8 +9589,15 @@ var Wolbarg = class {
9127
9589
  try {
9128
9590
  await this.requireReady();
9129
9591
  const provider = this.requireCheckpointProvider();
9592
+ const existing = await provider.getCheckpoint(name);
9130
9593
  const tDelete = performance.now();
9131
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
+ }
9132
9601
  trace.mark("databaseWriteMs", performance.now() - tDelete);
9133
9602
  trace.success({
9134
9603
  provider: provider.name,
@@ -9190,7 +9659,9 @@ var Wolbarg = class {
9190
9659
  const result = await this.transfer.exportTo(
9191
9660
  exportPath,
9192
9661
  source,
9193
- this.organization ?? void 0
9662
+ this.organization ?? void 0,
9663
+ this.embedding?.model,
9664
+ this.embeddingDimensions ?? void 0
9194
9665
  );
9195
9666
  await this.snapshotGraphAlongside(result.path, "export");
9196
9667
  trace.success({
@@ -9213,16 +9684,21 @@ var Wolbarg = class {
9213
9684
  let storageClosed = false;
9214
9685
  try {
9215
9686
  await this.requireReady();
9687
+ this.assertGraphCheckpointSupported("import");
9216
9688
  const target = this.requireMemoryDbPath();
9217
9689
  if (this.storage) {
9218
9690
  await this.storage.close();
9219
9691
  storageClosed = true;
9220
9692
  }
9221
9693
  const graphClosed = await this.closeGraphForSnapshot();
9222
- this.assertGraphCheckpointSupported("import");
9223
9694
  const result = await this.transfer.importFrom(
9224
9695
  exportPath,
9225
- target
9696
+ target,
9697
+ {
9698
+ organization: this.organization ?? void 0,
9699
+ embeddingModel: this.embedding?.model,
9700
+ embeddingDimensions: this.embeddingDimensions ?? void 0
9701
+ }
9226
9702
  );
9227
9703
  await this.restoreGraphAlongside(result.path, "import");
9228
9704
  if (graphClosed) {
@@ -9417,18 +9893,26 @@ var Wolbarg = class {
9417
9893
  const graph = this.requireGraph("getRelated");
9418
9894
  const related = await graph.getRelated(memoryId, options);
9419
9895
  const { storage, organization } = await this.requireReady();
9896
+ const rows = await Promise.all(
9897
+ related.map((stub) => storage.getMemoryById(stub.id, organization))
9898
+ );
9420
9899
  const out = [];
9421
- for (const stub of related) {
9422
- const row = await storage.getMemoryById(stub.id, organization);
9423
- if (row) {
9424
- out.push(toMemoryRecord(row));
9425
- } else {
9426
- out.push(stub);
9427
- }
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);
9428
9904
  }
9429
9905
  return out;
9430
9906
  }
9431
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
+ }
9432
9916
  if (!this.memoryDbPath || this.memoryDbPath === ":memory:") {
9433
9917
  throw new ConfigurationError(
9434
9918
  "This operation requires a file-backed SQLite memory database (not :memory:).",
@@ -9657,7 +10141,14 @@ var HttpRerankerProvider = class {
9657
10141
  }));
9658
10142
  }
9659
10143
  const parsed = this.options.parseResults(body, documents);
9660
- 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;
9661
10152
  } catch {
9662
10153
  return documents.slice(0, topK).map((d, i) => ({
9663
10154
  id: d.id,
@@ -9842,21 +10333,31 @@ function tesseract() {
9842
10333
  return {
9843
10334
  name: "tesseract",
9844
10335
  async recognize(image) {
10336
+ let mod;
9845
10337
  try {
9846
- const mod = await import('tesseract.js');
9847
- const createWorker = mod.createWorker ?? mod.default?.createWorker;
9848
- if (!createWorker) {
9849
- return { text: "" };
9850
- }
9851
- const worker = await createWorker("eng");
9852
- try {
9853
- const result = await worker.recognize(image);
9854
- return { text: result.data.text.trim() };
9855
- } finally {
9856
- await worker.terminate();
9857
- }
9858
- } catch {
9859
- 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();
9860
10361
  }
9861
10362
  }
9862
10363
  };