@qloo/qloo-harness 0.1.18 → 0.1.20
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/README.md +67 -23
- package/dist/cli.js +1 -1
- package/dist/exec.js +7 -3
- package/dist/index.d.ts +71 -7
- package/dist/index.js +9 -1
- package/dist/integration-plan.js +7 -3
- package/dist/mcp.js +7 -3
- package/dist/qloo-presentation.js +653 -16
- package/dist/qloo-tools.js +31 -20
- package/dist/resolution-provider.js +74 -7
- package/dist/router.js +12 -12
- package/dist/runtime/explore-policy.js +109 -24
- package/dist/runtime/resolution-choice-ui.js +170 -0
- package/dist/runtime/resolution-memory.js +235 -0
- package/dist/workflow-executor.js +7 -3
- package/package.json +1 -1
- package/resources/EXPLORE.md +2 -2
- package/resources/SYSTEM.md +7 -4
package/dist/qloo-tools.js
CHANGED
|
@@ -786,12 +786,12 @@ var QLOO_LIMIT_SCHEMA = Type.Optional(Type.Integer({
|
|
|
786
786
|
var QLOO_ENTITY_INPUTS_SCHEMA = Type.Array(Type.String({ minLength: 1 }), {
|
|
787
787
|
minItems: 1,
|
|
788
788
|
maxItems: 10,
|
|
789
|
-
description: "
|
|
789
|
+
description: "Named entities or Qloo entity UUIDs; use tag fields for abstract concepts."
|
|
790
790
|
});
|
|
791
791
|
var QLOO_TAG_INPUTS_SCHEMA = Type.Array(Type.String({ minLength: 1 }), {
|
|
792
792
|
minItems: 1,
|
|
793
793
|
maxItems: 10,
|
|
794
|
-
description: "
|
|
794
|
+
description: "Genres, moods, styles, traits, or other concepts, or Qloo tag URNs; not named entities."
|
|
795
795
|
});
|
|
796
796
|
var QLOO_DEMOGRAPHIC_SCHEMA = Type.String({
|
|
797
797
|
minLength: 1,
|
|
@@ -951,6 +951,7 @@ var QLOO_WORKFLOW_METADATA = {
|
|
|
951
951
|
promptSnippet: "Recommend entities from a combined Qloo taste profile",
|
|
952
952
|
promptGuidelines: [
|
|
953
953
|
"Pass every related taste and audience signal together in one qloo_recommend call.",
|
|
954
|
+
"Put named things in signals; put genres, moods, styles, traits, and concepts such as quiet luxury in signal_tags; reuse returned Qloo UUIDs and tag URNs.",
|
|
954
955
|
"Use signal_location for audience location and filter_location to constrain result geography.",
|
|
955
956
|
"Include demographic and location in the same call when both describe the audience."
|
|
956
957
|
],
|
|
@@ -961,7 +962,10 @@ var QLOO_WORKFLOW_METADATA = {
|
|
|
961
962
|
label: "Qloo rank",
|
|
962
963
|
description: "Rank one supplied option set against one shared entity, location, demographic, and tag profile. Scores from separate calls are not comparable.",
|
|
963
964
|
promptSnippet: "Rank a caller-supplied shortlist with Qloo",
|
|
964
|
-
promptGuidelines: [
|
|
965
|
+
promptGuidelines: [
|
|
966
|
+
"Pass every option in one qloo_rank call so scores remain comparable.",
|
|
967
|
+
"Put named things in entity fields and concepts in tag fields; reuse returned Qloo UUIDs and tag URNs."
|
|
968
|
+
],
|
|
965
969
|
documentation: ["https://docs.qloo.com/reference/insights-api-deep-dive"]
|
|
966
970
|
},
|
|
967
971
|
describe: {
|
|
@@ -1445,7 +1449,7 @@ function interpretedEntity(entity) {
|
|
|
1445
1449
|
function interpretedEntities(entities) {
|
|
1446
1450
|
return entities.map(interpretedEntity);
|
|
1447
1451
|
}
|
|
1448
|
-
async function resolveEntities(provider, inputs, type, signal, correlationId) {
|
|
1452
|
+
async function resolveEntities(provider, inputs, type, signal, correlationId, field) {
|
|
1449
1453
|
const { outcomes } = await provider.resolveEntities({
|
|
1450
1454
|
inputs,
|
|
1451
1455
|
...type ? { type } : {}
|
|
@@ -1466,6 +1470,9 @@ async function resolveEntities(provider, inputs, type, signal, correlationId) {
|
|
|
1466
1470
|
issues: outcomes.flatMap((outcome) => outcome.status === "resolved" ? [] : [{
|
|
1467
1471
|
input: outcome.input,
|
|
1468
1472
|
kind: outcome.status,
|
|
1473
|
+
input_kind: "entity",
|
|
1474
|
+
field,
|
|
1475
|
+
...type ? { scope: type } : {},
|
|
1469
1476
|
...outcome.candidates ? {
|
|
1470
1477
|
candidates: outcome.candidates.map((candidate) => ({ ...candidate }))
|
|
1471
1478
|
} : {},
|
|
@@ -1484,7 +1491,7 @@ function interpretedTags(tags) {
|
|
|
1484
1491
|
...tag.score !== void 0 ? { score: tag.score } : {}
|
|
1485
1492
|
}));
|
|
1486
1493
|
}
|
|
1487
|
-
async function resolveTags(provider, inputs, targetType, purpose, signal, correlationId) {
|
|
1494
|
+
async function resolveTags(provider, inputs, targetType, purpose, signal, correlationId, field) {
|
|
1488
1495
|
const { outcomes } = await provider.resolveTags({
|
|
1489
1496
|
inputs,
|
|
1490
1497
|
...targetType ? { targetType } : {},
|
|
@@ -1509,6 +1516,10 @@ async function resolveTags(provider, inputs, targetType, purpose, signal, correl
|
|
|
1509
1516
|
issues: outcomes.flatMap((outcome) => outcome.status === "resolved" ? [] : [{
|
|
1510
1517
|
input: outcome.input,
|
|
1511
1518
|
kind: outcome.status,
|
|
1519
|
+
input_kind: "tag",
|
|
1520
|
+
field,
|
|
1521
|
+
...targetType ? { scope: targetType } : {},
|
|
1522
|
+
purpose,
|
|
1512
1523
|
...outcome.candidates ? {
|
|
1513
1524
|
candidates: outcome.candidates.map((candidate) => ({ ...candidate }))
|
|
1514
1525
|
} : {},
|
|
@@ -1656,10 +1667,10 @@ function makeRecommendTool(clientOption, resolutionProviderOption) {
|
|
|
1656
1667
|
return needsInput("recommend", [], "Provide at least one entity, tag, location, or demographic taste signal.");
|
|
1657
1668
|
}
|
|
1658
1669
|
const [entities, signalTags, includeTags, excludeTags] = await Promise.all([
|
|
1659
|
-
resolveEntities(resolutionProvider, params.signals ?? [], void 0, signal, toolCallId),
|
|
1660
|
-
resolveTags(resolutionProvider, params.signal_tags ?? [], targetType, "signal", signal, toolCallId),
|
|
1661
|
-
resolveTags(resolutionProvider, params.include_tags ?? [], targetType, "include_filter", signal, toolCallId),
|
|
1662
|
-
resolveTags(resolutionProvider, params.exclude_tags ?? [], targetType, "exclude_filter", signal, toolCallId)
|
|
1670
|
+
resolveEntities(resolutionProvider, params.signals ?? [], void 0, signal, toolCallId, "signals"),
|
|
1671
|
+
resolveTags(resolutionProvider, params.signal_tags ?? [], targetType, "signal", signal, toolCallId, "signal_tags"),
|
|
1672
|
+
resolveTags(resolutionProvider, params.include_tags ?? [], targetType, "include_filter", signal, toolCallId, "include_tags"),
|
|
1673
|
+
resolveTags(resolutionProvider, params.exclude_tags ?? [], targetType, "exclude_filter", signal, toolCallId, "exclude_tags")
|
|
1663
1674
|
]);
|
|
1664
1675
|
const issues = [...entities.issues, ...signalTags.issues, ...includeTags.issues, ...excludeTags.issues];
|
|
1665
1676
|
if (issues.length > 0)
|
|
@@ -1743,10 +1754,10 @@ function makeRankTool(clientOption, resolutionProviderOption) {
|
|
|
1743
1754
|
return needsInput("rank", [], "Ranking requires an entity, location, or demographic signal.");
|
|
1744
1755
|
}
|
|
1745
1756
|
const [options, signals, includeTags, excludeTags] = await Promise.all([
|
|
1746
|
-
resolveEntities(resolutionProvider, params.options, targetType, signal, toolCallId),
|
|
1747
|
-
resolveEntities(resolutionProvider, params.signals ?? [], void 0, signal, toolCallId),
|
|
1748
|
-
resolveTags(resolutionProvider, params.include_tags ?? [], targetType, "include_filter", signal, toolCallId),
|
|
1749
|
-
resolveTags(resolutionProvider, params.exclude_tags ?? [], targetType, "exclude_filter", signal, toolCallId)
|
|
1757
|
+
resolveEntities(resolutionProvider, params.options, targetType, signal, toolCallId, "options"),
|
|
1758
|
+
resolveEntities(resolutionProvider, params.signals ?? [], void 0, signal, toolCallId, "signals"),
|
|
1759
|
+
resolveTags(resolutionProvider, params.include_tags ?? [], targetType, "include_filter", signal, toolCallId, "include_tags"),
|
|
1760
|
+
resolveTags(resolutionProvider, params.exclude_tags ?? [], targetType, "exclude_filter", signal, toolCallId, "exclude_tags")
|
|
1750
1761
|
]);
|
|
1751
1762
|
const issues = [...options.issues, ...signals.issues, ...includeTags.issues, ...excludeTags.issues];
|
|
1752
1763
|
if (issues.length > 0)
|
|
@@ -1805,7 +1816,7 @@ function makeDescribeTool(clientOption, resolutionProviderOption) {
|
|
|
1805
1816
|
const client = requireClient(clientOption);
|
|
1806
1817
|
const resolutionProvider = requireResolutionProvider(resolutionProviderOption);
|
|
1807
1818
|
const type = params.type ? resolveSearchType(params.type) : void 0;
|
|
1808
|
-
const resolution = await resolveEntities(resolutionProvider, [params.entity], type, signal, toolCallId);
|
|
1819
|
+
const resolution = await resolveEntities(resolutionProvider, [params.entity], type, signal, toolCallId, "entity");
|
|
1809
1820
|
if (resolution.issues.length > 0)
|
|
1810
1821
|
return needsInput("describe", resolution.issues, "Choose the intended Qloo entity.");
|
|
1811
1822
|
const needsDetailHydration = resolution.resolved.some(({ match }) => match !== "identifier");
|
|
@@ -1842,7 +1853,7 @@ function makeWherePopularTool(clientOption, resolutionProviderOption) {
|
|
|
1842
1853
|
const client = requireClient(clientOption);
|
|
1843
1854
|
const resolutionProvider = requireResolutionProvider(resolutionProviderOption);
|
|
1844
1855
|
const type = params.entity_type ? resolveSearchType(params.entity_type) : void 0;
|
|
1845
|
-
const resolution = await resolveEntities(resolutionProvider, [params.entity], type, signal, toolCallId);
|
|
1856
|
+
const resolution = await resolveEntities(resolutionProvider, [params.entity], type, signal, toolCallId, "entity");
|
|
1846
1857
|
if (resolution.issues.length > 0)
|
|
1847
1858
|
return needsInput("where_popular", resolution.issues, "Choose the intended Qloo entity.");
|
|
1848
1859
|
const entity = resolution.resolved[0];
|
|
@@ -1880,8 +1891,8 @@ function makeCompareAudiencesTool(clientOption, resolutionProviderOption) {
|
|
|
1880
1891
|
const client = requireClient(clientOption);
|
|
1881
1892
|
const resolutionProvider = requireResolutionProvider(resolutionProviderOption);
|
|
1882
1893
|
const [groupA, groupB] = await Promise.all([
|
|
1883
|
-
resolveEntities(resolutionProvider, params.group_a, void 0, signal, toolCallId),
|
|
1884
|
-
resolveEntities(resolutionProvider, params.group_b, void 0, signal, toolCallId)
|
|
1894
|
+
resolveEntities(resolutionProvider, params.group_a, void 0, signal, toolCallId, "group_a"),
|
|
1895
|
+
resolveEntities(resolutionProvider, params.group_b, void 0, signal, toolCallId, "group_b")
|
|
1885
1896
|
]);
|
|
1886
1897
|
const issues = [...groupA.issues, ...groupB.issues];
|
|
1887
1898
|
if (issues.length > 0)
|
|
@@ -1920,7 +1931,7 @@ function makeEntityTagsTool(clientOption, resolutionProviderOption) {
|
|
|
1920
1931
|
const client = requireClient(clientOption);
|
|
1921
1932
|
const resolutionProvider = requireResolutionProvider(resolutionProviderOption);
|
|
1922
1933
|
const type = params.entity_type ? resolveSearchType(params.entity_type) : void 0;
|
|
1923
|
-
const resolution = await resolveEntities(resolutionProvider, params.entities, type, signal, toolCallId);
|
|
1934
|
+
const resolution = await resolveEntities(resolutionProvider, params.entities, type, signal, toolCallId, "entities");
|
|
1924
1935
|
if (resolution.issues.length > 0)
|
|
1925
1936
|
return needsInput("entity_tags", resolution.issues, "Choose or correct the unresolved Qloo entities.");
|
|
1926
1937
|
const query = {
|
|
@@ -1953,7 +1964,7 @@ function makeAudienceDemographicsTool(clientOption, resolutionProviderOption) {
|
|
|
1953
1964
|
const client = requireClient(clientOption);
|
|
1954
1965
|
const resolutionProvider = requireResolutionProvider(resolutionProviderOption);
|
|
1955
1966
|
const type = params.entity_type ? resolveSearchType(params.entity_type) : void 0;
|
|
1956
|
-
const resolution = await resolveEntities(resolutionProvider, [params.entity], type, signal, toolCallId);
|
|
1967
|
+
const resolution = await resolveEntities(resolutionProvider, [params.entity], type, signal, toolCallId, "entity");
|
|
1957
1968
|
if (resolution.issues.length > 0)
|
|
1958
1969
|
return needsInput("audience_demographics", resolution.issues, "Choose the intended Qloo entity.");
|
|
1959
1970
|
const entity = resolution.resolved[0];
|
|
@@ -1992,7 +2003,7 @@ function makeTrendsTool(clientOption, resolutionProviderOption) {
|
|
|
1992
2003
|
throw new Error("Trend dates must be valid YYYY-MM-DD values with start_date on or before end_date.");
|
|
1993
2004
|
}
|
|
1994
2005
|
const type = resolveInsightsType(params.entity_type);
|
|
1995
|
-
const resolution = await resolveEntities(resolutionProvider, params.entities, type, signal, toolCallId);
|
|
2006
|
+
const resolution = await resolveEntities(resolutionProvider, params.entities, type, signal, toolCallId, "entities");
|
|
1996
2007
|
if (resolution.issues.length > 0)
|
|
1997
2008
|
return needsInput("trends", resolution.issues, "Choose or correct the unresolved Qloo entities.");
|
|
1998
2009
|
const requests = resolution.resolved.map((entity) => ({
|
|
@@ -188,12 +188,12 @@ var QLOO_LIMIT_SCHEMA = Type.Optional(Type.Integer({
|
|
|
188
188
|
var QLOO_ENTITY_INPUTS_SCHEMA = Type.Array(Type.String({ minLength: 1 }), {
|
|
189
189
|
minItems: 1,
|
|
190
190
|
maxItems: 10,
|
|
191
|
-
description: "
|
|
191
|
+
description: "Named entities or Qloo entity UUIDs; use tag fields for abstract concepts."
|
|
192
192
|
});
|
|
193
193
|
var QLOO_TAG_INPUTS_SCHEMA = Type.Array(Type.String({ minLength: 1 }), {
|
|
194
194
|
minItems: 1,
|
|
195
195
|
maxItems: 10,
|
|
196
|
-
description: "
|
|
196
|
+
description: "Genres, moods, styles, traits, or other concepts, or Qloo tag URNs; not named entities."
|
|
197
197
|
});
|
|
198
198
|
var QLOO_DEMOGRAPHIC_SCHEMA = Type.String({
|
|
199
199
|
minLength: 1,
|
|
@@ -353,6 +353,7 @@ var QLOO_WORKFLOW_METADATA = {
|
|
|
353
353
|
promptSnippet: "Recommend entities from a combined Qloo taste profile",
|
|
354
354
|
promptGuidelines: [
|
|
355
355
|
"Pass every related taste and audience signal together in one qloo_recommend call.",
|
|
356
|
+
"Put named things in signals; put genres, moods, styles, traits, and concepts such as quiet luxury in signal_tags; reuse returned Qloo UUIDs and tag URNs.",
|
|
356
357
|
"Use signal_location for audience location and filter_location to constrain result geography.",
|
|
357
358
|
"Include demographic and location in the same call when both describe the audience."
|
|
358
359
|
],
|
|
@@ -363,7 +364,10 @@ var QLOO_WORKFLOW_METADATA = {
|
|
|
363
364
|
label: "Qloo rank",
|
|
364
365
|
description: "Rank one supplied option set against one shared entity, location, demographic, and tag profile. Scores from separate calls are not comparable.",
|
|
365
366
|
promptSnippet: "Rank a caller-supplied shortlist with Qloo",
|
|
366
|
-
promptGuidelines: [
|
|
367
|
+
promptGuidelines: [
|
|
368
|
+
"Pass every option in one qloo_rank call so scores remain comparable.",
|
|
369
|
+
"Put named things in entity fields and concepts in tag fields; reuse returned Qloo UUIDs and tag URNs."
|
|
370
|
+
],
|
|
367
371
|
documentation: ["https://docs.qloo.com/reference/insights-api-deep-dive"]
|
|
368
372
|
},
|
|
369
373
|
describe: {
|
|
@@ -578,6 +582,60 @@ function scalar(record, ...keys) {
|
|
|
578
582
|
function normalizeName(value) {
|
|
579
583
|
return value.normalize("NFKC").trim().toLocaleLowerCase("en-US");
|
|
580
584
|
}
|
|
585
|
+
var ENTITY_CONTEXT_STOP_WORDS = /* @__PURE__ */ new Set([
|
|
586
|
+
"a",
|
|
587
|
+
"an",
|
|
588
|
+
"and",
|
|
589
|
+
"at",
|
|
590
|
+
"by",
|
|
591
|
+
"for",
|
|
592
|
+
"from",
|
|
593
|
+
"in",
|
|
594
|
+
"of",
|
|
595
|
+
"on",
|
|
596
|
+
"the",
|
|
597
|
+
"to",
|
|
598
|
+
"with"
|
|
599
|
+
]);
|
|
600
|
+
function contextTokens(value) {
|
|
601
|
+
return [...new Set(value.normalize("NFKC").toLocaleLowerCase("en-US").match(/[\p{L}\p{N}]+/gu)?.filter((token) => token.length >= 2 && !ENTITY_CONTEXT_STOP_WORDS.has(token)) ?? [])];
|
|
602
|
+
}
|
|
603
|
+
function candidateContextText(candidate) {
|
|
604
|
+
const address = typeof candidate.address === "string" ? candidate.address : candidate.address === void 0 ? "" : JSON.stringify(candidate.address);
|
|
605
|
+
return [candidate.name, candidate.type, candidate.description, address].filter((value) => typeof value === "string").join(" ");
|
|
606
|
+
}
|
|
607
|
+
function contextualEntityMatch(input, candidates) {
|
|
608
|
+
if (candidates.length < 2)
|
|
609
|
+
return { candidates };
|
|
610
|
+
const inputTokens = contextTokens(input);
|
|
611
|
+
if (inputTokens.length < 2)
|
|
612
|
+
return { candidates };
|
|
613
|
+
const ranked = candidates.map((candidate) => {
|
|
614
|
+
const searchable = new Set(contextTokens(candidateContextText(candidate)));
|
|
615
|
+
return {
|
|
616
|
+
candidate,
|
|
617
|
+
matched: inputTokens.filter((token) => searchable.has(token)).length
|
|
618
|
+
};
|
|
619
|
+
});
|
|
620
|
+
const maximum = Math.max(...ranked.map(({ matched }) => matched));
|
|
621
|
+
const strongest = ranked.filter(({ matched }) => matched === maximum);
|
|
622
|
+
const decisive = maximum === inputTokens.length && strongest.length === 1 && ranked.some(({ matched }) => matched < maximum);
|
|
623
|
+
if (decisive && strongest[0]) {
|
|
624
|
+
return {
|
|
625
|
+
candidates: [strongest[0].candidate],
|
|
626
|
+
selected: strongest[0].candidate,
|
|
627
|
+
alternatives: candidates.filter(({ id }) => id !== strongest[0]?.candidate.id).slice(0, 4),
|
|
628
|
+
warning: "Qloo selected the only candidate matching every supplied name and location term; reuse its Qloo ID to override future name resolution."
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
if (maximum === inputTokens.length && strongest.length < candidates.length) {
|
|
632
|
+
return {
|
|
633
|
+
candidates: strongest.map(({ candidate }) => candidate),
|
|
634
|
+
warning: "Qloo candidate choices were narrowed using the supplied name and location terms."
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
return { candidates };
|
|
638
|
+
}
|
|
581
639
|
function requestOptions(context) {
|
|
582
640
|
return {
|
|
583
641
|
...context.signal ? { signal: context.signal } : {},
|
|
@@ -686,7 +744,7 @@ function createNativeQlooResolutionProvider(client, options = {}) {
|
|
|
686
744
|
contract_version: QLOO_RESOLUTION_CONTRACT_VERSION,
|
|
687
745
|
provider_id: "qloo_api",
|
|
688
746
|
name: "Qloo native resolution",
|
|
689
|
-
entity_strategy: "
|
|
747
|
+
entity_strategy: "exact_name_context_or_choice",
|
|
690
748
|
tag_strategy: "target_scoped_semantic_search_exact_name_or_choice",
|
|
691
749
|
remote: true,
|
|
692
750
|
uses_model: false,
|
|
@@ -696,6 +754,7 @@ function createNativeQlooResolutionProvider(client, options = {}) {
|
|
|
696
754
|
const cacheKey = (kind, input, scope) => JSON.stringify([
|
|
697
755
|
QLOO_RESOLUTION_CONTRACT_VERSION,
|
|
698
756
|
descriptor.provider_id,
|
|
757
|
+
descriptor.entity_strategy,
|
|
699
758
|
descriptor.tag_strategy,
|
|
700
759
|
kind,
|
|
701
760
|
scope ?? "",
|
|
@@ -752,7 +811,7 @@ function createNativeQlooResolutionProvider(client, options = {}) {
|
|
|
752
811
|
return { input, status: "ambiguous", candidates: exact2.slice(0, 5), provenance: source2 };
|
|
753
812
|
return { input, status: "not_found", provenance: source2 };
|
|
754
813
|
}
|
|
755
|
-
const source = provenance(descriptor, "/search", "
|
|
814
|
+
const source = provenance(descriptor, "/search", "exact_name_context_or_choice");
|
|
756
815
|
const response = await client.searchEntities({
|
|
757
816
|
query: input,
|
|
758
817
|
...type ? { types: type } : {},
|
|
@@ -766,11 +825,19 @@ function createNativeQlooResolutionProvider(client, options = {}) {
|
|
|
766
825
|
const alternatives = candidates.filter(({ id }) => id !== exact[0]?.id).slice(0, 4);
|
|
767
826
|
return resolved(input, exact[0], "exact", source, { alternatives });
|
|
768
827
|
}
|
|
828
|
+
const contextual = contextualEntityMatch(input, candidates);
|
|
829
|
+
if (contextual.selected) {
|
|
830
|
+
return resolved(input, contextual.selected, "semantic", source, {
|
|
831
|
+
...contextual.alternatives ? { alternatives: contextual.alternatives } : {},
|
|
832
|
+
...contextual.warning ? { warnings: [contextual.warning] } : {}
|
|
833
|
+
});
|
|
834
|
+
}
|
|
769
835
|
return {
|
|
770
836
|
input,
|
|
771
837
|
status: "ambiguous",
|
|
772
|
-
candidates: (exact.length > 0 ? exact : candidates).slice(0, 5),
|
|
773
|
-
provenance: source
|
|
838
|
+
candidates: (exact.length > 0 ? exact : contextual.candidates).slice(0, 5),
|
|
839
|
+
provenance: source,
|
|
840
|
+
...contextual.warning ? { warnings: [contextual.warning] } : {}
|
|
774
841
|
};
|
|
775
842
|
};
|
|
776
843
|
const resolveTag = async (input, targetType, context) => {
|
package/dist/router.js
CHANGED
|
@@ -3940,7 +3940,7 @@ var qloo_workflow_contract_default = {
|
|
|
3940
3940
|
},
|
|
3941
3941
|
minItems: 1,
|
|
3942
3942
|
maxItems: 10,
|
|
3943
|
-
description: "
|
|
3943
|
+
description: "Named entities or Qloo entity UUIDs; use tag fields for abstract concepts."
|
|
3944
3944
|
},
|
|
3945
3945
|
signal_tags: {
|
|
3946
3946
|
type: "array",
|
|
@@ -3950,7 +3950,7 @@ var qloo_workflow_contract_default = {
|
|
|
3950
3950
|
},
|
|
3951
3951
|
minItems: 1,
|
|
3952
3952
|
maxItems: 10,
|
|
3953
|
-
description: "
|
|
3953
|
+
description: "Genres, moods, styles, traits, or other concepts, or Qloo tag URNs; not named entities."
|
|
3954
3954
|
},
|
|
3955
3955
|
signal_tags_operator: {
|
|
3956
3956
|
type: "string",
|
|
@@ -3986,7 +3986,7 @@ var qloo_workflow_contract_default = {
|
|
|
3986
3986
|
},
|
|
3987
3987
|
minItems: 1,
|
|
3988
3988
|
maxItems: 10,
|
|
3989
|
-
description: "
|
|
3989
|
+
description: "Genres, moods, styles, traits, or other concepts, or Qloo tag URNs; not named entities."
|
|
3990
3990
|
},
|
|
3991
3991
|
include_tags_operator: {
|
|
3992
3992
|
type: "string",
|
|
@@ -4004,7 +4004,7 @@ var qloo_workflow_contract_default = {
|
|
|
4004
4004
|
},
|
|
4005
4005
|
minItems: 1,
|
|
4006
4006
|
maxItems: 10,
|
|
4007
|
-
description: "
|
|
4007
|
+
description: "Genres, moods, styles, traits, or other concepts, or Qloo tag URNs; not named entities."
|
|
4008
4008
|
},
|
|
4009
4009
|
exclude_tags_operator: {
|
|
4010
4010
|
type: "string",
|
|
@@ -4075,7 +4075,7 @@ var qloo_workflow_contract_default = {
|
|
|
4075
4075
|
},
|
|
4076
4076
|
minItems: 1,
|
|
4077
4077
|
maxItems: 10,
|
|
4078
|
-
description: "
|
|
4078
|
+
description: "Named entities or Qloo entity UUIDs; use tag fields for abstract concepts."
|
|
4079
4079
|
},
|
|
4080
4080
|
option_type: {
|
|
4081
4081
|
type: "string",
|
|
@@ -4100,7 +4100,7 @@ var qloo_workflow_contract_default = {
|
|
|
4100
4100
|
},
|
|
4101
4101
|
minItems: 1,
|
|
4102
4102
|
maxItems: 10,
|
|
4103
|
-
description: "
|
|
4103
|
+
description: "Named entities or Qloo entity UUIDs; use tag fields for abstract concepts."
|
|
4104
4104
|
},
|
|
4105
4105
|
signal_location: {
|
|
4106
4106
|
type: "string",
|
|
@@ -4124,7 +4124,7 @@ var qloo_workflow_contract_default = {
|
|
|
4124
4124
|
},
|
|
4125
4125
|
minItems: 1,
|
|
4126
4126
|
maxItems: 10,
|
|
4127
|
-
description: "
|
|
4127
|
+
description: "Genres, moods, styles, traits, or other concepts, or Qloo tag URNs; not named entities."
|
|
4128
4128
|
},
|
|
4129
4129
|
exclude_tags: {
|
|
4130
4130
|
type: "array",
|
|
@@ -4134,7 +4134,7 @@ var qloo_workflow_contract_default = {
|
|
|
4134
4134
|
},
|
|
4135
4135
|
minItems: 1,
|
|
4136
4136
|
maxItems: 10,
|
|
4137
|
-
description: "
|
|
4137
|
+
description: "Genres, moods, styles, traits, or other concepts, or Qloo tag URNs; not named entities."
|
|
4138
4138
|
}
|
|
4139
4139
|
},
|
|
4140
4140
|
additionalProperties: false
|
|
@@ -4330,7 +4330,7 @@ var qloo_workflow_contract_default = {
|
|
|
4330
4330
|
},
|
|
4331
4331
|
minItems: 1,
|
|
4332
4332
|
maxItems: 10,
|
|
4333
|
-
description: "
|
|
4333
|
+
description: "Named entities or Qloo entity UUIDs; use tag fields for abstract concepts."
|
|
4334
4334
|
},
|
|
4335
4335
|
group_b: {
|
|
4336
4336
|
type: "array",
|
|
@@ -4340,7 +4340,7 @@ var qloo_workflow_contract_default = {
|
|
|
4340
4340
|
},
|
|
4341
4341
|
minItems: 1,
|
|
4342
4342
|
maxItems: 10,
|
|
4343
|
-
description: "
|
|
4343
|
+
description: "Named entities or Qloo entity UUIDs; use tag fields for abstract concepts."
|
|
4344
4344
|
},
|
|
4345
4345
|
target_type: {
|
|
4346
4346
|
type: "string",
|
|
@@ -4413,7 +4413,7 @@ var qloo_workflow_contract_default = {
|
|
|
4413
4413
|
},
|
|
4414
4414
|
minItems: 1,
|
|
4415
4415
|
maxItems: 10,
|
|
4416
|
-
description: "
|
|
4416
|
+
description: "Named entities or Qloo entity UUIDs; use tag fields for abstract concepts."
|
|
4417
4417
|
},
|
|
4418
4418
|
entity_type: {
|
|
4419
4419
|
type: "string",
|
|
@@ -4721,7 +4721,7 @@ var qloo_workflow_contract_default = {
|
|
|
4721
4721
|
}
|
|
4722
4722
|
]
|
|
4723
4723
|
},
|
|
4724
|
-
contract_checksum: "sha256:
|
|
4724
|
+
contract_checksum: "sha256:762bdcc5e14ff797b6de739d2658a83f78af35e68d7a5a3449455be6046924f5"
|
|
4725
4725
|
};
|
|
4726
4726
|
|
|
4727
4727
|
// apps/qloo-cli/dist/mcp.js
|
|
@@ -4,10 +4,12 @@ const require = __qlooCreateRequire(import.meta.url);
|
|
|
4
4
|
// apps/qloo-harness/dist/runtime/explore-policy.js
|
|
5
5
|
import { findGuidedGoal, guidedGoalLabel, QLOO_GUIDED_GOALS, qlooNextActions, qlooStarterIdeaLines } from "../guided-journey.js";
|
|
6
6
|
import { getQlooProfileDefinition } from "../profiles.js";
|
|
7
|
-
import { applyResolutionChoices,
|
|
7
|
+
import { applyResolutionChoices, paginateQlooResult, parseQlooRawPage, resolutionCandidates } from "../qloo-presentation.js";
|
|
8
8
|
import { QLOO_NO_GUIDED_START_ENVIRONMENT_VARIABLE } from "../setup.js";
|
|
9
9
|
import { isBlockedPiInteractiveCommand } from "./pi-command-policy.js";
|
|
10
10
|
import { renderQlooHeader } from "./qloo-header.js";
|
|
11
|
+
import { selectQlooResolutionChoices } from "./resolution-choice-ui.js";
|
|
12
|
+
import { QLOO_RESOLUTION_CHOICE_ENTRY_TYPE, QlooResolutionMemory, clearResolutionChoicesEvent, forgetResolutionChoiceEvent, rememberResolutionChoicesEvent } from "./resolution-memory.js";
|
|
11
13
|
var QLOO_WORKSPACE_TOOL_NAMES = [
|
|
12
14
|
...getQlooProfileDefinition("build").workspaceTools
|
|
13
15
|
];
|
|
@@ -24,6 +26,12 @@ var AUTOMATIC_DIAGNOSTIC_CODES = /* @__PURE__ */ new Set([
|
|
|
24
26
|
"RESOLVER_CONNECT",
|
|
25
27
|
"RESOLVER_TIMEOUT"
|
|
26
28
|
]);
|
|
29
|
+
function formatContextUsage(usage) {
|
|
30
|
+
const tokens = usage?.tokens ?? "unknown";
|
|
31
|
+
const contextWindow = usage?.contextWindow ?? "unknown";
|
|
32
|
+
const percentage = usage?.percent === null || usage?.percent === void 0 ? "percentage unavailable" : `${usage.percent.toFixed(1)}%`;
|
|
33
|
+
return `${tokens} / ${contextWindow} tokens (${percentage})`;
|
|
34
|
+
}
|
|
27
35
|
var reservedBuiltInTools = /* @__PURE__ */ new Set([
|
|
28
36
|
...QLOO_WORKSPACE_TOOL_NAMES,
|
|
29
37
|
"grep",
|
|
@@ -141,6 +149,7 @@ function createHarnessPolicyExtension(profile, allowedToolNames, runtimeInfo, co
|
|
|
141
149
|
let lastQlooToolName;
|
|
142
150
|
let rawPage = 1;
|
|
143
151
|
let activeProfile = profile;
|
|
152
|
+
const resolutionMemory = new QlooResolutionMemory();
|
|
144
153
|
const customToolNames = [...allowedToolNames].filter((name) => !reservedBuiltInTools.has(name));
|
|
145
154
|
const activeDefinition = () => getQlooProfileDefinition(activeProfile);
|
|
146
155
|
const activeAllowedToolNames = () => /* @__PURE__ */ new Set([
|
|
@@ -156,6 +165,7 @@ function createHarnessPolicyExtension(profile, allowedToolNames, runtimeInfo, co
|
|
|
156
165
|
};
|
|
157
166
|
const restoreLastQlooResult = (context) => {
|
|
158
167
|
const entries = context.sessionManager?.getBranch() ?? [];
|
|
168
|
+
resolutionMemory.restore(entries);
|
|
159
169
|
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
160
170
|
const entry = entries[index];
|
|
161
171
|
if (entry === null || typeof entry !== "object")
|
|
@@ -190,7 +200,64 @@ ${bounded}`);
|
|
|
190
200
|
const setNextWidget = (context) => {
|
|
191
201
|
if (context?.mode !== "tui" || !context.ui?.setWidget)
|
|
192
202
|
return;
|
|
193
|
-
context.ui.setWidget(QLOO_NEXT_WIDGET_KEY, ["Next: /next
|
|
203
|
+
context.ui.setWidget(QLOO_NEXT_WIDGET_KEY, ["Next: /next follow-up \xB7 /why interpretation \xB7 /request API request \xB7 /matches remembered choices"], { placement: "belowEditor" });
|
|
204
|
+
};
|
|
205
|
+
const bindingLabel = (binding, index) => {
|
|
206
|
+
const prefix = index === void 0 ? "" : `${index + 1}. `;
|
|
207
|
+
const scope = binding.scope ? ` \xB7 ${binding.scope.replace(/^urn:entity:/u, "")}` : "";
|
|
208
|
+
return `${prefix}${binding.input} \u2192 ${binding.selected.name} \xB7 ${binding.input_kind}${scope}`;
|
|
209
|
+
};
|
|
210
|
+
const decorateConfirmedChoices = (details, bindings) => {
|
|
211
|
+
if (bindings.length === 0)
|
|
212
|
+
return { ...details };
|
|
213
|
+
const resolution = details.resolution !== null && typeof details.resolution === "object" && !Array.isArray(details.resolution) ? details.resolution : {};
|
|
214
|
+
return {
|
|
215
|
+
...details,
|
|
216
|
+
resolution: {
|
|
217
|
+
...resolution,
|
|
218
|
+
confirmed_choices: bindings.map((binding) => ({
|
|
219
|
+
input: binding.input,
|
|
220
|
+
input_kind: binding.input_kind,
|
|
221
|
+
field: binding.field,
|
|
222
|
+
...binding.scope ? { scope: binding.scope } : {},
|
|
223
|
+
selected: { ...binding.selected },
|
|
224
|
+
instruction: "Reuse this Qloo identifier verbatim for this meaning in later calls."
|
|
225
|
+
}))
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
const showRememberedMatches = async (args, context) => {
|
|
230
|
+
if (args.trim().length > 0) {
|
|
231
|
+
context.ui.notify("Usage: /matches", "warning");
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const bindings = resolutionMemory.list();
|
|
235
|
+
if (bindings.length === 0) {
|
|
236
|
+
context.ui.notify("No user-confirmed Qloo matches are remembered in this session.");
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const visible = bindings.slice(-20);
|
|
240
|
+
const forgetLabels = visible.map((binding2, index2) => `Forget ${bindingLabel(binding2, index2)}`);
|
|
241
|
+
const clearLabel = `Forget all ${bindings.length} remembered matches`;
|
|
242
|
+
const cancelLabel = "Keep remembered matches";
|
|
243
|
+
const selected = await context.ui.select("Remembered Qloo matches", [...forgetLabels, clearLabel, cancelLabel]);
|
|
244
|
+
if (!selected || selected === cancelLabel)
|
|
245
|
+
return;
|
|
246
|
+
if (selected === clearLabel) {
|
|
247
|
+
const confirmed = await context.ui.confirm("Forget all remembered Qloo matches?", "The harness will ask again when any of these inputs are ambiguous.");
|
|
248
|
+
if (!confirmed)
|
|
249
|
+
return;
|
|
250
|
+
resolutionMemory.clear();
|
|
251
|
+
pi.appendEntry(QLOO_RESOLUTION_CHOICE_ENTRY_TYPE, clearResolutionChoicesEvent());
|
|
252
|
+
context.ui.notify("Forgot all remembered Qloo matches.");
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const index = forgetLabels.indexOf(selected);
|
|
256
|
+
const binding = visible[index];
|
|
257
|
+
if (index < 0 || !binding || !resolutionMemory.forget(binding))
|
|
258
|
+
return;
|
|
259
|
+
pi.appendEntry(QLOO_RESOLUTION_CHOICE_ENTRY_TYPE, forgetResolutionChoiceEvent(binding));
|
|
260
|
+
context.ui.notify(`Forgot \u201C${binding.input}\u201D \u2192 ${binding.selected.name}.`);
|
|
194
261
|
};
|
|
195
262
|
const recordQlooResult = (toolName, input, details, context) => {
|
|
196
263
|
const boundedInput = boundedWorkflowInput(input);
|
|
@@ -371,6 +438,10 @@ ${bounded}`);
|
|
|
371
438
|
description: "Choose a guided Qloo goal and editable starter prompt",
|
|
372
439
|
handler: async (_args, context) => showGuidedStart(context)
|
|
373
440
|
});
|
|
441
|
+
pi.registerCommand("exit", {
|
|
442
|
+
description: "Exit the Qloo harness cleanly",
|
|
443
|
+
handler: async (_args, context) => context.shutdown()
|
|
444
|
+
});
|
|
374
445
|
pi.registerCommand("status", {
|
|
375
446
|
description: "Show Qloo profile, transport, model, workspace, and context usage",
|
|
376
447
|
handler: async (_args, context) => {
|
|
@@ -384,11 +455,23 @@ ${bounded}`);
|
|
|
384
455
|
`Fallback: ${runtimeInfo?.fallback ?? "none"}`,
|
|
385
456
|
`Model: ${model}`,
|
|
386
457
|
`Workspace: ${context.cwd}`,
|
|
387
|
-
`
|
|
458
|
+
`Remembered matches: ${resolutionMemory.list().length}`,
|
|
459
|
+
`Context: ${formatContextUsage(usage)}`,
|
|
388
460
|
`Turn budget: ${activeDefinition().maxToolCallsPerTurn} calls; ${activeDefinition().maxIdenticalToolCallsPerTurn} identical calls`
|
|
389
461
|
].join("\n"));
|
|
390
462
|
}
|
|
391
463
|
});
|
|
464
|
+
pi.registerCommand("usage", {
|
|
465
|
+
description: "Show current model context-window usage",
|
|
466
|
+
handler: async (_args, context) => {
|
|
467
|
+
const model = context.model ? `${context.model.provider}/${context.model.id}` : "unavailable";
|
|
468
|
+
context.ui.notify([
|
|
469
|
+
"Qloo context usage",
|
|
470
|
+
`Model: ${model}`,
|
|
471
|
+
`Context used: ${formatContextUsage(context.getContextUsage())}`
|
|
472
|
+
].join("\n"));
|
|
473
|
+
}
|
|
474
|
+
});
|
|
392
475
|
pi.registerCommand("why", {
|
|
393
476
|
description: "Show the normalized intent for the latest Qloo workflow",
|
|
394
477
|
handler: async (_args, context) => {
|
|
@@ -483,6 +566,10 @@ ${bounded}`);
|
|
|
483
566
|
await retryLastWorkflow(context);
|
|
484
567
|
}
|
|
485
568
|
});
|
|
569
|
+
pi.registerCommand("matches", {
|
|
570
|
+
description: "Inspect or forget user-confirmed Qloo entity and tag matches",
|
|
571
|
+
handler: async (args, context) => showRememberedMatches(args, context)
|
|
572
|
+
});
|
|
486
573
|
pi.on("session_start", async (event, context) => {
|
|
487
574
|
if (activeProfile === "build" && event.reason !== "startup") {
|
|
488
575
|
activateProfile("plan", context);
|
|
@@ -549,42 +636,35 @@ ${bounded}`);
|
|
|
549
636
|
if (event.details === null || typeof event.details !== "object" || Array.isArray(event.details))
|
|
550
637
|
return;
|
|
551
638
|
const details = event.details;
|
|
552
|
-
const issues = resolutionCandidates(details);
|
|
639
|
+
const issues = resolutionCandidates(details, event.toolName, event.input);
|
|
553
640
|
if (issues.length === 0 || !context.hasUI || !commandActions.retry) {
|
|
554
641
|
recordQlooResult(event.toolName, event.input, details, context);
|
|
555
642
|
await maybeRunAutomaticDiagnosis(details, context);
|
|
556
643
|
return;
|
|
557
644
|
}
|
|
558
|
-
const choices =
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
const selected = await context.ui.select(`Choose the Qloo match for \u201C${issue.input.slice(0, 120)}\u201D`, [...labels, cancelLabel]);
|
|
563
|
-
if (!selected || selected === cancelLabel) {
|
|
564
|
-
recordQlooResult(event.toolName, event.input, details, context);
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
const selectedIndex = labels.indexOf(selected);
|
|
568
|
-
const selectedId = issue.candidates[selectedIndex]?.id;
|
|
569
|
-
if (selectedIndex < 0 || typeof selectedId !== "string") {
|
|
570
|
-
recordQlooResult(event.toolName, event.input, details, context);
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
choices.push({ input: issue.input, selectedId });
|
|
645
|
+
const choices = await selectQlooResolutionChoices(context.ui, issues, context.mode === "tui");
|
|
646
|
+
if (!choices) {
|
|
647
|
+
recordQlooResult(event.toolName, event.input, details, context);
|
|
648
|
+
return;
|
|
574
649
|
}
|
|
575
650
|
const patchedInput = applyResolutionChoices(event.toolName, event.input, choices);
|
|
576
651
|
if (!patchedInput) {
|
|
577
652
|
recordQlooResult(event.toolName, event.input, details, context);
|
|
578
653
|
return;
|
|
579
654
|
}
|
|
655
|
+
const remembered = resolutionMemory.remember(event.toolName, choices);
|
|
656
|
+
if (remembered.length > 0) {
|
|
657
|
+
pi.appendEntry(QLOO_RESOLUTION_CHOICE_ENTRY_TYPE, rememberResolutionChoicesEvent(remembered));
|
|
658
|
+
}
|
|
580
659
|
try {
|
|
581
660
|
context.ui.setWorkingMessage("Qloo \xB7 applying your selection\u2026");
|
|
582
661
|
const retriedDetails = await commandActions.retry(event.toolName, patchedInput);
|
|
583
|
-
|
|
584
|
-
|
|
662
|
+
const confirmedDetails = decorateConfirmedChoices(retriedDetails, remembered);
|
|
663
|
+
recordQlooResult(event.toolName, patchedInput, confirmedDetails, context);
|
|
664
|
+
await maybeRunAutomaticDiagnosis(confirmedDetails, context);
|
|
585
665
|
return {
|
|
586
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
587
|
-
details:
|
|
666
|
+
content: [{ type: "text", text: JSON.stringify(confirmedDetails) }],
|
|
667
|
+
details: confirmedDetails,
|
|
588
668
|
isError: false
|
|
589
669
|
};
|
|
590
670
|
} catch {
|
|
@@ -603,6 +683,11 @@ ${bounded}`);
|
|
|
603
683
|
reason: `Tool "${event.toolName}" is unavailable in Qloo's ${activeProfile} profile.`
|
|
604
684
|
};
|
|
605
685
|
}
|
|
686
|
+
if (event.toolName.startsWith("qloo_") && event.toolName !== "qloo_capabilities") {
|
|
687
|
+
const remembered = resolutionMemory.apply(event.toolName, event.input);
|
|
688
|
+
if (remembered)
|
|
689
|
+
Object.assign(event.input, remembered.input);
|
|
690
|
+
}
|
|
606
691
|
toolCallsThisTurn += 1;
|
|
607
692
|
if (toolCallsThisTurn > definition.maxToolCallsPerTurn) {
|
|
608
693
|
return {
|