@pellux/goodvibes-sdk 0.28.13 → 0.28.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_internal/contracts/artifacts/operator-contract.json +19 -1
- package/dist/_internal/contracts/generated/foundation-metadata.d.ts +1 -1
- package/dist/_internal/contracts/generated/foundation-metadata.js +1 -1
- package/dist/_internal/contracts/generated/operator-contract.d.ts.map +1 -1
- package/dist/_internal/contracts/generated/operator-contract.js +19 -1
- package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.d.ts.map +1 -1
- package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.js +1 -0
- package/dist/_internal/platform/knowledge/home-graph/generated-pages.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/home-graph/generated-pages.js +28 -3
- package/dist/_internal/platform/knowledge/home-graph/service.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/home-graph/service.js +48 -46
- package/dist/_internal/platform/knowledge/home-graph/types.d.ts +1 -0
- package/dist/_internal/platform/knowledge/home-graph/types.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/semantic/answer-fallback.d.ts +11 -0
- package/dist/_internal/platform/knowledge/semantic/answer-fallback.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/semantic/answer-fallback.js +45 -0
- package/dist/_internal/platform/knowledge/semantic/answer.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/semantic/answer.js +7 -18
- package/dist/_internal/platform/knowledge/semantic/gap-repair.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/semantic/gap-repair.js +171 -14
- package/dist/_internal/platform/knowledge/semantic/self-improvement-promotion.d.ts +11 -0
- package/dist/_internal/platform/knowledge/semantic/self-improvement-promotion.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/semantic/self-improvement-promotion.js +243 -0
- package/dist/_internal/platform/knowledge/semantic/self-improvement-tasks.d.ts +12 -0
- package/dist/_internal/platform/knowledge/semantic/self-improvement-tasks.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/semantic/self-improvement-tasks.js +87 -0
- package/dist/_internal/platform/knowledge/semantic/self-improvement.d.ts +4 -0
- package/dist/_internal/platform/knowledge/semantic/self-improvement.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/semantic/self-improvement.js +64 -90
- package/dist/_internal/platform/knowledge/semantic/service.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/semantic/service.js +12 -2
- package/dist/_internal/platform/knowledge/semantic/types.d.ts +2 -0
- package/dist/_internal/platform/knowledge/semantic/types.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/store-refinement.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/store-refinement.js +8 -0
- package/dist/_internal/platform/knowledge/store-schema.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/store-schema.js +6 -1
- package/dist/_internal/platform/knowledge/store.d.ts +1 -0
- package/dist/_internal/platform/knowledge/store.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/store.js +1 -0
- package/dist/_internal/platform/knowledge/types.d.ts +2 -0
- package/dist/_internal/platform/knowledge/types.d.ts.map +1 -1
- package/dist/_internal/platform/state/sqlite-store.d.ts +3 -0
- package/dist/_internal/platform/state/sqlite-store.d.ts.map +1 -1
- package/dist/_internal/platform/state/sqlite-store.js +19 -0
- package/dist/_internal/platform/version.js +1 -1
- package/package.json +1 -1
|
@@ -17,21 +17,28 @@ async function repairKnowledgeGapsWithWeb(request, options) {
|
|
|
17
17
|
const existing = existingSources(request);
|
|
18
18
|
const sourceLimit = Math.max(2, Math.min(5, request.maxSources ?? options.maxSources ?? options.maxIngest ?? 5));
|
|
19
19
|
const searchLimit = Math.max(1, Math.min(5, options.maxSearches ?? queries.length));
|
|
20
|
+
const existingCandidates = selectExistingRepairSources(request, options, sourceLimit);
|
|
20
21
|
const searchResults = new Map();
|
|
21
22
|
const providerIds = new Set();
|
|
22
23
|
let lastError;
|
|
23
24
|
for (const query of queries.slice(0, searchLimit)) {
|
|
25
|
+
if (request.signal?.aborted || deadlineExceeded(request.deadlineAt))
|
|
26
|
+
break;
|
|
24
27
|
try {
|
|
28
|
+
const timeoutMs = operationTimeout(request.deadlineAt, options.searchTimeoutMs ?? 8_000);
|
|
29
|
+
if (timeoutMs <= 0)
|
|
30
|
+
break;
|
|
25
31
|
const response = await withTimeout(options.searchService.search({
|
|
26
32
|
query,
|
|
27
33
|
maxResults: Math.max(sourceLimit, Math.min(8, options.maxResults ?? sourceLimit)),
|
|
28
34
|
verbosity: 'snippets',
|
|
29
35
|
safeSearch: 'moderate',
|
|
36
|
+
trustedHosts: trustedHostsForRepair(request),
|
|
30
37
|
metadata: {
|
|
31
38
|
purpose: 'knowledge-gap-repair',
|
|
32
39
|
knowledgeSpaceId: request.spaceId,
|
|
33
40
|
},
|
|
34
|
-
}), Math.max(1_000,
|
|
41
|
+
}), Math.max(1_000, timeoutMs), 'Semantic gap repair search timed out.');
|
|
35
42
|
if (response.providerId)
|
|
36
43
|
providerIds.add(response.providerId);
|
|
37
44
|
for (const result of response.results) {
|
|
@@ -40,30 +47,44 @@ async function repairKnowledgeGapsWithWeb(request, options) {
|
|
|
40
47
|
continue;
|
|
41
48
|
searchResults.set(canonical, { ...result, searchQuery: query, searchProviderId: response.providerId });
|
|
42
49
|
}
|
|
43
|
-
const partial = selectGapRepairCandidates([...searchResults.values()], existing, options, request, sourceLimit);
|
|
44
|
-
if (partial
|
|
50
|
+
const partial = mergeRepairCandidates(existingCandidates, selectGapRepairCandidates([...searchResults.values()], existing, options, request, sourceLimit), sourceLimit);
|
|
51
|
+
if (hasEnoughRepairEvidence(partial, options))
|
|
45
52
|
break;
|
|
46
53
|
}
|
|
47
54
|
catch (error) {
|
|
48
55
|
lastError = error instanceof Error ? error.message : String(error);
|
|
56
|
+
if (deadlineExceeded(request.deadlineAt))
|
|
57
|
+
break;
|
|
49
58
|
}
|
|
50
59
|
}
|
|
51
60
|
const allResults = [...searchResults.values()];
|
|
52
|
-
const candidates = selectGapRepairCandidates(allResults, existing, options, request, sourceLimit);
|
|
53
|
-
if (candidates
|
|
61
|
+
const candidates = mergeRepairCandidates(existingCandidates, selectGapRepairCandidates(allResults, existing, options, request, sourceLimit), sourceLimit);
|
|
62
|
+
if (!hasEnoughRepairEvidence(candidates, options)) {
|
|
54
63
|
return {
|
|
55
64
|
searched: true,
|
|
56
65
|
query: queries[0],
|
|
66
|
+
evidenceSufficient: false,
|
|
67
|
+
acceptedSourceIds: existingCandidates.map((candidate) => candidate.existingSourceId).filter((id) => Boolean(id)),
|
|
57
68
|
ingestedSourceIds: [],
|
|
58
69
|
skippedUrls: allResults.map((result) => result.url),
|
|
59
|
-
sourceAssessments:
|
|
60
|
-
|
|
70
|
+
sourceAssessments: [
|
|
71
|
+
...existingCandidates.map((candidate) => candidateToAssessment(candidate, true)),
|
|
72
|
+
...buildSourceAssessments(allResults, candidates, existing, options, request),
|
|
73
|
+
],
|
|
74
|
+
reason: lastError ?? 'Insufficient distinct source-backed evidence was found for gap repair.',
|
|
61
75
|
};
|
|
62
76
|
}
|
|
63
77
|
const ingestedSourceIds = [];
|
|
64
78
|
const skippedUrls = [];
|
|
65
|
-
|
|
79
|
+
const existingSourceIds = candidates.map((candidate) => candidate.existingSourceId).filter((id) => Boolean(id));
|
|
80
|
+
const newCandidates = candidates.filter((candidate) => !candidate.existingSourceId);
|
|
81
|
+
for (const result of newCandidates.slice(0, Math.max(0, Math.min(sourceLimit - existingSourceIds.length, options.maxIngest ?? sourceLimit)))) {
|
|
82
|
+
if (request.signal?.aborted || deadlineExceeded(request.deadlineAt))
|
|
83
|
+
break;
|
|
66
84
|
try {
|
|
85
|
+
const timeoutMs = operationTimeout(request.deadlineAt, options.ingestTimeoutMs ?? 10_000);
|
|
86
|
+
if (timeoutMs <= 0)
|
|
87
|
+
break;
|
|
67
88
|
const ingested = await withTimeout(options.ingestService.ingestUrl({
|
|
68
89
|
url: result.url,
|
|
69
90
|
...(result.title ? { title: result.title } : {}),
|
|
@@ -92,7 +113,7 @@ async function repairKnowledgeGapsWithWeb(request, options) {
|
|
|
92
113
|
searchedAt: Date.now(),
|
|
93
114
|
},
|
|
94
115
|
},
|
|
95
|
-
}), Math.max(1_000,
|
|
116
|
+
}), Math.max(1_000, timeoutMs), 'Semantic gap repair source ingest timed out.');
|
|
96
117
|
if (ingested.source.status === 'indexed' || ingested.source.status === 'pending') {
|
|
97
118
|
ingestedSourceIds.push(ingested.source.id);
|
|
98
119
|
}
|
|
@@ -105,10 +126,15 @@ async function repairKnowledgeGapsWithWeb(request, options) {
|
|
|
105
126
|
return {
|
|
106
127
|
searched: true,
|
|
107
128
|
query: queries[0],
|
|
129
|
+
evidenceSufficient: true,
|
|
130
|
+
acceptedSourceIds: uniqueStrings([...existingSourceIds, ...ingestedSourceIds]),
|
|
108
131
|
ingestedSourceIds,
|
|
109
132
|
skippedUrls,
|
|
110
|
-
sourceAssessments:
|
|
111
|
-
|
|
133
|
+
sourceAssessments: [
|
|
134
|
+
...existingCandidates.map((candidate) => candidateToAssessment(candidate, candidates.some((entry) => entry.url === candidate.url))),
|
|
135
|
+
...buildSourceAssessments(allResults, candidates, existing, options, request),
|
|
136
|
+
],
|
|
137
|
+
...(existingSourceIds.length + ingestedSourceIds.length < 1 ? { reason: 'Gap repair searched but did not accept usable sources.' } : {}),
|
|
112
138
|
};
|
|
113
139
|
}
|
|
114
140
|
function buildGapRepairQueries(request) {
|
|
@@ -195,9 +221,47 @@ function selectGapRepairCandidates(results, existingCanonicalUris, options, requ
|
|
|
195
221
|
byDomain.set(domain, { ...result, confidence: assessment.confidence, reasons: assessment.reasons });
|
|
196
222
|
}
|
|
197
223
|
return [...byDomain.values()]
|
|
198
|
-
.sort(
|
|
224
|
+
.sort(compareRepairCandidates)
|
|
199
225
|
.slice(0, sourceLimit);
|
|
200
226
|
}
|
|
227
|
+
function selectExistingRepairSources(request, options, sourceLimit) {
|
|
228
|
+
const minimumConfidence = Math.max(1, Math.min(100, options.minConfidence ?? 70));
|
|
229
|
+
const byDomain = new Map();
|
|
230
|
+
for (const source of request.sources) {
|
|
231
|
+
if (source.status !== 'indexed' || isGeneratedOrSyntheticSource(source))
|
|
232
|
+
continue;
|
|
233
|
+
const url = source.sourceUri ?? source.canonicalUri;
|
|
234
|
+
if (!url)
|
|
235
|
+
continue;
|
|
236
|
+
const domain = safeDomain(url);
|
|
237
|
+
if (!domain || byDomain.has(domain))
|
|
238
|
+
continue;
|
|
239
|
+
const snippet = [source.summary, source.description, source.tags.join(' ')].filter(Boolean).join(' ');
|
|
240
|
+
const result = {
|
|
241
|
+
rank: 0,
|
|
242
|
+
url,
|
|
243
|
+
domain,
|
|
244
|
+
displayUrl: url,
|
|
245
|
+
type: 'organic',
|
|
246
|
+
providerId: 'indexed',
|
|
247
|
+
metadata: source.metadata,
|
|
248
|
+
searchQuery: request.query,
|
|
249
|
+
searchProviderId: 'indexed',
|
|
250
|
+
...(source.title ? { title: source.title } : {}),
|
|
251
|
+
...(snippet ? { snippet } : {}),
|
|
252
|
+
};
|
|
253
|
+
const assessment = assessGapRepairSource(result, request.query, request);
|
|
254
|
+
if (assessment.confidence < minimumConfidence && !isOfficialOrVendorSource(assessment.reasons))
|
|
255
|
+
continue;
|
|
256
|
+
byDomain.set(domain, {
|
|
257
|
+
...result,
|
|
258
|
+
existingSourceId: source.id,
|
|
259
|
+
confidence: Math.max(assessment.confidence, isOfficialOrVendorSource(assessment.reasons) ? 82 : assessment.confidence),
|
|
260
|
+
reasons: uniqueStrings([...assessment.reasons, 'already-indexed']),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return [...byDomain.values()].sort(compareRepairCandidates).slice(0, sourceLimit);
|
|
264
|
+
}
|
|
201
265
|
function assessGapRepairSource(result, query, request) {
|
|
202
266
|
const searchable = [result.title, result.snippet, result.url, result.domain].filter(Boolean).join(' ').toLowerCase();
|
|
203
267
|
const reasons = [];
|
|
@@ -240,9 +304,13 @@ function assessGapRepairSource(result, query, request) {
|
|
|
240
304
|
reasons.push('source-purpose');
|
|
241
305
|
}
|
|
242
306
|
if (domain && identities.manufacturers.some((manufacturer) => manufacturer.length >= 2 && domain.includes(manufacturer.toLowerCase()))) {
|
|
243
|
-
score +=
|
|
307
|
+
score += 28;
|
|
244
308
|
reasons.push('manufacturer-domain');
|
|
245
309
|
}
|
|
310
|
+
if (domain && isOfficialVendorDomain(domain, identities.manufacturers)) {
|
|
311
|
+
score += 28;
|
|
312
|
+
reasons.push('official-vendor-domain');
|
|
313
|
+
}
|
|
246
314
|
return {
|
|
247
315
|
url: result.url,
|
|
248
316
|
...(result.title ? { title: result.title } : {}),
|
|
@@ -254,6 +322,36 @@ function assessGapRepairSource(result, query, request) {
|
|
|
254
322
|
...(reasons.length > 0 ? { trustReason: reasons.join(', ') } : {}),
|
|
255
323
|
};
|
|
256
324
|
}
|
|
325
|
+
function mergeRepairCandidates(existingCandidates, searchCandidates, sourceLimit) {
|
|
326
|
+
const byCanonical = new Map();
|
|
327
|
+
for (const candidate of [...existingCandidates, ...searchCandidates]) {
|
|
328
|
+
const key = canonicalizeUri(candidate.url) ?? candidate.url;
|
|
329
|
+
const current = byCanonical.get(key);
|
|
330
|
+
if (!current || compareRepairCandidates(candidate, current) < 0)
|
|
331
|
+
byCanonical.set(key, candidate);
|
|
332
|
+
}
|
|
333
|
+
return [...byCanonical.values()].sort(compareRepairCandidates).slice(0, sourceLimit);
|
|
334
|
+
}
|
|
335
|
+
function hasEnoughRepairEvidence(candidates, options) {
|
|
336
|
+
if (candidates.some((candidate) => candidate.reasons.includes('official-vendor-domain')))
|
|
337
|
+
return true;
|
|
338
|
+
const required = Math.max(2, options.minDistinctDomains ?? 2);
|
|
339
|
+
return new Set(candidates.map((candidate) => candidate.domain ?? safeDomain(candidate.url)).filter(Boolean)).size >= required;
|
|
340
|
+
}
|
|
341
|
+
function compareRepairCandidates(left, right) {
|
|
342
|
+
return sourceAuthorityScore(right) - sourceAuthorityScore(left)
|
|
343
|
+
|| right.confidence - left.confidence
|
|
344
|
+
|| left.rank - right.rank;
|
|
345
|
+
}
|
|
346
|
+
function sourceAuthorityScore(candidate) {
|
|
347
|
+
if (candidate.reasons.includes('official-vendor-domain'))
|
|
348
|
+
return 3;
|
|
349
|
+
if (candidate.reasons.includes('manufacturer-domain'))
|
|
350
|
+
return 2;
|
|
351
|
+
if (candidate.existingSourceId)
|
|
352
|
+
return 1;
|
|
353
|
+
return 0;
|
|
354
|
+
}
|
|
257
355
|
function buildSourceAssessments(results, candidates, existingCanonicalUris, options, request) {
|
|
258
356
|
const accepted = new Set(candidates.map((candidate) => canonicalizeUri(candidate.url)));
|
|
259
357
|
const acceptedDomains = new Set(candidates.map((candidate) => candidate.domain ?? safeDomain(candidate.url)).filter(Boolean));
|
|
@@ -284,6 +382,20 @@ function buildSourceAssessments(results, candidates, existingCanonicalUris, opti
|
|
|
284
382
|
};
|
|
285
383
|
});
|
|
286
384
|
}
|
|
385
|
+
function candidateToAssessment(candidate, accepted) {
|
|
386
|
+
return {
|
|
387
|
+
url: candidate.url,
|
|
388
|
+
...(candidate.title ? { title: candidate.title } : {}),
|
|
389
|
+
...(candidate.domain ? { domain: candidate.domain } : {}),
|
|
390
|
+
rank: candidate.rank,
|
|
391
|
+
query: candidate.searchQuery,
|
|
392
|
+
accepted,
|
|
393
|
+
confidence: candidate.confidence,
|
|
394
|
+
reasons: candidate.reasons,
|
|
395
|
+
trustReason: candidate.reasons.join(', '),
|
|
396
|
+
...(accepted ? {} : { rejectionReason: 'not-selected' }),
|
|
397
|
+
};
|
|
398
|
+
}
|
|
287
399
|
function sourceIdentityHints(request) {
|
|
288
400
|
const subjects = uniqueStrings([
|
|
289
401
|
...request.linkedObjects.flatMap((node) => [node.title, ...node.aliases]),
|
|
@@ -298,9 +410,26 @@ function sourceIdentityHints(request) {
|
|
|
298
410
|
const manufacturers = uniqueStrings(request.linkedObjects.flatMap((node) => [
|
|
299
411
|
readString(node.metadata.manufacturer),
|
|
300
412
|
readString(node.metadata.vendor),
|
|
301
|
-
]).concat(request.sources.flatMap((source) => manufacturerHints(`${source.title ?? ''} ${source.sourceUri ?? ''} ${source.canonicalUri ?? ''}`)))
|
|
413
|
+
]).concat(request.sources.flatMap((source) => manufacturerHints(`${source.title ?? ''} ${source.sourceUri ?? ''} ${source.canonicalUri ?? ''}`)))
|
|
414
|
+
.map((manufacturer) => manufacturer?.trim().toLowerCase())
|
|
415
|
+
.filter((manufacturer) => Boolean(manufacturer)));
|
|
302
416
|
return { models, manufacturers, subjects };
|
|
303
417
|
}
|
|
418
|
+
function trustedHostsForRepair(request) {
|
|
419
|
+
const manufacturers = sourceIdentityHints(request).manufacturers;
|
|
420
|
+
const hosts = [];
|
|
421
|
+
if (manufacturers.includes('lg'))
|
|
422
|
+
hosts.push('lg.com', 'www.lg.com');
|
|
423
|
+
if (manufacturers.includes('sony'))
|
|
424
|
+
hosts.push('sony.com', 'www.sony.com');
|
|
425
|
+
if (manufacturers.includes('samsung'))
|
|
426
|
+
hosts.push('samsung.com', 'www.samsung.com');
|
|
427
|
+
if (manufacturers.includes('apple'))
|
|
428
|
+
hosts.push('apple.com', 'support.apple.com');
|
|
429
|
+
if (manufacturers.includes('espressif') || manufacturers.includes('esp32'))
|
|
430
|
+
hosts.push('espressif.com', 'docs.espressif.com');
|
|
431
|
+
return uniqueStrings(hosts);
|
|
432
|
+
}
|
|
304
433
|
function isGenericSubject(value) {
|
|
305
434
|
return /^(tv|television|device|manual|user guide|owner manual|home assistant|service|provider|integration)$/i.test(value.trim());
|
|
306
435
|
}
|
|
@@ -311,6 +440,34 @@ function manufacturerHints(value) {
|
|
|
311
440
|
const hints = value.match(/\b(lg|samsung|sony|vizio|tcl|hisense|philips|panasonic|kasa|tp-link|ecobee|honeywell|ring|arlo|nest|eufy|aqara|sonoff|shelly|lutron|leviton|ikea|bosch|ge|whirlpool|frigidaire|apple|espressif|esp32|nabu casa|home assistant)\b/gi) ?? [];
|
|
312
441
|
return uniqueStrings(hints.map((hint) => hint.toLowerCase()));
|
|
313
442
|
}
|
|
443
|
+
function isGeneratedOrSyntheticSource(source) {
|
|
444
|
+
return source.tags.includes('generated-page')
|
|
445
|
+
|| source.metadata.projectionKind === 'device-passport'
|
|
446
|
+
|| source.metadata.projectionKind === 'room-page';
|
|
447
|
+
}
|
|
448
|
+
function isOfficialVendorDomain(domain, manufacturers) {
|
|
449
|
+
const lower = domain.toLowerCase();
|
|
450
|
+
if (manufacturers.some((manufacturer) => manufacturer === 'lg' && /(^|\.)lg\.com$/.test(lower)))
|
|
451
|
+
return true;
|
|
452
|
+
if (manufacturers.some((manufacturer) => manufacturer === 'sony' && /(^|\.)sony\.(com|net)$/.test(lower)))
|
|
453
|
+
return true;
|
|
454
|
+
if (manufacturers.some((manufacturer) => manufacturer === 'samsung' && /(^|\.)samsung\.com$/.test(lower)))
|
|
455
|
+
return true;
|
|
456
|
+
if (manufacturers.some((manufacturer) => manufacturer === 'apple' && /(^|\.)apple\.com$/.test(lower)))
|
|
457
|
+
return true;
|
|
458
|
+
return manufacturers.some((manufacturer) => manufacturer.length >= 4 && lower.includes(manufacturer.toLowerCase()) && /\b(support|docs?|developer|product)\b/.test(lower));
|
|
459
|
+
}
|
|
460
|
+
function isOfficialOrVendorSource(reasons) {
|
|
461
|
+
return reasons.includes('official-vendor-domain') || reasons.includes('manufacturer-domain');
|
|
462
|
+
}
|
|
463
|
+
function deadlineExceeded(deadlineAt) {
|
|
464
|
+
return typeof deadlineAt === 'number' && Date.now() >= deadlineAt;
|
|
465
|
+
}
|
|
466
|
+
function operationTimeout(deadlineAt, fallbackMs) {
|
|
467
|
+
if (typeof deadlineAt !== 'number')
|
|
468
|
+
return fallbackMs;
|
|
469
|
+
return Math.min(fallbackMs, Math.max(0, deadlineAt - Date.now()));
|
|
470
|
+
}
|
|
314
471
|
function hasIdentity(searchable, value) {
|
|
315
472
|
const normalized = value.trim().toLowerCase();
|
|
316
473
|
if (!normalized)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { KnowledgeStore } from '../store.js';
|
|
2
|
+
import type { KnowledgeNodeRecord, KnowledgeRefinementTaskRecord } from '../types.js';
|
|
3
|
+
export interface SelfImprovePromotionContext {
|
|
4
|
+
readonly store: KnowledgeStore;
|
|
5
|
+
readonly enrichSource?: (sourceId: string, options: {
|
|
6
|
+
readonly force?: boolean;
|
|
7
|
+
readonly knowledgeSpaceId?: string;
|
|
8
|
+
}) => Promise<unknown>;
|
|
9
|
+
}
|
|
10
|
+
export declare function promoteRepairSources(context: SelfImprovePromotionContext, spaceId: string, gap: KnowledgeNodeRecord, sourceIds: readonly string[], task: KnowledgeRefinementTaskRecord, deadlineAt: number): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=self-improvement-promotion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-improvement-promotion.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/self-improvement-promotion.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAEV,mBAAmB,EACnB,6BAA6B,EAE9B,MAAM,aAAa,CAAC;AAiBrB,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3I;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,2BAA2B,EACpC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,mBAAmB,EACxB,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,IAAI,EAAE,6BAA6B,EACnC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAoBf"}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { yieldEvery, yieldToEventLoop } from '../cooperative.js';
|
|
2
|
+
import { getKnowledgeSpaceId } from '../spaces.js';
|
|
3
|
+
import { hasConcreteFeatureSignal, isLowValueFeatureOrSpecText } from './fact-quality.js';
|
|
4
|
+
import { updateRefinementTask } from './self-improvement-tasks.js';
|
|
5
|
+
import { withTimeout } from './timeouts.js';
|
|
6
|
+
import { clampText, normalizeWhitespace, readRecord, readString, semanticHash, semanticMetadata, semanticSlug, sourceSemanticText, splitSentences, uniqueStrings, } from './utils.js';
|
|
7
|
+
export async function promoteRepairSources(context, spaceId, gap, sourceIds, task, deadlineAt) {
|
|
8
|
+
if (context.enrichSource) {
|
|
9
|
+
for (const [index, sourceId] of sourceIds.entries()) {
|
|
10
|
+
await yieldEvery(index, 2);
|
|
11
|
+
const remainingMs = Math.max(0, deadlineAt - Date.now());
|
|
12
|
+
if (remainingMs < 1_000)
|
|
13
|
+
break;
|
|
14
|
+
await withTimeout(context.enrichSource(sourceId, { knowledgeSpaceId: spaceId, force: true }), Math.min(remainingMs, 20_000), 'Semantic repair source enrichment exceeded its run budget.');
|
|
15
|
+
await yieldToEventLoop();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const promotedFactCount = await promoteRepairEvidenceFacts(context.store, spaceId, gap, sourceIds);
|
|
19
|
+
await linkPromotedFactsToRepairSubjects(context.store, spaceId, gap, sourceIds);
|
|
20
|
+
await updateRefinementTask(context.store, context.store.getRefinementTask(task.id) ?? task, 'verified', 'Accepted repair sources were semantically enriched.', {
|
|
21
|
+
promotedSourceIds: sourceIds,
|
|
22
|
+
promotedFactCount,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async function promoteRepairEvidenceFacts(store, spaceId, gap, sourceIds) {
|
|
26
|
+
const subjects = linkedRepairSubjects(store, spaceId, gap);
|
|
27
|
+
if (subjects.length === 0)
|
|
28
|
+
return 0;
|
|
29
|
+
let promoted = 0;
|
|
30
|
+
for (const sourceId of sourceIds) {
|
|
31
|
+
const source = store.getSource(sourceId);
|
|
32
|
+
if (!source)
|
|
33
|
+
continue;
|
|
34
|
+
const extraction = store.getExtractionBySourceId(source.id);
|
|
35
|
+
const authority = sourceAuthority(source);
|
|
36
|
+
const sentences = selectRepairFactSentences({
|
|
37
|
+
query: gap.title,
|
|
38
|
+
source,
|
|
39
|
+
text: sourceSemanticText(source, extraction),
|
|
40
|
+
});
|
|
41
|
+
for (const [index, sentence] of sentences.entries()) {
|
|
42
|
+
const classification = classifyRepairFact(sentence);
|
|
43
|
+
const fact = await store.upsertNode({
|
|
44
|
+
id: `sem-fact-${semanticHash(spaceId, source.id, gap.id, classification.title, sentence)}`,
|
|
45
|
+
kind: 'fact',
|
|
46
|
+
slug: semanticSlug(`${spaceId}-${classification.title}-${source.id}-${index}`),
|
|
47
|
+
title: classification.title,
|
|
48
|
+
summary: sentence,
|
|
49
|
+
aliases: classification.aliases,
|
|
50
|
+
status: 'active',
|
|
51
|
+
confidence: authority === 'official-vendor' ? 88 : 76,
|
|
52
|
+
sourceId: source.id,
|
|
53
|
+
metadata: semanticMetadata(spaceId, {
|
|
54
|
+
semanticKind: 'fact',
|
|
55
|
+
factKind: classification.kind,
|
|
56
|
+
value: classification.value,
|
|
57
|
+
evidence: sentence,
|
|
58
|
+
labels: classification.labels,
|
|
59
|
+
sourceId: source.id,
|
|
60
|
+
gapId: gap.id,
|
|
61
|
+
linkedObjectIds: subjects.map((subject) => subject.id),
|
|
62
|
+
extractor: 'repair-promotion',
|
|
63
|
+
sourceAuthority: authority,
|
|
64
|
+
sourceDiscovery: readRecord(source.metadata.sourceDiscovery),
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
await store.upsertEdge({
|
|
68
|
+
fromKind: 'source',
|
|
69
|
+
fromId: source.id,
|
|
70
|
+
toKind: 'node',
|
|
71
|
+
toId: fact.id,
|
|
72
|
+
relation: 'supports_fact',
|
|
73
|
+
weight: authority === 'official-vendor' ? 0.95 : 0.84,
|
|
74
|
+
metadata: semanticMetadata(spaceId, {
|
|
75
|
+
linkedBy: 'semantic-gap-repair',
|
|
76
|
+
gapId: gap.id,
|
|
77
|
+
}),
|
|
78
|
+
});
|
|
79
|
+
for (const subject of subjects) {
|
|
80
|
+
await store.upsertEdge({
|
|
81
|
+
fromKind: 'node',
|
|
82
|
+
fromId: fact.id,
|
|
83
|
+
toKind: 'node',
|
|
84
|
+
toId: subject.id,
|
|
85
|
+
relation: 'describes',
|
|
86
|
+
weight: authority === 'official-vendor' ? 0.94 : 0.82,
|
|
87
|
+
metadata: semanticMetadata(spaceId, {
|
|
88
|
+
linkedBy: 'semantic-gap-repair',
|
|
89
|
+
repairedAt: Date.now(),
|
|
90
|
+
sourceId: source.id,
|
|
91
|
+
gapId: gap.id,
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
promoted += 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return promoted;
|
|
99
|
+
}
|
|
100
|
+
async function linkPromotedFactsToRepairSubjects(store, spaceId, gap, sourceIds) {
|
|
101
|
+
const linkedObjectIds = readStringArray(gap.metadata.linkedObjectIds).filter((nodeId) => Boolean(store.getNode(nodeId)));
|
|
102
|
+
if (linkedObjectIds.length === 0)
|
|
103
|
+
return;
|
|
104
|
+
const edges = store.listEdges();
|
|
105
|
+
const nodesById = new Map(store.listNodes(10_000).filter((node) => getKnowledgeSpaceId(node) === spaceId).map((node) => [node.id, node]));
|
|
106
|
+
for (const sourceId of sourceIds) {
|
|
107
|
+
for (const fact of factsForSource(sourceId, edges, nodesById)) {
|
|
108
|
+
for (const objectId of linkedObjectIds) {
|
|
109
|
+
await store.upsertEdge({
|
|
110
|
+
fromKind: 'node',
|
|
111
|
+
fromId: fact.id,
|
|
112
|
+
toKind: 'node',
|
|
113
|
+
toId: objectId,
|
|
114
|
+
relation: 'describes',
|
|
115
|
+
weight: 0.82,
|
|
116
|
+
metadata: semanticMetadata(spaceId, {
|
|
117
|
+
linkedBy: 'semantic-gap-repair',
|
|
118
|
+
repairedAt: Date.now(),
|
|
119
|
+
sourceId,
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function selectRepairFactSentences(input) {
|
|
127
|
+
const wanted = repairIntentPatterns(input.query);
|
|
128
|
+
const sourceText = normalizeWhitespace(input.text);
|
|
129
|
+
const candidates = splitSentences(sourceText, 360)
|
|
130
|
+
.map((sentence) => normalizeWhitespace(sentence))
|
|
131
|
+
.filter((sentence) => sentence.length >= 24 && sentence.length <= 360)
|
|
132
|
+
.filter((sentence) => !sentence.trim().endsWith('?'))
|
|
133
|
+
.filter((sentence) => hasConcreteFeatureSignal(sentence))
|
|
134
|
+
.filter((sentence) => !isLowValueFeatureOrSpecText(sentence));
|
|
135
|
+
const scored = candidates.map((sentence) => ({
|
|
136
|
+
sentence,
|
|
137
|
+
score: repairSentenceScore(sentence, wanted, input.source),
|
|
138
|
+
})).filter((entry) => entry.score > 0);
|
|
139
|
+
return uniqueStrings(scored
|
|
140
|
+
.sort((left, right) => right.score - left.score || left.sentence.localeCompare(right.sentence))
|
|
141
|
+
.map((entry) => entry.sentence))
|
|
142
|
+
.slice(0, 14);
|
|
143
|
+
}
|
|
144
|
+
function repairSentenceScore(sentence, wanted, source) {
|
|
145
|
+
const lower = sentence.toLowerCase();
|
|
146
|
+
let score = sourceAuthority(source) === 'official-vendor' ? 12 : 0;
|
|
147
|
+
if (wanted.some((pattern) => pattern.test(lower)))
|
|
148
|
+
score += 20;
|
|
149
|
+
if (hasConcreteFeatureSignal(lower))
|
|
150
|
+
score += 8;
|
|
151
|
+
if (/\b(hdmi|usb|ethernet|wi-?fi|bluetooth|hdr|dolby|resolution|refresh|120\s*hz|speaker|tuner|atsc|qam|webos|airplay|homekit|freesync|vrr|allm|earc|arc)\b/.test(lower)) {
|
|
152
|
+
score += 12;
|
|
153
|
+
}
|
|
154
|
+
if (/\b(specifications?|features?|connectivity|ports?|display|audio|network|smart tv|gaming)\b/.test(lower))
|
|
155
|
+
score += 6;
|
|
156
|
+
if (/^\s*\d+\s/.test(lower) || /\b(question|answer|faq|click|price|review|manual\.nz)\b/.test(lower))
|
|
157
|
+
score -= 20;
|
|
158
|
+
return score;
|
|
159
|
+
}
|
|
160
|
+
function classifyRepairFact(sentence) {
|
|
161
|
+
const lower = sentence.toLowerCase();
|
|
162
|
+
if (/\b(resolution|4k|8k|uhd|display|screen|nanocell|lcd|oled|qled|refresh|hz|hdr|dolby vision|hlg)\b/.test(lower)) {
|
|
163
|
+
return { kind: 'specification', title: 'Display and picture specifications', labels: ['display', 'picture'], aliases: ['display', 'picture'] };
|
|
164
|
+
}
|
|
165
|
+
if (/\b(hdmi|usb|ethernet|optical|rf|antenna|rs-?232|composite|component|earc|arc|ports?|input|output)\b/.test(lower)) {
|
|
166
|
+
return { kind: 'specification', title: 'Input and output ports', labels: ['ports', 'connectivity'], aliases: ['ports', 'inputs', 'outputs'] };
|
|
167
|
+
}
|
|
168
|
+
if (/\b(wi-?fi|bluetooth|wireless|airplay|homekit|miracast|chromecast|ethernet)\b/.test(lower)) {
|
|
169
|
+
return { kind: 'capability', title: 'Network and wireless capabilities', labels: ['network', 'wireless'], aliases: ['network', 'wireless'] };
|
|
170
|
+
}
|
|
171
|
+
if (/\b(speaker|audio|dolby atmos|dolby audio|sound|watts?|channels?)\b/.test(lower)) {
|
|
172
|
+
return { kind: 'specification', title: 'Audio capabilities', labels: ['audio'], aliases: ['audio', 'speakers'] };
|
|
173
|
+
}
|
|
174
|
+
if (/\b(game|gaming|vrr|allm|freesync|g-?sync|low latency)\b/.test(lower)) {
|
|
175
|
+
return { kind: 'feature', title: 'Gaming features', labels: ['gaming'], aliases: ['gaming'] };
|
|
176
|
+
}
|
|
177
|
+
if (/\b(webos|smart tv|apps?|voice assistant|alexa|google assistant|streaming)\b/.test(lower)) {
|
|
178
|
+
return { kind: 'feature', title: 'Smart TV features', labels: ['smart-tv'], aliases: ['smart tv', 'apps'] };
|
|
179
|
+
}
|
|
180
|
+
if (/\b(tuner|atsc|ntsc|qam|broadcast|clear qam)\b/.test(lower)) {
|
|
181
|
+
return { kind: 'specification', title: 'Tuner support', labels: ['tuner'], aliases: ['tuner', 'broadcast'] };
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
kind: 'feature',
|
|
185
|
+
title: clampText(sentence, 80),
|
|
186
|
+
labels: ['source-backed'],
|
|
187
|
+
aliases: [],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function repairIntentPatterns(query) {
|
|
191
|
+
const lower = query.toLowerCase();
|
|
192
|
+
const patterns = [];
|
|
193
|
+
if (/\b(port|ports|input|output|hdmi|usb|optical|rf|antenna|rs-?232|composite|component|i\/o)\b/.test(lower))
|
|
194
|
+
patterns.push(/\b(hdmi|usb|optical|rf|antenna|ethernet|rs-?232|composite|component|earc|arc|ports?|input|output)\b/);
|
|
195
|
+
if (/\b(bluetooth|wifi|wi-fi|wireless|network)\b/.test(lower))
|
|
196
|
+
patterns.push(/\b(bluetooth|wi-?fi|wireless|network|ethernet|airplay|homekit)\b/);
|
|
197
|
+
if (/\b(refresh|hz|hdr|dolby|vision|gaming|vrr|allm|freesync)\b/.test(lower))
|
|
198
|
+
patterns.push(/\b(refresh|hz|hdr|hdr10|dolby vision|hlg|game|vrr|allm|freesync|120\s*hz|100\s*hz)\b/);
|
|
199
|
+
if (/\b(display|screen|resolution|panel|nanocell|lcd|oled)\b/.test(lower))
|
|
200
|
+
patterns.push(/\b(display|screen|resolution|4k|uhd|nanocell|lcd|oled|panel)\b/);
|
|
201
|
+
if (patterns.length === 0)
|
|
202
|
+
patterns.push(/\b(hdmi|usb|hdr|dolby|resolution|refresh|wi-?fi|bluetooth|speaker|audio|webos|smart tv|tuner|gaming|ports?)\b/);
|
|
203
|
+
return patterns;
|
|
204
|
+
}
|
|
205
|
+
function linkedRepairSubjects(store, spaceId, gap) {
|
|
206
|
+
return uniqueById(readStringArray(gap.metadata.linkedObjectIds)
|
|
207
|
+
.map((id) => store.getNode(id))
|
|
208
|
+
.filter((node) => Boolean(node))
|
|
209
|
+
.filter((node) => getKnowledgeSpaceId(node) === spaceId && node.status !== 'stale'));
|
|
210
|
+
}
|
|
211
|
+
function factsForSource(sourceId, edges, nodesById) {
|
|
212
|
+
return uniqueById(edges
|
|
213
|
+
.filter((edge) => edge.fromKind === 'source' && edge.fromId === sourceId && edge.toKind === 'node')
|
|
214
|
+
.map((edge) => nodesById.get(edge.toId))
|
|
215
|
+
.filter((node) => Boolean(node))
|
|
216
|
+
.filter((node) => node.kind === 'fact' && node.status !== 'stale'));
|
|
217
|
+
}
|
|
218
|
+
function sourceAuthority(source) {
|
|
219
|
+
const discovery = readRecord(source.metadata.sourceDiscovery);
|
|
220
|
+
const trust = `${readString(discovery.trustReason) ?? ''} ${readString(discovery.sourceDomain) ?? ''} ${source.sourceUri ?? ''} ${source.canonicalUri ?? ''}`.toLowerCase();
|
|
221
|
+
if (/\bofficial-vendor-domain\b/.test(trust) || /(^|[/.])lg\.com\b|(^|[/.])sony\.com\b|(^|[/.])samsung\.com\b|(^|[/.])apple\.com\b/.test(trust)) {
|
|
222
|
+
return 'official-vendor';
|
|
223
|
+
}
|
|
224
|
+
if (/\bmanufacturer-domain\b/.test(trust))
|
|
225
|
+
return 'vendor';
|
|
226
|
+
return 'secondary';
|
|
227
|
+
}
|
|
228
|
+
function uniqueById(items) {
|
|
229
|
+
const seen = new Set();
|
|
230
|
+
const result = [];
|
|
231
|
+
for (const item of items) {
|
|
232
|
+
if (!item || seen.has(item.id))
|
|
233
|
+
continue;
|
|
234
|
+
seen.add(item.id);
|
|
235
|
+
result.push(item);
|
|
236
|
+
}
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
function readStringArray(value) {
|
|
240
|
+
if (!Array.isArray(value))
|
|
241
|
+
return [];
|
|
242
|
+
return uniqueStrings(value.map((entry) => typeof entry === 'string' ? entry : undefined));
|
|
243
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { KnowledgeNodeRecord, KnowledgeRefinementTaskRecord, KnowledgeRefinementTaskState, KnowledgeRefinementTaskTrigger } from '../types.js';
|
|
2
|
+
import type { KnowledgeStore } from '../store.js';
|
|
3
|
+
export interface RefinementTaskGapContext {
|
|
4
|
+
readonly gap: KnowledgeNodeRecord;
|
|
5
|
+
readonly sources: readonly {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
}[];
|
|
8
|
+
readonly linkedObjects: readonly KnowledgeNodeRecord[];
|
|
9
|
+
}
|
|
10
|
+
export declare function upsertRefinementTaskForGap(store: KnowledgeStore, spaceId: string, context: RefinementTaskGapContext, trigger: KnowledgeRefinementTaskTrigger, state: KnowledgeRefinementTaskState, message: string, data?: Record<string, unknown>): Promise<KnowledgeRefinementTaskRecord>;
|
|
11
|
+
export declare function updateRefinementTask(store: KnowledgeStore, task: KnowledgeRefinementTaskRecord, state: KnowledgeRefinementTaskState, message: string, data?: Record<string, unknown>): Promise<KnowledgeRefinementTaskRecord>;
|
|
12
|
+
//# sourceMappingURL=self-improvement-tasks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-improvement-tasks.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/self-improvement-tasks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,6BAA6B,EAC7B,4BAA4B,EAC5B,8BAA8B,EAC/B,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,GAAG,EAAE,mBAAmB,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACrD,QAAQ,CAAC,aAAa,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACxD;AAED,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,wBAAwB,EACjC,OAAO,EAAE,8BAA8B,EACvC,KAAK,EAAE,4BAA4B,EACnC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACjC,OAAO,CAAC,6BAA6B,CAAC,CAiCxC;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,6BAA6B,EACnC,KAAK,EAAE,4BAA4B,EACnC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACjC,OAAO,CAAC,6BAA6B,CAAC,CAwBxC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { readString, semanticHash, semanticMetadata } from './utils.js';
|
|
2
|
+
export async function upsertRefinementTaskForGap(store, spaceId, context, trigger, state, message, data = {}) {
|
|
3
|
+
const subject = context.linkedObjects[0];
|
|
4
|
+
const id = `kref-${semanticHash(spaceId, context.gap.id, subject?.id ?? 'unscoped')}`;
|
|
5
|
+
const existing = store.getRefinementTask(id);
|
|
6
|
+
const attemptCount = existing?.attemptCount ?? 0;
|
|
7
|
+
return store.upsertRefinementTask({
|
|
8
|
+
id,
|
|
9
|
+
spaceId,
|
|
10
|
+
...(subject ? {
|
|
11
|
+
subjectKind: 'node',
|
|
12
|
+
subjectId: subject.id,
|
|
13
|
+
subjectTitle: subjectTitle(subject),
|
|
14
|
+
subjectType: subject.kind,
|
|
15
|
+
} : {}),
|
|
16
|
+
gapId: context.gap.id,
|
|
17
|
+
state,
|
|
18
|
+
trigger,
|
|
19
|
+
priority: refinementPriority(context.gap),
|
|
20
|
+
budget: {
|
|
21
|
+
maxSearches: 5,
|
|
22
|
+
maxSources: 5,
|
|
23
|
+
maxLlmCalls: 1,
|
|
24
|
+
},
|
|
25
|
+
attemptCount,
|
|
26
|
+
appendTrace: [trace(state, message, data)],
|
|
27
|
+
metadata: semanticMetadata(spaceId, {
|
|
28
|
+
gapTitle: context.gap.title,
|
|
29
|
+
gapKind: readString(context.gap.metadata.gapKind) ?? 'unknown',
|
|
30
|
+
sourceIds: context.sources.map((source) => source.id),
|
|
31
|
+
linkedObjectIds: context.linkedObjects.map((node) => node.id),
|
|
32
|
+
policyVersion: 'knowledge-refinement-v1',
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export async function updateRefinementTask(store, task, state, message, data = {}) {
|
|
37
|
+
const nextRepairAttemptAt = readNumber(data.nextRepairAttemptAt) ?? task.nextRepairAttemptAt;
|
|
38
|
+
return store.upsertRefinementTask({
|
|
39
|
+
id: task.id,
|
|
40
|
+
spaceId: task.spaceId,
|
|
41
|
+
subjectKind: task.subjectKind,
|
|
42
|
+
subjectId: task.subjectId,
|
|
43
|
+
subjectTitle: task.subjectTitle,
|
|
44
|
+
subjectType: task.subjectType,
|
|
45
|
+
gapId: task.gapId,
|
|
46
|
+
issueId: task.issueId,
|
|
47
|
+
state,
|
|
48
|
+
priority: task.priority,
|
|
49
|
+
trigger: task.trigger,
|
|
50
|
+
budget: task.budget,
|
|
51
|
+
attemptCount: state === 'searching' ? task.attemptCount + 1 : task.attemptCount,
|
|
52
|
+
...(state === 'blocked' || state === 'failed' ? { blockedReason: message } : {}),
|
|
53
|
+
...(typeof nextRepairAttemptAt === 'number' ? { nextRepairAttemptAt } : {}),
|
|
54
|
+
appendTrace: [trace(state, message, data)],
|
|
55
|
+
metadata: {
|
|
56
|
+
...task.metadata,
|
|
57
|
+
...(typeof nextRepairAttemptAt === 'number' ? { nextRepairAttemptAt } : {}),
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function trace(state, message, data = {}) {
|
|
62
|
+
return {
|
|
63
|
+
at: Date.now(),
|
|
64
|
+
state,
|
|
65
|
+
message,
|
|
66
|
+
...(Object.keys(data).length > 0 ? { data } : {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function refinementPriority(gap) {
|
|
70
|
+
const severity = readString(gap.metadata.severity);
|
|
71
|
+
const kind = readString(gap.metadata.gapKind);
|
|
72
|
+
if (severity === 'error')
|
|
73
|
+
return 'urgent';
|
|
74
|
+
if (severity === 'warning' || kind === 'intrinsic_features')
|
|
75
|
+
return 'high';
|
|
76
|
+
return 'normal';
|
|
77
|
+
}
|
|
78
|
+
function subjectTitle(subject) {
|
|
79
|
+
return [
|
|
80
|
+
readString(subject.metadata.manufacturer),
|
|
81
|
+
readString(subject.metadata.model),
|
|
82
|
+
subject.title,
|
|
83
|
+
].filter(Boolean).join(' ');
|
|
84
|
+
}
|
|
85
|
+
function readNumber(value) {
|
|
86
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
87
|
+
}
|