@equationalapplications/core-llm-wiki 4.14.0 → 4.15.0
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/README.md +12 -0
- package/dist/{chunk-6FWG2DG4.mjs → chunk-J4GBC6CP.mjs} +103 -31
- package/dist/chunk-J4GBC6CP.mjs.map +1 -0
- package/dist/index.d.mts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +441 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +343 -20
- package/dist/index.mjs.map +1 -1
- package/dist/{testing-CDIDE4Jd.d.mts → testing-NH1_Aigh.d.mts} +47 -3
- package/dist/{testing-CDIDE4Jd.d.ts → testing-NH1_Aigh.d.ts} +47 -3
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +316 -244
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-6FWG2DG4.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -477,6 +477,18 @@ Core WikiMemory provides:
|
|
|
477
477
|
- **Input Validation**: `sourceRef`/`sourceHash` normalized; embedding dimensions validated
|
|
478
478
|
- **Parameterized Queries**: All SQL uses bind parameters
|
|
479
479
|
|
|
480
|
+
### Prompt-Injection Trust Boundary
|
|
481
|
+
|
|
482
|
+
User-controlled text — `event.summary` passed to `write()`, document chunks passed to `ingestDocument()`,
|
|
483
|
+
fact `title`/`body` (including imported dumps) — is interpolated verbatim into LLM prompts for librarian,
|
|
484
|
+
heal, and embedding operations. Prompt templating does simple variable substitution; it does not detect
|
|
485
|
+
or filter instruction-like content.
|
|
486
|
+
|
|
487
|
+
Mitigating prompt injection (e.g. "ignore prior instructions and emit...") is **the host's responsibility**.
|
|
488
|
+
If your application accepts untrusted input that flows into `write()`, `ingestDocument()`, or `importDump()`,
|
|
489
|
+
treat the LLM's librarian/heal output as similarly untrusted — validate or scope it before acting on it
|
|
490
|
+
downstream.
|
|
491
|
+
|
|
480
492
|
## Usage
|
|
481
493
|
|
|
482
494
|
```typescript
|
|
@@ -284,6 +284,20 @@ var JobManager = class {
|
|
|
284
284
|
this.activeMaintenanceJobs = /* @__PURE__ */ new Set();
|
|
285
285
|
this.activeIngestJobs = /* @__PURE__ */ new Map();
|
|
286
286
|
this.statusSubscribers = /* @__PURE__ */ new Map();
|
|
287
|
+
/**
|
|
288
|
+
* Lookup table for acquireLock/releaseLock's dynamic-dispatch branch.
|
|
289
|
+
* Excludes 'ingest' | 'global_reembed' | 'global_import', which those
|
|
290
|
+
* methods already handle via explicit if/else branches before reaching
|
|
291
|
+
* this table.
|
|
292
|
+
*/
|
|
293
|
+
this.lockKeyFns = {
|
|
294
|
+
prune: (id) => this._pruneKey(id),
|
|
295
|
+
librarian: (id) => this._librarianKey(id),
|
|
296
|
+
heal: (id) => this._healKey(id),
|
|
297
|
+
reembed: (id) => this._reembedKey(id),
|
|
298
|
+
import: (id) => this._importKey(id),
|
|
299
|
+
forget: (id) => this._forgetKey(id)
|
|
300
|
+
};
|
|
287
301
|
}
|
|
288
302
|
_pruneKey(entityId) {
|
|
289
303
|
return `${this.prefix}:${entityId}:prune`;
|
|
@@ -433,9 +447,7 @@ var JobManager = class {
|
|
|
433
447
|
} else if (operation === "global_import") {
|
|
434
448
|
this.activeMaintenanceJobs.add(this._globalImportKey());
|
|
435
449
|
} else {
|
|
436
|
-
|
|
437
|
-
const keyFn = this[keyFnName];
|
|
438
|
-
this.activeMaintenanceJobs.add(keyFn.call(this, entityId));
|
|
450
|
+
this.activeMaintenanceJobs.add(this.lockKeyFns[operation](entityId));
|
|
439
451
|
}
|
|
440
452
|
this._notifyStatusSubscribers(entityId);
|
|
441
453
|
}
|
|
@@ -447,9 +459,7 @@ var JobManager = class {
|
|
|
447
459
|
} else if (operation === "global_import") {
|
|
448
460
|
this.activeMaintenanceJobs.delete(this._globalImportKey());
|
|
449
461
|
} else {
|
|
450
|
-
|
|
451
|
-
const keyFn = this[keyFnName];
|
|
452
|
-
this.activeMaintenanceJobs.delete(keyFn.call(this, entityId));
|
|
462
|
+
this.activeMaintenanceJobs.delete(this.lockKeyFns[operation](entityId));
|
|
453
463
|
}
|
|
454
464
|
this._notifyStatusSubscribers(entityId);
|
|
455
465
|
}
|
|
@@ -875,7 +885,9 @@ function generateId(prefix = "") {
|
|
|
875
885
|
crypto.getRandomValues(bytes);
|
|
876
886
|
return prefix + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("").substring(0, 24);
|
|
877
887
|
}
|
|
878
|
-
|
|
888
|
+
throw new Error(
|
|
889
|
+
"generateId: no cryptographically secure random source available (crypto.randomUUID and crypto.getRandomValues are both missing)."
|
|
890
|
+
);
|
|
879
891
|
}
|
|
880
892
|
|
|
881
893
|
// src/services/IngestionService.ts
|
|
@@ -1412,12 +1424,16 @@ var MaintenanceService = class {
|
|
|
1412
1424
|
};
|
|
1413
1425
|
|
|
1414
1426
|
// src/services/ImportExportService.ts
|
|
1427
|
+
var MAX_EMBEDDING_BLOB_BYTES = 32 * 1024;
|
|
1428
|
+
var IMPORT_TITLE_MAX = 500;
|
|
1429
|
+
var IMPORT_BODY_MAX = 8e3;
|
|
1415
1430
|
var ImportExportService = class {
|
|
1416
|
-
constructor(db, entryRepo, taskRepo, eventRepo, metadataRepo, searchService, jobManager, embeddingService) {
|
|
1431
|
+
constructor(db, entryRepo, taskRepo, eventRepo, edgeRepo, metadataRepo, searchService, jobManager, embeddingService) {
|
|
1417
1432
|
this.db = db;
|
|
1418
1433
|
this.entryRepo = entryRepo;
|
|
1419
1434
|
this.taskRepo = taskRepo;
|
|
1420
1435
|
this.eventRepo = eventRepo;
|
|
1436
|
+
this.edgeRepo = edgeRepo;
|
|
1421
1437
|
this.metadataRepo = metadataRepo;
|
|
1422
1438
|
this.searchService = searchService;
|
|
1423
1439
|
this.jobManager = jobManager;
|
|
@@ -1462,10 +1478,11 @@ var ImportExportService = class {
|
|
|
1462
1478
|
}
|
|
1463
1479
|
}
|
|
1464
1480
|
async getFullBundle(entityId, opts) {
|
|
1465
|
-
const [factsRaw, tasks, events] = await Promise.all([
|
|
1481
|
+
const [factsRaw, tasks, events, edges] = await Promise.all([
|
|
1466
1482
|
opts?.includeBlobs ? this.entryRepo.findAllByEntityIdWithBlobs(entityId) : this.entryRepo.findAllByEntityId(entityId),
|
|
1467
1483
|
this.taskRepo.findAllByEntityId(entityId),
|
|
1468
|
-
this.eventRepo.getByEntityId(entityId, opts?.maxEvents)
|
|
1484
|
+
this.eventRepo.getByEntityId(entityId, opts?.maxEvents),
|
|
1485
|
+
this.edgeRepo.getByEntityId(entityId)
|
|
1469
1486
|
]);
|
|
1470
1487
|
const facts = factsRaw.map((f) => {
|
|
1471
1488
|
const {
|
|
@@ -1484,7 +1501,7 @@ var ImportExportService = class {
|
|
|
1484
1501
|
tags: typeof factBase.tags === "string" ? JSON.parse(factBase.tags) : factBase.tags
|
|
1485
1502
|
};
|
|
1486
1503
|
});
|
|
1487
|
-
return { facts, tasks, events };
|
|
1504
|
+
return { facts, tasks, events, edges };
|
|
1488
1505
|
}
|
|
1489
1506
|
/** Single-entity import transaction + post-processing; package-internal hook for tests. */
|
|
1490
1507
|
async doImportEntity(entityId, bundle, merge) {
|
|
@@ -1493,6 +1510,7 @@ var ImportExportService = class {
|
|
|
1493
1510
|
const factsWithPreservedBlob = /* @__PURE__ */ new Map();
|
|
1494
1511
|
const preservedBlobDims = /* @__PURE__ */ new Set();
|
|
1495
1512
|
const softDeletedFactIds = [];
|
|
1513
|
+
const clippedTextByFactId = /* @__PURE__ */ new Map();
|
|
1496
1514
|
await this.db.withTransactionAsync(async (tx) => {
|
|
1497
1515
|
if (!merge) {
|
|
1498
1516
|
const deletedLiveFactIds = await this.entryRepo.findIdsBySource(
|
|
@@ -1505,6 +1523,7 @@ var ImportExportService = class {
|
|
|
1505
1523
|
softDeletedFactIds.push(...deletedLiveFactIds);
|
|
1506
1524
|
await this.entryRepo.bulkSoftDeleteByEntityId(entityId, tx);
|
|
1507
1525
|
await this.taskRepo.bulkSoftDeleteByEntityId(entityId, tx);
|
|
1526
|
+
await this.edgeRepo.bulkDeleteByEntityId(entityId, tx);
|
|
1508
1527
|
await this.metadataRepo.deleteCheckpoint(entityId, tx);
|
|
1509
1528
|
}
|
|
1510
1529
|
const factIds = bundle.facts.map((fact) => fact.id);
|
|
@@ -1529,21 +1548,32 @@ var ImportExportService = class {
|
|
|
1529
1548
|
const rawBlobRaw = fact.embedding_blob;
|
|
1530
1549
|
let rawBlob = null;
|
|
1531
1550
|
if (rawBlobRaw instanceof Uint8Array) {
|
|
1532
|
-
|
|
1551
|
+
if (rawBlobRaw.byteLength <= MAX_EMBEDDING_BLOB_BYTES) {
|
|
1552
|
+
rawBlob = rawBlobRaw;
|
|
1553
|
+
}
|
|
1533
1554
|
} else if (rawBlobRaw !== null && rawBlobRaw !== void 0 && typeof rawBlobRaw === "object") {
|
|
1534
1555
|
const obj = rawBlobRaw;
|
|
1535
1556
|
if (obj["type"] === "Buffer" && Array.isArray(obj["data"])) {
|
|
1536
|
-
|
|
1557
|
+
const data = obj["data"];
|
|
1558
|
+
if (data.length <= MAX_EMBEDDING_BLOB_BYTES) {
|
|
1559
|
+
rawBlob = new Uint8Array(data);
|
|
1560
|
+
}
|
|
1537
1561
|
} else if (!Array.isArray(rawBlobRaw)) {
|
|
1538
1562
|
const entries = Object.keys(obj);
|
|
1539
1563
|
if (entries.length > 0 && entries.every((k) => /^\d+$/.test(k))) {
|
|
1540
1564
|
const len = entries.length;
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1565
|
+
if (len <= MAX_EMBEDDING_BLOB_BYTES) {
|
|
1566
|
+
rawBlob = new Uint8Array(len);
|
|
1567
|
+
for (let i = 0; i < len; i++) {
|
|
1568
|
+
rawBlob[i] = obj[String(i)] ?? 0;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1544
1571
|
}
|
|
1545
1572
|
}
|
|
1546
1573
|
}
|
|
1574
|
+
if (rawBlob !== null && rawBlob.byteLength > MAX_EMBEDDING_BLOB_BYTES) {
|
|
1575
|
+
rawBlob = null;
|
|
1576
|
+
}
|
|
1547
1577
|
let blobData = null;
|
|
1548
1578
|
if (rawBlob !== null && rawBlob.byteLength > 0 && rawBlob.byteLength % 4 === 0) {
|
|
1549
1579
|
const copy = new ArrayBuffer(rawBlob.byteLength);
|
|
@@ -1573,11 +1603,14 @@ var ImportExportService = class {
|
|
|
1573
1603
|
}
|
|
1574
1604
|
if (merge && safeUpdatedAt <= existing.updated_at) continue;
|
|
1575
1605
|
}
|
|
1606
|
+
const safeTitle = clip(String(fact.title ?? ""), IMPORT_TITLE_MAX);
|
|
1607
|
+
const safeBody = clip(String(fact.body ?? ""), IMPORT_BODY_MAX);
|
|
1608
|
+
clippedTextByFactId.set(fact.id, { title: safeTitle, body: safeBody });
|
|
1576
1609
|
const factObj = {
|
|
1577
1610
|
id: fact.id,
|
|
1578
1611
|
entity_id: entityId,
|
|
1579
|
-
title:
|
|
1580
|
-
body:
|
|
1612
|
+
title: safeTitle,
|
|
1613
|
+
body: safeBody,
|
|
1581
1614
|
tags: Array.isArray(fact.tags) ? fact.tags : [],
|
|
1582
1615
|
confidence: fact.confidence,
|
|
1583
1616
|
source_type: sourceType,
|
|
@@ -1588,7 +1621,8 @@ var ImportExportService = class {
|
|
|
1588
1621
|
last_accessed_at: fact.last_accessed_at,
|
|
1589
1622
|
access_count: fact.access_count,
|
|
1590
1623
|
deleted_at: fact.deleted_at,
|
|
1591
|
-
embedding_blob: blobData ?? void 0
|
|
1624
|
+
embedding_blob: blobData ?? void 0,
|
|
1625
|
+
okf_type: fact.okf_type ?? null
|
|
1592
1626
|
};
|
|
1593
1627
|
await this.entryRepo.upsertForImport(factObj, tx);
|
|
1594
1628
|
if (blobData != null) {
|
|
@@ -1637,7 +1671,8 @@ var ImportExportService = class {
|
|
|
1637
1671
|
created_at: task.created_at,
|
|
1638
1672
|
updated_at: safeUpdatedAt,
|
|
1639
1673
|
resolved_at: task.resolved_at,
|
|
1640
|
-
deleted_at: task.deleted_at
|
|
1674
|
+
deleted_at: task.deleted_at,
|
|
1675
|
+
okf_type: task.okf_type ?? null
|
|
1641
1676
|
},
|
|
1642
1677
|
tx,
|
|
1643
1678
|
safeUpdatedAt
|
|
@@ -1661,15 +1696,29 @@ var ImportExportService = class {
|
|
|
1661
1696
|
tx
|
|
1662
1697
|
);
|
|
1663
1698
|
}
|
|
1699
|
+
for (const edge of bundle.edges ?? []) {
|
|
1700
|
+
await this.edgeRepo.addIgnoreDuplicate(
|
|
1701
|
+
{
|
|
1702
|
+
id: edge.id,
|
|
1703
|
+
entity_id: entityId,
|
|
1704
|
+
source_id: edge.source_id,
|
|
1705
|
+
target_id: edge.target_id,
|
|
1706
|
+
edge_type: edge.edge_type,
|
|
1707
|
+
created_at: edge.created_at
|
|
1708
|
+
},
|
|
1709
|
+
tx
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1664
1712
|
});
|
|
1665
1713
|
await this.searchService.sync(entityId);
|
|
1666
1714
|
for (const fact of bundle.facts) {
|
|
1667
1715
|
if (!fact.deleted_at && upsertedFactIds.has(fact.id) && !factsWithPreservedBlob.has(fact.id)) {
|
|
1716
|
+
const clipped = clippedTextByFactId.get(fact.id);
|
|
1668
1717
|
const embedded = await this.embeddingService.embedFact({
|
|
1669
1718
|
id: fact.id,
|
|
1670
1719
|
entity_id: entityId,
|
|
1671
|
-
title: fact.title,
|
|
1672
|
-
body: fact.body,
|
|
1720
|
+
title: clipped?.title ?? fact.title,
|
|
1721
|
+
body: clipped?.body ?? fact.body,
|
|
1673
1722
|
tags: Array.isArray(fact.tags) || typeof fact.tags === "string" ? fact.tags : []
|
|
1674
1723
|
});
|
|
1675
1724
|
if (!embedded) {
|
|
@@ -1769,7 +1818,7 @@ var ImportExportService = class {
|
|
|
1769
1818
|
}
|
|
1770
1819
|
_warnCrossEntityCollision(type, id, existingEntityId, targetEntityId) {
|
|
1771
1820
|
console.warn(
|
|
1772
|
-
`[WikiMemory] importDump: ${type} id
|
|
1821
|
+
`[WikiMemory] importDump: ${type} id ${JSON.stringify(id)} already belongs to entity ${JSON.stringify(existingEntityId)}; skipping for entity ${JSON.stringify(targetEntityId)}`
|
|
1773
1822
|
);
|
|
1774
1823
|
}
|
|
1775
1824
|
_normalizeImportedSourceType(raw, ctx) {
|
|
@@ -1848,7 +1897,7 @@ var EmbeddingService = class {
|
|
|
1848
1897
|
tagsStr = fact.tags;
|
|
1849
1898
|
}
|
|
1850
1899
|
}
|
|
1851
|
-
const text = `${fact.title} ${fact.body} ${tagsStr}`.trim();
|
|
1900
|
+
const text = clip(`${fact.title} ${fact.body} ${tagsStr}`.trim(), 16e3);
|
|
1852
1901
|
try {
|
|
1853
1902
|
const vector = await embedFn(text);
|
|
1854
1903
|
if (vector.length === 0 || !vector.every((v) => typeof v === "number" && isFinite(v))) {
|
|
@@ -1976,7 +2025,7 @@ var RetrievalService = class {
|
|
|
1976
2025
|
const sanitizedTierWeights = shouldExposeReadMetadata(entityId) ? sanitizeTierWeights(entityIds, options?.tierWeights) : void 0;
|
|
1977
2026
|
const exposeMetadata = shouldExposeReadMetadata(entityId);
|
|
1978
2027
|
if (entityIds.length === 0) {
|
|
1979
|
-
const empty = { facts: [], tasks: [], events: [] };
|
|
2028
|
+
const empty = { facts: [], tasks: [], events: [], edges: [] };
|
|
1980
2029
|
if (exposeMetadata) {
|
|
1981
2030
|
empty.metadata = { query, entityIds: [] };
|
|
1982
2031
|
if (sanitizedTierWeights && Object.keys(sanitizedTierWeights).length > 0) empty.metadata.tierWeights = sanitizedTierWeights;
|
|
@@ -2366,7 +2415,7 @@ var RetrievalService = class {
|
|
|
2366
2415
|
if (exposeMetadata && trimmedQuery && scoreByFactId) {
|
|
2367
2416
|
factScores = Object.fromEntries(facts.map((fact) => [fact.id, scoreByFactId.get(fact.id) ?? 0]));
|
|
2368
2417
|
}
|
|
2369
|
-
const bundle = { facts, tasks, events: events.reverse() };
|
|
2418
|
+
const bundle = { facts, tasks, events: events.reverse(), edges: [] };
|
|
2370
2419
|
if (exposeMetadata) {
|
|
2371
2420
|
bundle.metadata = { query, entityIds };
|
|
2372
2421
|
if (sanitizedTierWeights && Object.keys(sanitizedTierWeights).length > 0) bundle.metadata.tierWeights = sanitizedTierWeights;
|
|
@@ -2462,15 +2511,38 @@ var RetrievalService = class {
|
|
|
2462
2511
|
|
|
2463
2512
|
// src/services/WriteService.ts
|
|
2464
2513
|
var WriteService = class {
|
|
2465
|
-
constructor(db, options, eventRepo, metadataRepo, jobManager, maintenanceService) {
|
|
2514
|
+
constructor(db, options, entryRepo, eventRepo, metadataRepo, jobManager, maintenanceService) {
|
|
2466
2515
|
this.db = db;
|
|
2467
2516
|
this.options = options;
|
|
2517
|
+
this.entryRepo = entryRepo;
|
|
2468
2518
|
this.eventRepo = eventRepo;
|
|
2469
2519
|
this.metadataRepo = metadataRepo;
|
|
2470
2520
|
this.jobManager = jobManager;
|
|
2471
2521
|
this.maintenanceService = maintenanceService;
|
|
2472
2522
|
}
|
|
2473
2523
|
async write(entityId, event) {
|
|
2524
|
+
if (typeof entityId !== "string" || entityId.length === 0 || entityId.length > 200 || entityId.includes("\0")) {
|
|
2525
|
+
throw new TypeError(
|
|
2526
|
+
`Invalid entityId: must be a non-empty string at most 200 chars with no null bytes; got ${JSON.stringify(entityId)}.`
|
|
2527
|
+
);
|
|
2528
|
+
}
|
|
2529
|
+
if (event === null || typeof event !== "object" || Array.isArray(event)) {
|
|
2530
|
+
throw new TypeError("Invalid event: must be a non-null object.");
|
|
2531
|
+
}
|
|
2532
|
+
if (typeof event.summary !== "string") {
|
|
2533
|
+
throw new TypeError("Invalid event.summary: must be a string.");
|
|
2534
|
+
}
|
|
2535
|
+
const summary = clip(event.summary, 4e3);
|
|
2536
|
+
let relatedEntryId = null;
|
|
2537
|
+
const rawRelatedEntryId = event.related_entry_id;
|
|
2538
|
+
if (rawRelatedEntryId != null && rawRelatedEntryId !== "") {
|
|
2539
|
+
if (typeof rawRelatedEntryId !== "string" || rawRelatedEntryId.length > 200 || rawRelatedEntryId.includes("\0")) {
|
|
2540
|
+
relatedEntryId = null;
|
|
2541
|
+
} else {
|
|
2542
|
+
const existing = await this.entryRepo.findByIds([rawRelatedEntryId], [entityId]);
|
|
2543
|
+
relatedEntryId = existing.length > 0 ? rawRelatedEntryId : null;
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2474
2546
|
const id = generateId("evt_");
|
|
2475
2547
|
const now = Date.now();
|
|
2476
2548
|
let eventType = event.event_type;
|
|
@@ -2481,8 +2553,8 @@ var WriteService = class {
|
|
|
2481
2553
|
id,
|
|
2482
2554
|
entity_id: entityId,
|
|
2483
2555
|
event_type: eventType,
|
|
2484
|
-
summary
|
|
2485
|
-
related_entry_id:
|
|
2556
|
+
summary,
|
|
2557
|
+
related_entry_id: relatedEntryId,
|
|
2486
2558
|
created_at: now
|
|
2487
2559
|
};
|
|
2488
2560
|
let shouldRunLibrarian = false;
|
|
@@ -2543,5 +2615,5 @@ var WriteService = class {
|
|
|
2543
2615
|
};
|
|
2544
2616
|
|
|
2545
2617
|
export { EmbeddingService, HOOK_TIMEOUT_MARKER, ImportExportService, IngestionService, JobManager, MaintenanceService, PromptService, PrunePartialFailureError, RetrievalService, SearchService, WikiBusyError, WriteService, __privateAdd, __privateGet, __privateSet, generateId, normalizeSourceHash, normalizeSourceRef, parseEmbedding };
|
|
2546
|
-
//# sourceMappingURL=chunk-
|
|
2547
|
-
//# sourceMappingURL=chunk-
|
|
2618
|
+
//# sourceMappingURL=chunk-J4GBC6CP.mjs.map
|
|
2619
|
+
//# sourceMappingURL=chunk-J4GBC6CP.mjs.map
|