ARCH: ввести resumable discovery loop state для clarification follow-up
This commit is contained in:
parent
0d3b33578e
commit
e4cab85dd9
|
|
@ -5,6 +5,13 @@ exports.readAssistantMcpDiscoveryEntityAmbiguityCandidates = readAssistantMcpDis
|
||||||
exports.readAssistantMcpDiscoveryEntityCandidates = readAssistantMcpDiscoveryEntityCandidates;
|
exports.readAssistantMcpDiscoveryEntityCandidates = readAssistantMcpDiscoveryEntityCandidates;
|
||||||
exports.readAssistantMcpDiscoveryPilotScope = readAssistantMcpDiscoveryPilotScope;
|
exports.readAssistantMcpDiscoveryPilotScope = readAssistantMcpDiscoveryPilotScope;
|
||||||
exports.readAssistantMcpDiscoveryRankingNeed = readAssistantMcpDiscoveryRankingNeed;
|
exports.readAssistantMcpDiscoveryRankingNeed = readAssistantMcpDiscoveryRankingNeed;
|
||||||
|
exports.readAssistantMcpDiscoveryLoopStatus = readAssistantMcpDiscoveryLoopStatus;
|
||||||
|
exports.readAssistantMcpDiscoveryLoopSelectedChainId = readAssistantMcpDiscoveryLoopSelectedChainId;
|
||||||
|
exports.readAssistantMcpDiscoveryLoopPendingAxes = readAssistantMcpDiscoveryLoopPendingAxes;
|
||||||
|
exports.readAssistantMcpDiscoveryLoopProvidedAxes = readAssistantMcpDiscoveryLoopProvidedAxes;
|
||||||
|
exports.readAssistantMcpDiscoveryLoopAskedDomainFamily = readAssistantMcpDiscoveryLoopAskedDomainFamily;
|
||||||
|
exports.readAssistantMcpDiscoveryLoopAskedActionFamily = readAssistantMcpDiscoveryLoopAskedActionFamily;
|
||||||
|
exports.readAssistantMcpDiscoveryLoopUnsupportedFamily = readAssistantMcpDiscoveryLoopUnsupportedFamily;
|
||||||
exports.readAssistantMcpDiscoveryMetadataRouteFamily = readAssistantMcpDiscoveryMetadataRouteFamily;
|
exports.readAssistantMcpDiscoveryMetadataRouteFamily = readAssistantMcpDiscoveryMetadataRouteFamily;
|
||||||
exports.readAssistantMcpDiscoveryMetadataRouteFamilySelectionBasis = readAssistantMcpDiscoveryMetadataRouteFamilySelectionBasis;
|
exports.readAssistantMcpDiscoveryMetadataRouteFamilySelectionBasis = readAssistantMcpDiscoveryMetadataRouteFamilySelectionBasis;
|
||||||
exports.readAssistantMcpDiscoveryMetadataSelectedEntitySet = readAssistantMcpDiscoveryMetadataSelectedEntitySet;
|
exports.readAssistantMcpDiscoveryMetadataSelectedEntitySet = readAssistantMcpDiscoveryMetadataSelectedEntitySet;
|
||||||
|
|
@ -98,6 +105,9 @@ function readAssistantMcpDiscoveryActionFamily(debug, toNonEmptyString = fallbac
|
||||||
function readAssistantMcpDiscoveryBridge(debug) {
|
function readAssistantMcpDiscoveryBridge(debug) {
|
||||||
return toRecordObject(readAssistantMcpDiscoveryEntry(debug)?.bridge);
|
return toRecordObject(readAssistantMcpDiscoveryEntry(debug)?.bridge);
|
||||||
}
|
}
|
||||||
|
function readAssistantMcpDiscoveryLoopState(debug) {
|
||||||
|
return toRecordObject(readAssistantMcpDiscoveryBridge(debug)?.loop_state);
|
||||||
|
}
|
||||||
function readAssistantMcpDiscoveryDerivedMetadataSurface(debug) {
|
function readAssistantMcpDiscoveryDerivedMetadataSurface(debug) {
|
||||||
const bridge = readAssistantMcpDiscoveryBridge(debug);
|
const bridge = readAssistantMcpDiscoveryBridge(debug);
|
||||||
const pilot = toRecordObject(bridge?.pilot);
|
const pilot = toRecordObject(bridge?.pilot);
|
||||||
|
|
@ -152,7 +162,37 @@ function readAssistantMcpDiscoveryPilotScope(debug, toNonEmptyString = fallbackT
|
||||||
return toNonEmptyString(pilot?.pilot_scope);
|
return toNonEmptyString(pilot?.pilot_scope);
|
||||||
}
|
}
|
||||||
function readAssistantMcpDiscoveryRankingNeed(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
function readAssistantMcpDiscoveryRankingNeed(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
return toNonEmptyString(readAssistantMcpDiscoveryDataNeedGraph(debug)?.ranking_need);
|
return (toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.ranking_need) ??
|
||||||
|
toNonEmptyString(readAssistantMcpDiscoveryDataNeedGraph(debug)?.ranking_need));
|
||||||
|
}
|
||||||
|
function readAssistantMcpDiscoveryLoopStatus(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
|
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.loop_status);
|
||||||
|
}
|
||||||
|
function readAssistantMcpDiscoveryLoopSelectedChainId(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
|
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.selected_chain_id);
|
||||||
|
}
|
||||||
|
function readAssistantMcpDiscoveryLoopPendingAxes(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
|
const values = readAssistantMcpDiscoveryLoopState(debug)?.pending_axes;
|
||||||
|
if (!Array.isArray(values)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return values.map((item) => toNonEmptyString(item)).filter((item) => Boolean(item));
|
||||||
|
}
|
||||||
|
function readAssistantMcpDiscoveryLoopProvidedAxes(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
|
const values = readAssistantMcpDiscoveryLoopState(debug)?.provided_axes;
|
||||||
|
if (!Array.isArray(values)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return values.map((item) => toNonEmptyString(item)).filter((item) => Boolean(item));
|
||||||
|
}
|
||||||
|
function readAssistantMcpDiscoveryLoopAskedDomainFamily(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
|
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.asked_domain_family);
|
||||||
|
}
|
||||||
|
function readAssistantMcpDiscoveryLoopAskedActionFamily(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
|
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.asked_action_family);
|
||||||
|
}
|
||||||
|
function readAssistantMcpDiscoveryLoopUnsupportedFamily(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
|
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.unsupported_but_understood_family);
|
||||||
}
|
}
|
||||||
function readAssistantMcpDiscoveryMetadataRouteFamily(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
function readAssistantMcpDiscoveryMetadataRouteFamily(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
return toNonEmptyString(readAssistantMcpDiscoveryDerivedMetadataSurface(debug)?.downstream_route_family);
|
return toNonEmptyString(readAssistantMcpDiscoveryDerivedMetadataSurface(debug)?.downstream_route_family);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION = void 0;
|
exports.ASSISTANT_MCP_DISCOVERY_LOOP_STATE_SCHEMA_VERSION = exports.ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION = void 0;
|
||||||
exports.runAssistantMcpDiscoveryRuntimeBridge = runAssistantMcpDiscoveryRuntimeBridge;
|
exports.runAssistantMcpDiscoveryRuntimeBridge = runAssistantMcpDiscoveryRuntimeBridge;
|
||||||
const assistantMcpDiscoveryAnswerAdapter_1 = require("./assistantMcpDiscoveryAnswerAdapter");
|
const assistantMcpDiscoveryAnswerAdapter_1 = require("./assistantMcpDiscoveryAnswerAdapter");
|
||||||
const assistantMcpDiscoveryPilotExecutor_1 = require("./assistantMcpDiscoveryPilotExecutor");
|
const assistantMcpDiscoveryPilotExecutor_1 = require("./assistantMcpDiscoveryPilotExecutor");
|
||||||
const assistantMcpDiscoveryPlanner_1 = require("./assistantMcpDiscoveryPlanner");
|
const assistantMcpDiscoveryPlanner_1 = require("./assistantMcpDiscoveryPlanner");
|
||||||
exports.ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION = "assistant_mcp_discovery_runtime_bridge_v1";
|
exports.ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION = "assistant_mcp_discovery_runtime_bridge_v1";
|
||||||
|
exports.ASSISTANT_MCP_DISCOVERY_LOOP_STATE_SCHEMA_VERSION = "assistant_mcp_discovery_loop_state_v1";
|
||||||
function normalizeReasonCode(value) {
|
function normalizeReasonCode(value) {
|
||||||
const normalized = value
|
const normalized = value
|
||||||
.trim()
|
.trim()
|
||||||
|
|
@ -48,6 +49,58 @@ function bridgeStatusFor(pilot, draft) {
|
||||||
function businessFactAnswerAllowed(draft) {
|
function businessFactAnswerAllowed(draft) {
|
||||||
return draft.answer_mode === "confirmed_with_bounded_inference" || draft.answer_mode === "bounded_inference_only";
|
return draft.answer_mode === "confirmed_with_bounded_inference" || draft.answer_mode === "bounded_inference_only";
|
||||||
}
|
}
|
||||||
|
function loopStatusFor(bridgeStatus) {
|
||||||
|
if (bridgeStatus === "needs_clarification") {
|
||||||
|
return "awaiting_clarification";
|
||||||
|
}
|
||||||
|
if (bridgeStatus === "blocked" || bridgeStatus === "unsupported") {
|
||||||
|
return "blocked";
|
||||||
|
}
|
||||||
|
return "ready_for_next_hop";
|
||||||
|
}
|
||||||
|
function flattenAxes(pilot, source) {
|
||||||
|
const result = [];
|
||||||
|
for (const step of pilot.dry_run.execution_steps) {
|
||||||
|
if (source === "provided_axes") {
|
||||||
|
for (const axis of step.provided_axes) {
|
||||||
|
if (axis && !result.includes(axis)) {
|
||||||
|
result.push(axis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const option of step.missing_axis_options) {
|
||||||
|
for (const axis of option) {
|
||||||
|
if (axis && !result.includes(axis)) {
|
||||||
|
result.push(axis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function entityCandidatesFromPlanner(planner) {
|
||||||
|
const values = planner.discovery_plan.turn_meaning_ref?.explicit_entity_candidates ?? [];
|
||||||
|
return uniqueStrings(values);
|
||||||
|
}
|
||||||
|
function buildLoopState(planner, pilot, bridgeStatus) {
|
||||||
|
return {
|
||||||
|
schema_version: exports.ASSISTANT_MCP_DISCOVERY_LOOP_STATE_SCHEMA_VERSION,
|
||||||
|
policy_owner: "assistantMcpDiscoveryRuntimeBridge",
|
||||||
|
loop_status: loopStatusFor(bridgeStatus),
|
||||||
|
selected_chain_id: planner.selected_chain_id,
|
||||||
|
pilot_scope: pilot.pilot_scope,
|
||||||
|
asked_domain_family: planner.discovery_plan.turn_meaning_ref?.asked_domain_family ?? null,
|
||||||
|
asked_action_family: planner.discovery_plan.turn_meaning_ref?.asked_action_family ?? null,
|
||||||
|
unsupported_but_understood_family: planner.discovery_plan.turn_meaning_ref?.unsupported_but_understood_family ?? null,
|
||||||
|
ranking_need: planner.data_need_graph?.ranking_need ?? planner.discovery_plan.turn_meaning_ref?.seeded_ranking_need ?? null,
|
||||||
|
pending_axes: flattenAxes(pilot, "missing_axis_options"),
|
||||||
|
provided_axes: flattenAxes(pilot, "provided_axes"),
|
||||||
|
explicit_entity_candidates: entityCandidatesFromPlanner(planner),
|
||||||
|
explicit_organization_scope: planner.discovery_plan.turn_meaning_ref?.explicit_organization_scope ?? null,
|
||||||
|
explicit_date_scope: planner.discovery_plan.turn_meaning_ref?.explicit_date_scope ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
async function runAssistantMcpDiscoveryRuntimeBridge(input) {
|
async function runAssistantMcpDiscoveryRuntimeBridge(input) {
|
||||||
const planner = (0, assistantMcpDiscoveryPlanner_1.planAssistantMcpDiscovery)({
|
const planner = (0, assistantMcpDiscoveryPlanner_1.planAssistantMcpDiscovery)({
|
||||||
semanticDataNeed: input.semanticDataNeed,
|
semanticDataNeed: input.semanticDataNeed,
|
||||||
|
|
@ -58,9 +111,11 @@ async function runAssistantMcpDiscoveryRuntimeBridge(input) {
|
||||||
const pilot = await (0, assistantMcpDiscoveryPilotExecutor_1.executeAssistantMcpDiscoveryPilot)(planner, input.deps);
|
const pilot = await (0, assistantMcpDiscoveryPilotExecutor_1.executeAssistantMcpDiscoveryPilot)(planner, input.deps);
|
||||||
const answerDraft = (0, assistantMcpDiscoveryAnswerAdapter_1.buildAssistantMcpDiscoveryAnswerDraft)(pilot);
|
const answerDraft = (0, assistantMcpDiscoveryAnswerAdapter_1.buildAssistantMcpDiscoveryAnswerDraft)(pilot);
|
||||||
const bridgeStatus = bridgeStatusFor(pilot, answerDraft);
|
const bridgeStatus = bridgeStatusFor(pilot, answerDraft);
|
||||||
|
const loopState = buildLoopState(planner, pilot, bridgeStatus);
|
||||||
const reasonCodes = uniqueStrings([...planner.reason_codes, ...pilot.reason_codes, ...answerDraft.reason_codes]);
|
const reasonCodes = uniqueStrings([...planner.reason_codes, ...pilot.reason_codes, ...answerDraft.reason_codes]);
|
||||||
pushReason(reasonCodes, `runtime_bridge_status_${bridgeStatus}`);
|
pushReason(reasonCodes, `runtime_bridge_status_${bridgeStatus}`);
|
||||||
pushReason(reasonCodes, "runtime_bridge_not_wired_to_hot_assistant_answer");
|
pushReason(reasonCodes, "runtime_bridge_not_wired_to_hot_assistant_answer");
|
||||||
|
pushReason(reasonCodes, `runtime_bridge_loop_state_${loopState.loop_status}`);
|
||||||
return {
|
return {
|
||||||
schema_version: exports.ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION,
|
schema_version: exports.ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION,
|
||||||
policy_owner: "assistantMcpDiscoveryRuntimeBridge",
|
policy_owner: "assistantMcpDiscoveryRuntimeBridge",
|
||||||
|
|
@ -69,6 +124,7 @@ async function runAssistantMcpDiscoveryRuntimeBridge(input) {
|
||||||
planner,
|
planner,
|
||||||
pilot,
|
pilot,
|
||||||
answer_draft: answerDraft,
|
answer_draft: answerDraft,
|
||||||
|
loop_state: loopState,
|
||||||
user_facing_response_allowed: bridgeStatus !== "blocked",
|
user_facing_response_allowed: bridgeStatus !== "blocked",
|
||||||
business_fact_answer_allowed: businessFactAnswerAllowed(answerDraft),
|
business_fact_answer_allowed: businessFactAnswerAllowed(answerDraft),
|
||||||
requires_user_clarification: bridgeStatus === "needs_clarification",
|
requires_user_clarification: bridgeStatus === "needs_clarification",
|
||||||
|
|
|
||||||
|
|
@ -254,14 +254,92 @@ function mapAddressIntentToFollowupMeaning(intent) {
|
||||||
unsupported: null
|
unsupported: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function mapLoopClarificationSeedToFollowupMeaning(input) {
|
||||||
|
if (input.domain || input.action || input.unsupported) {
|
||||||
|
return {
|
||||||
|
domain: input.domain,
|
||||||
|
action: input.action,
|
||||||
|
unsupported: input.unsupported
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (input.selectedChainId === "metadata_lane_clarification") {
|
||||||
|
return {
|
||||||
|
domain: "metadata",
|
||||||
|
action: "resolve_next_lane",
|
||||||
|
unsupported: "metadata_lane_choice_clarification"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
domain: null,
|
||||||
|
action: null,
|
||||||
|
unsupported: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function pilotScopeFromLoopClarificationSeed(selectedChainId, action) {
|
||||||
|
if (!selectedChainId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (selectedChainId === "metadata_inspection" ||
|
||||||
|
selectedChainId === "metadata_lane_clarification" ||
|
||||||
|
selectedChainId === "catalog_drilldown") {
|
||||||
|
return "metadata_inspection_v1";
|
||||||
|
}
|
||||||
|
if (selectedChainId === "movement_evidence") {
|
||||||
|
return "counterparty_movement_evidence_query_movements_v1";
|
||||||
|
}
|
||||||
|
if (selectedChainId === "document_evidence") {
|
||||||
|
return "counterparty_document_evidence_query_documents_v1";
|
||||||
|
}
|
||||||
|
if (selectedChainId === "lifecycle") {
|
||||||
|
return "counterparty_lifecycle_query_documents_v1";
|
||||||
|
}
|
||||||
|
if (selectedChainId === "entity_resolution") {
|
||||||
|
return "entity_resolution_search_v1";
|
||||||
|
}
|
||||||
|
if (selectedChainId === "value_flow_comparison") {
|
||||||
|
return "counterparty_bidirectional_value_flow_query_movements_v1";
|
||||||
|
}
|
||||||
|
if (selectedChainId === "value_flow_ranking" || selectedChainId === "value_flow") {
|
||||||
|
if (action === "payout") {
|
||||||
|
return "counterparty_supplier_payout_query_movements_v1";
|
||||||
|
}
|
||||||
|
if (action === "net_value_flow") {
|
||||||
|
return "counterparty_bidirectional_value_flow_query_movements_v1";
|
||||||
|
}
|
||||||
|
return "counterparty_value_flow_query_movements_v1";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
function collectFollowupDiscoverySeed(followupContext) {
|
function collectFollowupDiscoverySeed(followupContext) {
|
||||||
const previousFilters = toRecordObject(followupContext?.previous_filters);
|
const previousFilters = toRecordObject(followupContext?.previous_filters);
|
||||||
const rootFilters = toRecordObject(followupContext?.root_filters);
|
const rootFilters = toRecordObject(followupContext?.root_filters);
|
||||||
const pilotScope = toNonEmptyString(followupContext?.previous_discovery_pilot_scope);
|
const pilotScope = toNonEmptyString(followupContext?.previous_discovery_pilot_scope);
|
||||||
|
const loopStatus = toNonEmptyString(followupContext?.previous_discovery_loop_status);
|
||||||
|
const loopSelectedChainId = toNonEmptyString(followupContext?.previous_discovery_loop_selected_chain_id);
|
||||||
|
const loopPendingAxes = collectEntityCandidates(followupContext?.previous_discovery_loop_pending_axes);
|
||||||
|
const loopProvidedAxes = collectEntityCandidates(followupContext?.previous_discovery_loop_provided_axes);
|
||||||
|
const loopAskedDomainFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_domain_family);
|
||||||
|
const loopAskedActionFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_action_family);
|
||||||
|
const loopUnsupportedFamily = toNonEmptyString(followupContext?.previous_discovery_loop_unsupported_family);
|
||||||
const previousIntent = toNonEmptyString(followupContext?.target_intent) ?? toNonEmptyString(followupContext?.previous_intent);
|
const previousIntent = toNonEmptyString(followupContext?.target_intent) ?? toNonEmptyString(followupContext?.previous_intent);
|
||||||
const mapped = mapPilotScopeToFollowupMeaning(pilotScope).domain !== null
|
const loopMapped = loopStatus === "awaiting_clarification"
|
||||||
? mapPilotScopeToFollowupMeaning(pilotScope)
|
? mapLoopClarificationSeedToFollowupMeaning({
|
||||||
: mapAddressIntentToFollowupMeaning(previousIntent);
|
selectedChainId: loopSelectedChainId,
|
||||||
|
domain: loopAskedDomainFamily,
|
||||||
|
action: loopAskedActionFamily,
|
||||||
|
unsupported: loopUnsupportedFamily
|
||||||
|
})
|
||||||
|
: {
|
||||||
|
domain: null,
|
||||||
|
action: null,
|
||||||
|
unsupported: null
|
||||||
|
};
|
||||||
|
const effectivePilotScope = pilotScope ?? pilotScopeFromLoopClarificationSeed(loopSelectedChainId, loopMapped.action);
|
||||||
|
const mapped = loopMapped.domain !== null || loopMapped.action !== null || loopMapped.unsupported !== null
|
||||||
|
? loopMapped
|
||||||
|
: mapPilotScopeToFollowupMeaning(effectivePilotScope).domain !== null
|
||||||
|
? mapPilotScopeToFollowupMeaning(effectivePilotScope)
|
||||||
|
: mapAddressIntentToFollowupMeaning(previousIntent);
|
||||||
const discoveryEntities = collectEntityCandidates(followupContext?.previous_discovery_entity_candidates);
|
const discoveryEntities = collectEntityCandidates(followupContext?.previous_discovery_entity_candidates);
|
||||||
const entityResolutionStatus = toNonEmptyString(followupContext?.previous_discovery_entity_resolution_status);
|
const entityResolutionStatus = toNonEmptyString(followupContext?.previous_discovery_entity_resolution_status);
|
||||||
const entityResolutionAmbiguityCandidates = collectEntityCandidates(followupContext?.previous_discovery_entity_ambiguity_candidates);
|
const entityResolutionAmbiguityCandidates = collectEntityCandidates(followupContext?.previous_discovery_entity_ambiguity_candidates);
|
||||||
|
|
@ -280,10 +358,14 @@ function collectFollowupDiscoverySeed(followupContext) {
|
||||||
const dateScope = collectDateScopeFromFilters(previousFilters) ??
|
const dateScope = collectDateScopeFromFilters(previousFilters) ??
|
||||||
collectDateScopeFromFilters(rootFilters);
|
collectDateScopeFromFilters(rootFilters);
|
||||||
return {
|
return {
|
||||||
pilotScope,
|
pilotScope: effectivePilotScope,
|
||||||
domain: mapped.domain,
|
domain: mapped.domain,
|
||||||
action: mapped.action,
|
action: mapped.action,
|
||||||
unsupported: mapped.unsupported,
|
unsupported: mapped.unsupported,
|
||||||
|
loopStatus,
|
||||||
|
loopSelectedChainId,
|
||||||
|
loopPendingAxes,
|
||||||
|
loopProvidedAxes,
|
||||||
counterparty,
|
counterparty,
|
||||||
discoveryEntity: ambiguityBlocksImplicitGrounding ? null : discoveryEntities[0] ?? null,
|
discoveryEntity: ambiguityBlocksImplicitGrounding ? null : discoveryEntities[0] ?? null,
|
||||||
entityResolutionStatus,
|
entityResolutionStatus,
|
||||||
|
|
@ -963,6 +1045,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
!predecomposeDateScope &&
|
!predecomposeDateScope &&
|
||||||
!rawDateScope &&
|
!rawDateScope &&
|
||||||
followupSeed.dateScope);
|
followupSeed.dateScope);
|
||||||
|
const clarificationLoopSeedApplied = Boolean(followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopSelectedChainId);
|
||||||
const turnMeaning = {
|
const turnMeaning = {
|
||||||
asked_domain_family: lifecycleSignal
|
asked_domain_family: lifecycleSignal
|
||||||
? "counterparty_lifecycle"
|
? "counterparty_lifecycle"
|
||||||
|
|
@ -1151,6 +1234,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
|
||||||
if (followupDiscoverySeedApplicable) {
|
if (followupDiscoverySeedApplicable) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_seeded_from_followup_context");
|
pushReason(reasonCodes, "mcp_discovery_seeded_from_followup_context");
|
||||||
}
|
}
|
||||||
|
if (clarificationLoopSeedApplied) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_resumed_from_saved_loop_state");
|
||||||
|
}
|
||||||
if (effectiveMetadataFollowupSeedApplicable) {
|
if (effectiveMetadataFollowupSeedApplicable) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_metadata_seeded_from_followup_context");
|
pushReason(reasonCodes, "mcp_discovery_metadata_seeded_from_followup_context");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -508,6 +508,13 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
const sourceDiscoveryMetadataAmbiguityEntitySets = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryMetadataAmbiguityEntitySets)(carryoverSourceDebug, deps.toNonEmptyString);
|
const sourceDiscoveryMetadataAmbiguityEntitySets = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryMetadataAmbiguityEntitySets)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
const sourceDiscoveryEntityResolutionStatus = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityResolutionStatus)(carryoverSourceDebug, deps.toNonEmptyString);
|
const sourceDiscoveryEntityResolutionStatus = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityResolutionStatus)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
const sourceDiscoveryEntityCandidates = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityCandidates)(carryoverSourceDebug, deps.toNonEmptyString);
|
const sourceDiscoveryEntityCandidates = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityCandidates)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
|
const sourceDiscoveryLoopStatus = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopStatus)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
|
const sourceDiscoveryLoopSelectedChainId = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopSelectedChainId)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
|
const sourceDiscoveryLoopPendingAxes = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopPendingAxes)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
|
const sourceDiscoveryLoopProvidedAxes = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopProvidedAxes)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
|
const sourceDiscoveryLoopAskedDomainFamily = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopAskedDomainFamily)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
|
const sourceDiscoveryLoopAskedActionFamily = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopAskedActionFamily)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
|
const sourceDiscoveryLoopUnsupportedFamily = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopUnsupportedFamily)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
const sourceDiscoveryRankingNeed = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryRankingNeed)(carryoverSourceDebug, deps.toNonEmptyString);
|
const sourceDiscoveryRankingNeed = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryRankingNeed)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
const sourceDiscoveryEntityAmbiguityCandidates = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityAmbiguityCandidates)(carryoverSourceDebug, deps.toNonEmptyString);
|
const sourceDiscoveryEntityAmbiguityCandidates = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryEntityAmbiguityCandidates)(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
const llmExplicitIntent = deps.toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||||
|
|
@ -746,6 +753,13 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
previous_discovery_pilot_scope: sourceDiscoveryPilotScope ?? undefined,
|
previous_discovery_pilot_scope: sourceDiscoveryPilotScope ?? undefined,
|
||||||
previous_discovery_entity_resolution_status: sourceDiscoveryEntityResolutionStatus ?? undefined,
|
previous_discovery_entity_resolution_status: sourceDiscoveryEntityResolutionStatus ?? undefined,
|
||||||
previous_discovery_entity_candidates: sourceDiscoveryEntityCandidates.length > 0 ? sourceDiscoveryEntityCandidates : undefined,
|
previous_discovery_entity_candidates: sourceDiscoveryEntityCandidates.length > 0 ? sourceDiscoveryEntityCandidates : undefined,
|
||||||
|
previous_discovery_loop_status: sourceDiscoveryLoopStatus ?? undefined,
|
||||||
|
previous_discovery_loop_selected_chain_id: sourceDiscoveryLoopSelectedChainId ?? undefined,
|
||||||
|
previous_discovery_loop_pending_axes: sourceDiscoveryLoopPendingAxes.length > 0 ? sourceDiscoveryLoopPendingAxes : undefined,
|
||||||
|
previous_discovery_loop_provided_axes: sourceDiscoveryLoopProvidedAxes.length > 0 ? sourceDiscoveryLoopProvidedAxes : undefined,
|
||||||
|
previous_discovery_loop_asked_domain_family: sourceDiscoveryLoopAskedDomainFamily ?? undefined,
|
||||||
|
previous_discovery_loop_asked_action_family: sourceDiscoveryLoopAskedActionFamily ?? undefined,
|
||||||
|
previous_discovery_loop_unsupported_family: sourceDiscoveryLoopUnsupportedFamily ?? undefined,
|
||||||
previous_discovery_ranking_need: sourceDiscoveryRankingNeed ?? undefined,
|
previous_discovery_ranking_need: sourceDiscoveryRankingNeed ?? undefined,
|
||||||
previous_discovery_entity_ambiguity_candidates: sourceDiscoveryEntityAmbiguityCandidates.length > 0
|
previous_discovery_entity_ambiguity_candidates: sourceDiscoveryEntityAmbiguityCandidates.length > 0
|
||||||
? sourceDiscoveryEntityAmbiguityCandidates
|
? sourceDiscoveryEntityAmbiguityCandidates
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,12 @@ function readAssistantMcpDiscoveryBridge(
|
||||||
return toRecordObject(readAssistantMcpDiscoveryEntry(debug)?.bridge);
|
return toRecordObject(readAssistantMcpDiscoveryEntry(debug)?.bridge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function readAssistantMcpDiscoveryLoopState(
|
||||||
|
debug: Record<string, unknown> | null
|
||||||
|
): Record<string, unknown> | null {
|
||||||
|
return toRecordObject(readAssistantMcpDiscoveryBridge(debug)?.loop_state);
|
||||||
|
}
|
||||||
|
|
||||||
function readAssistantMcpDiscoveryDerivedMetadataSurface(
|
function readAssistantMcpDiscoveryDerivedMetadataSurface(
|
||||||
debug: Record<string, unknown> | null
|
debug: Record<string, unknown> | null
|
||||||
): Record<string, unknown> | null {
|
): Record<string, unknown> | null {
|
||||||
|
|
@ -260,7 +266,67 @@ export function readAssistantMcpDiscoveryRankingNeed(
|
||||||
debug: Record<string, unknown> | null,
|
debug: Record<string, unknown> | null,
|
||||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
): string | null {
|
): string | null {
|
||||||
return toNonEmptyString(readAssistantMcpDiscoveryDataNeedGraph(debug)?.ranking_need);
|
return (
|
||||||
|
toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.ranking_need) ??
|
||||||
|
toNonEmptyString(readAssistantMcpDiscoveryDataNeedGraph(debug)?.ranking_need)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readAssistantMcpDiscoveryLoopStatus(
|
||||||
|
debug: Record<string, unknown> | null,
|
||||||
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
): string | null {
|
||||||
|
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.loop_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readAssistantMcpDiscoveryLoopSelectedChainId(
|
||||||
|
debug: Record<string, unknown> | null,
|
||||||
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
): string | null {
|
||||||
|
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.selected_chain_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readAssistantMcpDiscoveryLoopPendingAxes(
|
||||||
|
debug: Record<string, unknown> | null,
|
||||||
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
): string[] {
|
||||||
|
const values = readAssistantMcpDiscoveryLoopState(debug)?.pending_axes;
|
||||||
|
if (!Array.isArray(values)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return values.map((item) => toNonEmptyString(item)).filter((item): item is string => Boolean(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readAssistantMcpDiscoveryLoopProvidedAxes(
|
||||||
|
debug: Record<string, unknown> | null,
|
||||||
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
): string[] {
|
||||||
|
const values = readAssistantMcpDiscoveryLoopState(debug)?.provided_axes;
|
||||||
|
if (!Array.isArray(values)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return values.map((item) => toNonEmptyString(item)).filter((item): item is string => Boolean(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readAssistantMcpDiscoveryLoopAskedDomainFamily(
|
||||||
|
debug: Record<string, unknown> | null,
|
||||||
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
): string | null {
|
||||||
|
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.asked_domain_family);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readAssistantMcpDiscoveryLoopAskedActionFamily(
|
||||||
|
debug: Record<string, unknown> | null,
|
||||||
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
): string | null {
|
||||||
|
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.asked_action_family);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readAssistantMcpDiscoveryLoopUnsupportedFamily(
|
||||||
|
debug: Record<string, unknown> | null,
|
||||||
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
): string | null {
|
||||||
|
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.unsupported_but_understood_family);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readAssistantMcpDiscoveryMetadataRouteFamily(
|
export function readAssistantMcpDiscoveryMetadataRouteFamily(
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
} from "./assistantMcpDiscoveryPilotExecutor";
|
} from "./assistantMcpDiscoveryPilotExecutor";
|
||||||
import {
|
import {
|
||||||
planAssistantMcpDiscovery,
|
planAssistantMcpDiscovery,
|
||||||
|
type AssistantMcpDiscoveryChainId,
|
||||||
type AssistantMcpDiscoveryMetadataSurfaceRef,
|
type AssistantMcpDiscoveryMetadataSurfaceRef,
|
||||||
type AssistantMcpDiscoveryPlannerContract
|
type AssistantMcpDiscoveryPlannerContract
|
||||||
} from "./assistantMcpDiscoveryPlanner";
|
} from "./assistantMcpDiscoveryPlanner";
|
||||||
|
|
@ -17,6 +18,8 @@ import type { AssistantMcpDiscoveryTurnMeaningRef } from "./assistantMcpDiscover
|
||||||
|
|
||||||
export const ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION =
|
export const ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION =
|
||||||
"assistant_mcp_discovery_runtime_bridge_v1" as const;
|
"assistant_mcp_discovery_runtime_bridge_v1" as const;
|
||||||
|
export const ASSISTANT_MCP_DISCOVERY_LOOP_STATE_SCHEMA_VERSION =
|
||||||
|
"assistant_mcp_discovery_loop_state_v1" as const;
|
||||||
|
|
||||||
export type AssistantMcpDiscoveryRuntimeBridgeStatus =
|
export type AssistantMcpDiscoveryRuntimeBridgeStatus =
|
||||||
| "answer_draft_ready"
|
| "answer_draft_ready"
|
||||||
|
|
@ -24,6 +27,10 @@ export type AssistantMcpDiscoveryRuntimeBridgeStatus =
|
||||||
| "needs_clarification"
|
| "needs_clarification"
|
||||||
| "blocked"
|
| "blocked"
|
||||||
| "unsupported";
|
| "unsupported";
|
||||||
|
export type AssistantMcpDiscoveryLoopStatus =
|
||||||
|
| "awaiting_clarification"
|
||||||
|
| "ready_for_next_hop"
|
||||||
|
| "blocked";
|
||||||
|
|
||||||
export interface AssistantMcpDiscoveryRuntimeBridgeInput {
|
export interface AssistantMcpDiscoveryRuntimeBridgeInput {
|
||||||
semanticDataNeed?: string | null;
|
semanticDataNeed?: string | null;
|
||||||
|
|
@ -33,6 +40,23 @@ export interface AssistantMcpDiscoveryRuntimeBridgeInput {
|
||||||
deps?: AssistantMcpDiscoveryPilotExecutorDeps;
|
deps?: AssistantMcpDiscoveryPilotExecutorDeps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AssistantMcpDiscoveryLoopStateContract {
|
||||||
|
schema_version: typeof ASSISTANT_MCP_DISCOVERY_LOOP_STATE_SCHEMA_VERSION;
|
||||||
|
policy_owner: "assistantMcpDiscoveryRuntimeBridge";
|
||||||
|
loop_status: AssistantMcpDiscoveryLoopStatus;
|
||||||
|
selected_chain_id: AssistantMcpDiscoveryChainId;
|
||||||
|
pilot_scope: AssistantMcpDiscoveryPilotExecutionContract["pilot_scope"];
|
||||||
|
asked_domain_family: string | null;
|
||||||
|
asked_action_family: string | null;
|
||||||
|
unsupported_but_understood_family: string | null;
|
||||||
|
ranking_need: string | null;
|
||||||
|
pending_axes: string[];
|
||||||
|
provided_axes: string[];
|
||||||
|
explicit_entity_candidates: string[];
|
||||||
|
explicit_organization_scope: string | null;
|
||||||
|
explicit_date_scope: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AssistantMcpDiscoveryRuntimeBridgeContract {
|
export interface AssistantMcpDiscoveryRuntimeBridgeContract {
|
||||||
schema_version: typeof ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION;
|
schema_version: typeof ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION;
|
||||||
policy_owner: "assistantMcpDiscoveryRuntimeBridge";
|
policy_owner: "assistantMcpDiscoveryRuntimeBridge";
|
||||||
|
|
@ -41,6 +65,7 @@ export interface AssistantMcpDiscoveryRuntimeBridgeContract {
|
||||||
planner: AssistantMcpDiscoveryPlannerContract;
|
planner: AssistantMcpDiscoveryPlannerContract;
|
||||||
pilot: AssistantMcpDiscoveryPilotExecutionContract;
|
pilot: AssistantMcpDiscoveryPilotExecutionContract;
|
||||||
answer_draft: AssistantMcpDiscoveryAnswerDraftContract;
|
answer_draft: AssistantMcpDiscoveryAnswerDraftContract;
|
||||||
|
loop_state: AssistantMcpDiscoveryLoopStateContract;
|
||||||
user_facing_response_allowed: boolean;
|
user_facing_response_allowed: boolean;
|
||||||
business_fact_answer_allowed: boolean;
|
business_fact_answer_allowed: boolean;
|
||||||
requires_user_clarification: boolean;
|
requires_user_clarification: boolean;
|
||||||
|
|
@ -97,6 +122,72 @@ function businessFactAnswerAllowed(draft: AssistantMcpDiscoveryAnswerDraftContra
|
||||||
return draft.answer_mode === "confirmed_with_bounded_inference" || draft.answer_mode === "bounded_inference_only";
|
return draft.answer_mode === "confirmed_with_bounded_inference" || draft.answer_mode === "bounded_inference_only";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loopStatusFor(
|
||||||
|
bridgeStatus: AssistantMcpDiscoveryRuntimeBridgeStatus
|
||||||
|
): AssistantMcpDiscoveryLoopStatus {
|
||||||
|
if (bridgeStatus === "needs_clarification") {
|
||||||
|
return "awaiting_clarification";
|
||||||
|
}
|
||||||
|
if (bridgeStatus === "blocked" || bridgeStatus === "unsupported") {
|
||||||
|
return "blocked";
|
||||||
|
}
|
||||||
|
return "ready_for_next_hop";
|
||||||
|
}
|
||||||
|
|
||||||
|
function flattenAxes(
|
||||||
|
pilot: AssistantMcpDiscoveryPilotExecutionContract,
|
||||||
|
source: "provided_axes" | "missing_axis_options"
|
||||||
|
): string[] {
|
||||||
|
const result: string[] = [];
|
||||||
|
for (const step of pilot.dry_run.execution_steps) {
|
||||||
|
if (source === "provided_axes") {
|
||||||
|
for (const axis of step.provided_axes) {
|
||||||
|
if (axis && !result.includes(axis)) {
|
||||||
|
result.push(axis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const option of step.missing_axis_options) {
|
||||||
|
for (const axis of option) {
|
||||||
|
if (axis && !result.includes(axis)) {
|
||||||
|
result.push(axis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function entityCandidatesFromPlanner(planner: AssistantMcpDiscoveryPlannerContract): string[] {
|
||||||
|
const values = planner.discovery_plan.turn_meaning_ref?.explicit_entity_candidates ?? [];
|
||||||
|
return uniqueStrings(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildLoopState(
|
||||||
|
planner: AssistantMcpDiscoveryPlannerContract,
|
||||||
|
pilot: AssistantMcpDiscoveryPilotExecutionContract,
|
||||||
|
bridgeStatus: AssistantMcpDiscoveryRuntimeBridgeStatus
|
||||||
|
): AssistantMcpDiscoveryLoopStateContract {
|
||||||
|
return {
|
||||||
|
schema_version: ASSISTANT_MCP_DISCOVERY_LOOP_STATE_SCHEMA_VERSION,
|
||||||
|
policy_owner: "assistantMcpDiscoveryRuntimeBridge",
|
||||||
|
loop_status: loopStatusFor(bridgeStatus),
|
||||||
|
selected_chain_id: planner.selected_chain_id,
|
||||||
|
pilot_scope: pilot.pilot_scope,
|
||||||
|
asked_domain_family: planner.discovery_plan.turn_meaning_ref?.asked_domain_family ?? null,
|
||||||
|
asked_action_family: planner.discovery_plan.turn_meaning_ref?.asked_action_family ?? null,
|
||||||
|
unsupported_but_understood_family:
|
||||||
|
planner.discovery_plan.turn_meaning_ref?.unsupported_but_understood_family ?? null,
|
||||||
|
ranking_need: planner.data_need_graph?.ranking_need ?? planner.discovery_plan.turn_meaning_ref?.seeded_ranking_need ?? null,
|
||||||
|
pending_axes: flattenAxes(pilot, "missing_axis_options"),
|
||||||
|
provided_axes: flattenAxes(pilot, "provided_axes"),
|
||||||
|
explicit_entity_candidates: entityCandidatesFromPlanner(planner),
|
||||||
|
explicit_organization_scope: planner.discovery_plan.turn_meaning_ref?.explicit_organization_scope ?? null,
|
||||||
|
explicit_date_scope: planner.discovery_plan.turn_meaning_ref?.explicit_date_scope ?? null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function runAssistantMcpDiscoveryRuntimeBridge(
|
export async function runAssistantMcpDiscoveryRuntimeBridge(
|
||||||
input: AssistantMcpDiscoveryRuntimeBridgeInput
|
input: AssistantMcpDiscoveryRuntimeBridgeInput
|
||||||
): Promise<AssistantMcpDiscoveryRuntimeBridgeContract> {
|
): Promise<AssistantMcpDiscoveryRuntimeBridgeContract> {
|
||||||
|
|
@ -109,10 +200,12 @@ export async function runAssistantMcpDiscoveryRuntimeBridge(
|
||||||
const pilot = await executeAssistantMcpDiscoveryPilot(planner, input.deps);
|
const pilot = await executeAssistantMcpDiscoveryPilot(planner, input.deps);
|
||||||
const answerDraft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
const answerDraft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
|
||||||
const bridgeStatus = bridgeStatusFor(pilot, answerDraft);
|
const bridgeStatus = bridgeStatusFor(pilot, answerDraft);
|
||||||
|
const loopState = buildLoopState(planner, pilot, bridgeStatus);
|
||||||
const reasonCodes = uniqueStrings([...planner.reason_codes, ...pilot.reason_codes, ...answerDraft.reason_codes]);
|
const reasonCodes = uniqueStrings([...planner.reason_codes, ...pilot.reason_codes, ...answerDraft.reason_codes]);
|
||||||
|
|
||||||
pushReason(reasonCodes, `runtime_bridge_status_${bridgeStatus}`);
|
pushReason(reasonCodes, `runtime_bridge_status_${bridgeStatus}`);
|
||||||
pushReason(reasonCodes, "runtime_bridge_not_wired_to_hot_assistant_answer");
|
pushReason(reasonCodes, "runtime_bridge_not_wired_to_hot_assistant_answer");
|
||||||
|
pushReason(reasonCodes, `runtime_bridge_loop_state_${loopState.loop_status}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
schema_version: ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION,
|
schema_version: ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION,
|
||||||
|
|
@ -122,6 +215,7 @@ export async function runAssistantMcpDiscoveryRuntimeBridge(
|
||||||
planner,
|
planner,
|
||||||
pilot,
|
pilot,
|
||||||
answer_draft: answerDraft,
|
answer_draft: answerDraft,
|
||||||
|
loop_state: loopState,
|
||||||
user_facing_response_allowed: bridgeStatus !== "blocked",
|
user_facing_response_allowed: bridgeStatus !== "blocked",
|
||||||
business_fact_answer_allowed: businessFactAnswerAllowed(answerDraft),
|
business_fact_answer_allowed: businessFactAnswerAllowed(answerDraft),
|
||||||
requires_user_clarification: bridgeStatus === "needs_clarification",
|
requires_user_clarification: bridgeStatus === "needs_clarification",
|
||||||
|
|
|
||||||
|
|
@ -339,11 +339,87 @@ function mapAddressIntentToFollowupMeaning(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapLoopClarificationSeedToFollowupMeaning(input: {
|
||||||
|
selectedChainId: string | null;
|
||||||
|
domain: string | null;
|
||||||
|
action: string | null;
|
||||||
|
unsupported: string | null;
|
||||||
|
}): {
|
||||||
|
domain: string | null;
|
||||||
|
action: string | null;
|
||||||
|
unsupported: string | null;
|
||||||
|
} {
|
||||||
|
if (input.domain || input.action || input.unsupported) {
|
||||||
|
return {
|
||||||
|
domain: input.domain,
|
||||||
|
action: input.action,
|
||||||
|
unsupported: input.unsupported
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (input.selectedChainId === "metadata_lane_clarification") {
|
||||||
|
return {
|
||||||
|
domain: "metadata",
|
||||||
|
action: "resolve_next_lane",
|
||||||
|
unsupported: "metadata_lane_choice_clarification"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
domain: null,
|
||||||
|
action: null,
|
||||||
|
unsupported: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function pilotScopeFromLoopClarificationSeed(
|
||||||
|
selectedChainId: string | null,
|
||||||
|
action: string | null
|
||||||
|
): string | null {
|
||||||
|
if (!selectedChainId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
selectedChainId === "metadata_inspection" ||
|
||||||
|
selectedChainId === "metadata_lane_clarification" ||
|
||||||
|
selectedChainId === "catalog_drilldown"
|
||||||
|
) {
|
||||||
|
return "metadata_inspection_v1";
|
||||||
|
}
|
||||||
|
if (selectedChainId === "movement_evidence") {
|
||||||
|
return "counterparty_movement_evidence_query_movements_v1";
|
||||||
|
}
|
||||||
|
if (selectedChainId === "document_evidence") {
|
||||||
|
return "counterparty_document_evidence_query_documents_v1";
|
||||||
|
}
|
||||||
|
if (selectedChainId === "lifecycle") {
|
||||||
|
return "counterparty_lifecycle_query_documents_v1";
|
||||||
|
}
|
||||||
|
if (selectedChainId === "entity_resolution") {
|
||||||
|
return "entity_resolution_search_v1";
|
||||||
|
}
|
||||||
|
if (selectedChainId === "value_flow_comparison") {
|
||||||
|
return "counterparty_bidirectional_value_flow_query_movements_v1";
|
||||||
|
}
|
||||||
|
if (selectedChainId === "value_flow_ranking" || selectedChainId === "value_flow") {
|
||||||
|
if (action === "payout") {
|
||||||
|
return "counterparty_supplier_payout_query_movements_v1";
|
||||||
|
}
|
||||||
|
if (action === "net_value_flow") {
|
||||||
|
return "counterparty_bidirectional_value_flow_query_movements_v1";
|
||||||
|
}
|
||||||
|
return "counterparty_value_flow_query_movements_v1";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> | null): {
|
function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> | null): {
|
||||||
pilotScope: string | null;
|
pilotScope: string | null;
|
||||||
domain: string | null;
|
domain: string | null;
|
||||||
action: string | null;
|
action: string | null;
|
||||||
unsupported: string | null;
|
unsupported: string | null;
|
||||||
|
loopStatus: string | null;
|
||||||
|
loopSelectedChainId: string | null;
|
||||||
|
loopPendingAxes: string[];
|
||||||
|
loopProvidedAxes: string[];
|
||||||
counterparty: string | null;
|
counterparty: string | null;
|
||||||
discoveryEntity: string | null;
|
discoveryEntity: string | null;
|
||||||
entityResolutionStatus: string | null;
|
entityResolutionStatus: string | null;
|
||||||
|
|
@ -362,12 +438,36 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
||||||
const previousFilters = toRecordObject(followupContext?.previous_filters);
|
const previousFilters = toRecordObject(followupContext?.previous_filters);
|
||||||
const rootFilters = toRecordObject(followupContext?.root_filters);
|
const rootFilters = toRecordObject(followupContext?.root_filters);
|
||||||
const pilotScope = toNonEmptyString(followupContext?.previous_discovery_pilot_scope);
|
const pilotScope = toNonEmptyString(followupContext?.previous_discovery_pilot_scope);
|
||||||
|
const loopStatus = toNonEmptyString(followupContext?.previous_discovery_loop_status);
|
||||||
|
const loopSelectedChainId = toNonEmptyString(followupContext?.previous_discovery_loop_selected_chain_id);
|
||||||
|
const loopPendingAxes = collectEntityCandidates(followupContext?.previous_discovery_loop_pending_axes);
|
||||||
|
const loopProvidedAxes = collectEntityCandidates(followupContext?.previous_discovery_loop_provided_axes);
|
||||||
|
const loopAskedDomainFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_domain_family);
|
||||||
|
const loopAskedActionFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_action_family);
|
||||||
|
const loopUnsupportedFamily = toNonEmptyString(followupContext?.previous_discovery_loop_unsupported_family);
|
||||||
const previousIntent =
|
const previousIntent =
|
||||||
toNonEmptyString(followupContext?.target_intent) ?? toNonEmptyString(followupContext?.previous_intent);
|
toNonEmptyString(followupContext?.target_intent) ?? toNonEmptyString(followupContext?.previous_intent);
|
||||||
|
const loopMapped =
|
||||||
|
loopStatus === "awaiting_clarification"
|
||||||
|
? mapLoopClarificationSeedToFollowupMeaning({
|
||||||
|
selectedChainId: loopSelectedChainId,
|
||||||
|
domain: loopAskedDomainFamily,
|
||||||
|
action: loopAskedActionFamily,
|
||||||
|
unsupported: loopUnsupportedFamily
|
||||||
|
})
|
||||||
|
: {
|
||||||
|
domain: null,
|
||||||
|
action: null,
|
||||||
|
unsupported: null
|
||||||
|
};
|
||||||
|
const effectivePilotScope =
|
||||||
|
pilotScope ?? pilotScopeFromLoopClarificationSeed(loopSelectedChainId, loopMapped.action);
|
||||||
const mapped =
|
const mapped =
|
||||||
mapPilotScopeToFollowupMeaning(pilotScope).domain !== null
|
loopMapped.domain !== null || loopMapped.action !== null || loopMapped.unsupported !== null
|
||||||
? mapPilotScopeToFollowupMeaning(pilotScope)
|
? loopMapped
|
||||||
: mapAddressIntentToFollowupMeaning(previousIntent);
|
: mapPilotScopeToFollowupMeaning(effectivePilotScope).domain !== null
|
||||||
|
? mapPilotScopeToFollowupMeaning(effectivePilotScope)
|
||||||
|
: mapAddressIntentToFollowupMeaning(previousIntent);
|
||||||
const discoveryEntities = collectEntityCandidates(followupContext?.previous_discovery_entity_candidates);
|
const discoveryEntities = collectEntityCandidates(followupContext?.previous_discovery_entity_candidates);
|
||||||
const entityResolutionStatus = toNonEmptyString(followupContext?.previous_discovery_entity_resolution_status);
|
const entityResolutionStatus = toNonEmptyString(followupContext?.previous_discovery_entity_resolution_status);
|
||||||
const entityResolutionAmbiguityCandidates = collectEntityCandidates(
|
const entityResolutionAmbiguityCandidates = collectEntityCandidates(
|
||||||
|
|
@ -392,10 +492,14 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
|
||||||
collectDateScopeFromFilters(previousFilters) ??
|
collectDateScopeFromFilters(previousFilters) ??
|
||||||
collectDateScopeFromFilters(rootFilters);
|
collectDateScopeFromFilters(rootFilters);
|
||||||
return {
|
return {
|
||||||
pilotScope,
|
pilotScope: effectivePilotScope,
|
||||||
domain: mapped.domain,
|
domain: mapped.domain,
|
||||||
action: mapped.action,
|
action: mapped.action,
|
||||||
unsupported: mapped.unsupported,
|
unsupported: mapped.unsupported,
|
||||||
|
loopStatus,
|
||||||
|
loopSelectedChainId,
|
||||||
|
loopPendingAxes,
|
||||||
|
loopProvidedAxes,
|
||||||
counterparty,
|
counterparty,
|
||||||
discoveryEntity: ambiguityBlocksImplicitGrounding ? null : discoveryEntities[0] ?? null,
|
discoveryEntity: ambiguityBlocksImplicitGrounding ? null : discoveryEntities[0] ?? null,
|
||||||
entityResolutionStatus,
|
entityResolutionStatus,
|
||||||
|
|
@ -1278,6 +1382,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
!rawDateScope &&
|
!rawDateScope &&
|
||||||
followupSeed.dateScope
|
followupSeed.dateScope
|
||||||
);
|
);
|
||||||
|
const clarificationLoopSeedApplied = Boolean(
|
||||||
|
followupSeed.loopStatus === "awaiting_clarification" && followupSeed.loopSelectedChainId
|
||||||
|
);
|
||||||
|
|
||||||
const turnMeaning: AssistantMcpDiscoveryTurnMeaningRef = {
|
const turnMeaning: AssistantMcpDiscoveryTurnMeaningRef = {
|
||||||
asked_domain_family:
|
asked_domain_family:
|
||||||
|
|
@ -1478,6 +1585,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
|
||||||
if (followupDiscoverySeedApplicable) {
|
if (followupDiscoverySeedApplicable) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_seeded_from_followup_context");
|
pushReason(reasonCodes, "mcp_discovery_seeded_from_followup_context");
|
||||||
}
|
}
|
||||||
|
if (clarificationLoopSeedApplied) {
|
||||||
|
pushReason(reasonCodes, "mcp_discovery_resumed_from_saved_loop_state");
|
||||||
|
}
|
||||||
if (effectiveMetadataFollowupSeedApplicable) {
|
if (effectiveMetadataFollowupSeedApplicable) {
|
||||||
pushReason(reasonCodes, "mcp_discovery_metadata_seeded_from_followup_context");
|
pushReason(reasonCodes, "mcp_discovery_metadata_seeded_from_followup_context");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,13 @@ import {
|
||||||
readAssistantMcpDiscoveryMetadataSelectedSurfaceObjects,
|
readAssistantMcpDiscoveryMetadataSelectedSurfaceObjects,
|
||||||
readAssistantMcpDiscoveryMetadataRecommendedNextPrimitive,
|
readAssistantMcpDiscoveryMetadataRecommendedNextPrimitive,
|
||||||
readAssistantMcpDiscoveryRankingNeed,
|
readAssistantMcpDiscoveryRankingNeed,
|
||||||
|
readAssistantMcpDiscoveryLoopStatus,
|
||||||
|
readAssistantMcpDiscoveryLoopSelectedChainId,
|
||||||
|
readAssistantMcpDiscoveryLoopPendingAxes,
|
||||||
|
readAssistantMcpDiscoveryLoopProvidedAxes,
|
||||||
|
readAssistantMcpDiscoveryLoopAskedDomainFamily,
|
||||||
|
readAssistantMcpDiscoveryLoopAskedActionFamily,
|
||||||
|
readAssistantMcpDiscoveryLoopUnsupportedFamily,
|
||||||
readAddressDebugTemporalScope,
|
readAddressDebugTemporalScope,
|
||||||
readAssistantMcpDiscoveryPilotScope,
|
readAssistantMcpDiscoveryPilotScope,
|
||||||
resolveOrganizationClarificationContinuation,
|
resolveOrganizationClarificationContinuation,
|
||||||
|
|
@ -695,6 +702,31 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
carryoverSourceDebug,
|
carryoverSourceDebug,
|
||||||
deps.toNonEmptyString
|
deps.toNonEmptyString
|
||||||
);
|
);
|
||||||
|
const sourceDiscoveryLoopStatus = readAssistantMcpDiscoveryLoopStatus(carryoverSourceDebug, deps.toNonEmptyString);
|
||||||
|
const sourceDiscoveryLoopSelectedChainId = readAssistantMcpDiscoveryLoopSelectedChainId(
|
||||||
|
carryoverSourceDebug,
|
||||||
|
deps.toNonEmptyString
|
||||||
|
);
|
||||||
|
const sourceDiscoveryLoopPendingAxes = readAssistantMcpDiscoveryLoopPendingAxes(
|
||||||
|
carryoverSourceDebug,
|
||||||
|
deps.toNonEmptyString
|
||||||
|
);
|
||||||
|
const sourceDiscoveryLoopProvidedAxes = readAssistantMcpDiscoveryLoopProvidedAxes(
|
||||||
|
carryoverSourceDebug,
|
||||||
|
deps.toNonEmptyString
|
||||||
|
);
|
||||||
|
const sourceDiscoveryLoopAskedDomainFamily = readAssistantMcpDiscoveryLoopAskedDomainFamily(
|
||||||
|
carryoverSourceDebug,
|
||||||
|
deps.toNonEmptyString
|
||||||
|
);
|
||||||
|
const sourceDiscoveryLoopAskedActionFamily = readAssistantMcpDiscoveryLoopAskedActionFamily(
|
||||||
|
carryoverSourceDebug,
|
||||||
|
deps.toNonEmptyString
|
||||||
|
);
|
||||||
|
const sourceDiscoveryLoopUnsupportedFamily = readAssistantMcpDiscoveryLoopUnsupportedFamily(
|
||||||
|
carryoverSourceDebug,
|
||||||
|
deps.toNonEmptyString
|
||||||
|
);
|
||||||
const sourceDiscoveryRankingNeed = readAssistantMcpDiscoveryRankingNeed(
|
const sourceDiscoveryRankingNeed = readAssistantMcpDiscoveryRankingNeed(
|
||||||
carryoverSourceDebug,
|
carryoverSourceDebug,
|
||||||
deps.toNonEmptyString
|
deps.toNonEmptyString
|
||||||
|
|
@ -1042,6 +1074,15 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
previous_discovery_entity_resolution_status: sourceDiscoveryEntityResolutionStatus ?? undefined,
|
previous_discovery_entity_resolution_status: sourceDiscoveryEntityResolutionStatus ?? undefined,
|
||||||
previous_discovery_entity_candidates:
|
previous_discovery_entity_candidates:
|
||||||
sourceDiscoveryEntityCandidates.length > 0 ? sourceDiscoveryEntityCandidates : undefined,
|
sourceDiscoveryEntityCandidates.length > 0 ? sourceDiscoveryEntityCandidates : undefined,
|
||||||
|
previous_discovery_loop_status: sourceDiscoveryLoopStatus ?? undefined,
|
||||||
|
previous_discovery_loop_selected_chain_id: sourceDiscoveryLoopSelectedChainId ?? undefined,
|
||||||
|
previous_discovery_loop_pending_axes:
|
||||||
|
sourceDiscoveryLoopPendingAxes.length > 0 ? sourceDiscoveryLoopPendingAxes : undefined,
|
||||||
|
previous_discovery_loop_provided_axes:
|
||||||
|
sourceDiscoveryLoopProvidedAxes.length > 0 ? sourceDiscoveryLoopProvidedAxes : undefined,
|
||||||
|
previous_discovery_loop_asked_domain_family: sourceDiscoveryLoopAskedDomainFamily ?? undefined,
|
||||||
|
previous_discovery_loop_asked_action_family: sourceDiscoveryLoopAskedActionFamily ?? undefined,
|
||||||
|
previous_discovery_loop_unsupported_family: sourceDiscoveryLoopUnsupportedFamily ?? undefined,
|
||||||
previous_discovery_ranking_need: sourceDiscoveryRankingNeed ?? undefined,
|
previous_discovery_ranking_need: sourceDiscoveryRankingNeed ?? undefined,
|
||||||
previous_discovery_entity_ambiguity_candidates:
|
previous_discovery_entity_ambiguity_candidates:
|
||||||
sourceDiscoveryEntityAmbiguityCandidates.length > 0
|
sourceDiscoveryEntityAmbiguityCandidates.length > 0
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,46 @@ describe("assistant MCP discovery runtime bridge", () => {
|
||||||
expect(result.answer_draft.next_step_line).not.toContain("Уточните контрагента");
|
expect(result.answer_draft.next_step_line).not.toContain("Уточните контрагента");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("emits a resumable loop state for clarification-driven ranking chains", async () => {
|
||||||
|
const result = await runAssistantMcpDiscoveryRuntimeBridge({
|
||||||
|
dataNeedGraph: {
|
||||||
|
schema_version: "assistant_data_need_graph_v1",
|
||||||
|
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
|
||||||
|
subject_candidates: [],
|
||||||
|
business_fact_family: "value_flow",
|
||||||
|
action_family: "turnover",
|
||||||
|
aggregation_need: null,
|
||||||
|
time_scope_need: "explicit_period",
|
||||||
|
comparison_need: null,
|
||||||
|
ranking_need: "top_desc",
|
||||||
|
proof_expectation: "coverage_checked_fact",
|
||||||
|
clarification_gaps: [],
|
||||||
|
decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"],
|
||||||
|
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
|
||||||
|
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
|
||||||
|
},
|
||||||
|
turnMeaning: {
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "turnover",
|
||||||
|
explicit_date_scope: "2020"
|
||||||
|
},
|
||||||
|
deps: buildDeps([])
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.bridge_status).toBe("needs_clarification");
|
||||||
|
expect(result.loop_state).toMatchObject({
|
||||||
|
loop_status: "awaiting_clarification",
|
||||||
|
selected_chain_id: "value_flow_ranking",
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "turnover",
|
||||||
|
ranking_need: "top_desc",
|
||||||
|
explicit_date_scope: "2020"
|
||||||
|
});
|
||||||
|
expect(result.loop_state.pending_axes).toContain("organization");
|
||||||
|
expect(result.loop_state.provided_axes).toContain("aggregate_axis");
|
||||||
|
expect(result.reason_codes).toContain("runtime_bridge_loop_state_awaiting_clarification");
|
||||||
|
});
|
||||||
|
|
||||||
it("produces a bounded ranked value-flow answer when period and organization are known", async () => {
|
it("produces a bounded ranked value-flow answer when period and organization are known", async () => {
|
||||||
const result = await runAssistantMcpDiscoveryRuntimeBridge({
|
const result = await runAssistantMcpDiscoveryRuntimeBridge({
|
||||||
dataNeedGraph: {
|
dataNeedGraph: {
|
||||||
|
|
|
||||||
|
|
@ -1598,6 +1598,81 @@ describe("assistant MCP discovery turn input adapter", () => {
|
||||||
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
expect(result.data_need_graph?.clarification_gaps).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("resumes an open-scope ranking from saved loop state even without a previous pilot scope", () => {
|
||||||
|
const orgName = "ООО Альтернатива Плюс";
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage: "по ООО Альтернатива Плюс",
|
||||||
|
predecomposeContract: {
|
||||||
|
entities: { organization: orgName }
|
||||||
|
},
|
||||||
|
followupContext: {
|
||||||
|
previous_discovery_loop_status: "awaiting_clarification",
|
||||||
|
previous_discovery_loop_selected_chain_id: "value_flow_ranking",
|
||||||
|
previous_discovery_loop_pending_axes: ["organization"],
|
||||||
|
previous_discovery_loop_provided_axes: ["aggregate_axis", "amount", "coverage_target"],
|
||||||
|
previous_discovery_loop_asked_domain_family: "counterparty_value",
|
||||||
|
previous_discovery_loop_asked_action_family: "turnover",
|
||||||
|
previous_discovery_loop_unsupported_family: "counterparty_value_or_turnover",
|
||||||
|
previous_discovery_ranking_need: "top_desc",
|
||||||
|
previous_filters: {
|
||||||
|
period_from: "2020-01-01",
|
||||||
|
period_to: "2020-12-31"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.adapter_status).toBe("ready");
|
||||||
|
expect(result.should_run_discovery).toBe(true);
|
||||||
|
expect(result.source_signal).toBe("followup_context");
|
||||||
|
expect(result.semantic_data_need).toBe("counterparty value-flow evidence");
|
||||||
|
expect(result.turn_meaning_ref).toMatchObject({
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "turnover",
|
||||||
|
seeded_ranking_need: "top_desc",
|
||||||
|
explicit_organization_scope: orgName,
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
unsupported_but_understood_family: "counterparty_value_or_turnover",
|
||||||
|
stale_replay_forbidden: true
|
||||||
|
});
|
||||||
|
expect(result.data_need_graph?.ranking_need).toBe("top_desc");
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_resumed_from_saved_loop_state");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves metadata lane choice from saved loop state even without a previous pilot scope", () => {
|
||||||
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
userMessage: "по движениям",
|
||||||
|
followupContext: {
|
||||||
|
previous_discovery_loop_status: "awaiting_clarification",
|
||||||
|
previous_discovery_loop_selected_chain_id: "metadata_lane_clarification",
|
||||||
|
previous_discovery_loop_pending_axes: ["lane_family_choice"],
|
||||||
|
previous_discovery_loop_asked_domain_family: "metadata",
|
||||||
|
previous_discovery_loop_asked_action_family: "resolve_next_lane",
|
||||||
|
previous_discovery_loop_unsupported_family: "metadata_lane_choice_clarification",
|
||||||
|
previous_filters: {
|
||||||
|
counterparty: "Группа СВК",
|
||||||
|
period_from: "2020-01-01",
|
||||||
|
period_to: "2020-12-31"
|
||||||
|
},
|
||||||
|
previous_discovery_metadata_ambiguity_detected: true,
|
||||||
|
previous_discovery_metadata_ambiguity_entity_sets: ["Документ", "РегистрНакопления"]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.adapter_status).toBe("ready");
|
||||||
|
expect(result.should_run_discovery).toBe(true);
|
||||||
|
expect(result.source_signal).toBe("followup_context");
|
||||||
|
expect(result.semantic_data_need).toBe("movement evidence");
|
||||||
|
expect(result.turn_meaning_ref).toMatchObject({
|
||||||
|
asked_domain_family: "movements",
|
||||||
|
asked_action_family: "list_movements",
|
||||||
|
explicit_entity_candidates: ["Группа СВК"],
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
unsupported_but_understood_family: "movement_evidence",
|
||||||
|
stale_replay_forbidden: true
|
||||||
|
});
|
||||||
|
expect(result.reason_codes).toContain("mcp_discovery_resumed_from_saved_loop_state");
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps seeded ranking through a year-switch follow-up after organization clarification", () => {
|
it("keeps seeded ranking through a year-switch follow-up after organization clarification", () => {
|
||||||
const orgName = "ООО Альтернатива Плюс";
|
const orgName = "ООО Альтернатива Плюс";
|
||||||
const result = buildAssistantMcpDiscoveryTurnInput({
|
const result = buildAssistantMcpDiscoveryTurnInput({
|
||||||
|
|
|
||||||
|
|
@ -1332,6 +1332,88 @@ describe("assistantTransitionPolicy", () => {
|
||||||
expect(carryover?.followupContext?.target_intent).toBe("customer_revenue_and_payments");
|
expect(carryover?.followupContext?.target_intent).toBe("customer_revenue_and_payments");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("carries resumable discovery loop state into followup context", () => {
|
||||||
|
const policy = buildPolicy({
|
||||||
|
findLastAddressAssistantItem: () => null,
|
||||||
|
hasAddressFollowupContextSignal: () => true
|
||||||
|
});
|
||||||
|
|
||||||
|
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||||
|
"ООО Альтернатива Плюс",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
text: "Нужно уточнить организацию, чтобы продолжить рейтинг.",
|
||||||
|
debug: {
|
||||||
|
execution_lane: "living_chat",
|
||||||
|
mcp_discovery_response_applied: true,
|
||||||
|
assistant_mcp_discovery_entry_point_v1: {
|
||||||
|
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||||
|
entry_status: "bridge_executed",
|
||||||
|
turn_input: {
|
||||||
|
data_need_graph: {
|
||||||
|
business_fact_family: "value_flow",
|
||||||
|
ranking_need: "top_desc",
|
||||||
|
subject_candidates: [],
|
||||||
|
clarification_gaps: ["organization"]
|
||||||
|
},
|
||||||
|
turn_meaning_ref: {
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "turnover",
|
||||||
|
explicit_date_scope: "2020",
|
||||||
|
seeded_ranking_need: "top_desc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bridge: {
|
||||||
|
bridge_status: "needs_clarification",
|
||||||
|
business_fact_answer_allowed: false,
|
||||||
|
pilot: {
|
||||||
|
pilot_scope: "counterparty_value_flow_query_movements_v1"
|
||||||
|
},
|
||||||
|
loop_state: {
|
||||||
|
schema_version: "assistant_mcp_discovery_loop_state_v1",
|
||||||
|
policy_owner: "assistantMcpDiscoveryRuntimeBridge",
|
||||||
|
loop_status: "awaiting_clarification",
|
||||||
|
selected_chain_id: "value_flow_ranking",
|
||||||
|
pilot_scope: "counterparty_value_flow_query_movements_v1",
|
||||||
|
asked_domain_family: "counterparty_value",
|
||||||
|
asked_action_family: "turnover",
|
||||||
|
unsupported_but_understood_family: "counterparty_value_or_turnover",
|
||||||
|
ranking_need: "top_desc",
|
||||||
|
pending_axes: ["organization"],
|
||||||
|
provided_axes: ["aggregate_axis", "amount", "coverage_target"],
|
||||||
|
explicit_entity_candidates: [],
|
||||||
|
explicit_organization_scope: null,
|
||||||
|
explicit_date_scope: "2020"
|
||||||
|
},
|
||||||
|
answer_draft: {
|
||||||
|
answer_mode: "needs_clarification"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_loop_status).toBe("awaiting_clarification");
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_loop_selected_chain_id).toBe("value_flow_ranking");
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_loop_pending_axes).toEqual(["organization"]);
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_loop_provided_axes).toEqual([
|
||||||
|
"aggregate_axis",
|
||||||
|
"amount",
|
||||||
|
"coverage_target"
|
||||||
|
]);
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_loop_asked_domain_family).toBe("counterparty_value");
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_loop_asked_action_family).toBe("turnover");
|
||||||
|
expect(carryover?.followupContext?.previous_discovery_loop_unsupported_family).toBe(
|
||||||
|
"counterparty_value_or_turnover"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("carries grounded metadata downstream route hints into followup context", () => {
|
it("carries grounded metadata downstream route hints into followup context", () => {
|
||||||
const policy = buildPolicy({
|
const policy = buildPolicy({
|
||||||
findLastAddressAssistantItem: () => null,
|
findLastAddressAssistantItem: () => null,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue