АДРЕСНЫЙ РЕЖИМ - авторан история - базовая версия

This commit is contained in:
2026-04-09 12:34:10 +03:00
parent df29798fa2
commit edfa09c9af
31 changed files with 5261 additions and 2392 deletions
@@ -499,7 +499,12 @@ function collectAnalyticsStrings(row: Record<string, unknown>): string[] {
"Counterparty",
"Контрагент",
"Contract",
"Договор"
"Договор",
"Organization",
"Организация",
"ОрганизацияПредставление",
"organization",
"organization_name"
];
const collected: string[] = [];
@@ -512,7 +517,14 @@ function collectAnalyticsStrings(row: Record<string, unknown>): string[] {
for (const [key, rawValue] of Object.entries(row)) {
const lowerKey = key.toLowerCase();
if (lowerKey.includes("subconto") || lowerKey.includes("субконто") || lowerKey.includes("контраг") || lowerKey.includes("договор")) {
if (
lowerKey.includes("subconto") ||
lowerKey.includes("субконто") ||
lowerKey.includes("контраг") ||
lowerKey.includes("договор") ||
lowerKey.includes("organization") ||
lowerKey.includes("организац")
) {
const value = valueAsString(rawValue).trim();
if (value) {
collected.push(value);
@@ -624,6 +636,15 @@ function applyAddressFilters(rows: NormalizedAddressRow[], filters: AddressFilte
}
}
if (filters.organization && String(filters.organization).trim()) {
const needle = String(filters.organization);
const before = filtered.length;
filtered = filtered.filter((row) => matchesAnchorText(rowSearchableText(row), needle));
if (before > 0 && filtered.length === 0 && mismatchReason === null) {
mismatchReason = "organization_anchor_not_matched_in_materialized_rows";
}
}
if (filters.document_ref && String(filters.document_ref).trim()) {
const needle = String(filters.document_ref);
const before = filtered.length;
@@ -338,11 +338,16 @@ function mergeFollowupFilters(
const previousCounterparty = toNonEmptyString(previous.counterparty);
const previousContract = toNonEmptyString(previous.contract);
const previousAccount = toNonEmptyString(previous.account);
const previousOrganization = toNonEmptyString(previous.organization);
const previousAsOfDate = toNonEmptyString(previous.as_of_date);
const previousPeriodFrom = toNonEmptyString(previous.period_from);
const previousPeriodTo = toNonEmptyString(previous.period_to);
const allTimeRequested = hasAllTimeHint(userMessage);
const sameDateRequested = hasSameDateHint(userMessage);
if (!toNonEmptyString(merged.organization) && previousOrganization) {
merged.organization = previousOrganization;
reasons.push("organization_from_followup_context");
}
if (
intent === "list_documents_by_counterparty" ||
@@ -2681,6 +2681,12 @@ function resolveAddressFollowupCarryoverContext(userMessage, items, alternateMes
previousFilters.counterparty = historicalCounterparty;
}
}
if (!toNonEmptyString(previousFilters.organization)) {
const historicalOrganization = findRecentAddressFilterValue(items, "organization");
if (historicalOrganization) {
previousFilters.organization = historicalOrganization;
}
}
if (!previousIntent && !previousAnchor && Object.keys(previousFilters).length === 0) {
return null;
}
@@ -3600,7 +3606,7 @@ export function resolveAssistantOrchestrationDecision(input) {
}
function hasStrongDataIntentSignal(text) {
const lower = String(text ?? "").toLowerCase();
return /(база|док|документ|проводк|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|оборот|баланс|период|месяц|год|инн|mcp|bank|counterparty|contract|document|ledger|posting|account)/i.test(lower);
return /(база|док|документ|проводк|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|оборот|баланс|период|месяц|год|инн|mcp|bank|counterparty|contract|document|ledger|posting|account|организац|компан|контор|фирм)/i.test(lower);
}
function hasDataRetrievalRequestSignal(text) {
const lower = compactWhitespace(String(text ?? "").toLowerCase());
@@ -3612,7 +3618,7 @@ function hasDataRetrievalRequestSignal(text) {
if (!hasExplicitRetrievalAction && !hasInterrogativeRetrievalAction) {
return false;
}
const hasRetrievalObject = /(1с|база|док|документ|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|период|месяц|год|инн|bank|counterparty|contract|document|account|balance|ledger|posting)/i.test(lower);
const hasRetrievalObject = /(1с|база|док|документ|контрагент|договор|контракт|счет|сч[её]т|остат|сальдо|хвост|платеж|плат[её]ж|операц|поставщик|клиент|заказчик|дебитор|кредитор|период|месяц|год|инн|bank|counterparty|contract|document|account|balance|ledger|posting|организац|компан|контор|фирм|возраст|дата\s+регистрац|регистрац|основан)/i.test(lower);
if (!hasRetrievalObject) {
return false;
}
@@ -3622,6 +3628,77 @@ function hasDataRetrievalRequestSignal(text) {
const hasMetaCapabilityShape = /(?:мож(?:ем|ешь|ете|но)|уме(?:ешь|ете)|доступ|подключ|чья|как\s+называ(?:ет|ется)|работ(?:ать|аем|аешь|аете)|в\s+тебе|у\s+тебя)/i.test(lower);
return !hasMetaCapabilityShape;
}
function hasOrganizationFactLookupSignal(text) {
const repaired = repairAddressMojibake(String(text ?? ""));
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
if (!normalized) {
return false;
}
const hasFactCue = /(?:возраст|сколько\s+лет|дата\s+регистрац|когда\s+(?:зарегистр|создан|основан)|год\s+регистрац|год\s+основан|с\s+какого\s+года|when\s+was\s+(?:it\s+)?(?:registered|founded|created))/i.test(normalized);
if (!hasFactCue) {
return false;
}
return /(?:организац|компан|контор|фирм|ооо|ао|зао|ип|альтернатив|лайсвуд|райм|organization|company)/i.test(normalized);
}
function findLastAssistantLivingChatDebug(items) {
if (!Array.isArray(items)) {
return null;
}
for (let index = items.length - 1; index >= 0; index -= 1) {
const item = items[index];
if (!item || item.role !== "assistant") {
continue;
}
if (item.debug && typeof item.debug === "object") {
return item.debug;
}
}
return null;
}
function hasOrganizationFactFollowupSignal(userMessage, items) {
const repaired = repairAddressMojibake(String(userMessage ?? ""));
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
if (!normalized) {
return false;
}
if (hasOrganizationFactLookupSignal(normalized)) {
return false;
}
const hasFollowupCue = /(?:^|\s)(?:давай|го|погнали|ок(?:ей)?|хорошо|принято|подтверждаю|запрашивай|запроси|проверь|продолжай|ну\s+давай|да\s+давай)(?=$|[\s,.!?;:])/iu.test(normalized);
if (!hasFollowupCue) {
return false;
}
const lastDebug = findLastAssistantLivingChatDebug(items);
const lastSource = toNonEmptyString(lastDebug?.living_chat_response_source);
const lastGuardReason = toNonEmptyString(lastDebug?.living_chat_grounding_guard_reason);
const inOrganizationFactBoundary = lastSource === "deterministic_organization_fact_boundary" ||
lastSource === "deterministic_organization_fact_boundary_followup" ||
lastGuardReason === "organization_fact_without_live_source_blocked";
return inOrganizationFactBoundary;
}
function shouldEmitOrganizationSelectionReply(userMessage, selectedOrganization) {
const selected = normalizeOrganizationScopeValue(selectedOrganization);
if (!selected) {
return false;
}
const repaired = repairAddressMojibake(String(userMessage ?? ""));
const normalized = compactWhitespace(repaired.toLowerCase()).replace(/ё/g, "е");
if (!normalized) {
return false;
}
if (hasOrganizationFactLookupSignal(normalized) || hasDataRetrievalRequestSignal(normalized) || hasStrongDataIntentSignal(normalized)) {
return false;
}
const hasAnalyticalCue = /(?:какой|какая|какие|когда|сколько|кто|почему|зачем|возраст|дата|регистрац|ндс|налог|контракт|договор|документ|операц|оборот|сумм|остат|сальдо|founded|registered|created)/i.test(normalized);
if (hasAnalyticalCue) {
return false;
}
const hasSelectionCue = /(?:давай|го|погнали|ок(?:ей)?|хорошо|отлично|берем|выберем|выбираем|переключ(?:им|аем|ай)|фиксир|работаем|обсудим|тогда)\b/i.test(normalized);
if (hasSelectionCue) {
return true;
}
return normalized.length <= 36 && !/[?]/.test(String(userMessage ?? ""));
}
function hasOperationalAdminActionRequestSignal(text) {
const lower = compactWhitespace(String(text ?? "").toLowerCase()).replace(/ё/g, "е");
const normalized = lower.replace(/\b1\s*[cс]\b/giu, "1с");
@@ -3819,6 +3896,319 @@ function normalizeScopeLabel(value) {
function normalizeScopeKey(value) {
return repairAddressMojibake(String(value ?? "")).toLowerCase().replace(/ё/g, "е");
}
const ORGANIZATION_SCOPE_STOPWORDS = new Set([
"ооо",
"ao",
"ао",
"зао",
"ип",
"llc",
"ltd",
"company",
"компания",
"организация",
"организации",
"контора",
"конторы",
"фирма",
"фирмы",
"по",
"для",
"над",
"под",
"без",
"с",
"со",
"в",
"во",
"на",
"и",
"или",
"а",
"но",
"не",
"мы",
"нам",
"наш",
"наша",
"наше",
"наши",
"ты",
"тебе",
"твой",
"сейчас",
"щас",
"тут",
"вот",
"давай",
"го",
"погнали",
"тогда",
"обсудим",
"обсуждать",
"работать",
"работаем",
"работаешь",
"работаете",
"можем",
"можно",
"какая",
"какой",
"какие",
"чья",
"чье",
"чьи"
]);
function normalizeOrganizationScopeValue(value) {
const normalized = normalizeScopeLabel(value);
if (!normalized) {
return null;
}
const unwrapped = normalized
.replace(/^\\+|\\+$/g, "")
.replace(/^"+|"+$/g, "")
.replace(/^'+|'+$/g, "")
.trim();
return unwrapped ? unwrapped : null;
}
function normalizeOrganizationScopeSearchText(value) {
const source = normalizeScopeKey(value);
return source
.replace(/[^a-zа-я0-9]+/giu, " ")
.replace(/\s+/g, " ")
.trim();
}
function tokenizeOrganizationScope(value) {
const normalized = normalizeOrganizationScopeSearchText(value);
if (!normalized) {
return [];
}
return normalized
.split(" ")
.map((token) => token.trim())
.filter((token) => token.length >= 3 && !ORGANIZATION_SCOPE_STOPWORDS.has(token));
}
function organizationTokenVariants(token) {
const source = String(token ?? "").trim().toLowerCase();
if (!source) {
return [];
}
const variants = new Set([source]);
const withoutLongEnding = source.replace(/(?:ами|ями|ого|ему|ому|ыми|ими|иях|ях|ах|ей|ой|ом|ем|ам|ям|ую|юю|ая|яя|ое|ее|ые|ие|ов|ев|ий|ый|ой)$/iu, "");
if (withoutLongEnding.length >= 4) {
variants.add(withoutLongEnding);
}
const withoutShortEnding = source.replace(/[аеёиоуыэюя]$/iu, "");
if (withoutShortEnding.length >= 4) {
variants.add(withoutShortEnding);
}
return Array.from(variants);
}
function scoreOrganizationMentionInMessage(message, organization) {
const messageNorm = normalizeOrganizationScopeSearchText(message);
const organizationNorm = normalizeOrganizationScopeSearchText(organization);
if (!messageNorm || !organizationNorm) {
return 0;
}
if (messageNorm.includes(organizationNorm)) {
return 10_000 + organizationNorm.length;
}
const organizationTokens = tokenizeOrganizationScope(organizationNorm);
if (organizationTokens.length === 0) {
return 0;
}
const messageTokens = tokenizeOrganizationScope(messageNorm);
if (messageTokens.length === 0) {
return 0;
}
let matchedTokens = 0;
let score = 0;
for (const token of organizationTokens) {
const variants = organizationTokenVariants(token);
let matched = false;
let variantScore = 0;
for (const variant of variants) {
if (!variant) {
continue;
}
if (messageNorm.includes(variant)) {
matched = true;
variantScore = Math.max(variantScore, variant.length * 5);
continue;
}
const fuzzyMatched = messageTokens.some((messageToken) => {
if (messageToken === variant) {
return true;
}
if (messageToken.length >= 5 && variant.length >= 5) {
return messageToken.startsWith(variant) || variant.startsWith(messageToken);
}
return false;
});
if (fuzzyMatched) {
matched = true;
variantScore = Math.max(variantScore, Math.max(20, variant.length * 3));
}
}
if (matched) {
matchedTokens += 1;
score += variantScore > 0 ? variantScore : 10;
}
}
if (matchedTokens === 0) {
return 0;
}
if (matchedTokens === organizationTokens.length) {
score += 400;
} else {
score += matchedTokens * 50;
}
return score;
}
function parseOrganizationsFromDataScopeAssistantText(text) {
const source = repairAddressMojibake(String(text ?? ""));
if (!source) {
return [];
}
const extracted = [];
const singleMatch = source.match(/доступна\s+организация:\s*([^.\n]+)/iu);
if (singleMatch) {
const value = normalizeOrganizationScopeValue(singleMatch[1]);
if (value) {
extracted.push(value);
}
}
const multiMatch = source.match(/доступны\s+организац(?:ии|ия)\s*(?:\(\d+\))?:\s*([^.\n]+)/iu);
if (multiMatch) {
const parts = String(multiMatch[1] ?? "")
.split(",")
.map((item) => normalizeOrganizationScopeValue(item))
.filter(Boolean);
extracted.push(...parts);
}
return Array.from(new Set(extracted));
}
function mergeKnownOrganizations(values) {
const dedup = new Map();
for (const raw of Array.isArray(values) ? values : []) {
const normalized = normalizeOrganizationScopeValue(raw);
if (!normalized) {
continue;
}
const key = normalizeOrganizationScopeSearchText(normalized);
if (!key) {
continue;
}
if (!dedup.has(key)) {
dedup.set(key, normalized);
}
}
return Array.from(dedup.values()).slice(0, 20);
}
function extractKnownOrganizationsFromHistory(items) {
const collected = [];
for (let index = (Array.isArray(items) ? items.length : 0) - 1; index >= 0; index -= 1) {
const item = items[index];
if (!item || item.role !== "assistant") {
continue;
}
const debug = item.debug && typeof item.debug === "object" ? item.debug : null;
if (debug) {
const directFromProbe = Array.isArray(debug.living_chat_data_scope_probe_organizations)
? debug.living_chat_data_scope_probe_organizations
: [];
const knownFromDebug = Array.isArray(debug.assistant_known_organizations)
? debug.assistant_known_organizations
: [];
if (directFromProbe.length > 0 || knownFromDebug.length > 0) {
collected.push(...directFromProbe, ...knownFromDebug);
}
}
const parsedFromText = parseOrganizationsFromDataScopeAssistantText(item.text);
if (parsedFromText.length > 0) {
collected.push(...parsedFromText);
}
if (collected.length >= 20) {
break;
}
}
return mergeKnownOrganizations(collected);
}
function findLastAssistantActiveOrganization(items) {
for (let index = (Array.isArray(items) ? items.length : 0) - 1; index >= 0; index -= 1) {
const item = items[index];
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
continue;
}
const direct = normalizeOrganizationScopeValue(item.debug.assistant_active_organization);
if (direct) {
return direct;
}
const selected = normalizeOrganizationScopeValue(item.debug.living_chat_selected_organization);
if (selected) {
return selected;
}
}
return null;
}
function resolveOrganizationSelectionFromMessage(userMessage, knownOrganizations) {
const known = mergeKnownOrganizations(knownOrganizations);
if (!userMessage || known.length === 0) {
return null;
}
const messageNorm = normalizeOrganizationScopeSearchText(userMessage);
if (!messageNorm) {
return null;
}
const scored = known
.map((organization) => ({
organization,
score: scoreOrganizationMentionInMessage(messageNorm, organization)
}))
.filter((item) => item.score > 0)
.sort((a, b) => b.score - a.score || a.organization.length - b.organization.length);
if (scored.length === 0) {
return null;
}
const best = scored[0];
const second = scored[1];
if (best.score < 90) {
return null;
}
if (second && second.score === best.score) {
return null;
}
return best.organization;
}
function resolveSessionOrganizationScopeContext(userMessage, items) {
const knownOrganizations = extractKnownOrganizationsFromHistory(items);
const selectedOrganization = resolveOrganizationSelectionFromMessage(userMessage, knownOrganizations);
const lastActiveOrganization = findLastAssistantActiveOrganization(items);
const activeOrganization = selectedOrganization ?? normalizeOrganizationScopeValue(lastActiveOrganization);
return {
knownOrganizations,
selectedOrganization,
activeOrganization
};
}
function mergeFollowupContextWithOrganizationScope(followupContext, organization) {
const normalizedOrganization = normalizeOrganizationScopeValue(organization);
const base = followupContext && typeof followupContext === "object" ? { ...followupContext } : {};
if (!normalizedOrganization) {
return followupContext && typeof followupContext === "object" ? base : null;
}
const previousFilters = base.previous_filters && typeof base.previous_filters === "object"
? { ...base.previous_filters }
: {};
if (!toNonEmptyString(previousFilters.organization)) {
previousFilters.organization = normalizedOrganization;
}
base.previous_filters = previousFilters;
return base;
}
export function resolveSessionOrganizationScopeContextForTests(userMessage, items) {
return resolveSessionOrganizationScopeContext(userMessage, items);
}
function normalizeGuidValue(value) {
const source = normalizeScopeLabel(value);
if (!source) {
@@ -4193,6 +4583,26 @@ function buildAssistantDataScopeContractReply(scopeProbe = null) {
"Если подключено несколько баз, для автосписка нужен MCP-метод метаданных (перечень баз/организаций); без него можно анализировать только активный контур запросов."
].join(" ");
}
function buildAssistantDataScopeSelectionReply(organization) {
const selected = normalizeOrganizationScopeValue(organization) ?? String(organization ?? "").trim();
return [
`Отлично, фиксирую рабочую организацию: ${selected}.`,
"Дальше буду держать этот контур как активный, пока вы не переключите организацию."
].join(" ");
}
function buildAssistantOrganizationFactBoundaryReply(organization) {
const selected = normalizeOrganizationScopeValue(organization) ?? String(organization ?? "").trim();
if (selected) {
return [
`По организации ${selected} не буду называть дату/возраст без live-подтвержденного источника.`,
"Если нужно, запрошу факт из 1С и верну только подтвержденный ответ."
].join(" ");
}
return [
"Не буду называть дату/возраст организации без live-подтвержденного источника.",
"Сначала получу факт из 1С, потом дам точный ответ."
].join(" ");
}
function buildAssistantOperationalBoundaryReply() {
return [
"Понимаю, что ситуация срочная.",
@@ -4256,6 +4666,45 @@ function applyLivingChatScriptGuard(chatText, userMessage) {
reason: "unexpected_cjk_fragment_fallback"
};
}
function applyLivingChatGroundingGuard(input) {
const userMessage = String(input?.userMessage ?? "");
const chatText = String(input?.chatText ?? "").trim();
const organization = toNonEmptyString(input?.organization);
if (!chatText) {
return {
text: chatText,
applied: false,
reason: null
};
}
if (!hasOrganizationFactLookupSignal(userMessage)) {
return {
text: chatText,
applied: false,
reason: null
};
}
if (/(?:не\s+могу|не\s+вижу|после\s+проверки|live|подтвержден)/i.test(chatText)) {
return {
text: chatText,
applied: false,
reason: null
};
}
const hasSpecificUnverifiedFact = /(?:\b\d{1,2}[./-]\d{1,2}[./-](?:\d{2}|\d{4})\b|\b(?:19|20)\d{2}\b|\b\d+\s+лет\b)/i.test(chatText);
if (!hasSpecificUnverifiedFact) {
return {
text: chatText,
applied: false,
reason: null
};
}
return {
text: buildAssistantOrganizationFactBoundaryReply(organization),
applied: true,
reason: "organization_fact_without_live_source_blocked"
};
}
export function resolveLivingAssistantModeDecision(input) {
const userMessage = String(input?.userMessage ?? "");
if (input?.addressLaneTriggered) {
@@ -4334,7 +4783,9 @@ export class AssistantService {
async handleMessage(payload) {
const session = this.sessions.ensureSession(payload.session_id);
const sessionId = session.session_id;
const userMessage = String(payload.user_message ?? payload.message ?? "").trim();
const userMessageRaw = String(payload.user_message ?? payload.message ?? "").trim();
const repairedUserMessage = compactWhitespace(repairAddressMojibake(userMessageRaw));
const userMessage = repairedUserMessage || userMessageRaw;
const userItem = {
message_id: `msg-${(0, nanoid_1.nanoid)(10)}`,
session_id: sessionId,
@@ -4346,6 +4797,7 @@ export class AssistantService {
debug: null
};
this.sessions.appendItem(sessionId, userItem);
const sessionOrganizationScope = resolveSessionOrganizationScopeContext(userMessage, session.items);
const finalizeAddressLaneResponse = (addressLane, effectiveAddressUserMessage, carryoverMeta = null, llmPreDecomposeMeta = null) => {
const safeAddressReply = sanitizeOutgoingAssistantText(addressLane.reply_text);
const debug = buildAddressDebugPayload(addressLane.debug, llmPreDecomposeMeta);
@@ -4353,6 +4805,15 @@ export class AssistantService {
if (followupOffer) {
debug.address_followup_offer = followupOffer;
}
const debugKnownOrganizations = mergeKnownOrganizations(sessionOrganizationScope.knownOrganizations);
const debugActiveOrganization = toNonEmptyString(debug?.extracted_filters?.organization) ??
toNonEmptyString(sessionOrganizationScope.activeOrganization);
if (debugKnownOrganizations.length > 0) {
debug.assistant_known_organizations = debugKnownOrganizations;
}
if (debugActiveOrganization) {
debug.assistant_active_organization = debugActiveOrganization;
}
const assistantItem = {
message_id: `msg-${(0, nanoid_1.nanoid)(10)}`,
session_id: sessionId,
@@ -4458,6 +4919,11 @@ export class AssistantService {
let livingChatSource = "llm_chat";
let livingChatScriptGuardApplied = false;
let livingChatScriptGuardReason = null;
let livingChatGroundingGuardApplied = false;
let livingChatGroundingGuardReason = null;
let knownOrganizations = mergeKnownOrganizations(sessionOrganizationScope.knownOrganizations);
let selectedOrganization = toNonEmptyString(sessionOrganizationScope.selectedOrganization);
let activeOrganization = toNonEmptyString(sessionOrganizationScope.activeOrganization);
if (capabilityMetaQuery && (destructiveSignal || dangerSignal)) {
chatText = buildAssistantSafetyRefusalReply();
livingChatSource = "deterministic_safety_refusal";
@@ -4465,10 +4931,35 @@ export class AssistantService {
else if (dataScopeMetaQuery) {
dataScopeProbe = await resolveAssistantDataScopeProbe();
chatText = buildAssistantDataScopeContractReply(dataScopeProbe);
knownOrganizations = mergeKnownOrganizations([
...knownOrganizations,
...(Array.isArray(dataScopeProbe?.organizations) ? dataScopeProbe.organizations : [])
]);
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) && hasOrganizationFactLookupSignal(userMessage)) {
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
chatText = buildAssistantOrganizationFactBoundaryReply(scopedOrganization);
activeOrganization = scopedOrganization ?? activeOrganization;
livingChatSource = "deterministic_organization_fact_boundary";
}
else if ((selectedOrganization || activeOrganization) && hasOrganizationFactFollowupSignal(userMessage, session.items)) {
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
chatText = buildAssistantOrganizationFactBoundaryReply(scopedOrganization);
activeOrganization = scopedOrganization ?? activeOrganization;
livingChatSource = "deterministic_organization_fact_boundary_followup";
}
else if (!capabilityMetaQuery && shouldEmitOrganizationSelectionReply(userMessage, selectedOrganization ?? activeOrganization)) {
const scopedOrganization = selectedOrganization ?? activeOrganization ?? null;
chatText = buildAssistantDataScopeSelectionReply(scopedOrganization);
activeOrganization = scopedOrganization ?? activeOrganization;
livingChatSource = "deterministic_data_scope_selection_contract";
}
else if (capabilityMetaQuery && operationalSignal && !hasAssistantCapabilityQuestionSignal(userMessage)) {
chatText = buildAssistantOperationalBoundaryReply();
livingChatSource = "deterministic_operational_boundary";
@@ -4508,6 +4999,17 @@ export class AssistantService {
livingChatScriptGuardReason = scriptGuard.reason;
livingChatSource = "llm_chat_script_guard";
}
const groundingGuard = applyLivingChatGroundingGuard({
userMessage,
chatText,
organization: activeOrganization ?? selectedOrganization ?? null
});
chatText = groundingGuard.text;
if (groundingGuard.applied) {
livingChatGroundingGuardApplied = true;
livingChatGroundingGuardReason = groundingGuard.reason;
livingChatSource = "llm_chat_grounding_guard";
}
}
if (!chatText) {
return null;
@@ -4525,12 +5027,20 @@ export class AssistantService {
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_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)
? mergeKnownOrganizations(dataScopeProbe.organizations)
: [],
living_chat_data_scope_probe_error: dataScopeProbe?.error ?? null,
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,
@@ -4716,9 +5226,10 @@ export class AssistantService {
};
};
const runAddressLaneAttempt = async (messageUsed, carryMeta) => {
if (carryMeta?.followupContext) {
const scopedFollowupContext = mergeFollowupContextWithOrganizationScope(carryMeta?.followupContext ?? null, sessionOrganizationScope.activeOrganization);
if (scopedFollowupContext) {
return this.addressQueryService.tryHandle(messageUsed, {
followupContext: carryMeta.followupContext
followupContext: scopedFollowupContext
});
}
return this.addressQueryService.tryHandle(messageUsed);