@pellux/goodvibes-sdk 0.27.8 → 0.27.10

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 +287 -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 +287 -1
  6. package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.d.ts +1 -1
  7. package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.d.ts.map +1 -1
  8. package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.js +23 -0
  9. package/dist/_internal/platform/knowledge/home-graph/ask.js +4 -4
  10. package/dist/_internal/platform/knowledge/home-graph/service.d.ts +1 -0
  11. package/dist/_internal/platform/knowledge/home-graph/service.d.ts.map +1 -1
  12. package/dist/_internal/platform/knowledge/home-graph/service.js +18 -1
  13. package/dist/_internal/platform/knowledge/home-graph/types.d.ts +3 -1
  14. package/dist/_internal/platform/knowledge/home-graph/types.d.ts.map +1 -1
  15. package/dist/_internal/platform/knowledge/home-graph/types.js +1 -0
  16. package/dist/_internal/platform/knowledge/index.d.ts +2 -2
  17. package/dist/_internal/platform/knowledge/index.d.ts.map +1 -1
  18. package/dist/_internal/platform/knowledge/index.js +1 -1
  19. package/dist/_internal/platform/knowledge/scheduling.d.ts +1 -0
  20. package/dist/_internal/platform/knowledge/scheduling.d.ts.map +1 -1
  21. package/dist/_internal/platform/knowledge/scheduling.js +17 -18
  22. package/dist/_internal/platform/knowledge/semantic/answer.d.ts.map +1 -1
  23. package/dist/_internal/platform/knowledge/semantic/answer.js +38 -5
  24. package/dist/_internal/platform/knowledge/semantic/fact-quality.d.ts.map +1 -1
  25. package/dist/_internal/platform/knowledge/semantic/fact-quality.js +16 -3
  26. package/dist/_internal/platform/knowledge/semantic/gap-repair.d.ts +32 -0
  27. package/dist/_internal/platform/knowledge/semantic/gap-repair.d.ts.map +1 -0
  28. package/dist/_internal/platform/knowledge/semantic/gap-repair.js +148 -0
  29. package/dist/_internal/platform/knowledge/semantic/index.d.ts +4 -1
  30. package/dist/_internal/platform/knowledge/semantic/index.d.ts.map +1 -1
  31. package/dist/_internal/platform/knowledge/semantic/index.js +2 -0
  32. package/dist/_internal/platform/knowledge/semantic/self-improvement.d.ts +10 -0
  33. package/dist/_internal/platform/knowledge/semantic/self-improvement.d.ts.map +1 -0
  34. package/dist/_internal/platform/knowledge/semantic/self-improvement.js +486 -0
  35. package/dist/_internal/platform/knowledge/semantic/service.d.ts +9 -2
  36. package/dist/_internal/platform/knowledge/semantic/service.d.ts.map +1 -1
  37. package/dist/_internal/platform/knowledge/semantic/service.js +124 -2
  38. package/dist/_internal/platform/knowledge/semantic/types.d.ts +38 -0
  39. package/dist/_internal/platform/knowledge/semantic/types.d.ts.map +1 -1
  40. package/dist/_internal/platform/knowledge/service.d.ts.map +1 -1
  41. package/dist/_internal/platform/knowledge/service.js +17 -1
  42. package/dist/_internal/platform/knowledge/types.d.ts +1 -1
  43. package/dist/_internal/platform/knowledge/types.d.ts.map +1 -1
  44. package/dist/_internal/platform/runtime/services.d.ts.map +1 -1
  45. package/dist/_internal/platform/runtime/services.js +5 -1
  46. package/dist/_internal/platform/version.js +1 -1
  47. package/package.json +1 -1
@@ -0,0 +1,486 @@
1
+ import { isGeneratedKnowledgeSource } from '../generated-projections.js';
2
+ import { getKnowledgeSpaceId, normalizeKnowledgeSpaceId } from '../spaces.js';
3
+ import { readRecord, readString, semanticHash, semanticMetadata, semanticSlug, sourceKnowledgeSpace, uniqueStrings, } from './utils.js';
4
+ const RETRY_DELAY_MS = 6 * 60 * 60 * 1000;
5
+ export async function runKnowledgeSemanticSelfImprovement(context, input = {}) {
6
+ await context.store.init();
7
+ const sourceIdFilter = input.sourceIds?.length ? new Set(input.sourceIds) : null;
8
+ const gapIdFilter = input.gapIds?.length ? new Set(input.gapIds) : null;
9
+ const spaceId = resolveSelfImproveSpace(context.store, input);
10
+ const createdGaps = await discoverIntrinsicGaps(context.store, spaceId, sourceIdFilter);
11
+ const gaps = collectCandidateGaps(context.store, spaceId, sourceIdFilter, gapIdFilter)
12
+ .slice(0, Math.max(1, input.limit ?? 24));
13
+ let repairableGaps = 0;
14
+ let suppressedGaps = 0;
15
+ let skippedGaps = 0;
16
+ let searched = 0;
17
+ let ingestedSources = 0;
18
+ let linkedRepairs = 0;
19
+ const errors = [];
20
+ for (const gap of gaps) {
21
+ const gapContext = buildGapContext(context.store, spaceId, gap);
22
+ const classification = classifyGap(gapContext, input.force === true);
23
+ if (classification.action === 'suppress') {
24
+ await suppressGap(context.store, gap, classification.reason, spaceId);
25
+ suppressedGaps += 1;
26
+ continue;
27
+ }
28
+ if (classification.action === 'skip') {
29
+ skippedGaps += 1;
30
+ if (classification.markAttempt) {
31
+ await markGapRepairAttempt(context.store, gap, spaceId, {
32
+ status: classification.status ?? 'skipped',
33
+ reason: classification.reason,
34
+ });
35
+ }
36
+ continue;
37
+ }
38
+ repairableGaps += 1;
39
+ if (!context.gapRepairer) {
40
+ skippedGaps += 1;
41
+ await markGapRepairAttempt(context.store, gap, spaceId, {
42
+ status: 'no_repairer',
43
+ reason: 'No semantic gap repairer is configured.',
44
+ });
45
+ continue;
46
+ }
47
+ const repairKey = `${spaceId}:${gap.id}`;
48
+ if (context.activeGapRepairs.has(repairKey)) {
49
+ skippedGaps += 1;
50
+ continue;
51
+ }
52
+ context.activeGapRepairs.add(repairKey);
53
+ try {
54
+ const result = await context.gapRepairer({
55
+ spaceId,
56
+ query: gap.title,
57
+ gaps: [gap],
58
+ sources: gapContext.sources,
59
+ linkedObjects: gapContext.linkedObjects,
60
+ facts: gapContext.facts,
61
+ });
62
+ if (result?.searched)
63
+ searched += 1;
64
+ ingestedSources += result?.ingestedSourceIds.length ?? 0;
65
+ linkedRepairs += await linkRepairSources(context.store, spaceId, gap, result?.ingestedSourceIds ?? [], result?.query ?? gap.title);
66
+ await markGapRepairAttempt(context.store, gap, spaceId, {
67
+ status: result?.ingestedSourceIds.length ? 'repaired' : 'searched_no_sources',
68
+ reason: result?.reason,
69
+ query: result?.query,
70
+ });
71
+ }
72
+ catch (error) {
73
+ errors.push({ gapId: gap.id, error: error instanceof Error ? error.message : String(error) });
74
+ await markGapRepairAttempt(context.store, gap, spaceId, {
75
+ status: 'failed',
76
+ reason: error instanceof Error ? error.message : String(error),
77
+ });
78
+ }
79
+ finally {
80
+ context.activeGapRepairs.delete(repairKey);
81
+ }
82
+ }
83
+ return {
84
+ scannedGaps: gaps.length,
85
+ createdGaps,
86
+ repairableGaps,
87
+ suppressedGaps,
88
+ skippedGaps,
89
+ searched,
90
+ ingestedSources,
91
+ linkedRepairs,
92
+ errors,
93
+ };
94
+ }
95
+ function resolveSelfImproveSpace(store, input) {
96
+ if (input.knowledgeSpaceId)
97
+ return normalizeKnowledgeSpaceId(input.knowledgeSpaceId);
98
+ const firstSource = input.sourceIds?.map((id) => store.getSource(id)).find((source) => Boolean(source));
99
+ if (firstSource)
100
+ return sourceKnowledgeSpace(firstSource);
101
+ const firstGap = input.gapIds?.map((id) => store.getNode(id)).find((node) => Boolean(node));
102
+ return normalizeKnowledgeSpaceId(firstGap ? getKnowledgeSpaceId(firstGap) : undefined);
103
+ }
104
+ async function discoverIntrinsicGaps(store, spaceId, sourceIdFilter) {
105
+ const edges = store.listEdges();
106
+ const sourcesById = new Map(store.listSources(10_000)
107
+ .filter((source) => source.status === 'indexed')
108
+ .filter((source) => !isGeneratedKnowledgeSource(source))
109
+ .filter((source) => getKnowledgeSpaceId(source) === spaceId)
110
+ .filter((source) => !sourceIdFilter || sourceIdFilter.has(source.id))
111
+ .map((source) => [source.id, source]));
112
+ const nodesById = new Map(store.listNodes(10_000).filter((node) => getKnowledgeSpaceId(node) === spaceId).map((node) => [node.id, node]));
113
+ const createdIds = new Set();
114
+ let created = 0;
115
+ for (const source of sourcesById.values()) {
116
+ const linkedObjects = linkedObjectsForSource(source.id, edges, nodesById);
117
+ const facts = factsForSource(source.id, edges, nodesById);
118
+ for (const subject of linkedObjects.filter(isConcreteRepairSubject)) {
119
+ if (!shouldCreateIntrinsicFeatureGap(subject, facts, source))
120
+ continue;
121
+ if (await upsertIntrinsicFeatureGap(store, spaceId, subject, [source], createdIds))
122
+ created += 1;
123
+ }
124
+ }
125
+ if (!sourceIdFilter) {
126
+ for (const subject of nodesById.values()) {
127
+ if (!isConcreteRepairSubject(subject))
128
+ continue;
129
+ const sourceList = sourcesForObject(subject.id, edges, sourcesById);
130
+ const facts = [
131
+ ...sourceList.flatMap((source) => factsForSource(source.id, edges, nodesById)),
132
+ ...factsForObject(subject.id, edges, nodesById),
133
+ ];
134
+ const coverage = factCoverage(facts);
135
+ if (coverage.coreFactCount >= 4 && coverage.coveredAreas.size >= 3)
136
+ continue;
137
+ if (!hasSpecificIdentity(subject, sourceList[0]))
138
+ continue;
139
+ if (await upsertIntrinsicFeatureGap(store, spaceId, subject, sourceList, createdIds))
140
+ created += 1;
141
+ }
142
+ }
143
+ return created;
144
+ }
145
+ async function upsertIntrinsicFeatureGap(store, spaceId, subject, sources, createdIds) {
146
+ const id = `sem-intrinsic-gap-${semanticHash(spaceId, subject.id, 'features-specifications')}`;
147
+ if (createdIds.has(id))
148
+ return false;
149
+ createdIds.add(id);
150
+ const existing = store.getNode(id);
151
+ if (existing?.status === 'active' && readString(existing.metadata.repairStatus) === 'repaired')
152
+ return false;
153
+ const title = `What are the complete features and specifications for ${subjectTitle(subject)}?`;
154
+ const primarySource = sources[0];
155
+ const gap = await store.upsertNode({
156
+ id,
157
+ kind: 'knowledge_gap',
158
+ slug: semanticSlug(`${spaceId}-intrinsic-gap-${subject.title}`),
159
+ title,
160
+ summary: `The current source-backed facts for ${subjectTitle(subject)} do not yet cover the full feature/specification profile.`,
161
+ aliases: [subject.title, ...subject.aliases].slice(0, 8),
162
+ confidence: 75,
163
+ ...(primarySource ? { sourceId: primarySource.id } : {}),
164
+ metadata: semanticMetadata(spaceId, {
165
+ semanticKind: 'gap',
166
+ gapKind: 'intrinsic_features',
167
+ subject: subject.title,
168
+ sourceIds: sources.map((source) => source.id),
169
+ linkedObjectIds: [subject.id],
170
+ repairStatus: readString(existing?.metadata.repairStatus) ?? 'open',
171
+ createdBy: 'semantic-self-improvement',
172
+ }),
173
+ });
174
+ for (const source of sources) {
175
+ await store.upsertEdge({
176
+ fromKind: 'source',
177
+ fromId: source.id,
178
+ toKind: 'node',
179
+ toId: gap.id,
180
+ relation: 'has_gap',
181
+ metadata: semanticMetadata(spaceId, { intrinsic: true }),
182
+ });
183
+ }
184
+ await store.upsertEdge({
185
+ fromKind: 'node',
186
+ fromId: subject.id,
187
+ toKind: 'node',
188
+ toId: gap.id,
189
+ relation: 'has_gap',
190
+ metadata: semanticMetadata(spaceId, { intrinsic: true }),
191
+ });
192
+ await store.upsertIssue({
193
+ id: `sem-intrinsic-gap-issue-${semanticHash(spaceId, subject.id, 'features-specifications')}`,
194
+ severity: 'info',
195
+ code: 'knowledge.intrinsic_gap',
196
+ message: title,
197
+ status: 'open',
198
+ ...(primarySource ? { sourceId: primarySource.id } : {}),
199
+ nodeId: gap.id,
200
+ metadata: semanticMetadata(spaceId, {
201
+ namespace: `knowledge:${spaceId}:semantic`,
202
+ subjectId: subject.id,
203
+ gapKind: 'intrinsic_features',
204
+ }),
205
+ });
206
+ return !existing;
207
+ }
208
+ function collectCandidateGaps(store, spaceId, sourceIdFilter, gapIdFilter) {
209
+ const edges = store.listEdges();
210
+ return store.listNodes(10_000)
211
+ .filter((node) => node.kind === 'knowledge_gap' && node.status === 'active')
212
+ .filter((node) => getKnowledgeSpaceId(node) === spaceId)
213
+ .filter((node) => !gapIdFilter || gapIdFilter.has(node.id))
214
+ .filter((node) => !sourceIdFilter || gapMatchesSourceFilter(node, sourceIdFilter, edges))
215
+ .sort((left, right) => right.confidence - left.confidence || left.id.localeCompare(right.id));
216
+ }
217
+ function gapMatchesSourceFilter(gap, sourceIdFilter, edges) {
218
+ if (gap.sourceId && sourceIdFilter.has(gap.sourceId))
219
+ return true;
220
+ if (readStringArray(gap.metadata.sourceIds).some((sourceId) => sourceIdFilter.has(sourceId)))
221
+ return true;
222
+ return edges.some((edge) => (edge.fromKind === 'source'
223
+ && sourceIdFilter.has(edge.fromId)
224
+ && edge.toKind === 'node'
225
+ && edge.toId === gap.id));
226
+ }
227
+ function buildGapContext(store, spaceId, gap) {
228
+ const edges = store.listEdges();
229
+ const sourcesById = new Map(store.listSources(10_000).filter((source) => getKnowledgeSpaceId(source) === spaceId).map((source) => [source.id, source]));
230
+ const nodesById = new Map(store.listNodes(10_000).filter((node) => getKnowledgeSpaceId(node) === spaceId).map((node) => [node.id, node]));
231
+ const sourceIds = uniqueStrings([
232
+ gap.sourceId,
233
+ ...readStringArray(gap.metadata.sourceIds),
234
+ ...edges
235
+ .filter((edge) => edge.toKind === 'node' && edge.toId === gap.id && edge.fromKind === 'source')
236
+ .map((edge) => edge.fromId),
237
+ ]);
238
+ const directSources = sourceIds.map((id) => sourcesById.get(id)).filter((source) => Boolean(source));
239
+ const linkedObjects = uniqueById([
240
+ ...readStringArray(gap.metadata.linkedObjectIds).map((id) => nodesById.get(id)).filter((node) => Boolean(node)),
241
+ ...sourceIds.flatMap((sourceId) => linkedObjectsForSource(sourceId, edges, nodesById)),
242
+ ...edges
243
+ .filter((edge) => edge.fromKind === 'node' && edge.toKind === 'node' && edge.toId === gap.id)
244
+ .map((edge) => nodesById.get(edge.fromId))
245
+ .filter((node) => Boolean(node)),
246
+ ]);
247
+ const sources = uniqueById([
248
+ ...directSources,
249
+ ...linkedObjects.flatMap((object) => sourcesForObject(object.id, edges, sourcesById)),
250
+ ]);
251
+ const facts = uniqueById([
252
+ ...sources.flatMap((source) => factsForSource(source.id, edges, nodesById)),
253
+ ...linkedObjects.flatMap((object) => factsForObject(object.id, edges, nodesById)),
254
+ ]);
255
+ return { gap, sources, linkedObjects, facts };
256
+ }
257
+ function classifyGap(context, force) {
258
+ const status = readString(context.gap.metadata.repairStatus);
259
+ const nextAttemptAt = readNumber(context.gap.metadata.nextRepairAttemptAt);
260
+ if (!force && status === 'repaired')
261
+ return { action: 'skip', reason: 'Gap already has linked repair sources.' };
262
+ if (!force && nextAttemptAt && nextAttemptAt > Date.now())
263
+ return { action: 'skip', reason: 'Gap repair retry window has not elapsed.' };
264
+ if (hasRepairEdge(context))
265
+ return { action: 'skip', reason: 'Gap already has a repair source.' };
266
+ if (isNotApplicableGap(context))
267
+ return { action: 'suppress', reason: 'The gap is not applicable to the linked subject.' };
268
+ if (!hasConcreteSubject(context)) {
269
+ return { action: 'skip', reason: 'Gap has no concrete source or subject for automatic repair.', status: 'needs_context', markAttempt: true };
270
+ }
271
+ if (context.sources.length === 0 && context.linkedObjects.length === 0) {
272
+ return { action: 'skip', reason: 'Gap has no source context for automatic repair.', status: 'needs_context', markAttempt: true };
273
+ }
274
+ return { action: 'repair' };
275
+ }
276
+ function hasRepairEdge(context) {
277
+ return context.sources.some((source) => source.metadata.sourceDiscovery && readRecord(source.metadata.sourceDiscovery).purpose === 'semantic-gap-repair')
278
+ || context.facts.some((fact) => readString(fact.metadata.repairStatus) === 'repaired');
279
+ }
280
+ function isNotApplicableGap(context) {
281
+ const text = `${context.gap.title} ${context.gap.summary ?? ''}`.toLowerCase();
282
+ if (text.includes('battery')) {
283
+ return context.linkedObjects.some((node) => node.metadata.batteryPowered === false || readString(node.metadata.batteryType) === 'none');
284
+ }
285
+ return false;
286
+ }
287
+ function hasConcreteSubject(context) {
288
+ return context.linkedObjects.some((node) => {
289
+ if (isConcreteRepairSubject(node))
290
+ return true;
291
+ return Boolean(readString(node.metadata.manufacturer) && readString(node.metadata.model));
292
+ }) || context.sources.some((source) => Boolean(source.title || source.sourceUri || source.canonicalUri));
293
+ }
294
+ async function suppressGap(store, gap, reason, spaceId) {
295
+ await store.upsertNode({
296
+ id: gap.id,
297
+ kind: gap.kind,
298
+ slug: gap.slug,
299
+ title: gap.title,
300
+ summary: gap.summary,
301
+ aliases: gap.aliases,
302
+ status: 'stale',
303
+ confidence: gap.confidence,
304
+ sourceId: gap.sourceId,
305
+ metadata: {
306
+ ...gap.metadata,
307
+ repairStatus: 'not_applicable',
308
+ repairReason: reason,
309
+ repairedAt: Date.now(),
310
+ },
311
+ });
312
+ for (const issue of store.listIssues(10_000).filter((entry) => entry.nodeId === gap.id && entry.status === 'open')) {
313
+ await resolveIssue(store, issue, spaceId, reason ?? 'Gap was classified as not applicable.');
314
+ }
315
+ }
316
+ async function markGapRepairAttempt(store, gap, spaceId, details) {
317
+ await store.upsertNode({
318
+ id: gap.id,
319
+ kind: gap.kind,
320
+ slug: gap.slug,
321
+ title: gap.title,
322
+ summary: gap.summary,
323
+ aliases: gap.aliases,
324
+ status: gap.status,
325
+ confidence: gap.confidence,
326
+ sourceId: gap.sourceId,
327
+ metadata: {
328
+ ...gap.metadata,
329
+ repairStatus: details.status,
330
+ ...(details.reason ? { repairReason: details.reason } : {}),
331
+ ...(details.query ? { repairQuery: details.query } : {}),
332
+ lastRepairAttemptAt: Date.now(),
333
+ nextRepairAttemptAt: details.status === 'searched_no_sources' || details.status === 'failed'
334
+ ? Date.now() + RETRY_DELAY_MS
335
+ : undefined,
336
+ knowledgeSpaceId: spaceId,
337
+ },
338
+ });
339
+ }
340
+ async function linkRepairSources(store, spaceId, gap, sourceIds, query) {
341
+ let linked = 0;
342
+ for (const sourceId of sourceIds) {
343
+ if (!store.getSource(sourceId))
344
+ continue;
345
+ await store.upsertEdge({
346
+ fromKind: 'source',
347
+ fromId: sourceId,
348
+ toKind: 'node',
349
+ toId: gap.id,
350
+ relation: 'repairs_gap',
351
+ weight: 0.8,
352
+ metadata: semanticMetadata(spaceId, {
353
+ query,
354
+ repairedAt: Date.now(),
355
+ }),
356
+ });
357
+ linked += 1;
358
+ }
359
+ return linked;
360
+ }
361
+ async function resolveIssue(store, issue, spaceId, reason) {
362
+ await store.upsertIssue({
363
+ id: issue.id,
364
+ severity: issue.severity,
365
+ code: issue.code,
366
+ message: issue.message,
367
+ status: 'resolved',
368
+ sourceId: issue.sourceId,
369
+ nodeId: issue.nodeId,
370
+ metadata: semanticMetadata(spaceId, {
371
+ ...issue.metadata,
372
+ resolution: {
373
+ reason,
374
+ resolvedBy: 'semantic-self-improvement',
375
+ resolvedAt: Date.now(),
376
+ },
377
+ }),
378
+ });
379
+ }
380
+ function linkedObjectsForSource(sourceId, edges, nodesById) {
381
+ return uniqueById(edges
382
+ .filter((edge) => edge.fromKind === 'source' && edge.fromId === sourceId && edge.toKind === 'node')
383
+ .map((edge) => nodesById.get(edge.toId))
384
+ .filter((node) => Boolean(node))
385
+ .filter((node) => node.status !== 'stale')
386
+ .filter((node) => node.metadata.semanticKind !== 'fact' && node.metadata.semanticKind !== 'gap' && node.kind !== 'wiki_page'));
387
+ }
388
+ function factsForSource(sourceId, edges, nodesById) {
389
+ return uniqueById(edges
390
+ .filter((edge) => edge.fromKind === 'source' && edge.fromId === sourceId && edge.toKind === 'node')
391
+ .map((edge) => nodesById.get(edge.toId))
392
+ .filter((node) => Boolean(node))
393
+ .filter((node) => node.kind === 'fact' && node.status !== 'stale'));
394
+ }
395
+ function sourcesForObject(objectId, edges, sourcesById) {
396
+ return uniqueById(edges
397
+ .filter((edge) => edge.fromKind === 'source' && edge.toKind === 'node' && edge.toId === objectId)
398
+ .map((edge) => sourcesById.get(edge.fromId))
399
+ .filter((source) => Boolean(source))
400
+ .filter((source) => source.status === 'indexed' && !isGeneratedKnowledgeSource(source)));
401
+ }
402
+ function factsForObject(objectId, edges, nodesById) {
403
+ const directlyDescribing = edges
404
+ .filter((edge) => edge.fromKind === 'node' && edge.toKind === 'node' && edge.toId === objectId && edge.relation === 'describes')
405
+ .map((edge) => nodesById.get(edge.fromId));
406
+ return uniqueById(directlyDescribing
407
+ .filter((node) => Boolean(node))
408
+ .filter((node) => node.kind === 'fact' && node.status !== 'stale'));
409
+ }
410
+ function shouldCreateIntrinsicFeatureGap(subject, facts, source) {
411
+ if (!isConcreteRepairSubject(subject))
412
+ return false;
413
+ if (!hasSpecificIdentity(subject, source))
414
+ return false;
415
+ const coverage = factCoverage(facts);
416
+ return coverage.coreFactCount < 4 || coverage.coveredAreas.size < 3;
417
+ }
418
+ function isConcreteRepairSubject(node) {
419
+ if (node.status === 'stale')
420
+ return false;
421
+ if (typeof node.metadata.semanticKind === 'string')
422
+ return false;
423
+ if (['ha_device', 'ha_integration', 'service', 'provider', 'capability'].includes(node.kind))
424
+ return true;
425
+ if (node.kind !== 'knowledge_entity')
426
+ return false;
427
+ const entityKind = readString(node.metadata.entityKind)?.toLowerCase() ?? '';
428
+ return /\b(device|product|service|appliance|controller|platform|provider|tool)\b/.test(entityKind);
429
+ }
430
+ function hasSpecificIdentity(subject, source) {
431
+ if (readString(subject.metadata.model))
432
+ return true;
433
+ if (readString(subject.metadata.manufacturer) && readString(subject.metadata.model))
434
+ return true;
435
+ const text = `${subject.title} ${subject.aliases.join(' ')} ${source?.title ?? ''}`;
436
+ return /\b[A-Z]{2,}[-_ ]?[0-9][A-Z0-9._-]{2,}\b/.test(text);
437
+ }
438
+ function factCoverage(facts) {
439
+ const coveredAreas = new Set();
440
+ let coreFactCount = 0;
441
+ for (const fact of facts) {
442
+ const kind = readString(fact.metadata.factKind);
443
+ if (!['feature', 'capability', 'specification', 'compatibility', 'configuration'].includes(kind ?? ''))
444
+ continue;
445
+ coreFactCount += 1;
446
+ const text = `${fact.title} ${fact.summary ?? ''} ${fact.aliases.join(' ')} ${JSON.stringify(fact.metadata)}`.toLowerCase();
447
+ for (const [area, pattern] of [
448
+ ['display', /\b(display|screen|resolution|hdr|dolby vision|refresh|panel)\b/],
449
+ ['ports', /\b(hdmi|usb|ethernet|port|input|output|earc|arc)\b/],
450
+ ['audio', /\b(audio|speaker|dolby|pcm|sound)\b/],
451
+ ['network', /\b(wi-?fi|bluetooth|ethernet|network|wireless)\b/],
452
+ ['smart', /\b(app|webos|smart|assistant|voice|stream)\b/],
453
+ ['control', /\b(remote|rs-?232|control|automation|api)\b/],
454
+ ]) {
455
+ if (pattern.test(text))
456
+ coveredAreas.add(area);
457
+ }
458
+ }
459
+ return { coreFactCount, coveredAreas };
460
+ }
461
+ function subjectTitle(subject) {
462
+ return uniqueStrings([
463
+ readString(subject.metadata.manufacturer),
464
+ readString(subject.metadata.model),
465
+ subject.title,
466
+ ]).join(' ');
467
+ }
468
+ function uniqueById(items) {
469
+ const seen = new Set();
470
+ const result = [];
471
+ for (const item of items) {
472
+ if (!item || seen.has(item.id))
473
+ continue;
474
+ seen.add(item.id);
475
+ result.push(item);
476
+ }
477
+ return result;
478
+ }
479
+ function readStringArray(value) {
480
+ if (!Array.isArray(value))
481
+ return [];
482
+ return uniqueStrings(value.map((entry) => typeof entry === 'string' ? entry : undefined));
483
+ }
484
+ function readNumber(value) {
485
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
486
+ }
@@ -1,14 +1,17 @@
1
1
  import type { KnowledgeStore } from '../store.js';
2
2
  import type { KnowledgeSourceRecord } from '../types.js';
3
- import type { KnowledgeSemanticAnswerInput, KnowledgeSemanticAnswerResult, KnowledgeSemanticEnrichmentResult, KnowledgeSemanticLlm } from './types.js';
3
+ import type { KnowledgeSemanticAnswerInput, KnowledgeSemanticAnswerResult, KnowledgeSemanticEnrichmentResult, KnowledgeSemanticGapRepairer, KnowledgeSemanticLlm, KnowledgeSemanticSelfImproveInput, KnowledgeSemanticSelfImproveResult } from './types.js';
4
4
  export interface KnowledgeSemanticServiceOptions {
5
5
  readonly llm?: KnowledgeSemanticLlm | null;
6
6
  readonly maxLlmSourcesPerReindex?: number;
7
+ readonly gapRepairer?: KnowledgeSemanticGapRepairer | null;
7
8
  }
8
9
  export declare class KnowledgeSemanticService {
9
10
  private readonly store;
10
- private readonly options;
11
+ private options;
12
+ private readonly activeGapRepairs;
11
13
  constructor(store: KnowledgeStore, options?: KnowledgeSemanticServiceOptions);
14
+ setGapRepairer(gapRepairer: KnowledgeSemanticGapRepairer | null | undefined): void;
12
15
  enrichSource(sourceId: string, input?: {
13
16
  readonly force?: boolean;
14
17
  readonly knowledgeSpaceId?: string;
@@ -32,7 +35,11 @@ export declare class KnowledgeSemanticService {
32
35
  readonly sourceId: string;
33
36
  readonly error: string;
34
37
  }[];
38
+ readonly selfImprovement: KnowledgeSemanticSelfImproveResult;
35
39
  }>;
36
40
  answer(input: KnowledgeSemanticAnswerInput): Promise<KnowledgeSemanticAnswerResult>;
41
+ selfImprove(input?: KnowledgeSemanticSelfImproveInput): Promise<KnowledgeSemanticSelfImproveResult>;
42
+ private queueGapRepair;
43
+ private hasGapRepairSource;
37
44
  }
38
45
  //# sourceMappingURL=service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGzD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,iCAAiC,EACjC,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAC3C;AAED,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,KAAK,EAAE,cAAc,EACrB,OAAO,GAAE,+BAAoC;IAG1D,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAO,GAC3E,OAAO,CAAC,iCAAiC,GAAG,IAAI,CAAC;IAO9C,aAAa,CACjB,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GACpG,OAAO,CAAC,SAAS,iCAAiC,EAAE,CAAC;IASlD,OAAO,CAAC,KAAK,GAAE;QACnB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC/B,GAAG,OAAO,CAAC;QACf,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,EAAE,SAAS;YAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KACnF,CAAC;IA6BI,MAAM,CAAC,KAAK,EAAE,4BAA4B,GAAG,OAAO,CAAC,6BAA6B,CAAC;CAI1F"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAIzD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,iCAAiC,EACjC,4BAA4B,EAC5B,oBAAoB,EACpB,iCAAiC,EACjC,kCAAkC,EACnC,MAAM,YAAY,CAAC;AAKpB,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,4BAA4B,GAAG,IAAI,CAAC;CAC5D;AAED,qBAAa,wBAAwB;IAIjC,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,OAAO;IAJjB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;gBAGnC,KAAK,EAAE,cAAc,EAC9B,OAAO,GAAE,+BAAoC;IAGvD,cAAc,CAAC,WAAW,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAO5E,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAO,GAC3E,OAAO,CAAC,iCAAiC,GAAG,IAAI,CAAC;IAO9C,aAAa,CACjB,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GACpG,OAAO,CAAC,SAAS,iCAAiC,EAAE,CAAC;IASlD,OAAO,CAAC,KAAK,GAAE;QACnB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QACzB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC/B,GAAG,OAAO,CAAC;QACf,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,MAAM,EAAE,SAAS;YAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAClF,QAAQ,CAAC,eAAe,EAAE,kCAAkC,CAAC;KAC9D,CAAC;IAoCI,MAAM,CAAC,KAAK,EAAE,4BAA4B,GAAG,OAAO,CAAC,6BAA6B,CAAC;IAOnF,WAAW,CAAC,KAAK,GAAE,iCAAsC,GAAG,OAAO,CAAC,kCAAkC,CAAC;IAyB7G,OAAO,CAAC,cAAc;IA2CtB,OAAO,CAAC,kBAAkB;CAS3B"}
@@ -1,13 +1,23 @@
1
+ import { getKnowledgeSpaceId } from '../spaces.js';
1
2
  import { answerKnowledgeQuery } from './answer.js';
2
3
  import { enrichKnowledgeSource } from './enrichment.js';
3
4
  import { readRecord, readString, sourceSemanticHash, sourceSemanticText } from './utils.js';
5
+ import { uniqueStrings } from './utils.js';
6
+ import { runKnowledgeSemanticSelfImprovement } from './self-improvement.js';
4
7
  export class KnowledgeSemanticService {
5
8
  store;
6
9
  options;
10
+ activeGapRepairs = new Set();
7
11
  constructor(store, options = {}) {
8
12
  this.store = store;
9
13
  this.options = options;
10
14
  }
15
+ setGapRepairer(gapRepairer) {
16
+ this.options = {
17
+ ...this.options,
18
+ gapRepairer: gapRepairer ?? null,
19
+ };
20
+ }
11
21
  async enrichSource(sourceId, input = {}) {
12
22
  await this.store.init();
13
23
  const source = this.store.getSource(sourceId);
@@ -54,11 +64,97 @@ export class KnowledgeSemanticService {
54
64
  errors.push({ sourceId: source.id, error: error instanceof Error ? error.message : String(error) });
55
65
  }
56
66
  }
57
- return { scanned: sources.length, enriched, skipped, failed, errors };
67
+ const selfImprovement = await this.selfImprove({
68
+ knowledgeSpaceId: input.knowledgeSpaceId,
69
+ sourceIds: input.sourceIds,
70
+ force: input.force,
71
+ reason: 'reindex',
72
+ limit: input.limit,
73
+ });
74
+ return { scanned: sources.length, enriched, skipped, failed, errors, selfImprovement };
58
75
  }
59
76
  async answer(input) {
60
77
  await this.store.init();
61
- return answerKnowledgeQuery({ store: this.store, llm: this.options.llm }, input);
78
+ const answer = await answerKnowledgeQuery({ store: this.store, llm: this.options.llm }, input);
79
+ this.queueGapRepair(answer);
80
+ return answer;
81
+ }
82
+ async selfImprove(input = {}) {
83
+ await this.store.init();
84
+ if (!input.knowledgeSpaceId && !input.sourceIds?.length && !input.gapIds?.length) {
85
+ const spaces = uniqueStrings([
86
+ ...this.store.listSources(10_000).map((source) => getKnowledgeSpaceId(source)),
87
+ ...this.store.listNodes(10_000).map((node) => getKnowledgeSpaceId(node)),
88
+ ]);
89
+ let combined = emptySelfImproveResult();
90
+ for (const spaceId of spaces) {
91
+ const result = await runKnowledgeSemanticSelfImprovement({
92
+ store: this.store,
93
+ gapRepairer: this.options.gapRepairer,
94
+ activeGapRepairs: this.activeGapRepairs,
95
+ }, { ...input, knowledgeSpaceId: spaceId });
96
+ combined = mergeSelfImproveResults(combined, result);
97
+ }
98
+ return combined;
99
+ }
100
+ return runKnowledgeSemanticSelfImprovement({
101
+ store: this.store,
102
+ gapRepairer: this.options.gapRepairer,
103
+ activeGapRepairs: this.activeGapRepairs,
104
+ }, input);
105
+ }
106
+ queueGapRepair(answer) {
107
+ if (!this.options.gapRepairer || answer.answer.gaps.length === 0 || answer.answer.sources.length === 0)
108
+ return;
109
+ const gaps = answer.answer.gaps.filter((gap) => !this.hasGapRepairSource(answer.spaceId, gap.id));
110
+ if (gaps.length === 0)
111
+ return;
112
+ const repairKey = `${answer.spaceId}:${gaps.map((gap) => gap.id).sort().join(',')}`;
113
+ if (this.activeGapRepairs.has(repairKey))
114
+ return;
115
+ this.activeGapRepairs.add(repairKey);
116
+ const repairer = this.options.gapRepairer;
117
+ void repairer({
118
+ spaceId: answer.spaceId,
119
+ query: answer.query,
120
+ gaps,
121
+ sources: answer.answer.sources,
122
+ linkedObjects: answer.answer.linkedObjects,
123
+ facts: answer.answer.facts,
124
+ })
125
+ .then(async (result) => {
126
+ if (!result?.ingestedSourceIds.length)
127
+ return;
128
+ for (const sourceId of result.ingestedSourceIds) {
129
+ for (const gap of gaps) {
130
+ await this.store.upsertEdge({
131
+ fromKind: 'source',
132
+ fromId: sourceId,
133
+ toKind: 'node',
134
+ toId: gap.id,
135
+ relation: 'repairs_gap',
136
+ weight: 0.8,
137
+ metadata: {
138
+ semantic: true,
139
+ knowledgeSpaceId: answer.spaceId,
140
+ query: result.query ?? answer.query,
141
+ repairedAt: Date.now(),
142
+ },
143
+ });
144
+ }
145
+ }
146
+ })
147
+ .catch(() => { })
148
+ .finally(() => {
149
+ this.activeGapRepairs.delete(repairKey);
150
+ });
151
+ }
152
+ hasGapRepairSource(spaceId, gapId) {
153
+ return this.store.listEdges().some((edge) => (edge.relation === 'repairs_gap'
154
+ && edge.toKind === 'node'
155
+ && edge.toId === gapId
156
+ && edge.fromKind === 'source'
157
+ && readString(edge.metadata.knowledgeSpaceId) === spaceId));
62
158
  }
63
159
  }
64
160
  function sourceCanUseLlmUpgrade(store, source) {
@@ -70,3 +166,29 @@ function sourceCanUseLlmUpgrade(store, source) {
70
166
  return existingSemantic.textHash !== sourceSemanticHash(source, extraction)
71
167
  || readString(existingSemantic.extractor) !== 'llm';
72
168
  }
169
+ function emptySelfImproveResult() {
170
+ return {
171
+ scannedGaps: 0,
172
+ createdGaps: 0,
173
+ repairableGaps: 0,
174
+ suppressedGaps: 0,
175
+ skippedGaps: 0,
176
+ searched: 0,
177
+ ingestedSources: 0,
178
+ linkedRepairs: 0,
179
+ errors: [],
180
+ };
181
+ }
182
+ function mergeSelfImproveResults(left, right) {
183
+ return {
184
+ scannedGaps: left.scannedGaps + right.scannedGaps,
185
+ createdGaps: left.createdGaps + right.createdGaps,
186
+ repairableGaps: left.repairableGaps + right.repairableGaps,
187
+ suppressedGaps: left.suppressedGaps + right.suppressedGaps,
188
+ skippedGaps: left.skippedGaps + right.skippedGaps,
189
+ searched: left.searched + right.searched,
190
+ ingestedSources: left.ingestedSources + right.ingestedSources,
191
+ linkedRepairs: left.linkedRepairs + right.linkedRepairs,
192
+ errors: [...left.errors, ...right.errors],
193
+ };
194
+ }