Spaces:
Paused
Paused
File size: 10,783 Bytes
ee826ee | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | /**
* coordinator.js β Agente Coordinador de Zelin
* =============================================
* Basado en arquitectura real de Manus AI (2025):
*
* "Manus uses multi-agent with a planner that assigns tasks, a knowledge manager
* that reviews conversations and determines what should be saved in the filesystem,
* and an executor sub-agent that performs tasks assigned by the planner."
* β Lance Martin, LangChain (webinar con Peak Ji de Manus, Oct 2025)
*
* "Don't over-anthropomorphize your agents. You don't need an 'Org Chart' of agents
* (Manager, Designer, Coder) that chat with each other. The primary goal of sub-agents
* is to ISOLATE CONTEXT."
* β Manus context engineering, Part 2
*
* LO QUE HACE:
* 1. PLANNER β Para tareas complejas: descompone en pasos antes de ejecutar
* 2. ROUTER β Clasifica la intenciΓ³n y elige el handler mΓ‘s adecuado
* 3. KNOWLEDGE MANAGER β Decide quΓ© recordar de cada conversaciΓ³n (sin gastar tokens)
* 4. ERROR PRESERVATION β Guarda errores en contexto (Manus: "erasing failure removes evidence")
*
* PRINCIPIO CLAVE: sub-agentes = AISLAMIENTO DE CONTEXTO, no org-chart de roles
*/
import { callAI, callAIBackground } from './ai.js';
import { isLocalAIReady, ollamaChatDirect } from './local-ai.js';
import * as db from './db.js';
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// 1. PLANNER β Descomponer tareas complejas en pasos
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Manus descubriΓ³ que gastar un 5% de tokens en planning ahorra 30%+ en ejecuciΓ³n
// "A better pattern is a specific Planner sub-agent that returns a structured Plan object"
export async function planTask(goal, context = '') {
// El planner tiene su PROPIO contexto aislado β no hereda el historial de chat
const plannerMessages = [
{
role : 'system',
content: `Eres un planificador de tareas para Zelin, bot de TomateSMP.
Tu trabajo: descomponer una tarea compleja en pasos concretos y ejecutables.
Reglas:
- MΓ‘ximo 5 pasos (mΓ‘s = complejidad innecesaria)
- Cada paso debe ser verificable (ΒΏcΓ³mo sΓ© que terminΓ³?)
- SΓ© especΓfico: no "buscar info" sino "buscar en la DB de jugadores por username X"
- Identifica quΓ© herramientas necesita cada paso
- Si la tarea es simple (1 paso), responde directamente sin planificar
Responde SOLO JSON:
{
"simple": true/false,
"steps": [
{"id": 1, "action": "quΓ© hacer exactamente", "tool": "quΓ© tool usar o null", "verify": "cΓ³mo verificar"}
],
"expected_output": "quΓ© deberΓa producir este plan"
}`,
},
{
role : 'user',
content: `Tarea: ${goal}\n${context ? `Contexto disponible: ${context}` : ''}`,
},
];
try {
// Planner usa modelo fast con su propio contexto aislado
let raw;
if (isLocalAIReady()) {
raw = await ollamaChatDirect(plannerMessages, 300, 10000);
} else {
raw = await callAIBackground(plannerMessages, 'fast', 300);
}
return JSON.parse(raw.replace(/```json|```/g, '').trim());
} catch {
return { simple: true, steps: [] }; // si falla planning, ejecutar directo
}
}
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// 2. ROUTER β Clasificar intenciΓ³n y elegir el modo de respuesta Γ³ptimo
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const ROUTE_PATTERNS = {
// Respuesta directa sin tool use β patterns de Manus "atomic" level
DIRECT : /^(hola|hey|gg|xd|jaja|ok|gracias|sΓ|no|bien|mal|\?|!|π|π|lol)/i,
// Necesita buscar en la DB de Zelin
DB_QUERY : /cuΓ‘ntos|cuΓ‘ndo|historial|ban|sanciΓ³n|jugador.+(fue|tiene|hizo|estΓ‘)/i,
// Tarea multi-paso que necesita planning
COMPLEX : /crea|genera|analiza|investiga|recopila|haz un resumen|explica detalladamente|compara/i,
// Pregunta sobre el servidor
SERVER : /ip|cΓ³mo entro|cΓ³mo me registro|cΓ³mo juego|dΓ³nde estΓ‘|quΓ© es|para quΓ©/i,
};
export function routeRequest(message, hasTools = false) {
const m = message?.trim() ?? '';
// Mensajes muy cortos β respuesta directa siempre
if (m.length < 20) return { mode: 'direct', reason: 'short_message' };
// Patrones directos
if (ROUTE_PATTERNS.DIRECT.test(m)) return { mode: 'direct', reason: 'casual' };
if (ROUTE_PATTERNS.DB_QUERY.test(m)) return { mode: 'tool_use', reason: 'needs_db' };
if (ROUTE_PATTERNS.COMPLEX.test(m) && hasTools) return { mode: 'planned', reason: 'complex_task' };
if (ROUTE_PATTERNS.SERVER.test(m)) return { mode: 'rag_enhanced', reason: 'server_info' };
return { mode: 'standard', reason: 'default' };
}
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// 3. KNOWLEDGE MANAGER β Decidir quΓ© recordar de cada conversaciΓ³n
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Manus: "a knowledge manager that reviews conversations and determines
// what should be saved in the filesystem"
// Para Zelin: determinar quΓ© hechos del usuario guardar en memoria
export async function extractKnowledge(userId, userMessage, zelinResponse) {
// Solo extraer si hay algo potencialmente valioso
const worthExtracting = /me llamo|soy|mi|tengo|juego|rango|casa|coords|discord/i.test(userMessage);
if (!worthExtracting) return null;
try {
const raw = await callAIBackground([
{
role : 'system',
content: `Extrae hechos CONCRETOS sobre el usuario de esta conversaciΓ³n para recordarlos en el futuro.
Solo hechos objetivos (no opiniones). Si no hay nada ΓΊtil, devuelve null.
Responde SOLO JSON o null:
{"facts": ["hecho 1", "hecho 2"], "summary": "resumen en 1 frase"}`,
},
{
role : 'user',
content: `Usuario dijo: "${userMessage.slice(0, 300)}"\nZelin respondiΓ³: "${zelinResponse.slice(0, 200)}"`,
},
], 'fast', 150);
const clean = raw.replace(/```json|```/g, '').trim();
if (clean === 'null' || clean === '') return null;
const knowledge = JSON.parse(clean);
// Persistir en DB
if (knowledge?.facts?.length) {
const existing = await db.memGet(`knowledge.user.${userId}`) ?? { facts: [], updatedAt: null };
existing.facts = [...new Set([...existing.facts, ...knowledge.facts])].slice(-20); // max 20 hechos
existing.updatedAt = new Date().toISOString();
await db.memSet(`knowledge.user.${userId}`, existing, 'user_knowledge');
}
return knowledge;
} catch {
return null;
}
}
export async function getUserKnowledge(userId) {
return await db.memGet(`knowledge.user.${userId}`) ?? null;
}
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// 4. ERROR PRESERVATION β Guardar errores en contexto (principio Manus)
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// "Erasing failure removes evidence. Retaining errors allows the model to reason
// about why something failed and how to adapt."
// β Manus AI context engineering blog
const _errorLog = new Map(); // channelId β [{ error, context, ts }]
export function preserveError(channelId, error, context = '') {
const log = _errorLog.get(channelId) ?? [];
log.push({ error: error.slice(0, 300), context: context.slice(0, 200), ts: Date.now() });
// Mantener ΓΊltimos 3 errores por canal (no saturar)
if (log.length > 3) log.shift();
_errorLog.set(channelId, log);
}
// Inyectar errores previos en el contexto para que la IA aprenda de ellos
export function getErrorContext(channelId) {
const log = _errorLog.get(channelId);
if (!log?.length) return '';
const recent = log.filter(e => Date.now() - e.ts < 30 * 60_000); // ΓΊltimos 30 min
if (!recent.length) return '';
return '\n\n[Errores recientes en este canal β aprende de ellos]:\n' +
recent.map(e => `- ${e.error}`).join('\n');
}
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// 5. GOAL RECITATION β Anti-lost-in-middle (tΓ©cnica de Manus con todo.md)
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Manus: "By constantly rewriting the todo list, Manus is reciting its objectives
// into the END of the context. This pushes the global plan into the model's
// recent attention span."
// Para Zelin: en tareas multi-paso, aΓ±adir el objetivo al FINAL del ΓΊltimo mensaje
export function injectGoalRecitation(messages, currentGoal) {
if (!currentGoal || !messages.length) return messages;
const last = messages[messages.length - 1];
if (last.role !== 'user') return messages;
// AΓ±adir recordatorio del objetivo al final del ΓΊltimo mensaje del usuario
const enriched = [...messages];
enriched[enriched.length - 1] = {
...last,
content: last.content + `\n\n[Recordatorio del objetivo actual]: ${currentGoal}`,
};
return enriched;
}
export function coordinatorStats() {
const channels = [..._errorLog.keys()].length;
const totalErrors = [..._errorLog.values()].reduce((s, log) => s + log.length, 0);
return { channels_tracked: channels, errors_preserved: totalErrors };
}
|