ARCH: добавить pilot executor MCP discovery

This commit is contained in:
2026-04-20 09:48:11 +03:00
parent 9d5ae69953
commit 4181bb9c9b
4 changed files with 784 additions and 0 deletions
@@ -0,0 +1,363 @@
import {
executeAddressMcpQuery,
type AddressMcpMetadataRowsResult
} from "./addressMcpClient";
import {
buildAssistantMcpDiscoveryRuntimeDryRun,
type AssistantMcpDiscoveryRuntimeDryRunContract,
type AssistantMcpDiscoveryRuntimeStepContract
} from "./assistantMcpDiscoveryRuntimeAdapter";
import type { AssistantMcpDiscoveryPlannerContract } from "./assistantMcpDiscoveryPlanner";
import {
resolveAssistantMcpDiscoveryEvidence,
type AssistantMcpDiscoveryEvidenceContract,
type AssistantMcpDiscoveryProbeResult
} from "./assistantMcpDiscoveryPolicy";
import { buildAddressRecipePlan, selectAddressRecipe } from "./addressRecipeCatalog";
import type { AddressFilterSet } from "../types/addressQuery";
export const ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION =
"assistant_mcp_discovery_pilot_executor_v1" as const;
export type AssistantMcpDiscoveryPilotStatus =
| "executed"
| "skipped_needs_clarification"
| "blocked"
| "unsupported";
export interface AssistantMcpDiscoveryPilotExecutorDeps {
executeAddressMcpQuery: typeof executeAddressMcpQuery;
}
export interface AssistantMcpDiscoveryPilotExecutionContract {
schema_version: typeof ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION;
policy_owner: "assistantMcpDiscoveryPilotExecutor";
pilot_status: AssistantMcpDiscoveryPilotStatus;
pilot_scope: "counterparty_lifecycle_query_documents_v1";
dry_run: AssistantMcpDiscoveryRuntimeDryRunContract;
mcp_execution_performed: boolean;
executed_primitives: string[];
skipped_primitives: string[];
probe_results: AssistantMcpDiscoveryProbeResult[];
evidence: AssistantMcpDiscoveryEvidenceContract;
source_rows_summary: string | null;
query_limitations: string[];
reason_codes: string[];
}
type AddressMcpQueryExecutorResult = Awaited<ReturnType<typeof executeAddressMcpQuery>>;
const DEFAULT_DEPS: AssistantMcpDiscoveryPilotExecutorDeps = {
executeAddressMcpQuery
};
function toNonEmptyString(value: unknown): string | null {
if (value === null || value === undefined) {
return null;
}
const text = String(value).trim();
return text.length > 0 ? text : null;
}
function normalizeReasonCode(value: string): string | null {
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: string[], value: string): void {
const normalized = normalizeReasonCode(value);
if (normalized && !target.includes(normalized)) {
target.push(normalized);
}
}
function pushUnique(target: string[], value: string): void {
const text = value.trim();
if (text && !target.includes(text)) {
target.push(text);
}
}
function firstEntityCandidate(planner: AssistantMcpDiscoveryPlannerContract): string | null {
const candidates = planner.discovery_plan.turn_meaning_ref?.explicit_entity_candidates ?? [];
for (const candidate of candidates) {
const text = toNonEmptyString(candidate);
if (text) {
return text;
}
}
return null;
}
function dateScopeToFilters(dateScope: string | null): Pick<AddressFilterSet, "period_from" | "period_to"> {
if (!dateScope) {
return {};
}
const yearMatch = dateScope.match(/^(\d{4})$/);
if (yearMatch) {
return {
period_from: `${yearMatch[1]}-01-01`,
period_to: `${yearMatch[1]}-12-31`
};
}
const dateMatch = dateScope.match(/^(\d{4})-(\d{2})-(\d{2})/);
if (dateMatch) {
const date = `${dateMatch[1]}-${dateMatch[2]}-${dateMatch[3]}`;
return {
period_from: date,
period_to: date
};
}
return {};
}
function buildLifecycleFilters(planner: AssistantMcpDiscoveryPlannerContract): AddressFilterSet {
const meaning = planner.discovery_plan.turn_meaning_ref;
const counterparty = firstEntityCandidate(planner);
const organization = toNonEmptyString(meaning?.explicit_organization_scope);
const dateScope = toNonEmptyString(meaning?.explicit_date_scope);
return {
...dateScopeToFilters(dateScope),
...(counterparty ? { counterparty } : {}),
...(organization ? { organization } : {}),
limit: planner.discovery_plan.execution_budget.max_rows_per_probe,
sort: "period_asc"
};
}
function isLifecyclePilotEligible(planner: AssistantMcpDiscoveryPlannerContract): boolean {
const meaning = planner.discovery_plan.turn_meaning_ref;
const domain = String(meaning?.asked_domain_family ?? "").toLowerCase();
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
const combined = `${domain} ${action}`;
return (
planner.proposed_primitives.includes("query_documents") &&
(combined.includes("lifecycle") || combined.includes("activity") || combined.includes("duration") || combined.includes("age"))
);
}
function skippedProbeResult(step: AssistantMcpDiscoveryRuntimeStepContract, limitation: string): AssistantMcpDiscoveryProbeResult {
return {
primitive_id: step.primitive_id,
status: "skipped",
rows_received: 0,
rows_matched: 0,
limitation
};
}
function queryResultToProbeResult(
primitiveId: string,
result: AddressMcpQueryExecutorResult
): AssistantMcpDiscoveryProbeResult {
return {
primitive_id: primitiveId,
status: result.error ? "error" : "ok",
rows_received: result.fetched_rows,
rows_matched: result.matched_rows,
limitation: result.error
};
}
function summarizeRows(result: AddressMcpQueryExecutorResult): string | null {
if (result.error) {
return null;
}
if (result.fetched_rows <= 0) {
return "0 MCP document rows fetched";
}
return `${result.fetched_rows} MCP document rows fetched, ${result.matched_rows} matched lifecycle scope`;
}
function buildConfirmedFacts(result: AddressMcpQueryExecutorResult, counterparty: string | null): string[] {
if (result.error || result.matched_rows <= 0) {
return [];
}
return [
counterparty
? `1C activity rows were found for counterparty ${counterparty}`
: "1C activity rows were found for the requested counterparty scope"
];
}
function buildInferredFacts(result: AddressMcpQueryExecutorResult): string[] {
if (result.error || result.fetched_rows <= 0) {
return [];
}
return ["Business activity duration may be inferred from first and latest confirmed 1C activity rows"];
}
function buildUnknownFacts(): string[] {
return ["Legal registration date is not proven by this MCP discovery pilot"];
}
function buildEmptyEvidence(
planner: AssistantMcpDiscoveryPlannerContract,
dryRun: AssistantMcpDiscoveryRuntimeDryRunContract,
probeResults: AssistantMcpDiscoveryProbeResult[],
reason: string
): AssistantMcpDiscoveryEvidenceContract {
return resolveAssistantMcpDiscoveryEvidence({
plan: planner.discovery_plan,
probeResults,
unknownFacts: [reason],
queryLimitations: [reason],
recommendedNextProbe: dryRun.user_facing_fallback
});
}
export async function executeAssistantMcpDiscoveryPilot(
planner: AssistantMcpDiscoveryPlannerContract,
deps: AssistantMcpDiscoveryPilotExecutorDeps = DEFAULT_DEPS
): Promise<AssistantMcpDiscoveryPilotExecutionContract> {
const dryRun = buildAssistantMcpDiscoveryRuntimeDryRun(planner);
const reasonCodes = [...dryRun.reason_codes];
const executedPrimitives: string[] = [];
const skippedPrimitives: string[] = [];
const probeResults: AssistantMcpDiscoveryProbeResult[] = [];
const queryLimitations: string[] = [];
if (dryRun.adapter_status === "blocked") {
pushReason(reasonCodes, "pilot_blocked_before_mcp_execution");
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "MCP discovery pilot was blocked before execution");
return {
schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryPilotExecutor",
pilot_status: "blocked",
pilot_scope: "counterparty_lifecycle_query_documents_v1",
dry_run: dryRun,
mcp_execution_performed: false,
executed_primitives: executedPrimitives,
skipped_primitives: skippedPrimitives,
probe_results: probeResults,
evidence,
source_rows_summary: null,
query_limitations: ["MCP discovery pilot was blocked before execution"],
reason_codes: reasonCodes
};
}
if (dryRun.adapter_status !== "dry_run_ready") {
pushReason(reasonCodes, "pilot_needs_clarification_before_mcp_execution");
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "MCP discovery pilot needs more scope before execution");
return {
schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryPilotExecutor",
pilot_status: "skipped_needs_clarification",
pilot_scope: "counterparty_lifecycle_query_documents_v1",
dry_run: dryRun,
mcp_execution_performed: false,
executed_primitives: executedPrimitives,
skipped_primitives: skippedPrimitives,
probe_results: probeResults,
evidence,
source_rows_summary: null,
query_limitations: ["MCP discovery pilot needs more scope before execution"],
reason_codes: reasonCodes
};
}
if (!isLifecyclePilotEligible(planner)) {
pushReason(reasonCodes, "pilot_scope_unsupported_for_live_execution");
for (const step of dryRun.execution_steps) {
skippedPrimitives.push(step.primitive_id);
probeResults.push(skippedProbeResult(step, "pilot_scope_unsupported_for_live_execution"));
}
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "MCP discovery pilot scope is not implemented yet");
return {
schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryPilotExecutor",
pilot_status: "unsupported",
pilot_scope: "counterparty_lifecycle_query_documents_v1",
dry_run: dryRun,
mcp_execution_performed: false,
executed_primitives: executedPrimitives,
skipped_primitives: skippedPrimitives,
probe_results: probeResults,
evidence,
source_rows_summary: null,
query_limitations: ["MCP discovery pilot scope is not implemented yet"],
reason_codes: reasonCodes
};
}
let queryResult: AddressMcpQueryExecutorResult | null = null;
const counterparty = firstEntityCandidate(planner);
const filters = buildLifecycleFilters(planner);
const selection = selectAddressRecipe("counterparty_activity_lifecycle", filters);
if (!selection.selected_recipe) {
pushReason(reasonCodes, "pilot_lifecycle_recipe_not_available");
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Lifecycle recipe is not available");
return {
schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryPilotExecutor",
pilot_status: "unsupported",
pilot_scope: "counterparty_lifecycle_query_documents_v1",
dry_run: dryRun,
mcp_execution_performed: false,
executed_primitives: executedPrimitives,
skipped_primitives: skippedPrimitives,
probe_results: probeResults,
evidence,
source_rows_summary: null,
query_limitations: ["Lifecycle recipe is not available"],
reason_codes: reasonCodes
};
}
const recipePlan = buildAddressRecipePlan(selection.selected_recipe, filters);
for (const step of dryRun.execution_steps) {
if (step.primitive_id !== "query_documents") {
skippedPrimitives.push(step.primitive_id);
probeResults.push(skippedProbeResult(step, "pilot_only_executes_query_documents"));
continue;
}
queryResult = await deps.executeAddressMcpQuery({
query: recipePlan.query,
limit: recipePlan.limit,
account_scope: recipePlan.account_scope
});
executedPrimitives.push(step.primitive_id);
probeResults.push(queryResultToProbeResult(step.primitive_id, queryResult));
if (queryResult.error) {
pushUnique(queryLimitations, queryResult.error);
pushReason(reasonCodes, "pilot_query_documents_mcp_error");
} else {
pushReason(reasonCodes, "pilot_query_documents_mcp_executed");
}
}
const sourceRowsSummary = queryResult ? summarizeRows(queryResult) : null;
const evidence = resolveAssistantMcpDiscoveryEvidence({
plan: planner.discovery_plan,
probeResults,
confirmedFacts: queryResult ? buildConfirmedFacts(queryResult, counterparty) : [],
inferredFacts: queryResult ? buildInferredFacts(queryResult) : [],
unknownFacts: buildUnknownFacts(),
sourceRowsSummary,
queryLimitations,
recommendedNextProbe: "explain_evidence_basis"
});
return {
schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryPilotExecutor",
pilot_status: "executed",
pilot_scope: "counterparty_lifecycle_query_documents_v1",
dry_run: dryRun,
mcp_execution_performed: executedPrimitives.length > 0,
executed_primitives: executedPrimitives,
skipped_primitives: skippedPrimitives,
probe_results: probeResults,
evidence,
source_rows_summary: sourceRowsSummary,
query_limitations: queryLimitations,
reason_codes: reasonCodes
};
}
export type AssistantMcpDiscoveryPilotMetadataResult = AddressMcpMetadataRowsResult;