@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.
Files changed (47) hide show
  1. package/dist/_internal/contracts/artifacts/operator-contract.json +19 -1
  2. package/dist/_internal/contracts/generated/foundation-metadata.d.ts +1 -1
  3. package/dist/_internal/contracts/generated/foundation-metadata.js +1 -1
  4. package/dist/_internal/contracts/generated/operator-contract.d.ts.map +1 -1
  5. package/dist/_internal/contracts/generated/operator-contract.js +19 -1
  6. package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.d.ts.map +1 -1
  7. package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.js +1 -0
  8. package/dist/_internal/platform/knowledge/home-graph/generated-pages.d.ts.map +1 -1
  9. package/dist/_internal/platform/knowledge/home-graph/generated-pages.js +28 -3
  10. package/dist/_internal/platform/knowledge/home-graph/service.d.ts.map +1 -1
  11. package/dist/_internal/platform/knowledge/home-graph/service.js +48 -46
  12. package/dist/_internal/platform/knowledge/home-graph/types.d.ts +1 -0
  13. package/dist/_internal/platform/knowledge/home-graph/types.d.ts.map +1 -1
  14. package/dist/_internal/platform/knowledge/semantic/answer-fallback.d.ts +11 -0
  15. package/dist/_internal/platform/knowledge/semantic/answer-fallback.d.ts.map +1 -0
  16. package/dist/_internal/platform/knowledge/semantic/answer-fallback.js +45 -0
  17. package/dist/_internal/platform/knowledge/semantic/answer.d.ts.map +1 -1
  18. package/dist/_internal/platform/knowledge/semantic/answer.js +7 -18
  19. package/dist/_internal/platform/knowledge/semantic/gap-repair.d.ts.map +1 -1
  20. package/dist/_internal/platform/knowledge/semantic/gap-repair.js +171 -14
  21. package/dist/_internal/platform/knowledge/semantic/self-improvement-promotion.d.ts +11 -0
  22. package/dist/_internal/platform/knowledge/semantic/self-improvement-promotion.d.ts.map +1 -0
  23. package/dist/_internal/platform/knowledge/semantic/self-improvement-promotion.js +243 -0
  24. package/dist/_internal/platform/knowledge/semantic/self-improvement-tasks.d.ts +12 -0
  25. package/dist/_internal/platform/knowledge/semantic/self-improvement-tasks.d.ts.map +1 -0
  26. package/dist/_internal/platform/knowledge/semantic/self-improvement-tasks.js +87 -0
  27. package/dist/_internal/platform/knowledge/semantic/self-improvement.d.ts +4 -0
  28. package/dist/_internal/platform/knowledge/semantic/self-improvement.d.ts.map +1 -1
  29. package/dist/_internal/platform/knowledge/semantic/self-improvement.js +64 -90
  30. package/dist/_internal/platform/knowledge/semantic/service.d.ts.map +1 -1
  31. package/dist/_internal/platform/knowledge/semantic/service.js +12 -2
  32. package/dist/_internal/platform/knowledge/semantic/types.d.ts +2 -0
  33. package/dist/_internal/platform/knowledge/semantic/types.d.ts.map +1 -1
  34. package/dist/_internal/platform/knowledge/store-refinement.d.ts.map +1 -1
  35. package/dist/_internal/platform/knowledge/store-refinement.js +8 -0
  36. package/dist/_internal/platform/knowledge/store-schema.d.ts.map +1 -1
  37. package/dist/_internal/platform/knowledge/store-schema.js +6 -1
  38. package/dist/_internal/platform/knowledge/store.d.ts +1 -0
  39. package/dist/_internal/platform/knowledge/store.d.ts.map +1 -1
  40. package/dist/_internal/platform/knowledge/store.js +1 -0
  41. package/dist/_internal/platform/knowledge/types.d.ts +2 -0
  42. package/dist/_internal/platform/knowledge/types.d.ts.map +1 -1
  43. package/dist/_internal/platform/state/sqlite-store.d.ts +3 -0
  44. package/dist/_internal/platform/state/sqlite-store.d.ts.map +1 -1
  45. package/dist/_internal/platform/state/sqlite-store.js +19 -0
  46. package/dist/_internal/platform/version.js +1 -1
  47. 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, options.searchTimeoutMs ?? 8_000), 'Semantic gap repair search timed out.');
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.length >= Math.max(2, options.minDistinctDomains ?? 2))
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.length < Math.max(2, options.minDistinctDomains ?? 2)) {
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: buildSourceAssessments(allResults, candidates, existing, options, request),
60
- reason: lastError ?? 'Fewer than two distinct external sources were found for source-backed gap repair.',
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
- for (const result of candidates.slice(0, Math.max(2, Math.min(sourceLimit, options.maxIngest ?? sourceLimit)))) {
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, options.ingestTimeoutMs ?? 10_000), 'Semantic gap repair source ingest timed out.');
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: buildSourceAssessments(allResults, candidates, existing, options, request),
111
- ...(ingestedSourceIds.length < 2 ? { reason: 'Gap repair searched but fewer than two sources were ingested.' } : {}),
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((left, right) => right.confidence - left.confidence || left.rank - right.rank)
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 += 10;
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
+ }