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

This commit is contained in:
2026-04-20 18:42:58 +03:00
parent 49fd08652c
commit 52da709671
20 changed files with 688 additions and 48 deletions
@@ -10,6 +10,7 @@ import {
import { applyAssistantCapabilityBindingResponseGuard } from "./assistantCapabilityBindingResponseGuard";
import { attachAssistantCapabilityRuntimeBinding } from "./assistantCapabilityRuntimeBindingAdapter";
import { attachAssistantMcpDiscoveryDebug } from "./assistantMcpDiscoveryDebugAttachment";
import { applyAssistantMcpDiscoveryResponsePolicy } from "./assistantMcpDiscoveryResponsePolicy";
import { attachAssistantRuntimeContractShadow } from "./assistantRuntimeContractResolver";
import { attachAssistantStateTransition } from "./assistantStateTransitionRuntimeAdapter";
import { attachAssistantTruthAnswerPolicy } from "./assistantTruthAnswerPolicyRuntimeAdapter";
@@ -280,14 +281,29 @@ export function runAssistantAddressLaneResponseRuntime<ResponseType = AssistantM
...debugWithMcpDiscovery,
capability_binding_response_guard: guardedResponse.audit
};
const mcpDiscoveryResponsePolicy = applyAssistantMcpDiscoveryResponsePolicy({
currentReply: guardedResponse.assistantReply,
currentReplySource: "address_query_runtime_v1",
addressRuntimeMeta: debugWithResponseGuard
});
const finalAssistantReply = mcpDiscoveryResponsePolicy.applied
? mcpDiscoveryResponsePolicy.reply_text
: guardedResponse.assistantReply;
const finalReplyType = mcpDiscoveryResponsePolicy.applied ? "partial_coverage" : guardedResponse.replyType;
const finalDebug = {
...debugWithResponseGuard,
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied
};
const finalization = finalizeAddressTurnSafe({
sessionId: input.sessionId,
userMessage: input.userMessage,
effectiveAddressUserMessage: input.effectiveAddressUserMessage,
assistantReply: guardedResponse.assistantReply,
replyType: guardedResponse.replyType,
assistantReply: finalAssistantReply,
replyType: finalReplyType,
addressLaneDebug: normalizeAddressLaneDebug(input.addressLane.debug),
debug: debugWithResponseGuard,
debug: finalDebug,
carryoverMeta: normalizeCarryoverMeta(input.carryoverMeta),
llmPreDecomposeMeta: normalizeLlmPreDecomposeMeta(input.llmPreDecomposeMeta),
appendItem: input.appendItem,
@@ -300,6 +316,6 @@ export function runAssistantAddressLaneResponseRuntime<ResponseType = AssistantM
return {
response: finalization.response as ResponseType,
debug: debugWithResponseGuard
debug: finalDebug
};
}
@@ -89,8 +89,18 @@ function modeFor(pilot: AssistantMcpDiscoveryPilotExecutionContract): AssistantM
return "checked_sources_only";
}
function isValueFlowPilot(pilot: AssistantMcpDiscoveryPilotExecutionContract): boolean {
return (
pilot.pilot_scope === "counterparty_value_flow_query_movements_v1" ||
pilot.pilot_scope === "counterparty_supplier_payout_query_movements_v1"
);
}
function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpDiscoveryPilotExecutionContract): string {
if (pilot.derived_value_flow && mode === "confirmed_with_bounded_inference") {
if (pilot.derived_value_flow.value_flow_direction === "outgoing_supplier_payout") {
return "По данным 1С найдены строки исходящих платежей/списаний; сумму можно называть только в рамках проверенного периода и найденных строк.";
}
return "По данным 1С найдены строки денежных движений; сумму можно называть только в рамках проверенного периода и найденных строк.";
}
if (mode === "confirmed_with_bounded_inference") {
@@ -130,7 +140,7 @@ function buildMustNotClaim(pilot: AssistantMcpDiscoveryPilotExecutionContract):
claims.push("Do not claim legal registration age unless a legal registration source is confirmed.");
claims.push("Do not present inferred activity duration as a formally confirmed legal fact.");
}
if (pilot.pilot_scope === "counterparty_value_flow_query_movements_v1") {
if (isValueFlowPilot(pilot)) {
claims.push("Do not claim full all-time turnover unless the checked period and coverage prove it.");
claims.push("Do not present a derived sum as a legal/accounting final total outside the checked 1C rows.");
}
@@ -159,11 +169,24 @@ function derivedValueFlowConfirmedLine(pilot: AssistantMcpDiscoveryPilotExecutio
}
const counterparty = flow.counterparty ? ` по контрагенту ${flow.counterparty}` : "";
const period = flow.period_scope ? ` за период ${flow.period_scope}` : " в проверенном окне";
const movementLabel =
flow.value_flow_direction === "outgoing_supplier_payout" ? "исходящих платежей/списаний" : "денежных движений";
const totalLabel =
flow.value_flow_direction === "outgoing_supplier_payout"
? "сумма исходящих платежей/списаний составляет"
: "сумма составляет";
const caveat =
flow.value_flow_direction === "outgoing_supplier_payout"
? "Это расчет по найденным строкам 1С, а не подтверждение полного объема платежей вне проверенного окна."
: "Это расчет по найденным строкам 1С, а не подтверждение полного оборота вне проверенного окна.";
const dates =
flow.first_movement_date && flow.latest_movement_date
? ` Первая найденная дата движения: ${flow.first_movement_date}; последняя: ${flow.latest_movement_date}.`
: "";
return `По найденным строкам денежных движений в 1С${counterparty}${period} сумма составляет ${flow.total_amount_human_ru} Учтено строк с суммой: ${flow.rows_with_amount} из ${flow.rows_matched}.${dates} Это расчет по найденным строкам 1С, а не подтверждение полного оборота вне проверенного окна.`;
const limitCaveat = flow.coverage_limited_by_probe_limit
? " Лимит строк проверки достигнут; полный запрошенный период может быть покрыт не полностью."
: "";
return `По найденным строкам ${movementLabel} в 1С${counterparty}${period} ${totalLabel} ${flow.total_amount_human_ru} Учтено строк с суммой: ${flow.rows_with_amount} из ${flow.rows_matched}.${dates}${limitCaveat} ${caveat}`;
}
export function buildAssistantMcpDiscoveryAnswerDraft(
@@ -14,7 +14,7 @@ import {
type AssistantMcpDiscoveryProbeResult
} from "./assistantMcpDiscoveryPolicy";
import { buildAddressRecipePlan, selectAddressRecipe } from "./addressRecipeCatalog";
import type { AddressFilterSet } from "../types/addressQuery";
import type { AddressFilterSet, AddressIntent } from "../types/addressQuery";
export const ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION =
"assistant_mcp_discovery_pilot_executor_v1" as const;
@@ -41,6 +41,7 @@ export interface AssistantMcpDiscoveryDerivedActivityPeriod {
}
export interface AssistantMcpDiscoveryDerivedValueFlow {
value_flow_direction: "incoming_customer_revenue" | "outgoing_supplier_payout";
counterparty: string | null;
period_scope: string | null;
rows_matched: number;
@@ -49,12 +50,14 @@ export interface AssistantMcpDiscoveryDerivedValueFlow {
total_amount_human_ru: string;
first_movement_date: string | null;
latest_movement_date: string | null;
coverage_limited_by_probe_limit: boolean;
inference_basis: "sum_of_confirmed_1c_value_flow_rows";
}
export type AssistantMcpDiscoveryPilotScope =
| "counterparty_lifecycle_query_documents_v1"
| "counterparty_value_flow_query_movements_v1";
| "counterparty_value_flow_query_movements_v1"
| "counterparty_supplier_payout_query_movements_v1";
export interface AssistantMcpDiscoveryPilotExecutionContract {
schema_version: typeof ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION;
@@ -199,6 +202,39 @@ function isValueFlowPilotEligible(planner: AssistantMcpDiscoveryPlannerContract)
);
}
interface ValueFlowPilotProfile {
scope: Extract<
AssistantMcpDiscoveryPilotScope,
"counterparty_value_flow_query_movements_v1" | "counterparty_supplier_payout_query_movements_v1"
>;
recipe_intent: Extract<AddressIntent, "customer_revenue_and_payments" | "supplier_payouts_profile">;
direction: AssistantMcpDiscoveryDerivedValueFlow["value_flow_direction"];
}
function valueFlowPilotProfile(planner: AssistantMcpDiscoveryPlannerContract): ValueFlowPilotProfile {
const meaning = planner.discovery_plan.turn_meaning_ref;
const action = String(meaning?.asked_action_family ?? "").toLowerCase();
const unsupported = String(meaning?.unsupported_but_understood_family ?? "").toLowerCase();
const combined = `${action} ${unsupported}`;
if (
combined.includes("payout") ||
combined.includes("outflow") ||
combined.includes("supplier") ||
combined.includes("paid")
) {
return {
scope: "counterparty_supplier_payout_query_movements_v1",
recipe_intent: "supplier_payouts_profile",
direction: "outgoing_supplier_payout"
};
}
return {
scope: "counterparty_value_flow_query_movements_v1",
recipe_intent: "customer_revenue_and_payments",
direction: "incoming_customer_revenue"
};
}
function skippedProbeResult(step: AssistantMcpDiscoveryRuntimeStepContract, limitation: string): AssistantMcpDiscoveryProbeResult {
return {
primitive_id: step.primitive_id,
@@ -361,7 +397,9 @@ function formatAmountHumanRu(amount: number): string {
function deriveValueFlow(
result: AddressMcpQueryExecutorResult | null,
counterparty: string | null,
periodScope: string | null
periodScope: string | null,
direction: AssistantMcpDiscoveryDerivedValueFlow["value_flow_direction"],
probeLimit: number
): AssistantMcpDiscoveryDerivedValueFlow | null {
if (!result || result.error || result.matched_rows <= 0) {
return null;
@@ -383,6 +421,7 @@ function deriveValueFlow(
.filter((value): value is string => Boolean(value))
.sort();
return {
value_flow_direction: direction,
counterparty,
period_scope: periodScope,
rows_matched: result.matched_rows,
@@ -391,6 +430,7 @@ function deriveValueFlow(
total_amount_human_ru: formatAmountHumanRu(totalAmount),
first_movement_date: dates[0] ?? null,
latest_movement_date: dates[dates.length - 1] ?? null,
coverage_limited_by_probe_limit: result.matched_rows >= probeLimit,
inference_basis: "sum_of_confirmed_1c_value_flow_rows"
};
}
@@ -406,10 +446,21 @@ function buildLifecycleConfirmedFacts(result: AddressMcpQueryExecutorResult, cou
];
}
function buildValueFlowConfirmedFacts(result: AddressMcpQueryExecutorResult, counterparty: string | null): string[] {
function buildValueFlowConfirmedFacts(
result: AddressMcpQueryExecutorResult,
counterparty: string | null,
direction: AssistantMcpDiscoveryDerivedValueFlow["value_flow_direction"]
): string[] {
if (result.error || result.matched_rows <= 0) {
return [];
}
if (direction === "outgoing_supplier_payout") {
return [
counterparty
? `1C supplier-payout rows were found for counterparty ${counterparty}`
: "1C supplier-payout rows were found for the requested counterparty scope"
];
}
return [
counterparty
? `1C value-flow rows were found for counterparty ${counterparty}`
@@ -428,6 +479,9 @@ function buildValueFlowInferredFacts(derived: AssistantMcpDiscoveryDerivedValueF
if (!derived) {
return [];
}
if (derived.value_flow_direction === "outgoing_supplier_payout") {
return ["Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows"];
}
return ["Counterparty value-flow total was calculated from confirmed 1C movement rows"];
}
@@ -435,12 +489,29 @@ function buildLifecycleUnknownFacts(): string[] {
return ["Legal registration date is not proven by this MCP discovery pilot"];
}
function buildValueFlowUnknownFacts(periodScope: string | null): string[] {
return [
function buildValueFlowUnknownFacts(
periodScope: string | null,
direction: AssistantMcpDiscoveryDerivedValueFlow["value_flow_direction"],
derived: AssistantMcpDiscoveryDerivedValueFlow | null
): string[] {
const unknownFacts: string[] = [];
if (derived?.coverage_limited_by_probe_limit) {
unknownFacts.push("Complete requested-period coverage is not proven because the MCP discovery probe row limit was reached");
}
if (direction === "outgoing_supplier_payout") {
unknownFacts.push(
periodScope
? "Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot"
: "Full all-time supplier-payout amount is not proven without an explicit checked period"
);
return unknownFacts;
}
unknownFacts.push(
periodScope
? "Full turnover outside the checked period is not proven by this MCP discovery pilot"
: "Full all-time turnover is not proven without an explicit checked period"
];
);
return unknownFacts;
}
function buildEmptyEvidence(
@@ -548,7 +619,8 @@ export async function executeAssistantMcpDiscoveryPilot(
if (valueFlowPilotEligible) {
let queryResult: AddressMcpQueryExecutorResult | null = null;
const filters = buildValueFlowFilters(planner);
const selection = selectAddressRecipe("customer_revenue_and_payments", filters);
const valueFlowProfile = valueFlowPilotProfile(planner);
const selection = selectAddressRecipe(valueFlowProfile.recipe_intent, filters);
if (!selection.selected_recipe) {
pushReason(reasonCodes, "pilot_value_flow_recipe_not_available");
const evidence = buildEmptyEvidence(planner, dryRun, probeResults, "Value-flow recipe is not available");
@@ -556,7 +628,7 @@ export async function executeAssistantMcpDiscoveryPilot(
schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryPilotExecutor",
pilot_status: "unsupported",
pilot_scope: "counterparty_value_flow_query_movements_v1",
pilot_scope: valueFlowProfile.scope,
dry_run: dryRun,
mcp_execution_performed: false,
executed_primitives: executedPrimitives,
@@ -570,6 +642,12 @@ export async function executeAssistantMcpDiscoveryPilot(
reason_codes: reasonCodes
};
}
pushReason(
reasonCodes,
valueFlowProfile.direction === "outgoing_supplier_payout"
? "pilot_supplier_payout_recipe_selected"
: "pilot_customer_revenue_recipe_selected"
);
const recipePlan = buildAddressRecipePlan(selection.selected_recipe, filters);
for (const step of dryRun.execution_steps) {
@@ -594,16 +672,22 @@ export async function executeAssistantMcpDiscoveryPilot(
}
const sourceRowsSummary = queryResult ? summarizeValueFlowRows(queryResult) : null;
const derivedValueFlow = deriveValueFlow(queryResult, counterparty, dateScope);
const derivedValueFlow = deriveValueFlow(
queryResult,
counterparty,
dateScope,
valueFlowProfile.direction,
planner.discovery_plan.execution_budget.max_rows_per_probe
);
if (derivedValueFlow) {
pushReason(reasonCodes, "pilot_derived_value_flow_from_confirmed_rows");
}
const evidence = resolveAssistantMcpDiscoveryEvidence({
plan: planner.discovery_plan,
probeResults,
confirmedFacts: queryResult ? buildValueFlowConfirmedFacts(queryResult, counterparty) : [],
confirmedFacts: queryResult ? buildValueFlowConfirmedFacts(queryResult, counterparty, valueFlowProfile.direction) : [],
inferredFacts: buildValueFlowInferredFacts(derivedValueFlow),
unknownFacts: buildValueFlowUnknownFacts(dateScope),
unknownFacts: buildValueFlowUnknownFacts(dateScope, valueFlowProfile.direction, derivedValueFlow),
sourceRowsSummary,
queryLimitations,
recommendedNextProbe: "explain_evidence_basis"
@@ -613,7 +697,7 @@ export async function executeAssistantMcpDiscoveryPilot(
schema_version: ASSISTANT_MCP_DISCOVERY_PILOT_EXECUTOR_SCHEMA_VERSION,
policy_owner: "assistantMcpDiscoveryPilotExecutor",
pilot_status: "executed",
pilot_scope: "counterparty_value_flow_query_movements_v1",
pilot_scope: valueFlowProfile.scope,
dry_run: dryRun,
mcp_execution_performed: executedPrimitives.length > 0,
executed_primitives: executedPrimitives,
@@ -105,21 +105,40 @@ function localizeLine(value: string): string {
if (/^1C value-flow rows were found for the requested counterparty scope$/i.test(value)) {
return "В 1С найдены строки денежных движений по запрошенному контрагентскому контуру.";
}
const supplierPayoutMatch = value.match(/^1C supplier-payout rows were found for counterparty\s+(.+)$/i);
if (supplierPayoutMatch) {
return `В 1С найдены строки исходящих платежей/списаний по контрагенту ${supplierPayoutMatch[1]}.`;
}
if (/^1C supplier-payout rows were found for the requested counterparty scope$/i.test(value)) {
return "В 1С найдены строки исходящих платежей/списаний по запрошенному контрагентскому контуру.";
}
if (/^Business activity duration may be inferred from first and latest confirmed 1C activity rows$/i.test(value)) {
return "Длительность деловой активности можно оценивать только как вывод по первой и последней подтвержденной строке активности в 1С.";
}
if (/^Counterparty value-flow total was calculated from confirmed 1C movement rows$/i.test(value)) {
return "Сумма рассчитана только по подтвержденным строкам денежных движений в 1С.";
}
if (/^Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows$/i.test(value)) {
return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С.";
}
if (/^Legal registration date is not proven by this MCP discovery pilot$/i.test(value)) {
return "Юридическая дата регистрации этим поиском не подтверждена.";
}
if (/^Complete requested-period coverage is not proven because the MCP discovery probe row limit was reached$/i.test(value)) {
return "Полное покрытие запрошенного периода не подтверждено: проверка достигла лимита найденных строк.";
}
if (/^Full turnover outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
return "Полный оборот вне проверенного периода этим поиском не подтвержден.";
}
if (/^Full all-time turnover is not proven without an explicit checked period$/i.test(value)) {
return "Полный оборот за все время без явно проверенного периода не подтвержден.";
}
if (/^Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot$/i.test(value)) {
return "Полный объем исходящих платежей вне проверенного периода этим поиском не подтвержден.";
}
if (/^Full all-time supplier-payout amount is not proven without an explicit checked period$/i.test(value)) {
return "Полный объем исходящих платежей за все время без явно проверенного периода не подтвержден.";
}
return value;
}
@@ -137,6 +137,20 @@ function isDiscoveryReadyDeepCandidate(
);
}
function isDiscoveryReadyAddressCandidate(
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
): boolean {
const turnInput = toRecordObject(entryPoint?.turn_input);
const source = String(input.currentReplySource ?? input.livingChatSource ?? "").trim().toLowerCase();
return (
entryPoint?.entry_status === "bridge_executed" &&
entryPoint.discovery_attempted === true &&
turnInput?.should_run_discovery === true &&
(source === "address_lane" || source === "address_exact" || source === "address_query_runtime_v1")
);
}
export function applyAssistantMcpDiscoveryResponsePolicy(
input: ApplyAssistantMcpDiscoveryResponsePolicyInput
): AssistantMcpDiscoveryResponsePolicyResult {
@@ -149,6 +163,7 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
const unsupportedBoundary = isUnsupportedCurrentTurnBoundary(input);
const discoveryReadyChatCandidate = isDiscoveryReadyChatCandidate(input, entryPoint);
const discoveryReadyDeepCandidate = isDiscoveryReadyDeepCandidate(input, entryPoint);
const discoveryReadyAddressCandidate = isDiscoveryReadyAddressCandidate(input, entryPoint);
if (!entryPoint) {
pushReason(reasonCodes, "mcp_discovery_response_policy_no_entry_point");
@@ -162,6 +177,9 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
if (!discoveryReadyDeepCandidate) {
pushReason(reasonCodes, "mcp_discovery_response_policy_not_discovery_ready_deep_candidate");
}
if (!discoveryReadyAddressCandidate) {
pushReason(reasonCodes, "mcp_discovery_response_policy_not_discovery_ready_address_candidate");
}
if (!ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status)) {
pushReason(reasonCodes, "mcp_discovery_response_policy_candidate_status_not_allowed");
}
@@ -177,7 +195,7 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
const canApply =
Boolean(entryPoint) &&
(unsupportedBoundary || discoveryReadyChatCandidate || discoveryReadyDeepCandidate) &&
(unsupportedBoundary || discoveryReadyChatCandidate || discoveryReadyDeepCandidate || discoveryReadyAddressCandidate) &&
ALLOWED_CANDIDATE_STATUSES.has(candidate.candidate_status) &&
candidate.eligible_for_future_hot_runtime &&
Boolean(toNonEmptyString(candidate.reply_text)) &&
@@ -139,7 +139,13 @@ function hasLifecycleSignal(text: string): boolean {
}
function hasValueFlowSignal(text: string): boolean {
return /(?:оборот|выручк|оплат|плат[её]ж|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|value[-\s]?flow|turnover|revenue|payment|payout|cash\s+flow)/iu.test(
return /(?:оборот|выручк|оплат|плат[её]ж|заплат|перечисл|списан|расход|исходящ|поступлен|денежн[а-яёa-z0-9_-]*\s+поток|supplier|value[-\s]?flow|turnover|revenue|payment|payout|outflow|cash\s+flow)/iu.test(
text
);
}
function hasPayoutSignal(text: string): boolean {
return /(?:\bмы\s+(?:за)?плат|(?:за)?платил|оплатил|перечисл|списан|расход|поставщик|исходящ|supplier|payout|outflow|paid\s+to|payment\s+to)/iu.test(
text
);
}
@@ -196,6 +202,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
const rawText = compactLower(`${input.userMessage ?? ""} ${input.effectiveMessage ?? ""}`);
const lifecycleSignal = hasLifecycleSignal(rawText);
const valueFlowSignal = !lifecycleSignal && hasValueFlowSignal(rawText);
const payoutSignal = valueFlowSignal && hasPayoutSignal(rawText);
const rawDomain = toNonEmptyString(assistantTurnMeaning?.asked_domain_family);
const rawAction = toNonEmptyString(assistantTurnMeaning?.asked_action_family);
@@ -218,11 +225,12 @@ export function buildAssistantMcpDiscoveryTurnInput(
const turnMeaning: AssistantMcpDiscoveryTurnMeaningRef = {
asked_domain_family: lifecycleSignal ? "counterparty_lifecycle" : valueFlowSignal ? "counterparty_value" : rawDomain,
asked_action_family: lifecycleSignal ? "activity_duration" : valueFlowSignal ? "turnover" : rawAction,
asked_action_family: lifecycleSignal ? "activity_duration" : valueFlowSignal ? (payoutSignal ? "payout" : "turnover") : rawAction,
explicit_entity_candidates: entityCandidates,
explicit_organization_scope: explicitOrganizationScope,
explicit_date_scope: collectDateScope(predecomposeContract),
unsupported_but_understood_family: unsupported ?? (lifecycleSignal ? "counterparty_lifecycle" : valueFlowSignal ? "counterparty_value_or_turnover" : null),
unsupported_but_understood_family:
unsupported ?? (lifecycleSignal ? "counterparty_lifecycle" : valueFlowSignal ? (payoutSignal ? "counterparty_payouts_or_outflow" : "counterparty_value_or_turnover") : null),
stale_replay_forbidden: Boolean(assistantTurnMeaning?.stale_replay_forbidden || unsupported || lifecycleSignal || valueFlowSignal)
};
@@ -273,6 +281,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
if (valueFlowSignal) {
pushReason(reasonCodes, "mcp_discovery_value_flow_signal_detected");
}
if (payoutSignal) {
pushReason(reasonCodes, "mcp_discovery_payout_signal_detected");
}
if (unsupported) {
pushReason(reasonCodes, "mcp_discovery_unsupported_but_understood_turn");
}