ARCH: перезапустить план на MCP bounded autonomy и добавить metadata pilot

This commit is contained in:
2026-04-21 22:04:23 +03:00
parent bda7ca9cc1
commit 561b4ea45c
15 changed files with 1214 additions and 57 deletions
@@ -1,4 +1,5 @@
import {
executeAddressMcpMetadata,
executeAddressMcpQuery,
type AddressMcpMetadataRowsResult
} from "./addressMcpClient";
@@ -26,7 +27,13 @@ export type AssistantMcpDiscoveryPilotStatus =
| "unsupported";
export interface AssistantMcpDiscoveryPilotExecutorDeps {
executeAddressMcpQuery?: typeof executeAddressMcpQuery;
executeAddressMcpMetadata?: typeof executeAddressMcpMetadata;
}
interface ResolvedAssistantMcpDiscoveryPilotExecutorDeps {
executeAddressMcpQuery: typeof executeAddressMcpQuery;
executeAddressMcpMetadata: typeof executeAddressMcpMetadata;
}
export interface AssistantMcpDiscoveryDerivedActivityPeriod {
@@ -109,6 +116,17 @@ export interface AssistantMcpDiscoveryDerivedBidirectionalValueFlow {
inference_basis: "incoming_minus_outgoing_confirmed_1c_value_flow_rows";
}
export interface AssistantMcpDiscoveryDerivedMetadataSurface {
metadata_scope: string | null;
requested_meta_types: string[];
matched_rows: number;
available_entity_sets: string[];
matched_objects: string[];
available_fields: string[];
known_limitations: string[];
inference_basis: "confirmed_1c_metadata_surface_rows";
}
interface AssistantMcpDiscoveryCoverageAwareQueryResult extends AddressMcpQueryExecutorResult {
coverage_limited_by_probe_limit: boolean;
coverage_recovered_by_period_chunking: boolean;
@@ -124,6 +142,7 @@ interface AssistantMcpDiscoveryCoverageAwareQueryExecution {
}
export type AssistantMcpDiscoveryPilotScope =
| "metadata_inspection_v1"
| "counterparty_lifecycle_query_documents_v1"
| "counterparty_value_flow_query_movements_v1"
| "counterparty_supplier_payout_query_movements_v1"
@@ -141,6 +160,7 @@ export interface AssistantMcpDiscoveryPilotExecutionContract {
probe_results: AssistantMcpDiscoveryProbeResult[];
evidence: AssistantMcpDiscoveryEvidenceContract;
source_rows_summary: string | null;
derived_metadata_surface: AssistantMcpDiscoveryDerivedMetadataSurface | null;
derived_activity_period: AssistantMcpDiscoveryDerivedActivityPeriod | null;
derived_value_flow: AssistantMcpDiscoveryDerivedValueFlow | null;
derived_bidirectional_value_flow: AssistantMcpDiscoveryDerivedBidirectionalValueFlow | null;
@@ -150,8 +170,9 @@ export interface AssistantMcpDiscoveryPilotExecutionContract {
type AddressMcpQueryExecutorResult = Awaited<ReturnType<typeof executeAddressMcpQuery>>;
const DEFAULT_DEPS: AssistantMcpDiscoveryPilotExecutorDeps = {
executeAddressMcpQuery
const DEFAULT_DEPS: ResolvedAssistantMcpDiscoveryPilotExecutorDeps = {
executeAddressMcpQuery,
executeAddressMcpMetadata
};
function toNonEmptyString(value: unknown): string | null {
@@ -280,6 +301,60 @@ function isValueFlowPilotEligible(planner: AssistantMcpDiscoveryPlannerContract)
);
}
function isMetadataPilotEligible(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 unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase();
const semanticNeed = String(planner.semantic_data_need ?? "").toLowerCase();
const combined = `${domain} ${action} ${unsupported} ${semanticNeed}`;
return (
planner.proposed_primitives.includes("inspect_1c_metadata") &&
(combined.includes("metadata") ||
combined.includes("schema") ||
combined.includes("catalog") ||
combined.includes("inspect_documents") ||
combined.includes("inspect_registers") ||
combined.includes("inspect_fields"))
);
}
function metadataScopeForPlanner(planner: AssistantMcpDiscoveryPlannerContract): string | null {
const entityCandidate = firstEntityCandidate(planner);
if (entityCandidate) {
return entityCandidate;
}
const meaning = planner.discovery_plan.turn_meaning_ref;
const combined = `${meaning?.asked_domain_family ?? ""} ${meaning?.asked_action_family ?? ""} ${meaning?.unsupported_but_understood_family ?? ""}`
.toLowerCase()
.trim();
if (combined.includes("vat")) {
return "НДС";
}
if (combined.includes("inventory")) {
return "склад";
}
if (combined.includes("counterparty")) {
return "контрагент";
}
return null;
}
function metadataTypesForPlanner(planner: AssistantMcpDiscoveryPlannerContract): string[] {
const meaning = planner.discovery_plan.turn_meaning_ref;
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
if (action === "inspect_registers") {
return ["РегистрНакопления", "РегистрСведений"];
}
if (action === "inspect_documents") {
return ["Документ"];
}
if (action === "inspect_catalog") {
return ["Справочник"];
}
return ["Документ", "РегистрНакопления", "РегистрСведений", "Справочник"];
}
interface ValueFlowPilotProfile {
scope: Extract<
AssistantMcpDiscoveryPilotScope,
@@ -350,6 +425,19 @@ function queryResultToProbeResult(
};
}
function metadataResultToProbeResult(
primitiveId: string,
result: AddressMcpMetadataRowsResult
): AssistantMcpDiscoveryProbeResult {
return {
primitive_id: primitiveId,
status: result.error ? "error" : "ok",
rows_received: result.fetched_rows,
rows_matched: result.error ? 0 : result.rows.length,
limitation: result.error
};
}
function toCoverageAwareQueryResult(
result: AddressMcpQueryExecutorResult | null,
options: {
@@ -428,7 +516,7 @@ async function executeCoverageAwareValueFlowQuery(input: {
dateScope: string | null;
maxProbeCount: number;
maxRowsPerProbe: number;
deps: AssistantMcpDiscoveryPilotExecutorDeps;
deps: ResolvedAssistantMcpDiscoveryPilotExecutorDeps;
}): Promise<AssistantMcpDiscoveryCoverageAwareQueryExecution> {
const queryLimitations: string[] = [];
const probeResults: AssistantMcpDiscoveryProbeResult[] = [];
@@ -560,6 +648,167 @@ function summarizeValueFlowRows(result: AssistantMcpDiscoveryCoverageAwareQueryR
return `${result.fetched_rows} MCP value-flow rows fetched, ${result.matched_rows} matched value-flow scope`;
}
function summarizeMetadataRows(result: AddressMcpMetadataRowsResult): string | null {
if (result.error) {
return null;
}
if (result.fetched_rows <= 0) {
return "0 MCP metadata rows fetched";
}
return `${result.fetched_rows} MCP metadata rows fetched`;
}
function metadataRowText(row: Record<string, unknown>, keys: string[]): string | null {
for (const key of keys) {
const text = toNonEmptyString(row[key]);
if (text) {
return text;
}
}
return null;
}
function metadataObjectName(row: Record<string, unknown>): string | null {
return metadataRowText(row, [
"ПолноеИмя",
"full_name",
"FullName",
"Имя",
"name",
"Name",
"presentation",
"Представление",
"synonym",
"Synonym"
]);
}
function metadataEntitySet(row: Record<string, unknown>): string | null {
return metadataRowText(row, [
"ТипМетаданных",
"type",
"Type",
"meta_type",
"MetaType",
"ВидМетаданных",
"kind"
]);
}
function metadataChildNames(value: unknown): string[] {
if (!Array.isArray(value)) {
return [];
}
const result: string[] = [];
for (const item of value) {
if (!item || typeof item !== "object" || Array.isArray(item)) {
continue;
}
const record = item as Record<string, unknown>;
const fieldName = metadataRowText(record, ["Имя", "name", "Name", "full_name", "FullName"]);
if (fieldName) {
pushUnique(result, fieldName);
}
}
return result;
}
function metadataAvailableFields(rows: Array<Record<string, unknown>>): string[] {
const result: string[] = [];
for (const row of rows) {
for (const field of metadataChildNames(row["Реквизиты"])) {
pushUnique(result, field);
}
for (const field of metadataChildNames(row["attributes"])) {
pushUnique(result, field);
}
for (const field of metadataChildNames(row["Attributes"])) {
pushUnique(result, field);
}
for (const field of metadataChildNames(row["Измерения"])) {
pushUnique(result, field);
}
for (const field of metadataChildNames(row["dimensions"])) {
pushUnique(result, field);
}
for (const field of metadataChildNames(row["Ресурсы"])) {
pushUnique(result, field);
}
for (const field of metadataChildNames(row["resources"])) {
pushUnique(result, field);
}
}
return result;
}
function deriveMetadataSurface(
result: AddressMcpMetadataRowsResult | null,
metadataScope: string | null,
requestedMetaTypes: string[]
): AssistantMcpDiscoveryDerivedMetadataSurface | null {
if (!result || result.error || result.rows.length <= 0) {
return null;
}
const matchedObjects: string[] = [];
const availableEntitySets: string[] = [];
for (const row of result.rows) {
const objectName = metadataObjectName(row);
if (objectName) {
pushUnique(matchedObjects, objectName);
}
const entitySet = metadataEntitySet(row);
if (entitySet) {
pushUnique(availableEntitySets, entitySet);
}
}
return {
metadata_scope: metadataScope,
requested_meta_types: requestedMetaTypes,
matched_rows: result.rows.length,
available_entity_sets: availableEntitySets,
matched_objects: matchedObjects,
available_fields: metadataAvailableFields(result.rows),
known_limitations: [],
inference_basis: "confirmed_1c_metadata_surface_rows"
};
}
function buildMetadataConfirmedFacts(
surface: AssistantMcpDiscoveryDerivedMetadataSurface | null
): string[] {
if (!surface) {
return [];
}
const facts: string[] = [];
const scopeSuffix = surface.metadata_scope ? ` for ${surface.metadata_scope}` : "";
facts.push(
`Confirmed 1C metadata surface${scopeSuffix}: ${surface.matched_rows} rows and ${surface.matched_objects.length} matching objects`
);
if (surface.available_entity_sets.length > 0) {
facts.push(`Available metadata object sets: ${surface.available_entity_sets.join(", ")}`);
}
if (surface.available_fields.length > 0) {
facts.push(`Available metadata fields/sections: ${surface.available_fields.slice(0, 12).join(", ")}`);
}
return facts;
}
function buildMetadataUnknownFacts(
surface: AssistantMcpDiscoveryDerivedMetadataSurface | null,
metadataScope: string | null
): string[] {
if (surface) {
if (surface.available_fields.length > 0) {
return [];
}
return ["Detailed metadata fields were not returned by this MCP metadata probe"];
}
if (metadataScope) {
return [`No matching 1C metadata objects were confirmed for scope "${metadataScope}"`];
}
return ["No matching 1C metadata objects were confirmed by this MCP metadata probe"];
}
function rowDateValue(row: Record<string, unknown>): string | null {
const candidates = [
row["Период"],
@@ -1072,16 +1321,31 @@ function buildEmptyEvidence(
});
}
function pilotScopeForPlanner(planner: AssistantMcpDiscoveryPlannerContract): AssistantMcpDiscoveryPilotScope {
if (isMetadataPilotEligible(planner)) {
return "metadata_inspection_v1";
}
if (isValueFlowPilotEligible(planner)) {
return valueFlowPilotProfile(planner).scope;
}
return "counterparty_lifecycle_query_documents_v1";
}
export async function executeAssistantMcpDiscoveryPilot(
planner: AssistantMcpDiscoveryPlannerContract,
deps: AssistantMcpDiscoveryPilotExecutorDeps = DEFAULT_DEPS
): Promise<AssistantMcpDiscoveryPilotExecutionContract> {
const runtimeDeps: ResolvedAssistantMcpDiscoveryPilotExecutorDeps = {
...DEFAULT_DEPS,
...deps
};
const dryRun = buildAssistantMcpDiscoveryRuntimeDryRun(planner);
const reasonCodes = [...dryRun.reason_codes];
const executedPrimitives: string[] = [];
const skippedPrimitives: string[] = [];
const probeResults: AssistantMcpDiscoveryProbeResult[] = [];
const queryLimitations: string[] = [];
const pilotScope = pilotScopeForPlanner(planner);
if (dryRun.adapter_status === "blocked") {
pushReason(reasonCodes, "pilot_blocked_before_mcp_execution");
@@ -1090,7 +1354,7 @@ export async function executeAssistantMcpDiscoveryPilot(
schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryPilotExecutor",
pilot_status: "blocked",
pilot_scope: "counterparty_lifecycle_query_documents_v1",
pilot_scope: pilotScope,
dry_run: dryRun,
mcp_execution_performed: false,
executed_primitives: executedPrimitives,
@@ -1098,6 +1362,7 @@ export async function executeAssistantMcpDiscoveryPilot(
probe_results: probeResults,
evidence,
source_rows_summary: null,
derived_metadata_surface: null,
derived_activity_period: null,
derived_value_flow: null,
derived_bidirectional_value_flow: null,
@@ -1113,7 +1378,7 @@ export async function executeAssistantMcpDiscoveryPilot(
schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryPilotExecutor",
pilot_status: "skipped_needs_clarification",
pilot_scope: "counterparty_lifecycle_query_documents_v1",
pilot_scope: pilotScope,
dry_run: dryRun,
mcp_execution_performed: false,
executed_primitives: executedPrimitives,
@@ -1121,6 +1386,7 @@ export async function executeAssistantMcpDiscoveryPilot(
probe_results: probeResults,
evidence,
source_rows_summary: null,
derived_metadata_surface: null,
derived_activity_period: null,
derived_value_flow: null,
derived_bidirectional_value_flow: null,
@@ -1129,10 +1395,11 @@ export async function executeAssistantMcpDiscoveryPilot(
};
}
const metadataPilotEligible = isMetadataPilotEligible(planner);
const lifecyclePilotEligible = isLifecyclePilotEligible(planner);
const valueFlowPilotEligible = isValueFlowPilotEligible(planner);
if (!lifecyclePilotEligible && !valueFlowPilotEligible) {
if (!metadataPilotEligible && !lifecyclePilotEligible && !valueFlowPilotEligible) {
pushReason(reasonCodes, "pilot_scope_unsupported_for_live_execution");
for (const step of dryRun.execution_steps) {
skippedPrimitives.push(step.primitive_id);
@@ -1143,7 +1410,7 @@ export async function executeAssistantMcpDiscoveryPilot(
schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryPilotExecutor",
pilot_status: "unsupported",
pilot_scope: "counterparty_lifecycle_query_documents_v1",
pilot_scope: pilotScope,
dry_run: dryRun,
mcp_execution_performed: false,
executed_primitives: executedPrimitives,
@@ -1151,6 +1418,7 @@ export async function executeAssistantMcpDiscoveryPilot(
probe_results: probeResults,
evidence,
source_rows_summary: null,
derived_metadata_surface: null,
derived_activity_period: null,
derived_value_flow: null,
derived_bidirectional_value_flow: null,
@@ -1163,6 +1431,68 @@ export async function executeAssistantMcpDiscoveryPilot(
const dateScope = toNonEmptyString(planner.discovery_plan.turn_meaning_ref?.explicit_date_scope);
const aggregationAxis = aggregationAxisForPlanner(planner);
if (metadataPilotEligible) {
let metadataResult: AddressMcpMetadataRowsResult | null = null;
const metadataScope = metadataScopeForPlanner(planner);
const requestedMetaTypes = metadataTypesForPlanner(planner);
for (const step of dryRun.execution_steps) {
if (step.primitive_id !== "inspect_1c_metadata") {
skippedPrimitives.push(step.primitive_id);
probeResults.push(skippedProbeResult(step, "pilot_metadata_uses_only_inspect_1c_metadata"));
continue;
}
metadataResult = await runtimeDeps.executeAddressMcpMetadata({
meta_type: requestedMetaTypes,
name_mask: metadataScope ?? undefined,
limit: planner.discovery_plan.execution_budget.max_rows_per_probe
});
pushUnique(executedPrimitives, step.primitive_id);
probeResults.push(metadataResultToProbeResult(step.primitive_id, metadataResult));
if (metadataResult.error) {
pushUnique(queryLimitations, metadataResult.error);
pushReason(reasonCodes, "pilot_inspect_1c_metadata_mcp_error");
} else {
pushReason(reasonCodes, "pilot_inspect_1c_metadata_mcp_executed");
}
}
const sourceRowsSummary = metadataResult ? summarizeMetadataRows(metadataResult) : null;
const derivedMetadataSurface = deriveMetadataSurface(metadataResult, metadataScope, requestedMetaTypes);
if (derivedMetadataSurface) {
pushReason(reasonCodes, "pilot_derived_metadata_surface_from_confirmed_rows");
}
const evidence = resolveAssistantMcpDiscoveryEvidence({
plan: planner.discovery_plan,
probeResults,
confirmedFacts: buildMetadataConfirmedFacts(derivedMetadataSurface),
unknownFacts: buildMetadataUnknownFacts(derivedMetadataSurface, metadataScope),
sourceRowsSummary,
queryLimitations,
recommendedNextProbe: "inspect_1c_metadata"
});
return {
schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryPilotExecutor",
pilot_status: "executed",
pilot_scope: "metadata_inspection_v1",
dry_run: dryRun,
mcp_execution_performed: executedPrimitives.length > 0,
executed_primitives: executedPrimitives,
skipped_primitives: skippedPrimitives,
probe_results: probeResults,
evidence,
source_rows_summary: sourceRowsSummary,
derived_metadata_surface: derivedMetadataSurface,
derived_activity_period: null,
derived_value_flow: null,
derived_bidirectional_value_flow: null,
query_limitations: queryLimitations,
reason_codes: reasonCodes
};
}
if (valueFlowPilotEligible) {
let queryResult: AssistantMcpDiscoveryCoverageAwareQueryResult | null = null;
const filters = buildValueFlowFilters(planner);
@@ -1187,6 +1517,7 @@ export async function executeAssistantMcpDiscoveryPilot(
probe_results: probeResults,
evidence,
source_rows_summary: null,
derived_metadata_surface: null,
derived_activity_period: null,
derived_value_flow: null,
derived_bidirectional_value_flow: null,
@@ -1213,7 +1544,7 @@ export async function executeAssistantMcpDiscoveryPilot(
dateScope,
maxProbeCount: planner.discovery_plan.execution_budget.max_probe_count,
maxRowsPerProbe: planner.discovery_plan.execution_budget.max_rows_per_probe,
deps
deps: runtimeDeps
});
const outgoingExecution = await executeCoverageAwareValueFlowQuery({
primitiveId: step.primitive_id,
@@ -1222,7 +1553,7 @@ export async function executeAssistantMcpDiscoveryPilot(
dateScope,
maxProbeCount: planner.discovery_plan.execution_budget.max_probe_count,
maxRowsPerProbe: planner.discovery_plan.execution_budget.max_rows_per_probe,
deps
deps: runtimeDeps
});
incomingResult = incomingExecution.result;
outgoingResult = outgoingExecution.result;
@@ -1285,6 +1616,7 @@ export async function executeAssistantMcpDiscoveryPilot(
probe_results: probeResults,
evidence,
source_rows_summary: sourceRowsSummary,
derived_metadata_surface: null,
derived_activity_period: null,
derived_value_flow: null,
derived_bidirectional_value_flow: derivedBidirectionalValueFlow,
@@ -1310,6 +1642,7 @@ export async function executeAssistantMcpDiscoveryPilot(
probe_results: probeResults,
evidence,
source_rows_summary: null,
derived_metadata_surface: null,
derived_activity_period: null,
derived_value_flow: null,
derived_bidirectional_value_flow: null,
@@ -1337,7 +1670,7 @@ export async function executeAssistantMcpDiscoveryPilot(
dateScope,
maxProbeCount: planner.discovery_plan.execution_budget.max_probe_count,
maxRowsPerProbe: planner.discovery_plan.execution_budget.max_rows_per_probe,
deps
deps: runtimeDeps
});
queryResult = execution.result;
pushUnique(executedPrimitives, step.primitive_id);
@@ -1392,6 +1725,7 @@ export async function executeAssistantMcpDiscoveryPilot(
probe_results: probeResults,
evidence,
source_rows_summary: sourceRowsSummary,
derived_metadata_surface: null,
derived_activity_period: null,
derived_value_flow: derivedValueFlow,
derived_bidirectional_value_flow: null,
@@ -1418,6 +1752,7 @@ export async function executeAssistantMcpDiscoveryPilot(
probe_results: probeResults,
evidence,
source_rows_summary: null,
derived_metadata_surface: null,
derived_activity_period: null,
derived_value_flow: null,
derived_bidirectional_value_flow: null,
@@ -1433,7 +1768,7 @@ export async function executeAssistantMcpDiscoveryPilot(
probeResults.push(skippedProbeResult(step, "pilot_only_executes_query_documents"));
continue;
}
queryResult = await deps.executeAddressMcpQuery({
queryResult = await runtimeDeps.executeAddressMcpQuery({
query: recipePlan.query,
limit: recipePlan.limit,
account_scope: recipePlan.account_scope
@@ -1476,6 +1811,7 @@ export async function executeAssistantMcpDiscoveryPilot(
probe_results: probeResults,
evidence,
source_rows_summary: sourceRowsSummary,
derived_metadata_surface: null,
derived_activity_period: derivedActivityPeriod,
derived_value_flow: null,
derived_bidirectional_value_flow: null,