godcrm/pes-core/run-pes.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

135 lines
5.2 KiB
JavaScript
Raw Permalink 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
// ============================================================
// PES Runner — Multi-User PES via PesManager
// ============================================================
// Launches PES Manager: single polling loop, multiple users.
// Master user (Тор) loads from legacy bublik.db.
// New users get their own PES on /start.
//
// Usage:
// node run-pes.js
// BOT_TOKEN=xxx CHAT_ID=yyy node run-pes.js
//
// Environment:
// BOT_TOKEN — Telegram Bot API token
// CHAT_ID — Master owner's Telegram chat ID (default: Тор)
// LOG_FILE — Log file path (default: ./pes-data/pes.log)
// ============================================================
import { PesManager } from './pes-manager.js';
import { existsSync, mkdirSync, appendFileSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
// ── CONFIG ────────────────────────────────────────────────
const BOT_TOKEN = process.env.BOT_TOKEN || '<TELEGRAM_BOT_TOKEN>';
const CHAT_ID = process.env.CHAT_ID || '331468767';
const DATA_DIR = join(__dirname, 'pes-data');
const LOG_FILE = process.env.LOG_FILE || join(DATA_DIR, 'pes.log');
// ── LLM API Key (read from backend .env or env var) ──────────
function loadLLMKey() {
if (process.env.OPENAI_API_KEY) return process.env.OPENAI_API_KEY;
try {
const envPath = join(__dirname, '..', 'backend', '.env');
const envContent = readFileSync(envPath, 'utf-8');
const match = envContent.match(/^OPENAI_API_KEY=(.+)$/m);
if (match) return match[1].trim();
} catch (_) {}
return null;
}
const LLM_API_KEY = loadLLMKey();
// ── JWT Secret (for CRM Bridge v2) ───────────────────────
function loadJwtSecret() {
if (process.env.JWT_SECRET) return process.env.JWT_SECRET;
try {
const envPath = join(__dirname, '..', '.env');
const envContent = readFileSync(envPath, 'utf-8');
const match = envContent.match(/^JWT_SECRET=(.+)$/m);
if (match) return match[1].trim();
} catch (_) {}
return null;
}
const JWT_SECRET = loadJwtSecret();
// Ensure data dir exists
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
// ── LOGGER ────────────────────────────────────────────────
function log(category, message, data = null) {
const ts = new Date().toISOString();
const line = data
? `[${ts}] [${category}] ${message} ${JSON.stringify(data)}`
: `[${ts}] [${category}] ${message}`;
console.log(line);
try { appendFileSync(LOG_FILE, line + '\n'); } catch (_) {}
}
// ── GLOBAL ERROR HANDLERS (prevent crash → PM2 restart loop) ──
process.on('uncaughtException', (err) => {
const ts = new Date().toISOString();
const msg = `[${ts}] [UNCAUGHT] ❌ ${err.message}\n${err.stack}`;
console.error(msg);
try { appendFileSync(LOG_FILE, msg + '\n'); } catch (_) {}
});
process.on('unhandledRejection', (reason) => {
const ts = new Date().toISOString();
const msg = `[${ts}] [UNHANDLED_REJECTION] ❌ ${reason?.message || reason}\n${reason?.stack || ''}`;
console.error(msg);
try { appendFileSync(LOG_FILE, msg + '\n'); } catch (_) {}
});
// ── MAIN ──────────────────────────────────────────────────
async function main() {
log('SYSTEM', '═══════════════════════════════════════');
log('SYSTEM', 'PES Runner (Multi-User) starting...');
log('SYSTEM', `Master chat: ${CHAT_ID}`);
log('SYSTEM', `LLM: ${LLM_API_KEY ? 'ACTIVE' : 'DISABLED'}`);
log('SYSTEM', `CRM Bridge: ${JWT_SECRET ? 'ACTIVE' : 'DISABLED'}`);
log('SYSTEM', '═══════════════════════════════════════');
const manager = new PesManager({
botToken: BOT_TOKEN,
llmApiKey: LLM_API_KEY,
masterChatId: CHAT_ID,
jwtSecret: JWT_SECRET,
});
// ── Graceful shutdown ───────────────────────────────
const shutdown = (signal) => {
log('SYSTEM', `${signal} received — shutting down...`);
manager.stop();
log('SYSTEM', 'PES Manager safely shut down. Goodbye.');
process.exit(0);
};
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));
// ── Start manager (polling + instances) ─────────────
await manager.start();
// Keep alive + heartbeat
setInterval(() => {
const status = manager.getStatus();
log('HEARTBEAT', `${status.activeInstances} instances, ${status.totalUsers} users`);
for (const inst of status.instances) {
log('HEARTBEAT', ` ${inst.name} (${inst.chatId}): level=${inst.level} mood=${inst.mood} mode=${inst.mode}`);
}
}, 300_000); // every 5 min
}
main().catch((err) => {
log('FATAL', `💀 ${err.message}\n${err.stack}`);
process.exit(1);
});