Добавить handoff допуска для автономного value-flow

This commit is contained in:
2026-05-22 19:11:16 +03:00
parent d64d7f84cc
commit 5125c747d8
8 changed files with 345 additions and 14 deletions
@@ -1,5 +1,6 @@
import type { AssistantMcpDiscoveryRuntimeEntryPointContract } from "./assistantMcpDiscoveryRuntimeEntryPoint";
import type { AssistantMcpRouteCandidateContract } from "./assistantMcpDiscoveryRuntimeBridge";
import type { AssistantMcpDiscoveryExecutionHandoffContract } from "./assistantMcpDiscoveryExecutionHandoff";
import type { AssistantEvidencePlannerContract } from "./assistantEvidencePlanner";
export interface AssistantMcpDiscoveryDebugAttachmentFields {
@@ -31,6 +32,10 @@ export interface AssistantMcpDiscoveryDebugAttachmentFields {
mcp_discovery_route_candidate_executable_now: boolean;
mcp_discovery_route_candidate_enablement_reason: string | null;
mcp_discovery_route_candidate_next_action: string | null;
mcp_discovery_execution_handoff_v1: AssistantMcpDiscoveryExecutionHandoffContract | null;
mcp_discovery_execution_handoff_status: string | null;
mcp_discovery_execution_handoff_allowed_hot_chain: boolean;
mcp_discovery_execution_handoff_can_use_guarded_response: boolean;
mcp_discovery_answer_mode: string | null;
mcp_discovery_business_fact_answer_allowed: boolean;
mcp_discovery_user_facing_response_allowed: boolean;
@@ -92,6 +97,14 @@ function isEvidencePlannerContract(value: unknown): value is AssistantEvidencePl
return record?.schema_version === "assistant_evidence_planner_v1" && record?.policy_owner === "assistantEvidencePlanner";
}
function isExecutionHandoffContract(value: unknown): value is AssistantMcpDiscoveryExecutionHandoffContract {
const record = toRecordObject(value);
return (
record?.schema_version === "assistant_mcp_discovery_execution_handoff_v1" &&
record?.policy_owner === "assistantMcpDiscoveryExecutionHandoff"
);
}
function resolveEntryPoint(input: AttachAssistantMcpDiscoveryDebugInput): AssistantMcpDiscoveryRuntimeEntryPointContract | null {
if (isMcpDiscoveryEntryPointContract(input.entryPoint)) {
return input.entryPoint;
@@ -115,6 +128,7 @@ export function buildAssistantMcpDiscoveryDebugAttachmentFields(
const evidencePlanAnswerContract = toRecordObject(evidencePlan?.answer_contract);
const chainAlignment = toRecordObject(planner?.catalog_chain_template_alignment);
const routeCandidate = isRouteCandidateContract(bridge?.route_candidate) ? bridge.route_candidate : null;
const executionHandoff = isExecutionHandoffContract(bridge?.execution_handoff) ? bridge.execution_handoff : null;
const answerDraft = toRecordObject(bridge?.answer_draft);
return {
@@ -146,6 +160,10 @@ export function buildAssistantMcpDiscoveryDebugAttachmentFields(
mcp_discovery_route_candidate_executable_now: routeCandidate?.executable_now === true,
mcp_discovery_route_candidate_enablement_reason: toNonEmptyString(routeCandidate?.enablement_reason),
mcp_discovery_route_candidate_next_action: toNonEmptyString(routeCandidate?.recommended_next_action),
mcp_discovery_execution_handoff_v1: executionHandoff,
mcp_discovery_execution_handoff_status: toNonEmptyString(executionHandoff?.handoff_status),
mcp_discovery_execution_handoff_allowed_hot_chain: executionHandoff?.allowed_hot_chain === true,
mcp_discovery_execution_handoff_can_use_guarded_response: executionHandoff?.can_use_guarded_response === true,
mcp_discovery_answer_mode: toNonEmptyString(answerDraft?.answer_mode),
mcp_discovery_business_fact_answer_allowed: bridge?.business_fact_answer_allowed === true,
mcp_discovery_user_facing_response_allowed: bridge?.user_facing_response_allowed === true,
@@ -0,0 +1,134 @@
import type { AssistantMcpDiscoveryAnswerDraftContract } from "./assistantMcpDiscoveryAnswerAdapter";
import type { AssistantMcpDiscoveryPilotExecutionContract } from "./assistantMcpDiscoveryPilotExecutor";
import type { AssistantMcpDiscoveryChainId } from "./assistantMcpDiscoveryPlanner";
import type {
AssistantMcpDiscoveryRuntimeBridgeStatus,
AssistantMcpRouteCandidateContract
} from "./assistantMcpDiscoveryRuntimeBridge";
export const ASSISTANT_MCP_DISCOVERY_EXECUTION_HANDOFF_SCHEMA_VERSION =
"assistant_mcp_discovery_execution_handoff_v1" as const;
export type AssistantMcpDiscoveryExecutionHandoffStatus =
| "ready_for_guarded_response"
| "awaiting_user_scope"
| "checked_sources_only"
| "not_enabled_for_chain"
| "blocked";
export interface AssistantMcpDiscoveryExecutionHandoffInput {
bridgeStatus: AssistantMcpDiscoveryRuntimeBridgeStatus;
routeCandidate: AssistantMcpRouteCandidateContract;
pilot: AssistantMcpDiscoveryPilotExecutionContract;
answerDraft: AssistantMcpDiscoveryAnswerDraftContract;
businessFactAnswerAllowed: boolean;
userFacingResponseAllowed: boolean;
}
export interface AssistantMcpDiscoveryExecutionHandoffContract {
schema_version: typeof ASSISTANT_MCP_DISCOVERY_EXECUTION_HANDOFF_SCHEMA_VERSION;
policy_owner: "assistantMcpDiscoveryExecutionHandoff";
handoff_status: AssistantMcpDiscoveryExecutionHandoffStatus;
selected_chain_id: AssistantMcpDiscoveryChainId;
route_candidate_status: AssistantMcpRouteCandidateContract["candidate_status"];
evidence_answer_mode: string | null;
evidence_expected_coverage: string | null;
pilot_status: AssistantMcpDiscoveryPilotExecutionContract["pilot_status"];
answer_mode: AssistantMcpDiscoveryAnswerDraftContract["answer_mode"];
mcp_execution_performed: boolean;
allowed_hot_chain: boolean;
can_use_guarded_response: boolean;
must_keep_internal_mechanics_hidden: true;
reason_codes: string[];
}
const HOT_HANDOFF_CHAIN_ALLOWLIST: AssistantMcpDiscoveryChainId[] = ["value_flow"];
function uniqueStrings(values: string[]): string[] {
const result: string[] = [];
for (const value of values) {
const text = String(value ?? "").trim();
if (text && !result.includes(text)) {
result.push(text);
}
}
return result;
}
function handoffStatusFor(
input: AssistantMcpDiscoveryExecutionHandoffInput,
allowedHotChain: boolean
): AssistantMcpDiscoveryExecutionHandoffStatus {
if (input.bridgeStatus === "needs_clarification" || input.routeCandidate.candidate_status === "needs_user_scope") {
return "awaiting_user_scope";
}
if (input.bridgeStatus === "checked_sources_only") {
return "checked_sources_only";
}
if (
input.bridgeStatus === "blocked" ||
input.bridgeStatus === "unsupported" ||
input.routeCandidate.candidate_status === "blocked" ||
input.routeCandidate.candidate_status === "needs_route_enablement"
) {
return "blocked";
}
if (!allowedHotChain) {
return "not_enabled_for_chain";
}
return "ready_for_guarded_response";
}
export function buildAssistantMcpDiscoveryExecutionHandoff(
input: AssistantMcpDiscoveryExecutionHandoffInput
): AssistantMcpDiscoveryExecutionHandoffContract {
const allowedHotChain = HOT_HANDOFF_CHAIN_ALLOWLIST.includes(input.routeCandidate.selected_chain_id);
const baseStatus = handoffStatusFor(input, allowedHotChain);
const readinessChecksPassed =
baseStatus === "ready_for_guarded_response" &&
input.bridgeStatus === "answer_draft_ready" &&
input.routeCandidate.candidate_status === "ready_for_reviewed_execution" &&
input.routeCandidate.executable_now === true &&
input.pilot.pilot_status === "executed" &&
input.pilot.mcp_execution_performed === true &&
input.businessFactAnswerAllowed === true &&
input.userFacingResponseAllowed === true &&
input.answerDraft.internal_mechanics_allowed === false;
const handoffStatus = readinessChecksPassed ? "ready_for_guarded_response" : baseStatus;
const reasonCodes = uniqueStrings([
`execution_handoff_status_${handoffStatus}`,
allowedHotChain ? "execution_handoff_chain_allowlisted" : "execution_handoff_chain_not_allowlisted",
input.routeCandidate.executable_now
? "execution_handoff_route_candidate_executable"
: "execution_handoff_route_candidate_not_executable",
input.pilot.mcp_execution_performed
? "execution_handoff_mcp_execution_performed"
: "execution_handoff_mcp_execution_not_performed",
input.businessFactAnswerAllowed
? "execution_handoff_business_fact_allowed"
: "execution_handoff_business_fact_not_allowed",
input.userFacingResponseAllowed
? "execution_handoff_user_facing_allowed"
: "execution_handoff_user_facing_not_allowed",
readinessChecksPassed
? "execution_handoff_guarded_response_ready"
: "execution_handoff_guarded_response_not_ready"
]);
return {
schema_version: ASSISTANT_MCP_DISCOVERY_EXECUTION_HANDOFF_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryExecutionHandoff",
handoff_status: handoffStatus,
selected_chain_id: input.routeCandidate.selected_chain_id,
route_candidate_status: input.routeCandidate.candidate_status,
evidence_answer_mode: input.routeCandidate.evidence_answer_mode,
evidence_expected_coverage: input.routeCandidate.evidence_expected_coverage,
pilot_status: input.pilot.pilot_status,
answer_mode: input.answerDraft.answer_mode,
mcp_execution_performed: input.pilot.mcp_execution_performed,
allowed_hot_chain: allowedHotChain,
can_use_guarded_response: readinessChecksPassed,
must_keep_internal_mechanics_hidden: true,
reason_codes: reasonCodes
};
}
@@ -7,6 +7,10 @@ import {
type AssistantMcpDiscoveryPilotExecutionContract,
type AssistantMcpDiscoveryPilotExecutorDeps
} from "./assistantMcpDiscoveryPilotExecutor";
import {
buildAssistantMcpDiscoveryExecutionHandoff,
type AssistantMcpDiscoveryExecutionHandoffContract
} from "./assistantMcpDiscoveryExecutionHandoff";
import {
planAssistantMcpDiscovery,
type AssistantMcpDiscoveryChainId,
@@ -101,6 +105,7 @@ export interface AssistantMcpDiscoveryRuntimeBridgeContract {
answer_draft: AssistantMcpDiscoveryAnswerDraftContract;
loop_state: AssistantMcpDiscoveryLoopStateContract;
route_candidate: AssistantMcpRouteCandidateContract;
execution_handoff: AssistantMcpDiscoveryExecutionHandoffContract;
user_facing_response_allowed: boolean;
business_fact_answer_allowed: boolean;
requires_user_clarification: boolean;
@@ -414,6 +419,16 @@ export async function runAssistantMcpDiscoveryRuntimeBridge(
const bridgeStatus = bridgeStatusFor(pilot, answerDraft);
const loopState = buildLoopState(planner, pilot, bridgeStatus);
const routeCandidate = buildRouteCandidate(planner, pilot, bridgeStatus);
const userFacingResponseAllowed = bridgeStatus !== "blocked";
const businessFactAllowed = businessFactAnswerAllowed(answerDraft);
const executionHandoff = buildAssistantMcpDiscoveryExecutionHandoff({
bridgeStatus,
routeCandidate,
pilot,
answerDraft,
businessFactAnswerAllowed: businessFactAllowed,
userFacingResponseAllowed
});
const reasonCodes = uniqueStrings([...planner.reason_codes, ...pilot.reason_codes, ...answerDraft.reason_codes]);
pushReason(reasonCodes, `runtime_bridge_status_${bridgeStatus}`);
@@ -421,6 +436,10 @@ export async function runAssistantMcpDiscoveryRuntimeBridge(
pushReason(reasonCodes, `runtime_bridge_loop_state_${loopState.loop_status}`);
pushReason(reasonCodes, "runtime_bridge_route_candidate_built");
pushReason(reasonCodes, `runtime_bridge_route_candidate_${routeCandidate.candidate_status}`);
pushReason(reasonCodes, `runtime_bridge_execution_handoff_${executionHandoff.handoff_status}`);
for (const reasonCode of executionHandoff.reason_codes) {
pushReason(reasonCodes, reasonCode);
}
return {
schema_version: ASSISTANT_MCP_DISCOVERY_RUNTIME_BRIDGE_SCHEMA_VERSION,
@@ -432,8 +451,9 @@ export async function runAssistantMcpDiscoveryRuntimeBridge(
answer_draft: answerDraft,
loop_state: loopState,
route_candidate: routeCandidate,
user_facing_response_allowed: bridgeStatus !== "blocked",
business_fact_answer_allowed: businessFactAnswerAllowed(answerDraft),
execution_handoff: executionHandoff,
user_facing_response_allowed: userFacingResponseAllowed,
business_fact_answer_allowed: businessFactAllowed,
requires_user_clarification: bridgeStatus === "needs_clarification",
reason_codes: reasonCodes
};