@equationalapplications/core-llm-wiki 5.3.1 → 5.5.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 +53 -0
- package/dist/{chunk-KIJINNYU.mjs → chunk-QVY5DFJV.mjs} +566 -109
- package/dist/chunk-QVY5DFJV.mjs.map +1 -0
- package/dist/index.d.mts +10 -5
- package/dist/index.d.ts +10 -5
- package/dist/index.js +1191 -154
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +629 -50
- package/dist/index.mjs.map +1 -1
- package/dist/{testing-zf3Jp_RL.d.mts → testing-D0RnZjyW.d.mts} +212 -11
- package/dist/{testing-zf3Jp_RL.d.ts → testing-D0RnZjyW.d.ts} +212 -11
- package/dist/testing.d.mts +2 -1
- package/dist/testing.d.ts +2 -1
- package/dist/testing.js +563 -106
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +3 -3
- package/dist/chunk-KIJINNYU.mjs.map +0 -1
package/dist/testing.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var coreOkf = require('@equationalapplications/core-okf');
|
|
3
4
|
var MiniSearch = require('minisearch');
|
|
4
5
|
|
|
5
6
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -109,26 +110,88 @@ var PrunePartialFailureError = class extends Error {
|
|
|
109
110
|
}
|
|
110
111
|
};
|
|
111
112
|
var HOOK_TIMEOUT_MARKER = /* @__PURE__ */ Symbol("WikiMemoryHookTimeout");
|
|
113
|
+
var WikiParseError = class extends Error {
|
|
114
|
+
constructor(message, opts) {
|
|
115
|
+
super(message);
|
|
116
|
+
this.name = "WikiParseError";
|
|
117
|
+
this.tier = opts.tier;
|
|
118
|
+
this.position = opts.position ?? null;
|
|
119
|
+
this.slice = opts.slice ?? "";
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var WikiIngestEmptyError = class extends Error {
|
|
123
|
+
constructor(params) {
|
|
124
|
+
const summary = `All ${params.chunks} chunks failed for sourceRef "${params.sourceRef}"; see parseFailures for per-chunk detail`;
|
|
125
|
+
super(summary);
|
|
126
|
+
this.name = "WikiIngestEmptyError";
|
|
127
|
+
this.parseFailures = params.parseFailures;
|
|
128
|
+
this.sourceRef = params.sourceRef;
|
|
129
|
+
this.chunks = params.chunks;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
112
132
|
|
|
113
133
|
// src/utils/pure.ts
|
|
134
|
+
var MAX_REPAIR_CANDIDATES = 5;
|
|
135
|
+
function throwNoJsonFound(text, start) {
|
|
136
|
+
throw new WikiParseError(
|
|
137
|
+
"No JSON object/array found in LLM response",
|
|
138
|
+
{ tier: "strict", position: start, slice: text }
|
|
139
|
+
);
|
|
140
|
+
}
|
|
114
141
|
function parseJsonResponse(text) {
|
|
115
142
|
const firstBrace = text.indexOf("{");
|
|
116
143
|
const firstBracket = text.indexOf("[");
|
|
117
144
|
let start;
|
|
118
145
|
let openChar;
|
|
119
|
-
let closeChar;
|
|
120
146
|
const useBrace = firstBrace !== -1 && (firstBracket === -1 || firstBrace < firstBracket);
|
|
121
147
|
if (useBrace) {
|
|
122
148
|
start = firstBrace;
|
|
123
149
|
openChar = "{";
|
|
124
|
-
closeChar = "}";
|
|
125
150
|
} else if (firstBracket !== -1) {
|
|
126
151
|
start = firstBracket;
|
|
127
152
|
openChar = "[";
|
|
128
|
-
closeChar = "]";
|
|
129
153
|
} else {
|
|
130
|
-
|
|
154
|
+
throwNoJsonFound(text, null);
|
|
155
|
+
}
|
|
156
|
+
const slice = scanJsonSlice(text, start, openChar);
|
|
157
|
+
if (slice !== null) {
|
|
158
|
+
try {
|
|
159
|
+
return JSON.parse(slice);
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
131
162
|
}
|
|
163
|
+
const repairOdd = containerAwareRepair(text, start, false);
|
|
164
|
+
if (repairOdd.success !== null && !repairOdd.ambiguous) {
|
|
165
|
+
return repairOdd.success;
|
|
166
|
+
}
|
|
167
|
+
const repairEven = containerAwareRepair(text, start, true);
|
|
168
|
+
if (repairOdd.success !== null && repairEven.success !== null) {
|
|
169
|
+
const pick = repairEven.escapes < repairOdd.escapes ? repairEven : repairOdd;
|
|
170
|
+
return pick.success;
|
|
171
|
+
}
|
|
172
|
+
if (repairOdd.success !== null) {
|
|
173
|
+
return repairOdd.success;
|
|
174
|
+
}
|
|
175
|
+
if (repairEven.success !== null) {
|
|
176
|
+
return repairEven.success;
|
|
177
|
+
}
|
|
178
|
+
const repair = repairEven.failed !== null ? repairEven : repairOdd;
|
|
179
|
+
if (repair.failed !== null) {
|
|
180
|
+
throw new WikiParseError(
|
|
181
|
+
`Repair produced a candidate but JSON.parse rejected it: ${repair.failed.message}`,
|
|
182
|
+
{ tier: "repair", position: repair.failed.position, slice: repair.failed.candidate }
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if (slice === null) {
|
|
186
|
+
throwNoJsonFound(text, start);
|
|
187
|
+
}
|
|
188
|
+
throw new WikiParseError(
|
|
189
|
+
"No parsable JSON candidate found",
|
|
190
|
+
{ tier: "all", position: start, slice: text }
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
function scanJsonSlice(text, start, openChar) {
|
|
194
|
+
const closeChar = openChar === "{" ? "}" : "]";
|
|
132
195
|
let depth = 0;
|
|
133
196
|
let inString = false;
|
|
134
197
|
let escape = false;
|
|
@@ -160,8 +223,193 @@ function parseJsonResponse(text) {
|
|
|
160
223
|
}
|
|
161
224
|
}
|
|
162
225
|
}
|
|
163
|
-
if (end === -1)
|
|
164
|
-
return
|
|
226
|
+
if (end === -1) return null;
|
|
227
|
+
return safeSlice(text, start, end + 1);
|
|
228
|
+
}
|
|
229
|
+
function containerAwareRepair(text, start, closeOnEvenParity) {
|
|
230
|
+
const stack = [];
|
|
231
|
+
let inString = false;
|
|
232
|
+
let stringRole = null;
|
|
233
|
+
let escape = false;
|
|
234
|
+
let i = start;
|
|
235
|
+
let out = "";
|
|
236
|
+
let bareQuoteCount = 0;
|
|
237
|
+
let bestFailed = null;
|
|
238
|
+
let escapes = 0;
|
|
239
|
+
let ambiguous = false;
|
|
240
|
+
let bestSuccess = null;
|
|
241
|
+
let attempts = 0;
|
|
242
|
+
function tryEmitCandidate(candidate) {
|
|
243
|
+
attempts++;
|
|
244
|
+
try {
|
|
245
|
+
bestSuccess = JSON.parse(candidate);
|
|
246
|
+
return true;
|
|
247
|
+
} catch (err) {
|
|
248
|
+
if (bestFailed === null) {
|
|
249
|
+
bestFailed = {
|
|
250
|
+
candidate,
|
|
251
|
+
position: extractParsePosition(err),
|
|
252
|
+
message: safeErrorToString(err)
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
while (i < text.length) {
|
|
259
|
+
const ch = text[i];
|
|
260
|
+
if (stack.length === 0 && !inString) {
|
|
261
|
+
if (ch === "{" || ch === "[") {
|
|
262
|
+
stack.push({ container: ch === "{" ? "object" : "array", expectKey: true });
|
|
263
|
+
out += ch;
|
|
264
|
+
}
|
|
265
|
+
i++;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (escape) {
|
|
269
|
+
out += ch;
|
|
270
|
+
escape = false;
|
|
271
|
+
i++;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (inString && ch === "\\") {
|
|
275
|
+
out += ch;
|
|
276
|
+
escape = true;
|
|
277
|
+
i++;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (inString) {
|
|
281
|
+
const code = ch.charCodeAt(0);
|
|
282
|
+
if (code < 32) {
|
|
283
|
+
out += controlCharEscape(code);
|
|
284
|
+
i++;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (ch === '"') {
|
|
289
|
+
let j = i + 1;
|
|
290
|
+
while (j < text.length && (text[j] === " " || text[j] === " " || text[j] === "\n" || text[j] === "\r")) {
|
|
291
|
+
j++;
|
|
292
|
+
}
|
|
293
|
+
const next = j < text.length ? text[j] : "";
|
|
294
|
+
if (!inString) {
|
|
295
|
+
if (next === "}" || next === "]") {
|
|
296
|
+
if (j > i + 1) out += text.slice(i + 1, j);
|
|
297
|
+
out += next;
|
|
298
|
+
i = j + 1;
|
|
299
|
+
stack.pop();
|
|
300
|
+
if (stack.length === 0) {
|
|
301
|
+
if (tryEmitCandidate(out)) return { success: bestSuccess, failed: bestFailed, escapes, ambiguous };
|
|
302
|
+
if (attempts >= MAX_REPAIR_CANDIDATES) break;
|
|
303
|
+
out = "";
|
|
304
|
+
}
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const top = stack[stack.length - 1];
|
|
308
|
+
stringRole = top?.container === "object" && top.expectKey ? "key" : "value";
|
|
309
|
+
out += ch;
|
|
310
|
+
inString = true;
|
|
311
|
+
i++;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const commaOnly = next === ",";
|
|
315
|
+
const isKeyClose = stringRole === "key" && (next === ":" || next === '"' && j + 1 < text.length && text[j + 1] === ":");
|
|
316
|
+
const isValueClose = !commaOnly && (next === "," || next === "}" || next === "]");
|
|
317
|
+
if (commaOnly && bareQuoteCount > 0) ambiguous = true;
|
|
318
|
+
const shouldCommaClose = commaOnly && (bareQuoteCount === 0 || (closeOnEvenParity ? bareQuoteCount % 2 === 0 : bareQuoteCount % 2 === 1));
|
|
319
|
+
const isClosing = isKeyClose || isValueClose || shouldCommaClose;
|
|
320
|
+
if (isClosing) {
|
|
321
|
+
out += ch;
|
|
322
|
+
inString = false;
|
|
323
|
+
const topFrame = stack[stack.length - 1];
|
|
324
|
+
if (topFrame && topFrame.container === "object") {
|
|
325
|
+
topFrame.expectKey = stringRole === "value";
|
|
326
|
+
}
|
|
327
|
+
stringRole = null;
|
|
328
|
+
bareQuoteCount = 0;
|
|
329
|
+
i++;
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
out += "\\" + ch;
|
|
333
|
+
bareQuoteCount++;
|
|
334
|
+
escapes++;
|
|
335
|
+
i++;
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (!inString && (ch === "{" || ch === "[")) {
|
|
339
|
+
stack.push({ container: ch === "{" ? "object" : "array", expectKey: true });
|
|
340
|
+
out += ch;
|
|
341
|
+
i++;
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (!inString && ch === ",") {
|
|
345
|
+
const topFrame = stack[stack.length - 1];
|
|
346
|
+
if (topFrame && topFrame.container === "object") {
|
|
347
|
+
topFrame.expectKey = true;
|
|
348
|
+
}
|
|
349
|
+
out += ch;
|
|
350
|
+
i++;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (!inString && (ch === "}" || ch === "]")) {
|
|
354
|
+
out += ch;
|
|
355
|
+
stack.pop();
|
|
356
|
+
if (stack.length === 0) {
|
|
357
|
+
if (tryEmitCandidate(out)) return { success: bestSuccess, failed: bestFailed, escapes, ambiguous };
|
|
358
|
+
if (attempts >= MAX_REPAIR_CANDIDATES) break;
|
|
359
|
+
out = "";
|
|
360
|
+
}
|
|
361
|
+
i++;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
out += ch;
|
|
365
|
+
i++;
|
|
366
|
+
}
|
|
367
|
+
return { success: null, failed: bestFailed, escapes, ambiguous };
|
|
368
|
+
}
|
|
369
|
+
function controlCharEscape(code) {
|
|
370
|
+
switch (code) {
|
|
371
|
+
case 10:
|
|
372
|
+
return "\\n";
|
|
373
|
+
case 9:
|
|
374
|
+
return "\\t";
|
|
375
|
+
case 13:
|
|
376
|
+
return "\\r";
|
|
377
|
+
case 8:
|
|
378
|
+
return "\\b";
|
|
379
|
+
case 12:
|
|
380
|
+
return "\\f";
|
|
381
|
+
default:
|
|
382
|
+
return "\\u" + code.toString(16).padStart(4, "0");
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function extractParsePosition(err) {
|
|
386
|
+
const match = /position\s+(\d+)/.exec(safeErrorToString(err));
|
|
387
|
+
return match ? Number(match[1]) : null;
|
|
388
|
+
}
|
|
389
|
+
function safeErrorToString(e) {
|
|
390
|
+
if (e instanceof Error) {
|
|
391
|
+
const msg = readErrorField(e, "message");
|
|
392
|
+
if (typeof msg === "string" && msg.length > 0) return msg;
|
|
393
|
+
const name = readErrorField(e, "name");
|
|
394
|
+
if (typeof name === "string" && name.length > 0) return name;
|
|
395
|
+
return "[Error]";
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
return String(e);
|
|
399
|
+
} catch {
|
|
400
|
+
try {
|
|
401
|
+
return Object.prototype.toString.call(e);
|
|
402
|
+
} catch {
|
|
403
|
+
return "[unstringifiable error]";
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function readErrorField(e, key) {
|
|
408
|
+
try {
|
|
409
|
+
return e[key];
|
|
410
|
+
} catch {
|
|
411
|
+
return void 0;
|
|
412
|
+
}
|
|
165
413
|
}
|
|
166
414
|
function sanitizeRankerError(err, sanitizeRankerErrors) {
|
|
167
415
|
if (sanitizeRankerErrors === false) {
|
|
@@ -459,8 +707,6 @@ function normalizeTitleKey(title) {
|
|
|
459
707
|
function entitySummaryMetaKey(entityId) {
|
|
460
708
|
return `entity_summary:${entityId}`;
|
|
461
709
|
}
|
|
462
|
-
|
|
463
|
-
// src/services/ImportExportService.ts
|
|
464
710
|
var MAX_EMBEDDING_BLOB_BYTES = 32 * 1024;
|
|
465
711
|
var IMPORT_TITLE_MAX = 500;
|
|
466
712
|
var IMPORT_BODY_MAX = 8e3;
|
|
@@ -680,7 +926,23 @@ var ImportExportService = class {
|
|
|
680
926
|
access_count: fact.access_count,
|
|
681
927
|
deleted_at: fact.deleted_at,
|
|
682
928
|
embedding_blob: blobData ?? void 0,
|
|
683
|
-
okf_type: fact.okf_type ?? null
|
|
929
|
+
okf_type: fact.okf_type ?? null,
|
|
930
|
+
// OKF v0.2 — forwarded as-is; parseOkfBundle / the caller is responsible for
|
|
931
|
+
// populating these per spec, importDump does not derive or validate them.
|
|
932
|
+
lifecycle_status: fact.lifecycle_status ?? "stable",
|
|
933
|
+
stale_after: fact.stale_after ?? null,
|
|
934
|
+
generated_by: fact.generated_by ?? null,
|
|
935
|
+
okf_sources: fact.okf_sources ?? [],
|
|
936
|
+
okf_verified: fact.okf_verified ?? [],
|
|
937
|
+
okf_usage_window: fact.okf_usage_window ?? null,
|
|
938
|
+
// last_verified_at/_by are DERIVED mirrors of `okf_verified` per spec
|
|
939
|
+
// §5.3. Recomputing here (instead of trusting the payload field) closes
|
|
940
|
+
// the invariant that a raw importDump cannot carry a
|
|
941
|
+
// last_verified_by that contradicts its own okf_verified list.
|
|
942
|
+
...(() => {
|
|
943
|
+
const latest = coreOkf.latestVerified(fact.okf_verified ?? [], Date.now());
|
|
944
|
+
return { last_verified_at: latest?.at ?? null, last_verified_by: latest?.by ?? null };
|
|
945
|
+
})()
|
|
684
946
|
};
|
|
685
947
|
await this.entryRepo.upsertForImport(factObj, tx);
|
|
686
948
|
if (blobData != null) {
|
|
@@ -730,7 +992,19 @@ var ImportExportService = class {
|
|
|
730
992
|
updated_at: safeUpdatedAt,
|
|
731
993
|
resolved_at: task.resolved_at,
|
|
732
994
|
deleted_at: task.deleted_at,
|
|
733
|
-
okf_type: task.okf_type ?? null
|
|
995
|
+
okf_type: task.okf_type ?? null,
|
|
996
|
+
// OKF v0.2
|
|
997
|
+
lifecycle_status: task.lifecycle_status ?? "stable",
|
|
998
|
+
stale_after: task.stale_after ?? null,
|
|
999
|
+
generated_by: task.generated_by ?? null,
|
|
1000
|
+
okf_sources: task.okf_sources ?? [],
|
|
1001
|
+
okf_verified: task.okf_verified ?? [],
|
|
1002
|
+
okf_usage_window: task.okf_usage_window ?? null,
|
|
1003
|
+
// Derived mirrors — see fact branch above; same invariant.
|
|
1004
|
+
...(() => {
|
|
1005
|
+
const latest = coreOkf.latestVerified(task.okf_verified ?? [], Date.now());
|
|
1006
|
+
return { last_verified_at: latest?.at ?? null, last_verified_by: latest?.by ?? null };
|
|
1007
|
+
})()
|
|
734
1008
|
},
|
|
735
1009
|
tx,
|
|
736
1010
|
safeUpdatedAt
|
|
@@ -942,7 +1216,7 @@ Return ONLY a valid JSON object matching this schema:
|
|
|
942
1216
|
{
|
|
943
1217
|
"facts": [{ "title": "string (max 80 chars)", "body": "string (max 800 chars)", "tags": ["string"], "confidence": "certain|inferred|tentative" }]
|
|
944
1218
|
}
|
|
945
|
-
Extract verbatim factual content. Do not return markdown, just raw JSON.`;
|
|
1219
|
+
Extract verbatim factual content. JSON escaping rules: every literal " character in the source must be escaped as \\" inside any JSON string body, and every literal newline as \\n. Source prose containing quotes (e.g. a worked example with "...") must still be reproduced exactly \u2014 re-escape, do not omit. Do not return markdown, just raw JSON.`;
|
|
946
1220
|
var ONTOLOGY_BACKFILL_SYSTEM_PROMPT = `You are a knowledge classification agent. You will receive existing memory facts that currently have no ontology type. For each input fact { "id", "title", "body", "tags" }, assign the best matching okf_type from the ontology manifest and optionally propose edges to related facts by title.
|
|
947
1221
|
Return ONLY a valid JSON object matching this schema:
|
|
948
1222
|
{
|
|
@@ -951,7 +1225,7 @@ Return ONLY a valid JSON object matching this schema:
|
|
|
951
1225
|
]
|
|
952
1226
|
}
|
|
953
1227
|
If no manifest type fits a fact, omit that fact from "classifications" entirely \u2014 do not guess.
|
|
954
|
-
Do not return markdown, just raw JSON.`;
|
|
1228
|
+
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.`;
|
|
955
1229
|
|
|
956
1230
|
// src/services/PromptService.ts
|
|
957
1231
|
var PromptService = class {
|
|
@@ -1064,6 +1338,16 @@ var DEFAULT_MAX_CHUNK_LENGTH = 12e3;
|
|
|
1064
1338
|
var DEFAULT_CHUNK_OVERLAP = 400;
|
|
1065
1339
|
|
|
1066
1340
|
// src/services/IngestionService.ts
|
|
1341
|
+
function zeroChunkResult(duplicateOf) {
|
|
1342
|
+
const result = {
|
|
1343
|
+
truncated: false,
|
|
1344
|
+
chunks: 0,
|
|
1345
|
+
ingestedChunks: 0,
|
|
1346
|
+
failedChunks: 0
|
|
1347
|
+
};
|
|
1348
|
+
if (duplicateOf !== void 0) result.duplicateOf = duplicateOf;
|
|
1349
|
+
return result;
|
|
1350
|
+
}
|
|
1067
1351
|
var IngestionService = class {
|
|
1068
1352
|
constructor(db, prefix, options, entryRepo, sourceRefIndexRepo, metadataRepo, edgeRepo, searchService, jobManager, embeddingService, promptService, ontologyService) {
|
|
1069
1353
|
this.db = db;
|
|
@@ -1104,113 +1388,110 @@ var IngestionService = class {
|
|
|
1104
1388
|
if (onDuplicateHash === "throw") {
|
|
1105
1389
|
throw new WikiDuplicateHashError({ canonical, sourceHash, entityId });
|
|
1106
1390
|
}
|
|
1107
|
-
return
|
|
1391
|
+
return zeroChunkResult(canonical);
|
|
1108
1392
|
}
|
|
1109
1393
|
}
|
|
1110
1394
|
const { chunks, truncated } = chunkText(params.documentChunk, maxChunkLength, chunkOverlap);
|
|
1111
|
-
if (chunks.length === 0) return
|
|
1395
|
+
if (chunks.length === 0) return zeroChunkResult();
|
|
1396
|
+
const ontologyContext = await this.ontologyService?.buildPromptContext(entityId) ?? null;
|
|
1112
1397
|
const chunkResults = await withConcurrency(
|
|
1113
|
-
chunks.map((chunk) => async () => {
|
|
1114
|
-
const ontologyContext = await this.ontologyService?.buildPromptContext(entityId) ?? null;
|
|
1398
|
+
chunks.map((chunk, chunkIndex) => async () => {
|
|
1115
1399
|
const { systemPrompt, userPrompt } = this.promptService.buildIngestPrompt(
|
|
1116
1400
|
chunk,
|
|
1117
1401
|
params.promptOverride,
|
|
1118
1402
|
ontologyContext
|
|
1119
1403
|
);
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1404
|
+
try {
|
|
1405
|
+
const responseText = await this.options.llmProvider.generateText({ systemPrompt, userPrompt });
|
|
1406
|
+
const result2 = parseJsonResponse(responseText);
|
|
1407
|
+
return {
|
|
1408
|
+
status: "ok",
|
|
1409
|
+
facts: (Array.isArray(result2.facts) ? result2.facts : []).map(validateFact).filter((f) => f !== null),
|
|
1410
|
+
ontology_updates: result2.ontology_updates
|
|
1411
|
+
};
|
|
1412
|
+
} catch (e) {
|
|
1413
|
+
const failure = e instanceof WikiParseError ? {
|
|
1414
|
+
chunkIndex,
|
|
1415
|
+
sourceRef,
|
|
1416
|
+
source: "parse",
|
|
1417
|
+
tier: e.tier,
|
|
1418
|
+
position: e.position,
|
|
1419
|
+
message: e.message
|
|
1420
|
+
} : {
|
|
1421
|
+
chunkIndex,
|
|
1422
|
+
sourceRef,
|
|
1423
|
+
source: "llm",
|
|
1424
|
+
// `safeErrorToString` is a non-throwing coercion: an LLM
|
|
1425
|
+
// provider may reject with a non-Error value, and even
|
|
1426
|
+
// `String(e)` itself can throw when `e.toString()` throws.
|
|
1427
|
+
// Letting such an exception escape this catch would cause
|
|
1428
|
+
// `withConcurrency` to reject, discarding every sibling
|
|
1429
|
+
// chunk's results — the exact failure mode this per-chunk
|
|
1430
|
+
// try/catch exists to prevent. Keeping the value in the
|
|
1431
|
+
// `ChunkFailure` preserves the typed diagnostic for
|
|
1432
|
+
// callers (see parseFailures) without unwinding the loop.
|
|
1433
|
+
position: null,
|
|
1434
|
+
message: safeErrorToString(e)
|
|
1435
|
+
};
|
|
1436
|
+
const total = chunks.length;
|
|
1437
|
+
const tags = [];
|
|
1438
|
+
if (failure.tier) tags.push(`tier=${failure.tier}`);
|
|
1439
|
+
if (failure.position !== null) tags.push(`position=${failure.position}`);
|
|
1440
|
+
const tagsSuffix = tags.length > 0 ? `; ${tags.join(" ")}` : "";
|
|
1441
|
+
console.warn(
|
|
1442
|
+
`[WikiMemory] ingest chunk ${chunkIndex + 1}/${total} ${failure.source} failed (sourceRef=${sourceRef}${tagsSuffix})`
|
|
1443
|
+
);
|
|
1444
|
+
return { status: "failed", error: failure };
|
|
1445
|
+
}
|
|
1126
1446
|
}),
|
|
1127
1447
|
chunkConcurrency
|
|
1128
1448
|
);
|
|
1449
|
+
let ingestedChunks = 0;
|
|
1450
|
+
let failedChunks = 0;
|
|
1451
|
+
const failures = [];
|
|
1129
1452
|
const seen = /* @__PURE__ */ new Set();
|
|
1130
1453
|
const orderedChunkFacts = [];
|
|
1131
|
-
for (const
|
|
1454
|
+
for (const slot of chunkResults) {
|
|
1455
|
+
if (slot.status === "failed") {
|
|
1456
|
+
failedChunks++;
|
|
1457
|
+
failures.push(slot.error);
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1460
|
+
ingestedChunks++;
|
|
1132
1461
|
const dedupedFacts = [];
|
|
1133
|
-
for (const fact of
|
|
1462
|
+
for (const fact of slot.facts) {
|
|
1134
1463
|
const normalizedTitle = normalizeTitleKey(fact.title);
|
|
1135
1464
|
if (!seen.has(normalizedTitle)) {
|
|
1136
1465
|
seen.add(normalizedTitle);
|
|
1137
1466
|
dedupedFacts.push(fact);
|
|
1138
1467
|
}
|
|
1139
1468
|
}
|
|
1140
|
-
orderedChunkFacts.push({ facts: dedupedFacts, ontology_updates:
|
|
1469
|
+
orderedChunkFacts.push({ facts: dedupedFacts, ontology_updates: slot.ontology_updates });
|
|
1470
|
+
}
|
|
1471
|
+
if (failedChunks === chunks.length) {
|
|
1472
|
+
throw new WikiIngestEmptyError({
|
|
1473
|
+
parseFailures: failures,
|
|
1474
|
+
sourceRef,
|
|
1475
|
+
chunks: chunks.length
|
|
1476
|
+
});
|
|
1141
1477
|
}
|
|
1142
|
-
const now = Date.now();
|
|
1143
1478
|
const insertedFacts = [];
|
|
1144
1479
|
const deletedSourceFactIds = [];
|
|
1145
1480
|
try {
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
for (const { facts, ontology_updates } of orderedChunkFacts) {
|
|
1161
|
-
if (mode === "emergent" && ontology_updates && this.ontologyService) {
|
|
1162
|
-
manifest = await this.ontologyService.mergeEmergentUpdates(entityId, ontology_updates, tx);
|
|
1163
|
-
ontologyState = await this.ontologyService.getEffectiveState(entityId, tx);
|
|
1164
|
-
mode = ontologyState.mode;
|
|
1165
|
-
}
|
|
1166
|
-
for (const fact of facts) {
|
|
1167
|
-
const ontologyFact = fact;
|
|
1168
|
-
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false }) ?? { okf_type: null, edges: [] };
|
|
1169
|
-
const id = generateId("fact_");
|
|
1170
|
-
hostNodes.push({
|
|
1171
|
-
id,
|
|
1172
|
-
type: ontologyFact.okf_type ?? "",
|
|
1173
|
-
title: fact.title,
|
|
1174
|
-
body: fact.body,
|
|
1175
|
-
// Forward the LLM-extracted tags/confidence through the
|
|
1176
|
-
// extracted-shape upsertGraphCore path so the entries row
|
|
1177
|
-
// stores them (search filterability, heal-candidate
|
|
1178
|
-
// selection, runReembed embedding-text signal). The public
|
|
1179
|
-
// WikiMemory.upsertGraph API doesn't accept tags/confidence
|
|
1180
|
-
// — host-supplied deterministic nodes default to [] and
|
|
1181
|
-
// 'certain' as documented.
|
|
1182
|
-
tags: fact.tags,
|
|
1183
|
-
confidence: fact.confidence
|
|
1184
|
-
});
|
|
1185
|
-
insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
|
|
1186
|
-
titleIndex.set(normalizeTitleKey(fact.title), { id, okf_type: normalized.okf_type });
|
|
1187
|
-
if (normalized.edges.length > 0) {
|
|
1188
|
-
rawEdgeRequests.push({ sourceId: id, sourceType: normalized.okf_type, edges: normalized.edges });
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
const hostEdges = [];
|
|
1193
|
-
for (const req of rawEdgeRequests) {
|
|
1194
|
-
const resolved = this.ontologyService?.resolveEdges(
|
|
1195
|
-
entityId,
|
|
1196
|
-
req.sourceId,
|
|
1197
|
-
req.sourceType,
|
|
1198
|
-
req.edges,
|
|
1199
|
-
manifest,
|
|
1200
|
-
titleIndex,
|
|
1201
|
-
now
|
|
1202
|
-
) ?? [];
|
|
1203
|
-
for (const e of resolved) {
|
|
1204
|
-
hostEdges.push({ type: e.edge_type, sourceId: e.source_id, targetId: e.target_id });
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
await this.upsertGraphCore(
|
|
1208
|
-
entityId,
|
|
1209
|
-
{ sourceRef, sourceHash, nodes: hostNodes, edges: hostEdges },
|
|
1210
|
-
tx,
|
|
1211
|
-
{ strict: false }
|
|
1212
|
-
);
|
|
1213
|
-
});
|
|
1481
|
+
if (failedChunks === 0) {
|
|
1482
|
+
const fullResult = await this.db.withTransactionAsync(async (tx) => {
|
|
1483
|
+
return await this.runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx);
|
|
1484
|
+
});
|
|
1485
|
+
deletedSourceFactIds.push(...fullResult.deletedSourceFactIds);
|
|
1486
|
+
insertedFacts.push(...fullResult.insertedFacts);
|
|
1487
|
+
} else {
|
|
1488
|
+
const partialResult = await this.db.withTransactionAsync(async (tx) => {
|
|
1489
|
+
const flat = [];
|
|
1490
|
+
for (const slot of orderedChunkFacts) flat.push(...slot.facts);
|
|
1491
|
+
return await this.appendPartialFacts(entityId, sourceRef, flat, tx);
|
|
1492
|
+
});
|
|
1493
|
+
insertedFacts.push(...partialResult.insertedDescriptors);
|
|
1494
|
+
}
|
|
1214
1495
|
} catch (err) {
|
|
1215
1496
|
const sqliteCode = err instanceof WikiTransactionError ? err.sqliteErrorCode : extractSqliteCode(err);
|
|
1216
1497
|
if (sqliteCode !== "SQLITE_CONSTRAINT_UNIQUE") throw err;
|
|
@@ -1219,22 +1500,31 @@ var IngestionService = class {
|
|
|
1219
1500
|
if (onDuplicateHash === "throw" || onDuplicateHash === "ingest") {
|
|
1220
1501
|
throw new WikiDuplicateHashError({ canonical, sourceHash, entityId });
|
|
1221
1502
|
}
|
|
1222
|
-
return
|
|
1503
|
+
return zeroChunkResult(canonical);
|
|
1223
1504
|
}
|
|
1224
1505
|
await this.searchService.sync(entityId);
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1506
|
+
if (failedChunks === 0) {
|
|
1507
|
+
const uniqueDeletedSourceFactIds = Array.from(new Set(deletedSourceFactIds));
|
|
1508
|
+
for (const factId of uniqueDeletedSourceFactIds) {
|
|
1509
|
+
try {
|
|
1510
|
+
await this.embeddingService.notifyEmbeddingPersisted(entityId, factId, null);
|
|
1511
|
+
} catch (hookErr) {
|
|
1512
|
+
console.warn(`[WikiMemory] onEmbeddingPersisted hook failed during ingest for ${factId}:`, hookErr);
|
|
1513
|
+
}
|
|
1231
1514
|
}
|
|
1232
1515
|
}
|
|
1233
1516
|
for (const fact of insertedFacts) {
|
|
1234
1517
|
await this.embeddingService.embedFact(fact);
|
|
1235
1518
|
}
|
|
1236
1519
|
this.searchService.evictCache(entityId);
|
|
1237
|
-
|
|
1520
|
+
const result = {
|
|
1521
|
+
truncated,
|
|
1522
|
+
chunks: chunks.length,
|
|
1523
|
+
ingestedChunks,
|
|
1524
|
+
failedChunks
|
|
1525
|
+
};
|
|
1526
|
+
if (failedChunks > 0) result.parseFailures = failures;
|
|
1527
|
+
return result;
|
|
1238
1528
|
} finally {
|
|
1239
1529
|
releaseIngestLocks();
|
|
1240
1530
|
}
|
|
@@ -1359,6 +1649,156 @@ var IngestionService = class {
|
|
|
1359
1649
|
superseded: deletedFactIds.length + deletedEdgeCount
|
|
1360
1650
|
};
|
|
1361
1651
|
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Full supersession + ownership path: identical to the pre-issue-#92 behavior
|
|
1654
|
+
* for the happy path. Called from the `failedChunks === 0` branch of
|
|
1655
|
+
* `ingestDocument`. Runs INSIDE the caller's tx.
|
|
1656
|
+
*
|
|
1657
|
+
* Returns `{ deletedSourceFactIds, insertedFacts }` so the caller can fire
|
|
1658
|
+
* post-commit hooks (embedding lifecycle) AFTER the transaction commits.
|
|
1659
|
+
* On the partial path the caller never invokes this method; the empty
|
|
1660
|
+
* arrays stay empty.
|
|
1661
|
+
*/
|
|
1662
|
+
async runFullUpsertGraph(entityId, sourceRef, sourceHash, orderedChunkFacts, tx) {
|
|
1663
|
+
const deletedSourceFactIds = [];
|
|
1664
|
+
const insertedFacts = [];
|
|
1665
|
+
deletedSourceFactIds.push(...await this.entryRepo.findIdsBySource(entityId, sourceRef, null, tx, false));
|
|
1666
|
+
const titleIndex = /* @__PURE__ */ new Map();
|
|
1667
|
+
const existingFacts = await this.entryRepo.findRecentByEntityId(entityId, 500, tx, sourceRef);
|
|
1668
|
+
for (const existing of existingFacts) {
|
|
1669
|
+
titleIndex.set(normalizeTitleKey(existing.title), {
|
|
1670
|
+
id: existing.id,
|
|
1671
|
+
okf_type: existing.okf_type ?? null
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
let ontologyState = await this.ontologyService?.getEffectiveState(entityId, tx) ?? { mode: "off", manifest: { node_types: [], edge_types: [] } };
|
|
1675
|
+
let { mode, manifest } = ontologyState;
|
|
1676
|
+
const hostNodes = [];
|
|
1677
|
+
const rawEdgeRequests = [];
|
|
1678
|
+
const now = Date.now();
|
|
1679
|
+
for (const { facts, ontology_updates } of orderedChunkFacts) {
|
|
1680
|
+
if (mode === "emergent" && ontology_updates && this.ontologyService) {
|
|
1681
|
+
manifest = await this.ontologyService.mergeEmergentUpdates(entityId, ontology_updates, tx);
|
|
1682
|
+
ontologyState = await this.ontologyService.getEffectiveState(entityId, tx);
|
|
1683
|
+
mode = ontologyState.mode;
|
|
1684
|
+
}
|
|
1685
|
+
for (const fact of facts) {
|
|
1686
|
+
const ontologyFact = fact;
|
|
1687
|
+
const normalized = this.ontologyService?.validateAndNormalizeFact(ontologyFact, manifest, { strict: false }) ?? { okf_type: null, edges: [] };
|
|
1688
|
+
const id = generateId("fact_");
|
|
1689
|
+
hostNodes.push({
|
|
1690
|
+
id,
|
|
1691
|
+
type: ontologyFact.okf_type ?? "",
|
|
1692
|
+
title: fact.title,
|
|
1693
|
+
body: fact.body,
|
|
1694
|
+
// Forward the LLM-extracted tags/confidence through the
|
|
1695
|
+
// extracted-shape upsertGraphCore path so the entries row
|
|
1696
|
+
// stores them (search filterability, heal-candidate
|
|
1697
|
+
// selection, runReembed embedding-text signal). The public
|
|
1698
|
+
// WikiMemory.upsertGraph API doesn't accept tags/confidence
|
|
1699
|
+
// — host-supplied deterministic nodes default to [] and
|
|
1700
|
+
// 'certain' as documented.
|
|
1701
|
+
tags: fact.tags,
|
|
1702
|
+
confidence: fact.confidence
|
|
1703
|
+
});
|
|
1704
|
+
insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
|
|
1705
|
+
titleIndex.set(normalizeTitleKey(fact.title), { id, okf_type: normalized.okf_type });
|
|
1706
|
+
if (normalized.edges.length > 0) {
|
|
1707
|
+
rawEdgeRequests.push({ sourceId: id, sourceType: normalized.okf_type, edges: normalized.edges });
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
const hostEdges = [];
|
|
1712
|
+
for (const req of rawEdgeRequests) {
|
|
1713
|
+
const resolved = this.ontologyService?.resolveEdges(
|
|
1714
|
+
entityId,
|
|
1715
|
+
req.sourceId,
|
|
1716
|
+
req.sourceType,
|
|
1717
|
+
req.edges,
|
|
1718
|
+
manifest,
|
|
1719
|
+
titleIndex,
|
|
1720
|
+
now
|
|
1721
|
+
) ?? [];
|
|
1722
|
+
for (const e of resolved) {
|
|
1723
|
+
hostEdges.push({ type: e.edge_type, sourceId: e.source_id, targetId: e.target_id });
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
await this.upsertGraphCore(
|
|
1727
|
+
entityId,
|
|
1728
|
+
{ sourceRef, sourceHash, nodes: hostNodes, edges: hostEdges },
|
|
1729
|
+
tx,
|
|
1730
|
+
{ strict: false }
|
|
1731
|
+
);
|
|
1732
|
+
return { deletedSourceFactIds, insertedFacts };
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Partial-commit path for `ingestDocument`. Inserts ONLY facts whose
|
|
1736
|
+
* normalized title is not already in the live set for this (entityId,
|
|
1737
|
+
* sourceRef). Does NOT call `entryRepo.softDeleteBySource` (no
|
|
1738
|
+
* supersession) and does NOT call `sourceRefIndexRepo.upsert` (no
|
|
1739
|
+
* ownership). Edges are NOT resolved on this path (no ontology-context
|
|
1740
|
+
* build, no `resolveEdges`, no `mergeEmergentUpdates`). Stale edges from
|
|
1741
|
+
* prior attempts are recovered on the next full run's supersession.
|
|
1742
|
+
*
|
|
1743
|
+
* Source-hash is stored as NULL on partial rows. `findLatestSourceHash`
|
|
1744
|
+
* reads from the most recently updated live row for the sourceRef, so
|
|
1745
|
+
* storing the incoming hash here would cause `hasChanged` to return
|
|
1746
|
+
* `false` on a same-hash retry — the failed chunks would never get a
|
|
1747
|
+
* second chance. With NULL, a retry sees `storedHash === null`, `hasChanged`
|
|
1748
|
+
* returns `true`, and the partial commit's sibling rows remain live
|
|
1749
|
+
* (deduped, not superseded) for the next attempt to extend.
|
|
1750
|
+
*
|
|
1751
|
+
* Runs INSIDE the caller's `tx`. Does not open a nested transaction.
|
|
1752
|
+
* Returns `{ inserted, skippedDuplicate }` for observability.
|
|
1753
|
+
*/
|
|
1754
|
+
async appendPartialFacts(entityId, sourceRef, dedupedFacts, tx) {
|
|
1755
|
+
const liveIds = await this.entryRepo.findIdsBySource(entityId, sourceRef, null, tx, false);
|
|
1756
|
+
const liveFacts = liveIds.length === 0 ? [] : await this.entryRepo.findByIds(liveIds, void 0, tx);
|
|
1757
|
+
const liveTitles = new Set(liveFacts.map((f) => normalizeTitleKey(f.title)));
|
|
1758
|
+
let inserted = 0;
|
|
1759
|
+
let skippedDuplicate = 0;
|
|
1760
|
+
const now = Date.now();
|
|
1761
|
+
const insertedDescriptors = [];
|
|
1762
|
+
for (const fact of dedupedFacts) {
|
|
1763
|
+
const normalizedTitle = normalizeTitleKey(fact.title);
|
|
1764
|
+
if (liveTitles.has(normalizedTitle)) {
|
|
1765
|
+
skippedDuplicate++;
|
|
1766
|
+
continue;
|
|
1767
|
+
}
|
|
1768
|
+
liveTitles.add(normalizedTitle);
|
|
1769
|
+
const id = generateId("fact_");
|
|
1770
|
+
const wikiFact = {
|
|
1771
|
+
id,
|
|
1772
|
+
entity_id: entityId,
|
|
1773
|
+
title: fact.title,
|
|
1774
|
+
body: fact.body,
|
|
1775
|
+
tags: fact.tags,
|
|
1776
|
+
confidence: fact.confidence,
|
|
1777
|
+
source_type: "immutable_document",
|
|
1778
|
+
// source_hash: null on partial rows — see method docstring. The
|
|
1779
|
+
// full path stamps the actual hash; partial rows stay hash-less so
|
|
1780
|
+
// `hasChanged` keeps returning true for retries.
|
|
1781
|
+
source_hash: null,
|
|
1782
|
+
source_ref: sourceRef,
|
|
1783
|
+
created_at: now,
|
|
1784
|
+
updated_at: now,
|
|
1785
|
+
last_accessed_at: null,
|
|
1786
|
+
access_count: 0,
|
|
1787
|
+
deleted_at: null,
|
|
1788
|
+
okf_type: null
|
|
1789
|
+
};
|
|
1790
|
+
await this.entryRepo.upsert(wikiFact, tx);
|
|
1791
|
+
insertedDescriptors.push({
|
|
1792
|
+
id,
|
|
1793
|
+
entity_id: entityId,
|
|
1794
|
+
title: fact.title,
|
|
1795
|
+
body: fact.body,
|
|
1796
|
+
tags: JSON.stringify(fact.tags)
|
|
1797
|
+
});
|
|
1798
|
+
inserted++;
|
|
1799
|
+
}
|
|
1800
|
+
return { inserted, skippedDuplicate, insertedDescriptors };
|
|
1801
|
+
}
|
|
1362
1802
|
};
|
|
1363
1803
|
|
|
1364
1804
|
// src/utils/embedding.ts
|
|
@@ -1507,6 +1947,25 @@ var HEAL_ANCHOR_SEARCH_OVERFETCH = 4;
|
|
|
1507
1947
|
var HEAL_MAX_PROMPT_CHARS = 4e4;
|
|
1508
1948
|
var HEAL_BATCH_SIZE = 25;
|
|
1509
1949
|
var HEAL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1950
|
+
var SKIP_ERROR_LOG_CHARS = 4096;
|
|
1951
|
+
var formatSkipError = (err) => {
|
|
1952
|
+
let base;
|
|
1953
|
+
if (err instanceof Error || typeof err !== "object" && typeof err !== "function") {
|
|
1954
|
+
base = safeErrorToString(err);
|
|
1955
|
+
} else {
|
|
1956
|
+
try {
|
|
1957
|
+
const json = JSON.stringify(err);
|
|
1958
|
+
base = typeof json === "string" ? json : safeErrorToString(err);
|
|
1959
|
+
} catch {
|
|
1960
|
+
base = safeErrorToString(err);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
if (base.length > SKIP_ERROR_LOG_CHARS) {
|
|
1964
|
+
const truncated = safeSlice(base, 0, SKIP_ERROR_LOG_CHARS);
|
|
1965
|
+
return `${truncated}\u2026[+${base.length - SKIP_ERROR_LOG_CHARS} chars truncated]`;
|
|
1966
|
+
}
|
|
1967
|
+
return base;
|
|
1968
|
+
};
|
|
1510
1969
|
var MaintenanceService = class {
|
|
1511
1970
|
constructor(db, prefix, options, entryRepo, sourceRefIndexRepo, taskRepo, eventRepo, metadataRepo, searchService, jobManager, embeddingService, promptService, ontologyService) {
|
|
1512
1971
|
this.db = db;
|
|
@@ -1987,8 +2446,7 @@ var MaintenanceService = class {
|
|
|
1987
2446
|
maxPromptChars: HEAL_MAX_PROMPT_CHARS,
|
|
1988
2447
|
onSkip: (fact, err) => {
|
|
1989
2448
|
console.warn(
|
|
1990
|
-
`[WikiMemory] heal skipped ${entityId}/${fact.id}: response could not be bounded
|
|
1991
|
-
err
|
|
2449
|
+
`[WikiMemory] heal skipped ${entityId}/${fact.id}: response could not be bounded: ${formatSkipError(err)}`
|
|
1992
2450
|
);
|
|
1993
2451
|
}
|
|
1994
2452
|
});
|
|
@@ -2126,8 +2584,7 @@ var MaintenanceService = class {
|
|
|
2126
2584
|
maxPromptChars: ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS,
|
|
2127
2585
|
onSkip: (fact, err) => {
|
|
2128
2586
|
console.warn(
|
|
2129
|
-
`[WikiMemory] ontology backfill skipped ${entityId}/${fact.id}: response could not be bounded
|
|
2130
|
-
err
|
|
2587
|
+
`[WikiMemory] ontology backfill skipped ${entityId}/${fact.id}: response could not be bounded: ${formatSkipError(err)}`
|
|
2131
2588
|
);
|
|
2132
2589
|
}
|
|
2133
2590
|
});
|