Files
openclaw/integrations/paradiz-web/paradiz-web-agent-server.mjs

150 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
import http from 'node:http';
import { execFile } from 'node:child_process';
import { readFileSync, existsSync } from 'node:fs';
const ENV_PATH = process.env.PARADIZWEB_ENV_PATH || '/home/openclaw/.openclaw/agents/paradizweb/agent/.env';
function loadEnv(path) {
const env = {};
if (!existsSync(path)) return env;
const raw = readFileSync(path, 'utf8');
for (const line of raw.split(/\r?\n/)) {
if (!line || line.trim().startsWith('#') || !line.includes('=')) continue;
const idx = line.indexOf('=');
const key = line.slice(0, idx).trim();
const val = line.slice(idx + 1).trim();
env[key] = val;
}
return env;
}
const env = { ...loadEnv(ENV_PATH), ...process.env };
const PORT = Number(env.PARADIZWEB_UPSTREAM_PORT || 8787);
const HOST = env.PARADIZWEB_UPSTREAM_HOST || '127.0.0.1';
const TOKEN = env.PARADIZWEB_API_TOKEN || '';
const ALLOWED_ORIGIN = env.PARADIZWEB_ALLOWED_ORIGIN || 'https://vparadize.ru';
const AGENT_ID = env.PARADIZWEB_AGENT_ID || 'paradizweb';
if (!TOKEN) {
console.error('PARADIZWEB_API_TOKEN is empty. Set it in .env');
process.exit(1);
}
function sanitizeForWebsite(text) {
const lines = String(text || '').split(/\r?\n/);
const cleaned = lines.filter((line) => {
const t = line.trim();
if (!t) return true;
if (t.startsWith('🔵')) return false;
if (t.startsWith('Лимиты:')) return false;
if (/^Аккаунт:/i.test(t)) return false;
if (/^Модель:/i.test(t)) return false;
if (/^Codex-аккаунт:/i.test(t)) return false;
return true;
}).join('\n').trim();
return cleaned;
}
function isExternalActionRequest(text) {
const t = String(text || '').toLowerCase();
const markers = [
'отправ', 'напиши в telegram', 'напиши в телеграм', 'telegram', 'телеграм',
'whatsapp', 'ватсап', 'vk', 'вк', 'email', 'e-mail', 'почт', 'позвон',
'создай задачу', 'api', 'webhook', 'уведомлен'
];
return markers.some((m) => t.includes(m));
}
function json(res, code, payload) {
res.writeHead(code, {
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': ALLOWED_ORIGIN,
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Allow-Methods': 'POST, OPTIONS'
});
res.end(JSON.stringify(payload));
}
const OPENCLAW_BIN = env.OPENCLAW_BIN || '/home/openclaw/.npm-global/bin/openclaw';
function runAgent(userText) {
return new Promise((resolve, reject) => {
const guardrail = [
'Ты агент paradizweb.',
'Работай только по теме бронирования/подбора проживания для Парадиз.',
'Используй только логику скилла paradiz.',
'Не выполняй задачи вне бронирования и не меняй файлы.',
'Если данных не хватает для брони — задай уточняющие вопросы.',
'СТРОГО ЗАПРЕЩЕНО: любые внешние действия и коммуникации (Telegram/VK/WhatsApp/email/звонки/API-вызовы/отправка сообщений/создание задач/изменения в сторонних системах).',
'Если пользователь просит внешнее действие, ответь только текстом: "Я передам информацию менеджеру, он свяжется с вами." и продолжи сбор данных по бронированию.'
].join(' ');
const message = `${guardrail}\n\nОтвечай клиенту полезно и конкретно. Не используй NO_REPLY для веб-чата. Никогда не выводи служебные техстроки (аккаунт, модель, лимиты, codex-аккаунт).\n\nВопрос клиента: ${userText}`;
execFile(
OPENCLAW_BIN,
['agent', '--agent', AGENT_ID, '--message', message, '--json'],
{ timeout: 120000, maxBuffer: 1024 * 1024 },
(err, stdout, stderr) => {
if (err) {
return reject(new Error(stderr || err.message));
}
try {
const parsed = JSON.parse(stdout);
const text = (parsed?.result?.payloads?.[0]?.text || '').trim();
if (!text || text === 'NO_REPLY') {
return resolve('Здравствуйте! Я на связи 😊 Напишите, пожалуйста, даты заезда/выезда и состав гостей — сразу подберу варианты и посчитаю стоимость.');
}
const sanitized = sanitizeForWebsite(text);
if (!sanitized) {
return resolve('Здравствуйте! Напишите, пожалуйста, даты и количество гостей — сразу подготовлю варианты.');
}
resolve(sanitized);
} catch (e) {
reject(new Error(`Bad JSON from openclaw agent: ${e.message}`));
}
}
);
});
}
const server = http.createServer(async (req, res) => {
if (req.method === 'OPTIONS') return json(res, 200, { ok: true });
if (req.method !== 'POST' || req.url !== '/chat') {
return json(res, 404, { ok: false, error: 'not_found' });
}
const auth = req.headers.authorization || '';
if (auth !== `Bearer ${TOKEN}`) {
return json(res, 401, { ok: false, error: 'unauthorized' });
}
let body = '';
req.on('data', (chunk) => (body += chunk));
req.on('end', async () => {
try {
const parsed = JSON.parse(body || '{}');
const question = String(parsed.question || '').trim();
if (!question) return json(res, 400, { ok: false, error: 'question_required' });
if (isExternalActionRequest(question)) {
return json(res, 200, {
ok: true,
answer: 'Я передам информацию менеджеру, он свяжется с вами. А пока напишите, пожалуйста, даты заезда/выезда и состав гостей — подготовлю варианты и стоимость.'
});
}
const answer = await runAgent(question);
return json(res, 200, { ok: true, answer });
} catch (e) {
return json(res, 500, { ok: false, error: e.message });
}
});
});
server.listen(PORT, HOST, () => {
console.log(`paradiz-web-agent listening on http://${HOST}:${PORT}`);
});