399 lines
24 KiB
JavaScript
399 lines
24 KiB
JavaScript
"use strict";
|
||
// @ts-nocheck
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.buildInventoryHistoryCapabilityFollowupReply = buildInventoryHistoryCapabilityFollowupReply;
|
||
exports.buildAddressMemoryRecapReply = buildAddressMemoryRecapReply;
|
||
exports.buildBroadBusinessEvaluationReply = buildBroadBusinessEvaluationReply;
|
||
exports.buildSelectedObjectAnswerInspectionReply = buildSelectedObjectAnswerInspectionReply;
|
||
exports.resolveAssistantLivingChatMemoryContext = resolveAssistantLivingChatMemoryContext;
|
||
exports.createAssistantMemoryRecapPolicy = createAssistantMemoryRecapPolicy;
|
||
const assistantContinuityPolicy_1 = require("./assistantContinuityPolicy");
|
||
function toNonEmptyString(value) {
|
||
if (value === null || value === undefined) {
|
||
return null;
|
||
}
|
||
const text = String(value).trim();
|
||
return text.length > 0 ? text : null;
|
||
}
|
||
function toRecordObject(value) {
|
||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||
return null;
|
||
}
|
||
return value;
|
||
}
|
||
function ensureSentence(value) {
|
||
const text = String(value ?? "").trim();
|
||
if (!text) {
|
||
return "";
|
||
}
|
||
return /[.!?]$/.test(text) ? text : `${text}.`;
|
||
}
|
||
function periodPartForRecap(scopedDate) {
|
||
if (!scopedDate) {
|
||
return "";
|
||
}
|
||
return /^\d{2}\.\d{2}\.\d{4}$/.test(scopedDate) ? ` на ${scopedDate}` : ` за период ${scopedDate}`;
|
||
}
|
||
function buildDiscoveryRecapFactLine(input) {
|
||
if (!input.debug || !input.counterparty) {
|
||
return null;
|
||
}
|
||
const pilotScope = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryPilotScope)(input.debug, toNonEmptyString);
|
||
const discoveryEntry = toRecordObject(input.debug.assistant_mcp_discovery_entry_point_v1);
|
||
const bridge = toRecordObject(discoveryEntry?.bridge);
|
||
const pilot = toRecordObject(bridge?.pilot);
|
||
const periodPart = periodPartForRecap(input.scopedDate);
|
||
if (pilotScope === "counterparty_lifecycle_query_documents_v1") {
|
||
const activityPeriod = toRecordObject(pilot?.derived_activity_period);
|
||
const duration = toNonEmptyString(activityPeriod?.duration_human_ru);
|
||
return duration
|
||
? `смотрели подтвержденную активность по контрагенту «${input.counterparty}»${periodPart} и оценили период взаимодействия примерно как ${duration}`
|
||
: `смотрели подтвержденную активность по контрагенту «${input.counterparty}»${periodPart}`;
|
||
}
|
||
if (pilotScope === "counterparty_supplier_payout_query_movements_v1") {
|
||
const flow = toRecordObject(pilot?.derived_value_flow);
|
||
const amount = toNonEmptyString(flow?.total_amount_human_ru);
|
||
return amount
|
||
? `считали исходящие платежи/списания по контрагенту «${input.counterparty}»${periodPart}: ${amount}`
|
||
: `считали исходящие платежи/списания по контрагенту «${input.counterparty}»${periodPart}`;
|
||
}
|
||
if (pilotScope === "counterparty_value_flow_query_movements_v1") {
|
||
const flow = toRecordObject(pilot?.derived_value_flow);
|
||
const amount = toNonEmptyString(flow?.total_amount_human_ru);
|
||
return amount
|
||
? `смотрели денежный поток по контрагенту «${input.counterparty}»${periodPart}: ${amount}`
|
||
: `смотрели денежный поток по контрагенту «${input.counterparty}»${periodPart}`;
|
||
}
|
||
if (pilotScope === "counterparty_bidirectional_value_flow_query_movements_v1") {
|
||
const flow = toRecordObject(pilot?.derived_bidirectional_value_flow);
|
||
const incoming = toRecordObject(flow?.incoming_customer_revenue);
|
||
const outgoing = toRecordObject(flow?.outgoing_supplier_payout);
|
||
const incomingAmount = toNonEmptyString(incoming?.total_amount_human_ru);
|
||
const outgoingAmount = toNonEmptyString(outgoing?.total_amount_human_ru);
|
||
const netAmount = toNonEmptyString(flow?.net_amount_human_ru);
|
||
if (incomingAmount && outgoingAmount && netAmount) {
|
||
return `считали нетто по деньгам с контрагентом «${input.counterparty}»${periodPart}: получили ${incomingAmount}, заплатили ${outgoingAmount}, расчетное нетто ${netAmount}`;
|
||
}
|
||
return `считали нетто по деньгам с контрагентом «${input.counterparty}»${periodPart}`;
|
||
}
|
||
return null;
|
||
}
|
||
function collectMessageSamples(input) {
|
||
const values = [
|
||
input.rawUserMessage,
|
||
input.repairedRawUserMessage,
|
||
input.effectiveAddressUserMessage,
|
||
input.repairedEffectiveAddressUserMessage
|
||
];
|
||
return Array.from(new Set(values
|
||
.map((item) => String(item ?? "").trim())
|
||
.filter((item) => item.length > 0)));
|
||
}
|
||
function hasSignalAcrossSamples(samples, detector) {
|
||
return samples.some((sample) => detector(sample));
|
||
}
|
||
function hasExplicitRecapPromptSignal(samples) {
|
||
return samples.some((sample) => /(?:что\s+мы\s+.*(?:обсуждали|выяснили)|что\s+уже\s+выяснили|что\s+уже\s+поняли|напомни\s+что\s+мы)/iu.test(sample));
|
||
}
|
||
function buildInventoryHistoryCapabilityFollowupReply(input) {
|
||
const contextFacts = (0, assistantContinuityPolicy_1.resolveAddressDebugContextFacts)(input.addressDebug, input.toNonEmptyString);
|
||
const organization = input.organization ?? contextFacts.organization;
|
||
const lastAsOfDate = contextFacts.scopedDate;
|
||
const organizationPart = organization ? ` по компании «${organization}»` : "";
|
||
const referenceLine = lastAsOfDate
|
||
? `Да, могу. Сейчас мы уже смотрели складской срез${organizationPart} на ${lastAsOfDate}.`
|
||
: `Да, могу показать исторические данные${organizationPart} в этом же складском контуре.`;
|
||
return [
|
||
referenceLine,
|
||
`Могу показать исторические остатки${organizationPart} за нужный месяц, дату или год.`,
|
||
"Например:",
|
||
"- `на март 2020`",
|
||
"- `на июнь 2016`",
|
||
"- `за 2017 год`",
|
||
"- `сравни июнь 2016 с текущим срезом`",
|
||
"Если хочешь, сразу покажу нужный исторический период."
|
||
].join("\n");
|
||
}
|
||
function normalizeRecapIdentity(value) {
|
||
return String(value ?? "")
|
||
.trim()
|
||
.toLowerCase()
|
||
.replace(/[«»"'`]/g, "")
|
||
.replace(/\s+/g, " ");
|
||
}
|
||
function buildRecapFactLine(input) {
|
||
const detectedIntent = String(input.debug?.detected_intent ?? "");
|
||
const scopedDate = (0, assistantContinuityPolicy_1.resolveAddressDebugContextFacts)(input.debug).scopedDate;
|
||
const discoveryFact = buildDiscoveryRecapFactLine({
|
||
debug: input.debug,
|
||
counterparty: input.counterparty,
|
||
scopedDate
|
||
});
|
||
if (discoveryFact) {
|
||
return discoveryFact;
|
||
}
|
||
const itemPart = input.item ? `по позиции «${input.item}»` : null;
|
||
const counterpartyPart = input.counterparty ? `по контрагенту «${input.counterparty}»` : null;
|
||
const organizationPart = input.organization ? `по компании «${input.organization}»` : null;
|
||
const datePart = scopedDate ? ` на ${scopedDate}` : "";
|
||
if (detectedIntent === "inventory_on_hand_as_of_date") {
|
||
return `смотрели остатки${organizationPart ? ` ${organizationPart}` : ""}${datePart}`.trim();
|
||
}
|
||
if (detectedIntent === "inventory_purchase_provenance_for_item" && itemPart) {
|
||
return `разобрали, кто поставлял ${itemPart}${datePart}`.trim();
|
||
}
|
||
if (detectedIntent === "inventory_purchase_documents_for_item" && itemPart) {
|
||
return `подняли документы закупки ${itemPart}${datePart}`.trim();
|
||
}
|
||
if (detectedIntent === "inventory_sale_trace_for_item" && itemPart) {
|
||
return `разобрали, кому продавали ${itemPart}${datePart}`.trim();
|
||
}
|
||
if (detectedIntent === "inventory_purchase_to_sale_chain" && itemPart) {
|
||
return `проследили цепочку от закупки до продажи ${itemPart}${datePart}`.trim();
|
||
}
|
||
if (detectedIntent === "inventory_profitability_for_item" && itemPart) {
|
||
return `смотрели рентабельность ${itemPart}${datePart}`.trim();
|
||
}
|
||
if (detectedIntent === "inventory_aging_by_purchase_date" && itemPart) {
|
||
return `смотрели возраст остатков ${itemPart}${datePart}`.trim();
|
||
}
|
||
if (detectedIntent === "counterparty_activity_lifecycle" && organizationPart) {
|
||
return `смотрели активность в базе 1С ${organizationPart}`.trim();
|
||
}
|
||
if (detectedIntent === "list_documents_by_counterparty" && counterpartyPart) {
|
||
return `поднимали документы ${counterpartyPart}${datePart}`.trim();
|
||
}
|
||
if (detectedIntent === "list_documents_by_counterparty" && organizationPart) {
|
||
return `поднимали документы ${organizationPart}${datePart}`.trim();
|
||
}
|
||
return null;
|
||
}
|
||
function collectRecentRecapFacts(input) {
|
||
const sessionItems = Array.isArray(input.sessionItems) ? input.sessionItems : [];
|
||
if (sessionItems.length === 0) {
|
||
return [];
|
||
}
|
||
const currentItemKey = normalizeRecapIdentity(input.item);
|
||
const currentOrganizationKey = normalizeRecapIdentity(input.organization);
|
||
const facts = [];
|
||
const seen = new Set();
|
||
for (let index = sessionItems.length - 1; index >= 0; index -= 1) {
|
||
const item = sessionItems[index];
|
||
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
|
||
continue;
|
||
}
|
||
if (!(0, assistantContinuityPolicy_1.isGroundedAddressDebug)(item.debug, input.toNonEmptyString)) {
|
||
continue;
|
||
}
|
||
const debugContext = (0, assistantContinuityPolicy_1.resolveAddressDebugContextFacts)(item.debug, input.toNonEmptyString);
|
||
const debugItem = debugContext.item;
|
||
const debugOrganization = debugContext.organization;
|
||
const itemMatches = currentItemKey ? normalizeRecapIdentity(debugItem) === currentItemKey : false;
|
||
const organizationMatches = currentOrganizationKey
|
||
? normalizeRecapIdentity(debugOrganization) === currentOrganizationKey
|
||
: false;
|
||
if (currentItemKey && !itemMatches) {
|
||
continue;
|
||
}
|
||
if (!currentItemKey && currentOrganizationKey && !organizationMatches) {
|
||
continue;
|
||
}
|
||
const fact = buildRecapFactLine({
|
||
debug: item.debug,
|
||
item: debugItem,
|
||
counterparty: debugContext.counterparty,
|
||
organization: debugOrganization
|
||
});
|
||
if (!fact || seen.has(fact)) {
|
||
continue;
|
||
}
|
||
seen.add(fact);
|
||
facts.push(fact);
|
||
if (facts.length >= 3) {
|
||
break;
|
||
}
|
||
}
|
||
return facts.reverse();
|
||
}
|
||
function buildAddressMemoryRecapReply(input) {
|
||
const contextFacts = (0, assistantContinuityPolicy_1.resolveAddressDebugContextFacts)(input.addressDebug, input.toNonEmptyString);
|
||
const item = contextFacts.item;
|
||
const counterparty = contextFacts.counterparty;
|
||
const organization = input.organization ?? contextFacts.organization;
|
||
const scopedDate = contextFacts.scopedDate;
|
||
const recapFacts = collectRecentRecapFacts({
|
||
sessionItems: input.sessionItems,
|
||
item,
|
||
organization,
|
||
toNonEmptyString: input.toNonEmptyString
|
||
});
|
||
if (item) {
|
||
if (recapFacts.length > 0) {
|
||
const datePart = scopedDate ? ` в срезе на ${scopedDate}` : "";
|
||
const organizationPart = organization ? ` по компании «${organization}»` : "";
|
||
return [
|
||
`Да, помню. По позиции «${item}»${organizationPart}${datePart} мы уже выяснили:`,
|
||
...recapFacts.map((fact) => `- ${fact}.`),
|
||
"Могу сразу продолжить по ней: поставщик, закупка, документы или продажа."
|
||
].join("\n");
|
||
}
|
||
const datePart = scopedDate ? ` в срезе на ${scopedDate}` : "";
|
||
const organizationPart = organization ? ` по компании «${organization}»` : "";
|
||
return [
|
||
`Да, помню. Мы обсуждали позицию «${item}»${organizationPart}${datePart}.`,
|
||
"Могу продолжить по ней без переписывания сущности: кто поставил, когда купили, по каким документам или кому продали."
|
||
].join(" ");
|
||
}
|
||
if (counterparty) {
|
||
const organizationPart = organization ? ` по компании «${organization}»` : "";
|
||
const periodPart = periodPartForRecap(scopedDate);
|
||
if (recapFacts.length > 0) {
|
||
return [
|
||
`Да, помню. По контрагенту «${counterparty}»${organizationPart}${periodPart} мы уже выяснили:`,
|
||
...recapFacts.map((fact) => `- ${fact}.`),
|
||
"Могу сразу продолжить по нему: поступления, платежи, нетто, помесячную раскладку или границы подтверждения."
|
||
].join("\n");
|
||
}
|
||
return [
|
||
`Да, помню. Мы уже смотрели контур по контрагенту «${counterparty}»${organizationPart}${periodPart}.`,
|
||
"Могу продолжить по нему без переписывания контекста: поступления, платежи, нетто, документы или пояснение границ ответа."
|
||
].join(" ");
|
||
}
|
||
if (organization || scopedDate) {
|
||
const organizationPart = organization ? ` по компании «${organization}»` : "";
|
||
const datePart = scopedDate ? ` на ${scopedDate}` : "";
|
||
return [
|
||
`Да, помню. Мы уже смотрели адресный контур${organizationPart}${datePart}.`,
|
||
"Могу кратко напомнить контекст или сразу продолжить следующий шаг по этому же сценарию."
|
||
].join(" ");
|
||
}
|
||
return "Да, помню предыдущий адресный контур. Могу кратко напомнить, что мы уже подтвердили, или сразу продолжить следующий шаг.";
|
||
}
|
||
function buildBroadBusinessEvaluationReply(input) {
|
||
const contextFacts = (0, assistantContinuityPolicy_1.resolveAddressDebugContextFacts)(input.addressDebug, input.toNonEmptyString);
|
||
const organization = input.organization ?? contextFacts.organization;
|
||
const recapFacts = collectRecentRecapFacts({
|
||
sessionItems: input.sessionItems,
|
||
item: null,
|
||
organization,
|
||
toNonEmptyString: input.toNonEmptyString
|
||
});
|
||
const organizationPart = organization ? ` по компании «${organization}»` : "";
|
||
if (recapFacts.length > 0) {
|
||
return [
|
||
`Коротко: по тому, что мы уже подтвердили в 1С${organizationPart}, компания выглядит операционно живой, но это пока только частичная оценка бизнеса.`,
|
||
"Сейчас я опираюсь на такие подтвержденные факты:",
|
||
...recapFacts.map((fact) => `- ${ensureSentence(fact)}`),
|
||
"Это еще не полная диагностика всего бизнеса и не вывод о прибыли: я честно суммирую только те контуры, которые мы уже проверили в диалоге.",
|
||
"Если хочешь, следующим шагом могу сузить оценку до денежного потока, долгов, НДС или ключевых контрагентов."
|
||
].join("\n");
|
||
}
|
||
return [
|
||
`Коротко: по нынешнему контексту 1С${organizationPart} я вижу признаки операционной активности, но для содержательной оценки бизнеса нужно еще несколько опорных срезов.`,
|
||
"Если хочешь, я быстро доберу основу для такой оценки: денежный поток, дебиторка/кредиторка, НДС или ключевые контрагенты."
|
||
].join(" ");
|
||
}
|
||
function buildSelectedObjectAnswerInspectionReply(input) {
|
||
const contextFacts = (0, assistantContinuityPolicy_1.resolveAddressDebugContextFacts)(input.addressDebug, input.toNonEmptyString);
|
||
const itemLabel = contextFacts.item ?? "эта позиция";
|
||
const counterpartyLabel = contextFacts.counterparty;
|
||
const detectedIntent = String(input.addressDebug?.detected_intent ?? "");
|
||
const pilotScope = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryPilotScope)(input.addressDebug, input.toNonEmptyString);
|
||
const periodPart = periodPartForRecap(contextFacts.scopedDate);
|
||
if (counterpartyLabel && pilotScope === "counterparty_bidirectional_value_flow_query_movements_v1") {
|
||
return [
|
||
`Да, в предыдущем ответе речь шла о двустороннем денежном потоке с контрагентом «${counterpartyLabel}»${periodPart}.`,
|
||
"Нетто там означало разницу между тем, что получили, и тем, что заплатили по найденным строкам 1С.",
|
||
"Это расчет по проверенному периоду и подтвержденным строкам, а не заявление про весь оборот вне этого окна."
|
||
].join(" ");
|
||
}
|
||
if (counterpartyLabel && pilotScope === "counterparty_supplier_payout_query_movements_v1") {
|
||
return [
|
||
`Да, в предыдущем ответе речь шла об исходящих платежах/списаниях по контрагенту «${counterpartyLabel}»${periodPart}.`,
|
||
"Это сумма по найденным строкам 1С за проверенный период, а не обещание, что за пределами этого окна больше движений не было."
|
||
].join(" ");
|
||
}
|
||
if (counterpartyLabel && pilotScope === "counterparty_value_flow_query_movements_v1") {
|
||
return [
|
||
`Да, в предыдущем ответе речь шла о денежном потоке по контрагенту «${counterpartyLabel}»${periodPart}.`,
|
||
"Это расчет по найденным движениям 1С за проверенный период, а не безусловный итог по всем временам."
|
||
].join(" ");
|
||
}
|
||
if (counterpartyLabel && pilotScope === "counterparty_lifecycle_query_documents_v1") {
|
||
return [
|
||
`Да, в предыдущем ответе речь шла об активности контрагента «${counterpartyLabel}»${periodPart}.`,
|
||
"Это оценка по подтвержденным строкам 1С, а не юридически подтвержденная дата регистрации."
|
||
].join(" ");
|
||
}
|
||
if (detectedIntent === "inventory_sale_trace_for_item") {
|
||
return [
|
||
`Да, если так прозвучало, это ошибка чтения ответа. «${itemLabel}» здесь не контрагент, а сама позиция, по которой мы смотрели продажу.`,
|
||
"В предыдущем ответе я показывал документы выбытия по этой позиции. Покупатель в доступных данных отдельно не выделен, поэтому назвать контрагента-покупателя я там не мог.",
|
||
"Если хочешь, следующим шагом могу отдельно проверить, можно ли вытащить покупателя по связанным документам реализации."
|
||
].join(" ");
|
||
}
|
||
if (detectedIntent === "inventory_purchase_provenance_for_item" ||
|
||
detectedIntent === "inventory_purchase_documents_for_item") {
|
||
return [
|
||
`Да, если так прозвучало, это ошибка чтения ответа. «${itemLabel}» здесь не контрагент, а сама позиция или номенклатура.`,
|
||
"В предыдущем ответе речь шла о закупке этой позиции: я перечислял поставщиков или закупочные документы по ней, а не называл саму позицию контрагентом."
|
||
].join(" ");
|
||
}
|
||
return [
|
||
`Да, если так прозвучало, это ошибка чтения ответа. «${itemLabel}» здесь не контрагент, а выбранный объект разбора.`,
|
||
"Я сейчас уточняю именно смысл предыдущего grounded-ответа по этой позиции, а не запускаю новый адресный поиск."
|
||
].join(" ");
|
||
}
|
||
function resolveAssistantLivingChatMemoryContext(input) {
|
||
const contextualInventoryHistoryCapabilityFollowup = String(input.modeDecisionReason ?? "") === "inventory_history_capability_followup_detected";
|
||
const contextualMemoryRecapFollowup = String(input.modeDecisionReason ?? "") === "memory_recap_followup_detected";
|
||
const contextualAnswerInspectionFollowup = String(input.modeDecisionReason ?? "") === "answer_inspection_followup_detected";
|
||
const continuity = (0, assistantContinuityPolicy_1.resolveAssistantContinuitySnapshot)({
|
||
sessionItems: input.sessionItems,
|
||
toNonEmptyString
|
||
});
|
||
return {
|
||
contextualInventoryHistoryCapabilityFollowup,
|
||
contextualMemoryRecapFollowup,
|
||
contextualAnswerInspectionFollowup,
|
||
lastGroundedInventoryAddressDebug: contextualInventoryHistoryCapabilityFollowup
|
||
? continuity.lastGroundedInventoryAddressDebug
|
||
: null,
|
||
lastMemoryAddressDebug: contextualMemoryRecapFollowup
|
||
? continuity.lastGroundedItemAddressDebug ?? continuity.lastGroundedAddressDebug
|
||
: null,
|
||
lastAnswerInspectionAddressDebug: contextualAnswerInspectionFollowup
|
||
? continuity.lastGroundedItemAddressDebug ?? continuity.lastGroundedAddressDebug
|
||
: null
|
||
};
|
||
}
|
||
function createAssistantMemoryRecapPolicy(deps) {
|
||
function resolveRouteMemorySignals(input) {
|
||
const samples = collectMessageSamples(input);
|
||
const continuity = (0, assistantContinuityPolicy_1.resolveAssistantContinuitySnapshot)({
|
||
sessionItems: input.sessionItems,
|
||
toNonEmptyString
|
||
});
|
||
const groundedInventoryContext = continuity.lastGroundedInventoryAddressDebug ?? input.lastGroundedAddressDebug;
|
||
const historicalCapabilitySignal = hasSignalAcrossSamples(samples, deps.hasHistoricalCapabilityFollowupSignal);
|
||
const memoryRecapSignal = hasSignalAcrossSamples(samples, deps.hasConversationMemoryRecallFollowupSignal);
|
||
const explicitRecapPromptSignal = hasExplicitRecapPromptSignal(samples);
|
||
return {
|
||
contextualHistoricalCapabilityFollowupDetected: Boolean(input.capabilityMetaQuery &&
|
||
!input.dataScopeMetaQuery &&
|
||
!input.dataRetrievalSignal &&
|
||
historicalCapabilitySignal &&
|
||
deps.isGroundedInventoryContextDebug(groundedInventoryContext)),
|
||
contextualMemoryRecapFollowupDetected: Boolean(!input.dataScopeMetaQuery &&
|
||
!input.capabilityMetaQuery &&
|
||
!input.aggregateBusinessAnalyticsSignal &&
|
||
memoryRecapSignal &&
|
||
(explicitRecapPromptSignal || (!input.dataRetrievalSignal && !input.strongDataSignal)) &&
|
||
continuity.hasGroundedAddressContext)
|
||
};
|
||
}
|
||
return {
|
||
resolveRouteMemorySignals
|
||
};
|
||
}
|