wolbarg 0.5.3 → 0.5.5

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
@@ -42,6 +42,11 @@ var WolbargError = class extends Error {
42
42
  reason;
43
43
  suggestion;
44
44
  operation;
45
+ /**
46
+ * @param message - Human-readable error message.
47
+ * @param code - Stable error code string.
48
+ * @param options - Optional cause, reason, suggestion, and operation name.
49
+ */
45
50
  constructor(message, code, options) {
46
51
  super(message, options);
47
52
  this.name = "WolbargError";
@@ -53,48 +58,80 @@ var WolbargError = class extends Error {
53
58
  }
54
59
  };
55
60
  var InitializationError = class extends WolbargError {
61
+ /**
62
+ * @param message - Description of the initialization failure.
63
+ * @param options - Optional cause and structured hints.
64
+ */
56
65
  constructor(message, options) {
57
66
  super(message, "INITIALIZATION_ERROR", options);
58
67
  this.name = "InitializationError";
59
68
  }
60
69
  };
61
70
  var ConfigurationError = class extends WolbargError {
71
+ /**
72
+ * @param message - Description of the misconfiguration.
73
+ * @param options - Optional cause, reason, suggestion, and operation name.
74
+ */
62
75
  constructor(message, options) {
63
76
  super(message, "CONFIGURATION_ERROR", options);
64
77
  this.name = "ConfigurationError";
65
78
  }
66
79
  };
67
80
  var ValidationError = class extends WolbargError {
81
+ /**
82
+ * @param message - Which argument failed and why.
83
+ * @param options - Optional cause and structured hints.
84
+ */
68
85
  constructor(message, options) {
69
86
  super(message, "VALIDATION_ERROR", options);
70
87
  this.name = "ValidationError";
71
88
  }
72
89
  };
73
90
  var DatabaseError = class extends WolbargError {
91
+ /**
92
+ * @param message - Operation-scoped failure message.
93
+ * @param options - Optional underlying `cause` and hints.
94
+ */
74
95
  constructor(message, options) {
75
96
  super(message, "DATABASE_ERROR", options);
76
97
  this.name = "DatabaseError";
77
98
  }
78
99
  };
79
100
  var StorageLockedError = class extends WolbargError {
101
+ /**
102
+ * @param message - Lock contention description.
103
+ * @param options - Typically includes suggestion to tune concurrency or use Postgres.
104
+ */
80
105
  constructor(message, options) {
81
106
  super(message, "WOLBARG_STORAGE_LOCKED", options);
82
107
  this.name = "StorageLockedError";
83
108
  }
84
109
  };
85
110
  var EmbeddingError = class extends WolbargError {
111
+ /**
112
+ * @param message - Embedding failure description.
113
+ * @param options - Optional HTTP cause and provider hints.
114
+ */
86
115
  constructor(message, options) {
87
116
  super(message, "EMBEDDING_ERROR", options);
88
117
  this.name = "EmbeddingError";
89
118
  }
90
119
  };
91
120
  var CompressionError = class extends WolbargError {
121
+ /**
122
+ * @param message - Compression failure description.
123
+ * @param options - Optional LLM cause chain.
124
+ */
92
125
  constructor(message, options) {
93
126
  super(message, "COMPRESSION_ERROR", options);
94
127
  this.name = "CompressionError";
95
128
  }
96
129
  };
97
130
  var MemoryNotFoundError = class extends WolbargError {
131
+ /**
132
+ * @param message - Which memory was not found.
133
+ * @param options - Optional operation context.
134
+ */
98
135
  constructor(message, options) {
99
136
  super(message, "MEMORY_NOT_FOUND", options);
100
137
  this.name = "MemoryNotFoundError";
@@ -102,6 +139,11 @@ var MemoryNotFoundError = class extends WolbargError {
102
139
  };
103
140
  var ProviderNotConfiguredError = class extends ConfigurationError {
104
141
  provider;
142
+ /**
143
+ * @param provider - Provider name (e.g. `"reranker"`, `"graph"`).
144
+ * @param method - Facade method that requires the provider.
145
+ * @param hint - Install or config instruction shown to the developer.
146
+ */
105
147
  constructor(provider, method, hint) {
106
148
  super(`${method} requires ${provider} \u2014 ${hint}`, {
107
149
  operation: method,
@@ -113,6 +155,10 @@ var ProviderNotConfiguredError = class extends ConfigurationError {
113
155
  }
114
156
  };
115
157
  var GraphCheckpointNotSupportedError = class extends WolbargError {
158
+ /**
159
+ * @param backend - Graph backend name (e.g. `"neo4j"`).
160
+ * @param operation - Requested operation (e.g. `"checkpoint"`).
161
+ */
116
162
  constructor(backend, operation) {
117
163
  super(
118
164
  `graph checkpoint not supported for network-backed graph providers (${backend})`,
@@ -185,9 +231,11 @@ Rules:
185
231
  var LlmCompressionProvider = class {
186
232
  name = "llm";
187
233
  llm;
234
+ /** @param llm - Configured LLM used for summarization. */
188
235
  constructor(llm) {
189
236
  this.llm = llm;
190
237
  }
238
+ /** @inheritdoc */
191
239
  async compress(memories) {
192
240
  return compressMemories(this.llm, memories);
193
241
  }
@@ -296,6 +344,7 @@ function slidingWindow(text, chunkSize, overlap) {
296
344
  }
297
345
  var FixedChunkingStrategy = class {
298
346
  name = "fixed";
347
+ /** @inheritdoc */
299
348
  chunk(text, options) {
300
349
  const { chunkSize, overlap } = clampOptions(options);
301
350
  return slidingWindow(text, chunkSize, overlap);
@@ -303,6 +352,7 @@ var FixedChunkingStrategy = class {
303
352
  };
304
353
  var SentenceChunkingStrategy = class {
305
354
  name = "sentence";
355
+ /** @inheritdoc */
306
356
  chunk(text, options) {
307
357
  const { chunkSize, overlap } = clampOptions(options);
308
358
  const sentences = text.split(/(?<=[.!?])\s+/).map((s) => s.trim()).filter(Boolean);
@@ -311,6 +361,7 @@ var SentenceChunkingStrategy = class {
311
361
  };
312
362
  var ParagraphChunkingStrategy = class {
313
363
  name = "paragraph";
364
+ /** @inheritdoc */
314
365
  chunk(text, options) {
315
366
  const { chunkSize, overlap } = clampOptions(options);
316
367
  const paragraphs = text.split(/\n\s*\n/).map((p) => p.trim()).filter(Boolean);
@@ -319,6 +370,7 @@ var ParagraphChunkingStrategy = class {
319
370
  };
320
371
  var MarkdownChunkingStrategy = class {
321
372
  name = "markdown";
373
+ /** @inheritdoc */
322
374
  chunk(text, options) {
323
375
  const { chunkSize, overlap } = clampOptions(options);
324
376
  const sections = text.split(/(?=^#{1,6}\s)/m).map((s) => s.trim()).filter(Boolean);
@@ -330,6 +382,7 @@ var MarkdownChunkingStrategy = class {
330
382
  };
331
383
  var HeadingChunkingStrategy = class {
332
384
  name = "heading";
385
+ /** @inheritdoc */
333
386
  chunk(text, options) {
334
387
  return new MarkdownChunkingStrategy().chunk(text, options);
335
388
  }
@@ -411,6 +464,12 @@ function distanceToSimilarity(distance) {
411
464
  }
412
465
  var AsyncMutex = class {
413
466
  chain = Promise.resolve();
467
+ /**
468
+ * Run `fn` exclusively — concurrent callers queue on the same mutex.
469
+ *
470
+ * @param fn - Async or sync work to serialize.
471
+ * @returns The value returned by `fn`.
472
+ */
414
473
  async runExclusive(fn) {
415
474
  let release;
416
475
  const next = new Promise((resolve) => {
@@ -434,16 +493,32 @@ function joinUrl(baseUrl, path8) {
434
493
 
435
494
  // src/embedding/index.ts
436
495
  var OpenAICompatibleEmbeddingProvider = class {
496
+ /** Model name sent in the request body. */
437
497
  model;
438
498
  baseUrl;
439
499
  apiKey;
440
500
  timeoutMs;
501
+ /**
502
+ * @param config - OpenAI-compatible embedding endpoint settings.
503
+ * @param config.baseUrl - API root, e.g. `https://api.openai.com/v1`.
504
+ * @param config.apiKey - Bearer token. Use any non-empty string for local
505
+ * servers that ignore auth.
506
+ * @param config.model - Embedding model id (e.g. `"text-embedding-3-small"`).
507
+ * @param config.timeoutMs - Abort after this many ms. Defaults to `30_000`.
508
+ */
441
509
  constructor(config) {
442
510
  this.baseUrl = config.baseUrl;
443
511
  this.apiKey = config.apiKey;
444
512
  this.model = config.model;
445
513
  this.timeoutMs = config.timeoutMs ?? 3e4;
446
514
  }
515
+ /**
516
+ * Embed a single string.
517
+ *
518
+ * @param text - Input text.
519
+ * @returns Embedding vector.
520
+ * @throws {EmbeddingError} On HTTP errors, timeouts, or empty vectors.
521
+ */
447
522
  async embed(text) {
448
523
  const response = await this.request(text);
449
524
  const vector = response.data?.[0]?.embedding;
@@ -452,6 +527,13 @@ var OpenAICompatibleEmbeddingProvider = class {
452
527
  }
453
528
  return Float32Array.from(vector);
454
529
  }
530
+ /**
531
+ * Embed multiple strings in one API call when the server supports batch input.
532
+ * Falls back to sequential {@link embed} if the response length mismatches.
533
+ *
534
+ * @param texts - Inputs to embed.
535
+ * @returns One vector per input, same order.
536
+ */
455
537
  async embedBatch(texts) {
456
538
  if (texts.length === 0) {
457
539
  return [];
@@ -478,6 +560,12 @@ var OpenAICompatibleEmbeddingProvider = class {
478
560
  return Float32Array.from(item.embedding);
479
561
  });
480
562
  }
563
+ /**
564
+ * Probe the endpoint and report vector dimensionality.
565
+ *
566
+ * @returns `{ dimensions }` from a fixed health-check string.
567
+ * @throws {EmbeddingError} When the probe fails.
568
+ */
481
569
  async validate() {
482
570
  try {
483
571
  const embedding = await this.embed("Wolbarg health check");
@@ -492,6 +580,12 @@ var OpenAICompatibleEmbeddingProvider = class {
492
580
  );
493
581
  }
494
582
  }
583
+ /**
584
+ * Low-level HTTP request to `/embeddings`.
585
+ *
586
+ * @param input - Single string or batch of strings.
587
+ * @returns Parsed OpenAI-style JSON body.
588
+ */
495
589
  async request(input) {
496
590
  const url = joinUrl(this.baseUrl, "/embeddings");
497
591
  const controller = new AbortController();
@@ -539,6 +633,7 @@ var OpenAICompatibleEmbeddingProvider = class {
539
633
  clearTimeout(timer);
540
634
  }
541
635
  }
636
+ /** @param error - Unknown thrown value. @returns Human-readable message. */
542
637
  describe(error) {
543
638
  if (error instanceof Error) {
544
639
  return error.message;
@@ -689,6 +784,45 @@ function withEmbeddingCache(provider, store, config) {
689
784
  };
690
785
  }
691
786
 
787
+ // src/utils/vector.ts
788
+ function cosineDistance(a, b) {
789
+ if (a.length !== b.length) {
790
+ throw new Error(
791
+ `cosineDistance dimension mismatch: a.length=${a.length}, b.length=${b.length}`
792
+ );
793
+ }
794
+ const len = a.length;
795
+ let dot = 0;
796
+ let normA = 0;
797
+ let normB = 0;
798
+ for (let i = 0; i < len; i += 1) {
799
+ const av = a[i] ?? 0;
800
+ const bv = b[i] ?? 0;
801
+ dot += av * bv;
802
+ normA += av * av;
803
+ normB += bv * bv;
804
+ }
805
+ if (normA === 0 || normB === 0) {
806
+ return 1;
807
+ }
808
+ const similarity = dot / (Math.sqrt(normA) * Math.sqrt(normB));
809
+ return 1 - similarity;
810
+ }
811
+ function bufferToEmbedding(data) {
812
+ const byteOffset = data.byteOffset ?? 0;
813
+ const byteLength = data.byteLength;
814
+ if (byteOffset % Float32Array.BYTES_PER_ELEMENT === 0) {
815
+ return new Float32Array(
816
+ data.buffer,
817
+ byteOffset,
818
+ byteLength / Float32Array.BYTES_PER_ELEMENT
819
+ );
820
+ }
821
+ const copy = new Uint8Array(byteLength);
822
+ copy.set(data);
823
+ return new Float32Array(copy.buffer);
824
+ }
825
+
692
826
  // src/embedding/cache-store.ts
693
827
  var TOUCH_FLUSH_THRESHOLD = 64;
694
828
  var L1_DEFAULT_MAX = 5e4;
@@ -756,6 +890,10 @@ var SqliteEmbeddingCacheStore = class {
756
890
  pendingSets = /* @__PURE__ */ new Map();
757
891
  persistScheduled = false;
758
892
  stmts = null;
893
+ /**
894
+ * @param dbOrGetter - SQLite `DatabaseSync` or lazy getter (allows late bind).
895
+ * @param options.ttlMs - Optional TTL for cache entries on read.
896
+ */
759
897
  constructor(dbOrGetter, options) {
760
898
  this.ttlMs = options?.ttlMs ?? null;
761
899
  this.getDb = typeof dbOrGetter === "function" ? dbOrGetter : () => dbOrGetter;
@@ -773,7 +911,9 @@ var SqliteEmbeddingCacheStore = class {
773
911
  }
774
912
  this.stmts = {
775
913
  get: db.prepare(
776
- `SELECT vector, last_used_at, created_at FROM embedding_cache WHERE cache_key = ?`
914
+ `SELECT model, vector, last_used_at, created_at
915
+ FROM embedding_cache
916
+ WHERE cache_key = ?`
777
917
  ),
778
918
  delete: db.prepare(`DELETE FROM embedding_cache WHERE cache_key = ?`),
779
919
  set: db.prepare(
@@ -808,7 +948,29 @@ var SqliteEmbeddingCacheStore = class {
808
948
  this.l1.set(cacheKey, pending.model, pending.vector);
809
949
  return pending.vector;
810
950
  }
811
- return null;
951
+ const db = this.requireDb();
952
+ if (!db) return null;
953
+ try {
954
+ const stmts = this.ensureStatements(db);
955
+ const row = stmts.get.get(cacheKey);
956
+ if (!row) return null;
957
+ if (this.ttlMs !== null) {
958
+ const createdMs = row.created_at ? Date.parse(row.created_at) : 0;
959
+ if (createdMs && Date.now() - createdMs > this.ttlMs) {
960
+ stmts.delete.run(cacheKey);
961
+ return null;
962
+ }
963
+ }
964
+ const vector = bufferToEmbedding(row.vector);
965
+ this.l1.set(cacheKey, row.model, vector);
966
+ this.pendingTouches.add(cacheKey);
967
+ if (this.pendingTouches.size >= TOUCH_FLUSH_THRESHOLD) {
968
+ this.schedulePersist();
969
+ }
970
+ return vector;
971
+ } catch {
972
+ return null;
973
+ }
812
974
  }
813
975
  async set(cacheKey, model, vector) {
814
976
  this.l1.set(cacheKey, model, vector);
@@ -860,6 +1022,16 @@ var SqliteEmbeddingCacheStore = class {
860
1022
  this.pendingSets.clear();
861
1023
  const touches = [...this.pendingTouches];
862
1024
  this.pendingTouches.clear();
1025
+ const requeue = () => {
1026
+ for (const [key, value] of sets) {
1027
+ this.pendingSets.set(key, value);
1028
+ }
1029
+ for (const key of touches) {
1030
+ this.pendingTouches.add(key);
1031
+ }
1032
+ this.stmts = null;
1033
+ this.schedulePersist();
1034
+ };
863
1035
  const now = (/* @__PURE__ */ new Date()).toISOString();
864
1036
  try {
865
1037
  const stmts = this.ensureStatements(db);
@@ -894,10 +1066,10 @@ var SqliteEmbeddingCacheStore = class {
894
1066
  db.exec("ROLLBACK");
895
1067
  } catch {
896
1068
  }
897
- this.stmts = null;
1069
+ requeue();
898
1070
  }
899
1071
  } catch {
900
- this.stmts = null;
1072
+ requeue();
901
1073
  }
902
1074
  }
903
1075
  };
@@ -998,12 +1170,23 @@ var PostgresEmbeddingCacheStore = class {
998
1170
 
999
1171
  // src/llm/index.ts
1000
1172
  var OpenAICompatibleLlmProvider = class {
1173
+ /** Model name sent in the request body and exposed on the provider. */
1001
1174
  model;
1002
1175
  baseUrl;
1003
1176
  apiKey;
1004
1177
  temperature;
1005
1178
  maxTokens;
1006
1179
  timeoutMs;
1180
+ /**
1181
+ * @param config - OpenAI-compatible endpoint settings.
1182
+ * @param config.baseUrl - API root, e.g. `https://api.openai.com/v1` (no trailing `/chat/completions`).
1183
+ * @param config.apiKey - Bearer token (`Authorization: Bearer …`). Use any non-empty
1184
+ * string for local servers that ignore auth (e.g. `"ollama"`).
1185
+ * @param config.model - Chat model id (e.g. `"gpt-4o-mini"`, `"llama3.2"`).
1186
+ * @param config.temperature - Sampling temperature. Defaults to `0.2`.
1187
+ * @param config.maxTokens - Max completion tokens. Defaults to `4096`.
1188
+ * @param config.timeoutMs - Abort after this many ms. Defaults to `60_000`.
1189
+ */
1007
1190
  constructor(config) {
1008
1191
  this.baseUrl = config.baseUrl;
1009
1192
  this.apiKey = config.apiKey;
@@ -1012,6 +1195,13 @@ var OpenAICompatibleLlmProvider = class {
1012
1195
  this.maxTokens = config.maxTokens ?? 4096;
1013
1196
  this.timeoutMs = config.timeoutMs ?? 6e4;
1014
1197
  }
1198
+ /**
1199
+ * Call `/chat/completions` and return the first choice’s message content.
1200
+ *
1201
+ * @param messages - Chat turns in OpenAI message format.
1202
+ * @returns Trimmed assistant text.
1203
+ * @throws {CompressionError} On HTTP errors, timeouts, or empty content.
1204
+ */
1015
1205
  async complete(messages) {
1016
1206
  const response = await this.request(messages);
1017
1207
  const content = response.choices?.[0]?.message?.content;
@@ -1020,6 +1210,11 @@ var OpenAICompatibleLlmProvider = class {
1020
1210
  }
1021
1211
  return content.trim();
1022
1212
  }
1213
+ /**
1214
+ * Probe the endpoint with a fixed `"ok"` prompt.
1215
+ *
1216
+ * @throws {CompressionError} When validation fails.
1217
+ */
1023
1218
  async validate() {
1024
1219
  try {
1025
1220
  await this.complete([
@@ -1041,6 +1236,12 @@ var OpenAICompatibleLlmProvider = class {
1041
1236
  );
1042
1237
  }
1043
1238
  }
1239
+ /**
1240
+ * Low-level HTTP request to `/chat/completions`.
1241
+ *
1242
+ * @param messages - Chat turns to send.
1243
+ * @returns Parsed OpenAI-style JSON body.
1244
+ */
1044
1245
  async request(messages) {
1045
1246
  const url = joinUrl(this.baseUrl, "/chat/completions");
1046
1247
  const controller = new AbortController();
@@ -1089,6 +1290,7 @@ var OpenAICompatibleLlmProvider = class {
1089
1290
  clearTimeout(timer);
1090
1291
  }
1091
1292
  }
1293
+ /** @param error - Unknown thrown value. @returns Human-readable message. */
1092
1294
  describe(error) {
1093
1295
  if (error instanceof Error) {
1094
1296
  return error.message;
@@ -1206,12 +1408,27 @@ var DocxParser = class {
1206
1408
  name = "docx";
1207
1409
  extensions = [".docx"];
1208
1410
  async parse(input) {
1411
+ let mod;
1412
+ try {
1413
+ mod = await import('mammoth');
1414
+ } catch (error) {
1415
+ throw new ConfigurationError(
1416
+ 'DOCX ingest requires the optional peer package "mammoth". Install it with: npm install mammoth',
1417
+ {
1418
+ cause: error instanceof Error ? error : void 0,
1419
+ suggestion: "npm install mammoth",
1420
+ operation: "parse"
1421
+ }
1422
+ );
1423
+ }
1424
+ const extractRawText = mod.extractRawText ?? mod.default?.extractRawText;
1425
+ if (!extractRawText) {
1426
+ throw new ConfigurationError(
1427
+ "DOCX ingest could not find mammoth.extractRawText. Ensure your mammoth version is compatible.",
1428
+ { operation: "parse" }
1429
+ );
1430
+ }
1209
1431
  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
1432
  const result = await extractRawText({ buffer: input.buffer });
1216
1433
  return {
1217
1434
  text: result.value ?? "",
@@ -1219,9 +1436,14 @@ var DocxParser = class {
1219
1436
  filename: input.filename,
1220
1437
  isImage: false
1221
1438
  };
1222
- } catch {
1439
+ } catch (error) {
1223
1440
  throw new ConfigurationError(
1224
- 'DOCX ingest requires the optional "mammoth" package. Install it with: npm install mammoth'
1441
+ "DOCX ingest failed to parse the document (it may be corrupt or an unsupported .docx).",
1442
+ {
1443
+ cause: error instanceof Error ? error : void 0,
1444
+ suggestion: "Try a different DOCX file or ensure it is not password-protected.",
1445
+ operation: "parse"
1446
+ }
1225
1447
  );
1226
1448
  }
1227
1449
  }
@@ -1368,6 +1590,12 @@ function compileComparison(field, op) {
1368
1590
  const extract = `json_extract(metadata_json, '${path8}')`;
1369
1591
  if ("eq" in op) {
1370
1592
  const value = op.eq;
1593
+ if (value === null) {
1594
+ return {
1595
+ expression: `json_type(metadata_json, '${path8}') = 'null'`,
1596
+ params: []
1597
+ };
1598
+ }
1371
1599
  if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
1372
1600
  return null;
1373
1601
  }
@@ -1448,6 +1676,79 @@ function compileMetadataFilterToSql(filter) {
1448
1676
  return compileComparison(filter.field, filter.op);
1449
1677
  }
1450
1678
 
1679
+ // src/telemetry/logger.ts
1680
+ var LEVEL_ORDER = {
1681
+ off: 100,
1682
+ error: 50,
1683
+ warn: 40,
1684
+ info: 30,
1685
+ debug: 20,
1686
+ trace: 10
1687
+ };
1688
+ var WolbargLogger = class {
1689
+ /** @param level - Minimum level to print (default `"info"`). */
1690
+ constructor(level = "info") {
1691
+ this.level = level;
1692
+ }
1693
+ level;
1694
+ /** Change the minimum log level at runtime. */
1695
+ setLevel(level) {
1696
+ this.level = level;
1697
+ }
1698
+ /** @returns Current minimum log level. */
1699
+ getLevel() {
1700
+ return this.level;
1701
+ }
1702
+ /** Log at error level. */
1703
+ error(message, extra) {
1704
+ this.write("error", message, extra);
1705
+ }
1706
+ /** Log at warn level.
1707
+ * @param message - Primary log line.
1708
+ * @param extra - Optional structured payload.
1709
+ */
1710
+ warn(message, extra) {
1711
+ this.write("warn", message, extra);
1712
+ }
1713
+ /** Log at info level.
1714
+ * @param message - Primary log line.
1715
+ * @param extra - Optional structured payload.
1716
+ */
1717
+ info(message, extra) {
1718
+ this.write("info", message, extra);
1719
+ }
1720
+ /** Log at debug level.
1721
+ * @param message - Primary log line.
1722
+ * @param extra - Optional structured payload.
1723
+ */
1724
+ debug(message, extra) {
1725
+ this.write("debug", message, extra);
1726
+ }
1727
+ /** Log at trace level.
1728
+ * @param message - Primary log line.
1729
+ * @param extra - Optional structured payload.
1730
+ */
1731
+ trace(message, extra) {
1732
+ this.write("trace", message, extra);
1733
+ }
1734
+ write(level, message, extra) {
1735
+ if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level]) {
1736
+ return;
1737
+ }
1738
+ if (this.level === "off") {
1739
+ return;
1740
+ }
1741
+ const line = `[wolbarg:${level}] ${message}`;
1742
+ if (level === "error") {
1743
+ console.error(line, extra ?? "");
1744
+ } else if (level === "warn") {
1745
+ console.warn(line, extra ?? "");
1746
+ } else {
1747
+ console.log(line, extra ?? "");
1748
+ }
1749
+ }
1750
+ };
1751
+
1451
1752
  // src/schema/index.ts
1452
1753
  var SCHEMA_VERSION = 4;
1453
1754
  var META_KEYS = {
@@ -1762,40 +2063,6 @@ var SQL = {
1762
2063
  `
1763
2064
  };
1764
2065
 
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
2066
  // src/utils/vector-index.ts
1800
2067
  var InMemoryVectorIndex = class {
1801
2068
  dims;
@@ -1803,18 +2070,29 @@ var InMemoryVectorIndex = class {
1803
2070
  data;
1804
2071
  count = 0;
1805
2072
  rowidToSlot = /* @__PURE__ */ new Map();
2073
+ /**
2074
+ * @param dimensions - Embedding vector length.
2075
+ * @param initialCapacity - Initial slot capacity (default `256`).
2076
+ */
1806
2077
  constructor(dimensions, initialCapacity = 256) {
1807
2078
  this.dims = dimensions;
1808
2079
  this.data = new Float32Array(initialCapacity * dimensions);
1809
2080
  }
2081
+ /** Number of indexed vectors. */
1810
2082
  get size() {
1811
2083
  return this.count;
1812
2084
  }
2085
+ /** Remove all vectors from the index. */
1813
2086
  clear() {
1814
2087
  this.rowids.length = 0;
1815
2088
  this.rowidToSlot.clear();
1816
2089
  this.count = 0;
1817
2090
  }
2091
+ /**
2092
+ * Insert or replace a vector for `rowid` (L2-normalized internally).
2093
+ * @param rowid - Memory table row identifier.
2094
+ * @param embedding - Raw embedding vector.
2095
+ */
1818
2096
  upsert(rowid, embedding) {
1819
2097
  const existing = this.rowidToSlot.get(rowid);
1820
2098
  const slot = existing !== void 0 ? existing : (() => {
@@ -1845,6 +2123,10 @@ var InMemoryVectorIndex = class {
1845
2123
  }
1846
2124
  }
1847
2125
  }
2126
+ /**
2127
+ * Remove a vector by `rowid`.
2128
+ * @param rowid - Memory table row identifier.
2129
+ */
1848
2130
  remove(rowid) {
1849
2131
  const slot = this.rowidToSlot.get(rowid);
1850
2132
  if (slot === void 0) {
@@ -1864,7 +2146,12 @@ var InMemoryVectorIndex = class {
1864
2146
  this.rowidToSlot.delete(rowid);
1865
2147
  this.count = last;
1866
2148
  }
1867
- /** Top-k by cosine distance (1 - dot) assuming query is L2-normalized. */
2149
+ /**
2150
+ * Top-k by cosine distance (1 - dot) assuming query is L2-normalized.
2151
+ *
2152
+ * @param queryNormalized - L2-normalized query vector (use {@link normalizeEmbedding}).
2153
+ * @param topK - Maximum hits to return.
2154
+ */
1868
2155
  search(queryNormalized, topK) {
1869
2156
  const n = this.count;
1870
2157
  if (n === 0 || topK <= 0) {
@@ -1888,24 +2175,33 @@ var InMemoryVectorIndex = class {
1888
2175
  dot += queryNormalized[d] * data[base + d];
1889
2176
  }
1890
2177
  const distance = 1 - dot;
2178
+ const rowid = rowids[i];
1891
2179
  if (heapSize < k) {
1892
2180
  heapDist[heapSize] = distance;
1893
- heapRow[heapSize] = rowids[i];
2181
+ heapRow[heapSize] = rowid;
1894
2182
  heapSize += 1;
1895
2183
  if (heapSize === k) {
1896
2184
  buildMaxHeap(heapDist, heapRow, k);
1897
2185
  }
1898
- } else if (distance < heapDist[0]) {
1899
- heapDist[0] = distance;
1900
- heapRow[0] = rowids[i];
1901
- siftDown(heapDist, heapRow, 0, k);
2186
+ } else {
2187
+ const worstDist = heapDist[0];
2188
+ const worstRow = heapRow[0];
2189
+ if (distance < worstDist || distance === worstDist && rowid < worstRow) {
2190
+ heapDist[0] = distance;
2191
+ heapRow[0] = rowid;
2192
+ siftDown(heapDist, heapRow, 0, k);
2193
+ }
1902
2194
  }
1903
2195
  }
1904
2196
  const hits = new Array(heapSize);
1905
2197
  for (let i = 0; i < heapSize; i += 1) {
1906
2198
  hits[i] = { memoryRowid: heapRow[i], distance: heapDist[i] };
1907
2199
  }
1908
- hits.sort((a, b) => a.distance - b.distance);
2200
+ hits.sort((a, b) => {
2201
+ const diff = a.distance - b.distance;
2202
+ if (diff !== 0) return diff;
2203
+ return a.memoryRowid - b.memoryRowid;
2204
+ });
1909
2205
  return hits;
1910
2206
  }
1911
2207
  ensureCapacity(needed) {
@@ -1951,8 +2247,8 @@ function siftDown(dist, rows, i, n) {
1951
2247
  let largest = i;
1952
2248
  const left = (i << 1) + 1;
1953
2249
  const right = left + 1;
1954
- if (left < n && dist[left] > dist[largest]) largest = left;
1955
- if (right < n && dist[right] > dist[largest]) largest = right;
2250
+ if (left < n && (dist[left] > dist[largest] || dist[left] === dist[largest] && rows[left] > rows[largest])) largest = left;
2251
+ if (right < n && (dist[right] > dist[largest] || dist[right] === dist[largest] && rows[right] > rows[largest])) largest = right;
1956
2252
  if (largest === i) return;
1957
2253
  const td = dist[i];
1958
2254
  dist[i] = dist[largest];
@@ -2094,6 +2390,9 @@ var BATCH_INSERT_CHUNK = 96;
2094
2390
  var MAX_ANN_OVERFETCH = 8192;
2095
2391
  var INSERT_COALESCE_THRESHOLD = 24;
2096
2392
  var INSERT_COALESCE_MAX = 96;
2393
+ var warnLogger = new WolbargLogger("warn");
2394
+ var warnedSqliteVecFallback = false;
2395
+ var warnedFtsKeywordSearch = false;
2097
2396
  var SqliteStorageProvider = class _SqliteStorageProvider {
2098
2397
  name = "sqlite";
2099
2398
  connectionString;
@@ -2103,6 +2402,10 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2103
2402
  vectorDimensions = null;
2104
2403
  vectorBackend = null;
2105
2404
  sqliteVecLoaded = false;
2405
+ /** Tracks nesting depth for {@link withTransaction} so we can use savepoints. */
2406
+ transactionDepth = 0;
2407
+ /** Incrementing counter for deterministic savepoint names. */
2408
+ savepointCounter = 0;
2106
2409
  /** Hot in-process ANN for blob backend (sqlite-vec unavailable platforms). */
2107
2410
  memoryIndex = null;
2108
2411
  memoryIndexDirty = false;
@@ -2124,6 +2427,9 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2124
2427
  /** Coalesce concurrent insertMemory callers into one IMMEDIATE transaction. */
2125
2428
  insertQueue = [];
2126
2429
  insertFlushScheduled = false;
2430
+ /**
2431
+ * @param options - SQLite path / `:memory:` and optional concurrency config.
2432
+ */
2127
2433
  constructor(options) {
2128
2434
  this.connectionString = options.connectionString;
2129
2435
  this.concurrency = resolveConcurrencyConfig(options.concurrency);
@@ -2136,9 +2442,11 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2136
2442
  getDatabase() {
2137
2443
  return this.db;
2138
2444
  }
2445
+ /** Register a callback invoked when SQLite busy retries occur (tests / diagnostics). */
2139
2446
  setRetryLogger(fn) {
2140
2447
  this.retryLog = fn;
2141
2448
  }
2449
+ /** Open the database, run migrations, and prepare statements. */
2142
2450
  async open() {
2143
2451
  try {
2144
2452
  const dbPath = this.resolvePath(this.connectionString);
@@ -2169,6 +2477,12 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2169
2477
  } else if (this.sqliteVecLoaded) {
2170
2478
  this.vectorBackend = "sqlite-vec";
2171
2479
  } else {
2480
+ if (!warnedSqliteVecFallback) {
2481
+ warnedSqliteVecFallback = true;
2482
+ warnLogger.warn(
2483
+ "sqlite-vec unavailable on this platform; using blob ANN fallback."
2484
+ );
2485
+ }
2172
2486
  this.vectorBackend = "blob";
2173
2487
  }
2174
2488
  if (dims !== null) {
@@ -2192,6 +2506,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2192
2506
  );
2193
2507
  }
2194
2508
  }
2509
+ /** Drain pending inserts, optimize, and close the SQLite connection. */
2195
2510
  async close() {
2196
2511
  const deadline = Date.now() + 2e3;
2197
2512
  while (this.insertQueue.length > 0 && Date.now() < deadline) {
@@ -2222,6 +2537,11 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2222
2537
  this.batchBlobEmbStatements.clear();
2223
2538
  }
2224
2539
  }
2540
+ /**
2541
+ * Create or validate vec0 / blob vector storage for the given dimensionality.
2542
+ *
2543
+ * @param dimensions - Embedding length from the configured model.
2544
+ */
2225
2545
  async ensureVectorSchema(dimensions) {
2226
2546
  const existing = await this.getEmbeddingDimensions();
2227
2547
  if (existing !== null && existing !== dimensions) {
@@ -2244,13 +2564,16 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2244
2564
  this.reprepareVectorStatements();
2245
2565
  this.hydrateMemoryIndex();
2246
2566
  }
2567
+ /** @inheritdoc StorageProvider.getEmbeddingDimensions */
2247
2568
  async getEmbeddingDimensions() {
2248
2569
  return this.readMetaNumber(META_KEYS.embeddingDimensions);
2249
2570
  }
2571
+ /** @inheritdoc StorageProvider.setEmbeddingDimensions */
2250
2572
  async setEmbeddingDimensions(dimensions) {
2251
2573
  await this.setMeta(META_KEYS.embeddingDimensions, String(dimensions));
2252
2574
  this.vectorDimensions = dimensions;
2253
2575
  }
2576
+ /** Insert a single memory row (coalesced under concurrent writers). */
2254
2577
  async insertMemory(input) {
2255
2578
  this.requireVectorReady();
2256
2579
  return new Promise((resolve, reject) => {
@@ -2332,6 +2655,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2332
2655
  return row;
2333
2656
  });
2334
2657
  }
2658
+ /** Batch insert memories in chunked multi-row transactions. */
2335
2659
  async insertMemoriesBatch(inputs) {
2336
2660
  if (inputs.length === 0) {
2337
2661
  return [];
@@ -2347,6 +2671,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2347
2671
  return rows;
2348
2672
  });
2349
2673
  }
2674
+ /** Update memory content, metadata, embedding, and content hash. */
2350
2675
  async updateMemory(input) {
2351
2676
  const stmts = this.requireStatements();
2352
2677
  return this.withTransaction(() => {
@@ -2390,6 +2715,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2390
2715
  return stmts.getMemoryById.get(input.id, input.organization);
2391
2716
  });
2392
2717
  }
2718
+ /** Find an active memory by org, agent, and content hash (dedupe). */
2393
2719
  async findActiveByContentHash(organization, agent, contentHash) {
2394
2720
  const stmts = this.requireStatements();
2395
2721
  const row = stmts.findActiveByContentHash.get(
@@ -2399,6 +2725,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2399
2725
  );
2400
2726
  return row ?? null;
2401
2727
  }
2728
+ /** Scan memories matching metadata filters (may paginate in SQL). */
2402
2729
  async searchByMetadata(filter, limit) {
2403
2730
  const rows = await this.listMemories(filter, limit);
2404
2731
  if (!filter.metadata) {
@@ -2409,9 +2736,16 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2409
2736
  );
2410
2737
  }
2411
2738
  /** Keyword search via FTS5 BM25. Returns memory IDs ranked by relevance. */
2739
+ /** BM25 keyword search via FTS5 (`memories_fts`). */
2412
2740
  async searchKeyword(query, organization, topK) {
2413
2741
  const stmts = this.requireStatements();
2414
2742
  if (!stmts.searchFts) {
2743
+ if (!warnedFtsKeywordSearch) {
2744
+ warnedFtsKeywordSearch = true;
2745
+ warnLogger.warn(
2746
+ "FTS keyword search is unavailable; returning empty keyword hits."
2747
+ );
2748
+ }
2415
2749
  return [];
2416
2750
  }
2417
2751
  try {
@@ -2425,20 +2759,32 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2425
2759
  // bm25 returns lower (more negative) for better matches — invert to [0, ∞)
2426
2760
  score: 1 / (1 + Math.abs(row.rank))
2427
2761
  }));
2428
- } catch {
2762
+ } catch (error) {
2763
+ if (!warnedFtsKeywordSearch) {
2764
+ warnedFtsKeywordSearch = true;
2765
+ warnLogger.warn(
2766
+ "FTS keyword search failed; returning empty keyword hits.",
2767
+ {
2768
+ cause: error instanceof Error ? error.message : String(error)
2769
+ }
2770
+ );
2771
+ }
2429
2772
  return [];
2430
2773
  }
2431
2774
  }
2775
+ /** Fetch a memory row by UUID within an organization. */
2432
2776
  async getMemoryById(id, organization) {
2433
2777
  const stmts = this.requireStatements();
2434
2778
  const row = stmts.getMemoryById.get(id, organization);
2435
2779
  return row ?? null;
2436
2780
  }
2781
+ /** Fetch a memory row by SQLite integer rowid within an organization. */
2437
2782
  async getMemoryByRowid(rowid, organization) {
2438
2783
  const stmts = this.requireStatements();
2439
2784
  const row = stmts.getMemoryByRowid.get(rowid, organization);
2440
2785
  return row ?? null;
2441
2786
  }
2787
+ /** Batch-fetch memory rows by SQLite rowids within an organization. */
2442
2788
  async getMemoriesByRowids(rowids, organization) {
2443
2789
  const out = /* @__PURE__ */ new Map();
2444
2790
  if (rowids.length === 0) {
@@ -2457,6 +2803,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2457
2803
  }
2458
2804
  return out;
2459
2805
  }
2806
+ /** List memories matching repository filters with optional limit. */
2460
2807
  async listMemories(filter, limit) {
2461
2808
  try {
2462
2809
  if (filter.metadata) {
@@ -2469,6 +2816,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2469
2816
  });
2470
2817
  }
2471
2818
  }
2819
+ /** Approximate nearest-neighbor search (vec0 or in-memory blob index). */
2472
2820
  async searchVectors(embedding, topK) {
2473
2821
  this.requireVectorReady();
2474
2822
  if (this.vectorBackend === "sqlite-vec") {
@@ -2480,6 +2828,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2480
2828
  * Org-scoped KNN + memory rows with adaptive overfetch.
2481
2829
  * Global ANN is post-filtered by org/agent/archived; underfill triggers larger k.
2482
2830
  */
2831
+ /** ANN search joined with memory rows and org/archived post-filters. */
2483
2832
  async searchVectorsWithMemories(embedding, topK, organization, options) {
2484
2833
  this.requireVectorReady();
2485
2834
  if (topK <= 0) {
@@ -2518,6 +2867,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2518
2867
  }
2519
2868
  return out;
2520
2869
  }
2870
+ /** Soft-archive memories (compression / forget paths). */
2521
2871
  async archiveMemories(ids, organization, compressedIntoId, archivedAt) {
2522
2872
  const stmts = this.requireStatements();
2523
2873
  const archived = [];
@@ -2555,6 +2905,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2555
2905
  return archived;
2556
2906
  });
2557
2907
  }
2908
+ /** Hard-delete a single memory by id; returns whether a row was removed. */
2558
2909
  async deleteMemoryById(id, organization) {
2559
2910
  const stmts = this.requireStatements();
2560
2911
  return this.withTransaction(() => {
@@ -2568,6 +2919,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2568
2919
  return Number(result.changes) > 0;
2569
2920
  });
2570
2921
  }
2922
+ /** Hard-delete memories matching org / agent / metadata filters. */
2571
2923
  async deleteMemoriesByFilter(filter) {
2572
2924
  const stmts = this.requireStatements();
2573
2925
  const agent = filter.agent;
@@ -2584,6 +2936,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2584
2936
  return Number(result.changes);
2585
2937
  });
2586
2938
  }
2939
+ /** Remove all memories (and side tables) for an organization. */
2587
2940
  async clearOrganization(organization) {
2588
2941
  const stmts = this.requireStatements();
2589
2942
  return this.withTransaction(() => {
@@ -2600,10 +2953,12 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2600
2953
  return Number(result.changes);
2601
2954
  });
2602
2955
  }
2956
+ /** List audit history events for a memory id. */
2603
2957
  async getHistory(memoryId) {
2604
2958
  const stmts = this.requireStatements();
2605
2959
  return stmts.getHistory.all(memoryId);
2606
2960
  }
2961
+ /** Append a history row (created / archived / compressed / updated). */
2607
2962
  async insertHistoryEvent(event) {
2608
2963
  const stmts = this.requireStatements();
2609
2964
  stmts.insertHistory.run(
@@ -2614,6 +2969,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2614
2969
  event.created_at
2615
2970
  );
2616
2971
  }
2972
+ /** Aggregate memory counts for an organization (single-pass SQL). */
2617
2973
  async getStats(organization) {
2618
2974
  const stmts = this.requireStatements();
2619
2975
  const row = stmts.getStats.get(organization);
@@ -2624,6 +2980,7 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2624
2980
  totalAgents: Number(row.agents)
2625
2981
  };
2626
2982
  }
2983
+ /** On-disk database file size in bytes (0 for `:memory:`). */
2627
2984
  async getDatabaseSizeBytes() {
2628
2985
  const db = this.requireDb();
2629
2986
  if (this.connectionString === ":memory:") {
@@ -2656,21 +3013,44 @@ var SqliteStorageProvider = class _SqliteStorageProvider {
2656
3013
  }
2657
3014
  async withTransaction(fn) {
2658
3015
  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
- );
3016
+ if (this.transactionDepth > 0) {
3017
+ const savepointName = `wolbarg_sp_${this.savepointCounter++}`;
3018
+ db.exec(`SAVEPOINT ${savepointName}`);
3019
+ try {
3020
+ this.transactionDepth += 1;
3021
+ const result = await fn();
3022
+ db.exec(`RELEASE SAVEPOINT ${savepointName}`);
3023
+ return result;
3024
+ } catch (error) {
3025
+ try {
3026
+ db.exec(`ROLLBACK TO SAVEPOINT ${savepointName}`);
3027
+ } catch {
3028
+ }
3029
+ throw error;
3030
+ } finally {
3031
+ this.transactionDepth -= 1;
2667
3032
  }
2668
- );
2669
- }
2670
- // ─── internals ───────────────────────────────────────────────────────────
2671
- tryLoadSqliteVec(db) {
2672
- const plat = `${process.platform}-${process.arch}`;
2673
- if (_SqliteStorageProvider.sqliteVecUnsupported.has(plat)) {
3033
+ }
3034
+ this.transactionDepth = 1;
3035
+ try {
3036
+ return await withImmediateTransaction(
3037
+ db,
3038
+ this.concurrency,
3039
+ fn,
3040
+ (attempt, delayMs) => {
3041
+ this.retryLog?.(
3042
+ `SQLITE_BUSY retry attempt=${attempt} backoffMs=${Math.round(delayMs)}`
3043
+ );
3044
+ }
3045
+ );
3046
+ } finally {
3047
+ this.transactionDepth = 0;
3048
+ }
3049
+ }
3050
+ // ─── internals ───────────────────────────────────────────────────────────
3051
+ tryLoadSqliteVec(db) {
3052
+ const plat = `${process.platform}-${process.arch}`;
3053
+ if (_SqliteStorageProvider.sqliteVecUnsupported.has(plat)) {
2674
3054
  return false;
2675
3055
  }
2676
3056
  try {
@@ -3659,11 +4039,17 @@ var PostgresSubscribeListener = class {
3659
4039
  reconnectDelayMs;
3660
4040
  connect;
3661
4041
  onError;
4042
+ /**
4043
+ * @param options.connect - Factory returning a dedicated LISTEN client.
4044
+ * @param options.reconnectDelayMs - Delay before reconnect attempts (default 1000).
4045
+ * @param options.onError - Optional error hook for connection failures.
4046
+ */
3662
4047
  constructor(options) {
3663
4048
  this.connect = options.connect;
3664
4049
  this.reconnectDelayMs = options.reconnectDelayMs ?? 1e3;
3665
4050
  this.onError = options.onError;
3666
4051
  }
4052
+ /** Open the LISTEN connection (lazy — also started on first subscribe). */
3667
4053
  async start() {
3668
4054
  if (this.closed) {
3669
4055
  return;
@@ -3760,6 +4146,13 @@ var PostgresSubscribeListener = class {
3760
4146
  }
3761
4147
  }
3762
4148
  }
4149
+ /**
4150
+ * Register a filtered callback for memory change events.
4151
+ *
4152
+ * @param filter - Organization / agent / event-type filter.
4153
+ * @param callback - Invoked synchronously on each matching NOTIFY.
4154
+ * @returns Unsubscribe function.
4155
+ */
3763
4156
  subscribe(filter, callback) {
3764
4157
  if (this.closed) {
3765
4158
  throw new Error("Subscribe backend is closed");
@@ -3781,6 +4174,7 @@ var PostgresSubscribeListener = class {
3781
4174
  */
3782
4175
  emit(_event) {
3783
4176
  }
4177
+ /** Tear down LISTEN connection and clear subscriptions. */
3784
4178
  async close() {
3785
4179
  this.closed = true;
3786
4180
  this.subscriptions.clear();
@@ -3819,9 +4213,13 @@ function createPostgresListenerFromPool(pool, onError) {
3819
4213
 
3820
4214
  // src/storage/providers/postgres.ts
3821
4215
  var txStore = new async_hooks.AsyncLocalStorage();
4216
+ var warnLogger2 = new WolbargLogger("warn");
4217
+ var warnedPgvectorFallback = false;
4218
+ var warnedFtsKeywordSearch2 = false;
4219
+ var warnedHnswSoftFail = false;
3822
4220
  var STMT = {
3823
4221
  insertOne: "Wolbarg_insert_one_v5",
3824
- insertBatch: "Wolbarg_insert_batch_v5"
4222
+ insertBatch: "Wolbarg_insert_batch_v6"
3825
4223
  };
3826
4224
  function toFloat4Param(embedding) {
3827
4225
  const out = new Array(embedding.length);
@@ -3892,13 +4290,13 @@ var INSERT_ONE_SQL = `WITH mem AS (
3892
4290
  var INSERT_BATCH_SQL = `WITH mem AS (
3893
4291
  INSERT INTO memories (
3894
4292
  id, organization, agent, content_text, metadata_json,
3895
- archived, compressed_into, created_at, updated_at
4293
+ archived, compressed_into, content_hash, created_at, updated_at
3896
4294
  )
3897
- SELECT id, org, agent, txt, meta::jsonb, false, NULL, c, u
4295
+ SELECT id, org, agent, txt, meta::jsonb, false, NULL, h, c, u
3898
4296
  FROM unnest(
3899
4297
  $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)
4298
+ $5::text[], $6::timestamptz[], $7::timestamptz[], $8::text[]
4299
+ ) AS t(id, org, agent, txt, meta, c, u, h)
3902
4300
  RETURNING id
3903
4301
  ),
3904
4302
  hist AS (
@@ -3914,7 +4312,7 @@ var INSERT_BATCH_SQL = `WITH mem AS (
3914
4312
  emb AS (
3915
4313
  INSERT INTO memory_embeddings (memory_id, embedding, organization, agent, archived)
3916
4314
  SELECT id, emb::vector, org, agent, false
3917
- FROM unnest($1::text[], $8::text[], $2::text[], $3::text[]) AS t(id, emb, org, agent)
4315
+ FROM unnest($1::text[], $9::text[], $2::text[], $3::text[]) AS t(id, emb, org, agent)
3918
4316
  )
3919
4317
  SELECT id FROM mem`;
3920
4318
  var COALESCE_FLUSH_MAX = 128;
@@ -3941,6 +4339,9 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
3941
4339
  insertFlushScheduled = false;
3942
4340
  insertFlushTimer = null;
3943
4341
  insertFlushInFlight = 0;
4342
+ /**
4343
+ * @param options - Connection string, pool size, and durability flags.
4344
+ */
3944
4345
  constructor(options) {
3945
4346
  this.maxPoolSize = options.maxPoolSize ?? 64;
3946
4347
  this.durableWrites = options.durableWrites !== false;
@@ -3949,6 +4350,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
3949
4350
  this.durableWrites
3950
4351
  );
3951
4352
  }
4353
+ /** Current pg pool occupancy stats (for diagnostics / subscribe setup). */
3952
4354
  getPoolStats() {
3953
4355
  const pool = this.pool;
3954
4356
  return {
@@ -3962,6 +4364,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
3962
4364
  getPool() {
3963
4365
  return this.pool;
3964
4366
  }
4367
+ /** Open the connection pool and run migrations. */
3965
4368
  async open() {
3966
4369
  let PoolCtor;
3967
4370
  try {
@@ -4010,6 +4413,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4010
4413
  );
4011
4414
  }
4012
4415
  }
4416
+ /** Drain coalesced inserts and end the connection pool. */
4013
4417
  async close() {
4014
4418
  if (this.insertFlushTimer) {
4015
4419
  clearTimeout(this.insertFlushTimer);
@@ -4050,6 +4454,11 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4050
4454
  async ensureVectorIndex() {
4051
4455
  await this.ensureHnswIndex();
4052
4456
  }
4457
+ /**
4458
+ * Create pgvector tables and HNSW index for the given dimensionality.
4459
+ *
4460
+ * @param dimensions - Embedding length from the configured model.
4461
+ */
4053
4462
  async ensureVectorSchema(dimensions) {
4054
4463
  const existing = await this.getEmbeddingDimensions();
4055
4464
  if (existing !== null && existing !== dimensions) {
@@ -4114,6 +4523,11 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4114
4523
  }
4115
4524
  }
4116
4525
  /** Cross-process subscribe: NOTIFY after a committed write. */
4526
+ /**
4527
+ * Issue `pg_notify` for cross-process {@link subscribe} delivery.
4528
+ *
4529
+ * @param event - Memory change payload (IDs + metadata only).
4530
+ */
4117
4531
  async notifyChange(event) {
4118
4532
  await notifyMemoryChange(this.requirePool(), event);
4119
4533
  }
@@ -4121,6 +4535,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4121
4535
  * Soft reset for a single organization. Drops HNSW only when the embeddings
4122
4536
  * table is empty so other corpora on a shared bench DB stay intact.
4123
4537
  */
4538
+ /** Delete all rows for an organization (dev / test helper). */
4124
4539
  async resetOrganization(organization) {
4125
4540
  await this.query(`DELETE FROM memories WHERE organization = $1`, [
4126
4541
  organization
@@ -4138,6 +4553,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4138
4553
  /**
4139
4554
  * Wipe all Wolbarg tables (explicit opt-in). Prefer {@link resetOrganization}.
4140
4555
  */
4556
+ /** Truncate all Wolbarg tables (dev / test helper). */
4141
4557
  async wipeAllData() {
4142
4558
  await this.query(`TRUNCATE TABLE memories CASCADE`).catch(() => void 0);
4143
4559
  await this.query(
@@ -4183,6 +4599,12 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4183
4599
  return;
4184
4600
  }
4185
4601
  this.hnswCreateFailures += 1;
4602
+ if (this.hnswCreateFailures === 1 && !warnedHnswSoftFail) {
4603
+ warnedHnswSoftFail = true;
4604
+ warnLogger2.warn(
4605
+ "HNSW index creation failed; ANN will fall back to sequential scan."
4606
+ );
4607
+ }
4186
4608
  if (this.hnswCreateFailures >= 3) {
4187
4609
  throw new DatabaseError(
4188
4610
  `Failed to create HNSW index after ${this.hnswCreateFailures} attempts: ${this.describe(error)}`,
@@ -4191,6 +4613,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4191
4613
  }
4192
4614
  }
4193
4615
  }
4616
+ /** @inheritdoc StorageProvider.getEmbeddingDimensions */
4194
4617
  async getEmbeddingDimensions() {
4195
4618
  const result = await this.query(
4196
4619
  `SELECT value FROM Wolbarg_meta WHERE key = $1`,
@@ -4203,6 +4626,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4203
4626
  const parsed = Number(value);
4204
4627
  return Number.isFinite(parsed) ? parsed : null;
4205
4628
  }
4629
+ /** @inheritdoc StorageProvider.setEmbeddingDimensions */
4206
4630
  async setEmbeddingDimensions(dimensions) {
4207
4631
  await this.query(
4208
4632
  `INSERT INTO Wolbarg_meta (key, value) VALUES ($1, $2)
@@ -4211,6 +4635,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4211
4635
  );
4212
4636
  this.vectorDimensions = dimensions;
4213
4637
  }
4638
+ /** Insert a single memory row (coalesced under concurrent writers). */
4214
4639
  async insertMemory(input) {
4215
4640
  this.requireVectorReady();
4216
4641
  return new Promise((resolve, reject) => {
@@ -4297,6 +4722,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4297
4722
  }
4298
4723
  return this.insertOneBlob(input);
4299
4724
  }
4725
+ /** Batch insert via unnest / COPY paths with optional HNSW deferral. */
4300
4726
  async insertMemoriesBatch(inputs) {
4301
4727
  if (inputs.length === 0) {
4302
4728
  return [];
@@ -4327,6 +4753,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4327
4753
  const metas = new Array(inputs.length);
4328
4754
  const created = new Array(inputs.length);
4329
4755
  const updated = new Array(inputs.length);
4756
+ const contentHashes = new Array(inputs.length);
4330
4757
  const vectors = new Array(inputs.length);
4331
4758
  for (let i = 0; i < inputs.length; i += 1) {
4332
4759
  const input = inputs[i];
@@ -4337,6 +4764,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4337
4764
  metas[i] = serializeMetadata(input.metadata);
4338
4765
  created[i] = input.createdAt;
4339
4766
  updated[i] = input.updatedAt;
4767
+ contentHashes[i] = input.contentHash !== void 0 ? input.contentHash : hashMemoryContent(input.contentText);
4340
4768
  vectors[i] = toVectorLiteral(input.embedding);
4341
4769
  }
4342
4770
  await this.queryNamed(STMT.insertBatch, INSERT_BATCH_SQL, [
@@ -4347,9 +4775,10 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4347
4775
  metas,
4348
4776
  created,
4349
4777
  updated,
4778
+ contentHashes,
4350
4779
  vectors
4351
4780
  ]);
4352
- return inputs.map((input, i) => ({
4781
+ return inputs.map((_, i) => ({
4353
4782
  id: ids[i],
4354
4783
  organization: orgs[i],
4355
4784
  agent: agents[i],
@@ -4357,7 +4786,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4357
4786
  metadata_json: metas[i],
4358
4787
  archived: 0,
4359
4788
  compressed_into: null,
4360
- content_hash: input.contentHash !== void 0 ? input.contentHash : null,
4789
+ content_hash: contentHashes[i] ?? null,
4361
4790
  created_at: created[i],
4362
4791
  updated_at: updated[i]
4363
4792
  }));
@@ -4398,81 +4827,87 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4398
4827
  return out;
4399
4828
  }
4400
4829
  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;
4830
+ return this.withTransaction(async () => {
4831
+ const buf = Buffer.from(
4832
+ input.embedding.buffer,
4833
+ input.embedding.byteOffset,
4834
+ input.embedding.byteLength
4835
+ );
4836
+ const contentHash = input.contentHash !== void 0 ? input.contentHash : hashMemoryContent(input.contentText);
4837
+ const inserted = await this.query(
4838
+ `INSERT INTO memories (
4839
+ id, organization, agent, content_text, metadata_json,
4840
+ archived, compressed_into, content_hash, created_at, updated_at
4841
+ ) VALUES ($1,$2,$3,$4,$5::jsonb,false,NULL,$8,$6,$7)
4842
+ RETURNING id, organization, agent, content_text, metadata_json,
4843
+ archived::int AS archived, compressed_into, content_hash, created_at, updated_at`,
4844
+ [
4845
+ input.id,
4846
+ input.organization,
4847
+ input.agent,
4848
+ input.contentText,
4849
+ serializeMetadata(input.metadata),
4850
+ input.createdAt,
4851
+ input.updatedAt,
4852
+ contentHash
4853
+ ]
4854
+ );
4855
+ const row = this.mapRow(inserted.rows[0]);
4856
+ await this.query(
4857
+ `WITH mapped AS (
4858
+ INSERT INTO memory_row_map (memory_id) VALUES ($1)
4859
+ ON CONFLICT (memory_id) DO NOTHING
4860
+ )
4861
+ INSERT INTO memory_embeddings_blob (memory_id, embedding)
4862
+ VALUES ($1, $2)
4863
+ ON CONFLICT (memory_id) DO UPDATE SET embedding = EXCLUDED.embedding`,
4864
+ [input.id, buf]
4865
+ );
4866
+ await this.query(
4867
+ `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4868
+ VALUES ($1,$2,'created',NULL,$3)`,
4869
+ [crypto.randomUUID(), input.id, input.createdAt]
4870
+ );
4871
+ return row;
4872
+ });
4442
4873
  }
4874
+ /** Update memory content, metadata, embedding, and content hash. */
4443
4875
  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);
4876
+ return this.withTransaction(async () => {
4877
+ const existing = await this.getMemoryById(input.id, input.organization);
4878
+ if (!existing) {
4879
+ return null;
4880
+ }
4881
+ const contentHash = input.contentHash !== void 0 ? input.contentHash : input.contentText !== void 0 ? hashMemoryContent(input.contentText) : existing.content_hash ?? null;
4882
+ await this.query(
4883
+ `UPDATE memories SET
4884
+ content_text = COALESCE($1, content_text),
4885
+ metadata_json = COALESCE($2::jsonb, metadata_json),
4886
+ content_hash = COALESCE($3, content_hash),
4887
+ updated_at = $4
4888
+ WHERE id = $5 AND organization = $6`,
4889
+ [
4890
+ input.contentText ?? null,
4891
+ input.metadata !== void 0 ? serializeMetadata(input.metadata) : null,
4892
+ contentHash,
4893
+ input.updatedAt,
4894
+ input.id,
4895
+ input.organization
4896
+ ]
4897
+ );
4898
+ if (input.embedding) {
4899
+ await this.deleteEmbedding(input.id);
4900
+ await this.insertEmbedding(input.id, input.embedding);
4901
+ }
4902
+ await this.query(
4903
+ `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
4904
+ VALUES ($1,$2,'updated',NULL,$3)`,
4905
+ [crypto.randomUUID(), input.id, input.updatedAt]
4906
+ );
4907
+ return this.getMemoryById(input.id, input.organization);
4908
+ });
4475
4909
  }
4910
+ /** Find an active memory by org, agent, and content hash (dedupe). */
4476
4911
  async findActiveByContentHash(organization, agent, contentHash) {
4477
4912
  const result = await this.query(
4478
4913
  `SELECT id, organization, agent, content_text, metadata_json,
@@ -4485,6 +4920,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4485
4920
  const row = result.rows[0];
4486
4921
  return row ? this.mapRow(row) : null;
4487
4922
  }
4923
+ /** Fetch a memory row by UUID within an organization. */
4488
4924
  async getMemoryById(id, organization) {
4489
4925
  const result = await this.query(
4490
4926
  `SELECT id, organization, agent, content_text, metadata_json,
@@ -4495,6 +4931,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4495
4931
  const row = result.rows[0];
4496
4932
  return row ? this.mapRow(row) : null;
4497
4933
  }
4934
+ /** Fetch a memory row by internal rowid within an organization. */
4498
4935
  async getMemoryByRowid(rowid, organization) {
4499
4936
  const result = await this.query(
4500
4937
  `SELECT m.id, m.organization, m.agent, m.content_text, m.metadata_json,
@@ -4508,6 +4945,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4508
4945
  const row = result.rows[0];
4509
4946
  return row ? this.mapRow(row) : null;
4510
4947
  }
4948
+ /** Batch-fetch memory rows by rowids within an organization. */
4511
4949
  async getMemoriesByRowids(rowids, organization) {
4512
4950
  const out = /* @__PURE__ */ new Map();
4513
4951
  if (rowids.length === 0) {
@@ -4530,6 +4968,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4530
4968
  }
4531
4969
  return out;
4532
4970
  }
4971
+ /** List memories matching repository filters with optional limit. */
4533
4972
  async listMemories(filter, limit) {
4534
4973
  const want = limit !== void 0 ? limit : filter.metadata ? 1e4 : void 0;
4535
4974
  const compiled = filter.metadata ? compileMetadataFilterToPostgres(filter.metadata, 2) : null;
@@ -4591,9 +5030,11 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4591
5030
  }
4592
5031
  return rows;
4593
5032
  }
5033
+ /** Scan memories matching metadata filters. */
4594
5034
  async searchByMetadata(filter, limit) {
4595
5035
  return this.listMemories(filter, limit);
4596
5036
  }
5037
+ /** Full-text keyword search via `tsvector` / GIN index. */
4597
5038
  async searchKeyword(query, organization, topK) {
4598
5039
  const trimmed = query.trim();
4599
5040
  if (!trimmed || topK <= 0) {
@@ -4620,10 +5061,20 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4620
5061
  memoryId: String(row.memory_id),
4621
5062
  score: Number(row.rank)
4622
5063
  }));
4623
- } catch {
5064
+ } catch (error) {
5065
+ if (!warnedFtsKeywordSearch2) {
5066
+ warnedFtsKeywordSearch2 = true;
5067
+ warnLogger2.warn(
5068
+ "FTS keyword search failed; returning empty keyword hits.",
5069
+ {
5070
+ cause: error instanceof Error ? error.message : String(error)
5071
+ }
5072
+ );
5073
+ }
4624
5074
  return [];
4625
5075
  }
4626
5076
  }
5077
+ /** pgvector cosine ANN search (HNSW when index is present). */
4627
5078
  async searchVectors(embedding, topK) {
4628
5079
  this.requireVectorReady();
4629
5080
  if (this.hasPgvector) {
@@ -4673,6 +5124,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4673
5124
  scored.sort((a, b) => a.distance - b.distance);
4674
5125
  return scored.slice(0, topK);
4675
5126
  }
5127
+ /** ANN search joined with memory rows and org/archived post-filters. */
4676
5128
  async searchVectorsWithMemories(embedding, topK, organization, options) {
4677
5129
  this.requireVectorReady();
4678
5130
  if (!this.hasPgvector) {
@@ -4737,46 +5189,50 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4737
5189
  );
4738
5190
  return mapHits(result.rows);
4739
5191
  }
5192
+ /** Soft-archive memories (compression / forget paths). */
4740
5193
  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;
5194
+ return this.withTransaction(async () => {
5195
+ if (ids.length === 0) {
5196
+ return [];
5197
+ }
5198
+ const result = await this.query(
5199
+ `UPDATE memories
5200
+ SET archived = true, compressed_into = $1, updated_at = $2
5201
+ WHERE organization = $3 AND archived = false AND id = ANY($4::text[])
5202
+ RETURNING id`,
5203
+ [compressedIntoId, archivedAt, organization, ids]
5204
+ );
5205
+ const archived = result.rows.map((r) => String(r.id));
5206
+ if (archived.length === 0) {
5207
+ return [];
5208
+ }
5209
+ await this.query(
5210
+ `UPDATE memory_embeddings
5211
+ SET archived = true
5212
+ WHERE memory_id = ANY($1::text[])`,
5213
+ [archived]
5214
+ );
5215
+ const histIds = [];
5216
+ const memIds = [];
5217
+ const types = [];
5218
+ const related = [];
5219
+ const times = [];
5220
+ for (const id of archived) {
5221
+ histIds.push(crypto.randomUUID(), crypto.randomUUID());
5222
+ memIds.push(id, compressedIntoId);
5223
+ types.push("archived", "compressed");
5224
+ related.push(compressedIntoId, id);
5225
+ times.push(archivedAt, archivedAt);
5226
+ }
5227
+ await this.query(
5228
+ `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
5229
+ SELECT * FROM unnest($1::text[], $2::text[], $3::text[], $4::text[], $5::timestamptz[])`,
5230
+ [histIds, memIds, types, related, times]
5231
+ );
5232
+ return archived;
5233
+ });
4779
5234
  }
5235
+ /** Hard-delete a single memory by id; returns whether a row was removed. */
4780
5236
  async deleteMemoryById(id, organization) {
4781
5237
  const result = await this.query(
4782
5238
  `DELETE FROM memories WHERE id = $1 AND organization = $2`,
@@ -4784,6 +5240,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4784
5240
  );
4785
5241
  return (result.rowCount ?? 0) > 0;
4786
5242
  }
5243
+ /** Hard-delete memories matching org / agent / metadata filters. */
4787
5244
  async deleteMemoriesByFilter(filter) {
4788
5245
  if (!filter.agent) {
4789
5246
  throw new DatabaseError("deleteMemoriesByFilter requires an agent filter");
@@ -4794,6 +5251,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4794
5251
  );
4795
5252
  return result.rowCount ?? 0;
4796
5253
  }
5254
+ /** Remove all memories for an organization. */
4797
5255
  async clearOrganization(organization) {
4798
5256
  const result = await this.query(
4799
5257
  `DELETE FROM memories WHERE organization = $1`,
@@ -4801,6 +5259,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4801
5259
  );
4802
5260
  return result.rowCount ?? 0;
4803
5261
  }
5262
+ /** List audit history events for a memory id. */
4804
5263
  async getHistory(memoryId) {
4805
5264
  const result = await this.query(
4806
5265
  `SELECT id, memory_id, event_type, related_memory_id, created_at
@@ -4815,6 +5274,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4815
5274
  created_at: String(row.created_at)
4816
5275
  }));
4817
5276
  }
5277
+ /** Append a history row (created / archived / compressed / updated). */
4818
5278
  async insertHistoryEvent(event) {
4819
5279
  await this.query(
4820
5280
  `INSERT INTO memory_history (id, memory_id, event_type, related_memory_id, created_at)
@@ -4828,6 +5288,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4828
5288
  ]
4829
5289
  );
4830
5290
  }
5291
+ /** Aggregate memory counts for an organization. */
4831
5292
  async getStats(organization) {
4832
5293
  const result = await this.query(
4833
5294
  `SELECT
@@ -4845,6 +5306,7 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4845
5306
  totalAgents: Number(result.rows[0]?.agents ?? 0)
4846
5307
  };
4847
5308
  }
5309
+ /** Approximate on-disk database size in bytes (`pg_database_size`). */
4848
5310
  async getDatabaseSizeBytes() {
4849
5311
  const result = await this.query(
4850
5312
  `SELECT pg_database_size(current_database())::bigint AS size`
@@ -4886,6 +5348,11 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
4886
5348
  [META_KEYS.schemaVersion]
4887
5349
  ).catch(() => ({ rows: [] }));
4888
5350
  const current = versionRow.rows[0]?.value !== void 0 ? Number(versionRow.rows[0].value) : null;
5351
+ if (current !== null && current > SCHEMA_VERSION) {
5352
+ throw new InitializationError(
5353
+ `Database schema version ${current} is newer than this Wolbarg SDK (${SCHEMA_VERSION}). Please upgrade Wolbarg to open this database.`
5354
+ );
5355
+ }
4889
5356
  if (current === SCHEMA_VERSION) {
4890
5357
  const tsvProbe2 = await this.query(
4891
5358
  `SELECT 1 FROM information_schema.columns
@@ -5036,8 +5503,17 @@ var PostgresStorageProvider = class _PostgresStorageProvider {
5036
5503
  await this.query(`CREATE EXTENSION IF NOT EXISTS vector`);
5037
5504
  _PostgresStorageProvider.pgvectorByConn.set(this.connectionString, true);
5038
5505
  return true;
5039
- } catch {
5506
+ } catch (error) {
5040
5507
  _PostgresStorageProvider.pgvectorByConn.set(this.connectionString, false);
5508
+ if (!warnedPgvectorFallback) {
5509
+ warnedPgvectorFallback = true;
5510
+ warnLogger2.warn(
5511
+ "pgvector extension is unavailable; using blob cosine search fallback.",
5512
+ {
5513
+ cause: error instanceof Error ? error.message : `unknown error: ${String(error)}`
5514
+ }
5515
+ );
5516
+ }
5041
5517
  return false;
5042
5518
  }
5043
5519
  }
@@ -5178,11 +5654,12 @@ function createDatabaseProvider(config) {
5178
5654
  }
5179
5655
 
5180
5656
  // src/version.ts
5181
- var SDK_VERSION = "0.5.3";
5657
+ var SDK_VERSION = "0.5.5";
5182
5658
 
5183
5659
  // src/memory/transfer.ts
5660
+ var warnedMissingExportManifest = false;
5184
5661
  var SqliteMemoryTransferProvider = class {
5185
- async exportTo(exportPath, sourcePath, organization) {
5662
+ async exportTo(exportPath, sourcePath, organization, embeddingModel, embeddingDimensions) {
5186
5663
  const resolvedSource = resolvePath(sourcePath);
5187
5664
  if (resolvedSource === ":memory:") {
5188
5665
  throw new ConfigurationError(
@@ -5209,7 +5686,9 @@ Initialize Wolbarg and verify the database path.`
5209
5686
  sdkVersion: SDK_VERSION,
5210
5687
  provider: "sqlite",
5211
5688
  sourcePath: resolvedSource,
5212
- organization
5689
+ organization,
5690
+ ...embeddingModel ? { embeddingModel } : {},
5691
+ ...embeddingDimensions ? { embeddingDimensions } : {}
5213
5692
  };
5214
5693
  fs6__default.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
5215
5694
  return {
@@ -5218,7 +5697,7 @@ Initialize Wolbarg and verify the database path.`
5218
5697
  sizeBytes: fs6__default.default.statSync(dbExportPath).size
5219
5698
  };
5220
5699
  }
5221
- async importFrom(exportPath, targetPath) {
5700
+ async importFrom(exportPath, targetPath, expected) {
5222
5701
  const resolvedExport = resolvePath(exportPath);
5223
5702
  const dbExportPath = resolvedExport.endsWith(".db") ? resolvedExport : fs6__default.default.existsSync(`${resolvedExport}.db`) ? `${resolvedExport}.db` : resolvedExport;
5224
5703
  if (!fs6__default.default.existsSync(dbExportPath)) {
@@ -5240,26 +5719,57 @@ Pass the path returned by export().`
5240
5719
  );
5241
5720
  }
5242
5721
  } else {
5243
- manifest = {
5244
- format: "wolbarg-export-v1",
5245
- exportedAt: nowIso(),
5246
- sdkVersion: SDK_VERSION,
5247
- provider: "sqlite",
5248
- sourcePath: dbExportPath
5249
- };
5722
+ if (!warnedMissingExportManifest) {
5723
+ warnedMissingExportManifest = true;
5724
+ console.warn(
5725
+ "[wolbarg:warn] export manifest is missing; skipping manifest-based import validation."
5726
+ );
5727
+ }
5728
+ }
5729
+ if (manifest) {
5730
+ if (expected?.organization && manifest.organization && expected.organization !== manifest.organization) {
5731
+ throw new ValidationError(
5732
+ "Import failed: export organization does not match the current Wolbarg organization."
5733
+ );
5734
+ }
5735
+ if (expected?.embeddingDimensions != null && manifest.embeddingDimensions != null && expected.embeddingDimensions !== manifest.embeddingDimensions) {
5736
+ throw new ValidationError(
5737
+ `Import failed: embedding dimension mismatch (expected ${expected.embeddingDimensions}, got ${manifest.embeddingDimensions}).`
5738
+ );
5739
+ }
5740
+ if (expected?.embeddingModel && manifest.embeddingModel && expected.embeddingModel !== manifest.embeddingModel) {
5741
+ throw new ValidationError(
5742
+ `Import failed: embedding model mismatch (expected ${expected.embeddingModel}, got ${manifest.embeddingModel}).`
5743
+ );
5744
+ }
5250
5745
  }
5251
5746
  const resolvedTarget = resolvePath(targetPath);
5252
5747
  if (resolvedTarget === ":memory:") {
5253
5748
  throw new ConfigurationError("Cannot import into an in-memory database.");
5254
5749
  }
5750
+ const tmpTarget = `${resolvedTarget}.tmp-import-${Date.now()}`;
5751
+ for (const suffix of ["", "-wal", "-shm"]) {
5752
+ const side = `${tmpTarget}${suffix}`;
5753
+ if (fs6__default.default.existsSync(side)) {
5754
+ fs6__default.default.rmSync(side, { force: true });
5755
+ }
5756
+ }
5757
+ await copySqlite(dbExportPath, tmpTarget);
5255
5758
  for (const suffix of ["", "-wal", "-shm"]) {
5256
5759
  const side = `${resolvedTarget}${suffix}`;
5257
5760
  if (fs6__default.default.existsSync(side)) {
5258
5761
  fs6__default.default.rmSync(side, { force: true });
5259
5762
  }
5260
5763
  }
5261
- await copySqlite(dbExportPath, resolvedTarget);
5262
- return { path: resolvedTarget, manifest };
5764
+ fs6__default.default.renameSync(tmpTarget, resolvedTarget);
5765
+ for (const suffix of ["-wal", "-shm"]) {
5766
+ const from = `${tmpTarget}${suffix}`;
5767
+ const to = `${resolvedTarget}${suffix}`;
5768
+ if (fs6__default.default.existsSync(from)) {
5769
+ fs6__default.default.renameSync(from, to);
5770
+ }
5771
+ }
5772
+ return { path: resolvedTarget, manifest: manifest ?? void 0 };
5263
5773
  }
5264
5774
  };
5265
5775
  async function copySqlite(sourcePath, destPath) {
@@ -5488,7 +5998,8 @@ function applyMmr(candidates, topK, lambda) {
5488
5998
  );
5489
5999
  }
5490
6000
  const score = lambda * relevance - (1 - lambda) * maxSim;
5491
- if (score > bestScore) {
6001
+ const best = remaining[bestIdx];
6002
+ if (score > bestScore || score === bestScore && candidate.id < best.id) {
5492
6003
  bestScore = score;
5493
6004
  bestIdx = i;
5494
6005
  }
@@ -5612,6 +6123,9 @@ var SqliteGraphProvider = class {
5612
6123
  concurrency;
5613
6124
  db = null;
5614
6125
  opened = false;
6126
+ /**
6127
+ * @param options - Graph SQLite path and optional concurrency tuning.
6128
+ */
5615
6129
  constructor(options) {
5616
6130
  if (!options?.path || typeof options.path !== "string" || !options.path.trim()) {
5617
6131
  throw new ConfigurationError("sqlite graph requires a non-empty path");
@@ -5619,12 +6133,15 @@ var SqliteGraphProvider = class {
5619
6133
  this.dbPath = path7__default.default.resolve(options.path.trim());
5620
6134
  this.concurrency = resolveConcurrencyConfig(options.concurrency);
5621
6135
  }
6136
+ /** Whether this provider supports file snapshots for checkpoint / export. */
5622
6137
  supportsFileSnapshot() {
5623
6138
  return this.dbPath !== ":memory:";
5624
6139
  }
6140
+ /** Absolute graph database path, or `null` for `:memory:`. */
5625
6141
  getDataPath() {
5626
6142
  return this.dbPath === ":memory:" ? null : this.dbPath;
5627
6143
  }
6144
+ /** Open the graph SQLite file, apply pragmas, and create schema if needed. */
5628
6145
  async open() {
5629
6146
  if (this.opened) return;
5630
6147
  try {
@@ -5661,6 +6178,7 @@ var SqliteGraphProvider = class {
5661
6178
  );
5662
6179
  }
5663
6180
  }
6181
+ /** Close the graph database connection. */
5664
6182
  async close() {
5665
6183
  if (!this.db) {
5666
6184
  this.opened = false;
@@ -5682,6 +6200,14 @@ var SqliteGraphProvider = class {
5682
6200
  this.opened = false;
5683
6201
  }
5684
6202
  }
6203
+ /**
6204
+ * Create or replace a directed memory↔memory edge.
6205
+ *
6206
+ * @param fromId - Source memory UUID.
6207
+ * @param toId - Target memory UUID.
6208
+ * @param relation - Edge label (e.g. `"related_to"`, `"caused_by"`).
6209
+ * @param metadata - Optional JSON metadata stored on the edge.
6210
+ */
5685
6211
  async linkMemories(fromId, toId, relation, metadata) {
5686
6212
  const db = this.requireDb();
5687
6213
  await this.withTx(() => {
@@ -5704,6 +6230,13 @@ var SqliteGraphProvider = class {
5704
6230
  );
5705
6231
  });
5706
6232
  }
6233
+ /**
6234
+ * Remove edges between two memories.
6235
+ *
6236
+ * @param fromId - Source memory UUID.
6237
+ * @param toId - Target memory UUID.
6238
+ * @param relation - When set, only delete edges with this relation label.
6239
+ */
5707
6240
  async unlinkMemories(fromId, toId, relation) {
5708
6241
  const db = this.requireDb();
5709
6242
  await this.withTx(() => {
@@ -5753,6 +6286,12 @@ var SqliteGraphProvider = class {
5753
6286
  }
5754
6287
  return out;
5755
6288
  }
6289
+ /**
6290
+ * Insert or update a named entity node.
6291
+ *
6292
+ * @param entity - Entity name, type, and optional metadata.
6293
+ * @returns Stable entity id ({@link entityIdFrom}).
6294
+ */
5756
6295
  async upsertEntity(entity) {
5757
6296
  const id = entityIdFrom(entity.name, entity.type);
5758
6297
  const db = this.requireDb();
@@ -5777,6 +6316,13 @@ var SqliteGraphProvider = class {
5777
6316
  });
5778
6317
  return id;
5779
6318
  }
6319
+ /**
6320
+ * Link an entity to a memory via a `MENTIONS` edge (not traversed by {@link getRelated}).
6321
+ *
6322
+ * @param entityId - Entity id from {@link upsertEntity}.
6323
+ * @param memoryId - Target memory UUID.
6324
+ * @param role - Optional role string stored on the edge metadata.
6325
+ */
5780
6326
  async linkEntityToMemory(entityId, memoryId, role) {
5781
6327
  const db = this.requireDb();
5782
6328
  await this.withTx(() => {
@@ -5806,6 +6352,11 @@ var SqliteGraphProvider = class {
5806
6352
  );
5807
6353
  });
5808
6354
  }
6355
+ /**
6356
+ * Remove the memory node and incident edges when a memory is hard-deleted.
6357
+ *
6358
+ * @param memoryId - Wolbarg memory UUID.
6359
+ */
5809
6360
  async deleteMemory(memoryId) {
5810
6361
  const db = this.requireDb();
5811
6362
  await this.withTx(() => {
@@ -5825,6 +6376,7 @@ var SqliteGraphProvider = class {
5825
6376
  }
5826
6377
  );
5827
6378
  }
6379
+ /** Lightweight connectivity and schema statistics check. */
5828
6380
  async health() {
5829
6381
  try {
5830
6382
  if (!this.opened || !this.db) {
@@ -5970,6 +6522,9 @@ var Neo4jGraphProvider = class {
5970
6522
  database;
5971
6523
  driver = null;
5972
6524
  opened = false;
6525
+ /**
6526
+ * @param options - Bolt URL, credentials, and optional database name.
6527
+ */
5973
6528
  constructor(options) {
5974
6529
  if (!options?.url?.trim()) {
5975
6530
  throw new ConfigurationError("neo4j graph requires a non-empty url");
@@ -5985,12 +6540,15 @@ var Neo4jGraphProvider = class {
5985
6540
  this.password = options.password;
5986
6541
  this.database = options.database?.trim() || void 0;
5987
6542
  }
6543
+ /** Neo4j does not support local file snapshots for checkpoint pairing. */
5988
6544
  supportsFileSnapshot() {
5989
6545
  return false;
5990
6546
  }
6547
+ /** Always `null` — graph data lives in the remote Neo4j cluster. */
5991
6548
  getDataPath() {
5992
6549
  return null;
5993
6550
  }
6551
+ /** Connect to Neo4j, verify connectivity, and ensure uniqueness constraints. */
5994
6552
  async open() {
5995
6553
  if (this.opened) return;
5996
6554
  let mod;
@@ -6022,6 +6580,7 @@ var Neo4jGraphProvider = class {
6022
6580
  }
6023
6581
  });
6024
6582
  }
6583
+ /** Close the neo4j-driver connection. */
6025
6584
  async close() {
6026
6585
  if (this.driver) {
6027
6586
  await this.driver.close();
@@ -6048,24 +6607,26 @@ var Neo4jGraphProvider = class {
6048
6607
  }
6049
6608
  }
6050
6609
  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;
6610
+ return this.withSession(
6611
+ (session) => this.runOnSession(session, cypher, params)
6612
+ );
6613
+ }
6614
+ async runOnSession(session, cypher, params = {}) {
6615
+ const result = await session.run(cypher, params);
6616
+ return result.records.map((rec) => {
6617
+ try {
6618
+ return rec.toObject();
6619
+ } catch {
6620
+ const obj = {};
6621
+ for (const key of rec.keys) {
6622
+ obj[key] = unwrapNeo4jValue(rec.get(key));
6062
6623
  }
6063
- });
6624
+ return obj;
6625
+ }
6064
6626
  });
6065
6627
  }
6066
- async ensureMemoryNode(id) {
6067
- await this.run(
6068
- `MERGE (m:Memory { id: $id })
6628
+ async ensureMemoryNode(id, session) {
6629
+ const cypher = `MERGE (m:Memory { id: $id })
6069
6630
  ON CREATE SET
6070
6631
  m.organization = '',
6071
6632
  m.agent = '',
@@ -6074,25 +6635,46 @@ var Neo4jGraphProvider = class {
6074
6635
  m.archived = false,
6075
6636
  m.compressed_into = '',
6076
6637
  m.created_at = '',
6077
- m.updated_at = ''`,
6078
- { id }
6079
- );
6638
+ m.updated_at = ''`;
6639
+ if (session) {
6640
+ await this.runOnSession(session, cypher, { id });
6641
+ return;
6642
+ }
6643
+ await this.run(cypher, { id });
6080
6644
  }
6645
+ /**
6646
+ * MERGE a directed `RELATED` relationship between two memory nodes.
6647
+ *
6648
+ * @param fromId - Source memory UUID.
6649
+ * @param toId - Target memory UUID.
6650
+ * @param relation - Relationship label stored on `RELATED.relation`.
6651
+ * @param metadata - Optional JSON metadata on the relationship.
6652
+ */
6081
6653
  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
- );
6654
+ await this.withSession(async (session) => {
6655
+ await this.ensureMemoryNode(fromId, session);
6656
+ await this.ensureMemoryNode(toId, session);
6657
+ await this.runOnSession(
6658
+ session,
6659
+ `MATCH (a:Memory { id: $fromId }), (b:Memory { id: $toId })
6660
+ MERGE (a)-[r:RELATED { relation: $relation }]->(b)
6661
+ SET r.metadata_json = $metadata`,
6662
+ {
6663
+ fromId,
6664
+ toId,
6665
+ relation,
6666
+ metadata: serializeMetadata2(metadata)
6667
+ }
6668
+ );
6669
+ });
6095
6670
  }
6671
+ /**
6672
+ * Delete `RELATED` relationship(s) between two memories.
6673
+ *
6674
+ * @param fromId - Source memory UUID.
6675
+ * @param toId - Target memory UUID.
6676
+ * @param relation - When set, only delete edges with this relation property.
6677
+ */
6096
6678
  async unlinkMemories(fromId, toId, relation) {
6097
6679
  if (relation !== void 0) {
6098
6680
  await this.run(
@@ -6138,6 +6720,12 @@ var Neo4jGraphProvider = class {
6138
6720
  (row) => rowToMemoryRecord(unwrapRow(row))
6139
6721
  );
6140
6722
  }
6723
+ /**
6724
+ * MERGE an `Entity` node keyed by deterministic id.
6725
+ *
6726
+ * @param entity - Entity name, type, and optional metadata.
6727
+ * @returns Stable entity id ({@link entityIdFrom}).
6728
+ */
6141
6729
  async upsertEntity(entity) {
6142
6730
  const id = entityIdFrom(entity.name, entity.type);
6143
6731
  await this.run(
@@ -6152,24 +6740,40 @@ var Neo4jGraphProvider = class {
6152
6740
  );
6153
6741
  return id;
6154
6742
  }
6743
+ /**
6744
+ * MERGE a `MENTIONS` relationship from entity to memory.
6745
+ *
6746
+ * @param entityId - Entity id from {@link upsertEntity}.
6747
+ * @param memoryId - Target memory UUID.
6748
+ * @param role - Optional role string on the relationship.
6749
+ */
6155
6750
  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
- );
6751
+ await this.withSession(async (session) => {
6752
+ await this.ensureMemoryNode(memoryId, session);
6753
+ const found = await this.runOnSession(
6754
+ session,
6755
+ `MATCH (e:Entity { id: $id }) RETURN e.id AS id`,
6756
+ { id: entityId }
6757
+ );
6758
+ if (found.length === 0) {
6759
+ throw new DatabaseError(`Entity not found: ${entityId}`, {
6760
+ operation: "linkEntityToMemory"
6761
+ });
6762
+ }
6763
+ await this.runOnSession(
6764
+ session,
6765
+ `MATCH (e:Entity { id: $entityId }), (m:Memory { id: $memoryId })
6766
+ MERGE (e)-[r:MENTIONS]->(m)
6767
+ SET r.role = $role`,
6768
+ { entityId, memoryId, role: role ?? "" }
6769
+ );
6770
+ });
6172
6771
  }
6772
+ /**
6773
+ * DETACH DELETE the memory node and all incident relationships.
6774
+ *
6775
+ * @param memoryId - Wolbarg memory UUID.
6776
+ */
6173
6777
  async deleteMemory(memoryId) {
6174
6778
  await this.run(
6175
6779
  `MATCH (m:Memory { id: $id })
@@ -6177,9 +6781,17 @@ var Neo4jGraphProvider = class {
6177
6781
  { id: memoryId }
6178
6782
  );
6179
6783
  }
6784
+ /**
6785
+ * Run arbitrary parameterized Cypher (escape hatch for advanced graph queries).
6786
+ *
6787
+ * @param cypher - Cypher query string.
6788
+ * @param params - Bound parameters for the query.
6789
+ * @returns Raw row objects from the driver.
6790
+ */
6180
6791
  async query(cypher, params) {
6181
6792
  return this.run(cypher, params ?? {});
6182
6793
  }
6794
+ /** Verify connectivity and return server metadata when available. */
6183
6795
  async health() {
6184
6796
  try {
6185
6797
  if (!this.opened || !this.driver) {
@@ -6241,6 +6853,21 @@ function assertNonEmpty(value, fieldName) {
6241
6853
  throw new ConfigurationError(`${fieldName} must be a non-empty string`);
6242
6854
  }
6243
6855
  }
6856
+ function assertFiniteNumber2(value, fieldName) {
6857
+ if (typeof value !== "number" || !Number.isFinite(value)) {
6858
+ throw new ConfigurationError(`${fieldName} must be a finite number`);
6859
+ }
6860
+ }
6861
+ function assertNumberMin(value, fieldName, min) {
6862
+ if (!Number.isFinite(value) || value < min) {
6863
+ throw new ConfigurationError(`${fieldName} must be >= ${min}`);
6864
+ }
6865
+ }
6866
+ function assertNumberInRange(value, fieldName, min, max) {
6867
+ if (!Number.isFinite(value) || value < min || value > max) {
6868
+ throw new ConfigurationError(`${fieldName} must be between ${min} and ${max}`);
6869
+ }
6870
+ }
6244
6871
  function assertUrl(value, fieldName) {
6245
6872
  assertNonEmpty(value, fieldName);
6246
6873
  try {
@@ -6287,6 +6914,135 @@ function validateLlmConfig(config) {
6287
6914
  ...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {}
6288
6915
  };
6289
6916
  }
6917
+ function validateRetrievalConfig(config) {
6918
+ if (config === null || typeof config !== "object") {
6919
+ throw new ConfigurationError("retrieval must be an object");
6920
+ }
6921
+ const out = {};
6922
+ if (config.overFetchFactor !== void 0) {
6923
+ assertFiniteNumber2(config.overFetchFactor, "retrieval.overFetchFactor");
6924
+ assertNumberMin(config.overFetchFactor, "retrieval.overFetchFactor", 0);
6925
+ if (config.overFetchFactor <= 0) {
6926
+ throw new ConfigurationError("retrieval.overFetchFactor must be > 0");
6927
+ }
6928
+ out.overFetchFactor = config.overFetchFactor;
6929
+ }
6930
+ if (config.hybrid !== void 0) {
6931
+ if (config.hybrid === null || typeof config.hybrid !== "object") {
6932
+ throw new ConfigurationError("retrieval.hybrid must be an object");
6933
+ }
6934
+ const hybrid = config.hybrid;
6935
+ const outHybrid = {};
6936
+ if (hybrid.semanticWeight !== void 0) {
6937
+ assertFiniteNumber2(hybrid.semanticWeight, "retrieval.hybrid.semanticWeight");
6938
+ if (hybrid.semanticWeight < 0) {
6939
+ throw new ConfigurationError(
6940
+ "retrieval.hybrid.semanticWeight must be >= 0"
6941
+ );
6942
+ }
6943
+ outHybrid.semanticWeight = hybrid.semanticWeight;
6944
+ }
6945
+ if (hybrid.keywordWeight !== void 0) {
6946
+ assertFiniteNumber2(hybrid.keywordWeight, "retrieval.hybrid.keywordWeight");
6947
+ if (hybrid.keywordWeight < 0) {
6948
+ throw new ConfigurationError(
6949
+ "retrieval.hybrid.keywordWeight must be >= 0"
6950
+ );
6951
+ }
6952
+ outHybrid.keywordWeight = hybrid.keywordWeight;
6953
+ }
6954
+ if (Object.keys(outHybrid).length > 0) {
6955
+ out.hybrid = outHybrid;
6956
+ }
6957
+ }
6958
+ if (config.mmr !== void 0) {
6959
+ if (config.mmr === null || typeof config.mmr !== "object") {
6960
+ throw new ConfigurationError("retrieval.mmr must be an object");
6961
+ }
6962
+ const mmr = config.mmr;
6963
+ const outMmr = {};
6964
+ if (mmr.lambda !== void 0) {
6965
+ assertFiniteNumber2(mmr.lambda, "retrieval.mmr.lambda");
6966
+ assertNumberInRange(mmr.lambda, "retrieval.mmr.lambda", 0, 1);
6967
+ outMmr.lambda = mmr.lambda;
6968
+ }
6969
+ if (Object.keys(outMmr).length > 0) {
6970
+ out.mmr = outMmr;
6971
+ }
6972
+ }
6973
+ return out;
6974
+ }
6975
+ function validateMemoryDedupeConfig(config) {
6976
+ if (config === null || typeof config !== "object") {
6977
+ throw new ConfigurationError("memory.dedupe must be an object");
6978
+ }
6979
+ const out = {};
6980
+ if (config.enabled !== void 0) {
6981
+ if (typeof config.enabled !== "boolean") {
6982
+ throw new ConfigurationError("memory.dedupe.enabled must be a boolean");
6983
+ }
6984
+ out.enabled = config.enabled;
6985
+ }
6986
+ if (config.strategy !== void 0) {
6987
+ const allowed = /* @__PURE__ */ new Set(["exact", "near", "exact-or-near"]);
6988
+ if (!allowed.has(config.strategy)) {
6989
+ throw new ConfigurationError(
6990
+ `memory.dedupe.strategy must be one of ${Array.from(allowed).join(", ")}`
6991
+ );
6992
+ }
6993
+ out.strategy = config.strategy;
6994
+ }
6995
+ if (config.nearThreshold !== void 0) {
6996
+ assertFiniteNumber2(config.nearThreshold, "memory.dedupe.nearThreshold");
6997
+ assertNumberInRange(
6998
+ config.nearThreshold,
6999
+ "memory.dedupe.nearThreshold",
7000
+ 0,
7001
+ 1
7002
+ );
7003
+ out.nearThreshold = config.nearThreshold;
7004
+ }
7005
+ if (config.nearCandidateLimit !== void 0) {
7006
+ assertFiniteNumber2(
7007
+ config.nearCandidateLimit,
7008
+ "memory.dedupe.nearCandidateLimit"
7009
+ );
7010
+ assertNumberMin(
7011
+ config.nearCandidateLimit,
7012
+ "memory.dedupe.nearCandidateLimit",
7013
+ 1
7014
+ );
7015
+ out.nearCandidateLimit = config.nearCandidateLimit;
7016
+ }
7017
+ return out;
7018
+ }
7019
+ function validateEmbeddingCacheConfig(config) {
7020
+ if (config === null || typeof config !== "object") {
7021
+ throw new ConfigurationError("embeddingCache must be an object");
7022
+ }
7023
+ const out = {};
7024
+ if (config.enabled !== void 0) {
7025
+ if (typeof config.enabled !== "boolean") {
7026
+ throw new ConfigurationError("embeddingCache.enabled must be a boolean");
7027
+ }
7028
+ out.enabled = config.enabled;
7029
+ }
7030
+ if (config.ttlMs !== void 0) {
7031
+ assertFiniteNumber2(config.ttlMs, "embeddingCache.ttlMs");
7032
+ if (config.ttlMs < 0) {
7033
+ throw new ConfigurationError("embeddingCache.ttlMs must be >= 0");
7034
+ }
7035
+ out.ttlMs = config.ttlMs;
7036
+ }
7037
+ if (config.maxEntries !== void 0) {
7038
+ assertFiniteNumber2(config.maxEntries, "embeddingCache.maxEntries");
7039
+ if (config.maxEntries < 0) {
7040
+ throw new ConfigurationError("embeddingCache.maxEntries must be >= 0");
7041
+ }
7042
+ out.maxEntries = config.maxEntries;
7043
+ }
7044
+ return out;
7045
+ }
6290
7046
  function normalizeDatabaseConfig(config) {
6291
7047
  const provider = config.provider;
6292
7048
  if (provider !== "sqlite" && provider !== "postgres") {
@@ -6396,11 +7152,22 @@ function validateWolbargOptions(options) {
6396
7152
  throw new ConfigurationError("Wolbarg options must be an object");
6397
7153
  }
6398
7154
  assertNonEmpty(options.organization, "organization");
7155
+ const checkpointDirectory = options.checkpointDirectory !== void 0 ? (() => {
7156
+ assertNonEmpty(options.checkpointDirectory, "checkpointDirectory");
7157
+ return options.checkpointDirectory.trim();
7158
+ })() : void 0;
6399
7159
  const storageInput = resolveStorageInput(options);
6400
7160
  let storage = storageInput;
6401
7161
  if (!isStorageProvider(storageInput)) {
6402
7162
  storage = normalizeDatabaseConfig(storageInput);
6403
7163
  }
7164
+ if (!isStorageProvider(storageInput) && storageInput.provider === "postgres" && storageInput.maxPoolSize !== void 0) {
7165
+ assertFiniteNumber2(
7166
+ storageInput.maxPoolSize,
7167
+ "database.maxPoolSize"
7168
+ );
7169
+ assertNumberMin(storageInput.maxPoolSize, "database.maxPoolSize", 1);
7170
+ }
6404
7171
  if (!options.embedding) {
6405
7172
  throw new ConfigurationError("embedding is required");
6406
7173
  }
@@ -6418,13 +7185,20 @@ function validateWolbargOptions(options) {
6418
7185
  if (graph !== void 0) {
6419
7186
  graph = resolveGraphInput(graph);
6420
7187
  }
7188
+ const retrieval = options.retrieval !== void 0 ? validateRetrievalConfig(options.retrieval) : void 0;
7189
+ const embeddingCache = options.embeddingCache !== void 0 ? validateEmbeddingCacheConfig(options.embeddingCache) : void 0;
7190
+ const memory = options.memory !== void 0 && options.memory.dedupe !== void 0 ? { ...options.memory, dedupe: validateMemoryDedupeConfig(options.memory.dedupe) } : options.memory;
6421
7191
  return {
6422
7192
  ...options,
6423
7193
  organization: options.organization.trim(),
6424
7194
  storage,
6425
7195
  database: void 0,
6426
7196
  ...telemetry ? { telemetry } : {},
6427
- ...graph !== void 0 ? { graph } : {}
7197
+ ...graph !== void 0 ? { graph } : {},
7198
+ ...retrieval !== void 0 ? { retrieval } : {},
7199
+ ...embeddingCache !== void 0 ? { embeddingCache } : {},
7200
+ ...checkpointDirectory !== void 0 ? { checkpointDirectory } : {},
7201
+ ...memory !== void 0 ? { memory } : {}
6428
7202
  };
6429
7203
  }
6430
7204
  function resolveGraphInput(input) {
@@ -6483,59 +7257,6 @@ function resolveGraphInput(input) {
6483
7257
  );
6484
7258
  }
6485
7259
 
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
7260
  // src/telemetry/trace.ts
6540
7261
  function createSessionId() {
6541
7262
  return createId();
@@ -6561,21 +7282,32 @@ function createChildTrace(parent) {
6561
7282
  // src/telemetry/emitter.ts
6562
7283
  var NoopTelemetryProvider = class {
6563
7284
  name = "noop";
7285
+ /** @inheritdoc TelemetryProvider.open */
6564
7286
  async open() {
6565
7287
  }
7288
+ /** @inheritdoc TelemetryProvider.close */
6566
7289
  async close() {
6567
7290
  }
7291
+ /** @inheritdoc TelemetryProvider.emit */
6568
7292
  emit(_event) {
6569
7293
  }
7294
+ /** @inheritdoc TelemetryProvider.flush */
6570
7295
  async flush() {
6571
7296
  }
6572
7297
  };
6573
7298
  var TelemetryEmitter = class {
7299
+ /** Stable session id for this Wolbarg instance lifetime. */
6574
7300
  sessionId;
7301
+ /** Structured logger gated by telemetry log level. */
6575
7302
  logger;
6576
7303
  provider;
6577
7304
  config;
6578
7305
  context;
7306
+ /**
7307
+ * @param provider - Telemetry backend, or `null` for {@link NoopTelemetryProvider}.
7308
+ * @param config - Capture flags and log level.
7309
+ * @param context - Default organization injected into events.
7310
+ */
6579
7311
  constructor(provider, config, context = {}) {
6580
7312
  this.sessionId = createSessionId();
6581
7313
  this.provider = provider ?? new NoopTelemetryProvider();
@@ -6591,25 +7323,37 @@ var TelemetryEmitter = class {
6591
7323
  this.context = context;
6592
7324
  this.logger = new WolbargLogger(this.config.level);
6593
7325
  }
7326
+ /** Whether telemetry is enabled and backed by a non-noop provider. */
6594
7327
  get enabled() {
6595
7328
  return this.config.enabled && this.provider.name !== "noop";
6596
7329
  }
7330
+ /** Replace the underlying telemetry provider (e.g. after lazy init). */
6597
7331
  setProvider(provider) {
6598
7332
  this.provider = provider;
6599
7333
  }
7334
+ /** Open the underlying telemetry provider when enabled. */
6600
7335
  async open() {
6601
7336
  if (!this.config.enabled) {
6602
7337
  return;
6603
7338
  }
6604
7339
  await this.provider.open();
6605
7340
  }
7341
+ /** Flush pending events and close the telemetry provider. */
6606
7342
  async close() {
6607
7343
  await this.provider.flush();
6608
7344
  await this.provider.close();
6609
7345
  }
7346
+ /** Flush pending telemetry events without closing the provider. */
6610
7347
  async flush() {
6611
7348
  await this.provider.flush();
6612
7349
  }
7350
+ /**
7351
+ * Begin tracing an operation.
7352
+ *
7353
+ * @param operation - Public telemetry operation name.
7354
+ * @param parent - Optional parent trace for nested spans.
7355
+ * @returns Handle — call `success()` or `failure()` when done.
7356
+ */
6613
7357
  start(operation, parent) {
6614
7358
  const context = parent ? createChildTrace(parent) : createRootTrace(this.sessionId);
6615
7359
  const startedAt = performance.now();
@@ -6620,14 +7364,14 @@ var TelemetryEmitter = class {
6620
7364
  const totalMs = performance.now() - startedAt;
6621
7365
  const errMessage = error instanceof Error ? error.message : error ? String(error) : fields?.error ?? null;
6622
7366
  const errStack = error instanceof Error ? error.stack ?? null : fields?.errorStack ?? null;
7367
+ if (!self.config.enabled) {
7368
+ return;
7369
+ }
6623
7370
  if (status === "error") {
6624
7371
  self.logger.error(`${operation} failed: ${errMessage ?? "unknown"}`);
6625
7372
  } else {
6626
7373
  self.logger.debug(`${operation} completed in ${totalMs.toFixed(2)}ms`);
6627
7374
  }
6628
- if (!self.config.enabled) {
6629
- return;
6630
- }
6631
7375
  const event = {
6632
7376
  id: createId(),
6633
7377
  timestamp: nowIso(),
@@ -6684,10 +7428,12 @@ var TelemetryEmitter = class {
6684
7428
  }
6685
7429
  };
6686
7430
  }
7431
+ /** Emit a startup telemetry event. */
6687
7432
  emitStartup(provider) {
6688
7433
  const trace = this.start("startup");
6689
7434
  trace.success({ provider });
6690
7435
  }
7436
+ /** Emit a shutdown telemetry event. */
6691
7437
  emitShutdown(provider) {
6692
7438
  const trace = this.start("shutdown");
6693
7439
  trace.success({ provider });
@@ -6753,10 +7499,15 @@ var SqliteEventDatabase = class {
6753
7499
  db = null;
6754
7500
  insertStmt = null;
6755
7501
  columns = /* @__PURE__ */ new Set();
7502
+ /**
7503
+ * @param options.url - Path to the telemetry SQLite file.
7504
+ * @param options.readonly - Open read-only (Studio / analytics).
7505
+ */
6756
7506
  constructor(options) {
6757
7507
  this.url = options.url;
6758
7508
  this.readonly = options.readonly ?? false;
6759
7509
  }
7510
+ /** Open the telemetry database and run schema migrations. */
6760
7511
  async open() {
6761
7512
  try {
6762
7513
  const dbPath = this.resolvePath(this.url);
@@ -6811,6 +7562,7 @@ var SqliteEventDatabase = class {
6811
7562
  );
6812
7563
  }
6813
7564
  }
7565
+ /** Close the telemetry database connection. */
6814
7566
  async close() {
6815
7567
  if (!this.db) return;
6816
7568
  try {
@@ -6821,6 +7573,7 @@ var SqliteEventDatabase = class {
6821
7573
  this.columns.clear();
6822
7574
  }
6823
7575
  }
7576
+ /** Insert one normalized telemetry event row. */
6824
7577
  async insertEvent(input) {
6825
7578
  const event = normalizeEvent(input);
6826
7579
  const stmt = this.insertStmt;
@@ -6866,6 +7619,7 @@ var SqliteEventDatabase = class {
6866
7619
  );
6867
7620
  }
6868
7621
  }
7622
+ /** Insert many events in a single transaction. */
6869
7623
  async insertEvents(inputs) {
6870
7624
  const db = this.requireDb();
6871
7625
  const out = [];
@@ -6884,6 +7638,7 @@ var SqliteEventDatabase = class {
6884
7638
  throw error;
6885
7639
  }
6886
7640
  }
7641
+ /** Query telemetry events with filters, sort, and pagination. */
6887
7642
  async query(options) {
6888
7643
  const db = this.requireDb();
6889
7644
  const clauses = [];
@@ -6929,8 +7684,10 @@ var SqliteEventDatabase = class {
6929
7684
  }
6930
7685
  }
6931
7686
  if (options.memoryId) {
6932
- clauses.push(`memory_ids_json LIKE ?`);
6933
- params.push(`%${options.memoryId}%`);
7687
+ clauses.push(
7688
+ `EXISTS (SELECT 1 FROM json_each(memory_ids_json) WHERE json_each.value = ?)`
7689
+ );
7690
+ params.push(options.memoryId);
6934
7691
  }
6935
7692
  if (options.queryText) {
6936
7693
  clauses.push(`query LIKE ?`);
@@ -6962,11 +7719,13 @@ var SqliteEventDatabase = class {
6962
7719
  offset
6963
7720
  };
6964
7721
  }
7722
+ /** Fetch one event by primary key. */
6965
7723
  async getEvent(id) {
6966
7724
  const db = this.requireDb();
6967
7725
  const row = db.prepare(`SELECT * FROM telemetry_events WHERE id = ?`).get(id);
6968
7726
  return row ? rowToEvent(row) : null;
6969
7727
  }
7728
+ /** Count events optionally filtered by time and operation. */
6970
7729
  async countEvents(filter) {
6971
7730
  const db = this.requireDb();
6972
7731
  const clauses = [];
@@ -7107,6 +7866,7 @@ function describe2(error) {
7107
7866
  }
7108
7867
 
7109
7868
  // src/providers/sqlite/sqliteTelemetryProvider.ts
7869
+ var MAX_QUEUE_SIZE = 1e4;
7110
7870
  var SqliteTelemetryProvider = class {
7111
7871
  name = "sqlite";
7112
7872
  db;
@@ -7114,9 +7874,15 @@ var SqliteTelemetryProvider = class {
7114
7874
  flushing = null;
7115
7875
  closed = false;
7116
7876
  openPromise = null;
7877
+ warnedQueueDrop = false;
7878
+ warnedFlushFailure = false;
7879
+ /**
7880
+ * @param options.url - Path to the independent telemetry SQLite database file.
7881
+ */
7117
7882
  constructor(options) {
7118
7883
  this.db = new SqliteEventDatabase({ url: options.url });
7119
7884
  }
7885
+ /** Open the telemetry EventDatabase (lazy — also invoked on first emit). */
7120
7886
  async open() {
7121
7887
  if (!this.openPromise) {
7122
7888
  this.openPromise = this.db.open();
@@ -7124,19 +7890,35 @@ var SqliteTelemetryProvider = class {
7124
7890
  await this.openPromise;
7125
7891
  this.closed = false;
7126
7892
  }
7893
+ /** Flush pending events and close the telemetry database. */
7127
7894
  async close() {
7128
7895
  this.closed = true;
7129
7896
  await this.flush();
7130
7897
  await this.db.close();
7131
7898
  this.openPromise = null;
7132
7899
  }
7900
+ /**
7901
+ * Enqueue a telemetry event for async persistence (never throws).
7902
+ *
7903
+ * @param event - Partial or complete {@link TelemetryEventInput}.
7904
+ */
7133
7905
  emit(event) {
7134
7906
  if (this.closed) {
7135
7907
  return;
7136
7908
  }
7909
+ if (this.queue.length >= MAX_QUEUE_SIZE) {
7910
+ this.queue.shift();
7911
+ if (!this.warnedQueueDrop) {
7912
+ this.warnedQueueDrop = true;
7913
+ console.warn(
7914
+ `[wolbarg telemetry] event queue capped at ${MAX_QUEUE_SIZE}; dropping oldest events`
7915
+ );
7916
+ }
7917
+ }
7137
7918
  this.queue.push(event);
7138
7919
  void this.scheduleFlush();
7139
7920
  }
7921
+ /** Block until all queued events are written (or dropped on failure). */
7140
7922
  async flush() {
7141
7923
  while (this.queue.length > 0 || this.flushing) {
7142
7924
  await this.scheduleFlush();
@@ -7145,10 +7927,12 @@ var SqliteTelemetryProvider = class {
7145
7927
  }
7146
7928
  }
7147
7929
  }
7930
+ /** Query persisted telemetry events with filters and pagination. */
7148
7931
  async query(options) {
7149
7932
  await this.open();
7150
7933
  return this.db.query(options);
7151
7934
  }
7935
+ /** Fetch a single telemetry event by id. */
7152
7936
  async getEvent(id) {
7153
7937
  await this.open();
7154
7938
  return this.db.getEvent(id);
@@ -7176,7 +7960,13 @@ var SqliteTelemetryProvider = class {
7176
7960
  await this.db.insertEvent(event);
7177
7961
  }
7178
7962
  }
7179
- } catch {
7963
+ } catch (error) {
7964
+ if (!this.warnedFlushFailure) {
7965
+ this.warnedFlushFailure = true;
7966
+ console.warn(
7967
+ `[wolbarg telemetry] flush failed; dropping batch: ${error instanceof Error ? error.message : String(error)}`
7968
+ );
7969
+ }
7180
7970
  }
7181
7971
  }
7182
7972
  };
@@ -7184,16 +7974,28 @@ var SqliteCheckpointProvider = class {
7184
7974
  name = "sqlite";
7185
7975
  directory;
7186
7976
  ready = false;
7977
+ /**
7978
+ * @param options.directory - Folder for `.json` metadata and `.db` snapshot files.
7979
+ */
7187
7980
  constructor(options) {
7188
7981
  this.directory = options?.directory ?? path7__default.default.resolve(process.cwd(), ".wolbarg", "checkpoints");
7189
7982
  }
7983
+ /** Ensure the checkpoint directory exists. */
7190
7984
  async open() {
7191
7985
  fs6__default.default.mkdirSync(this.directory, { recursive: true });
7192
7986
  this.ready = true;
7193
7987
  }
7988
+ /** Mark the provider closed (does not delete snapshots). */
7194
7989
  async close() {
7195
7990
  this.ready = false;
7196
7991
  }
7992
+ /**
7993
+ * Create an immutable SQLite backup of `sourcePath` under a unique name.
7994
+ *
7995
+ * @param name - Checkpoint label (must not already exist).
7996
+ * @param sourcePath - Live memory database path to snapshot.
7997
+ * @param options.description - Optional human-readable description stored in metadata.
7998
+ */
7197
7999
  async checkpoint(name, sourcePath, options) {
7198
8000
  this.requireReady();
7199
8001
  assertCheckpointName(name);
@@ -7219,8 +8021,20 @@ Ensure Wolbarg has been initialized and the database path is correct.`
7219
8021
  );
7220
8022
  }
7221
8023
  const snapshotPath = this.snapshotPath(name);
7222
- await safeSqliteBackup(resolvedSource, snapshotPath);
7223
- const stats = fs6__default.default.statSync(snapshotPath);
8024
+ const tmpSnapshotPath = `${snapshotPath}.tmp-${Date.now()}`;
8025
+ try {
8026
+ await safeSqliteBackup(resolvedSource, tmpSnapshotPath);
8027
+ } catch (error) {
8028
+ if (fs6__default.default.existsSync(tmpSnapshotPath)) {
8029
+ fs6__default.default.rmSync(tmpSnapshotPath, { force: true });
8030
+ }
8031
+ throw error;
8032
+ }
8033
+ const stats = fs6__default.default.statSync(tmpSnapshotPath);
8034
+ if (fs6__default.default.existsSync(snapshotPath)) {
8035
+ fs6__default.default.rmSync(snapshotPath, { force: true });
8036
+ }
8037
+ fs6__default.default.renameSync(tmpSnapshotPath, snapshotPath);
7224
8038
  const meta2 = {
7225
8039
  name,
7226
8040
  description: options?.description ?? null,
@@ -7231,9 +8045,17 @@ Ensure Wolbarg has been initialized and the database path is correct.`
7231
8045
  sourcePath: resolvedSource,
7232
8046
  sizeBytes: stats.size
7233
8047
  };
7234
- fs6__default.default.writeFileSync(metaPath, JSON.stringify(meta2, null, 2), "utf8");
8048
+ const tmpMetaPath = `${metaPath}.tmp-${Date.now()}`;
8049
+ fs6__default.default.writeFileSync(tmpMetaPath, JSON.stringify(meta2, null, 2), "utf8");
8050
+ fs6__default.default.renameSync(tmpMetaPath, metaPath);
7235
8051
  return meta2;
7236
8052
  }
8053
+ /**
8054
+ * Restore a named checkpoint over `targetPath` (replaces WAL/SHM side files).
8055
+ *
8056
+ * @param name - Existing checkpoint name.
8057
+ * @param targetPath - Live memory database path to overwrite.
8058
+ */
7237
8059
  async rollback(name, targetPath) {
7238
8060
  this.requireReady();
7239
8061
  const meta2 = await this.getCheckpoint(name);
@@ -7256,6 +8078,7 @@ Ensure Wolbarg has been initialized and the database path is correct.`
7256
8078
  await safeSqliteBackup(meta2.snapshotPath, resolvedTarget);
7257
8079
  return meta2;
7258
8080
  }
8081
+ /** Delete checkpoint metadata and snapshot files for `name`. */
7259
8082
  async deleteCheckpoint(name) {
7260
8083
  this.requireReady();
7261
8084
  const metaPath = this.metaPath(name);
@@ -7271,6 +8094,7 @@ Ensure Wolbarg has been initialized and the database path is correct.`
7271
8094
  }
7272
8095
  return removed;
7273
8096
  }
8097
+ /** List all checkpoints sorted by creation time. */
7274
8098
  async listCheckpoints() {
7275
8099
  this.requireReady();
7276
8100
  if (!fs6__default.default.existsSync(this.directory)) {
@@ -7287,6 +8111,7 @@ Ensure Wolbarg has been initialized and the database path is correct.`
7287
8111
  }
7288
8112
  return out.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
7289
8113
  }
8114
+ /** Load checkpoint metadata by name, or `null` if missing / corrupt. */
7290
8115
  async getCheckpoint(name) {
7291
8116
  this.requireReady();
7292
8117
  const metaPath = this.metaPath(name);
@@ -7471,6 +8296,13 @@ var SqliteSubscribeEmitter = class {
7471
8296
  }
7472
8297
  });
7473
8298
  }
8299
+ /**
8300
+ * Register a filtered callback for in-process memory change events.
8301
+ *
8302
+ * @param filter - Organization / agent / event-type filter.
8303
+ * @param callback - Invoked synchronously on each matching emit.
8304
+ * @returns Unsubscribe function.
8305
+ */
7474
8306
  subscribe(filter, callback) {
7475
8307
  if (this.closed) {
7476
8308
  throw new Error("Subscribe backend is closed");
@@ -7481,12 +8313,14 @@ var SqliteSubscribeEmitter = class {
7481
8313
  this.subscriptions.delete(id);
7482
8314
  };
7483
8315
  }
8316
+ /** Emit a memory change event to registered subscribers. */
7484
8317
  emit(event) {
7485
8318
  if (this.closed) {
7486
8319
  return;
7487
8320
  }
7488
8321
  this.emitter.emit("change", event);
7489
8322
  }
8323
+ /** Remove all listeners and mark the emitter closed. */
7490
8324
  async close() {
7491
8325
  this.closed = true;
7492
8326
  this.subscriptions.clear();
@@ -7495,6 +8329,9 @@ var SqliteSubscribeEmitter = class {
7495
8329
  };
7496
8330
 
7497
8331
  // src/core/wolbarg.ts
8332
+ var warnLogger3 = new WolbargLogger("warn");
8333
+ var warnedHybridNoKeyword = false;
8334
+ var warnedRerankNoProvider = false;
7498
8335
  var Wolbarg = class {
7499
8336
  initialized = false;
7500
8337
  booting = null;
@@ -7521,6 +8358,20 @@ var Wolbarg = class {
7521
8358
  memoryDedupe = resolveMemoryDedupeConfig();
7522
8359
  embeddingCacheConfig = resolveEmbeddingCacheConfig();
7523
8360
  rawEmbedding = null;
8361
+ /**
8362
+ * @param options - Full {@link WolbargOptions}. Key fields:
8363
+ * - `organization` — namespace isolating memories in a shared DB
8364
+ * - `database` / `storage` — SQLite/Postgres config **or** a custom
8365
+ * {@link StorageProvider} instance
8366
+ * - `embedding` — {@link EmbeddingProvider} instance **or**
8367
+ * {@link EmbeddingConfig} / {@link openaiEmbedding} (etc.)
8368
+ * - `llm` — optional {@link LlmProvider} or {@link LlmConfig} / {@link openaiLlm};
8369
+ * required for {@link Wolbarg.compress} and extract-mode
8370
+ * {@link Wolbarg.rememberFromMessages}
8371
+ * - `graph`, `telemetry`, `reranker`, `keywordSearch`, `ocr`, `vision`,
8372
+ * `compression`, `chunking`, `retrieval`, `concurrency`, `embeddingCache`,
8373
+ * `memory.dedupe` — optional plugins / tuning
8374
+ */
7524
8375
  constructor(options) {
7525
8376
  this.telemetry = new TelemetryEmitter(null, { enabled: false, level: "off" });
7526
8377
  if (!options) {
@@ -7579,6 +8430,12 @@ var Wolbarg = class {
7579
8430
  }
7580
8431
  /**
7581
8432
  * Backwards-compatible initialization (v0.1 API).
8433
+ * Prefer constructing with options + {@link Wolbarg.ready} instead.
8434
+ *
8435
+ * @param options - Organization, database, embedding, and optional LLM config.
8436
+ * @returns Resolves when storage is open and the vector schema is ready.
8437
+ * @throws {InitializationError} If already initialized.
8438
+ * @throws {ConfigurationError} | {ValidationError} On invalid options.
7582
8439
  */
7583
8440
  async init(options) {
7584
8441
  if (this.initialized || this.storage) {
@@ -7600,6 +8457,7 @@ var Wolbarg = class {
7600
8457
  this.storage = createStorageProvider(validated.database);
7601
8458
  this.memoryDbPath = resolveDatabaseUrl(validated.database);
7602
8459
  this.embedding = createEmbeddingProvider(validated.embedding);
8460
+ this.rawEmbedding = this.embedding;
7603
8461
  if (validated.llm) {
7604
8462
  this.llm = createLlmProvider(validated.llm);
7605
8463
  this.compression = createCompressionProvider(this.llm);
@@ -7609,7 +8467,14 @@ var Wolbarg = class {
7609
8467
  }
7610
8468
  await this.ready();
7611
8469
  }
7612
- /** Ensure storage (and optional telemetry) are open. */
8470
+ /**
8471
+ * Open storage, optional telemetry / checkpoint / graph providers, wrap the
8472
+ * embedding cache, and probe embedding dimensions. Safe to call multiple times;
8473
+ * concurrent callers share one boot promise.
8474
+ *
8475
+ * @returns Resolves when the instance is ready for remember/recall.
8476
+ * @throws {InitializationError} If neither constructor options nor {@link init} were used.
8477
+ */
7613
8478
  async ready() {
7614
8479
  if (this.initialized) {
7615
8480
  return;
@@ -7625,6 +8490,7 @@ var Wolbarg = class {
7625
8490
  this.booting = null;
7626
8491
  }
7627
8492
  }
8493
+ /** Internal boot sequence invoked by {@link Wolbarg.ready}. */
7628
8494
  async boot() {
7629
8495
  if (!this.storage || !this.embedding || !this.organization) {
7630
8496
  throw new InitializationError(
@@ -7700,7 +8566,17 @@ var Wolbarg = class {
7700
8566
  );
7701
8567
  }
7702
8568
  }
7703
- /** Store a semantic memory for an agent (may upsert when dedupe is enabled). */
8569
+ /**
8570
+ * Store a semantic memory for an agent. Embeds `content.text`, writes the row,
8571
+ * and may **upsert** an existing active memory when dedupe is enabled
8572
+ * (constructor `memory.dedupe` or per-call `options.dedupe`).
8573
+ *
8574
+ * @param options - Agent id, text content, optional metadata / dedupe overrides.
8575
+ * See {@link RememberOptions}.
8576
+ * @returns The stored record plus `action` (`"created"` | `"updated"`).
8577
+ * @throws {ValidationError} On empty agent/content.
8578
+ * @throws {InitializationError} If not configured / ready.
8579
+ */
7704
8580
  async remember(options) {
7705
8581
  const trace = this.telemetry.start("remember");
7706
8582
  try {
@@ -7722,7 +8598,15 @@ var Wolbarg = class {
7722
8598
  throw wrapOperationError("remember", error);
7723
8599
  }
7724
8600
  }
7725
- /** Batch remember — sequential when dedupe enabled; otherwise one TX batch. */
8601
+ /**
8602
+ * Remember many memories in one call. Runs sequentially when any item (or the
8603
+ * global config) enables dedupe; otherwise embeds in batch and inserts in one
8604
+ * transaction for throughput.
8605
+ *
8606
+ * @param items - Non-empty list of {@link RememberOptions}.
8607
+ * @returns One {@link RememberResult} per input, same order.
8608
+ * @throws {ValidationError} If `items` is empty or an entry is invalid.
8609
+ */
7726
8610
  async rememberBatch(items) {
7727
8611
  const parent = this.telemetry.start("rememberBatch");
7728
8612
  try {
@@ -7848,7 +8732,15 @@ var Wolbarg = class {
7848
8732
  * **Experimental** until 1.0 — API shape may change.
7849
8733
  *
7850
8734
  * - `mode: "raw"` (default) — remember user message text (no LLM).
7851
- * - `mode: "extract"` — require configured `llm`; extract atomic facts then remember each.
8735
+ * - `mode: "extract"` — requires configured `llm`; extracts atomic facts then
8736
+ * remembers each. Pass a custom {@link LlmProvider} or {@link openaiLlm}
8737
+ * in the constructor.
8738
+ *
8739
+ * @param messages - Conversation turns (`role` + `content`).
8740
+ * @param options - Agent, mode, optional metadata/dedupe. See
8741
+ * {@link RememberFromMessagesOptions}.
8742
+ * @returns One {@link RememberResult} per remembered fact/message.
8743
+ * @throws {ProviderNotConfiguredError} When `mode: "extract"` but no `llm` was configured.
7852
8744
  */
7853
8745
  async rememberFromMessages(messages, options) {
7854
8746
  const parent = this.telemetry.start("rememberFromMessages");
@@ -7918,10 +8810,17 @@ var Wolbarg = class {
7918
8810
  }
7919
8811
  }
7920
8812
  /**
7921
- * Update an existing memory by id (re-embeds when content changes).
8813
+ * Update an existing memory by id. Re-embeds when `content` changes; merges
8814
+ * metadata when provided.
8815
+ *
8816
+ * @param options.id - Memory id to update.
8817
+ * @param options.content - Optional new `{ text }` (triggers re-embed + content hash).
8818
+ * @param options.metadata - Optional metadata merged onto the existing record.
8819
+ * @returns Updated record with `action: "updated"`.
8820
+ * @throws {MemoryNotFoundError} If the id is unknown in this organization.
7922
8821
  */
7923
8822
  async update(options) {
7924
- const trace = this.telemetry.start("remember");
8823
+ const trace = this.telemetry.start("update");
7925
8824
  try {
7926
8825
  const { storage, embedding, organization } = await this.requireReady();
7927
8826
  assertNonEmptyString(options.id, "id");
@@ -7988,10 +8887,18 @@ var Wolbarg = class {
7988
8887
  }
7989
8888
  }
7990
8889
  /**
7991
- * Subscribe to memory change events.
8890
+ * Subscribe to memory change events (remember / update / forget / compress / clear).
7992
8891
  *
7993
8892
  * **SQLite:** delivers events only within this Node.js process.
7994
8893
  * A second process writing the same `memory.db` will not notify subscribers here.
8894
+ *
8895
+ * **Postgres:** uses LISTEN/NOTIFY across processes (lazy connection on first subscribe).
8896
+ *
8897
+ * @param filter - Scope by `organization` (defaults to this instance’s org),
8898
+ * optional `agent`, and optional `events` allow-list. See {@link SubscribeFilter}.
8899
+ * @param callback - Invoked with each {@link MemoryChangeEvent}.
8900
+ * @returns {@link Unsubscribe} function — call to stop receiving events.
8901
+ * @throws {ValidationError} If organization cannot be resolved.
7995
8902
  */
7996
8903
  subscribe(filter, callback) {
7997
8904
  const org = filter.organization || this.organization;
@@ -8020,6 +8927,7 @@ var Wolbarg = class {
8020
8927
  }
8021
8928
  return this.subscribeBackend.subscribe(normalized, callback);
8022
8929
  }
8930
+ /** Broadcast a memory change event to in-process or Postgres NOTIFY subscribers. */
8023
8931
  emitChange(event) {
8024
8932
  try {
8025
8933
  if (this.storage instanceof PostgresStorageProvider) {
@@ -8245,6 +9153,17 @@ var Wolbarg = class {
8245
9153
  throw error;
8246
9154
  }
8247
9155
  }
9156
+ /**
9157
+ * Find the best active near-duplicate memory for upsert (cosine similarity ≥ threshold).
9158
+ *
9159
+ * @param storage - Open storage provider.
9160
+ * @param organization - Org namespace.
9161
+ * @param agent - Agent scope for the search.
9162
+ * @param vector - Embedding of the candidate text.
9163
+ * @param threshold - Minimum similarity (0–1) to treat as a duplicate.
9164
+ * @param limit - Max vector candidates to inspect.
9165
+ * @returns Matching memory row, or `null` if none qualify.
9166
+ */
8248
9167
  async findNearDuplicate(storage, organization, agent, vector, threshold, limit) {
8249
9168
  if (typeof storage.searchVectorsWithMemories === "function") {
8250
9169
  const hits2 = await storage.searchVectorsWithMemories(
@@ -8447,17 +9366,37 @@ var Wolbarg = class {
8447
9366
  }
8448
9367
  }
8449
9368
  }
9369
+ if (hybridWeights && keywordSignal === "disabled" && !warnedHybridNoKeyword) {
9370
+ warnedHybridNoKeyword = true;
9371
+ warnLogger3.warn(
9372
+ "hybrid search requested but no keyword channel is available; using semantic-only results."
9373
+ );
9374
+ }
8450
9375
  const tRank = performance.now();
8451
- let results = [...byId.values()].sort(
8452
- (a, b) => b.similarity - a.similarity
8453
- );
9376
+ let results = [...byId.values()].sort((a, b) => {
9377
+ const diff = b.similarity - a.similarity;
9378
+ if (diff !== 0) return diff;
9379
+ return a.id.localeCompare(b.id);
9380
+ });
8454
9381
  const mmrLambda = resolveMmr(options.mmr) ?? resolveMmr(
8455
9382
  this.retrievalConfig.mmr ? { lambda: this.retrievalConfig.mmr.lambda } : void 0
8456
9383
  );
8457
9384
  let rankingReason = "cosine similarity";
9385
+ let mmrApplied = false;
8458
9386
  if (mmrLambda !== null) {
8459
- results = applyMmr(results, Math.max(topK * 2, topK), mmrLambda);
8460
- rankingReason = `MMR(lambda=${mmrLambda})`;
9387
+ mmrApplied = results.length > topK;
9388
+ if (mmrApplied) {
9389
+ results = applyMmr(results, topK, mmrLambda);
9390
+ rankingReason = `MMR(lambda=${mmrLambda})`;
9391
+ } else {
9392
+ rankingReason = `MMR(lambda=${mmrLambda})(skipped:insufficient_candidates)`;
9393
+ }
9394
+ }
9395
+ if (options.rerank === true && !this.reranker && !warnedRerankNoProvider) {
9396
+ warnedRerankNoProvider = true;
9397
+ warnLogger3.warn(
9398
+ "rerank is enabled but no reranker provider is configured; using identity order."
9399
+ );
8461
9400
  }
8462
9401
  if (options.rerank && this.reranker) {
8463
9402
  const reranked = await this.reranker.rerank(
@@ -8492,14 +9431,16 @@ var Wolbarg = class {
8492
9431
  agentId: options.filter?.agent ?? commonAgent(serialized.map((result) => result.agent)),
8493
9432
  tags: commonTags(serialized.map((result) => result.metadata))
8494
9433
  };
8495
- if (!explain) {
8496
- if (options.includeGraph === true && this.graph) {
8497
- const tGraph = performance.now();
8498
- for (const hit of serialized) {
9434
+ if (options.includeGraph === true && this.graph) {
9435
+ const tGraph = performance.now();
9436
+ await Promise.all(
9437
+ serialized.map(async (hit) => {
8499
9438
  hit.related = await this.hydrateRelated(hit.id);
8500
- }
8501
- trace.mark("databaseReadMs", performance.now() - tGraph);
8502
- }
9439
+ })
9440
+ );
9441
+ trace.mark("databaseReadMs", performance.now() - tGraph);
9442
+ }
9443
+ if (!explain) {
8503
9444
  trace.success(telemetryFields);
8504
9445
  return serialized;
8505
9446
  }
@@ -8528,7 +9469,7 @@ var Wolbarg = class {
8528
9469
  semantic: "enabled",
8529
9470
  keyword: keywordSignal,
8530
9471
  reranker: options.rerank === true && this.reranker ? "enabled" : "disabled",
8531
- mmr: mmrLambda === null ? "disabled" : "enabled",
9472
+ mmr: mmrLambda === null ? "disabled" : mmrApplied ? "enabled" : "disabled",
8532
9473
  recency: "disabled"
8533
9474
  },
8534
9475
  results: explanations.map((hit) => ({
@@ -8560,7 +9501,13 @@ var Wolbarg = class {
8560
9501
  throw wrapOperationError("recall", error);
8561
9502
  }
8562
9503
  }
8563
- /** Batch recall — parent event + child traces. */
9504
+ /**
9505
+ * Run multiple recalls sequentially under one parent telemetry span.
9506
+ *
9507
+ * @param queries - Non-empty list of recall options (`explain` is forced off).
9508
+ * @returns One hit array per query, same order.
9509
+ * @throws {ValidationError} If `queries` is empty.
9510
+ */
8564
9511
  async recallBatch(queries) {
8565
9512
  const parent = this.telemetry.start("recallBatch");
8566
9513
  try {
@@ -8594,10 +9541,21 @@ var Wolbarg = class {
8594
9541
  throw wrapOperationError("recallBatch", error);
8595
9542
  }
8596
9543
  }
9544
+ /**
9545
+ * Compress the newest active memories for an agent into one summary memory
9546
+ * and archive the sources. Requires an `llm` (or custom `compression`) at
9547
+ * construction — typed only on `Wolbarg<true>`.
9548
+ *
9549
+ * @param options - `agent` required; optional `limit` (default 50, min 2).
9550
+ * See {@link CompressOptions}.
9551
+ * @returns Summary record plus archived source ids. See {@link CompressResult}.
9552
+ * @throws {ProviderNotConfiguredError} If no LLM/compression provider is set.
9553
+ * @throws {ValidationError} If fewer than 2 active memories exist for the agent.
9554
+ */
8597
9555
  async compress(options) {
8598
9556
  return this.runCompress(options);
8599
9557
  }
8600
- /** @internal */
9558
+ /** @internal Runs compress for both typed and untyped call sites. */
8601
9559
  async runCompress(options) {
8602
9560
  const trace = this.telemetry.start("compress");
8603
9561
  try {
@@ -8637,31 +9595,34 @@ var Wolbarg = class {
8637
9595
  const timestamp = nowIso();
8638
9596
  const result = await this.withWriteLock(async () => {
8639
9597
  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
9598
+ const txResult = await storage.withTransaction(async () => {
9599
+ const summaryRow = await storage.insertMemory({
9600
+ id: summaryId,
9601
+ organization,
9602
+ agent: options.agent.trim(),
9603
+ contentText: summaryText,
9604
+ metadata: {
9605
+ compressed: true,
9606
+ sourceCount: records.length,
9607
+ sourceIds: records.map((r) => r.id)
9608
+ },
9609
+ embedding: vector,
9610
+ createdAt: timestamp,
9611
+ updatedAt: timestamp
9612
+ });
9613
+ const archivedIds = await storage.archiveMemories(
9614
+ records.map((r) => r.id),
9615
+ organization,
9616
+ summaryId,
9617
+ timestamp
9618
+ );
9619
+ return {
9620
+ summary: toMemoryRecord(summaryRow),
9621
+ archivedIds
9622
+ };
8653
9623
  });
8654
- const archivedIds = await storage.archiveMemories(
8655
- records.map((r) => r.id),
8656
- organization,
8657
- summaryId,
8658
- timestamp
8659
- );
8660
9624
  trace.mark("databaseWriteMs", performance.now() - tWrite);
8661
- return {
8662
- summary: toMemoryRecord(summaryRow),
8663
- archivedIds
8664
- };
9625
+ return txResult;
8665
9626
  });
8666
9627
  trace.success({
8667
9628
  provider: storage.name,
@@ -8683,6 +9644,16 @@ var Wolbarg = class {
8683
9644
  throw wrapOperationError("compress", error);
8684
9645
  }
8685
9646
  }
9647
+ /**
9648
+ * Ingest a document (path, URL, Buffer, or text), chunk it, embed chunks, and
9649
+ * store each as a memory. Images use optional `ocr` / `vision` providers from
9650
+ * the constructor.
9651
+ *
9652
+ * @param options - `agent`, `source`, optional chunking overrides / metadata.
9653
+ * See {@link IngestOptions}.
9654
+ * @returns Counts and created memory ids. See {@link IngestResult}.
9655
+ * @throws {ValidationError} If no text can be extracted or chunking yields zero chunks.
9656
+ */
8686
9657
  async ingest(options) {
8687
9658
  const trace = this.telemetry.start("ingest");
8688
9659
  try {
@@ -8853,8 +9824,14 @@ var Wolbarg = class {
8853
9824
  }
8854
9825
  }
8855
9826
  /**
8856
- * Link two memories in the optional graph layer.
8857
- * Throws {@link ProviderNotConfiguredError} when no graph provider is set.
9827
+ * Link two memories in the optional graph layer with a typed relation edge.
9828
+ *
9829
+ * @param fromId - Source memory id.
9830
+ * @param toId - Target memory id.
9831
+ * @param relation - Edge label (e.g. `"supports"`, `"caused_by"`).
9832
+ * @param metadata - Optional edge properties stored on the graph provider.
9833
+ * @throws {ProviderNotConfiguredError} When no `graph` was configured.
9834
+ * @throws {ValidationError} On empty ids/relation.
8858
9835
  */
8859
9836
  async linkMemories(fromId, toId, relation, metadata) {
8860
9837
  const trace = this.telemetry.start("linkMemories");
@@ -8884,8 +9861,13 @@ var Wolbarg = class {
8884
9861
  }
8885
9862
  /**
8886
9863
  * Traverse related memories via the optional graph layer.
8887
- * Throws {@link ProviderNotConfiguredError} when no graph provider is set.
8888
- * Results are re-hydrated from SQL storage when available.
9864
+ * Neighbor stubs from the graph are re-hydrated from SQL storage when available.
9865
+ *
9866
+ * @param memoryId - Seed memory id.
9867
+ * @param options - Optional `relation`, `direction`, `depth`, `limit`.
9868
+ * See {@link GetRelatedOptions}.
9869
+ * @returns Related {@link MemoryRecord} list (may be empty).
9870
+ * @throws {ProviderNotConfiguredError} When no `graph` was configured.
8889
9871
  */
8890
9872
  async getRelated(memoryId, options) {
8891
9873
  const trace = this.telemetry.start("getRelated");
@@ -8893,8 +9875,23 @@ var Wolbarg = class {
8893
9875
  await this.requireReady();
8894
9876
  const graph = this.requireGraph("getRelated");
8895
9877
  assertNonEmptyString(memoryId, "memoryId");
9878
+ let validatedOptions = options;
9879
+ if (options?.depth !== void 0) {
9880
+ if (!Number.isFinite(options.depth)) {
9881
+ throw new ConfigurationError("graph.depth must be a finite number");
9882
+ }
9883
+ if (options.depth < 1) {
9884
+ throw new ConfigurationError("graph.depth must be >= 1");
9885
+ }
9886
+ if (options.depth > 16) {
9887
+ validatedOptions = { ...options, depth: 16 };
9888
+ }
9889
+ }
8896
9890
  const tGraph = performance.now();
8897
- const related = await this.hydrateRelated(memoryId.trim(), options);
9891
+ const related = await this.hydrateRelated(
9892
+ memoryId.trim(),
9893
+ validatedOptions
9894
+ );
8898
9895
  trace.mark("databaseReadMs", performance.now() - tGraph);
8899
9896
  trace.success({
8900
9897
  provider: graph.name,
@@ -8907,6 +9904,14 @@ var Wolbarg = class {
8907
9904
  throw wrapOperationError("getRelated", error);
8908
9905
  }
8909
9906
  }
9907
+ /**
9908
+ * Delete memories by id or by agent filter. Cascades graph nodes/edges when
9909
+ * a graph provider is configured.
9910
+ *
9911
+ * @param options - Either `{ id }` or `{ filter: { agent } }`. See {@link ForgetOptions}.
9912
+ * @returns Number of memories deleted.
9913
+ * @throws {ValidationError} If neither `id` nor `filter.agent` is provided.
9914
+ */
8910
9915
  async forget(options) {
8911
9916
  const trace = this.telemetry.start("forget");
8912
9917
  try {
@@ -8971,6 +9976,13 @@ var Wolbarg = class {
8971
9976
  throw wrapOperationError("forget", error);
8972
9977
  }
8973
9978
  }
9979
+ /**
9980
+ * Return the audit/history timeline for a single memory.
9981
+ *
9982
+ * @param options - `{ id }` of the memory. See {@link HistoryOptions}.
9983
+ * @returns Memory record plus ordered {@link HistoryEvent} list.
9984
+ * @throws {MemoryNotFoundError} If the id is unknown.
9985
+ */
8974
9986
  async history(options) {
8975
9987
  const trace = this.telemetry.start("history");
8976
9988
  try {
@@ -8998,6 +10010,11 @@ var Wolbarg = class {
8998
10010
  throw wrapOperationError("history", error);
8999
10011
  }
9000
10012
  }
10013
+ /**
10014
+ * Aggregate counts for this organization (active / archived / agents / etc.).
10015
+ *
10016
+ * @returns {@link StatsResult} snapshot.
10017
+ */
9001
10018
  async stats() {
9002
10019
  const trace = this.telemetry.start("stats");
9003
10020
  try {
@@ -9025,6 +10042,13 @@ var Wolbarg = class {
9025
10042
  throw wrapOperationError("stats", error);
9026
10043
  }
9027
10044
  }
10045
+ /**
10046
+ * Delete all memories for this organization (optionally scoped by agent).
10047
+ * Cascades graph cleanup when a graph provider is configured.
10048
+ *
10049
+ * @param options - Optional `{ agent }` scope. See {@link ClearOptions}.
10050
+ * @returns Number of memories deleted.
10051
+ */
9028
10052
  async clear(options) {
9029
10053
  const trace = this.telemetry.start("clear");
9030
10054
  try {
@@ -9053,7 +10077,16 @@ var Wolbarg = class {
9053
10077
  throw wrapOperationError("clear", error);
9054
10078
  }
9055
10079
  }
9056
- /** Create an immutable named checkpoint of the memory database. */
10080
+ /**
10081
+ * Create an immutable named checkpoint of the file-backed SQLite memory DB
10082
+ * (and SQLite graph snapshot when applicable).
10083
+ *
10084
+ * @param name - Checkpoint name (unique).
10085
+ * @param options - Optional metadata. See {@link CheckpointOptions}.
10086
+ * @returns {@link CheckpointInfo} for the new checkpoint.
10087
+ * @throws {ProviderNotConfiguredError} Without a checkpoint provider / file DB.
10088
+ * @throws {GraphCheckpointNotSupportedError} For Neo4j graph backends.
10089
+ */
9057
10090
  async checkpoint(name, options) {
9058
10091
  const trace = this.telemetry.start("checkpoint");
9059
10092
  try {
@@ -9063,7 +10096,15 @@ var Wolbarg = class {
9063
10096
  this.assertGraphCheckpointSupported("checkpoint");
9064
10097
  const tCheckpoint = performance.now();
9065
10098
  const meta2 = await provider.checkpoint(name, source, options);
9066
- await this.snapshotGraphAlongside(meta2.snapshotPath, "checkpoint");
10099
+ try {
10100
+ await this.snapshotGraphAlongside(meta2.snapshotPath, "checkpoint");
10101
+ } catch (error) {
10102
+ const sidecar = this.graphSnapshotDir(meta2.snapshotPath);
10103
+ if (fs6__default.default.existsSync(sidecar)) {
10104
+ fs6__default.default.rmSync(sidecar, { recursive: true, force: true });
10105
+ }
10106
+ throw error;
10107
+ }
9067
10108
  trace.mark("databaseWriteMs", performance.now() - tCheckpoint);
9068
10109
  trace.success({
9069
10110
  provider: provider.name,
@@ -9076,7 +10117,12 @@ var Wolbarg = class {
9076
10117
  throw wrapOperationError("checkpoint", error);
9077
10118
  }
9078
10119
  }
9079
- /** Restore the memory database from a named checkpoint. */
10120
+ /**
10121
+ * Restore the memory database from a named checkpoint (replaces the live DB file).
10122
+ *
10123
+ * @param name - Checkpoint name previously created with {@link Wolbarg.checkpoint}.
10124
+ * @returns {@link CheckpointInfo} of the restored checkpoint.
10125
+ */
9080
10126
  async rollback(name) {
9081
10127
  const trace = this.telemetry.start("rollback");
9082
10128
  let storageClosed = false;
@@ -9122,13 +10168,26 @@ var Wolbarg = class {
9122
10168
  throw wrapOperationError("rollback", error);
9123
10169
  }
9124
10170
  }
10171
+ /**
10172
+ * Delete a named checkpoint from disk / the checkpoint store.
10173
+ *
10174
+ * @param name - Checkpoint name.
10175
+ * @returns `true` if a checkpoint was removed.
10176
+ */
9125
10177
  async deleteCheckpoint(name) {
9126
10178
  const trace = this.telemetry.start("deleteCheckpoint");
9127
10179
  try {
9128
10180
  await this.requireReady();
9129
10181
  const provider = this.requireCheckpointProvider();
10182
+ const existing = await provider.getCheckpoint(name);
9130
10183
  const tDelete = performance.now();
9131
10184
  const removed = await provider.deleteCheckpoint(name);
10185
+ if (removed && existing) {
10186
+ const sidecar = this.graphSnapshotDir(existing.snapshotPath);
10187
+ if (fs6__default.default.existsSync(sidecar)) {
10188
+ fs6__default.default.rmSync(sidecar, { recursive: true, force: true });
10189
+ }
10190
+ }
9132
10191
  trace.mark("databaseWriteMs", performance.now() - tDelete);
9133
10192
  trace.success({
9134
10193
  provider: provider.name,
@@ -9141,6 +10200,11 @@ var Wolbarg = class {
9141
10200
  throw wrapOperationError("deleteCheckpoint", error);
9142
10201
  }
9143
10202
  }
10203
+ /**
10204
+ * List all known checkpoints for this instance.
10205
+ *
10206
+ * @returns Array of {@link CheckpointInfo} (may be empty).
10207
+ */
9144
10208
  async listCheckpoints() {
9145
10209
  const trace = this.telemetry.start("listCheckpoints");
9146
10210
  try {
@@ -9159,6 +10223,12 @@ var Wolbarg = class {
9159
10223
  throw wrapOperationError("listCheckpoints", error);
9160
10224
  }
9161
10225
  }
10226
+ /**
10227
+ * Look up a single checkpoint by name.
10228
+ *
10229
+ * @param name - Checkpoint name.
10230
+ * @returns {@link CheckpointInfo} or `null` if missing.
10231
+ */
9162
10232
  async getCheckpoint(name) {
9163
10233
  const trace = this.telemetry.start("getCheckpoint");
9164
10234
  try {
@@ -9178,7 +10248,13 @@ var Wolbarg = class {
9178
10248
  throw wrapOperationError("getCheckpoint", error);
9179
10249
  }
9180
10250
  }
9181
- /** Export the memory database to a portable SQLite + manifest bundle. */
10251
+ /**
10252
+ * Export the memory database to a portable SQLite + manifest bundle
10253
+ * (includes SQLite graph sidecar when applicable).
10254
+ *
10255
+ * @param exportPath - Destination file path for the export bundle.
10256
+ * @returns {@link ExportResult} with path, size, and timestamp.
10257
+ */
9182
10258
  async export(exportPath) {
9183
10259
  const trace = this.telemetry.start("export");
9184
10260
  try {
@@ -9190,7 +10266,9 @@ var Wolbarg = class {
9190
10266
  const result = await this.transfer.exportTo(
9191
10267
  exportPath,
9192
10268
  source,
9193
- this.organization ?? void 0
10269
+ this.organization ?? void 0,
10270
+ this.embedding?.model,
10271
+ this.embeddingDimensions ?? void 0
9194
10272
  );
9195
10273
  await this.snapshotGraphAlongside(result.path, "export");
9196
10274
  trace.success({
@@ -9207,22 +10285,33 @@ var Wolbarg = class {
9207
10285
  throw wrapOperationError("export", error);
9208
10286
  }
9209
10287
  }
9210
- /** Import a previously exported memory database, replacing the current file. */
10288
+ /**
10289
+ * Import a previously exported memory database, replacing the current file.
10290
+ * Closes and reopens storage around the swap.
10291
+ *
10292
+ * @param exportPath - Path to a bundle produced by {@link Wolbarg.export}.
10293
+ * @returns {@link ImportResult} with restored path and timestamp.
10294
+ */
9211
10295
  async import(exportPath) {
9212
10296
  const trace = this.telemetry.start("import");
9213
10297
  let storageClosed = false;
9214
10298
  try {
9215
10299
  await this.requireReady();
10300
+ this.assertGraphCheckpointSupported("import");
9216
10301
  const target = this.requireMemoryDbPath();
9217
10302
  if (this.storage) {
9218
10303
  await this.storage.close();
9219
10304
  storageClosed = true;
9220
10305
  }
9221
10306
  const graphClosed = await this.closeGraphForSnapshot();
9222
- this.assertGraphCheckpointSupported("import");
9223
10307
  const result = await this.transfer.importFrom(
9224
10308
  exportPath,
9225
- target
10309
+ target,
10310
+ {
10311
+ organization: this.organization ?? void 0,
10312
+ embeddingModel: this.embedding?.model,
10313
+ embeddingDimensions: this.embeddingDimensions ?? void 0
10314
+ }
9226
10315
  );
9227
10316
  await this.restoreGraphAlongside(result.path, "import");
9228
10317
  if (graphClosed) {
@@ -9252,11 +10341,17 @@ var Wolbarg = class {
9252
10341
  throw wrapOperationError("import", error);
9253
10342
  }
9254
10343
  }
9255
- /** Flush pending telemetry (useful in tests). */
10344
+ /**
10345
+ * Flush pending telemetry writes (useful in tests / before process exit).
10346
+ *
10347
+ * @returns Resolves when the telemetry provider has flushed.
10348
+ */
9256
10349
  async flushTelemetry() {
9257
10350
  await this.telemetry.flush();
9258
10351
  }
9259
- /** Session id for this SDK instance (telemetry traces). */
10352
+ /**
10353
+ * Session id for this SDK instance (attached to telemetry traces and change events).
10354
+ */
9260
10355
  get sessionId() {
9261
10356
  return this.telemetry.sessionId;
9262
10357
  }
@@ -9271,6 +10366,12 @@ var Wolbarg = class {
9271
10366
  }
9272
10367
  return fn();
9273
10368
  }
10369
+ /**
10370
+ * Close storage, graph, telemetry, checkpoints, and subscribe backends.
10371
+ * The instance can be discarded afterward; construct a new one to reopen.
10372
+ *
10373
+ * @returns Resolves when all providers have closed (errors are swallowed per-provider).
10374
+ */
9274
10375
  async close() {
9275
10376
  if (this.storage) {
9276
10377
  this.telemetry.emitShutdown(this.storage.name);
@@ -9295,9 +10396,11 @@ var Wolbarg = class {
9295
10396
  this.embeddingDimensions = null;
9296
10397
  this.initialized = false;
9297
10398
  }
10399
+ /** Whether {@link Wolbarg.ready} / {@link Wolbarg.init} has completed successfully. */
9298
10400
  get isInitialized() {
9299
10401
  return this.initialized;
9300
10402
  }
10403
+ /** Ensure {@link Wolbarg.ready} completed and core providers are available. */
9301
10404
  async requireReady() {
9302
10405
  await this.ready();
9303
10406
  if (!this.initialized || !this.storage || !this.embedding || !this.organization) {
@@ -9311,6 +10414,7 @@ var Wolbarg = class {
9311
10414
  organization: this.organization
9312
10415
  };
9313
10416
  }
10417
+ /** Reject embeddings whose dimensionality differs from the initialized model. */
9314
10418
  assertEmbeddingDimensions(dimensions) {
9315
10419
  if (this.embeddingDimensions !== null && dimensions !== this.embeddingDimensions) {
9316
10420
  throw new ValidationError(
@@ -9318,6 +10422,7 @@ var Wolbarg = class {
9318
10422
  );
9319
10423
  }
9320
10424
  }
10425
+ /** Return the configured checkpoint provider or throw {@link ProviderNotConfiguredError}. */
9321
10426
  requireCheckpointProvider() {
9322
10427
  if (!this.checkpointProvider) {
9323
10428
  throw new ProviderNotConfiguredError(
@@ -9328,6 +10433,7 @@ var Wolbarg = class {
9328
10433
  }
9329
10434
  return this.checkpointProvider;
9330
10435
  }
10436
+ /** Return the configured graph provider or throw with a setup hint for `method`. */
9331
10437
  requireGraph(method) {
9332
10438
  if (!this.graph) {
9333
10439
  throw new ProviderNotConfiguredError(
@@ -9338,20 +10444,24 @@ var Wolbarg = class {
9338
10444
  }
9339
10445
  return this.graph;
9340
10446
  }
10447
+ /** Throw when graph checkpoint pairing is requested on a non-file-backed graph backend. */
9341
10448
  assertGraphCheckpointSupported(operation) {
9342
10449
  if (!this.graph) return;
9343
10450
  if (!this.graph.supportsFileSnapshot()) {
9344
10451
  throw new GraphCheckpointNotSupportedError(this.graph.name, operation);
9345
10452
  }
9346
10453
  }
10454
+ /** Directory path where a graph file snapshot is stored alongside a memory checkpoint. */
9347
10455
  graphSnapshotDir(alongsidePath) {
9348
10456
  return `${alongsidePath}.graph`;
9349
10457
  }
10458
+ /** Close the graph provider before copying files; returns whether a close occurred. */
9350
10459
  async closeGraphForSnapshot() {
9351
10460
  if (!this.graph || !this.graph.supportsFileSnapshot()) return false;
9352
10461
  await this.graph.close();
9353
10462
  return true;
9354
10463
  }
10464
+ /** Copy the SQLite graph database next to a memory checkpoint directory. */
9355
10465
  async snapshotGraphAlongside(alongsidePath, operation) {
9356
10466
  if (!this.graph) return;
9357
10467
  if (!this.graph.supportsFileSnapshot()) {
@@ -9390,6 +10500,7 @@ var Wolbarg = class {
9390
10500
  await this.graph.open();
9391
10501
  }
9392
10502
  }
10503
+ /** Restore a graph file snapshot written by {@link snapshotGraphAlongside}. */
9393
10504
  async restoreGraphAlongside(alongsidePath, operation) {
9394
10505
  if (!this.graph) return;
9395
10506
  if (!this.graph.supportsFileSnapshot()) {
@@ -9413,22 +10524,32 @@ var Wolbarg = class {
9413
10524
  fs6__default.default.cpSync(src, dataPath, { recursive: true });
9414
10525
  }
9415
10526
  }
10527
+ /** Run graph traversal then re-hydrate stub records from storage when rows exist. */
9416
10528
  async hydrateRelated(memoryId, options) {
9417
10529
  const graph = this.requireGraph("getRelated");
9418
10530
  const related = await graph.getRelated(memoryId, options);
9419
10531
  const { storage, organization } = await this.requireReady();
10532
+ const rows = await Promise.all(
10533
+ related.map((stub) => storage.getMemoryById(stub.id, organization))
10534
+ );
9420
10535
  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
- }
10536
+ for (let i = 0; i < related.length; i += 1) {
10537
+ const stub = related[i];
10538
+ const row = rows[i];
10539
+ out.push(row ? toMemoryRecord(row) : stub);
9428
10540
  }
9429
10541
  return out;
9430
10542
  }
10543
+ /** Resolve the on-disk SQLite memory database path (throws for Postgres / `:memory:`). */
9431
10544
  requireMemoryDbPath() {
10545
+ if (this.storage && !(this.storage instanceof SqliteStorageProvider)) {
10546
+ throw new ConfigurationError(
10547
+ "This operation requires a file-backed SQLite database (checkpoints / export-import are not supported for PostgreSQL).",
10548
+ {
10549
+ suggestion: 'Use database: { provider: "sqlite", url: "./memory.db" }'
10550
+ }
10551
+ );
10552
+ }
9432
10553
  if (!this.memoryDbPath || this.memoryDbPath === ":memory:") {
9433
10554
  throw new ConfigurationError(
9434
10555
  "This operation requires a file-backed SQLite memory database (not :memory:).",
@@ -9471,6 +10592,7 @@ function commonTags(metadata) {
9471
10592
 
9472
10593
  // src/filters/types.ts
9473
10594
  var meta = {
10595
+ /** Field equals value (strict equality). */
9474
10596
  eq: (field, value) => ({
9475
10597
  field,
9476
10598
  op: { eq: value }
@@ -9554,16 +10676,23 @@ function createTelemetryProvider(config) {
9554
10676
  function wolbarg2(options) {
9555
10677
  return new Wolbarg(options);
9556
10678
  }
10679
+ var createWolbarg = wolbarg2;
9557
10680
 
9558
10681
  // src/keyword/index.ts
9559
10682
  var Bm25KeywordSearchProvider = class {
9560
10683
  name = "bm25";
9561
10684
  k1;
9562
10685
  b;
10686
+ /**
10687
+ * @param options - BM25 tuning parameters.
10688
+ * @param options.k1 - Term frequency saturation (default `1.2`).
10689
+ * @param options.b - Length normalization (default `0.75`).
10690
+ */
9563
10691
  constructor(options) {
9564
10692
  this.k1 = options?.k1 ?? 1.2;
9565
10693
  this.b = options?.b ?? 0.75;
9566
10694
  }
10695
+ /** @inheritdoc */
9567
10696
  async search(query, documents, topK) {
9568
10697
  if (documents.length === 0 || topK <= 0) {
9569
10698
  return [];
@@ -9657,7 +10786,14 @@ var HttpRerankerProvider = class {
9657
10786
  }));
9658
10787
  }
9659
10788
  const parsed = this.options.parseResults(body, documents);
9660
- return parsed.slice(0, topK);
10789
+ const hits = parsed.slice(0, topK);
10790
+ if (hits.length === 0) {
10791
+ return documents.slice(0, topK).map((d, i) => ({
10792
+ id: d.id,
10793
+ score: 1 - i / Math.max(documents.length, 1)
10794
+ }));
10795
+ }
10796
+ return hits;
9661
10797
  } catch {
9662
10798
  return documents.slice(0, topK).map((d, i) => ({
9663
10799
  id: d.id,
@@ -9842,21 +10978,31 @@ function tesseract() {
9842
10978
  return {
9843
10979
  name: "tesseract",
9844
10980
  async recognize(image) {
10981
+ let mod;
9845
10982
  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: "" };
10983
+ mod = await import('tesseract.js');
10984
+ } catch (error) {
10985
+ throw new ConfigurationError(
10986
+ 'OCR provider "tesseract" requires the optional peer package "tesseract.js". Install it with: npm install tesseract.js',
10987
+ {
10988
+ cause: error instanceof Error ? error : void 0,
10989
+ suggestion: "npm install tesseract.js",
10990
+ operation: "recognize"
10991
+ }
10992
+ );
10993
+ }
10994
+ const createWorker = mod.createWorker ?? mod.default?.createWorker;
10995
+ if (!createWorker) {
10996
+ throw new ConfigurationError(
10997
+ 'OCR provider "tesseract" could not find tesseract.js.createWorker. Ensure your tesseract.js version is compatible.'
10998
+ );
10999
+ }
11000
+ const worker = await createWorker("eng");
11001
+ try {
11002
+ const result = await worker.recognize(image);
11003
+ return { text: result.data.text.trim() };
11004
+ } finally {
11005
+ await worker.terminate();
9860
11006
  }
9861
11007
  }
9862
11008
  };
@@ -10039,7 +11185,7 @@ exports.createEmbeddingProvider = createEmbeddingProvider;
10039
11185
  exports.createLlmProvider = createLlmProvider;
10040
11186
  exports.createStorageProvider = createStorageProvider;
10041
11187
  exports.createTelemetryProvider = createTelemetryProvider;
10042
- exports.createWolbarg = wolbarg2;
11188
+ exports.createWolbarg = createWolbarg;
10043
11189
  exports.crossEncoder = crossEncoder;
10044
11190
  exports.geminiEmbedding = geminiEmbedding;
10045
11191
  exports.geminiVision = geminiVision;