@qloo/qloo-harness 0.1.18
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/LICENSE +21 -0
- package/README.md +394 -0
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/app.js +48 -0
- package/dist/bin.js +14 -0
- package/dist/build.js +117 -0
- package/dist/cli.js +95 -0
- package/dist/doctor.js +1019 -0
- package/dist/exec.js +782 -0
- package/dist/guided-journey.js +223 -0
- package/dist/index.d.ts +1321 -0
- package/dist/index.js +156 -0
- package/dist/integration-plan.js +1097 -0
- package/dist/mcp.js +909 -0
- package/dist/observability.js +115 -0
- package/dist/paths.js +77 -0
- package/dist/plan.js +163 -0
- package/dist/profiles.js +63 -0
- package/dist/project-context.js +364 -0
- package/dist/qloo-presentation.js +358 -0
- package/dist/qloo-tools.js +2195 -0
- package/dist/resolution-provider.js +1380 -0
- package/dist/router.js +6061 -0
- package/dist/runtime/explore-policy.js +666 -0
- package/dist/runtime/pi-adapter.js +259 -0
- package/dist/runtime/pi-command-policy.js +74 -0
- package/dist/runtime/qloo-header.js +173 -0
- package/dist/runtime/resources.js +37 -0
- package/dist/setup.js +1138 -0
- package/dist/update-manager.js +906 -0
- package/dist/workflow-executor.js +976 -0
- package/package.json +72 -0
- package/resources/BUILD.md +24 -0
- package/resources/EXPLORE.md +34 -0
- package/resources/INTEGRATE.md +28 -0
- package/resources/PLAN.md +23 -0
- package/resources/SYSTEM.md +49 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { createRequire as __qlooCreateRequire } from "node:module";
|
|
2
|
+
const require = __qlooCreateRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// apps/qloo-harness/dist/qloo-presentation.js
|
|
5
|
+
var OPERATION_LABELS = {
|
|
6
|
+
recommend: "recommendations",
|
|
7
|
+
rank: "ranking",
|
|
8
|
+
describe: "entity details",
|
|
9
|
+
where_popular: "geographic affinity",
|
|
10
|
+
compare_audiences: "audience comparison",
|
|
11
|
+
entity_tags: "entity tags",
|
|
12
|
+
audience_demographics: "aggregate demographics",
|
|
13
|
+
trends: "trends",
|
|
14
|
+
find_tags: "tag search"
|
|
15
|
+
};
|
|
16
|
+
var SUPPORT_TEXT = {
|
|
17
|
+
recommend: "Relative Qloo affinity for the interpreted taste profile; it does not establish causality or an individual preference.",
|
|
18
|
+
rank: "Relative ordering of the supplied options for the interpreted signals, not a universal quality score.",
|
|
19
|
+
compare_audiences: "Shared and differentiating aggregate affinities between the two supplied signal groups.",
|
|
20
|
+
audience_demographics: "Aggregate audience distribution only; it must not be used to infer an individual's demographics.",
|
|
21
|
+
where_popular: "Comparative geographic affinity within the requested area, not population size or visit counts.",
|
|
22
|
+
trends: "Observed Qloo trend values over the requested period; later values do not imply a causal change."
|
|
23
|
+
};
|
|
24
|
+
function asRecord(value) {
|
|
25
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
26
|
+
}
|
|
27
|
+
function asArray(value) {
|
|
28
|
+
return Array.isArray(value) ? value : [];
|
|
29
|
+
}
|
|
30
|
+
function safeText(value) {
|
|
31
|
+
return String(value ?? "").replace(/[\u0000-\u001f\u007f]/gu, " ").replace(/\s+/gu, " ").trim();
|
|
32
|
+
}
|
|
33
|
+
function truncate(value, width) {
|
|
34
|
+
const maximum = Math.max(1, Math.trunc(width));
|
|
35
|
+
return value.length <= maximum ? value : `${value.slice(0, maximum - 1)}\u2026`;
|
|
36
|
+
}
|
|
37
|
+
function scalarSummary(value) {
|
|
38
|
+
if (typeof value === "string")
|
|
39
|
+
return safeText(value);
|
|
40
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
41
|
+
return String(value);
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
return value.map((item) => {
|
|
44
|
+
const record2 = asRecord(item);
|
|
45
|
+
return safeText(record2?.name ?? record2?.input ?? record2?.entityId ?? record2?.tagId ?? item);
|
|
46
|
+
}).filter(Boolean).join(", ");
|
|
47
|
+
}
|
|
48
|
+
const record = asRecord(value);
|
|
49
|
+
if (!record)
|
|
50
|
+
return safeText(value);
|
|
51
|
+
const preferred = record.name ?? record.input ?? record.resolved ?? record.entityId ?? record.tagId;
|
|
52
|
+
if (preferred !== void 0)
|
|
53
|
+
return safeText(preferred);
|
|
54
|
+
return safeText(JSON.stringify(record));
|
|
55
|
+
}
|
|
56
|
+
function metricSummary(record) {
|
|
57
|
+
const metrics = ["rank", "affinity", "score", "popularity", "population_rank"].flatMap((key) => typeof record[key] === "number" ? [`${key} ${record[key]}`] : []);
|
|
58
|
+
return metrics.length > 0 ? ` \xB7 ${metrics.join(" \xB7 ")}` : "";
|
|
59
|
+
}
|
|
60
|
+
function readableUrnType(value) {
|
|
61
|
+
const type = safeText(value).toLocaleLowerCase("en-US");
|
|
62
|
+
if (!type)
|
|
63
|
+
return "";
|
|
64
|
+
if (type === "urn:tag")
|
|
65
|
+
return "tag";
|
|
66
|
+
if (type.startsWith("urn:entity:")) {
|
|
67
|
+
return type.slice("urn:entity:".length).split(":").at(-1)?.replaceAll("_", " ") ?? "";
|
|
68
|
+
}
|
|
69
|
+
if (type.startsWith("urn:tag:")) {
|
|
70
|
+
const category = type.slice("urn:tag:".length).split(":")[0]?.replaceAll("_", " ");
|
|
71
|
+
return category ? `${category} tag` : "tag";
|
|
72
|
+
}
|
|
73
|
+
return type.split(":").at(-1)?.replaceAll("_", " ") ?? type;
|
|
74
|
+
}
|
|
75
|
+
function addressSummary(value) {
|
|
76
|
+
if (typeof value === "string")
|
|
77
|
+
return safeText(value);
|
|
78
|
+
const record = asRecord(value);
|
|
79
|
+
if (!record)
|
|
80
|
+
return "";
|
|
81
|
+
const formatted = safeText(record.formatted ?? record.formatted_address ?? record.display_name ?? record.name);
|
|
82
|
+
if (formatted)
|
|
83
|
+
return formatted;
|
|
84
|
+
return [...new Set([
|
|
85
|
+
record.city,
|
|
86
|
+
record.locality,
|
|
87
|
+
record.region,
|
|
88
|
+
record.state,
|
|
89
|
+
record.country
|
|
90
|
+
].map(safeText).filter(Boolean))].join(", ");
|
|
91
|
+
}
|
|
92
|
+
function compactCandidateId(value) {
|
|
93
|
+
const id = safeText(value);
|
|
94
|
+
if (/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/iu.test(id))
|
|
95
|
+
return id.slice(0, 8);
|
|
96
|
+
const tail = id.split(/[:/]/u).filter(Boolean).at(-1) ?? id;
|
|
97
|
+
let hash = 2166136261;
|
|
98
|
+
for (const character of id) {
|
|
99
|
+
hash ^= character.codePointAt(0) ?? 0;
|
|
100
|
+
hash = Math.imul(hash, 16777619);
|
|
101
|
+
}
|
|
102
|
+
return `${truncate(tail, 18)}-${(hash >>> 0).toString(16).padStart(8, "0").slice(0, 4)}`;
|
|
103
|
+
}
|
|
104
|
+
function candidateBaseLabel(candidate) {
|
|
105
|
+
const name = safeText(candidate.name ?? candidate.id) || "Unnamed Qloo match";
|
|
106
|
+
const type = readableUrnType(candidate.type);
|
|
107
|
+
const parentTypes = asArray(candidate.parent_types).map(readableUrnType).filter(Boolean);
|
|
108
|
+
const scope = parentTypes.length > 0 ? `for ${[...new Set(parentTypes)].join(", ")}` : "";
|
|
109
|
+
const releaseYear = typeof candidate.release_year === "number" ? `released ${candidate.release_year}` : "";
|
|
110
|
+
const location = addressSummary(candidate.address);
|
|
111
|
+
const description = truncate(safeText(candidate.description), 96);
|
|
112
|
+
const rank = Number.isSafeInteger(candidate.rank) ? `rank ${candidate.rank}` : "";
|
|
113
|
+
const score = typeof candidate.score === "number" && Number.isFinite(candidate.score) ? `match ${candidate.score}` : "";
|
|
114
|
+
const discriminators = [type, scope, releaseYear, location, description, rank, score].filter(Boolean);
|
|
115
|
+
return {
|
|
116
|
+
label: [name, ...discriminators].join(" \xB7 "),
|
|
117
|
+
hasDiscriminator: discriminators.length > 0
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function formatQlooResolutionCandidateLabels(candidates) {
|
|
121
|
+
const maximumLabelLength = 180;
|
|
122
|
+
const prepared = candidates.map((candidate) => {
|
|
123
|
+
const base = candidateBaseLabel(candidate);
|
|
124
|
+
return {
|
|
125
|
+
candidate,
|
|
126
|
+
...base,
|
|
127
|
+
visibleLabel: truncate(base.label, maximumLabelLength)
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
const labelCounts = /* @__PURE__ */ new Map();
|
|
131
|
+
for (const { visibleLabel } of prepared) {
|
|
132
|
+
const key = visibleLabel.toLocaleLowerCase("en-US");
|
|
133
|
+
labelCounts.set(key, (labelCounts.get(key) ?? 0) + 1);
|
|
134
|
+
}
|
|
135
|
+
return prepared.map(({ candidate, hasDiscriminator, visibleLabel }) => {
|
|
136
|
+
const duplicate = (labelCounts.get(visibleLabel.toLocaleLowerCase("en-US")) ?? 0) > 1;
|
|
137
|
+
if (hasDiscriminator && !duplicate)
|
|
138
|
+
return visibleLabel;
|
|
139
|
+
const identity = `ID ${compactCandidateId(candidate.id)}`;
|
|
140
|
+
return `${truncate(visibleLabel, maximumLabelLength - identity.length - 3)} \xB7 ${identity}`;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function resultSummary(value, index) {
|
|
144
|
+
const record = asRecord(value);
|
|
145
|
+
if (!record)
|
|
146
|
+
return `${index + 1}. ${scalarSummary(value)}`;
|
|
147
|
+
const properties = asRecord(record.properties);
|
|
148
|
+
const label = safeText(record.name ?? record.resolved ?? record.date ?? record.id ?? record.entity_id ?? record.tag_id ?? `Result ${index + 1}`);
|
|
149
|
+
const location = safeText(properties?.address ?? record.address);
|
|
150
|
+
return `${index + 1}. ${label}${metricSummary(record)}${location ? ` \xB7 ${location}` : ""}`;
|
|
151
|
+
}
|
|
152
|
+
function operationFromDetails(details) {
|
|
153
|
+
const operation = details.operation;
|
|
154
|
+
return typeof operation === "string" && Object.hasOwn(OPERATION_LABELS, operation) ? operation : void 0;
|
|
155
|
+
}
|
|
156
|
+
function interpretationLines(details) {
|
|
157
|
+
const interpretation = asRecord(details.interpretation) ?? asRecord(details.query_intent);
|
|
158
|
+
if (!interpretation)
|
|
159
|
+
return [];
|
|
160
|
+
return Object.entries(interpretation).filter(([, value]) => value !== void 0 && value !== null && scalarSummary(value)).slice(0, 10).map(([key, value]) => ` ${key.replaceAll("_", " ")}: ${scalarSummary(value)}`);
|
|
161
|
+
}
|
|
162
|
+
function ambiguityLines(details, maximum) {
|
|
163
|
+
const resolution = asRecord(details.resolution);
|
|
164
|
+
const issues = asArray(resolution?.issues).map(asRecord).filter((issue) => issue !== void 0);
|
|
165
|
+
return issues.flatMap((issue) => {
|
|
166
|
+
const input = safeText(issue.input);
|
|
167
|
+
const candidates = asArray(issue.candidates).map(asRecord).filter((candidate) => candidate !== void 0).slice(0, maximum);
|
|
168
|
+
const labels = formatQlooResolutionCandidateLabels(candidates).map((label, index) => ` ${index + 1}. ${label}`);
|
|
169
|
+
return [` ${input || "Unresolved input"}`, ...labels];
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function sourceLine(details) {
|
|
173
|
+
const provenance = asRecord(details.provenance);
|
|
174
|
+
const execution = asRecord(details.execution);
|
|
175
|
+
const values = [
|
|
176
|
+
provenance?.source,
|
|
177
|
+
provenance?.endpoint,
|
|
178
|
+
execution?.transport,
|
|
179
|
+
provenance?.documentation
|
|
180
|
+
].map(safeText).filter(Boolean);
|
|
181
|
+
return values.length > 0 ? ` ${values.join(" \xB7 ")}` : void 0;
|
|
182
|
+
}
|
|
183
|
+
function renderQlooCallLines(toolName, input, width) {
|
|
184
|
+
const operation = toolName.startsWith("qloo_") ? toolName.slice("qloo_".length) : void 0;
|
|
185
|
+
const label = operation && OPERATION_LABELS[operation] ? OPERATION_LABELS[operation] : toolName;
|
|
186
|
+
const focus = Object.entries(input).filter(([, value]) => value !== void 0).slice(0, 4).map(([key, value]) => `${key.replaceAll("_", " ")}=${scalarSummary(value)}`).join(" \xB7 ");
|
|
187
|
+
return [
|
|
188
|
+
truncate(`Qloo \xB7 ${label}`, width),
|
|
189
|
+
...focus ? [truncate(` Interpreting ${focus}`, width)] : []
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
function renderQlooResultLines(detailsValue, options = {}) {
|
|
193
|
+
const details = asRecord(detailsValue) ?? {};
|
|
194
|
+
const width = options.width ?? 100;
|
|
195
|
+
const expanded = options.expanded ?? false;
|
|
196
|
+
const operation = operationFromDetails(details);
|
|
197
|
+
const status = safeText(details.status) || "unknown";
|
|
198
|
+
const resultLimit = expanded ? 10 : 5;
|
|
199
|
+
const results = asArray(details.results);
|
|
200
|
+
const warnings = asArray(details.warnings).filter((value) => typeof value === "string");
|
|
201
|
+
const lines = [
|
|
202
|
+
`Qloo ${operation ? OPERATION_LABELS[operation] : "result"} \xB7 ${status}`
|
|
203
|
+
];
|
|
204
|
+
const interpreted = interpretationLines(details);
|
|
205
|
+
if (interpreted.length > 0)
|
|
206
|
+
lines.push("Interpreted as", ...interpreted);
|
|
207
|
+
if (status === "needs_input") {
|
|
208
|
+
lines.push("Choose a Qloo match", ...ambiguityLines(details, resultLimit));
|
|
209
|
+
} else if (status === "error") {
|
|
210
|
+
const error = asRecord(details.error);
|
|
211
|
+
lines.push("Qloo result", ` ${safeText(details.summary) || "The workflow did not complete."}`, ...error?.recovery ? [` Next: ${safeText(error.recovery)}`] : []);
|
|
212
|
+
} else {
|
|
213
|
+
lines.push(`Qloo result${results.length > 0 ? ` (${safeText(details.result_count ?? results.length)})` : ""}`);
|
|
214
|
+
if (results.length === 0)
|
|
215
|
+
lines.push(" No matching results were returned.");
|
|
216
|
+
else
|
|
217
|
+
lines.push(...results.slice(0, resultLimit).map(resultSummary));
|
|
218
|
+
if (results.length > resultLimit) {
|
|
219
|
+
lines.push(` \u2026 ${results.length - resultLimit} more; use /raw next for detail`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const support = operation ? SUPPORT_TEXT[operation] : void 0;
|
|
223
|
+
if (support)
|
|
224
|
+
lines.push("What this supports", ` ${support}`);
|
|
225
|
+
if (warnings.length > 0) {
|
|
226
|
+
lines.push("Warnings", ...warnings.slice(0, expanded ? 8 : 3).map((warning) => ` ${safeText(warning)}`));
|
|
227
|
+
}
|
|
228
|
+
const source = sourceLine(details);
|
|
229
|
+
if (source)
|
|
230
|
+
lines.push("Sources", source);
|
|
231
|
+
return lines.map((line) => truncate(line, width));
|
|
232
|
+
}
|
|
233
|
+
function createQlooCallComponent(toolName, input, theme) {
|
|
234
|
+
return {
|
|
235
|
+
render(width) {
|
|
236
|
+
return renderQlooCallLines(toolName, input, width).map((line, index) => index === 0 ? theme.fg("accent", theme.bold(line)) : theme.fg("dim", line));
|
|
237
|
+
},
|
|
238
|
+
invalidate() {
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function createQlooResultComponent(details, expanded, theme) {
|
|
243
|
+
const status = safeText(asRecord(details)?.status);
|
|
244
|
+
const statusColor = status === "ok" ? "success" : status === "error" ? "error" : status === "needs_input" ? "warning" : "accent";
|
|
245
|
+
return {
|
|
246
|
+
render(width) {
|
|
247
|
+
const lines = renderQlooResultLines(details, { expanded, width });
|
|
248
|
+
return lines.map((line, index) => index === 0 ? theme.fg(statusColor, theme.bold(line)) : /^(?:Interpreted as|Qloo result|Choose a Qloo match|What this supports|Warnings|Sources)$/u.test(line) ? theme.fg("muted", theme.bold(line)) : line);
|
|
249
|
+
},
|
|
250
|
+
invalidate() {
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function paginateQlooResult(detailsValue, requestedPage, pageSize = 5) {
|
|
255
|
+
if (!Number.isSafeInteger(pageSize) || pageSize < 1) {
|
|
256
|
+
throw new RangeError("Qloo result page size must be a positive integer.");
|
|
257
|
+
}
|
|
258
|
+
const results = asArray(detailsValue.results);
|
|
259
|
+
const totalPages = Math.max(1, Math.ceil(results.length / pageSize));
|
|
260
|
+
const page = Math.min(totalPages, Math.max(1, Math.trunc(requestedPage)));
|
|
261
|
+
const start = (page - 1) * pageSize;
|
|
262
|
+
return {
|
|
263
|
+
...detailsValue,
|
|
264
|
+
results: results.slice(start, start + pageSize),
|
|
265
|
+
page: {
|
|
266
|
+
number: page,
|
|
267
|
+
size: pageSize,
|
|
268
|
+
total_pages: totalPages,
|
|
269
|
+
total_results: results.length,
|
|
270
|
+
has_previous: page > 1,
|
|
271
|
+
has_next: page < totalPages
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function parseQlooRawPage(argument, currentPage) {
|
|
276
|
+
const value = argument.trim().toLocaleLowerCase("en-US");
|
|
277
|
+
if (!value)
|
|
278
|
+
return 1;
|
|
279
|
+
if (value === "next")
|
|
280
|
+
return currentPage + 1;
|
|
281
|
+
if (value === "prev" || value === "previous")
|
|
282
|
+
return Math.max(1, currentPage - 1);
|
|
283
|
+
if (/^[1-9]\d*$/u.test(value))
|
|
284
|
+
return Number(value);
|
|
285
|
+
return void 0;
|
|
286
|
+
}
|
|
287
|
+
var RESOLUTION_INPUT_FIELDS = {
|
|
288
|
+
qloo_recommend: ["signals", "signal_tags", "include_tags", "exclude_tags"],
|
|
289
|
+
qloo_rank: ["options", "signals", "include_tags", "exclude_tags"],
|
|
290
|
+
qloo_describe: ["entity"],
|
|
291
|
+
qloo_where_popular: ["entity"],
|
|
292
|
+
qloo_compare_audiences: ["group_a", "group_b"],
|
|
293
|
+
qloo_entity_tags: ["entities"],
|
|
294
|
+
qloo_audience_demographics: ["entities"],
|
|
295
|
+
qloo_trends: ["entities"]
|
|
296
|
+
};
|
|
297
|
+
function resolutionCandidates(detailsValue) {
|
|
298
|
+
const details = asRecord(detailsValue);
|
|
299
|
+
if (details?.status !== "needs_input")
|
|
300
|
+
return [];
|
|
301
|
+
const resolution = asRecord(details.resolution);
|
|
302
|
+
return asArray(resolution?.issues).slice(0, 5).flatMap((value) => {
|
|
303
|
+
const issue = asRecord(value);
|
|
304
|
+
const input = safeText(issue?.input);
|
|
305
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
306
|
+
const candidates = asArray(issue?.candidates).map(asRecord).filter((candidate) => candidate !== void 0).filter((candidate) => {
|
|
307
|
+
const id = safeText(candidate.id);
|
|
308
|
+
if (!id || seenIds.has(id))
|
|
309
|
+
return false;
|
|
310
|
+
seenIds.add(id);
|
|
311
|
+
return true;
|
|
312
|
+
}).slice(0, 5);
|
|
313
|
+
return input && candidates.length > 0 ? [{ input, candidates }] : [];
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
function applyResolutionChoices(toolName, input, choices) {
|
|
317
|
+
const fields = RESOLUTION_INPUT_FIELDS[toolName];
|
|
318
|
+
if (!fields || choices.length === 0)
|
|
319
|
+
return void 0;
|
|
320
|
+
const byInput = new Map(choices.map((choice) => [choice.input, choice.selectedId]));
|
|
321
|
+
let changed = false;
|
|
322
|
+
const patched = { ...input };
|
|
323
|
+
for (const field of fields) {
|
|
324
|
+
const value = input[field];
|
|
325
|
+
if (typeof value === "string") {
|
|
326
|
+
const replacement = byInput.get(value);
|
|
327
|
+
if (replacement) {
|
|
328
|
+
patched[field] = replacement;
|
|
329
|
+
changed = true;
|
|
330
|
+
}
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (!Array.isArray(value))
|
|
334
|
+
continue;
|
|
335
|
+
const replaced = value.map((item) => {
|
|
336
|
+
if (typeof item !== "string")
|
|
337
|
+
return item;
|
|
338
|
+
const replacement = byInput.get(item);
|
|
339
|
+
if (!replacement)
|
|
340
|
+
return item;
|
|
341
|
+
changed = true;
|
|
342
|
+
return replacement;
|
|
343
|
+
});
|
|
344
|
+
patched[field] = replaced;
|
|
345
|
+
}
|
|
346
|
+
return changed ? patched : void 0;
|
|
347
|
+
}
|
|
348
|
+
export {
|
|
349
|
+
applyResolutionChoices,
|
|
350
|
+
createQlooCallComponent,
|
|
351
|
+
createQlooResultComponent,
|
|
352
|
+
formatQlooResolutionCandidateLabels,
|
|
353
|
+
paginateQlooResult,
|
|
354
|
+
parseQlooRawPage,
|
|
355
|
+
renderQlooCallLines,
|
|
356
|
+
renderQlooResultLines,
|
|
357
|
+
resolutionCandidates
|
|
358
|
+
};
|