173 lines
8.5 KiB
JavaScript
173 lines
8.5 KiB
JavaScript
"use strict";
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.createAssistantBoundaryPolicy = createAssistantBoundaryPolicy;
|
||
function normalizeSelectedOrganization(value, normalizeOrganizationScopeValue) {
|
||
return normalizeOrganizationScopeValue(value) ?? String(value ?? "").trim();
|
||
}
|
||
function containsCjkChars(text) {
|
||
const source = String(text ?? "");
|
||
if (!source) {
|
||
return false;
|
||
}
|
||
return /[\u3400-\u9FFF\uF900-\uFAFF]/u.test(source);
|
||
}
|
||
function containsLetterLikeChars(text) {
|
||
const source = String(text ?? "");
|
||
if (!source) {
|
||
return false;
|
||
}
|
||
return /[A-Za-z\u0400-\u04FF]/u.test(source);
|
||
}
|
||
function createAssistantBoundaryPolicy(deps) {
|
||
const defaultChannel = String(deps.activeMcpChannel ?? "default");
|
||
function buildAssistantDataScopeContractReply(scopeProbe = null) {
|
||
const channel = String(scopeProbe?.channel ?? defaultChannel);
|
||
const organizations = Array.isArray(scopeProbe?.organizations)
|
||
? scopeProbe.organizations
|
||
.map((item) => String(item ?? "").trim())
|
||
.filter((item) => item.length > 0)
|
||
: [];
|
||
if (organizations.length === 1) {
|
||
return [
|
||
`Сейчас в активном MCP-канале \`${channel}\` доступна организация: ${organizations[0]}.`,
|
||
"Работаю в read-only режиме. Могу сразу показать по этой организации документы, операции, договоры или остатки."
|
||
].join(" ");
|
||
}
|
||
if (organizations.length > 1) {
|
||
const preview = organizations.slice(0, 10).join(", ");
|
||
return [
|
||
`Сейчас в активном MCP-канале \`${channel}\` доступны организации (${organizations.length}): ${preview}.`,
|
||
"Работаю в read-only режиме. Скажи, по какой организации смотреть документы/операции."
|
||
].join(" ");
|
||
}
|
||
if (scopeProbe?.status === "unresolved_with_error" && scopeProbe?.error) {
|
||
return [
|
||
`Не смог прочитать название организации из live MCP-канала \`${channel}\`: ${scopeProbe.error}.`,
|
||
"Работаю в read-only режиме и вижу только данные активного контура. Проверь подключение MCP/1С, после этого сразу назову контур."
|
||
].join(" ");
|
||
}
|
||
return [
|
||
`Работаю в read-only режиме и вижу только те данные, которые отдает текущий MCP-канал \`${channel}\`.`,
|
||
"Словарь компаний не зашит в код: рабочий контур определяется live-подключением.",
|
||
"Если подключено несколько баз, для автосписка нужен MCP-метод метаданных (перечень баз/организаций); без него можно анализировать только активный контур запросов."
|
||
].join(" ");
|
||
}
|
||
function buildAssistantDataScopeSelectionReply(organization) {
|
||
const selected = normalizeSelectedOrganization(organization, deps.normalizeOrganizationScopeValue);
|
||
return [
|
||
`Отлично, фиксирую рабочую организацию: ${selected}.`,
|
||
"Дальше буду держать этот контур как активный, пока вы не переключите организацию."
|
||
].join(" ");
|
||
}
|
||
function buildAssistantOrganizationFactBoundaryReply(organization) {
|
||
const selected = normalizeSelectedOrganization(organization, deps.normalizeOrganizationScopeValue);
|
||
if (selected) {
|
||
return [
|
||
`По организации ${selected} не буду называть дату/возраст без live-подтвержденного источника.`,
|
||
"Если нужно, запрошу факт из 1С и верну только подтвержденный ответ."
|
||
].join(" ");
|
||
}
|
||
return [
|
||
"Не буду называть дату/возраст организации без live-подтвержденного источника.",
|
||
"Сначала получу факт из 1С, потом дам точный ответ."
|
||
].join(" ");
|
||
}
|
||
function buildAssistantOperationalBoundaryReply() {
|
||
return [
|
||
"Понимаю, что ситуация срочная.",
|
||
"Я не могу сам настраивать 1С или менять базу/конфигурацию.",
|
||
"Могу помочь безопасно: разберем симптомы и подготовим точные шаги для вашего 1С/ИТ-админа."
|
||
].join(" ");
|
||
}
|
||
function buildAssistantSafetyRefusalReply() {
|
||
return [
|
||
"Я не могу помогать с удалением базы или скрытием данных.",
|
||
"Если вам угрожает опасность, срочно звоните 112 и следуйте указаниям экстренных служб.",
|
||
"По 1С могу дать только безопасные диагностические рекомендации."
|
||
].join(" ");
|
||
}
|
||
function applyLivingChatScriptGuard(chatText, userMessage) {
|
||
const source = String(chatText ?? "").trim();
|
||
if (!source) {
|
||
return {
|
||
text: "",
|
||
applied: false,
|
||
reason: null
|
||
};
|
||
}
|
||
if (!containsCjkChars(source) || containsCjkChars(userMessage)) {
|
||
return {
|
||
text: source,
|
||
applied: false,
|
||
reason: null
|
||
};
|
||
}
|
||
const stripped = source
|
||
.replace(/[\u3400-\u9FFF\uF900-\uFAFF]+/gu, "")
|
||
.replace(/[,。、!?;:()【】]/gu, "")
|
||
.replace(/\s{2,}/g, " ")
|
||
.replace(/\s+([,.!?;:])/g, "$1")
|
||
.trim();
|
||
if (stripped && containsLetterLikeChars(stripped)) {
|
||
return {
|
||
text: stripped,
|
||
applied: true,
|
||
reason: "unexpected_cjk_fragment_stripped"
|
||
};
|
||
}
|
||
return {
|
||
text: "Понял. Сформулируйте, что именно нужно по данным 1С, и я помогу по шагам.",
|
||
applied: true,
|
||
reason: "unexpected_cjk_fragment_fallback"
|
||
};
|
||
}
|
||
function applyLivingChatGroundingGuard(input) {
|
||
const userMessage = String(input?.userMessage ?? "");
|
||
const chatText = String(input?.chatText ?? "").trim();
|
||
const organization = deps.toNonEmptyString(input?.organization);
|
||
if (!chatText) {
|
||
return {
|
||
text: chatText,
|
||
applied: false,
|
||
reason: null
|
||
};
|
||
}
|
||
if (!deps.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"
|
||
};
|
||
}
|
||
return {
|
||
buildAssistantDataScopeContractReply,
|
||
buildAssistantDataScopeSelectionReply,
|
||
buildAssistantOrganizationFactBoundaryReply,
|
||
buildAssistantOperationalBoundaryReply,
|
||
buildAssistantSafetyRefusalReply,
|
||
applyLivingChatScriptGuard,
|
||
applyLivingChatGroundingGuard
|
||
};
|
||
}
|