@equationalapplications/core-llm-wiki 5.5.0 → 6.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.
@@ -1220,7 +1220,12 @@ function extractParsePosition(err) {
1220
1220
  return match ? Number(match[1]) : null;
1221
1221
  }
1222
1222
  function safeErrorToString(e) {
1223
- if (e instanceof Error) {
1223
+ let isErrorLike = false;
1224
+ try {
1225
+ isErrorLike = e instanceof Error;
1226
+ } catch {
1227
+ }
1228
+ if (isErrorLike) {
1224
1229
  const msg = readErrorField(e, "message");
1225
1230
  if (typeof msg === "string" && msg.length > 0) return msg;
1226
1231
  const name = readErrorField(e, "name");
@@ -1245,16 +1250,54 @@ function readErrorField(e, key) {
1245
1250
  }
1246
1251
  }
1247
1252
  function sanitizeRankerError(err, sanitizeRankerErrors) {
1253
+ let isErrorLike = false;
1254
+ try {
1255
+ isErrorLike = err instanceof Error;
1256
+ } catch {
1257
+ }
1248
1258
  if (sanitizeRankerErrors === false) {
1249
- return err instanceof Error ? err : new Error(String(err));
1259
+ return isErrorLike ? err : new Error(safeErrorToString(err));
1260
+ }
1261
+ let errLike = null;
1262
+ if (isErrorLike) errLike = err;
1263
+ let typeName;
1264
+ try {
1265
+ if (errLike) {
1266
+ const rawName = errLike.constructor?.name;
1267
+ typeName = typeof rawName === "string" ? rawName : "Error";
1268
+ } else {
1269
+ typeName = typeof err;
1270
+ }
1271
+ } catch {
1272
+ typeName = isErrorLike ? "Error" : typeof err;
1273
+ }
1274
+ let innerCause;
1275
+ if (errLike) {
1276
+ let cause;
1277
+ try {
1278
+ cause = errLike.cause;
1279
+ } catch {
1280
+ cause = void 0;
1281
+ }
1282
+ if (cause !== void 0) {
1283
+ let causeName;
1284
+ try {
1285
+ const rawCauseName = cause?.constructor?.name;
1286
+ causeName = typeof rawCauseName === "string" ? rawCauseName : typeof cause;
1287
+ } catch {
1288
+ causeName = typeof cause;
1289
+ }
1290
+ innerCause = new Error(`Caused by: ${causeName}`);
1291
+ }
1250
1292
  }
1251
- const typeName = err instanceof Error ? err.constructor?.name ?? "Error" : typeof err;
1252
- const innerCause = err instanceof Error && err.cause !== void 0 ? new Error(`Caused by: ${err.cause?.constructor?.name ?? typeof err.cause}`) : void 0;
1253
1293
  const sanitized = new Error(
1254
1294
  `VectorRanker ${typeName} (message scrubbed for security)`,
1255
1295
  innerCause ? { cause: innerCause } : void 0
1256
1296
  );
1257
- sanitized.name = typeName;
1297
+ try {
1298
+ sanitized.name = typeName;
1299
+ } catch {
1300
+ }
1258
1301
  return sanitized;
1259
1302
  }
1260
1303
  function safeSlice(value, start, end) {
@@ -1438,6 +1481,12 @@ Return ONLY a valid JSON object matching this schema:
1438
1481
  If no manifest type fits a fact, omit that fact from "classifications" entirely \u2014 do not guess.
1439
1482
  When echoing an existing fact's title verbatim into "target_title", preserve every JSON escape sequence (\\", \\n, \\\\, \\/) exactly as it appeared in the input body \u2014 do not strip backslashes, do not add unescaped quotes. Do not return markdown, just raw JSON.`;
1440
1483
 
1484
+ // src/utils/healConstants.ts
1485
+ var HEAL_MAX_ANCHORS = 50;
1486
+ var HEAL_ANCHORS_PER_CANDIDATE = 4;
1487
+ var HEAL_MAX_FACT_BODY_CHARS_L3 = 4e3;
1488
+ var HEAL_MAX_TASKS = 50;
1489
+
1441
1490
  // src/services/PromptService.ts
1442
1491
  var PromptService = class {
1443
1492
  constructor(globalOverrides) {
@@ -1505,25 +1554,67 @@ Current Facts:
1505
1554
  ${JSON.stringify(currentFacts, null, 2)}`
1506
1555
  };
1507
1556
  }
1508
- buildHealPrompt(healCandidates, documentAnchors, allTasks, recentEvents, runtimeOverride) {
1557
+ /**
1558
+ * Heal-prompt level interpretation for the `attemptLevel` ladder.
1559
+ *
1560
+ * Caller contract: `documentAnchors` may be a slice sized for `batch.length`
1561
+ * or a larger set (e.g. a cache hit from `_selectHealAnchors`). This function
1562
+ * applies the prompt-side anchor cap `min(HEAL_MAX_ANCHORS=50, batch.length
1563
+ * * HEAL_ANCHORS_PER_CANDIDATE=4)` so the rendered prompt is bounded
1564
+ * regardless of caller input. `HEAL_MAX_ANCHORS` and
1565
+ * `HEAL_ANCHORS_PER_CANDIDATE` live here too — keeping the formula
1566
+ * co-located with its application avoids a "MaintenanceService policy"
1567
+ * import cycle (`PromptService` is constructed before `MaintenanceService`
1568
+ * exists) and makes the cap testable without a `MaintenanceService`
1569
+ * instance. Task 3 exports the same two constants from `MaintenanceService`
1570
+ * for caller-side overfetch sizing; the values must match.
1571
+ *
1572
+ * Level semantics:
1573
+ * - L0: allTasks + recentEvents + full candidate bodies; anchors re-capped
1574
+ * - L1: drop allTasks; recentEvents present; candidate bodies full
1575
+ * - L2: drop allTasks and recentEvents; candidate bodies full
1576
+ * - L3: drop allTasks and recentEvents; truncate each candidate body to
1577
+ * `bodyTruncationChars` and emit a `degraded` record per truncated fact
1578
+ */
1579
+ buildHealPrompt(healCandidates, documentAnchors, allTasks, recentEvents, runtimeOverride, attemptLevel, bodyTruncationChars = HEAL_MAX_FACT_BODY_CHARS_L3) {
1580
+ const effectiveTasks = attemptLevel >= 1 ? [] : allTasks;
1581
+ const effectiveEvents = attemptLevel >= 2 ? [] : recentEvents;
1582
+ const maxAnchors = Math.max(1, Math.min(HEAL_MAX_ANCHORS, healCandidates.length * HEAL_ANCHORS_PER_CANDIDATE));
1583
+ const effectiveAnchors = documentAnchors.slice(0, maxAnchors);
1584
+ const { shapedCandidates, degraded } = applyBodyTruncation(
1585
+ healCandidates,
1586
+ attemptLevel,
1587
+ bodyTruncationChars
1588
+ );
1509
1589
  const template = runtimeOverride ?? this.globalOverrides?.healSystemPrompt ?? HEAL_SYSTEM_PROMPT;
1510
1590
  if (/\{\{\s*healCandidates\s*\}\}/.test(template) || /\{\{\s*documentAnchors\s*\}\}/.test(template) || /\{\{\s*allTasks\s*\}\}/.test(template) || /\{\{\s*recentEvents\s*\}\}/.test(template)) {
1511
1591
  return {
1512
- systemPrompt: this.hydrate(template, { healCandidates, documentAnchors, allTasks, recentEvents }),
1513
- userPrompt: "Please heal the memory graph."
1592
+ prompts: {
1593
+ systemPrompt: this.hydrate(template, {
1594
+ healCandidates: shapedCandidates,
1595
+ documentAnchors: effectiveAnchors,
1596
+ allTasks: effectiveTasks,
1597
+ recentEvents: effectiveEvents
1598
+ }),
1599
+ userPrompt: "Please heal the memory graph."
1600
+ },
1601
+ degraded
1514
1602
  };
1515
1603
  }
1516
1604
  return {
1517
- systemPrompt: template,
1518
- userPrompt: `Heal Candidates:
1519
- ${JSON.stringify(healCandidates, null, 2)}
1605
+ prompts: {
1606
+ systemPrompt: template,
1607
+ userPrompt: `Heal Candidates:
1608
+ ${JSON.stringify(shapedCandidates, null, 2)}
1520
1609
  Document Anchors (DO NOT MODIFY OR DELETE):
1521
- ${JSON.stringify(documentAnchors, null, 2)}
1610
+ ${JSON.stringify(effectiveAnchors, null, 2)}
1522
1611
  All Tasks:
1523
- ${JSON.stringify(allTasks, null, 2)}
1612
+ ${JSON.stringify(effectiveTasks, null, 2)}
1524
1613
  Recent Events:
1525
- ${JSON.stringify(recentEvents, null, 2)}
1614
+ ${JSON.stringify(effectiveEvents, null, 2)}
1526
1615
  The following document anchors are provided for contradiction detection only. Do not include them in \`downgraded\`, \`deleted\`, or \`newFacts\`.`
1616
+ },
1617
+ degraded
1527
1618
  };
1528
1619
  }
1529
1620
  buildOntologyBackfillPrompt(facts, runtimeOverride, ontologyContext) {
@@ -1543,6 +1634,36 @@ ${JSON.stringify(facts, null, 2)}`
1543
1634
  };
1544
1635
  }
1545
1636
  };
1637
+ function applyBodyTruncation(candidates, attemptLevel, bodyTruncationChars) {
1638
+ if (attemptLevel < 3) {
1639
+ return { shapedCandidates: candidates, degraded: [] };
1640
+ }
1641
+ const shapedCandidates = [];
1642
+ const degraded = [];
1643
+ for (const c of candidates) {
1644
+ if (typeof c !== "object" || c === null) {
1645
+ shapedCandidates.push(c);
1646
+ continue;
1647
+ }
1648
+ const fact = c;
1649
+ const body = typeof fact.body === "string" ? fact.body : "";
1650
+ if (body.length <= bodyTruncationChars) {
1651
+ shapedCandidates.push(c);
1652
+ continue;
1653
+ }
1654
+ if (typeof fact.id !== "string") {
1655
+ shapedCandidates.push(c);
1656
+ continue;
1657
+ }
1658
+ const originalBodyChars = body.length;
1659
+ const prefix = safeSlice(body, 0, bodyTruncationChars);
1660
+ const truncatedBodyChars = prefix.length;
1661
+ const truncated = `${prefix}\u2026[truncated at ${truncatedBodyChars} chars, original was ${originalBodyChars}]`;
1662
+ shapedCandidates.push({ ...fact, body: truncated });
1663
+ degraded.push({ id: fact.id, originalBodyChars, truncatedBodyChars });
1664
+ }
1665
+ return { shapedCandidates, degraded };
1666
+ }
1546
1667
 
1547
1668
  // src/utils/chunkingDefaults.ts
1548
1669
  var DEFAULT_MAX_CHUNK_LENGTH = 12e3;
@@ -2197,11 +2318,24 @@ var TRUNCATION_PATTERNS = [
2197
2318
  /finish[_ ]?reason/i
2198
2319
  ];
2199
2320
  var EXCEEDS_LIMIT_PATTERN = /exceed[a-z]*[^.]{0,40}\b(model|context)?[ _-]?limit/i;
2321
+ function getErrorMessage(err) {
2322
+ try {
2323
+ return err instanceof Error ? err.message : String(err ?? "");
2324
+ } catch {
2325
+ return void 0;
2326
+ }
2327
+ }
2200
2328
  function isTruncationError(err) {
2201
- const message = err instanceof Error ? err.message : String(err ?? "");
2329
+ const message = getErrorMessage(err);
2330
+ if (message === void 0) return false;
2202
2331
  if (EXCEEDS_LIMIT_PATTERN.test(message)) return false;
2203
2332
  return TRUNCATION_PATTERNS.some((pattern) => pattern.test(message));
2204
2333
  }
2334
+ function isConfigError(err) {
2335
+ const message = getErrorMessage(err);
2336
+ if (message === void 0) return false;
2337
+ return EXCEEDS_LIMIT_PATTERN.test(message);
2338
+ }
2205
2339
  function initialBatchSize(maxOutputTokens) {
2206
2340
  if (!maxOutputTokens || !Number.isFinite(maxOutputTokens) || maxOutputTokens <= 0) {
2207
2341
  return DEFAULT_BATCH_SIZE;
@@ -2243,14 +2377,18 @@ async function runBatched(args) {
2243
2377
  const single = candidate.slice(0, 1);
2244
2378
  return { batch: single, prompts: await buildPrompt(single) };
2245
2379
  };
2246
- const onFailure = async (batch, err) => {
2247
- if (batch.length <= 1) {
2248
- if (batch.length === 1) {
2249
- skipped.push(batch[0]);
2380
+ const onFailure = async (batch, err, attemptLevel, fromCall) => {
2381
+ if (batch.length === 1) {
2382
+ if (fromCall && isTruncationError(err) && attemptLevel < 3) {
2383
+ await attempt(batch, void 0, attemptLevel + 1);
2384
+ } else {
2385
+ const reason = fromCall && !isTruncationError(err) && !isConfigError(err) ? "call_error" : "non_convergent";
2386
+ skipped.push({ item: batch[0], reason });
2250
2387
  onSkip?.(batch[0], err);
2251
2388
  }
2252
2389
  return;
2253
2390
  }
2391
+ if (fromCall && !isTruncationError(err)) throw err;
2254
2392
  const mid = Math.ceil(batch.length / 2);
2255
2393
  if (mid < batchSize) batchSize = mid;
2256
2394
  let i = 0;
@@ -2261,23 +2399,22 @@ async function runBatched(args) {
2261
2399
  i += trimmed.batch.length;
2262
2400
  }
2263
2401
  };
2264
- const attempt = async (batch, prebuilt) => {
2402
+ const attempt = async (batch, prebuilt, attemptLevel = 0) => {
2265
2403
  if (batch.length === 0) return;
2266
- const prompts = prebuilt ?? await buildPrompt(batch);
2404
+ const prompts = prebuilt && attemptLevel === 0 ? prebuilt : await buildPrompt(batch, attemptLevel);
2267
2405
  batches++;
2268
2406
  let responseText;
2269
2407
  try {
2270
2408
  responseText = await call(prompts);
2271
2409
  } catch (err) {
2272
- if (!isTruncationError(err)) throw err;
2273
- await onFailure(batch, err);
2410
+ await onFailure(batch, err, attemptLevel, true);
2274
2411
  return;
2275
2412
  }
2276
2413
  let result;
2277
2414
  try {
2278
2415
  result = parse(responseText, batch);
2279
2416
  } catch (err) {
2280
- await onFailure(batch, err);
2417
+ await onFailure(batch, err, attemptLevel, false);
2281
2418
  return;
2282
2419
  }
2283
2420
  results.push(result);
@@ -2286,7 +2423,7 @@ async function runBatched(args) {
2286
2423
  while (index < items.length) {
2287
2424
  const { batch, prompts } = await trim(items.slice(index, index + batchSize));
2288
2425
  index += batch.length;
2289
- await attempt(batch, prompts);
2426
+ await attempt(batch, prompts, 0);
2290
2427
  }
2291
2428
  return { results, skipped, batches };
2292
2429
  }
@@ -2297,7 +2434,6 @@ var MIN_TOKENS_TO_QUALIFY = 3;
2297
2434
  var ONTOLOGY_BACKFILL_BATCH_SIZE = 25;
2298
2435
  var ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS = 4e4;
2299
2436
  var ONTOLOGY_BACKFILL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
2300
- var HEAL_MAX_ANCHORS = 50;
2301
2437
  var HEAL_ANCHOR_SEARCH_OVERFETCH = 4;
2302
2438
  var HEAL_MAX_PROMPT_CHARS = 4e4;
2303
2439
  var HEAL_BATCH_SIZE = 25;
@@ -2305,7 +2441,12 @@ var HEAL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
2305
2441
  var SKIP_ERROR_LOG_CHARS = 4096;
2306
2442
  var formatSkipError = (err) => {
2307
2443
  let base;
2308
- if (err instanceof Error || typeof err !== "object" && typeof err !== "function") {
2444
+ let isErrorLike = false;
2445
+ try {
2446
+ isErrorLike = err instanceof Error;
2447
+ } catch {
2448
+ }
2449
+ if (isErrorLike || typeof err !== "object" && typeof err !== "function") {
2309
2450
  base = safeErrorToString(err);
2310
2451
  } else {
2311
2452
  try {
@@ -2723,6 +2864,10 @@ var MaintenanceService = class {
2723
2864
  if (!Number.isInteger(batchSize) || batchSize < 1) {
2724
2865
  throw new Error("Invalid batchSize: must be an integer >= 1");
2725
2866
  }
2867
+ const bodyTruncationChars = options?.bodyTruncationChars ?? HEAL_MAX_FACT_BODY_CHARS_L3;
2868
+ if (!Number.isInteger(bodyTruncationChars) || bodyTruncationChars < 1) {
2869
+ throw new Error("Invalid bodyTruncationChars: must be an integer >= 1");
2870
+ }
2726
2871
  const now = Date.now();
2727
2872
  const recheckCutoff = now - HEAL_RECHECK_MS;
2728
2873
  const orphanAfterDays = this.options.config?.orphanAfterDays !== void 0 ? this.options.config?.orphanAfterDays : 30;
@@ -2763,29 +2908,44 @@ var MaintenanceService = class {
2763
2908
  downgraded: staleDowngradedIds.length,
2764
2909
  deleted: orphanedIds.length,
2765
2910
  newFactsCreated: 0,
2766
- skipped: 0,
2911
+ skipped: [],
2912
+ degraded: [],
2767
2913
  remaining: counts2.eligible,
2768
2914
  deferred: counts2.deferred
2769
2915
  };
2770
2916
  }
2771
- const allTasks = await this.taskRepo.findAllPending([entityId]);
2917
+ const allTasks = await this.taskRepo.findAllPending([entityId], HEAL_MAX_TASKS);
2772
2918
  const recentEvents = await this.eventRepo.getRecent(entityId, 20);
2773
2919
  const toPromptShape = (f) => {
2774
2920
  const { embedding: _embedding, embedding_blob: _blob, ...rest } = f;
2775
2921
  return { ...rest, tags: typeof rest.tags === "string" ? JSON.parse(rest.tags) : rest.tags };
2776
2922
  };
2777
2923
  const anchorCache = /* @__PURE__ */ new Map();
2924
+ const degraded = [];
2778
2925
  const outcome = await runBatched({
2779
2926
  items: healCandidates,
2780
- buildPrompt: async (batch) => {
2781
- const documentAnchors = await this._selectHealAnchors(entityId, batch, anchorCache);
2782
- return this.promptService.buildHealPrompt(
2927
+ buildPrompt: async (batch, attemptLevel = 0) => {
2928
+ const documentAnchors = await this._selectHealAnchors(
2929
+ entityId,
2930
+ batch,
2931
+ // Per-batch anchor cap: `batch.length * HEAL_ANCHORS_PER_CANDIDATE` (capped at
2932
+ // HEAL_MAX_ANCHORS) right-sizes the anchor lookup so a 1-fact batch does
2933
+ // not overfetch the same 200 keyword hits a 25-fact batch once did.
2934
+ // buildHealPrompt applies the matching cap on its side — values must match.
2935
+ Math.min(HEAL_MAX_ANCHORS, batch.length * HEAL_ANCHORS_PER_CANDIDATE),
2936
+ anchorCache
2937
+ );
2938
+ const { prompts, degraded: batchDegraded } = await this.promptService.buildHealPrompt(
2783
2939
  batch.map(toPromptShape),
2784
2940
  documentAnchors,
2785
2941
  allTasks,
2786
2942
  recentEvents,
2787
- promptOverride
2943
+ promptOverride,
2944
+ attemptLevel,
2945
+ bodyTruncationChars
2788
2946
  );
2947
+ degraded.push(...batchDegraded);
2948
+ return prompts;
2789
2949
  },
2790
2950
  call: (prompts) => this.options.llmProvider.generateText(prompts),
2791
2951
  parse: (responseText, batch) => {
@@ -2859,8 +3019,14 @@ var MaintenanceService = class {
2859
3019
  insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
2860
3020
  healFactsForDedupe.push({ id, title: fact.title });
2861
3021
  }
3022
+ const callErrorIds = new Set(
3023
+ outcome.skipped.filter((s) => s.reason === "call_error").map((s) => s.item.id)
3024
+ );
2862
3025
  await this.entryRepo.markHealChecked(
2863
- [...healCandidates.map((f) => f.id), ...insertedFacts.map((f) => f.id)],
3026
+ [
3027
+ ...healCandidates.map((f) => f.id).filter((id) => !callErrorIds.has(id)),
3028
+ ...insertedFacts.map((f) => f.id)
3029
+ ],
2864
3030
  entityId,
2865
3031
  now,
2866
3032
  tx
@@ -2878,6 +3044,13 @@ var MaintenanceService = class {
2878
3044
  await this.embeddingService.embedFact(fact);
2879
3045
  }
2880
3046
  this.searchService.evictCache(entityId);
3047
+ const skippedIds = new Set(outcome.skipped.map(({ item }) => item.id));
3048
+ const healedDegraded = degraded.filter((d) => !skippedIds.has(d.id));
3049
+ for (const d of healedDegraded) {
3050
+ console.warn(
3051
+ `[WikiMemory] heal healed under degraded context ${entityId}/${d.id}: body truncated from ${d.originalBodyChars} to ${d.truncatedBodyChars} chars`
3052
+ );
3053
+ }
2881
3054
  let scanned = outcome.skipped.length;
2882
3055
  for (const batchResult of outcome.results) scanned += batchResult.batch.length;
2883
3056
  const allDowngraded = /* @__PURE__ */ new Set([...staleDowngradedIds, ...safeDowngraded]);
@@ -2888,7 +3061,8 @@ var MaintenanceService = class {
2888
3061
  downgraded: allDowngraded.size,
2889
3062
  deleted: allDeleted.size,
2890
3063
  newFactsCreated: insertedFacts.length,
2891
- skipped: outcome.skipped.length,
3064
+ skipped: outcome.skipped.map(({ item, reason }) => ({ id: item.id, reason })),
3065
+ degraded: healedDegraded,
2892
3066
  remaining: counts.eligible,
2893
3067
  deferred: counts.deferred
2894
3068
  };
@@ -2972,7 +3146,15 @@ var MaintenanceService = class {
2972
3146
  };
2973
3147
  }
2974
3148
  if (outcome.skipped.length > 0) {
2975
- await this.entryRepo.markOntologyChecked(outcome.skipped.map((f) => f.id), entityId, now, this.db);
3149
+ const callErrorIds = new Set(
3150
+ outcome.skipped.filter((s) => s.reason === "call_error").map((s) => s.item.id)
3151
+ );
3152
+ await this.entryRepo.markOntologyChecked(
3153
+ outcome.skipped.filter(({ item }) => !callErrorIds.has(item.id)).map(({ item }) => item.id),
3154
+ entityId,
3155
+ now,
3156
+ this.db
3157
+ );
2976
3158
  }
2977
3159
  this.searchService.evictCache(entityId);
2978
3160
  const counts = await this.entryRepo.countUntypedByEntityId(entityId, recheckCutoff);
@@ -3088,7 +3270,7 @@ var MaintenanceService = class {
3088
3270
  * batches that reduce to the same query share one lookup. Caller-owned and
3089
3271
  * per-pass — see the call site in doRunHeal.
3090
3272
  */
3091
- async _selectHealAnchors(entityId, batch, cache) {
3273
+ async _selectHealAnchors(entityId, batch, cap = HEAL_MAX_ANCHORS, cache) {
3092
3274
  const query = batch.map((f) => f.title).join(" ").trim();
3093
3275
  if (!query) return [];
3094
3276
  const cached = cache?.get(query);
@@ -3096,7 +3278,7 @@ var MaintenanceService = class {
3096
3278
  const hits = this.searchService.searchKeyword(
3097
3279
  query,
3098
3280
  [entityId],
3099
- HEAL_MAX_ANCHORS * HEAL_ANCHOR_SEARCH_OVERFETCH
3281
+ cap * HEAL_ANCHOR_SEARCH_OVERFETCH
3100
3282
  );
3101
3283
  const hitIds = hits.map((h) => h.id);
3102
3284
  const anchors = [];
@@ -3107,7 +3289,7 @@ var MaintenanceService = class {
3107
3289
  const row = byId.get(id);
3108
3290
  if (!row) continue;
3109
3291
  anchors.push(row);
3110
- if (anchors.length >= HEAL_MAX_ANCHORS) break;
3292
+ if (anchors.length >= cap) break;
3111
3293
  }
3112
3294
  }
3113
3295
  cache?.set(query, anchors);
@@ -3997,7 +4179,12 @@ var RetrievalService = class {
3997
4179
  }
3998
4180
  }
3999
4181
  } catch (rankerErr) {
4000
- const rankerError = rankerErr instanceof Error ? rankerErr : new Error(String(rankerErr));
4182
+ let isErrorLike = false;
4183
+ try {
4184
+ isErrorLike = rankerErr instanceof Error;
4185
+ } catch {
4186
+ }
4187
+ const rankerError = isErrorLike ? rankerErr : new Error(safeErrorToString(rankerErr));
4001
4188
  const policy = this.options.vectorRankerFallback ?? "js-cosine";
4002
4189
  this.options.onVectorRankerFallback?.({
4003
4190
  error: this._sanitizeRankerError(rankerError),
@@ -4109,12 +4296,21 @@ var RetrievalService = class {
4109
4296
  }
4110
4297
  }
4111
4298
  } catch (err) {
4112
- const error = err instanceof Error ? err : new Error(String(err));
4299
+ let isErrorLike = false;
4300
+ try {
4301
+ isErrorLike = err instanceof Error;
4302
+ } catch {
4303
+ }
4304
+ const error = isErrorLike ? err : new Error(safeErrorToString(err));
4113
4305
  if (rankerShouldRethrow) {
4114
4306
  throw error;
4115
4307
  }
4116
4308
  if (pendingRankerFallbackError) {
4117
- error.cause = pendingRankerFallbackError;
4309
+ try {
4310
+ error.cause = pendingRankerFallbackError;
4311
+ } catch {
4312
+ this.options.onRetrievalFallback?.(pendingRankerFallbackError);
4313
+ }
4118
4314
  pendingRankerFallbackError = void 0;
4119
4315
  }
4120
4316
  this.options.onRetrievalFallback?.(error);
@@ -4370,6 +4566,6 @@ var WriteService = class {
4370
4566
  }
4371
4567
  };
4372
4568
 
4373
- export { BaseRepository, DEFAULT_CHUNK_OVERLAP, DEFAULT_MAX_CHUNK_LENGTH, 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, WikiDuplicateHashError, WikiIngestEmptyError, WikiParseError, WikiSourceRefHashCollision, WikiStrictOntologyViolation, WikiTransactionError, WriteService, __privateAdd, __privateGet, __privateSet, chunkText, configureRandomSource, emptyManifest, entitySummaryMetaKey, extractSqliteCode, generateId, normalizeSourceHash, normalizeSourceRef, normalizeTitleKey, parseEmbedding, resolveEdgeDefinitions, resolveNodeType, safeSlice, validateInlineEdges, validateManifest };
4374
- //# sourceMappingURL=chunk-QVY5DFJV.mjs.map
4375
- //# sourceMappingURL=chunk-QVY5DFJV.mjs.map
4569
+ export { BaseRepository, DEFAULT_CHUNK_OVERLAP, DEFAULT_MAX_CHUNK_LENGTH, EmbeddingService, HEAL_ANCHORS_PER_CANDIDATE, HEAL_BATCH_SIZE, HEAL_MAX_FACT_BODY_CHARS_L3, HEAL_MAX_TASKS, 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, WikiDuplicateHashError, WikiIngestEmptyError, WikiParseError, WikiSourceRefHashCollision, WikiStrictOntologyViolation, WikiTransactionError, WriteService, __privateAdd, __privateGet, __privateSet, chunkText, configureRandomSource, emptyManifest, entitySummaryMetaKey, extractSqliteCode, generateId, normalizeSourceHash, normalizeSourceRef, normalizeTitleKey, parseEmbedding, resolveEdgeDefinitions, resolveNodeType, safeSlice, validateInlineEdges, validateManifest };
4570
+ //# sourceMappingURL=chunk-HREQ6F6Z.mjs.map
4571
+ //# sourceMappingURL=chunk-HREQ6F6Z.mjs.map