@equationalapplications/core-llm-wiki 4.23.0 → 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.
@@ -1569,6 +1569,8 @@ var ONTOLOGY_BACKFILL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
1569
1569
  var HEAL_MAX_ANCHORS = 50;
1570
1570
  var HEAL_ANCHOR_SEARCH_OVERFETCH = 4;
1571
1571
  var HEAL_MAX_PROMPT_CHARS = 4e4;
1572
+ var HEAL_BATCH_SIZE = 25;
1573
+ var HEAL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
1572
1574
  var MaintenanceService = class {
1573
1575
  constructor(db, prefix, options, entryRepo, taskRepo, eventRepo, metadataRepo, searchService, jobManager, embeddingService, promptService, ontologyService) {
1574
1576
  this.db = db;
@@ -1669,7 +1671,7 @@ var MaintenanceService = class {
1669
1671
  async runHeal(entityId, options) {
1670
1672
  this.jobManager.acquireLock("heal", entityId);
1671
1673
  try {
1672
- await this.doRunHeal(entityId, options?.promptOverride);
1674
+ return await this.doRunHeal(entityId, options);
1673
1675
  } finally {
1674
1676
  this.jobManager.releaseLock("heal", entityId);
1675
1677
  }
@@ -1921,9 +1923,21 @@ var MaintenanceService = class {
1921
1923
  }
1922
1924
  this.searchService.evictCache(entityId);
1923
1925
  }
1924
- /** Core heal pass (locks handled by {@link runHeal}). Package-internal orchestration hook. */
1925
- async doRunHeal(entityId, promptOverride) {
1926
+ /**
1927
+ * Core heal pass (locks handled by {@link runHeal}). Package-internal orchestration hook.
1928
+ *
1929
+ * Bounded: at most `batchSize` candidates per pass (#67). Loop on
1930
+ * `result.remaining > 0` for convergence — see {@link HealResult.remaining}
1931
+ * for what convergence means here.
1932
+ */
1933
+ async doRunHeal(entityId, options) {
1934
+ const promptOverride = options?.promptOverride;
1935
+ const batchSize = options?.batchSize ?? HEAL_BATCH_SIZE;
1936
+ if (!Number.isInteger(batchSize) || batchSize < 1) {
1937
+ throw new Error("Invalid batchSize: must be an integer >= 1");
1938
+ }
1926
1939
  const now = Date.now();
1940
+ const recheckCutoff = now - HEAL_RECHECK_MS;
1927
1941
  const orphanAfterDays = this.options.config?.orphanAfterDays !== void 0 ? this.options.config?.orphanAfterDays : 30;
1928
1942
  const staleInferredAfterDays = this.options.config?.staleInferredAfterDays !== void 0 ? this.options.config?.staleInferredAfterDays : 60;
1929
1943
  const MS_PER_DAY = 24 * 60 * 60 * 1e3;
@@ -1934,6 +1948,7 @@ var MaintenanceService = class {
1934
1948
  throw new Error("Invalid staleInferredAfterDays: must be a finite number >= 0 or null");
1935
1949
  }
1936
1950
  const orphanedIds = [];
1951
+ const staleDowngradedIds = [];
1937
1952
  await this.db.withTransactionAsync(async (tx) => {
1938
1953
  if (orphanAfterDays !== null) {
1939
1954
  const orphanThreshold = now - orphanAfterDays * MS_PER_DAY;
@@ -1941,7 +1956,7 @@ var MaintenanceService = class {
1941
1956
  }
1942
1957
  if (staleInferredAfterDays !== null) {
1943
1958
  const staleThreshold = now - staleInferredAfterDays * MS_PER_DAY;
1944
- await this.entryRepo.downgradeStaleInferred(entityId, staleThreshold, tx);
1959
+ staleDowngradedIds.push(...await this.entryRepo.downgradeStaleInferred(entityId, staleThreshold, tx));
1945
1960
  }
1946
1961
  });
1947
1962
  for (const factId of orphanedIds) {
@@ -1951,7 +1966,21 @@ var MaintenanceService = class {
1951
1966
  console.warn(`[WikiMemory] onEmbeddingPersisted hook failed during heal orphan pass for ${factId}:`, hookErr);
1952
1967
  }
1953
1968
  }
1954
- const healCandidates = await this.entryRepo.findHealCandidatesByEntityId(entityId);
1969
+ const healCandidates = await this.entryRepo.findHealCandidatesByEntityId(entityId, batchSize, recheckCutoff);
1970
+ if (healCandidates.length === 0) {
1971
+ await this.searchService.sync(entityId);
1972
+ this.searchService.evictCache(entityId);
1973
+ const counts2 = await this.entryRepo.countHealCandidatesByEntityId(entityId, recheckCutoff);
1974
+ return {
1975
+ scanned: 0,
1976
+ downgraded: staleDowngradedIds.length,
1977
+ deleted: orphanedIds.length,
1978
+ newFactsCreated: 0,
1979
+ skipped: 0,
1980
+ remaining: counts2.eligible,
1981
+ deferred: counts2.deferred
1982
+ };
1983
+ }
1955
1984
  const allTasks = await this.taskRepo.findAllPending([entityId]);
1956
1985
  const recentEvents = await this.eventRepo.getRecent(entityId, 20);
1957
1986
  const toPromptShape = (f) => {
@@ -2004,7 +2033,7 @@ var MaintenanceService = class {
2004
2033
  const validNewFacts = newFacts.map(validateFact).filter((f) => f !== null);
2005
2034
  const insertedFacts = [];
2006
2035
  const uniqueDeletedFactIds = Array.from(new Set(safeDeleted));
2007
- const healFactsForDedupe = [...healCandidates];
2036
+ const healFactsForDedupe = (await this.entryRepo.findInferredTitlesByEntityId(entityId)).filter((f) => !safeDeletedSet.has(f.id));
2008
2037
  await this.db.withTransactionAsync(async (tx) => {
2009
2038
  await this.entryRepo.downgradeByIds(safeDowngraded, entityId, tx);
2010
2039
  await this.entryRepo.softDeleteByIds(safeDeleted, entityId, tx);
@@ -2013,7 +2042,6 @@ var MaintenanceService = class {
2013
2042
  let skip = false;
2014
2043
  if (newTokens.size >= MIN_TOKENS_TO_QUALIFY) {
2015
2044
  for (const existing of healFactsForDedupe) {
2016
- if (existing.source_type !== "librarian_inferred") continue;
2017
2045
  const existingTokens = titleTokens(existing.title);
2018
2046
  if (existingTokens.size >= MIN_TOKENS_TO_QUALIFY) {
2019
2047
  if (jaccardScore(newTokens, existingTokens) >= FUZZY_THRESHOLD) {
@@ -2043,8 +2071,14 @@ var MaintenanceService = class {
2043
2071
  };
2044
2072
  await this.entryRepo.upsert(factObj, tx);
2045
2073
  insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
2046
- healFactsForDedupe.push(factObj);
2074
+ healFactsForDedupe.push({ id, title: fact.title });
2047
2075
  }
2076
+ await this.entryRepo.markHealChecked(
2077
+ [...healCandidates.map((f) => f.id), ...insertedFacts.map((f) => f.id)],
2078
+ entityId,
2079
+ now,
2080
+ tx
2081
+ );
2048
2082
  });
2049
2083
  await this.searchService.sync(entityId);
2050
2084
  for (const factId of uniqueDeletedFactIds) {
@@ -2058,6 +2092,20 @@ var MaintenanceService = class {
2058
2092
  await this.embeddingService.embedFact(fact);
2059
2093
  }
2060
2094
  this.searchService.evictCache(entityId);
2095
+ let scanned = outcome.skipped.length;
2096
+ for (const batchResult of outcome.results) scanned += batchResult.batch.length;
2097
+ const allDowngraded = /* @__PURE__ */ new Set([...staleDowngradedIds, ...safeDowngraded]);
2098
+ const allDeleted = /* @__PURE__ */ new Set([...orphanedIds, ...uniqueDeletedFactIds]);
2099
+ const counts = await this.entryRepo.countHealCandidatesByEntityId(entityId, recheckCutoff);
2100
+ return {
2101
+ scanned,
2102
+ downgraded: allDowngraded.size,
2103
+ deleted: allDeleted.size,
2104
+ newFactsCreated: insertedFacts.length,
2105
+ skipped: outcome.skipped.length,
2106
+ remaining: counts.eligible,
2107
+ deferred: counts.deferred
2108
+ };
2061
2109
  }
2062
2110
  /** Core ontology backfill pass (locks handled by {@link runOntologyBackfill}). Package-internal orchestration hook. */
2063
2111
  async doRunOntologyBackfill(entityId, options) {
@@ -3433,6 +3481,7 @@ var WriteService = class {
3433
3481
  let shouldRunLibrarian = false;
3434
3482
  let librarianCount = 0;
3435
3483
  let prevMemoryCheckpoint = 0;
3484
+ let eventCount = 0;
3436
3485
  await this.db.withTransactionAsync(async (tx) => {
3437
3486
  await this.eventRepo.add(newEvent, tx);
3438
3487
  const threshold = this.options.config?.autoLibrarianThreshold || 20;
@@ -3440,6 +3489,7 @@ var WriteService = class {
3440
3489
  this.eventRepo.count(entityId, tx),
3441
3490
  this.metadataRepo.getCheckpoint(entityId, tx)
3442
3491
  ]);
3492
+ eventCount = count;
3443
3493
  let memoryCheckpoint = cp.memory ?? 0;
3444
3494
  if (memoryCheckpoint > count) memoryCheckpoint = 0;
3445
3495
  if (count - memoryCheckpoint >= threshold) {
@@ -3461,6 +3511,8 @@ var WriteService = class {
3461
3511
  if (!(e instanceof WikiBusyError)) throw e;
3462
3512
  await this.metadataRepo.updateCheckpoint(entityId, { memory: prevMemoryCheckpoint }, this.db);
3463
3513
  }
3514
+ } else if (!this.jobManager.isBlocked("librarian", entityId)) {
3515
+ this.maybeRunHeal(entityId, eventCount).catch(console.error);
3464
3516
  }
3465
3517
  }
3466
3518
  async runLibrarianThenMaybeHeal(entityId, currentEventCount, prevCheckpoint) {
@@ -3471,6 +3523,14 @@ var WriteService = class {
3471
3523
  await this.metadataRepo.updateCheckpoint(entityId, { memory: prevCheckpoint }, this.db);
3472
3524
  throw e;
3473
3525
  }
3526
+ await this.maybeRunHeal(entityId, currentEventCount);
3527
+ }
3528
+ /**
3529
+ * Run one bounded auto-heal pass if the heal checkpoint has fallen
3530
+ * `autoHealThreshold` events behind. Called after every write (see
3531
+ * {@link write}) so a partial pass retries on the next write.
3532
+ */
3533
+ async maybeRunHeal(entityId, currentEventCount) {
3474
3534
  const autoHealThreshold = this.options.config?.autoHealThreshold || 100;
3475
3535
  const cp = await this.metadataRepo.getCheckpoint(entityId, this.db);
3476
3536
  let healCheckpoint = cp.heal ?? 0;
@@ -3478,8 +3538,10 @@ var WriteService = class {
3478
3538
  const shouldRunHeal = currentEventCount - healCheckpoint >= autoHealThreshold;
3479
3539
  if (shouldRunHeal && this.jobManager.tryAcquireAutoHealLock(entityId)) {
3480
3540
  try {
3481
- await this.maintenanceService.doRunHeal(entityId);
3482
- await this.metadataRepo.updateCheckpoint(entityId, { heal: currentEventCount }, this.db);
3541
+ const result = await this.maintenanceService.doRunHeal(entityId);
3542
+ if (result.remaining === 0) {
3543
+ await this.metadataRepo.updateCheckpoint(entityId, { heal: currentEventCount }, this.db);
3544
+ }
3483
3545
  } finally {
3484
3546
  this.jobManager.releaseLock("heal", entityId);
3485
3547
  }
@@ -3487,6 +3549,6 @@ var WriteService = class {
3487
3549
  }
3488
3550
  };
3489
3551
 
3490
- export { BaseRepository, EmbeddingService, HOOK_TIMEOUT_MARKER, ImportExportService, IngestionService, JobManager, MaintenanceService, MetadataRepository, ONTOLOGY_BACKFILL_BATCH_SIZE, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, ONTOLOGY_BACKFILL_RECHECK_MS, ONTOLOGY_BACKFILL_SYSTEM_PROMPT, PromptService, PrunePartialFailureError, RetrievalService, SearchService, WikiBusyError, WikiTransactionError, WriteService, __privateAdd, __privateGet, __privateSet, configureRandomSource, emptyManifest, entitySummaryMetaKey, extractSqliteCode, generateId, normalizeSourceHash, normalizeSourceRef, normalizeTitleKey, parseEmbedding, resolveEdgeDefinitions, resolveNodeType, validateInlineEdges, validateManifest };
3491
- //# sourceMappingURL=chunk-MYZJLVX4.mjs.map
3492
- //# sourceMappingURL=chunk-MYZJLVX4.mjs.map
3552
+ export { BaseRepository, EmbeddingService, HEAL_BATCH_SIZE, HEAL_RECHECK_MS, HOOK_TIMEOUT_MARKER, ImportExportService, IngestionService, JobManager, MaintenanceService, MetadataRepository, ONTOLOGY_BACKFILL_BATCH_SIZE, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS, ONTOLOGY_BACKFILL_RECHECK_MS, ONTOLOGY_BACKFILL_SYSTEM_PROMPT, PromptService, PrunePartialFailureError, RetrievalService, SearchService, WikiBusyError, WikiTransactionError, WriteService, __privateAdd, __privateGet, __privateSet, configureRandomSource, emptyManifest, entitySummaryMetaKey, extractSqliteCode, generateId, normalizeSourceHash, normalizeSourceRef, normalizeTitleKey, parseEmbedding, resolveEdgeDefinitions, resolveNodeType, validateInlineEdges, validateManifest };
3553
+ //# sourceMappingURL=chunk-W53E3I44.mjs.map
3554
+ //# sourceMappingURL=chunk-W53E3I44.mjs.map