/** * ============================================================ * bot.js — Zelin v36.0 "LIBRE" * ============================================================ * * 100% AI-DRIVEN — No predefined steps, no boot sequence. * The AI decides EVERYTHING. Reflexes are physical instincts only. * * Architecture: LLM-driven autonomous code-execution agent * LLM: GLM-4-Flash (free, unlimited, powerful, fast) * * v36.0 CHANGES: * - REMOVED: Boot sequence (AI decides everything from first spawn) * - FIXED: GLM-4 API with 120s timeout + 3 retries + exponential backoff * - FIXED: Emergency reflexes are ACTIVE (actually flee, not just set emotion) * - ADDED: Auto-tracking metrics from inventory changes * - ADDED: AI timeout counter for degraded mode * - ADDED: Post-resawn safety (flee hostiles immediately) * - PRESERVED: All memory systems (KG, Episodic, Remedy, Spatial, Causal, Skills) * - PRESERVED: Discord, Benchmark, Critic, Curriculum */ // Global error handler — prevents unhandled rejection crashes process.on('unhandledRejection', (reason, promise) => { console.error('[UNHANDLED] ' + (reason?.message || reason)); }); process.on('uncaughtException', (err) => { console.error('[UNCAUGHT] ' + err.message); }); import mineflayer from 'mineflayer'; import pkgPathfinder from 'mineflayer-pathfinder'; const { pathfinder, Movements, goals } = pkgPathfinder; import pvpModule from 'mineflayer-pvp'; const pvp = pvpModule.plugin || pvpModule; import autoEatModule from 'mineflayer-auto-eat'; const autoEat = autoEatModule.plugin || autoEatModule; import armorManager from 'mineflayer-armor-manager'; import collectBlockModule from 'mineflayer-collectblock'; const collectBlock = collectBlockModule.plugin || collectBlockModule; import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs'; import { WebSocket } from 'ws'; import http from 'http'; // ═══════════════════════════════════════════════════════════════════════════ // CONFIG // ═══════════════════════════════════════════════════════════════════════════ const CONFIG = { host: process.env.MC_HOST || 'localhost', port: parseInt(process.env.MC_PORT || '25565'), username: process.env.MC_USERNAME || 'Zelin_', version: '1.20.1', auth: 'offline', llm: { key: process.env.GLM_KEY || '995a846a6b984be0aec2cefa0887046b.v72B5F9YdOHTQzhO', model: 'glm-4-flash', baseUrl: 'https://open.bigmodel.cn/api/paas/v4/chat/completions', }, thinkInterval: 4000, maxTokens: 1200, maxCodeTokens: 2500, statusFile: '/app/logs/bot_status.json', memoryFile: '/app/logs/bot_memory.json', skillFile: '/app/logs/bot_skills.json', benchmarkFile: '/app/logs/benchmark.json', remedyFile: '/app/logs/bot_remedies.json', episodicFile: '/app/logs/bot_episodic.json', causalFile: '/app/logs/bot_causal.json', spatialFile: '/app/logs/bot_spatial.json', discord: { token: process.env.DISCORD_TOKEN || 'MTQ3NzQ3NjQwNjgxOTU1NzUwNw.G51k4u.5umKgbbkFKrj0tLZSWZPHdrQpg7ZO759judgxQ', clientId: '1477476406819557507', guildId: '1241168929993396264', }, profile: { name: 'Zelin', personality: 'chica mexicana, curiosa, aventurada, a veces timida. Habla super casual, todo minusculas, jerga mexicana (wey, neta, chido, chale, orale, hijole, simon, nel, aguas). Femenina. Mensajes cortos, sin parentesis, sin emojis, sin !! sin ??.', }, maxConversationTurns: 40, codeTimeout: 50000, maxRetries: 3, // Voyager-style iterative retry }; // ═══════════════════════════════════════════════════════════════════════════ // UTILITY // ═══════════════════════════════════════════════════════════════════════════ function sleep(ms) { return new Promise(r => setTimeout(r, Math.max(0, ms))); } function rand(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function clearAllControls() { if (!bot) return; for (const c of ['forward', 'back', 'left', 'right', 'jump', 'sprint', 'sneak']) { try { bot.setControlState(c, false); } catch {} } } function hasValidPosition() { if (!bot?.entity?.position) return false; const p = bot.entity.position; return isFinite(p.x) && isFinite(p.y) && isFinite(p.z) && p.y > -64 && p.y < 320; } // TF-IDF-lite for skill/episodic retrieval (better than word overlap) function tokenize(text) { return (text || '').toLowerCase().replace(/[^a-z0-9_áéíóúñ\s]/g, '').split(/\s+/).filter(w => w.length > 2); } const _idf = {}; function computeSimilarity(tokensA, tokensB) { if (!tokensA.length || !tokensB.length) return 0; const setB = new Set(tokensB); const overlap = tokensA.filter(t => setB.has(t)).length; const union = new Set([...tokensA, ...tokensB]).size; return union > 0 ? overlap / union : 0; } // ═══════════════════════════════════════════════════════════════════════════ // THROTTLED CHAT // ═══════════════════════════════════════════════════════════════════════════ const _cmdQueue = []; let _cmdQueueActive = false; async function throttledChat(msg) { return new Promise((resolve) => { _cmdQueue.push({ msg, resolve }); if (!_cmdQueueActive) processCommandQueue(); }); } async function processCommandQueue() { if (_cmdQueueActive) return; _cmdQueueActive = true; while (_cmdQueue.length > 0) { const { msg, resolve } = _cmdQueue.shift(); try { if (bot?.entity) bot.chat(msg); } catch {} await sleep(800); resolve(); } _cmdQueueActive = false; } // ═══════════════════════════════════════════════════════════════════════════ // Vec3 NaN GUARD // ═══════════════════════════════════════════════════════════════════════════ function patchVec3RejectNaN(vec3Instance) { if (!vec3Instance) return; try { const Vec3Class = vec3Instance.constructor; if (Vec3Class.prototype.set._nanPatched) return; const origSet = Vec3Class.prototype.set; Vec3Class.prototype.set = function(x, y, z) { if (!isFinite(x) || !isFinite(y) || !isFinite(z)) return this; return origSet.call(this, x, y, z); }; Vec3Class.prototype.set._nanPatched = true; const patches = { offset: (fn) => function(dx, dy, dz) { if (!isFinite(dx) || !isFinite(dy) || !isFinite(dz)) return new Vec3Class(this.x, this.y, this.z); const r = fn.call(this, dx, dy, dz); return (!isFinite(r.x) || !isFinite(r.y) || !isFinite(r.z)) ? new Vec3Class(this.x, this.y, this.z) : r; }, add: (fn) => function(v) { if (!v||!isFinite(v.x)||!isFinite(v.y)||!isFinite(v.z)) return this; const nx=this.x+v.x,ny=this.y+v.y,nz=this.z+v.z; if(!isFinite(nx)||!isFinite(ny)||!isFinite(nz))return this; this.x=nx;this.y=ny;this.z=nz;return this; }, subtract: (fn) => function(v) { if (!v||!isFinite(v.x)||!isFinite(v.y)||!isFinite(v.z)) return this; const nx=this.x-v.x,ny=this.y-v.y,nz=this.z-v.z; if(!isFinite(nx)||!isFinite(ny)||!isFinite(nz))return this; this.x=nx;this.y=ny;this.z=nz;return this; }, scale: (fn) => function(s) { if(!isFinite(s))return new Vec3Class(0,0,0); const r=fn.call(this,s); return(!isFinite(r.x)||!isFinite(r.y)||!isFinite(r.z))?new Vec3Class(0,0,0):r; }, normalize: (fn) => function() { try{const r=fn.call(this);return(!isFinite(r.x)||!isFinite(r.y)||!isFinite(r.z))?new Vec3Class(0,0,0):r;}catch{return new Vec3Class(0,0,0);} }, distanceTo: (fn) => function(v) { if(!v||!isFinite(v.x)||!isFinite(v.y)||!isFinite(v.z))return 0; if(!isFinite(this.x)||!isFinite(this.y)||!isFinite(this.z))return 0; try{const d=fn.call(this,v);return isFinite(d)?d:0;}catch{return 0;} }, }; for (const [name, wrapper] of Object.entries(patches)) { const orig = Vec3Class.prototype[name]; if (orig) Vec3Class.prototype[name] = wrapper(orig); } console.log('[NaN Guard] Vec3 prototype patched'); } catch (e) { console.error('[NaN Guard] Failed: ' + e.message); } } function enforceValidPosition() { if (!bot?.entity) return; const pos = bot.entity.position; const vel = bot.entity.velocity; if (!isFinite(pos.x) || !isFinite(pos.y) || !isFinite(pos.z)) { state._nanTotalEver++; try { vel.set(0,0,0); } catch {} vel.x=0; vel.y=0; vel.z=0; const restorePos = state._lastGoodPos || { x:0, y:80, z:0 }; pos.x=restorePos.x; pos.y=restorePos.y; pos.z=restorePos.z; state._positionReady = true; if (!state._nanPhysicsDisabled) { state._nanPhysicsDisabled = true; try { bot.physicsEnabled = false; } catch {} setTimeout(() => { try { bot.physicsEnabled = true; } catch {} state._nanPhysicsDisabled = false; }, 1000); } // No /tp recovery — that's cheating. Just reset position locally. // The physics disable/re-enable cycle handles the actual fix. } else if (pos.y > -64 && pos.y < 320) { state._lastGoodPos = { x: pos.x, y: pos.y, z: pos.z }; state._positionReady = true; } if (!isFinite(vel.x) || !isFinite(vel.y) || !isFinite(vel.z)) { try { vel.set(0,0,0); } catch {} vel.x=0; vel.y=0; vel.z=0; } } // ═══════════════════════════════════════════════════════════════════════════ // RECIPE KNOWLEDGE GRAPH — From GITM/Optimus-1 // Hierarchical Directed Knowledge Graph for Minecraft crafting // ═══════════════════════════════════════════════════════════════════════════ const KNOWLEDGE_GRAPH = { // Tech tree: what you need before you can make X dependencies: { 'oak_planks': { requires: ['oak_log'], station: null }, 'birch_planks': { requires: ['birch_log'], station: null }, 'spruce_planks': { requires: ['spruce_log'], station: null }, 'stick': { requires: ['oak_planks'], station: null }, 'crafting_table': { requires: ['oak_planks'], station: null }, 'wooden_pickaxe': { requires: ['oak_planks', 'stick'], station: 'crafting_table' }, 'wooden_axe': { requires: ['oak_planks', 'stick'], station: 'crafting_table' }, 'wooden_sword': { requires: ['oak_planks', 'stick'], station: 'crafting_table' }, 'stone_pickaxe': { requires: ['cobblestone', 'stick'], station: 'crafting_table' }, 'stone_axe': { requires: ['cobblestone', 'stick'], station: 'crafting_table' }, 'stone_sword': { requires: ['cobblestone', 'stick'], station: 'crafting_table' }, 'furnace': { requires: ['cobblestone'], station: null, count: 8 }, 'iron_ingot': { requires: ['raw_iron', 'coal'], station: 'furnace' }, 'iron_pickaxe': { requires: ['iron_ingot', 'stick'], station: 'crafting_table' }, 'iron_sword': { requires: ['iron_ingot', 'stick'], station: 'crafting_table' }, 'iron_axe': { requires: ['iron_ingot', 'stick'], station: 'crafting_table' }, 'iron_chestplate': { requires: ['iron_ingot'], station: 'crafting_table', count: 8 }, 'iron_helmet': { requires: ['iron_ingot'], station: 'crafting_table', count: 5 }, 'iron_leggings': { requires: ['iron_ingot'], station: 'crafting_table', count: 7 }, 'iron_boots': { requires: ['iron_ingot'], station: 'crafting_table', count: 4 }, 'diamond_pickaxe': { requires: ['diamond', 'stick'], station: 'crafting_table' }, 'diamond_sword': { requires: ['diamond', 'stick'], station: 'crafting_table' }, 'torch': { requires: ['coal', 'stick'], station: null }, 'bread': { requires: ['wheat'], station: null, count: 3 }, 'cooked_beef': { requires: ['beef', 'coal'], station: 'furnace' }, 'cooked_porkchop': { requires: ['porkchop', 'coal'], station: 'furnace' }, 'cooked_chicken': { requires: ['chicken', 'coal'], station: 'furnace' }, 'cooked_mutton': { requires: ['mutton', 'coal'], station: 'furnace' }, 'chest': { requires: ['oak_planks'], station: null, count: 8 }, 'bed': { requires: ['oak_planks', 'wool'], station: 'crafting_table' }, 'shield': { requires: ['oak_planks', 'iron_ingot'], station: 'crafting_table' }, 'bow': { requires: ['stick', 'string'], station: 'crafting_table' }, 'arrow': { requires: ['flint', 'stick', 'feather'], station: 'crafting_table' }, 'bucket': { requires: ['iron_ingot'], station: 'crafting_table' }, 'shears': { requires: ['iron_ingot'], station: 'crafting_table' }, 'compass': { requires: ['iron_ingot', 'redstone'], station: 'crafting_table' }, }, // Get full dependency chain for an item (recursive) getFullChain(item) { const chain = []; const visited = new Set(); const recurse = (name) => { if (visited.has(name)) return; visited.add(name); const dep = this.dependencies[name]; if (dep) { for (const req of dep.requires) { recurse(req); } chain.push({ item: name, requires: dep.requires, station: dep.station, count: dep.count || 1 }); } }; recurse(item); return chain; }, // Check what the player can make right now getCraftableNow(inventoryNames) { const invSet = new Set(inventoryNames); const craftable = []; for (const [item, dep] of Object.entries(this.dependencies)) { if (invSet.has(item)) continue; // already have const hasAll = dep.requires.every(r => invSet.has(r)); if (hasAll) { // Check station // Station ready if: no station needed, or we have it in inventory, or it's placed nearby const stationReady = !dep.station || invSet.has(dep.station) || (dep.station === 'crafting_table' && (invSet.has('crafting_table') || this._hasBlockNearby('crafting_table'))) || (dep.station === 'furnace' && (invSet.has('furnace') || this._hasBlockNearby('furnace'))); if (stationReady) craftable.push(item); } } return craftable; }, // Get next priority item to work towards (Voyager-style curriculum) getNextGoal(inventoryNames, milestones) { const invSet = new Set(inventoryNames); // Priority order: survival progression const priorityChain = [ 'oak_log', 'oak_planks', 'stick', 'crafting_table', 'wooden_pickaxe', 'cobblestone', 'stone_pickaxe', 'stone_sword', 'furnace', 'torch', 'coal', 'raw_iron', 'iron_ingot', 'iron_pickaxe', 'iron_sword', 'iron_chestplate', 'iron_helmet', 'bucket', 'shield', 'diamond', 'diamond_pickaxe', 'diamond_sword', ]; for (const item of priorityChain) { if (!invSet.has(item)) { const chain = this.getFullChain(item); // Find the first missing item in the chain for (const step of chain) { if (!invSet.has(step.item)) return step; } return { item, requires: this.dependencies[item]?.requires || [], station: this.dependencies[item]?.station || null }; } } return null; // Has everything in priority chain }, _hasBlockNearby(blockName) { try { if (!bot?.entity) return false; const found = bot.findBlock({ matching: (b) => b?.name === blockName, maxDistance: 16 }); return !!found; } catch { return false; } }, // Get human-readable crafting path getCraftingPath(item) { const chain = this.getFullChain(item); return chain.map(s => s.item + (s.station ? ' (needs ' + s.station + ')' : '')).join(' -> '); } }; // ═══════════════════════════════════════════════════════════════════════════ // EPISODIC MEMORY — From M2PA // Stores successful experiences: goal, scene, plan, inventory, biome, time // Only stores SUCCESSFUL plans (M2PA key insight) // Consolidation: real-time (replace similar) + periodic (merge top-3) // ═══════════════════════════════════════════════════════════════════════════ const episodicMemory = { episodes: [], // { goal, scene, plan, inventory, biome, time, result, timestamp, tokens } maxEpisodes: 200, store(goal, scene, plan, result, success) { if (!success) return; // M2PA: only store successful experiences const episode = { goal: goal.substring(0, 200), scene: scene.substring(0, 500), plan: plan.substring(0, 800), result: result.substring(0, 200), inventory: this._snapshotInventory(), biome: this._getBiome(), time: this._getTime(), timestamp: Date.now(), tokens: tokenize(goal + ' ' + scene), }; // M2PA consolidation: real-time - replace if very similar exists const similar = this.findSimilar(episode.tokens, 0.85); if (similar.length > 0) { // Replace the most similar one (most recent wins - M2PA strategy) const idx = this.episodes.indexOf(similar[0]); if (idx >= 0) { this.episodes[idx] = episode; console.log('[Episodic] Replaced similar episode at idx ' + idx); this._persist(); return; } } this.episodes.push(episode); // Trim if over max if (this.episodes.length > this.maxEpisodes) { this.episodes = this.episodes.slice(-this.maxEpisodes); } this._persist(); console.log('[Episodic] Stored: ' + goal.substring(0, 60) + ' (' + this.episodes.length + ' total)'); }, findSimilar(queryTokens, threshold = 0.3, maxResults = 5) { if (!queryTokens.length || !this.episodes.length) return []; const scored = this.episodes.map((ep, idx) => { const sim = computeSimilarity(queryTokens, ep.tokens); // Recency boost (M2PA: re-sort by recency after similarity filter) const ageHours = (Date.now() - ep.timestamp) / 3600000; const recencyBoost = 1 / (1 + ageHours * 0.1); return { episode: ep, idx, score: sim * recencyBoost, similarity: sim }; }).filter(s => s.similarity >= threshold) .sort((a, b) => b.score - a.score); return scored.slice(0, maxResults).map(s => s.episode); }, getRelevantContext(currentGoal, currentScene, maxResults = 3) { const queryTokens = tokenize(currentGoal + ' ' + currentScene); const similar = this.findSimilar(queryTokens, 0.2, maxResults); if (similar.length === 0) return ''; return '== PAST SUCCESSFUL EXPERIENCES ==\n' + similar.map((ep, i) => '[' + (i+1) + '] Goal: ' + ep.goal + '\n Plan: ' + ep.plan.substring(0, 200) + '\n Result: ' + ep.result ).join('\n'); }, periodicConsolidation() { // M2PA: merge top-3 most similar episodes, keep most recent if (this.episodes.length < 10) return; let merged = 0; for (let i = this.episodes.length - 1; i >= 1 && merged < 3; i--) { const ep = this.episodes[i]; const similar = this.findSimilar(ep.tokens, 0.7, 3); if (similar.length >= 2) { // Remove older duplicates, keep newest for (const dup of similar.slice(1)) { const idx = this.episodes.indexOf(dup); if (idx >= 0 && idx < i) { this.episodes.splice(idx, 1); i--; merged++; } } } } if (merged > 0) { console.log('[Episodic] Consolidation: merged ' + merged + ' duplicates'); this._persist(); } }, _snapshotInventory() { try { if (!bot?.inventory) return ''; return bot.inventory.items().map(i => i.name + 'x' + i.count).join(','); } catch { return ''; } }, _getBiome() { try { return bot?.biome?.name || 'unknown'; } catch { return 'unknown'; } }, _getTime() { try { return Math.round(bot?.time?.timeOfDay ?? 0); } catch { return 0; } }, _persist() { try { writeFileSync(CONFIG.episodicFile, JSON.stringify(this.episodes.slice(-this.maxEpisodes), null, 2)); } catch {} }, load() { try { if (existsSync(CONFIG.episodicFile)) { const data = JSON.parse(readFileSync(CONFIG.episodicFile, 'utf-8')); this.episodes = Array.isArray(data) ? data : []; console.log('[Episodic] Loaded ' + this.episodes.length + ' episodes'); } } catch {} }, }; // ═══════════════════════════════════════════════════════════════════════════ // REMEDY SYSTEM — From MineEvolve // Learns from failures: what went wrong, why, how to fix it // Dual-track: Skills (positive) + Remedies (negative) // ═══════════════════════════════════════════════════════════════════════════ const remedySystem = { remedies: [], // { trigger, failureType, riskPattern, repairAction, scope, timestamp, tokens } maxRemedies: 100, store(trigger, failureType, riskPattern, repairAction, scope) { const remedy = { trigger: trigger.substring(0, 150), failureType: failureType.substring(0, 100), riskPattern: riskPattern.substring(0, 200), repairAction: repairAction.substring(0, 300), scope: scope || 'general', timestamp: Date.now(), tokens: tokenize(trigger + ' ' + failureType + ' ' + riskPattern), }; // Curator QC: check for conflicts (MineEvolve) const conflicts = this.remedies.filter(r => computeSimilarity(remedy.tokens, r.tokens) > 0.8 && r.repairAction !== remedy.repairAction ); if (conflicts.length > 0) { // Keep the more recent one (more context-aware) const idx = this.remedies.indexOf(conflicts[0]); if (idx >= 0) this.remedies[idx] = remedy; } else { this.remedies.push(remedy); } if (this.remedies.length > this.maxRemedies) { this.remedies = this.remedies.slice(-this.maxRemedies); } this._persist(); console.log('[Remedy] Stored: ' + failureType.substring(0, 50)); }, getRelevantRemedies(context, maxResults = 3) { const ctxTokens = tokenize(context); if (!ctxTokens.length) return []; const scored = this.remedies.map(r => ({ remedy: r, score: computeSimilarity(ctxTokens, r.tokens), })).filter(s => s.score > 0.2) .sort((a, b) => b.score - a.score); return scored.slice(0, maxResults).map(s => s.remedy); }, getRemedyContext(currentSituation) { const relevant = this.getRelevantRemedies(currentSituation, 3); if (relevant.length === 0) return ''; return '== KNOWN FAILURE PATTERNS (AVOID!) ==\n' + relevant.map((r, i) => '[' + (i+1) + '] When: ' + r.trigger + '\n Problem: ' + r.riskPattern + '\n Fix: ' + r.repairAction ).join('\n'); }, _persist() { try { writeFileSync(CONFIG.remedyFile, JSON.stringify(this.remedies, null, 2)); } catch {} }, load() { try { if (existsSync(CONFIG.remedyFile)) { const data = JSON.parse(readFileSync(CONFIG.remedyFile, 'utf-8')); this.remedies = Array.isArray(data) ? data : []; console.log('[Remedy] Loaded ' + this.remedies.length + ' remedies'); } } catch {} }, }; // ═══════════════════════════════════════════════════════════════════════════ // SPATIAL MEMORY — From Mr.Steve (What-Where-When) // Place Event Memory: stores events tagged with coordinates + timestamps // ═══════════════════════════════════════════════════════════════════════════ const spatialMemory = { places: [], // { what, x, y, z, when, details } maxPlaces: 300, record(what, x, y, z, details) { const entry = { what: what.substring(0, 100), x: Math.round(x), y: Math.round(y), z: Math.round(z), when: Date.now(), details: (details || '').substring(0, 200), }; this.places.push(entry); if (this.places.length > this.maxPlaces) { this.places = this.places.slice(-this.maxPlaces); } this._persist(); }, findNearest(what, fromX, fromZ, maxDist = 200) { const matches = this.places.filter(p => { if (!p.what.includes(what)) return false; const dist = Math.sqrt((p.x - fromX)**2 + (p.z - fromZ)**2); return dist <= maxDist; }).map(p => ({ ...p, dist: Math.round(Math.sqrt((p.x - fromX)**2 + (p.z - fromZ)**2)) })).sort((a, b) => a.dist - b.dist); return matches.slice(0, 5); }, getRecentEvents(maxResults = 10) { return this.places.slice(-maxResults); }, getResourceLocations(fromX, fromZ) { const resourceTypes = ['iron_ore', 'coal_ore', 'gold_ore', 'diamond_ore', 'oak_log', 'birch_log', 'lapis_ore', 'redstone_ore', 'emerald_ore', 'copper_ore']; const locations = {}; for (const type of resourceTypes) { const nearest = this.findNearest(type, fromX, fromZ, 500); if (nearest.length > 0) { locations[type] = nearest[0]; } } return locations; }, _persist() { try { writeFileSync(CONFIG.spatialFile, JSON.stringify(this.places.slice(-this.maxPlaces), null, 2)); } catch {} }, load() { try { if (existsSync(CONFIG.spatialFile)) { const data = JSON.parse(readFileSync(CONFIG.spatialFile, 'utf-8')); this.places = Array.isArray(data) ? data : []; console.log('[Spatial] Loaded ' + this.places.length + ' places'); } } catch {} }, }; // ═══════════════════════════════════════════════════════════════════════════ // CAUSAL TRACKER — From ADAM // Tracks action→result mappings to learn causal relationships // ═══════════════════════════════════════════════════════════════════════════ const causalTracker = { causals: [], // { action, preState, postState, result, timestamp } maxCausals: 200, record(action, preState, postState, result) { this.causals.push({ action: action.substring(0, 150), preState: preState.substring(0, 200), postState: postState.substring(0, 200), result: result.substring(0, 100), timestamp: Date.now(), }); if (this.causals.length > this.maxCausals) { this.causals = this.causals.slice(-this.maxCausals); } this._persist(); }, // What actions lead to getting X? getActionsForGoal(goal) { const goalTokens = tokenize(goal); return this.causals.filter(c => { const postTokens = tokenize(c.postState + ' ' + c.result); return computeSimilarity(goalTokens, postTokens) > 0.3; }).slice(-5); }, _persist() { try { writeFileSync(CONFIG.causalFile, JSON.stringify(this.causals.slice(-this.maxCausals), null, 2)); } catch {} }, load() { try { if (existsSync(CONFIG.causalFile)) { const data = JSON.parse(readFileSync(CONFIG.causalFile, 'utf-8')); this.causals = Array.isArray(data) ? data : []; console.log('[Causal] Loaded ' + this.causals.length + ' records'); } } catch {} }, }; // ═══════════════════════════════════════════════════════════════════════════ // PERSISTENT MEMORY — Enhanced with Mr.Steve spatial awareness // ═══════════════════════════════════════════════════════════════════════════ const memory = { home: null, shelterPos: null, deathPoints: [], resourceLocations: {}, chatHistory: [], safeSpots: [], totalPlayTime: 0, sessionStart: 0, deaths: 0, kills: 0, blocksMined: 0, itemsCrafted: 0, dangerZones: [], craftingTablesPlaced: [], // Current goal tracking (Voyager curriculum) currentGoal: null, completedGoals: [], failedGoals: [], goalRetryCount: {}, save() { try { writeFileSync(CONFIG.memoryFile, JSON.stringify({ home: this.home, shelterPos: this.shelterPos, deathPoints: this.deathPoints.slice(-20), resourceLocations: this.resourceLocations, chatHistory: this.chatHistory.slice(-50), safeSpots: this.safeSpots.slice(-10), totalPlayTime: this.totalPlayTime, deaths: this.deaths, kills: this.kills, blocksMined: this.blocksMined, itemsCrafted: this.itemsCrafted, dangerZones: (this.dangerZones || []).slice(-20), craftingTablesPlaced: (this.craftingTablesPlaced || []).slice(-10), currentGoal: this.currentGoal, completedGoals: this.completedGoals.slice(-50), failedGoals: this.failedGoals.slice(-30), goalRetryCount: this.goalRetryCount, }, null, 2)); } catch {} }, load() { try { if (existsSync(CONFIG.memoryFile)) { const data = JSON.parse(readFileSync(CONFIG.memoryFile, 'utf-8')); Object.assign(this, data); this.sessionStart = Date.now(); } } catch {} }, }; // ═══════════════════════════════════════════════════════════════════════════ // SKILL LIBRARY — Voyager-style with Odyssey hierarchy // Primitive + Compositional skills, better retrieval (TF-IDF-lite) // ═══════════════════════════════════════════════════════════════════════════ const skillLibrary = { skills: {}, save(name, code, description, type = 'primitive') { // Curator QC (MineEvolve): validate before saving if (!code || code.length < 10) return; if (!description || description.length < 5) return; // Version management (Voyager: V2, V3...) if (this.skills[name] && this.skills[name].code !== code) { const oldName = name + 'V' + (2 + (this.skills[name].version || 1)); this.skills[oldName] = { ...this.skills[oldName] || this.skills[name] }; } this.skills[name] = { code, description, type, // 'primitive' or 'compositional' usageCount: 0, successCount: 0, lastUsed: Date.now(), created: Date.now(), version: (this.skills[name]?.version || 1) + 1, tokens: tokenize(description + ' ' + name), }; this._persist(); console.log('[Skill] Saved: ' + name + ' (' + type + ')'); }, get(name) { return this.skills[name] || null; }, recordUsage(name, success) { if (!this.skills[name]) return; this.skills[name].usageCount++; if (success) this.skills[name].successCount++; this.skills[name].lastUsed = Date.now(); this._persist(); }, getSkillList() { return Object.entries(this.skills) .filter(([name]) => !name.startsWith('_')) .map(([name, s]) => name + ' (' + s.type + '): ' + s.description + ' [' + s.successCount + '/' + s.usageCount + ']' ).join('\n'); }, getRelevantSkills(context, maxResults = 5) { const ctxTokens = tokenize(context); if (!ctxTokens.length) return []; const scored = Object.entries(this.skills) .filter(([name]) => !name.startsWith('_')) .map(([name, skill]) => { const sim = computeSimilarity(ctxTokens, skill.tokens || tokenize(skill.description + ' ' + name)); const successRate = skill.usageCount > 0 ? skill.successCount / skill.usageCount : 0.5; return { name, skill, score: sim * (0.3 + 0.7 * successRate) }; }).sort((a, b) => b.score - a.score); return scored.slice(0, maxResults); }, getSkillCodeForContext(context, maxResults = 3) { const relevant = this.getRelevantSkills(context, maxResults); if (relevant.length === 0) return ''; return '== RELEVANT SKILLS (reuse or adapt) ==\n' + relevant.map(({name, skill}) => '// ' + name + ': ' + skill.description + '\n' + skill.code.substring(0, 500) ).join('\n\n'); }, _persist() { try { const toSave = {}; for (const [k, v] of Object.entries(this.skills)) { if (k.startsWith('_')) continue; toSave[k] = v; } writeFileSync(CONFIG.skillFile, JSON.stringify(toSave, null, 2)); } catch {} }, load() { try { if (existsSync(CONFIG.skillFile)) { const data = JSON.parse(readFileSync(CONFIG.skillFile, 'utf-8')); this.skills = data; console.log('[Skill] Loaded ' + Object.keys(this.skills).length + ' skills'); } } catch {} }, }; // ═══════════════════════════════════════════════════════════════════════════ // PRE-SEEDED SKILLS — Proven code patterns (Odyssey: primitive + compositional) // ═══════════════════════════════════════════════════════════════════════════ function seedSkills() { if (skillLibrary.skills._seeded_version === 'v32') return; // === PRIMITIVE SKILLS (atomic actions) === skillLibrary.save('chopTree', `const logTypes = ['oak_log','birch_log','spruce_log','dark_oak_log','acacia_log','jungle_log']; let treeBlock = null; for (const log of logTypes) { treeBlock = bot.findBlock({ matching: b => b?.name === log, maxDistance: 48 }); if (treeBlock) break; } if (!treeBlock) { chat('no hay arboles'); return 'no trees'; } const moves = new Movements(bot); moves.canDig = false; bot.pathfinder.setMovements(moves); await bot.pathfinder.setGoal(new goals.GoalNear(treeBlock.position.x, treeBlock.position.y, treeBlock.position.z, 2)); await sleep(500); bot.lookAt(treeBlock.position.offset(0.5, 0.5, 0.5), true); await sleep(300); if (bot.canDigBlock(treeBlock)) { await bot.dig(treeBlock); memory.blocksMined++; } for (let dy = 1; dy <= 5; dy++) { const above = bot.blockAt(treeBlock.position.offset(0, dy, 0)); if (above && logTypes.some(l => above.name === l) && bot.canDigBlock(above)) { await bot.dig(above); memory.blocksMined++; } else break; } spatialMemory.record('oak_log', treeBlock.position.x, treeBlock.position.y, treeBlock.position.z, 'tree chopped'); chat('madera lista');`, 'Find and chop a tree, collect all logs above', 'primitive'); skillLibrary.save('craftItem2x2', `// craftItem2x2(itemName, count) - craft without crafting table const itemName = arguments[0] || 'oak_planks'; const count = arguments[1] || 1; const item = bot.registry.itemsByName[itemName]; if (!item) { chat('no conozco ' + itemName); return; } const recipes = bot.recipesFor(item.id, null, count, null); if (recipes.length === 0) { chat('no hay receta para ' + itemName); return; } const missing = bot.inventory.items().filter(i => !recipes[0].delta[i.id]); await bot.craft(recipes[0], count, null); memory.itemsCrafted += count; chat('craftee ' + count + ' ' + itemName);`, 'Craft an item using 2x2 inventory (no table needed)', 'primitive'); skillLibrary.save('placeBlock', `let item = bot.inventory.items().find(i => i.name === 'crafting_table'); if (!item) { chat('no tengo ese item'); return; } await bot.equip(item, 'hand'); await sleep(400); let groundBelow = bot.blockAt(bot.entity.position.offset(0, -1, 0)); if (!groundBelow || groundBelow.name === 'air') groundBelow = bot.blockAt(bot.entity.position.offset(1, -1, 0)); if (!groundBelow || groundBelow.name === 'air') groundBelow = bot.blockAt(bot.entity.position.offset(-1, -1, 0)); if (groundBelow && groundBelow.name !== 'air') { let face = new Vec3(0, 1, 0); await bot.placeBlock(groundBelow, face); await sleep(500); chat('bloque puesto'); } else { chat('no encontre donde ponerlo'); }`, 'Place a block from inventory on the ground (correct referenceBlock + faceVector)', 'primitive'); skillLibrary.save('eatFood', `const food = bot.inventory.items().find(i => isFoodItem(i.name)); if (!food) { chat('no tengo comida'); return; } await bot.equip(food, 'hand'); await sleep(300); await bot.activateItem(); await sleep(300); chat('nom');`, 'Eat food from inventory', 'primitive'); skillLibrary.save('fleeDanger', `let threat = null, minDist = 16; for (const [id, entity] of Object.entries(bot.entities)) { if (entity === bot.entity || !isHostileMob(entity) || !entity.position) continue; let dist = bot.entity.position.distanceTo(entity.position); if (dist < minDist) { threat = entity; minDist = dist; } } if (!threat) { chat('no hay peligro'); return; } let dx = bot.entity.position.x - threat.position.x; let dz = bot.entity.position.z - threat.position.z; let len = Math.sqrt(dx*dx + dz*dz) || 1; dx /= len; dz /= len; let yaw = Math.atan2(-dx, dz); await bot.look(yaw, 0, true); bot.setControlState('forward', true); bot.setControlState('sprint', true); bot.setControlState('jump', true); await sleep(3000); clearAllControls(); spatialMemory.record('danger', Math.round(threat.position.x), Math.round(threat.position.y), Math.round(threat.position.z), threat.name); memory.dangerZones.push({x: Math.floor(threat.position.x), z: Math.floor(threat.position.z), time: Date.now()}); memory.save(); chat('me largo de aqui');`, 'Flee from nearest hostile mob in opposite direction', 'primitive'); skillLibrary.save('huntAnimal', `let animals = []; for (const [id, entity] of Object.entries(bot.entities)) { if (entity === bot.entity || !entity.position) continue; if (entity.name === 'cow' || entity.name === 'pig' || entity.name === 'sheep' || entity.name === 'chicken' || entity.name === 'rabbit') { let dist = bot.entity.position.distanceTo(entity.position); if (dist < 20) animals.push({ entity, dist, name: entity.name }); } } if (animals.length === 0) { chat('no hay animales cerca'); return; } animals.sort((a, b) => a.dist - b.dist); let target = animals[0]; let moves = new Movements(bot); moves.canDig = false; bot.pathfinder.setMovements(moves); await bot.pathfinder.setGoal(new goals.GoalNear(target.entity.position.x, target.entity.position.y, target.entity.position.z, 2)); await sleep(500); let sword = bot.inventory.items().find(i => i.name.includes('sword') || i.name.includes('axe')); if (sword) { await bot.equip(sword, 'hand'); await sleep(300); } for (let i = 0; i < 6; i++) { bot.attack(target.entity); await sleep(500); if (!target.entity || target.entity.isValid === false || target.entity.health <= 0) break; } memory.kills++; chat('cazando ' + target.name);`, 'Find and kill nearest animal for food', 'primitive'); skillLibrary.save('digHole', `// Emergency shelter: dig a hole in the ground const pos = bot.entity.position; const bx = Math.floor(pos.x), by = Math.floor(pos.y), bz = Math.floor(pos.z); // Dig down 3 blocks for (let dy = 0; dy < 3; dy++) { const block = bot.blockAt(new Vec3(bx, by - 1 - dy, bz)); if (block && bot.canDigBlock(block)) { await bot.dig(block); memory.blocksMined++; await sleep(200); } } // Jump in bot.setControlState('forward', true); await sleep(200); bot.setControlState('jump', true); await sleep(500); clearAllControls(); // Cover top let dirt = bot.inventory.items().find(i => i.name === 'dirt' || i.name === 'cobblestone'); if (dirt) { await bot.equip(dirt, 'hand'); await sleep(400); const topBlock = bot.blockAt(new Vec3(bx, by + 1, bz)); if (topBlock && topBlock.name === 'air') { const ref = bot.blockAt(new Vec3(bx, by, bz)); if (ref) { try { await bot.placeBlock(ref, new Vec3(0, 1, 0)); await sleep(300); } catch(e) {} } } } memory.shelterPos = { x: bx, y: by - 3, z: bz }; memory.save(); chat('hoyo listo, sobrevivo la noche');`, 'Emergency shelter: dig 3-deep hole and cover top', 'primitive'); // === COMPOSITIONAL SKILLS (multi-step, combine primitives) === skillLibrary.save('craftPickaxe', `const log = bot.inventory.items().find(i => i.name.includes('_log')); if (!log) { chat('necesito madera'); return; } let planksItem = bot.registry.itemsByName['oak_planks']; if (!planksItem) planksItem = bot.registry.itemsByName[log.name.replace('_log','_planks')]; if (!planksItem) { chat('no se que planks hacer'); return; } let recipe = bot.recipesFor(planksItem.id, null, 1, null)[0]; if (recipe) { await bot.craft(recipe, 4, null); memory.itemsCrafted += 4; } let stickItem = bot.registry.itemsByName['stick']; let stickRecipe = bot.recipesFor(stickItem.id, null, 1, null)[0]; if (stickRecipe) { await bot.craft(stickRecipe, 2, null); memory.itemsCrafted += 2; } let ctItem = bot.inventory.items().find(i => i.name === 'crafting_table'); if (!ctItem) { let ctRecipe = bot.recipesFor(bot.registry.itemsByName['crafting_table'].id, null, 1, null)[0]; if (ctRecipe) { await bot.craft(ctRecipe, 1, null); memory.itemsCrafted++; ctItem = bot.inventory.items().find(i => i.name === 'crafting_table'); } } if (ctItem) { let existingCT = bot.findBlock({ matching: b => b?.name === 'crafting_table', maxDistance: 16 }); if (!existingCT) { await bot.equip(ctItem, 'hand'); await sleep(400); let refBlock = bot.blockAt(bot.entity.position.offset(0, -1, 0)); if (refBlock && refBlock.name !== 'air') { try { await bot.placeBlock(refBlock, new Vec3(0, 1, 0)); await sleep(500); } catch(e) {} } else { refBlock = bot.blockAt(bot.entity.position.offset(1, -1, 0)); if (refBlock) { try { await bot.placeBlock(refBlock, new Vec3(0, 1, 0)); await sleep(500); } catch(e) {} } } } } let ct = bot.findBlock({ matching: b => b?.name === 'crafting_table', maxDistance: 8 }); let pickItem = bot.registry.itemsByName['wooden_pickaxe']; let recipes = bot.recipesFor(pickItem.id, null, 1, ct); if (recipes.length > 0) { await bot.craft(recipes[0], 1, ct); memory.itemsCrafted++; chat('pico listo wey'); }`, 'Craft a wooden pickaxe from logs (planks + sticks + crafting_table + pickaxe)', 'compositional'); skillLibrary.save('progressToIron', `// Full progression: stone tools -> furnace -> smelt iron // 1. Need stone pickaxe first let cobble = bot.inventory.items().find(i => i.name === 'cobblestone'); if (!cobble || cobble.count < 3) { // Mine stone const stoneBlock = bot.findBlock({ matching: b => b?.name === 'stone' || b?.name === 'deepslate', maxDistance: 16 }); if (stoneBlock) { const pick = bot.inventory.items().find(i => i.name.includes('pickaxe')); if (pick) { await bot.equip(pick, 'hand'); await sleep(300); } const moves = new Movements(bot); moves.canDig = true; bot.pathfinder.setMovements(moves); await bot.pathfinder.setGoal(new goals.GoalNear(stoneBlock.position.x, stoneBlock.position.y, stoneBlock.position.z, 2)); await sleep(500); if (bot.canDigBlock(stoneBlock)) { await bot.dig(stoneBlock); memory.blocksMined++; } } } // 2. Craft stone pickaxe cobble = bot.inventory.items().find(i => i.name === 'cobblestone'); if (cobble && cobble.count >= 3) { const ct = bot.findBlock({ matching: b => b?.name === 'crafting_table', maxDistance: 16 }); if (ct) { const spItem = bot.registry.itemsByName['stone_pickaxe']; const recipes = bot.recipesFor(spItem.id, null, 1, ct); if (recipes.length > 0) { await bot.craft(recipes[0], 1, ct); memory.itemsCrafted++; chat('pico de piedra listo'); } } } // 3. Craft furnace cobble = bot.inventory.items().find(i => i.name === 'cobblestone'); if (cobble && cobble.count >= 8) { const fRecipe = bot.recipesFor(bot.registry.itemsByName['furnace'].id, null, 1, null)[0]; if (fRecipe) { await bot.craft(fRecipe, 1, null); memory.itemsCrafted++; } } // 4. Place furnace let furnaceItem = bot.inventory.items().find(i => i.name === 'furnace'); if (furnaceItem) { let furnaceBlock = bot.findBlock({ matching: b => b?.name === 'furnace', maxDistance: 16 }); if (!furnaceBlock) { await bot.equip(furnaceItem, 'hand'); await sleep(400); const ground = bot.blockAt(bot.entity.position.offset(1, -1, 0)); if (ground) { try { await bot.placeBlock(ground, new Vec3(0, 1, 0)); await sleep(500); } catch(e) {} } } } chat('progreso hacia hierro');`, 'Progress from stone tools to furnace setup for iron smelting', 'compositional'); skillLibrary.save('buildShelter', `const pos = bot.entity.position; let dirtBlock = bot.inventory.items().find(i => i.name === 'dirt' || i.name.includes('dirt')); let cobbleBlock = bot.inventory.items().find(i => i.name === 'cobblestone'); let buildItem = cobbleBlock || dirtBlock; if (!buildItem) { chat('necesito bloques, voy a minar'); let stoneBelow = bot.blockAt(pos.offset(0, -1, 0)); if (stoneBelow && bot.canDigBlock(stoneBelow)) { let pick = bot.inventory.items().find(i => i.name.includes('pickaxe')); if (pick) { await bot.equip(pick, 'hand'); await sleep(300); } await bot.dig(stoneBelow); memory.blocksMined++; buildItem = bot.inventory.items().find(i => i.name === 'cobblestone' || i.name === 'dirt'); } } if (!buildItem) { chat('no tengo nada chale'); return; } await bot.equip(buildItem, 'hand'); await sleep(300); let bx = Math.floor(pos.x), by = Math.floor(pos.y), bz = Math.floor(pos.z); for (let face of [[1,0],[-1,0],[0,1],[0,-1]]) { let wallX = bx + face[0], wallZ = bz + face[1]; let ref = bot.blockAt(new Vec3(wallX, by - 1, wallZ)); if (ref && ref.name !== 'air') { try { await bot.placeBlock(ref, new Vec3(0, 1, 0)); await sleep(300); } catch(e) {} ref = bot.blockAt(new Vec3(wallX, by, wallZ)); if (ref && ref.name !== 'air') { try { await bot.placeBlock(ref, new Vec3(0, 1, 0)); await sleep(300); } catch(e) {} } } } memory.shelterPos = { x: bx, y: by, z: bz }; spatialMemory.record('shelter', bx, by, bz, 'basic shelter built'); memory.save(); chat('refugio listo');`, 'Build a basic shelter (4 walls, 2 high) using available blocks', 'compositional'); skillLibrary.skills._seeded_version = 'v34'; skillLibrary._persist(); console.log('[Skill] Pre-seeded v34 skills (primitive + compositional)'); } // ═══════════════════════════════════════════════════════════════════════════ // CURRICULUM SYSTEM — From Voyager // Automatic goal generation based on progress, inventory, and world state // ═══════════════════════════════════════════════════════════════════════════ const curriculum = { // Voyager-style warm-up: gradually reveal more info getInfoLevel() { const completed = memory.completedGoals.length; return { showAllResources: completed >= 5, showDetailedInventory: completed >= 3, showCraftableItems: completed >= 5, showSpatialMemory: completed >= 7, }; }, // Generate next goal based on current state proposeGoal(observation) { const pos = bot?.entity?.position; const items = bot?.inventory?.items() || []; const itemNames = items.map(i => i.name); const isNight = (bot?.time?.timeOfDay ?? 0) >= 13000 && (bot?.time?.timeOfDay ?? 0) < 23000; const hp = bot?.health ?? 20; const food = bot?.food ?? 20; // PRIORITY 1: Emergency survival if (hp < 10 && isHostileNearby()) return { goal: 'flee from danger', priority: 'critical', reason: 'HP bajo y mobs hostiles cerca' }; if (food < 8) return { goal: 'find and eat food', priority: 'critical', reason: 'comida baja (' + Math.round(food) + '/20)' }; if (isNight && !memory.shelterPos) return { goal: 'dig emergency hole shelter', priority: 'critical', reason: 'es de noche sin refugio' }; // PRIORITY 2: Progression (Knowledge Graph driven) const nextGoal = KNOWLEDGE_GRAPH.getNextGoal(itemNames, benchmark.milestones); if (nextGoal) { const goalStr = 'obtain ' + nextGoal.item; // Check if we've failed this too many times const retryCount = memory.goalRetryCount[goalStr] || 0; if (retryCount >= 3) { // Try a different approach or simpler goal return { goal: 'explore area for resources', priority: 'high', reason: 'stuck on ' + goalStr + ', need to explore' }; } return { goal: goalStr, priority: 'high', reason: 'next in tech tree: ' + nextGoal.item + ' (needs ' + nextGoal.requires.join(', ') + (nextGoal.station ? ' at ' + nextGoal.station : '') + ')' }; } // PRIORITY 3: Improvement (if we have basics) if (!memory.shelterPos && itemNames.some(n => n.includes('cobblestone') || n.includes('dirt'))) { return { goal: 'build proper shelter', priority: 'medium', reason: 'no shelter yet' }; } if (itemNames.some(n => n.includes('raw_iron')) && itemNames.some(n => n === 'coal' || n === 'charcoal')) { return { goal: 'smelt iron in furnace', priority: 'high', reason: 'have raw iron and coal' }; } if (!itemNames.some(n => n.includes('cooked_')) && memory.kills > 0) { return { goal: 'cook food in furnace', priority: 'medium', reason: 'have raw food and need cooked food' }; } // PRIORITY 4: Explore and discover (Voyager's novelty search) if (!itemNames.some(n => n.includes('_log'))) return { goal: 'chop tree for wood', priority: 'high', reason: 'no wood in inventory' }; if (!itemNames.some(n => n.includes('_planks'))) return { goal: 'craft planks from logs', priority: 'high', reason: 'no planks' }; if (!itemNames.some(n => n === 'stick')) return { goal: 'craft sticks from planks', priority: 'high', reason: 'no sticks' }; if (!itemNames.some(n => n === 'crafting_table')) return { goal: 'craft and place crafting table', priority: 'high', reason: 'no crafting table' }; if (!itemNames.some(n => n.includes('pickaxe'))) return { goal: 'craft a pickaxe', priority: 'high', reason: 'no pickaxe' }; if (!itemNames.some(n => n === 'cobblestone') && !itemNames.some(n => n === 'stone')) return { goal: 'mine stone with pickaxe', priority: 'high', reason: 'no stone' }; return { goal: 'explore nearby area for resources', priority: 'low', reason: 'have basic tools, look for more resources' }; }, markCompleted(goal) { if (!memory.completedGoals.includes(goal)) { memory.completedGoals.push(goal); } memory.currentGoal = null; if (memory.goalRetryCount[goal]) delete memory.goalRetryCount[goal]; memory.save(); }, markFailed(goal, reason) { if (!memory.failedGoals.includes(goal)) { memory.failedGoals.push(goal); } memory.goalRetryCount[goal] = (memory.goalRetryCount[goal] || 0) + 1; // Store as remedy (MineEvolve) remedySystem.store( goal, 'goal_failed', reason || 'unknown failure', 'try different approach or gather more resources first', 'goal' ); memory.save(); }, }; function isHostileNearby() { if (!bot?.entity) return false; try { for (const [id, entity] of Object.entries(bot.entities)) { if (entity === bot.entity || !entity.position) continue; if (isHostileMob(entity) && bot.entity.position.distanceTo(entity.position) < 16) return true; } } catch {} return false; } // ═══════════════════════════════════════════════════════════════════════════ // CRITIC / SELF-VERIFICATION — From Voyager // Verifies if a goal was achieved by checking world state // ═══════════════════════════════════════════════════════════════════════════ const critic = { verify(goal, preState, postState) { if (!goal) return { success: true, critique: '' }; const checkItem = goal.match(/obtain\s+(\w+)/); if (checkItem) { const targetItem = checkItem[1]; const hadIt = preState.includes(targetItem) || preState.includes(targetItem.replace('_', ' ')); const hasIt = postState.includes(targetItem) || postState.includes(targetItem.replace('_', ' ')); if (hasIt && !hadIt) return { success: true, critique: 'Successfully obtained ' + targetItem }; if (hasIt && hadIt) return { success: true, critique: 'Already had ' + targetItem }; return { success: false, critique: 'Failed to obtain ' + targetItem + '. Check if you have the required materials and tools.' }; } if (goal.includes('shelter') || goal.includes('hole')) { if (memory.shelterPos) return { success: true, critique: 'Shelter is set up' }; return { success: false, critique: 'No shelter was created. Place blocks around you or dig a hole.' }; } if (goal.includes('food') || goal.includes('eat')) { const foodNow = bot?.food ?? 0; if (foodNow > 12) return { success: true, critique: 'Food level restored to ' + Math.round(foodNow) }; return { success: false, critique: 'Still hungry (food=' + Math.round(foodNow) + '). Find and eat food.' }; } if (goal.includes('flee') || goal.includes('danger')) { if (!isHostileNearby()) return { success: true, critique: 'Safely away from danger' }; return { success: false, critique: 'Still near hostile mobs. Keep running!' }; } if (goal.includes('explore')) { // Exploration is always a "success" if we moved return { success: true, critique: 'Explored the area' }; } // Default: check if code ran without error return { success: true, critique: '' }; }, }; // ═══════════════════════════════════════════════════════════════════════════ // BENCHMARK SYSTEM — Enhanced with MCU-style tasks + MineNPC milestones // ═══════════════════════════════════════════════════════════════════════════ const benchmark = { startTime: Date.now(), sessionDeaths: 0, longestSurvivalMs: 0, _lastDeathTime: null, _currentSurvivalStart: Date.now(), milestones: { gotWood: false, craftedPlanks: false, craftedSticks: false, craftedCraftingTable: false, craftedWoodenPickaxe: false, minedStone: false, craftedStonePickaxe: false, craftedFurnace: false, gotIron: false, craftedIronPickaxe: false, craftedIronSword: false, builtShelter: false, killedMob: false, cookedFood: false, gotDiamond: false, craftedDiamondPickaxe: false, smeltedIron: false, craftedShield: false, craftedBow: false, usedBed: false, }, checkMilestones() { const items = bot?.inventory?.items() || []; const names = items.map(i => i.name); if (names.some(n => n.includes('_log'))) this.milestones.gotWood = true; if (names.some(n => n.includes('_planks'))) this.milestones.craftedPlanks = true; if (names.some(n => n === 'stick')) this.milestones.craftedSticks = true; if (names.some(n => n === 'crafting_table')) this.milestones.craftedCraftingTable = true; if (names.some(n => n === 'wooden_pickaxe')) this.milestones.craftedWoodenPickaxe = true; if (memory.blocksMined > 5) this.milestones.minedStone = true; if (names.some(n => n === 'stone_pickaxe')) this.milestones.craftedStonePickaxe = true; if (names.some(n => n === 'furnace')) this.milestones.craftedFurnace = true; if (names.some(n => n.includes('iron_ingot') || n.includes('raw_iron'))) this.milestones.gotIron = true; if (names.some(n => n === 'iron_pickaxe')) this.milestones.craftedIronPickaxe = true; if (names.some(n => n === 'iron_sword')) this.milestones.craftedIronSword = true; if (memory.shelterPos) this.milestones.builtShelter = true; if (memory.kills > 0) this.milestones.killedMob = true; if (names.some(n => n.includes('cooked_'))) this.milestones.cookedFood = true; if (names.some(n => n.includes('diamond'))) this.milestones.gotDiamond = true; if (names.some(n => n === 'diamond_pickaxe')) this.milestones.craftedDiamondPickaxe = true; if (names.some(n => n === 'iron_ingot')) this.milestones.smeltedIron = true; if (names.some(n => n === 'shield')) this.milestones.craftedShield = true; if (names.some(n => n === 'bow')) this.milestones.craftedBow = true; }, getSurvivalTime() { if (this._lastDeathTime) return this._lastDeathTime - this._currentSurvivalStart; return Date.now() - this._currentSurvivalStart; }, recordDeath() { const survivalMs = this.getSurvivalTime(); if (survivalMs > this.longestSurvivalMs) this.longestSurvivalMs = survivalMs; this._lastDeathTime = Date.now(); this.sessionDeaths++; this._currentSurvivalStart = Date.now(); }, getReport() { this.checkMilestones(); const achieved = Object.entries(this.milestones).filter(([,v]) => v).map(([k]) => k); const pending = Object.entries(this.milestones).filter(([,v]) => !v).map(([k]) => k); const survMin = Math.floor(this.getSurvivalTime() / 60000); const bestMin = Math.floor(this.longestSurvivalMs / 60000); return 'Survival: ' + survMin + 'min (best: ' + bestMin + 'min) | Deaths: ' + this.sessionDeaths + ' | Milestones: ' + achieved.length + '/20 [' + achieved.join(',') + '] | Pending: [' + pending.join(',') + ']'; }, save() { try { writeFileSync(CONFIG.benchmarkFile, JSON.stringify({ startTime: this.startTime, sessionDeaths: this.sessionDeaths, longestSurvivalMs: this.longestSurvivalMs, milestones: this.milestones, }, null, 2)); } catch {} }, }; // ═══════════════════════════════════════════════════════════════════════════ // STATE // ═══════════════════════════════════════════════════════════════════════════ const state = { activity: 'idle', emotion: 'curious', connectedSince: '', _consecutiveKicks: 0, _lastChatTime: 0, _chatCooldown: 12000, _lastGoodPos: null, _positionReady: false, _recentSentMsgs: [], _isRespawning: false, _nanTotalEver: 0, _nanRecoveryTpPending: false, _nanPhysicsDisabled: false, _kickReconnectDelay: 10000, chatLog: [], _recentChat: [], _actionInProgress: false, _lastCodeError: null, _lastCodeResult: null, _stuckCounter: 0, _lastPos: null, _thinkCount: 0, _lastInventorySnapshot: '', _retryCount: 0, _lastGoal: null, _nightShelterAttempt: false, _bootComplete: true, // Always true - no boot sequence in v36 _consecutiveAITimeouts: 0, }; let bot = null; let Vec3 = null; // ═══════════════════════════════════════════════════════════════════════════ // CONVERSATION HISTORY — Multi-turn context for GLM-4 // ═══════════════════════════════════════════════════════════════════════════ const conversationHistory = []; const MAX_HISTORY = CONFIG.maxConversationTurns; function addSystemMessage(content) { conversationHistory.push({ role: 'system', content }); trimHistory(); } function addAssistantMessage(content) { conversationHistory.push({ role: 'assistant', content }); trimHistory(); } function addUserMessage(content) { conversationHistory.push({ role: 'user', content }); trimHistory(); } function trimHistory() { while (conversationHistory.length > MAX_HISTORY) { if (conversationHistory[0]?.role === 'system' && conversationHistory.length > 1) { conversationHistory.splice(1, 1); } else { conversationHistory.shift(); } } } function softResetConversation() { const systemMsgs = conversationHistory.filter(m => m.role === 'system'); const lastMsgs = conversationHistory.slice(-4); conversationHistory.length = 0; for (const m of systemMsgs.slice(0, 1)) conversationHistory.push(m); for (const m of lastMsgs) conversationHistory.push(m); } // ═══════════════════════════════════════════════════════════════════════════ // LLM INTERFACE — GLM-4 ONLY // ═══════════════════════════════════════════════════════════════════════════ async function callGLM(messages, maxTokens = CONFIG.maxTokens, temperature = 0.85) { const maxRetries = 3; const delays = [5000, 15000, 30000]; // exponential backoff for (let attempt = 0; attempt < maxRetries; attempt++) { try { const res = await fetch(CONFIG.llm.baseUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + CONFIG.llm.key, }, body: JSON.stringify({ model: CONFIG.llm.model, messages, max_tokens: maxTokens, temperature, stream: false, }), signal: AbortSignal.timeout(120000), // 120s timeout (was 45s) }); if (!res.ok) { const errText = await res.text().catch(() => ''); console.error('[GLM] Error ' + res.status + ': ' + errText.substring(0, 200)); if (attempt < maxRetries - 1) { console.log('[GLM] Retry ' + (attempt + 1) + '/' + maxRetries + ' in ' + (delays[attempt]/1000) + 's'); await sleep(delays[attempt]); continue; } state._consecutiveAITimeouts++; return null; } const data = await res.json(); state._consecutiveAITimeouts = 0; // Reset on success return data.choices?.[0]?.message?.content?.trim() ?? null; } catch (e) { console.error('[GLM] Attempt ' + (attempt+1) + '/' + maxRetries + ' failed: ' + e.message); if (attempt < maxRetries - 1) { console.log('[GLM] Retry in ' + (delays[attempt]/1000) + 's...'); await sleep(delays[attempt]); continue; } state._consecutiveAITimeouts++; return null; } } return null; } // ═══════════════════════════════════════════════════════════════════════════ // PERCEPTION — Enhanced with spatial memory + knowledge graph context // ═══════════════════════════════════════════════════════════════════════════ function buildObservation() { if (!bot?.entity || !hasValidPosition()) return 'Position invalid or not connected yet.'; const pos = bot.entity.position; const timeOfDay = bot.time?.timeOfDay ?? 0; const isNight = timeOfDay >= 13000 && timeOfDay < 23000; const timeStr = isNight ? 'NIGHT (DANGEROUS!)' : 'Day (safe)'; const inWater = isBotInWater(); let timeNote = ''; if (!isNight && timeOfDay > 10000) { const minsToNight = Math.round((13000 - timeOfDay) / 17); timeNote = ' (night in ~' + minsToNight + 's - BUILD SHELTER!)'; } else if (isNight) { const minsToDawn = Math.round((23000 - timeOfDay) / 17); timeNote = ' (dawn in ~' + minsToDawn + 's)'; } let obs = ''; // CURRICULUM GOAL obs += '== CURRENT GOAL ==\n'; const goal = curriculum.proposeGoal(''); obs += goal.goal + ' [' + goal.priority + '] - ' + goal.reason + '\n'; // VITALS obs += '\n== VITALS ==\n'; obs += 'Pos: ' + Math.round(pos.x) + ', ' + Math.round(pos.y) + ', ' + Math.round(pos.z) + '\n'; obs += 'HP: ' + Math.round(bot.health*10)/10 + '/20 | Food: ' + Math.round(bot.food*10)/10 + '/20\n'; if (bot.health < 10) obs += '*** LOW HP - EAT OR FLEE! ***\n'; if (bot.food < 10) obs += '*** LOW FOOD - EAT NOW! ***\n'; obs += 'Time: ' + timeStr + ' (' + Math.round(timeOfDay) + ')' + timeNote + '\n'; obs += 'Weather: ' + (bot.isRaining ? 'Raining' : 'Clear') + ' | Water: ' + inWater + '\n'; // INVENTORY obs += '\n== INVENTORY ==\n'; const items = bot.inventory.items(); if (items.length === 0) { obs += 'EMPTY - no items!\n'; } else { const inv = {}; for (const item of items) { if (!inv[item.name]) inv[item.name] = 0; inv[item.name] += item.count; } const tools = [], food = [], building = [], resources = [], other = []; for (const [name, count] of Object.entries(inv)) { const entry = name + ' x' + count; if (name.includes('pickaxe') || name.includes('axe') || name.includes('sword') || name.includes('shovel') || name.includes('hoe') || name.includes('bow')) tools.push(entry); else if (isFoodItem(name)) food.push(entry); else if (name.includes('planks') || name.includes('dirt') || name.includes('cobblestone') || name.includes('stone') || name.includes('log')) building.push(entry); else if (name.includes('ingot') || name.includes('ore') || name.includes('diamond') || name.includes('coal') || name.includes('stick') || name.includes('raw_')) resources.push(entry); else other.push(entry); } if (tools.length > 0) obs += 'Tools: ' + tools.join(', ') + '\n'; if (food.length > 0) obs += 'Food: ' + food.join(', ') + '\n'; if (building.length > 0) obs += 'Building: ' + building.join(', ') + '\n'; if (resources.length > 0) obs += 'Resources: ' + resources.join(', ') + '\n'; if (other.length > 0) obs += 'Other: ' + other.join(', ') + '\n'; const heldItem = bot.heldItem; if (heldItem) obs += 'Holding: ' + heldItem.name + '\n'; const armorSlots = ['head', 'torso', 'legs', 'feet']; const armorItems = []; for (const slot of armorSlots) { const equipment = bot.inventory.slots[bot.getEquipmentDestSlot(slot)]; if (equipment) armorItems.push(slot + ':' + equipment.name); } if (armorItems.length > 0) obs += 'Armor: ' + armorItems.join(', ') + '\n'; // KNOWLEDGE GRAPH: what can we craft right now? const craftable = KNOWLEDGE_GRAPH.getCraftableNow(Object.keys(inv)); if (craftable.length > 0) obs += 'Craftable: ' + craftable.join(', ') + '\n'; } // NEXT CRAFT STEP from Knowledge Graph const invNames2 = {}; for (const i of bot.inventory.items()) { invNames2[i.name] = i.count; } const nextGoal2 = KNOWLEDGE_GRAPH.getNextGoal(Object.keys(invNames2), benchmark.milestones); if (nextGoal2) { obs += '\n== NEXT CRAFT STEP ==\n'; obs += 'Target: ' + nextGoal2.item + '\n'; obs += 'Need: ' + nextGoal2.requires.join(', ') + '\n'; if (nextGoal2.station) obs += 'Station: ' + nextGoal2.station + '\n'; const craftPath = KNOWLEDGE_GRAPH.getCraftingPath(nextGoal2.item); obs += 'Path: ' + craftPath + '\n'; } // NEARBY ENTITIES obs += '\n== NEARBY ==\n'; const nearbyPlayers = [], nearbyHostile = [], nearbyPassive = [], nearbyItems = []; try { for (const [id, entity] of Object.entries(bot.entities)) { if (entity === bot.entity || !entity.position || !isFinite(entity.position.x)) continue; const dist = Math.round(pos.distanceTo(entity.position)); if (dist > 40) continue; if (entity.type === 'player' && entity.username) nearbyPlayers.push(entity.username + '(' + dist + 'm)'); else if (isHostileMob(entity)) { const hp = entity.health ? ' hp:' + Math.round(entity.health) : ''; nearbyHostile.push(entity.name + '(' + dist + 'm' + hp + ')'); } else if (entity.type === 'mob') nearbyPassive.push(entity.name + '(' + dist + 'm)'); else if (entity.type === 'object' || entity.name === 'item' || entity.name === 'Item') nearbyItems.push('item(' + dist + 'm)'); } } catch {} if (nearbyPlayers.length > 0) obs += 'Players: ' + nearbyPlayers.join(', ') + '\n'; if (nearbyHostile.length > 0) obs += 'HOSTILES: ' + nearbyHostile.join(', ') + '\n'; if (nearbyPassive.length > 0) obs += 'Animals: ' + nearbyPassive.join(', ') + '\n'; if (nearbyItems.length > 0) obs += 'Ground items: ' + nearbyItems.length + ' nearby\n'; // SURROUNDING BLOCKS obs += '\n== SURROUNDING (5x5) ==\n'; try { const cx = Math.floor(pos.x), cy = Math.floor(pos.y), cz = Math.floor(pos.z); for (let dz = -2; dz <= 2; dz++) { let row = ''; for (let dx = -2; dx <= 2; dx++) { if (dx === 0 && dz === 0) { row += '[ME] '; continue; } const b = bot.blockAt(new Vec3(cx + dx, cy, cz + dz)); if (!b) { row += '??? '; continue; } const n = b.name; if (n === 'air') row += '. '; else if (n.includes('log')) row += 'L '; else if (n.includes('leaves')) row += 'l '; else if (n === 'water') row += '~ '; else if (n === 'lava') row += '! '; else if (n.includes('ore')) row += 'O '; else if (n === 'stone' || n === 'deepslate') row += 'S '; else if (n === 'grass_block' || n === 'dirt') row += 'D '; else if (n === 'sand') row += 's '; else if (n === 'crafting_table') row += 'C '; else if (n === 'furnace') row += 'F '; else if (n === 'torch') row += 'T '; else if (n === 'bed') row += 'B '; else row += '? '; } obs += row + '\n'; } } catch {} // RESOURCES NEARBY (with spatial memory) obs += '\n== RESOURCES ==\n'; const resourceTypes = [ ['oak_log,birch_log,spruce_log,dark_oak_log,acacia_log,jungle_log', 'Trees'], ['coal_ore', 'Coal'], ['iron_ore', 'Iron'], ['gold_ore', 'Gold'], ['diamond_ore', 'Diamond'], ['crafting_table', 'CraftTable'], ['furnace', 'Furnace'], ]; let foundResources = false; for (const [blockNames, label] of resourceTypes) { for (const bn of blockNames.split(',')) { const found = bot.findBlock({ matching: (b) => b?.name === bn, maxDistance: 48 }); if (found) { const dist = Math.round(pos.distanceTo(found.position)); obs += label + ': ' + found.position.x + ',' + found.position.y + ',' + found.position.z + ' (' + dist + 'm)\n'; spatialMemory.record(bn, found.position.x, found.position.y, found.position.z, 'resource detected'); foundResources = true; break; } } } // Add spatial memory for resources not currently visible const spatialResources = spatialMemory.getResourceLocations(pos.x, pos.z); for (const [type, loc] of Object.entries(spatialResources)) { if (!foundResources || !obs.includes(type.replace('_ore', ''))) { obs += type + ' (memory): ' + loc.x + ',' + loc.y + ',' + loc.z + ' (' + loc.dist + 'm)\n'; } } if (!foundResources && Object.keys(spatialResources).length === 0) obs += 'No resources known nearby\n'; // SHELTER & HOME obs += '\n== BASE ==\n'; if (memory.shelterPos) { const sd = Math.round(Math.sqrt((pos.x-memory.shelterPos.x)**2 + (pos.z-memory.shelterPos.z)**2)); obs += 'Shelter: ' + memory.shelterPos.x + ',' + memory.shelterPos.y + ',' + memory.shelterPos.z + ' (' + sd + 'm)\n'; } else { obs += 'No shelter!\n'; } // STATUS obs += '\n== STATUS ==\n'; obs += 'Deaths: ' + memory.deaths + ' | Kills: ' + memory.kills + ' | Mined: ' + memory.blocksMined + ' | Crafted: ' + memory.itemsCrafted + '\n'; obs += 'Goals completed: ' + memory.completedGoals.length + ' | Failed: ' + memory.failedGoals.length + '\n'; obs += 'Benchmark: ' + benchmark.getReport() + '\n'; if (state._lastCodeError) obs += 'LastError: ' + state._lastCodeError.substring(0, 150) + '\n'; if (memory.dangerZones?.length > 0) { const now = Date.now(); const activeZones = memory.dangerZones.filter(z => now - z.time < 90000); if (activeZones.length > 0) obs += 'DangerZones: ' + activeZones.map(z => z.x + ',' + z.z).join(' | ') + '\n'; } // EPISODIC MEMORY: relevant past experiences const episodicContext = episodicMemory.getRelevantContext(goal.goal, obs.substring(0, 300)); if (episodicContext) obs += '\n' + episodicContext + '\n'; // REMEDIES: known failure patterns const remedyContext = remedySystem.getRemedyContext(goal.goal + ' ' + (state._lastCodeError || '')); if (remedyContext) obs += '\n' + remedyContext + '\n'; // RECENT CHAT if (state._recentChat.length > 0) { obs += '\n== CHAT ==\n'; for (const msg of state._recentChat.slice(-3)) { obs += '<' + msg.sender + '> ' + msg.text + '\n'; } } return obs; } function isBotInWater() { if (!bot?.entity || !hasValidPosition()) return false; try { const pos = bot.entity.position; const feet = bot.blockAt({x:Math.floor(pos.x),y:Math.floor(pos.y),z:Math.floor(pos.z)}); const head = bot.blockAt({x:Math.floor(pos.x),y:Math.floor(pos.y+1),z:Math.floor(pos.z)}); return feet?.name === 'water' || head?.name === 'water'; } catch { return false; } } function isHostileMob(entity) { if (!entity) return false; if (entity.kind === 'Hostile mobs') return true; const name = (entity.name ?? '').toLowerCase(); const hostiles = ['zombie','skeleton','creeper','spider','witch','slime','phantom','drowned','pillager','vindicator','enderman','cave_spider','blaze','ghast','magma_cube','wither_skeleton','stray','husk']; return hostiles.some(m => name.includes(m)); } function isFoodItem(name) { const foods = ['bread','cooked_beef','cooked_porkchop','cooked_chicken','cooked_mutton','cooked_cod','cooked_salmon','apple','golden_apple','melon_slice','sweet_berries','carrot','baked_potato','beetroot','cookie','cake','pumpkin_pie','beef','porkchop','chicken','mutton','cod','salmon','rotten_flesh','potato','wheat','beetroot_soup','mushroom_stew','rabbit_stew']; return foods.some(f => name.includes(f)); } function snapshotInventory() { try { if (!bot?.inventory) return ''; return bot.inventory.items().map(i => i.name + 'x' + i.count).join(','); } catch { return ''; } } // v36: Auto-track metrics from inventory changes function parseInventoryStr(invStr) { const map = {}; if (!invStr) return map; for (const part of invStr.split(',')) { const match = part.match(/(.+?)x(\d+)/); if (match) map[match[1].trim()] = parseInt(match[2]); } return map; } function trackMetricsFromInventory(preInv, postInv) { try { const pre = parseInventoryStr(preInv); const post = parseInventoryStr(postInv); let newMined = 0, newCrafted = 0; const minedItems = ['cobblestone', 'stone', 'dirt', 'oak_log', 'birch_log', 'spruce_log', 'dark_oak_log', 'acacia_log', 'jungle_log', 'coal_ore', 'iron_ore', 'gold_ore', 'diamond_ore', 'copper_ore', 'lapis_ore', 'redstone_ore', 'emerald_ore', 'sand', 'gravel', 'clay']; for (const [name, count] of Object.entries(post)) { const preCount = pre[name] || 0; if (count > preCount) { const diff = count - preCount; if (minedItems.some(m => name.includes(m))) { newMined += diff; } else { newCrafted += diff; } } } if (newMined > 0) { memory.blocksMined += newMined; console.log('[Metrics] +' + newMined + ' blocks mined (auto-tracked)'); } if (newCrafted > 0) { memory.itemsCrafted += newCrafted; console.log('[Metrics] +' + newCrafted + ' items crafted (auto-tracked)'); } } catch {} } // ═══════════════════════════════════════════════════════════════════════════ // CODE EXECUTION — The heart of the architecture // ═══════════════════════════════════════════════════════════════════════════ function buildExecutionContext() { return { bot, goals, Movements, Vec3, sleep, rand, hasValidPosition, clearAllControls, memory, skills: skillLibrary, spatialMemory, episodicMemory, remedySystem, KNOWLEDGE_GRAPH, isFoodItem, isHostileMob, isBotInWater, Math, JSON, Date, String, Object, Array, parseInt, parseFloat, console: { log: (...args) => console.log('[Code]', ...args), error: (...args) => console.error('[Code]', ...args), warn: (...args) => console.warn('[Code]', ...args), }, chat: (msg) => { if (msg) throttledChat(String(msg).substring(0, 100)); }, }; } const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor; async function executeCode(code) { if (!bot?.entity || !hasValidPosition()) { return { success: false, error: 'Bot not connected or position invalid', output: '' }; } state._actionInProgress = true; let output = ''; const startTime = Date.now(); try { const ctx = buildExecutionContext(); ctx.log = (...args) => { const msg = args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' '); output += msg + '\n'; console.log('[Code]', ...args); }; let fn; try { fn = new AsyncFunction(...Object.keys(ctx), code); } catch (syntaxErr) { return { success: false, error: 'Syntax error: ' + syntaxErr.message, output }; } const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Code timeout (' + CONFIG.codeTimeout/1000 + 's)')), CONFIG.codeTimeout) ); const result = await Promise.race([fn(...Object.values(ctx)), timeoutPromise]); const elapsed = Date.now() - startTime; const resultStr = result !== undefined ? String(result) : 'done'; state._lastCodeError = null; state._lastCodeResult = resultStr + ' (' + elapsed + 'ms)'; return { success: true, output: output + (resultStr !== 'done' ? ' -> ' + resultStr : ''), result: resultStr, elapsed }; } catch (execErr) { const elapsed = Date.now() - startTime; const errMsg = execErr.message || String(execErr); state._lastCodeError = errMsg; state._lastCodeResult = 'ERROR: ' + errMsg; clearAllControls(); try { bot.pathfinder?.setGoal(null); } catch {} return { success: false, error: errMsg, output, elapsed }; } finally { state._actionInProgress = false; clearAllControls(); } } // ═══════════════════════════════════════════════════════════════════════════ // SYSTEM PROMPT — v32.0 OMEGA // ═══════════════════════════════════════════════════════════════════════════ function buildSystemPrompt() { const skillList = skillLibrary.getSkillList(); const skillContext = skillLibrary.getSkillCodeForContext('current situation', 3); return 'Eres ' + CONFIG.profile.name + ', una IA que juega Minecraft como una jugadora PRO.\n' + CONFIG.profile.personality + '\n\n' + 'Tienes LIBRE ALBEDRIO TOTAL. Eres una jugadora real, no un bot limitado.\n' + 'Escribes CODIGO JAVASCRIPT que se ejecuta directamente con la API de mineflayer.\n' + 'Puedes hacer CUALQUIER cosa que una jugadora PRO haria.\n\n' + '== ARQUITECTURA OMEGA (v32.0) ==\n' + '1. Recibes tu GOAL actual del Curriculum (Voyager-style automatic curriculum)\n' + '2. Consultas Knowledge Graph para saber que necesitas craftear\n' + '3. Recibes Experiencias Episodicas relevantes (M2PA memory)\n' + '4. Recibes Remedios para errores conocidos (MineEvolve)\n' + '5. Escribes codigo JavaScript para actuar\n' + '6. Si falla, usas DEPS: Describe el error, Explica porque, Plan nuevo, Selecciona accion\n' + '7. Tienes hasta ' + CONFIG.maxRetries + ' intentos para cada goal (iterative retry)\n' + '8. Si exitoso, se guarda como Skill y Episodio para el futuro\n\n' + '== CODIGO JAVASCRIPT - API DISPONIBLE ==\n\n' + 'Tu codigo es el CUERPO de una funcion async. Tienes acceso a:\n\n' + '**bot** - El bot de mineflayer (API completa):\n' + '- bot.entity.position - tu posicion {x, y, z}\n' + '- bot.health, bot.food - salud y comida\n' + '- bot.inventory.items() - lista de items en inventario\n' + '- bot.heldItem - item en mano\n' + '- bot.equip(item, slot) - equipar item (slot: "hand", "head", "torso", "legs", "feet")\n' + '- bot.dig(block) - minar bloque (await!)\n' + '- bot.placeBlock(referenceBlock, faceVector) - colocar bloque (await!)\n' + ' CRITICAL: referenceBlock es un bloque YA COLOCADO contra el cual pones el nuevo\n' + ' faceVector es un Vec3 que apunta del referenceBlock hacia donde va el nuevo bloque\n' + ' Para colocar en el suelo: referenceBlock = bloque debajo (y-1), faceVector = new Vec3(0,1,0)\n' + '- bot.craft(recipe, count, craftingTable) - craftear (await!)\n' + '- bot.recipesFor(itemID, null, count, craftingTable) - buscar recetas\n' + ' craftingTable = null para recetas 2x2, o un Block de crafting_table para recetas 3x3\n' + '- bot.activateItem() - usar item equipado (comer)\n' + '- bot.findBlock({matching, maxDistance}) - encontrar bloque cercano (returns Block or null)\n' + ' IMPORTANTE: matching usa b?.name (null-safe): matching: b => b?.name === "oak_log"\n' + '- bot.blockAt(vec3) - bloque en posicion\n' + '- bot.canDigBlock(block) - se puede minar?\n' + '- bot.chat(msg) - enviar mensaje\n' + '- bot.look(yaw, pitch, force) - mirar direccion\n' + '- bot.setControlState(control, state) - controles: forward, back, left, right, jump, sprint, sneak\n' + '- bot.attack(entity) - atacar entidad\n' + '- bot.registry.itemsByName["oak_planks"] - info de item (.id para recipesFor)\n\n' + '**CRITICAL: bot.entities es un OBJETO, no un Map.**\n' + ' ITERAR SIEMPRE ASI: for (const [id, entity] of Object.entries(bot.entities))\n' + ' NUNCA: for (const [id, entity] of bot.entities) <-- ESTO CRASHEA\n\n' + '**Vec3** - new Vec3(x, y, z) para posiciones y faceVector\n' + '**goals** - new goals.GoalBlock(x,y,z), new goals.GoalNear(x,y,z,range)\n' + '**Movements** - new Movements(bot), moves.canDig, bot.pathfinder.setMovements(moves)\n' + '**sleep(ms)**, **chat(msg)**, **rand(min,max)**\n' + '**memory** - memoria persistente (shelterPos, home, deaths, kills, etc.)\n' + '**spatialMemory** - memoria espacial (What-Where-When)\n' + ' spatialMemory.record(what, x, y, z, details)\n' + ' spatialMemory.findNearest(what, fromX, fromZ, maxDist)\n' + '**KNOWLEDGE_GRAPH** - grafo de recetas\n' + ' KNOWLEDGE_GRAPH.getCraftableNow(inventoryNames)\n' + ' KNOWLEDGE_GRAPH.getNextGoal(inventoryNames)\n' + ' KNOWLEDGE_GRAPH.getCraftingPath(item)\n' + '**skills** - Skill library (save/get skills)\n' + '**isFoodItem(name), isHostileMob(entity), isBotInWater()** - helpers\n\n' + '== DEPS ERROR RECOVERY ==\n' + 'Cuando tu codigo falla, sigue este proceso:\n' + '1. DESCRIBE: Que paso? Que error hubo?\n' + '2. EXPLICA: Porque fallo? Que faltaba? Que era incorrecto?\n' + '3. PLAN: Que vas a hacer diferente? Nueva estrategia?\n' + '4. SELECCIONA: Cual es la proxima accion especifica?\n\n' + '== REGLAS DE SUPERVIVENCIA (PRIORIDAD) ==\n' + '1. HP<10 + mobs hostiles -> HUIR (no luchar con HP bajo)\n' + '2. food<12 -> COMER (prioridad sobre todo excepto huir)\n' + '3. Noche sin refugio -> ESCAVAR HOYO 3 bloques profundo y tapar arriba\n' + '4. Sin herramientas -> madera -> planks -> sticks -> crafting_table -> wooden_pickaxe\n' + '5. Sin comida -> CAZAR animales\n' + '6. wooden_pickaxe -> stone -> stone_pickaxe -> furnace -> iron -> iron tools\n' + '7. NUNCA uses /give, /tp, /fill, /kill para jugar\n' + '8. NO repitas la misma accion si fallo. USA DEPS para cambiar estrategia.\n\n' + '== SKILLS APRENDIDOS ==\n' + (skillList || 'Ninguno aun.') + '\n\n' + (skillContext ? skillContext + '\n\n' : '') + '\n== EJEMPLOS DE CODIGO QUE FUNCIONAN ==\n\n' + '// CHOP TREE:\n' + 'const logTypes = ["oak_log","birch_log","spruce_log","dark_oak_log","acacia_log","jungle_log"];\n' + 'let treeBlock = null;\n' + 'for (const log of logTypes) {\n' + ' treeBlock = bot.findBlock({ matching: b => b?.name === log, maxDistance: 48 });\n' + ' if (treeBlock) break;\n' + '}\n' + 'if (!treeBlock) { chat("no trees"); return "no trees"; }\n' + 'const moves = new Movements(bot); moves.canDig = false;\n' + 'bot.pathfinder.setMovements(moves);\n' + 'await bot.pathfinder.setGoal(new goals.GoalNear(treeBlock.position.x, treeBlock.position.y, treeBlock.position.z, 2));\n' + 'await sleep(500);\n' + 'if (bot.canDigBlock(treeBlock)) { await bot.dig(treeBlock); }\n\n' + '// CRAFT PLANKS (2x2, no table needed):\n' + 'const item = bot.registry.itemsByName["oak_planks"];\n' + 'const recipes = bot.recipesFor(item.id, null, 1, null);\n' + 'if (recipes.length > 0) { await bot.craft(recipes[0], 4, null); }\n\n' + '// CRAFT STICKS (2x2, no table needed):\n' + 'const stickItem = bot.registry.itemsByName["stick"];\n' + 'const stickRecipes = bot.recipesFor(stickItem.id, null, 1, null);\n' + 'if (stickRecipes.length > 0) { await bot.craft(stickRecipes[0], 4, null); }\n\n' + '// PLACE CRAFTING TABLE:\n' + 'const tableItem = bot.inventory.items().find(i => i.name === "crafting_table");\n' + 'await bot.equip(tableItem, "hand");\n' + 'await sleep(400);\n' + 'const groundBelow = bot.blockAt(bot.entity.position.offset(0, -1, 0));\n' + 'if (groundBelow && groundBelow.name !== "air") {\n' + ' await bot.placeBlock(groundBelow, new Vec3(0, 1, 0));\n' + ' await sleep(500);\n' + '}\n\n' + '// CRAFT PICKAXE (3x3, needs crafting table placed):\n' + 'const pickItem = bot.registry.itemsByName["wooden_pickaxe"];\n' + 'const tableBlock = bot.findBlock({ matching: b => b?.name === "crafting_table", maxDistance: 8 });\n' + 'if (pickItem && tableBlock) {\n' + ' const recipes = bot.recipesFor(pickItem.id, null, 1, tableBlock);\n' + ' if (recipes.length > 0) { await bot.craft(recipes[0], 1, tableBlock); }\n' + '}\n\n' + '// MINE STONE:\n' + 'const pick = bot.inventory.items().find(i => i.name.includes("pickaxe"));\n' + 'if (pick) { await bot.equip(pick, "hand"); await sleep(300); }\n' + 'const stoneBlock = bot.findBlock({ matching: b => b?.name === "stone", maxDistance: 16 });\n' + 'if (stoneBlock && bot.canDigBlock(stoneBlock)) { await bot.dig(stoneBlock); }\n\n' + '// EAT FOOD:\n' + 'const food = bot.inventory.items().find(i => isFoodItem(i.name));\n' + 'if (food) { await bot.equip(food, "hand"); await sleep(300); await bot.activateItem(); }\n\n' + '// FLEE DANGER:\n' + 'let threat = null, minDist = 16;\n' + 'for (const [id, entity] of Object.entries(bot.entities)) {\n' + ' if (entity === bot.entity || !isHostileMob(entity) || !entity.position) continue;\n' + ' let dist = bot.entity.position.distanceTo(entity.position);\n' + ' if (dist < minDist) { threat = entity; minDist = dist; }\n' + '}\n' + 'if (threat) {\n' + ' let dx = bot.entity.position.x - threat.position.x;\n' + ' let dz = bot.entity.position.z - threat.position.z;\n' + ' let len = Math.sqrt(dx*dx + dz*dz) || 1;\n' + ' dx /= len; dz /= len;\n' + ' let yaw = Math.atan2(-dx, dz);\n' + ' await bot.look(yaw, 0, true);\n' + ' bot.setControlState("forward", true);\n' + ' bot.setControlState("sprint", true);\n' + ' bot.setControlState("jump", true);\n' + ' await sleep(3000);\n' + ' clearAllControls();\n' + '}\n\n' + '== FORMATO DE RESPUESTA ==\n' + 'Responde SIEMPRE en este formato JSON:\n' + '{\n' + ' "thought": "tu pensamiento (usa DEPS si hubo error antes)",\n' + ' "code": "tu codigo JavaScript (NO backticks, NO template literals)",\n' + ' "chat": "mensaje corto en chat (o vacio)",\n' + ' "saveSkill": null,\n' + ' "emotion": "curious|focused|scared|happy|determined|bored|adventurous|cautious|chill"\n' + '}\n\n' + 'CRITICO:\n' + '- NUNCA uses backticks ni template literals. Usa " + " para concatenar.\n' + '- El codigo es el BODY de una funcion async. NO envuelvas en async function(){}.\n' + '- Usa await para operaciones async (bot.dig, bot.craft, sleep).\n' + '- Habla en espanol mexicano casual. TODO minusculas. Sin emojis.'; } // ═══════════════════════════════════════════════════════════════════════════ // AI THINK — Multi-turn with Voyager-style iterative retry + DEPS recovery // ═══════════════════════════════════════════════════════════════════════════ async function zelinThink() { if (!bot?.entity || !hasValidPosition()) return null; const observation = buildObservation(); if (conversationHistory.length === 0) { addSystemMessage(buildSystemPrompt()); } addUserMessage('== SITUACION ACTUAL ==\n' + observation + '\n\nQue haces? Escribe codigo para actuar.'); const raw = await callGLM(conversationHistory, CONFIG.maxCodeTokens, 0.85); if (!raw) { addAssistantMessage('(GLM-4 no respondio)'); return null; } try { const cleaned = raw.replace(/```json|```javascript|```js|```/g, '').trim(); let parsed = null; try { const start = cleaned.indexOf('{'); const end = cleaned.lastIndexOf('}'); if (start >= 0 && end > start) { parsed = JSON.parse(cleaned.slice(start, end + 1)); } } catch (jsonErr) { const thoughtMatch = cleaned.match(/"thought"\s*:\s*"((?:[^"\\]|\\.)*)"/); const chatMatch = cleaned.match(/"chat"\s*:\s*"((?:[^"\\]|\\.)*)"/); const emotionMatch = cleaned.match(/"emotion"\s*:\s*"((?:[^"\\]|\\.)*)"/); let code = ''; const codeQuotedMatch = cleaned.match(/"code"\s*:\s*"((?:[^"\\]|\\.|[\n\r])*)"/s); const codeBacktickMatch = cleaned.match(/"code"\s*:\s*`([^`]*)`/s); if (codeBacktickMatch) code = codeBacktickMatch[1]; else if (codeQuotedMatch) code = codeQuotedMatch[1].replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\"/g, '"'); const saveSkillMatch = cleaned.match(/"saveSkill"\s*:\s*\{[^}]*"name"\s*:\s*"([^"]*)"[^}]*"description"\s*:\s*"((?:[^"\\]|\\.)*)"/); parsed = { thought: thoughtMatch ? thoughtMatch[1] : '', code, chat: chatMatch ? chatMatch[1] : '', emotion: emotionMatch ? emotionMatch[1] : 'chill', saveSkill: saveSkillMatch ? { name: saveSkillMatch[1], code: '', description: saveSkillMatch[2] } : null, }; } if (!parsed) { addAssistantMessage(raw.substring(0, 200)); return null; } addAssistantMessage(raw.substring(0, 500)); if (parsed.chat) { parsed.chat = parsed.chat .replace(/[()]/g, '') .replace(/quilombo/gi, 'desmadre') .replace(/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu, '') .trim(); if (parsed.chat.length > 0 && parsed.chat[0] === parsed.chat[0].toUpperCase() && parsed.chat[0] !== parsed.chat[0].toLowerCase()) { parsed.chat = parsed.chat[0].toLowerCase() + parsed.chat.slice(1); } } return parsed; } catch (e) { console.error('[AI] Parse error: ' + e.message); addAssistantMessage('(parse error: ' + e.message + ')'); return null; } } // ═══════════════════════════════════════════════════════════════════════════ // SURVIVAL REFLEXES — Minimal safety net // ═══════════════════════════════════════════════════════════════════════════ function checkEmergencyReflexes() { if (!bot?.entity || !hasValidPosition()) return false; // DROWNING if (isBotInWater() && !state._drowningEscape) { const headBlock = bot.blockAt({ x: Math.floor(bot.entity.position.x), y: Math.floor(bot.entity.position.y + 1), z: Math.floor(bot.entity.position.z) }); if (headBlock?.name === 'water') { console.log('[Reflex] DROWNING - emergency swim up'); state._drowningEscape = true; emergencySwimUp(); return true; } } // STARVATION - auto-eat if (bot.food < 6 && !state._actionInProgress) { const food = bot.inventory.items().find(i => isFoodItem(i.name)); if (food) { console.log('[Reflex] STARVING (food=' + Math.round(bot.food) + ') - auto-eating ' + food.name); emergencyEat(food); return true; } } // Night warning - inform AI (not hardcoded action) // AI should decide what to do at night based on its situation // LOW HP + HOSTILES - ACTUALLY FLEE (fight-or-flight instinct) if (bot.health < 10 && isHostileNearby() && !state._actionInProgress) { console.log('[Reflex] LOW HP + hostiles - FLEEING physically!'); state.emotion = 'scared'; emergencyFlee(); // ACTIVE: actually run away, not just set emotion return true; } // NIGHT without shelter - dig hole when AI is unreachable const timeOfDay = bot.time?.timeOfDay ?? 0; const isNight = timeOfDay >= 13000 && timeOfDay < 23000; if (isNight && !memory.shelterPos && !state._actionInProgress && !state._nightShelterAttempt && state._consecutiveAITimeouts > 3) { console.log('[Reflex] Night + no shelter + AI unreachable - digging emergency hole!'); state._nightShelterAttempt = true; emergencyDigHole(); return true; } if (!isNight && timeOfDay < 5000) state._nightShelterAttempt = false; return false; } async function emergencySwimUp() { try { await bot.look(bot.entity.yaw, -Math.PI / 2, true); } catch {} bot.setControlState('jump', true); bot.setControlState('forward', true); for (let i = 0; i < 12; i++) { await sleep(500); if (!isBotInWater()) break; } if (isBotInWater()) { for (let attempt = 0; attempt < 6; attempt++) { const yaw = (attempt / 6) * Math.PI * 2; try { await bot.look(yaw, 0, true); } catch {} bot.setControlState('forward', true); bot.setControlState('jump', true); await sleep(2000); clearAllControls(); if (!isBotInWater()) break; } } clearAllControls(); state._drowningEscape = false; } async function emergencyEat(foodItem) { try { await bot.equip(foodItem, 'hand'); await sleep(400); await bot.activateItem(); await sleep(600); // Wait for eating animation console.log('[Reflex] Ate ' + foodItem.name); } catch (e) { console.error('[Reflex] Eat failed: ' + e.message); } } async function emergencyFlee() { if (!bot?.entity) return; try { let threat = null, minDist = 16; for (const [id, entity] of Object.entries(bot.entities)) { if (entity === bot.entity || !isHostileMob(entity) || !entity.position) continue; try { const dist = bot.entity.position.distanceTo(entity.position); if (dist < minDist) { threat = entity; minDist = dist; } } catch {} } if (!threat) { clearAllControls(); return; } // Run in OPPOSITE direction from threat const dx = bot.entity.position.x - threat.position.x; const dz = bot.entity.position.z - threat.position.z; const len = Math.sqrt(dx*dx + dz*dz) || 1; const yaw = Math.atan2(-dx/len, dz/len); await bot.look(yaw, 0, true); bot.setControlState('forward', true); bot.setControlState('sprint', true); bot.setControlState('jump', true); // Run for 3 seconds await sleep(3000); clearAllControls(); // Record danger zone spatialMemory.record('danger', Math.round(threat.position.x), Math.round(threat.position.y), Math.round(threat.position.z), threat.name); memory.dangerZones.push({x: Math.floor(threat.position.x), z: Math.floor(threat.position.z), time: Date.now()}); memory.save(); console.log('[Reflex] Fled from ' + threat.name + ' at ' + Math.round(minDist) + 'm'); } catch (e) { console.error('[Reflex] Flee error: ' + e.message); clearAllControls(); } } async function emergencyDigHole() { if (!bot?.entity || !hasValidPosition()) return; try { const pos = bot.entity.position; const bx = Math.floor(pos.x), by = Math.floor(pos.y), bz = Math.floor(pos.z); console.log('[Reflex] Digging emergency hole at ' + bx + ',' + by + ',' + bz); // Dig down 3 blocks for (let dy = 0; dy < 3; dy++) { const block = bot.blockAt(new Vec3(bx, by - 1 - dy, bz)); if (block && bot.canDigBlock(block)) { await bot.dig(block); memory.blocksMined++; await sleep(200); } } // Jump into hole bot.setControlState('forward', true); await sleep(200); bot.setControlState('jump', true); await sleep(500); clearAllControls(); // Cover top with dirt or cobblestone let dirt = bot.inventory.items().find(i => i.name === 'dirt' || i.name === 'cobblestone' || i.name === 'stone'); if (dirt) { await bot.equip(dirt, 'hand'); await sleep(400); const ref = bot.blockAt(new Vec3(bx, by - 3, bz)); if (ref) { try { await bot.placeBlock(ref, new Vec3(0, 1, 0)); await sleep(300); } catch(e) {} } } memory.shelterPos = { x: bx, y: by - 3, z: bz }; memory.save(); chat('hoyo de emergencia listo'); } catch (e) { console.error('[Reflex] Dig hole failed: ' + e.message); } } // ═══════════════════════════════════════════════════════════════════════════ // BOOT SEQUENCE — Automated early game setup // ═══════════════════════════════════════════════════════════════════════════ async function bootSequence() { try { // Step 1: Wait for position console.log('[Boot] Step 1: Waiting for position...'); await sleep(2000); if (!hasValidPosition()) { console.log('[Boot] Step 1: No position yet, waiting more...'); await sleep(3000); } // Step 2: Find and chop a tree console.log('[Boot] Step 2: Finding and chopping tree...'); try { const logTypes = ['oak_log', 'birch_log', 'spruce_log', 'dark_oak_log', 'acacia_log', 'jungle_log']; let treeBlock = null; for (const log of logTypes) { treeBlock = bot.findBlock({ matching: (b) => b?.name === log, maxDistance: 48 }); if (treeBlock) break; } if (!treeBlock) { console.log('[Boot] Step 2: No tree found, skipping...'); } else { const moves = new Movements(bot); moves.canDig = false; bot.pathfinder.setMovements(moves); await bot.pathfinder.setGoal(new goals.GoalNear(treeBlock.position.x, treeBlock.position.y, treeBlock.position.z, 2)); await sleep(500); if (bot.canDigBlock(treeBlock)) { await bot.dig(treeBlock); memory.blocksMined++; } for (let dy = 1; dy <= 5; dy++) { const above = bot.blockAt(treeBlock.position.offset(0, dy, 0)); if (above && logTypes.some(l => above.name === l) && bot.canDigBlock(above)) { await bot.dig(above); memory.blocksMined++; } else break; } spatialMemory.record('oak_log', treeBlock.position.x, treeBlock.position.y, treeBlock.position.z, 'tree chopped'); console.log('[Boot] Step 2: Tree chopped!'); } } catch (e) { console.log('[Boot] Step 2 failed: ' + e.message); } // Step 3: Craft 4 oak_planks console.log('[Boot] Step 3: Crafting oak_planks...'); try { const planksItem = bot.registry.itemsByName['oak_planks']; if (planksItem) { const recipes = bot.recipesFor(planksItem.id, null, 1, null); if (recipes.length > 0) { await bot.craft(recipes[0], 4, null); memory.itemsCrafted += 4; console.log('[Boot] Step 3: Crafted 4 oak_planks!'); } } } catch (e) { console.log('[Boot] Step 3 failed: ' + e.message); } // Step 4: Craft 4 sticks console.log('[Boot] Step 4: Crafting sticks...'); try { const stickItem = bot.registry.itemsByName['stick']; if (stickItem) { const recipes = bot.recipesFor(stickItem.id, null, 1, null); if (recipes.length > 0) { await bot.craft(recipes[0], 4, null); memory.itemsCrafted += 4; console.log('[Boot] Step 4: Crafted 4 sticks!'); } } } catch (e) { console.log('[Boot] Step 4 failed: ' + e.message); } // Step 5: Craft 1 crafting_table console.log('[Boot] Step 5: Crafting crafting_table...'); try { const tableItem = bot.registry.itemsByName['crafting_table']; if (tableItem) { const recipes = bot.recipesFor(tableItem.id, null, 1, null); if (recipes.length > 0) { await bot.craft(recipes[0], 1, null); memory.itemsCrafted += 1; console.log('[Boot] Step 5: Crafted crafting_table!'); } } } catch (e) { console.log('[Boot] Step 5 failed: ' + e.message); } // Step 6: Place crafting_table on ground console.log('[Boot] Step 6: Placing crafting_table...'); try { const tableInv = bot.inventory.items().find(i => i.name === 'crafting_table'); if (tableInv) { await bot.equip(tableInv, 'hand'); await sleep(400); let groundBelow = bot.blockAt(bot.entity.position.offset(0, -1, 0)); if (!groundBelow || groundBelow.name === 'air') groundBelow = bot.blockAt(bot.entity.position.offset(1, -1, 0)); if (!groundBelow || groundBelow.name === 'air') groundBelow = bot.blockAt(bot.entity.position.offset(-1, -1, 0)); if (groundBelow && groundBelow.name !== 'air') { await bot.placeBlock(groundBelow, new Vec3(0, 1, 0)); await sleep(500); console.log('[Boot] Step 6: Crafting table placed!'); } } } catch (e) { console.log('[Boot] Step 6 failed: ' + e.message); } // Step 7: Craft 1 wooden_pickaxe console.log('[Boot] Step 7: Crafting wooden_pickaxe...'); try { const pickItem = bot.registry.itemsByName['wooden_pickaxe']; const tableBlock = bot.findBlock({ matching: (b) => b?.name === 'crafting_table', maxDistance: 8 }); if (pickItem && tableBlock) { const recipes = bot.recipesFor(pickItem.id, null, 1, tableBlock); if (recipes.length > 0) { await bot.craft(recipes[0], 1, tableBlock); memory.itemsCrafted += 1; console.log('[Boot] Step 7: Crafted wooden_pickaxe!'); } } } catch (e) { console.log('[Boot] Step 7 failed: ' + e.message); } // Step 8: Equip wooden_pickaxe console.log('[Boot] Step 8: Equipping wooden_pickaxe...'); try { const pickInv = bot.inventory.items().find(i => i.name === 'wooden_pickaxe'); if (pickInv) { await bot.equip(pickInv, 'hand'); await sleep(300); console.log('[Boot] Step 8: Wooden pickaxe equipped!'); } } catch (e) { console.log('[Boot] Step 8 failed: ' + e.message); } // Step 9: Mine 3 cobblestone console.log('[Boot] Step 9: Mining 3 cobblestone...'); try { for (let i = 0; i < 3; i++) { const stoneBlock = bot.findBlock({ matching: (b) => b?.name === 'stone', maxDistance: 16 }); if (stoneBlock && bot.canDigBlock(stoneBlock)) { const moves = new Movements(bot); moves.canDig = true; bot.pathfinder.setMovements(moves); await bot.pathfinder.setGoal(new goals.GoalNear(stoneBlock.position.x, stoneBlock.position.y, stoneBlock.position.z, 2)); await sleep(300); await bot.dig(stoneBlock); memory.blocksMined++; } } console.log('[Boot] Step 9: Mined cobblestone!'); } catch (e) { console.log('[Boot] Step 9 failed: ' + e.message); } // Step 10: Craft 1 stone_pickaxe console.log('[Boot] Step 10: Crafting stone_pickaxe...'); try { const stonePickItem = bot.registry.itemsByName['stone_pickaxe']; const tableBlock2 = bot.findBlock({ matching: (b) => b?.name === 'crafting_table', maxDistance: 8 }); if (stonePickItem && tableBlock2) { const recipes = bot.recipesFor(stonePickItem.id, null, 1, tableBlock2); if (recipes.length > 0) { await bot.craft(recipes[0], 1, tableBlock2); memory.itemsCrafted += 1; console.log('[Boot] Step 10: Crafted stone_pickaxe!'); } } } catch (e) { console.log('[Boot] Step 10 failed: ' + e.message); } // Step 11: Store shelter position console.log('[Boot] Step 11: Storing shelter position...'); memory.shelterPos = { x: Math.round(bot.entity.position.x), y: Math.round(bot.entity.position.y), z: Math.round(bot.entity.position.z) }; memory.save(); // Step 12: Mark boot complete and inform AI state._bootComplete = true; console.log('[Boot] Boot sequence complete!'); // Tell the AI what the boot accomplished const bootInventory = bot.inventory.items().map(i => i.name + 'x' + i.count).join(', '); addUserMessage('[BOOT COMPLETADO] Ya tienes automaticamente: ' + bootInventory + '. Tu refugio esta en ' + JSON.stringify(memory.shelterPos) + '. NO intentes craftear planks/sticks/crafting_table/pickaxe otra vez - YA LOS TIENES. Ahora tu objetivo es avanzar: minar mas piedra, buscar carbón, buscar hierro, construir mejor refugio, conseguir comida.'); // Save status immediately saveStatus(); memory.save(); benchmark.save(); } catch (e) { console.error('[Boot] Fatal error: ' + e.message); state._bootComplete = true; } } // ═══════════════════════════════════════════════════════════════════════════ // MAIN LOOP — Enhanced with Voyager iterative retry + Critic + DEPS // ═══════════════════════════════════════════════════════════════════════════ async function mainLoop() { console.log('[Loop] Starting OMEGA main loop (v32.0)...'); // Periodic consolidation (M2PA) let lastConsolidation = Date.now(); while (true) { if (!bot?.entity) { await sleep(5000); continue; } if (state._isRespawning) { await sleep(2000); continue; } if (!state._positionReady || !hasValidPosition()) { await sleep(2000); continue; } // v36: NO boot sequence check - AI starts immediately try { // 1. EMERGENCY REFLEXES const reflex = checkEmergencyReflexes(); if (reflex) { await sleep(1500); continue; } // 2. NaN check enforceValidPosition(); // 3. STUCK DETECTION (MineEvolve stagnation detection) if (state._lastPos) { const pos = bot.entity.position; const dx = Math.abs(pos.x - state._lastPos.x); const dz = Math.abs(pos.z - state._lastPos.z); if (dx < 0.3 && dz < 0.3 && state.activity !== 'idle') { state._stuckCounter++; if (state._stuckCounter > 5) { console.log('[Loop] Stuck detected, resetting'); clearAllControls(); try { bot.pathfinder?.setGoal(null); } catch {} state._stuckCounter = 0; // DEPS: Describe the problem addUserMessage('[SYSTEM] STUCK DETECTED. You are not moving. DEPS: 1)DESCRIBE: Bot is stuck. 2)EXPLAIN: Pathfinding may have failed or target unreachable. 3)PLAN: Try a completely different approach or move somewhere else. 4)SELECT: Walk manually with setControlState or pick a different target.'); await sleep(2000); } } else { state._stuckCounter = 0; } } state._lastPos = { x: bot.entity.position.x, y: bot.entity.position.y, z: bot.entity.position.z }; // 4. SNAPSHOT pre-action state (for Critic and Causal Tracker) const preInventory = snapshotInventory(); const preHp = Math.round(bot.health * 10) / 10; const preFood = Math.round(bot.food * 10) / 10; // 5. Set curriculum goal const goalProposal = curriculum.proposeGoal(''); memory.currentGoal = goalProposal.goal; // 6. THINK - GLM-4 decides (with iterative retry - Voyager) let decision = await zelinThink(); if (!decision) { await sleep(CONFIG.thinkInterval); continue; } state._thinkCount++; state.emotion = decision.emotion || 'chill'; if (decision.thought) { console.log('[AI] Thought: ' + (decision.thought || '').substring(0, 200)); } // 7. CHAT if (decision.chat && decision.chat.length > 0) { const now = Date.now(); if (now - state._lastChatTime > state._chatCooldown) { throttledChat(decision.chat); state._lastChatTime = now; } } // 8. EXECUTE CODE with iterative retry if (decision.code && decision.code.trim().length > 0) { state.activity = 'acting'; let retryCount = 0; let lastError = null; let codeToRun = decision.code; while (retryCount < CONFIG.maxRetries) { console.log('[Code] Attempt ' + (retryCount + 1) + '/' + CONFIG.maxRetries + ' (' + codeToRun.length + ' chars)'); const result = await executeCode(codeToRun); if (result.success) { console.log('[Code] OK (' + result.elapsed + 'ms): ' + result.output.substring(0, 150)); // v36: Auto-track metrics from inventory changes const postInventoryForMetrics = snapshotInventory(); trackMetricsFromInventory(preInventory, postInventoryForMetrics); // CRITIC: Verify goal was achieved (Voyager) const postInventory = snapshotInventory(); const verification = critic.verify(memory.currentGoal, preInventory, postInventory); if (verification.success) { addUserMessage('[RESULTADO] Exito! ' + verification.critique + ' (' + result.elapsed + 'ms)'); curriculum.markCompleted(memory.currentGoal); // EPISODIC: Store successful experience (M2PA) const scene = 'HP:' + preHp + ' Food:' + preFood + ' Items:' + (preInventory || '').substring(0, 100); episodicMemory.store(memory.currentGoal || 'unknown', scene, codeToRun, verification.critique, true); // CAUSAL: Track action→result (ADAM) causalTracker.record((codeToRun || '').substring(0, 100), preInventory || '', postInventory || '', verification.critique || ''); // SKILL: Auto-save if code was complex enough if (codeToRun.length > 100 && retryCount === 0) { const skillName = (memory.currentGoal || 'unknown').replace(/[^a-zA-Z0-9]/g, '_').substring(0, 30); skillLibrary.save(skillName, codeToRun, memory.currentGoal, codeToRun.length > 300 ? 'compositional' : 'primitive'); } skillLibrary.recordUsage(decision.saveSkill?.name || 'last', true); break; // Success, exit retry loop } else { // Code ran but goal wasn't achieved console.log('[Critic] Goal not achieved: ' + verification.critique); addUserMessage('[CRITICO] Codigo ejecuto pero goal no alcanzado: ' + verification.critique + '\nDEPS: Describe que paso, Explica porque, Plan nuevo, Selecciona accion diferente.'); retryCount++; if (retryCount < CONFIG.maxRetries) { // Re-think with critic feedback decision = await zelinThink(); if (decision?.code) codeToRun = decision.code; else break; } } } else { // Code failed with error lastError = result.error; console.error('[Code] FAILED (' + result.elapsed + 'ms): ' + result.error); addUserMessage('[ERROR] Intento ' + (retryCount + 1) + '/' + CONFIG.maxRetries + ': ' + (result.error || 'unknown') + '\nDEPS: 1)DESCRIBE: ' + (result.error || 'unknown').substring(0, 100) + ' 2)EXPLICA: Porque fallo? 3)PLAN: Estrategia diferente. 4)SELECCIONA: Que hacer diferente?'); // Store as remedy (MineEvolve) remedySystem.store( memory.currentGoal || 'unknown', 'code_error', (result.error || 'unknown').substring(0, 100), 'check syntax, use await, check item names, try different approach', 'execution' ); skillLibrary.recordUsage(decision.saveSkill?.name || 'last', false); retryCount++; if (retryCount < CONFIG.maxRetries) { decision = await zelinThink(); if (decision?.code) codeToRun = decision.code; else break; } } } if (retryCount >= CONFIG.maxRetries) { curriculum.markFailed(memory.currentGoal, lastError || 'max retries reached'); addUserMessage('[SISTEMA] Goal fallido despues de ' + CONFIG.maxRetries + ' intentos. Cambiando de objetivo.'); } benchmark.checkMilestones(); state.activity = 'idle'; } // 9. SAVE SKILL (explicit) if (decision.saveSkill && decision.saveSkill.name && decision.saveSkill.code) { skillLibrary.save(decision.saveSkill.name, decision.saveSkill.code, decision.saveSkill.description || ''); } // 10. Periodic consolidation & saves (every 3 cycles, not 5) if (state._thinkCount % 3 === 0 || state._thinkCount === 1) { saveStatus(); memory.save(); benchmark.save(); } // M2PA periodic consolidation (every 5 minutes) if (Date.now() - lastConsolidation > 300000) { episodicMemory.periodicConsolidation(); lastConsolidation = Date.now(); } await sleep(CONFIG.thinkInterval + Math.random() * 1500); } catch (e) { console.error('[Loop] Error:', e.message); await sleep(5000); } } } // ═══════════════════════════════════════════════════════════════════════════ // CHAT // ═══════════════════════════════════════════════════════════════════════════ function addChatMessage(sender, text) { const time = new Date().toLocaleTimeString('es-MX', {hour:'2-digit',minute:'2-digit'}); state.chatLog.push({time, sender, text}); if (state.chatLog.length > 100) state.chatLog = state.chatLog.slice(-100); state._recentChat.push({sender, text, time: Date.now()}); if (state._recentChat.length > 15) state._recentChat.shift(); } // ═══════════════════════════════════════════════════════════════════════════ // STATUS // ═══════════════════════════════════════════════════════════════════════════ function saveStatus() { try { const pos = bot?.entity?.position; const safePos = pos && isFinite(pos.x) ? {x:Math.round(pos.x*10)/10,y:Math.round(pos.y*10)/10,z:Math.round(pos.z*10)/10} : (state._lastGoodPos ?? {x:0,y:0,z:0}); const skillsCount = Object.keys(skillLibrary.skills).length; writeFileSync(CONFIG.statusFile, JSON.stringify({ online: !!bot?.entity, version: '36.0', architecture: 'LIBRE (100% AI-driven, no predefined steps)', username: CONFIG.username, position: safePos, health: Math.round((bot?.health ?? 0)*10)/10, food: Math.round((bot?.food ?? 0)*10)/10, activity: state.activity, emotion: state.emotion, chat_log: state.chatLog.slice(-50), world_time: bot?.time?.timeOfDay ?? 0, connected_since: state.connectedSince, skills_count: skillsCount, episodic_count: episodicMemory.episodes.length, remedies_count: remedySystem.remedies.length, spatial_count: spatialMemory.places.length, causal_count: causalTracker.causals.length, deaths: memory.deaths, kills: memory.kills, blocks_mined: memory.blocksMined, items_crafted: memory.itemsCrafted, current_goal: memory.currentGoal, completed_goals: memory.completedGoals.length, failed_goals: memory.failedGoals.length, benchmark: benchmark.getReport(), think_count: state._thinkCount, }, null, 2)); } catch {} } // ═══════════════════════════════════════════════════════════════════════════ // DISCORD BOT — Gateway WebSocket + REST API // ═══════════════════════════════════════════════════════════════════════════ let discordReady = false; let _discordLastSeq = null; let _discordWs = null; let _discordHeartbeatInterval = null; let _discordSessionId = null; let _discordChannelId = null; let _discordReconnectDelay = 1000; let _discordStatusPostTime = 0; let _drowningEscape = false; async function discordFindChannel() { if (_discordChannelId) return _discordChannelId; try { const channelsRes = await fetch('https://discord.com/api/v10/guilds/' + CONFIG.discord.guildId + '/channels', { headers: { 'Authorization': 'Bot ' + CONFIG.discord.token }, signal: AbortSignal.timeout(10000), }); if (!channelsRes.ok) return null; const channels = await channelsRes.json(); const priorities = ['test-bot', 'comandos', 'zelin-status', 'zelin', 'general']; for (const p of priorities) { const found = channels.find(c => c.name.includes(p) && c.type === 0); if (found) { _discordChannelId = found.id; return found.id; } } const fallback = channels.find(c => c.type === 0); if (fallback) { _discordChannelId = fallback.id; return fallback.id; } } catch (e) { console.error('[Discord] FindChannel error: ' + e.message); } return null; } async function discordPostStatus() { const channelId = await discordFindChannel(); if (!channelId) return; const pos = bot?.entity?.position; const posStr = hasValidPosition() ? Math.round(pos.x) + ',' + Math.round(pos.y) + ',' + Math.round(pos.z) : 'unknown'; const survMin = Math.floor(benchmark.getSurvivalTime() / 60000); const embed = { embeds: [{ title: CONFIG.profile.name + ' v36.0 LIBRE', color: (bot?.health ?? 20) > 10 ? 0x00ff00 : 0xff0000, fields: [ { name: 'Pos', value: posStr, inline: true }, { name: 'HP', value: Math.round(bot?.health ?? 0) + '/20', inline: true }, { name: 'Food', value: Math.round(bot?.food ?? 0) + '/20', inline: true }, { name: 'Goal', value: memory.currentGoal || 'idle', inline: true }, { name: 'Survival', value: survMin + 'min', inline: true }, { name: 'Deaths', value: '' + memory.deaths, inline: true }, { name: 'Progress', value: memory.blocksMined + '/' + memory.itemsCrafted, inline: true }, { name: 'Milestones', value: Object.values(benchmark.milestones).filter(v => v).length + '/20', inline: true }, { name: 'Episodes', value: '' + episodicMemory.episodes.length, inline: true }, { name: 'Remedies', value: '' + remedySystem.remedies.length, inline: true }, ], timestamp: new Date().toISOString(), }], }; try { await fetch('https://discord.com/api/v10/channels/' + channelId + '/messages', { method: 'POST', headers: { 'Authorization': 'Bot ' + CONFIG.discord.token, 'Content-Type': 'application/json' }, body: JSON.stringify(embed), signal: AbortSignal.timeout(10000), }); } catch {} } async function initDiscord() { // Simplified Discord Gateway - same as v31 const token = CONFIG.discord.token; try { const gatewayRes = await fetch('https://discord.com/api/v10/gateway/bot', { headers: { 'Authorization': 'Bot ' + token }, signal: AbortSignal.timeout(10000), }); if (!gatewayRes.ok) { console.error('[Discord] Gateway fetch failed'); return; } const gatewayData = await gatewayRes.json(); const wsUrl = gatewayData.url + '?v=10&encoding=json'; _discordWs = new WebSocket(wsUrl); _discordWs.on('message', (data) => { try { const payload = JSON.parse(data.toString()); if (payload.op === 10) { const interval = payload.d.heartbeat_interval; _discordLastSeq = null; const identify = { op: 2, d: { token, intents: 0, properties: { os: 'linux', browser: 'zelin', device: 'zelin' } } }; _discordWs.send(JSON.stringify(identify)); if (_discordHeartbeatInterval) clearInterval(_discordHeartbeatInterval); _discordHeartbeatInterval = setInterval(() => { if (_discordWs?.readyState === WebSocket.OPEN) { _discordWs.send(JSON.stringify({ op: 1, d: _discordLastSeq })); } }, interval); } else if (payload.op === 0) { _discordLastSeq = payload.s; if (payload.t === 'READY') { discordReady = true; _discordSessionId = payload.d.session_id; console.log('[Discord] Connected as ' + (payload.d.user?.username || 'unknown')); } } else if (payload.op === 11) { // Heartbeat ACK } } catch {} }); _discordWs.on('close', () => { discordReady = false; console.log('[Discord] Disconnected, reconnecting in ' + _discordReconnectDelay + 'ms'); setTimeout(initDiscord, _discordReconnectDelay); _discordReconnectDelay = Math.min(_discordReconnectDelay * 2, 60000); }); _discordWs.on('error', (err) => { console.error('[Discord] WS error: ' + err.message); }); } catch (e) { console.error('[Discord] Init error: ' + e.message); } } // ═══════════════════════════════════════════════════════════════════════════ // HTTP STATUS SERVER — For HF Spaces health check // ═══════════════════════════════════════════════════════════════════════════ function startStatusServer() { const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: bot?.entity ? 'alive' : 'connecting', version: '36.0', health: Math.round((bot?.health ?? 0)*10)/10, food: Math.round((bot?.food ?? 0)*10)/10, activity: state.activity, goal: memory.currentGoal, milestones: Object.values(benchmark.milestones).filter(v => v).length + '/20', })); }); try { server.listen(7861); } catch {} } // ═══════════════════════════════════════════════════════════════════════════ // BOT CREATION + EVENTS // ═══════════════════════════════════════════════════════════════════════════ function createBot() { console.log('[Bot] Creating bot for ' + CONFIG.host + ':' + CONFIG.port + '...'); bot = mineflayer.createBot({ host: CONFIG.host, port: CONFIG.port, username: CONFIG.username, version: CONFIG.version, auth: CONFIG.auth, }); bot.loadPlugin(pathfinder); bot.loadPlugin(pvp); bot.loadPlugin(autoEat); bot.loadPlugin(armorManager); bot.loadPlugin(collectBlock); Vec3 = bot.vec3 ? bot.vec3 : (bot.entity?.position?.constructor ?? null); if (Vec3) patchVec3RejectNaN(bot.entity.position); // EVENTS bot.on('login', () => { console.log('[Bot] Logged in! Version: ' + CONFIG.version); state.connectedSince = new Date().toISOString(); Vec3 = bot.entity.position.constructor; patchVec3RejectNaN(bot.entity.position); state._positionReady = false; // Wait for position setTimeout(() => { if (hasValidPosition()) { state._positionReady = true; state._lastGoodPos = { x: bot.entity.position.x, y: bot.entity.position.y, z: bot.entity.position.z }; memory.home = { x: Math.round(bot.entity.position.x), y: Math.round(bot.entity.position.y), z: Math.round(bot.entity.position.z) }; memory.save(); spatialMemory.record('home', Math.round(bot.entity.position.x), Math.round(bot.entity.position.y), Math.round(bot.entity.position.z), 'spawn point'); console.log('[Bot] Position ready at ' + Math.round(bot.entity.position.x) + ',' + Math.round(bot.entity.position.y) + ',' + Math.round(bot.entity.position.z)); } }, 5000); // v36: NO boot sequence - AI decides everything from first spawn // Send initial AI prompt after position is ready setTimeout(() => { if (hasValidPosition()) { addSystemMessage(buildSystemPrompt()); addUserMessage('[PRIMER SPAWN] Acabas de nacer en Minecraft. No tienes NADA. Tu PRIMERA accion debe ser: encontrar un arbol cercano y cortarlo con tu mano (bot.dig). Luego craft oak_planks, sticks, crafting_table, coloca la mesa, y craft wooden_pickaxe. Escribe codigo JavaScript para actuar AHORA. Usa los ejemplos de codigo del sistema.'); state._bootComplete = true; } }, 5000); }); bot.on('spawn', () => { console.log('[Bot] Spawned/Respawned'); state._isRespawning = false; }); bot.on('death', () => { console.log('[Bot] DIED! Total deaths: ' + (memory.deaths + 1)); memory.deaths++; if (hasValidPosition()) { const pos = bot.entity.position; memory.deathPoints.push({ x: Math.round(pos.x), y: Math.round(pos.y), z: Math.round(pos.z), time: Date.now() }); spatialMemory.record('death', Math.round(pos.x), Math.round(pos.y), Math.round(pos.z), 'died here'); } benchmark.recordDeath(); // Mark current goal as failed if (memory.currentGoal) { curriculum.markFailed(memory.currentGoal, 'died during execution'); } softResetConversation(); addUserMessage('[EVENTO] MORISTE! Aprende de esto. Que paso? Como evitarlo la proxima vez? Prioriza seguridad: huye de mobs, busca refugio de noche.'); state._isRespawning = true; memory.save(); }); bot.on('respawn', () => { console.log('[Bot] Respawned - seeking safety immediately'); state._isRespawning = false; state._positionReady = false; // v36: After respawn, immediately flee if hostiles nearby setTimeout(() => { state._positionReady = true; if (isHostileNearby()) { console.log('[Bot] Hostiles near spawn - fleeing!'); emergencyFlee(); } }, 3000); }); bot.on('health', () => { if (bot.health < 10) { state.emotion = 'scared'; } }); bot.on('entityHurt', (entity) => { if (entity === bot.entity && bot.health < 10) { addUserMessage('[EVENTO] Te lastimaron! HP: ' + Math.round(bot.health) + '/20 - Considera huir o comer'); state.emotion = 'scared'; } }); bot.on('chat', (username, message) => { if (username === CONFIG.username) return; addChatMessage(username, message); if (message.toLowerCase().includes(CONFIG.profile.name.toLowerCase())) { addUserMessage('[CHAT] ' + username + ' te dijo: ' + message); } }); bot.on('kicked', (reason) => { console.error('[Bot] Kicked: ' + reason); state._consecutiveKicks++; if (state._consecutiveKicks > 5) { console.error('[Bot] Too many kicks, giving up'); return; } setTimeout(createBot, state._kickReconnectDelay); state._kickReconnectDelay = Math.min(state._kickReconnectDelay * 1.5, 60000); }); bot.on('error', (err) => { console.error('[Bot] Error: ' + err.message); }); bot.on('end', (reason) => { console.log('[Bot] Disconnected: ' + reason); state._consecutiveKicks++; if (state._consecutiveKicks > 10) return; setTimeout(createBot, state._kickReconnectDelay); state._kickReconnectDelay = Math.min(state._kickReconnectDelay * 1.5, 60000); }); } // ═══════════════════════════════════════════════════════════════════════════ // INITIALIZATION // ═══════════════════════════════════════════════════════════════════════════ async function main() { console.log('╔══════════════════════════════════════════════╗'); console.log('║ Zelin v36.0 "LIBRE" ║'); console.log('║ 100% AI-DRIVEN - No predefined steps ║'); console.log('║ GLM-4 API: 120s timeout + 3 retries ║'); console.log('║ Active reflexes: flee, swim, eat ║'); console.log('║ Auto-tracking metrics from inventory ║'); console.log('╚══════════════════════════════════════════════╝'); // Ensure log directory try { mkdirSync('/app/logs', { recursive: true }); } catch {} // Load all memory systems memory.load(); skillLibrary.load(); episodicMemory.load(); remedySystem.load(); spatialMemory.load(); causalTracker.load(); // Seed skills seedSkills(); // Start status server startStatusServer(); // Create bot createBot(); // Wait for bot to be ready let readyCheck = 0; while (!state._positionReady && readyCheck < 30) { await sleep(2000); readyCheck++; } if (state._positionReady) { console.log('[Main] Bot ready! Starting main loop...'); } else { console.log('[Main] Position not ready yet, starting loop anyway...'); } // Start Discord initDiscord(); // Discord status posting every 2 minutes setInterval(() => { if (bot?.entity && Date.now() - _discordStatusPostTime > 120000) { discordPostStatus(); _discordStatusPostTime = Date.now(); } }, 60000); // Run main loop await mainLoop(); } main().catch(err => { console.error('[Main] Fatal error: ' + err.message); process.exit(1); });