@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.
- package/README.md +136 -31
- package/dist/{chunk-QVY5DFJV.mjs → chunk-HREQ6F6Z.mjs} +241 -45
- package/dist/chunk-HREQ6F6Z.mjs.map +1 -0
- package/dist/index.d.mts +18 -3
- package/dist/index.d.ts +18 -3
- package/dist/index.js +241 -42
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{testing-D0RnZjyW.d.mts → testing-DgqVB29I.d.mts} +72 -6
- package/dist/{testing-D0RnZjyW.d.ts → testing-DgqVB29I.d.ts} +72 -6
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +238 -42
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-QVY5DFJV.mjs.map +0 -1
package/dist/testing.js
CHANGED
|
@@ -387,7 +387,12 @@ function extractParsePosition(err) {
|
|
|
387
387
|
return match ? Number(match[1]) : null;
|
|
388
388
|
}
|
|
389
389
|
function safeErrorToString(e) {
|
|
390
|
-
|
|
390
|
+
let isErrorLike = false;
|
|
391
|
+
try {
|
|
392
|
+
isErrorLike = e instanceof Error;
|
|
393
|
+
} catch {
|
|
394
|
+
}
|
|
395
|
+
if (isErrorLike) {
|
|
391
396
|
const msg = readErrorField(e, "message");
|
|
392
397
|
if (typeof msg === "string" && msg.length > 0) return msg;
|
|
393
398
|
const name = readErrorField(e, "name");
|
|
@@ -412,16 +417,54 @@ function readErrorField(e, key) {
|
|
|
412
417
|
}
|
|
413
418
|
}
|
|
414
419
|
function sanitizeRankerError(err, sanitizeRankerErrors) {
|
|
420
|
+
let isErrorLike = false;
|
|
421
|
+
try {
|
|
422
|
+
isErrorLike = err instanceof Error;
|
|
423
|
+
} catch {
|
|
424
|
+
}
|
|
415
425
|
if (sanitizeRankerErrors === false) {
|
|
416
|
-
return
|
|
426
|
+
return isErrorLike ? err : new Error(safeErrorToString(err));
|
|
427
|
+
}
|
|
428
|
+
let errLike = null;
|
|
429
|
+
if (isErrorLike) errLike = err;
|
|
430
|
+
let typeName;
|
|
431
|
+
try {
|
|
432
|
+
if (errLike) {
|
|
433
|
+
const rawName = errLike.constructor?.name;
|
|
434
|
+
typeName = typeof rawName === "string" ? rawName : "Error";
|
|
435
|
+
} else {
|
|
436
|
+
typeName = typeof err;
|
|
437
|
+
}
|
|
438
|
+
} catch {
|
|
439
|
+
typeName = isErrorLike ? "Error" : typeof err;
|
|
440
|
+
}
|
|
441
|
+
let innerCause;
|
|
442
|
+
if (errLike) {
|
|
443
|
+
let cause;
|
|
444
|
+
try {
|
|
445
|
+
cause = errLike.cause;
|
|
446
|
+
} catch {
|
|
447
|
+
cause = void 0;
|
|
448
|
+
}
|
|
449
|
+
if (cause !== void 0) {
|
|
450
|
+
let causeName;
|
|
451
|
+
try {
|
|
452
|
+
const rawCauseName = cause?.constructor?.name;
|
|
453
|
+
causeName = typeof rawCauseName === "string" ? rawCauseName : typeof cause;
|
|
454
|
+
} catch {
|
|
455
|
+
causeName = typeof cause;
|
|
456
|
+
}
|
|
457
|
+
innerCause = new Error(`Caused by: ${causeName}`);
|
|
458
|
+
}
|
|
417
459
|
}
|
|
418
|
-
const typeName = err instanceof Error ? err.constructor?.name ?? "Error" : typeof err;
|
|
419
|
-
const innerCause = err instanceof Error && err.cause !== void 0 ? new Error(`Caused by: ${err.cause?.constructor?.name ?? typeof err.cause}`) : void 0;
|
|
420
460
|
const sanitized = new Error(
|
|
421
461
|
`VectorRanker ${typeName} (message scrubbed for security)`,
|
|
422
462
|
innerCause ? { cause: innerCause } : void 0
|
|
423
463
|
);
|
|
424
|
-
|
|
464
|
+
try {
|
|
465
|
+
sanitized.name = typeName;
|
|
466
|
+
} catch {
|
|
467
|
+
}
|
|
425
468
|
return sanitized;
|
|
426
469
|
}
|
|
427
470
|
function safeSlice(value, start, end) {
|
|
@@ -1227,6 +1270,12 @@ Return ONLY a valid JSON object matching this schema:
|
|
|
1227
1270
|
If no manifest type fits a fact, omit that fact from "classifications" entirely \u2014 do not guess.
|
|
1228
1271
|
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.`;
|
|
1229
1272
|
|
|
1273
|
+
// src/utils/healConstants.ts
|
|
1274
|
+
var HEAL_MAX_ANCHORS = 50;
|
|
1275
|
+
var HEAL_ANCHORS_PER_CANDIDATE = 4;
|
|
1276
|
+
var HEAL_MAX_FACT_BODY_CHARS_L3 = 4e3;
|
|
1277
|
+
var HEAL_MAX_TASKS = 50;
|
|
1278
|
+
|
|
1230
1279
|
// src/services/PromptService.ts
|
|
1231
1280
|
var PromptService = class {
|
|
1232
1281
|
constructor(globalOverrides) {
|
|
@@ -1294,25 +1343,67 @@ Current Facts:
|
|
|
1294
1343
|
${JSON.stringify(currentFacts, null, 2)}`
|
|
1295
1344
|
};
|
|
1296
1345
|
}
|
|
1297
|
-
|
|
1346
|
+
/**
|
|
1347
|
+
* Heal-prompt level interpretation for the `attemptLevel` ladder.
|
|
1348
|
+
*
|
|
1349
|
+
* Caller contract: `documentAnchors` may be a slice sized for `batch.length`
|
|
1350
|
+
* or a larger set (e.g. a cache hit from `_selectHealAnchors`). This function
|
|
1351
|
+
* applies the prompt-side anchor cap `min(HEAL_MAX_ANCHORS=50, batch.length
|
|
1352
|
+
* * HEAL_ANCHORS_PER_CANDIDATE=4)` so the rendered prompt is bounded
|
|
1353
|
+
* regardless of caller input. `HEAL_MAX_ANCHORS` and
|
|
1354
|
+
* `HEAL_ANCHORS_PER_CANDIDATE` live here too — keeping the formula
|
|
1355
|
+
* co-located with its application avoids a "MaintenanceService policy"
|
|
1356
|
+
* import cycle (`PromptService` is constructed before `MaintenanceService`
|
|
1357
|
+
* exists) and makes the cap testable without a `MaintenanceService`
|
|
1358
|
+
* instance. Task 3 exports the same two constants from `MaintenanceService`
|
|
1359
|
+
* for caller-side overfetch sizing; the values must match.
|
|
1360
|
+
*
|
|
1361
|
+
* Level semantics:
|
|
1362
|
+
* - L0: allTasks + recentEvents + full candidate bodies; anchors re-capped
|
|
1363
|
+
* - L1: drop allTasks; recentEvents present; candidate bodies full
|
|
1364
|
+
* - L2: drop allTasks and recentEvents; candidate bodies full
|
|
1365
|
+
* - L3: drop allTasks and recentEvents; truncate each candidate body to
|
|
1366
|
+
* `bodyTruncationChars` and emit a `degraded` record per truncated fact
|
|
1367
|
+
*/
|
|
1368
|
+
buildHealPrompt(healCandidates, documentAnchors, allTasks, recentEvents, runtimeOverride, attemptLevel, bodyTruncationChars = HEAL_MAX_FACT_BODY_CHARS_L3) {
|
|
1369
|
+
const effectiveTasks = attemptLevel >= 1 ? [] : allTasks;
|
|
1370
|
+
const effectiveEvents = attemptLevel >= 2 ? [] : recentEvents;
|
|
1371
|
+
const maxAnchors = Math.max(1, Math.min(HEAL_MAX_ANCHORS, healCandidates.length * HEAL_ANCHORS_PER_CANDIDATE));
|
|
1372
|
+
const effectiveAnchors = documentAnchors.slice(0, maxAnchors);
|
|
1373
|
+
const { shapedCandidates, degraded } = applyBodyTruncation(
|
|
1374
|
+
healCandidates,
|
|
1375
|
+
attemptLevel,
|
|
1376
|
+
bodyTruncationChars
|
|
1377
|
+
);
|
|
1298
1378
|
const template = runtimeOverride ?? this.globalOverrides?.healSystemPrompt ?? HEAL_SYSTEM_PROMPT;
|
|
1299
1379
|
if (/\{\{\s*healCandidates\s*\}\}/.test(template) || /\{\{\s*documentAnchors\s*\}\}/.test(template) || /\{\{\s*allTasks\s*\}\}/.test(template) || /\{\{\s*recentEvents\s*\}\}/.test(template)) {
|
|
1300
1380
|
return {
|
|
1301
|
-
|
|
1302
|
-
|
|
1381
|
+
prompts: {
|
|
1382
|
+
systemPrompt: this.hydrate(template, {
|
|
1383
|
+
healCandidates: shapedCandidates,
|
|
1384
|
+
documentAnchors: effectiveAnchors,
|
|
1385
|
+
allTasks: effectiveTasks,
|
|
1386
|
+
recentEvents: effectiveEvents
|
|
1387
|
+
}),
|
|
1388
|
+
userPrompt: "Please heal the memory graph."
|
|
1389
|
+
},
|
|
1390
|
+
degraded
|
|
1303
1391
|
};
|
|
1304
1392
|
}
|
|
1305
1393
|
return {
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1394
|
+
prompts: {
|
|
1395
|
+
systemPrompt: template,
|
|
1396
|
+
userPrompt: `Heal Candidates:
|
|
1397
|
+
${JSON.stringify(shapedCandidates, null, 2)}
|
|
1309
1398
|
Document Anchors (DO NOT MODIFY OR DELETE):
|
|
1310
|
-
${JSON.stringify(
|
|
1399
|
+
${JSON.stringify(effectiveAnchors, null, 2)}
|
|
1311
1400
|
All Tasks:
|
|
1312
|
-
${JSON.stringify(
|
|
1401
|
+
${JSON.stringify(effectiveTasks, null, 2)}
|
|
1313
1402
|
Recent Events:
|
|
1314
|
-
${JSON.stringify(
|
|
1403
|
+
${JSON.stringify(effectiveEvents, null, 2)}
|
|
1315
1404
|
The following document anchors are provided for contradiction detection only. Do not include them in \`downgraded\`, \`deleted\`, or \`newFacts\`.`
|
|
1405
|
+
},
|
|
1406
|
+
degraded
|
|
1316
1407
|
};
|
|
1317
1408
|
}
|
|
1318
1409
|
buildOntologyBackfillPrompt(facts, runtimeOverride, ontologyContext) {
|
|
@@ -1332,6 +1423,36 @@ ${JSON.stringify(facts, null, 2)}`
|
|
|
1332
1423
|
};
|
|
1333
1424
|
}
|
|
1334
1425
|
};
|
|
1426
|
+
function applyBodyTruncation(candidates, attemptLevel, bodyTruncationChars) {
|
|
1427
|
+
if (attemptLevel < 3) {
|
|
1428
|
+
return { shapedCandidates: candidates, degraded: [] };
|
|
1429
|
+
}
|
|
1430
|
+
const shapedCandidates = [];
|
|
1431
|
+
const degraded = [];
|
|
1432
|
+
for (const c of candidates) {
|
|
1433
|
+
if (typeof c !== "object" || c === null) {
|
|
1434
|
+
shapedCandidates.push(c);
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
const fact = c;
|
|
1438
|
+
const body = typeof fact.body === "string" ? fact.body : "";
|
|
1439
|
+
if (body.length <= bodyTruncationChars) {
|
|
1440
|
+
shapedCandidates.push(c);
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
if (typeof fact.id !== "string") {
|
|
1444
|
+
shapedCandidates.push(c);
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
const originalBodyChars = body.length;
|
|
1448
|
+
const prefix = safeSlice(body, 0, bodyTruncationChars);
|
|
1449
|
+
const truncatedBodyChars = prefix.length;
|
|
1450
|
+
const truncated = `${prefix}\u2026[truncated at ${truncatedBodyChars} chars, original was ${originalBodyChars}]`;
|
|
1451
|
+
shapedCandidates.push({ ...fact, body: truncated });
|
|
1452
|
+
degraded.push({ id: fact.id, originalBodyChars, truncatedBodyChars });
|
|
1453
|
+
}
|
|
1454
|
+
return { shapedCandidates, degraded };
|
|
1455
|
+
}
|
|
1335
1456
|
|
|
1336
1457
|
// src/utils/chunkingDefaults.ts
|
|
1337
1458
|
var DEFAULT_MAX_CHUNK_LENGTH = 12e3;
|
|
@@ -1842,11 +1963,24 @@ var TRUNCATION_PATTERNS = [
|
|
|
1842
1963
|
/finish[_ ]?reason/i
|
|
1843
1964
|
];
|
|
1844
1965
|
var EXCEEDS_LIMIT_PATTERN = /exceed[a-z]*[^.]{0,40}\b(model|context)?[ _-]?limit/i;
|
|
1966
|
+
function getErrorMessage(err) {
|
|
1967
|
+
try {
|
|
1968
|
+
return err instanceof Error ? err.message : String(err ?? "");
|
|
1969
|
+
} catch {
|
|
1970
|
+
return void 0;
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1845
1973
|
function isTruncationError(err) {
|
|
1846
|
-
const message =
|
|
1974
|
+
const message = getErrorMessage(err);
|
|
1975
|
+
if (message === void 0) return false;
|
|
1847
1976
|
if (EXCEEDS_LIMIT_PATTERN.test(message)) return false;
|
|
1848
1977
|
return TRUNCATION_PATTERNS.some((pattern) => pattern.test(message));
|
|
1849
1978
|
}
|
|
1979
|
+
function isConfigError(err) {
|
|
1980
|
+
const message = getErrorMessage(err);
|
|
1981
|
+
if (message === void 0) return false;
|
|
1982
|
+
return EXCEEDS_LIMIT_PATTERN.test(message);
|
|
1983
|
+
}
|
|
1850
1984
|
function initialBatchSize(maxOutputTokens) {
|
|
1851
1985
|
if (!maxOutputTokens || !Number.isFinite(maxOutputTokens) || maxOutputTokens <= 0) {
|
|
1852
1986
|
return DEFAULT_BATCH_SIZE;
|
|
@@ -1888,14 +2022,18 @@ async function runBatched(args) {
|
|
|
1888
2022
|
const single = candidate.slice(0, 1);
|
|
1889
2023
|
return { batch: single, prompts: await buildPrompt(single) };
|
|
1890
2024
|
};
|
|
1891
|
-
const onFailure = async (batch, err) => {
|
|
1892
|
-
if (batch.length
|
|
1893
|
-
if (
|
|
1894
|
-
|
|
2025
|
+
const onFailure = async (batch, err, attemptLevel, fromCall) => {
|
|
2026
|
+
if (batch.length === 1) {
|
|
2027
|
+
if (fromCall && isTruncationError(err) && attemptLevel < 3) {
|
|
2028
|
+
await attempt(batch, void 0, attemptLevel + 1);
|
|
2029
|
+
} else {
|
|
2030
|
+
const reason = fromCall && !isTruncationError(err) && !isConfigError(err) ? "call_error" : "non_convergent";
|
|
2031
|
+
skipped.push({ item: batch[0], reason });
|
|
1895
2032
|
onSkip?.(batch[0], err);
|
|
1896
2033
|
}
|
|
1897
2034
|
return;
|
|
1898
2035
|
}
|
|
2036
|
+
if (fromCall && !isTruncationError(err)) throw err;
|
|
1899
2037
|
const mid = Math.ceil(batch.length / 2);
|
|
1900
2038
|
if (mid < batchSize) batchSize = mid;
|
|
1901
2039
|
let i = 0;
|
|
@@ -1906,23 +2044,22 @@ async function runBatched(args) {
|
|
|
1906
2044
|
i += trimmed.batch.length;
|
|
1907
2045
|
}
|
|
1908
2046
|
};
|
|
1909
|
-
const attempt = async (batch, prebuilt) => {
|
|
2047
|
+
const attempt = async (batch, prebuilt, attemptLevel = 0) => {
|
|
1910
2048
|
if (batch.length === 0) return;
|
|
1911
|
-
const prompts = prebuilt
|
|
2049
|
+
const prompts = prebuilt && attemptLevel === 0 ? prebuilt : await buildPrompt(batch, attemptLevel);
|
|
1912
2050
|
batches++;
|
|
1913
2051
|
let responseText;
|
|
1914
2052
|
try {
|
|
1915
2053
|
responseText = await call(prompts);
|
|
1916
2054
|
} catch (err) {
|
|
1917
|
-
|
|
1918
|
-
await onFailure(batch, err);
|
|
2055
|
+
await onFailure(batch, err, attemptLevel, true);
|
|
1919
2056
|
return;
|
|
1920
2057
|
}
|
|
1921
2058
|
let result;
|
|
1922
2059
|
try {
|
|
1923
2060
|
result = parse(responseText, batch);
|
|
1924
2061
|
} catch (err) {
|
|
1925
|
-
await onFailure(batch, err);
|
|
2062
|
+
await onFailure(batch, err, attemptLevel, false);
|
|
1926
2063
|
return;
|
|
1927
2064
|
}
|
|
1928
2065
|
results.push(result);
|
|
@@ -1931,7 +2068,7 @@ async function runBatched(args) {
|
|
|
1931
2068
|
while (index < items.length) {
|
|
1932
2069
|
const { batch, prompts } = await trim(items.slice(index, index + batchSize));
|
|
1933
2070
|
index += batch.length;
|
|
1934
|
-
await attempt(batch, prompts);
|
|
2071
|
+
await attempt(batch, prompts, 0);
|
|
1935
2072
|
}
|
|
1936
2073
|
return { results, skipped, batches };
|
|
1937
2074
|
}
|
|
@@ -1942,7 +2079,6 @@ var MIN_TOKENS_TO_QUALIFY = 3;
|
|
|
1942
2079
|
var ONTOLOGY_BACKFILL_BATCH_SIZE = 25;
|
|
1943
2080
|
var ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS = 4e4;
|
|
1944
2081
|
var ONTOLOGY_BACKFILL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1945
|
-
var HEAL_MAX_ANCHORS = 50;
|
|
1946
2082
|
var HEAL_ANCHOR_SEARCH_OVERFETCH = 4;
|
|
1947
2083
|
var HEAL_MAX_PROMPT_CHARS = 4e4;
|
|
1948
2084
|
var HEAL_BATCH_SIZE = 25;
|
|
@@ -1950,7 +2086,12 @@ var HEAL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
1950
2086
|
var SKIP_ERROR_LOG_CHARS = 4096;
|
|
1951
2087
|
var formatSkipError = (err) => {
|
|
1952
2088
|
let base;
|
|
1953
|
-
|
|
2089
|
+
let isErrorLike = false;
|
|
2090
|
+
try {
|
|
2091
|
+
isErrorLike = err instanceof Error;
|
|
2092
|
+
} catch {
|
|
2093
|
+
}
|
|
2094
|
+
if (isErrorLike || typeof err !== "object" && typeof err !== "function") {
|
|
1954
2095
|
base = safeErrorToString(err);
|
|
1955
2096
|
} else {
|
|
1956
2097
|
try {
|
|
@@ -2368,6 +2509,10 @@ var MaintenanceService = class {
|
|
|
2368
2509
|
if (!Number.isInteger(batchSize) || batchSize < 1) {
|
|
2369
2510
|
throw new Error("Invalid batchSize: must be an integer >= 1");
|
|
2370
2511
|
}
|
|
2512
|
+
const bodyTruncationChars = options?.bodyTruncationChars ?? HEAL_MAX_FACT_BODY_CHARS_L3;
|
|
2513
|
+
if (!Number.isInteger(bodyTruncationChars) || bodyTruncationChars < 1) {
|
|
2514
|
+
throw new Error("Invalid bodyTruncationChars: must be an integer >= 1");
|
|
2515
|
+
}
|
|
2371
2516
|
const now = Date.now();
|
|
2372
2517
|
const recheckCutoff = now - HEAL_RECHECK_MS;
|
|
2373
2518
|
const orphanAfterDays = this.options.config?.orphanAfterDays !== void 0 ? this.options.config?.orphanAfterDays : 30;
|
|
@@ -2408,29 +2553,44 @@ var MaintenanceService = class {
|
|
|
2408
2553
|
downgraded: staleDowngradedIds.length,
|
|
2409
2554
|
deleted: orphanedIds.length,
|
|
2410
2555
|
newFactsCreated: 0,
|
|
2411
|
-
skipped:
|
|
2556
|
+
skipped: [],
|
|
2557
|
+
degraded: [],
|
|
2412
2558
|
remaining: counts2.eligible,
|
|
2413
2559
|
deferred: counts2.deferred
|
|
2414
2560
|
};
|
|
2415
2561
|
}
|
|
2416
|
-
const allTasks = await this.taskRepo.findAllPending([entityId]);
|
|
2562
|
+
const allTasks = await this.taskRepo.findAllPending([entityId], HEAL_MAX_TASKS);
|
|
2417
2563
|
const recentEvents = await this.eventRepo.getRecent(entityId, 20);
|
|
2418
2564
|
const toPromptShape = (f) => {
|
|
2419
2565
|
const { embedding: _embedding, embedding_blob: _blob, ...rest } = f;
|
|
2420
2566
|
return { ...rest, tags: typeof rest.tags === "string" ? JSON.parse(rest.tags) : rest.tags };
|
|
2421
2567
|
};
|
|
2422
2568
|
const anchorCache = /* @__PURE__ */ new Map();
|
|
2569
|
+
const degraded = [];
|
|
2423
2570
|
const outcome = await runBatched({
|
|
2424
2571
|
items: healCandidates,
|
|
2425
|
-
buildPrompt: async (batch) => {
|
|
2426
|
-
const documentAnchors = await this._selectHealAnchors(
|
|
2427
|
-
|
|
2572
|
+
buildPrompt: async (batch, attemptLevel = 0) => {
|
|
2573
|
+
const documentAnchors = await this._selectHealAnchors(
|
|
2574
|
+
entityId,
|
|
2575
|
+
batch,
|
|
2576
|
+
// Per-batch anchor cap: `batch.length * HEAL_ANCHORS_PER_CANDIDATE` (capped at
|
|
2577
|
+
// HEAL_MAX_ANCHORS) right-sizes the anchor lookup so a 1-fact batch does
|
|
2578
|
+
// not overfetch the same 200 keyword hits a 25-fact batch once did.
|
|
2579
|
+
// buildHealPrompt applies the matching cap on its side — values must match.
|
|
2580
|
+
Math.min(HEAL_MAX_ANCHORS, batch.length * HEAL_ANCHORS_PER_CANDIDATE),
|
|
2581
|
+
anchorCache
|
|
2582
|
+
);
|
|
2583
|
+
const { prompts, degraded: batchDegraded } = await this.promptService.buildHealPrompt(
|
|
2428
2584
|
batch.map(toPromptShape),
|
|
2429
2585
|
documentAnchors,
|
|
2430
2586
|
allTasks,
|
|
2431
2587
|
recentEvents,
|
|
2432
|
-
promptOverride
|
|
2588
|
+
promptOverride,
|
|
2589
|
+
attemptLevel,
|
|
2590
|
+
bodyTruncationChars
|
|
2433
2591
|
);
|
|
2592
|
+
degraded.push(...batchDegraded);
|
|
2593
|
+
return prompts;
|
|
2434
2594
|
},
|
|
2435
2595
|
call: (prompts) => this.options.llmProvider.generateText(prompts),
|
|
2436
2596
|
parse: (responseText, batch) => {
|
|
@@ -2504,8 +2664,14 @@ var MaintenanceService = class {
|
|
|
2504
2664
|
insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
|
|
2505
2665
|
healFactsForDedupe.push({ id, title: fact.title });
|
|
2506
2666
|
}
|
|
2667
|
+
const callErrorIds = new Set(
|
|
2668
|
+
outcome.skipped.filter((s) => s.reason === "call_error").map((s) => s.item.id)
|
|
2669
|
+
);
|
|
2507
2670
|
await this.entryRepo.markHealChecked(
|
|
2508
|
-
[
|
|
2671
|
+
[
|
|
2672
|
+
...healCandidates.map((f) => f.id).filter((id) => !callErrorIds.has(id)),
|
|
2673
|
+
...insertedFacts.map((f) => f.id)
|
|
2674
|
+
],
|
|
2509
2675
|
entityId,
|
|
2510
2676
|
now,
|
|
2511
2677
|
tx
|
|
@@ -2523,6 +2689,13 @@ var MaintenanceService = class {
|
|
|
2523
2689
|
await this.embeddingService.embedFact(fact);
|
|
2524
2690
|
}
|
|
2525
2691
|
this.searchService.evictCache(entityId);
|
|
2692
|
+
const skippedIds = new Set(outcome.skipped.map(({ item }) => item.id));
|
|
2693
|
+
const healedDegraded = degraded.filter((d) => !skippedIds.has(d.id));
|
|
2694
|
+
for (const d of healedDegraded) {
|
|
2695
|
+
console.warn(
|
|
2696
|
+
`[WikiMemory] heal healed under degraded context ${entityId}/${d.id}: body truncated from ${d.originalBodyChars} to ${d.truncatedBodyChars} chars`
|
|
2697
|
+
);
|
|
2698
|
+
}
|
|
2526
2699
|
let scanned = outcome.skipped.length;
|
|
2527
2700
|
for (const batchResult of outcome.results) scanned += batchResult.batch.length;
|
|
2528
2701
|
const allDowngraded = /* @__PURE__ */ new Set([...staleDowngradedIds, ...safeDowngraded]);
|
|
@@ -2533,7 +2706,8 @@ var MaintenanceService = class {
|
|
|
2533
2706
|
downgraded: allDowngraded.size,
|
|
2534
2707
|
deleted: allDeleted.size,
|
|
2535
2708
|
newFactsCreated: insertedFacts.length,
|
|
2536
|
-
skipped: outcome.skipped.
|
|
2709
|
+
skipped: outcome.skipped.map(({ item, reason }) => ({ id: item.id, reason })),
|
|
2710
|
+
degraded: healedDegraded,
|
|
2537
2711
|
remaining: counts.eligible,
|
|
2538
2712
|
deferred: counts.deferred
|
|
2539
2713
|
};
|
|
@@ -2617,7 +2791,15 @@ var MaintenanceService = class {
|
|
|
2617
2791
|
};
|
|
2618
2792
|
}
|
|
2619
2793
|
if (outcome.skipped.length > 0) {
|
|
2620
|
-
|
|
2794
|
+
const callErrorIds = new Set(
|
|
2795
|
+
outcome.skipped.filter((s) => s.reason === "call_error").map((s) => s.item.id)
|
|
2796
|
+
);
|
|
2797
|
+
await this.entryRepo.markOntologyChecked(
|
|
2798
|
+
outcome.skipped.filter(({ item }) => !callErrorIds.has(item.id)).map(({ item }) => item.id),
|
|
2799
|
+
entityId,
|
|
2800
|
+
now,
|
|
2801
|
+
this.db
|
|
2802
|
+
);
|
|
2621
2803
|
}
|
|
2622
2804
|
this.searchService.evictCache(entityId);
|
|
2623
2805
|
const counts = await this.entryRepo.countUntypedByEntityId(entityId, recheckCutoff);
|
|
@@ -2733,7 +2915,7 @@ var MaintenanceService = class {
|
|
|
2733
2915
|
* batches that reduce to the same query share one lookup. Caller-owned and
|
|
2734
2916
|
* per-pass — see the call site in doRunHeal.
|
|
2735
2917
|
*/
|
|
2736
|
-
async _selectHealAnchors(entityId, batch, cache) {
|
|
2918
|
+
async _selectHealAnchors(entityId, batch, cap = HEAL_MAX_ANCHORS, cache) {
|
|
2737
2919
|
const query = batch.map((f) => f.title).join(" ").trim();
|
|
2738
2920
|
if (!query) return [];
|
|
2739
2921
|
const cached = cache?.get(query);
|
|
@@ -2741,7 +2923,7 @@ var MaintenanceService = class {
|
|
|
2741
2923
|
const hits = this.searchService.searchKeyword(
|
|
2742
2924
|
query,
|
|
2743
2925
|
[entityId],
|
|
2744
|
-
|
|
2926
|
+
cap * HEAL_ANCHOR_SEARCH_OVERFETCH
|
|
2745
2927
|
);
|
|
2746
2928
|
const hitIds = hits.map((h) => h.id);
|
|
2747
2929
|
const anchors = [];
|
|
@@ -2752,7 +2934,7 @@ var MaintenanceService = class {
|
|
|
2752
2934
|
const row = byId.get(id);
|
|
2753
2935
|
if (!row) continue;
|
|
2754
2936
|
anchors.push(row);
|
|
2755
|
-
if (anchors.length >=
|
|
2937
|
+
if (anchors.length >= cap) break;
|
|
2756
2938
|
}
|
|
2757
2939
|
}
|
|
2758
2940
|
cache?.set(query, anchors);
|
|
@@ -3043,7 +3225,12 @@ var RetrievalService = class {
|
|
|
3043
3225
|
}
|
|
3044
3226
|
}
|
|
3045
3227
|
} catch (rankerErr) {
|
|
3046
|
-
|
|
3228
|
+
let isErrorLike = false;
|
|
3229
|
+
try {
|
|
3230
|
+
isErrorLike = rankerErr instanceof Error;
|
|
3231
|
+
} catch {
|
|
3232
|
+
}
|
|
3233
|
+
const rankerError = isErrorLike ? rankerErr : new Error(safeErrorToString(rankerErr));
|
|
3047
3234
|
const policy = this.options.vectorRankerFallback ?? "js-cosine";
|
|
3048
3235
|
this.options.onVectorRankerFallback?.({
|
|
3049
3236
|
error: this._sanitizeRankerError(rankerError),
|
|
@@ -3155,12 +3342,21 @@ var RetrievalService = class {
|
|
|
3155
3342
|
}
|
|
3156
3343
|
}
|
|
3157
3344
|
} catch (err) {
|
|
3158
|
-
|
|
3345
|
+
let isErrorLike = false;
|
|
3346
|
+
try {
|
|
3347
|
+
isErrorLike = err instanceof Error;
|
|
3348
|
+
} catch {
|
|
3349
|
+
}
|
|
3350
|
+
const error = isErrorLike ? err : new Error(safeErrorToString(err));
|
|
3159
3351
|
if (rankerShouldRethrow) {
|
|
3160
3352
|
throw error;
|
|
3161
3353
|
}
|
|
3162
3354
|
if (pendingRankerFallbackError) {
|
|
3163
|
-
|
|
3355
|
+
try {
|
|
3356
|
+
error.cause = pendingRankerFallbackError;
|
|
3357
|
+
} catch {
|
|
3358
|
+
this.options.onRetrievalFallback?.(pendingRankerFallbackError);
|
|
3359
|
+
}
|
|
3164
3360
|
pendingRankerFallbackError = void 0;
|
|
3165
3361
|
}
|
|
3166
3362
|
this.options.onRetrievalFallback?.(error);
|