@pellux/goodvibes-sdk 0.28.12 → 0.28.14
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.
- package/dist/_internal/contracts/artifacts/operator-contract.json +7 -1
- package/dist/_internal/contracts/generated/foundation-metadata.d.ts +1 -1
- package/dist/_internal/contracts/generated/foundation-metadata.js +1 -1
- package/dist/_internal/contracts/generated/operator-contract.d.ts.map +1 -1
- package/dist/_internal/contracts/generated/operator-contract.js +7 -1
- package/dist/_internal/platform/control-plane/method-catalog-homegraph.d.ts.map +1 -1
- package/dist/_internal/platform/control-plane/method-catalog-homegraph.js +1 -0
- package/dist/_internal/platform/control-plane/method-catalog-knowledge.d.ts.map +1 -1
- package/dist/_internal/platform/control-plane/method-catalog-knowledge.js +2 -1
- package/dist/_internal/platform/knowledge/home-graph/ask.d.ts +1 -1
- package/dist/_internal/platform/knowledge/home-graph/ask.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/home-graph/ask.js +3 -84
- package/dist/_internal/platform/knowledge/home-graph/generated-pages.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/home-graph/generated-pages.js +16 -0
- package/dist/_internal/platform/knowledge/home-graph/service.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/home-graph/service.js +3 -5
- package/dist/_internal/platform/knowledge/home-graph/types.d.ts +2 -0
- package/dist/_internal/platform/knowledge/home-graph/types.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/semantic/answer-fallback.d.ts +11 -0
- package/dist/_internal/platform/knowledge/semantic/answer-fallback.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/semantic/answer-fallback.js +31 -0
- package/dist/_internal/platform/knowledge/semantic/answer.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/semantic/answer.js +18 -23
- package/dist/_internal/platform/knowledge/semantic/enrichment.js +6 -1
- package/dist/_internal/platform/knowledge/semantic/gap-repair.d.ts +2 -0
- package/dist/_internal/platform/knowledge/semantic/gap-repair.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/semantic/gap-repair.js +262 -71
- package/dist/_internal/platform/knowledge/semantic/self-improvement-tasks.d.ts +12 -0
- package/dist/_internal/platform/knowledge/semantic/self-improvement-tasks.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/semantic/self-improvement-tasks.js +79 -0
- package/dist/_internal/platform/knowledge/semantic/self-improvement.d.ts +4 -0
- package/dist/_internal/platform/knowledge/semantic/self-improvement.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/semantic/self-improvement.js +107 -89
- package/dist/_internal/platform/knowledge/semantic/service.d.ts.map +1 -1
- package/dist/_internal/platform/knowledge/semantic/service.js +8 -0
- package/dist/_internal/platform/knowledge/semantic/timeouts.d.ts +4 -0
- package/dist/_internal/platform/knowledge/semantic/timeouts.d.ts.map +1 -0
- package/dist/_internal/platform/knowledge/semantic/timeouts.js +26 -0
- package/dist/_internal/platform/knowledge/semantic/types.d.ts +6 -0
- package/dist/_internal/platform/knowledge/semantic/types.d.ts.map +1 -1
- package/dist/_internal/platform/version.js +1 -1
- package/package.json +1 -1
|
@@ -24,6 +24,8 @@ export interface WebGapRepairOptions {
|
|
|
24
24
|
readonly searchService: GapRepairSearch;
|
|
25
25
|
readonly ingestService: GapRepairIngest;
|
|
26
26
|
readonly maxResults?: number;
|
|
27
|
+
readonly maxSearches?: number;
|
|
28
|
+
readonly maxSources?: number;
|
|
27
29
|
readonly minDistinctDomains?: number;
|
|
28
30
|
readonly minConfidence?: number;
|
|
29
31
|
readonly maxIngest?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gap-repair.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/gap-repair.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EAElB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EACV,4BAA4B,EAG7B,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"gap-repair.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/gap-repair.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EAElB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EACV,4BAA4B,EAG7B,MAAM,YAAY,CAAC;AAIpB,UAAU,eAAe;IACvB,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC/D;AAED,UAAU,eAAe;IACvB,SAAS,CAAC,KAAK,EAAE;QACf,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,mBAAmB,CAAC;QAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;QACrC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC7C,GAAG,OAAO,CAAC;QAAE,QAAQ,CAAC,MAAM,EAAE;YAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;CACpF;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,aAAa,EAAE,eAAe,CAAC;IACxC,QAAQ,CAAC,aAAa,EAAE,eAAe,CAAC;IACxC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AA0BD,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,mBAAmB,GAAG,4BAA4B,CAExG"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { canonicalizeUri } from '../internal.js';
|
|
2
|
+
import { withTimeout } from './timeouts.js';
|
|
2
3
|
import { readString, scoreSemanticText, tokenizeSemanticQuery, uniqueStrings } from './utils.js';
|
|
3
4
|
export function createWebKnowledgeGapRepairer(options) {
|
|
4
5
|
return async (request) => repairKnowledgeGapsWithWeb(request, options);
|
|
5
6
|
}
|
|
6
7
|
async function repairKnowledgeGapsWithWeb(request, options) {
|
|
7
|
-
const
|
|
8
|
-
if (
|
|
8
|
+
const queries = buildGapRepairQueries(request);
|
|
9
|
+
if (queries.length === 0) {
|
|
9
10
|
return {
|
|
10
11
|
searched: false,
|
|
11
12
|
ingestedSourceIds: [],
|
|
@@ -13,47 +14,77 @@ async function repairKnowledgeGapsWithWeb(request, options) {
|
|
|
13
14
|
reason: 'No concrete subject was available for gap repair.',
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
const existing = existingSources(request);
|
|
18
|
+
const sourceLimit = Math.max(2, Math.min(5, request.maxSources ?? options.maxSources ?? options.maxIngest ?? 5));
|
|
19
|
+
const searchLimit = Math.max(1, Math.min(5, options.maxSearches ?? queries.length));
|
|
20
|
+
const existingCandidates = selectExistingRepairSources(request, options, sourceLimit);
|
|
21
|
+
const searchResults = new Map();
|
|
22
|
+
const providerIds = new Set();
|
|
23
|
+
let lastError;
|
|
24
|
+
for (const query of queries.slice(0, searchLimit)) {
|
|
25
|
+
if (request.signal?.aborted || deadlineExceeded(request.deadlineAt))
|
|
26
|
+
break;
|
|
27
|
+
try {
|
|
28
|
+
const timeoutMs = operationTimeout(request.deadlineAt, options.searchTimeoutMs ?? 8_000);
|
|
29
|
+
if (timeoutMs <= 0)
|
|
30
|
+
break;
|
|
31
|
+
const response = await withTimeout(options.searchService.search({
|
|
32
|
+
query,
|
|
33
|
+
maxResults: Math.max(sourceLimit, Math.min(8, options.maxResults ?? sourceLimit)),
|
|
34
|
+
verbosity: 'snippets',
|
|
35
|
+
safeSearch: 'moderate',
|
|
36
|
+
trustedHosts: trustedHostsForRepair(request),
|
|
37
|
+
metadata: {
|
|
38
|
+
purpose: 'knowledge-gap-repair',
|
|
39
|
+
knowledgeSpaceId: request.spaceId,
|
|
40
|
+
},
|
|
41
|
+
}), Math.max(1_000, timeoutMs), 'Semantic gap repair search timed out.');
|
|
42
|
+
if (response.providerId)
|
|
43
|
+
providerIds.add(response.providerId);
|
|
44
|
+
for (const result of response.results) {
|
|
45
|
+
const canonical = canonicalizeUri(result.url);
|
|
46
|
+
if (!canonical || searchResults.has(canonical))
|
|
47
|
+
continue;
|
|
48
|
+
searchResults.set(canonical, { ...result, searchQuery: query, searchProviderId: response.providerId });
|
|
49
|
+
}
|
|
50
|
+
const partial = mergeRepairCandidates(existingCandidates, selectGapRepairCandidates([...searchResults.values()], existing, options, request, sourceLimit), sourceLimit);
|
|
51
|
+
if (hasEnoughRepairEvidence(partial, options))
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
56
|
+
if (deadlineExceeded(request.deadlineAt))
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
28
59
|
}
|
|
29
|
-
|
|
60
|
+
const allResults = [...searchResults.values()];
|
|
61
|
+
const candidates = mergeRepairCandidates(existingCandidates, selectGapRepairCandidates(allResults, existing, options, request, sourceLimit), sourceLimit);
|
|
62
|
+
if (!hasEnoughRepairEvidence(candidates, options)) {
|
|
30
63
|
return {
|
|
31
64
|
searched: true,
|
|
32
|
-
query,
|
|
65
|
+
query: queries[0],
|
|
66
|
+
evidenceSufficient: false,
|
|
67
|
+
acceptedSourceIds: existingCandidates.map((candidate) => candidate.existingSourceId).filter((id) => Boolean(id)),
|
|
33
68
|
ingestedSourceIds: [],
|
|
34
|
-
skippedUrls:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
canonicalizeUri(source.sourceUri ?? ''),
|
|
41
|
-
].filter((value) => Boolean(value))));
|
|
42
|
-
const candidates = selectGapRepairCandidates(response.results, existing, options, query, request);
|
|
43
|
-
if (candidates.length < Math.max(2, options.minDistinctDomains ?? 2)) {
|
|
44
|
-
return {
|
|
45
|
-
searched: true,
|
|
46
|
-
query,
|
|
47
|
-
ingestedSourceIds: [],
|
|
48
|
-
skippedUrls: response.results.map((result) => result.url),
|
|
49
|
-
sourceAssessments: buildSourceAssessments(response.results, candidates, existing, options, query, request),
|
|
50
|
-
reason: 'Fewer than two distinct external sources were found for source-backed gap repair.',
|
|
69
|
+
skippedUrls: allResults.map((result) => result.url),
|
|
70
|
+
sourceAssessments: [
|
|
71
|
+
...existingCandidates.map((candidate) => candidateToAssessment(candidate, true)),
|
|
72
|
+
...buildSourceAssessments(allResults, candidates, existing, options, request),
|
|
73
|
+
],
|
|
74
|
+
reason: lastError ?? 'Insufficient distinct source-backed evidence was found for gap repair.',
|
|
51
75
|
};
|
|
52
76
|
}
|
|
53
77
|
const ingestedSourceIds = [];
|
|
54
78
|
const skippedUrls = [];
|
|
55
|
-
|
|
79
|
+
const existingSourceIds = candidates.map((candidate) => candidate.existingSourceId).filter((id) => Boolean(id));
|
|
80
|
+
const newCandidates = candidates.filter((candidate) => !candidate.existingSourceId);
|
|
81
|
+
for (const result of newCandidates.slice(0, Math.max(0, Math.min(sourceLimit - existingSourceIds.length, options.maxIngest ?? sourceLimit)))) {
|
|
82
|
+
if (request.signal?.aborted || deadlineExceeded(request.deadlineAt))
|
|
83
|
+
break;
|
|
56
84
|
try {
|
|
85
|
+
const timeoutMs = operationTimeout(request.deadlineAt, options.ingestTimeoutMs ?? 10_000);
|
|
86
|
+
if (timeoutMs <= 0)
|
|
87
|
+
break;
|
|
57
88
|
const ingested = await withTimeout(options.ingestService.ingestUrl({
|
|
58
89
|
url: result.url,
|
|
59
90
|
...(result.title ? { title: result.title } : {}),
|
|
@@ -64,8 +95,9 @@ async function repairKnowledgeGapsWithWeb(request, options) {
|
|
|
64
95
|
knowledgeSpaceId: request.spaceId,
|
|
65
96
|
sourceDiscovery: {
|
|
66
97
|
purpose: 'semantic-gap-repair',
|
|
67
|
-
query,
|
|
68
|
-
|
|
98
|
+
query: result.searchQuery,
|
|
99
|
+
searchQueries: queries.slice(0, searchLimit),
|
|
100
|
+
providerId: result.searchProviderId ?? [...providerIds][0],
|
|
69
101
|
gapIds: request.gaps.map((gap) => gap.id),
|
|
70
102
|
gapQuestions: request.gaps.map((gap) => gap.title),
|
|
71
103
|
originalSourceIds: request.sources.map((source) => source.id),
|
|
@@ -76,11 +108,12 @@ async function repairKnowledgeGapsWithWeb(request, options) {
|
|
|
76
108
|
sourceDomain: result.domain ?? safeDomain(result.url),
|
|
77
109
|
trustReason: result.reasons.join(', '),
|
|
78
110
|
agreementSourceCount: candidates.length,
|
|
111
|
+
checkedSourceLimit: sourceLimit,
|
|
79
112
|
selectedUrl: result.url,
|
|
80
113
|
searchedAt: Date.now(),
|
|
81
114
|
},
|
|
82
115
|
},
|
|
83
|
-
}), Math.max(1_000,
|
|
116
|
+
}), Math.max(1_000, timeoutMs), 'Semantic gap repair source ingest timed out.');
|
|
84
117
|
if (ingested.source.status === 'indexed' || ingested.source.status === 'pending') {
|
|
85
118
|
ingestedSourceIds.push(ingested.source.id);
|
|
86
119
|
}
|
|
@@ -92,27 +125,35 @@ async function repairKnowledgeGapsWithWeb(request, options) {
|
|
|
92
125
|
}
|
|
93
126
|
return {
|
|
94
127
|
searched: true,
|
|
95
|
-
query,
|
|
128
|
+
query: queries[0],
|
|
129
|
+
evidenceSufficient: true,
|
|
130
|
+
acceptedSourceIds: uniqueStrings([...existingSourceIds, ...ingestedSourceIds]),
|
|
96
131
|
ingestedSourceIds,
|
|
97
132
|
skippedUrls,
|
|
98
|
-
sourceAssessments:
|
|
99
|
-
|
|
133
|
+
sourceAssessments: [
|
|
134
|
+
...existingCandidates.map((candidate) => candidateToAssessment(candidate, candidates.some((entry) => entry.url === candidate.url))),
|
|
135
|
+
...buildSourceAssessments(allResults, candidates, existing, options, request),
|
|
136
|
+
],
|
|
137
|
+
...(existingSourceIds.length + ingestedSourceIds.length < 1 ? { reason: 'Gap repair searched but did not accept usable sources.' } : {}),
|
|
100
138
|
};
|
|
101
139
|
}
|
|
102
|
-
function
|
|
140
|
+
function buildGapRepairQueries(request) {
|
|
103
141
|
const subject = bestSubject(request);
|
|
104
142
|
if (!subject)
|
|
105
|
-
return
|
|
106
|
-
const gapTerms = uniqueStrings(request.gaps.flatMap((gap) => [
|
|
143
|
+
return [];
|
|
144
|
+
const gapTerms = clampSearchTerms(uniqueStrings(request.gaps.flatMap((gap) => [
|
|
107
145
|
gap.title,
|
|
108
146
|
gap.summary,
|
|
109
147
|
readString(gap.metadata.reason),
|
|
110
|
-
])).join(' ');
|
|
148
|
+
])).join(' '));
|
|
149
|
+
const profileTerms = inferGapProfileTerms(request);
|
|
111
150
|
return uniqueStrings([
|
|
112
|
-
subject,
|
|
113
|
-
|
|
114
|
-
'
|
|
115
|
-
|
|
151
|
+
[subject, gapTerms, 'official specifications'].filter(Boolean).join(' '),
|
|
152
|
+
[subject, profileTerms, 'product specifications'].filter(Boolean).join(' '),
|
|
153
|
+
[subject, profileTerms, 'features ports connectivity audio display'].filter(Boolean).join(' '),
|
|
154
|
+
[subject, 'manufacturer product page specifications'].join(' '),
|
|
155
|
+
[subject, 'datasheet manual specifications'].join(' '),
|
|
156
|
+
].map((query) => query.replace(/\s+/g, ' ').trim()).filter(Boolean));
|
|
116
157
|
}
|
|
117
158
|
function bestSubject(request) {
|
|
118
159
|
const linked = request.linkedObjects[0];
|
|
@@ -129,8 +170,39 @@ function bestSubject(request) {
|
|
|
129
170
|
source?.title,
|
|
130
171
|
]).join(' ') || null;
|
|
131
172
|
}
|
|
132
|
-
function
|
|
133
|
-
const
|
|
173
|
+
function inferGapProfileTerms(request) {
|
|
174
|
+
const text = request.gaps.map((gap) => `${gap.title} ${gap.summary ?? ''}`).join(' ').toLowerCase();
|
|
175
|
+
const terms = [];
|
|
176
|
+
if (/\b(port|ports|hdmi|usb|optical|rf|antenna|ethernet|rs-?232|composite|component|input|output|i\/o)\b/.test(text)) {
|
|
177
|
+
terms.push('ports inputs outputs connectivity');
|
|
178
|
+
}
|
|
179
|
+
if (/\b(bluetooth|wifi|wi-fi|wireless|network)\b/.test(text))
|
|
180
|
+
terms.push('wireless bluetooth wi-fi network');
|
|
181
|
+
if (/\b(refresh|hz|hdr|dolby|vision|gaming|vrr|allm|freesync)\b/.test(text))
|
|
182
|
+
terms.push('refresh rate hdr gaming vrr allm');
|
|
183
|
+
if (/\b(audio|speaker|sound|earc|arc)\b/.test(text))
|
|
184
|
+
terms.push('audio speakers earc arc');
|
|
185
|
+
if (/\b(display|screen|resolution|panel|lcd|oled|qled|nanocell)\b/.test(text))
|
|
186
|
+
terms.push('display resolution panel');
|
|
187
|
+
return uniqueStrings([
|
|
188
|
+
...terms,
|
|
189
|
+
...tokenizeSemanticQuery(text)
|
|
190
|
+
.filter((token) => token.length >= 3)
|
|
191
|
+
.filter((token) => !['what', 'does', 'have', 'which', 'with', 'from', 'that', 'this', 'current', 'source', 'text'].includes(token))
|
|
192
|
+
.slice(0, 12),
|
|
193
|
+
]).join(' ');
|
|
194
|
+
}
|
|
195
|
+
function clampSearchTerms(value) {
|
|
196
|
+
return tokenizeSemanticQuery(value).slice(0, 24).join(' ');
|
|
197
|
+
}
|
|
198
|
+
function existingSources(request) {
|
|
199
|
+
return new Set(request.sources.flatMap((source) => [
|
|
200
|
+
canonicalizeUri(source.canonicalUri ?? ''),
|
|
201
|
+
canonicalizeUri(source.sourceUri ?? ''),
|
|
202
|
+
].filter((value) => Boolean(value))));
|
|
203
|
+
}
|
|
204
|
+
function selectGapRepairCandidates(results, existingCanonicalUris, options, request, sourceLimit) {
|
|
205
|
+
const tokens = tokenizeSemanticQuery([bestSubject(request), inferGapProfileTerms(request)].filter(Boolean).join(' '));
|
|
134
206
|
const minimumConfidence = Math.max(1, Math.min(100, options.minConfidence ?? 70));
|
|
135
207
|
const byDomain = new Map();
|
|
136
208
|
for (const result of results) {
|
|
@@ -143,14 +215,52 @@ function selectGapRepairCandidates(results, existingCanonicalUris, options, quer
|
|
|
143
215
|
const domain = result.domain ?? safeDomain(result.url);
|
|
144
216
|
if (!domain || byDomain.has(domain))
|
|
145
217
|
continue;
|
|
146
|
-
const assessment = assessGapRepairSource(result,
|
|
218
|
+
const assessment = assessGapRepairSource(result, result.searchQuery, request);
|
|
147
219
|
if (assessment.confidence < minimumConfidence)
|
|
148
220
|
continue;
|
|
149
221
|
byDomain.set(domain, { ...result, confidence: assessment.confidence, reasons: assessment.reasons });
|
|
150
222
|
}
|
|
151
223
|
return [...byDomain.values()]
|
|
152
|
-
.sort(
|
|
153
|
-
.slice(0,
|
|
224
|
+
.sort(compareRepairCandidates)
|
|
225
|
+
.slice(0, sourceLimit);
|
|
226
|
+
}
|
|
227
|
+
function selectExistingRepairSources(request, options, sourceLimit) {
|
|
228
|
+
const minimumConfidence = Math.max(1, Math.min(100, options.minConfidence ?? 70));
|
|
229
|
+
const byDomain = new Map();
|
|
230
|
+
for (const source of request.sources) {
|
|
231
|
+
if (source.status !== 'indexed' || isGeneratedOrSyntheticSource(source))
|
|
232
|
+
continue;
|
|
233
|
+
const url = source.sourceUri ?? source.canonicalUri;
|
|
234
|
+
if (!url)
|
|
235
|
+
continue;
|
|
236
|
+
const domain = safeDomain(url);
|
|
237
|
+
if (!domain || byDomain.has(domain))
|
|
238
|
+
continue;
|
|
239
|
+
const snippet = [source.summary, source.description, source.tags.join(' ')].filter(Boolean).join(' ');
|
|
240
|
+
const result = {
|
|
241
|
+
rank: 0,
|
|
242
|
+
url,
|
|
243
|
+
domain,
|
|
244
|
+
displayUrl: url,
|
|
245
|
+
type: 'organic',
|
|
246
|
+
providerId: 'indexed',
|
|
247
|
+
metadata: source.metadata,
|
|
248
|
+
searchQuery: request.query,
|
|
249
|
+
searchProviderId: 'indexed',
|
|
250
|
+
...(source.title ? { title: source.title } : {}),
|
|
251
|
+
...(snippet ? { snippet } : {}),
|
|
252
|
+
};
|
|
253
|
+
const assessment = assessGapRepairSource(result, request.query, request);
|
|
254
|
+
if (assessment.confidence < minimumConfidence && !isOfficialOrVendorSource(assessment.reasons))
|
|
255
|
+
continue;
|
|
256
|
+
byDomain.set(domain, {
|
|
257
|
+
...result,
|
|
258
|
+
existingSourceId: source.id,
|
|
259
|
+
confidence: Math.max(assessment.confidence, isOfficialOrVendorSource(assessment.reasons) ? 82 : assessment.confidence),
|
|
260
|
+
reasons: uniqueStrings([...assessment.reasons, 'already-indexed']),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return [...byDomain.values()].sort(compareRepairCandidates).slice(0, sourceLimit);
|
|
154
264
|
}
|
|
155
265
|
function assessGapRepairSource(result, query, request) {
|
|
156
266
|
const searchable = [result.title, result.snippet, result.url, result.domain].filter(Boolean).join(' ').toLowerCase();
|
|
@@ -194,26 +304,61 @@ function assessGapRepairSource(result, query, request) {
|
|
|
194
304
|
reasons.push('source-purpose');
|
|
195
305
|
}
|
|
196
306
|
if (domain && identities.manufacturers.some((manufacturer) => manufacturer.length >= 2 && domain.includes(manufacturer.toLowerCase()))) {
|
|
197
|
-
score +=
|
|
307
|
+
score += 28;
|
|
198
308
|
reasons.push('manufacturer-domain');
|
|
199
309
|
}
|
|
310
|
+
if (domain && isOfficialVendorDomain(domain, identities.manufacturers)) {
|
|
311
|
+
score += 28;
|
|
312
|
+
reasons.push('official-vendor-domain');
|
|
313
|
+
}
|
|
200
314
|
return {
|
|
201
315
|
url: result.url,
|
|
202
316
|
...(result.title ? { title: result.title } : {}),
|
|
203
317
|
...(domain ? { domain } : {}),
|
|
204
318
|
rank: result.rank,
|
|
319
|
+
query: result.searchQuery,
|
|
205
320
|
confidence: Math.max(0, Math.min(100, score)),
|
|
206
321
|
reasons,
|
|
207
322
|
...(reasons.length > 0 ? { trustReason: reasons.join(', ') } : {}),
|
|
208
323
|
};
|
|
209
324
|
}
|
|
210
|
-
function
|
|
325
|
+
function mergeRepairCandidates(existingCandidates, searchCandidates, sourceLimit) {
|
|
326
|
+
const byCanonical = new Map();
|
|
327
|
+
for (const candidate of [...existingCandidates, ...searchCandidates]) {
|
|
328
|
+
const key = canonicalizeUri(candidate.url) ?? candidate.url;
|
|
329
|
+
const current = byCanonical.get(key);
|
|
330
|
+
if (!current || compareRepairCandidates(candidate, current) < 0)
|
|
331
|
+
byCanonical.set(key, candidate);
|
|
332
|
+
}
|
|
333
|
+
return [...byCanonical.values()].sort(compareRepairCandidates).slice(0, sourceLimit);
|
|
334
|
+
}
|
|
335
|
+
function hasEnoughRepairEvidence(candidates, options) {
|
|
336
|
+
if (candidates.some((candidate) => candidate.reasons.includes('official-vendor-domain')))
|
|
337
|
+
return true;
|
|
338
|
+
const required = Math.max(2, options.minDistinctDomains ?? 2);
|
|
339
|
+
return new Set(candidates.map((candidate) => candidate.domain ?? safeDomain(candidate.url)).filter(Boolean)).size >= required;
|
|
340
|
+
}
|
|
341
|
+
function compareRepairCandidates(left, right) {
|
|
342
|
+
return sourceAuthorityScore(right) - sourceAuthorityScore(left)
|
|
343
|
+
|| right.confidence - left.confidence
|
|
344
|
+
|| left.rank - right.rank;
|
|
345
|
+
}
|
|
346
|
+
function sourceAuthorityScore(candidate) {
|
|
347
|
+
if (candidate.reasons.includes('official-vendor-domain'))
|
|
348
|
+
return 3;
|
|
349
|
+
if (candidate.reasons.includes('manufacturer-domain'))
|
|
350
|
+
return 2;
|
|
351
|
+
if (candidate.existingSourceId)
|
|
352
|
+
return 1;
|
|
353
|
+
return 0;
|
|
354
|
+
}
|
|
355
|
+
function buildSourceAssessments(results, candidates, existingCanonicalUris, options, request) {
|
|
211
356
|
const accepted = new Set(candidates.map((candidate) => canonicalizeUri(candidate.url)));
|
|
212
357
|
const acceptedDomains = new Set(candidates.map((candidate) => candidate.domain ?? safeDomain(candidate.url)).filter(Boolean));
|
|
213
358
|
const minimumConfidence = Math.max(1, Math.min(100, options.minConfidence ?? 70));
|
|
214
|
-
const tokens = tokenizeSemanticQuery(
|
|
359
|
+
const tokens = tokenizeSemanticQuery([bestSubject(request), inferGapProfileTerms(request)].filter(Boolean).join(' '));
|
|
215
360
|
return results.map((result) => {
|
|
216
|
-
const assessment = assessGapRepairSource(result,
|
|
361
|
+
const assessment = assessGapRepairSource(result, result.searchQuery, request);
|
|
217
362
|
const canonical = canonicalizeUri(result.url);
|
|
218
363
|
const domain = result.domain ?? safeDomain(result.url);
|
|
219
364
|
const isAccepted = Boolean(canonical && accepted.has(canonical));
|
|
@@ -237,6 +382,20 @@ function buildSourceAssessments(results, candidates, existingCanonicalUris, opti
|
|
|
237
382
|
};
|
|
238
383
|
});
|
|
239
384
|
}
|
|
385
|
+
function candidateToAssessment(candidate, accepted) {
|
|
386
|
+
return {
|
|
387
|
+
url: candidate.url,
|
|
388
|
+
...(candidate.title ? { title: candidate.title } : {}),
|
|
389
|
+
...(candidate.domain ? { domain: candidate.domain } : {}),
|
|
390
|
+
rank: candidate.rank,
|
|
391
|
+
query: candidate.searchQuery,
|
|
392
|
+
accepted,
|
|
393
|
+
confidence: candidate.confidence,
|
|
394
|
+
reasons: candidate.reasons,
|
|
395
|
+
trustReason: candidate.reasons.join(', '),
|
|
396
|
+
...(accepted ? {} : { rejectionReason: 'not-selected' }),
|
|
397
|
+
};
|
|
398
|
+
}
|
|
240
399
|
function sourceIdentityHints(request) {
|
|
241
400
|
const subjects = uniqueStrings([
|
|
242
401
|
...request.linkedObjects.flatMap((node) => [node.title, ...node.aliases]),
|
|
@@ -251,9 +410,26 @@ function sourceIdentityHints(request) {
|
|
|
251
410
|
const manufacturers = uniqueStrings(request.linkedObjects.flatMap((node) => [
|
|
252
411
|
readString(node.metadata.manufacturer),
|
|
253
412
|
readString(node.metadata.vendor),
|
|
254
|
-
]).concat(request.sources.flatMap((source) => manufacturerHints(`${source.title ?? ''} ${source.sourceUri ?? ''} ${source.canonicalUri ?? ''}`)))
|
|
413
|
+
]).concat(request.sources.flatMap((source) => manufacturerHints(`${source.title ?? ''} ${source.sourceUri ?? ''} ${source.canonicalUri ?? ''}`)))
|
|
414
|
+
.map((manufacturer) => manufacturer?.trim().toLowerCase())
|
|
415
|
+
.filter((manufacturer) => Boolean(manufacturer)));
|
|
255
416
|
return { models, manufacturers, subjects };
|
|
256
417
|
}
|
|
418
|
+
function trustedHostsForRepair(request) {
|
|
419
|
+
const manufacturers = sourceIdentityHints(request).manufacturers;
|
|
420
|
+
const hosts = [];
|
|
421
|
+
if (manufacturers.includes('lg'))
|
|
422
|
+
hosts.push('lg.com', 'www.lg.com');
|
|
423
|
+
if (manufacturers.includes('sony'))
|
|
424
|
+
hosts.push('sony.com', 'www.sony.com');
|
|
425
|
+
if (manufacturers.includes('samsung'))
|
|
426
|
+
hosts.push('samsung.com', 'www.samsung.com');
|
|
427
|
+
if (manufacturers.includes('apple'))
|
|
428
|
+
hosts.push('apple.com', 'support.apple.com');
|
|
429
|
+
if (manufacturers.includes('espressif') || manufacturers.includes('esp32'))
|
|
430
|
+
hosts.push('espressif.com', 'docs.espressif.com');
|
|
431
|
+
return uniqueStrings(hosts);
|
|
432
|
+
}
|
|
257
433
|
function isGenericSubject(value) {
|
|
258
434
|
return /^(tv|television|device|manual|user guide|owner manual|home assistant|service|provider|integration)$/i.test(value.trim());
|
|
259
435
|
}
|
|
@@ -264,6 +440,34 @@ function manufacturerHints(value) {
|
|
|
264
440
|
const hints = value.match(/\b(lg|samsung|sony|vizio|tcl|hisense|philips|panasonic|kasa|tp-link|ecobee|honeywell|ring|arlo|nest|eufy|aqara|sonoff|shelly|lutron|leviton|ikea|bosch|ge|whirlpool|frigidaire|apple|espressif|esp32|nabu casa|home assistant)\b/gi) ?? [];
|
|
265
441
|
return uniqueStrings(hints.map((hint) => hint.toLowerCase()));
|
|
266
442
|
}
|
|
443
|
+
function isGeneratedOrSyntheticSource(source) {
|
|
444
|
+
return source.tags.includes('generated-page')
|
|
445
|
+
|| source.metadata.projectionKind === 'device-passport'
|
|
446
|
+
|| source.metadata.projectionKind === 'room-page';
|
|
447
|
+
}
|
|
448
|
+
function isOfficialVendorDomain(domain, manufacturers) {
|
|
449
|
+
const lower = domain.toLowerCase();
|
|
450
|
+
if (manufacturers.some((manufacturer) => manufacturer === 'lg' && /(^|\.)lg\.com$/.test(lower)))
|
|
451
|
+
return true;
|
|
452
|
+
if (manufacturers.some((manufacturer) => manufacturer === 'sony' && /(^|\.)sony\.(com|net)$/.test(lower)))
|
|
453
|
+
return true;
|
|
454
|
+
if (manufacturers.some((manufacturer) => manufacturer === 'samsung' && /(^|\.)samsung\.com$/.test(lower)))
|
|
455
|
+
return true;
|
|
456
|
+
if (manufacturers.some((manufacturer) => manufacturer === 'apple' && /(^|\.)apple\.com$/.test(lower)))
|
|
457
|
+
return true;
|
|
458
|
+
return manufacturers.some((manufacturer) => manufacturer.length >= 4 && lower.includes(manufacturer.toLowerCase()) && /\b(support|docs?|developer|product)\b/.test(lower));
|
|
459
|
+
}
|
|
460
|
+
function isOfficialOrVendorSource(reasons) {
|
|
461
|
+
return reasons.includes('official-vendor-domain') || reasons.includes('manufacturer-domain');
|
|
462
|
+
}
|
|
463
|
+
function deadlineExceeded(deadlineAt) {
|
|
464
|
+
return typeof deadlineAt === 'number' && Date.now() >= deadlineAt;
|
|
465
|
+
}
|
|
466
|
+
function operationTimeout(deadlineAt, fallbackMs) {
|
|
467
|
+
if (typeof deadlineAt !== 'number')
|
|
468
|
+
return fallbackMs;
|
|
469
|
+
return Math.min(fallbackMs, Math.max(0, deadlineAt - Date.now()));
|
|
470
|
+
}
|
|
267
471
|
function hasIdentity(searchable, value) {
|
|
268
472
|
const normalized = value.trim().toLowerCase();
|
|
269
473
|
if (!normalized)
|
|
@@ -282,19 +486,6 @@ function safeDomain(url) {
|
|
|
282
486
|
return undefined;
|
|
283
487
|
}
|
|
284
488
|
}
|
|
285
|
-
async function withTimeout(promise, timeoutMs, message) {
|
|
286
|
-
let timer;
|
|
287
|
-
const timeout = new Promise((_, reject) => {
|
|
288
|
-
timer = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
289
|
-
});
|
|
290
|
-
try {
|
|
291
|
-
return await Promise.race([promise, timeout]);
|
|
292
|
-
}
|
|
293
|
-
finally {
|
|
294
|
-
if (timer)
|
|
295
|
-
clearTimeout(timer);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
489
|
async function yieldToEventLoop() {
|
|
299
490
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
300
491
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { KnowledgeNodeRecord, KnowledgeRefinementTaskRecord, KnowledgeRefinementTaskState, KnowledgeRefinementTaskTrigger } from '../types.js';
|
|
2
|
+
import type { KnowledgeStore } from '../store.js';
|
|
3
|
+
export interface RefinementTaskGapContext {
|
|
4
|
+
readonly gap: KnowledgeNodeRecord;
|
|
5
|
+
readonly sources: readonly {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
}[];
|
|
8
|
+
readonly linkedObjects: readonly KnowledgeNodeRecord[];
|
|
9
|
+
}
|
|
10
|
+
export declare function upsertRefinementTaskForGap(store: KnowledgeStore, spaceId: string, context: RefinementTaskGapContext, trigger: KnowledgeRefinementTaskTrigger, state: KnowledgeRefinementTaskState, message: string, data?: Record<string, unknown>): Promise<KnowledgeRefinementTaskRecord>;
|
|
11
|
+
export declare function updateRefinementTask(store: KnowledgeStore, task: KnowledgeRefinementTaskRecord, state: KnowledgeRefinementTaskState, message: string, data?: Record<string, unknown>): Promise<KnowledgeRefinementTaskRecord>;
|
|
12
|
+
//# sourceMappingURL=self-improvement-tasks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-improvement-tasks.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/self-improvement-tasks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,6BAA6B,EAC7B,4BAA4B,EAC5B,8BAA8B,EAC/B,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,GAAG,EAAE,mBAAmB,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACrD,QAAQ,CAAC,aAAa,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACxD;AAED,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,wBAAwB,EACjC,OAAO,EAAE,8BAA8B,EACvC,KAAK,EAAE,4BAA4B,EACnC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACjC,OAAO,CAAC,6BAA6B,CAAC,CAiCxC;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,6BAA6B,EACnC,KAAK,EAAE,4BAA4B,EACnC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACjC,OAAO,CAAC,6BAA6B,CAAC,CAmBxC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { readString, semanticHash, semanticMetadata } from './utils.js';
|
|
2
|
+
export async function upsertRefinementTaskForGap(store, spaceId, context, trigger, state, message, data = {}) {
|
|
3
|
+
const subject = context.linkedObjects[0];
|
|
4
|
+
const id = `kref-${semanticHash(spaceId, context.gap.id, subject?.id ?? 'unscoped')}`;
|
|
5
|
+
const existing = store.getRefinementTask(id);
|
|
6
|
+
const attemptCount = existing?.attemptCount ?? 0;
|
|
7
|
+
return store.upsertRefinementTask({
|
|
8
|
+
id,
|
|
9
|
+
spaceId,
|
|
10
|
+
...(subject ? {
|
|
11
|
+
subjectKind: 'node',
|
|
12
|
+
subjectId: subject.id,
|
|
13
|
+
subjectTitle: subjectTitle(subject),
|
|
14
|
+
subjectType: subject.kind,
|
|
15
|
+
} : {}),
|
|
16
|
+
gapId: context.gap.id,
|
|
17
|
+
state,
|
|
18
|
+
trigger,
|
|
19
|
+
priority: refinementPriority(context.gap),
|
|
20
|
+
budget: {
|
|
21
|
+
maxSearches: 5,
|
|
22
|
+
maxSources: 5,
|
|
23
|
+
maxLlmCalls: 1,
|
|
24
|
+
},
|
|
25
|
+
attemptCount,
|
|
26
|
+
appendTrace: [trace(state, message, data)],
|
|
27
|
+
metadata: semanticMetadata(spaceId, {
|
|
28
|
+
gapTitle: context.gap.title,
|
|
29
|
+
gapKind: readString(context.gap.metadata.gapKind) ?? 'unknown',
|
|
30
|
+
sourceIds: context.sources.map((source) => source.id),
|
|
31
|
+
linkedObjectIds: context.linkedObjects.map((node) => node.id),
|
|
32
|
+
policyVersion: 'knowledge-refinement-v1',
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export async function updateRefinementTask(store, task, state, message, data = {}) {
|
|
37
|
+
return store.upsertRefinementTask({
|
|
38
|
+
id: task.id,
|
|
39
|
+
spaceId: task.spaceId,
|
|
40
|
+
subjectKind: task.subjectKind,
|
|
41
|
+
subjectId: task.subjectId,
|
|
42
|
+
subjectTitle: task.subjectTitle,
|
|
43
|
+
subjectType: task.subjectType,
|
|
44
|
+
gapId: task.gapId,
|
|
45
|
+
issueId: task.issueId,
|
|
46
|
+
state,
|
|
47
|
+
priority: task.priority,
|
|
48
|
+
trigger: task.trigger,
|
|
49
|
+
budget: task.budget,
|
|
50
|
+
attemptCount: state === 'searching' ? task.attemptCount + 1 : task.attemptCount,
|
|
51
|
+
...(state === 'blocked' || state === 'failed' ? { blockedReason: message } : {}),
|
|
52
|
+
appendTrace: [trace(state, message, data)],
|
|
53
|
+
metadata: task.metadata,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function trace(state, message, data = {}) {
|
|
57
|
+
return {
|
|
58
|
+
at: Date.now(),
|
|
59
|
+
state,
|
|
60
|
+
message,
|
|
61
|
+
...(Object.keys(data).length > 0 ? { data } : {}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function refinementPriority(gap) {
|
|
65
|
+
const severity = readString(gap.metadata.severity);
|
|
66
|
+
const kind = readString(gap.metadata.gapKind);
|
|
67
|
+
if (severity === 'error')
|
|
68
|
+
return 'urgent';
|
|
69
|
+
if (severity === 'warning' || kind === 'intrinsic_features')
|
|
70
|
+
return 'high';
|
|
71
|
+
return 'normal';
|
|
72
|
+
}
|
|
73
|
+
function subjectTitle(subject) {
|
|
74
|
+
return [
|
|
75
|
+
readString(subject.metadata.manufacturer),
|
|
76
|
+
readString(subject.metadata.model),
|
|
77
|
+
subject.title,
|
|
78
|
+
].filter(Boolean).join(' ');
|
|
79
|
+
}
|
|
@@ -4,6 +4,10 @@ interface SelfImproveContext {
|
|
|
4
4
|
readonly store: KnowledgeStore;
|
|
5
5
|
readonly gapRepairer?: KnowledgeSemanticGapRepairer | null;
|
|
6
6
|
readonly activeGapRepairs: Set<string>;
|
|
7
|
+
readonly enrichSource?: (sourceId: string, options: {
|
|
8
|
+
readonly force?: boolean;
|
|
9
|
+
readonly knowledgeSpaceId?: string;
|
|
10
|
+
}) => Promise<unknown>;
|
|
7
11
|
}
|
|
8
12
|
export declare function runKnowledgeSemanticSelfImprovement(context: SelfImproveContext, input?: KnowledgeSemanticSelfImproveInput): Promise<KnowledgeSemanticSelfImproveResult>;
|
|
9
13
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"self-improvement.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/self-improvement.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"self-improvement.d.ts","sourceRoot":"","sources":["../../../../../src/_internal/platform/knowledge/semantic/self-improvement.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAQlD,OAAO,KAAK,EACV,4BAA4B,EAC5B,iCAAiC,EACjC,kCAAkC,EACnC,MAAM,YAAY,CAAC;AAYpB,UAAU,kBAAkB;IAC1B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,4BAA4B,GAAG,IAAI,CAAC;IAC3D,QAAQ,CAAC,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3I;AAUD,wBAAsB,mCAAmC,CACvD,OAAO,EAAE,kBAAkB,EAC3B,KAAK,GAAE,iCAAsC,GAC5C,OAAO,CAAC,kCAAkC,CAAC,CA0N7C"}
|