АРЧ АП11 - Commit title: Добавить контрактный слой переходов и capability-деклараций ассистента
This commit is contained in:
@@ -1040,12 +1040,78 @@ function trimInventoryItemArrowSuffix(rawValue) {
|
||||
return cleanupAnchorValue(cleanupAnchorValue(rawValue).replace(/\s*(?:->|=>|→).+$/u, ""));
|
||||
}
|
||||
function isTemporalWarehousePhrase(candidate) {
|
||||
const temporalZaPattern = /^(?:за)\s+(?:январ(?:е|ь)|феврал(?:е|ь)|март(?:е)?|апрел(?:е|ь)|ма(?:й|е)|июн(?:е|ь)|июл(?:е|ь)|август(?:е)?|сентябр(?:е|ь)|октябр(?:е|ь)|ноябр(?:е|ь)|декабр(?:е|ь))(?:\s+\d{4}(?:\s+г(?:\.|ода)?)?)?$/iu;
|
||||
if (temporalZaPattern.test(cleanupAnchorValue(candidate).toLowerCase().replace(/ё/g, "е").trim())) {
|
||||
return true;
|
||||
}
|
||||
const normalized = cleanupAnchorValue(candidate)
|
||||
.toLowerCase()
|
||||
.replace(/ё/g, "е")
|
||||
.trim();
|
||||
return /^(?:в|на)\s+(?:январ(?:е|ь)|феврал(?:е|ь)|март(?:е)?|апрел(?:е|ь)|ма(?:й|е)|июн(?:е|ь)|июл(?:е|ь)|август(?:е)?|сентябр(?:е|ь)|октябр(?:е|ь)|ноябр(?:е|ь)|декабр(?:е|ь))(?:\s+\d{4}(?:\s+г(?:\.|ода)?)?)?$/iu.test(normalized);
|
||||
}
|
||||
function isLowQualityWarehouseAnchorValue(rawValue) {
|
||||
const value = cleanupAnchorValue(rawValue)
|
||||
.toLowerCase()
|
||||
.replace(/ё/g, "е")
|
||||
.trim();
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
if (isTemporalWarehousePhrase(value) || isImplicitSelfScopeWarehouseAnchor(value)) {
|
||||
return true;
|
||||
}
|
||||
const hasQuestionOrRepairCue = /(?:^|[\s,.;:!?()\-])(?:что|какой|какая|какие|как|где|когда|почему|зачем|имел(?:ось|ся)\s+в\s+виду|имеется\s+в\s+виду|в\s+смысле|то\s+есть|which|what|where|when|why)(?=$|[\s,.;:!?()\-])/iu.test(value) || /[?]/u.test(rawValue);
|
||||
const hasProfanityCue = /(?:^|[\s,.;:!?()\-])(?:аху|оху|хуе|хуё|хуй|ебан|ебуч|бля|блять|пизд|нахуй|shit|fuck|damn)(?=$|[\s,.;:!?()\-])/iu.test(value);
|
||||
const lowQualityTokens = new Set([
|
||||
"что",
|
||||
"какой",
|
||||
"какая",
|
||||
"какие",
|
||||
"как",
|
||||
"где",
|
||||
"когда",
|
||||
"почему",
|
||||
"зачем",
|
||||
"имелось",
|
||||
"имелся",
|
||||
"имеется",
|
||||
"в",
|
||||
"виду",
|
||||
"то",
|
||||
"есть",
|
||||
"лежит",
|
||||
"лежат",
|
||||
"лежало",
|
||||
"лежали",
|
||||
"на",
|
||||
"по",
|
||||
"складе",
|
||||
"складу",
|
||||
"складом",
|
||||
"ебаном",
|
||||
"ахуеть",
|
||||
"охуеть",
|
||||
"пиздец",
|
||||
"блять",
|
||||
"бля"
|
||||
]);
|
||||
const tokens = value
|
||||
.split(/[^a-zа-я0-9]+/iu)
|
||||
.map((token) => token.trim())
|
||||
.filter(Boolean);
|
||||
if (tokens.length === 0) {
|
||||
return true;
|
||||
}
|
||||
const meaningfulTokens = tokens.filter((token) => !lowQualityTokens.has(token) && token.length > 1);
|
||||
if (meaningfulTokens.length === 0) {
|
||||
return true;
|
||||
}
|
||||
if ((hasQuestionOrRepairCue || hasProfanityCue) && meaningfulTokens.length <= 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function normalizeSemanticAnchorCandidate(value) {
|
||||
return cleanupAnchorValue(value)
|
||||
.toLowerCase()
|
||||
@@ -1079,6 +1145,7 @@ function extractInventoryWarehouseAnchor(text) {
|
||||
candidate.includes("->") ||
|
||||
candidate.includes("=>") ||
|
||||
isImplicitSelfScopeWarehouseAnchor(candidate) ||
|
||||
isLowQualityWarehouseAnchorValue(candidate) ||
|
||||
normalizedCandidate.startsWith("по состоянию") ||
|
||||
isTemporalWarehousePhrase(candidate) ||
|
||||
/^(?:сейчас|на|дату|дате|остаток|остатки)$/iu.test(candidate)) {
|
||||
|
||||
@@ -1598,6 +1598,14 @@ function resolveAddressIntent(userMessage) {
|
||||
reasons: ["inventory_selected_object_sale_trace_signal_detected"]
|
||||
};
|
||||
}
|
||||
if (/(?:кому\s+(?:мы\s+)?впарили(?:\s+(?:это|его|товар|позицию))?|кому\s+в\s+итоге\s+мы\s+впарили)/iu.test(text) &&
|
||||
/(?:товар|номенклатур|sku|item|product|позици(?:я|ю|и)|продукци(?:я|ю|и))/iu.test(text)) {
|
||||
return {
|
||||
intent: "inventory_sale_trace_for_item",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_sale_trace_signal_detected"]
|
||||
};
|
||||
}
|
||||
if (hasInventorySaleTraceSignalV2(text)) {
|
||||
return {
|
||||
intent: "inventory_sale_trace_for_item",
|
||||
|
||||
@@ -1588,7 +1588,13 @@ function hasExplicitPeriodWindow(filters) {
|
||||
(typeof filters.period_to === "string" && filters.period_to.trim().length > 0));
|
||||
}
|
||||
function canAutoBroadenPeriodWindow(intent, filters) {
|
||||
if (!hasExplicitPeriodWindow(filters)) {
|
||||
const hasRecoverableAsOfOnlyWindow = !hasExplicitPeriodWindow(filters) &&
|
||||
typeof filters.as_of_date === "string" &&
|
||||
filters.as_of_date.trim().length > 0 &&
|
||||
typeof filters.item === "string" &&
|
||||
filters.item.trim().length > 0 &&
|
||||
(intent === "inventory_purchase_provenance_for_item" || intent === "inventory_purchase_documents_for_item");
|
||||
if (!hasExplicitPeriodWindow(filters) && !hasRecoverableAsOfOnlyWindow) {
|
||||
return false;
|
||||
}
|
||||
return (intent === "list_documents_by_counterparty" ||
|
||||
|
||||
@@ -32,6 +32,9 @@ function hasAllTimeHint(text) {
|
||||
function hasSameDateHint(text) {
|
||||
return /(?:на\s+ту\s+же\s+дат[ауеы]|на\s+эту\s+же\s+дат[ауеы]|на\s+эту\s+дат[ауеы]|эту\s+дат[ауеы]|та\s+же\s+дата|same\s+date|as\s+of\s+same\s+date|the\s+same\s+date)/iu.test(String(text ?? ""));
|
||||
}
|
||||
function hasSamePeriodHint(text) {
|
||||
return /(?:на\s+тот\s+же\s+период|за\s+тот\s+же\s+период|тот\s+же\s+период(?:\s+рассмотрения)?|на\s+этот\s+же\s+период|за\s+этот\s+же\s+период|аналогичн\w+\s+текущ\w+\s+период\w+|same\s+period|same\s+range|same\s+window)/iu.test(String(text ?? ""));
|
||||
}
|
||||
function hasExplicitPeriodLiteral(text) {
|
||||
return /(?:^|[^\d*×xх])((?:19|20)\d{2}(?:[./-](?:0?[1-9]|1[0-2]))?)(?=$|[^\d*×xх])/iu.test(String(text ?? ""));
|
||||
}
|
||||
@@ -383,10 +386,15 @@ function shouldRestoreInventoryRootFrame(userMessage, intent, extractedFilters,
|
||||
const currentFrameKind = followupContext.current_frame_kind ?? null;
|
||||
const previousIntent = followupContext.previous_intent;
|
||||
const comingFromInventoryDrilldown = currentFrameKind === "inventory_drilldown" || isInventoryDrilldownFrameIntent(previousIntent);
|
||||
if (!comingFromInventoryDrilldown) {
|
||||
const normalized = String(userMessage ?? "");
|
||||
const hasInventoryRootRestatementCue = /(?:склад|остат(?:ок|ки)|позици(?:я|и|ю)|товар(?:ы|ов)?|номенклатур)/iu.test(normalized) &&
|
||||
/(?:покажи|показать|выведи|раскрой|еще\s+раз|ещ[её]\s+раз|снова|опять|верни|вернись|повтори|тот\s+же|этот\s+же|same|again)/iu.test(normalized);
|
||||
const canReenterInventoryRoot = comingFromInventoryDrilldown ||
|
||||
(currentFrameKind === "inventory_root" && hasSamePeriodHint(normalized)) ||
|
||||
(currentFrameKind === "generic" && hasInventoryRootRestatementCue && hasSamePeriodHint(normalized));
|
||||
if (!canReenterInventoryRoot) {
|
||||
return false;
|
||||
}
|
||||
const normalized = String(userMessage ?? "");
|
||||
if (hasSelectedObjectInventorySignal(normalized) ||
|
||||
hasInventorySupplierFollowupCue(normalized) ||
|
||||
hasInventoryPurchaseDocumentsFollowupCue(normalized) ||
|
||||
@@ -401,6 +409,7 @@ function shouldRestoreInventoryRootFrame(userMessage, intent, extractedFilters,
|
||||
}
|
||||
const hasTemporalPatch = hasExplicitPeriodWindow(extractedFilters) ||
|
||||
Boolean(toNonEmptyString(extractedFilters.as_of_date)) ||
|
||||
hasSamePeriodHint(normalized) ||
|
||||
hasExplicitPeriodLiteral(normalized) ||
|
||||
Boolean(resolveRelativeMonthPeriodFromInventoryRoot(normalized, followupContext));
|
||||
return hasTemporalPatch;
|
||||
@@ -408,6 +417,26 @@ function shouldRestoreInventoryRootFrame(userMessage, intent, extractedFilters,
|
||||
function hasSelectedObjectInventorySignal(text) {
|
||||
return /(?:по\s+выбранному\s+объекту|for\s+selected\s+object)/iu.test(String(text ?? ""));
|
||||
}
|
||||
function hasSelectedObjectInlineSnapshotMetadata(text) {
|
||||
return /(?:дата\s+строки|строка\s+от|количество\s*:|стоимость\s*:|склад\s*:|организация\s*:|\|\s*(?:склад|количество|стоимость|организация|дата\s+строки)\s*:)/iu.test(String(text ?? ""));
|
||||
}
|
||||
function extractSelectedObjectItemFromFollowupText(text) {
|
||||
const rawSelectedObject = toNonEmptyString((0, addressFilterExtractor_1.extractSelectedObjectQuotedValue)(text));
|
||||
if (!rawSelectedObject) {
|
||||
return null;
|
||||
}
|
||||
const firstLine = rawSelectedObject
|
||||
.replace(/\r\n?/g, "\n")
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.find(Boolean);
|
||||
const primarySegment = String(firstLine ?? rawSelectedObject)
|
||||
.replace(/^\d+\.\s*/, "")
|
||||
.split("|")[0]
|
||||
?.trim();
|
||||
const normalized = toNonEmptyString(primarySegment);
|
||||
return normalized;
|
||||
}
|
||||
function hasInventorySupplierFollowupCue(text) {
|
||||
return (0, inventoryLifecycleCueHelpers_1.hasInventorySupplierCue)(String(text ?? ""));
|
||||
}
|
||||
@@ -506,6 +535,7 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||
const relativeMonthFromInventoryRoot = resolveRelativeMonthPeriodFromInventoryRoot(userMessage, followupContext);
|
||||
const allTimeRequested = hasAllTimeHint(userMessage);
|
||||
const sameDateRequested = hasSameDateHint(userMessage);
|
||||
const samePeriodRequested = hasSamePeriodHint(userMessage);
|
||||
if (!toNonEmptyString(merged.organization) && previousOrganization) {
|
||||
merged.organization = previousOrganization;
|
||||
reasons.push("organization_from_followup_context");
|
||||
@@ -618,7 +648,7 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date")) {
|
||||
const inheritedItem = previousItem ?? previousAnchorItem;
|
||||
const explicitQuotedItem = toNonEmptyString((0, addressFilterExtractor_1.extractSelectedObjectQuotedValue)(userMessage));
|
||||
const explicitQuotedItem = extractSelectedObjectItemFromFollowupText(userMessage);
|
||||
const currentItem = toNonEmptyString(merged.item);
|
||||
const shouldAdoptExplicitQuotedItem = Boolean(explicitQuotedItem) &&
|
||||
(!currentItem ||
|
||||
@@ -653,6 +683,22 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||
reasons.push("as_of_date_from_followup_context");
|
||||
}
|
||||
}
|
||||
if (samePeriodRequested &&
|
||||
(intent === "inventory_on_hand_as_of_date" || intent === "inventory_supplier_stock_overlap_as_of_date")) {
|
||||
if (previousPeriodFrom && merged.period_from !== previousPeriodFrom) {
|
||||
merged.period_from = previousPeriodFrom;
|
||||
reasons.push("period_from_from_followup_context");
|
||||
}
|
||||
if (previousPeriodTo && merged.period_to !== previousPeriodTo) {
|
||||
merged.period_to = previousPeriodTo;
|
||||
reasons.push("period_to_from_followup_context");
|
||||
}
|
||||
const inheritedAsOfDate = previousAsOfDate ?? previousPeriodTo ?? previousPeriodFrom;
|
||||
if (inheritedAsOfDate && merged.as_of_date !== inheritedAsOfDate) {
|
||||
merged.as_of_date = inheritedAsOfDate;
|
||||
reasons.push("as_of_date_from_followup_context");
|
||||
}
|
||||
}
|
||||
if (!sameDateRequested &&
|
||||
(intent === "inventory_aging_by_purchase_date" || isInventoryLifecycleHistoryIntent(intent)) &&
|
||||
!hasExplicitPeriodLiteral(userMessage) &&
|
||||
@@ -668,6 +714,21 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((Boolean(previousPeriodFrom) || Boolean(previousPeriodTo)) &&
|
||||
hasSelectedObjectInventorySignal(userMessage) &&
|
||||
hasSelectedObjectInlineSnapshotMetadata(userMessage) &&
|
||||
(intent === "inventory_purchase_provenance_for_item" || intent === "inventory_purchase_documents_for_item") &&
|
||||
!hasExplicitPeriodLiteral(userMessage) &&
|
||||
!hasExplicitCurrentDateHint(userMessage)) {
|
||||
if (previousPeriodFrom && merged.period_from !== previousPeriodFrom) {
|
||||
merged.period_from = previousPeriodFrom;
|
||||
reasons.push("period_from_from_followup_context");
|
||||
}
|
||||
if (previousPeriodTo && merged.period_to !== previousPeriodTo) {
|
||||
merged.period_to = previousPeriodTo;
|
||||
reasons.push("period_to_from_followup_context");
|
||||
}
|
||||
}
|
||||
if (!sameDateRequested &&
|
||||
(intent === "inventory_on_hand_as_of_date" || intent === "inventory_supplier_stock_overlap_as_of_date") &&
|
||||
!hasExplicitPeriodLiteral(userMessage) &&
|
||||
|
||||
@@ -38,6 +38,47 @@ function findLastGroundedInventoryAddressDebug(items) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function findLastAddressDebugWithItem(items) {
|
||||
if (!Array.isArray(items)) {
|
||||
return null;
|
||||
}
|
||||
for (let index = items.length - 1; index >= 0; index -= 1) {
|
||||
const item = items[index];
|
||||
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
|
||||
continue;
|
||||
}
|
||||
const debug = item.debug;
|
||||
if (String(debug.execution_lane ?? "") !== "address_query") {
|
||||
continue;
|
||||
}
|
||||
const extractedFilters = debug.extracted_filters && typeof debug.extracted_filters === "object"
|
||||
? debug.extracted_filters
|
||||
: null;
|
||||
const itemLabel = String(extractedFilters?.item ?? "").trim() ||
|
||||
(String(debug.anchor_type ?? "") === "item"
|
||||
? String(debug.anchor_value_resolved ?? debug.anchor_value_raw ?? "").trim()
|
||||
: "");
|
||||
if (itemLabel) {
|
||||
return debug;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function findLastAddressDebug(items) {
|
||||
if (!Array.isArray(items)) {
|
||||
return null;
|
||||
}
|
||||
for (let index = items.length - 1; index >= 0; index -= 1) {
|
||||
const item = items[index];
|
||||
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
|
||||
continue;
|
||||
}
|
||||
if (String(item.debug.execution_lane ?? "") === "address_query") {
|
||||
return item.debug;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function buildInventoryHistoryCapabilityFollowupReply(input) {
|
||||
const rootFrameContext = input.addressDebug?.address_root_frame_context && typeof input.addressDebug.address_root_frame_context === "object"
|
||||
? input.addressDebug.address_root_frame_context
|
||||
@@ -65,6 +106,42 @@ function buildInventoryHistoryCapabilityFollowupReply(input) {
|
||||
"Если хочешь, сразу покажу нужный исторический период."
|
||||
].join("\n");
|
||||
}
|
||||
function buildAddressMemoryRecapReply(input) {
|
||||
const extractedFilters = input.addressDebug?.extracted_filters && typeof input.addressDebug.extracted_filters === "object"
|
||||
? input.addressDebug.extracted_filters
|
||||
: null;
|
||||
const rootFrameContext = input.addressDebug?.address_root_frame_context && typeof input.addressDebug.address_root_frame_context === "object"
|
||||
? input.addressDebug.address_root_frame_context
|
||||
: null;
|
||||
const item = input.toNonEmptyString(extractedFilters?.item) ??
|
||||
(String(input.addressDebug?.anchor_type ?? "") === "item"
|
||||
? input.toNonEmptyString(input.addressDebug?.anchor_value_resolved) ??
|
||||
input.toNonEmptyString(input.addressDebug?.anchor_value_raw)
|
||||
: null);
|
||||
const organization = input.organization ??
|
||||
input.toNonEmptyString(extractedFilters?.organization) ??
|
||||
input.toNonEmptyString(rootFrameContext?.organization);
|
||||
const scopedDate = formatIsoDateForReply(extractedFilters?.as_of_date) ??
|
||||
formatIsoDateForReply(rootFrameContext?.as_of_date) ??
|
||||
formatIsoDateForReply(extractedFilters?.period_to);
|
||||
if (item) {
|
||||
const datePart = scopedDate ? ` в срезе на ${scopedDate}` : "";
|
||||
const organizationPart = organization ? ` по компании «${organization}»` : "";
|
||||
return [
|
||||
`Да, помню. Мы обсуждали позицию «${item}»${organizationPart}${datePart}.`,
|
||||
"Могу продолжить по ней без переписывания сущности: кто поставил, когда купили, по каким документам или кому продали."
|
||||
].join(" ");
|
||||
}
|
||||
if (organization || scopedDate) {
|
||||
const organizationPart = organization ? ` по компании «${organization}»` : "";
|
||||
const datePart = scopedDate ? ` на ${scopedDate}` : "";
|
||||
return [
|
||||
`Да, помню. Мы уже смотрели адресный контур${organizationPart}${datePart}.`,
|
||||
"Могу кратко напомнить контекст или сразу продолжить следующий шаг по этому же сценарию."
|
||||
].join(" ");
|
||||
}
|
||||
return "Да, помню предыдущий адресный контур. Могу кратко напомнить, что мы уже подтвердили, или сразу продолжить следующий шаг.";
|
||||
}
|
||||
async function runAssistantLivingChatRuntime(input) {
|
||||
const userMessage = String(input.userMessage ?? "");
|
||||
const dataScopeMetaQuery = input.hasAssistantDataScopeMetaQuestionSignal(userMessage);
|
||||
@@ -83,9 +160,13 @@ async function runAssistantLivingChatRuntime(input) {
|
||||
let selectedOrganization = input.toNonEmptyString(input.sessionScope.selectedOrganization);
|
||||
let activeOrganization = input.toNonEmptyString(input.sessionScope.activeOrganization);
|
||||
const contextualInventoryHistoryCapabilityFollowup = input.modeDecision?.reason === "inventory_history_capability_followup_detected";
|
||||
const contextualMemoryRecapFollowup = input.modeDecision?.reason === "memory_recap_followup_detected";
|
||||
const lastGroundedInventoryAddressDebug = contextualInventoryHistoryCapabilityFollowup
|
||||
? findLastGroundedInventoryAddressDebug(input.sessionItems)
|
||||
: null;
|
||||
const lastMemoryAddressDebug = contextualMemoryRecapFollowup
|
||||
? findLastAddressDebugWithItem(input.sessionItems) ?? findLastAddressDebug(input.sessionItems)
|
||||
: null;
|
||||
if (capabilityMetaQuery && (destructiveSignal || dangerSignal)) {
|
||||
chatText = input.buildAssistantSafetyRefusalReply();
|
||||
livingChatSource = "deterministic_safety_refusal";
|
||||
@@ -139,6 +220,16 @@ async function runAssistantLivingChatRuntime(input) {
|
||||
activeOrganization = scopedOrganization ?? activeOrganization;
|
||||
livingChatSource = "deterministic_inventory_history_capability_contract";
|
||||
}
|
||||
else if (contextualMemoryRecapFollowup) {
|
||||
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
|
||||
chatText = buildAddressMemoryRecapReply({
|
||||
organization: scopedOrganization,
|
||||
addressDebug: lastMemoryAddressDebug,
|
||||
toNonEmptyString: input.toNonEmptyString
|
||||
});
|
||||
activeOrganization = scopedOrganization ?? activeOrganization;
|
||||
livingChatSource = "deterministic_memory_recap_contract";
|
||||
}
|
||||
else if (capabilityMetaQuery) {
|
||||
chatText = input.buildAssistantCapabilityContractReply();
|
||||
livingChatSource = "deterministic_capability_contract";
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.INVENTORY_CAPABILITY_CONTRACTS = exports.ASSISTANT_TRANSITION_CONTRACTS = void 0;
|
||||
exports.listAssistantTransitionContracts = listAssistantTransitionContracts;
|
||||
exports.getAssistantTransitionContract = getAssistantTransitionContract;
|
||||
exports.listInventoryCapabilityContracts = listInventoryCapabilityContracts;
|
||||
exports.getAssistantCapabilityContract = getAssistantCapabilityContract;
|
||||
exports.getAssistantCapabilityContractByIntent = getAssistantCapabilityContractByIntent;
|
||||
const assistantRuntimeContracts_1 = require("../types/assistantRuntimeContracts");
|
||||
exports.ASSISTANT_TRANSITION_CONTRACTS = [
|
||||
{
|
||||
schema_version: assistantRuntimeContracts_1.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T1",
|
||||
title: "Root Query Entry",
|
||||
trigger_class: "new_root_business_question",
|
||||
required_prior_state: ["living_mode_state"],
|
||||
allowed_carryover_depth: "none",
|
||||
state_mutations: ["create_root_frame_state", "clear_selected_object_frame_state", "create_coverage_gate_state"],
|
||||
forbidden_carryover: ["stale_focus_object", "stale_object_intent"],
|
||||
expected_answer_mode: "confirmed"
|
||||
},
|
||||
{
|
||||
schema_version: assistantRuntimeContracts_1.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T2",
|
||||
title: "Root Follow-Up With Date Or Scope Change",
|
||||
trigger_class: "root_followup_temporal_or_organization_shift",
|
||||
required_prior_state: ["root_frame_state"],
|
||||
allowed_carryover_depth: "root_only",
|
||||
state_mutations: ["update_root_frame_state", "run_exact_route", "refresh_coverage_gate_state"],
|
||||
forbidden_carryover: ["incompatible_selected_object_route"],
|
||||
expected_answer_mode: "confirmed"
|
||||
},
|
||||
{
|
||||
schema_version: assistantRuntimeContracts_1.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T3",
|
||||
title: "Explicit Selected Object Drilldown",
|
||||
trigger_class: "explicit_selected_object_or_ui_object_selection",
|
||||
required_prior_state: ["root_frame_state"],
|
||||
allowed_carryover_depth: "object_only",
|
||||
state_mutations: ["create_selected_object_frame_state", "bind_source_result_set", "preserve_temporal_ceiling"],
|
||||
forbidden_carryover: ["unrelated_prior_focus_object"],
|
||||
expected_answer_mode: "confirmed"
|
||||
},
|
||||
{
|
||||
schema_version: assistantRuntimeContracts_1.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T4",
|
||||
title: "Short Action Follow-Up On Selected Object",
|
||||
trigger_class: "short_action_followup_on_active_focus_object",
|
||||
required_prior_state: ["selected_object_frame_state"],
|
||||
allowed_carryover_depth: "object_only",
|
||||
state_mutations: ["reuse_selected_object_frame_state", "route_to_compatible_item_action"],
|
||||
forbidden_carryover: ["generic_chat_fallback", "data_scope_selection_fallback", "object_focus_reset"],
|
||||
expected_answer_mode: "confirmed"
|
||||
},
|
||||
{
|
||||
schema_version: assistantRuntimeContracts_1.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T5",
|
||||
title: "Pronoun Or Compressed Object Follow-Up",
|
||||
trigger_class: "pronoun_or_compressed_reference_to_active_focus_object",
|
||||
required_prior_state: ["selected_object_frame_state"],
|
||||
allowed_carryover_depth: "object_only",
|
||||
state_mutations: ["reuse_selected_object_frame_state", "resolve_pronoun_to_focus_object"],
|
||||
forbidden_carryover: ["low_quality_object_rewrite", "semantic_noise_as_anchor"],
|
||||
expected_answer_mode: "confirmed"
|
||||
},
|
||||
{
|
||||
schema_version: assistantRuntimeContracts_1.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T6",
|
||||
title: "Domain Pivot With Root-Only Carryover",
|
||||
trigger_class: "supported_domain_pivot_from_active_drilldown",
|
||||
required_prior_state: ["root_frame_state", "selected_object_frame_state"],
|
||||
allowed_carryover_depth: "root_only",
|
||||
state_mutations: ["preserve_root_frame_state", "drop_selected_object_frame_state"],
|
||||
forbidden_carryover: ["object_route_replay_into_new_domain"],
|
||||
expected_answer_mode: "confirmed"
|
||||
},
|
||||
{
|
||||
schema_version: assistantRuntimeContracts_1.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T7",
|
||||
title: "Clarification Continuation",
|
||||
trigger_class: "user_resolves_missing_anchor_or_scope",
|
||||
required_prior_state: ["clarification_state"],
|
||||
allowed_carryover_depth: "full",
|
||||
state_mutations: ["resume_target_route", "update_or_clear_clarification_state"],
|
||||
forbidden_carryover: ["forget_suspended_route"],
|
||||
expected_answer_mode: "confirmed"
|
||||
},
|
||||
{
|
||||
schema_version: assistantRuntimeContracts_1.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T8",
|
||||
title: "Meta Follow-Up Over Answer Object",
|
||||
trigger_class: "evaluation_comparison_or_interpretation_of_previous_answer",
|
||||
required_prior_state: ["answer_context_state", "coverage_gate_state"],
|
||||
allowed_carryover_depth: "meta_only",
|
||||
state_mutations: ["create_meta_frame_state", "reuse_answer_object_without_blind_replay"],
|
||||
forbidden_carryover: ["blind_exact_route_replay"],
|
||||
expected_answer_mode: "meta"
|
||||
},
|
||||
{
|
||||
schema_version: assistantRuntimeContracts_1.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T9",
|
||||
title: "Memory Recap",
|
||||
trigger_class: "conversation_memory_recap_request",
|
||||
required_prior_state: ["answer_context_state"],
|
||||
allowed_carryover_depth: "meta_only",
|
||||
state_mutations: ["reuse_grounded_prior_answer_context"],
|
||||
forbidden_carryover: ["invented_conversation_memory"],
|
||||
expected_answer_mode: "recap"
|
||||
},
|
||||
{
|
||||
schema_version: assistantRuntimeContracts_1.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T10",
|
||||
title: "Unsupported Or Blocked Boundary",
|
||||
trigger_class: "unsupported_route_or_blocked_evidence_gate",
|
||||
required_prior_state: ["coverage_gate_state"],
|
||||
allowed_carryover_depth: "none",
|
||||
state_mutations: ["emit_bounded_boundary_or_clarification"],
|
||||
forbidden_carryover: ["blocked_as_confirmed_factual_answer"],
|
||||
expected_answer_mode: "boundary"
|
||||
}
|
||||
];
|
||||
const SHARED_INVENTORY_ACCEPTANCE_FAMILIES = [
|
||||
"canonical",
|
||||
"colloquial",
|
||||
"ui_selected_object",
|
||||
"ui_selected_object_colloquial",
|
||||
"short_action_followup",
|
||||
"pronoun_followup",
|
||||
"followup_date_carryover"
|
||||
];
|
||||
const INVENTORY_ITEM_ANCHOR_RULES = [
|
||||
"no_low_quality_item_rewrite",
|
||||
"no_numeric_tail_account_poisoning",
|
||||
"no_conversational_noise_as_entity",
|
||||
"confirmed_focus_object_beats_semantic_hint"
|
||||
];
|
||||
const INVENTORY_SELECTED_OBJECT_TESTS = [
|
||||
"selected_object_memory_survives_short_followup",
|
||||
"new_explicit_selected_object_overrides_old_focus",
|
||||
"full_anchor_not_degraded_by_canonical_rewrite"
|
||||
];
|
||||
function inventoryExactCapability(input) {
|
||||
return {
|
||||
schema_version: assistantRuntimeContracts_1.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
capability_id: input.capability_id,
|
||||
domain_id: "inventory_stock",
|
||||
runtime_lane: "address_exact",
|
||||
intent_ids: input.intent_ids,
|
||||
entry_modes: input.entry_modes,
|
||||
supported_transition_classes: input.transitions,
|
||||
frame_compatibility: {
|
||||
root_frame: input.entry_modes.includes("root_entry") ? "optional" : "required",
|
||||
selected_object_frame: input.requiresFocusObject ? "required" : "optional",
|
||||
meta_frame: "forbidden"
|
||||
},
|
||||
required_anchors: input.requiredAnchors,
|
||||
optional_anchors: ["organization", "warehouse", "date_scope"],
|
||||
anchor_source_priority: ["explicit_user_anchor", "ui_selected_object", "selected_object_frame", "root_frame", "semantic_hint"],
|
||||
anchor_admissibility_rules: [...INVENTORY_ITEM_ANCHOR_RULES],
|
||||
organization_scope_behavior: "reuse_or_clarify",
|
||||
date_scope_behavior: "reuse",
|
||||
temporal_ceiling_policy: input.requiresFocusObject ? "respect_root_temporal_ceiling" : "must_not_expand_without_reason_code",
|
||||
root_context_compatibility: "required",
|
||||
requires_focus_object: input.requiresFocusObject,
|
||||
accepted_focus_object_kinds: input.requiresFocusObject ? ["inventory_item", "item"] : [],
|
||||
focus_object_override_policy: input.requiresFocusObject ? "explicit_new_object_wins" : "not_applicable",
|
||||
bundle_reuse_policy: input.bundleReusePolicy,
|
||||
resolver_owner: "addressIntentResolver",
|
||||
recipe_owner: "addressRecipeCatalog",
|
||||
execution_adapter: "AddressQueryService",
|
||||
result_shape: input.resultShape,
|
||||
answer_object_shape: input.answerObjectShape,
|
||||
minimum_evidence_policy: "route_specific_threshold",
|
||||
coverage_gate_behavior: "partial_or_blocked_if_evidence_insufficient",
|
||||
truth_mode_fallbacks: ["limited", "clarification_required", "unsupported"],
|
||||
blocked_reason_codes: ["missing_anchor", "route_expectation_failure", "execution_error", "insufficient_evidence"],
|
||||
clarification_triggers: ["missing_required_item_anchor", "ambiguous_organization_scope", "ambiguous_date_scope"],
|
||||
clarification_questions: ["Уточните товар, организацию или дату, чтобы не подставлять неподтвержденный anchor."],
|
||||
resume_policy: "resume_original_route_with_resolved_anchors",
|
||||
empty_match_behavior: "truthful_empty_match",
|
||||
route_expectation_failure_behavior: "blocked_route_expectation_failure",
|
||||
execution_error_behavior: "blocked_execution_error",
|
||||
required_unit_tests: input.requiresFocusObject
|
||||
? [...INVENTORY_SELECTED_OBJECT_TESTS, "limited_mode_remains_truthful"]
|
||||
: ["root_context_survives_domain_pivot_without_object_leak", "limited_mode_remains_truthful"],
|
||||
required_transition_tests: input.transitions.map((transitionId) => `transition_${transitionId}`),
|
||||
required_scenario_families: input.scenarioFamilies ?? [...SHARED_INVENTORY_ACCEPTANCE_FAMILIES]
|
||||
};
|
||||
}
|
||||
exports.INVENTORY_CAPABILITY_CONTRACTS = [
|
||||
inventoryExactCapability({
|
||||
capability_id: "confirmed_inventory_on_hand_as_of_date",
|
||||
intent_ids: ["inventory_on_hand_as_of_date"],
|
||||
entry_modes: ["root_entry", "root_followup", "clarification_resume"],
|
||||
transitions: ["T1", "T2", "T7"],
|
||||
requiresFocusObject: false,
|
||||
requiredAnchors: [],
|
||||
resultShape: "item_list_with_quantity_cost_warehouse_organization",
|
||||
answerObjectShape: "inventory_stock_snapshot",
|
||||
bundleReusePolicy: "none",
|
||||
scenarioFamilies: ["canonical", "colloquial", "followup_date_carryover"]
|
||||
}),
|
||||
inventoryExactCapability({
|
||||
capability_id: "inventory_inventory_purchase_provenance_for_item",
|
||||
intent_ids: ["inventory_purchase_provenance_for_item"],
|
||||
entry_modes: ["selected_object_drilldown", "clarification_resume"],
|
||||
transitions: ["T3", "T4", "T5", "T7"],
|
||||
requiresFocusObject: true,
|
||||
requiredAnchors: ["item"],
|
||||
resultShape: "supplier_purchase_provenance_trace",
|
||||
answerObjectShape: "inventory_provenance_bundle",
|
||||
bundleReusePolicy: "provenance_bundle_preferred"
|
||||
}),
|
||||
inventoryExactCapability({
|
||||
capability_id: "inventory_inventory_purchase_documents_for_item",
|
||||
intent_ids: ["inventory_purchase_documents_for_item"],
|
||||
entry_modes: ["selected_object_drilldown", "clarification_resume"],
|
||||
transitions: ["T3", "T4", "T5", "T7"],
|
||||
requiresFocusObject: true,
|
||||
requiredAnchors: ["item"],
|
||||
resultShape: "purchase_document_list_for_selected_item",
|
||||
answerObjectShape: "inventory_purchase_documents_bundle",
|
||||
bundleReusePolicy: "provenance_bundle_preferred"
|
||||
}),
|
||||
inventoryExactCapability({
|
||||
capability_id: "inventory_inventory_supplier_stock_overlap_as_of_date",
|
||||
intent_ids: ["inventory_supplier_stock_overlap_as_of_date"],
|
||||
entry_modes: ["root_entry", "root_followup", "clarification_resume"],
|
||||
transitions: ["T1", "T2", "T7"],
|
||||
requiresFocusObject: false,
|
||||
requiredAnchors: ["supplier"],
|
||||
resultShape: "supplier_to_stock_item_overlap",
|
||||
answerObjectShape: "inventory_supplier_overlap",
|
||||
bundleReusePolicy: "none",
|
||||
scenarioFamilies: ["canonical", "colloquial", "followup_date_carryover"]
|
||||
}),
|
||||
inventoryExactCapability({
|
||||
capability_id: "inventory_inventory_sale_trace_for_item",
|
||||
intent_ids: ["inventory_sale_trace_for_item"],
|
||||
entry_modes: ["selected_object_drilldown", "clarification_resume"],
|
||||
transitions: ["T3", "T4", "T5", "T7"],
|
||||
requiresFocusObject: true,
|
||||
requiredAnchors: ["item"],
|
||||
resultShape: "buyer_sale_trace_for_selected_item",
|
||||
answerObjectShape: "inventory_sale_trace_bundle",
|
||||
bundleReusePolicy: "sale_trace_bundle_preferred"
|
||||
}),
|
||||
inventoryExactCapability({
|
||||
capability_id: "inventory_inventory_purchase_to_sale_chain",
|
||||
intent_ids: ["inventory_purchase_to_sale_chain"],
|
||||
entry_modes: ["selected_object_drilldown", "clarification_resume"],
|
||||
transitions: ["T3", "T4", "T5", "T7"],
|
||||
requiresFocusObject: true,
|
||||
requiredAnchors: ["item"],
|
||||
resultShape: "purchase_stock_sale_document_chain",
|
||||
answerObjectShape: "inventory_purchase_to_sale_chain",
|
||||
bundleReusePolicy: "sale_trace_bundle_preferred"
|
||||
}),
|
||||
inventoryExactCapability({
|
||||
capability_id: "inventory_inventory_aging_by_purchase_date",
|
||||
intent_ids: ["inventory_aging_by_purchase_date"],
|
||||
entry_modes: ["root_entry", "root_followup", "clarification_resume"],
|
||||
transitions: ["T1", "T2", "T6", "T7"],
|
||||
requiresFocusObject: false,
|
||||
requiredAnchors: [],
|
||||
resultShape: "oldest_first_inventory_aging_list",
|
||||
answerObjectShape: "inventory_aging_snapshot",
|
||||
bundleReusePolicy: "none",
|
||||
scenarioFamilies: ["canonical", "colloquial", "followup_date_carryover"]
|
||||
})
|
||||
];
|
||||
function listAssistantTransitionContracts() {
|
||||
return exports.ASSISTANT_TRANSITION_CONTRACTS;
|
||||
}
|
||||
function getAssistantTransitionContract(transitionId) {
|
||||
return exports.ASSISTANT_TRANSITION_CONTRACTS.find((contract) => contract.transition_id === transitionId) ?? null;
|
||||
}
|
||||
function listInventoryCapabilityContracts() {
|
||||
return exports.INVENTORY_CAPABILITY_CONTRACTS;
|
||||
}
|
||||
function getAssistantCapabilityContract(capabilityId) {
|
||||
return exports.INVENTORY_CAPABILITY_CONTRACTS.find((contract) => contract.capability_id === capabilityId) ?? null;
|
||||
}
|
||||
function getAssistantCapabilityContractByIntent(intent) {
|
||||
return exports.INVENTORY_CAPABILITY_CONTRACTS.find((contract) => contract.intent_ids.includes(intent)) ?? null;
|
||||
}
|
||||
+128
-2
@@ -2508,6 +2508,32 @@ function isInventoryDrilldownFrameIntent(intent) {
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date";
|
||||
}
|
||||
function resolveAddressIntentFamily(intent) {
|
||||
const normalizedIntent = toNonEmptyString(intent);
|
||||
if (!normalizedIntent) {
|
||||
return null;
|
||||
}
|
||||
if (normalizedIntent.startsWith("inventory_")) {
|
||||
return "inventory";
|
||||
}
|
||||
if (normalizedIntent.startsWith("vat_")) {
|
||||
return "vat";
|
||||
}
|
||||
if (normalizedIntent === "account_balance_snapshot" || normalizedIntent === "documents_forming_balance") {
|
||||
return "balance";
|
||||
}
|
||||
if (normalizedIntent === "open_items_by_counterparty_or_contract" ||
|
||||
normalizedIntent === "list_documents_by_counterparty" ||
|
||||
normalizedIntent === "bank_operations_by_counterparty" ||
|
||||
normalizedIntent === "list_contracts_by_counterparty" ||
|
||||
normalizedIntent === "list_documents_by_contract" ||
|
||||
normalizedIntent === "bank_operations_by_contract" ||
|
||||
normalizedIntent === "receivables_confirmed_for_period" ||
|
||||
normalizedIntent === "payables_confirmed_for_period") {
|
||||
return "settlements";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function extractAddressCarryoverAnchor(addressDebug) {
|
||||
if (!isAddressLaneDebugPayload(addressDebug)) {
|
||||
return {
|
||||
@@ -2877,6 +2903,18 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
|
||||
? extractDisplayedEntityIndexMention(String(alternateMessage ?? "")) !== null
|
||||
: false;
|
||||
const hasIndexReferenceSignal = hasPrimaryIndexReferenceSignal || hasAlternateIndexReferenceSignal;
|
||||
const hasStrongFollowupReference = hasPrimaryIndexReferenceSignal ||
|
||||
hasAlternateIndexReferenceSignal ||
|
||||
hasOrganizationClarificationContinuation ||
|
||||
hasImplicitContinuationSignal ||
|
||||
inventoryShortFollowupPrimary ||
|
||||
inventoryShortFollowupAlternate ||
|
||||
Boolean(debtRoleSwapIntent) ||
|
||||
hasFollowupMarker(userMessage) ||
|
||||
hasReferentialPointer(userMessage) ||
|
||||
(toNonEmptyString(alternateMessage)
|
||||
? hasFollowupMarker(String(alternateMessage ?? "")) || hasReferentialPointer(String(alternateMessage ?? ""))
|
||||
: false);
|
||||
const hasStandaloneAddressTopic = hasStandaloneAddressTopicSignal(userMessage) ||
|
||||
(toNonEmptyString(alternateMessage) ? hasStandaloneAddressTopicSignal(alternateMessage) : false);
|
||||
if (hasStandaloneAddressTopic &&
|
||||
@@ -2898,6 +2936,23 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
|
||||
return null;
|
||||
}
|
||||
const sourceIntent = toNonEmptyString(previousAddressDebug.detected_intent);
|
||||
const llmExplicitIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
|
||||
const resolvedPrimaryIntent = (0, addressIntentResolver_1.resolveAddressIntent)(repairAddressMojibake(String(userMessage ?? ""))).intent;
|
||||
const resolvedAlternateIntent = toNonEmptyString(alternateMessage)
|
||||
? (0, addressIntentResolver_1.resolveAddressIntent)(repairAddressMojibake(String(alternateMessage ?? ""))).intent
|
||||
: null;
|
||||
const explicitIntent = llmExplicitIntent && llmExplicitIntent !== "unknown"
|
||||
? llmExplicitIntent
|
||||
: resolvedPrimaryIntent && resolvedPrimaryIntent !== "unknown"
|
||||
? resolvedPrimaryIntent
|
||||
: resolvedAlternateIntent && resolvedAlternateIntent !== "unknown"
|
||||
? resolvedAlternateIntent
|
||||
: null;
|
||||
const sourceIntentFamily = resolveAddressIntentFamily(sourceIntent);
|
||||
const explicitIntentFamily = resolveAddressIntentFamily(explicitIntent);
|
||||
if (sourceIntentFamily && explicitIntentFamily && sourceIntentFamily !== explicitIntentFamily && !hasStrongFollowupReference) {
|
||||
return null;
|
||||
}
|
||||
let previousIntent = sourceIntent;
|
||||
let followupSelectionMode = "carry_previous_intent";
|
||||
if (debtRoleSwapIntent) {
|
||||
@@ -4375,6 +4430,17 @@ function resolveAssistantOrchestrationDecision(input) {
|
||||
hasHistoricalCapabilityFollowupSignal(effectiveAddressUserMessage) ||
|
||||
hasHistoricalCapabilityFollowupSignal(repairedEffectiveAddressUserMessage)) &&
|
||||
isGroundedInventoryContextDebug(lastGroundedAddressDebug));
|
||||
const contextualMemoryRecapFollowupDetected = Boolean(!dataScopeMetaQuery &&
|
||||
!capabilityMetaQuery &&
|
||||
!dataRetrievalSignal &&
|
||||
!strongDataSignal &&
|
||||
!aggregateBusinessAnalyticsSignal &&
|
||||
(hasConversationMemoryRecallFollowupSignal(rawUserMessage) ||
|
||||
hasConversationMemoryRecallFollowupSignal(repairedRawUserMessage) ||
|
||||
hasConversationMemoryRecallFollowupSignal(effectiveAddressUserMessage) ||
|
||||
hasConversationMemoryRecallFollowupSignal(repairedEffectiveAddressUserMessage)) &&
|
||||
(lastGroundedAddressDebug ||
|
||||
findLastAddressAssistantItem(sessionItems)?.debug));
|
||||
const hardMetaMode = dataScopeMetaQuery
|
||||
? "data_scope"
|
||||
: capabilityMetaQuery && !dataRetrievalSignal
|
||||
@@ -4465,6 +4531,34 @@ function resolveAssistantOrchestrationDecision(input) {
|
||||
};
|
||||
}
|
||||
if (nonDomainQueryIndexed) {
|
||||
if (contextualMemoryRecapFollowupDetected) {
|
||||
return {
|
||||
runAddressLane: false,
|
||||
toolGateDecision: "skip_address_lane",
|
||||
toolGateReason: "memory_recap_followup_detected",
|
||||
livingMode: "chat",
|
||||
livingReason: "memory_recap_followup_detected",
|
||||
orchestrationContract: {
|
||||
schema_version: "assistant_orchestration_contract_v1",
|
||||
hard_meta_mode: "non_domain",
|
||||
address_mode: resolvedModeDetection.mode,
|
||||
address_mode_confidence: resolvedModeDetection.confidence,
|
||||
address_intent: resolvedIntentResolution.intent,
|
||||
address_intent_confidence: resolvedIntentResolution.confidence,
|
||||
strong_data_signal_detected: strongDataSignal,
|
||||
data_retrieval_signal_detected: dataRetrievalSignal,
|
||||
followup_context_detected: Boolean(followupContext || lastGroundedAddressDebug),
|
||||
unsupported_address_intent_fallback_to_deep: false,
|
||||
final_decision: {
|
||||
run_address_lane: false,
|
||||
tool_gate_decision: "skip_address_lane",
|
||||
tool_gate_reason: "memory_recap_followup_detected",
|
||||
living_mode: "chat",
|
||||
living_reason: "memory_recap_followup_detected"
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
runAddressLane: false,
|
||||
toolGateDecision: "skip_address_lane",
|
||||
@@ -4569,6 +4663,9 @@ function resolveAssistantOrchestrationDecision(input) {
|
||||
const vatExplainFollowupSignal = Boolean(followupContext &&
|
||||
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
||||
/(?:\u043f\u043e\u0447\u0435\u043c\u0443|why).*(?:\u043f\u0440\u043e\u0433\u043d\u043e\u0437|forecast).*(?:\u0443\u043f\u043b\u0430\u0442|payable|\b0\b)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
|
||||
const vatEvaluativeFollowupSignal = Boolean(followupContext &&
|
||||
toNonEmptyString(followupContext.previous_intent) === "vat_payable_forecast" &&
|
||||
/(?:^|\s)(?:это\s+)?много\s+или\s+мало(?:\?|$)|(?:^|\s)(?:это\s+)?нормально(?:\?|$)|(?:^|\s)(?:это\s+)?плохо(?:\?|$)|(?:^|\s)(?:это\s+)?хорошо(?:\?|$)/iu.test(compactWhitespace(`${repairedRawUserMessage} ${repairedEffectiveAddressUserMessage}`)));
|
||||
const deepAnalysisSignalFallbackToDeep = Boolean(baseToolGate?.runAddressLane &&
|
||||
!llmRuntimeUnavailableDetected &&
|
||||
(deepAnalysisPreferenceDetected || semanticDeepInvestigationHintDetected) &&
|
||||
@@ -4599,7 +4696,7 @@ function resolveAssistantOrchestrationDecision(input) {
|
||||
const hasPriorAddressAnswerContext = Boolean(lastGroundedAddressDebug || toNonEmptyString(followupContext?.previous_intent));
|
||||
const metaFollowupOverGroundedAnswer = Boolean(followupContext &&
|
||||
hasPriorAddressAnswerContext &&
|
||||
metaAnswerFollowupSignal &&
|
||||
(metaAnswerFollowupSignal || vatEvaluativeFollowupSignal) &&
|
||||
!dataScopeMetaQuery &&
|
||||
!capabilityMetaQuery &&
|
||||
!aggregateBusinessAnalyticsSignal &&
|
||||
@@ -4844,7 +4941,28 @@ function hasMetaAnswerFollowupSignal(userMessage) {
|
||||
sample.includes("по этому поводу") ||
|
||||
sample.includes("об этом") ||
|
||||
(sample.includes("это") && hasReferentialPointer(sample)));
|
||||
if (!(hasReflectionCue && hasTopicPointerCue)) {
|
||||
const hasEvaluationCue = samples.some((sample) => /\b(?:много|мало|нормально|хорошо|плохо|критично|перебор|слабо)\b/iu.test(sample));
|
||||
if (!((hasReflectionCue || hasEvaluationCue) &&
|
||||
(hasTopicPointerCue || (hasEvaluationCue && samples.some((sample) => /^(?:это|ну это)\b/iu.test(sample)))))) {
|
||||
return false;
|
||||
}
|
||||
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
|
||||
shouldHandleAsAssistantCapabilityMetaQuery(sample) ||
|
||||
hasDataRetrievalRequestSignal(sample) ||
|
||||
hasStrongDataIntentSignal(sample));
|
||||
}
|
||||
function hasConversationMemoryRecallFollowupSignal(userMessage) {
|
||||
const rawText = compactWhitespace(String(userMessage ?? "").toLowerCase());
|
||||
const repairedText = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase());
|
||||
const samples = [rawText, repairedText]
|
||||
.filter((item) => item.length > 0)
|
||||
.map((item) => item.replace(/ё/g, "е"));
|
||||
if (samples.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const hasMemoryCue = samples.some((sample) => /(?:помни(?:шь|те|м)?|remember|recall)/iu.test(sample));
|
||||
const hasDiscussionCue = samples.some((sample) => /(?:обсуждал[аи]?|говорил[аи]?|смотрел[аи]?|разбирал[аи]?|спрашивал[аи]?)/iu.test(sample));
|
||||
if (!hasMemoryCue || !hasDiscussionCue) {
|
||||
return false;
|
||||
}
|
||||
return !samples.some((sample) => hasAssistantDataScopeMetaQuestionSignal(sample) ||
|
||||
@@ -4920,6 +5038,14 @@ function shouldEmitOrganizationSelectionReply(userMessage, selectedOrganization)
|
||||
if (hasSelectionCue) {
|
||||
return true;
|
||||
}
|
||||
const hasAffectiveReactionCue = /(?:^|[\s,.;:!?()\-])(?:ну|мда|ох|ах|офигеть|офигенно|ахуеть|охуеть|пиздец|пизда|нихуя|хуево|хуёво|ебать|ебан|бля|блять|fuck|shit|damn)(?=$|[\s,.;:!?()\-])/iu.test(normalized) ||
|
||||
normalized.includes("\u0430\u0445\u0443") ||
|
||||
normalized.includes("\u043e\u0445\u0443") ||
|
||||
normalized.includes("\u043f\u0438\u0437\u0434") ||
|
||||
normalized.includes("\u0431\u043b\u044f");
|
||||
if (hasAffectiveReactionCue) {
|
||||
return false;
|
||||
}
|
||||
return normalized.length <= 36 && !/[?]/.test(String(userMessage ?? ""));
|
||||
}
|
||||
function hasOperationalAdminActionRequestSignal(text) {
|
||||
|
||||
@@ -11,7 +11,10 @@ function hasInventoryPurchaseStem(text) {
|
||||
}
|
||||
function hasInventorySupplierCue(text) {
|
||||
const value = toText(text);
|
||||
if (/(?:кто\s+(?:(?:это|этот\s+товар|эту\s+позицию)\s+)?(?:нам\s+)?поставил|кто\s+(?:нам\s+)?поставил\s+(?:это|этот\s+товар|эту\s+позицию)|от\s+какого\s+поставщика|у\s+какого\s+поставщика|от\s+кого\s+куплен|у\s+кого\s+купили|у\s+кого\s+куплено|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|этот\s+товар|эту\s+позицию))?|где\s+куплено|supplier|vendor|поставщик)/iu.test(value)) {
|
||||
if (/(?:купил(?:и|о)?\s+у\s+кого|куплен(?:о)?\s+у\s+кого|купил(?:и|о)?\s+от\s+кого|куплен(?:о)?\s+от\s+кого)/iu.test(value)) {
|
||||
return true;
|
||||
}
|
||||
if (/(?:кто\s+(?:(?:это|этот\s+товар|эту\s+позицию)\s+)?(?:нам\s+)?поставил|кто\s+(?:нам\s+)?поставил\s+(?:это|этот\s+товар|эту\s+позицию)|от\s+какого\s+поставщика|у\s+какого\s+поставщика|от\s+кого\s+куплен|у\s+кого\s+купили|у\s+кого\s+куплено|где\s+(?:мы\s+)?купили(?:\s+(?:это|его|этот\s+товар|эту\s+позицию))?|где\s+(?:мы\s+)?взяли(?:\s+(?:это|его|этот\s+товар|эту\s+позицию))?|откуда\s+(?:мы\s+)?взяли(?:\s+(?:это|его|этот\s+товар|эту\s+позицию))?|где\s+куплено|supplier|vendor|поставщик)/iu.test(value)) {
|
||||
return true;
|
||||
}
|
||||
return hasInventoryPurchaseStem(value) && /(?:у\s+кого|от\s+кого|где)/iu.test(value);
|
||||
@@ -21,11 +24,11 @@ function hasInventorySaleCue(text) {
|
||||
if (/(?:buyer|покупател)/iu.test(value)) {
|
||||
return true;
|
||||
}
|
||||
if (/(?:куда\s+ушла\s+позиция|куда\s+ушел\s+товар|кто\s+купил)/iu.test(value)) {
|
||||
if (/(?:куда\s+ушла\s+позиция|куда\s+ушел\s+товар|кто\s+купил|кому\s+(?:мы\s+)?впарили(?:\s+(?:это|его|товар|позицию))?)/iu.test(value)) {
|
||||
return true;
|
||||
}
|
||||
const hasDirectionCue = /(?:кому|каму|куда)/iu.test(value);
|
||||
const hasSaleVerb = /(?:продал(?:и|а|о|ы)?|продан(?:а|о|ы)?|продано|реализовал(?:и|а|о|ы)?|реализован(?:а|о|ы)?|реализовано)/iu.test(value);
|
||||
const hasSaleVerb = /(?:продал(?:и|а|о|ы)?|продан(?:а|о|ы)?|продано|реализовал(?:и|а|о|ы)?|реализован(?:а|о|ы)?|реализовано|впарил(?:и|а|о|ы)?|отгрузил(?:и|а|о|ы)?|ушло|ушел|ушла)/iu.test(value);
|
||||
if (hasDirectionCue && hasSaleVerb) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION = void 0;
|
||||
exports.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION = "assistant_runtime_contracts_v1";
|
||||
@@ -1191,6 +1191,75 @@ function isTemporalWarehousePhrase(candidate: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isLowQualityWarehouseAnchorValue(rawValue: string): boolean {
|
||||
const value = cleanupAnchorValue(rawValue)
|
||||
.toLowerCase()
|
||||
.replace(/ё/g, "е")
|
||||
.trim();
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
if (isTemporalWarehousePhrase(value) || isImplicitSelfScopeWarehouseAnchor(value)) {
|
||||
return true;
|
||||
}
|
||||
const hasQuestionOrRepairCue =
|
||||
/(?:^|[\s,.;:!?()\-])(?:что|какой|какая|какие|как|где|когда|почему|зачем|имел(?:ось|ся)\s+в\s+виду|имеется\s+в\s+виду|в\s+смысле|то\s+есть|which|what|where|when|why)(?=$|[\s,.;:!?()\-])/iu.test(
|
||||
value
|
||||
) || /[?]/u.test(rawValue);
|
||||
const hasProfanityCue =
|
||||
/(?:^|[\s,.;:!?()\-])(?:аху|оху|хуе|хуё|хуй|ебан|ебуч|бля|блять|пизд|нахуй|shit|fuck|damn)(?=$|[\s,.;:!?()\-])/iu.test(
|
||||
value
|
||||
);
|
||||
const lowQualityTokens = new Set([
|
||||
"что",
|
||||
"какой",
|
||||
"какая",
|
||||
"какие",
|
||||
"как",
|
||||
"где",
|
||||
"когда",
|
||||
"почему",
|
||||
"зачем",
|
||||
"имелось",
|
||||
"имелся",
|
||||
"имеется",
|
||||
"в",
|
||||
"виду",
|
||||
"то",
|
||||
"есть",
|
||||
"лежит",
|
||||
"лежат",
|
||||
"лежало",
|
||||
"лежали",
|
||||
"на",
|
||||
"по",
|
||||
"складе",
|
||||
"складу",
|
||||
"складом",
|
||||
"ебаном",
|
||||
"ахуеть",
|
||||
"охуеть",
|
||||
"пиздец",
|
||||
"блять",
|
||||
"бля"
|
||||
]);
|
||||
const tokens = value
|
||||
.split(/[^a-zа-я0-9]+/iu)
|
||||
.map((token) => token.trim())
|
||||
.filter(Boolean);
|
||||
if (tokens.length === 0) {
|
||||
return true;
|
||||
}
|
||||
const meaningfulTokens = tokens.filter((token) => !lowQualityTokens.has(token) && token.length > 1);
|
||||
if (meaningfulTokens.length === 0) {
|
||||
return true;
|
||||
}
|
||||
if ((hasQuestionOrRepairCue || hasProfanityCue) && meaningfulTokens.length <= 1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function normalizeSemanticAnchorCandidate(value: string): string {
|
||||
return cleanupAnchorValue(value)
|
||||
.toLowerCase()
|
||||
@@ -1236,6 +1305,7 @@ function extractInventoryWarehouseAnchor(text: string): string | undefined {
|
||||
candidate.includes("->") ||
|
||||
candidate.includes("=>") ||
|
||||
isImplicitSelfScopeWarehouseAnchor(candidate) ||
|
||||
isLowQualityWarehouseAnchorValue(candidate) ||
|
||||
normalizedCandidate.startsWith("по состоянию") ||
|
||||
isTemporalWarehousePhrase(candidate) ||
|
||||
/^(?:сейчас|на|дату|дате|остаток|остатки)$/iu.test(candidate)
|
||||
|
||||
@@ -1949,6 +1949,17 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
/(?:кому\s+(?:мы\s+)?впарили(?:\s+(?:это|его|товар|позицию))?|кому\s+в\s+итоге\s+мы\s+впарили)/iu.test(text) &&
|
||||
/(?:товар|номенклатур|sku|item|product|позици(?:я|ю|и)|продукци(?:я|ю|и))/iu.test(text)
|
||||
) {
|
||||
return {
|
||||
intent: "inventory_sale_trace_for_item",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_sale_trace_signal_detected"]
|
||||
};
|
||||
}
|
||||
|
||||
if (hasInventorySaleTraceSignalV2(text)) {
|
||||
return {
|
||||
intent: "inventory_sale_trace_for_item",
|
||||
|
||||
@@ -1978,7 +1978,14 @@ function hasExplicitPeriodWindow(filters: AddressFilterSet): boolean {
|
||||
}
|
||||
|
||||
function canAutoBroadenPeriodWindow(intent: AddressIntent, filters: AddressFilterSet): boolean {
|
||||
if (!hasExplicitPeriodWindow(filters)) {
|
||||
const hasRecoverableAsOfOnlyWindow =
|
||||
!hasExplicitPeriodWindow(filters) &&
|
||||
typeof filters.as_of_date === "string" &&
|
||||
filters.as_of_date.trim().length > 0 &&
|
||||
typeof filters.item === "string" &&
|
||||
filters.item.trim().length > 0 &&
|
||||
(intent === "inventory_purchase_provenance_for_item" || intent === "inventory_purchase_documents_for_item");
|
||||
if (!hasExplicitPeriodWindow(filters) && !hasRecoverableAsOfOnlyWindow) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
|
||||
@@ -533,6 +533,30 @@ function hasSelectedObjectInventorySignal(text: string): boolean {
|
||||
return /(?:по\s+выбранному\s+объекту|for\s+selected\s+object)/iu.test(String(text ?? ""));
|
||||
}
|
||||
|
||||
function hasSelectedObjectInlineSnapshotMetadata(text: string): boolean {
|
||||
return /(?:дата\s+строки|строка\s+от|количество\s*:|стоимость\s*:|склад\s*:|организация\s*:|\|\s*(?:склад|количество|стоимость|организация|дата\s+строки)\s*:)/iu.test(
|
||||
String(text ?? "")
|
||||
);
|
||||
}
|
||||
|
||||
function extractSelectedObjectItemFromFollowupText(text: string): string | null {
|
||||
const rawSelectedObject = toNonEmptyString(extractSelectedObjectQuotedValue(text));
|
||||
if (!rawSelectedObject) {
|
||||
return null;
|
||||
}
|
||||
const firstLine = rawSelectedObject
|
||||
.replace(/\r\n?/g, "\n")
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.find(Boolean);
|
||||
const primarySegment = String(firstLine ?? rawSelectedObject)
|
||||
.replace(/^\d+\.\s*/, "")
|
||||
.split("|")[0]
|
||||
?.trim();
|
||||
const normalized = toNonEmptyString(primarySegment);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function hasInventorySupplierFollowupCue(text: string): boolean {
|
||||
return hasInventorySupplierCue(String(text ?? ""));
|
||||
}
|
||||
@@ -800,7 +824,7 @@ function mergeFollowupFilters(
|
||||
intent === "inventory_aging_by_purchase_date")
|
||||
) {
|
||||
const inheritedItem = previousItem ?? previousAnchorItem;
|
||||
const explicitQuotedItem = toNonEmptyString(extractSelectedObjectQuotedValue(userMessage));
|
||||
const explicitQuotedItem = extractSelectedObjectItemFromFollowupText(userMessage);
|
||||
const currentItem = toNonEmptyString(merged.item);
|
||||
const shouldAdoptExplicitQuotedItem =
|
||||
Boolean(explicitQuotedItem) &&
|
||||
@@ -873,6 +897,23 @@ function mergeFollowupFilters(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
(Boolean(previousPeriodFrom) || Boolean(previousPeriodTo)) &&
|
||||
hasSelectedObjectInventorySignal(userMessage) &&
|
||||
hasSelectedObjectInlineSnapshotMetadata(userMessage) &&
|
||||
(intent === "inventory_purchase_provenance_for_item" || intent === "inventory_purchase_documents_for_item") &&
|
||||
!hasExplicitPeriodLiteral(userMessage) &&
|
||||
!hasExplicitCurrentDateHint(userMessage)
|
||||
) {
|
||||
if (previousPeriodFrom && merged.period_from !== previousPeriodFrom) {
|
||||
merged.period_from = previousPeriodFrom;
|
||||
reasons.push("period_from_from_followup_context");
|
||||
}
|
||||
if (previousPeriodTo && merged.period_to !== previousPeriodTo) {
|
||||
merged.period_to = previousPeriodTo;
|
||||
reasons.push("period_to_from_followup_context");
|
||||
}
|
||||
}
|
||||
if (
|
||||
!sameDateRequested &&
|
||||
(intent === "inventory_on_hand_as_of_date" || intent === "inventory_supplier_stock_overlap_as_of_date") &&
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
import {
|
||||
ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
type AssistantCapabilityContract,
|
||||
type AssistantTransitionClassId,
|
||||
type AssistantTransitionContract
|
||||
} from "../types/assistantRuntimeContracts";
|
||||
import type { AddressIntent } from "../types/addressQuery";
|
||||
|
||||
export const ASSISTANT_TRANSITION_CONTRACTS: readonly AssistantTransitionContract[] = [
|
||||
{
|
||||
schema_version: ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T1",
|
||||
title: "Root Query Entry",
|
||||
trigger_class: "new_root_business_question",
|
||||
required_prior_state: ["living_mode_state"],
|
||||
allowed_carryover_depth: "none",
|
||||
state_mutations: ["create_root_frame_state", "clear_selected_object_frame_state", "create_coverage_gate_state"],
|
||||
forbidden_carryover: ["stale_focus_object", "stale_object_intent"],
|
||||
expected_answer_mode: "confirmed"
|
||||
},
|
||||
{
|
||||
schema_version: ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T2",
|
||||
title: "Root Follow-Up With Date Or Scope Change",
|
||||
trigger_class: "root_followup_temporal_or_organization_shift",
|
||||
required_prior_state: ["root_frame_state"],
|
||||
allowed_carryover_depth: "root_only",
|
||||
state_mutations: ["update_root_frame_state", "run_exact_route", "refresh_coverage_gate_state"],
|
||||
forbidden_carryover: ["incompatible_selected_object_route"],
|
||||
expected_answer_mode: "confirmed"
|
||||
},
|
||||
{
|
||||
schema_version: ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T3",
|
||||
title: "Explicit Selected Object Drilldown",
|
||||
trigger_class: "explicit_selected_object_or_ui_object_selection",
|
||||
required_prior_state: ["root_frame_state"],
|
||||
allowed_carryover_depth: "object_only",
|
||||
state_mutations: ["create_selected_object_frame_state", "bind_source_result_set", "preserve_temporal_ceiling"],
|
||||
forbidden_carryover: ["unrelated_prior_focus_object"],
|
||||
expected_answer_mode: "confirmed"
|
||||
},
|
||||
{
|
||||
schema_version: ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T4",
|
||||
title: "Short Action Follow-Up On Selected Object",
|
||||
trigger_class: "short_action_followup_on_active_focus_object",
|
||||
required_prior_state: ["selected_object_frame_state"],
|
||||
allowed_carryover_depth: "object_only",
|
||||
state_mutations: ["reuse_selected_object_frame_state", "route_to_compatible_item_action"],
|
||||
forbidden_carryover: ["generic_chat_fallback", "data_scope_selection_fallback", "object_focus_reset"],
|
||||
expected_answer_mode: "confirmed"
|
||||
},
|
||||
{
|
||||
schema_version: ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T5",
|
||||
title: "Pronoun Or Compressed Object Follow-Up",
|
||||
trigger_class: "pronoun_or_compressed_reference_to_active_focus_object",
|
||||
required_prior_state: ["selected_object_frame_state"],
|
||||
allowed_carryover_depth: "object_only",
|
||||
state_mutations: ["reuse_selected_object_frame_state", "resolve_pronoun_to_focus_object"],
|
||||
forbidden_carryover: ["low_quality_object_rewrite", "semantic_noise_as_anchor"],
|
||||
expected_answer_mode: "confirmed"
|
||||
},
|
||||
{
|
||||
schema_version: ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T6",
|
||||
title: "Domain Pivot With Root-Only Carryover",
|
||||
trigger_class: "supported_domain_pivot_from_active_drilldown",
|
||||
required_prior_state: ["root_frame_state", "selected_object_frame_state"],
|
||||
allowed_carryover_depth: "root_only",
|
||||
state_mutations: ["preserve_root_frame_state", "drop_selected_object_frame_state"],
|
||||
forbidden_carryover: ["object_route_replay_into_new_domain"],
|
||||
expected_answer_mode: "confirmed"
|
||||
},
|
||||
{
|
||||
schema_version: ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T7",
|
||||
title: "Clarification Continuation",
|
||||
trigger_class: "user_resolves_missing_anchor_or_scope",
|
||||
required_prior_state: ["clarification_state"],
|
||||
allowed_carryover_depth: "full",
|
||||
state_mutations: ["resume_target_route", "update_or_clear_clarification_state"],
|
||||
forbidden_carryover: ["forget_suspended_route"],
|
||||
expected_answer_mode: "confirmed"
|
||||
},
|
||||
{
|
||||
schema_version: ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T8",
|
||||
title: "Meta Follow-Up Over Answer Object",
|
||||
trigger_class: "evaluation_comparison_or_interpretation_of_previous_answer",
|
||||
required_prior_state: ["answer_context_state", "coverage_gate_state"],
|
||||
allowed_carryover_depth: "meta_only",
|
||||
state_mutations: ["create_meta_frame_state", "reuse_answer_object_without_blind_replay"],
|
||||
forbidden_carryover: ["blind_exact_route_replay"],
|
||||
expected_answer_mode: "meta"
|
||||
},
|
||||
{
|
||||
schema_version: ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T9",
|
||||
title: "Memory Recap",
|
||||
trigger_class: "conversation_memory_recap_request",
|
||||
required_prior_state: ["answer_context_state"],
|
||||
allowed_carryover_depth: "meta_only",
|
||||
state_mutations: ["reuse_grounded_prior_answer_context"],
|
||||
forbidden_carryover: ["invented_conversation_memory"],
|
||||
expected_answer_mode: "recap"
|
||||
},
|
||||
{
|
||||
schema_version: ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_id: "T10",
|
||||
title: "Unsupported Or Blocked Boundary",
|
||||
trigger_class: "unsupported_route_or_blocked_evidence_gate",
|
||||
required_prior_state: ["coverage_gate_state"],
|
||||
allowed_carryover_depth: "none",
|
||||
state_mutations: ["emit_bounded_boundary_or_clarification"],
|
||||
forbidden_carryover: ["blocked_as_confirmed_factual_answer"],
|
||||
expected_answer_mode: "boundary"
|
||||
}
|
||||
] as const;
|
||||
|
||||
const SHARED_INVENTORY_ACCEPTANCE_FAMILIES = [
|
||||
"canonical",
|
||||
"colloquial",
|
||||
"ui_selected_object",
|
||||
"ui_selected_object_colloquial",
|
||||
"short_action_followup",
|
||||
"pronoun_followup",
|
||||
"followup_date_carryover"
|
||||
] as const;
|
||||
|
||||
const INVENTORY_ITEM_ANCHOR_RULES = [
|
||||
"no_low_quality_item_rewrite",
|
||||
"no_numeric_tail_account_poisoning",
|
||||
"no_conversational_noise_as_entity",
|
||||
"confirmed_focus_object_beats_semantic_hint"
|
||||
] as const;
|
||||
|
||||
const INVENTORY_SELECTED_OBJECT_TESTS = [
|
||||
"selected_object_memory_survives_short_followup",
|
||||
"new_explicit_selected_object_overrides_old_focus",
|
||||
"full_anchor_not_degraded_by_canonical_rewrite"
|
||||
] as const;
|
||||
|
||||
function inventoryExactCapability(input: {
|
||||
capability_id: string;
|
||||
intent_ids: AddressIntent[];
|
||||
entry_modes: AssistantCapabilityContract["entry_modes"];
|
||||
transitions: AssistantTransitionClassId[];
|
||||
requiresFocusObject: boolean;
|
||||
requiredAnchors: string[];
|
||||
resultShape: string;
|
||||
answerObjectShape: string;
|
||||
bundleReusePolicy: AssistantCapabilityContract["bundle_reuse_policy"];
|
||||
scenarioFamilies?: string[];
|
||||
}): AssistantCapabilityContract {
|
||||
return {
|
||||
schema_version: ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
capability_id: input.capability_id,
|
||||
domain_id: "inventory_stock",
|
||||
runtime_lane: "address_exact",
|
||||
intent_ids: input.intent_ids,
|
||||
entry_modes: input.entry_modes,
|
||||
supported_transition_classes: input.transitions,
|
||||
frame_compatibility: {
|
||||
root_frame: input.entry_modes.includes("root_entry") ? "optional" : "required",
|
||||
selected_object_frame: input.requiresFocusObject ? "required" : "optional",
|
||||
meta_frame: "forbidden"
|
||||
},
|
||||
required_anchors: input.requiredAnchors,
|
||||
optional_anchors: ["organization", "warehouse", "date_scope"],
|
||||
anchor_source_priority: ["explicit_user_anchor", "ui_selected_object", "selected_object_frame", "root_frame", "semantic_hint"],
|
||||
anchor_admissibility_rules: [...INVENTORY_ITEM_ANCHOR_RULES],
|
||||
organization_scope_behavior: "reuse_or_clarify",
|
||||
date_scope_behavior: "reuse",
|
||||
temporal_ceiling_policy: input.requiresFocusObject ? "respect_root_temporal_ceiling" : "must_not_expand_without_reason_code",
|
||||
root_context_compatibility: "required",
|
||||
requires_focus_object: input.requiresFocusObject,
|
||||
accepted_focus_object_kinds: input.requiresFocusObject ? ["inventory_item", "item"] : [],
|
||||
focus_object_override_policy: input.requiresFocusObject ? "explicit_new_object_wins" : "not_applicable",
|
||||
bundle_reuse_policy: input.bundleReusePolicy,
|
||||
resolver_owner: "addressIntentResolver",
|
||||
recipe_owner: "addressRecipeCatalog",
|
||||
execution_adapter: "AddressQueryService",
|
||||
result_shape: input.resultShape,
|
||||
answer_object_shape: input.answerObjectShape,
|
||||
minimum_evidence_policy: "route_specific_threshold",
|
||||
coverage_gate_behavior: "partial_or_blocked_if_evidence_insufficient",
|
||||
truth_mode_fallbacks: ["limited", "clarification_required", "unsupported"],
|
||||
blocked_reason_codes: ["missing_anchor", "route_expectation_failure", "execution_error", "insufficient_evidence"],
|
||||
clarification_triggers: ["missing_required_item_anchor", "ambiguous_organization_scope", "ambiguous_date_scope"],
|
||||
clarification_questions: ["Уточните товар, организацию или дату, чтобы не подставлять неподтвержденный anchor."],
|
||||
resume_policy: "resume_original_route_with_resolved_anchors",
|
||||
empty_match_behavior: "truthful_empty_match",
|
||||
route_expectation_failure_behavior: "blocked_route_expectation_failure",
|
||||
execution_error_behavior: "blocked_execution_error",
|
||||
required_unit_tests: input.requiresFocusObject
|
||||
? [...INVENTORY_SELECTED_OBJECT_TESTS, "limited_mode_remains_truthful"]
|
||||
: ["root_context_survives_domain_pivot_without_object_leak", "limited_mode_remains_truthful"],
|
||||
required_transition_tests: input.transitions.map((transitionId) => `transition_${transitionId}`),
|
||||
required_scenario_families: input.scenarioFamilies ?? [...SHARED_INVENTORY_ACCEPTANCE_FAMILIES]
|
||||
};
|
||||
}
|
||||
|
||||
export const INVENTORY_CAPABILITY_CONTRACTS: readonly AssistantCapabilityContract[] = [
|
||||
inventoryExactCapability({
|
||||
capability_id: "confirmed_inventory_on_hand_as_of_date",
|
||||
intent_ids: ["inventory_on_hand_as_of_date"],
|
||||
entry_modes: ["root_entry", "root_followup", "clarification_resume"],
|
||||
transitions: ["T1", "T2", "T7"],
|
||||
requiresFocusObject: false,
|
||||
requiredAnchors: [],
|
||||
resultShape: "item_list_with_quantity_cost_warehouse_organization",
|
||||
answerObjectShape: "inventory_stock_snapshot",
|
||||
bundleReusePolicy: "none",
|
||||
scenarioFamilies: ["canonical", "colloquial", "followup_date_carryover"]
|
||||
}),
|
||||
inventoryExactCapability({
|
||||
capability_id: "inventory_inventory_purchase_provenance_for_item",
|
||||
intent_ids: ["inventory_purchase_provenance_for_item"],
|
||||
entry_modes: ["selected_object_drilldown", "clarification_resume"],
|
||||
transitions: ["T3", "T4", "T5", "T7"],
|
||||
requiresFocusObject: true,
|
||||
requiredAnchors: ["item"],
|
||||
resultShape: "supplier_purchase_provenance_trace",
|
||||
answerObjectShape: "inventory_provenance_bundle",
|
||||
bundleReusePolicy: "provenance_bundle_preferred"
|
||||
}),
|
||||
inventoryExactCapability({
|
||||
capability_id: "inventory_inventory_purchase_documents_for_item",
|
||||
intent_ids: ["inventory_purchase_documents_for_item"],
|
||||
entry_modes: ["selected_object_drilldown", "clarification_resume"],
|
||||
transitions: ["T3", "T4", "T5", "T7"],
|
||||
requiresFocusObject: true,
|
||||
requiredAnchors: ["item"],
|
||||
resultShape: "purchase_document_list_for_selected_item",
|
||||
answerObjectShape: "inventory_purchase_documents_bundle",
|
||||
bundleReusePolicy: "provenance_bundle_preferred"
|
||||
}),
|
||||
inventoryExactCapability({
|
||||
capability_id: "inventory_inventory_supplier_stock_overlap_as_of_date",
|
||||
intent_ids: ["inventory_supplier_stock_overlap_as_of_date"],
|
||||
entry_modes: ["root_entry", "root_followup", "clarification_resume"],
|
||||
transitions: ["T1", "T2", "T7"],
|
||||
requiresFocusObject: false,
|
||||
requiredAnchors: ["supplier"],
|
||||
resultShape: "supplier_to_stock_item_overlap",
|
||||
answerObjectShape: "inventory_supplier_overlap",
|
||||
bundleReusePolicy: "none",
|
||||
scenarioFamilies: ["canonical", "colloquial", "followup_date_carryover"]
|
||||
}),
|
||||
inventoryExactCapability({
|
||||
capability_id: "inventory_inventory_sale_trace_for_item",
|
||||
intent_ids: ["inventory_sale_trace_for_item"],
|
||||
entry_modes: ["selected_object_drilldown", "clarification_resume"],
|
||||
transitions: ["T3", "T4", "T5", "T7"],
|
||||
requiresFocusObject: true,
|
||||
requiredAnchors: ["item"],
|
||||
resultShape: "buyer_sale_trace_for_selected_item",
|
||||
answerObjectShape: "inventory_sale_trace_bundle",
|
||||
bundleReusePolicy: "sale_trace_bundle_preferred"
|
||||
}),
|
||||
inventoryExactCapability({
|
||||
capability_id: "inventory_inventory_purchase_to_sale_chain",
|
||||
intent_ids: ["inventory_purchase_to_sale_chain"],
|
||||
entry_modes: ["selected_object_drilldown", "clarification_resume"],
|
||||
transitions: ["T3", "T4", "T5", "T7"],
|
||||
requiresFocusObject: true,
|
||||
requiredAnchors: ["item"],
|
||||
resultShape: "purchase_stock_sale_document_chain",
|
||||
answerObjectShape: "inventory_purchase_to_sale_chain",
|
||||
bundleReusePolicy: "sale_trace_bundle_preferred"
|
||||
}),
|
||||
inventoryExactCapability({
|
||||
capability_id: "inventory_inventory_aging_by_purchase_date",
|
||||
intent_ids: ["inventory_aging_by_purchase_date"],
|
||||
entry_modes: ["root_entry", "root_followup", "clarification_resume"],
|
||||
transitions: ["T1", "T2", "T6", "T7"],
|
||||
requiresFocusObject: false,
|
||||
requiredAnchors: [],
|
||||
resultShape: "oldest_first_inventory_aging_list",
|
||||
answerObjectShape: "inventory_aging_snapshot",
|
||||
bundleReusePolicy: "none",
|
||||
scenarioFamilies: ["canonical", "colloquial", "followup_date_carryover"]
|
||||
})
|
||||
] as const;
|
||||
|
||||
export function listAssistantTransitionContracts(): readonly AssistantTransitionContract[] {
|
||||
return ASSISTANT_TRANSITION_CONTRACTS;
|
||||
}
|
||||
|
||||
export function getAssistantTransitionContract(transitionId: AssistantTransitionClassId): AssistantTransitionContract | null {
|
||||
return ASSISTANT_TRANSITION_CONTRACTS.find((contract) => contract.transition_id === transitionId) ?? null;
|
||||
}
|
||||
|
||||
export function listInventoryCapabilityContracts(): readonly AssistantCapabilityContract[] {
|
||||
return INVENTORY_CAPABILITY_CONTRACTS;
|
||||
}
|
||||
|
||||
export function getAssistantCapabilityContract(capabilityId: string): AssistantCapabilityContract | null {
|
||||
return INVENTORY_CAPABILITY_CONTRACTS.find((contract) => contract.capability_id === capabilityId) ?? null;
|
||||
}
|
||||
|
||||
export function getAssistantCapabilityContractByIntent(intent: AddressIntent): AssistantCapabilityContract | null {
|
||||
return INVENTORY_CAPABILITY_CONTRACTS.find((contract) => contract.intent_ids.includes(intent)) ?? null;
|
||||
}
|
||||
@@ -4997,6 +4997,14 @@ function shouldEmitOrganizationSelectionReply(userMessage, selectedOrganization)
|
||||
if (hasSelectionCue) {
|
||||
return true;
|
||||
}
|
||||
const hasAffectiveReactionCue = /(?:^|[\s,.;:!?()\-])(?:ну|мда|ох|ах|офигеть|офигенно|ахуеть|охуеть|пиздец|пизда|нихуя|хуево|хуёво|ебать|ебан|бля|блять|fuck|shit|damn)(?=$|[\s,.;:!?()\-])/iu.test(normalized) ||
|
||||
normalized.includes("\u0430\u0445\u0443") ||
|
||||
normalized.includes("\u043e\u0445\u0443") ||
|
||||
normalized.includes("\u043f\u0438\u0437\u0434") ||
|
||||
normalized.includes("\u0431\u043b\u044f");
|
||||
if (hasAffectiveReactionCue) {
|
||||
return false;
|
||||
}
|
||||
return normalized.length <= 36 && !/[?]/.test(String(userMessage ?? ""));
|
||||
}
|
||||
function hasOperationalAdminActionRequestSignal(text) {
|
||||
|
||||
@@ -26,12 +26,12 @@ export function hasInventorySaleCue(text: string): boolean {
|
||||
if (/(?:buyer|покупател)/iu.test(value)) {
|
||||
return true;
|
||||
}
|
||||
if (/(?:куда\s+ушла\s+позиция|куда\s+ушел\s+товар|кто\s+купил)/iu.test(value)) {
|
||||
if (/(?:куда\s+ушла\s+позиция|куда\s+ушел\s+товар|кто\s+купил|кому\s+(?:мы\s+)?впарили(?:\s+(?:это|его|товар|позицию))?)/iu.test(value)) {
|
||||
return true;
|
||||
}
|
||||
const hasDirectionCue = /(?:кому|каму|куда)/iu.test(value);
|
||||
const hasSaleVerb =
|
||||
/(?:продал(?:и|а|о|ы)?|продан(?:а|о|ы)?|продано|реализовал(?:и|а|о|ы)?|реализован(?:а|о|ы)?|реализовано)/iu.test(
|
||||
/(?:продал(?:и|а|о|ы)?|продан(?:а|о|ы)?|продано|реализовал(?:и|а|о|ы)?|реализован(?:а|о|ы)?|реализовано|впарил(?:и|а|о|ы)?|отгрузил(?:и|а|о|ы)?|ушло|ушел|ушла)/iu.test(
|
||||
value
|
||||
);
|
||||
if (hasDirectionCue && hasSaleVerb) {
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
import type { AddressIntent } from "./addressQuery";
|
||||
|
||||
export const ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION = "assistant_runtime_contracts_v1" as const;
|
||||
|
||||
export type AssistantLivingMode = "address_data" | "assistant_data_scope" | "chat" | "meta_followup" | "clarification";
|
||||
export type AssistantFrameStatus = "active" | "suspended" | "closed" | "blocked";
|
||||
export type AssistantTransitionClassId = "T1" | "T2" | "T3" | "T4" | "T5" | "T6" | "T7" | "T8" | "T9" | "T10";
|
||||
export type AssistantStateSlice =
|
||||
| "living_mode_state"
|
||||
| "root_frame_state"
|
||||
| "selected_object_frame_state"
|
||||
| "meta_frame_state"
|
||||
| "clarification_state"
|
||||
| "coverage_gate_state"
|
||||
| "answer_context_state";
|
||||
export type AssistantCarryoverDepth = "full" | "root_only" | "object_only" | "meta_only" | "none";
|
||||
export type AssistantAnswerMode = "confirmed" | "limited" | "clarification" | "boundary" | "meta" | "recap";
|
||||
|
||||
export interface AssistantDateScopeState {
|
||||
as_of_date: string | null;
|
||||
period_from: string | null;
|
||||
period_to: string | null;
|
||||
}
|
||||
|
||||
export interface AssistantRootFrameState {
|
||||
domain_id: string | null;
|
||||
root_route_id: string | null;
|
||||
organization_scope: string | null;
|
||||
date_scope: AssistantDateScopeState;
|
||||
root_result_set_id: string | null;
|
||||
root_answer_object_ref: string | null;
|
||||
frame_status: AssistantFrameStatus;
|
||||
}
|
||||
|
||||
export interface AssistantSelectedObjectFrameState {
|
||||
focus_object_ref: string | null;
|
||||
focus_object_kind: string | null;
|
||||
source_result_set_id: string | null;
|
||||
compatible_route_family: string[];
|
||||
provenance_bundle_ref: string | null;
|
||||
temporal_ceiling: AssistantDateScopeState;
|
||||
frame_status: AssistantFrameStatus;
|
||||
}
|
||||
|
||||
export interface AssistantMetaFrameState {
|
||||
source_answer_object_ref: string | null;
|
||||
meta_question_kind: "evaluation" | "comparison" | "memory_recap" | "boundary_explanation" | "answer_interpretation" | null;
|
||||
source_gate_status: AssistantCoverageGateState["coverage_status"] | null;
|
||||
meta_truth_mode: AssistantCoverageGateState["truth_mode"] | null;
|
||||
}
|
||||
|
||||
export interface AssistantClarificationState {
|
||||
clarification_kind: string | null;
|
||||
missing_anchors: string[];
|
||||
candidate_scopes: string[];
|
||||
resume_target_route: string | null;
|
||||
resume_target_frame: AssistantStateSlice | null;
|
||||
}
|
||||
|
||||
export interface AssistantCoverageGateState {
|
||||
coverage_status: "full" | "partial" | "blocked";
|
||||
evidence_grade: "none" | "weak" | "medium" | "strong";
|
||||
grounding_status: "grounded" | "partial" | "route_mismatch_blocked" | "no_grounded_answer" | "unsupported";
|
||||
truth_mode: "confirmed" | "limited" | "clarification_required" | "unsupported";
|
||||
carryover_eligibility: AssistantCarryoverDepth;
|
||||
reason_codes: string[];
|
||||
}
|
||||
|
||||
export interface AssistantSessionAggregateState {
|
||||
schema_version: typeof ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION;
|
||||
living_mode_state: {
|
||||
living_mode: AssistantLivingMode;
|
||||
mode_reason: string | null;
|
||||
mode_source: "router" | "transition" | "clarification" | "manual" | null;
|
||||
mode_entry_turn_id: string | null;
|
||||
};
|
||||
root_frame_state: AssistantRootFrameState | null;
|
||||
selected_object_frame_state: AssistantSelectedObjectFrameState | null;
|
||||
meta_frame_state: AssistantMetaFrameState | null;
|
||||
clarification_state: AssistantClarificationState | null;
|
||||
coverage_gate_state: AssistantCoverageGateState | null;
|
||||
answer_context_state: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
export interface AssistantTransitionContract {
|
||||
schema_version: typeof ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION;
|
||||
transition_id: AssistantTransitionClassId;
|
||||
title: string;
|
||||
trigger_class: string;
|
||||
required_prior_state: AssistantStateSlice[];
|
||||
allowed_carryover_depth: AssistantCarryoverDepth;
|
||||
state_mutations: string[];
|
||||
forbidden_carryover: string[];
|
||||
expected_answer_mode: AssistantAnswerMode;
|
||||
}
|
||||
|
||||
export type AssistantCapabilityEntryMode =
|
||||
| "root_entry"
|
||||
| "root_followup"
|
||||
| "selected_object_drilldown"
|
||||
| "meta_reuse"
|
||||
| "clarification_resume";
|
||||
export type AssistantRuntimeLane = "address_exact" | "assistant_data_scope" | "chat" | "meta";
|
||||
export type AssistantFrameRequirement = "required" | "optional" | "forbidden";
|
||||
export type AssistantScopeBehavior = "create" | "reuse" | "reuse_or_clarify" | "narrow" | "reject" | "none";
|
||||
export type AssistantTemporalCeilingPolicy = "none" | "respect_root_temporal_ceiling" | "must_not_expand_without_reason_code";
|
||||
export type AssistantBundleReusePolicy = "none" | "provenance_bundle_preferred" | "sale_trace_bundle_preferred";
|
||||
export type AssistantCoverageGateBehavior = "full_required" | "partial_or_blocked_if_evidence_insufficient";
|
||||
export type AssistantTruthFallback = "limited" | "clarification_required" | "unsupported";
|
||||
|
||||
export interface AssistantCapabilityContract {
|
||||
schema_version: typeof ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION;
|
||||
capability_id: string;
|
||||
domain_id: string;
|
||||
runtime_lane: AssistantRuntimeLane;
|
||||
intent_ids: AddressIntent[];
|
||||
entry_modes: AssistantCapabilityEntryMode[];
|
||||
supported_transition_classes: AssistantTransitionClassId[];
|
||||
frame_compatibility: {
|
||||
root_frame: AssistantFrameRequirement;
|
||||
selected_object_frame: AssistantFrameRequirement;
|
||||
meta_frame: AssistantFrameRequirement;
|
||||
};
|
||||
required_anchors: string[];
|
||||
optional_anchors: string[];
|
||||
anchor_source_priority: string[];
|
||||
anchor_admissibility_rules: string[];
|
||||
organization_scope_behavior: AssistantScopeBehavior;
|
||||
date_scope_behavior: AssistantScopeBehavior;
|
||||
temporal_ceiling_policy: AssistantTemporalCeilingPolicy;
|
||||
root_context_compatibility: "required" | "optional" | "not_applicable";
|
||||
requires_focus_object: boolean;
|
||||
accepted_focus_object_kinds: string[];
|
||||
focus_object_override_policy: "explicit_new_object_wins" | "preserve_existing" | "not_applicable";
|
||||
bundle_reuse_policy: AssistantBundleReusePolicy;
|
||||
resolver_owner: string;
|
||||
recipe_owner: string;
|
||||
execution_adapter: string;
|
||||
result_shape: string;
|
||||
answer_object_shape: string;
|
||||
minimum_evidence_policy: string;
|
||||
coverage_gate_behavior: AssistantCoverageGateBehavior;
|
||||
truth_mode_fallbacks: AssistantTruthFallback[];
|
||||
blocked_reason_codes: string[];
|
||||
clarification_triggers: string[];
|
||||
clarification_questions: string[];
|
||||
resume_policy: string;
|
||||
empty_match_behavior: string;
|
||||
route_expectation_failure_behavior: string;
|
||||
execution_error_behavior: string;
|
||||
required_unit_tests: string[];
|
||||
required_transition_tests: string[];
|
||||
required_scenario_families: string[];
|
||||
}
|
||||
@@ -47,4 +47,21 @@ describe("inventory warehouse anchor extraction", () => {
|
||||
expect(result.semantic_frame?.date_scope_kind).toBe("implicit_current");
|
||||
expect(result.semantic_frame?.date_basis_hint).toBe("implicit_current_snapshot");
|
||||
});
|
||||
it("does not materialize profanity tail as warehouse anchor in slang stock query", () => {
|
||||
const filters = extractAddressFilters(
|
||||
"ассистент, рассказывай что нам на складе ебаном лежит",
|
||||
"inventory_on_hand_as_of_date"
|
||||
).extracted_filters;
|
||||
|
||||
expect(filters.warehouse).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not materialize repair phrasing as warehouse anchor in stock follow-up", () => {
|
||||
const filters = extractAddressFilters(
|
||||
"остатки на складе какие имелось в виду",
|
||||
"inventory_on_hand_as_of_date"
|
||||
).extracted_filters;
|
||||
|
||||
expect(filters.warehouse).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5038,6 +5038,11 @@ it("routes old purchase residue questions to aging-by-purchase-date", () => {
|
||||
expect(result.intent).toBe("inventory_sale_trace_for_item");
|
||||
});
|
||||
|
||||
it("routes colloquial buyer wording with 'впарили' to inventory sale trace intent", () => {
|
||||
const result = resolveAddressIntent("Кому мы впарили этот товар Шкаф картотечный?");
|
||||
expect(result.intent).toBe("inventory_sale_trace_for_item");
|
||||
});
|
||||
|
||||
it("keeps inventory provenance wording out of inventory-on-hand routing", () => {
|
||||
const result = resolveAddressIntent("От кого куплен товар Шкаф картоотечный и когда был куплен?");
|
||||
expect(result.intent).toBe("inventory_purchase_provenance_for_item");
|
||||
|
||||
@@ -1064,4 +1064,65 @@ describe("assistant living chat mode", () => {
|
||||
expect(addressQueryService.tryHandle).toHaveBeenCalledTimes(1);
|
||||
expect(chatClient.chat).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
it("does not treat short emotional reaction as organization-selection confirmation", async () => {
|
||||
const normalizer = {
|
||||
normalize: vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
trace_id: "norm-chat-affective-reaction",
|
||||
prompt_version: "normalizer_v2_0_2",
|
||||
schema_version: "v2_0_2",
|
||||
normalized: null,
|
||||
validation: { passed: false, errors: ["mock"] },
|
||||
route_hint_summary: null,
|
||||
raw_model_output: {},
|
||||
usage: { input_tokens: 0, output_tokens: 0, total_tokens: 0 },
|
||||
latency_ms: 1,
|
||||
request_count_for_case: 1
|
||||
})
|
||||
} as any;
|
||||
|
||||
const sessions = new AssistantSessionStore();
|
||||
const sessionId = "asst-living-chat-affective-reaction";
|
||||
sessions.ensureSession(sessionId);
|
||||
sessions.appendItem(sessionId, {
|
||||
message_id: "msg-seed-selected-org",
|
||||
session_id: sessionId,
|
||||
role: "assistant",
|
||||
text: "Отлично, фиксирую рабочую организацию: ООО Альтернатива Плюс.",
|
||||
reply_type: "factual_with_explanation",
|
||||
created_at: new Date().toISOString(),
|
||||
trace_id: "chat-seed-selected-org",
|
||||
debug: {
|
||||
assistant_known_organizations: ["ООО Альтернатива Плюс", "ООО Лайсвуд"],
|
||||
assistant_selected_organization: "ООО Альтернатива Плюс",
|
||||
assistant_active_organization: "ООО Альтернатива Плюс"
|
||||
}
|
||||
} as any);
|
||||
|
||||
const addressQueryService = {
|
||||
tryHandle: vi.fn().mockResolvedValue({ handled: false })
|
||||
} as any;
|
||||
const chatClient = {
|
||||
chat: vi.fn().mockResolvedValue({
|
||||
raw: { id: "chat-affective-reaction" },
|
||||
outputText: "Понимаю, это выглядит неприятно. Давай разберем следующий шаг.",
|
||||
usage: { input_tokens: 0, output_tokens: 0, total_tokens: 0 }
|
||||
})
|
||||
} as any;
|
||||
|
||||
const service = new AssistantService(normalizer as any, sessions, undefined as any, undefined as any, addressQueryService, chatClient);
|
||||
const response = await service.handleMessage({
|
||||
session_id: sessionId,
|
||||
user_message: "ну ахуеть",
|
||||
llmProvider: "local",
|
||||
model: "qwen2.5",
|
||||
useMock: false
|
||||
} as any);
|
||||
|
||||
expect(response.ok).toBe(true);
|
||||
expect(response.reply_type).toBe("factual_with_explanation");
|
||||
expect(response.debug?.living_chat_response_source).not.toBe("deterministic_data_scope_selection_contract");
|
||||
expect(String(response.assistant_reply)).not.toContain("фиксирую рабочую организацию");
|
||||
expect(chatClient.chat).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveAddressCapabilityRouteDecision } from "../src/services/addressCapabilityPolicy";
|
||||
import {
|
||||
getAssistantCapabilityContract,
|
||||
getAssistantCapabilityContractByIntent,
|
||||
getAssistantTransitionContract,
|
||||
listAssistantTransitionContracts,
|
||||
listInventoryCapabilityContracts
|
||||
} from "../src/services/assistantRuntimeContractRegistry";
|
||||
|
||||
describe("assistant runtime contract registry", () => {
|
||||
it("declares the architecture turnaround transition set T1-T10", () => {
|
||||
const transitions = listAssistantTransitionContracts();
|
||||
expect(transitions.map((item) => item.transition_id)).toEqual(["T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10"]);
|
||||
|
||||
const ids = new Set(transitions.map((item) => item.transition_id));
|
||||
expect(ids.size).toBe(10);
|
||||
expect(transitions.every((item) => item.schema_version === "assistant_runtime_contracts_v1")).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps selected-object action follow-ups object-scoped instead of generic-chat scoped", () => {
|
||||
const transition = getAssistantTransitionContract("T4");
|
||||
expect(transition).not.toBeNull();
|
||||
expect(transition?.required_prior_state).toContain("selected_object_frame_state");
|
||||
expect(transition?.allowed_carryover_depth).toBe("object_only");
|
||||
expect(transition?.forbidden_carryover).toContain("generic_chat_fallback");
|
||||
expect(transition?.forbidden_carryover).toContain("object_focus_reset");
|
||||
});
|
||||
|
||||
it("declares meta follow-up as answer-object reuse, not blind exact-route replay", () => {
|
||||
const transition = getAssistantTransitionContract("T8");
|
||||
expect(transition).not.toBeNull();
|
||||
expect(transition?.required_prior_state).toEqual(["answer_context_state", "coverage_gate_state"]);
|
||||
expect(transition?.allowed_carryover_depth).toBe("meta_only");
|
||||
expect(transition?.state_mutations).toContain("reuse_answer_object_without_blind_replay");
|
||||
expect(transition?.forbidden_carryover).toContain("blind_exact_route_replay");
|
||||
expect(transition?.expected_answer_mode).toBe("meta");
|
||||
});
|
||||
|
||||
it("keeps pilot inventory capability ids aligned with the current address capability policy", () => {
|
||||
for (const contract of listInventoryCapabilityContracts()) {
|
||||
for (const intent of contract.intent_ids) {
|
||||
const decision = resolveAddressCapabilityRouteDecision(intent);
|
||||
expect(decision.capability_id).toBe(contract.capability_id);
|
||||
expect(decision.capability_route_mode).toBe("exact");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("declares root inventory snapshot as root-capable and focus-object-free", () => {
|
||||
const contract = getAssistantCapabilityContract("confirmed_inventory_on_hand_as_of_date");
|
||||
expect(contract).not.toBeNull();
|
||||
expect(contract?.entry_modes).toEqual(["root_entry", "root_followup", "clarification_resume"]);
|
||||
expect(contract?.supported_transition_classes).toEqual(["T1", "T2", "T7"]);
|
||||
expect(contract?.requires_focus_object).toBe(false);
|
||||
expect(contract?.result_shape).toBe("item_list_with_quantity_cost_warehouse_organization");
|
||||
expect(contract?.required_scenario_families).toContain("colloquial");
|
||||
});
|
||||
|
||||
it("declares selected-item provenance as focus-object and bundle-aware", () => {
|
||||
const contract = getAssistantCapabilityContractByIntent("inventory_purchase_provenance_for_item");
|
||||
expect(contract?.capability_id).toBe("inventory_inventory_purchase_provenance_for_item");
|
||||
expect(contract?.requires_focus_object).toBe(true);
|
||||
expect(contract?.accepted_focus_object_kinds).toEqual(["inventory_item", "item"]);
|
||||
expect(contract?.supported_transition_classes).toEqual(["T3", "T4", "T5", "T7"]);
|
||||
expect(contract?.required_anchors).toEqual(["item"]);
|
||||
expect(contract?.bundle_reuse_policy).toBe("provenance_bundle_preferred");
|
||||
expect(contract?.anchor_admissibility_rules).toContain("confirmed_focus_object_beats_semantic_hint");
|
||||
expect(contract?.required_scenario_families).toContain("ui_selected_object_colloquial");
|
||||
expect(contract?.required_scenario_families).toContain("pronoun_followup");
|
||||
});
|
||||
|
||||
it("keeps truth semantics outside answer wording for every pilot inventory capability", () => {
|
||||
for (const contract of listInventoryCapabilityContracts()) {
|
||||
expect(contract.coverage_gate_behavior).toBe("partial_or_blocked_if_evidence_insufficient");
|
||||
expect(contract.truth_mode_fallbacks).toEqual(["limited", "clarification_required", "unsupported"]);
|
||||
expect(contract.blocked_reason_codes).toEqual(
|
||||
expect.arrayContaining(["missing_anchor", "route_expectation_failure", "execution_error", "insufficient_evidence"])
|
||||
);
|
||||
expect(contract.route_expectation_failure_behavior).toBe("blocked_route_expectation_failure");
|
||||
expect(contract.execution_error_behavior).toBe("blocked_execution_error");
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user