"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AssistantService = void 0; exports.evaluateCoverageForTests = evaluateCoverageForTests; exports.extractSubjectTokensForTests = extractSubjectTokensForTests; exports.resolveAssistantOrchestrationDecision = resolveAssistantOrchestrationDecision; exports.resolveSessionOrganizationScopeContextForTests = resolveSessionOrganizationScopeContextForTests; exports.extractOrganizationFactsFromRowsForTests = extractOrganizationFactsFromRowsForTests; exports.resolveOrganizationNamesByRefsForTests = resolveOrganizationNamesByRefsForTests; exports.resolveLivingAssistantModeDecision = resolveLivingAssistantModeDecision; // @ts-nocheck const nanoid_1 = __importStar(require("nanoid")); const config_1 = __importStar(require("../config")); const log_1 = __importStar(require("../utils/log")); const answerComposer_1 = __importStar(require("./answerComposer")); const assistantDataLayer_1 = __importStar(require("./assistantDataLayer")); const assistantSessionLogger_1 = __importStar(require("./assistantSessionLogger")); const investigationState_1 = __importStar(require("./investigationState")); const retrievalResultNormalizer_1 = __importStar(require("./retrievalResultNormalizer")); const addressQueryService_1 = __importStar(require("./addressQueryService")); const addressQueryClassifier_1 = __importStar(require("./addressQueryClassifier")); const addressIntentResolver_1 = __importStar(require("./addressIntentResolver")); const addressFilterExtractor_1 = __importStar(require("./addressFilterExtractor")); const predecomposeContract_1 = __importStar(require("./address_runtime/predecomposeContract")); const openaiResponsesClient_1 = __importStar(require("./openaiResponsesClient")); const addressMcpClient_1 = __importStar(require("./addressMcpClient")); const capabilitiesRegistry_1 = __importStar(require("./capabilitiesRegistry")); const assistantCanon_1 = __importStar(require("./assistantCanon")); const assistantAddressAttemptRuntimeAdapter_1 = __importStar(require("./assistantAddressAttemptRuntimeAdapter")); const assistantCoverageGrounding_1 = __importStar(require("./assistantCoverageGrounding")); const assistantDeepTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnAttemptRuntimeAdapter")); const assistantOrganizationScopeRuntimeAdapter_1 = __importStar(require("./assistantOrganizationScopeRuntimeAdapter")); const assistantTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantTurnAttemptRuntimeAdapter")); const assistantTurnRuntimeDepsAdapter_1 = __importStar(require("./assistantTurnRuntimeDepsAdapter")); const assistantTurnRuntimeInputBuilder_1 = __importStar(require("./assistantTurnRuntimeInputBuilder")); const assistantUserTurnBootstrapRuntimeAdapter_1 = __importStar(require("./assistantUserTurnBootstrapRuntimeAdapter")); const assistantQueryPlanning_1 = __importStar(require("./assistantQueryPlanning")); const iconv_lite_1 = __importDefault(require("iconv-lite")); const DATA_SCOPE_CACHE_TTL_MS = 60_000; const dataScopeProbeCache = new Map(); function retrievalSummaryForRoute(route) { if (route === "store_canonical") return "Canonical accounting data path selected."; if (route === "store_feature_risk") return "Risk/control profile path selected."; if (route === "hybrid_store_plus_live") return "Hybrid chain analysis path selected."; if (route === "live_mcp_drilldown") return "Live drilldown path selected."; if (route === "batch_refresh_then_store") return "Heavy analytical batch path selected."; return "Route selected."; } function mapNoRouteReason(reason) { if (reason === "out_of_scope") return "Fragment out of scope."; if (reason === "insufficient_specificity") return "Needs clarification."; if (reason === "missing_mapping") return "Route mapping is missing."; if (reason === "unsupported_fragment_type") return "Fragment type unsupported."; return "No-route decision."; } function extractFragments(normalized) { if (!normalized || typeof normalized !== "object") { return []; } const source = normalized; return Array.isArray(source.fragments) ? source.fragments : []; } function hasExplicitPeriodAnchorFromNormalized(normalized) { const fragments = extractFragments(normalized); const explicitPeriodPattern = /(?:\b20\d{2}(?:[-./](?:0?[1-9]|1[0-2]))?(?:[-./](?:0?[1-9]|[12]\d|3[01]))?\b|\b(?:0?[1-9]|[12]\d|3[01])[./-](?:0?[1-9]|1[0-2])[./-](?:\d{2}|\d{4})\b|\b(?:январ[ьяе]|феврал[ьяе]|март[ае]?|апрел[ьяе]|ма[йея]|июн[ьяе]|июл[ьяе]|август[ае]?|сентябр[ьяе]|октябр[ьяе]|ноябр[ьяе]|декабр[ьяе]|january|february|march|april|may|june|july|august|september|october|november|december)\b)/i; for (const item of fragments) { if (!item || typeof item !== "object") { continue; } const fragment = item; const timeScope = fragment.time_scope && typeof fragment.time_scope === "object" ? fragment.time_scope : null; if (timeScope) { const type = String(timeScope.type ?? "").trim().toLowerCase(); const value = String(timeScope.value ?? "").trim(); const confidence = String(timeScope.confidence ?? "").trim().toLowerCase(); if ((type === "explicit" || type === "range") && value.length > 0 && confidence !== "low") { return true; } } const rawText = `${typeof fragment.raw_fragment_text === "string" ? fragment.raw_fragment_text : ""} ${typeof fragment.normalized_fragment_text === "string" ? fragment.normalized_fragment_text : ""}`; if (explicitPeriodPattern.test(rawText)) { return true; } } return false; } function normalizeIsoDate(value) { if (typeof value !== "string") { return null; } const trimmed = value.trim(); const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); if (!match) { return null; } const year = Number(match[1]); const month = Number(match[2]); const day = Number(match[3]); if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { return null; } const candidate = new Date(Date.UTC(year, month - 1, day)); if (candidate.getUTCFullYear() !== year || candidate.getUTCMonth() + 1 !== month || candidate.getUTCDate() !== day) { return null; } return `${match[1]}-${match[2]}-${match[3]}`; } function resolveRuntimeAnalysisContext(context) { const input = context && typeof context === "object" ? context : {}; const analysis = input.analysis_context && typeof input.analysis_context === "object" ? input.analysis_context : {}; const asOfDate = normalizeIsoDate(analysis.as_of_date) ?? normalizeIsoDate(input.period_hint); const periodFrom = normalizeIsoDate(analysis.period_from); const periodTo = normalizeIsoDate(analysis.period_to); const sourceRaw = typeof analysis.source === "string" ? analysis.source.trim() : ""; const source = sourceRaw || (asOfDate ? "period_hint_compat" : null); const snapshotModeRaw = typeof analysis.snapshot_mode === "string" ? analysis.snapshot_mode.trim() : ""; const snapshotMode = snapshotModeRaw === "force_snapshot" || snapshotModeRaw === "force_live" ? snapshotModeRaw : "auto"; return { as_of_date: asOfDate, period_from: periodFrom, period_to: periodTo, source, snapshot_mode: snapshotMode, active: Boolean(asOfDate || (periodFrom && periodTo)) }; } function extractExecutionState(normalized) { const fragments = extractFragments(normalized); return fragments.map((item) => { if (!item || typeof item !== "object") { return {}; } const fragment = item; return { fragment_id: fragment.fragment_id ?? null, execution_readiness: fragment.execution_readiness ?? null, route_status: fragment.route_status ?? null, no_route_reason: fragment.no_route_reason ?? null }; }); } function collectBusinessScopesFromNormalized(normalized) { const scopes = []; for (const item of extractFragments(normalized)) { if (!item || typeof item !== "object") { continue; } const scope = String(item.business_scope ?? "").trim(); if (scope) { scopes.push(scope); } } return Array.from(new Set(scopes)); } function hasJuly2020SnapshotSignal(userMessage, companyAnchors) { const lower = String(userMessage ?? "").toLowerCase(); if (/(?:\b2020[-/.]0?7\b|\bиюл[ьяе]?\b(?:\s+20\d{2})?|\bjuly\b(?:\s+20\d{2})?)/i.test(lower)) { return true; } const periods = Array.isArray(companyAnchors?.periods) ? companyAnchors.periods : []; const dates = Array.isArray(companyAnchors?.dates) ? companyAnchors.dates : []; return [...periods, ...dates].some((item) => /2020[-/.]0?7|июл|july/i.test(String(item ?? "").toLowerCase())); } function hasP0DomainSignal(userMessage, companyAnchors) { if (inferP0DomainFromMessage(userMessage)) { return true; } const accounts = Array.isArray(companyAnchors?.accounts) ? companyAnchors.accounts : []; if (accounts.some((item) => /^(?:01|02|08|19|20|21|23|25|26|28|29|44|51|60|62|68|76|97)(?:\.|$)/.test(String(item ?? "").trim()))) { return true; } return /(?:ндс|vat|рбп|deferred|амортиз|supplier|customer|settlement|month\s*close|закрыти[ея]\s+месяц|поставщ|покупат)/i.test(String(userMessage ?? "").toLowerCase()); } function resolveBusinessScopeAlignment(input) { const rawScopes = collectBusinessScopesFromNormalized(input.normalized); const needsCompanyGrounding = hasJuly2020SnapshotSignal(input.userMessage, input.companyAnchors) && hasP0DomainSignal(input.userMessage, input.companyAnchors); const reasons = []; if (needsCompanyGrounding) { reasons.push("july_2020_snapshot_p0_signal"); } if (!input.routeSummary || input.routeSummary.mode !== "deterministic_v2" || !needsCompanyGrounding) { return { business_scope_raw: rawScopes, business_scope_resolved: rawScopes, company_grounding_applied: false, scope_resolution_reason: reasons, route_summary_resolved: input.routeSummary }; } let changed = false; const decisions = input.routeSummary.decisions.map((decision) => { const scopeValue = String(decision.business_scope ?? "").trim(); if (scopeValue !== "generic_accounting" && scopeValue !== "unclear") { return decision; } changed = true; return { ...decision, business_scope: "company_specific_accounting" }; }); const resolvedSummary = changed ? { ...input.routeSummary, decisions } : input.routeSummary; const resolvedScopes = changed ? Array.from(new Set(decisions.map((decision) => String(decision.business_scope ?? "").trim()).filter(Boolean))) : rawScopes; return { business_scope_raw: rawScopes, business_scope_resolved: resolvedScopes, company_grounding_applied: changed, scope_resolution_reason: changed ? [...reasons, "generic_or_unclear_to_company_specific_override"] : reasons, route_summary_resolved: resolvedSummary }; } function isJuly2020TemporalResolved(temporalGuard) { if (!temporalGuard || typeof temporalGuard !== "object") { return false; } const resolvedAnchor = String(temporalGuard.resolved_time_anchor ?? "").trim(); if (/^2020-07(?:-\d{2})?$/.test(resolvedAnchor)) { return true; } const effective = temporalGuard.effective_primary_period && typeof temporalGuard.effective_primary_period === "object" ? temporalGuard.effective_primary_period : null; if (effective) { const from = String(effective.from ?? "").trim(); const to = String(effective.to ?? "").trim(); if (/^2020-07-\d{2}$/.test(from) && /^2020-07-\d{2}$/.test(to)) { return true; } } const resolvedPrimary = temporalGuard.resolved_primary_period && typeof temporalGuard.resolved_primary_period === "object" ? temporalGuard.resolved_primary_period : null; if (!resolvedPrimary) { return false; } const from = String(resolvedPrimary.from ?? "").trim(); const to = String(resolvedPrimary.to ?? "").trim(); return /^2020-07-\d{2}$/.test(from) && /^2020-07-\d{2}$/.test(to); } function hasP0ClaimSignal(claimType, focusDomainHint) { const claim = String(claimType ?? "").trim(); if (claim === "prove_settlement_closure_state" || claim === "prove_advance_offset_state" || claim === "prove_vat_chain_completeness" || claim === "prove_month_close_state" || claim === "prove_rbp_tail_state" || claim === "prove_fixed_asset_amortization_coverage") { return true; } return (focusDomainHint === "settlements_60_62" || focusDomainHint === "vat_document_register_book" || focusDomainHint === "month_close_costs_20_44" || focusDomainHint === "fixed_asset_amortization"); } function hasSettlementScopeSignal(input) { const claim = String(input.claimType ?? "").trim(); const domain = String(input.focusDomainHint ?? "").trim(); if (claim === "prove_settlement_closure_state" || claim === "prove_advance_offset_state" || domain === "settlements_60_62") { return true; } if (Boolean(input.followupApplied) && domain === "settlements_60_62") { return true; } const lower = String(input.userMessage ?? "").toLowerCase(); if (/(?:60(?:\\.\\d{2})?|62(?:\\.\\d{2})?|76(?:\\.\\d{2})?|оплат|расч[её]т|зач[её]т|аванс|долг|хвост|supplier|customer|settlement|payable|receivable|поставщ|покупат)/i.test(lower)) { return true; } const accounts = Array.isArray(input.companyAnchors?.accounts) ? input.companyAnchors.accounts : []; return accounts.some((item) => /^(?:51|60|62|76)(?:\\.|$)/.test(String(item ?? "").trim())); } function resolveBusinessScopeFromLiveContext(input) { const current = input.current; const routeSummary = current?.route_summary_resolved; const julyResolved = isJuly2020TemporalResolved(input.temporalGuard); const p0Signal = hasP0ClaimSignal(input.claimType, input.focusDomainHint); const settlementScopeSignal = hasSettlementScopeSignal({ userMessage: input.userMessage, companyAnchors: input.companyAnchors, claimType: input.claimType, focusDomainHint: input.focusDomainHint, followupApplied: input.followupApplied }); const shouldRecoverScope = p0Signal && (julyResolved || settlementScopeSignal); if (!shouldRecoverScope) { return current; } const reasons = Array.isArray(current.scope_resolution_reason) ? [...current.scope_resolution_reason] : []; if (julyResolved && !reasons.includes("temporal_claim_bound_company_scope_recovery")) { reasons.push("temporal_claim_bound_company_scope_recovery"); } if (settlementScopeSignal && !reasons.includes("settlement_claim_company_scope_recovery")) { reasons.push("settlement_claim_company_scope_recovery"); } const currentScopes = Array.isArray(current.business_scope_resolved) ? current.business_scope_resolved : []; let changed = false; const normalizedScopes = currentScopes .map((item) => String(item ?? "").trim()) .filter(Boolean) .map((item) => { if (item === "generic_accounting" || item === "unclear") { changed = true; return "company_specific_accounting"; } return item; }); if (!normalizedScopes.includes("company_specific_accounting")) { normalizedScopes.push("company_specific_accounting"); changed = true; } let routeSummaryResolved = routeSummary; if (routeSummary && routeSummary.mode === "deterministic_v2" && Array.isArray(routeSummary.decisions)) { const decisions = routeSummary.decisions.map((decision) => { const scopeValue = String(decision.business_scope ?? "").trim(); if (scopeValue !== "generic_accounting" && scopeValue !== "unclear") { return decision; } changed = true; return { ...decision, business_scope: "company_specific_accounting" }; }); routeSummaryResolved = changed ? { ...routeSummary, decisions } : routeSummary; } return { ...current, business_scope_resolved: Array.from(new Set(normalizedScopes)), company_grounding_applied: current.company_grounding_applied || changed, scope_resolution_reason: reasons, route_summary_resolved: routeSummaryResolved }; } function escapeRegex(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function enrichFragmentTextWithHints(fragment, text) { const baseText = String(text ?? "").trim(); const accountHints = Array.isArray(fragment.account_hints) ? Array.from(new Set(fragment.account_hints .map((item) => String(item ?? "").trim()) .filter((item) => item.length > 0))) : []; if (accountHints.length === 0) { return baseText; } const hasAccountInText = accountHints.some((account) => new RegExp(`\\b${escapeRegex(account)}\\b`, "i").test(baseText)); if (hasAccountInText) { return baseText; } return `${baseText}, по счету ${accountHints.join(", ")}`; } function fragmentTextById(normalized) { return (0, assistantQueryPlanning_1.buildFragmentTextById)(extractFragments(normalized)); } function extractDiscardedIntentSegments(normalized) { if (!normalized || typeof normalized !== "object") { return []; } const source = normalized; if (!Array.isArray(source.discarded_fragments)) { return []; } return source.discarded_fragments .map((item) => { if (!item || typeof item !== "object") { return null; } const value = item; const text = typeof value.raw_fragment_text === "string" ? value.raw_fragment_text.trim() : ""; return text || null; }) .filter((item) => Boolean(item)); } function collectDateSpans(text) { const spans = []; const datePatterns = [ /\b20\d{2}(?:[-/.](?:0?[1-9]|1[0-2]))(?:[-/.](?:0?[1-9]|[12]\d|3[01]))?\b/g, /\b(?:0?[1-9]|[12]\d|3[01])[./-](?:0?[1-9]|1[0-2])[./-](?:\d{2}|\d{4})\b/g, /\b(?:0?[1-9]|[12]\d|3[01])\s+(?:январ[ьяе]|феврал[ьяе]|март[ае]?|апрел[ьяе]|ма[йея]|июн[ьяе]?|июл[ьяе]?|август[ае]?|сентябр[ьяе]?|октябр[ьяе]?|ноябр[ьяе]?|декабр[ьяе]?|january|february|march|april|may|june|july|august|september|october|november|december)(?:\s+20\d{2})?\b/giu ]; for (const datePattern of datePatterns) { let match = null; while ((match = datePattern.exec(text)) !== null) { spans.push({ start: match.index, end: match.index + match[0].length }); } } return spans; } function collectContractSpans(text) { const spans = []; const contractPatterns = [ /(?:\b(?:договор(?:а|у|ом|е)?|contract)\b[^\r\n]{0,24}(?:№|#|n|no\.?)\s*[a-zа-я0-9][a-zа-я0-9/_-]{1,})/giu, /(?:№|#)\s*[a-zа-я0-9_-]{1,10}\/[a-zа-я0-9_-]{1,12}/giu, /\b\d{2}\/\d{2}(?:-[a-zа-я]{1,10})?\b/giu ]; for (const contractPattern of contractPatterns) { let match = null; while ((match = contractPattern.exec(text)) !== null) { spans.push({ start: match.index, end: match.index + match[0].length }); } } return spans; } function collectAmountSpans(text) { const spans = []; const amountPatterns = [/\b\d{1,3}(?:[ \u00A0]\d{3})+(?:[.,]\d{2})?\b/g, /\b\d+[.,]\d{2}\b/g]; for (const amountPattern of amountPatterns) { let match = null; while ((match = amountPattern.exec(text)) !== null) { spans.push({ start: match.index, end: match.index + match[0].length }); } } return spans; } function collectPercentSpans(text) { const spans = []; const percentPattern = /\b\d{1,3}(?:[.,]\d+)?\s*%/g; let match = null; while ((match = percentPattern.exec(text)) !== null) { spans.push({ start: match.index, end: match.index + match[0].length }); } return spans; } function intersectsAnySpan(start, end, spans) { return spans.some((span) => start < span.end && end > span.start); } function hasAccountContextAround(text, start, end) { const left = text.slice(Math.max(0, start - 28), start); const right = text.slice(end, Math.min(text.length, end + 28)); return /(?:счет|сч\.?|account|schet|оплат|расч[её]т|расчет|аванс|зач[её]т|долг|постав|покуп|supplier|customer|settlement|payment|ндс|vat|проводк|posting)/iu.test(`${left} ${right}`); } function extractAccountTokens(text) { const lower = String(text ?? "").toLowerCase(); const explicitAccounts = new Set(); const knownAccountPrefixes = new Set([ "01", "02", "07", "08", "10", "13", "19", "20", "21", "23", "25", "26", "28", "29", "41", "43", "44", "45", "50", "51", "52", "55", "57", "58", "60", "62", "66", "67", "68", "69", "70", "71", "73", "76", "90", "91", "94", "96", "97" ]); const contextualPattern = /(?:\bсчет(?:а|у|ом|ов)?\b|\bсч\.?\b|\baccount(?:s)?\b|\bschet(?:a|u|om|ov)?\b)\s*(?:№|#|:)?\s*(\d{2}(?:\.\d{2})?)/giu; let contextual = null; while ((contextual = contextualPattern.exec(lower)) !== null) { if (contextual[1]) { const token = String(contextual[1]).trim(); const prefix = token.match(/^(\d{2})/)?.[1]; if (prefix && knownAccountPrefixes.has(prefix)) { explicitAccounts.add(token); } } } const pairPattern = /\b(\d{2}\.\d{2})\s*\/\s*(\d{2}\.\d{2})\b/g; let pairMatch = null; while ((pairMatch = pairPattern.exec(lower)) !== null) { const left = String(pairMatch[1] ?? "").trim(); const right = String(pairMatch[2] ?? "").trim(); const leftPrefix = left.match(/^(\d{2})/)?.[1]; const rightPrefix = right.match(/^(\d{2})/)?.[1]; if (leftPrefix && knownAccountPrefixes.has(leftPrefix)) { explicitAccounts.add(left); } if (rightPrefix && knownAccountPrefixes.has(rightPrefix)) { explicitAccounts.add(right); } } if (explicitAccounts.size > 0) { return Array.from(explicitAccounts); } const contractSpans = collectContractSpans(lower); const spans = [...collectDateSpans(lower), ...collectAmountSpans(lower), ...collectPercentSpans(lower), ...contractSpans]; const hasAccountingLexeme = /(?:\bсчет(?:а|у|ом|ов)?\b|\bсч\.?\b|\baccount(?:s)?\b|\bschet(?:a|u|om|ov)?\b|оплат|расчет|аванс|долг|settlement|payment)/iu.test(lower); if (!hasAccountingLexeme) { return []; } const accountList = []; const genericPattern = /\b\d{2}(?:\.\d{2})?\b/g; let generic = null; while ((generic = genericPattern.exec(lower)) !== null) { const value = generic[0]; const start = generic.index; const end = start + value.length; if (intersectsAnySpan(start, end, spans)) { continue; } if (!hasAccountContextAround(lower, start, end)) { continue; } const prefix = value.match(/^(\d{2})/)?.[1]; if (!prefix || !knownAccountPrefixes.has(prefix)) { continue; } accountList.push(value); } return Array.from(new Set(accountList)); } function extractSubjectTokens(text) { const lower = text.toLowerCase(); const tokens = []; const push = (token, match) => { if (match) tokens.push(token); }; push("nds", /\b(?:\u043d\u0434\u0441|vat)\b/iu.test(lower)); push("os", /(?:\b\u043e\u0441\b|\u043e\u0441\u043d\u043e\u0432\u043d(?:\u044b\u0435|\u044b\u0445)\s+\u0441\u0440\u0435\u0434|osnovn(?:ye|yh)\s+sred)/iu.test(lower)); push("counterparty", /(?:\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b|supplier|buyer|counterparty|kontragent|postavshch|pokupatel)/iu.test(lower)); push("document", /(?:\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446|\u043f\u043e\u0441\u0442\u0443\u043f\u043b\u0435\u043d|\u0432\u044b\u043f\u0438\u0441\u043a|\u043f\u043b\u0430\u0442\u0435\u0436|document|invoice|posting|dokument|realiz|postuplen|vypisk|platezh)/iu.test(lower)); push("saldo", /(?:\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u0441\u0442\u0430\u0442\u043a|saldo|balance)/iu.test(lower)); push("anomaly", /(?:\u0430\u043d\u043e\u043c\u0430\u043b|\u0440\u0438\u0441\u043a|\u043f\u043e\u0434\u043e\u0437\u0440|\u0445\u0432\u043e\u0441\u0442|anomaly|risk|anomali|hvost|tail)/iu.test(lower)); push("chain", /(?:\u0446\u0435\u043f\u043e\u0447|\u0440\u0430\u0437\u043b\u043e\u0436|\u0441\u0432\u044f\u0437\u043a|\u0440\u0430\u0437\u0440\u044b\u0432|chain|razlozh|svyaz|razryv)/iu.test(lower)); const accountMatches = extractAccountTokens(lower); for (const account of accountMatches) { tokens.push(`account_${account}`); } return Array.from(new Set(tokens)); } function extractRequirements(routeSummary, normalized, userMessage) { const fragmentText = fragmentTextById(normalized); return (0, assistantCoverageGrounding_1.extractRequirementsForRoute)({ routeSummary, userMessage, fragmentTextById: fragmentText, extractSubjectTokens }); } function toExecutionPlan(routeSummary, normalized, userMessage, requirementByFragment) { const fragmentText = fragmentTextById(normalized); return (0, assistantQueryPlanning_1.buildExecutionPlanFromRoute)({ routeSummary, userMessage, fragmentTextById: fragmentText, requirementByFragment }); } function enrichRbpFragmentForLive(fragmentText, temporalGuard) { const base = compactWhitespace(String(fragmentText ?? "")); const hints = ["Списание РБП", "объект РБП", "остаток на конец периода", "счет 97"]; const effective = temporalGuard && typeof temporalGuard === "object" ? temporalGuard.effective_primary_period : null; if (effective && effective.from && effective.to) { hints.push(`период ${effective.from}..${effective.to}`); } const hintText = hints.filter(Boolean).join(", "); if (!base) { return hintText; } if (/списани[ея]\s+рбп|счет\s*97|account\s*97|остат/i.test(base)) { return base; } return `${base}; ${hintText}`; } function enforceRbpLiveRoutePlan(input) { if (input.claimType !== "prove_rbp_tail_state") { return { executionPlan: input.executionPlan, audit: null }; } const requiredLiveCalls = [ "find_rbp_writeoff_documents_in_period", "find_rbp_object_movements_account_97", "find_month_close_entries_linked_to_rbp", "compute_end_period_residual_by_rbp_object" ]; let routeAdjusted = 0; let rescuedNoRoute = 0; const replacedRoutes = []; const adjustedPlan = input.executionPlan.map((item) => { if (!item || typeof item !== "object") { return item; } if (item.should_execute !== true && item.no_route_reason === "insufficient_specificity") { rescuedNoRoute += 1; routeAdjusted += 1; return { ...item, route: "live_mcp_drilldown", should_execute: true, no_route_reason: null, clarification_reason: null, fragment_text: enrichRbpFragmentForLive(item.fragment_text, input.temporalGuard) }; } if (item.should_execute === true && item.route !== "hybrid_store_plus_live" && item.route !== "live_mcp_drilldown") { routeAdjusted += 1; if (item.route && item.route !== "no_route") { replacedRoutes.push(String(item.route)); } return { ...item, route: "hybrid_store_plus_live", fragment_text: enrichRbpFragmentForLive(item.fragment_text, input.temporalGuard) }; } if (item.should_execute === true) { return { ...item, fragment_text: enrichRbpFragmentForLive(item.fragment_text, input.temporalGuard) }; } return item; }); return { executionPlan: adjustedPlan, audit: { claim_type: "prove_rbp_tail_state", required_live_calls: requiredLiveCalls, route_adjustments_applied: routeAdjusted, rescued_no_route_fragments: rescuedNoRoute, replaced_routes: Array.from(new Set(replacedRoutes)), route_gap_reason: routeAdjusted > 0 ? "rbp_claim_bound_live_route_override_applied" : null } }; } function collectRbpLiveRouteAudit(input) { if (input.claimType !== "prove_rbp_tail_state") { return null; } const required = new Set(Array.isArray(input.planAudit?.required_live_calls) ? input.planAudit.required_live_calls : []); const executed = []; const missing = new Set(); const routeGaps = []; let matchedRowsTotal = 0; let returnedRowsTotal = 0; let fetchedRowsTotal = 0; for (const result of input.retrievalResults) { if (!result || typeof result !== "object") { continue; } const summary = result.summary && typeof result.summary === "object" ? result.summary : null; const live = summary && typeof summary.live_mcp === "object" && summary.live_mcp ? summary.live_mcp : null; if (!live) { continue; } const requiredCalls = Array.isArray(live.required_live_calls) ? live.required_live_calls : []; for (const callId of requiredCalls) { required.add(String(callId ?? "").trim()); } const executedCalls = Array.isArray(live.executed_live_calls) ? live.executed_live_calls : []; for (const call of executedCalls) { if (!call || typeof call !== "object") { continue; } executed.push(call); } const missingCalls = Array.isArray(live.missing_live_calls) ? live.missing_live_calls : []; for (const callId of missingCalls) { const token = String(callId ?? "").trim(); if (token) { missing.add(token); } } const routeGapReason = String(live.route_gap_reason ?? "").trim(); if (routeGapReason) { routeGaps.push(routeGapReason); } fetchedRowsTotal += Number(live.fetched_rows ?? 0) || 0; matchedRowsTotal += Number(live.matched_rows ?? 0) || 0; returnedRowsTotal += Number(live.returned_rows ?? 0) || 0; } const requiredList = Array.from(required).filter(Boolean); const executedList = executed; const missingFromExecuted = requiredList.filter((callId) => !executedList.some((item) => String(item.call_id ?? "") === callId)); for (const callId of missingFromExecuted) { missing.add(callId); } const missingList = Array.from(missing); const routeGapReason = missingList.length > 0 ? "required_live_calls_not_executed" : matchedRowsTotal <= 0 ? "claim_live_calls_executed_but_zero_matches" : routeGaps[0] ?? null; const executionRate = requiredList.length > 0 ? Number(((requiredList.length - missingList.length) / requiredList.length).toFixed(4)) : 1; return { claim_type: "prove_rbp_tail_state", required_live_calls: requiredList, executed_live_calls: executedList, missing_live_calls: missingList, route_gap_reason: routeGapReason, live_route_execution_rate: executionRate, fetched_rows_total: fetchedRowsTotal, matched_rows_total: matchedRowsTotal, returned_rows_total: returnedRowsTotal, plan_override: input.planAudit ?? null }; } function enrichFaFragmentForLive(fragmentText, temporalGuard) { const base = compactWhitespace(String(fragmentText ?? "")); const hints = [ "Начисление амортизации", "объект ОС", "expected set ОС", "счет 01/02" ]; const effective = temporalGuard && typeof temporalGuard === "object" ? temporalGuard.effective_primary_period : null; if (effective && effective.from && effective.to) { hints.push(`период ${effective.from}..${effective.to}`); } const hintText = hints.filter(Boolean).join(", "); if (!base) { return hintText; } if (/амортиз|основн(?:ые|ых)\s+сред|fixed\s*asset|depreciat|счет\s*0[12]|account\s*0[12]/i.test(base)) { return base; } return `${base}; ${hintText}`; } function enforceFaLiveRoutePlan(input) { if (input.claimType !== "prove_fixed_asset_amortization_coverage") { return { executionPlan: input.executionPlan, audit: null }; } const requiredLiveCalls = [ "find_amortization_documents_in_period", "find_fixed_asset_movements_accounts_01_02", "find_fixed_asset_cards_expected_for_period", "match_expected_vs_actual_fa_coverage" ]; let routeAdjusted = 0; let rescuedNoRoute = 0; const replacedRoutes = []; const adjustedPlan = input.executionPlan.map((item) => { if (!item || typeof item !== "object") { return item; } if (item.should_execute !== true && item.no_route_reason === "insufficient_specificity") { rescuedNoRoute += 1; routeAdjusted += 1; return { ...item, route: "live_mcp_drilldown", should_execute: true, no_route_reason: null, clarification_reason: null, fragment_text: enrichFaFragmentForLive(item.fragment_text, input.temporalGuard) }; } if (item.should_execute === true && item.route !== "hybrid_store_plus_live" && item.route !== "live_mcp_drilldown") { routeAdjusted += 1; if (item.route && item.route !== "no_route") { replacedRoutes.push(String(item.route)); } return { ...item, route: "hybrid_store_plus_live", fragment_text: enrichFaFragmentForLive(item.fragment_text, input.temporalGuard) }; } if (item.should_execute === true) { return { ...item, fragment_text: enrichFaFragmentForLive(item.fragment_text, input.temporalGuard) }; } return item; }); return { executionPlan: adjustedPlan, audit: { claim_type: "prove_fixed_asset_amortization_coverage", required_live_calls: requiredLiveCalls, route_adjustments_applied: routeAdjusted, rescued_no_route_fragments: rescuedNoRoute, replaced_routes: Array.from(new Set(replacedRoutes)), route_gap_reason: routeAdjusted > 0 ? "fa_claim_bound_live_route_override_applied" : null } }; } function collectFaLiveRouteAudit(input) { if (input.claimType !== "prove_fixed_asset_amortization_coverage") { return null; } const required = new Set(Array.isArray(input.planAudit?.required_live_calls) ? input.planAudit.required_live_calls : []); const executed = []; const missing = new Set(); const routeGaps = []; let matchedRowsTotal = 0; let returnedRowsTotal = 0; let fetchedRowsTotal = 0; for (const result of input.retrievalResults) { if (!result || typeof result !== "object") { continue; } const summary = result.summary && typeof result.summary === "object" ? result.summary : null; const live = summary && typeof summary.live_mcp === "object" && summary.live_mcp ? summary.live_mcp : null; if (!live) { continue; } const requiredCalls = Array.isArray(live.required_live_calls) ? live.required_live_calls : []; for (const callId of requiredCalls) { required.add(String(callId ?? "").trim()); } const executedCalls = Array.isArray(live.executed_live_calls) ? live.executed_live_calls : []; for (const call of executedCalls) { if (!call || typeof call !== "object") { continue; } executed.push(call); } const missingCalls = Array.isArray(live.missing_live_calls) ? live.missing_live_calls : []; for (const callId of missingCalls) { const token = String(callId ?? "").trim(); if (token) { missing.add(token); } } const routeGapReason = String(live.route_gap_reason ?? "").trim(); if (routeGapReason) { routeGaps.push(routeGapReason); } fetchedRowsTotal += Number(live.fetched_rows ?? 0) || 0; matchedRowsTotal += Number(live.matched_rows ?? 0) || 0; returnedRowsTotal += Number(live.returned_rows ?? 0) || 0; } const requiredList = Array.from(required).filter(Boolean); const executedList = executed; const missingFromExecuted = requiredList.filter((callId) => !executedList.some((item) => String(item.call_id ?? "") === callId)); for (const callId of missingFromExecuted) { missing.add(callId); } const missingList = Array.from(missing); const routeGapReason = missingList.length > 0 ? "required_live_calls_not_executed" : matchedRowsTotal <= 0 ? "claim_live_calls_executed_but_zero_matches" : routeGaps[0] ?? null; const executionRate = requiredList.length > 0 ? Number(((requiredList.length - missingList.length) / requiredList.length).toFixed(4)) : 1; return { claim_type: "prove_fixed_asset_amortization_coverage", required_live_calls: requiredList, executed_live_calls: executedList, missing_live_calls: missingList, route_gap_reason: routeGapReason, live_route_execution_rate: executionRate, fetched_rows_total: fetchedRowsTotal, matched_rows_total: matchedRowsTotal, returned_rows_total: returnedRowsTotal, plan_override: input.planAudit ?? null }; } function toDebugRoutes(routeSummary) { return (0, assistantQueryPlanning_1.buildDebugRoutesFromRoute)({ routeSummary, resolveLegacyRouteReason: retrievalSummaryForRoute }); } function buildSkippedResult(item) { return (0, retrievalResultNormalizer_1.normalizeRetrievalResult)(item.fragment_id, item.requirement_ids, "no_route", { status: "empty", result_type: "summary", items: [], summary: { skipped: true, reason: mapNoRouteReason(item.no_route_reason), no_route_reason: item.no_route_reason, clarification_reason: item.clarification_reason }, evidence: [], why_included: [], selection_reason: [mapNoRouteReason(item.no_route_reason)], risk_factors: [], business_interpretation: ["Данный фрагмент не был выполнен из-за no-route решения."], confidence: "low", limitations: ["Фрагмент требует уточнения или отсутствует поддерживаемый маршрут."], errors: [] }); } function evaluateCoverage(requirements, retrievalResults) { return (0, assistantCoverageGrounding_1.evaluateCoverageForRequirements)(requirements, retrievalResults); } function evaluateCoverageForTests(requirements, retrievalResults) { return (0, assistantCoverageGrounding_1.evaluateCoverageForRequirements)(requirements, retrievalResults); } function extractSubjectTokensForTests(text) { return extractSubjectTokens(text); } function checkGrounding(userMessage, requirements, coverage, retrievalResults) { return (0, assistantCoverageGrounding_1.checkGroundingForRequirements)({ userMessage, requirements, coverage, retrievalResults, extractSubjectTokens }); } const FOLLOWUP_ROUTE_HINTS = new Set(["store_canonical", "store_feature_risk", "hybrid_store_plus_live", "live_mcp_drilldown", "batch_refresh_then_store"]); const FOLLOWUP_ACTIVE_DOMAIN_ROUTE_MAP = { settlements_60_62: "hybrid_store_plus_live", vat_document_register_book: "hybrid_store_plus_live", month_close_costs_20_44: "hybrid_store_plus_live", fixed_asset_amortization: "hybrid_store_plus_live" }; const FOLLOWUP_BUSINESS_CONTEXT_MAX = 320; const FOLLOWUP_SUBJECT_MAX = 160; const FOLLOWUP_QUESTION_APPEND_MAX = 260; function compactWhitespace(value) { return value.replace(/\s+/g, " ").trim(); } function hasAccountingSignal(text) { const lower = repairAddressMojibake(String(text ?? "")).toLowerCase(); const excludedSpans = [...collectDateSpans(lower), ...collectAmountSpans(lower), ...collectPercentSpans(lower), ...collectContractSpans(lower)]; const accountTokenPattern = /\b(?:01|02|07|08|10|13|19|20|21|23|25|26|28|29|41|43|44|50|51|52|55|57|58|60|62|66|67|68|69|70|71|73|75|76|80|81|84|90|91|97)(?:[.,]\d{1,2})?\b/g; let accountMatch = null; while ((accountMatch = accountTokenPattern.exec(lower)) !== null) { const token = String(accountMatch[0] ?? "").trim(); if (!token) { continue; } const start = accountMatch.index; const end = start + token.length; if (intersectsAnySpan(start, end, excludedSpans)) { continue; } const hasExplicitSubaccount = /[.,]\d{1,2}/.test(token); if (hasExplicitSubaccount || hasAccountContextAround(lower, start, end) || countTokens(lower) <= 4) { return true; } } return /(проводк|документ|реализац|поступлен|взаиморасчет|сальдо|остатк|счет|счёт|ндс|амортиз|рбп|контрагент|поставщик|покупател|оплат|банк|выписк|склад|товар|материал|закрыти|период|postavshchik|kontragent|schet|schetu|period|counterparty|supplier|invoice|posting|ledger|account|anomaly|risk)/i.test(lower); } function hasFollowupMarker(text) { const compact = compactWhitespace(text.toLowerCase()); return /^(и|а\s+кто|а еще|а ещё|еще|ещё|добав|уточн|продолж|также|а если|а теперь|теперь|plus|also|dobav|utochn|prodolzh|then|now)/i.test(compact); } function hasReferentialPointer(text) { return /(по этому|по тому|это же|этой|этим|этому|этого|этот|эту|этом|это|эти|этих|из этого|из них|из этих|из тех|в этом|тот же|same thing|that one|po etomu|po tomu)/i.test(text.toLowerCase()); } function hasSmallTalkSignal(text) { return /(привет|как дела|спасибо|благодарю|thanks|thank you|hello|hi)\b/i.test(text.toLowerCase()); } function countTokens(text) { return compactWhitespace(text) .split(" ") .filter(Boolean).length; } function hasPeriodLiteral(text) { return /\b(20\d{2}(?:[-/.](?:0[1-9]|1[0-2]))?)\b/.test(text); } function hasShortNamedPeriodFollowupLiteral(text) { const normalized = compactWhitespace(String(text ?? "").toLowerCase()); if (!normalized) { return false; } return /^(?:(?:\u0430|a|\u0438|i)\s+)?(?:\u043d\u0430|\u0437\u0430|\u043f\u043e|na|za|for)\s+(?:(?:\u044f\u043d\u0432(?:\u0430\u0440)?|\u0444\u0435\u0432(?:\u0440\u0430\u043b)?|\u043c\u0430\u0440\u0442|\u0430\u043f\u0440(?:\u0435\u043b)?|\u043c\u0430(?:\u0439|\u044f)|\u0438\u044e\u043d(?:\u044c)?|\u0438\u044e\u043b(?:\u044c)?|\u0430\u0432\u0433(?:\u0443\u0441\u0442)?|\u0441\u0435\u043d\u0442(?:\u044f\u0431\u0440)?|\u043e\u043a\u0442(?:\u044f\u0431\u0440)?|\u043d\u043e\u044f(?:\u0431\u0440)?|\u0434\u0435\u043a(?:\u0430\u0431\u0440)?)(?:[\u0430-\u044f\u0451]*)?|q[1-4]|(?:[1-4]|[ivx]{1,4})\s*(?:-?\u0439)?\s*\u043a\u0432(?:\.|\u0430\u0440\u0442(?:\u0430\u043b(?:\u0430|\u0435|\u0443|\u043e\u043c)?)?)?|(?:20\d{2})\s*(?:\u0433(?:\.|\u043e\u0434(?:\u0430|\u0443|\u043e\u043c)?)?))(?=$|[\s,.;:!?])/iu.test(normalized); } function hasShortCurrentDateFollowupLiteral(text) { const normalized = compactWhitespace(String(text ?? "").toLowerCase()); if (!normalized) { return false; } return /^(?:(?:\u0430|a|\u0438|i)\s+)?(?:(?:\u043d\u0430|na)\s+)?(?:(?:\u0442\u0435\u043a\u0443\u0449(?:[\u0430-\u044f\u0451]+)?\s+\u0434\u0430\u0442(?:[\u0430-\u044f\u0451]+)?|(?:\u043d\u0430\s+)?\u0441\u0435\u0433\u043e\u0434\u043d(?:\u044f|\u0435\u0448\u043d(?:[\u0430-\u044f\u0451]+)?\s+\u0434\u0430\u0442(?:[\u0430-\u044f\u0451]+)?)|(?:\u043d\u0430\s+)?\u0442\u0435\u043a\u0443\u0449(?:[\u0430-\u044f\u0451]+)?\s+\u043c\u043e\u043c\u0435\u043d\u0442|today|current\s+date|current\s+moment|now)|(?:\u043f\u043e\s+\u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044e\s+\u043d\u0430|as\s+of)\s+(?:\u0441\u0435\u0433\u043e\u0434\u043d\u044f|today|current\s+date))(?=$|[\s,.;:!?])/iu.test(normalized); } function hasStandaloneAddressTopicSignal(text) { const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()); if (!normalized) { return false; } if (hasFollowupMarker(normalized) || hasReferentialPointer(normalized)) { return false; } const hasRequestCue = /(?:^|[\s,.;:!?()\-])(?:покажи|показать|выведи|дай|найди|список|какие|какой|какая|каких|сколько|где|show|list|find|which|what)/iu.test(normalized); if (!hasRequestCue) { return false; } const hasBusinessObject = /(?:договор|контракт|контрагент|поставщик|покупател|клиент|документ|платеж|оплат|сальдо|остатк|сч[её]т|оборот|выруч|доход|прибыл|ндс|дебитор|кредитор|организац|компан|контор|contract|counterparty|supplier|customer|document|payment|turnover|revenue|profit|balance|account|vat)/iu.test(normalized); if (!hasBusinessObject) { return false; } const hasStructuredAnchor = hasPeriodLiteral(normalized) || /\b\d{2}(?:[.,]\d{1,2})?\b/.test(normalized) || /(?:альтернатива|лайсвуд|райм|ооо\s+[a-zа-яё])/iu.test(normalized); return hasStructuredAnchor || countTokens(normalized) >= 6; } function extractNormalizedPeriodLiteral(text) { const monthly = text.match(/\b(20\d{2})[-/.](0[1-9]|1[0-2])\b/); if (monthly) { return `${monthly[1]}-${monthly[2]}`; } const yearly = text.match(/\b(20\d{2})\b/); if (yearly) { return yearly[1]; } return null; } function extractFollowupAccountAnchorsLoose(text) { const lower = String(text ?? "").toLowerCase(); const spans = [...collectDateSpans(lower), ...collectAmountSpans(lower), ...collectPercentSpans(lower), ...collectContractSpans(lower)]; const anchors = []; const followupAccountPattern = /\b(?:01|02|08|19|20|21|23|25|26|28|29|44|51|60|62|68|76|97)(?:\.\d{2})?\b/g; let match = null; while ((match = followupAccountPattern.exec(lower)) !== null) { const value = String(match[0] ?? "").trim(); const start = match.index; const end = start + value.length; if (intersectsAnySpan(start, end, spans)) { continue; } anchors.push(value); } return Array.from(new Set(anchors)); } function accountPrefixToken(value) { const token = String(value ?? "").trim(); const match = token.match(/^(\d{2})/); return match ? match[1] : null; } function hasCrossScopeConflictWithState(userMessage, state) { const explicitPeriod = extractNormalizedPeriodLiteral(userMessage); const statePeriod = compactWhitespace(state.focus.period ?? ""); if (explicitPeriod && statePeriod && explicitPeriod !== statePeriod) { return true; } const inferredDomain = inferP0DomainFromMessage(userMessage); const stateDomain = compactWhitespace(state.followup_context?.active_domain ?? state.focus.domain ?? ""); if (inferredDomain && stateDomain && inferredDomain !== stateDomain) { const followupDomainRefinement = hasFollowupMarker(userMessage) || hasReferentialPointer(userMessage) || hasPeriodLiteral(userMessage); if (!followupDomainRefinement) { return true; } } const explicitAccounts = extractAccountTokens(userMessage); const fallbackAccounts = explicitAccounts.length > 0 ? explicitAccounts : extractFollowupAccountAnchorsLoose(userMessage); const knownAccounts = Array.isArray(state.focus.primary_accounts) ? state.focus.primary_accounts : []; if (fallbackAccounts.length > 0 && knownAccounts.length > 0) { const knownPrefixes = new Set(knownAccounts.map((item) => accountPrefixToken(item)).filter(Boolean)); const newPrefixes = new Set(fallbackAccounts.map((item) => accountPrefixToken(item)).filter(Boolean)); if (newPrefixes.size > 0 && knownPrefixes.size > 0) { let intersects = false; for (const prefix of newPrefixes) { if (knownPrefixes.has(prefix)) { intersects = true; break; } } if (!intersects) { return true; } } } return false; } function inferP0DomainFromMessage(text) { if (typeof investigationState_1.inferP0DomainFromMessage === "function") { return investigationState_1.inferP0DomainFromMessage(text); } return null; } function hasStrongFollowupAnchors(userMessage, state) { const normalizedMessage = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase()); const periodRefinementCue = /(?:^(?:\u0430\s+)?\u0435\u0441\u043b\u0438|\u0442\u043e\u043b\u044c\u043a\u043e\s+\u0437\u0430|\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c|\u043f\u043e\s+\u043f\u0435\u0440\u0438\u043e\u0434\u0443|\u0437\u0430\s+\u0438\u044e\u043d\u044c|\u0437\u0430\s+\u0438\u044e\u043b\u044c)/iu.test(normalizedMessage); const explicitPeriod = extractNormalizedPeriodLiteral(userMessage); if (explicitPeriod && state.focus.period && explicitPeriod !== state.focus.period) { const periodLooksLikeFollowupRefinement = hasFollowupMarker(userMessage) || hasReferentialPointer(userMessage) || periodRefinementCue; if (!periodLooksLikeFollowupRefinement) { return true; } } const inferredDomain = inferP0DomainFromMessage(userMessage); const activeDomain = compactWhitespace(state.followup_context?.active_domain ?? state.focus.domain ?? ""); if (inferredDomain && activeDomain && inferredDomain !== activeDomain) { const domainLooksLikeFollowupRefinement = hasFollowupMarker(userMessage) && hasReferentialPointer(userMessage); if (!domainLooksLikeFollowupRefinement) { return true; } } const explicitAccounts = extractAccountTokens(userMessage); const followupAccounts = explicitAccounts.length > 0 ? explicitAccounts : extractFollowupAccountAnchorsLoose(userMessage); if (followupAccounts.length > 0) { const knownAccounts = new Set(state.focus.primary_accounts.map((item) => item.trim())); if (knownAccounts.size === 0) { return true; } if (followupAccounts.some((item) => !knownAccounts.has(item))) { return true; } } return false; } function routeFromInvestigationState(state) { const rawDomain = compactWhitespace(state.focus.domain ?? ""); if (!rawDomain) { const mappedFromFollowup = state.followup_context?.active_domain ? FOLLOWUP_ACTIVE_DOMAIN_ROUTE_MAP[compactWhitespace(state.followup_context.active_domain)] ?? null : null; return mappedFromFollowup; } if (FOLLOWUP_ROUTE_HINTS.has(rawDomain)) { return rawDomain; } for (const candidate of rawDomain.split(",").map((item) => compactWhitespace(item))) { if (FOLLOWUP_ROUTE_HINTS.has(candidate)) { return candidate; } if (Object.prototype.hasOwnProperty.call(FOLLOWUP_ACTIVE_DOMAIN_ROUTE_MAP, candidate)) { return FOLLOWUP_ACTIVE_DOMAIN_ROUTE_MAP[candidate]; } } const mappedFromFollowup = state.followup_context?.active_domain ? FOLLOWUP_ACTIVE_DOMAIN_ROUTE_MAP[compactWhitespace(state.followup_context.active_domain)] ?? null : null; if (mappedFromFollowup) { return mappedFromFollowup; } return null; } function withCappedLength(value, max) { return value.length <= max ? value : value.slice(0, max); } function mergeBusinessContext(existing, patchParts) { const existingText = compactWhitespace(existing ?? ""); const patchText = compactWhitespace(patchParts.filter(Boolean).join("; ")); if (!existingText && !patchText) { return undefined; } const merged = existingText && patchText ? `${existingText}; ${patchText}` : existingText || patchText; return withCappedLength(merged, FOLLOWUP_BUSINESS_CONTEXT_MAX); } function buildFollowupStateBinding(input) { const userMessage = String(input.userMessage ?? "").trim(); if (!userMessage || input.investigationState.status !== "active" || input.investigationState.turn_index <= 0) { return { normalizedQuestion: userMessage, mergedContext: input.payloadContext, usage: null }; } const strongSignal = hasAccountingSignal(userMessage); const followupMarker = hasFollowupMarker(userMessage); const referentialPointer = hasReferentialPointer(userMessage); const shortPrompt = countTokens(userMessage) <= 10; const smallTalkSignal = hasSmallTalkSignal(userMessage); const problemState = input.investigationState.problem_unit_state; const problemContinuityAvailable = config_1.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1 && Boolean(problemState) && ((problemState?.active_problem_units.length ?? 0) > 0 || (problemState?.focus_problem_types.length ?? 0) > 0); const strongNewAnchorDetected = hasStrongFollowupAnchors(userMessage, input.investigationState); const scopeConflictDetected = hasCrossScopeConflictWithState(userMessage, input.investigationState); const periodRefinementFollowup = hasPeriodLiteral(userMessage) && problemContinuityAvailable; const shouldBind = !smallTalkSignal && !strongNewAnchorDetected && !scopeConflictDetected && (followupMarker || referentialPointer || periodRefinementFollowup || (!strongSignal && shortPrompt)); if (!shouldBind) { return { normalizedQuestion: userMessage, mergedContext: input.payloadContext, usage: null }; } const context = { ...(input.payloadContext ?? {}) }; const hasExplicitExpectedRoute = Boolean(input.payloadContext?.expected_route); const expectedRouteFromState = !context?.expected_route ? routeFromInvestigationState(input.investigationState) : null; const periodHintFromState = !context?.period_hint ? input.investigationState.focus.period : null; const followupContext = input.investigationState.followup_context; if (expectedRouteFromState) { context.expected_route = expectedRouteFromState; } if (periodHintFromState) { context.period_hint = periodHintFromState; const existingAnalysisContext = context.analysis_context && typeof context.analysis_context === "object" ? context.analysis_context : {}; if (!existingAnalysisContext.as_of_date) { context.analysis_context = { ...existingAnalysisContext, as_of_date: periodHintFromState, source: existingAnalysisContext.source ?? "followup_state_period_hint" }; } } const subject = withCappedLength(compactWhitespace(input.investigationState.focus.active_query_subject ?? ""), FOLLOWUP_SUBJECT_MAX); const businessContextPatch = ["followup_state_binding_v1"]; let problemContinuityApplied = false; let problemContinuitySkippedReason = null; if (input.investigationState.focus.period) { businessContextPatch.push("active_period"); } if (subject) { businessContextPatch.push(`focus_subject:${subject}`); } if (input.investigationState.focus.primary_accounts.length > 0) { businessContextPatch.push(`focus_accounts:${input.investigationState.focus.primary_accounts.join(",")}`); } if (followupContext?.active_domain) { businessContextPatch.push(`focus_domain:${followupContext.active_domain}`); } if ((followupContext?.active_requirement_ids?.length ?? 0) > 0) { businessContextPatch.push(`active_requirements:${followupContext.active_requirement_ids.slice(0, 4).join(",")}`); } if ((followupContext?.uncovered_requirement_ids?.length ?? 0) > 0) { businessContextPatch.push(`uncovered_requirements:${followupContext.uncovered_requirement_ids.slice(0, 4).join(",")}`); } if (followupContext?.last_problem_unit_id) { businessContextPatch.push(`last_problem_unit:${followupContext.last_problem_unit_id}`); } if ((followupContext?.evidence_summary?.length ?? 0) > 0) { businessContextPatch.push(`evidence_state:${followupContext.evidence_summary.slice(0, 3).join("|")}`); } if ((followupContext?.settlement_next_actions?.length ?? 0) > 0) { businessContextPatch.push("settlement_focus_retained_v1"); } if (problemContinuityAvailable) { if (hasExplicitExpectedRoute) { problemContinuitySkippedReason = "explicit_expected_route"; } else { const focusTypes = (problemState?.focus_problem_types ?? []).slice(0, 3); const activeCount = problemState?.active_problem_units.length ?? 0; businessContextPatch.push("problem_unit_continuity_v1"); if (focusTypes.length > 0) { businessContextPatch.push(`problem_focus_types:${focusTypes.join(",")}`); } businessContextPatch.push(`problem_active_count:${activeCount}`); problemContinuityApplied = true; } } const mergedBusinessContext = mergeBusinessContext(context?.business_context, businessContextPatch); if (mergedBusinessContext) { context.business_context = mergedBusinessContext; } const shouldAugmentQuestion = Boolean(subject) && (followupMarker || referentialPointer || !strongSignal); let normalizedQuestion = userMessage; if (shouldAugmentQuestion) { const appendParts = [`Фокус текущего разбора: ${subject}`]; if (input.investigationState.focus.primary_accounts.length > 0 && !/\b\d{2}(?:\.\d{2})?\b/.test(userMessage)) { appendParts.push(`Счета фокуса: ${input.investigationState.focus.primary_accounts.join(", ")}`); } if (periodHintFromState && !hasPeriodLiteral(userMessage)) { appendParts.push(`Период фокуса: ${periodHintFromState}`); } const appendBlock = withCappedLength(compactWhitespace(appendParts.join("; ")), FOLLOWUP_QUESTION_APPEND_MAX); normalizedQuestion = `${userMessage}\n${appendBlock}`.trim(); } const reason = followupMarker ? "followup_marker" : referentialPointer ? "referential_pointer" : "underspecified_short_followup"; return { normalizedQuestion, mergedContext: context, usage: { applied: true, reason, state_turn_index: input.investigationState.turn_index, context_patch: { period_hint_from_state: Boolean(periodHintFromState), expected_route_from_state: Boolean(expectedRouteFromState), business_context_from_state: Boolean(mergedBusinessContext), question_augmented: shouldAugmentQuestion, problem_continuity_available: problemContinuityAvailable, problem_continuity_applied: problemContinuityApplied, problem_continuity_skipped_reason: problemContinuityApplied ? null : problemContinuitySkippedReason, strong_new_anchor_detected: strongNewAnchorDetected, scope_isolation_applied: true, scope_carryover_allowed: !scopeConflictDetected, scope_reset_reason: scopeConflictDetected ? "cross_scope_conflict" : null } } }; } function cloneItems(items) { return items.map((item) => ({ ...item, debug: item.debug ? { ...item.debug } : null })); } function buildAddressCoverageReport() { return { requirements_total: 0, requirements_covered: 0, requirements_uncovered: [], requirements_partially_covered: [], clarification_needed_for: [], out_of_scope_requirements: [] }; } function buildAssistantBackendErrorDebugPayload(errorMessage) { return { trace_id: `chat-${(0, nanoid_1.nanoid)(10)}`, prompt_version: "assistant_backend_error_fallback_v1", schema_version: "assistant_backend_error_fallback_v1", fallback_type: "unknown", route_summary: null, fragments: [], requirements_extracted: [], coverage_report: buildAddressCoverageReport(), routes: [], retrieval_status: [], retrieval_results: [], answer_grounding_check: { status: "no_grounded_answer", route_subject_match: true, missing_requirements: [], reasons: [ `backend_error:${String(errorMessage ?? "unknown_error").slice(0, 280)}` ], why_included_summary: [], selection_reason_summary: [] }, dropped_intent_segments: [] }; } function buildAssistantBackendErrorReply() { return "Сейчас не удалось завершить разбор из-за внутренней ошибки контуров LLM. Могу продолжить в адресном режиме: проверить документы, договоры и операции по нужному периоду или контрагенту."; } function buildAddressDebugPayload(addressDebug, llmPreDecomposeMeta = null) { const grounded = addressDebug.response_type === "LIMITED_WITH_REASON" ? "partial" : "grounded"; const llmMeta = llmPreDecomposeMeta && typeof llmPreDecomposeMeta === "object" ? llmPreDecomposeMeta : null; return { trace_id: `address-${(0, nanoid_1.nanoid)(10)}`, prompt_version: "address_query_runtime_v1", schema_version: "address_query_runtime_v1", fallback_type: addressDebug.response_type === "LIMITED_WITH_REASON" ? "partial" : "none", route_summary: null, fragments: [], requirements_extracted: [], coverage_report: buildAddressCoverageReport(), routes: [], retrieval_status: [], retrieval_results: [], answer_grounding_check: { status: grounded, route_subject_match: true, missing_requirements: [], reasons: addressDebug.reasons ?? [], why_included_summary: [], selection_reason_summary: [] }, dropped_intent_segments: [], detected_mode: addressDebug.detected_mode, detected_mode_confidence: addressDebug.detected_mode_confidence, query_shape: addressDebug.query_shape, query_shape_confidence: addressDebug.query_shape_confidence, detected_intent: addressDebug.detected_intent, detected_intent_confidence: addressDebug.detected_intent_confidence, extracted_filters: addressDebug.extracted_filters, missing_required_filters: addressDebug.missing_required_filters, selected_recipe: addressDebug.selected_recipe, mcp_call_status_legacy: addressDebug.mcp_call_status_legacy, account_scope_mode: addressDebug.account_scope_mode, account_scope_fallback_applied: addressDebug.account_scope_fallback_applied, anchor_type: addressDebug.anchor_type, anchor_value_raw: addressDebug.anchor_value_raw, anchor_value_resolved: addressDebug.anchor_value_resolved, resolver_confidence: addressDebug.resolver_confidence, ambiguity_count: addressDebug.ambiguity_count, match_failure_stage: addressDebug.match_failure_stage, match_failure_reason: addressDebug.match_failure_reason, mcp_call_status: addressDebug.mcp_call_status, rows_fetched: addressDebug.rows_fetched, raw_rows_received: addressDebug.raw_rows_received, rows_after_account_scope: addressDebug.rows_after_account_scope, rows_after_recipe_filter: addressDebug.rows_after_recipe_filter, rows_materialized: addressDebug.rows_materialized, rows_matched: addressDebug.rows_matched, raw_row_keys_sample: addressDebug.raw_row_keys_sample, materialization_drop_reason: addressDebug.materialization_drop_reason, account_token_raw: addressDebug.account_token_raw, account_token_normalized: addressDebug.account_token_normalized, account_scope_fields_checked: addressDebug.account_scope_fields_checked, account_scope_match_strategy: addressDebug.account_scope_match_strategy, account_scope_drop_reason: addressDebug.account_scope_drop_reason, runtime_readiness: addressDebug.runtime_readiness, limited_reason_category: addressDebug.limited_reason_category, response_type: addressDebug.response_type, requested_result_mode: addressDebug.requested_result_mode ?? undefined, result_mode: addressDebug.result_mode ?? undefined, evidence_strength: addressDebug.evidence_strength ?? undefined, balance_confirmed: typeof addressDebug.balance_confirmed === "boolean" ? addressDebug.balance_confirmed : undefined, as_of_date_basis: addressDebug.as_of_date_basis ?? undefined, capability_id: addressDebug.capability_id ?? undefined, capability_layer: addressDebug.capability_layer ?? undefined, capability_route_mode: addressDebug.capability_route_mode ?? undefined, capability_route_enabled: typeof addressDebug.capability_route_enabled === "boolean" ? addressDebug.capability_route_enabled : undefined, capability_route_reason: addressDebug.capability_route_reason ?? undefined, shadow_route_intent: addressDebug.shadow_route_intent ?? undefined, shadow_route_selected_recipe: addressDebug.shadow_route_selected_recipe ?? undefined, shadow_route_status: addressDebug.shadow_route_status ?? undefined, route_expectation_status: addressDebug.route_expectation_status ?? undefined, route_expectation_reason: addressDebug.route_expectation_reason ?? undefined, route_expectation_expected_selected_recipes: addressDebug.route_expectation_expected_selected_recipes ?? undefined, route_expectation_expected_requested_result_modes: addressDebug.route_expectation_expected_requested_result_modes ?? undefined, route_expectation_expected_result_modes: addressDebug.route_expectation_expected_result_modes ?? undefined, execution_lane: "address_query", llm_decomposition_applied: Boolean(llmMeta?.applied), llm_decomposition_attempted: Boolean(llmMeta?.attempted), llm_provider_used: llmMeta?.provider ?? null, llm_decomposition_trace_id: llmMeta?.traceId ?? null, llm_decomposition_effective_message: llmMeta?.effectiveMessage ?? null, llm_decomposition_reason: llmMeta?.reason ?? null, llm_canonical_candidate_detected: Boolean(llmMeta?.llmCanonicalCandidateDetected), llm_predecompose_contract: llmMeta?.predecomposeContract ?? null, fallback_rule_hit: llmMeta?.fallbackRuleHit ?? null, sanitized_user_message: llmMeta?.sanitizedUserMessage ?? null, tool_gate_decision: llmMeta?.toolGateDecision ?? null, tool_gate_reason: llmMeta?.toolGateReason ?? null, orchestration_contract_v1: llmMeta?.orchestrationContract ?? null, dialog_continuation_contract_v2: llmMeta?.dialogContinuationContract ?? null, address_retry_audit: llmMeta?.addressRetryAudit ?? null, answer_structure_v11: null, investigation_state_snapshot: null, normalized: null, normalizer_output: llmMeta?.traceId ? { trace_id: llmMeta.traceId, prompt_version: "normalizer_v2_0_2", applied: Boolean(llmMeta?.applied), effective_message: llmMeta?.effectiveMessage ?? null } : null }; } function toNonEmptyString(value) { if (value === null || value === undefined) { return null; } const text = String(value).trim(); return text.length > 0 ? text : null; } const ADDRESS_PREDECOMPOSE_NOISE_TOKENS = new Set([ "за", "с", "по", "на", "и", "или", "док", "доки", "docs", "documents", "doki", "dokument", "dokumenty", "документ", "документы", "документов", "банк", "банковские", "операции", "платеж", "платёж", "платежи", "контрагент", "контрагенту", "контрагента", "год", "года", "г", "year", "god", "плс", "pls", "пж", "пжлст", "пожалуйста", "please", "покеж", "покажи", "скажи", "показать", "show", "list", "skazhi", "выведи", "кроме", "помимо", "этого", "этот", "эта", "эту", "этом", "это", "эти", "этих", "другие", "другое", "остальное", "что", "чо", "которые", "какие", "какой", "активный", "активная", "активное", "активности", "месяц", "месяца", "месяцев", "количество", "количеству", "количества", "были", "был", "была", "было", "ли", "списания", "списание", "поступления", "поступление", "расчетного", "расчётного", "счета", "счёта", "есть", "est", "kakie", "kakoi", "vse", "all", "\u043f\u0443\u043d\u043a\u0442", "\u043f\u0443\u043d\u043a\u0442\u0430", "\u043f\u0443\u043d\u043a\u0442\u0443", "\u043f\u0443\u043d\u043a\u0442\u043e\u043c", "\u043f\u043e\u0437\u0438\u0446\u0438\u044f", "\u043f\u043e\u0437\u0438\u0446\u0438\u0438", "\u043f\u043e\u0437\u0438\u0446\u0438\u044e", "\u0441\u0442\u0440\u043e\u043a\u0430", "\u0441\u0442\u0440\u043e\u043a\u0438", "\u0441\u0442\u0440\u043e\u043a\u0443", "item", "row", "line", "blya", "blyat", "епт", "ёпт", "бля" ]); const ADDRESS_FALLBACK_STRIP_TOKENS = new Set([ "бля", "блять", "blya", "blyat", "епт", "ёпт", "епта", "нах", "нахуй", "плс", "pls", "пж", "пжлст", "пожалуйста", "please" ]); const ADDRESS_MONTH_ALIAS_MAP = { "\u044f\u043d\u0432": "01", "\u044f\u043d\u0432\u0430\u0440": "01", january: "01", jan: "01", "\u0444\u0435\u0432": "02", "\u0444\u0435\u0432\u0440\u0430\u043b": "02", february: "02", feb: "02", "\u043c\u0430\u0440": "03", "\u043c\u0430\u0440\u0442": "03", march: "03", apr: "04", "\u0430\u043f\u0440": "04", "\u0430\u043f\u0440\u0435\u043b": "04", april: "04", "\u043c\u0430\u0439": "05", "\u043c\u0430": "05", may: "05", "\u0438\u044e\u043d": "06", "\u0438\u044e\u043d\u044c": "06", june: "06", jun: "06", "\u0438\u044e\u043b": "07", "\u0438\u044e\u043b\u044c": "07", july: "07", jul: "07", "\u0430\u0432\u0433": "08", "\u0430\u0432\u0433\u0443\u0441\u0442": "08", august: "08", aug: "08", "\u0441\u0435\u043d": "09", "\u0441\u0435\u043d\u0442": "09", "\u0441\u0435\u043d\u0442\u044f\u0431\u0440": "09", september: "09", sep: "09", "\u043e\u043a\u0442": "10", "\u043e\u043a\u0442\u044f\u0431\u0440": "10", october: "10", oct: "10", "\u043d\u043e\u044f": "11", "\u043d\u043e\u044f\u0431\u0440": "11", november: "11", nov: "11", "\u0434\u0435\u043a": "12", "\u0434\u0435\u043a\u0430\u0431\u0440": "12", december: "12", dec: "12" }; const ADDRESS_DOCS_SIGNAL_PATTERN = /(?:док|доки|документ|документы|документов|docs?|documents?|doki|docy|doci|bank|выписк|плат[её]ж|оплат|поступлен|списан|операц|опер|transaction)/i; const ADDRESS_BANK_SIGNAL_PATTERN = /(?:bank|банк|банков|выписк|плат[её]ж|оплат|поступлен|списан|операц|опер|расчетн|транзак)/i; const ADDRESS_CONTRACT_SIGNAL_PATTERN = /(?:договор(?:а|у|ом|е)?|(?:^|[^\p{L}\p{N}_])(?:дог\.?|[dд][oо][gг]\.?|dog\.?)(?=$|[^\p{L}\p{N}_])|contract|dogovor)/iu; const ADDRESS_BALANCE_SIGNAL_PATTERN = /(?:остат|сальдо|баланс|взаиморасч|долг|saldo|balance)/i; const ADDRESS_ALL_TIME_PATTERN = /(?:за\s+вс[её]\s+время|за\s+весь\s+период|за\s+всю\s+истори(?:ю|и)|for\s+all\s+time|all\s+time|entire\s+period|full\s+history)/iu; const ADDRESS_MANAGEMENT_PROFILE_PATTERN = /(?:за\s+какие\s+год[а-яё]*|сам(?:ый|ая|ое)\s+(?:актив|пассив)|наименее\s+актив|минимальн|покрыт(?:ие|ия)\s+период|диапазон\s+лет|тип[аы]\s+док(?:умент|ов|и)?|раздел[ыа]\s+уч[её]та|по\s+количеств[аоуе]|редк|реже|(?:сколько|скока|скок)\s+(?:всего\s+)?(?:уникальн(?:ых|ые|ого)?\s+)?контрагент(?:ов|а)?|(?:сколько|скока|скок)\s+(?:у\s+нас\s+)?(?:заказчик(?:ов|а)?|поставщик(?:ов|а)?|клиент(?:ов|а)?|покупател(?:ей|я)|смешан(?:ных|ые)\s+контрагент(?:ов|а)?)|(?:покажи|выведи|список|какие|кто).*(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?).*(?:за\s+вс[её]\s+время|all\s+time|(?:^|[^\d])(19|20)\d{2}(?:[^\d]|$)|(?:^|[^\d])\d{2}\s*(?:г(?:од|ода)?|г)(?:[^\p{L}\p{N}]|$)|за\s+год|в\s+году)|(?:какие|кто|покажи|выведи|список).*(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?).*(?:работал(?:и)?|активн(?:ые|ых|а|о)?).*(?:за\s+вс[её]\s+время|(?:19|20)\d{2}|за\s+год|в\s+году)|договорн(?:ая|ой)\s+баз[аы]|total\s+vs\s+used)/iu; function normalizeAddressMonthAliasToken(token) { const source = String(token ?? "").trim().toLowerCase(); if (!source) { return null; } const direct = ADDRESS_MONTH_ALIAS_MAP[source]; if (direct) { return direct; } for (const [key, value] of Object.entries(ADDRESS_MONTH_ALIAS_MAP)) { if (source.startsWith(key)) { return value; } } return null; } function normalizeAddressShortYearMentions(text) { return String(text ?? "").replace(/(^|[^0-9])(\d{2})\s*(?:г(?:од|ода)?|г|год|year|god)(?=$|[^a-zа-яё0-9])/giu, (_full, prefix, shortYear) => { const normalized = Number(shortYear); if (!Number.isFinite(normalized) || normalized < 0 || normalized > 99) { return _full; } return `${prefix}${String(2000 + normalized)} год`; }); } function sanitizeAddressMessageForFallback(userMessage) { const repaired = compactWhitespace(repairAddressMojibake(String(userMessage ?? ""))); if (!repaired) { return ""; } let sanitized = repaired.toLowerCase(); sanitized = sanitized .replace(/\bpokezh\b/giu, "покажи") .replace(/\bpokazh(?:i)?\b/giu, "покажи") .replace(/\bpokaji\b/giu, "покажи") .replace(/\bop(?:er|ers?)\b/giu, "операции") .replace(/(^|[^\p{L}\p{N}_])опер(?:аци[яиюе]|ы|)?(?=$|[^\p{L}\p{N}_])/giu, "$1операции") .replace(/(^|[^\p{L}\p{N}_])дог\.?(?=$|[^\p{L}\p{N}_])/giu, "$1договор") .replace(/(^|[^\p{L}\p{N}_])dog\.?(?=$|[^\p{L}\p{N}_])/giu, "$1contract") .replace(/\bdoc(?:y|i)\b/giu, "доки") .replace(/\bdok(?:i|y)?\b/giu, "доки") .replace(/\bdocuments?\b/giu, "документы") .replace(/\bdocs?\b/giu, "документы") .replace(/\bschet(?:u)?\b/giu, "счет") .replace(/\bsaldo\b/giu, "сальдо") .replace(/\bgod\b/giu, "год"); sanitized = normalizeAddressShortYearMentions(sanitized); const tokens = sanitized .split(/\s+/) .map((item) => item.trim()) .filter(Boolean); const filteredTokens = tokens.filter((token) => { const normalizedToken = token.replace(/^[^a-zа-яё0-9]+|[^a-zа-яё0-9]+$/giu, ""); if (!normalizedToken) { return true; } return !ADDRESS_FALLBACK_STRIP_TOKENS.has(normalizedToken); }); const compact = compactWhitespace(filteredTokens.join(" ")); return compact || compactWhitespace(repaired.toLowerCase()); } function extractAddressFallbackYear(text) { const source = String(text ?? ""); const fullYearMatch = source.match(/\b(20\d{2})\b/); if (fullYearMatch) { return fullYearMatch[1]; } const shortYearMatch = source.match(/(?:^|[^0-9])(\d{2})\s*(?:г(?:од|ода)?|г|год|year|god)(?=$|[^a-zа-яё0-9])/iu); if (shortYearMatch) { const shortYear = Number(shortYearMatch[1]); if (Number.isFinite(shortYear) && shortYear >= 0 && shortYear <= 99) { return String(2000 + shortYear); } } const shortOrdinalYearMatch = source.match(/(?:^|[^0-9])(\d{2})\s*(?:[-\s]?(?:й|ый|ой|th))(?=$|[^a-zа-яё0-9])/iu); if (!shortOrdinalYearMatch) { return null; } const shortOrdinalYear = Number(shortOrdinalYearMatch[1]); if (!Number.isFinite(shortOrdinalYear) || shortOrdinalYear < 0 || shortOrdinalYear > 99) { return null; } return String(2000 + shortOrdinalYear); } function extractAddressFallbackMonthYear(text) { const source = String(text ?? ""); const numericYearMonth = source.match(/\b(20\d{2})[./-](0?[1-9]|1[0-2])\b/); if (numericYearMonth) { const year = numericYearMonth[1]; const month = String(Number(numericYearMonth[2])).padStart(2, "0"); return `${year}-${month}`; } const numericMonthYear = source.match(/\b(0?[1-9]|1[0-2])[./-](20\d{2})\b/); if (numericMonthYear) { const month = String(Number(numericMonthYear[1])).padStart(2, "0"); const year = numericMonthYear[2]; return `${year}-${month}`; } const namedMonthYear = source.match(/(?:^|[^a-zа-яё0-9])([a-zа-яё]+)\s+(20\d{2})(?=$|[^a-zа-яё0-9])/iu); if (namedMonthYear) { const month = normalizeAddressMonthAliasToken(namedMonthYear[1]); if (month) { return `${namedMonthYear[2]}-${month}`; } } const yearNamedMonth = source.match(/(?:^|[^a-zа-яё0-9])(20\d{2})\s+([a-zа-яё]+)(?=$|[^a-zа-яё0-9])/iu); if (yearNamedMonth) { const month = normalizeAddressMonthAliasToken(yearNamedMonth[2]); if (month) { return `${yearNamedMonth[1]}-${month}`; } } return null; } function extractAddressFallbackAccountToken(text) { const source = String(text ?? ""); const explicitMatch = source.match(/(?:сч[её]т(?:а|у|ом|е)?|account)\D{0,12}(\d{2}(?:[.,]\d{1,2})?)/iu); if (explicitMatch && explicitMatch[1]) { return String(explicitMatch[1]).replace(",", "."); } const tokenPattern = /\b(\d{2}(?:[.,]\d{1,2})?)\b/giu; let match = tokenPattern.exec(source); while (match) { const raw = String(match[1] ?? ""); const start = match.index; const end = start + raw.length; const prev = start > 0 ? source[start - 1] : " "; const next = end < source.length ? source[end] : " "; if (!/[./-]/.test(prev) && !/[./-]/.test(next) && !/\d/.test(prev) && !/\d/.test(next)) { return raw.replace(",", "."); } match = tokenPattern.exec(source); } return null; } function pickAddressFallbackCounterpartyToken(text) { const source = String(text ?? ""); const byAnchor = source.match(/(?:^|[\s,.;:!?()\-])(?:по|от)\s+([a-zа-яё][a-zа-яё0-9._-]{1,})(?=$|[\s,.;:!?()\-])/iu); if (byAnchor && byAnchor[1]) { const byToken = String(byAnchor[1]).trim(); const normalizedByToken = byToken.toLowerCase(); if (byToken && !ADDRESS_PREDECOMPOSE_NOISE_TOKENS.has(normalizedByToken) && !/^\d{2}(?:\.\d{1,2})?$/.test(normalizedByToken) && !/^(?:19|20)\d{2}$/.test(normalizedByToken) && !/^(?:янв|фев|мар|апр|май|июн|июл|авг|сен|сент|окт|ноя|дек|january|february|march|april|may|june|july|august|september|october|november|december)/i.test(normalizedByToken) && !/^(?:договор|договора|договору|договором|договоре|contract|dogovor|dog|дог|d[oо]g|д[oо]г)$/.test(normalizedByToken)) { return byToken; } } const candidates = extractAddressAnchorTokens(text); for (const token of candidates) { const normalized = String(token ?? "").toLowerCase(); if (!normalized || /^\d{2}(?:\.\d{1,2})?$/.test(normalized)) { continue; } if (/^(?:19|20)\d{2}$/.test(normalized)) { continue; } if (/^(?:янв|фев|мар|апр|май|июн|июл|авг|сен|сент|окт|ноя|дек|january|february|march|april|may|june|july|august|september|october|november|december)/i.test(normalized)) { continue; } if (/^(?:договор|договора|договору|договором|договоре|contract|dogovor|dog|дог|d[oо]g|д[oо]г)$/.test(normalized)) { continue; } return token; } return null; } function extractAddressFallbackContractToken(text) { const source = String(text ?? ""); const patterns = [ /(?:договор(?:а|у|ом|е)?|дог\.?|[dд][oо][gг]\.?|contract|dogovor|dog\.?)\s*(?:№|#|n|no\.?)?\s*([a-zа-я0-9][a-zа-я0-9/_-]{1,})/iu, /(?:№|#|n|no\.?)\s*([a-zа-я0-9][a-zа-я0-9/_-]{1,})\s*(?:договор(?:а|у|ом|е)?|дог\.?|[dд][oо][gг]\.?|contract|dogovor|dog\.?)/iu ]; for (const pattern of patterns) { const match = pattern.exec(source); if (!match || !match[1]) { continue; } const candidate = String(match[1]).replace(/^[^a-zа-я0-9]+|[^a-zа-я0-9/_-]+$/giu, ""); if (!candidate || candidate.length < 2) { continue; } if (/^(?:19|20)\d{2}$/.test(candidate)) { continue; } if (/^(?:19|20)\d{2}[./-](?:0?[1-9]|1[0-2])(?:[./-](?:0?[1-9]|[12]\d|3[01]))?$/.test(candidate)) { continue; } return candidate; } if (ADDRESS_CONTRACT_SIGNAL_PATTERN.test(source) || /(?:^|[^\p{L}\p{N}_])(?:[dд][oо][gг]|dogovor)(?=$|[^\p{L}\p{N}_])/iu.test(source)) { const generic = source.match(/\b([a-zа-я0-9]{1,10}[/-][a-zа-я0-9]{1,10}(?:[/-][a-zа-я0-9]{1,10})?)\b/iu); if (generic && generic[1]) { return generic[1]; } } return null; } function resolveAddressDeterministicFallback(userMessage, sanitizedUserMessage) { const sourceRaw = compactWhitespace(repairAddressMojibake(String(userMessage ?? ""))); const source = compactWhitespace(String(sanitizedUserMessage ?? sourceRaw).toLowerCase()); if (!source) { return null; } if (ADDRESS_MANAGEMENT_PROFILE_PATTERN.test(source)) { return null; } const monthYear = extractAddressFallbackMonthYear(source); const year = extractAddressFallbackYear(source); const allTime = ADDRESS_ALL_TIME_PATTERN.test(source); const account = extractAddressFallbackAccountToken(source); const docsSignal = ADDRESS_DOCS_SIGNAL_PATTERN.test(source); const bankSignal = ADDRESS_BANK_SIGNAL_PATTERN.test(source); const contractSignal = ADDRESS_CONTRACT_SIGNAL_PATTERN.test(source); const balanceSignal = ADDRESS_BALANCE_SIGNAL_PATTERN.test(source); const hasIndexPointerSignal = /(?:\u043f\u0443\u043d\u043a\u0442|\u043f\u043e\u0437\u0438\u0446|\u0441\u0442\u0440\u043e\u043a|item|row|line)/iu.test(sourceRaw); if (hasIndexPointerSignal && extractDisplayedEntityIndexMention(sourceRaw) !== null) { return null; } if (balanceSignal && account) { let periodClause = ""; let rule = "balance_account_rewrite"; if (monthYear) { periodClause = ` на ${monthYear}`; rule = "balance_month_period_rewrite"; } else if (year) { periodClause = ` на ${year}-12-31`; rule = "balance_year_period_rewrite"; } const candidate = compactWhitespace(`остаток по счету ${account}${periodClause}`); if (candidate && candidate !== sourceRaw.toLowerCase()) { return { candidate, rule }; } } if (!docsSignal && !contractSignal && !balanceSignal) { const counterparty = pickAddressFallbackCounterpartyToken(source); const genericLookupSignal = /(?:\bесть\b|\bпокажи\b|\bвыведи\b|\bч[её]\b|\bчто\b)/iu.test(source); if (counterparty && (allTime || monthYear || year) && genericLookupSignal) { let periodClause = ""; let rule = "documents_counterparty_rewrite_from_generic_lookup"; if (allTime) { periodClause = " за все время"; rule = "documents_counterparty_all_time_rewrite_from_generic_lookup"; } else if (monthYear) { periodClause = ` за ${monthYear}`; rule = "documents_counterparty_month_rewrite_from_generic_lookup"; } else if (year) { periodClause = ` за ${year} год`; rule = "documents_counterparty_year_rewrite_from_generic_lookup"; } const candidate = compactWhitespace(`документы по контрагенту ${counterparty}${periodClause}`); if (candidate && candidate !== sourceRaw.toLowerCase()) { return { candidate, rule }; } } } if (docsSignal) { const contract = extractAddressFallbackContractToken(sourceRaw || source); if (contractSignal || contract) { if (contract) { let periodClause = ""; let rule = bankSignal ? "bank_operations_contract_rewrite" : "documents_contract_rewrite"; if (allTime) { periodClause = " за все время"; rule = bankSignal ? "bank_operations_contract_all_time_rewrite" : "documents_contract_all_time_rewrite"; } else if (monthYear) { periodClause = ` за ${monthYear}`; rule = bankSignal ? "bank_operations_contract_month_rewrite" : "documents_contract_month_rewrite"; } else if (year) { periodClause = ` за ${year} год`; rule = bankSignal ? "bank_operations_contract_year_rewrite" : "documents_contract_year_rewrite"; } const subject = bankSignal ? "банковские операции" : "документы"; const candidate = compactWhitespace(`${subject} по договору ${contract}${periodClause}`); if (candidate && candidate !== sourceRaw.toLowerCase()) { return { candidate, rule }; } } } else { const counterparty = pickAddressFallbackCounterpartyToken(source); if (counterparty) { let periodClause = ""; const subject = bankSignal ? "банковские операции" : "документы"; const rulePrefix = bankSignal ? "bank_operations_counterparty" : "documents_counterparty"; let rule = `${rulePrefix}_rewrite`; if (allTime) { periodClause = " за все время"; rule = `${rulePrefix}_all_time_rewrite`; } else if (monthYear) { periodClause = ` за ${monthYear}`; rule = `${rulePrefix}_month_rewrite`; } else if (year) { periodClause = ` за ${year} год`; rule = `${rulePrefix}_year_rewrite`; } const candidate = compactWhitespace(`${subject} по контрагенту ${counterparty}${periodClause}`); if (candidate && candidate !== sourceRaw.toLowerCase()) { return { candidate, rule }; } } } } if (source !== sourceRaw.toLowerCase() && isAddressLlmPreDecomposeCandidate(source)) { return { candidate: source, rule: "noise_cleanup" }; } return null; } function textMojibakeScoreForAddress(value) { const source = String(value ?? ""); const cyrillic = (source.match(/[А-Яа-яЁё]/g) ?? []).length; const latin = (source.match(/[A-Za-z]/g) ?? []).length; const hardMarkers = (source.match(/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ�?’“”•–—™љ›њќћџ]/g) ?? []).length; const pairMarkers = (source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length; const doubleEncodedMarkers = (source.match(/(?:Г[Ђ-џ]|В[Ђ-џ]|Ã.|Â.)/gu) ?? []).length; return cyrillic + latin - hardMarkers * 3 - pairMarkers * 2 - doubleEncodedMarkers * 2; } function looksLikeMojibakeForAddress(value) { const source = String(value ?? ""); if (!source.trim()) { return false; } if (/[Ѓѓ‚„…†‡€‰‹ЉЊЌЋЏ�?’“”•–—™љ›њќћџ]/.test(source)) { return true; } if ((source.match(/(?:Р.|С.|Ð.|Ñ.)/g) ?? []).length >= 2) { return true; } if ((source.match(/(?:Г[Ђ-џ]|В[Ђ-џ]|Ã.|Â.)/gu) ?? []).length >= 2) { return true; } return false; } function repairAddressMojibake(value) { const source = String(value ?? ""); if (!looksLikeMojibakeForAddress(source)) { return source; } let candidate = source; for (let pass = 0; pass < 3; pass += 1) { let improved = false; try { const fromWin1251 = iconv_lite_1.default.encode(candidate, "win1251").toString("utf8"); if (textMojibakeScoreForAddress(fromWin1251) > textMojibakeScoreForAddress(candidate)) { candidate = fromWin1251; improved = true; } } catch (_error) { } try { const fromLatin1 = Buffer.from(candidate, "latin1").toString("utf8"); if (textMojibakeScoreForAddress(fromLatin1) > textMojibakeScoreForAddress(candidate)) { candidate = fromLatin1; improved = true; } } catch (_error) { } if (!improved) { break; } } return candidate; } function sanitizeOutgoingAssistantText(value, fallback = "Не смог сформировать читаемый ответ. Уточните запрос.") { const repaired = repairAddressMojibake(String(value ?? "")); const sanitized = String((0, answerComposer_1.sanitizeAssistantReplyForUserFacing)(repaired) ?? "").trim(); if (sanitized) { return sanitized; } const fallbackText = String(fallback ?? "").trim(); return fallbackText || "Не смог сформировать читаемый ответ. Уточните запрос."; } function extractAddressAnchorTokens(value) { const source = repairAddressMojibake(compactWhitespace(String(value ?? "").toLowerCase())); if (!source) { return []; } const tokens = source .split(/[^a-zа-яё0-9._-]+/iu) .map((item) => item.trim()) .filter((item) => item.length >= 2); const filtered = []; for (const token of tokens) { if (/^\d+$/.test(token)) { continue; } if (/^(?:19|20)\d{2}$/.test(token)) { continue; } if (/^(?:0?[1-9]|1[0-2])[./-](?:19|20)\d{2}$/.test(token) || /^(?:19|20)\d{2}[./-](?:0?[1-9]|1[0-2])$/.test(token)) { continue; } if (/^(?:янв|фев|мар|апр|май|июн|июл|авг|сен|сент|окт|ноя|дек|january|february|march|april|may|june|july|august|september|october|november|december)/i.test(token)) { continue; } if (ADDRESS_PREDECOMPOSE_NOISE_TOKENS.has(token)) { continue; } filtered.push(token); } return Array.from(new Set(filtered)); } function selectPreferredAddressFragmentCandidate(rawText, normalizedText) { const normalizedCandidate = compactWhitespace(repairAddressMojibake(normalizedText ?? "")); const rawCandidate = compactWhitespace(repairAddressMojibake(rawText ?? "")); if (!normalizedCandidate && !rawCandidate) { return null; } if (!normalizedCandidate) { return rawCandidate; } if (!rawCandidate) { return normalizedCandidate; } const normalizedAnchors = extractAddressAnchorTokens(normalizedCandidate); const rawAnchors = extractAddressAnchorTokens(rawCandidate); if (rawAnchors.length > 0 && normalizedAnchors.length === 0) { return rawCandidate; } return normalizedCandidate; } function readAddressFilterString(addressDebug, key) { const filters = addressDebug?.extracted_filters; if (!filters || typeof filters !== "object") { return null; } return toNonEmptyString(filters[key]); } function isAddressLaneDebugPayload(debug) { if (!debug || typeof debug !== "object") { return false; } if (debug.detected_mode === "address_query") { return true; } if (typeof debug.selected_recipe === "string" && debug.selected_recipe.trim().length > 0) { return true; } if (typeof debug.mcp_call_status === "string" && debug.mcp_call_status.trim().length > 0) { return true; } if (typeof debug.anchor_type === "string" && debug.anchor_type.trim().length > 0) { return true; } if (debug.extracted_filters && typeof debug.extracted_filters === "object") { const keys = Object.keys(debug.extracted_filters); if (keys.length > 0 && typeof debug.detected_intent === "string" && debug.detected_intent.trim().length > 0) { return true; } } return false; } function findLastAddressAssistantItem(items) { for (let index = items.length - 1; index >= 0; index -= 1) { const item = items[index]; if (!item || item.role !== "assistant" || !item.debug) { continue; } const debug = item.debug; if (isAddressLaneDebugPayload(debug)) { return item; } } return null; } function findLastAddressAssistantDebug(items) { return findLastAddressAssistantItem(items)?.debug ?? null; } const FOLLOWUP_DISPLAY_COUNTERPARTY_STOPWORDS = new Set([ "группа", "компания", "организация", "контрагент", "контрагента", "контрагенту", "клиент", "клиента", "клиенту", "заказчик", "заказчика", "заказчику", "поставщик", "поставщика", "поставщику", "ип", "ооо", "ао", "зао", "пао", "оао", "llc", "ltd", "inc", "corp", "company", "group", "vendor", "supplier", "customer", "client" ]); const FOLLOWUP_DISPLAY_COUNTERPARTY_LEGAL_TOKENS = new Set([ "ип", "ооо", "ао", "зао", "пао", "оао", "llc", "ltd", "inc", "corp", "company", "group" ]); const FOLLOWUP_DISPLAY_ENTITY_TYPE_BY_INTENT = { counterparty_activity_lifecycle: "counterparty", customer_revenue_and_payments: "counterparty", supplier_payouts_profile: "counterparty", counterparty_population_and_roles: "counterparty", contract_usage_and_value: "contract", list_contracts_by_counterparty: "contract" }; function normalizeCounterpartyForFollowupMatch(value) { return compactWhitespace(repairAddressMojibake(String(value ?? "")) .toLowerCase() .replace(/ё/g, "е") .replace(/[«»"'`“”„’�?]/g, " ") .replace(/[^a-zа-я0-9\s._-]+/giu, " ")); } function normalizeCounterpartyTokenForFollowupMatch(value) { return normalizeCounterpartyForFollowupMatch(value).replace(/[._-]+/g, ""); } function normalizeCounterpartyStemForFollowupMatch(value) { const compact = normalizeCounterpartyTokenForFollowupMatch(value); if (!compact || !/[а-яё]/iu.test(compact)) { return compact; } const stem = compact.replace(/(?:иями|ями|ами|ией|ей|ий|ов|ев|ом|ем|ах|ях|ую|юю|ая|яя|ое|ее|ые|ие|ого|его|ому|ему|ыми|ими|ым|им|ам|ям|у|ю|а|я|е|и|ы|о)$/iu, ""); return stem.length >= 3 ? stem : compact; } function inferDisplayedEntityTypeFromIntent(intent) { const normalized = compactWhitespace(String(intent ?? "").toLowerCase()); if (!normalized) { return "unknown"; } return FOLLOWUP_DISPLAY_ENTITY_TYPE_BY_INTENT[normalized] ?? "unknown"; } function extractDisplayedAddressEntityCandidates(replyText, entityType = "unknown") { if (entityType === "unknown") { return []; } const lines = String(replyText ?? "").split(/\r?\n/); const candidates = []; for (const line of lines) { const compactLine = compactWhitespace(line); if (!compactLine) { continue; } const numberedMatch = compactLine.match(/^(\d+)\.\s+(.+)$/); if (!numberedMatch) { continue; } const index = Number.parseInt(String(numberedMatch[1] ?? ""), 10); if (!Number.isFinite(index) || index <= 0) { continue; } const afterNumber = String(numberedMatch[2] ?? ""); const parts = afterNumber.split("|").map((item) => compactWhitespace(item)); let counterpartyCandidate = parts[0] ?? ""; if (parts.length >= 2 && /^\d{4}-\d{2}-\d{2}/.test(parts[0] ?? "")) { counterpartyCandidate = parts[1] ?? counterpartyCandidate; } const cleanedCandidate = compactWhitespace(counterpartyCandidate.replace(/^["'«»“”„`’�?]+|["'«»“”„`’�?]+$/gu, "")); if (!cleanedCandidate || cleanedCandidate.length < 2) { continue; } candidates.push({ index, value: cleanedCandidate, entityType }); } const dedup = new Map(); for (const candidate of candidates) { const key = `${candidate.entityType}:${candidate.index}:${normalizeCounterpartyForFollowupMatch(candidate.value)}`; if (!dedup.has(key)) { dedup.set(key, candidate); } } return Array.from(dedup.values()); } function buildCounterpartyAliasesForFollowupMatch(counterpartyName) { const aliases = new Set(); const normalized = normalizeCounterpartyForFollowupMatch(counterpartyName); if (!normalized) { return []; } aliases.add(normalized); const normalizedTokens = normalized .split(/\s+/) .map((token) => token.trim()) .filter(Boolean); const tokensForAlias = Array.from(new Set(normalizedTokens.flatMap((token) => [token, ...token.split(/-+/).map((part) => part.trim()).filter(Boolean)]))); const withoutLegalTokens = tokensForAlias .filter((token) => !FOLLOWUP_DISPLAY_COUNTERPARTY_LEGAL_TOKENS.has(token)) .join(" "); if (withoutLegalTokens) { aliases.add(withoutLegalTokens); } for (const token of tokensForAlias) { const compactToken = normalizeCounterpartyTokenForFollowupMatch(token); if (compactToken.length < 3) { continue; } if (FOLLOWUP_DISPLAY_COUNTERPARTY_STOPWORDS.has(compactToken)) { continue; } if (/^(?:19|20)\d{2}$/.test(compactToken)) { continue; } aliases.add(compactToken); const stemToken = normalizeCounterpartyStemForFollowupMatch(compactToken); if (stemToken.length >= 4) { aliases.add(stemToken); } } return Array.from(aliases) .map((alias) => compactWhitespace(alias)) .filter((alias) => alias.length > 0) .sort((left, right) => right.length - left.length); } function hasCounterpartyAliasMention(normalizedMessage, alias) { const trimmedAlias = compactWhitespace(String(alias ?? "").toLowerCase()); if (!trimmedAlias) { return false; } const aliasPattern = escapeRegex(trimmedAlias).replace(/\s+/g, "\\s+"); const boundaryPattern = new RegExp(`(?:^|[^a-zа-я0-9])${aliasPattern}(?:$|[^a-zа-я0-9])`, "iu"); if (boundaryPattern.test(normalizedMessage)) { return true; } if (trimmedAlias.length < 4 || !/[а-яё]/iu.test(trimmedAlias)) { return false; } const fuzzyPattern = new RegExp(`(?:^|[^a-zа-я0-9])${aliasPattern}[а-яё]{0,3}(?:$|[^a-zа-я0-9])`, "iu"); return fuzzyPattern.test(normalizedMessage); } function extractDisplayedEntityIndexMention(userMessage) { const normalized = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase()); if (!normalized) { return null; } const tokenStart = "(?:^|[^\\p{L}\\p{N}_])"; const tokenEnd = "(?=$|[^\\p{L}\\p{N}_])"; const pointerPattern = "(?:\\u043f\\u0443\\u043d\\u043a\\u0442(?:\\u0430|\\u0443|\\u043e\\u043c)?|\\u043f\\u043e\\u0437\\u0438\\u0446\\u0438(?:\\u044f|\\u0438|\\u044e|\\u0435\\u0439)|\\u0441\\u0442\\u0440\\u043e\\u043a(?:\\u0430|\\u0438|\\u0435|\\u0443)|item|row|line)"; const pointerSignalPattern = new RegExp(`${tokenStart}${pointerPattern}${tokenEnd}`, "iu"); const directPattern = new RegExp(`${tokenStart}${pointerPattern}${tokenEnd}\\D{0,8}(\\d{1,3})(?!\\d)`, "iu"); const directMatch = normalized.match(directPattern); if (directMatch) { const value = Number.parseInt(String(directMatch[1] ?? ""), 10); return Number.isFinite(value) && value > 0 ? value : null; } const reversePattern = new RegExp(`${tokenStart}(\\d{1,3})(?:-?(?:\\u0439|\\u044f|\\u0435|\\u0433\\u043e|\\u043c\\u0443))?\\s+${pointerPattern}${tokenEnd}`, "iu"); const reverseMatch = normalized.match(reversePattern); if (reverseMatch) { const value = Number.parseInt(String(reverseMatch[1] ?? ""), 10); return Number.isFinite(value) && value > 0 ? value : null; } if (pointerSignalPattern.test(normalized)) { const numericMatches = Array.from(normalized.matchAll(/(?:^|[^\p{N}])(\d{1,3})(?!\d)/gu)) .map((match) => Number.parseInt(String(match[1] ?? ""), 10)) .filter((value) => Number.isFinite(value) && value > 0); if (numericMatches.length === 1) { return numericMatches[0]; } } return null; } function resolveDisplayedAddressEntityMention(userMessage, displayedEntities) { const normalizedMessage = normalizeCounterpartyForFollowupMatch(userMessage); if (!normalizedMessage) { return null; } if (!Array.isArray(displayedEntities) || displayedEntities.length === 0) { return null; } const indexMention = extractDisplayedEntityIndexMention(userMessage); if (indexMention !== null) { const indexedCandidate = displayedEntities.find((candidate) => Number(candidate.index) === indexMention); if (indexedCandidate) { return { value: indexedCandidate.value, entityType: indexedCandidate.entityType, matchKind: "index", index: indexedCandidate.index }; } } let bestMatch = null; for (const candidate of displayedEntities) { const aliases = buildCounterpartyAliasesForFollowupMatch(candidate.value); for (const alias of aliases) { if (!hasCounterpartyAliasMention(normalizedMessage, alias)) { continue; } const score = alias.length * 10 + (normalizeCounterpartyForFollowupMatch(candidate.value) === alias ? 1 : 0); if (!bestMatch || score > bestMatch.score) { bestMatch = { value: candidate.value, entityType: candidate.entityType, index: candidate.index, matchKind: "alias", score }; } break; } } if (!bestMatch) { return null; } return { value: bestMatch.value, entityType: bestMatch.entityType, matchKind: bestMatch.matchKind, index: bestMatch.index }; } function findRecentAddressFilterValue(items, key) { for (let index = items.length - 1; index >= 0; index -= 1) { const item = items[index]; if (!item || item.role !== "assistant" || !item.debug) { continue; } const debug = item.debug; if (!isAddressLaneDebugPayload(debug)) { continue; } const directFilterValue = readAddressFilterString(debug, key); if (directFilterValue) { return directFilterValue; } if (key === "contract" && String(debug.anchor_type ?? "").trim() === "contract") { const anchorValue = toNonEmptyString(debug.anchor_value_resolved) ?? toNonEmptyString(debug.anchor_value_raw); if (anchorValue) { return anchorValue; } } if (key === "counterparty" && String(debug.anchor_type ?? "").trim() === "counterparty") { const anchorValue = toNonEmptyString(debug.anchor_value_resolved) ?? toNonEmptyString(debug.anchor_value_raw); if (anchorValue) { return anchorValue; } } } return null; } const ADDRESS_FOLLOWUP_OFFER_BY_INTENT = { list_documents_by_counterparty: ["bank_operations_by_counterparty", "list_contracts_by_counterparty"], bank_operations_by_counterparty: ["list_documents_by_counterparty", "list_contracts_by_counterparty"], list_contracts_by_counterparty: ["list_documents_by_contract", "bank_operations_by_contract"], list_documents_by_contract: ["bank_operations_by_contract"], bank_operations_by_contract: ["list_documents_by_contract"], open_items_by_counterparty_or_contract: ["list_documents_by_counterparty", "bank_operations_by_counterparty"] }; function buildAddressFollowupOffer(addressDebug) { if (!isAddressLaneDebugPayload(addressDebug)) { return null; } const intent = toNonEmptyString(addressDebug.detected_intent); if (!intent) { return null; } const suggestedIntents = ADDRESS_FOLLOWUP_OFFER_BY_INTENT[intent]; if (!Array.isArray(suggestedIntents) || suggestedIntents.length === 0) { return null; } const anchorType = toNonEmptyString(addressDebug.anchor_type); const anchorValue = toNonEmptyString(addressDebug.anchor_value_resolved) ?? toNonEmptyString(addressDebug.anchor_value_raw) ?? readAddressFilterString(addressDebug, "counterparty") ?? readAddressFilterString(addressDebug, "contract") ?? readAddressFilterString(addressDebug, "account"); return { enabled: true, source_intent: intent, anchor_type: anchorType ?? "unknown", anchor_value: anchorValue, suggested_intents: suggestedIntents }; } function hasAddressFollowupOffer(addressDebug) { if (!addressDebug || typeof addressDebug !== "object") { return false; } const existingOffer = addressDebug.address_followup_offer; if (existingOffer && typeof existingOffer === "object") { return existingOffer.enabled === true; } return Boolean(buildAddressFollowupOffer(addressDebug)); } function isImplicitAddressContinuationByLlm(userMessage, llmPreDecomposeMeta) { const contract = llmPreDecomposeMeta?.predecomposeContract; if (!contract || typeof contract !== "object") { return false; } const entities = contract.entities && typeof contract.entities === "object" ? contract.entities : {}; const hasEntity = [entities.account, entities.counterparty, entities.contract, entities.document_type, entities.document_ref, entities.organization] .some((value) => Boolean(toNonEmptyString(value))); if (hasEntity) { return false; } const hasExplicitPeriod = Boolean(contract.period && typeof contract.period === "object" && contract.period.has_explicit_period); if (hasExplicitPeriod) { return false; } const mode = toNonEmptyString(contract.mode) ?? "unknown"; const modeConfidence = toNonEmptyString(contract.mode_confidence) ?? "low"; const intent = toNonEmptyString(contract.intent) ?? "unknown"; const intentConfidence = toNonEmptyString(contract.intent_confidence) ?? "low"; const shape = toNonEmptyString(contract.query_shape) ?? "UNKNOWN"; const shapeConfidence = toNonEmptyString(contract.query_shape_confidence) ?? "low"; const looksUnderspecified = (mode !== "address_query" || modeConfidence === "low") && (intent === "unknown" || intentConfidence === "low") && (shape === "UNKNOWN" || shapeConfidence === "low"); if (!looksUnderspecified) { return false; } const normalized = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase()); if (!normalized || hasSmallTalkSignal(normalized) || /[??]$/.test(normalized)) { return false; } const tokenCount = countTokens(normalized); return tokenCount > 0 && tokenCount <= 4; } function hasAddressFollowupContextSignal(userMessage) { const rawText = compactWhitespace(String(userMessage ?? "").toLowerCase()); const repaired = repairAddressMojibake(String(userMessage ?? "")); const repairedText = compactWhitespace(repaired.toLowerCase()); const samples = [rawText, repairedText].filter((item) => item.length > 0); if (samples.length === 0) { return false; } const hasAny = (pattern) => samples.some((sample) => pattern.test(sample)); const hasMarker = () => samples.some((sample) => hasFollowupMarker(sample)); const hasPointer = () => samples.some((sample) => hasReferentialPointer(sample)); const minTokens = samples.reduce((min, sample) => Math.min(min, countTokens(sample)), Number.POSITIVE_INFINITY); const shortFollowup = minTokens <= 8; const ultraShortFollowup = minTokens <= 3; const debtRoleSwapToReceivables = shortFollowup && (/^(?:\u0430|a|\u0438|i)\s+(?:\u043d\u0430\u043c\s+)?\u043a\u0442\u043e(?=$|[\s,.;:!?])/iu.test(rawText) || /^(?:р°|a|рё|i)\s+(?:рЅр°рј\s+)?рєс‚рѕ(?=$|[\s,.;:!?])/iu.test(rawText)); if (debtRoleSwapToReceivables) { return true; } const debtRoleSwapToPayables = shortFollowup && (/^(?:\u0430|a|\u0438|i)\s+(?:\u043c\u044b\s+)?\u043a\u043e\u043c\u0443(?=$|[\s,.;:!?])/iu.test(rawText) || /^(?:р°|a|рё|i)\s+(?:рјс‹\s+)?рєрѕрјсѓ(?=$|[\s,.;:!?])/iu.test(rawText)); if (debtRoleSwapToPayables) { return true; } const shortContinuationCue = ultraShortFollowup && (/^(?:\u0434\u0430\u0432\u0430\u0439|\u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0439|\u043f\u043e\u043a\u0430\u0437\u044b\u0432\u044b\u0430\u0439|\u0435\u0449[\u0435\u0451]|also|again|go|ok|okay)(?=$|[\s,.;:!?])/iu.test(rawText) || /^(?:рґр°рір°р№|рїрѕрєр°р·с‹рір°р№|рїрѕрєр°р·с‹ріс‹р°р№|рµс‰[рµс‘]|also|again|go|ok|okay)(?=$|[\s,.;:!?])/iu.test(rawText)); if (shortContinuationCue) { return true; } const shortVatCue = ultraShortFollowup && /^(?:(?:\u0430|\u0438)\s+)?(?:(?:\u043f\u043e|po)\s+)?(?:\u043d\u0434\u0441|vat)(?=$|[\s,.;:!?])/iu.test(rawText); if (shortVatCue) { return true; } const shortCurrentDateCue = shortFollowup && samples.some((sample) => hasShortCurrentDateFollowupLiteral(sample)); if (shortCurrentDateCue) { return true; } if (shortFollowup && hasAny(/^(?:а|a|и|i)\s+(?:нам\s+)?кто(?=$|[\s,.;:!?])/iu)) { return true; } if (shortFollowup && hasAny(/^(?:а|a|и|i)\s+(?:мы\s+)?кому(?=$|[\s,.;:!?])/iu)) { return true; } if (ultraShortFollowup && hasAny(/^(?:давай|показывай|показывыай|ещ[её]|also|again|go|ok|okay)(?=$|[\s,.;:!?])/iu)) { return true; } if (hasStandaloneAddressTopicSignal(rawText || repairedText)) { return false; } if (shouldHandleAsAssistantCapabilityMetaQuery(rawText || repairedText)) { return false; } if (hasAny(/(?:за\s+вс[её]\s+время|за\s+весь\s+период|за\s+всю\s+истори(?:ю|и)|за\s+любой\s+период|for\s+all\s+time|all\s+time|for\s+entire\s+period|entire\s+period|for\s+any\s+period|any\s+period)/iu)) { return true; } if (hasPointer()) { return true; } if (hasAny(/(?:на\s+ту\s+же\s+дат[ауеы]|на\s+эту\s+же\s+дат[ауеы]|same\s+date|the\s+same\s+date|as\s+of\s+same\s+date)/iu)) { return true; } if (hasAny(/(?:кроме|помимо)\s+(?:этого|этой|этот|эту|этих|этого\s+документа|этого\s+договора|этого\s+контрагента)/iu)) { return true; } if (hasAny(/(?:есть\s+ещ[её]|что\s+ещ[её]|ещ[её]\s+что|ещ[её]\s+что-?то|остал(?:ось|ось\?)|друг(?:ое|ие))/iu) && minTokens <= 12) { return true; } if (shortFollowup && hasMarker()) { return true; } if (shortFollowup && hasAny(/(?:^|\s)(?:также|тоже|also|same|again|ещ[её]|теперь|then|now)(?=$|[\s,.;:!?])/iu)) { return true; } if (shortFollowup && hasAny(/(?:кто\s+из\s+(?:них|этих|тех)|кто\s+нов(?:ые|ых|ый)|кто\s+потом\s+исчез|кто\s+был\s+(?:только|ровно)\s+один\s+раз)/iu)) { return true; } if (shortFollowup && hasAny(/(?:почему|why|из[-\s]?за\s+чего|как\s+так|reason)/iu) && hasAny(/(?:ндс|vat|прогноз|к\s+уплате|нул|ноль|\b0(?:[.,]0+)?\b)/iu)) { return true; } if (shortFollowup && hasAny(/(?:^|\s)по\s+[a-zа-яё][a-zа-яё0-9._-]{1,}(?=$|[\s,.;:!?])/iu) && !hasAny(/(?:по\s+этому|по\s+тому|по\s+нему|по\s+ней|по\s+ним)/iu)) { return true; } if (shortFollowup && samples.some((sample) => hasPeriodLiteral(sample))) { return true; } if (shortFollowup && samples.some((sample) => hasShortNamedPeriodFollowupLiteral(sample))) { return true; } return false; } function hasShortDebtMirrorFollowupSignal(userMessage) { const rawText = compactWhitespace(String(userMessage ?? "").toLowerCase()); const repairedText = compactWhitespace(repairAddressMojibake(String(userMessage ?? "")).toLowerCase()); const samples = [rawText, repairedText].filter((item) => item.length > 0); if (samples.length === 0) { return false; } const minTokens = samples.reduce((min, sample) => Math.min(min, countTokens(sample)), Number.POSITIVE_INFINITY); if (minTokens > 8) { return false; } return samples.some((sample) => /^(?:а|a|и|i)\s+(?:нам\s+)?кто(?=$|[\s,.;:!?])/iu.test(sample) || /^(?:а|a|и|i)\s+(?:мы\s+)?кому(?=$|[\s,.;:!?])/iu.test(sample) || /^(?:р°|a|рё|i)\s+(?:рЅр°рј\s+)?рєс‚рѕ(?=$|[\s,.;:!?])/iu.test(sample) || /^(?:р°|a|рё|i)\s+(?:рјс‹\s+)?рєрѕрјсѓ(?=$|[\s,.;:!?])/iu.test(sample)); } function resolveDebtRoleSwapFollowupIntent(userMessage, previousIntent) { const normalized = compactWhitespace(String(userMessage ?? "").toLowerCase()); if (!normalized || countTokens(normalized) > 10) { return null; } const hasReceivablesCue = /(?:нам\s+кто\s+долж|кто\s+нам\s+долж|кто\s+долж[а-яё]*\s+нам|дебитор|к\s+получению|к\s+взысканию|receivable)/iu.test(normalized) || /^(?:а|a|и|i)\s+(?:нам\s+)?кто(?=$|[\s,.;:!?])/iu.test(normalized) || /^(?:р°|a|рё|i)\s+(?:рЅр°рј\s+)?рєс‚рѕ(?=$|[\s,.;:!?])/iu.test(normalized); const hasPayablesCue = /(?:мы\s+кому\s+долж|кому\s+мы\s+долж|кому\s+долж[а-яё]*\s+мы|кредитор|к\s+уплате|payable)/iu.test(normalized) || /^(?:а|a|и|i)\s+(?:мы\s+)?кому(?=$|[\s,.;:!?])/iu.test(normalized) || /^(?:р°|a|рё|i)\s+(?:рјс‹\s+)?рєрѕрјсѓ(?=$|[\s,.;:!?])/iu.test(normalized); if ((previousIntent === "payables_confirmed_as_of_date" || previousIntent === "list_payables_counterparties") && hasReceivablesCue) { return "receivables_confirmed_as_of_date"; } if ((previousIntent === "receivables_confirmed_as_of_date" || previousIntent === "list_receivables_counterparties") && hasPayablesCue) { return "payables_confirmed_as_of_date"; } return null; } function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMessage = null, llmPreDecomposeMeta = null) { const previousAddressItem = findLastAddressAssistantItem(items); const previousAddressDebug = previousAddressItem?.debug ?? null; const followupOffer = previousAddressDebug ? buildAddressFollowupOffer(previousAddressDebug) : null; const hasImplicitContinuationSignal = Boolean(previousAddressDebug) && Boolean(followupOffer?.enabled) && (isImplicitAddressContinuationByLlm(userMessage, llmPreDecomposeMeta) || (toNonEmptyString(alternateMessage) ? isImplicitAddressContinuationByLlm(alternateMessage, llmPreDecomposeMeta) : false)); const sourceIntentHint = toNonEmptyString(previousAddressDebug?.detected_intent); const debtRoleSwapPrimary = sourceIntentHint ? resolveDebtRoleSwapFollowupIntent(userMessage, sourceIntentHint) : null; const debtRoleSwapAlternate = sourceIntentHint && toNonEmptyString(alternateMessage) ? resolveDebtRoleSwapFollowupIntent(String(alternateMessage ?? ""), sourceIntentHint) : null; const debtRoleSwapIntent = debtRoleSwapPrimary ?? debtRoleSwapAlternate ?? null; const hasPrimaryFollowupSignal = hasAddressFollowupContextSignal(userMessage) || Boolean(debtRoleSwapPrimary); const hasAlternateFollowupSignal = toNonEmptyString(alternateMessage) ? hasAddressFollowupContextSignal(alternateMessage) || Boolean(debtRoleSwapAlternate) : false; const hasPrimaryIndexReferenceSignal = extractDisplayedEntityIndexMention(userMessage) !== null; const hasAlternateIndexReferenceSignal = toNonEmptyString(alternateMessage) ? extractDisplayedEntityIndexMention(String(alternateMessage ?? "")) !== null : false; const hasIndexReferenceSignal = hasPrimaryIndexReferenceSignal || hasAlternateIndexReferenceSignal; const hasStandaloneAddressTopic = hasStandaloneAddressTopicSignal(userMessage) || (toNonEmptyString(alternateMessage) ? hasStandaloneAddressTopicSignal(alternateMessage) : false); if (hasStandaloneAddressTopic && !hasPrimaryFollowupSignal && !hasAlternateFollowupSignal && !hasImplicitContinuationSignal && !hasIndexReferenceSignal) { return null; } if (!hasPrimaryFollowupSignal && !hasAlternateFollowupSignal && !hasImplicitContinuationSignal && !hasIndexReferenceSignal) { return null; } if (!previousAddressDebug) { return null; } const sourceIntent = toNonEmptyString(previousAddressDebug.detected_intent); let previousIntent = sourceIntent; let followupSelectionMode = "carry_previous_intent"; if (debtRoleSwapIntent) { previousIntent = debtRoleSwapIntent; } if (hasImplicitContinuationSignal) { const suggestedIntent = Array.isArray(followupOffer?.suggested_intents) ? toNonEmptyString(followupOffer.suggested_intents[0]) : null; if (suggestedIntent) { previousIntent = suggestedIntent; followupSelectionMode = "switch_to_suggested_intent"; } } let previousAnchorType = toNonEmptyString(previousAddressDebug.anchor_type); let previousAnchor = toNonEmptyString(previousAddressDebug.anchor_value_resolved) ?? toNonEmptyString(previousAddressDebug.anchor_value_raw) ?? readAddressFilterString(previousAddressDebug, "counterparty") ?? readAddressFilterString(previousAddressDebug, "account") ?? readAddressFilterString(previousAddressDebug, "contract"); let resolvedCounterpartyFromDisplay = false; const previousFiltersRaw = previousAddressDebug.extracted_filters; const previousFilters = previousFiltersRaw && typeof previousFiltersRaw === "object" ? { ...previousFiltersRaw } : {}; if (!toNonEmptyString(previousFilters.contract)) { const historicalContract = findRecentAddressFilterValue(items, "contract"); if (historicalContract) { previousFilters.contract = historicalContract; } } if (!toNonEmptyString(previousFilters.counterparty)) { const historicalCounterparty = findRecentAddressFilterValue(items, "counterparty"); if (historicalCounterparty) { previousFilters.counterparty = historicalCounterparty; } } if (!toNonEmptyString(previousFilters.organization)) { const historicalOrganization = findRecentAddressFilterValue(items, "organization"); if (historicalOrganization) { previousFilters.organization = historicalOrganization; } } const displayedEntityType = inferDisplayedEntityTypeFromIntent(sourceIntent); const displayedEntities = extractDisplayedAddressEntityCandidates(toNonEmptyString(previousAddressItem?.text) ?? "", displayedEntityType); const resolvedEntityFromFollowup = resolveDisplayedAddressEntityMention(userMessage, displayedEntities) ?? (toNonEmptyString(alternateMessage) ? resolveDisplayedAddressEntityMention(String(alternateMessage ?? ""), displayedEntities) : null); if (resolvedEntityFromFollowup) { if (resolvedEntityFromFollowup.entityType === "counterparty") { previousFilters.counterparty = resolvedEntityFromFollowup.value; previousAnchorType = "counterparty"; previousAnchor = resolvedEntityFromFollowup.value; resolvedCounterpartyFromDisplay = true; } else if (resolvedEntityFromFollowup.entityType === "contract") { previousFilters.contract = resolvedEntityFromFollowup.value; previousAnchorType = "contract"; previousAnchor = resolvedEntityFromFollowup.value; } if (followupSelectionMode !== "switch_to_suggested_intent") { followupSelectionMode = "carry_referenced_entity"; } } if (!previousIntent && !previousAnchor && Object.keys(previousFilters).length === 0) { return null; } return { followupContext: { previous_intent: previousIntent ?? undefined, previous_filters: previousFilters, previous_anchor_type: previousAnchorType ?? undefined, previous_anchor_value: previousAnchor, resolved_counterparty_from_display: resolvedCounterpartyFromDisplay || undefined }, previousAddressIntent: previousIntent, previousAddressAnchor: previousAnchor, previousSourceIntent: sourceIntent, followupSelectionMode, hasImplicitContinuationSignal }; } function buildAddressDialogContinuationContractV2(userMessage, effectiveMessage, carryoverMeta, llmPreDecomposeMeta) { const sourceMessage = String(userMessage ?? ""); const canonicalMessage = String(effectiveMessage ?? sourceMessage); const hasFollowupContext = Boolean(carryoverMeta?.followupContext); const previousIntent = toNonEmptyString(carryoverMeta?.previousSourceIntent) ?? null; const selectionMode = toNonEmptyString(carryoverMeta?.followupSelectionMode) ?? null; const explicitIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent); const targetIntent = selectionMode === "switch_to_suggested_intent" ? toNonEmptyString(carryoverMeta?.previousAddressIntent) ?? null : explicitIntent ?? toNonEmptyString(carryoverMeta?.previousAddressIntent) ?? null; const hasImplicitContinuationSignal = Boolean(carryoverMeta?.hasImplicitContinuationSignal); const rewrittenByPredecompose = compactWhitespace(sourceMessage.toLowerCase()) !== compactWhitespace(canonicalMessage.toLowerCase()); const hasExplicitIntent = Boolean(explicitIntent); const decision = !hasFollowupContext ? "new_topic" : selectionMode === "switch_to_suggested_intent" ? "switch_to_suggested" : "continue_previous"; const reasons = []; if (hasFollowupContext) { reasons.push("followup_context_detected"); } if (hasImplicitContinuationSignal) { reasons.push("implicit_continuation_by_llm"); } if (rewrittenByPredecompose) { reasons.push("effective_message_rewritten_by_predecompose"); } if (hasExplicitIntent) { reasons.push("llm_contract_intent_available"); } if (selectionMode === "carry_referenced_entity" && explicitIntent && previousIntent && explicitIntent !== previousIntent) { reasons.push("operation_intent_from_current_message"); } return { schema_version: "address_dialog_continuation_contract_v2", source_message: sourceMessage, effective_message: canonicalMessage, decision, decision_reasons: reasons, followup_context_applied: hasFollowupContext, previous_intent: previousIntent, target_intent: targetIntent, intent_selection_mode: selectionMode, anchor_type: carryoverMeta?.followupContext?.previous_anchor_type ?? null, anchor_value: carryoverMeta?.followupContext?.previous_anchor_value ?? null, implicit_continuation_signal: hasImplicitContinuationSignal }; } function isRetryableAddressLimitedResult(addressLane) { if (!addressLane || !addressLane.handled) { return false; } if (String(addressLane.reply_type ?? "").trim() !== "partial_coverage") { return false; } const category = String(addressLane?.debug?.limited_reason_category ?? "").trim().toLowerCase(); return category === "missing_anchor" || category === "empty_match"; } function isAddressLlmPreDecomposeCandidate(userMessage) { const repaired = repairAddressMojibake(String(userMessage ?? "")); const text = compactWhitespace(repaired.toLowerCase()); if (!text) { return false; } return /(?:\bдок\b|доки|документ|контрагент|договор|остаток|сч(?:е|ё)т|сальдо|банк|выписк|платеж|оплат|поступлен|поступлени|списан|реализац|сверк|взаиморасч|кто\s+должен|show|list|documents?|counterparty|contract|account|balance|bank\s+operations?|doki|dokument(?:y|ov|am|a)?|platezh|oplata|schet|saldo)/i.test(text); } function extractAddressQuestionFromNormalized(normalized) { if (!normalized || typeof normalized !== "object") { return null; } const source = normalized; const fragments = Array.isArray(source.fragments) ? source.fragments : []; for (const item of fragments) { if (!item || typeof item !== "object") { continue; } const fragment = item; const domainRelevance = String(fragment.domain_relevance ?? "").trim().toLowerCase(); if (domainRelevance === "out_of_scope") { continue; } const normalizedText = toNonEmptyString(fragment.normalized_fragment_text); const rawText = toNonEmptyString(fragment.raw_fragment_text); const candidate = selectPreferredAddressFragmentCandidate(rawText ?? "", normalizedText ?? ""); if (!candidate) { continue; } if (candidate.length >= 3 && candidate.length <= 500) { return candidate; } } return null; } function stripMarkdownJsonFence(text) { return String(text ?? "") .trim() .replace(/^```json\s*/i, "") .replace(/^```\s*/i, "") .replace(/```$/i, "") .trim(); } function safeParseLooseJson(text) { const fenced = stripMarkdownJsonFence(text); if (!fenced) { return null; } try { return JSON.parse(fenced); } catch (_error) { // Local OpenAI-compatible models often wrap JSON with extra text. // Try extracting the first top-level JSON object defensively. const start = fenced.indexOf("{"); const end = fenced.lastIndexOf("}"); if (start < 0 || end < 0 || end <= start) { return null; } const candidate = fenced.slice(start, end + 1).trim(); try { return JSON.parse(candidate); } catch (_nestedError) { return null; } } } function extractOutputTextFromRawNormalizerOutput(raw) { if (!raw || typeof raw !== "object") { return null; } const source = raw; if (typeof source.output_text === "string" && source.output_text.trim().length > 0) { return source.output_text; } if (Array.isArray(source.output)) { for (const item of source.output) { if (!item || typeof item !== "object") { continue; } const content = item.content; if (!Array.isArray(content)) { continue; } for (const block of content) { if (!block || typeof block !== "object") { continue; } if (typeof block.text === "string" && block.text.trim().length > 0) { return block.text; } } } } if (source.response && typeof source.response === "object") { const nested = source.response; if (typeof nested.output_text === "string" && nested.output_text.trim().length > 0) { return nested.output_text; } } if (Array.isArray(source.choices) && source.choices.length > 0) { const first = source.choices[0]; if (first && typeof first === "object" && first.message && typeof first.message === "object") { const message = first.message; if (typeof message.content === "string" && message.content.trim().length > 0) { return message.content; } } } return null; } function extractAddressQuestionFromRawNormalizerOutput(rawModelOutput) { const outputText = extractOutputTextFromRawNormalizerOutput(rawModelOutput); if (!outputText) { return null; } const parsed = safeParseLooseJson(outputText); if (!parsed || typeof parsed !== "object") { return null; } const source = parsed; const fragments = Array.isArray(source.fragments) ? source.fragments : []; for (const item of fragments) { if (!item || typeof item !== "object") { continue; } const fragment = item; const domainRelevance = fragment.domain_relevance; if (typeof domainRelevance === "string" && domainRelevance.trim().toLowerCase() === "out_of_scope") { continue; } if (domainRelevance === false) { continue; } const normalizedText = toNonEmptyString(fragment.normalized_fragment_text); const rawText = toNonEmptyString(fragment.raw_fragment_text); const candidate = selectPreferredAddressFragmentCandidate(rawText ?? "", normalizedText ?? ""); if (!candidate) { continue; } if (candidate.length >= 3 && candidate.length <= 500) { return candidate; } } return null; } const ADDRESS_PREDECOMPOSE_LOW_QUALITY_COUNTERPARTY_TOKENS = new Set([ "есть", "же", "что", "все", "всё", "год", "года", "году", "контрагентам", "предоставьте", "получить", "скажи", "skazhi", "покажи", "выведи", "сверка", "теперь", "сейчас", "этому", "этомуже", "тому", "томуже", "нему", "ней", "ним", "неуказанному", "неуказанный", "неуказанная", "неуказанное", "указанному", "указанный", "указанная", "указанное", "объект", "объекту", "период", "периоду", "сводные", "сводный", "сводная", "сводную", "сводном", "сводного", "сводному", "кроме", "помимо", "этого", "этот", "эта", "эту", "этом", "это", "эти", "этих", "документ", "документа", "документы", "документов", "договор", "договора", "контрагент", "контрагента", "еще", "ещё", "другие", "другое", "остальное" ]); const ADDRESS_PREDECOMPOSE_LOW_QUALITY_CONTRACT_TOKENS = new Set([ "за", "же", "это", "указанный", "указанному", "период", "периоду", "тот", "тотже", "этот", "этому", "этомуже", "договор", "договору", "номер" ]); function normalizePredecomposeAnchorTokens(value) { return String(value ?? "") .trim() .toLowerCase() .replace(/ё/g, "е") .split(/[^a-zа-я0-9]+/iu) .map((token) => token.trim()) .filter(Boolean); } function isLowQualityPredecomposeCounterpartyAnchor(value) { const tokens = normalizePredecomposeAnchorTokens(value); if (tokens.length === 0) { return true; } const meaningful = tokens.filter((token) => { if (token.length < 2) { return false; } if (/^(?:19|20)\d{2}$/.test(token)) { return false; } return !ADDRESS_PREDECOMPOSE_LOW_QUALITY_COUNTERPARTY_TOKENS.has(token); }); return meaningful.length === 0; } function normalizePredecomposeCounterpartyAnchorTokensForMatch(value) { return normalizePredecomposeAnchorTokens(value).filter((token) => { if (token.length < 2) { return false; } if (/^(?:19|20)\d{2}$/.test(token)) { return false; } return !ADDRESS_PREDECOMPOSE_LOW_QUALITY_COUNTERPARTY_TOKENS.has(token); }); } function hasCounterpartyAnchorSubstitution(sourceValue, candidateValue) { const sourceNormalized = String(sourceValue ?? "").trim().toLowerCase().replace(/ё/g, "е"); const candidateNormalized = String(candidateValue ?? "").trim().toLowerCase().replace(/ё/g, "е"); if (!sourceNormalized || !candidateNormalized) { return false; } if (sourceNormalized === candidateNormalized) { return false; } if (sourceNormalized.includes(candidateNormalized) || candidateNormalized.includes(sourceNormalized)) { return false; } const sourceTokens = new Set(normalizePredecomposeCounterpartyAnchorTokensForMatch(sourceNormalized)); const candidateTokens = normalizePredecomposeCounterpartyAnchorTokensForMatch(candidateNormalized); if (sourceTokens.size === 0 || candidateTokens.length === 0) { return false; } for (const token of candidateTokens) { if (sourceTokens.has(token)) { return false; } } return true; } function isLowQualityPredecomposeContractAnchor(value) { const normalized = String(value ?? "").trim().toLowerCase().replace(/ё/g, "е"); if (!normalized) { return true; } if (/\b[a-zа-я0-9]{1,20}[\/_-][a-zа-я0-9]{1,20}(?:[\/_-][a-zа-я0-9]{1,20})?\b/iu.test(normalized)) { return false; } if (!/\d/.test(normalized)) { return true; } const tokens = normalizePredecomposeAnchorTokens(normalized); if (tokens.length === 0) { return true; } const meaningful = tokens.filter((token) => !ADDRESS_PREDECOMPOSE_LOW_QUALITY_CONTRACT_TOKENS.has(token)); return meaningful.length === 0; } function resolveRequiredAnchorTypeForIntent(intent) { if (intent === "list_documents_by_counterparty" || intent === "bank_operations_by_counterparty" || intent === "list_contracts_by_counterparty") { return "counterparty"; } if (intent === "list_documents_by_contract" || intent === "bank_operations_by_contract") { return "contract"; } return null; } function evaluateAddressAnchorQuality(message) { const intentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(String(message ?? "")); const intent = intentResolution.intent; const anchorType = resolveRequiredAnchorTypeForIntent(intent); if (!anchorType) { return { intent, anchorType: null, anchorValue: null, quality: 0 }; } const extracted = (0, addressFilterExtractor_1.extractAddressFilters)(String(message ?? ""), intent); const anchorValue = anchorType === "counterparty" ? toNonEmptyString(extracted?.extracted_filters?.counterparty) : toNonEmptyString(extracted?.extracted_filters?.contract); if (!anchorValue) { return { intent, anchorType, anchorValue: null, quality: 0 }; } const lowQuality = anchorType === "counterparty" ? isLowQualityPredecomposeCounterpartyAnchor(anchorValue) : isLowQualityPredecomposeContractAnchor(anchorValue); return { intent, anchorType, anchorValue, quality: lowQuality ? 1 : 2 }; } function hasPredecomposeExplicitDrilldownSignal(text) { const source = String(text ?? ""); return ADDRESS_DOCS_SIGNAL_PATTERN.test(source) || ADDRESS_BANK_SIGNAL_PATTERN.test(source) || ADDRESS_CONTRACT_SIGNAL_PATTERN.test(source); } function hasSameDateAccountFollowupSignalForPredecompose(text) { const source = String(text ?? ""); const hasSameDate = /(?:на\s+ту\s+же\s+дат[ауеы]|на\s+эту\s+же\s+дат[ауеы]|та\s+же\s+дата|same\s+date|the\s+same\s+date|as\s+of\s+same\s+date)/iu.test(source); if (!hasSameDate) { return false; } return (/(?:сч[её]т|счет|account)\D{0,12}\d{2}(?:[.,]\d{1,2})?/iu.test(source) || /(?:^|\s)по\s+\d{2}(?:[.,]\d{1,2})?(?=$|[\s,.;:!?])/iu.test(source) || /\b\d{2}(?:[.,]\d{1,2})\b/u.test(source)); } function hasPredecomposeDiagnosticUncertaintyLead(text) { const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()); if (!normalized) { return false; } return /^(?:неясно|не\s+ясно|непонятно|не\s+понятно|unclear|not\s+clear|ambiguous|unknown)(?=$|[\s,.;:!?])/iu.test(normalized); } function attachAddressPredecomposeContract(meta, sourceMessage) { const canonicalMessage = toNonEmptyString(meta?.effectiveMessage) ?? String(sourceMessage ?? ""); const predecomposeContract = (0, predecomposeContract_1.buildAddressLlmPredecomposeContractV1)({ sourceMessage: String(sourceMessage ?? ""), canonicalMessage }); const semanticExtractionContract = (0, predecomposeContract_1.buildAddressSemanticExtractionContractV1)({ sourceMessage: String(sourceMessage ?? ""), canonicalMessage, predecomposeContract }); return { ...meta, predecomposeContract, semanticExtractionContract }; } async function runAddressLlmPreDecompose(normalizerService, payload, userMessage) { const provider = payload?.llmProvider === "local" ? "local" : payload?.llmProvider === "openai" ? "openai" : null; const sanitizedUserMessage = sanitizeAddressMessageForFallback(userMessage); const fallbackCandidate = resolveAddressDeterministicFallback(userMessage, sanitizedUserMessage); const baseMeta = { attempted: false, applied: false, provider, traceId: null, effectiveMessage: userMessage, reason: "not_attempted", llmCanonicalCandidateDetected: false, fallbackRuleHit: null, sanitizedUserMessage, toolGateDecision: null, toolGateReason: null }; if (Boolean(payload?.useMock)) { if (fallbackCandidate) { const fallbackCompact = compactWhitespace(String(fallbackCandidate.candidate ?? "").toLowerCase()); const sourceCompact = compactWhitespace(String(userMessage ?? "").toLowerCase()); const fallbackApplied = fallbackCompact.length > 0 && fallbackCompact !== sourceCompact; if (fallbackApplied) { return attachAddressPredecomposeContract({ ...baseMeta, applied: true, effectiveMessage: fallbackCandidate.candidate, reason: "fallback_rule_applied_without_llm", fallbackRuleHit: fallbackCandidate.rule }, userMessage); } } return attachAddressPredecomposeContract({ ...baseMeta, reason: "skipped_in_mock" }, userMessage); } const normalizePayload = { llmProvider: payload?.llmProvider, apiKey: payload?.apiKey, model: payload?.model, baseUrl: payload?.baseUrl, temperature: 0, maxOutputTokens: payload?.maxOutputTokens, promptVersion: "normalizer_v2_0_2", userQuestion: userMessage, context: payload?.context, useMock: Boolean(payload?.useMock), retryPolicy: "single-pass-strict" }; try { const normalized = await normalizerService.normalize(normalizePayload); const candidateFromNormalized = extractAddressQuestionFromNormalized(normalized?.normalized); const candidateFromRaw = candidateFromNormalized ? null : extractAddressQuestionFromRawNormalizerOutput(normalized?.raw_model_output); const candidate = candidateFromNormalized ?? candidateFromRaw; if (!candidate) { if (fallbackCandidate) { const fallbackCompact = compactWhitespace(String(fallbackCandidate.candidate ?? "").toLowerCase()); const sourceCompact = compactWhitespace(String(userMessage ?? "").toLowerCase()); const fallbackApplied = fallbackCompact.length > 0 && fallbackCompact !== sourceCompact; if (fallbackApplied) { return attachAddressPredecomposeContract({ ...baseMeta, attempted: true, applied: true, traceId: normalized?.trace_id ?? null, effectiveMessage: fallbackCandidate.candidate, reason: "fallback_rule_applied_after_llm", fallbackRuleHit: fallbackCandidate.rule }, userMessage); } } return attachAddressPredecomposeContract({ ...baseMeta, attempted: true, traceId: normalized?.trace_id ?? null, reason: normalized?.ok ? "no_usable_fragment" : "normalize_failed" }, userMessage); } const repairedSourceMessage = repairAddressMojibake(userMessage); const sourceIntentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(repairedSourceMessage || userMessage); const candidateIntentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(candidate); const sourceIntentKnown = sourceIntentResolution.intent !== "unknown"; const candidateIntentKnown = candidateIntentResolution.intent !== "unknown"; const candidateStartsWithDiagnosticUncertainty = hasPredecomposeDiagnosticUncertaintyLead(candidate); if (candidateStartsWithDiagnosticUncertainty && sourceIntentKnown) { return attachAddressPredecomposeContract({ ...baseMeta, attempted: true, applied: false, traceId: normalized?.trace_id ?? null, llmCanonicalCandidateDetected: true, effectiveMessage: userMessage, reason: "normalized_fragment_rejected_diagnostic_rewrite", fallbackRuleHit: null, sanitizedUserMessage }, userMessage); } const intentConflict = sourceIntentKnown && candidateIntentKnown && sourceIntentResolution.intent !== candidateIntentResolution.intent; const intentDroppedByCandidate = sourceIntentKnown && !candidateIntentKnown; const rejectCandidateForIntentSafety = intentDroppedByCandidate || (intentConflict && (sourceIntentResolution.confidence === "high" || candidateIntentResolution.confidence !== "high")); if (rejectCandidateForIntentSafety) { return attachAddressPredecomposeContract({ ...baseMeta, attempted: true, applied: false, traceId: normalized?.trace_id ?? null, llmCanonicalCandidateDetected: true, effectiveMessage: userMessage, reason: intentDroppedByCandidate ? "normalized_fragment_rejected_intent_drop" : "normalized_fragment_rejected_intent_conflict", fallbackRuleHit: null, sanitizedUserMessage }, userMessage); } const sourceHasExplicitDrilldownSignal = hasPredecomposeExplicitDrilldownSignal(repairedSourceMessage || userMessage); const candidateHasExplicitDrilldownSignal = hasPredecomposeExplicitDrilldownSignal(candidate); const sourceLooksLikeSameDateAccountFollowup = hasSameDateAccountFollowupSignalForPredecompose(repairedSourceMessage || userMessage); const candidateInjectsDrilldownIntent = candidateIntentResolution.intent === "documents_forming_balance"; if (sourceLooksLikeSameDateAccountFollowup && !sourceHasExplicitDrilldownSignal && candidateHasExplicitDrilldownSignal && candidateInjectsDrilldownIntent && sourceIntentResolution.intent !== "documents_forming_balance") { return attachAddressPredecomposeContract({ ...baseMeta, attempted: true, applied: false, traceId: normalized?.trace_id ?? null, llmCanonicalCandidateDetected: true, effectiveMessage: userMessage, reason: "normalized_fragment_rejected_followup_intent_injection", fallbackRuleHit: null, sanitizedUserMessage }, userMessage); } const sourceAnchorQuality = evaluateAddressAnchorQuality(repairedSourceMessage || userMessage); const candidateAnchorQuality = evaluateAddressAnchorQuality(candidate); const sameIntentForAnchorSafety = sourceAnchorQuality.intent !== "unknown" && sourceAnchorQuality.intent === candidateAnchorQuality.intent; const counterpartyAnchorSubstitutedByCandidate = sameIntentForAnchorSafety && sourceAnchorQuality.anchorType === "counterparty" && sourceAnchorQuality.quality >= 2 && Boolean(sourceAnchorQuality.anchorValue) && ((candidateAnchorQuality.anchorType === "counterparty" && candidateAnchorQuality.quality >= 2 && Boolean(candidateAnchorQuality.anchorValue) && hasCounterpartyAnchorSubstitution(sourceAnchorQuality.anchorValue ?? "", candidateAnchorQuality.anchorValue ?? "")) || (candidateAnchorQuality.quality < sourceAnchorQuality.quality && hasCounterpartyAnchorSubstitution(sourceAnchorQuality.anchorValue ?? "", candidate))); if (counterpartyAnchorSubstitutedByCandidate) { return attachAddressPredecomposeContract({ ...baseMeta, attempted: true, applied: false, traceId: normalized?.trace_id ?? null, llmCanonicalCandidateDetected: true, effectiveMessage: userMessage, reason: "normalized_fragment_rejected_anchor_substitution", fallbackRuleHit: null, sanitizedUserMessage }, userMessage); } const anchorDegradedByCandidate = sameIntentForAnchorSafety && sourceAnchorQuality.anchorType && sourceAnchorQuality.quality >= 2 && candidateAnchorQuality.quality < sourceAnchorQuality.quality; if (anchorDegradedByCandidate) { return attachAddressPredecomposeContract({ ...baseMeta, attempted: true, applied: false, traceId: normalized?.trace_id ?? null, llmCanonicalCandidateDetected: true, effectiveMessage: userMessage, reason: "normalized_fragment_rejected_anchor_degradation", fallbackRuleHit: null, sanitizedUserMessage }, userMessage); } if (fallbackCandidate) { const fallbackAnchorQuality = evaluateAddressAnchorQuality(String(fallbackCandidate.candidate ?? "")); const fallbackPreferredForAnchorSafety = sameIntentForAnchorSafety && fallbackAnchorQuality.intent === sourceAnchorQuality.intent && fallbackAnchorQuality.quality >= 2 && fallbackAnchorQuality.quality > candidateAnchorQuality.quality; if (fallbackPreferredForAnchorSafety) { return attachAddressPredecomposeContract({ ...baseMeta, attempted: true, applied: true, traceId: normalized?.trace_id ?? null, llmCanonicalCandidateDetected: true, effectiveMessage: fallbackCandidate.candidate, reason: "fallback_rule_preferred_over_llm_candidate_anchor_quality", fallbackRuleHit: fallbackCandidate.rule, sanitizedUserMessage }, userMessage); } } const semanticContractForCandidate = (0, predecomposeContract_1.buildAddressSemanticExtractionContractV1)({ sourceMessage: String(userMessage ?? ""), canonicalMessage: candidate }); if (!semanticContractForCandidate.apply_canonical_recommended) { const sourceDataSignalDetected = Boolean(semanticContractForCandidate?.guard_hints?.source_data_signal_detected); const rawFragmentCandidatePreferred = Boolean(sourceDataSignalDetected && candidateFromNormalized && candidateFromNormalized === candidate && toNonEmptyString(candidate)); if (rawFragmentCandidatePreferred) { return attachAddressPredecomposeContract({ ...baseMeta, attempted: true, applied: true, traceId: normalized?.trace_id ?? null, llmCanonicalCandidateDetected: true, effectiveMessage: candidate, reason: "normalized_fragment_semantic_guard_raw_fragment_preferred", fallbackRuleHit: null, sanitizedUserMessage }, userMessage); } if (fallbackCandidate) { const fallbackSemanticContract = (0, predecomposeContract_1.buildAddressSemanticExtractionContractV1)({ sourceMessage: String(userMessage ?? ""), canonicalMessage: String(fallbackCandidate.candidate ?? "") }); const fallbackCompact = compactWhitespace(String(fallbackCandidate.candidate ?? "").toLowerCase()); const sourceCompactForFallback = compactWhitespace(String(userMessage ?? "").toLowerCase()); const fallbackApplied = fallbackCompact.length > 0 && fallbackCompact !== sourceCompactForFallback; if (fallbackApplied && fallbackSemanticContract.apply_canonical_recommended && !sourceDataSignalDetected) { return attachAddressPredecomposeContract({ ...baseMeta, attempted: true, applied: true, traceId: normalized?.trace_id ?? null, llmCanonicalCandidateDetected: true, effectiveMessage: String(fallbackCandidate.candidate ?? ""), reason: "fallback_rule_preferred_over_llm_candidate_semantic_guard", fallbackRuleHit: fallbackCandidate.rule, sanitizedUserMessage }, userMessage); } } return attachAddressPredecomposeContract({ ...baseMeta, attempted: true, applied: false, traceId: normalized?.trace_id ?? null, llmCanonicalCandidateDetected: true, effectiveMessage: userMessage, reason: "normalized_fragment_rejected_semantic_guard", fallbackRuleHit: null, sanitizedUserMessage }, userMessage); } const sourceCompact = compactWhitespace(String(userMessage ?? "").toLowerCase()); const candidateCompact = compactWhitespace(candidate.toLowerCase()); const applied = sourceCompact !== candidateCompact; const candidateSource = candidateFromNormalized ? "normalized" : "raw"; const reason = candidateSource === "normalized" ? applied ? "normalized_fragment_applied" : "normalized_fragment_same" : normalized?.ok ? applied ? "raw_fragment_applied" : "raw_fragment_same" : applied ? "raw_fragment_applied_after_normalize_failed" : "raw_fragment_same_after_normalize_failed"; return attachAddressPredecomposeContract({ attempted: true, applied, provider, traceId: normalized?.trace_id ?? null, effectiveMessage: applied ? candidate : userMessage, reason, llmCanonicalCandidateDetected: true, fallbackRuleHit: null, sanitizedUserMessage }, userMessage); } catch (error) { if (fallbackCandidate) { const fallbackCompact = compactWhitespace(String(fallbackCandidate.candidate ?? "").toLowerCase()); const sourceCompact = compactWhitespace(String(userMessage ?? "").toLowerCase()); const fallbackApplied = fallbackCompact.length > 0 && fallbackCompact !== sourceCompact; if (fallbackApplied) { return attachAddressPredecomposeContract({ ...baseMeta, attempted: true, applied: true, effectiveMessage: fallbackCandidate.candidate, reason: "fallback_rule_applied_after_llm_error", fallbackRuleHit: fallbackCandidate.rule }, userMessage); } } return attachAddressPredecomposeContract({ ...baseMeta, attempted: true, reason: `error:${error instanceof Error ? error.message : String(error)}` }, userMessage); } } function resolveAddressToolGateDecision(addressInputMessage, followupContext, llmPreDecomposeMeta = null, rawUserMessage = null) { const repairedInputMessage = repairAddressMojibake(String(addressInputMessage ?? "")); const rawMessageForGate = String(rawUserMessage ?? addressInputMessage ?? ""); const dataScopeMetaQuery = hasAssistantDataScopeMetaQuestionSignal(rawMessageForGate) || hasAssistantDataScopeMetaQuestionSignal(repairedInputMessage); const capabilityMetaQuery = shouldHandleAsAssistantCapabilityMetaQuery(rawMessageForGate) || shouldHandleAsAssistantCapabilityMetaQuery(repairedInputMessage); const dataRetrievalSignal = hasDataRetrievalRequestSignal(rawMessageForGate) || hasDataRetrievalRequestSignal(repairedInputMessage); if (dataScopeMetaQuery || (capabilityMetaQuery && !dataRetrievalSignal)) { return { runAddressLane: false, decision: "skip_address_lane", reason: dataScopeMetaQuery ? "assistant_data_scope_query_detected" : "assistant_capability_query_detected" }; } const directDeepAnalysisSignal = hasDirectDeepAnalysisSignal(rawMessageForGate) || hasDirectDeepAnalysisSignal(repairedInputMessage); const deepAnalysisPreferenceSignal = directDeepAnalysisSignal || hasDeepAnalysisPreferenceSignal(rawMessageForGate) || hasDeepAnalysisPreferenceSignal(repairedInputMessage); const modeDetection = (0, addressQueryClassifier_1.detectAddressQuestionMode)(repairedInputMessage || addressInputMessage); const modeDetectionRaw = (0, addressQueryClassifier_1.detectAddressQuestionMode)(String(addressInputMessage ?? "")); const hasClassifierSignal = modeDetection.mode === "address_query" || modeDetectionRaw.mode === "address_query"; const intentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(repairedInputMessage || addressInputMessage); const intentResolutionRaw = (0, addressIntentResolver_1.resolveAddressIntent)(String(addressInputMessage ?? "")); const hasIntentSignal = intentResolution.intent !== "unknown" || intentResolutionRaw.intent !== "unknown"; const llmContractMode = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode); const llmContractModeConfidence = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode_confidence); const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent); const semanticExtractionContract = llmPreDecomposeMeta?.semanticExtractionContract && typeof llmPreDecomposeMeta.semanticExtractionContract === "object" ? llmPreDecomposeMeta.semanticExtractionContract : null; const semanticCanonicalRecommended = semanticExtractionContract?.apply_canonical_recommended !== false; const llmCanonicalEntitySignal = /(?:\u0437\u0430\u043a\u0430\u0437\u0447\u0438\u043a|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u043a\u043e\u043c\u043f\u0430\u043d|customer|supplier|counterparty|company|vendor|client)/iu.test(compactWhitespace(repairedInputMessage.toLowerCase())); const llmCanonicalAppliedSignal = Boolean(llmPreDecomposeMeta?.applied) && llmContractMode !== "deep_analysis"; const hasLlmCanonicalSignal = semanticCanonicalRecommended && Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) && ((llmContractMode === "address_query" && llmContractModeConfidence !== "low") || (llmCanonicalAppliedSignal && (hasStrongDataIntentSignal(repairedInputMessage) || llmCanonicalEntitySignal))); const hasLlmCanonicalDataSignal = semanticCanonicalRecommended && Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) && Boolean(llmPreDecomposeMeta?.applied) && (llmContractMode === "address_query" || llmContractMode === "unsupported" || llmContractMode === null) && hasStrongDataIntentSignal(repairedInputMessage); const sameDateAccountFollowupSignal = hasSameDateAccountFollowupSignalForPredecompose(rawMessageForGate) || hasSameDateAccountFollowupSignalForPredecompose(repairedInputMessage); const hasLexicalAddressSignal = isAddressLlmPreDecomposeCandidate(addressInputMessage) || isAddressLlmPreDecomposeCandidate(repairedInputMessage) || hasAccountingSignal(addressInputMessage) || hasAccountingSignal(repairedInputMessage) || hasShortDebtMirrorFollowupSignal(rawMessageForGate) || hasShortDebtMirrorFollowupSignal(repairedInputMessage) || sameDateAccountFollowupSignal; const hasUnsupportedLowConfidencePredecomposeSignal = llmContractMode === "unsupported" && (llmContractModeConfidence === "low" || llmContractModeConfidence === "medium") && llmContractIntent === "unknown"; const hasAnyAddressSignal = hasClassifierSignal || hasIntentSignal || hasLlmCanonicalSignal || hasLlmCanonicalDataSignal || hasLexicalAddressSignal; const strongDataSignalFromRawMessage = hasStrongDataIntentSignal(rawMessageForGate) || hasDataRetrievalRequestSignal(rawMessageForGate) || hasAccountingSignal(rawMessageForGate) || hasSameDateAccountFollowupSignalForPredecompose(rawMessageForGate); const strongDataSignalFromEffectiveMessage = hasStrongDataIntentSignal(repairedInputMessage) || hasAccountingSignal(repairedInputMessage) || hasDataRetrievalRequestSignal(repairedInputMessage); if (!semanticCanonicalRecommended && llmContractIntent === "unknown" && !followupContext && !hasClassifierSignal && !hasIntentSignal && !hasLexicalAddressSignal && !strongDataSignalFromRawMessage && !strongDataSignalFromEffectiveMessage) { return { runAddressLane: false, decision: "skip_address_lane", reason: "llm_predecompose_semantic_guard_rejected" }; } if (hasUnsupportedLowConfidencePredecomposeSignal && !followupContext && !hasAnyAddressSignal && !strongDataSignalFromRawMessage && !strongDataSignalFromEffectiveMessage) { return { runAddressLane: false, decision: "skip_address_lane", reason: "llm_predecompose_unsupported_mode" }; } const hasMessageSignal = hasAnyAddressSignal || strongDataSignalFromRawMessage || strongDataSignalFromEffectiveMessage; if (hasMessageSignal) { return { runAddressLane: true, decision: "run_address_lane", reason: hasClassifierSignal ? "address_mode_classifier_detected" : hasIntentSignal ? "address_intent_resolver_detected" : hasLlmCanonicalSignal ? "llm_canonical_candidate_detected" : hasLlmCanonicalDataSignal ? "llm_canonical_data_signal_detected" : "address_signal_detected" }; } if (followupContext) { return { runAddressLane: true, decision: "run_address_lane", reason: "followup_context_detected" }; } return { runAddressLane: false, decision: "skip_address_lane", reason: "no_address_signal_after_l0" }; } function hasLooseAllTimeAddressLookupSignal(text) { const repaired = repairAddressMojibake(String(text ?? "")); const normalized = compactWhitespace(repaired.toLowerCase()); if (!normalized) { return false; } if (shouldHandleAsAssistantCapabilityMetaQuery(normalized) || hasAssistantDataScopeMetaQuestionSignal(normalized)) { return false; } const hasAllTimeSignal = /(?:\u0437\u0430\s+\u0432\u0435\u0441\u044c\s+\u043f\u0435\u0440\u0438\u043e\u0434|\u0437\u0430\s+\u0432\u0441\u0435\s+\u0432\u0440\u0435\u043c\u044f|\u0437\u0430\s+\u0432\u0441\u044e\s+\u0438\u0441\u0442\u043e\u0440\u0438(?:\u044e|\u0438)|for\s+all\s+time|all\s+time|entire\s+period|full\s+period)/iu.test(normalized); if (!hasAllTimeSignal) { return false; } return /(?:\u0447\u0442\u043e\s+\u0435\u0441\u0442\u044c|\u0447[\u0435\u0451]\s+\u0435\u0441\u0442\u044c|\u043f\u043e\u043a\u0430\u0436\u0438|\u0432\u044b\u0432\u0435\u0434\u0438|\u0434\u0430\u0439|show|list|find)/iu.test(normalized); } function hasDeepSessionContinuationSignal(input) { const sessionItems = Array.isArray(input?.sessionItems) ? input.sessionItems : []; if (sessionItems.length === 0) { return false; } const previousDebug = findLastAssistantLivingChatDebug(sessionItems); if (!previousDebug || typeof previousDebug !== "object") { return false; } const investigationState = previousDebug.investigation_state_snapshot; if (!investigationState || typeof investigationState !== "object") { return false; } const candidateTexts = [ input?.rawUserMessage, input?.repairedRawUserMessage, input?.effectiveAddressUserMessage, input?.repairedEffectiveAddressUserMessage ] .map((value) => compactWhitespace(repairAddressMojibake(String(value ?? "")).toLowerCase())) .filter((value) => value.length > 0); if (candidateTexts.length === 0) { return false; } return candidateTexts.some((text) => { const hasContinuationCue = /^(?:\u0438|\u0430|\u0442\u0430\u043a\u0436\u0435|\u0435\u0449[\u0435\u0451]|\u0434\u043e\u0431\u0430\u0432\u044c|\u0434\u043e\u043f\u043e\u043b\u043d\u0438|\u0443\u0442\u043e\u0447\u043d\u0438|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438|\u0442\u0435\u043f\u0435\u0440\u044c|then|also|and)\b/iu.test(text) || /(?:\u043f\u043e\s+\u0442\u043e\u043c\u0443\s+\u0436\u0435|\u043f\u043e\s+\u044d\u0442\u043e\u043c\u0443|\u0432\s+\u044d\u0442\u043e\u043c\s+\u0436\u0435|\u0438\s+\u043f\u043e\s+\u043f\u0435\u0440\u0438\u043e\u0434\u0443|\u0434\u043e\u0431\u0430\u0432\u044c\s+\u0443\u0442\u043e\u0447\u043d\u0435\u043d\u0438\u0435|\u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u043c|\u0430\s+\u0435\u0441\u043b\u0438|\u0435\u0441\u043b\u0438\s+\u0442\u043e\u043b\u044c\u043a\u043e|\u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e)/iu.test(text); const hasAccountOrPeriodCue = /(?:\u0441\u0447[\u0435\u0451]\u0442|account|\b\d{2}(?:[.,]\d{1,2})?\b|\b20\d{2}(?:[-/.]\d{1,2})?\b|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446)/iu.test(text); const hasDeepRebindCue = /(?:\u0430\u043c\u043e\u0440\u0442\u0438\u0437|fixed\s*asset|\u043e\u0441\b|\u043d\u0434\u0441|vat|\u0440\u0430\u0437\u0440\u044b\u0432|\u0446\u0435\u043f\u043e\u0447\u043a|\u0430\u043d\u043e\u043c\u0430\u043b|lifecycle|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447)/iu.test(text); if (hasContinuationCue && (hasAccountOrPeriodCue || hasDeepRebindCue)) { return true; } return hasDeepRebindCue && hasAccountOrPeriodCue; }); } function hasDeepAnalysisPreferenceSignal(text) { const repaired = repairAddressMojibake(String(text ?? "")); const lower = compactWhitespace(repaired.toLowerCase()); if (!lower) { return false; } const riskOrAnomalySignal = /(?:\u0440\u0438\u0441\u043a|risk|\u0430\u043d\u043e\u043c\u0430\u043b|anomal|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442|conflict|deviation|\u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d|\u043d\u0435\u0441\u044b\u043a\u043e\u0432\u043a|\u043d\u0435\u0441\u0445\u043e\u0434|\u043e\u0448\u0438\u0431|error|issue|\u043f\u0440\u043e\u0431\u043b\u0435\u043c)/iu.test(lower); const chainSignal = /(?:\u0446\u0435\u043f\u043e\u0447\u043a|chain|trace\s*chain|lifecycle|\u0436\u0438\u0437\u043d\u0435\u043d\u043d[\u0430-\u044f]+\s+\u0446\u0438\u043a\u043b|state\s+transition|\u0440\u0430\u0437\u0440\u044b\u0432[\u0430-\u044f]*)/iu.test(lower); const diagnosticsKeywordSignal = /(?:\u0440\u0430\u0437\u043b\u043e\u0436\u0438|\u0434\u0435\u043a\u043e\u043c\u043f\u043e\u0437|\u0440\u0430\u0437\u0431\u0435\u0440\u0438|\u043f\u043e\u0447\u0435\u043c\u0443|why|audit|scan|\u043a\u043e\u0440\u043d\u0435\u0432[\u0430-\u044f]+\s+\u043f\u0440\u0438\u0447\u0438\u043d|root\s*cause|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c[\u0430-\u044f]*|\u0433\u0434\u0435\s+\u0440\u0430\u0437\u0440\u044b\u0432|\u0447\u0442\u043e\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442)/iu.test(lower); const diagnosticsCheckVerbSignal = /(?:^|[\s,.;:!?()\-])\u043f\u0440\u043e\u0432\u0435\u0440(?:\u044c|\u0438\u0442\u044c|\u043a\u0443|\u0438\u043c|\u043a\u0430)(?:$|[\s,.;:!?()\-])/iu.test(lower); const diagnosticsSignal = diagnosticsKeywordSignal || diagnosticsCheckVerbSignal; const closureSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]\s+\u043f\u0435\u0440\u0438\u043e\u0434|period\s*close|\u043d\u0435\s+\u0437\u0430\u043a\u0440\u044b\u043b[\u0430-\u044f]*|\u0445\u0432\u043e\u0441\u0442[\u0430-\u044f]*)/iu.test(lower); const closureIntentSignal = /(?:\u0437\u0430\u043a\u0440\u044b\u0442[\u0430-\u044f]*|period\s*close|close\s+period)/iu.test(lower); const closureDiagnosticPhraseSignal = /(?:\u0447\u0442\u043e(?:\s+\S+){0,8}\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442)/iu.test(lower); const signalVsNoiseDiagnostic = /(?:\u043d\u0435\s+\u043f\u0440\u043e\u0441\u0442\u043e\s+(?:\u043d\u0430\s+)?\u0448\u0443\u043c|\u043f\u043e\u0445\u043e\u0436[\u0438\u0435]\s+(?:\u0438\u043c\u0435\u043d\u043d\u043e\s+)?\u043d\u0430\s+\u043f\u0440\u043e\u0431\u043b\u0435\u043c)/iu.test(lower); const lifecycleMismatchSignal = /(?:\u043d\u0435\s+\u0442\u0435\u043c\s+\u0442\u0438\u043f(?:\u043e\u043c)?\s+\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043d\u0435\s+\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434|\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043d\u0435\s+\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434|wrong\s+closing\s+document|expected\s+transition)/iu.test(lower); const lifecycleTransitionGapSignal = /(?:\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432|\u043f\u0435\u0440\u0435\u0445\u043e\u0434[\u0430-\u044f]*\s+\u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432|\u0441\u0442\u0430\u0434\u0438[\u0438\u044f\u0435]\s+.*\u043f\u0440\u043e\u0439\u0434\u0435\u043d.*\u043f\u0435\u0440\u0435\u0445\u043e\u0434)/iu.test(lower); const expectedActualMismatchSignal = /(?:\u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a[\u0430-\u044f]+\s+\u0441\u043e\u0441\u0442\u043e\u044f\u043d[\u0438\u0435\u044f]+\s+.*\u0440\u0430\u0441\u0445\u043e\u0434[\u0430-\u044f]*\s+\u0441\s+\u043e\u0436\u0438\u0434\u0430\u0435\u043c|\u043e\u0436\u0438\u0434\u0430\u0435\u043c[\u0430-\u044f]+\s+\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u043d[\u0430-\u044f]*\s+\u0441\u043f\u0438\u0441\u0430\u043d)/iu.test(lower); return lifecycleMismatchSignal || (chainSignal && lifecycleTransitionGapSignal) || expectedActualMismatchSignal || (chainSignal && diagnosticsSignal) || (riskOrAnomalySignal && (chainSignal || diagnosticsSignal || lifecycleTransitionGapSignal)) || (diagnosticsSignal && (closureSignal || closureIntentSignal)) || closureDiagnosticPhraseSignal || signalVsNoiseDiagnostic; } function hasDirectDeepAnalysisSignal(text) { const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()); if (!normalized) { return false; } return /(?:\u0440\u0430\u0437\u043b\u043e\u0436|\u0446\u0435\u043f\u043e\u0447|lifecycle|\u0440\u0430\u0437\u0440\u044b\u0432|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u0430\u043d\u043e\u043c\u0430\u043b|\u043f\u043e\u0447\u0435\u043c\u0443|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c|\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]\s+\u043f\u0435\u0440\u0438\u043e\u0434|period\s*close|close\s+period|\u0447\u0442\u043e\s+\u043c\u0435\u0448\u0430[\u0430-\u044f]+\s+\u0437\u0430\u043a\u0440\u044b\u0442|state\s+transition|root\s*cause|trace\s*chain)/iu.test(normalized); } function hasStrictDeepInvestigationCue(text) { const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()); if (!normalized) { return false; } const hasInvestigativeVerb = /(?:\u043f\u0440\u043e\u0432\u0435\u0440(?:\u044c|\u0438\u0442\u044c)|\u0440\u0430\u0437\u0431\u0435\u0440(?:\u0438|\u0430\u0442\u044c)|\u0440\u0430\u0437\u043b\u043e\u0436(?:\u0438|\u0438\u0442\u044c)|\u043f\u043e\u0447\u0435\u043c\u0443|\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c|root\s*cause|trace\s*chain)/iu.test(normalized); if (!hasInvestigativeVerb) { return false; } return /(?:\u0445\u0432\u043e\u0441\u0442|\u0440\u0430\u0437\u0440\u044b\u0432|\u0446\u0435\u043f\u043e\u0447|\u0430\u043d\u043e\u043c\u0430\u043b|\u043f\u0440\u043e\u0442\u0438\u0432\u043e\u0440\u0435\u0447|\u0437\u0430\u043a\u0440\u044b\u0442\u0438[\u0435\u044f]|\u043e\u0431\u044a\u0435\u043a\u0442(?:\u0443)?\s+\u0440\u0430\u0441\u0447(?:\u0435|\u0451)\u0442|lifecycle|state\s+transition)/iu.test(normalized); } function hasAggregateBusinessAnalyticsSignal(text) { const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()); if (!normalized) { return false; } const hasMetricCue = /(?:\u043e\u0431\u043e\u0440\u043e\u0442|\u0432\u044b\u0440\u0443\u0447|\u0434\u043e\u0445\u043e\u0434|\u043f\u0440\u0438\u0431\u044b\u043b|\u043c\u0430\u0440\u0436|\u0440\u0435\u043d\u0442\u0430\u0431\u0435\u043b|\u043f\u043e\u043a\u0430\u0437\u0430\u0442\u0435\u043b|turnover|revenue|profit|margin)/iu.test(normalized); if (!hasMetricCue) { return false; } const hasRankingOrTrendCue = /(?:\u0441\u0430\u043c(?:\u044b\u0439|\u0430\u044f|\u043e\u0435|\u044b\u0435)|\u0442\u043e\u043f|\u043b\u0443\u0447\u0448|\u0445\u0443\u0434\u0448|\u043c\u0430\u043a\u0441(?:\u0438\u043c\u0443\u043c)?|\u043c\u0438\u043d(?:\u0438\u043c\u0443\u043c)?|\u0434\u0438\u043d\u0430\u043c|\u0442\u0440\u0435\u043d\u0434|\u0441\u0440\u0430\u0432\u043d|ranking|top|best|worst)/iu.test(normalized); const hasPeriodAggregateCue = /(?:\u043f\u043e\s+\u0433\u043e\u0434\u0430\u043c|\u0437\u0430\s+\d{4}\s+\u0433\u043e\u0434|\u0433\u043e\u0434(?:\u0430|\u0443|\u044b)?|year|years|\u043a\u0432\u0430\u0440\u0442\u0430\u043b|\u043c\u0435\u0441\u044f\u0446|\u043f\u0435\u0440\u0438\u043e\u0434)/iu.test(normalized); return hasRankingOrTrendCue || hasPeriodAggregateCue; } function hasOpenContractsAddressSignal(text) { const normalized = compactWhitespace(repairAddressMojibake(String(text ?? "")).toLowerCase()); if (!normalized) { return false; } const hasContractCue = /(?:договор|контракт|contract)/iu.test(normalized); if (!hasContractCue) { return false; } const hasOpenCue = /(?:незакрыт|не\s+закрыт|открыт|open\s+contract|open\s+item|open)/iu.test(normalized); if (!hasOpenCue) { return false; } const hasRequestCue = /(?:покажи|показать|список|какие|какой|show|list|find|на\s+дату|as\s+of)/iu.test(normalized); const hasTemporalCue = hasPeriodLiteral(normalized) || /\b\d{4}[-/.]\d{2}[-/.]\d{2}\b/.test(normalized); return hasRequestCue || hasTemporalCue; } const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([ "period_coverage_profile", "document_type_and_account_section_profile", "counterparty_population_and_roles", "counterparty_activity_lifecycle", "customer_revenue_and_payments", "supplier_payouts_profile", "list_open_contracts", "open_items_by_counterparty_or_contract", "list_payables_counterparties", "list_receivables_counterparties", "payables_confirmed_as_of_date", "receivables_confirmed_as_of_date", "list_documents_by_contract", "bank_operations_by_contract", "list_documents_by_counterparty", "bank_operations_by_counterparty", "list_contracts_by_counterparty", "contract_usage_overview", "contract_usage_and_value", "vat_payable_forecast", "vat_liability_confirmed_for_tax_period", "vat_payable_confirmed_as_of_date" ]); function resolveAssistantOrchestrationDecision(input) { const rawUserMessage = String(input?.rawUserMessage ?? input?.userMessage ?? ""); const effectiveAddressUserMessage = String(input?.effectiveAddressUserMessage ?? rawUserMessage); const repairedRawUserMessage = repairAddressMojibake(rawUserMessage); const repairedEffectiveAddressUserMessage = repairAddressMojibake(effectiveAddressUserMessage); const followupContext = input?.followupContext ?? null; const llmPreDecomposeMeta = input?.llmPreDecomposeMeta ?? null; const useMock = Boolean(input?.useMock); const sessionItems = Array.isArray(input?.sessionItems) ? input.sessionItems : null; const dataScopeMetaQuery = hasAssistantDataScopeMetaQuestionSignal(rawUserMessage) || hasAssistantDataScopeMetaQuestionSignal(repairedRawUserMessage) || hasAssistantDataScopeMetaQuestionSignal(effectiveAddressUserMessage) || hasAssistantDataScopeMetaQuestionSignal(repairedEffectiveAddressUserMessage); const capabilityMetaQuery = shouldHandleAsAssistantCapabilityMetaQuery(rawUserMessage) || shouldHandleAsAssistantCapabilityMetaQuery(repairedRawUserMessage) || shouldHandleAsAssistantCapabilityMetaQuery(effectiveAddressUserMessage) || shouldHandleAsAssistantCapabilityMetaQuery(repairedEffectiveAddressUserMessage); const dataRetrievalSignal = hasDataRetrievalRequestSignal(rawUserMessage) || hasDataRetrievalRequestSignal(repairedRawUserMessage) || hasDataRetrievalRequestSignal(effectiveAddressUserMessage) || hasDataRetrievalRequestSignal(repairedEffectiveAddressUserMessage); const aggregateBusinessAnalyticsSignal = hasAggregateBusinessAnalyticsSignal(rawUserMessage) || hasAggregateBusinessAnalyticsSignal(repairedRawUserMessage) || hasAggregateBusinessAnalyticsSignal(effectiveAddressUserMessage) || hasAggregateBusinessAnalyticsSignal(repairedEffectiveAddressUserMessage); const standaloneAddressTopicSignal = hasStandaloneAddressTopicSignal(rawUserMessage) || hasStandaloneAddressTopicSignal(repairedRawUserMessage) || hasStandaloneAddressTopicSignal(effectiveAddressUserMessage) || hasStandaloneAddressTopicSignal(repairedEffectiveAddressUserMessage); const openContractsAddressSignal = hasOpenContractsAddressSignal(rawUserMessage) || hasOpenContractsAddressSignal(repairedRawUserMessage) || hasOpenContractsAddressSignal(effectiveAddressUserMessage) || hasOpenContractsAddressSignal(repairedEffectiveAddressUserMessage); const modeSample = repairedEffectiveAddressUserMessage || effectiveAddressUserMessage; const modeDetection = (0, addressQueryClassifier_1.detectAddressQuestionMode)(modeSample); const intentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(modeSample); const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent); const llmPreDecomposeReason = toNonEmptyString(llmPreDecomposeMeta?.reason); const llmRuntimeUnavailableDetected = Boolean(llmPreDecomposeReason && /(?:openai\s+api\s+key\s+is\s+missing|api\s+key\s+is\s+missing|missing\s+api\s+key|authentication)/iu.test(llmPreDecomposeReason)); const semanticExtractionContract = llmPreDecomposeMeta?.semanticExtractionContract && typeof llmPreDecomposeMeta.semanticExtractionContract === "object" ? llmPreDecomposeMeta.semanticExtractionContract : null; const semanticContractValid = semanticExtractionContract?.valid !== false; const semanticApplyCanonicalRecommended = semanticExtractionContract?.apply_canonical_recommended !== false; const semanticReasonCodes = Array.isArray(semanticExtractionContract?.reason_codes) ? semanticExtractionContract.reason_codes : []; const strictDeepInvestigationCueDetected = hasStrictDeepInvestigationCue(rawUserMessage) || hasStrictDeepInvestigationCue(repairedRawUserMessage) || hasStrictDeepInvestigationCue(effectiveAddressUserMessage) || hasStrictDeepInvestigationCue(repairedEffectiveAddressUserMessage); const keepAddressLaneByIntent = semanticApplyCanonicalRecommended && Boolean((intentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(intentResolution.intent)) || (llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent)) || openContractsAddressSignal) && !strictDeepInvestigationCueDetected; const strongDataSignal = hasStrongDataIntentSignal(rawUserMessage) || hasStrongDataIntentSignal(repairedRawUserMessage) || hasStrongDataIntentSignal(effectiveAddressUserMessage) || hasStrongDataIntentSignal(repairedEffectiveAddressUserMessage) || hasAccountingSignal(rawUserMessage) || hasAccountingSignal(repairedRawUserMessage) || hasAccountingSignal(effectiveAddressUserMessage) || hasAccountingSignal(repairedEffectiveAddressUserMessage) || hasDataRetrievalRequestSignal(rawUserMessage) || hasDataRetrievalRequestSignal(repairedRawUserMessage); const llmContractMode = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode); const llmFirstAddressCandidate = Boolean(llmContractMode === "address_query" && llmContractIntent && llmContractIntent !== "unknown"); const llmFirstUnsupportedCandidate = Boolean(llmContractMode === "unsupported" && (!llmContractIntent || llmContractIntent === "unknown")); const dangerOrCoercionSignal = hasDangerOrCoercionSignal(rawUserMessage) || hasDangerOrCoercionSignal(repairedRawUserMessage) || hasDangerOrCoercionSignal(effectiveAddressUserMessage) || hasDangerOrCoercionSignal(repairedEffectiveAddressUserMessage); const explicitAddressFollowupSignal = hasAddressFollowupContextSignal(rawUserMessage) || hasAddressFollowupContextSignal(repairedRawUserMessage) || hasAddressFollowupContextSignal(effectiveAddressUserMessage) || hasAddressFollowupContextSignal(repairedEffectiveAddressUserMessage) || hasShortDebtMirrorFollowupSignal(rawUserMessage) || hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) || hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) || hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage); const effectiveAddressFollowupSignal = explicitAddressFollowupSignal && !dangerOrCoercionSignal; const deterministicNonDomainGuard = Boolean(!dataScopeMetaQuery && !capabilityMetaQuery && !dataRetrievalSignal && !effectiveAddressFollowupSignal && modeDetection.mode === "unsupported" && intentResolution.intent === "unknown"); const nonDomainQueryIndexed = Boolean(!llmFirstAddressCandidate && deterministicNonDomainGuard && (llmFirstUnsupportedCandidate || llmContractMode === null)); const hardMetaMode = dataScopeMetaQuery ? "data_scope" : capabilityMetaQuery && !dataRetrievalSignal ? "capability" : null; if (hardMetaMode === "data_scope") { return { runAddressLane: false, toolGateDecision: "skip_address_lane", toolGateReason: "assistant_data_scope_query_detected", livingMode: "chat", livingReason: "assistant_data_scope_query_detected", orchestrationContract: { schema_version: "assistant_orchestration_contract_v1", hard_meta_mode: "data_scope", address_mode: modeDetection.mode, address_mode_confidence: modeDetection.confidence, address_intent: intentResolution.intent, address_intent_confidence: intentResolution.confidence, strong_data_signal_detected: strongDataSignal, data_retrieval_signal_detected: dataRetrievalSignal, followup_context_detected: Boolean(followupContext), unsupported_address_intent_fallback_to_deep: false, final_decision: { run_address_lane: false, tool_gate_decision: "skip_address_lane", tool_gate_reason: "assistant_data_scope_query_detected", living_mode: "chat", living_reason: "assistant_data_scope_query_detected" } } }; } if (hardMetaMode === "capability") { return { runAddressLane: false, toolGateDecision: "skip_address_lane", toolGateReason: "assistant_capability_query_detected", livingMode: "chat", livingReason: "assistant_capability_query_detected", orchestrationContract: { schema_version: "assistant_orchestration_contract_v1", hard_meta_mode: "capability", address_mode: modeDetection.mode, address_mode_confidence: modeDetection.confidence, address_intent: intentResolution.intent, address_intent_confidence: intentResolution.confidence, strong_data_signal_detected: strongDataSignal, data_retrieval_signal_detected: dataRetrievalSignal, followup_context_detected: Boolean(followupContext), unsupported_address_intent_fallback_to_deep: false, final_decision: { run_address_lane: false, tool_gate_decision: "skip_address_lane", tool_gate_reason: "assistant_capability_query_detected", living_mode: "chat", living_reason: "assistant_capability_query_detected" } } }; } if (nonDomainQueryIndexed) { return { runAddressLane: false, toolGateDecision: "skip_address_lane", toolGateReason: "non_domain_query_indexed", livingMode: "chat", livingReason: "non_domain_query_indexed", orchestrationContract: { schema_version: "assistant_orchestration_contract_v1", hard_meta_mode: "non_domain", address_mode: modeDetection.mode, address_mode_confidence: modeDetection.confidence, address_intent: intentResolution.intent, address_intent_confidence: intentResolution.confidence, strong_data_signal_detected: strongDataSignal, data_retrieval_signal_detected: dataRetrievalSignal, followup_context_detected: Boolean(followupContext), unsupported_address_intent_fallback_to_deep: false, final_decision: { run_address_lane: false, tool_gate_decision: "skip_address_lane", tool_gate_reason: "non_domain_query_indexed", living_mode: "chat", living_reason: "non_domain_query_indexed" } } }; } const baseToolGate = resolveAddressToolGateDecision(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage); const preserveAddressLaneSignal = Boolean((llmPreDecomposeMeta?.llmCanonicalCandidateDetected && llmPreDecomposeMeta?.applied && llmContractMode === "address_query") || hasSameDateAccountFollowupSignalForPredecompose(rawUserMessage) || hasSameDateAccountFollowupSignalForPredecompose(effectiveAddressUserMessage) || hasSameDateAccountFollowupSignalForPredecompose(repairedRawUserMessage) || hasSameDateAccountFollowupSignalForPredecompose(repairedEffectiveAddressUserMessage) || hasLooseAllTimeAddressLookupSignal(rawUserMessage) || hasLooseAllTimeAddressLookupSignal(effectiveAddressUserMessage) || hasLooseAllTimeAddressLookupSignal(repairedRawUserMessage) || hasLooseAllTimeAddressLookupSignal(repairedEffectiveAddressUserMessage) || hasAddressFollowupContextSignal(rawUserMessage) || hasAddressFollowupContextSignal(effectiveAddressUserMessage) || hasAddressFollowupContextSignal(repairedRawUserMessage) || hasAddressFollowupContextSignal(repairedEffectiveAddressUserMessage) || hasShortDebtMirrorFollowupSignal(rawUserMessage) || hasShortDebtMirrorFollowupSignal(effectiveAddressUserMessage) || hasShortDebtMirrorFollowupSignal(repairedRawUserMessage) || hasShortDebtMirrorFollowupSignal(repairedEffectiveAddressUserMessage)); const supportedAddressIntentDetected = !strictDeepInvestigationCueDetected && Boolean((intentResolution.intent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(intentResolution.intent)) || (llmContractIntent && ADDRESS_INTENTS_KEEP_ADDRESS_LANE.has(llmContractIntent)) || openContractsAddressSignal); const semanticGuardHints = semanticExtractionContract?.guard_hints && typeof semanticExtractionContract.guard_hints === "object" ? semanticExtractionContract.guard_hints : null; const semanticExtraction = semanticExtractionContract?.extraction && typeof semanticExtractionContract.extraction === "object" ? semanticExtractionContract.extraction : null; const semanticDeepInvestigationHintDetected = semanticGuardHints?.deep_investigation_signal_detected === true; const semanticAggregateShapeDetected = semanticExtraction?.query_shape === "AGGREGATE_LOOKUP" || semanticExtraction?.aggregation_profile === "management_profile"; const followupSemanticOverrideToDeepAllowed = Boolean(followupContext && !supportedAddressIntentDetected && (llmContractMode === "unsupported" || semanticAggregateShapeDetected || semanticDeepInvestigationHintDetected || !semanticApplyCanonicalRecommended)); const unsupportedIntentOrMode = (modeDetection.mode !== "address_query" && intentResolution.intent === "unknown") || llmContractMode === "unsupported"; const unsupportedAddressIntentFallbackToDeep = Boolean(baseToolGate?.runAddressLane && !llmRuntimeUnavailableDetected && unsupportedIntentOrMode && strongDataSignal && (llmContractMode === "deep_analysis" || !dataRetrievalSignal || strictDeepInvestigationCueDetected || semanticDeepInvestigationHintDetected || aggregateBusinessAnalyticsSignal) && !preserveAddressLaneSignal && !keepAddressLaneByIntent && !supportedAddressIntentDetected && (!followupContext || followupSemanticOverrideToDeepAllowed)); const deepAnalysisPreferenceDetected = Boolean(hasDeepAnalysisPreferenceSignal(rawUserMessage) || hasDeepAnalysisPreferenceSignal(repairedRawUserMessage) || hasDeepAnalysisPreferenceSignal(effectiveAddressUserMessage) || hasDeepAnalysisPreferenceSignal(repairedEffectiveAddressUserMessage) || hasDirectDeepAnalysisSignal(rawUserMessage) || hasDirectDeepAnalysisSignal(repairedRawUserMessage) || hasDirectDeepAnalysisSignal(effectiveAddressUserMessage) || hasDirectDeepAnalysisSignal(repairedEffectiveAddressUserMessage)); 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 deepAnalysisSignalFallbackToDeep = Boolean(baseToolGate?.runAddressLane && !llmRuntimeUnavailableDetected && (deepAnalysisPreferenceDetected || semanticDeepInvestigationHintDetected) && !keepAddressLaneByIntent && !supportedAddressIntentDetected && !vatExplainFollowupSignal && (!followupContext || !dataRetrievalSignal || followupSemanticOverrideToDeepAllowed)); const aggregateAnalyticsFallbackToDeep = Boolean(baseToolGate?.runAddressLane && !llmRuntimeUnavailableDetected && aggregateBusinessAnalyticsSignal && !keepAddressLaneByIntent && !supportedAddressIntentDetected && (!followupContext || llmContractMode === "unsupported" || semanticAggregateShapeDetected || !semanticApplyCanonicalRecommended || standaloneAddressTopicSignal)); const deepSessionContinuationFallbackToDeep = Boolean(!followupContext && baseToolGate?.runAddressLane && !llmRuntimeUnavailableDetected && hasDeepSessionContinuationSignal({ rawUserMessage, repairedRawUserMessage, effectiveAddressUserMessage, repairedEffectiveAddressUserMessage, sessionItems })); let runAddressLane = Boolean(baseToolGate?.runAddressLane); let toolGateDecision = String(baseToolGate?.decision ?? "skip_address_lane"); let toolGateReason = String(baseToolGate?.reason ?? "no_address_signal_after_l0"); if (unsupportedAddressIntentFallbackToDeep) { runAddressLane = false; toolGateDecision = "skip_address_lane"; toolGateReason = "address_signal_unsupported_intent_fallback_to_deep"; } if (deepAnalysisSignalFallbackToDeep && !unsupportedAddressIntentFallbackToDeep) { runAddressLane = false; toolGateDecision = "skip_address_lane"; toolGateReason = "deep_analysis_signal_fallback_to_deep"; } if (aggregateAnalyticsFallbackToDeep && !unsupportedAddressIntentFallbackToDeep && !deepAnalysisSignalFallbackToDeep) { runAddressLane = false; toolGateDecision = "skip_address_lane"; toolGateReason = "aggregate_analytics_signal_fallback_to_deep"; } if (deepSessionContinuationFallbackToDeep) { runAddressLane = false; toolGateDecision = "skip_address_lane"; toolGateReason = "deep_session_continuation_fallback_to_deep"; } let livingDecision = resolveLivingAssistantModeDecision({ userMessage: rawUserMessage, addressLaneTriggered: runAddressLane, useMock, predecomposeMode: llmPreDecomposeMeta?.predecomposeContract?.mode ?? null, predecomposeModeConfidence: llmPreDecomposeMeta?.predecomposeContract?.mode_confidence ?? null }); if (unsupportedAddressIntentFallbackToDeep) { livingDecision = { mode: "deep_analysis", reason: "unsupported_address_intent_fallback_to_deep" }; } if (deepAnalysisSignalFallbackToDeep && !unsupportedAddressIntentFallbackToDeep) { livingDecision = { mode: "deep_analysis", reason: "deep_analysis_signal_fallback_to_deep" }; } if (aggregateAnalyticsFallbackToDeep && !unsupportedAddressIntentFallbackToDeep && !deepAnalysisSignalFallbackToDeep) { livingDecision = { mode: "deep_analysis", reason: "aggregate_analytics_signal_fallback_to_deep" }; } if (deepSessionContinuationFallbackToDeep) { livingDecision = { mode: "deep_analysis", reason: "deep_session_continuation_fallback_to_deep" }; } return { runAddressLane, toolGateDecision, toolGateReason, livingMode: livingDecision.mode, livingReason: livingDecision.reason, orchestrationContract: { schema_version: "assistant_orchestration_contract_v1", hard_meta_mode: null, address_mode: modeDetection.mode, address_mode_confidence: modeDetection.confidence, address_intent: intentResolution.intent, address_intent_confidence: intentResolution.confidence, strong_data_signal_detected: strongDataSignal, data_retrieval_signal_detected: dataRetrievalSignal, semantic_contract_valid: semanticContractValid, semantic_apply_canonical_recommended: semanticApplyCanonicalRecommended, semantic_reason_codes: semanticReasonCodes, semantic_route_arbitration: { supported_address_intent_detected: supportedAddressIntentDetected, semantic_deep_investigation_hint_detected: semanticDeepInvestigationHintDetected, semantic_aggregate_shape_detected: semanticAggregateShapeDetected, followup_semantic_override_to_deep_allowed: followupSemanticOverrideToDeepAllowed }, followup_context_detected: Boolean(followupContext), unsupported_address_intent_fallback_to_deep: unsupportedAddressIntentFallbackToDeep, deep_analysis_signal_fallback_to_deep: deepAnalysisSignalFallbackToDeep, aggregate_analytics_signal_fallback_to_deep: aggregateAnalyticsFallbackToDeep, deep_session_continuation_fallback_to_deep: deepSessionContinuationFallbackToDeep, final_decision: { run_address_lane: runAddressLane, tool_gate_decision: toolGateDecision, tool_gate_reason: toolGateReason, living_mode: livingDecision.mode, living_reason: livingDecision.reason } } }; } function hasStrongDataIntentSignal(text) { const lower = String(text ?? "").toLowerCase(); return /(база|док|документ|проводк|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|оборот|баланс|период|месяц|год|инн|аванс|предоплат|отгруз|задолж|долг|mcp|bank|counterparty|contract|document|ledger|posting|account|organization|company|advance|prepay|shipment|receivab|payab|организац|компан|контор|фирм)/i.test(lower); } function hasDataRetrievalRequestSignal(text) { const lower = compactWhitespace(String(text ?? "").toLowerCase()); if (!lower) { return false; } const hasBroadInterrogative = /(?:\u0433\u0434\u0435|\u0432\s+\u043a\u0430\u043a\u0438\u0445|\u043f\u043e\s+\u043a\u0430\u043a\u0438\u043c|\u043f\u043e\s+\u043a\u043e\u043c\u0443|\u043a\u0430\u043a\u0438\u0435|\u043a\u0430\u043a\u043e\u0439|\u043a\u0442\u043e|\u0441\u043a\u043e\u043b\u044c\u043a\u043e|where|which|who|how\s+many)/iu.test(lower); const hasBroadBusinessObject = /(?:\u0430\u0432\u0430\u043d\u0441|\u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442|\u043e\u0442\u0433\u0440\u0443\u0437|\u0437\u0430\u0434\u043e\u043b\u0436|\u0434\u043e\u043b\u0433|\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u043f\u043b\u0430\u0442|\u043f\u043b\u0430\u0442(?:\u0435|\u0451)\u0436|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0441\u0447(?:\u0435|\u0451)\u0442|\u043e\u0431\u043e\u0440\u043e\u0442|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446|\u0433\u043e\u0434|advance|prepay|shipment|receivab|payab|counterparty|contract|document|account|balance|turnover)/iu.test(lower); if (hasBroadInterrogative && hasBroadBusinessObject) { return true; } const hasRussianRetrievalAction = /(?:^|\s)(?:\u043f\u043e\u043a\u0430\u0436\u0438|\u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c|\u043d\u0430\u0439\u0434\u0438|\u0432\u044b\u0432\u0435\u0434\u0438|\u0434\u0430\u0439|\u0440\u0430\u0441\u043a\u0440\u043e\u0439|\u0441\u043f\u0438\u0441\u043e\u043a|\u043f\u0440\u043e\u0432\u0435\u0440\u044c|\u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c)(?:$|[\s,.!?;:])/iu.test(lower); const hasRussianRetrievalObject = /(?:\u0434\u043e\u0433\u043e\u0432\u043e\u0440|\u043a\u043e\u043d\u0442\u0440\u0430\u043a\u0442|\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442|\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u0441\u0447(?:\u0435|\u0451)\u0442|\u043e\u0441\u0442\u0430\u0442|\u0441\u0430\u043b\u044c\u0434\u043e|\u043e\u0431\u043e\u0440\u043e\u0442|\u043f\u043b\u0430\u0442(?:\u0435|\u0451)\u0436|\u043e\u043f\u0435\u0440\u0430\u0446|\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a|\u043a\u043b\u0438\u0435\u043d\u0442|\u0433\u043e\u0434|\u043f\u0435\u0440\u0438\u043e\u0434|\u043c\u0435\u0441\u044f\u0446|\u0430\u0432\u0430\u043d\u0441|\u043f\u0440\u0435\u0434\u043e\u043f\u043b\u0430\u0442|\u043e\u0442\u0433\u0440\u0443\u0437|\u0437\u0430\u0434\u043e\u043b\u0436|\u0434\u043e\u043b\u0433)/iu.test(lower); if (hasRussianRetrievalAction && hasRussianRetrievalObject) { return true; } const hasExplicitRetrievalAction = /(?:\bпокажи\b|\bпоказать\b|\bвыведи\b|\bнайди\b|\bсписок\b|\bдай\b|\bраскрой\b|\bshow\b|\blist\b|\bfind\b|\bcount\b)/i.test(lower); const hasInterrogativeRetrievalAction = /(?:\bсколько\b|\bкакой\b|\bкакая\b|\bкакое\b|\bкакую\b|\bкакие\b|\bкто\b|\bгде\b|\bпо\s+каким\b|\bпо\s+кому\b|\bу\s+кого\b|\bwhich\b|\bwho\b|\bwhere\b)/i.test(lower); if (!hasExplicitRetrievalAction && !hasInterrogativeRetrievalAction) { return false; } const hasRetrievalObject = /(1с|база|док|документ|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|период|месяц|год|инн|аванс|предоплат|отгруз|задолж|долг|bank|counterparty|contract|document|account|balance|ledger|posting|advance|prepay|shipment|receivab|payab|организац|компан|контор|фирм|возраст|дата\s+регистрац|регистрац|основан)/i.test(lower); if (!hasRetrievalObject) { return false; } if (hasExplicitRetrievalAction) { return true; } const hasMetaCapabilityShape = /(?:мож(?:ем|ешь|ете|но)|уме(?:ешь|ете)|доступ|подключ|чья|как\s+называ(?:ет|ется)|работ(?:ать|аем|аешь|аете)|в\s+тебе|у\s+тебя)/i.test(lower); return !hasMetaCapabilityShape; } function hasOrganizationFactLookupSignal(text) { const repaired = repairAddressMojibake(String(text ?? "")); const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е"); if (!normalized) { return false; } const hasFactCue = /(?:возраст|сколько\s+лет|дата\s+регистрац|когда\s+(?:зарегистр|создан|основан)|год\s+регистрац|год\s+основан|с\s+какого\s+года|when\s+was\s+(?:it\s+)?(?:registered|founded|created))/i.test(normalized); if (!hasFactCue) { return false; } return /(?:организац|компан|контор|фирм|ооо|ао|зао|ип|альтернатив|лайсвуд|райм|organization|company)/i.test(normalized); } function findLastAssistantLivingChatDebug(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") { continue; } if (item.debug && typeof item.debug === "object") { return item.debug; } } return null; } function hasOrganizationFactFollowupSignal(userMessage, items) { const repaired = repairAddressMojibake(String(userMessage ?? "")); const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е"); if (!normalized) { return false; } if (hasOrganizationFactLookupSignal(normalized)) { return false; } const hasFollowupCue = /(?:^|\s)(?:давай|го|погнали|ок(?:ей)?|хорошо|принято|подтверждаю|запрашивай|запроси|проверь|продолжай|ну\s+давай|да\s+давай)(?=$|[\s,.!?;:])/iu.test(normalized); if (!hasFollowupCue) { return false; } const lastDebug = findLastAssistantLivingChatDebug(items); const lastSource = toNonEmptyString(lastDebug?.living_chat_response_source); const lastGuardReason = toNonEmptyString(lastDebug?.living_chat_grounding_guard_reason); const inOrganizationFactBoundary = lastSource === "deterministic_organization_fact_boundary" || lastSource === "deterministic_organization_fact_boundary_followup" || lastGuardReason === "organization_fact_without_live_source_blocked"; return inOrganizationFactBoundary; } function shouldEmitOrganizationSelectionReply(userMessage, selectedOrganization) { const selected = normalizeOrganizationScopeValue(selectedOrganization); if (!selected) { return false; } const repaired = repairAddressMojibake(String(userMessage ?? "")); const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е"); if (!normalized) { return false; } if (hasOrganizationFactLookupSignal(normalized) || hasDataRetrievalRequestSignal(normalized) || hasStrongDataIntentSignal(normalized)) { return false; } const hasAnalyticalCue = /(?:какой|какая|какие|когда|сколько|кто|почему|зачем|возраст|дата|регистрац|ндс|налог|контракт|договор|документ|операц|оборот|сумм|остат|сальдо|founded|registered|created)/i.test(normalized); if (hasAnalyticalCue) { return false; } const hasSelectionCue = /(?:давай|го|погнали|ок(?:ей)?|хорошо|отлично|берем|выберем|выбираем|переключ(?:им|аем|ай)|фиксир|работаем|обсудим|тогда)\b/i.test(normalized); if (hasSelectionCue) { return true; } return normalized.length <= 36 && !/[?]/.test(String(userMessage ?? "")); } function hasOperationalAdminActionRequestSignal(text) { const lower = compactWhitespace(String(text ?? "").toLowerCase()).replace(/ё/g, "е"); const normalized = lower.replace(/\b1\s*[cс]\b/giu, "1с"); if (!normalized) { return false; } const hasAdminVerb = /(?:настро(?:й|ить|ите|им)|установ(?:и|ить|ите)|подключ(?:и|ить|ите)|обнов(?:и|ить|ите)|почин(?:и|ить|ите)|исправ(?:ь|ить|ьте)|перенастро|перезапуст|удал(?:и|ить|ите|яй)|снес(?:и|ти)|очист(?:и|ить)|восстанов(?:и|ить)|созд(?:ай|ать)|провед(?:и|и)|заведи|завести)/i.test(normalized); const hasAdminObject = /(?:1с|1c|баз|сервер|конфиг|конфигурац|платформ|админ|доступ|роль|права|db|database|документ)/i.test(normalized); if (hasAdminVerb && hasAdminObject) { return true; } return /(?:удаляй?\s+баз|удали\s+баз|снеси\s+баз|delete\s+database|drop\s+database)/i.test(normalized); } function hasDangerOrCoercionSignal(text) { const normalized = compactWhitespace(String(text ?? "").toLowerCase()).replace(/ё/g, "е"); if (!normalized) { return false; } return /(?:убью|убить|убьют|убиют|угрож|опасн|омон|полици|насили|заставля|принужда|шантаж|выпил(?:юсь|иться|ился|илась|иться)|суицид|самоубий|покончу\s+с\s+собой|не\s+хочу\s+жить|хочу\s+умереть|сдохнуть|вскрыть\s+вены|повешусь|повеситься|спрыгну|kill\s+myself|end\s+my\s+life|suicid|self[\s-]?harm)/iu.test(normalized); } function hasDestructiveDataActionSignal(text) { const lower = compactWhitespace(String(text ?? "").toLowerCase()).replace(/ё/g, "е"); if (!lower) { return false; } return /(?:удаляй?\s+баз|удали\s+баз|снеси\s+баз|drop\s+database|delete\s+database|очисти\s+баз)/i.test(lower); } function hasAssistantCapabilityQuestionSignal(text) { const repaired = repairAddressMojibake(String(text ?? "")); const lower = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е"); const normalized = lower.replace(/\b1\s*[cс]\b/giu, "1с"); if (!lower) { return false; } const directCapabilityPhrases = [ "кто ты", "что ты можешь", "что конкретно ты можешь", "чем ты можешь помочь", "что ты умеешь", "какой у тебя функционал", "какие у тебя функции", "какие фичи", "что отработано", "что у тебя отработано", "полный список возможностей", "полный список" ]; if (directCapabilityPhrases.some((phrase) => normalized.includes(phrase))) { return true; } if (/(?:каки[ею].*(?:фич|функц|возможност|отработан)|какого\s+рода\s+ошибк.*ты\s+мож(?:ешь|ете)|какие\s+ошибк.*ты\s+мож(?:ешь|ете))/iu.test(normalized)) { return true; } const hasCanVerb = /(?:можешь|можете|умеешь|умеете|можно)/i.test(normalized); const hasControlAction = /(?:настро|установ|подключ|обнов|созда|подготов|сдела|делат|дела)/i.test(normalized); const hasAnalysisAction = /(?:найт|искать|провер|анализ|разоб|объясн|расска|подсказ|показ)/i.test(normalized); const hasCapabilityObject = /(?:1с|1c|док|документ|баз|отчет|отч[её]т|конфигурац|настройк)/i.test(normalized); if (hasCanVerb && hasControlAction && hasCapabilityObject) { return true; } if (hasCanVerb && hasAnalysisAction && !hasDataRetrievalRequestSignal(normalized)) { return true; } const hasCapabilityMetaQuestion = /(?:что|чем)\s+(?:ты\s+)?(?:мож(?:ешь|ете)|уме(?:ешь|ете)|можно)(?=$|[\s,.!?;:])/iu.test(normalized); if (hasCapabilityMetaQuestion && hasCapabilityObject) { return true; } if ((normalized.includes("1с") || normalized.includes("1c")) && hasCanVerb && /(?:настро|установ|подключ|обнов)/i.test(normalized)) { return true; } return false; } function hasAssistantDataScopeMetaQuestionSignal(text) { const repaired = repairAddressMojibake(String(text ?? "")); const lower = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е"); const normalized = lower.replace(/\b1\s*[cс]\b/giu, "1с"); if (!normalized) { return false; } const hasDirectSlangScopeLead = /(?:по\s+каким\s+(?:контор(?:ам|ы|а)?|кантор(?:ам|ы|а)?|компан(?:иям|ии|ию|ия)|организац(?:иям|ии|ию|ия))\s+мож(?:ем|но)\s+(?:общат|работ)|база\s+какой\s+(?:контор|компан|организац|фирм)|какая\s+база\s+(?:подключ|подруб|актив))/iu.test(normalized); if (hasDirectSlangScopeLead) { return true; } const hasSlangScopeQuestion = /(?:\u043f\u043e\s+\u043a\u0430\u043a\u0438\u043c\s+(?:\u043a\u043e\u043d\u0442\u043e\u0440(?:\u0430\u043c|\u044b|\u0430)?|\u043a\u043e\u043c\u043f\u0430\u043d(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e|\u0438\u044f)|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446(?:\u0438\u044f\u043c|\u0438\u0438|\u0438\u044e|\u0438\u044f)|\u0444\u0438\u0440\u043c(?:\u0430\u043c|\u0435|\u0443|\u0430)).*(?:\u043c\u043e\u0436(?:\u0435\u043c|\u043d\u043e)|\u0440\u0430\u0431\u043e\u0442|\u043e\u0431\u0449\u0430\u0442|\u043f\u043e\u0434\u0440\u0443\u0431|\u043f\u043e\u0434\u043a\u043b\u044e\u0447)|(?:\u0431\u0430\u0437\u0430\s+\u043a\u0430\u043a\u043e\u0439\s+(?:\u043a\u043e\u043d\u0442\u043e\u0440|\u043a\u043e\u043c\u043f\u0430\u043d|\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446|\u0444\u0438\u0440\u043c))|(?:\u043a\u0430\u043a\u0430\u044f\s+\u0431\u0430\u0437\u0430\s+(?:\u043f\u043e\u0434\u043a\u043b\u044e\u0447|\u0430\u043a\u0442\u0438\u0432)))/iu.test(normalized); if (hasSlangScopeQuestion) { return true; } const hasBaseOrTenantObject = /(?:баз(?:а|е|у|ы)?|тенант|tenant|контур)/i.test(normalized); const hasCompanyObject = /(?:компан(?:ия|ии|ию|ией)|компин(?:ия|ии|ию|ией)?|компини(?:я|и|ю|ей)?|компани[яеию]|организац(?:ия|ии|ию|ией)|контор(?:а|ы|у|ой)?|фирм(?:а|ы|у|ой)?)/i.test(normalized); const hasConnectionCue = /(?:подключен(?:а|о|ы)?|подруб|воткнут|активн(?:ый|ая)\s+канал|mcp-?канал|канал)/i.test(normalized); const hasNamingCue = /(?:как\s+называ(?:ет|ется)|что\s+за\s+(?:контор|компан|организац|фирм))/i.test(normalized); const hasWorkabilityCue = /(?:мож(?:ем|ешь|ете|но)\s+работ|работ(?:ать|аем|аешь|аете))/i.test(normalized); const hasScopeObject = hasBaseOrTenantObject || hasCompanyObject || hasConnectionCue; if (!hasScopeObject) { return false; } const hasMetaPerspective = /(?:ты|тебе|твой|у\s+тебя|в\s+тебе|мы|нам|наш(?:а|е|и|у|ей)?|сейчас|щас)/i.test(normalized); const hasScopedInterrogativePair = /(?:^|\s)(?:по\s+какой|с\s+какой|какая|какой|какие)\s+(?:баз|компан|компин|компини|компани|организац|контор|фирм|тенант|контур)/i.test(normalized); const hasScopeQuestion = /(?:чья|чье|чьи|доступн|подключен|подруб|воткнут|какая\s+баз|какой\s+баз)/i.test(normalized) || hasNamingCue || hasWorkabilityCue || hasScopedInterrogativePair; const hasInterrogativeScopeLead = /(?:^|\s)(?:по\s+какой|с\s+какой|чья|чье|чьи|which|who|what)/i.test(normalized); const isQuestionLike = /[?]/.test(String(text ?? "")) || hasInterrogativeScopeLead || hasScopedInterrogativePair; const hasExplicitScopeContext = hasBaseOrTenantObject || hasConnectionCue || hasWorkabilityCue || hasNamingCue; const hasRetrievalSignal = hasDataRetrievalRequestSignal(normalized); const hasContractAnalyticsCue = /(?:договор|контракт|contract).*(?:топ|сам(?:ый|ая|ое|ые)|крупн|жирн|оборот|бюджет|сумм|стоим|value|turnover|all\s+time|всю\s+истори|за\s+вс[её]\s+время)/iu.test(normalized); if (hasContractAnalyticsCue) { return false; } if (hasRetrievalSignal && !hasExplicitScopeContext) { return false; } const hasEligibleScopeObject = hasBaseOrTenantObject || (hasCompanyObject && (hasConnectionCue || hasWorkabilityCue || hasNamingCue || hasMetaPerspective)); return hasEligibleScopeObject && hasScopeQuestion && (hasMetaPerspective || isQuestionLike || hasExplicitScopeContext); } function shouldHandleAsAssistantCapabilityMetaQuery(text) { const raw = String(text ?? ""); const repaired = repairAddressMojibake(raw); const hasScopeMetaSignal = hasAssistantDataScopeMetaQuestionSignal(raw) || hasAssistantDataScopeMetaQuestionSignal(repaired); if (hasScopeMetaSignal) { return true; } const hasCapabilitySignal = hasAssistantCapabilityQuestionSignal(raw) || hasAssistantCapabilityQuestionSignal(repaired) || hasOperationalAdminActionRequestSignal(raw) || hasOperationalAdminActionRequestSignal(repaired); const hasRetrievalSignal = hasDataRetrievalRequestSignal(raw) || hasDataRetrievalRequestSignal(repaired); return hasCapabilitySignal && !hasRetrievalSignal; } function hasLivingChatSignal(text) { const lower = compactWhitespace(String(text ?? "").toLowerCase()); if (!lower) { return false; } if (/^(?:а\s+)?(?:тут|здесь|там|сюда|туда)[\s!?.,:;\-]*$/iu.test(lower)) { return true; } if (/^(ага|угу|ок|окей|ясно|понял|поняла|принято|спасибо|благодарю|супер|класс|норм|го|давай|погнали|привет|хай|йо|yo|че\s+там|ч[её]\s+как|че\s+как|hello|hi|thanks?)$/i.test(lower)) { return true; } if (/(как дела|как ты|что нового|расскажи о себе|чем можешь помочь|давай поговорим|поговорим|обсудим|посоветуй|что думаешь)/i.test(lower)) { return true; } return hasSmallTalkSignal(lower); } function buildAssistantCapabilityContractReply() { return (0, capabilitiesRegistry_1.buildCapabilityContractReplyFromRegistry)(); } function normalizeScopeLabel(value) { const repaired = repairAddressMojibake(String(value ?? "")); let normalized = compactWhitespace(repaired.trim()); for (let index = 0; index < 2; index += 1) { const first = normalized[0]; const last = normalized[normalized.length - 1]; const wrappedInQuotes = (first === "\"" && last === "\"") || (first === "'" && last === "'") || (first === "«" && last === "»"); if (!wrappedInQuotes) { break; } normalized = compactWhitespace(normalized.slice(1, -1).trim()); } if (!normalized) { return null; } if (/^(?:null|undefined|nan|0|не\s*заполнено)$/i.test(normalized)) { return null; } return normalized; } function normalizeScopeKey(value) { return repairAddressMojibake(String(value ?? "")).toLowerCase().replace(/ё/g, "е"); } const ORGANIZATION_SCOPE_STOPWORDS = new Set([ "ооо", "ao", "ао", "зао", "ип", "llc", "ltd", "company", "компания", "организация", "организации", "контора", "конторы", "фирма", "фирмы", "по", "для", "над", "под", "без", "с", "со", "в", "во", "на", "и", "или", "а", "но", "не", "мы", "нам", "наш", "наша", "наше", "наши", "ты", "тебе", "твой", "сейчас", "щас", "тут", "вот", "давай", "го", "погнали", "тогда", "обсудим", "обсуждать", "работать", "работаем", "работаешь", "работаете", "можем", "можно", "какая", "какой", "какие", "чья", "чье", "чьи" ]); function normalizeOrganizationScopeValue(value) { const normalized = normalizeScopeLabel(value); if (!normalized) { return null; } const unwrapped = normalized .replace(/^\\+|\\+$/g, "") .replace(/^"+|"+$/g, "") .replace(/^'+|'+$/g, "") .trim(); return unwrapped ? unwrapped : null; } function normalizeOrganizationScopeSearchText(value) { const source = normalizeScopeKey(value); return source .replace(/[^a-zа-я0-9]+/giu, " ") .replace(/\s+/g, " ") .trim(); } function tokenizeOrganizationScope(value) { const normalized = normalizeOrganizationScopeSearchText(value); if (!normalized) { return []; } return normalized .split(" ") .map((token) => token.trim()) .filter((token) => token.length >= 3 && !ORGANIZATION_SCOPE_STOPWORDS.has(token)); } function organizationTokenVariants(token) { const source = String(token ?? "").trim().toLowerCase(); if (!source) { return []; } const variants = new Set([source]); const withoutLongEnding = source.replace(/(?:ами|ями|ого|ему|ому|ыми|ими|иях|ях|ах|ей|ой|ом|ем|ам|ям|ую|юю|ая|яя|ое|ее|ые|ие|ов|ев|ий|ый|ой)$/iu, ""); if (withoutLongEnding.length >= 4) { variants.add(withoutLongEnding); } const withoutShortEnding = source.replace(/[аеёиоуыэюя]$/iu, ""); if (withoutShortEnding.length >= 4) { variants.add(withoutShortEnding); } return Array.from(variants); } function scoreOrganizationMentionInMessage(message, organization) { const messageNorm = normalizeOrganizationScopeSearchText(message); const organizationNorm = normalizeOrganizationScopeSearchText(organization); if (!messageNorm || !organizationNorm) { return 0; } if (messageNorm.includes(organizationNorm)) { return 10_000 + organizationNorm.length; } const organizationTokens = tokenizeOrganizationScope(organizationNorm); if (organizationTokens.length === 0) { return 0; } const messageTokens = tokenizeOrganizationScope(messageNorm); if (messageTokens.length === 0) { return 0; } let matchedTokens = 0; let score = 0; for (const token of organizationTokens) { const variants = organizationTokenVariants(token); let matched = false; let variantScore = 0; for (const variant of variants) { if (!variant) { continue; } if (messageNorm.includes(variant)) { matched = true; variantScore = Math.max(variantScore, variant.length * 5); continue; } const fuzzyMatched = messageTokens.some((messageToken) => { if (messageToken === variant) { return true; } if (messageToken.length >= 5 && variant.length >= 5) { return messageToken.startsWith(variant) || variant.startsWith(messageToken); } return false; }); if (fuzzyMatched) { matched = true; variantScore = Math.max(variantScore, Math.max(20, variant.length * 3)); } } if (matched) { matchedTokens += 1; score += variantScore > 0 ? variantScore : 10; } } if (matchedTokens === 0) { return 0; } if (matchedTokens === organizationTokens.length) { score += 400; } else { score += matchedTokens * 50; } return score; } function parseOrganizationsFromDataScopeAssistantText(text) { const source = repairAddressMojibake(String(text ?? "")); if (!source) { return []; } const extracted = []; const singleMatch = source.match(/доступна\s+организация:\s*([^.\n]+)/iu); if (singleMatch) { const value = normalizeOrganizationScopeValue(singleMatch[1]); if (value) { extracted.push(value); } } const multiMatch = source.match(/доступны\s+организац(?:ии|ия)\s*(?:\(\d+\))?:\s*([^.\n]+)/iu); if (multiMatch) { const parts = String(multiMatch[1] ?? "") .split(",") .map((item) => normalizeOrganizationScopeValue(item)) .filter(Boolean); extracted.push(...parts); } return Array.from(new Set(extracted)); } function mergeKnownOrganizations(values) { const dedup = new Map(); for (const raw of Array.isArray(values) ? values : []) { const normalized = normalizeOrganizationScopeValue(raw); if (!normalized) { continue; } const key = normalizeOrganizationScopeSearchText(normalized); if (!key) { continue; } if (!dedup.has(key)) { dedup.set(key, normalized); } } return Array.from(dedup.values()).slice(0, 20); } function extractKnownOrganizationsFromHistory(items) { const collected = []; for (let index = (Array.isArray(items) ? items.length : 0) - 1; index >= 0; index -= 1) { const item = items[index]; if (!item || item.role !== "assistant") { continue; } const debug = item.debug && typeof item.debug === "object" ? item.debug : null; if (debug) { const directFromProbe = Array.isArray(debug.living_chat_data_scope_probe_organizations) ? debug.living_chat_data_scope_probe_organizations : []; const knownFromDebug = Array.isArray(debug.assistant_known_organizations) ? debug.assistant_known_organizations : []; if (directFromProbe.length > 0 || knownFromDebug.length > 0) { collected.push(...directFromProbe, ...knownFromDebug); } } const parsedFromText = parseOrganizationsFromDataScopeAssistantText(item.text); if (parsedFromText.length > 0) { collected.push(...parsedFromText); } if (collected.length >= 20) { break; } } return mergeKnownOrganizations(collected); } function findLastAssistantActiveOrganization(items) { for (let index = (Array.isArray(items) ? items.length : 0) - 1; index >= 0; index -= 1) { const item = items[index]; if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") { continue; } const direct = normalizeOrganizationScopeValue(item.debug.assistant_active_organization); if (direct) { return direct; } const selected = normalizeOrganizationScopeValue(item.debug.living_chat_selected_organization); if (selected) { return selected; } } return null; } function resolveOrganizationSelectionFromMessage(userMessage, knownOrganizations) { const known = mergeKnownOrganizations(knownOrganizations); if (!userMessage || known.length === 0) { return null; } const messageNorm = normalizeOrganizationScopeSearchText(userMessage); if (!messageNorm) { return null; } const scored = known .map((organization) => ({ organization, score: scoreOrganizationMentionInMessage(messageNorm, organization) })) .filter((item) => item.score > 0) .sort((a, b) => b.score - a.score || a.organization.length - b.organization.length); if (scored.length === 0) { return null; } const best = scored[0]; const second = scored[1]; if (best.score < 90) { return null; } if (second && second.score === best.score) { return null; } return best.organization; } function resolveSessionOrganizationScopeContext(userMessage, items) { return (0, assistantOrganizationScopeRuntimeAdapter_1.resolveSessionOrganizationScopeContextRuntime)({ userMessage, items, extractKnownOrganizationsFromHistory, resolveOrganizationSelectionFromMessage, findLastAssistantActiveOrganization, normalizeOrganizationScopeValue }); } function mergeFollowupContextWithOrganizationScope(followupContext, organization) { return (0, assistantOrganizationScopeRuntimeAdapter_1.mergeFollowupContextWithOrganizationScopeRuntime)({ followupContext, organization, normalizeOrganizationScopeValue, toNonEmptyString }); } function resolveSessionOrganizationScopeContextForTests(userMessage, items) { return resolveSessionOrganizationScopeContext(userMessage, items); } function normalizeGuidValue(value) { const source = normalizeScopeLabel(value); if (!source) { return null; } const match = source.match(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i); return match ? String(match[0]).toLowerCase() : null; } function extractGuidValuesFromText(value) { const source = normalizeScopeLabel(value); if (!source) { return []; } const matches = source.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/ig); if (!matches || matches.length === 0) { return []; } return Array.from(new Set(matches.map((item) => String(item).toLowerCase()))); } function hasOrganizationKeyHint(key) { const source = String(key ?? ""); return /(?:организац|organization|company|контор|org|ð¾ñ€ð³ð°ð½ð¸ð·ð°ñ†)/i.test(source); } function hasNameKeyHint(key) { const source = String(key ?? ""); return /(?:представ|наимен|name|title|display|presentation|description|ð¿ñ€ðµð´ññ‚ð°ð²|ð½ð°ð¸ð¼ðµð½)/i.test(source); } function hasGuidKeyHint(key) { const source = String(key ?? ""); return /(?:идентифик|guid|uuid|key|ref|ссылк|\bid\b|ð¸ð´ðµð½ñ‚ð¸ñ„|ñññ‹ð»)/i.test(source); } function looksLikeOrganizationTypeMarker(value) { const normalized = normalizeScopeKey(value); const raw = String(value ?? "").toLowerCase(); return /(?:справочникссылка\.\s*организац|catalogref\.\s*organization|организац|organization|company|ð¾ñ€ð³ð°ð½ð¸ð·ð°ñ†|ð¡ð¿ñ€ð°ð²ð¾ñ‡ð½ð¸ðºð¡ññ‹ð»ðºð°\.ðžñ€ð³ð°ð½ð¸ð·)/i.test(normalized) || /(?:ð¾ñ€ð³ð°ð½ð¸ð·ð°ñ†|ð¡ð¿ñ€ð°ð²ð¾ñ‡ð½ð¸ðºð¡ññ‹ð»ðºð°\.ðžñ€ð³ð°ð½ð¸ð·)/i.test(raw); } function isPlausibleOrganizationName(value) { const candidate = normalizeScopeLabel(value); if (!candidate) { return false; } if (/^(?:период|регистратор|счетдт|счеткт|amount|period|registrator|accountdt|accountkt)$/i.test(candidate)) { return false; } if (/^[0-9._:/\\-]+$/i.test(candidate)) { return false; } if (/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i.test(candidate)) { return false; } if (/(?:справочникссылка|документссылка|плансчетовссылка|standardodata|recordtype|cmp:)/i.test(candidate)) { return false; } return /[A-Za-z\u0400-\u04FF]/u.test(candidate); } function appendOrganizationFactsFromValue(value, hints, bucket, depth = 0) { if (depth > 4 || value === null || value === undefined) { return; } if (typeof value === "string") { for (const guid of extractGuidValuesFromText(value)) { if (hints.guidHint || hints.organizationHint || hints.nameHint) { bucket.refs.push(guid); } } if ((hints.organizationHint || hints.nameHint) && isPlausibleOrganizationName(value)) { const normalized = normalizeScopeLabel(value); if (normalized) { bucket.names.push(normalized); } } return; } if (Array.isArray(value)) { for (const item of value) { appendOrganizationFactsFromValue(item, hints, bucket, depth + 1); } return; } if (typeof value === "object") { const entries = Object.entries(value); let objectIsOrganization = false; let hasObjectRefMarker = false; let hasGuidLikeField = false; let hasTypeMarker = false; for (const [rawKey, rawVal] of entries) { const key = normalizeScopeKey(rawKey); if ((key.includes("objectref") || key.includes("_objectref")) && rawVal === true) { hasObjectRefMarker = true; } if (typeof rawVal === "string" && normalizeGuidValue(rawVal)) { hasGuidLikeField = true; } if (hasOrganizationKeyHint(key)) { objectIsOrganization = true; break; } if ((key.includes("типобъекта") || key.includes("type")) && typeof rawVal === "string" && looksLikeOrganizationTypeMarker(rawVal)) { objectIsOrganization = true; hasTypeMarker = true; break; } } if (!objectIsOrganization && hasObjectRefMarker && hasGuidLikeField) { const hasNameLikeValue = entries.some(([rawKey, rawVal]) => { if (typeof rawVal !== "string") { return false; } const key = normalizeScopeKey(rawKey); return hasNameKeyHint(key) || isPlausibleOrganizationName(rawVal); }); if (hasTypeMarker || hasNameLikeValue) { objectIsOrganization = true; } } for (const [rawKey, rawVal] of entries) { if (String(rawKey ?? "").startsWith("__")) { continue; } const key = normalizeScopeKey(rawKey); const childHints = { organizationHint: hints.organizationHint || objectIsOrganization || hasOrganizationKeyHint(key), nameHint: hints.nameHint || objectIsOrganization || hasNameKeyHint(key), guidHint: hints.guidHint || objectIsOrganization || hasGuidKeyHint(key) }; if (typeof rawVal === "string") { const guid = normalizeGuidValue(rawVal); if (guid && childHints.guidHint) { bucket.refs.push(guid); } } appendOrganizationFactsFromValue(rawVal, childHints, bucket, depth + 1); } } } function extractOrganizationFactsFromRows(rows) { const names = []; const refs = []; const pairs = []; for (const row of Array.isArray(rows) ? rows : []) { if (!row || typeof row !== "object") { continue; } const rowNames = []; const rowRefs = []; for (const [rawKey, rawValue] of Object.entries(row)) { if (String(rawKey ?? "").startsWith("__")) { continue; } const key = normalizeScopeKey(rawKey); const hints = { organizationHint: hasOrganizationKeyHint(key), nameHint: hasNameKeyHint(key), guidHint: hasGuidKeyHint(key) }; appendOrganizationFactsFromValue(rawValue, hints, { names: rowNames, refs: rowRefs }); } const dedupRowNames = Array.from(new Set(rowNames)) .filter((item) => isPlausibleOrganizationName(item)) .slice(0, 20); const dedupRowRefs = Array.from(new Set(rowRefs)) .map((item) => String(item ?? "").toLowerCase()) .filter((item) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(item)) .slice(0, 20); if (dedupRowNames.length === 0 && dedupRowRefs.length === 0) { const fallbackBucket = { names: [], refs: [] }; appendOrganizationFactsFromValue(row, { organizationHint: true, nameHint: true, guidHint: true }, fallbackBucket); for (const value of fallbackBucket.names) { if (isPlausibleOrganizationName(value)) { dedupRowNames.push(value); } } for (const value of fallbackBucket.refs) { const normalized = String(value ?? "").toLowerCase(); if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(normalized)) { dedupRowRefs.push(normalized); } } } for (const name of dedupRowNames) { names.push(name); } for (const ref of dedupRowRefs) { refs.push(ref); } if (dedupRowRefs.length > 0 && dedupRowNames.length > 0) { for (const ref of dedupRowRefs) { for (const name of dedupRowNames) { pairs.push({ ref, name }); } } } } return { names: Array.from(new Set(names)).slice(0, 20), refs: Array.from(new Set(refs)).slice(0, 20), pairs: Array.from(new Set(pairs.map((item) => `${item.ref}||${item.name}`))) .map((token) => { const [ref, name] = token.split("||"); return { ref, name }; }) .slice(0, 100) }; } function resolveOrganizationNamesByRefs(refs, facts) { const refSet = new Set((Array.isArray(refs) ? refs : []) .map((item) => String(item ?? "").toLowerCase()) .filter((item) => item.length > 0)); if (refSet.size === 0) { return []; } const names = []; for (const pair of Array.isArray(facts?.pairs) ? facts.pairs : []) { const ref = String(pair?.ref ?? "").toLowerCase(); const name = normalizeScopeLabel(pair?.name ?? ""); if (!ref || !name || !refSet.has(ref)) { continue; } names.push(name); } return Array.from(new Set(names)).slice(0, 20); } function buildResolvedDataScopeProbe(status, organizations) { return { status, channel: config_1.ASSISTANT_MCP_CHANNEL, organizations: Array.from(new Set(Array.isArray(organizations) ? organizations : [])).slice(0, 20), error: null }; } function extractOrganizationFactsFromRowsForTests(rows) { return extractOrganizationFactsFromRows(rows); } function resolveOrganizationNamesByRefsForTests(refs, facts) { return resolveOrganizationNamesByRefs(refs, facts); } async function resolveAssistantDataScopeProbe() { const cacheKey = `${config_1.ASSISTANT_MCP_PROXY_URL}|${config_1.ASSISTANT_MCP_CHANNEL}`; const now = Date.now(); const cached = dataScopeProbeCache.get(cacheKey); if (cached && typeof cached === "object" && Number(cached.expiresAt ?? 0) > now) { return cached.value; } if (String(process.env.NODE_ENV ?? "").toLowerCase() === "test") { return { status: "skipped_test_env", channel: config_1.ASSISTANT_MCP_CHANNEL, organizations: [], error: null }; } const catalogQueryCandidates = [ "ВЫБРАТЬ ПЕРВЫЕ 20 ПРЕДСТАВЛЕН�?Е(Организации.Ссылка) КАК Организация �?З Справочник.Организации КАК Организации", "ВЫБРАТЬ ПЕРВЫЕ 20 Организации.Наименование КАК Организация �?З Справочник.Организации КАК Организации", "ВЫБРАТЬ ПЕРВЫЕ 20 Организации.НаименованиеПолное КАК Организация �?З Справочник.Организации КАК Организации", "ВЫБРАТЬ ПЕРВЫЕ 100 Организации.Ссылка КАК Организация, ПРЕДСТАВЛЕН�?Е(Организации.Ссылка) КАК ОрганизацияПредставление �?З Справочник.Организации КАК Организации" ]; const movementProbeCandidates = [ "ВЫБРАТЬ ПЕРВЫЕ 60 Движения.Организация КАК Организация, ПРЕДСТАВЛЕН�?Е(Движения.Организация) КАК ОрганизацияПредставление �?З РегистрБухгалтерии.Хозрасчетный КАК Движения УПОРЯДОЧ�?ТЬ ПО Движения.Период УБЫВ", "ВЫБРАТЬ ПЕРВЫЕ 60 Движения.Организация КАК Организация �?З РегистрБухгалтерии.Хозрасчетный КАК Движения" ]; let lastError = null; const catalogFacts = { names: [], refs: [], pairs: [] }; for (const queryText of catalogQueryCandidates) { const probe = await (0, addressMcpClient_1.executeAddressMcpQuery)({ query: queryText, limit: 100 }); if (probe.error) { lastError = probe.error; continue; } const facts = extractOrganizationFactsFromRows(probe.rows); catalogFacts.names.push(...facts.names); catalogFacts.refs.push(...facts.refs); catalogFacts.pairs.push(...facts.pairs); if (facts.names.length > 0) { const resolved = buildResolvedDataScopeProbe("resolved", facts.names); dataScopeProbeCache.set(cacheKey, { expiresAt: now + DATA_SCOPE_CACHE_TTL_MS, value: resolved }); return resolved; } } const movementFacts = { names: [], refs: [], pairs: [] }; for (const queryText of movementProbeCandidates) { const probe = await (0, addressMcpClient_1.executeAddressMcpQuery)({ query: queryText, limit: 60 }); if (probe.error) { lastError = probe.error; continue; } const facts = extractOrganizationFactsFromRows(probe.rows); movementFacts.names.push(...facts.names); movementFacts.refs.push(...facts.refs); movementFacts.pairs.push(...facts.pairs); if (facts.names.length > 0) { const resolved = buildResolvedDataScopeProbe("resolved_from_activity", facts.names); dataScopeProbeCache.set(cacheKey, { expiresAt: now + DATA_SCOPE_CACHE_TTL_MS, value: resolved }); return resolved; } } const movementRefs = Array.from(new Set(movementFacts.refs)) .map((item) => String(item ?? "").toLowerCase()) .filter((item) => item.length > 0); if (movementRefs.length > 0) { const namesFromCatalogPairs = resolveOrganizationNamesByRefs(movementRefs, { names: Array.from(new Set(catalogFacts.names)), refs: Array.from(new Set(catalogFacts.refs)), pairs: catalogFacts.pairs }); if (namesFromCatalogPairs.length > 0) { const resolved = buildResolvedDataScopeProbe("resolved_from_ref_lookup", namesFromCatalogPairs); dataScopeProbeCache.set(cacheKey, { expiresAt: now + DATA_SCOPE_CACHE_TTL_MS, value: resolved }); return resolved; } } const fallback = { status: lastError ? "unresolved_with_error" : "unresolved", channel: config_1.ASSISTANT_MCP_CHANNEL, organizations: [], error: lastError }; dataScopeProbeCache.set(cacheKey, { expiresAt: now + DATA_SCOPE_CACHE_TTL_MS, value: fallback }); return fallback; } function buildAssistantDataScopeContractReply(scopeProbe = null) { const channel = String(scopeProbe?.channel ?? config_1.ASSISTANT_MCP_CHANNEL ?? "default"); const organizations = Array.isArray(scopeProbe?.organizations) ? scopeProbe.organizations : []; if (organizations.length === 1) { return [ `Сейчас в активном MCP-канале \`${channel}\` доступна организация: ${organizations[0]}.`, "Работаю в read-only режиме. Могу сразу показать по этой организации документы, операции, договоры или остатки." ].join(" "); } if (organizations.length > 1) { const preview = organizations.slice(0, 10).join(", "); return [ `Сейчас в активном MCP-канале \`${channel}\` доступны организации (${organizations.length}): ${preview}.`, "Работаю в read-only режиме. Скажи, по какой организации смотреть документы/операции." ].join(" "); } if (scopeProbe?.status === "unresolved_with_error" && scopeProbe?.error) { return [ `Не смог прочитать название организации из live MCP-канала \`${channel}\`: ${scopeProbe.error}.`, "Работаю в read-only режиме и вижу только данные активного контура. Проверь подключение MCP/1С, после этого сразу назову контору." ].join(" "); } return [ `Работаю в read-only режиме и вижу только те данные, которые отдает текущий MCP-канал \`${channel}\`.`, "Словарь компаний не зашит в код: рабочий контур определяется live-подключением.", "Если подключено несколько баз, для автосписка нужен MCP-метод метаданных (перечень баз/организаций); без него можно анализировать только активный контур запросов." ].join(" "); } function buildAssistantDataScopeSelectionReply(organization) { const selected = normalizeOrganizationScopeValue(organization) ?? String(organization ?? "").trim(); return [ `Отлично, фиксирую рабочую организацию: ${selected}.`, "Дальше буду держать этот контур как активный, пока вы не переключите организацию." ].join(" "); } function buildAssistantOrganizationFactBoundaryReply(organization) { const selected = normalizeOrganizationScopeValue(organization) ?? String(organization ?? "").trim(); if (selected) { return [ `По организации ${selected} не буду называть дату/возраст без live-подтвержденного источника.`, "Если нужно, запрошу факт из 1С и верну только подтвержденный ответ." ].join(" "); } return [ "Не буду называть дату/возраст организации без live-подтвержденного источника.", "Сначала получу факт из 1С, потом дам точный ответ." ].join(" "); } function buildAssistantOperationalBoundaryReply() { return [ "Понимаю, что ситуация срочная.", "Я не могу сам настраивать 1С или менять базу/конфигурацию.", "Могу помочь безопасно: разберем симптомы и подготовим точные шаги для вашего 1С/�?Т-админа." ].join(" "); } function buildAssistantSafetyRefusalReply() { return [ "Я не могу помогать с удалением базы или скрытием данных.", "Если вам угрожает опасность, срочно звоните 112 и следуйте указаниям экстренных служб.", "По 1С могу дать только безопасные диагностические рекомендации." ].join(" "); } function containsCjkChars(text) { const source = String(text ?? ""); if (!source) { return false; } return /[\u3400-\u9FFF\uF900-\uFAFF]/u.test(source); } function containsLetterLikeChars(text) { const source = String(text ?? ""); if (!source) { return false; } return /[A-Za-z\u0400-\u04FF]/u.test(source); } function applyLivingChatScriptGuard(chatText, userMessage) { const source = String(chatText ?? "").trim(); if (!source) { return { text: "", applied: false, reason: null }; } if (!containsCjkChars(source) || containsCjkChars(userMessage)) { return { text: source, applied: false, reason: null }; } const stripped = source .replace(/[\u3400-\u9FFF\uF900-\uFAFF]+/gu, "") .replace(/[,。!?;:]/gu, "") .replace(/\s{2,}/g, " ") .replace(/\s+([,.!?;:])/g, "$1") .trim(); if (stripped && containsLetterLikeChars(stripped)) { return { text: stripped, applied: true, reason: "unexpected_cjk_fragment_stripped" }; } return { text: "Понял. Сформулируйте, что именно нужно по данным 1С, и я помогу по шагам.", applied: true, reason: "unexpected_cjk_fragment_fallback" }; } function applyLivingChatGroundingGuard(input) { const userMessage = String(input?.userMessage ?? ""); const chatText = String(input?.chatText ?? "").trim(); const organization = toNonEmptyString(input?.organization); if (!chatText) { return { text: chatText, applied: false, reason: null }; } if (!hasOrganizationFactLookupSignal(userMessage)) { return { text: chatText, applied: false, reason: null }; } if (/(?:не\s+могу|не\s+вижу|после\s+проверки|live|подтвержден)/i.test(chatText)) { return { text: chatText, applied: false, reason: null }; } const hasSpecificUnverifiedFact = /(?:\b\d{1,2}[./-]\d{1,2}[./-](?:\d{2}|\d{4})\b|\b(?:19|20)\d{2}\b|\b\d+\s+лет\b)/i.test(chatText); if (!hasSpecificUnverifiedFact) { return { text: chatText, applied: false, reason: null }; } return { text: buildAssistantOrganizationFactBoundaryReply(organization), applied: true, reason: "organization_fact_without_live_source_blocked" }; } function resolveLivingAssistantModeDecision(input) { const userMessage = String(input?.userMessage ?? ""); if (input?.addressLaneTriggered) { return { mode: "address_data", reason: "address_lane_triggered" }; } if (!config_1.FEATURE_ASSISTANT_LIVING_CHAT_ROUTER_V1) { return { mode: "deep_analysis", reason: "living_chat_router_disabled" }; } if (Boolean(input?.useMock)) { return { mode: "deep_analysis", reason: "mock_mode_keeps_deep_pipeline" }; } if (hasAssistantDataScopeMetaQuestionSignal(userMessage)) { return { mode: "chat", reason: "assistant_data_scope_query_detected" }; } if (shouldHandleAsAssistantCapabilityMetaQuery(userMessage)) { return { mode: "chat", reason: "assistant_capability_query_detected" }; } if (hasOrganizationFactLookupSignal(userMessage) || hasOrganizationFactFollowupSignal(userMessage)) { return { mode: "chat", reason: "organization_fact_lookup_signal_detected" }; } if (hasStrongDataIntentSignal(userMessage)) { return { mode: "deep_analysis", reason: "strong_data_signal_detected" }; } if (hasLivingChatSignal(userMessage)) { return { mode: "chat", reason: "living_chat_signal_detected" }; } const predecomposeMode = toNonEmptyString(input?.predecomposeMode); const predecomposeConfidence = toNonEmptyString(input?.predecomposeModeConfidence); if (predecomposeMode === "unsupported" && (predecomposeConfidence === "low" || predecomposeConfidence === "medium")) { return { mode: "deep_analysis", reason: "predecompose_unsupported_mode_fallback_to_deep" }; } return { mode: "deep_analysis", reason: "default_deep_pipeline" }; } class AssistantService { normalizerService; sessions; dataLayer; sessionLogger; addressQueryService; chatClient; constructor(normalizerService, sessions, dataLayer = new assistantDataLayer_1.AssistantDataLayer(), sessionLogger = new assistantSessionLogger_1.AssistantSessionLogger(), addressQueryService = new addressQueryService_1.AddressQueryService(), chatClient = new openaiResponsesClient_1.OpenAIResponsesClient()) { this.normalizerService = normalizerService; this.sessions = sessions; this.dataLayer = dataLayer; this.sessionLogger = sessionLogger; this.addressQueryService = addressQueryService; this.chatClient = chatClient; } getSession(sessionId) { return this.sessions.getSession(sessionId); } async handleMessage(payload) { const turnRuntimeDeps = (0, assistantTurnRuntimeDepsAdapter_1.buildAssistantTurnRuntimeDeps)({ sessions: this.sessions, sessionLogger: this.sessionLogger, normalizerService: this.normalizerService, dataLayer: this.dataLayer, addressQueryService: this.addressQueryService, chatClient: this.chatClient, messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`, nowIso: () => new Date().toISOString(), defaultApiKey: process.env.OPENAI_API_KEY ?? "", logEvent: (runtimePayload) => (0, log_1.logJson)(runtimePayload), flags: { featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1, featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1, featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1, featureStateFollowupBindingV1: config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1, featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11, featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11, featureProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1, featureLifecycleAnswerV1: config_1.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1 }, defaults: { defaultModel: config_1.DEFAULT_MODEL, defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL }, helpers: { compactWhitespace, repairAddressMojibake, resolveRuntimeAnalysisContext, runAddressLlmPreDecompose: async (runtimePayload, runtimeUserMessage) => runAddressLlmPreDecompose(this.normalizerService, runtimePayload, runtimeUserMessage), buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1, sanitizeAddressMessageForFallback, toNonEmptyString, resolveAddressFollowupCarryoverContext, resolveAssistantOrchestrationDecision, buildAddressDialogContinuationContractV2, mergeFollowupContextWithOrganizationScope, isRetryableAddressLimitedResult, mergeKnownOrganizations, hasAssistantDataScopeMetaQuestionSignal, shouldHandleAsAssistantCapabilityMetaQuery, hasDestructiveDataActionSignal, hasDangerOrCoercionSignal, hasOperationalAdminActionRequestSignal, hasOrganizationFactLookupSignal, hasOrganizationFactFollowupSignal, shouldEmitOrganizationSelectionReply, hasAssistantCapabilityQuestionSignal, resolveDataScopeProbe: () => resolveAssistantDataScopeProbe(), applyScriptGuard: (chatText, runtimeUserMessage) => applyLivingChatScriptGuard(chatText, runtimeUserMessage), applyGroundingGuard: (guardInput) => applyLivingChatGroundingGuard(guardInput), buildAssistantSafetyRefusalReply, buildAssistantDataScopeContractReply, buildAssistantOrganizationFactBoundaryReply, buildAssistantDataScopeSelectionReply, buildAssistantOperationalBoundaryReply, buildAssistantCapabilityContractReply, loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt, sanitizeOutgoingAssistantText, buildAddressDebugPayload, buildAddressFollowupOffer, buildFollowupStateBinding, resolveBusinessScopeAlignment, inferP0DomainFromMessage, resolveBusinessScopeFromLiveContext, extractRequirements, toExecutionPlan, enforceRbpLiveRoutePlan, enforceFaLiveRoutePlan, mapNoRouteReason, buildSkippedResult, evaluateCoverage, checkGrounding, collectRbpLiveRouteAudit, collectFaLiveRouteAudit, hasExplicitPeriodAnchorFromNormalized, extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload), toDebugRoutes: (routeSummary) => toDebugRoutes(routeSummary), extractExecutionState } }); try { const turnRuntime = await (0, assistantTurnAttemptRuntimeAdapter_1.runAssistantTurnAttemptRuntime)({ payload, runUserTurnBootstrapRuntime: (runtimePayload) => (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)((0, assistantTurnRuntimeInputBuilder_1.buildAssistantUserTurnBootstrapRuntimeInput)(runtimePayload, turnRuntimeDeps)), resolveSessionOrganizationScopeContext: (runtimeUserMessage, sessionItems) => resolveSessionOrganizationScopeContext(runtimeUserMessage, sessionItems), runAddressAttemptRuntime: async (runtimeInput) => (0, assistantAddressAttemptRuntimeAdapter_1.runAssistantAddressAttemptRuntime)((0, assistantTurnRuntimeInputBuilder_1.buildAssistantAddressAttemptRuntimeInput)(runtimeInput, turnRuntimeDeps)), runDeepTurnAttemptRuntime: async (runtimeInput) => (0, assistantDeepTurnAttemptRuntimeAdapter_1.runAssistantDeepTurnAttemptRuntime)((0, assistantTurnRuntimeInputBuilder_1.buildAssistantDeepTurnAttemptRuntimeInput)(runtimeInput, turnRuntimeDeps)) }); return turnRuntime.response; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); const sessionId = String(payload?.session_id ?? payload?.sessionId ?? "").trim() || `asst-${(0, nanoid_1.nanoid)(10)}`; const ensuredSession = this.sessions.ensureSession(sessionId); const existingAssistant = [...ensuredSession.items].reverse().find((item) => item.role === "assistant") ?? null; if (existingAssistant) { return { ok: true, session_id: sessionId, assistant_reply: existingAssistant.text, reply_type: existingAssistant.reply_type ?? "backend_error", conversation_item: existingAssistant, debug: existingAssistant.debug ?? buildAssistantBackendErrorDebugPayload(errorMessage), conversation: cloneItems(ensuredSession.items) }; } const createdAt = new Date().toISOString(); const debugPayload = buildAssistantBackendErrorDebugPayload(errorMessage); const assistantItem = this.sessions.appendItem(sessionId, { message_id: `msg-${(0, nanoid_1.nanoid)(10)}`, session_id: sessionId, role: "assistant", text: buildAssistantBackendErrorReply(), reply_type: "backend_error", created_at: createdAt, trace_id: debugPayload.trace_id ?? null, debug: debugPayload }); const sessionSnapshot = this.sessions.getSession(sessionId) ?? this.sessions.ensureSession(sessionId); this.sessionLogger.persistSession(sessionSnapshot); return { ok: true, session_id: sessionId, assistant_reply: assistantItem.text, reply_type: "backend_error", conversation_item: assistantItem, debug: debugPayload, conversation: cloneItems(sessionSnapshot.items) }; } } } exports.AssistantService = AssistantService;