"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ASSISTANT_MCP_DISCOVERY_PLANNER_SCHEMA_VERSION = void 0; exports.planAssistantMcpDiscovery = planAssistantMcpDiscovery; const assistantMcpDiscoveryPolicy_1 = require("./assistantMcpDiscoveryPolicy"); const assistantMcpCatalogIndex_1 = require("./assistantMcpCatalogIndex"); exports.ASSISTANT_MCP_DISCOVERY_PLANNER_SCHEMA_VERSION = "assistant_mcp_discovery_planner_v1"; function toNonEmptyString(value) { if (value === null || value === undefined) { return null; } const text = String(value).trim(); return text.length > 0 ? text : null; } function lower(value) { return String(value ?? "").trim().toLowerCase(); } function normalizeReasonCode(value) { const normalized = value .trim() .replace(/[^\p{L}\p{N}_.:-]+/gu, "_") .replace(/^_+|_+$/g, "") .toLowerCase(); return normalized.length > 0 ? normalized.slice(0, 120) : null; } function pushReason(target, value) { const normalized = normalizeReasonCode(value); if (normalized && !target.includes(normalized)) { target.push(normalized); } } function pushUnique(target, value) { const text = value.trim(); if (text && !target.includes(text)) { target.push(text); } } function hasEntity(meaning) { return (meaning?.explicit_entity_candidates?.length ?? 0) > 0; } function hasSubjectCandidates(graph) { return (graph?.subject_candidates.length ?? 0) > 0; } function aggregationAxis(meaning) { return toNonEmptyString(meaning?.asked_aggregation_axis)?.toLowerCase() ?? null; } function addScopeAxes(axes, meaning) { if (hasEntity(meaning)) { pushUnique(axes, "counterparty"); } if (toNonEmptyString(meaning?.explicit_organization_scope)) { pushUnique(axes, "organization"); } if (toNonEmptyString(meaning?.explicit_date_scope)) { pushUnique(axes, "period"); } } function includesAny(text, tokens) { return tokens.some((token) => text.includes(token)); } function isYearDateScope(meaning) { return /^\d{4}$/.test(toNonEmptyString(meaning?.explicit_date_scope) ?? ""); } function budgetOverrideFor(input, recipe) { const meaning = input.turnMeaning ?? null; const requestedAggregationAxis = aggregationAxis(meaning); const isValueFlowRecipe = recipe.semanticDataNeed === "counterparty value-flow evidence" && recipe.primitives.includes("query_movements"); if (!isValueFlowRecipe) { return {}; } if (requestedAggregationAxis === "month" || isYearDateScope(meaning)) { return { maxProbeCount: 30 }; } return {}; } function recipeFor(input) { const meaning = input.turnMeaning ?? null; const dataNeedGraph = input.dataNeedGraph ?? null; const domain = lower(meaning?.asked_domain_family); const action = lower(meaning?.asked_action_family); const unsupported = lower(meaning?.unsupported_but_understood_family); const graphFactFamily = lower(dataNeedGraph?.business_fact_family); const graphAction = lower(dataNeedGraph?.action_family); const graphAggregation = lower(dataNeedGraph?.aggregation_need); const graphClarificationGaps = (dataNeedGraph?.clarification_gaps ?? []).map((item) => lower(item)); const organizationScope = toNonEmptyString(meaning?.explicit_organization_scope); const combined = `${domain} ${action} ${unsupported}`.trim(); const axes = []; const requestedAggregationAxis = aggregationAxis(meaning); addScopeAxes(axes, meaning); if (graphClarificationGaps.includes("lane_family_choice")) { pushUnique(axes, "lane_family_choice"); return { semanticDataNeed: "metadata lane clarification", chainId: "metadata_lane_clarification", chainSummary: "Preserve the ambiguous metadata surface and ask the user to choose the next data lane before running MCP probes.", primitives: [], axes, reason: "planner_selected_metadata_lane_clarification_from_data_need_graph" }; } if (graphFactFamily === "value_flow") { if (dataNeedGraph?.comparison_need === "incoming_vs_outgoing" && !hasSubjectCandidates(dataNeedGraph)) { pushUnique(axes, "amount"); pushUnique(axes, "coverage_target"); if (requestedAggregationAxis === "month" || graphAggregation === "by_month") { pushUnique(axes, "calendar_month"); } return { semanticDataNeed: "bidirectional value-flow comparison evidence", chainId: "value_flow_comparison", chainSummary: "Query incoming and outgoing movements for the checked period and organization, compare the checked sides, and probe coverage before answering a bounded comparison.", primitives: ["query_movements", "probe_coverage"], axes, reason: "planner_selected_bidirectional_value_flow_comparison_from_data_need_graph" }; } if (dataNeedGraph?.ranking_need && !hasSubjectCandidates(dataNeedGraph)) { pushUnique(axes, "aggregate_axis"); pushUnique(axes, "amount"); pushUnique(axes, "coverage_target"); return { semanticDataNeed: "ranked value-flow evidence", chainId: "value_flow_ranking", chainSummary: "Query scoped movements for the checked period and organization, aggregate checked amounts by counterparty, then probe coverage before answering a bounded ranking.", primitives: ["query_movements", "aggregate_by_axis", "probe_coverage"], axes, reason: dataNeedGraph.ranking_need === "bottom_asc" ? "planner_selected_bottom_ranked_value_flow_from_data_need_graph" : "planner_selected_top_ranked_value_flow_from_data_need_graph" }; } if (!hasSubjectCandidates(dataNeedGraph) && organizationScope) { pushUnique(axes, "aggregate_axis"); pushUnique(axes, "amount"); pushUnique(axes, "coverage_target"); if (requestedAggregationAxis === "month" || graphAggregation === "by_month") { pushUnique(axes, "calendar_month"); } return { semanticDataNeed: "organization-scoped value-flow evidence", chainId: "value_flow", chainSummary: "Query scoped movements for the checked period and organization without a preselected counterparty, aggregate checked amounts, then probe coverage before answering a bounded total.", primitives: ["query_movements", "aggregate_by_axis", "probe_coverage"], axes, reason: requestedAggregationAxis === "month" || graphAggregation === "by_month" ? "planner_selected_monthly_open_scope_value_flow_total_from_data_need_graph" : "planner_selected_open_scope_value_flow_total_from_data_need_graph" }; } pushUnique(axes, "aggregate_axis"); pushUnique(axes, "amount"); pushUnique(axes, "coverage_target"); if (requestedAggregationAxis === "month" || graphAggregation === "by_month") { pushUnique(axes, "calendar_month"); } return { semanticDataNeed: "counterparty value-flow evidence", chainId: "value_flow", chainSummary: "Resolve the business entity, query scoped movements, aggregate checked amounts, then probe coverage before answering.", primitives: ["resolve_entity_reference", "query_movements", "aggregate_by_axis", "probe_coverage"], axes, reason: requestedAggregationAxis === "month" || graphAggregation === "by_month" ? "planner_selected_monthly_value_flow_from_data_need_graph" : "planner_selected_value_flow_from_data_need_graph" }; } if (graphFactFamily === "activity_lifecycle") { pushUnique(axes, "document_date"); pushUnique(axes, "coverage_target"); pushUnique(axes, "evidence_basis"); return { semanticDataNeed: "counterparty lifecycle evidence", chainId: "lifecycle", chainSummary: "Resolve the business entity, query supporting documents, probe coverage, then explain the evidence basis for the inferred activity window.", primitives: ["resolve_entity_reference", "query_documents", "probe_coverage", "explain_evidence_basis"], axes, reason: "planner_selected_lifecycle_from_data_need_graph" }; } if (graphFactFamily === "schema_surface") { pushUnique(axes, "metadata_scope"); return { semanticDataNeed: "1C metadata evidence", chainId: "metadata_inspection", chainSummary: "Inspect the 1C metadata surface first, then ground the next safe lane from confirmed schema evidence.", primitives: ["inspect_1c_metadata"], axes, reason: "planner_selected_metadata_from_data_need_graph" }; } if (graphFactFamily === "movement_evidence") { pushUnique(axes, "coverage_target"); return { semanticDataNeed: "movement evidence", chainId: "movement_evidence", chainSummary: "Resolve the business entity, fetch scoped movement rows, and probe coverage without pretending to have a full movement universe.", primitives: ["resolve_entity_reference", "query_movements", "probe_coverage"], axes, reason: "planner_selected_movement_from_data_need_graph" }; } if (graphFactFamily === "document_evidence") { pushUnique(axes, "coverage_target"); return { semanticDataNeed: "document evidence", chainId: "document_evidence", chainSummary: "Resolve the business entity, fetch scoped document rows, and probe coverage before stating the checked document evidence.", primitives: ["resolve_entity_reference", "query_documents", "probe_coverage"], axes, reason: "planner_selected_document_from_data_need_graph" }; } if (graphFactFamily === "entity_grounding" || (!graphFactFamily && (dataNeedGraph?.subject_candidates.length ?? 0) > 0)) { pushUnique(axes, "business_entity"); pushUnique(axes, "coverage_target"); return { semanticDataNeed: "entity discovery evidence", chainId: "entity_resolution", chainSummary: "Search candidate business entities, resolve the most relevant 1C reference, and prove whether the entity grounding is stable enough for the next probe.", primitives: ["search_business_entity", "resolve_entity_reference", "probe_coverage"], axes, reason: graphAction === "search_business_entity" ? "planner_selected_entity_resolution_from_data_need_graph" : "planner_selected_entity_resolution_recipe" }; } if (includesAny(combined, ["metadata_lane_choice_clarification", "resolve_next_lane"])) { pushUnique(axes, "lane_family_choice"); return { semanticDataNeed: "metadata lane clarification", chainId: "metadata_lane_clarification", chainSummary: "Preserve the ambiguous metadata surface and ask the user to choose the next data lane before running MCP probes.", primitives: [], axes, reason: "planner_selected_metadata_lane_clarification_recipe" }; } if (includesAny(combined, ["turnover", "revenue", "payment", "payout", "value", "net", "netting", "balance", "cashflow"])) { pushUnique(axes, "aggregate_axis"); pushUnique(axes, "amount"); pushUnique(axes, "coverage_target"); if (requestedAggregationAxis === "month") { pushUnique(axes, "calendar_month"); } return { semanticDataNeed: "counterparty value-flow evidence", chainId: "value_flow", chainSummary: "Resolve the business entity, query scoped movements, aggregate checked amounts, then probe coverage before answering.", primitives: ["resolve_entity_reference", "query_movements", "aggregate_by_axis", "probe_coverage"], axes, reason: requestedAggregationAxis === "month" ? "planner_selected_monthly_value_flow_recipe" : "planner_selected_value_flow_recipe" }; } if (includesAny(combined, ["lifecycle", "activity", "duration", "age"])) { pushUnique(axes, "document_date"); pushUnique(axes, "coverage_target"); pushUnique(axes, "evidence_basis"); return { semanticDataNeed: "counterparty lifecycle evidence", chainId: "lifecycle", chainSummary: "Resolve the business entity, query supporting documents, probe coverage, then explain the evidence basis for the inferred activity window.", primitives: ["resolve_entity_reference", "query_documents", "probe_coverage", "explain_evidence_basis"], axes, reason: "planner_selected_lifecycle_recipe" }; } if (includesAny(combined, ["metadata", "schema", "catalog"])) { pushUnique(axes, "metadata_scope"); return { semanticDataNeed: "1C metadata evidence", chainId: "metadata_inspection", chainSummary: "Inspect the 1C metadata surface first, then ground the next safe lane from confirmed schema evidence.", primitives: ["inspect_1c_metadata"], axes, reason: "planner_selected_metadata_recipe" }; } if (includesAny(combined, ["movement", "movements", "bank_operations", "movement_evidence", "list_movements"])) { pushUnique(axes, "coverage_target"); return { semanticDataNeed: "movement evidence", chainId: "movement_evidence", chainSummary: "Resolve the business entity, fetch scoped movement rows, and probe coverage without pretending to have a full movement universe.", primitives: ["resolve_entity_reference", "query_movements", "probe_coverage"], axes, reason: "planner_selected_movement_recipe" }; } if (includesAny(combined, ["document", "documents"])) { pushUnique(axes, "coverage_target"); return { semanticDataNeed: "document evidence", chainId: "document_evidence", chainSummary: "Resolve the business entity, fetch scoped document rows, and probe coverage before stating the checked document evidence.", primitives: ["resolve_entity_reference", "query_documents", "probe_coverage"], axes, reason: "planner_selected_document_recipe" }; } if (hasEntity(meaning)) { pushUnique(axes, "business_entity"); pushUnique(axes, "coverage_target"); return { semanticDataNeed: "entity discovery evidence", chainId: "entity_resolution", chainSummary: "Search candidate business entities, resolve the most relevant 1C reference, and prove whether the entity grounding is stable enough for the next probe.", primitives: ["search_business_entity", "resolve_entity_reference", "probe_coverage"], axes, reason: "planner_selected_entity_resolution_recipe" }; } return { semanticDataNeed: "unclassified 1C discovery need", chainId: "metadata_inspection", chainSummary: "Start with metadata inspection instead of guessing a deeper fact route when the business need is still under-specified.", primitives: ["inspect_1c_metadata"], axes, reason: "planner_selected_clarification_recipe" }; } function statusFrom(plan, review) { if (plan.plan_status === "blocked" || review.review_status === "catalog_blocked") { return "blocked"; } if (plan.plan_status !== "allowed" || review.review_status !== "catalog_compatible") { return "needs_clarification"; } return "ready_for_execution"; } function planAssistantMcpDiscovery(input) { const recipe = recipeFor(input); const budgetOverride = budgetOverrideFor(input, recipe); const semanticDataNeed = toNonEmptyString(input.semanticDataNeed) ?? recipe.semanticDataNeed; const dataNeedGraph = input.dataNeedGraph ?? null; const reasonCodes = []; pushReason(reasonCodes, recipe.reason); if (dataNeedGraph) { pushReason(reasonCodes, "planner_consumed_data_need_graph_v1"); } if (budgetOverride.maxProbeCount) { pushReason(reasonCodes, "planner_enabled_chunked_coverage_probe_budget"); } const plan = (0, assistantMcpDiscoveryPolicy_1.buildAssistantMcpDiscoveryPlan)({ semanticDataNeed, turnMeaning: input.turnMeaning, proposedPrimitives: recipe.primitives, requiredAxes: recipe.axes, maxProbeCount: budgetOverride.maxProbeCount }); const review = (0, assistantMcpCatalogIndex_1.reviewAssistantMcpDiscoveryPlanAgainstCatalog)(plan); const plannerStatus = statusFrom(plan, review); if (plannerStatus === "ready_for_execution") { pushReason(reasonCodes, "planner_ready_for_guarded_mcp_execution"); } else if (plannerStatus === "blocked") { pushReason(reasonCodes, "planner_blocked_by_policy_or_catalog"); } else { pushReason(reasonCodes, "planner_needs_more_user_or_scope_context"); } return { schema_version: exports.ASSISTANT_MCP_DISCOVERY_PLANNER_SCHEMA_VERSION, policy_owner: "assistantMcpDiscoveryPlanner", planner_status: plannerStatus, semantic_data_need: semanticDataNeed, data_need_graph: dataNeedGraph, selected_chain_id: recipe.chainId, selected_chain_summary: recipe.chainSummary, proposed_primitives: recipe.primitives, required_axes: recipe.axes, discovery_plan: plan, catalog_review: review, reason_codes: reasonCodes }; }