317 lines
15 KiB
TypeScript
317 lines
15 KiB
TypeScript
import {
|
|
buildAddressMemoryRecapReply as buildAddressMemoryRecapReplyFromPolicy,
|
|
buildSelectedObjectAnswerInspectionReply as buildSelectedObjectAnswerInspectionReplyFromPolicy,
|
|
buildInventoryHistoryCapabilityFollowupReply as buildInventoryHistoryCapabilityFollowupReplyFromPolicy,
|
|
resolveAssistantLivingChatMemoryContext
|
|
} from "./assistantMemoryRecapPolicy";
|
|
import { resolveAssistantOrganizationAuthority } from "./assistantContinuityPolicy";
|
|
|
|
export interface AssistantLivingChatSessionScopeInput {
|
|
knownOrganizations?: unknown[];
|
|
selectedOrganization?: unknown;
|
|
activeOrganization?: unknown;
|
|
}
|
|
|
|
export interface AssistantLivingChatModeDecisionInput {
|
|
mode?: string | null;
|
|
reason?: string | null;
|
|
}
|
|
|
|
export interface AssistantLivingChatRuntimeInput {
|
|
userMessage: string;
|
|
sessionItems: unknown[];
|
|
modeDecision?: AssistantLivingChatModeDecisionInput | null;
|
|
sessionScope: AssistantLivingChatSessionScopeInput;
|
|
addressRuntimeMeta?: Record<string, unknown> | null;
|
|
traceIdFactory: () => string;
|
|
toNonEmptyString: (value: unknown) => string | null;
|
|
mergeKnownOrganizations: (values: unknown[]) => string[];
|
|
hasAssistantDataScopeMetaQuestionSignal: (message: string) => boolean;
|
|
shouldHandleAsAssistantCapabilityMetaQuery: (message: string) => boolean;
|
|
hasDestructiveDataActionSignal: (message: string) => boolean;
|
|
hasDangerOrCoercionSignal: (message: string) => boolean;
|
|
hasOperationalAdminActionRequestSignal: (message: string) => boolean;
|
|
hasOrganizationFactLookupSignal: (message: string) => boolean;
|
|
hasOrganizationFactFollowupSignal: (message: string, items: unknown[]) => boolean;
|
|
hasLivingChatSignal: (message: string) => boolean;
|
|
shouldEmitOrganizationSelectionReply: (message: string, activeOrganization: string | null) => boolean;
|
|
hasAssistantCapabilityQuestionSignal: (message: string) => boolean;
|
|
resolveDataScopeProbe: () => Promise<Record<string, unknown> | null>;
|
|
executeLlmChat: () => Promise<string>;
|
|
applyScriptGuard: (chatText: string, userMessage: string) => {
|
|
text: string;
|
|
applied: boolean;
|
|
reason: string | null;
|
|
};
|
|
applyGroundingGuard: (input: {
|
|
userMessage: string;
|
|
chatText: string;
|
|
organization: string | null;
|
|
}) => {
|
|
text: string;
|
|
applied: boolean;
|
|
reason: string | null;
|
|
};
|
|
buildAssistantSafetyRefusalReply: () => string;
|
|
buildAssistantDataScopeContractReply: (scopeProbe: Record<string, unknown> | null) => string;
|
|
buildAssistantProactiveOrganizationOfferReply: (scopeProbe: Record<string, unknown> | null) => string;
|
|
buildAssistantOrganizationFactBoundaryReply: (organization: string | null) => string;
|
|
buildAssistantDataScopeSelectionReply: (organization: string | null) => string;
|
|
buildAssistantOperationalBoundaryReply: () => string;
|
|
buildAssistantCapabilityContractReply: (userMessage?: string) => string;
|
|
}
|
|
|
|
export interface AssistantLivingChatRuntimeOutput {
|
|
handled: boolean;
|
|
chatText: string;
|
|
debug: Record<string, unknown> | null;
|
|
}
|
|
|
|
function hasPriorAssistantTurn(items: unknown[]): boolean {
|
|
if (!Array.isArray(items)) {
|
|
return false;
|
|
}
|
|
return items.some((item) => item && typeof item === "object" && (item as { role?: string }).role === "assistant");
|
|
}
|
|
|
|
function buildDeterministicSmalltalkLeadReply(): string {
|
|
return "\u041f\u0440\u0438\u0432\u0435\u0442! \u0412\u0441\u0451 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e.";
|
|
}
|
|
|
|
export async function runAssistantLivingChatRuntime(
|
|
input: AssistantLivingChatRuntimeInput
|
|
): Promise<AssistantLivingChatRuntimeOutput> {
|
|
const userMessage = String(input.userMessage ?? "");
|
|
const organizationAuthority = resolveAssistantOrganizationAuthority({
|
|
sessionItems: input.sessionItems,
|
|
sessionKnownOrganizations: Array.isArray(input.sessionScope.knownOrganizations)
|
|
? input.sessionScope.knownOrganizations
|
|
: [],
|
|
sessionSelectedOrganization: input.sessionScope.selectedOrganization,
|
|
sessionActiveOrganization: input.sessionScope.activeOrganization,
|
|
toNonEmptyString: input.toNonEmptyString,
|
|
normalizeOrganizationScopeValue: input.toNonEmptyString,
|
|
mergeKnownOrganizations: input.mergeKnownOrganizations
|
|
});
|
|
const continuitySnapshot = organizationAuthority.continuitySnapshot;
|
|
const dataScopeMetaQuery = input.hasAssistantDataScopeMetaQuestionSignal(userMessage);
|
|
const capabilityMetaQuery = input.shouldHandleAsAssistantCapabilityMetaQuery(userMessage);
|
|
const destructiveSignal = input.hasDestructiveDataActionSignal(userMessage);
|
|
const dangerSignal = input.hasDangerOrCoercionSignal(userMessage);
|
|
const operationalSignal = input.hasOperationalAdminActionRequestSignal(userMessage);
|
|
|
|
let dataScopeProbe: Record<string, unknown> | null = null;
|
|
let chatText = "";
|
|
let livingChatSource = "llm_chat";
|
|
let livingChatScriptGuardApplied = false;
|
|
let livingChatScriptGuardReason: string | null = null;
|
|
let livingChatGroundingGuardApplied = false;
|
|
let livingChatGroundingGuardReason: string | null = null;
|
|
let livingChatProactiveScopeOfferApplied = false;
|
|
const continuityActiveOrganization = organizationAuthority.continuityActiveOrganization;
|
|
let knownOrganizations = [...organizationAuthority.knownOrganizations];
|
|
let selectedOrganization = organizationAuthority.selectedOrganization;
|
|
let activeOrganization = organizationAuthority.activeOrganization;
|
|
const memoryRecapContext = resolveAssistantLivingChatMemoryContext({
|
|
modeDecisionReason: input.modeDecision?.reason ?? null,
|
|
sessionItems: input.sessionItems
|
|
});
|
|
const contextualInventoryHistoryCapabilityFollowup =
|
|
memoryRecapContext.contextualInventoryHistoryCapabilityFollowup;
|
|
const contextualMemoryRecapFollowup = memoryRecapContext.contextualMemoryRecapFollowup;
|
|
const contextualAnswerInspectionFollowup =
|
|
memoryRecapContext.contextualAnswerInspectionFollowup;
|
|
const lastGroundedInventoryAddressDebug = memoryRecapContext.lastGroundedInventoryAddressDebug;
|
|
const lastMemoryAddressDebug = memoryRecapContext.lastMemoryAddressDebug;
|
|
const lastAnswerInspectionAddressDebug = memoryRecapContext.lastAnswerInspectionAddressDebug;
|
|
|
|
if (capabilityMetaQuery && (destructiveSignal || dangerSignal)) {
|
|
chatText = input.buildAssistantSafetyRefusalReply();
|
|
livingChatSource = "deterministic_safety_refusal";
|
|
} else if (dataScopeMetaQuery) {
|
|
dataScopeProbe = await input.resolveDataScopeProbe();
|
|
chatText = input.buildAssistantDataScopeContractReply(dataScopeProbe);
|
|
knownOrganizations = input.mergeKnownOrganizations([
|
|
...knownOrganizations,
|
|
...(Array.isArray(dataScopeProbe?.organizations) ? (dataScopeProbe.organizations as unknown[]) : [])
|
|
]);
|
|
if (!activeOrganization && knownOrganizations.length === 1) {
|
|
activeOrganization = knownOrganizations[0];
|
|
}
|
|
livingChatSource =
|
|
dataScopeProbe?.status === "resolved"
|
|
? "deterministic_data_scope_contract_live"
|
|
: "deterministic_data_scope_contract";
|
|
} else if ((selectedOrganization || activeOrganization) && input.hasOrganizationFactLookupSignal(userMessage)) {
|
|
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
|
|
chatText = input.buildAssistantOrganizationFactBoundaryReply(scopedOrganization);
|
|
activeOrganization = scopedOrganization ?? activeOrganization;
|
|
livingChatSource = "deterministic_organization_fact_boundary";
|
|
} else if (
|
|
(selectedOrganization || activeOrganization) &&
|
|
input.hasOrganizationFactFollowupSignal(userMessage, input.sessionItems)
|
|
) {
|
|
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
|
|
chatText = input.buildAssistantOrganizationFactBoundaryReply(scopedOrganization);
|
|
activeOrganization = scopedOrganization ?? activeOrganization;
|
|
livingChatSource = "deterministic_organization_fact_boundary_followup";
|
|
} else if (
|
|
!capabilityMetaQuery &&
|
|
input.shouldEmitOrganizationSelectionReply(userMessage, selectedOrganization ?? activeOrganization)
|
|
) {
|
|
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
|
|
chatText = input.buildAssistantDataScopeSelectionReply(scopedOrganization);
|
|
activeOrganization = scopedOrganization ?? activeOrganization;
|
|
livingChatSource = "deterministic_data_scope_selection_contract";
|
|
} else if (capabilityMetaQuery && operationalSignal && !input.hasAssistantCapabilityQuestionSignal(userMessage)) {
|
|
chatText = input.buildAssistantOperationalBoundaryReply();
|
|
livingChatSource = "deterministic_operational_boundary";
|
|
} else if (contextualInventoryHistoryCapabilityFollowup) {
|
|
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
|
|
chatText = buildInventoryHistoryCapabilityFollowupReplyFromPolicy({
|
|
organization: scopedOrganization,
|
|
addressDebug: lastGroundedInventoryAddressDebug,
|
|
toNonEmptyString: input.toNonEmptyString
|
|
});
|
|
activeOrganization = scopedOrganization ?? activeOrganization;
|
|
livingChatSource = "deterministic_inventory_history_capability_contract";
|
|
} else if (contextualMemoryRecapFollowup) {
|
|
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
|
|
chatText = buildAddressMemoryRecapReplyFromPolicy({
|
|
organization: scopedOrganization,
|
|
addressDebug: lastMemoryAddressDebug,
|
|
sessionItems: input.sessionItems,
|
|
toNonEmptyString: input.toNonEmptyString
|
|
});
|
|
activeOrganization = scopedOrganization ?? activeOrganization;
|
|
livingChatSource = "deterministic_memory_recap_contract";
|
|
} else if (contextualAnswerInspectionFollowup) {
|
|
chatText = buildSelectedObjectAnswerInspectionReplyFromPolicy({
|
|
addressDebug: lastAnswerInspectionAddressDebug,
|
|
toNonEmptyString: input.toNonEmptyString
|
|
});
|
|
livingChatSource = "deterministic_answer_inspection_contract";
|
|
} else if (capabilityMetaQuery) {
|
|
chatText = input.buildAssistantCapabilityContractReply(userMessage);
|
|
livingChatSource = "deterministic_capability_contract";
|
|
} else {
|
|
chatText = await input.executeLlmChat();
|
|
const scriptGuard = input.applyScriptGuard(chatText, userMessage);
|
|
chatText = scriptGuard.text;
|
|
if (scriptGuard.applied) {
|
|
livingChatScriptGuardApplied = true;
|
|
livingChatScriptGuardReason = scriptGuard.reason;
|
|
livingChatSource = "llm_chat_script_guard";
|
|
}
|
|
const groundingGuard = input.applyGroundingGuard({
|
|
userMessage,
|
|
chatText,
|
|
organization: activeOrganization ?? selectedOrganization ?? null
|
|
});
|
|
chatText = groundingGuard.text;
|
|
if (groundingGuard.applied) {
|
|
livingChatGroundingGuardApplied = true;
|
|
livingChatGroundingGuardReason = groundingGuard.reason;
|
|
livingChatSource = "llm_chat_grounding_guard";
|
|
}
|
|
|
|
const shouldOfferProactiveOrganizationScope =
|
|
!selectedOrganization &&
|
|
!activeOrganization &&
|
|
!continuitySnapshot.hasGroundedAddressContext &&
|
|
!hasPriorAssistantTurn(input.sessionItems) &&
|
|
input.modeDecision?.mode === "chat" &&
|
|
input.hasLivingChatSignal(userMessage);
|
|
if (shouldOfferProactiveOrganizationScope) {
|
|
const proactiveScopeProbe = await input.resolveDataScopeProbe();
|
|
const mergedKnownOrganizations = input.mergeKnownOrganizations([
|
|
...knownOrganizations,
|
|
...(Array.isArray(proactiveScopeProbe?.organizations) ? (proactiveScopeProbe.organizations as unknown[]) : [])
|
|
]);
|
|
knownOrganizations = mergedKnownOrganizations;
|
|
if (!activeOrganization && mergedKnownOrganizations.length === 1) {
|
|
activeOrganization = mergedKnownOrganizations[0];
|
|
}
|
|
const proactiveOffer = input.buildAssistantProactiveOrganizationOfferReply(proactiveScopeProbe);
|
|
if (proactiveOffer) {
|
|
chatText = [buildDeterministicSmalltalkLeadReply(), proactiveOffer]
|
|
.filter((part) => String(part ?? "").trim().length > 0)
|
|
.join(" ");
|
|
livingChatProactiveScopeOfferApplied = true;
|
|
livingChatSource = "deterministic_smalltalk_with_proactive_scope_offer";
|
|
if (!dataScopeProbe) {
|
|
dataScopeProbe = proactiveScopeProbe;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!chatText) {
|
|
return {
|
|
handled: false,
|
|
chatText: "",
|
|
debug: null
|
|
};
|
|
}
|
|
|
|
const addressRuntimeMeta = (input.addressRuntimeMeta && typeof input.addressRuntimeMeta === "object"
|
|
? input.addressRuntimeMeta
|
|
: {}) as Record<string, unknown>;
|
|
const predecomposeContract =
|
|
addressRuntimeMeta.predecomposeContract && typeof addressRuntimeMeta.predecomposeContract === "object"
|
|
? (addressRuntimeMeta.predecomposeContract as Record<string, unknown>)
|
|
: null;
|
|
const semanticExtractionContract =
|
|
addressRuntimeMeta.semanticExtractionContract && typeof addressRuntimeMeta.semanticExtractionContract === "object"
|
|
? (addressRuntimeMeta.semanticExtractionContract as Record<string, unknown>)
|
|
: null;
|
|
|
|
const debug: Record<string, unknown> = {
|
|
trace_id: input.traceIdFactory(),
|
|
prompt_version: "living_chat_router_v1",
|
|
schema_version: "living_chat_router_v1",
|
|
fallback_type: "none",
|
|
detected_mode: "chat",
|
|
detected_mode_confidence: "high",
|
|
execution_lane: "living_chat",
|
|
living_router_mode: input.modeDecision?.mode ?? "chat",
|
|
living_router_reason: input.modeDecision?.reason ?? "living_chat_signal_detected",
|
|
living_chat_response_source: livingChatSource,
|
|
living_chat_script_guard_applied: livingChatScriptGuardApplied,
|
|
living_chat_script_guard_reason: livingChatScriptGuardReason,
|
|
living_chat_grounding_guard_applied: livingChatGroundingGuardApplied,
|
|
living_chat_grounding_guard_reason: livingChatGroundingGuardReason,
|
|
living_chat_proactive_scope_offer_applied: livingChatProactiveScopeOfferApplied,
|
|
living_chat_data_scope_probe_status: dataScopeProbe?.status ?? null,
|
|
living_chat_data_scope_probe_channel: dataScopeProbe?.channel ?? null,
|
|
living_chat_data_scope_probe_org_count: Array.isArray(dataScopeProbe?.organizations)
|
|
? dataScopeProbe.organizations.length
|
|
: 0,
|
|
living_chat_data_scope_probe_organizations: Array.isArray(dataScopeProbe?.organizations)
|
|
? input.mergeKnownOrganizations(dataScopeProbe.organizations as unknown[])
|
|
: [],
|
|
living_chat_data_scope_probe_error: dataScopeProbe?.error ?? null,
|
|
living_chat_continuity_grounded_context_detected: continuitySnapshot.hasGroundedAddressContext,
|
|
living_chat_continuity_active_organization: continuityActiveOrganization,
|
|
living_chat_selected_organization: selectedOrganization ?? null,
|
|
assistant_known_organizations: knownOrganizations,
|
|
assistant_active_organization: activeOrganization ?? null,
|
|
address_llm_predecompose_attempted: Boolean(addressRuntimeMeta.attempted),
|
|
address_llm_predecompose_applied: Boolean(addressRuntimeMeta.applied),
|
|
address_llm_predecompose_reason: addressRuntimeMeta.reason ?? null,
|
|
address_llm_predecompose_contract: predecomposeContract,
|
|
address_semantic_extraction_contract: semanticExtractionContract,
|
|
orchestration_contract_v1: addressRuntimeMeta.orchestrationContract ?? null,
|
|
tool_gate_decision: addressRuntimeMeta.toolGateDecision ?? null,
|
|
tool_gate_reason: addressRuntimeMeta.toolGateReason ?? null,
|
|
normalized: null,
|
|
normalizer_output: null
|
|
};
|
|
|
|
return {
|
|
handled: true,
|
|
chatText,
|
|
debug
|
|
};
|
|
}
|