From fbf2d6a19a3de543bde5f7cb52c19304f0c420a5 Mon Sep 17 00:00:00 2001 From: dctouch Date: Fri, 10 Apr 2026 23:23:35 +0300 Subject: [PATCH] =?UTF-8?q?=D0=93=D0=9B=D0=9E=D0=91=D0=90=D0=9B=D0=AC?= =?UTF-8?q?=D0=9D=D0=AB=D0=99=20=D0=A0=D0=95=D0=A4=D0=90=D0=9A=D0=A2=D0=9E?= =?UTF-8?q?=D0=A0=D0=98=D0=9D=D0=93=20=D0=90=D0=A0=D0=A5=D0=98=D0=A2=D0=95?= =?UTF-8?q?=D0=9A=D0=A2=D0=A3=D0=A0=D0=AB=20-=20=D0=A0=D0=B5=D1=84=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D1=8D=D1=82=D0=B0?= =?UTF-8?q?=D0=BF=D0=BE=D0=B2=20=20=202.41=20-=20=20=D0=B2=D1=8B=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=20=D1=81=D0=BA=D0=BB=D0=B5=D0=B9=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=B7=D0=BE=D0=B2=D0=B0=20live-chat=20=D0=B8=D0=B7?= =?UTF-8?q?=20handleMessage=20=D0=B2=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D0=B9=20runtime-=D0=B0=D0=B4=D0=B0=D0=BF?= =?UTF-8?q?=D1=82=D0=B5=D1=80=20=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20runtime=20bridg?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/TECH/1CLLMARCH-FACT.md | 30 ++++- ...ssistantLivingChatAttemptRuntimeAdapter.js | 62 +++++++++ .../backend/dist/services/assistantService.js | 103 +++++++-------- ...ssistantLivingChatAttemptRuntimeAdapter.ts | 93 ++++++++++++++ .../backend/src/services/assistantService.ts | 103 +++++++-------- ...antLivingChatAttemptRuntimeAdapter.test.ts | 121 ++++++++++++++++++ 6 files changed, 401 insertions(+), 111 deletions(-) create mode 100644 llm_normalizer/backend/dist/services/assistantLivingChatAttemptRuntimeAdapter.js create mode 100644 llm_normalizer/backend/src/services/assistantLivingChatAttemptRuntimeAdapter.ts create mode 100644 llm_normalizer/backend/tests/assistantLivingChatAttemptRuntimeAdapter.test.ts diff --git a/docs/TECH/1CLLMARCH-FACT.md b/docs/TECH/1CLLMARCH-FACT.md index 24080fc..c591d55 100644 --- a/docs/TECH/1CLLMARCH-FACT.md +++ b/docs/TECH/1CLLMARCH-FACT.md @@ -1288,7 +1288,35 @@ Validation: - `assistantDeepTurnPackagingRuntimeAdapter.test.ts` - `assistantWave10SettlementCorrectiveRegression.test.ts` -Status: **In progress (Phase 2.1 + 2.2 + 2.3 + 2.4 + 2.5 + 2.6 + 2.7 + 2.8 + 2.9 + 2.10 + 2.11 + 2.12 + 2.13 + 2.14 + 2.15 + 2.16 + 2.17 + 2.18 + 2.19 + 2.20 + 2.21 + 2.22 + 2.23 + 2.24 + 2.25 + 2.26 + 2.27 + 2.28 + 2.29 + 2.30 + 2.31 + 2.32 + 2.33 + 2.34 + 2.35 + 2.36 + 2.37 + 2.38 + 2.39 + 2.40 completed)** +Implemented in current pass (Phase 2.41): +1. Extracted living-chat attempt bridge (`tryHandleLivingChat`) from `assistantService` into dedicated runtime adapter: + - `assistantLivingChatAttemptRuntimeAdapter.ts` + - introduced: + - `runAssistantLivingChatAttemptRuntime(...)` +2. Centralized living-chat attempt handoff logic (behavior-preserving): + - delegated handler invocation (`tryHandleAssistantLivingChatRuntime(...)`); + - delegated LLM call bridge (`runAssistantLivingChatLlmRuntime(...)`) behind unified `executeLlmChat` contract; + - preserved all guard, scope and session finalization hooks. +3. Rewired `assistantService` to consume living-chat attempt runtime adapter. +4. Added focused unit tests: + - `assistantLivingChatAttemptRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted living/address/deep followup pack passed: + - `assistantLivingChatAttemptRuntimeAdapter.test.ts` + - `assistantAddressLaneAttemptRuntimeAdapter.test.ts` + - `assistantUserTurnBootstrapRuntimeAdapter.test.ts` + - `assistantLivingChatLlmRuntimeAdapter.test.ts` + - `assistantLivingChatHandlerRuntimeAdapter.test.ts` + - `assistantLivingChatRuntimeAdapter.test.ts` + - `assistantAddressRuntimeAdapter.test.ts` + - `assistantAddressLaneResponseRuntimeAdapter.test.ts` + - `assistantDeepTurnResponseRuntimeAdapter.test.ts` + - `assistantDeepTurnPackagingRuntimeAdapter.test.ts` + - `assistantWave10SettlementCorrectiveRegression.test.ts` + +Status: **In progress (Phase 2.1 + 2.2 + 2.3 + 2.4 + 2.5 + 2.6 + 2.7 + 2.8 + 2.9 + 2.10 + 2.11 + 2.12 + 2.13 + 2.14 + 2.15 + 2.16 + 2.17 + 2.18 + 2.19 + 2.20 + 2.21 + 2.22 + 2.23 + 2.24 + 2.25 + 2.26 + 2.27 + 2.28 + 2.29 + 2.30 + 2.31 + 2.32 + 2.33 + 2.34 + 2.35 + 2.36 + 2.37 + 2.38 + 2.39 + 2.40 + 2.41 completed)** ## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards) diff --git a/llm_normalizer/backend/dist/services/assistantLivingChatAttemptRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantLivingChatAttemptRuntimeAdapter.js new file mode 100644 index 0000000..43334d7 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantLivingChatAttemptRuntimeAdapter.js @@ -0,0 +1,62 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.runAssistantLivingChatAttemptRuntime = runAssistantLivingChatAttemptRuntime; +const assistantLivingChatHandlerRuntimeAdapter_1 = require("./assistantLivingChatHandlerRuntimeAdapter"); +const assistantLivingChatLlmRuntimeAdapter_1 = require("./assistantLivingChatLlmRuntimeAdapter"); +function buildExecuteLlmChat(input, runLivingChatLlmSafe) { + return async () => runLivingChatLlmSafe({ + userMessage: input.userMessage, + sessionItems: input.sessionItems, + payload: input.payload, + chatClient: input.chatClient, + loadAssistantCanonExcerpt: input.loadAssistantCanonExcerpt, + sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText, + defaultModel: input.defaultModel, + defaultBaseUrl: input.defaultBaseUrl, + defaultApiKey: input.defaultApiKey + }); +} +async function runAssistantLivingChatAttemptRuntime(input) { + const runLivingChatHandlerSafe = input.runLivingChatHandler ?? assistantLivingChatHandlerRuntimeAdapter_1.tryHandleAssistantLivingChatRuntime; + const runLivingChatLlmSafe = input.runLivingChatLlm ?? assistantLivingChatLlmRuntimeAdapter_1.runAssistantLivingChatLlmRuntime; + const executeLlmChat = buildExecuteLlmChat(input, runLivingChatLlmSafe); + return runLivingChatHandlerSafe({ + sessionId: input.sessionId, + userMessage: input.userMessage, + sessionItems: input.sessionItems, + modeDecision: input.modeDecision, + sessionScope: input.sessionScope, + addressRuntimeMeta: input.addressRuntimeMeta, + traceIdFactory: input.traceIdFactory, + toNonEmptyString: input.toNonEmptyString, + mergeKnownOrganizations: input.mergeKnownOrganizations, + hasAssistantDataScopeMetaQuestionSignal: input.hasAssistantDataScopeMetaQuestionSignal, + shouldHandleAsAssistantCapabilityMetaQuery: input.shouldHandleAsAssistantCapabilityMetaQuery, + hasDestructiveDataActionSignal: input.hasDestructiveDataActionSignal, + hasDangerOrCoercionSignal: input.hasDangerOrCoercionSignal, + hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal, + hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal, + hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal, + shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply, + hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal, + resolveDataScopeProbe: input.resolveDataScopeProbe, + executeLlmChat, + applyScriptGuard: input.applyScriptGuard, + applyGroundingGuard: input.applyGroundingGuard, + buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply, + buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply, + buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply, + buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply, + buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply, + buildAssistantCapabilityContractReply: input.buildAssistantCapabilityContractReply, + appendItem: input.appendItem, + getSession: input.getSession, + persistSession: input.persistSession, + cloneConversation: input.cloneConversation, + logEvent: input.logEvent, + messageIdFactory: input.messageIdFactory, + nowIso: input.nowIso, + runLivingChatRuntime: input.runLivingChatRuntime, + finalizeLivingChatTurn: input.finalizeLivingChatTurn + }); +} diff --git a/llm_normalizer/backend/dist/services/assistantService.js b/llm_normalizer/backend/dist/services/assistantService.js index 41ccc02..b6bdd29 100644 --- a/llm_normalizer/backend/dist/services/assistantService.js +++ b/llm_normalizer/backend/dist/services/assistantService.js @@ -80,8 +80,7 @@ const assistantDeepTurnResponseRuntimeAdapter_1 = __importStar(require("./assist const assistantDeepTurnRetrievalRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnRetrievalRuntimeAdapter")); const assistantAddressRuntimeAdapter_1 = __importStar(require("./assistantAddressRuntimeAdapter")); const assistantAddressLaneAttemptRuntimeAdapter_1 = __importStar(require("./assistantAddressLaneAttemptRuntimeAdapter")); -const assistantLivingChatHandlerRuntimeAdapter_1 = __importStar(require("./assistantLivingChatHandlerRuntimeAdapter")); -const assistantLivingChatLlmRuntimeAdapter_1 = __importStar(require("./assistantLivingChatLlmRuntimeAdapter")); +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")); @@ -4423,59 +4422,53 @@ class AssistantService { }); return runtime.response; }; - const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => { - return (0, assistantLivingChatHandlerRuntimeAdapter_1.tryHandleAssistantLivingChatRuntime)({ - 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(), - executeLlmChat: async () => (0, assistantLivingChatLlmRuntimeAdapter_1.runAssistantLivingChatLlmRuntime)({ - userMessage, - sessionItems: session.items, - 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 ?? "" - }), - 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() - }); - }; + 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, diff --git a/llm_normalizer/backend/src/services/assistantLivingChatAttemptRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantLivingChatAttemptRuntimeAdapter.ts new file mode 100644 index 0000000..7b524e9 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantLivingChatAttemptRuntimeAdapter.ts @@ -0,0 +1,93 @@ +import { + tryHandleAssistantLivingChatRuntime, + type TryHandleAssistantLivingChatRuntimeInput +} from "./assistantLivingChatHandlerRuntimeAdapter"; +import { + runAssistantLivingChatLlmRuntime, + type RunAssistantLivingChatLlmRuntimeInput +} from "./assistantLivingChatLlmRuntimeAdapter"; + +type AssistantLivingChatHandlerInput = TryHandleAssistantLivingChatRuntimeInput; +type AssistantLivingChatLlmInput = RunAssistantLivingChatLlmRuntimeInput; + +export interface RunAssistantLivingChatAttemptRuntimeInput + extends Omit, "executeLlmChat"> { + payload: AssistantLivingChatLlmInput["payload"]; + chatClient: AssistantLivingChatLlmInput["chatClient"]; + loadAssistantCanonExcerpt: AssistantLivingChatLlmInput["loadAssistantCanonExcerpt"]; + sanitizeOutgoingAssistantText: AssistantLivingChatLlmInput["sanitizeOutgoingAssistantText"]; + defaultModel: AssistantLivingChatLlmInput["defaultModel"]; + defaultBaseUrl: AssistantLivingChatLlmInput["defaultBaseUrl"]; + defaultApiKey?: AssistantLivingChatLlmInput["defaultApiKey"]; + runLivingChatHandler?: ( + input: AssistantLivingChatHandlerInput + ) => Promise; + runLivingChatLlm?: ( + input: AssistantLivingChatLlmInput + ) => Promise; +} + +function buildExecuteLlmChat( + input: RunAssistantLivingChatAttemptRuntimeInput, + runLivingChatLlmSafe: (input: AssistantLivingChatLlmInput) => Promise +): AssistantLivingChatHandlerInput["executeLlmChat"] { + return async () => + runLivingChatLlmSafe({ + userMessage: input.userMessage, + sessionItems: input.sessionItems, + payload: input.payload, + chatClient: input.chatClient, + loadAssistantCanonExcerpt: input.loadAssistantCanonExcerpt, + sanitizeOutgoingAssistantText: input.sanitizeOutgoingAssistantText, + defaultModel: input.defaultModel, + defaultBaseUrl: input.defaultBaseUrl, + defaultApiKey: input.defaultApiKey + }); +} + +export async function runAssistantLivingChatAttemptRuntime( + input: RunAssistantLivingChatAttemptRuntimeInput +): Promise { + const runLivingChatHandlerSafe = input.runLivingChatHandler ?? tryHandleAssistantLivingChatRuntime; + const runLivingChatLlmSafe = input.runLivingChatLlm ?? runAssistantLivingChatLlmRuntime; + const executeLlmChat = buildExecuteLlmChat(input, runLivingChatLlmSafe); + return runLivingChatHandlerSafe({ + sessionId: input.sessionId, + userMessage: input.userMessage, + sessionItems: input.sessionItems, + modeDecision: input.modeDecision, + sessionScope: input.sessionScope, + addressRuntimeMeta: input.addressRuntimeMeta, + traceIdFactory: input.traceIdFactory, + toNonEmptyString: input.toNonEmptyString, + mergeKnownOrganizations: input.mergeKnownOrganizations, + hasAssistantDataScopeMetaQuestionSignal: input.hasAssistantDataScopeMetaQuestionSignal, + shouldHandleAsAssistantCapabilityMetaQuery: input.shouldHandleAsAssistantCapabilityMetaQuery, + hasDestructiveDataActionSignal: input.hasDestructiveDataActionSignal, + hasDangerOrCoercionSignal: input.hasDangerOrCoercionSignal, + hasOperationalAdminActionRequestSignal: input.hasOperationalAdminActionRequestSignal, + hasOrganizationFactLookupSignal: input.hasOrganizationFactLookupSignal, + hasOrganizationFactFollowupSignal: input.hasOrganizationFactFollowupSignal, + shouldEmitOrganizationSelectionReply: input.shouldEmitOrganizationSelectionReply, + hasAssistantCapabilityQuestionSignal: input.hasAssistantCapabilityQuestionSignal, + resolveDataScopeProbe: input.resolveDataScopeProbe, + executeLlmChat, + applyScriptGuard: input.applyScriptGuard, + applyGroundingGuard: input.applyGroundingGuard, + buildAssistantSafetyRefusalReply: input.buildAssistantSafetyRefusalReply, + buildAssistantDataScopeContractReply: input.buildAssistantDataScopeContractReply, + buildAssistantOrganizationFactBoundaryReply: input.buildAssistantOrganizationFactBoundaryReply, + buildAssistantDataScopeSelectionReply: input.buildAssistantDataScopeSelectionReply, + buildAssistantOperationalBoundaryReply: input.buildAssistantOperationalBoundaryReply, + buildAssistantCapabilityContractReply: input.buildAssistantCapabilityContractReply, + appendItem: input.appendItem, + getSession: input.getSession, + persistSession: input.persistSession, + cloneConversation: input.cloneConversation, + logEvent: input.logEvent, + messageIdFactory: input.messageIdFactory, + nowIso: input.nowIso, + runLivingChatRuntime: input.runLivingChatRuntime, + finalizeLivingChatTurn: input.finalizeLivingChatTurn + }); +} diff --git a/llm_normalizer/backend/src/services/assistantService.ts b/llm_normalizer/backend/src/services/assistantService.ts index ab9b3cf..a7390d7 100644 --- a/llm_normalizer/backend/src/services/assistantService.ts +++ b/llm_normalizer/backend/src/services/assistantService.ts @@ -34,8 +34,7 @@ import * as assistantDeepTurnResponseRuntimeAdapter_1 from "./assistantDeepTurnR import * as assistantDeepTurnRetrievalRuntimeAdapter_1 from "./assistantDeepTurnRetrievalRuntimeAdapter"; import * as assistantAddressRuntimeAdapter_1 from "./assistantAddressRuntimeAdapter"; import * as assistantAddressLaneAttemptRuntimeAdapter_1 from "./assistantAddressLaneAttemptRuntimeAdapter"; -import * as assistantLivingChatHandlerRuntimeAdapter_1 from "./assistantLivingChatHandlerRuntimeAdapter"; -import * as assistantLivingChatLlmRuntimeAdapter_1 from "./assistantLivingChatLlmRuntimeAdapter"; +import * as assistantLivingChatAttemptRuntimeAdapter_1 from "./assistantLivingChatAttemptRuntimeAdapter"; import * as assistantUserTurnBootstrapRuntimeAdapter_1 from "./assistantUserTurnBootstrapRuntimeAdapter"; import * as assistantQueryPlanning_1 from "./assistantQueryPlanning"; import iconv from "iconv-lite"; @@ -4378,59 +4377,53 @@ export class AssistantService { }); return runtime.response; }; - const tryHandleLivingChat = async (modeDecision, addressRuntimeMeta = null) => { - return (0, assistantLivingChatHandlerRuntimeAdapter_1.tryHandleAssistantLivingChatRuntime)({ - 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(), - executeLlmChat: async () => (0, assistantLivingChatLlmRuntimeAdapter_1.runAssistantLivingChatLlmRuntime)({ - userMessage, - sessionItems: session.items, - 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 ?? "" - }), - 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() - }); - }; + 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, diff --git a/llm_normalizer/backend/tests/assistantLivingChatAttemptRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantLivingChatAttemptRuntimeAdapter.test.ts new file mode 100644 index 0000000..5de11b1 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantLivingChatAttemptRuntimeAdapter.test.ts @@ -0,0 +1,121 @@ +import { describe, expect, it, vi } from "vitest"; +import { + runAssistantLivingChatAttemptRuntime, + type RunAssistantLivingChatAttemptRuntimeInput +} from "../src/services/assistantLivingChatAttemptRuntimeAdapter"; + +type TestResponse = { ok: boolean; llm: string }; + +function buildInput( + overrides: Partial> = {} +): RunAssistantLivingChatAttemptRuntimeInput { + return { + sessionId: "asst-1", + userMessage: "контрагенты со сверкой", + sessionItems: [{ role: "user", text: "контекст" }], + modeDecision: { mode: "chat", reason: "living_chat_signal_detected" }, + sessionScope: { + knownOrganizations: [], + selectedOrganization: null, + activeOrganization: null + }, + addressRuntimeMeta: null, + traceIdFactory: () => "chat-trace-1", + toNonEmptyString: (value: unknown) => (typeof value === "string" && value.trim() ? value.trim() : null), + mergeKnownOrganizations: (values: string[]) => values, + hasAssistantDataScopeMetaQuestionSignal: () => false, + shouldHandleAsAssistantCapabilityMetaQuery: () => false, + hasDestructiveDataActionSignal: () => false, + hasDangerOrCoercionSignal: () => false, + hasOperationalAdminActionRequestSignal: () => false, + hasOrganizationFactLookupSignal: () => false, + hasOrganizationFactFollowupSignal: () => false, + shouldEmitOrganizationSelectionReply: () => false, + hasAssistantCapabilityQuestionSignal: () => false, + resolveDataScopeProbe: async () => null, + applyScriptGuard: (text: string) => ({ text, applied: false, reason: null }), + applyGroundingGuard: (payload: { chatText: string }) => ({ + text: payload.chatText, + applied: false, + reason: null + }), + buildAssistantSafetyRefusalReply: () => "safety", + buildAssistantDataScopeContractReply: () => "scope", + buildAssistantOrganizationFactBoundaryReply: () => "boundary", + buildAssistantDataScopeSelectionReply: () => "selection", + buildAssistantOperationalBoundaryReply: () => "operational", + buildAssistantCapabilityContractReply: () => "capability", + appendItem: () => {}, + getSession: () => ({ + session_id: "asst-1", + updated_at: "", + items: [], + investigation_state: null + }), + persistSession: () => {}, + cloneConversation: (items: unknown[]) => items, + logEvent: () => {}, + messageIdFactory: () => "msg-1", + nowIso: () => "2026-04-10T00:00:00.000Z", + payload: { + llmProvider: "openai", + apiKey: "key", + model: "gpt-5", + baseUrl: "http://localhost", + temperature: 0.2, + maxOutputTokens: 300 + }, + chatClient: { + chat: async () => ({ outputText: "chat-output" }) + }, + loadAssistantCanonExcerpt: () => "canon", + sanitizeOutgoingAssistantText: (value: unknown, fallback = "fallback") => { + const text = String(value ?? "").trim(); + return text || fallback; + }, + defaultModel: "gpt-5", + defaultBaseUrl: "http://localhost", + defaultApiKey: "key", + ...overrides + }; +} + +describe("assistant living chat attempt runtime adapter", () => { + it("wires llm runtime output into living handler execute callback", async () => { + const runLivingChatLlm = vi.fn(async () => "llm-answer"); + const runLivingChatHandler = vi.fn(async (input) => { + const llm = await input.executeLlmChat(); + return { ok: true, llm }; + }); + + const response = await runAssistantLivingChatAttemptRuntime( + buildInput({ + runLivingChatHandler, + runLivingChatLlm + }) + ); + + expect(runLivingChatHandler).toHaveBeenCalledTimes(1); + expect(runLivingChatLlm).toHaveBeenCalledWith( + expect.objectContaining({ + userMessage: "контрагенты со сверкой" + }) + ); + expect(response).toEqual({ ok: true, llm: "llm-answer" }); + }); + + it("returns null when delegated handler returns null", async () => { + const runLivingChatLlm = vi.fn(async () => "llm-answer"); + const runLivingChatHandler = vi.fn(async () => null); + + const response = await runAssistantLivingChatAttemptRuntime( + buildInput({ + runLivingChatHandler, + runLivingChatLlm + }) + ); + + expect(response).toBeNull(); + expect(runLivingChatLlm).not.toHaveBeenCalled(); + }); +});