import { describe, expect, it } from "vitest"; import { createAssistantLivingModePolicy } from "../src/services/assistantLivingModePolicy"; function buildPolicy(options?: { repairAddressMojibake?: (text: string) => string }) { return createAssistantLivingModePolicy({ featureAssistantLivingChatRouterV1: true, compactWhitespace: (text: string) => text.replace(/\s+/g, " ").trim(), repairAddressMojibake: options?.repairAddressMojibake ?? ((text: string) => text), toNonEmptyString: (value: unknown) => { if (value === null || value === undefined) { return null; } const text = String(value).trim(); return text.length > 0 ? text : null; }, normalizeOrganizationScopeValue: (value: unknown) => { if (value === null || value === undefined) { return null; } const text = String(value).trim().replace(/^"+|"+$/g, "").replace(/^'+|'+$/g, ""); return text.length > 0 ? text : null; }, hasReferentialPointer: (text: string) => /(same thing|that one|this one)/i.test(text.toLowerCase()), hasSmallTalkSignal: (text: string) => /(thanks|thank you|hello|hi)\b/i.test(text.toLowerCase()), hasAssistantCapabilityQuestionSignal: (text: string) => /(?:what can you do|capabilities|features)/i.test(text), hasOperationalAdminActionRequestSignal: (text: string) => /(?:install|update|delete\s+database|drop\s+database)/i.test(text), resolveProviderExecutionState: (input: { useMock?: unknown }) => ({ living_mode_forced_deep: Boolean(input?.useMock), living_mode_forced_reason: Boolean(input?.useMock) ? "mock_mode_keeps_deep_pipeline" : null }) }); } describe("assistantLivingModePolicy", () => { const colloquialGreeting = "\u043f\u0440\u0438\u0432\u0435\u0442\u0438\u043a - \u0447\u0435 \u043a\u0430\u043a \u0442\u0430\u043c \u0434\u0435\u043b\u0430"; it("detects explicit recap wording as memory signal even when selected-object words are present", () => { const policy = buildPolicy(); expect( policy.hasConversationMemoryRecallFollowupSignal("а ты помнишь, что мы по этой позиции уже выяснили?") ).toBe(true); }); it("detects bare recap wording without 'помнишь' as memory signal", () => { const policy = buildPolicy(); expect(policy.hasConversationMemoryRecallFollowupSignal("а что мы уже выяснили по этой позиции?")).toBe(true); }); it("detects answer inspection wording for previous answer correction", () => { const policy = buildPolicy(); expect(policy.hasAnswerInspectionFollowupSignal("у тебя написано кто контрагент: рабочая станция - это ошибка?")).toBe(true); }); it("routes casual small-talk to chat mode", () => { const policy = buildPolicy(); const decision = policy.resolveLivingAssistantModeDecision({ userMessage: "hello", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("chat"); expect(decision.reason).toBe("living_chat_signal_detected"); }); it("detects colloquial greeting with extra words as living chat signal", () => { const policy = buildPolicy(); expect(policy.hasLivingChatSignal(colloquialGreeting)).toBe(true); }); it("detects mojibake colloquial greeting after repair", () => { const policy = buildPolicy({ repairAddressMojibake: (text: string) => text === "приветик - че как там дела" ? colloquialGreeting : text }); expect(policy.hasLivingChatSignal("приветик - че как там дела")).toBe(true); }); it("keeps explicit inventory question in deep mode", () => { const policy = buildPolicy(); const decision = policy.resolveLivingAssistantModeDecision({ userMessage: "show warehouse inventory for 2020", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("deep_analysis"); expect(decision.reason).toBe("strong_data_signal_detected"); }); it("keeps deep pipeline in mock mode via provider execution policy", () => { const policy = buildPolicy(); const decision = policy.resolveLivingAssistantModeDecision({ userMessage: "hello", addressLaneTriggered: false, useMock: true, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("deep_analysis"); expect(decision.reason).toBe("mock_mode_keeps_deep_pipeline"); }); });