Spaces:
Paused
Paused
| /** | |
| * ============================================ | |
| * 🐛 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(); | |
| }); | |
| } | |