@equationalapplications/core-llm-wiki 4.23.1 → 5.0.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/dist/{chunk-MYZJLVX4.mjs → chunk-W53E3I44.mjs} +75 -13
- package/dist/chunk-W53E3I44.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +173 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +101 -12
- package/dist/index.mjs.map +1 -1
- package/dist/{testing-CBjAuTSl.d.mts → testing-DCRNie7k.d.mts} +118 -10
- package/dist/{testing-CBjAuTSl.d.ts → testing-DCRNie7k.d.ts} +118 -10
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +72 -10
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-MYZJLVX4.mjs.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, O as OntologyManifest, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-
|
|
2
|
-
export { E as EntityStatus, d as ExtractedFact, e as ExtractedFactEdge, f as ExtractedFactWithOntology, g as ExtractedTask, h as GraphTraversalOptions, H as HOOK_TIMEOUT_MARKER, L as LLMProvider,
|
|
1
|
+
import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, O as OntologyManifest, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-DCRNie7k.mjs';
|
|
2
|
+
export { E as EntityStatus, d as ExtractedFact, e as ExtractedFactEdge, f as ExtractedFactWithOntology, g as ExtractedTask, h as GraphTraversalOptions, H as HEAL_BATCH_SIZE, i as HEAL_RECHECK_MS, j as HOOK_TIMEOUT_MARKER, k as HealResult, L as LLMProvider, l as ONTOLOGY_BACKFILL_BATCH_SIZE, m as ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, n as ONTOLOGY_BACKFILL_RECHECK_MS, o as OntologyBackfillResult, p as OntologyConfig, q as OntologyEdgeType, r as OntologyMode, s as OntologyNodeType, t as OntologyPromptContext, u as OntologyUpdates, P as PromptOverrides, v as PromptService, w as PrunePartialFailureError, V as VectorRanker, x as VectorRankerFallback, y as VectorRankerRankArgs, z as VectorRankerSemanticResult, A as WikiBusyError, B as WikiBusyOperation, C as WikiCheckpoint, D as WikiConfig, I as WikiEdge, J as WikiEvent, K as WikiFact, N as WikiMemoryTestAccess, Q as WikiOutboxEvent, T as WikiTask, U as WikiTransactionError } from './testing-DCRNie7k.mjs';
|
|
3
3
|
import { OkfFile } from '@equationalapplications/core-okf';
|
|
4
4
|
import 'minisearch';
|
|
5
5
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, O as OntologyManifest, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-
|
|
2
|
-
export { E as EntityStatus, d as ExtractedFact, e as ExtractedFactEdge, f as ExtractedFactWithOntology, g as ExtractedTask, h as GraphTraversalOptions, H as HOOK_TIMEOUT_MARKER, L as LLMProvider,
|
|
1
|
+
import { M as MemoryBundle, F as FormatContextOptions, G as GraphNeighborhood, a as MemoryDump, b as FormattedMemoryDump, O as OntologyManifest, R as ReadOptions, S as SQLiteAdapter, W as WikiOptions, c as WikiMemory } from './testing-DCRNie7k.js';
|
|
2
|
+
export { E as EntityStatus, d as ExtractedFact, e as ExtractedFactEdge, f as ExtractedFactWithOntology, g as ExtractedTask, h as GraphTraversalOptions, H as HEAL_BATCH_SIZE, i as HEAL_RECHECK_MS, j as HOOK_TIMEOUT_MARKER, k as HealResult, L as LLMProvider, l as ONTOLOGY_BACKFILL_BATCH_SIZE, m as ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, n as ONTOLOGY_BACKFILL_RECHECK_MS, o as OntologyBackfillResult, p as OntologyConfig, q as OntologyEdgeType, r as OntologyMode, s as OntologyNodeType, t as OntologyPromptContext, u as OntologyUpdates, P as PromptOverrides, v as PromptService, w as PrunePartialFailureError, V as VectorRanker, x as VectorRankerFallback, y as VectorRankerRankArgs, z as VectorRankerSemanticResult, A as WikiBusyError, B as WikiBusyOperation, C as WikiCheckpoint, D as WikiConfig, I as WikiEdge, J as WikiEvent, K as WikiFact, N as WikiMemoryTestAccess, Q as WikiOutboxEvent, T as WikiTask, U as WikiTransactionError } from './testing-DCRNie7k.js';
|
|
3
3
|
import { OkfFile } from '@equationalapplications/core-okf';
|
|
4
4
|
import 'minisearch';
|
|
5
5
|
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,8 @@ async function setupDatabase(db, prefix) {
|
|
|
36
36
|
embedding TEXT,
|
|
37
37
|
embedding_blob BLOB,
|
|
38
38
|
okf_type TEXT,
|
|
39
|
-
ontology_checked_at INTEGER
|
|
39
|
+
ontology_checked_at INTEGER,
|
|
40
|
+
heal_checked_at INTEGER
|
|
40
41
|
);
|
|
41
42
|
|
|
42
43
|
CREATE INDEX IF NOT EXISTS ${prefix}entries_entity_idx ON ${prefix}entries(entity_id);
|
|
@@ -353,6 +354,20 @@ var MIGRATIONS = [
|
|
|
353
354
|
);
|
|
354
355
|
}
|
|
355
356
|
}
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
version: 8,
|
|
360
|
+
description: "Add heal_checked_at to entries for heal recheck cooldown",
|
|
361
|
+
run: async (db, prefix) => {
|
|
362
|
+
const cols = await db.getAllAsync(
|
|
363
|
+
`PRAGMA table_info(${prefix}entries)`
|
|
364
|
+
);
|
|
365
|
+
if (!cols.some((c) => c.name === "heal_checked_at")) {
|
|
366
|
+
await db.execAsync(
|
|
367
|
+
`ALTER TABLE ${prefix}entries ADD COLUMN heal_checked_at INTEGER`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
356
371
|
}
|
|
357
372
|
];
|
|
358
373
|
for (let i = 1; i < MIGRATIONS.length; i++) {
|
|
@@ -709,17 +724,21 @@ var EntryRepository = class extends BaseRepository {
|
|
|
709
724
|
}
|
|
710
725
|
/**
|
|
711
726
|
* Fetch live, mutable entries for an entity — everything heal is allowed to
|
|
712
|
-
* downgrade or delete
|
|
713
|
-
*
|
|
714
|
-
*
|
|
727
|
+
* downgrade or delete — oldest first, capped at `limit`, skipping anything
|
|
728
|
+
* stamped inside the recheck cooldown (heal_checked_at > recheckCutoff).
|
|
729
|
+
*
|
|
730
|
+
* Oldest-first rather than newest-first: with a cooldown in place, newest-first
|
|
731
|
+
* would keep re-selecting recently-touched facts every pass while older ones
|
|
732
|
+
* wait indefinitely to even enter a batch.
|
|
715
733
|
*/
|
|
716
|
-
async findHealCandidatesByEntityId(entityId, tx) {
|
|
734
|
+
async findHealCandidatesByEntityId(entityId, limit, recheckCutoff, tx) {
|
|
717
735
|
const executor = this.getExecutor(tx);
|
|
718
736
|
const rows = await executor.getAllAsync(
|
|
719
737
|
`SELECT * FROM ${this.prefix}entries
|
|
720
738
|
WHERE entity_id = ? AND deleted_at IS NULL AND source_type != 'immutable_document'
|
|
721
|
-
|
|
722
|
-
|
|
739
|
+
AND (heal_checked_at IS NULL OR heal_checked_at <= ?)
|
|
740
|
+
ORDER BY updated_at ASC LIMIT ?`,
|
|
741
|
+
[entityId, recheckCutoff, limit]
|
|
723
742
|
);
|
|
724
743
|
return rows.map(mapRowToFact);
|
|
725
744
|
}
|
|
@@ -882,6 +901,10 @@ var EntryRepository = class extends BaseRepository {
|
|
|
882
901
|
/**
|
|
883
902
|
* Downgrade stale inferred entries to 'tentative'.
|
|
884
903
|
* Used by MaintenanceService.doRunHeal().
|
|
904
|
+
*
|
|
905
|
+
* Returns the ids it downgraded so the caller can fold them into
|
|
906
|
+
* {@link HealResult.downgraded} without double-counting a fact the model also
|
|
907
|
+
* downgraded in the same pass.
|
|
885
908
|
*/
|
|
886
909
|
async downgradeStaleInferred(entityId, staleThreshold, tx) {
|
|
887
910
|
const executor = this.getExecutor(tx);
|
|
@@ -893,8 +916,8 @@ var EntryRepository = class extends BaseRepository {
|
|
|
893
916
|
AND source_type != 'immutable_document' AND deleted_at IS NULL`,
|
|
894
917
|
[entityId, staleThreshold, staleThreshold]
|
|
895
918
|
);
|
|
896
|
-
if (eligibleRows.length === 0) return
|
|
897
|
-
|
|
919
|
+
if (eligibleRows.length === 0) return [];
|
|
920
|
+
await executor.runAsync(
|
|
898
921
|
`UPDATE ${this.prefix}entries
|
|
899
922
|
SET confidence = 'tentative', updated_at = ?
|
|
900
923
|
WHERE entity_id = ? AND confidence = 'inferred' AND (last_accessed_at <= ? OR (last_accessed_at IS NULL AND created_at <= ?)) AND source_type != 'immutable_document' AND deleted_at IS NULL`,
|
|
@@ -909,7 +932,7 @@ var EntryRepository = class extends BaseRepository {
|
|
|
909
932
|
payload: { id: row.id, entity_id: entityId, confidence: "tentative", updated_at: now }
|
|
910
933
|
}, tx);
|
|
911
934
|
}
|
|
912
|
-
return
|
|
935
|
+
return eligibleRows.map((r) => r.id);
|
|
913
936
|
}
|
|
914
937
|
/**
|
|
915
938
|
* Downgrade specific entries to 'tentative' by IDs.
|
|
@@ -1228,6 +1251,58 @@ var EntryRepository = class extends BaseRepository {
|
|
|
1228
1251
|
);
|
|
1229
1252
|
return rows.map((r) => ({ id: r.id, title: r.title, okf_type: r.okf_type ?? null }));
|
|
1230
1253
|
}
|
|
1254
|
+
/** Counts live mutable facts: eligible (past cooldown) vs deferred (in cooldown). */
|
|
1255
|
+
async countHealCandidatesByEntityId(entityId, recheckCutoff, tx) {
|
|
1256
|
+
const executor = this.getExecutor(tx);
|
|
1257
|
+
const row = await executor.getFirstAsync(
|
|
1258
|
+
`SELECT
|
|
1259
|
+
SUM(CASE WHEN heal_checked_at IS NULL OR heal_checked_at <= ? THEN 1 ELSE 0 END) AS eligible,
|
|
1260
|
+
SUM(CASE WHEN heal_checked_at IS NOT NULL AND heal_checked_at > ? THEN 1 ELSE 0 END) AS deferred
|
|
1261
|
+
FROM ${this.prefix}entries
|
|
1262
|
+
WHERE entity_id = ? AND deleted_at IS NULL AND source_type != 'immutable_document'`,
|
|
1263
|
+
[recheckCutoff, recheckCutoff, entityId]
|
|
1264
|
+
);
|
|
1265
|
+
return { eligible: Number(row?.eligible ?? 0), deferred: Number(row?.deferred ?? 0) };
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Stamps the heal recheck cooldown. NEVER touches updated_at — import merge
|
|
1269
|
+
* resolution is last-write-wins on updated_at and a bump here would make an
|
|
1270
|
+
* unchanged local fact beat a genuinely newer remote edit.
|
|
1271
|
+
*
|
|
1272
|
+
* Soft-deleted rows are skipped: a candidate heal deleted earlier in the same
|
|
1273
|
+
* transaction is no longer a candidate under any future pass, so its cooldown
|
|
1274
|
+
* value is irrelevant. This means the stamped count can be lower than the
|
|
1275
|
+
* offered-candidate count.
|
|
1276
|
+
*/
|
|
1277
|
+
async markHealChecked(ids, entityId, now, tx) {
|
|
1278
|
+
if (ids.length === 0) return;
|
|
1279
|
+
for (let i = 0; i < ids.length; i += this.chunkSize) {
|
|
1280
|
+
const chunk = ids.slice(i, i + this.chunkSize);
|
|
1281
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
1282
|
+
await tx.runAsync(
|
|
1283
|
+
`UPDATE ${this.prefix}entries SET heal_checked_at = ?
|
|
1284
|
+
WHERE id IN (${placeholders}) AND entity_id = ? AND deleted_at IS NULL`,
|
|
1285
|
+
[now, ...chunk, entityId]
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Lightweight full-breadth index (id, title) over all live librarian_inferred
|
|
1291
|
+
* facts. This is heal's fuzzy-dedupe corpus, deliberately kept independent of
|
|
1292
|
+
* the bounded candidate window: seeding dedupe from a batchSize-limited read
|
|
1293
|
+
* would let a synthesized fact duplicating a fact outside the window pass the
|
|
1294
|
+
* Jaccard check, and a convergence loop would multiply those duplicates across
|
|
1295
|
+
* passes. Mirrors findTitleIndexByEntityId — full breadth, two columns, cheap.
|
|
1296
|
+
*/
|
|
1297
|
+
async findInferredTitlesByEntityId(entityId, tx) {
|
|
1298
|
+
const executor = this.getExecutor(tx);
|
|
1299
|
+
const rows = await executor.getAllAsync(
|
|
1300
|
+
`SELECT id, title FROM ${this.prefix}entries
|
|
1301
|
+
WHERE entity_id = ? AND deleted_at IS NULL AND source_type = 'librarian_inferred'`,
|
|
1302
|
+
[entityId]
|
|
1303
|
+
);
|
|
1304
|
+
return rows.map((r) => ({ id: r.id, title: r.title }));
|
|
1305
|
+
}
|
|
1231
1306
|
};
|
|
1232
1307
|
|
|
1233
1308
|
// src/utils/ids.ts
|
|
@@ -3252,6 +3327,8 @@ var ONTOLOGY_BACKFILL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
3252
3327
|
var HEAL_MAX_ANCHORS = 50;
|
|
3253
3328
|
var HEAL_ANCHOR_SEARCH_OVERFETCH = 4;
|
|
3254
3329
|
var HEAL_MAX_PROMPT_CHARS = 4e4;
|
|
3330
|
+
var HEAL_BATCH_SIZE = 25;
|
|
3331
|
+
var HEAL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
3255
3332
|
var MaintenanceService = class {
|
|
3256
3333
|
constructor(db, prefix, options, entryRepo, taskRepo, eventRepo, metadataRepo, searchService, jobManager, embeddingService, promptService, ontologyService) {
|
|
3257
3334
|
this.db = db;
|
|
@@ -3352,7 +3429,7 @@ var MaintenanceService = class {
|
|
|
3352
3429
|
async runHeal(entityId, options) {
|
|
3353
3430
|
this.jobManager.acquireLock("heal", entityId);
|
|
3354
3431
|
try {
|
|
3355
|
-
await this.doRunHeal(entityId, options
|
|
3432
|
+
return await this.doRunHeal(entityId, options);
|
|
3356
3433
|
} finally {
|
|
3357
3434
|
this.jobManager.releaseLock("heal", entityId);
|
|
3358
3435
|
}
|
|
@@ -3604,9 +3681,21 @@ var MaintenanceService = class {
|
|
|
3604
3681
|
}
|
|
3605
3682
|
this.searchService.evictCache(entityId);
|
|
3606
3683
|
}
|
|
3607
|
-
/**
|
|
3608
|
-
|
|
3684
|
+
/**
|
|
3685
|
+
* Core heal pass (locks handled by {@link runHeal}). Package-internal orchestration hook.
|
|
3686
|
+
*
|
|
3687
|
+
* Bounded: at most `batchSize` candidates per pass (#67). Loop on
|
|
3688
|
+
* `result.remaining > 0` for convergence — see {@link HealResult.remaining}
|
|
3689
|
+
* for what convergence means here.
|
|
3690
|
+
*/
|
|
3691
|
+
async doRunHeal(entityId, options) {
|
|
3692
|
+
const promptOverride = options?.promptOverride;
|
|
3693
|
+
const batchSize = options?.batchSize ?? HEAL_BATCH_SIZE;
|
|
3694
|
+
if (!Number.isInteger(batchSize) || batchSize < 1) {
|
|
3695
|
+
throw new Error("Invalid batchSize: must be an integer >= 1");
|
|
3696
|
+
}
|
|
3609
3697
|
const now = Date.now();
|
|
3698
|
+
const recheckCutoff = now - HEAL_RECHECK_MS;
|
|
3610
3699
|
const orphanAfterDays = this.options.config?.orphanAfterDays !== void 0 ? this.options.config?.orphanAfterDays : 30;
|
|
3611
3700
|
const staleInferredAfterDays = this.options.config?.staleInferredAfterDays !== void 0 ? this.options.config?.staleInferredAfterDays : 60;
|
|
3612
3701
|
const MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
@@ -3617,6 +3706,7 @@ var MaintenanceService = class {
|
|
|
3617
3706
|
throw new Error("Invalid staleInferredAfterDays: must be a finite number >= 0 or null");
|
|
3618
3707
|
}
|
|
3619
3708
|
const orphanedIds = [];
|
|
3709
|
+
const staleDowngradedIds = [];
|
|
3620
3710
|
await this.db.withTransactionAsync(async (tx) => {
|
|
3621
3711
|
if (orphanAfterDays !== null) {
|
|
3622
3712
|
const orphanThreshold = now - orphanAfterDays * MS_PER_DAY;
|
|
@@ -3624,7 +3714,7 @@ var MaintenanceService = class {
|
|
|
3624
3714
|
}
|
|
3625
3715
|
if (staleInferredAfterDays !== null) {
|
|
3626
3716
|
const staleThreshold = now - staleInferredAfterDays * MS_PER_DAY;
|
|
3627
|
-
await this.entryRepo.downgradeStaleInferred(entityId, staleThreshold, tx);
|
|
3717
|
+
staleDowngradedIds.push(...await this.entryRepo.downgradeStaleInferred(entityId, staleThreshold, tx));
|
|
3628
3718
|
}
|
|
3629
3719
|
});
|
|
3630
3720
|
for (const factId of orphanedIds) {
|
|
@@ -3634,7 +3724,21 @@ var MaintenanceService = class {
|
|
|
3634
3724
|
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed during heal orphan pass for ${factId}:`, hookErr);
|
|
3635
3725
|
}
|
|
3636
3726
|
}
|
|
3637
|
-
const healCandidates = await this.entryRepo.findHealCandidatesByEntityId(entityId);
|
|
3727
|
+
const healCandidates = await this.entryRepo.findHealCandidatesByEntityId(entityId, batchSize, recheckCutoff);
|
|
3728
|
+
if (healCandidates.length === 0) {
|
|
3729
|
+
await this.searchService.sync(entityId);
|
|
3730
|
+
this.searchService.evictCache(entityId);
|
|
3731
|
+
const counts2 = await this.entryRepo.countHealCandidatesByEntityId(entityId, recheckCutoff);
|
|
3732
|
+
return {
|
|
3733
|
+
scanned: 0,
|
|
3734
|
+
downgraded: staleDowngradedIds.length,
|
|
3735
|
+
deleted: orphanedIds.length,
|
|
3736
|
+
newFactsCreated: 0,
|
|
3737
|
+
skipped: 0,
|
|
3738
|
+
remaining: counts2.eligible,
|
|
3739
|
+
deferred: counts2.deferred
|
|
3740
|
+
};
|
|
3741
|
+
}
|
|
3638
3742
|
const allTasks = await this.taskRepo.findAllPending([entityId]);
|
|
3639
3743
|
const recentEvents = await this.eventRepo.getRecent(entityId, 20);
|
|
3640
3744
|
const toPromptShape = (f) => {
|
|
@@ -3687,7 +3791,7 @@ var MaintenanceService = class {
|
|
|
3687
3791
|
const validNewFacts = newFacts.map(validateFact).filter((f) => f !== null);
|
|
3688
3792
|
const insertedFacts = [];
|
|
3689
3793
|
const uniqueDeletedFactIds = Array.from(new Set(safeDeleted));
|
|
3690
|
-
const healFactsForDedupe =
|
|
3794
|
+
const healFactsForDedupe = (await this.entryRepo.findInferredTitlesByEntityId(entityId)).filter((f) => !safeDeletedSet.has(f.id));
|
|
3691
3795
|
await this.db.withTransactionAsync(async (tx) => {
|
|
3692
3796
|
await this.entryRepo.downgradeByIds(safeDowngraded, entityId, tx);
|
|
3693
3797
|
await this.entryRepo.softDeleteByIds(safeDeleted, entityId, tx);
|
|
@@ -3696,7 +3800,6 @@ var MaintenanceService = class {
|
|
|
3696
3800
|
let skip = false;
|
|
3697
3801
|
if (newTokens.size >= MIN_TOKENS_TO_QUALIFY) {
|
|
3698
3802
|
for (const existing of healFactsForDedupe) {
|
|
3699
|
-
if (existing.source_type !== "librarian_inferred") continue;
|
|
3700
3803
|
const existingTokens = titleTokens(existing.title);
|
|
3701
3804
|
if (existingTokens.size >= MIN_TOKENS_TO_QUALIFY) {
|
|
3702
3805
|
if (jaccardScore(newTokens, existingTokens) >= FUZZY_THRESHOLD) {
|
|
@@ -3726,8 +3829,14 @@ var MaintenanceService = class {
|
|
|
3726
3829
|
};
|
|
3727
3830
|
await this.entryRepo.upsert(factObj, tx);
|
|
3728
3831
|
insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
|
|
3729
|
-
healFactsForDedupe.push(
|
|
3832
|
+
healFactsForDedupe.push({ id, title: fact.title });
|
|
3730
3833
|
}
|
|
3834
|
+
await this.entryRepo.markHealChecked(
|
|
3835
|
+
[...healCandidates.map((f) => f.id), ...insertedFacts.map((f) => f.id)],
|
|
3836
|
+
entityId,
|
|
3837
|
+
now,
|
|
3838
|
+
tx
|
|
3839
|
+
);
|
|
3731
3840
|
});
|
|
3732
3841
|
await this.searchService.sync(entityId);
|
|
3733
3842
|
for (const factId of uniqueDeletedFactIds) {
|
|
@@ -3741,6 +3850,20 @@ var MaintenanceService = class {
|
|
|
3741
3850
|
await this.embeddingService.embedFact(fact);
|
|
3742
3851
|
}
|
|
3743
3852
|
this.searchService.evictCache(entityId);
|
|
3853
|
+
let scanned = outcome.skipped.length;
|
|
3854
|
+
for (const batchResult of outcome.results) scanned += batchResult.batch.length;
|
|
3855
|
+
const allDowngraded = /* @__PURE__ */ new Set([...staleDowngradedIds, ...safeDowngraded]);
|
|
3856
|
+
const allDeleted = /* @__PURE__ */ new Set([...orphanedIds, ...uniqueDeletedFactIds]);
|
|
3857
|
+
const counts = await this.entryRepo.countHealCandidatesByEntityId(entityId, recheckCutoff);
|
|
3858
|
+
return {
|
|
3859
|
+
scanned,
|
|
3860
|
+
downgraded: allDowngraded.size,
|
|
3861
|
+
deleted: allDeleted.size,
|
|
3862
|
+
newFactsCreated: insertedFacts.length,
|
|
3863
|
+
skipped: outcome.skipped.length,
|
|
3864
|
+
remaining: counts.eligible,
|
|
3865
|
+
deferred: counts.deferred
|
|
3866
|
+
};
|
|
3744
3867
|
}
|
|
3745
3868
|
/** Core ontology backfill pass (locks handled by {@link runOntologyBackfill}). Package-internal orchestration hook. */
|
|
3746
3869
|
async doRunOntologyBackfill(entityId, options) {
|
|
@@ -5116,6 +5239,7 @@ var WriteService = class {
|
|
|
5116
5239
|
let shouldRunLibrarian = false;
|
|
5117
5240
|
let librarianCount = 0;
|
|
5118
5241
|
let prevMemoryCheckpoint = 0;
|
|
5242
|
+
let eventCount = 0;
|
|
5119
5243
|
await this.db.withTransactionAsync(async (tx) => {
|
|
5120
5244
|
await this.eventRepo.add(newEvent, tx);
|
|
5121
5245
|
const threshold = this.options.config?.autoLibrarianThreshold || 20;
|
|
@@ -5123,6 +5247,7 @@ var WriteService = class {
|
|
|
5123
5247
|
this.eventRepo.count(entityId, tx),
|
|
5124
5248
|
this.metadataRepo.getCheckpoint(entityId, tx)
|
|
5125
5249
|
]);
|
|
5250
|
+
eventCount = count;
|
|
5126
5251
|
let memoryCheckpoint = cp.memory ?? 0;
|
|
5127
5252
|
if (memoryCheckpoint > count) memoryCheckpoint = 0;
|
|
5128
5253
|
if (count - memoryCheckpoint >= threshold) {
|
|
@@ -5144,6 +5269,8 @@ var WriteService = class {
|
|
|
5144
5269
|
if (!(e instanceof WikiBusyError)) throw e;
|
|
5145
5270
|
await this.metadataRepo.updateCheckpoint(entityId, { memory: prevMemoryCheckpoint }, this.db);
|
|
5146
5271
|
}
|
|
5272
|
+
} else if (!this.jobManager.isBlocked("librarian", entityId)) {
|
|
5273
|
+
this.maybeRunHeal(entityId, eventCount).catch(console.error);
|
|
5147
5274
|
}
|
|
5148
5275
|
}
|
|
5149
5276
|
async runLibrarianThenMaybeHeal(entityId, currentEventCount, prevCheckpoint) {
|
|
@@ -5154,6 +5281,14 @@ var WriteService = class {
|
|
|
5154
5281
|
await this.metadataRepo.updateCheckpoint(entityId, { memory: prevCheckpoint }, this.db);
|
|
5155
5282
|
throw e;
|
|
5156
5283
|
}
|
|
5284
|
+
await this.maybeRunHeal(entityId, currentEventCount);
|
|
5285
|
+
}
|
|
5286
|
+
/**
|
|
5287
|
+
* Run one bounded auto-heal pass if the heal checkpoint has fallen
|
|
5288
|
+
* `autoHealThreshold` events behind. Called after every write (see
|
|
5289
|
+
* {@link write}) so a partial pass retries on the next write.
|
|
5290
|
+
*/
|
|
5291
|
+
async maybeRunHeal(entityId, currentEventCount) {
|
|
5157
5292
|
const autoHealThreshold = this.options.config?.autoHealThreshold || 100;
|
|
5158
5293
|
const cp = await this.metadataRepo.getCheckpoint(entityId, this.db);
|
|
5159
5294
|
let healCheckpoint = cp.heal ?? 0;
|
|
@@ -5161,8 +5296,10 @@ var WriteService = class {
|
|
|
5161
5296
|
const shouldRunHeal = currentEventCount - healCheckpoint >= autoHealThreshold;
|
|
5162
5297
|
if (shouldRunHeal && this.jobManager.tryAcquireAutoHealLock(entityId)) {
|
|
5163
5298
|
try {
|
|
5164
|
-
await this.maintenanceService.doRunHeal(entityId);
|
|
5165
|
-
|
|
5299
|
+
const result = await this.maintenanceService.doRunHeal(entityId);
|
|
5300
|
+
if (result.remaining === 0) {
|
|
5301
|
+
await this.metadataRepo.updateCheckpoint(entityId, { heal: currentEventCount }, this.db);
|
|
5302
|
+
}
|
|
5166
5303
|
} finally {
|
|
5167
5304
|
this.jobManager.releaseLock("heal", entityId);
|
|
5168
5305
|
}
|
|
@@ -5513,9 +5650,23 @@ var WikiMemory = class {
|
|
|
5513
5650
|
return this.maintenanceService.runLibrarian(entityId, options);
|
|
5514
5651
|
}
|
|
5515
5652
|
/**
|
|
5653
|
+
* Reviews stored facts with the LLM: removes orphans, downgrades stale
|
|
5654
|
+
* inferences, synthesizes corrections.
|
|
5655
|
+
*
|
|
5656
|
+
* Bounded per call: covers at most `batchSize` candidates (default 25), so
|
|
5657
|
+
* one call no longer sweeps the whole entity. Hosts own the cadence; loop
|
|
5658
|
+
* `while (result.remaining > 0)` for convergence.
|
|
5659
|
+
*
|
|
5660
|
+
* `remaining === 0` means "every mutable fact is inside the 7-day recheck
|
|
5661
|
+
* cooldown", not "there is no more work" — heal's candidate set is the live
|
|
5662
|
+
* mutable corpus, not a draining backlog, so it climbs back as the cooldown
|
|
5663
|
+
* lapses and as new facts are written. Do not write a loop expecting a drain.
|
|
5664
|
+
*
|
|
5516
5665
|
* @param options.promptOverride - Applies only to this manual call. Does NOT affect
|
|
5517
5666
|
* WriteService-triggered auto-runs. For persistent prompt customization across auto-runs,
|
|
5518
5667
|
* set `options.config.prompts.healSystemPrompt` at WikiMemory construction time.
|
|
5668
|
+
* @param options.batchSize - Candidates per run (default 25) for providers with
|
|
5669
|
+
* tighter context limits.
|
|
5519
5670
|
*/
|
|
5520
5671
|
async runHeal(entityId, options) {
|
|
5521
5672
|
return this.maintenanceService.runHeal(entityId, options);
|
|
@@ -6376,6 +6527,8 @@ function createWiki(db, options) {
|
|
|
6376
6527
|
}
|
|
6377
6528
|
|
|
6378
6529
|
exports.DEFAULT_LIBRARIAN_SYNTHESIS_PROMPT = DEFAULT_LIBRARIAN_SYNTHESIS_PROMPT;
|
|
6530
|
+
exports.HEAL_BATCH_SIZE = HEAL_BATCH_SIZE;
|
|
6531
|
+
exports.HEAL_RECHECK_MS = HEAL_RECHECK_MS;
|
|
6379
6532
|
exports.HOOK_TIMEOUT_MARKER = HOOK_TIMEOUT_MARKER;
|
|
6380
6533
|
exports.ONTOLOGY_BACKFILL_BATCH_SIZE = ONTOLOGY_BACKFILL_BATCH_SIZE;
|
|
6381
6534
|
exports.ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS = ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS;
|