АРЧ АП11 - Архитектура: протянуть continuity snapshot в living-chat organization authority и добавить оценку риска правки в AGENTS

This commit is contained in:
2026-04-18 15:10:48 +03:00
parent 21738f8662
commit d8ce2b7d88
6 changed files with 188 additions and 4 deletions
@@ -2,6 +2,7 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.runAssistantLivingChatRuntime = runAssistantLivingChatRuntime;
const assistantMemoryRecapPolicy_1 = require("./assistantMemoryRecapPolicy");
const assistantContinuityPolicy_1 = require("./assistantContinuityPolicy");
function formatIsoDateForReply(value) {
const source = String(value ?? "").trim();
const match = source.match(/^(\d{4})-(\d{2})-(\d{2})$/);
@@ -151,6 +152,10 @@ function buildAddressMemoryRecapReply(input) {
}
async function runAssistantLivingChatRuntime(input) {
const userMessage = String(input.userMessage ?? "");
const continuitySnapshot = (0, assistantContinuityPolicy_1.resolveAssistantContinuitySnapshot)({
sessionItems: input.sessionItems,
toNonEmptyString: input.toNonEmptyString
});
const dataScopeMetaQuery = input.hasAssistantDataScopeMetaQuestionSignal(userMessage);
const capabilityMetaQuery = input.shouldHandleAsAssistantCapabilityMetaQuery(userMessage);
const destructiveSignal = input.hasDestructiveDataActionSignal(userMessage);
@@ -164,9 +169,13 @@ async function runAssistantLivingChatRuntime(input) {
let livingChatGroundingGuardApplied = false;
let livingChatGroundingGuardReason = null;
let livingChatProactiveScopeOfferApplied = false;
let knownOrganizations = input.mergeKnownOrganizations(input.sessionScope.knownOrganizations ?? []);
const continuityActiveOrganization = input.toNonEmptyString(continuitySnapshot.activeOrganization);
let knownOrganizations = input.mergeKnownOrganizations([
...(Array.isArray(input.sessionScope.knownOrganizations) ? input.sessionScope.knownOrganizations : []),
continuityActiveOrganization
]);
let selectedOrganization = input.toNonEmptyString(input.sessionScope.selectedOrganization);
let activeOrganization = input.toNonEmptyString(input.sessionScope.activeOrganization);
let activeOrganization = input.toNonEmptyString(input.sessionScope.activeOrganization) ?? continuityActiveOrganization;
const memoryRecapContext = (0, assistantMemoryRecapPolicy_1.resolveAssistantLivingChatMemoryContext)({
modeDecisionReason: input.modeDecision?.reason ?? null,
sessionItems: input.sessionItems
@@ -265,6 +274,7 @@ async function runAssistantLivingChatRuntime(input) {
}
const shouldOfferProactiveOrganizationScope = !selectedOrganization &&
!activeOrganization &&
!continuitySnapshot.hasGroundedAddressContext &&
!hasPriorAssistantTurn(input.sessionItems) &&
input.modeDecision?.mode === "chat" &&
input.hasLivingChatSignal(userMessage);
@@ -330,6 +340,8 @@ async function runAssistantLivingChatRuntime(input) {
? input.mergeKnownOrganizations(dataScopeProbe.organizations)
: [],
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,
@@ -3,6 +3,7 @@ import {
buildInventoryHistoryCapabilityFollowupReply as buildInventoryHistoryCapabilityFollowupReplyFromPolicy,
resolveAssistantLivingChatMemoryContext
} from "./assistantMemoryRecapPolicy";
import { resolveAssistantContinuitySnapshot } from "./assistantContinuityPolicy";
export interface AssistantLivingChatSessionScopeInput {
knownOrganizations?: unknown[];
@@ -248,6 +249,10 @@ export async function runAssistantLivingChatRuntime(
input: AssistantLivingChatRuntimeInput
): Promise<AssistantLivingChatRuntimeOutput> {
const userMessage = String(input.userMessage ?? "");
const continuitySnapshot = resolveAssistantContinuitySnapshot({
sessionItems: input.sessionItems,
toNonEmptyString: input.toNonEmptyString
});
const dataScopeMetaQuery = input.hasAssistantDataScopeMetaQuestionSignal(userMessage);
const capabilityMetaQuery = input.shouldHandleAsAssistantCapabilityMetaQuery(userMessage);
const destructiveSignal = input.hasDestructiveDataActionSignal(userMessage);
@@ -262,9 +267,13 @@ export async function runAssistantLivingChatRuntime(
let livingChatGroundingGuardApplied = false;
let livingChatGroundingGuardReason: string | null = null;
let livingChatProactiveScopeOfferApplied = false;
let knownOrganizations = input.mergeKnownOrganizations(input.sessionScope.knownOrganizations ?? []);
const continuityActiveOrganization = input.toNonEmptyString(continuitySnapshot.activeOrganization);
let knownOrganizations = input.mergeKnownOrganizations([
...(Array.isArray(input.sessionScope.knownOrganizations) ? input.sessionScope.knownOrganizations : []),
continuityActiveOrganization
]);
let selectedOrganization = input.toNonEmptyString(input.sessionScope.selectedOrganization);
let activeOrganization = input.toNonEmptyString(input.sessionScope.activeOrganization);
let activeOrganization = input.toNonEmptyString(input.sessionScope.activeOrganization) ?? continuityActiveOrganization;
const memoryRecapContext = resolveAssistantLivingChatMemoryContext({
modeDecisionReason: input.modeDecision?.reason ?? null,
sessionItems: input.sessionItems
@@ -362,6 +371,7 @@ export async function runAssistantLivingChatRuntime(
const shouldOfferProactiveOrganizationScope =
!selectedOrganization &&
!activeOrganization &&
!continuitySnapshot.hasGroundedAddressContext &&
!hasPriorAssistantTurn(input.sessionItems) &&
input.modeDecision?.mode === "chat" &&
input.hasLivingChatSignal(userMessage);
@@ -432,6 +442,8 @@ export async function runAssistantLivingChatRuntime(
? 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,
@@ -219,4 +219,39 @@ describe("assistant living chat runtime adapter", () => {
expect(output.debug?.living_chat_response_source).toBe("deterministic_memory_recap_contract");
expect(executeLlmChat).not.toHaveBeenCalled();
});
it("uses continuity-backed active organization for organization-fact boundary even when session scope is empty", async () => {
const executeLlmChat = vi.fn(async () => "raw-llm");
const input = buildRuntimeInput({
userMessage: "сколько лет альтернативе плюс",
sessionItems: [
{
role: "assistant",
debug: {
execution_lane: "address_query",
answer_grounding_check: {
status: "grounded"
},
detected_intent: "receivables_confirmed_as_of_date",
extracted_filters: {
organization: "ООО Альтернатива Плюс",
as_of_date: "2020-03-31"
}
}
}
],
hasOrganizationFactLookupSignal: () => true,
buildAssistantOrganizationFactBoundaryReply: (organization: string | null) => `org-boundary:${organization ?? "none"}`,
executeLlmChat
});
const output = await runAssistantLivingChatRuntime(input);
expect(output.handled).toBe(true);
expect(output.chatText).toBe("org-boundary:ООО Альтернатива Плюс");
expect(output.debug?.living_chat_response_source).toBe("deterministic_organization_fact_boundary");
expect(output.debug?.assistant_active_organization).toBe("ООО Альтернатива Плюс");
expect(output.debug?.living_chat_continuity_grounded_context_detected).toBe(true);
expect(output.debug?.living_chat_continuity_active_organization).toBe("ООО Альтернатива Плюс");
expect(executeLlmChat).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,114 @@
{
"suite_id": "assistant_saved_session_runtime_job-knjH7nTx3E",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_runtime_v0_1",
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "saved_user_sessions_runtime",
"title": "БОЛЬШОЙ ОБЩИЙ Ручная сессия 16.04.2026, 21:26:06",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "приветик - че как там дела"
},
{
"user_message": "расскажи что можешь интересного"
},
{
"user_message": "кайф - что там на складе по остаткам?"
},
{
"user_message": "а исторические остатки на другие даты умеешь?"
},
{
"user_message": "давай на июль 2017"
},
{
"user_message": "март 2016"
},
{
"user_message": "По выбранному объекту \"Рабочая станция универсального специалиста (индивидуальное изготовление)\": где взяли это?"
},
{
"user_message": "а кому продали?"
},
{
"user_message": "у тебя написано кто контрагент: рабочая станция - это ошибка?"
},
{
"user_message": "ндс можешь прикинуть на дату покупки рабочей станции?"
},
{
"user_message": "а какой ндс мы должны сгрузить на март 2020?"
},
{
"user_message": "прикинь какой ндс нам надо заплатить на февраль 2017"
},
{
"user_message": "кто у нас самый доходный клиент за все время"
},
{
"user_message": "кто нам должен денег на май 2017"
},
{
"user_message": "а какой ндс мы должны примерно заплатить за этот период?"
},
{
"user_message": "мы должны комуто денег на сегодня?"
},
{
"user_message": "а нам?"
},
{
"user_message": "какой у нас самый доходный год"
},
{
"user_message": "а за 2017 мы скок заработали?"
},
{
"user_message": "сколько вообще денег мы заработали за все время?"
},
{
"user_message": "ты умеешь считать дельту по договорам?"
},
{
"user_message": "по чепурнову покажи все доки"
},
{
"user_message": "а по свк"
},
{
"user_message": "а сейчас у нас есть что на складе?"
},
{
"user_message": "что нам отгружал чепурнов? какой товар или услугу?"
},
{
"user_message": "какие остатки на складе на сегодня"
},
{
"user_message": "остатки на март 2016"
},
{
"user_message": "хвосты покажи по счету 60 на август 2022"
},
{
"user_message": "Есть ли остатки товара, которые закупались очень давно"
},
{
"user_message": "Какие конкретно номенклатуры формируют остаток по складу на май 2020"
},
{
"user_message": "а по Альтернативе Плюс сколько лет активности в базе 1С?"
}
]
}
]
}