@pellux/goodvibes-sdk 0.27.0 → 0.27.1

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.27.0"
6
+ "version": "0.27.1"
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.27.0";
3
+ readonly productVersion: "0.27.1";
4
4
  readonly operatorMethodCount: 254;
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.27.0",
4
+ "productVersion": "0.27.1",
5
5
  "operatorMethodCount": 254,
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.27.0"
6
+ "version": "0.27.1"
7
7
  },
8
8
  "auth": {
9
9
  "modes": [
@@ -1 +1 @@
1
- {"version":3,"file":"home-graph-routes.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/daemon/http/home-graph-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,oCAAoC,CAAC;AAc5C,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1C,UAAU,qBAAqB;IAC7B,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,UAAU,GAAG,QAAQ,CAAC,CAAC;IACzE,QAAQ,CAAC,qBAAqB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,GAAG,QAAQ,CAAC,CAAC;IACxF,QAAQ,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,QAAQ,GAAG,IAAI,CAAC;CAC1D;AAED,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,qBAAqB;IAErD,MAAM,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;YAqGtC,KAAK;YAML,QAAQ;YAMR,gBAAgB;CAK/B"}
1
+ {"version":3,"file":"home-graph-routes.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/daemon/http/home-graph-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,oCAAoC,CAAC;AAc5C,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1C,UAAU,qBAAqB;IAC7B,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,aAAa,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,UAAU,GAAG,QAAQ,CAAC,CAAC;IACzE,QAAQ,CAAC,qBAAqB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,GAAG,QAAQ,CAAC,CAAC;IACxF,QAAQ,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,QAAQ,GAAG,IAAI,CAAC;CAC1D;AAED,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,qBAAqB;IAErD,MAAM,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;YAwGtC,KAAK;YAML,QAAQ;YAMR,gBAAgB;CAK/B"}
@@ -6,13 +6,14 @@ export class HomeGraphRoutes {
6
6
  }
7
7
  async handle(req) {
8
8
  const url = new URL(req.url);
9
- if (!url.pathname.startsWith('/api/homeassistant/home-graph'))
9
+ const pathname = normalizeHomeGraphPath(url.pathname);
10
+ if (!pathname.startsWith('/api/homeassistant/home-graph'))
10
11
  return null;
11
12
  try {
12
- if (url.pathname === '/api/homeassistant/home-graph/status' && req.method === 'GET') {
13
+ if (pathname === '/api/homeassistant/home-graph/status' && req.method === 'GET') {
13
14
  return Response.json(await this.context.homeGraphService.status(readSpaceFromUrl(url)));
14
15
  }
15
- if (url.pathname === '/api/homeassistant/home-graph/issues' && req.method === 'GET') {
16
+ if (pathname === '/api/homeassistant/home-graph/issues' && req.method === 'GET') {
16
17
  return Response.json(await this.context.homeGraphService.listIssues({
17
18
  ...readSpaceFromUrl(url),
18
19
  status: url.searchParams.get('status') ?? undefined,
@@ -21,23 +22,25 @@ export class HomeGraphRoutes {
21
22
  limit: readLimit(url, 100),
22
23
  }));
23
24
  }
24
- if (url.pathname === '/api/homeassistant/home-graph/sources' && req.method === 'GET') {
25
+ if (pathname === '/api/homeassistant/home-graph/sources' && req.method === 'GET') {
25
26
  return Response.json(await this.context.homeGraphService.listSources({
26
27
  ...readSpaceFromUrl(url),
27
28
  limit: readLimit(url, 100),
28
29
  }));
29
30
  }
30
- if (url.pathname === '/api/homeassistant/home-graph/browse' && req.method === 'GET') {
31
+ if (pathname === '/api/homeassistant/home-graph/browse' && req.method === 'GET') {
31
32
  return Response.json(await this.context.homeGraphService.browse({
32
33
  ...readSpaceFromUrl(url),
33
34
  limit: readLimit(url, 250),
34
35
  }));
35
36
  }
36
- if (url.pathname === '/api/homeassistant/home-graph/map' && req.method === 'GET') {
37
+ if (pathname === '/api/homeassistant/home-graph/map' && (req.method === 'GET' || req.method === 'POST')) {
38
+ const body = req.method === 'POST' ? await this.readOptionalBody(req) : {};
37
39
  const result = await this.context.homeGraphService.map({
38
40
  ...readSpaceFromUrl(url),
39
- limit: readLimit(url, 500),
40
- includeSources: readBoolean(url, 'includeSources', true),
41
+ ...body,
42
+ limit: readNumber(body.limit) ?? readLimit(url, 500),
43
+ includeSources: readBooleanValue(body.includeSources) ?? readBoolean(url, 'includeSources', true),
41
44
  });
42
45
  if (url.searchParams.get('format') === 'svg') {
43
46
  return new Response(result.svg, {
@@ -48,25 +51,25 @@ export class HomeGraphRoutes {
48
51
  }
49
52
  return Response.json(result);
50
53
  }
51
- if (url.pathname === '/api/homeassistant/home-graph/export' && req.method === 'POST') {
54
+ if (pathname === '/api/homeassistant/home-graph/export' && req.method === 'POST') {
52
55
  return Response.json(await this.context.homeGraphService.exportSpace(await this.readOptionalBody(req)));
53
56
  }
54
- if (url.pathname === '/api/homeassistant/home-graph/ask' && req.method === 'POST') {
57
+ if (pathname === '/api/homeassistant/home-graph/ask' && req.method === 'POST') {
55
58
  return Response.json(await this.context.homeGraphService.ask(await this.readBody(req)));
56
59
  }
57
- if (url.pathname === '/api/homeassistant/home-graph/reindex' && req.method === 'POST') {
60
+ if (pathname === '/api/homeassistant/home-graph/reindex' && req.method === 'POST') {
58
61
  return await this.admin(req, async () => Response.json(await this.context.homeGraphService.reindex(await this.readOptionalBody(req))));
59
62
  }
60
- if (url.pathname === '/api/homeassistant/home-graph/sync' && req.method === 'POST') {
63
+ if (pathname === '/api/homeassistant/home-graph/sync' && req.method === 'POST') {
61
64
  return await this.admin(req, async () => Response.json(await this.context.homeGraphService.syncSnapshot(await this.readBody(req))));
62
65
  }
63
- if (url.pathname === '/api/homeassistant/home-graph/ingest/url' && req.method === 'POST') {
66
+ if (pathname === '/api/homeassistant/home-graph/ingest/url' && req.method === 'POST') {
64
67
  return await this.admin(req, async () => Response.json(await this.context.homeGraphService.ingestUrl(await this.readBody(req))));
65
68
  }
66
- if (url.pathname === '/api/homeassistant/home-graph/ingest/note' && req.method === 'POST') {
69
+ if (pathname === '/api/homeassistant/home-graph/ingest/note' && req.method === 'POST') {
67
70
  return await this.admin(req, async () => Response.json(await this.context.homeGraphService.ingestNote(await this.readBody(req))));
68
71
  }
69
- if (url.pathname === '/api/homeassistant/home-graph/ingest/artifact' && req.method === 'POST') {
72
+ if (pathname === '/api/homeassistant/home-graph/ingest/artifact' && req.method === 'POST') {
70
73
  return await this.admin(req, async () => {
71
74
  if (isArtifactUploadRequest(req)) {
72
75
  const uploaded = await createArtifactFromUploadRequest(this.context.artifactStore, req);
@@ -80,25 +83,25 @@ export class HomeGraphRoutes {
80
83
  return Response.json(await this.context.homeGraphService.ingestArtifact(await this.readBody(req)));
81
84
  });
82
85
  }
83
- if (url.pathname === '/api/homeassistant/home-graph/link' && req.method === 'POST') {
86
+ if (pathname === '/api/homeassistant/home-graph/link' && req.method === 'POST') {
84
87
  return await this.admin(req, async () => Response.json(await this.context.homeGraphService.linkKnowledge(await this.readBody(req))));
85
88
  }
86
- if (url.pathname === '/api/homeassistant/home-graph/unlink' && req.method === 'POST') {
89
+ if (pathname === '/api/homeassistant/home-graph/unlink' && req.method === 'POST') {
87
90
  return await this.admin(req, async () => Response.json(await this.context.homeGraphService.unlinkKnowledge(await this.readBody(req))));
88
91
  }
89
- if (url.pathname === '/api/homeassistant/home-graph/device-passport' && req.method === 'POST') {
92
+ if (pathname === '/api/homeassistant/home-graph/device-passport' && req.method === 'POST') {
90
93
  return await this.admin(req, async () => Response.json(await this.context.homeGraphService.refreshDevicePassport(await this.readBody(req))));
91
94
  }
92
- if (url.pathname === '/api/homeassistant/home-graph/room-page' && req.method === 'POST') {
95
+ if (pathname === '/api/homeassistant/home-graph/room-page' && req.method === 'POST') {
93
96
  return await this.admin(req, async () => Response.json(await this.context.homeGraphService.generateRoomPage(await this.readBody(req))));
94
97
  }
95
- if (url.pathname === '/api/homeassistant/home-graph/packet' && req.method === 'POST') {
98
+ if (pathname === '/api/homeassistant/home-graph/packet' && req.method === 'POST') {
96
99
  return await this.admin(req, async () => Response.json(await this.context.homeGraphService.generatePacket(await this.readBody(req))));
97
100
  }
98
- if (url.pathname === '/api/homeassistant/home-graph/facts/review' && req.method === 'POST') {
101
+ if (pathname === '/api/homeassistant/home-graph/facts/review' && req.method === 'POST') {
99
102
  return await this.admin(req, async () => Response.json(await this.context.homeGraphService.reviewFact(await this.readBody(req))));
100
103
  }
101
- if (url.pathname === '/api/homeassistant/home-graph/import' && req.method === 'POST') {
104
+ if (pathname === '/api/homeassistant/home-graph/import' && req.method === 'POST') {
102
105
  return await this.admin(req, async () => Response.json(await this.context.homeGraphService.importSpace(await this.readBody(req))));
103
106
  }
104
107
  return Response.json({ error: 'Unknown Home Graph route' }, { status: 404 });
@@ -126,6 +129,9 @@ export class HomeGraphRoutes {
126
129
  return body ?? {};
127
130
  }
128
131
  }
132
+ function normalizeHomeGraphPath(pathname) {
133
+ return pathname.length > 1 ? pathname.replace(/\/+$/g, '') : pathname;
134
+ }
129
135
  function readSpaceFromUrl(url) {
130
136
  return {
131
137
  ...(url.searchParams.get('installationId') ? { installationId: url.searchParams.get('installationId') } : {}),
@@ -142,3 +148,14 @@ function readBoolean(url, key, fallback) {
142
148
  return fallback;
143
149
  return !['0', 'false', 'no', 'off'].includes(value.trim().toLowerCase());
144
150
  }
151
+ function readNumber(value) {
152
+ const parsed = typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : NaN;
153
+ return Number.isFinite(parsed) ? Math.max(1, Math.trunc(parsed)) : undefined;
154
+ }
155
+ function readBooleanValue(value) {
156
+ if (typeof value === 'boolean')
157
+ return value;
158
+ if (typeof value !== 'string')
159
+ return undefined;
160
+ return !['0', 'false', 'no', 'off'].includes(value.trim().toLowerCase());
161
+ }
@@ -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;AAErB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AA8FxD,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,CAiFzB;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,CAmCzB;AA2KD,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;AAErB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAqGxD,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,CAmFzB;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;AAuMD,wBAAgB,8BAA8B,CAAC,UAAU,EAAE,yBAAyB,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAWhH"}
@@ -49,15 +49,22 @@ const SHORT_MEANINGFUL_TOKENS = new Set(['ac', 'av', 'dc', 'ha', 'ip', 'ir', 'lg
49
49
  const GENERIC_ANCHOR_TOKENS = new Set([
50
50
  'area',
51
51
  'assistant',
52
+ 'automation',
53
+ 'calendar',
52
54
  'device',
53
55
  'entity',
54
56
  'graph',
55
57
  'home',
58
+ 'library',
56
59
  'living',
57
60
  'media',
58
61
  'media_player',
59
62
  'room',
63
+ 'sensor',
64
+ 'shows',
60
65
  'smart',
66
+ 'storage',
67
+ 'switch',
61
68
  ]);
62
69
  const QUERY_EXPANSIONS = {
63
70
  capability: ['capabilities', 'feature', 'features', 'function', 'functions', 'mode', 'modes', 'spec', 'specs', 'support', 'supports'],
@@ -111,10 +118,12 @@ export function scoreHomeGraphResults(query, sources, nodes, edges, extractionBy
111
118
  return [];
112
119
  const expandedTokens = expandTokens(tokens);
113
120
  const anchors = selectAnchorNodes(tokens, nodes);
114
- const anchorIds = new Set(anchors.map((anchor) => anchor.node.id));
115
- const anchorIdentityTokens = collectAnchorIdentityTokens(anchors.map((anchor) => anchor.node));
121
+ const sourceAnchors = selectSourceAnchors(tokens, anchors.map((anchor) => anchor.node));
122
+ const anchorIds = new Set(sourceAnchors.map((node) => node.id));
123
+ const anchorIdentityTokens = collectAnchorIdentityTokens(sourceAnchors);
116
124
  const sourceLinks = buildSourceLinkIndex(edges);
117
- const useAnchorScope = anchors.length > 0 && anchors.length <= ANCHOR_SCOPE_LIMIT;
125
+ const useAnchorScope = sourceAnchors.length > 0 && sourceAnchors.length <= ANCHOR_SCOPE_LIMIT;
126
+ const objectScopedQuery = sourceAnchors.length > 0;
118
127
  const sourceEvidenceQuery = queryNeedsSourceEvidence(expandedTokens);
119
128
  const integrationQuery = queryMentionsIntegration(tokens);
120
129
  const sourceResults = sources.map((source) => {
@@ -127,7 +136,7 @@ export function scoreHomeGraphResults(query, sources, nodes, edges, extractionBy
127
136
  }
128
137
  const linkedNodeIds = sourceLinks.get(source.id) ?? new Set();
129
138
  const linkedToAnchor = useAnchorScope && intersects(linkedNodeIds, anchorIds);
130
- const anchorIdentityScore = useAnchorScope
139
+ const anchorIdentityScore = objectScopedQuery
131
140
  ? sourceAnchorIdentityScore(anchorIdentityTokens, source, extraction)
132
141
  : 0;
133
142
  const identityScore = scoreFields(tokens, [
@@ -184,7 +193,7 @@ export function scoreHomeGraphResults(query, sources, nodes, edges, extractionBy
184
193
  results = anchoredSourceResults.sort(compareHomeGraphResults);
185
194
  }
186
195
  if (sourceEvidenceQuery) {
187
- results = pruneWeakSourceEvidence(results, tokens, sourceLinks, anchorIds, anchorIdentityTokens, extractionBySourceId);
196
+ results = pruneWeakSourceEvidence(results, tokens, sourceLinks, anchorIds, anchorIdentityTokens, objectScopedQuery, extractionBySourceId);
188
197
  }
189
198
  const strongResults = pruneWeakTokenCoverage(results, tokens);
190
199
  return strongResults.slice(0, Math.max(1, limit));
@@ -194,16 +203,17 @@ export function selectHomeGraphExtractionRepairCandidates(query, sources, nodes,
194
203
  if (tokens.length === 0)
195
204
  return [];
196
205
  const anchors = selectAnchorNodes(tokens, nodes);
197
- const anchorIds = new Set(anchors.map((anchor) => anchor.node.id));
198
- const anchorIdentityTokens = collectAnchorIdentityTokens(anchors.map((anchor) => anchor.node));
206
+ const sourceAnchors = selectSourceAnchors(tokens, anchors.map((anchor) => anchor.node));
207
+ const anchorIds = new Set(sourceAnchors.map((anchor) => anchor.id));
208
+ const anchorIdentityTokens = collectAnchorIdentityTokens(sourceAnchors);
199
209
  const sourceLinks = buildSourceLinkIndex(edges);
200
210
  return sources
201
211
  .map((source) => {
202
212
  if (!source.artifactId || !homeGraphExtractionNeedsRepair(extractionBySourceId(source.id)))
203
213
  return { source, score: 0 };
204
214
  const linkedNodeIds = sourceLinks.get(source.id) ?? new Set();
205
- const linkedToAnchor = anchors.length > 0 && intersects(linkedNodeIds, anchorIds);
206
- const anchorIdentityScore = anchors.length > 0
215
+ const linkedToAnchor = sourceAnchors.length > 0 && intersects(linkedNodeIds, anchorIds);
216
+ const anchorIdentityScore = sourceAnchors.length > 0
207
217
  ? sourceAnchorIdentityScore(anchorIdentityTokens, source, extractionBySourceId(source.id))
208
218
  : 0;
209
219
  const identityScore = scoreFields(tokens, [
@@ -309,15 +319,49 @@ function sourceAnchorIdentityScore(anchorTokens, source, extraction) {
309
319
  function selectAnchorNodes(tokens, nodes) {
310
320
  return nodes.map((node) => {
311
321
  const baseScore = scoreFields(tokens, nodeIdentityFields(node));
322
+ const intentBoost = sourceAnchorIntentBoost(tokens, node);
312
323
  return {
313
324
  node,
314
- score: baseScore > 0 ? baseScore + nodeKindBoost(node.kind) : 0,
325
+ score: baseScore > 0 ? baseScore + nodeKindBoost(node.kind) + intentBoost : 0,
315
326
  };
316
327
  })
317
328
  .filter((entry) => entry.score >= 10)
318
329
  .sort((a, b) => b.score - a.score || a.node.id.localeCompare(b.node.id))
319
330
  .slice(0, 12);
320
331
  }
332
+ function selectSourceAnchors(tokens, nodes) {
333
+ const preferred = nodes.filter((node) => sourceAnchorIntentBoost(tokens, node) >= 0);
334
+ if (preferred.length > 0)
335
+ return preferred.slice(0, 8);
336
+ return nodes.slice(0, ANCHOR_SCOPE_LIMIT);
337
+ }
338
+ function sourceAnchorIntentBoost(tokens, node) {
339
+ const metadata = readRecord(node.metadata);
340
+ const homeAssistant = readRecord(metadata.homeAssistant);
341
+ const domain = typeof metadata.domain === 'string' ? metadata.domain.toLowerCase() : '';
342
+ const platform = typeof metadata.platform === 'string' ? metadata.platform.toLowerCase() : '';
343
+ const objectKind = typeof homeAssistant.objectKind === 'string' ? homeAssistant.objectKind.toLowerCase() : '';
344
+ const text = nodeIdentityFields(node).join(' ').toLowerCase();
345
+ const tvQuery = tokens.some((token) => token === 'tv' || token === 'television' || token === 'media_player');
346
+ if (tvQuery) {
347
+ if (node.kind === 'ha_device' && /\b(tv|television|webos|bravia)\b/.test(text))
348
+ return 80;
349
+ if (node.kind === 'ha_entity' && (domain === 'media_player' || platform === 'webostv'))
350
+ return 70;
351
+ if (node.kind === 'ha_integration' && (platform === 'webostv' || text.includes('webos')))
352
+ return 40;
353
+ if (domain === 'calendar' || domain === 'sensor' || domain === 'automation' || domain === 'switch'
354
+ || objectKind === 'automation' || node.kind === 'ha_automation')
355
+ return -80;
356
+ }
357
+ if (node.kind === 'ha_device')
358
+ return 30;
359
+ if (node.kind === 'ha_entity')
360
+ return 10;
361
+ if (node.kind === 'ha_integration' && queryMentionsIntegration(tokens))
362
+ return 8;
363
+ return node.kind === 'ha_integration' ? -10 : 0;
364
+ }
321
365
  function buildSourceLinkIndex(edges) {
322
366
  const links = new Map();
323
367
  for (const edge of edges) {
@@ -419,7 +463,7 @@ function isHomeAssistantIntegrationSource(source) {
419
463
  || tags.includes('documentation')
420
464
  || sourceKind === 'documentation-candidate';
421
465
  }
422
- function pruneWeakSourceEvidence(results, tokens, sourceLinks, anchorIds, anchorIdentityTokens, extractionBySourceId) {
466
+ function pruneWeakSourceEvidence(results, tokens, sourceLinks, anchorIds, anchorIdentityTokens, objectScopedQuery, extractionBySourceId) {
423
467
  const sourceResults = results.filter((result) => result.source);
424
468
  if (sourceResults.length === 0)
425
469
  return [...results];
@@ -434,7 +478,7 @@ function pruneWeakSourceEvidence(results, tokens, sourceLinks, anchorIds, anchor
434
478
  const coverage = tokenCoverage(tokens, resultText(result));
435
479
  return linkedToAnchor
436
480
  || (matchesAnchorIdentity && coverage >= 1)
437
- || coverage >= Math.min(2, tokens.length);
481
+ || (!objectScopedQuery && coverage >= Math.min(2, tokens.length));
438
482
  });
439
483
  return strongSourceResults;
440
484
  }
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- let version = '0.27.0';
3
+ let version = '0.27.1';
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.27.0",
3
+ "version": "0.27.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/mgd34msu/goodvibes-sdk.git"