@equationalapplications/core-llm-wiki 4.6.0 → 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +182 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +182 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -145,6 +145,157 @@ var PrunePartialFailureError = class extends Error {
|
|
|
145
145
|
}
|
|
146
146
|
};
|
|
147
147
|
|
|
148
|
+
// src/repositories/BaseRepository.ts
|
|
149
|
+
var BaseRepository = class {
|
|
150
|
+
constructor(db, prefix) {
|
|
151
|
+
this.db = db;
|
|
152
|
+
this.prefix = prefix;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Return the DB executor for a given transaction handle.
|
|
156
|
+
* If tx is provided, use it; otherwise fall back to this.db.
|
|
157
|
+
*/
|
|
158
|
+
getExecutor(tx) {
|
|
159
|
+
return tx ?? this.db;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// src/repositories/EntryRepository.ts
|
|
164
|
+
function mapRowToFact(row) {
|
|
165
|
+
const tags = (() => {
|
|
166
|
+
if (Array.isArray(row.tags)) return row.tags;
|
|
167
|
+
try {
|
|
168
|
+
const p = JSON.parse(row.tags);
|
|
169
|
+
if (Array.isArray(p)) return p;
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
return [];
|
|
173
|
+
})();
|
|
174
|
+
return {
|
|
175
|
+
id: row.id,
|
|
176
|
+
entity_id: row.entity_id,
|
|
177
|
+
title: row.title,
|
|
178
|
+
body: row.body,
|
|
179
|
+
tags,
|
|
180
|
+
confidence: row.confidence,
|
|
181
|
+
source_type: row.source_type,
|
|
182
|
+
source_hash: row.source_hash ?? null,
|
|
183
|
+
source_ref: row.source_ref ?? null,
|
|
184
|
+
created_at: Number(row.created_at),
|
|
185
|
+
updated_at: Number(row.updated_at),
|
|
186
|
+
last_accessed_at: row.last_accessed_at === null || row.last_accessed_at === void 0 ? null : Number(row.last_accessed_at),
|
|
187
|
+
deleted_at: row.deleted_at != null ? Number(row.deleted_at) : null,
|
|
188
|
+
access_count: Number(row.access_count ?? 0)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
var EntryRepository = class extends BaseRepository {
|
|
192
|
+
constructor() {
|
|
193
|
+
super(...arguments);
|
|
194
|
+
this.chunkSize = 500;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Fetch facts by IDs, optionally scoped to entity IDs.
|
|
198
|
+
* Returns facts in the order of the input IDs (first match wins).
|
|
199
|
+
*/
|
|
200
|
+
async findByIds(ids, scopedEntityIds, tx) {
|
|
201
|
+
const executor = this.getExecutor(tx);
|
|
202
|
+
const rows = [];
|
|
203
|
+
const entityClause = scopedEntityIds && scopedEntityIds.length > 0 ? ` AND entity_id IN (${scopedEntityIds.map(() => "?").join(",")})` : "";
|
|
204
|
+
const entityParams = scopedEntityIds ?? [];
|
|
205
|
+
for (let i = 0; i < ids.length; i += this.chunkSize) {
|
|
206
|
+
const chunk = ids.slice(i, i + this.chunkSize);
|
|
207
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
208
|
+
const chunkRows = await executor.getAllAsync(
|
|
209
|
+
`SELECT * FROM ${this.prefix}entries WHERE id IN (${placeholders})${entityClause} AND deleted_at IS NULL`,
|
|
210
|
+
[...chunk, ...entityParams]
|
|
211
|
+
);
|
|
212
|
+
rows.push(...chunkRows);
|
|
213
|
+
}
|
|
214
|
+
const byId = new Map(rows.map((r) => [r.id, r]));
|
|
215
|
+
return ids.map((id) => byId.get(id)).filter((r) => r !== void 0).map(mapRowToFact);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Upsert a WikiFact. Nullable fields set to null when fact value is null.
|
|
219
|
+
* Returns { changes, lastInsertRowId }.
|
|
220
|
+
*/
|
|
221
|
+
async upsert(fact, tx) {
|
|
222
|
+
const executor = this.getExecutor(tx);
|
|
223
|
+
const now = Date.now();
|
|
224
|
+
const tagsJson = JSON.stringify(fact.tags);
|
|
225
|
+
const embeddingBlob = fact.embedding_blob instanceof Uint8Array ? fact.embedding_blob : fact.embedding_blob && typeof fact.embedding_blob === "object" && "type" in fact.embedding_blob ? new Uint8Array(fact.embedding_blob.data) : fact.embedding_blob && typeof fact.embedding_blob === "object" ? (() => {
|
|
226
|
+
const obj = fact.embedding_blob;
|
|
227
|
+
const keys = Object.keys(obj).map(Number).sort((a, b) => a - b);
|
|
228
|
+
const arr = new Uint8Array(keys.length);
|
|
229
|
+
for (let i = 0; i < keys.length; i++) arr[i] = obj[String(keys[i])];
|
|
230
|
+
return arr;
|
|
231
|
+
})() : void 0;
|
|
232
|
+
return executor.runAsync(
|
|
233
|
+
`INSERT INTO ${this.prefix}entries (
|
|
234
|
+
id, entity_id, title, body, tags, confidence, source_type,
|
|
235
|
+
source_hash, source_ref, created_at, updated_at, last_accessed_at, access_count,
|
|
236
|
+
deleted_at, embedding_blob, embedding
|
|
237
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
238
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
239
|
+
entity_id = excluded.entity_id,
|
|
240
|
+
title = excluded.title,
|
|
241
|
+
body = excluded.body,
|
|
242
|
+
tags = excluded.tags,
|
|
243
|
+
confidence = excluded.confidence,
|
|
244
|
+
source_type = excluded.source_type,
|
|
245
|
+
source_hash = excluded.source_hash,
|
|
246
|
+
source_ref = excluded.source_ref,
|
|
247
|
+
updated_at = excluded.updated_at,
|
|
248
|
+
last_accessed_at = excluded.last_accessed_at,
|
|
249
|
+
access_count = excluded.access_count,
|
|
250
|
+
deleted_at = excluded.deleted_at,
|
|
251
|
+
embedding_blob = CASE WHEN excluded.embedding_blob IS NULL THEN embedding_blob ELSE excluded.embedding_blob END,
|
|
252
|
+
embedding = NULL`,
|
|
253
|
+
[
|
|
254
|
+
fact.id,
|
|
255
|
+
fact.entity_id,
|
|
256
|
+
fact.title,
|
|
257
|
+
fact.body,
|
|
258
|
+
tagsJson,
|
|
259
|
+
fact.confidence,
|
|
260
|
+
fact.source_type,
|
|
261
|
+
fact.source_hash,
|
|
262
|
+
fact.source_ref,
|
|
263
|
+
fact.created_at,
|
|
264
|
+
now,
|
|
265
|
+
// updated_at set by repo
|
|
266
|
+
fact.last_accessed_at === null ? null : fact.last_accessed_at,
|
|
267
|
+
fact.access_count,
|
|
268
|
+
fact.deleted_at ?? null,
|
|
269
|
+
embeddingBlob ?? null,
|
|
270
|
+
null
|
|
271
|
+
]
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Soft-delete a single entry by ID scoped to entityId. Sets deleted_at + updated_at.
|
|
276
|
+
*/
|
|
277
|
+
async softDelete(entryId, entityId, tx) {
|
|
278
|
+
const executor = this.getExecutor(tx);
|
|
279
|
+
const now = Date.now();
|
|
280
|
+
return executor.runAsync(
|
|
281
|
+
`UPDATE ${this.prefix}entries SET deleted_at = ?, updated_at = ? WHERE id = ? AND entity_id = ? AND deleted_at IS NULL`,
|
|
282
|
+
[now, now, entryId, entityId]
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Fetch IDs + entity_ids of soft-deleted rows older than cutoff for a given entity.
|
|
287
|
+
* Used by runPrune().
|
|
288
|
+
*/
|
|
289
|
+
async getPrunableMetadata(entityId, cutoff, tx) {
|
|
290
|
+
const executor = this.getExecutor(tx);
|
|
291
|
+
return executor.getAllAsync(
|
|
292
|
+
`SELECT id, entity_id FROM ${this.prefix}entries
|
|
293
|
+
WHERE entity_id = ? AND deleted_at IS NOT NULL AND deleted_at <= ?`,
|
|
294
|
+
[entityId, cutoff]
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
148
299
|
// src/prompts.ts
|
|
149
300
|
var LIBRARIAN_SYSTEM_PROMPT = `You are a knowledge extraction agent. Your job is to analyze recent episodic events and extract stable facts and actionable tasks about the user or entity.
|
|
150
301
|
Return ONLY a valid JSON object matching this schema:
|
|
@@ -467,6 +618,7 @@ var _WikiMemory = class _WikiMemory {
|
|
|
467
618
|
this.db = db;
|
|
468
619
|
this.options = options;
|
|
469
620
|
this.prefix = options.config?.tablePrefix || "llm_wiki_";
|
|
621
|
+
this.entryRepo = new EntryRepository(db, this.prefix);
|
|
470
622
|
}
|
|
471
623
|
normalizeMiniSearchRow(row) {
|
|
472
624
|
return {
|
|
@@ -922,11 +1074,7 @@ After running the migration SQL, restart your application.`
|
|
|
922
1074
|
let deletedEvents = 0;
|
|
923
1075
|
if (retainSoftDeletedFor !== null) {
|
|
924
1076
|
const cutoff = now - retainSoftDeletedFor * 864e5;
|
|
925
|
-
const entriesToDelete = await this.
|
|
926
|
-
`SELECT id, entity_id FROM ${this.prefix}entries
|
|
927
|
-
WHERE entity_id = ? AND deleted_at IS NOT NULL AND deleted_at <= ?`,
|
|
928
|
-
[entityId, cutoff]
|
|
929
|
-
);
|
|
1077
|
+
const entriesToDelete = await this.entryRepo.getPrunableMetadata(entityId, cutoff);
|
|
930
1078
|
const succeeded = [];
|
|
931
1079
|
let failure = null;
|
|
932
1080
|
for (const row of entriesToDelete) {
|
|
@@ -1482,7 +1630,15 @@ After running the migration SQL, restart your application.`
|
|
|
1482
1630
|
const { embedding: _embedding, embedding_blob: _blob, ...rest } = f;
|
|
1483
1631
|
return {
|
|
1484
1632
|
...rest,
|
|
1485
|
-
tags:
|
|
1633
|
+
tags: (() => {
|
|
1634
|
+
if (Array.isArray(rest.tags)) return rest.tags;
|
|
1635
|
+
try {
|
|
1636
|
+
const p = JSON.parse(rest.tags);
|
|
1637
|
+
return Array.isArray(p) ? p : [];
|
|
1638
|
+
} catch {
|
|
1639
|
+
return [];
|
|
1640
|
+
}
|
|
1641
|
+
})()
|
|
1486
1642
|
};
|
|
1487
1643
|
});
|
|
1488
1644
|
}
|
|
@@ -1562,28 +1718,8 @@ After running the migration SQL, restart your application.`
|
|
|
1562
1718
|
* Hydrate full facts by ID. Pass scopedEntityIds to restrict to requested namespaces in SQL
|
|
1563
1719
|
* (defense-in-depth against a rogue VectorRanker returning cross-entity IDs).
|
|
1564
1720
|
*/
|
|
1565
|
-
async _hydrateFactsByIds(ids, scopedEntityIds) {
|
|
1566
|
-
|
|
1567
|
-
const chunkSize = 500;
|
|
1568
|
-
const entityClause = scopedEntityIds && scopedEntityIds.length > 0 ? ` AND entity_id IN (${scopedEntityIds.map(() => "?").join(",")})` : "";
|
|
1569
|
-
const entityParams = scopedEntityIds && scopedEntityIds.length > 0 ? [...scopedEntityIds] : [];
|
|
1570
|
-
for (let i = 0; i < ids.length; i += chunkSize) {
|
|
1571
|
-
const idChunk = ids.slice(i, i + chunkSize);
|
|
1572
|
-
const placeholders = idChunk.map(() => "?").join(",");
|
|
1573
|
-
const chunkRows = await this.db.getAllAsync(
|
|
1574
|
-
`SELECT * FROM ${this.prefix}entries WHERE id IN (${placeholders})${entityClause} AND deleted_at IS NULL`,
|
|
1575
|
-
[...idChunk, ...entityParams]
|
|
1576
|
-
);
|
|
1577
|
-
fullRows.push(...chunkRows);
|
|
1578
|
-
}
|
|
1579
|
-
const byId = new Map(fullRows.map((row) => [row.id, row]));
|
|
1580
|
-
return ids.map((id) => byId.get(id)).filter((fact) => fact !== void 0).map((fact) => {
|
|
1581
|
-
const { embedding: _embedding, embedding_blob: _blob, ...rest } = fact;
|
|
1582
|
-
return {
|
|
1583
|
-
...rest,
|
|
1584
|
-
tags: typeof rest.tags === "string" ? JSON.parse(rest.tags) : rest.tags
|
|
1585
|
-
};
|
|
1586
|
-
});
|
|
1721
|
+
async _hydrateFactsByIds(ids, scopedEntityIds, tx) {
|
|
1722
|
+
return this.entryRepo.findByIds(ids, scopedEntityIds, tx);
|
|
1587
1723
|
}
|
|
1588
1724
|
/**
|
|
1589
1725
|
* Strip potentially sensitive data from ranker errors before exposing to host callbacks.
|
|
@@ -2596,7 +2732,7 @@ The following document anchors are provided for contradiction detection only. Do
|
|
|
2596
2732
|
const entriesToDelete = await this.db.getAllAsync(q, args);
|
|
2597
2733
|
deletedEntryIds.push(...entriesToDelete.map((e) => e.id));
|
|
2598
2734
|
}
|
|
2599
|
-
const entryPromise = params.entryId ? this.
|
|
2735
|
+
const entryPromise = params.entryId ? this.entryRepo.softDelete(params.entryId, entityId) : null;
|
|
2600
2736
|
const taskPromise = params.taskId ? this.db.runAsync(`UPDATE ${this.prefix}tasks SET deleted_at = ?, updated_at = ? WHERE id = ? AND entity_id = ? AND deleted_at IS NULL`, [now, now, params.taskId, entityId]) : null;
|
|
2601
2737
|
let refPromise = null;
|
|
2602
2738
|
if (sourceRef || sourceHash) {
|
|
@@ -2733,11 +2869,23 @@ ${chunk}`;
|
|
|
2733
2869
|
);
|
|
2734
2870
|
for (const fact of allValidFacts) {
|
|
2735
2871
|
const id = generateId("fact_");
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2872
|
+
const wikiFact = {
|
|
2873
|
+
id,
|
|
2874
|
+
entity_id: entityId,
|
|
2875
|
+
title: fact.title,
|
|
2876
|
+
body: fact.body,
|
|
2877
|
+
tags: fact.tags,
|
|
2878
|
+
confidence: fact.confidence,
|
|
2879
|
+
source_type: "immutable_document",
|
|
2880
|
+
source_hash: sourceHash,
|
|
2881
|
+
source_ref: sourceRef,
|
|
2882
|
+
created_at: now,
|
|
2883
|
+
updated_at: now,
|
|
2884
|
+
last_accessed_at: null,
|
|
2885
|
+
access_count: 0,
|
|
2886
|
+
deleted_at: null
|
|
2887
|
+
};
|
|
2888
|
+
await this.entryRepo.upsert(wikiFact);
|
|
2741
2889
|
insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
|
|
2742
2890
|
}
|
|
2743
2891
|
});
|