agent-nav-3d / bot.js
Zelin Deploy
v36.0.1 - fix null substring crashes in main loop
6defe99
Raw
History Blame Contribute Delete
134 kB
/**
* ============================================================
* 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);
});