NODEDC_1C/llm_normalizer/backend/dist/services/assistantService.js

4613 lines
205 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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 assistantAddressLaneResponseAttemptRuntimeAdapter_1 = __importStar(require("./assistantAddressLaneResponseAttemptRuntimeAdapter"));
const assistantCoverageGrounding_1 = __importStar(require("./assistantCoverageGrounding"));
const assistantDeepTurnAnalysisAttemptRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnAnalysisAttemptRuntimeAdapter"));
const assistantDeepTurnFinalizeRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnFinalizeRuntimeAdapter"));
const assistantDeepTurnPackagingRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnPackagingRuntimeAdapter"));
const assistantDeepTurnNormalizationRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnNormalizationRuntimeAdapter"));
const assistantDeepTurnResponseRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnResponseRuntimeAdapter"));
const assistantAddressRuntimeAdapter_1 = __importStar(require("./assistantAddressRuntimeAdapter"));
const assistantAddressLaneAttemptRuntimeAdapter_1 = __importStar(require("./assistantAddressLaneAttemptRuntimeAdapter"));
const assistantLivingChatAttemptRuntimeAdapter_1 = __importStar(require("./assistantLivingChatAttemptRuntimeAdapter"));
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 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) {
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 explicitPeriod = extractNormalizedPeriodLiteral(userMessage);
if (explicitPeriod && state.focus.period && explicitPeriod !== state.focus.period) {
const periodLooksLikeFollowupRefinement = hasFollowupMarker(userMessage) || hasReferentialPointer(userMessage);
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 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,
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",
"blya",
"blyat",
"епт",
"ёпт",
"бля"
]);
const ADDRESS_FALLBACK_STRIP_TOKENS = new Set([
"бля",
"блять",
"blya",
"blyat",
"епт",
"ёпт",
"епта",
"нах",
"нахуй",
"плс",
"pls",
"пж",
"пжлст",
"пожалуйста",
"please"
]);
const ADDRESS_MONTH_ALIAS_MAP = {
янв: "01",
январ: "01",
january: "01",
jan: "01",
фев: "02",
феврал: "02",
february: "02",
feb: "02",
мар: "03",
март: "03",
march: "03",
apr: "04",
апр: "04",
апрел: "04",
april: "04",
май: "05",
ма: "05",
may: "05",
июн: "06",
июнь: "06",
june: "06",
jun: "06",
июл: "07",
июль: "07",
july: "07",
jul: "07",
авг: "08",
август: "08",
august: "08",
aug: "08",
сен: "09",
сент: "09",
сентябр: "09",
september: "09",
sep: "09",
окт: "10",
октябр: "10",
october: "10",
oct: "10",
ноя: "11",
ноябр: "11",
november: "11",
nov: "11",
дек: "12",
декабр: "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);
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 findLastAddressAssistantDebug(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 debug;
}
}
return null;
}
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 repaired = repairAddressMojibake(String(userMessage ?? ""));
const text = compactWhitespace(repaired.toLowerCase());
if (!text) {
return false;
}
if (shouldHandleAsAssistantCapabilityMetaQuery(text)) {
return false;
}
if (/(?:за\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.test(text)) {
return true;
}
if (hasReferentialPointer(text)) {
return true;
}
if (/(?:на\s+ту\s+же\s+дат[ауеы]|на\s+эту\s+же\s+дат[ауеы]|same\s+date|the\s+same\s+date|as\s+of\s+same\s+date)/iu.test(text)) {
return true;
}
const shortFollowup = countTokens(text) <= 8;
if (/(?:кроме|помимо)\s+(?:этого|этой|этот|эту|этих|этого\s+документа|этого\s+договора|этого\s+контрагента)/iu.test(text)) {
return true;
}
if (/(?:есть\s+ещ[её]|что\s+ещ[её]|ещ[её]\s+что|ещ[её]\s+что-?то|остал(?:ось|ось\?)|друг(?:ое|ие))/iu.test(text) && countTokens(text) <= 12) {
return true;
}
if (shortFollowup && hasFollowupMarker(text)) {
return true;
}
if (shortFollowup && /(?:^|\s)(?:также|тоже|also|same|again|ещ[её]|теперь|then|now)(?=$|[\s,.;:!?])/iu.test(text)) {
return true;
}
if (shortFollowup &&
/(?:кто\s+из\s+(?:них|этих|тех)|кто\s+нов(?:ые|ых|ый)|кто\s+потом\s+исчез|кто\s+был\s+(?:только|ровно)\s+один\s+раз)/iu.test(text)) {
return true;
}
if (shortFollowup && /^(?:а|и)\s+кто\b/iu.test(text)) {
return true;
}
if (shortFollowup &&
/(?:почему|why|из[-\s]?за\s+чего|как\s+так|reason)/iu.test(text) &&
/(?:ндс|vat|прогноз|к\s+уплате|нул|ноль|\b0(?:[.,]0+)?\b)/iu.test(text)) {
return true;
}
if (shortFollowup &&
/(?:^|\s)по\s+[a-zа-яё][a-zа-яё0-9._-]{1,}(?=$|[\s,.;:!?])/iu.test(text) &&
!/(?:по\s+этому|по\s+тому|по\s+нему|по\s+ней|по\s+ним)/iu.test(text)) {
return true;
}
if (shortFollowup && hasPeriodLiteral(text)) {
return true;
}
return false;
}
function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMessage = null, llmPreDecomposeMeta = null) {
const previousAddressDebug = findLastAddressAssistantDebug(items);
const followupOffer = previousAddressDebug ? buildAddressFollowupOffer(previousAddressDebug) : null;
const hasImplicitContinuationSignal = Boolean(previousAddressDebug) &&
Boolean(followupOffer?.enabled) &&
(isImplicitAddressContinuationByLlm(userMessage, llmPreDecomposeMeta) ||
(toNonEmptyString(alternateMessage) ? isImplicitAddressContinuationByLlm(alternateMessage, llmPreDecomposeMeta) : false));
const hasPrimaryFollowupSignal = hasAddressFollowupContextSignal(userMessage);
const hasAlternateFollowupSignal = toNonEmptyString(alternateMessage)
? hasAddressFollowupContextSignal(alternateMessage)
: false;
if (!hasPrimaryFollowupSignal && !hasAlternateFollowupSignal && !hasImplicitContinuationSignal) {
return null;
}
if (!previousAddressDebug) {
return null;
}
const sourceIntent = toNonEmptyString(previousAddressDebug.detected_intent);
let previousIntent = sourceIntent;
let followupSelectionMode = "carry_previous_intent";
if (hasImplicitContinuationSignal) {
const suggestedIntent = Array.isArray(followupOffer?.suggested_intents)
? toNonEmptyString(followupOffer.suggested_intents[0])
: null;
if (suggestedIntent) {
previousIntent = suggestedIntent;
followupSelectionMode = "switch_to_suggested_intent";
}
}
const previousAnchorType = toNonEmptyString(previousAddressDebug.anchor_type);
const previousAnchor = toNonEmptyString(previousAddressDebug.anchor_value_resolved) ??
toNonEmptyString(previousAddressDebug.anchor_value_raw) ??
readAddressFilterString(previousAddressDebug, "counterparty") ??
readAddressFilterString(previousAddressDebug, "account") ??
readAddressFilterString(previousAddressDebug, "contract");
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;
}
}
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
},
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 targetIntent = toNonEmptyString(carryoverMeta?.previousAddressIntent) ?? null;
const selectionMode = toNonEmptyString(carryoverMeta?.followupSelectionMode) ?? null;
const hasImplicitContinuationSignal = Boolean(carryoverMeta?.hasImplicitContinuationSignal);
const rewrittenByPredecompose = compactWhitespace(sourceMessage.toLowerCase()) !== compactWhitespace(canonicalMessage.toLowerCase());
const hasExplicitIntent = Boolean(toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent));
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");
}
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 attachAddressPredecomposeContract(meta, sourceMessage) {
const canonicalMessage = toNonEmptyString(meta?.effectiveMessage) ?? String(sourceMessage ?? "");
return {
...meta,
predecomposeContract: (0, predecomposeContract_1.buildAddressLlmPredecomposeContractV1)({
sourceMessage: String(sourceMessage ?? ""),
canonicalMessage
})
};
}
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 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 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);
}
const counterpartyAnchorSubstitutedByCandidate = sameIntentForAnchorSafety &&
sourceAnchorQuality.anchorType === "counterparty" &&
candidateAnchorQuality.anchorType === "counterparty" &&
sourceAnchorQuality.quality >= 2 &&
candidateAnchorQuality.quality >= 2 &&
Boolean(sourceAnchorQuality.anchorValue) &&
Boolean(candidateAnchorQuality.anchorValue) &&
hasCounterpartyAnchorSubstitution(sourceAnchorQuality.anchorValue ?? "", candidateAnchorQuality.anchorValue ?? "");
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);
}
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 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 modeDetection = (0, addressQueryClassifier_1.detectAddressQuestionMode)(repairedInputMessage || addressInputMessage);
const hasClassifierSignal = modeDetection.mode === "address_query";
const llmContractMode = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode);
const llmContractModeConfidence = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.mode_confidence);
const llmContractIntent = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent);
const llmContractIntentConfidence = toNonEmptyString(llmPreDecomposeMeta?.predecomposeContract?.intent_confidence);
const hasLlmCanonicalSignal = Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
llmContractMode === "address_query" &&
llmContractModeConfidence !== "low" &&
llmContractIntent !== null &&
llmContractIntent !== "unknown" &&
llmContractIntentConfidence !== "low";
const hasLlmCanonicalDataSignal = Boolean(llmPreDecomposeMeta?.llmCanonicalCandidateDetected) &&
Boolean(llmPreDecomposeMeta?.applied) &&
llmContractMode === "address_query" &&
hasStrongDataIntentSignal(repairedInputMessage);
const hasLexicalAddressSignal = isAddressLlmPreDecomposeCandidate(addressInputMessage) ||
isAddressLlmPreDecomposeCandidate(repairedInputMessage) ||
hasAccountingSignal(addressInputMessage) ||
hasAccountingSignal(repairedInputMessage);
const hasUnsupportedLowConfidencePredecomposeSignal = llmContractMode === "unsupported" &&
(llmContractModeConfidence === "low" || llmContractModeConfidence === "medium") &&
llmContractIntent === "unknown";
const hasAnyAddressSignal = hasClassifierSignal || hasLlmCanonicalSignal || hasLlmCanonicalDataSignal || hasLexicalAddressSignal;
const strongDataSignalFromRawMessage = hasStrongDataIntentSignal(rawMessageForGate) ||
hasAccountingSignal(rawMessageForGate) ||
hasSameDateAccountFollowupSignalForPredecompose(rawMessageForGate);
const strongDataSignalFromEffectiveMessage = hasStrongDataIntentSignal(repairedInputMessage) ||
hasAccountingSignal(repairedInputMessage) ||
hasDataRetrievalRequestSignal(repairedInputMessage);
if (hasUnsupportedLowConfidencePredecomposeSignal && !followupContext &&
!hasAnyAddressSignal &&
!strongDataSignalFromRawMessage &&
!strongDataSignalFromEffectiveMessage) {
return {
runAddressLane: false,
decision: "skip_address_lane",
reason: "llm_predecompose_unsupported_mode"
};
}
const hasMessageSignal = hasAnyAddressSignal;
if (hasMessageSignal) {
return {
runAddressLane: true,
decision: "run_address_lane",
reason: hasClassifierSignal
? "address_mode_classifier_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 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 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 modeSample = repairedEffectiveAddressUserMessage || effectiveAddressUserMessage;
const modeDetection = (0, addressQueryClassifier_1.detectAddressQuestionMode)(modeSample);
const intentResolution = (0, addressIntentResolver_1.resolveAddressIntent)(modeSample);
const strongDataSignal = hasStrongDataIntentSignal(rawUserMessage) ||
hasStrongDataIntentSignal(repairedRawUserMessage) ||
hasStrongDataIntentSignal(effectiveAddressUserMessage) ||
hasStrongDataIntentSignal(repairedEffectiveAddressUserMessage) ||
hasAccountingSignal(rawUserMessage) ||
hasAccountingSignal(repairedRawUserMessage) ||
hasAccountingSignal(effectiveAddressUserMessage) ||
hasAccountingSignal(repairedEffectiveAddressUserMessage) ||
hasDataRetrievalRequestSignal(rawUserMessage) ||
hasDataRetrievalRequestSignal(repairedRawUserMessage);
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"
}
}
};
}
const baseToolGate = resolveAddressToolGateDecision(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage);
const unsupportedAddressIntentFallbackToDeep = Boolean(!followupContext &&
baseToolGate?.runAddressLane &&
modeDetection.mode !== "address_query" &&
intentResolution.intent === "unknown" &&
strongDataSignal);
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";
}
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"
};
}
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,
followup_context_detected: Boolean(followupContext),
unsupported_address_intent_fallback_to_deep: unsupportedAddressIntentFallbackToDeep,
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|организац|компан|контор|фирм)/i.test(lower);
}
function hasDataRetrievalRequestSignal(text) {
const lower = compactWhitespace(String(text ?? "").toLowerCase());
if (!lower) {
return false;
}
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|\bwhich\b|\bwho\b)/i.test(lower);
if (!hasExplicitRetrievalAction && !hasInterrogativeRetrievalAction) {
return false;
}
const hasRetrievalObject = /(1с|база|док|документ|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|период|месяц|год|инн|bank|counterparty|contract|document|account|balance|ledger|posting|организац|компан|контор|фирм|возраст|дата\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 lower = compactWhitespace(String(text ?? "").toLowerCase()).replace(/ё/g, "е");
if (!lower) {
return false;
}
return /(?:убьют|убьют|убью|убить|убиют|угрож|опасн|омон|полици|насили|заставля|принужда|шантаж)/i.test(lower);
}
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 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 (/^(ага|угу|ок|окей|ясно|понял|поняла|принято|спасибо|благодарю|супер|класс|норм|го|давай|погнали|привет|хай|йо|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) {
const knownOrganizations = extractKnownOrganizationsFromHistory(items);
const selectedOrganization = resolveOrganizationSelectionFromMessage(userMessage, knownOrganizations);
const lastActiveOrganization = findLastAssistantActiveOrganization(items);
const activeOrganization = selectedOrganization ?? normalizeOrganizationScopeValue(lastActiveOrganization);
return {
knownOrganizations,
selectedOrganization,
activeOrganization
};
}
function mergeFollowupContextWithOrganizationScope(followupContext, organization) {
const normalizedOrganization = normalizeOrganizationScopeValue(organization);
const base = followupContext && typeof followupContext === "object" ? { ...followupContext } : {};
if (!normalizedOrganization) {
return followupContext && typeof followupContext === "object" ? base : null;
}
const previousFilters = base.previous_filters && typeof base.previous_filters === "object"
? { ...base.previous_filters }
: {};
if (!toNonEmptyString(previousFilters.organization)) {
previousFilters.organization = normalizedOrganization;
}
base.previous_filters = previousFilters;
return base;
}
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А-Яа-яЁё]/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 (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: "chat",
reason: "predecompose_unsupported_mode"
};
}
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 { session, sessionId, userMessage, runtimeAnalysisContext, userItem } = (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)({
payload,
ensureSession: (targetSessionId) => this.sessions.ensureSession(targetSessionId),
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
compactWhitespace,
repairAddressMojibake,
resolveRuntimeAnalysisContext,
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
nowIso: () => new Date().toISOString()
});
const sessionOrganizationScope = resolveSessionOrganizationScopeContext(userMessage, session.items);
const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => (0, assistantAddressLaneResponseAttemptRuntimeAdapter_1.runAssistantAddressLaneResponseAttemptRuntime)({
sessionId,
userMessage,
effectiveAddressUserMessage,
addressLane,
carryoverMeta,
llmPreDecomposeMeta,
knownOrganizations: sessionOrganizationScope.knownOrganizations,
activeOrganization: sessionOrganizationScope.activeOrganization,
sanitizeOutgoingAssistantText,
buildAddressDebugPayload,
buildAddressFollowupOffer,
mergeKnownOrganizations,
toNonEmptyString,
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
cloneConversation: (items) => cloneItems(items),
logEvent: (payload) => (0, log_1.logJson)(payload),
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`
});
const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => (0, assistantLivingChatAttemptRuntimeAdapter_1.runAssistantLivingChatAttemptRuntime)({
sessionId,
userMessage,
sessionItems: session.items,
modeDecision,
sessionScope: {
knownOrganizations: sessionOrganizationScope.knownOrganizations,
selectedOrganization: sessionOrganizationScope.selectedOrganization,
activeOrganization: sessionOrganizationScope.activeOrganization
},
addressRuntimeMeta,
traceIdFactory: () => `chat-${(0, nanoid_1.nanoid)(10)}`,
toNonEmptyString,
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,
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
cloneConversation: (items) => cloneItems(items),
logEvent: (payload) => (0, log_1.logJson)(payload),
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
nowIso: () => new Date().toISOString(),
payload,
chatClient: this.chatClient,
loadAssistantCanonExcerpt: assistantCanon_1.loadAssistantCanonExcerpt,
sanitizeOutgoingAssistantText,
defaultModel: config_1.DEFAULT_MODEL,
defaultBaseUrl: config_1.DEFAULT_OPENAI_BASE_URL,
defaultApiKey: process.env.OPENAI_API_KEY ?? ""
});
let addressRuntimeMetaForDeep = null;
const runAddressLaneAttempt = async (messageUsed, carryMeta, analysisDateHint) => (0, assistantAddressLaneAttemptRuntimeAdapter_1.runAssistantAddressLaneAttemptRuntime)({
messageUsed,
carryMeta: carryMeta ?? null,
analysisDateHint: analysisDateHint ?? null,
activeOrganization: sessionOrganizationScope.activeOrganization,
mergeFollowupContextWithOrganizationScope,
runAddressQueryTryHandle: (laneMessageUsed, options) => this.addressQueryService.tryHandle(laneMessageUsed, options)
});
const addressRuntime = await (0, assistantAddressRuntimeAdapter_1.runAssistantAddressRuntime)({
featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1,
sessionId,
userMessage,
sessionItems: session.items,
llmProvider: payload?.llmProvider,
useMock: Boolean(payload.useMock),
featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1,
runAddressLlmPreDecompose: async () => runAddressLlmPreDecompose(this.normalizerService, payload, userMessage),
buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1,
sanitizeAddressMessageForFallback,
toNonEmptyString,
resolveAddressFollowupCarryoverContext,
resolveAssistantOrchestrationDecision,
buildAddressDialogContinuationContractV2,
runtimeAnalysisContextAsOfDate: runtimeAnalysisContext.as_of_date,
payloadContextPeriodHint: payload?.context?.period_hint,
compactWhitespace,
runAddressLaneAttempt,
isRetryableAddressLimitedResult,
finalizeAddressLaneResponse,
tryHandleLivingChat: (modeDecision, runtimeMeta) => tryHandleLivingChat(modeDecision, runtimeMeta),
logEvent: (payload) => (0, log_1.logJson)(payload),
nowIso: () => new Date().toISOString()
});
addressRuntimeMetaForDeep = addressRuntime.addressRuntimeMetaForDeep;
if (addressRuntime.handled && addressRuntime.response) {
return addressRuntime.response;
}
const normalizationRuntime = await (0, assistantDeepTurnNormalizationRuntimeAdapter_1.buildAssistantDeepTurnNormalizationRuntime)({
userMessage,
payload,
featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1,
featureStateFollowupBindingV1: config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1,
sessionInvestigationState: session.investigation_state,
buildFollowupStateBinding,
normalize: (normalizePayload) => this.normalizerService.normalize(normalizePayload)
});
const followupBinding = normalizationRuntime.followupBinding;
const normalized = normalizationRuntime.normalized;
const deepTurnAnalysisRuntime = await (0, assistantDeepTurnAnalysisAttemptRuntimeAdapter_1.runAssistantDeepTurnAnalysisAttemptRuntime)({
userMessage,
normalizedPayload: normalized.normalized,
routeSummary: normalized.route_hint_summary,
runtimeAnalysisContext,
followupUsage: followupBinding.usage,
investigationState: session.investigation_state,
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,
resolveBusinessScopeAlignment,
inferP0DomainFromMessage,
resolveBusinessScopeFromLiveContext,
extractRequirements,
toExecutionPlan,
enforceRbpLiveRoutePlan,
enforceFaLiveRoutePlan,
executeRouteRuntime: (route, fragmentText, options) => this.dataLayer.executeRouteRuntime(route, fragmentText, options),
mapNoRouteReason,
buildSkippedResult,
evaluateCoverage,
checkGrounding,
collectRbpLiveRouteAudit,
collectFaLiveRouteAudit,
hasExplicitPeriodAnchor: (normalizedPayload) => hasExplicitPeriodAnchorFromNormalized(normalizedPayload)
});
const companyAnchors = deepTurnAnalysisRuntime.companyAnchors;
const temporalGuard = deepTurnAnalysisRuntime.temporalGuard;
const claimAnchorAudit = deepTurnAnalysisRuntime.claimAnchorAudit;
const businessScopeResolution = deepTurnAnalysisRuntime.businessScopeResolution;
const resolvedRouteSummary = deepTurnAnalysisRuntime.resolvedRouteSummary;
const requirementExtraction = deepTurnAnalysisRuntime.requirementExtraction;
const executionPlan = deepTurnAnalysisRuntime.executionPlan;
const retrievalCalls = deepTurnAnalysisRuntime.retrievalCalls;
const retrievalResultsRaw = deepTurnAnalysisRuntime.retrievalResultsRaw;
const retrievalResults = deepTurnAnalysisRuntime.retrievalResults;
const polarityGuardResult = deepTurnAnalysisRuntime.polarityGuardResult;
const targetedEvidenceResult = deepTurnAnalysisRuntime.targetedEvidenceResult;
const evidenceGateResult = deepTurnAnalysisRuntime.evidenceGateResult;
const rbpLiveRouteAudit = deepTurnAnalysisRuntime.rbpLiveRouteAudit;
const faLiveRouteAudit = deepTurnAnalysisRuntime.faLiveRouteAudit;
const coverageEvaluation = deepTurnAnalysisRuntime.coverageEvaluation;
const groundedAnswerEligibilityGuard = deepTurnAnalysisRuntime.groundedAnswerEligibilityGuard;
const groundingCheck = deepTurnAnalysisRuntime.groundingCheck;
const questionTypeClass = deepTurnAnalysisRuntime.questionTypeClass;
const composition = deepTurnAnalysisRuntime.composition;
const deepTurnResponseRuntime = (0, assistantDeepTurnResponseRuntimeAdapter_1.runAssistantDeepTurnResponseRuntime)({
featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1,
featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11,
featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11,
sessionId,
questionId: userItem.message_id,
userMessage,
normalized: {
trace_id: normalized.trace_id,
prompt_version: normalized.prompt_version,
schema_version: normalized.schema_version,
normalized: normalized.normalized
},
normalizedQuestion: followupBinding.normalizedQuestion,
routeSummary: resolvedRouteSummary,
executionPlan,
requirementExtractionRequirements: requirementExtraction.requirements,
coverageEvaluationRequirements: coverageEvaluation.requirements,
coverageReport: coverageEvaluation.coverage,
groundingCheck,
retrievalCalls,
retrievalResultsRaw,
retrievalResults,
questionTypeClass,
companyAnchors,
runtimeAnalysisContext,
businessScopeResolution,
temporalGuard,
polarityAudit: polarityGuardResult.audit,
claimAnchorAudit,
targetedEvidenceAudit: targetedEvidenceResult.audit,
evidenceAdmissibilityGateAudit: evidenceGateResult.audit,
rbpLiveRouteAudit,
faLiveRouteAudit,
groundedAnswerEligibilityGuard,
followupStateUsage: followupBinding.usage,
followupApplied: Boolean(followupBinding.usage?.applied),
composition,
previousInvestigationState: session.investigation_state,
addressRuntimeMetaForDeep,
extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload),
buildDebugRoutes: (routeSummary) => toDebugRoutes(routeSummary),
extractExecutionState: (normalizedPayload) => extractExecutionState(normalizedPayload),
sanitizeReply: (value, fallback) => sanitizeOutgoingAssistantText(value, fallback),
persistInvestigationState: (targetSessionId, snapshot) => this.sessions.setInvestigationState(targetSessionId, snapshot),
messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`,
appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item),
getSession: (targetSessionId) => this.sessions.getSession(targetSessionId),
persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState),
cloneConversation: (items) => cloneItems(items),
logEvent: (payload) => (0, log_1.logJson)(payload),
runPackagingRuntime: (input) => (0, assistantDeepTurnPackagingRuntimeAdapter_1.runAssistantDeepTurnPackagingRuntime)(input),
runFinalizeDeepTurn: (input) => (0, assistantDeepTurnFinalizeRuntimeAdapter_1.finalizeAssistantDeepTurn)(input)
});
return deepTurnResponseRuntime.response;
}
}
exports.AssistantService = AssistantService;