ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.47: вынесен верхний orchestration-скелет handleMessage (bootstrap -> address attempt -> deep attempt) в единый turn runtime adapter без изменения поведения.

This commit is contained in:
dctouch 2026-04-11 00:31:17 +03:00
parent 5f4e898c7c
commit 90d529f79b
6 changed files with 514 additions and 237 deletions

View File

@ -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)

View File

@ -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;

View File

@ -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
};
}

View File

@ -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;
}
}

View File

@ -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<PayloadType = unknown> {
payload: PayloadType;
sessionId: string;
userMessage: string;
sessionItems: unknown[];
runtimeAnalysisContext: { as_of_date: string | null };
sessionOrganizationScope: AssistantSessionOrganizationScopeContext;
}
export interface RunAssistantTurnAttemptRuntimeDeepInput<PayloadType = unknown> {
payload: PayloadType;
sessionId: string;
questionId: string;
userMessage: string;
runtimeAnalysisContext: unknown;
sessionInvestigationState: unknown;
addressRuntimeMetaForDeep: Record<string, unknown> | null;
}
export interface RunAssistantTurnAttemptRuntimeInput<ResponseType = unknown, PayloadType = unknown> {
payload: PayloadType;
runUserTurnBootstrapRuntime: (payload: PayloadType) => RunAssistantUserTurnBootstrapRuntimeOutput;
resolveSessionOrganizationScopeContext: (
userMessage: string,
sessionItems: unknown[]
) => AssistantSessionOrganizationScopeContext;
runAddressAttemptRuntime: (
input: RunAssistantTurnAttemptRuntimeAddressInput<PayloadType>
) => Promise<RunAssistantAddressRuntimeOutput<ResponseType>>;
runDeepTurnAttemptRuntime: (
input: RunAssistantTurnAttemptRuntimeDeepInput<PayloadType>
) => Promise<{ response: ResponseType }>;
}
export interface RunAssistantTurnAttemptRuntimeOutput<ResponseType = unknown> {
response: ResponseType;
source: "address" | "deep";
addressRuntimeMetaForDeep: Record<string, unknown> | null;
userTurn: RunAssistantUserTurnBootstrapRuntimeOutput;
sessionOrganizationScope: AssistantSessionOrganizationScopeContext;
}
export async function runAssistantTurnAttemptRuntime<ResponseType = unknown, PayloadType = unknown>(
input: RunAssistantTurnAttemptRuntimeInput<ResponseType, PayloadType>
): Promise<RunAssistantTurnAttemptRuntimeOutput<ResponseType>> {
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
};
}

View File

@ -0,0 +1,106 @@
import { describe, expect, it, vi } from "vitest";
import { runAssistantTurnAttemptRuntime } from "../src/services/assistantTurnAttemptRuntimeAdapter";
function buildUserTurn(overrides: Record<string, unknown> = {}) {
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" }
})
);
});
});