// @ts-nocheck export interface ResolveAssistantRouteMemorySignalsInput { rawUserMessage?: unknown; repairedRawUserMessage?: unknown; effectiveAddressUserMessage?: unknown; repairedEffectiveAddressUserMessage?: unknown; dataScopeMetaQuery?: boolean; capabilityMetaQuery?: boolean; dataRetrievalSignal?: boolean; strongDataSignal?: boolean; aggregateBusinessAnalyticsSignal?: boolean; lastGroundedAddressDebug?: unknown; hasPriorAddressDebug?: boolean; } export interface AssistantRouteMemorySignals { contextualHistoricalCapabilityFollowupDetected: boolean; contextualMemoryRecapFollowupDetected: boolean; } export interface ResolveAssistantLivingChatMemoryContextInput { modeDecisionReason?: unknown; sessionItems?: unknown[]; } export interface AssistantLivingChatMemoryContext { contextualInventoryHistoryCapabilityFollowup: boolean; contextualMemoryRecapFollowup: boolean; lastGroundedInventoryAddressDebug: Record | null; lastMemoryAddressDebug: Record | null; } export interface AssistantMemoryRecapPolicyDeps { hasHistoricalCapabilityFollowupSignal: (text: unknown) => boolean; hasConversationMemoryRecallFollowupSignal: (text: unknown) => boolean; isGroundedInventoryContextDebug: (debug: unknown) => boolean; } function formatIsoDateForReply(value: unknown): string | null { const source = String(value ?? "").trim(); const match = source.match(/^(\d{4})-(\d{2})-(\d{2})$/); if (!match) { return null; } return `${match[3]}.${match[2]}.${match[1]}`; } function collectMessageSamples(input: ResolveAssistantRouteMemorySignalsInput): string[] { 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: string[], detector: (text: unknown) => boolean ): boolean { return samples.some((sample) => detector(sample)); } function hasExplicitRecapPromptSignal(samples: string[]): boolean { return samples.some((sample) => /(?:что\s+мы\s+.*(?:обсуждали|выяснили)|что\s+уже\s+выяснили|что\s+уже\s+поняли|напомни\s+что\s+мы)/iu.test( sample ) ); } function findLastGroundedInventoryAddressDebug(items: unknown[]): Record | null { if (!Array.isArray(items)) { return null; } for (let index = items.length - 1; index >= 0; index -= 1) { const item = items[index] as { role?: string; debug?: Record } | null; if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") { continue; } const debug = item.debug; const answerGroundingCheck = debug.answer_grounding_check && typeof debug.answer_grounding_check === "object" ? (debug.answer_grounding_check as Record) : null; const groundingStatus = String(answerGroundingCheck?.status ?? ""); const detectedIntent = String(debug.detected_intent ?? ""); const capabilityId = String(debug.capability_id ?? ""); const rootFrameContext = debug.address_root_frame_context && typeof debug.address_root_frame_context === "object" ? (debug.address_root_frame_context as Record) : null; const rootIntent = String(rootFrameContext?.root_intent ?? ""); const isInventoryContext = detectedIntent === "inventory_on_hand_as_of_date" || capabilityId === "confirmed_inventory_on_hand_as_of_date" || rootIntent === "inventory_on_hand_as_of_date"; if (groundingStatus === "grounded" && isInventoryContext) { return debug; } } return null; } function findLastAddressDebugWithItem(items: unknown[]): Record | null { if (!Array.isArray(items)) { return null; } for (let index = items.length - 1; index >= 0; index -= 1) { const item = items[index] as { role?: string; debug?: Record } | null; if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") { continue; } const debug = item.debug; if (String(debug.execution_lane ?? "") !== "address_query") { continue; } const extractedFilters = debug.extracted_filters && typeof debug.extracted_filters === "object" ? (debug.extracted_filters as Record) : null; const itemLabel = String(extractedFilters?.item ?? "").trim() || (String(debug.anchor_type ?? "") === "item" ? String(debug.anchor_value_resolved ?? debug.anchor_value_raw ?? "").trim() : ""); if (itemLabel) { return debug; } } return null; } function findLastAddressDebug(items: unknown[]): Record | null { if (!Array.isArray(items)) { return null; } for (let index = items.length - 1; index >= 0; index -= 1) { const item = items[index] as { role?: string; debug?: Record } | null; if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") { continue; } if (String(item.debug.execution_lane ?? "") === "address_query") { return item.debug; } } return null; } export function buildInventoryHistoryCapabilityFollowupReply(input: { organization: string | null; addressDebug: Record | null; toNonEmptyString: (value: unknown) => string | null; }): string { const rootFrameContext = input.addressDebug?.address_root_frame_context && typeof input.addressDebug.address_root_frame_context === "object" ? (input.addressDebug.address_root_frame_context as Record) : null; const extractedFilters = input.addressDebug?.extracted_filters && typeof input.addressDebug.extracted_filters === "object" ? (input.addressDebug.extracted_filters as Record) : null; const organization = input.organization ?? input.toNonEmptyString(rootFrameContext?.organization) ?? input.toNonEmptyString(extractedFilters?.organization); const lastAsOfDate = formatIsoDateForReply(rootFrameContext?.as_of_date) ?? formatIsoDateForReply(extractedFilters?.as_of_date); const organizationPart = organization ? ` по компании «${organization}»` : ""; const referenceLine = lastAsOfDate ? `Да, могу. Сейчас мы уже смотрели складской срез${organizationPart} на ${lastAsOfDate}.` : `Да, могу показать исторические данные${organizationPart} в этом же складском контуре.`; return [ referenceLine, `Могу показать исторические остатки${organizationPart} за нужный месяц, дату или год.`, "Например:", "- `на март 2020`", "- `на июнь 2016`", "- `за 2017 год`", "- `сравни июнь 2016 с текущим срезом`", "Если хочешь, сразу покажу нужный исторический период." ].join("\n"); } export function buildAddressMemoryRecapReply(input: { organization: string | null; addressDebug: Record | null; toNonEmptyString: (value: unknown) => string | null; }): string { const extractedFilters = input.addressDebug?.extracted_filters && typeof input.addressDebug.extracted_filters === "object" ? (input.addressDebug.extracted_filters as Record) : null; const rootFrameContext = input.addressDebug?.address_root_frame_context && typeof input.addressDebug.address_root_frame_context === "object" ? (input.addressDebug.address_root_frame_context as Record) : null; const item = input.toNonEmptyString(extractedFilters?.item) ?? (String(input.addressDebug?.anchor_type ?? "") === "item" ? input.toNonEmptyString(input.addressDebug?.anchor_value_resolved) ?? input.toNonEmptyString(input.addressDebug?.anchor_value_raw) : null); const organization = input.organization ?? input.toNonEmptyString(extractedFilters?.organization) ?? input.toNonEmptyString(rootFrameContext?.organization); const scopedDate = formatIsoDateForReply(extractedFilters?.as_of_date) ?? formatIsoDateForReply(rootFrameContext?.as_of_date) ?? formatIsoDateForReply(extractedFilters?.period_to); if (item) { const datePart = scopedDate ? ` в срезе на ${scopedDate}` : ""; const organizationPart = organization ? ` по компании «${organization}»` : ""; return [ `Да, помню. Мы обсуждали позицию «${item}»${organizationPart}${datePart}.`, "Могу продолжить по ней без переписывания сущности: кто поставил, когда купили, по каким документам или кому продали." ].join(" "); } if (organization || scopedDate) { const organizationPart = organization ? ` по компании «${organization}»` : ""; const datePart = scopedDate ? ` на ${scopedDate}` : ""; return [ `Да, помню. Мы уже смотрели адресный контур${organizationPart}${datePart}.`, "Могу кратко напомнить контекст или сразу продолжить следующий шаг по этому же сценарию." ].join(" "); } return "Да, помню предыдущий адресный контур. Могу кратко напомнить, что мы уже подтвердили, или сразу продолжить следующий шаг."; } export function resolveAssistantLivingChatMemoryContext( input: ResolveAssistantLivingChatMemoryContextInput ): AssistantLivingChatMemoryContext { const contextualInventoryHistoryCapabilityFollowup = String(input.modeDecisionReason ?? "") === "inventory_history_capability_followup_detected"; const contextualMemoryRecapFollowup = String(input.modeDecisionReason ?? "") === "memory_recap_followup_detected"; const sessionItems = Array.isArray(input.sessionItems) ? input.sessionItems : []; return { contextualInventoryHistoryCapabilityFollowup, contextualMemoryRecapFollowup, lastGroundedInventoryAddressDebug: contextualInventoryHistoryCapabilityFollowup ? findLastGroundedInventoryAddressDebug(sessionItems) : null, lastMemoryAddressDebug: contextualMemoryRecapFollowup ? findLastAddressDebugWithItem(sessionItems) ?? findLastAddressDebug(sessionItems) : null }; } export function createAssistantMemoryRecapPolicy( deps: AssistantMemoryRecapPolicyDeps ) { function resolveRouteMemorySignals( input: ResolveAssistantRouteMemorySignalsInput ): AssistantRouteMemorySignals { const samples = collectMessageSamples(input); 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(input.lastGroundedAddressDebug) ), contextualMemoryRecapFollowupDetected: Boolean( !input.dataScopeMetaQuery && !input.capabilityMetaQuery && !input.aggregateBusinessAnalyticsSignal && memoryRecapSignal && (explicitRecapPromptSignal || (!input.dataRetrievalSignal && !input.strongDataSignal)) && input.hasPriorAddressDebug ) }; } return { resolveRouteMemorySignals }; }