@pellux/goodvibes-sdk 0.26.8 → 0.26.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 (37) hide show
  1. package/dist/_internal/contracts/artifacts/operator-contract.json +209 -4
  2. package/dist/_internal/contracts/generated/foundation-metadata.d.ts +2 -2
  3. package/dist/_internal/contracts/generated/foundation-metadata.js +2 -2
  4. package/dist/_internal/contracts/generated/operator-contract.d.ts.map +1 -1
  5. package/dist/_internal/contracts/generated/operator-contract.js +209 -4
  6. package/dist/_internal/contracts/generated/operator-method-ids.d.ts +1 -1
  7. package/dist/_internal/contracts/generated/operator-method-ids.d.ts.map +1 -1
  8. package/dist/_internal/contracts/generated/operator-method-ids.js +1 -0
  9. package/dist/_internal/platform/control-plane/method-catalog-homegraph.d.ts.map +1 -1
  10. package/dist/_internal/platform/control-plane/method-catalog-homegraph.js +11 -1
  11. package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.d.ts +1 -0
  12. package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.d.ts.map +1 -1
  13. package/dist/_internal/platform/control-plane/operator-contract-schemas-knowledge.js +10 -0
  14. package/dist/_internal/platform/daemon/http/home-graph-routes.d.ts.map +1 -1
  15. package/dist/_internal/platform/daemon/http/home-graph-routes.js +3 -0
  16. package/dist/_internal/platform/knowledge/extractors.d.ts.map +1 -1
  17. package/dist/_internal/platform/knowledge/extractors.js +73 -1
  18. package/dist/_internal/platform/knowledge/home-graph/index.d.ts +1 -1
  19. package/dist/_internal/platform/knowledge/home-graph/index.d.ts.map +1 -1
  20. package/dist/_internal/platform/knowledge/home-graph/reindex.d.ts +12 -0
  21. package/dist/_internal/platform/knowledge/home-graph/reindex.d.ts.map +1 -0
  22. package/dist/_internal/platform/knowledge/home-graph/reindex.js +35 -0
  23. package/dist/_internal/platform/knowledge/home-graph/search.d.ts +3 -1
  24. package/dist/_internal/platform/knowledge/home-graph/search.d.ts.map +1 -1
  25. package/dist/_internal/platform/knowledge/home-graph/search.js +438 -25
  26. package/dist/_internal/platform/knowledge/home-graph/service.d.ts +3 -1
  27. package/dist/_internal/platform/knowledge/home-graph/service.d.ts.map +1 -1
  28. package/dist/_internal/platform/knowledge/home-graph/service.js +43 -6
  29. package/dist/_internal/platform/knowledge/home-graph/state.d.ts +2 -0
  30. package/dist/_internal/platform/knowledge/home-graph/state.d.ts.map +1 -1
  31. package/dist/_internal/platform/knowledge/home-graph/state.js +1 -1
  32. package/dist/_internal/platform/knowledge/home-graph/types.d.ts +14 -0
  33. package/dist/_internal/platform/knowledge/home-graph/types.d.ts.map +1 -1
  34. package/dist/_internal/platform/knowledge/ingest-compile.d.ts.map +1 -1
  35. package/dist/_internal/platform/knowledge/ingest-compile.js +26 -1
  36. package/dist/_internal/platform/version.js +1 -1
  37. package/package.json +2 -1
@@ -2,6 +2,75 @@ import { belongsToSpace, edgeIsActive, readRecord } from './helpers.js';
2
2
  const MAX_FIELD_CHARS = 4_096;
3
3
  const MAX_SECTION_COUNT = 32;
4
4
  const MAX_SEARCH_TEXT_CHARS = 64 * 1024;
5
+ const MAX_ANSWER_EXCERPT_CHARS = 640;
6
+ const ANCHOR_SCOPE_LIMIT = 5;
7
+ const STOPWORDS = new Set([
8
+ 'a',
9
+ 'about',
10
+ 'all',
11
+ 'an',
12
+ 'and',
13
+ 'are',
14
+ 'as',
15
+ 'at',
16
+ 'available',
17
+ 'be',
18
+ 'can',
19
+ 'could',
20
+ 'do',
21
+ 'does',
22
+ 'for',
23
+ 'from',
24
+ 'has',
25
+ 'have',
26
+ 'how',
27
+ 'i',
28
+ 'in',
29
+ 'is',
30
+ 'it',
31
+ 'me',
32
+ 'my',
33
+ 'of',
34
+ 'on',
35
+ 'or',
36
+ 'our',
37
+ 'show',
38
+ 'tell',
39
+ 'that',
40
+ 'the',
41
+ 'this',
42
+ 'to',
43
+ 'use',
44
+ 'what',
45
+ 'which',
46
+ 'with',
47
+ ]);
48
+ const SHORT_MEANINGFUL_TOKENS = new Set(['ac', 'av', 'dc', 'ha', 'ip', 'ir', 'lg', 'pc', 'tv']);
49
+ const QUERY_EXPANSIONS = {
50
+ capability: ['capabilities', 'feature', 'features', 'function', 'functions', 'mode', 'modes', 'spec', 'specs', 'support', 'supports'],
51
+ capabilities: ['capability', 'feature', 'features', 'function', 'functions', 'mode', 'modes', 'spec', 'specs', 'support', 'supports'],
52
+ feature: ['features', 'capability', 'capabilities', 'function', 'functions', 'mode', 'modes', 'spec', 'specs', 'support', 'supports'],
53
+ features: ['feature', 'capability', 'capabilities', 'function', 'functions', 'mode', 'modes', 'spec', 'specs', 'support', 'supports'],
54
+ television: ['tv', 'media_player'],
55
+ tv: ['television', 'media_player'],
56
+ };
57
+ const SOURCE_EVIDENCE_TOKENS = new Set([
58
+ 'battery',
59
+ 'capabilities',
60
+ 'capability',
61
+ 'feature',
62
+ 'features',
63
+ 'manual',
64
+ 'model',
65
+ 'reset',
66
+ 'serial',
67
+ 'spec',
68
+ 'specs',
69
+ 'support',
70
+ 'supports',
71
+ 'warranty',
72
+ ]);
73
+ const INTEGRATION_QUERY_TOKENS = new Set(['automation', 'automations', 'homeassistant', 'integration', 'integrations', 'webostv']);
5
74
  export function readHomeGraphSearchState(store, spaceId) {
6
75
  const sources = store.listSources(10_000).filter((source) => belongsToSpace(source, spaceId));
7
76
  const nodes = store.listNodes(10_000).filter((node) => belongsToSpace(node, spaceId));
@@ -23,54 +92,122 @@ export function readHomeGraphSearchState(store, spaceId) {
23
92
  }
24
93
  return { spaceId, sources, nodes, edges, extractionBySourceId };
25
94
  }
26
- export function scoreHomeGraphResults(query, sources, nodes, extractionBySourceId, limit) {
27
- const tokens = tokenize(query);
95
+ export function scoreHomeGraphResults(query, sources, nodes, edges, extractionBySourceId, limit) {
96
+ const tokens = tokenizeQuery(query);
28
97
  if (tokens.length === 0)
29
98
  return [];
99
+ const expandedTokens = expandTokens(tokens);
100
+ const anchors = selectAnchorNodes(tokens, nodes);
101
+ const anchorIds = new Set(anchors.map((anchor) => anchor.node.id));
102
+ const sourceLinks = buildSourceLinkIndex(edges);
103
+ const useAnchorScope = anchors.length > 0 && anchors.length <= ANCHOR_SCOPE_LIMIT;
104
+ const sourceEvidenceQuery = queryNeedsSourceEvidence(expandedTokens);
105
+ const integrationQuery = queryMentionsIntegration(tokens);
30
106
  const sourceResults = sources.map((source) => {
31
107
  const extraction = extractionBySourceId(source.id);
32
- const baseScore = scoreFields(tokens, [
108
+ if (isPendingDocumentationCandidate(source, extraction)) {
109
+ return sourceResult(source, extraction, 0);
110
+ }
111
+ if (sourceEvidenceQuery && !integrationQuery && isHomeAssistantIntegrationSource(source)) {
112
+ return sourceResult(source, extraction, 0);
113
+ }
114
+ const linkedNodeIds = sourceLinks.get(source.id) ?? new Set();
115
+ const linkedToAnchor = useAnchorScope && intersects(linkedNodeIds, anchorIds);
116
+ const identityScore = scoreFields(tokens, [
33
117
  source.title,
34
118
  source.summary,
35
119
  source.description,
36
120
  source.sourceUri,
37
121
  source.canonicalUri,
38
122
  source.tags.join(' '),
123
+ ]);
124
+ const contentScore = scoreFields(expandedTokens, [
39
125
  extraction?.title,
40
126
  extraction?.summary,
41
127
  extraction?.excerpt,
42
128
  readSearchText(extraction),
43
129
  ...limitedSections(extraction),
44
130
  ]);
45
- return {
46
- kind: 'source',
47
- id: source.id,
48
- score: baseScore > 0 ? baseScore + (extraction ? 8 : 0) : 0,
49
- title: source.title ?? source.sourceUri ?? source.id,
50
- summary: extraction?.summary ?? source.summary,
51
- source,
52
- };
131
+ const baseScore = identityScore + contentScore;
132
+ const linkBoost = linkedToAnchor ? 120 + relationBoost(source.id, anchorIds, edges) : 0;
133
+ const manualBoost = isManualLikeSource(source) ? 24 : 0;
134
+ const indexedBoost = source.status === 'indexed' ? 18 : source.status === 'stale' ? 6 : 0;
135
+ const extractionBoost = extraction ? 20 : 0;
136
+ const score = baseScore > 0 || linkBoost > 0
137
+ ? baseScore + linkBoost + manualBoost + indexedBoost + extractionBoost
138
+ : 0;
139
+ return sourceResult(source, extraction, score, selectRelevantExcerpt(expandedTokens, source, extraction));
53
140
  });
54
141
  const nodeResults = nodes.map((node) => {
55
- const baseScore = scoreFields(tokens, [
56
- node.title,
57
- node.summary,
58
- node.aliases.join(' '),
59
- readNodeMetadataText(node),
60
- ]);
142
+ const baseScore = scoreFields(tokens, nodeIdentityFields(node));
143
+ const anchorBoost = anchorIds.has(node.id) ? 40 + nodeKindBoost(node.kind) : 0;
61
144
  return {
62
145
  kind: 'node',
63
146
  id: node.id,
64
- score: baseScore > 0 ? baseScore + Math.round(node.confidence / 20) : 0,
147
+ score: baseScore > 0 ? baseScore + anchorBoost + Math.round(node.confidence / 20) : 0,
65
148
  title: node.title,
66
149
  summary: node.summary,
150
+ excerpt: node.summary,
67
151
  node,
68
152
  };
69
153
  });
70
- return [...sourceResults, ...nodeResults]
154
+ let results = [...sourceResults, ...nodeResults]
155
+ .filter((entry) => entry.score > 0)
156
+ .sort(compareHomeGraphResults);
157
+ const anchoredSourceResults = useAnchorScope
158
+ ? results.filter((result) => result.source && intersects(sourceLinks.get(result.source.id) ?? new Set(), anchorIds))
159
+ : [];
160
+ if (anchoredSourceResults.length > 0) {
161
+ results = anchoredSourceResults.sort(compareHomeGraphResults);
162
+ }
163
+ if (sourceEvidenceQuery) {
164
+ results = pruneWeakSourceEvidence(results, tokens, sourceLinks, anchorIds);
165
+ }
166
+ const strongResults = pruneWeakTokenCoverage(results, tokens);
167
+ return strongResults.slice(0, Math.max(1, limit));
168
+ }
169
+ export function selectHomeGraphExtractionRepairCandidates(query, sources, nodes, edges, extractionBySourceId, limit) {
170
+ const tokens = tokenizeQuery(query);
171
+ if (tokens.length === 0)
172
+ return [];
173
+ const anchors = selectAnchorNodes(tokens, nodes);
174
+ const anchorIds = new Set(anchors.map((anchor) => anchor.node.id));
175
+ const sourceLinks = buildSourceLinkIndex(edges);
176
+ return sources
177
+ .map((source) => {
178
+ if (!source.artifactId || !homeGraphExtractionNeedsRepair(extractionBySourceId(source.id)))
179
+ return { source, score: 0 };
180
+ const linkedNodeIds = sourceLinks.get(source.id) ?? new Set();
181
+ const linkedToAnchor = anchors.length > 0 && intersects(linkedNodeIds, anchorIds);
182
+ const identityScore = scoreFields(tokens, [
183
+ source.title,
184
+ source.summary,
185
+ source.description,
186
+ source.sourceUri,
187
+ source.canonicalUri,
188
+ source.tags.join(' '),
189
+ ]);
190
+ const sourceKindBoost = isManualLikeSource(source) ? 30 : 0;
191
+ const score = linkedToAnchor
192
+ ? 140 + relationBoost(source.id, anchorIds, edges) + sourceKindBoost + identityScore
193
+ : identityScore > 0 ? identityScore + sourceKindBoost : 0;
194
+ return { source, score };
195
+ })
71
196
  .filter((entry) => entry.score > 0)
72
- .sort((a, b) => b.score - a.score || a.id.localeCompare(b.id))
73
- .slice(0, Math.max(1, limit));
197
+ .sort((left, right) => right.score - left.score || left.source.id.localeCompare(right.source.id))
198
+ .slice(0, Math.max(1, limit))
199
+ .map((entry) => entry.source);
200
+ }
201
+ function sourceResult(source, extraction, score, excerpt) {
202
+ return {
203
+ kind: 'source',
204
+ id: source.id,
205
+ score,
206
+ title: source.title ?? source.sourceUri ?? source.id,
207
+ summary: usefulExtractionSummary(extraction) ?? source.summary,
208
+ ...(excerpt ? { excerpt } : {}),
209
+ source,
210
+ };
74
211
  }
75
212
  function limitedSections(extraction) {
76
213
  if (!extraction)
@@ -103,6 +240,238 @@ function readNodeMetadataText(node) {
103
240
  ].filter((value) => typeof value === 'string' && value.trim().length > 0);
104
241
  return values.length > 0 ? values.join(' ') : undefined;
105
242
  }
243
+ function nodeIdentityFields(node) {
244
+ return [
245
+ node.title,
246
+ node.summary,
247
+ node.aliases.join(' '),
248
+ readNodeMetadataText(node),
249
+ ].filter((value) => typeof value === 'string' && value.trim().length > 0);
250
+ }
251
+ function selectAnchorNodes(tokens, nodes) {
252
+ return nodes.map((node) => {
253
+ const baseScore = scoreFields(tokens, nodeIdentityFields(node));
254
+ return {
255
+ node,
256
+ score: baseScore > 0 ? baseScore + nodeKindBoost(node.kind) : 0,
257
+ };
258
+ })
259
+ .filter((entry) => entry.score >= 10)
260
+ .sort((a, b) => b.score - a.score || a.node.id.localeCompare(b.node.id))
261
+ .slice(0, 12);
262
+ }
263
+ function buildSourceLinkIndex(edges) {
264
+ const links = new Map();
265
+ for (const edge of edges) {
266
+ if (edge.fromKind === 'source' && edge.toKind === 'node') {
267
+ addSourceLink(links, edge.fromId, edge.toId);
268
+ }
269
+ else if (edge.fromKind === 'node' && edge.toKind === 'source') {
270
+ addSourceLink(links, edge.toId, edge.fromId);
271
+ }
272
+ }
273
+ return links;
274
+ }
275
+ function addSourceLink(links, sourceId, nodeId) {
276
+ const current = links.get(sourceId) ?? new Set();
277
+ current.add(nodeId);
278
+ links.set(sourceId, current);
279
+ }
280
+ function relationBoost(sourceId, anchorIds, edges) {
281
+ let boost = 0;
282
+ for (const edge of edges) {
283
+ const connectsAnchor = (edge.fromKind === 'source' && edge.fromId === sourceId && edge.toKind === 'node' && anchorIds.has(edge.toId))
284
+ || (edge.fromKind === 'node' && anchorIds.has(edge.fromId) && edge.toKind === 'source' && edge.toId === sourceId);
285
+ if (!connectsAnchor)
286
+ continue;
287
+ if (edge.relation === 'has_manual')
288
+ boost = Math.max(boost, 45);
289
+ else if (edge.relation === 'source_for')
290
+ boost = Math.max(boost, 25);
291
+ else
292
+ boost = Math.max(boost, 15);
293
+ }
294
+ return boost;
295
+ }
296
+ function nodeKindBoost(kind) {
297
+ switch (kind) {
298
+ case 'ha_device':
299
+ case 'ha_entity':
300
+ return 20;
301
+ case 'ha_area':
302
+ case 'ha_room':
303
+ case 'ha_automation':
304
+ case 'ha_script':
305
+ case 'ha_scene':
306
+ return 12;
307
+ case 'ha_integration':
308
+ return 6;
309
+ default:
310
+ return 0;
311
+ }
312
+ }
313
+ function intersects(left, right) {
314
+ for (const value of left) {
315
+ if (right.has(value))
316
+ return true;
317
+ }
318
+ return false;
319
+ }
320
+ function isPendingDocumentationCandidate(source, extraction) {
321
+ return !extraction
322
+ && source.status !== 'indexed'
323
+ && source.metadata.homeGraphSourceKind === 'documentation-candidate';
324
+ }
325
+ export function homeGraphExtractionNeedsRepair(extraction) {
326
+ if (!extraction)
327
+ return true;
328
+ if (extraction.format === 'pdf' && extraction.extractorId !== 'pdfjs')
329
+ return true;
330
+ const searchText = readSearchText(extraction);
331
+ if (searchText && searchText.trim().length > 0)
332
+ return false;
333
+ if ((extraction.excerpt?.trim() && !isLowInformationExtractionText(extraction.excerpt))
334
+ || (extraction.summary?.trim() && !isLowInformationExtractionText(extraction.summary))
335
+ || extraction.sections.some((section) => section.trim() && !isLowInformationExtractionText(section))) {
336
+ return false;
337
+ }
338
+ return true;
339
+ }
340
+ function queryNeedsSourceEvidence(tokens) {
341
+ return tokens.some((token) => SOURCE_EVIDENCE_TOKENS.has(token));
342
+ }
343
+ function queryMentionsIntegration(tokens) {
344
+ return tokens.some((token) => INTEGRATION_QUERY_TOKENS.has(token));
345
+ }
346
+ function isManualLikeSource(source) {
347
+ const tags = source.tags.map((tag) => tag.toLowerCase());
348
+ return source.sourceType === 'manual'
349
+ || source.sourceType === 'document'
350
+ || source.sourceType === 'url'
351
+ || tags.includes('manual')
352
+ || tags.includes('artifact')
353
+ || tags.includes('document');
354
+ }
355
+ function isHomeAssistantIntegrationSource(source) {
356
+ const tags = source.tags.map((tag) => tag.toLowerCase());
357
+ const sourceKind = typeof source.metadata.homeGraphSourceKind === 'string'
358
+ ? source.metadata.homeGraphSourceKind.toLowerCase()
359
+ : '';
360
+ return tags.includes('integration')
361
+ || tags.includes('documentation')
362
+ || sourceKind === 'documentation-candidate';
363
+ }
364
+ function pruneWeakSourceEvidence(results, tokens, sourceLinks, anchorIds) {
365
+ const sourceResults = results.filter((result) => result.source);
366
+ if (sourceResults.length === 0)
367
+ return [...results];
368
+ const strongSourceResults = sourceResults.filter((result) => {
369
+ const source = result.source;
370
+ if (!source)
371
+ return false;
372
+ if (!hasUsefulSourceAnswerText(result))
373
+ return false;
374
+ const linkedToAnchor = intersects(sourceLinks.get(source.id) ?? new Set(), anchorIds);
375
+ return linkedToAnchor || tokenCoverage(tokens, resultText(result)) >= Math.min(2, tokens.length);
376
+ });
377
+ return strongSourceResults;
378
+ }
379
+ function hasUsefulSourceAnswerText(result) {
380
+ const detail = result.excerpt ?? result.summary ?? result.source?.description;
381
+ return typeof detail === 'string' && detail.trim().length > 0 && !isLowInformationExtractionText(detail);
382
+ }
383
+ function selectRelevantExcerpt(tokens, source, extraction) {
384
+ const chunks = candidateExcerptChunks(source, extraction);
385
+ let best;
386
+ for (const chunk of chunks) {
387
+ const text = cleanWhitespace(chunk);
388
+ if (!text || isLowInformationExtractionText(text))
389
+ continue;
390
+ const score = scoreFields(tokens, [text]);
391
+ if (!best || score > best.score || (score === best.score && text.length < best.text.length)) {
392
+ best = { score, text };
393
+ }
394
+ }
395
+ if (!best || best.score <= 0) {
396
+ return firstBoundedText(chunks.filter((chunk) => !isLowInformationExtractionText(chunk)), MAX_ANSWER_EXCERPT_CHARS);
397
+ }
398
+ return clampAroundBestToken(best.text, tokens, MAX_ANSWER_EXCERPT_CHARS);
399
+ }
400
+ function candidateExcerptChunks(source, extraction) {
401
+ const searchText = readSearchText(extraction);
402
+ return [
403
+ extraction?.excerpt,
404
+ extraction?.summary,
405
+ ...limitedSections(extraction),
406
+ ...sentenceChunks(searchText),
407
+ source.description,
408
+ source.summary,
409
+ ].filter((value) => typeof value === 'string' && value.trim().length > 0);
410
+ }
411
+ function usefulExtractionSummary(extraction) {
412
+ const summary = extraction?.summary?.trim();
413
+ return summary && !isLowInformationExtractionText(summary) ? summary : undefined;
414
+ }
415
+ function isLowInformationExtractionText(value) {
416
+ const normalized = value.toLowerCase();
417
+ return normalized.includes('pdf extraction produced limited text')
418
+ || normalized.includes('no readable text streams')
419
+ || normalized.includes('no specialized extractor matched')
420
+ || normalized.includes('has no specialized in-core extractor');
421
+ }
422
+ function sentenceChunks(value) {
423
+ const text = cleanWhitespace(value ?? '');
424
+ if (!text)
425
+ return [];
426
+ const matches = text.match(/[^.!?\n]+[.!?]?/g) ?? [text];
427
+ return matches.map((entry) => entry.trim()).filter(Boolean).slice(0, 80);
428
+ }
429
+ function clampAroundBestToken(value, tokens, maxLength) {
430
+ const text = cleanWhitespace(value);
431
+ if (text.length <= maxLength)
432
+ return text;
433
+ const lower = text.toLowerCase();
434
+ const index = tokens
435
+ .map((token) => lower.indexOf(token.toLowerCase()))
436
+ .filter((entry) => entry >= 0)
437
+ .sort((a, b) => a - b)[0] ?? 0;
438
+ const start = Math.max(0, index - Math.floor(maxLength / 3));
439
+ const end = Math.min(text.length, start + maxLength);
440
+ const prefix = start > 0 ? '...' : '';
441
+ const suffix = end < text.length ? '...' : '';
442
+ return `${prefix}${text.slice(start, end).trim()}${suffix}`;
443
+ }
444
+ function cleanWhitespace(value) {
445
+ return value.replace(/\s+/g, ' ').trim();
446
+ }
447
+ function pruneWeakTokenCoverage(results, tokens) {
448
+ if (results.length <= 1 || tokens.length <= 1)
449
+ return [...results];
450
+ const topCoverage = tokenCoverage(tokens, resultText(results[0]));
451
+ if (topCoverage < 2)
452
+ return [...results];
453
+ return results.filter((result) => tokenCoverage(tokens, resultText(result)) >= Math.max(1, topCoverage - 1));
454
+ }
455
+ function tokenCoverage(tokens, text) {
456
+ const haystack = text.toLowerCase();
457
+ let count = 0;
458
+ for (const token of tokens) {
459
+ if (fieldIncludesToken(haystack, token))
460
+ count += 1;
461
+ }
462
+ return count;
463
+ }
464
+ function resultText(result) {
465
+ return [
466
+ result.title,
467
+ result.summary,
468
+ result.excerpt,
469
+ result.source?.description,
470
+ result.source?.sourceUri,
471
+ result.source?.canonicalUri,
472
+ result.source?.tags.join(' '),
473
+ ].filter((value) => typeof value === 'string').join(' ');
474
+ }
106
475
  function firstBoundedText(values, maxLength) {
107
476
  for (const value of values) {
108
477
  if (typeof value !== 'string')
@@ -117,8 +486,31 @@ function firstBoundedText(values, maxLength) {
117
486
  function clampText(value, maxLength) {
118
487
  return value.length <= maxLength ? value : value.slice(0, maxLength);
119
488
  }
120
- function tokenize(value) {
121
- return value.toLowerCase().split(/[^a-z0-9_.:-]+/).map((entry) => entry.trim()).filter(Boolean);
489
+ function tokenizeQuery(value) {
490
+ const tokens = value.toLowerCase()
491
+ .split(/[^a-z0-9_.:-]+/)
492
+ .map((entry) => entry.trim())
493
+ .filter((entry) => isMeaningfulToken(entry));
494
+ return [...new Set(tokens)];
495
+ }
496
+ function expandTokens(tokens) {
497
+ const expanded = new Set();
498
+ for (const token of tokens) {
499
+ expanded.add(token);
500
+ for (const synonym of QUERY_EXPANSIONS[token] ?? []) {
501
+ expanded.add(synonym);
502
+ }
503
+ }
504
+ return [...expanded];
505
+ }
506
+ function isMeaningfulToken(token) {
507
+ if (!token || STOPWORDS.has(token))
508
+ return false;
509
+ if (token.length === 1)
510
+ return false;
511
+ if (token.length <= 2 && !SHORT_MEANINGFUL_TOKENS.has(token))
512
+ return false;
513
+ return true;
122
514
  }
123
515
  function scoreFields(tokens, fields) {
124
516
  let score = 0;
@@ -128,9 +520,30 @@ function scoreFields(tokens, fields) {
128
520
  if (!haystack)
129
521
  continue;
130
522
  for (const token of tokens) {
131
- if (haystack.includes(token))
132
- score += 10;
523
+ if (fieldIncludesToken(haystack, token))
524
+ score += token.length <= 3 ? 14 : 10;
133
525
  }
134
526
  }
135
527
  return score;
136
528
  }
529
+ function fieldIncludesToken(haystack, token) {
530
+ if (token.length <= 3 || token.includes('_') || token.includes('-')) {
531
+ return new RegExp(`(?:^|[^a-z0-9])${escapeRegExp(token)}(?:$|[^a-z0-9])`).test(haystack);
532
+ }
533
+ return haystack.includes(token);
534
+ }
535
+ function escapeRegExp(value) {
536
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
537
+ }
538
+ function compareHomeGraphResults(left, right) {
539
+ return right.score - left.score
540
+ || resultKindPriority(right) - resultKindPriority(left)
541
+ || left.id.localeCompare(right.id);
542
+ }
543
+ function resultKindPriority(result) {
544
+ if (result.source)
545
+ return 2;
546
+ if (result.node)
547
+ return 1;
548
+ return 0;
549
+ }
@@ -2,7 +2,7 @@ import type { ArtifactStore } from '../../artifacts/index.js';
2
2
  import type { KnowledgeStore } from '../store.js';
3
3
  import type { KnowledgeEdgeRecord, KnowledgeIssueRecord, KnowledgeNodeRecord, KnowledgeSourceRecord } from '../types.js';
4
4
  import { type HomeGraphReviewResult } from './review.js';
5
- import type { HomeGraphAskInput, HomeGraphAskResult, HomeGraphDevicePassportResult, HomeGraphExport, HomeGraphIngestArtifactInput, HomeGraphIngestNoteInput, HomeGraphIngestResult, HomeGraphIngestUrlInput, HomeGraphLinkInput, HomeGraphLinkResult, HomeGraphProjectionInput, HomeGraphProjectionResult, HomeGraphReviewInput, HomeGraphSpaceInput, HomeGraphSnapshotInput, HomeGraphStatus, HomeGraphSyncResult } from './types.js';
5
+ import type { HomeGraphAskInput, HomeGraphAskResult, HomeGraphDevicePassportResult, HomeGraphExport, HomeGraphIngestArtifactInput, HomeGraphIngestNoteInput, HomeGraphIngestResult, HomeGraphIngestUrlInput, HomeGraphLinkInput, HomeGraphLinkResult, HomeGraphProjectionInput, HomeGraphProjectionResult, HomeGraphReindexResult, HomeGraphReviewInput, HomeGraphSpaceInput, HomeGraphSnapshotInput, HomeGraphStatus, HomeGraphSyncResult } from './types.js';
6
6
  export declare class HomeGraphService {
7
7
  private readonly store;
8
8
  private readonly artifactStore;
@@ -18,6 +18,8 @@ export declare class HomeGraphService {
18
18
  linkKnowledge(input: HomeGraphLinkInput): Promise<HomeGraphLinkResult>;
19
19
  unlinkKnowledge(input: HomeGraphLinkInput): Promise<HomeGraphLinkResult>;
20
20
  ask(input: HomeGraphAskInput): Promise<HomeGraphAskResult>;
21
+ reindex(input?: HomeGraphSpaceInput): Promise<HomeGraphReindexResult>;
22
+ private repairWeakExtractionsForAsk;
21
23
  refreshDevicePassport(input: HomeGraphProjectionInput): Promise<HomeGraphDevicePassportResult>;
22
24
  generateRoomPage(input: HomeGraphProjectionInput): Promise<HomeGraphProjectionResult>;
23
25
  generatePacket(input: HomeGraphProjectionInput): Promise<HomeGraphProjectionResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/service.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,12 @@
1
1
  import { extractKnowledgeArtifact } from '../extractors.js';
2
- import { HOME_GRAPH_CONNECTOR_ID, belongsToSpace, buildHomeGraphMetadata, buildHomeGraphNodeInput, edgeIsActive, homeGraphNodeId, homeGraphSourceId, namespacedCanonicalUri, nodeKindForHomeGraphObject, normalizeHomeGraphObjectInput, resolveHomeGraphSpace, targetToReference, uniqueStrings, } from './helpers.js';
2
+ import { HOME_GRAPH_CONNECTOR_ID, belongsToSpace, buildHomeGraphMetadata, buildHomeGraphNodeInput, edgeIsActive, homeGraphNodeId, homeGraphSourceId, namespacedCanonicalUri, nodeKindForHomeGraphObject, normalizeHomeGraphObjectInput, readRecord, resolveHomeGraphSpace, targetToReference, uniqueStrings, } from './helpers.js';
3
3
  import { upsertIntegrationDocumentationCandidates } from './documentation.js';
4
4
  import { refreshHomeGraphQualityIssues } from './quality.js';
5
5
  import { reviewHomeGraphFact } from './review.js';
6
6
  import { collectLinkedObjects, findHomeAssistantNode, inferHomeGraphSourceType, missingDevicePassportFields, readHomeGraphState, renderAskAnswer, renderHomeGraphState, safeHomeGraphFilename, sourcesLinkedToNode, } from './state.js';
7
7
  import { renderDevicePassportPage, renderPacketPage, renderRoomPage, } from './rendering.js';
8
- import { readHomeGraphSearchState, scoreHomeGraphResults, } from './search.js';
8
+ import { reindexHomeGraphSources } from './reindex.js';
9
+ import { readHomeGraphSearchState, scoreHomeGraphResults, selectHomeGraphExtractionRepairCandidates, } from './search.js';
9
10
  import { HOME_GRAPH_CAPABILITIES } from './types.js';
10
11
  export class HomeGraphService {
11
12
  store;
@@ -189,9 +190,12 @@ export class HomeGraphService {
189
190
  }
190
191
  async ask(input) {
191
192
  await this.store.init();
192
- const { spaceId } = resolveHomeGraphSpace(input);
193
- const state = readHomeGraphSearchState(this.store, spaceId);
194
- const results = scoreHomeGraphResults(input.query, state.sources, state.nodes, (sourceId) => state.extractionBySourceId.get(sourceId), input.limit ?? 8);
193
+ const { spaceId, installationId } = resolveHomeGraphSpace(input);
194
+ let state = readHomeGraphSearchState(this.store, spaceId);
195
+ if (await this.repairWeakExtractionsForAsk(spaceId, installationId, input.query, state) > 0) {
196
+ state = readHomeGraphSearchState(this.store, spaceId);
197
+ }
198
+ const results = scoreHomeGraphResults(input.query, state.sources, state.nodes, state.edges, (sourceId) => state.extractionBySourceId.get(sourceId), input.limit ?? 8);
195
199
  const sources = results.flatMap((result) => result.source ? [result.source] : []);
196
200
  const linkedObjects = collectLinkedObjects(results, state);
197
201
  const confidence = Math.min(100, Math.max(10, results[0]?.score ?? 10));
@@ -209,6 +213,34 @@ export class HomeGraphService {
209
213
  results,
210
214
  };
211
215
  }
216
+ async reindex(input = {}) {
217
+ await this.store.init();
218
+ const { spaceId, installationId } = resolveHomeGraphSpace(input);
219
+ const state = readHomeGraphSearchState(this.store, spaceId);
220
+ return reindexHomeGraphSources({
221
+ spaceId,
222
+ sources: state.sources,
223
+ extractionBySourceId: state.extractionBySourceId,
224
+ artifactStore: this.artifactStore,
225
+ extract: (source, artifact) => this.extractArtifact(source, artifact, spaceId, installationId),
226
+ });
227
+ }
228
+ async repairWeakExtractionsForAsk(spaceId, installationId, query, state) {
229
+ const candidates = selectHomeGraphExtractionRepairCandidates(query, state.sources, state.nodes, state.edges, (sourceId) => state.extractionBySourceId.get(sourceId), 8);
230
+ let repaired = 0;
231
+ for (const source of candidates) {
232
+ const artifactId = typeof source.artifactId === 'string' ? source.artifactId : undefined;
233
+ if (!artifactId)
234
+ continue;
235
+ const artifact = this.artifactStore.get(artifactId);
236
+ if (!artifact)
237
+ continue;
238
+ const extraction = await this.extractArtifact(source, artifact, spaceId, installationId);
239
+ if (extraction && extractionHasSearchableText(extraction))
240
+ repaired += 1;
241
+ }
242
+ return repaired;
243
+ }
212
244
  async refreshDevicePassport(input) {
213
245
  await this.store.init();
214
246
  const { spaceId, installationId } = resolveHomeGraphSpace(input);
@@ -410,8 +442,9 @@ export class HomeGraphService {
410
442
  return undefined;
411
443
  const { buffer } = await this.artifactStore.readContent(artifact.id);
412
444
  const extracted = await extractKnowledgeArtifact(record, buffer);
445
+ const existing = this.store.getExtractionBySourceId(source.id);
413
446
  return this.store.upsertExtraction({
414
- id: `hg-extract-${source.id.replace(/^hg-src-/, '')}`,
447
+ id: existing?.id ?? `hg-extract-${source.id.replace(/^hg-src-/, '')}`,
415
448
  sourceId: source.id,
416
449
  artifactId: artifact.id,
417
450
  extractorId: extracted.extractorId,
@@ -577,3 +610,7 @@ export class HomeGraphService {
577
610
  };
578
611
  }
579
612
  }
613
+ function extractionHasSearchableText(extraction) {
614
+ const structure = readRecord(extraction.structure);
615
+ return typeof structure.searchText === 'string' && structure.searchText.trim().length > 0;
616
+ }
@@ -21,7 +21,9 @@ export declare function safeHomeGraphFilename(value: string): string;
21
21
  export declare function renderAskAnswer(query: string, results: readonly {
22
22
  readonly title: string;
23
23
  readonly summary?: string;
24
+ readonly excerpt?: string;
24
25
  readonly source?: KnowledgeSourceRecord;
26
+ readonly node?: KnowledgeNodeRecord;
25
27
  }[], mode: 'concise' | 'standard' | 'detailed'): string;
26
28
  export declare function edgeConnectsNode(edge: KnowledgeEdgeRecord, nodeId: string, relation: string, toId: string): boolean;
27
29
  //# sourceMappingURL=state.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EAEzB,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3D,MAAM,WAAW,cAAe,SAAQ,IAAI,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACzE,QAAQ,CAAC,WAAW,EAAE,SAAS,yBAAyB,EAAE,CAAC;CAC5D;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CAkBzF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,oBAAoB,CAUhH;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,qBAAqB,EAAE,CAKlG;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,SAAS;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,mBAAmB,CAAA;CAAE,EAAE,EACpG,KAAK,EAAE;IACL,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAChD,GACA,mBAAmB,EAAE,CAavB;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,SAAS,qBAAqB,EAAE,GACxC,MAAM,EAAE,CAOV;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,mBAAmB,GAAG,SAAS,CAOjC;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACnC,QAAQ,EAAE,mBAAmB,GAC5B,mBAAmB,CAMrB;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,CAAA;CAAE,EAAE,EAClH,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,GACxC,MAAM,CASR;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAOnH"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EAEzB,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACpB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3D,MAAM,WAAW,cAAe,SAAQ,IAAI,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACzE,QAAQ,CAAC,WAAW,EAAE,SAAS,yBAAyB,EAAE,CAAC;CAC5D;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CAkBzF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,oBAAoB,CAUhH;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,qBAAqB,EAAE,CAKlG;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,SAAS;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,mBAAmB,CAAA;CAAE,EAAE,EACpG,KAAK,EAAE;IACL,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAChD,GACA,mBAAmB,EAAE,CAavB;AAED,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,SAAS,qBAAqB,EAAE,GACxC,MAAM,EAAE,CAOV;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,mBAAmB,GAAG,SAAS,CAOjC;AAED,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACnC,QAAQ,EAAE,mBAAmB,GAC5B,mBAAmB,CAMrB;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,mBAAmB,CAAA;CAAE,EAAE,EAClL,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,UAAU,GACxC,MAAM,CASR;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAOnH"}