zelin-bot / src /debugify.js
Z User
v5.8.5: Gemma 4, MC Wiki, MC Player, anti-hallucination, CPU optimizations
ee826ee
/**
* ============================================
* 🐛 debugify.js — Diagnóstico Completo Total
* ============================================
*/
import { createWriteStream, mkdirSync, readFileSync, existsSync } from 'fs';
import { join } from 'path';
import archiver from 'archiver';
import * as db from './db.js';
const LOG_BUFFER_SIZE = 1000;
const logBuffer = [];
const errorBuffer = [];
export function initDebugify() {
const origLog = console.log.bind(console);
const origErr = console.error.bind(console);
const origWarn = console.warn.bind(console);
console.log = (...args) => {
const line = `[${new Date().toISOString()}] [LOG] ${args.join(' ')}`;
logBuffer.push(line);
if (logBuffer.length > LOG_BUFFER_SIZE) logBuffer.shift();
origLog(...args);
};
console.error = (...args) => {
const line = `[${new Date().toISOString()}] [ERR] ${args.join(' ')}`;
logBuffer.push(line); errorBuffer.push(line);
if (logBuffer.length > LOG_BUFFER_SIZE) logBuffer.shift();
if (errorBuffer.length > 500) errorBuffer.shift();
origErr(...args);
};
console.warn = (...args) => {
const line = `[${new Date().toISOString()}] [WARN] ${args.join(' ')}`;
logBuffer.push(line);
if (logBuffer.length > LOG_BUFFER_SIZE) logBuffer.shift();
origWarn(...args);
};
process.on('uncaughtException', (err) => {
errorBuffer.push(`[${new Date().toISOString()}] [UNCAUGHT] ${err.stack || err.message}`);
});
process.on('unhandledRejection', (reason) => {
errorBuffer.push(`[${new Date().toISOString()}] [UNHANDLED] ${reason?.stack || reason}`);
});
console.log('[Debugify] ✅ Sistema de debug iniciado');
}
async function q(sql, args = []) {
try {
const r = await db.db.execute({ sql, args });
return r.rows ?? [];
} catch (e) {
return [{ _error: e.message }];
}
}
export async function generateDebugZip(client, config) {
const now = new Date();
const ts = now.toISOString().replace(/[:.]/g, '-').slice(0, 19);
const zipName = `debugify-${ts}.zip`;
const zipPath = join('./debugify', zipName);
mkdirSync('./debugify', { recursive: true });
const output = createWriteStream(zipPath);
const archive = archiver('zip', { zlib: { level: 9 } });
return new Promise(async (resolve, reject) => {
output.on('close', () => resolve({ path: zipPath, name: zipName, size: archive.pointer() }));
archive.on('error', reject);
archive.pipe(output);
// 01 — LOGS
archive.append(logBuffer.join('\n') || '(sin logs)', { name: '01-logs/console-completo.txt' });
archive.append(errorBuffer.join('\n') || '(sin errores)', { name: '01-logs/solo-errores.txt' });
archive.append(logBuffer.slice(-100).join('\n') || '(vacío)', { name: '01-logs/ultimas-100-lineas.txt' });
// 02 — CONVERSACIONES USUARIO + ZELIN
try {
const convs = await q(`SELECT channel_id, role, content, username, created_at FROM conversation_context ORDER BY channel_id, created_at DESC LIMIT 2000`);
const byChannel = new Map();
for (const row of convs) {
const ch = row.channel_id ?? 'unknown';
if (!byChannel.has(ch)) byChannel.set(ch, []);
const who = row.role === 'assistant' ? '🤖 Zelin' : `👤 ${row.username ?? 'Usuario'}`;
byChannel.get(ch).push(`[${String(row.created_at ?? '').slice(0,19)}] ${who}: ${String(row.content ?? '').slice(0,500)}`);
}
for (const [ch, lines] of byChannel)
archive.append(lines.reverse().join('\n\n'), { name: `02-conversaciones/canal-${ch}.txt` });
archive.append(
[...byChannel.entries()].map(([ch, l]) => `${ch}: ${l.length} mensajes`).join('\n') || '(sin conversaciones)',
{ name: '02-conversaciones/RESUMEN.txt' }
);
} catch (e) { archive.append(`Error: ${e.message}`, { name: '02-conversaciones/error.txt' }); }
// 03 — MENSAJES DEL SERVIDOR
try {
const msgs = await q(`SELECT m.channel_id, u.username, m.content, m.created_at, m.is_edited FROM messages m LEFT JOIN users u ON m.user_id = u.user_id WHERE m.is_deleted = 0 ORDER BY m.created_at DESC LIMIT 500`);
const byChannel = new Map();
for (const r of msgs) {
const ch = r.channel_id ?? 'unknown';
if (!byChannel.has(ch)) byChannel.set(ch, []);
byChannel.get(ch).push(`[${String(r.created_at ?? '').slice(11,19)}] ${r.username ?? '?'}${r.is_edited ? '[editado]' : ''}: ${String(r.content ?? '').slice(0,200)}`);
}
for (const [ch, lines] of byChannel)
archive.append(lines.reverse().join('\n'), { name: `03-mensajes-servidor/canal-${ch}.txt` });
const deleted = await q(`SELECT username, content, channel_name, deleted_at FROM deleted_messages ORDER BY deleted_at DESC LIMIT 50`);
archive.append(
deleted.length ? deleted.map(r => `[${r.deleted_at}] ${r.username} en #${r.channel_name}: ${String(r.content ?? '').slice(0,150)}`).join('\n') : '(ninguno)',
{ name: '03-mensajes-servidor/mensajes-eliminados.txt' }
);
} catch (e) { archive.append(`Error: ${e.message}`, { name: '03-mensajes-servidor/error.txt' }); }
// 04 — DISCORD COMPLETO
try {
const guild = client?.guilds?.cache?.first();
archive.append([
`=== BOT ===`,
`Tag: ${client?.user?.tag ?? '?'} | ID: ${client?.user?.id ?? '?'}`,
`Guilds: ${client?.guilds?.cache?.size ?? 0} | Uptime: ${Math.floor((client?.uptime ?? 0)/60000)}min | Ping: ${client?.ws?.ping ?? '?'}ms`,
``,
`=== GUILD ===`,
`${guild?.name ?? 'N/A'} (ID: ${guild?.id ?? 'N/A'})`,
`Miembros: ${guild?.memberCount ?? '?'} | Canales: ${guild?.channels?.cache?.size ?? '?'} | Roles: ${guild?.roles?.cache?.size ?? '?'}`,
`Boost tier: ${guild?.premiumTier ?? '?'} | Boosts: ${guild?.premiumSubscriptionCount ?? 0}`,
``,
`=== CANALES (${guild?.channels?.cache?.size ?? 0}) ===`,
...(guild?.channels?.cache?.map(ch => ` ${ch.type===2?'🔊':'#'}${ch.name} [${ch.id}]`) ?? []),
``,
`=== ROLES (${guild?.roles?.cache?.size ?? 0}) ===`,
...(guild?.roles?.cache?.map(r => ` @${r.name}: ${r.members.size}m — ${r.id}${r.hexColor}`) ?? []),
].join('\n'), { name: '04-discord/estado-completo.txt' });
} catch (e) { archive.append(`Error: ${e.message}`, { name: '04-discord/error.txt' }); }
// 05 — BASE DE DATOS
try {
const s = (await q(`SELECT (SELECT COUNT(*) FROM messages WHERE is_deleted=0) AS total_msgs, (SELECT COUNT(*) FROM messages WHERE created_at>datetime('now','-1 hour') AND is_deleted=0) AS msgs_1h, (SELECT COUNT(*) FROM messages WHERE created_at>datetime('now','-24 hours') AND is_deleted=0) AS msgs_24h, (SELECT COUNT(*) FROM messages WHERE created_at>datetime('now','-7 days') AND is_deleted=0) AS msgs_7d, (SELECT COUNT(DISTINCT user_id) FROM messages WHERE is_deleted=0) AS usuarios_con_msgs, (SELECT COUNT(*) FROM users) AS total_usuarios, (SELECT COUNT(*) FROM conversation_context) AS turns_conv, (SELECT COUNT(*) FROM zelin_memory) AS entradas_mem, (SELECT COUNT(*) FROM deleted_messages) AS msgs_eliminados`))[0] ?? {};
archive.append([
`=== ESTADÍSTICAS DB ===`,
`Mensajes totales: ${s.total_msgs}`,
`Mensajes última hora: ${s.msgs_1h}`,
`Mensajes últimas 24h: ${s.msgs_24h}`,
`Mensajes últimos 7d: ${s.msgs_7d}`,
`Usuarios con mensajes: ${s.usuarios_con_msgs}`,
`Usuarios registrados: ${s.total_usuarios}`,
`Turns de conversación: ${s.turns_conv}`,
`Entradas de memoria: ${s.entradas_mem}`,
`Mensajes eliminados: ${s.msgs_eliminados}`,
].join('\n'), { name: '05-database/stats.txt' });
const topUsers = await q(`SELECT u.username, COUNT(m.id) as msgs, MAX(m.created_at) as ultimo FROM messages m JOIN users u ON m.user_id=u.user_id WHERE m.is_deleted=0 AND m.created_at>datetime('now','-7 days') GROUP BY m.user_id ORDER BY msgs DESC LIMIT 20`);
archive.append(topUsers.map(r => `${String(r.username??'?').padEnd(20)} ${r.msgs} msgs — ${String(r.ultimo??'').slice(0,16)}`).join('\n'), { name: '05-database/top-usuarios-activos.txt' });
const topCh = await q(`SELECT m.channel_id, COUNT(*) as msgs, MAX(m.created_at) as ultimo FROM messages m WHERE m.is_deleted=0 AND m.created_at>datetime('now','-7 days') GROUP BY m.channel_id ORDER BY msgs DESC LIMIT 20`);
archive.append(topCh.map(r => `${String(r.channel_id).padEnd(20)} ${r.msgs} msgs — ${String(r.ultimo??'').slice(0,16)}`).join('\n'), { name: '05-database/canales-activos.txt' });
const allUsers = await q(`SELECT username, nickname, joined_at, left_at, message_count, notes FROM users ORDER BY joined_at DESC LIMIT 200`);
archive.append(allUsers.map(r => `${r.username??'?'} (${r.nickname??'-'}) | entró:${r.joined_at??'?'} | msgs:${r.message_count??0} | notas:${r.notes??'ninguna'}`).join('\n'), { name: '05-database/usuarios-registrados.txt' });
const memByCat = await q(`SELECT category, COUNT(*) as n, MAX(updated_at) as ultimo FROM zelin_memory GROUP BY category ORDER BY n DESC`);
archive.append(memByCat.map(r => `${String(r.category).padEnd(25)} ${r.n} entradas — último: ${String(r.ultimo??'').slice(0,16)}`).join('\n'), { name: '05-database/memoria-por-categoria.txt' });
const allMem = await q(`SELECT key, value, category, updated_at, expires_at FROM zelin_memory WHERE category!='skill' ORDER BY category, updated_at DESC LIMIT 200`);
archive.append(allMem.map(r => `[${r.category}] ${r.key}\n ${String(r.value??'').slice(0,300)}\n actualizado:${r.updated_at} expira:${r.expires_at??'nunca'}`).join('\n---\n'), { name: '05-database/memoria-completa.txt' });
const events = await q(`SELECT type, user_id, target_id, old_val, new_val, created_at FROM server_events ORDER BY created_at DESC LIMIT 100`);
archive.append(events.length ? events.map(r => `[${r.created_at}] ${r.type} user:${r.user_id} target:${r.target_id} ${r.old_val??''}${r.new_val??''}`).join('\n') : '(sin eventos)', { name: '05-database/eventos-servidor.txt' });
const sums = await q(`SELECT channel_id, period_start, period_end, message_count, summary, top_topics FROM channel_summaries ORDER BY created_at DESC LIMIT 20`);
archive.append(sums.length ? sums.map(r => `Canal:${r.channel_id} ${r.period_start}${r.period_end} (${r.message_count} msgs)\nTemas:${r.top_topics??'?'}\n${String(r.summary??'').slice(0,400)}`).join('\n═══\n') : '(sin resúmenes)', { name: '05-database/resumenes.txt' });
const skills = await q(`SELECT key, value FROM zelin_memory WHERE category='skill'`);
archive.append(skills.length ? skills.map(r => `KEY: ${r.key}\n${String(r.value??'').slice(0,500)}`).join('\n---\n') : '(sin skills)', { name: '05-database/skills.txt' });
} catch (e) { archive.append(`Error: ${e.message}\n${e.stack??''}`, { name: '05-database/error.txt' }); }
// 06 — ESTADO IA
try {
const { getLocalAIStats, isLocalAIReady, getLocalAIError } = await import('./local-ai.js');
const { getDailyStats, getProviderStatus } = await import('./ai.js');
const { getHealerStats } = await import('./runtime-healer.js');
const aiStats = await getLocalAIStats().catch(() => ({}));
const daily = getDailyStats();
const providers = getProviderStatus();
const healer = getHealerStats();
archive.append([
`=== MODELO LOCAL ===`,
`Listo: ${isLocalAIReady()} | Error: ${getLocalAIError()??'ninguno'}`,
`Modelo: ${aiStats.model??'?'}`,
`Fast: ${aiStats.modelFast??'N/A'} (${aiStats.fastAvailable?'ok':'no disponible'})`,
`Embed: ${aiStats.embedModel??'N/A'} (${aiStats.embedAvailable?'ok':'no disponible'})`,
``,
`=== PROVIDERS (uso diario) ===`,
...Object.entries(daily).map(([n,s]) => ` ${n.padEnd(20)} req:${String(s.requests).padStart(4)}/${s.limit} (${s.pct}) state:${s.state} score:${s.score}`),
``,
`=== CIRCUIT BREAKERS ===`,
...providers.map(p => ` ${p.name.padEnd(20)} estado:${p.state} score:${p.score}`),
``,
`=== RUNTIME HEALER ===`,
`Errores: ${healer.errors_stored} total / ${healer.errors_last_hour} última hora`,
`Healings: ${healer.healing_this_hour}/${healer.healing_cap} | Degradado: ${healer.degraded}`,
`Performance: ${JSON.stringify(healer.performance)}`,
].join('\n'), { name: '06-ia/estado-completo.txt' });
const runtimeErrs = await q(`SELECT key, value, updated_at FROM zelin_memory WHERE category='runtime_errors' ORDER BY updated_at DESC LIMIT 50`);
archive.append(runtimeErrs.length ? runtimeErrs.map(r => `[${r.updated_at}] ${String(r.value??'').slice(0,400)}`).join('\n---\n') : '(sin errores runtime)', { name: '06-ia/errores-runtime.txt' });
const auditLogs = await q(`SELECT key, value, updated_at FROM zelin_memory WHERE category='audit_log' ORDER BY updated_at DESC LIMIT 30`);
archive.append(auditLogs.length ? auditLogs.map(r => `[${r.updated_at}] ${String(r.value??'').slice(0,300)}`).join('\n---\n') : '(sin audit logs)', { name: '06-ia/audit-logs.txt' });
} catch (e) { archive.append(`Error: ${e.message}\n${e.stack??''}`, { name: '06-ia/error.txt' }); }
// 07 — CONFIG
try {
const safe = JSON.parse(JSON.stringify(config ?? {}));
const M = '***REDACTED***';
if (safe.discord?.token) safe.discord.token = M;
if (safe.turso?.token) safe.turso.token = M;
if (safe.sftp?.password) safe.sftp.password = M;
if (safe.ai) for (const k of Object.keys(safe.ai)) {
if (safe.ai[k]?.apiKey) safe.ai[k].apiKey = M;
if (safe.ai[k]?.keys) safe.ai[k].keys = [`${(safe.ai[k].keys??[]).length} keys REDACTED`];
if (safe.ai[k]?.sk) safe.ai[k].sk = M;
if (safe.ai[k]?.apiToken) safe.ai[k].apiToken = M;
}
archive.append(JSON.stringify(safe, null, 2), { name: '07-config/config-sanitizada.json' });
if (existsSync('./package.json'))
archive.append(readFileSync('./package.json', 'utf8'), { name: '07-config/package.json' });
} catch (e) { archive.append(`Error: ${e.message}`, { name: '07-config/error.txt' }); }
// 08 — SISTEMA
const mem = process.memoryUsage();
archive.append([
`=== MEMORIA ===`,
`RSS: ${(mem.rss/1024/1024).toFixed(1)}MB | Heap: ${(mem.heapUsed/1024/1024).toFixed(1)}/${(mem.heapTotal/1024/1024).toFixed(1)}MB | External: ${(mem.external/1024/1024).toFixed(1)}MB`,
``,
`=== PROCESO ===`,
`Uptime: ${Math.floor(process.uptime())}s (${(process.uptime()/3600).toFixed(2)}h)`,
`PID: ${process.pid} | Node: ${process.version} | Platform: ${process.platform}/${process.arch}`,
``,
`Generado: ${now.toISOString()}`,
].join('\n'), { name: '08-sistema/proceso.txt' });
// 09 — PSYCHE
try {
const { getStateSnapshot } = await import('./psyche.js');
archive.append(JSON.stringify(getStateSnapshot(), null, 2), { name: '09-psyche/estado-psicologico.json' });
const psycheData = await q(`SELECT key, value, updated_at FROM zelin_memory WHERE category='psyche' ORDER BY updated_at DESC LIMIT 50`);
archive.append(psycheData.length ? psycheData.map(r => `[${r.updated_at}] ${r.key}: ${String(r.value??'').slice(0,200)}`).join('\n') : '(sin datos psyche)', { name: '09-psyche/afinidades-usuarios.txt' });
} catch (e) { archive.append(`Error: ${e.message}`, { name: '09-psyche/error.txt' }); }
// 10 — LEARNING
try {
const ld = await q(`SELECT key, value, category, updated_at FROM zelin_memory WHERE category IN ('learning','user_profile','knowledge') ORDER BY updated_at DESC LIMIT 100`);
archive.append(ld.length ? ld.map(r => `[${r.category}] ${r.key}${r.updated_at}\n ${String(r.value??'').slice(0,300)}`).join('\n---\n') : '(sin datos)', { name: '10-learning/datos.txt' });
} catch (e) { archive.append(`Error: ${e.message}`, { name: '10-learning/error.txt' }); }
// 11 — STANDING PROMPTS
try {
const sp = await q(`SELECT key, value, updated_at FROM zelin_memory WHERE category='standing_prompt' OR key LIKE 'standing%' ORDER BY updated_at DESC`);
archive.append(sp.length ? sp.map(r => `[${r.updated_at}] ${r.key}:\n${String(r.value??'').slice(0,500)}`).join('\n═══\n') : '(sin standing prompts)', { name: '11-standing-prompts/prompts.txt' });
} catch (e) { archive.append(`Error: ${e.message}`, { name: '11-standing-prompts/error.txt' }); }
// README
archive.append([
`DEBUGIFY COMPLETO — Zelin v5.8`,
`Generado: ${now.toISOString()}`,
``,
`01-logs/ → Consola completa, solo errores, últimas 100 líneas`,
`02-conversaciones/ → Todos los chats usuario↔Zelin por canal`,
`03-mensajes-servidor/ → Mensajes del servidor + mensajes eliminados`,
`04-discord/ → Estado completo Discord (guild, canales, roles)`,
`05-database/ → Stats, top usuarios, memoria, eventos, resúmenes, skills`,
`06-ia/ → Modelo local, providers, circuit breakers, errores runtime, audit logs`,
`07-config/ → Config sanitizada + package.json`,
`08-sistema/ → RAM, CPU, uptime, proceso`,
`09-psyche/ → Estado psicológico de Zelin + afinidades con usuarios`,
`10-learning/ → Datos de aprendizaje y perfiles de usuario`,
`11-standing-prompts/ → Standing prompts activos en DB`,
].join('\n'), { name: 'README.txt' });
archive.finalize();
});
}