363 lines
17 KiB
JavaScript
363 lines
17 KiB
JavaScript
"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 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"
|
|
};
|
|
}
|
|
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
|
|
};
|
|
}
|