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/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/dist/index.cjs +1542 -396
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2011 -140
- package/dist/index.d.ts +2011 -140
- package/dist/index.js +1542 -396
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
-
|
|
1069
|
+
requeue();
|
|
898
1070
|
}
|
|
899
1071
|
} catch {
|
|
900
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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] =
|
|
2181
|
+
heapRow[heapSize] = rowid;
|
|
1894
2182
|
heapSize += 1;
|
|
1895
2183
|
if (heapSize === k) {
|
|
1896
2184
|
buildMaxHeap(heapDist, heapRow, k);
|
|
1897
2185
|
}
|
|
1898
|
-
} else
|
|
1899
|
-
heapDist[0]
|
|
1900
|
-
|
|
1901
|
-
|
|
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) =>
|
|
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
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
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
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
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: "
|
|
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[], $
|
|
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((
|
|
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:
|
|
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
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
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
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
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
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
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.
|
|
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
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
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
|
-
|
|
5262
|
-
|
|
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
|
-
|
|
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(
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
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.
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
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(
|
|
6933
|
-
|
|
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
|
-
|
|
7223
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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"` —
|
|
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
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
8460
|
-
|
|
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 (
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
8888
|
-
*
|
|
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(
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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 (
|
|
9422
|
-
const
|
|
9423
|
-
|
|
9424
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9855
|
-
|
|
9856
|
-
|
|
9857
|
-
|
|
9858
|
-
|
|
9859
|
-
|
|
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 =
|
|
11188
|
+
exports.createWolbarg = createWolbarg;
|
|
10043
11189
|
exports.crossEncoder = crossEncoder;
|
|
10044
11190
|
exports.geminiEmbedding = geminiEmbedding;
|
|
10045
11191
|
exports.geminiVision = geminiVision;
|