From 90d529f79b3a22d7e8ad824c15209341f5b78a25 Mon Sep 17 00:00:00 2001 From: dctouch Date: Sat, 11 Apr 2026 00:31:17 +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=202.47:=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=B2=D0=B5=D1=80=D1=85=D0=BD=D0=B8=D0=B9=20orc?= =?UTF-8?q?hestration-=D1=81=D0=BA=D0=B5=D0=BB=D0=B5=D1=82=20handleMessage?= =?UTF-8?q?=20(bootstrap=20->=20address=20attempt=20->=20deep=20attempt)?= =?UTF-8?q?=20=D0=B2=20=D0=B5=D0=B4=D0=B8=D0=BD=D1=8B=D0=B9=20turn=20runti?= =?UTF-8?q?me=20adapter=20=D0=B1=D0=B5=D0=B7=20=D0=B8=D0=B7=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D0=B2=D0=B5=D0=B4?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/TECH/1CLLMARCH-FACT.md | 35 ++- .../backend/dist/services/assistantService.js | 236 +++++++++--------- .../assistantTurnAttemptRuntimeAdapter.js | 41 +++ .../backend/src/services/assistantService.ts | 236 +++++++++--------- .../assistantTurnAttemptRuntimeAdapter.ts | 97 +++++++ ...assistantTurnAttemptRuntimeAdapter.test.ts | 106 ++++++++ 6 files changed, 514 insertions(+), 237 deletions(-) create mode 100644 llm_normalizer/backend/dist/services/assistantTurnAttemptRuntimeAdapter.js create mode 100644 llm_normalizer/backend/src/services/assistantTurnAttemptRuntimeAdapter.ts create mode 100644 llm_normalizer/backend/tests/assistantTurnAttemptRuntimeAdapter.test.ts diff --git a/docs/TECH/1CLLMARCH-FACT.md b/docs/TECH/1CLLMARCH-FACT.md index a6f9591..3bffbb2 100644 --- a/docs/TECH/1CLLMARCH-FACT.md +++ b/docs/TECH/1CLLMARCH-FACT.md @@ -1468,7 +1468,40 @@ 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 + 2.41 + 2.42 + 2.43 + 2.44 + 2.45 + 2.46 completed)** +Implemented in current pass (Phase 2.47): +1. Extracted top-level assistant turn orchestration (`bootstrap -> address attempt -> deep attempt`) into dedicated runtime adapter: + - `assistantTurnAttemptRuntimeAdapter.ts` + - introduced: + - `runAssistantTurnAttemptRuntime(...)` +2. Rewired `assistantService.handleMessage` to use single top-level turn runtime boundary (behavior-preserving): + - user-turn bootstrap remains delegated to `assistantUserTurnBootstrapRuntime`; + - address and deep attempt runtimes remain unchanged, but orchestration/early-return logic moved out of service body. +3. Added focused unit tests: + - `assistantTurnAttemptRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted living/address/deep followup pack passed: + - `assistantTurnAttemptRuntimeAdapter.test.ts` + - `assistantAddressAttemptRuntimeAdapter.test.ts` + - `assistantDeepTurnAttemptRuntimeAdapter.test.ts` + - `assistantDeepTurnResponseAttemptRuntimeAdapter.test.ts` + - `assistantDeepTurnAnalysisAttemptRuntimeAdapter.test.ts` + - `assistantDeepTurnAnalysisRuntimeAdapter.test.ts` + - `assistantAddressLaneResponseAttemptRuntimeAdapter.test.ts` + - `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 + 2.42 + 2.43 + 2.44 + 2.45 + 2.46 + 2.47 completed)** ## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards) diff --git a/llm_normalizer/backend/dist/services/assistantService.js b/llm_normalizer/backend/dist/services/assistantService.js index 9cb2e83..03dd03d 100644 --- a/llm_normalizer/backend/dist/services/assistantService.js +++ b/llm_normalizer/backend/dist/services/assistantService.js @@ -65,6 +65,7 @@ const assistantCanon_1 = __importStar(require("./assistantCanon")); const assistantAddressAttemptRuntimeAdapter_1 = __importStar(require("./assistantAddressAttemptRuntimeAdapter")); const assistantCoverageGrounding_1 = __importStar(require("./assistantCoverageGrounding")); const assistantDeepTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnAttemptRuntimeAdapter")); +const assistantTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantTurnAttemptRuntimeAdapter")); const assistantUserTurnBootstrapRuntimeAdapter_1 = __importStar(require("./assistantUserTurnBootstrapRuntimeAdapter")); const assistantQueryPlanning_1 = __importStar(require("./assistantQueryPlanning")); const iconv_lite_1 = __importDefault(require("iconv-lite")); @@ -4369,126 +4370,125 @@ class AssistantService { return this.sessions.getSession(sessionId); } async handleMessage(payload) { - const { session, sessionId, userMessage, runtimeAnalysisContext, userItem } = (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)({ + const turnRuntime = await (0, assistantTurnAttemptRuntimeAdapter_1.runAssistantTurnAttemptRuntime)({ 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() + runUserTurnBootstrapRuntime: (runtimePayload) => (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)({ + payload: runtimePayload, + 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() + }), + resolveSessionOrganizationScopeContext: (runtimeUserMessage, sessionItems) => resolveSessionOrganizationScopeContext(runtimeUserMessage, sessionItems), + runAddressAttemptRuntime: async (runtimeInput) => (0, assistantAddressAttemptRuntimeAdapter_1.runAssistantAddressAttemptRuntime)({ + featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1, + sessionId: runtimeInput.sessionId, + userMessage: runtimeInput.userMessage, + sessionItems: runtimeInput.sessionItems, + payload: runtimeInput.payload, + sessionScope: { + knownOrganizations: runtimeInput.sessionOrganizationScope.knownOrganizations, + selectedOrganization: runtimeInput.sessionOrganizationScope.selectedOrganization, + activeOrganization: runtimeInput.sessionOrganizationScope.activeOrganization + }, + featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1, + runAddressLlmPreDecompose: async () => runAddressLlmPreDecompose(this.normalizerService, runtimeInput.payload, runtimeInput.userMessage), + buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1, + sanitizeAddressMessageForFallback, + toNonEmptyString, + resolveAddressFollowupCarryoverContext, + resolveAssistantOrchestrationDecision, + buildAddressDialogContinuationContractV2, + runtimeAnalysisContextAsOfDate: runtimeInput.runtimeAnalysisContext.as_of_date, + compactWhitespace, + mergeFollowupContextWithOrganizationScope, + runAddressQueryTryHandle: (laneMessageUsed, options) => this.addressQueryService.tryHandle(laneMessageUsed, options), + isRetryableAddressLimitedResult, + 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, + 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 ?? "", + buildAddressDebugPayload, + buildAddressFollowupOffer, + 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: (runtimePayload) => (0, log_1.logJson)(runtimePayload), + messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`, + nowIso: () => new Date().toISOString() + }), + runDeepTurnAttemptRuntime: async (runtimeInput) => (0, assistantDeepTurnAttemptRuntimeAdapter_1.runAssistantDeepTurnAttemptRuntime)({ + sessionId: runtimeInput.sessionId, + questionId: runtimeInput.questionId, + userMessage: runtimeInput.userMessage, + payload: runtimeInput.payload, + runtimeAnalysisContext: runtimeInput.runtimeAnalysisContext, + sessionInvestigationState: runtimeInput.sessionInvestigationState, + addressRuntimeMetaForDeep: runtimeInput.addressRuntimeMetaForDeep, + featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1, + featureStateFollowupBindingV1: config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1, + featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11, + 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, + buildFollowupStateBinding, + normalize: (normalizePayload) => this.normalizerService.normalize(normalizePayload), + 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), + 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: (runtimePayload) => (0, log_1.logJson)(runtimePayload) + }) }); - const sessionOrganizationScope = resolveSessionOrganizationScopeContext(userMessage, session.items); - const addressRuntime = await (0, assistantAddressAttemptRuntimeAdapter_1.runAssistantAddressAttemptRuntime)({ - featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1, - sessionId, - userMessage, - sessionItems: session.items, - payload, - sessionScope: { - knownOrganizations: sessionOrganizationScope.knownOrganizations, - selectedOrganization: sessionOrganizationScope.selectedOrganization, - activeOrganization: sessionOrganizationScope.activeOrganization - }, - 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, - compactWhitespace, - mergeFollowupContextWithOrganizationScope, - runAddressQueryTryHandle: (laneMessageUsed, options) => this.addressQueryService.tryHandle(laneMessageUsed, options), - isRetryableAddressLimitedResult, - 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, - 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 ?? "", - buildAddressDebugPayload, - buildAddressFollowupOffer, - 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 addressRuntimeMetaForDeep = addressRuntime.addressRuntimeMetaForDeep; - if (addressRuntime.handled && addressRuntime.response) { - return addressRuntime.response; - } - const deepTurnRuntime = await (0, assistantDeepTurnAttemptRuntimeAdapter_1.runAssistantDeepTurnAttemptRuntime)({ - sessionId, - questionId: userItem.message_id, - userMessage, - payload, - runtimeAnalysisContext, - sessionInvestigationState: session.investigation_state, - addressRuntimeMetaForDeep, - featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1, - featureStateFollowupBindingV1: config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1, - featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11, - 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, - buildFollowupStateBinding, - normalize: (normalizePayload) => this.normalizerService.normalize(normalizePayload), - 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), - 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) - }); - return deepTurnRuntime.response; + return turnRuntime.response; } } exports.AssistantService = AssistantService; diff --git a/llm_normalizer/backend/dist/services/assistantTurnAttemptRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantTurnAttemptRuntimeAdapter.js new file mode 100644 index 0000000..3158df9 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantTurnAttemptRuntimeAdapter.js @@ -0,0 +1,41 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.runAssistantTurnAttemptRuntime = runAssistantTurnAttemptRuntime; +async function runAssistantTurnAttemptRuntime(input) { + const userTurn = input.runUserTurnBootstrapRuntime(input.payload); + const sessionOrganizationScope = input.resolveSessionOrganizationScopeContext(userTurn.userMessage, userTurn.session.items); + const addressRuntime = await input.runAddressAttemptRuntime({ + payload: input.payload, + sessionId: userTurn.sessionId, + userMessage: userTurn.userMessage, + sessionItems: userTurn.session.items, + runtimeAnalysisContext: userTurn.runtimeAnalysisContext, + sessionOrganizationScope + }); + const addressRuntimeMetaForDeep = addressRuntime.addressRuntimeMetaForDeep ?? null; + if (addressRuntime.handled && addressRuntime.response) { + return { + response: addressRuntime.response, + source: "address", + addressRuntimeMetaForDeep, + userTurn, + sessionOrganizationScope + }; + } + const deepTurnRuntime = await input.runDeepTurnAttemptRuntime({ + payload: input.payload, + sessionId: userTurn.sessionId, + questionId: userTurn.userItem.message_id, + userMessage: userTurn.userMessage, + runtimeAnalysisContext: userTurn.runtimeAnalysisContext, + sessionInvestigationState: userTurn.session.investigation_state, + addressRuntimeMetaForDeep + }); + return { + response: deepTurnRuntime.response, + source: "deep", + addressRuntimeMetaForDeep, + userTurn, + sessionOrganizationScope + }; +} diff --git a/llm_normalizer/backend/src/services/assistantService.ts b/llm_normalizer/backend/src/services/assistantService.ts index ab0d78e..75b0f36 100644 --- a/llm_normalizer/backend/src/services/assistantService.ts +++ b/llm_normalizer/backend/src/services/assistantService.ts @@ -19,6 +19,7 @@ import * as assistantCanon_1 from "./assistantCanon"; import * as assistantAddressAttemptRuntimeAdapter_1 from "./assistantAddressAttemptRuntimeAdapter"; import * as assistantCoverageGrounding_1 from "./assistantCoverageGrounding"; import * as assistantDeepTurnAttemptRuntimeAdapter_1 from "./assistantDeepTurnAttemptRuntimeAdapter"; +import * as assistantTurnAttemptRuntimeAdapter_1 from "./assistantTurnAttemptRuntimeAdapter"; import * as assistantUserTurnBootstrapRuntimeAdapter_1 from "./assistantUserTurnBootstrapRuntimeAdapter"; import * as assistantQueryPlanning_1 from "./assistantQueryPlanning"; import iconv from "iconv-lite"; @@ -4324,125 +4325,124 @@ export class AssistantService { return this.sessions.getSession(sessionId); } async handleMessage(payload) { - const { session, sessionId, userMessage, runtimeAnalysisContext, userItem } = (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)({ + const turnRuntime = await (0, assistantTurnAttemptRuntimeAdapter_1.runAssistantTurnAttemptRuntime)({ 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() + runUserTurnBootstrapRuntime: (runtimePayload) => (0, assistantUserTurnBootstrapRuntimeAdapter_1.runAssistantUserTurnBootstrapRuntime)({ + payload: runtimePayload, + 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() + }), + resolveSessionOrganizationScopeContext: (runtimeUserMessage, sessionItems) => resolveSessionOrganizationScopeContext(runtimeUserMessage, sessionItems), + runAddressAttemptRuntime: async (runtimeInput) => (0, assistantAddressAttemptRuntimeAdapter_1.runAssistantAddressAttemptRuntime)({ + featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1, + sessionId: runtimeInput.sessionId, + userMessage: runtimeInput.userMessage, + sessionItems: runtimeInput.sessionItems, + payload: runtimeInput.payload, + sessionScope: { + knownOrganizations: runtimeInput.sessionOrganizationScope.knownOrganizations, + selectedOrganization: runtimeInput.sessionOrganizationScope.selectedOrganization, + activeOrganization: runtimeInput.sessionOrganizationScope.activeOrganization + }, + featureAddressLlmPredecomposeV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_LLM_PREDECOMPOSE_V1, + runAddressLlmPreDecompose: async () => runAddressLlmPreDecompose(this.normalizerService, runtimeInput.payload, runtimeInput.userMessage), + buildAddressLlmPredecomposeContractV1: predecomposeContract_1.buildAddressLlmPredecomposeContractV1, + sanitizeAddressMessageForFallback, + toNonEmptyString, + resolveAddressFollowupCarryoverContext, + resolveAssistantOrchestrationDecision, + buildAddressDialogContinuationContractV2, + runtimeAnalysisContextAsOfDate: runtimeInput.runtimeAnalysisContext.as_of_date, + compactWhitespace, + mergeFollowupContextWithOrganizationScope, + runAddressQueryTryHandle: (laneMessageUsed, options) => this.addressQueryService.tryHandle(laneMessageUsed, options), + isRetryableAddressLimitedResult, + 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, + 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 ?? "", + buildAddressDebugPayload, + buildAddressFollowupOffer, + 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: (runtimePayload) => (0, log_1.logJson)(runtimePayload), + messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}`, + nowIso: () => new Date().toISOString() + }), + runDeepTurnAttemptRuntime: async (runtimeInput) => (0, assistantDeepTurnAttemptRuntimeAdapter_1.runAssistantDeepTurnAttemptRuntime)({ + sessionId: runtimeInput.sessionId, + questionId: runtimeInput.questionId, + userMessage: runtimeInput.userMessage, + payload: runtimeInput.payload, + runtimeAnalysisContext: runtimeInput.runtimeAnalysisContext, + sessionInvestigationState: runtimeInput.sessionInvestigationState, + addressRuntimeMetaForDeep: runtimeInput.addressRuntimeMetaForDeep, + featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1, + featureStateFollowupBindingV1: config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1, + featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11, + 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, + buildFollowupStateBinding, + normalize: (normalizePayload) => this.normalizerService.normalize(normalizePayload), + 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), + 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: (runtimePayload) => (0, log_1.logJson)(runtimePayload) + }) }); - const sessionOrganizationScope = resolveSessionOrganizationScopeContext(userMessage, session.items); - const addressRuntime = await (0, assistantAddressAttemptRuntimeAdapter_1.runAssistantAddressAttemptRuntime)({ - featureAssistantAddressQueryV1: config_1.FEATURE_ASSISTANT_ADDRESS_QUERY_V1, - sessionId, - userMessage, - sessionItems: session.items, - payload, - sessionScope: { - knownOrganizations: sessionOrganizationScope.knownOrganizations, - selectedOrganization: sessionOrganizationScope.selectedOrganization, - activeOrganization: sessionOrganizationScope.activeOrganization - }, - 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, - compactWhitespace, - mergeFollowupContextWithOrganizationScope, - runAddressQueryTryHandle: (laneMessageUsed, options) => this.addressQueryService.tryHandle(laneMessageUsed, options), - isRetryableAddressLimitedResult, - 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, - 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 ?? "", - buildAddressDebugPayload, - buildAddressFollowupOffer, - 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 addressRuntimeMetaForDeep = addressRuntime.addressRuntimeMetaForDeep; - if (addressRuntime.handled && addressRuntime.response) { - return addressRuntime.response; - } - const deepTurnRuntime = await (0, assistantDeepTurnAttemptRuntimeAdapter_1.runAssistantDeepTurnAttemptRuntime)({ - sessionId, - questionId: userItem.message_id, - userMessage, - payload, - runtimeAnalysisContext, - sessionInvestigationState: session.investigation_state, - addressRuntimeMetaForDeep, - featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1, - featureStateFollowupBindingV1: config_1.FEATURE_ASSISTANT_STATE_FOLLOWUP_BINDING_V1, - featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11, - 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, - buildFollowupStateBinding, - normalize: (normalizePayload) => this.normalizerService.normalize(normalizePayload), - 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), - 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) - }); - return deepTurnRuntime.response; + return turnRuntime.response; } } diff --git a/llm_normalizer/backend/src/services/assistantTurnAttemptRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantTurnAttemptRuntimeAdapter.ts new file mode 100644 index 0000000..0704f62 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantTurnAttemptRuntimeAdapter.ts @@ -0,0 +1,97 @@ +import type { RunAssistantAddressRuntimeOutput } from "./assistantAddressRuntimeAdapter"; +import type { RunAssistantUserTurnBootstrapRuntimeOutput } from "./assistantUserTurnBootstrapRuntimeAdapter"; + +export interface AssistantSessionOrganizationScopeContext { + knownOrganizations: string[]; + selectedOrganization: string | null; + activeOrganization: string | null; +} + +export interface RunAssistantTurnAttemptRuntimeAddressInput { + payload: PayloadType; + sessionId: string; + userMessage: string; + sessionItems: unknown[]; + runtimeAnalysisContext: { as_of_date: string | null }; + sessionOrganizationScope: AssistantSessionOrganizationScopeContext; +} + +export interface RunAssistantTurnAttemptRuntimeDeepInput { + payload: PayloadType; + sessionId: string; + questionId: string; + userMessage: string; + runtimeAnalysisContext: unknown; + sessionInvestigationState: unknown; + addressRuntimeMetaForDeep: Record | null; +} + +export interface RunAssistantTurnAttemptRuntimeInput { + payload: PayloadType; + runUserTurnBootstrapRuntime: (payload: PayloadType) => RunAssistantUserTurnBootstrapRuntimeOutput; + resolveSessionOrganizationScopeContext: ( + userMessage: string, + sessionItems: unknown[] + ) => AssistantSessionOrganizationScopeContext; + runAddressAttemptRuntime: ( + input: RunAssistantTurnAttemptRuntimeAddressInput + ) => Promise>; + runDeepTurnAttemptRuntime: ( + input: RunAssistantTurnAttemptRuntimeDeepInput + ) => Promise<{ response: ResponseType }>; +} + +export interface RunAssistantTurnAttemptRuntimeOutput { + response: ResponseType; + source: "address" | "deep"; + addressRuntimeMetaForDeep: Record | null; + userTurn: RunAssistantUserTurnBootstrapRuntimeOutput; + sessionOrganizationScope: AssistantSessionOrganizationScopeContext; +} + +export async function runAssistantTurnAttemptRuntime( + input: RunAssistantTurnAttemptRuntimeInput +): Promise> { + const userTurn = input.runUserTurnBootstrapRuntime(input.payload); + const sessionOrganizationScope = input.resolveSessionOrganizationScopeContext( + userTurn.userMessage, + userTurn.session.items + ); + const addressRuntime = await input.runAddressAttemptRuntime({ + payload: input.payload, + sessionId: userTurn.sessionId, + userMessage: userTurn.userMessage, + sessionItems: userTurn.session.items, + runtimeAnalysisContext: userTurn.runtimeAnalysisContext, + sessionOrganizationScope + }); + + const addressRuntimeMetaForDeep = addressRuntime.addressRuntimeMetaForDeep ?? null; + if (addressRuntime.handled && addressRuntime.response) { + return { + response: addressRuntime.response, + source: "address", + addressRuntimeMetaForDeep, + userTurn, + sessionOrganizationScope + }; + } + + const deepTurnRuntime = await input.runDeepTurnAttemptRuntime({ + payload: input.payload, + sessionId: userTurn.sessionId, + questionId: userTurn.userItem.message_id, + userMessage: userTurn.userMessage, + runtimeAnalysisContext: userTurn.runtimeAnalysisContext, + sessionInvestigationState: userTurn.session.investigation_state, + addressRuntimeMetaForDeep + }); + + return { + response: deepTurnRuntime.response, + source: "deep", + addressRuntimeMetaForDeep, + userTurn, + sessionOrganizationScope + }; +} diff --git a/llm_normalizer/backend/tests/assistantTurnAttemptRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantTurnAttemptRuntimeAdapter.test.ts new file mode 100644 index 0000000..c5874ef --- /dev/null +++ b/llm_normalizer/backend/tests/assistantTurnAttemptRuntimeAdapter.test.ts @@ -0,0 +1,106 @@ +import { describe, expect, it, vi } from "vitest"; +import { runAssistantTurnAttemptRuntime } from "../src/services/assistantTurnAttemptRuntimeAdapter"; + +function buildUserTurn(overrides: Record = {}) { + return { + session: { + session_id: "asst-1", + updated_at: "2026-04-11T00:00:00.000Z", + items: [{ role: "user", text: "msg" }], + investigation_state: { focus: "settlements_60_62" } + }, + sessionId: "asst-1", + userMessageRaw: "where tail", + userMessage: "where tail", + runtimeAnalysisContext: { + as_of_date: "2020-07-31" + }, + userItem: { + message_id: "msg-q1", + session_id: "asst-1", + role: "user", + text: "where tail", + reply_type: null, + created_at: "2026-04-11T00:00:00.000Z", + trace_id: null, + debug: null + }, + ...overrides + } as any; +} + +describe("assistant turn attempt runtime adapter", () => { + it("returns address response and skips deep runtime when address lane handled the turn", async () => { + const runUserTurnBootstrapRuntime = vi.fn(() => buildUserTurn()); + const resolveSessionOrganizationScopeContext = vi.fn(() => ({ + knownOrganizations: ["Org A"], + selectedOrganization: "Org A", + activeOrganization: "Org A" + })); + const runAddressAttemptRuntime = vi.fn(async () => ({ + handled: true, + response: { lane: "address" }, + addressRuntimeMetaForDeep: { source: "address_runtime" } + })); + const runDeepTurnAttemptRuntime = vi.fn(async () => ({ + response: { lane: "deep" } + })); + + const runtime = await runAssistantTurnAttemptRuntime({ + payload: { user_message: "where tail" }, + runUserTurnBootstrapRuntime, + resolveSessionOrganizationScopeContext, + runAddressAttemptRuntime, + runDeepTurnAttemptRuntime + }); + + expect(runtime.response).toEqual({ lane: "address" }); + expect(runtime.source).toBe("address"); + expect(runtime.addressRuntimeMetaForDeep).toEqual({ source: "address_runtime" }); + expect(runAddressAttemptRuntime).toHaveBeenCalledWith( + expect.objectContaining({ + sessionId: "asst-1", + userMessage: "where tail", + runtimeAnalysisContext: { as_of_date: "2020-07-31" } + }) + ); + expect(runDeepTurnAttemptRuntime).not.toHaveBeenCalled(); + }); + + it("falls through to deep runtime when address lane does not return final response", async () => { + const runUserTurnBootstrapRuntime = vi.fn(() => buildUserTurn()); + const resolveSessionOrganizationScopeContext = vi.fn(() => ({ + knownOrganizations: [], + selectedOrganization: null, + activeOrganization: null + })); + const runAddressAttemptRuntime = vi.fn(async () => ({ + handled: false, + response: null, + addressRuntimeMetaForDeep: { attempted: true } + })); + const runDeepTurnAttemptRuntime = vi.fn(async () => ({ + response: { lane: "deep", ok: true } + })); + + const runtime = await runAssistantTurnAttemptRuntime({ + payload: { user_message: "where tail" }, + runUserTurnBootstrapRuntime, + resolveSessionOrganizationScopeContext, + runAddressAttemptRuntime, + runDeepTurnAttemptRuntime + }); + + expect(runtime.response).toEqual({ lane: "deep", ok: true }); + expect(runtime.source).toBe("deep"); + expect(runDeepTurnAttemptRuntime).toHaveBeenCalledWith( + expect.objectContaining({ + sessionId: "asst-1", + questionId: "msg-q1", + userMessage: "where tail", + addressRuntimeMetaForDeep: { attempted: true }, + sessionInvestigationState: { focus: "settlements_60_62" } + }) + ); + }); +});