@pellux/goodvibes-sdk 0.28.8 → 0.28.9

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.
@@ -3,7 +3,7 @@
3
3
  "product": {
4
4
  "id": "goodvibes",
5
5
  "surface": "operator",
6
- "version": "0.28.8"
6
+ "version": "0.28.9"
7
7
  },
8
8
  "auth": {
9
9
  "modes": [
@@ -1,6 +1,6 @@
1
1
  export declare const FOUNDATION_METADATA: {
2
2
  readonly productId: "goodvibes";
3
- readonly productVersion: "0.28.8";
3
+ readonly productVersion: "0.28.9";
4
4
  readonly operatorMethodCount: 264;
5
5
  readonly operatorEventCount: 30;
6
6
  readonly peerEndpointCount: 6;
@@ -1,7 +1,7 @@
1
1
  // Synced from packages/contracts/src/generated/foundation-metadata.ts
2
2
  export const FOUNDATION_METADATA = {
3
3
  "productId": "goodvibes",
4
- "productVersion": "0.28.8",
4
+ "productVersion": "0.28.9",
5
5
  "operatorMethodCount": 264,
6
6
  "operatorEventCount": 30,
7
7
  "peerEndpointCount": 6
@@ -3,7 +3,7 @@ export const OPERATOR_CONTRACT = {
3
3
  "product": {
4
4
  "id": "goodvibes",
5
5
  "surface": "operator",
6
- "version": "0.28.8"
6
+ "version": "0.28.9"
7
7
  },
8
8
  "auth": {
9
9
  "modes": [
@@ -0,0 +1,3 @@
1
+ export declare function intersects(left: ReadonlySet<string>, right: ReadonlySet<string>): boolean;
2
+ export declare function isSingularObjectQuery(query: string, tokens: readonly string[]): boolean;
3
+ //# sourceMappingURL=search-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-utils.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/search-utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAKzF;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAOvF"}
@@ -0,0 +1,16 @@
1
+ export function intersects(left, right) {
2
+ for (const value of left) {
3
+ if (right.has(value))
4
+ return true;
5
+ }
6
+ return false;
7
+ }
8
+ export function isSingularObjectQuery(query, tokens) {
9
+ const normalized = query.toLowerCase();
10
+ if (/\b(the|this|that|my)\s+(tv|television|device|sensor|switch|camera|printer|router|phone)\b/.test(normalized))
11
+ return true;
12
+ if (tokens.includes('tv') || tokens.includes('television')) {
13
+ return !tokens.includes('tvs') && !tokens.includes('televisions');
14
+ }
15
+ return false;
16
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAGrB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAoIxD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;CAC/E;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,oBAAoB,CAuBrG;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACxF,KAAK,EAAE,MAAM,GACZ,qBAAqB,EAAE,CAoFzB;AAED,wBAAgB,yCAAyC,CACvD,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACxF,KAAK,EAAE,MAAM,GACZ,qBAAqB,EAAE,CAoCzB;AAmND,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,yBAAyB,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAWhH"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/home-graph/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EACV,mBAAmB,EACnB,yBAAyB,EACzB,mBAAmB,EACnB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAIrB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAsIxD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC/C,QAAQ,CAAC,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;CAC/E;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,oBAAoB,CAuBrG;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACxF,KAAK,EAAE,MAAM,GACZ,qBAAqB,EAAE,CAoFzB;AAED,wBAAgB,yCAAyC,CACvD,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,KAAK,EAAE,SAAS,mBAAmB,EAAE,EACrC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACxF,KAAK,EAAE,MAAM,GACZ,qBAAqB,EAAE,CAoCzB;AAmND,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,yBAAyB,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAWhH"}
@@ -1,5 +1,6 @@
1
1
  import { belongsToSpace, edgeIsActive, isGeneratedPageSource, readRecord } from './helpers.js';
2
2
  import { isUnusableHomeGraphExtractionText } from './extraction-quality.js';
3
+ import { intersects, isSingularObjectQuery } from './search-utils.js';
3
4
  const MAX_FIELD_CHARS = 4_096;
4
5
  const MAX_SECTION_COUNT = 32;
5
6
  const MAX_SEARCH_TEXT_CHARS = 64 * 1024;
@@ -66,6 +67,8 @@ const GENERIC_ANCHOR_TOKENS = new Set([
66
67
  'smart',
67
68
  'storage',
68
69
  'switch',
70
+ 'television',
71
+ 'tv',
69
72
  ]);
70
73
  const QUERY_EXPANSIONS = {
71
74
  capability: ['capabilities', 'feature', 'features', 'function', 'functions', 'mode', 'modes', 'spec', 'specs', 'support', 'supports'],
@@ -149,7 +152,7 @@ export function scoreHomeGraphResults(query, sources, nodes, edges, extractionBy
149
152
  const expandedTokens = expandTokens(tokens);
150
153
  const anchorTokens = selectAnchorQueryTokens(tokens);
151
154
  const anchors = selectAnchorNodes(anchorTokens, nodes);
152
- const sourceAnchors = selectSourceAnchors(anchorTokens, anchors.map((anchor) => anchor.node));
155
+ const sourceAnchors = selectSourceAnchors(anchorTokens, anchors.map((anchor) => anchor.node), isSingularObjectQuery(query, tokens));
153
156
  const anchorIds = new Set(sourceAnchors.map((node) => node.id));
154
157
  const anchorIdentityTokens = collectAnchorIdentityTokens(sourceAnchors);
155
158
  const sourceLinks = buildSourceLinkIndex(edges);
@@ -234,7 +237,7 @@ export function selectHomeGraphExtractionRepairCandidates(query, sources, nodes,
234
237
  if (tokens.length === 0)
235
238
  return [];
236
239
  const anchors = selectAnchorNodes(tokens, nodes);
237
- const sourceAnchors = selectSourceAnchors(tokens, anchors.map((anchor) => anchor.node));
240
+ const sourceAnchors = selectSourceAnchors(tokens, anchors.map((anchor) => anchor.node), isSingularObjectQuery(query, tokens));
238
241
  const anchorIds = new Set(sourceAnchors.map((anchor) => anchor.id));
239
242
  const anchorIdentityTokens = collectAnchorIdentityTokens(sourceAnchors);
240
243
  const sourceLinks = buildSourceLinkIndex(edges);
@@ -351,7 +354,7 @@ function sourceAnchorIdentityScore(anchorTokens, source, extraction) {
351
354
  function selectAnchorNodes(tokens, nodes) {
352
355
  if (tokens.length === 0)
353
356
  return [];
354
- return nodes.map((node) => {
357
+ const scored = nodes.map((node) => {
355
358
  if (!isHomeGraphAnchorNode(node))
356
359
  return { node, score: 0 };
357
360
  const baseScore = scoreFields(tokens, nodeIdentityFields(node));
@@ -362,7 +365,10 @@ function selectAnchorNodes(tokens, nodes) {
362
365
  };
363
366
  })
364
367
  .filter((entry) => entry.score >= 10)
365
- .sort((a, b) => b.score - a.score || a.node.id.localeCompare(b.node.id))
368
+ .sort((a, b) => b.score - a.score || a.node.id.localeCompare(b.node.id));
369
+ const topScore = scored[0]?.score ?? 0;
370
+ return scored
371
+ .filter((entry) => entry.score >= Math.max(10, topScore - 12))
366
372
  .slice(0, 12);
367
373
  }
368
374
  function selectAnchorQueryTokens(tokens) {
@@ -373,10 +379,10 @@ function isHomeGraphAnchorNode(node) {
373
379
  return false;
374
380
  return HOME_GRAPH_ANCHOR_KINDS.has(node.kind);
375
381
  }
376
- function selectSourceAnchors(tokens, nodes) {
382
+ function selectSourceAnchors(tokens, nodes, singularObjectQuery = false) {
377
383
  const preferred = nodes.filter((node) => sourceAnchorIntentBoost(tokens, node) >= 0);
378
384
  if (preferred.length > 0)
379
- return preferred.slice(0, 8);
385
+ return preferred.slice(0, singularObjectQuery ? 1 : ANCHOR_SCOPE_LIMIT);
380
386
  return nodes.slice(0, ANCHOR_SCOPE_LIMIT);
381
387
  }
382
388
  function sourceAnchorIntentBoost(tokens, node) {
@@ -456,13 +462,6 @@ function nodeKindBoost(kind) {
456
462
  return 0;
457
463
  }
458
464
  }
459
- function intersects(left, right) {
460
- for (const value of left) {
461
- if (right.has(value))
462
- return true;
463
- }
464
- return false;
465
- }
466
465
  function isPendingDocumentationCandidate(source, extraction) {
467
466
  return !extraction
468
467
  && source.status !== 'indexed'
@@ -1 +1 @@
1
- {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/answer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAYlD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAE7B,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAsBpB,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC5C;AAsCD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,sBAAsB,EAC/B,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,6BAA6B,CAAC,CA+DxC"}
1
+ {"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/answer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAYlD,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAE7B,oBAAoB,EAErB,MAAM,YAAY,CAAC;AA2BpB,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC5C;AAsCD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,sBAAsB,EAC/B,KAAK,EAAE,4BAA4B,GAClC,OAAO,CAAC,6BAA6B,CAAC,CA+DxC"}
@@ -1,6 +1,7 @@
1
1
  import { getKnowledgeSpaceId, isHomeAssistantKnowledgeSpace, isInKnowledgeSpace, normalizeKnowledgeSpaceId, } from '../spaces.js';
2
2
  import { MAX_ANSWER_EVIDENCE_CHARS, clampText, normalizeWhitespace, readRecord, readString, scoreSemanticText, semanticHash, semanticMetadata, sourceSemanticText, splitSentences, tokenizeSemanticQuery, uniqueStrings, } from './utils.js';
3
3
  import { hasConcreteFeatureSignal, isLowValueFeatureOrSpecText, isSemanticAnswerLinkedObject, semanticFactText, } from './fact-quality.js';
4
+ import { inferHomeAssistantAnswerScope, nodeInHomeAssistantAnswerScope, sourceInHomeAssistantAnswerScope, } from './homeassistant-scope.js';
4
5
  const GENERIC_ANSWER_INTENT_TOKENS = new Set([
5
6
  'capabilities',
6
7
  'capability',
@@ -97,8 +98,12 @@ function collectAnswerEvidence(store, input, spaceId, limit) {
97
98
  const sourceFacts = buildSourceFactIndex(store, spaceId);
98
99
  const linkedSourceIds = sourceIdsLinkedToNodes(store, new Set([...candidateNodeIds, ...linkedObjectIds]), spaceId);
99
100
  const broadHomeAssistantAlias = normalizeKnowledgeSpaceId(spaceId) === 'homeassistant' && !strictCandidates;
101
+ const homeAssistantScope = !strictCandidates
102
+ ? inferHomeAssistantAnswerScope(store, spaceId, input.query, subjectTokens)
103
+ : null;
100
104
  const sourceItems = store.listSources(10_000)
101
105
  .filter((source) => belongsToAnswerSpace(source, spaceId))
106
+ .filter((source) => sourceInHomeAssistantAnswerScope(store, source, homeAssistantScope))
102
107
  .filter((source) => !strictCandidates || candidateSourceIds.has(source.id) || (candidateSourceIds.size === 0 && linkedSourceIds.has(source.id)))
103
108
  .map((source) => {
104
109
  const extraction = store.getExtractionBySourceId(source.id);
@@ -139,6 +144,7 @@ function collectAnswerEvidence(store, input, spaceId, limit) {
139
144
  });
140
145
  const nodeItems = store.listNodes(10_000)
141
146
  .filter((node) => belongsToAnswerSpace(node, spaceId) && node.status !== 'stale')
147
+ .filter((node) => nodeInHomeAssistantAnswerScope(node, homeAssistantScope))
142
148
  .filter((node) => !strictCandidates
143
149
  || candidateNodeIds.has(node.id)
144
150
  || linkedObjectIds.has(node.id)
@@ -0,0 +1,11 @@
1
+ import type { KnowledgeStore } from '../store.js';
2
+ import type { KnowledgeNodeRecord, KnowledgeSourceRecord } from '../types.js';
3
+ export interface HomeAssistantAnswerScope {
4
+ readonly anchorNodeIds: ReadonlySet<string>;
5
+ readonly linkedSourceIds: ReadonlySet<string>;
6
+ readonly anchorText: readonly string[];
7
+ }
8
+ export declare function inferHomeAssistantAnswerScope(store: KnowledgeStore, spaceId: string, query: string, subjectTokens: readonly string[]): HomeAssistantAnswerScope | null;
9
+ export declare function sourceInHomeAssistantAnswerScope(store: KnowledgeStore, source: KnowledgeSourceRecord, scope: HomeAssistantAnswerScope | null): boolean;
10
+ export declare function nodeInHomeAssistantAnswerScope(node: KnowledgeNodeRecord, scope: HomeAssistantAnswerScope | null): boolean;
11
+ //# sourceMappingURL=homeassistant-scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"homeassistant-scope.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/homeassistant-scope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAI9E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5C,QAAQ,CAAC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,wBAAwB,GAAG,IAAI,CA6BjC;AAED,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,wBAAwB,GAAG,IAAI,GACrC,OAAO,CAMT;AAED,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE,wBAAwB,GAAG,IAAI,GACrC,OAAO,CAKT"}
@@ -0,0 +1,179 @@
1
+ import { getKnowledgeSpaceId, isHomeAssistantKnowledgeSpace, normalizeKnowledgeSpaceId } from '../spaces.js';
2
+ import { readRecord, readString, scoreSemanticText, sourceSemanticText } from './utils.js';
3
+ export function inferHomeAssistantAnswerScope(store, spaceId, query, subjectTokens) {
4
+ const normalized = normalizeKnowledgeSpaceId(spaceId);
5
+ if (normalized !== 'homeassistant' && !isHomeAssistantKnowledgeSpace(normalized))
6
+ return null;
7
+ if (subjectTokens.length === 0)
8
+ return null;
9
+ const singularObjectQuery = isSingularObjectQuery(query, subjectTokens);
10
+ const strongIdentityTokens = subjectTokens.filter(isStrongIdentityToken);
11
+ if (!singularObjectQuery && strongIdentityTokens.length === 0)
12
+ return null;
13
+ const linkedSourceCountByNode = linkedSourceCountsByAnchor(store);
14
+ const anchors = store.listNodes(10_000)
15
+ .filter((node) => node.status !== 'stale' && isHomeAssistantObjectNode(node))
16
+ .filter((node) => normalized === 'homeassistant' || normalizeKnowledgeSpaceId(getKnowledgeSpaceId(node)) === normalized)
17
+ .map((node) => ({
18
+ node,
19
+ score: scoreHomeAssistantObject(node, subjectTokens)
20
+ + Math.min(24, (linkedSourceCountByNode.get(node.id) ?? 0) * 8),
21
+ }))
22
+ .filter((entry) => entry.score > 0)
23
+ .sort((left, right) => right.score - left.score || left.node.id.localeCompare(right.node.id));
24
+ const topScore = anchors[0]?.score ?? 0;
25
+ if (topScore <= 0)
26
+ return null;
27
+ const selectedAnchors = anchors
28
+ .filter((entry) => entry.score >= Math.max(1, topScore - 12))
29
+ .slice(0, singularObjectQuery ? 1 : 8);
30
+ const anchorNodeIds = new Set(selectedAnchors.map((entry) => entry.node.id));
31
+ const linkedSourceIds = sourceIdsLinkedToAnchors(store, anchorNodeIds);
32
+ const anchorText = selectedAnchors.map((entry) => homeAssistantObjectText(entry.node));
33
+ return { anchorNodeIds, linkedSourceIds, anchorText };
34
+ }
35
+ export function sourceInHomeAssistantAnswerScope(store, source, scope) {
36
+ if (!scope || scope.anchorNodeIds.size === 0)
37
+ return true;
38
+ if (scope.linkedSourceIds.has(source.id))
39
+ return true;
40
+ const extraction = store.getExtractionBySourceId(source.id);
41
+ const text = sourceSemanticText(source, extraction);
42
+ return scope.anchorText.some((anchor) => sourceMatchesAnchor(text, anchor));
43
+ }
44
+ export function nodeInHomeAssistantAnswerScope(node, scope) {
45
+ if (!scope || scope.anchorNodeIds.size === 0)
46
+ return true;
47
+ if (scope.anchorNodeIds.has(node.id))
48
+ return true;
49
+ const sourceId = readString(node.metadata.sourceId) ?? node.sourceId;
50
+ return Boolean(sourceId && scope.linkedSourceIds.has(sourceId));
51
+ }
52
+ function sourceIdsLinkedToAnchors(store, anchorNodeIds) {
53
+ const sourceIds = new Set();
54
+ if (anchorNodeIds.size === 0)
55
+ return sourceIds;
56
+ for (const edge of store.listEdges()) {
57
+ if (edge.fromKind === 'source' && edge.toKind === 'node' && anchorNodeIds.has(edge.toId))
58
+ sourceIds.add(edge.fromId);
59
+ if (edge.fromKind === 'node' && anchorNodeIds.has(edge.fromId) && edge.toKind === 'source')
60
+ sourceIds.add(edge.toId);
61
+ }
62
+ return sourceIds;
63
+ }
64
+ function linkedSourceCountsByAnchor(store) {
65
+ const counts = new Map();
66
+ for (const edge of store.listEdges()) {
67
+ if (edge.fromKind === 'source' && edge.toKind === 'node') {
68
+ counts.set(edge.toId, (counts.get(edge.toId) ?? 0) + 1);
69
+ }
70
+ else if (edge.fromKind === 'node' && edge.toKind === 'source') {
71
+ counts.set(edge.fromId, (counts.get(edge.fromId) ?? 0) + 1);
72
+ }
73
+ }
74
+ return counts;
75
+ }
76
+ function isHomeAssistantObjectNode(node) {
77
+ return node.kind === 'ha_device'
78
+ || node.kind === 'ha_entity'
79
+ || node.kind === 'ha_area'
80
+ || node.kind === 'ha_integration'
81
+ || node.kind === 'ha_automation'
82
+ || node.kind === 'ha_script'
83
+ || node.kind === 'ha_scene';
84
+ }
85
+ function scoreHomeAssistantObject(node, tokens) {
86
+ const metadata = readRecord(node.metadata);
87
+ const homeAssistant = readRecord(metadata.homeAssistant);
88
+ const text = homeAssistantObjectText(node);
89
+ const baseScore = scoreSemanticText(text, tokens);
90
+ const domain = (readString(metadata.domain) ?? readString(homeAssistant.domain) ?? '').toLowerCase();
91
+ const platform = (readString(metadata.platform) ?? readString(homeAssistant.integrationDomain) ?? '').toLowerCase();
92
+ const tvQuery = tokens.some((token) => token === 'tv' || token === 'television');
93
+ if (tvQuery) {
94
+ const lower = text.toLowerCase();
95
+ if (node.kind === 'ha_device' && /\b(tv|television|webos|bravia|roku)\b/.test(lower))
96
+ return baseScore + 80;
97
+ if (node.kind === 'ha_entity' && (domain === 'media_player' || platform === 'webostv'))
98
+ return baseScore + 70;
99
+ if (node.kind === 'ha_integration' && (platform === 'webostv' || lower.includes('webos')))
100
+ return baseScore + 30;
101
+ if (node.kind === 'ha_automation' || domain === 'sensor' || domain === 'switch')
102
+ return Math.max(0, baseScore - 40);
103
+ }
104
+ return baseScore;
105
+ }
106
+ function homeAssistantObjectText(node) {
107
+ const homeAssistant = readRecord(node.metadata.homeAssistant);
108
+ return [
109
+ node.title,
110
+ node.summary,
111
+ node.aliases.join(' '),
112
+ readString(node.metadata.manufacturer),
113
+ readString(node.metadata.model),
114
+ readString(homeAssistant.objectKind),
115
+ readString(homeAssistant.objectId),
116
+ readString(homeAssistant.entityId),
117
+ readString(homeAssistant.domain),
118
+ readString(homeAssistant.deviceClass),
119
+ readString(homeAssistant.integrationDomain),
120
+ ].filter(Boolean).join('\n');
121
+ }
122
+ function isSingularObjectQuery(query, tokens) {
123
+ const normalized = query.toLowerCase();
124
+ if (/\b(the|this|that|my)\s+(tv|television|device|sensor|switch|camera|printer|router|phone|object|thing)\b/.test(normalized)) {
125
+ return true;
126
+ }
127
+ if (tokens.includes('tv') || tokens.includes('television')) {
128
+ return !tokens.includes('tvs') && !tokens.includes('televisions');
129
+ }
130
+ return false;
131
+ }
132
+ function isStrongIdentityToken(token) {
133
+ return token.length >= 4 && !GENERIC_ANCHOR_TOKENS.has(token);
134
+ }
135
+ function sourceMatchesAnchor(sourceText, anchorText) {
136
+ const tokens = anchorText
137
+ .toLowerCase()
138
+ .split(/[^a-z0-9_.:-]+/)
139
+ .filter((token) => token.length >= 4 && !GENERIC_ANCHOR_TOKENS.has(token));
140
+ if (tokens.length === 0)
141
+ return false;
142
+ return scoreSemanticText(sourceText, tokens) >= Math.min(24, tokens.length * 12);
143
+ }
144
+ const GENERIC_ANCHOR_TOKENS = new Set([
145
+ 'audio',
146
+ 'available',
147
+ 'capabilities',
148
+ 'capability',
149
+ 'configuration',
150
+ 'device',
151
+ 'entity',
152
+ 'feature',
153
+ 'features',
154
+ 'format',
155
+ 'formats',
156
+ 'gaming',
157
+ 'have',
158
+ 'hdmi',
159
+ 'hdr',
160
+ 'home',
161
+ 'assistant',
162
+ 'integration',
163
+ 'network',
164
+ 'port',
165
+ 'ports',
166
+ 'rate',
167
+ 'refresh',
168
+ 'sensor',
169
+ 'smart',
170
+ 'spec',
171
+ 'specification',
172
+ 'specifications',
173
+ 'switch',
174
+ 'support',
175
+ 'supported',
176
+ 'supports',
177
+ 'television',
178
+ 'tv',
179
+ ]);
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- let version = '0.28.8';
3
+ let version = '0.28.9';
4
4
  try {
5
5
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', '..', 'package.json'), 'utf-8'));
6
6
  version = pkg.version ?? version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-sdk",
3
- "version": "0.28.8",
3
+ "version": "0.28.9",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/mgd34msu/goodvibes-sdk.git"