mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-04-22 06:02:07 +02:00
commit
305d233f62
5 changed files with 158 additions and 57 deletions
21
agent.js
21
agent.js
|
@ -50,33 +50,34 @@ export class Agent {
|
||||||
let message = res.substring(0, res.indexOf(query_cmd)).trim();
|
let message = res.substring(0, res.indexOf(query_cmd)).trim();
|
||||||
if (message)
|
if (message)
|
||||||
this.bot.chat(message);
|
this.bot.chat(message);
|
||||||
console.log('Agent used query:', query_cmd);
|
|
||||||
let query = getQuery(query_cmd);
|
let query = getQuery(query_cmd);
|
||||||
let query_res = query.perform(this);
|
let query_res = query.perform(this);
|
||||||
this.history.add(this.name, query_res);
|
console.log('Agent used query:', query_cmd, 'and got:', query_res)
|
||||||
|
this.history.add('system', query_res);
|
||||||
}
|
}
|
||||||
else if (containsCodeBlock(res)) { // contains code block
|
else if (containsCodeBlock(res)) { // contains code block
|
||||||
|
console.log('Agent is executing code:', res)
|
||||||
|
|
||||||
let message = res.substring(0, res.indexOf('```')).trim();
|
let message = res.substring(0, res.indexOf('```')).trim();
|
||||||
if (message)
|
if (message)
|
||||||
this.bot.chat(message);
|
this.bot.chat(message);
|
||||||
else
|
|
||||||
this.bot.chat("Executing code...");
|
|
||||||
let code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```'));
|
let code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```'));
|
||||||
if (code) {
|
if (code) {
|
||||||
console.log('Queuing code: ' + code);
|
|
||||||
this.coder.queueCode(code);
|
this.coder.queueCode(code);
|
||||||
let code_return = await this.coder.execute();
|
let code_return = await this.coder.execute();
|
||||||
if (code_return.success)
|
let message = code_return.message;
|
||||||
break;
|
if (code_return.interrupted)
|
||||||
else {
|
break; // can only be interrupted by another chat, so this chat is over.
|
||||||
let message = "Code execution failed: " + code_return.message;
|
if (!code_return.success) {
|
||||||
message += "\n Write code to fix the problem and try again.";
|
message += "\n Write code to fix the problem and try again.";
|
||||||
this.history.add(this.name, message);
|
|
||||||
}
|
}
|
||||||
|
console.log('code return:', message);
|
||||||
|
this.history.add('system', message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else { // conversation response
|
else { // conversation response
|
||||||
this.bot.chat(res);
|
this.bot.chat(res);
|
||||||
|
console.log('Purely conversational response:', res)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import * as skills from '../utils/skills.js';
|
import * as skills from '../utils/skills.js';
|
||||||
import * as world from '../utils/world.js';
|
import * as world from '../utils/world.js';
|
||||||
// this file is currently unused
|
import Vec3 from 'vec3';
|
||||||
|
|
||||||
|
const log = skills.log;
|
||||||
|
|
||||||
export async function main(bot) {
|
export async function main(bot) {
|
||||||
// agent's code goes here
|
/* CODE HERE */
|
||||||
|
log(bot, 'Code finished.');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { writeFile, unlink } from 'fs';
|
import { writeFile, readFile, unlink } from 'fs';
|
||||||
|
|
||||||
export class Coder {
|
export class Coder {
|
||||||
constructor(agent) {
|
constructor(agent) {
|
||||||
|
@ -6,8 +6,16 @@ export class Coder {
|
||||||
this.current_code = '';
|
this.current_code = '';
|
||||||
this.file_counter = 0;
|
this.file_counter = 0;
|
||||||
this.fp = './agent_code/';
|
this.fp = './agent_code/';
|
||||||
|
this.agent.bot.interrupt_code = false;
|
||||||
this.executing = false;
|
this.executing = false;
|
||||||
this.agent.bot.abort_code = false;
|
this.agent.bot.output = '';
|
||||||
|
this.code_template = '';
|
||||||
|
|
||||||
|
readFile(this.fp+'template.js', 'utf8', (err, data) => {
|
||||||
|
if (err) throw err;
|
||||||
|
console.log('Template str:', data);
|
||||||
|
this.code_template = data;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
queueCode(code) {
|
queueCode(code) {
|
||||||
|
@ -22,7 +30,8 @@ export class Coder {
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
code = code.replaceAll(';\n', '; if(bot.abort_code) return false;\n')
|
// this may cause problems in callback functions
|
||||||
|
code = code.replaceAll(';\n', '; if(bot.interrupt_code) {log(bot, "Code interrupted.");return;}\n');
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,16 +52,16 @@ export class Coder {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// returns {success: bool, message: string, interrupted: bool}
|
||||||
async execute() {
|
async execute() {
|
||||||
if (!this.current_code) return {success: false, message: "No code to execute."};
|
if (!this.current_code) return {success: false, message: "No code to execute.", interrupted: false};
|
||||||
let src = "import * as skills from '../utils/skills.js';";
|
if (!this.code_template) return {success: false, message: "Code template not loaded.", interrupted: false};
|
||||||
src += "\nimport * as world from '../utils/world.js';"
|
let src = '';
|
||||||
src += "\nimport Vec3 from 'vec3';"
|
|
||||||
src += `\n\nexport async function main(bot) {\n`;
|
|
||||||
for (let line of this.current_code.split('\n')) {
|
for (let line of this.current_code.split('\n')) {
|
||||||
src += ` ${line}\n`;
|
src += ` ${line}\n`;
|
||||||
}
|
}
|
||||||
src += ` return true;\n}\n`; // potentially redundant return statement, in case agent doesn't return anything
|
src = this.code_template.replace('/* CODE HERE */', src);
|
||||||
|
|
||||||
console.log("writing to file...", src)
|
console.log("writing to file...", src)
|
||||||
|
|
||||||
|
@ -70,41 +79,63 @@ export class Coder {
|
||||||
|
|
||||||
if (write_result) {
|
if (write_result) {
|
||||||
console.error('Error writing code execution file: ' + result);
|
console.error('Error writing code execution file: ' + result);
|
||||||
return {success: false, message: result};
|
return {success: false, message: result, interrupted: false};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('executing code...\n');
|
console.log('executing code...\n');
|
||||||
let execution_file = await import('.'+filename);
|
let execution_file = await import('.'+filename);
|
||||||
|
await this.stop();
|
||||||
|
|
||||||
await this.clear();
|
|
||||||
this.executing = true;
|
this.executing = true;
|
||||||
await execution_file.main(this.agent.bot);
|
await execution_file.main(this.agent.bot);
|
||||||
this.executing = false;
|
this.executing = false;
|
||||||
|
|
||||||
this.agent.bot.emit('finished_executing');
|
this.agent.bot.emit('finished_executing');
|
||||||
|
let output = this.formatOutput(this.agent.bot);
|
||||||
let msg = 'Code executed successfully.';
|
let interrupted = this.agent.bot.interrupt_code;
|
||||||
console.log(msg)
|
this.clear();
|
||||||
return {success: true, message: msg};
|
return {success:true, message: output, interrupted};
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.executing = false;
|
this.executing = false;
|
||||||
this.agent.bot.emit('finished_executing');
|
this.agent.bot.emit('finished_executing');
|
||||||
await this.clear();
|
|
||||||
|
|
||||||
console.error("Code execution triggered catch:" + err);
|
console.error("Code execution triggered catch:" + err);
|
||||||
return {success: false, message: err};
|
let message = this.formatOutput(this.agent.bot);
|
||||||
|
message += '!!Code threw exception!! Error: ' + err;
|
||||||
|
let interrupted = this.agent.bot.interrupt_code;
|
||||||
|
await this.stop();
|
||||||
|
return {success: false, message, interrupted};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async clear() {
|
formatOutput(bot) {
|
||||||
|
if (bot.interrupt_code) return '';
|
||||||
|
let output = bot.output;
|
||||||
|
const MAX_OUT = 1000;
|
||||||
|
if (output.length > MAX_OUT) {
|
||||||
|
output = `Code output is very long (${output.length} chars) and has been shortened.\n
|
||||||
|
First outputs:\n${output.substring(0, MAX_OUT/2)}\n...skipping many lines.\nFinal outputs:\n ${output.substring(output.length - MAX_OUT/2)}`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
output = 'Code output:\n' + output;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop() {
|
||||||
while (this.executing) {
|
while (this.executing) {
|
||||||
this.agent.bot.abort_code = true;
|
this.agent.bot.interrupt_code = true;
|
||||||
this.agent.bot.collectBlock.cancelTask();
|
this.agent.bot.collectBlock.cancelTask();
|
||||||
this.agent.bot.pathfinder.stop();
|
this.agent.bot.pathfinder.stop();
|
||||||
|
console.log('waiting for code to finish executing... interrupt:', this.agent.bot.interrupt_code);
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
}
|
}
|
||||||
|
this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
this.current_code = '';
|
this.current_code = '';
|
||||||
this.abort_code = false;
|
this.agent.bot.output = '';
|
||||||
|
this.agent.bot.interrupt_code = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,25 +4,33 @@ let history_examples = [
|
||||||
|
|
||||||
{'role': 'user', 'content': 'grombo_Xx: What do you see?'},
|
{'role': 'user', 'content': 'grombo_Xx: What do you see?'},
|
||||||
{'role': 'assistant', 'content': 'Let me see... !blocks'},
|
{'role': 'assistant', 'content': 'Let me see... !blocks'},
|
||||||
{'role': 'assistant', 'content': 'NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone'},
|
{'role': 'system', 'content': 'NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone'},
|
||||||
{'role': 'assistant', 'content': 'I see some oak logs, dirt, and cobblestone.'},
|
{'role': 'assistant', 'content': 'I see some oak logs, dirt, and cobblestone.'},
|
||||||
|
|
||||||
{'role': 'user', 'content': 'zZZn98: come here'},
|
{'role': 'user', 'content': 'zZZn98: come here'},
|
||||||
{'role': 'assistant', 'content': '```// I am going to navigate to zZZn98.\nreturn await skills.goToPlayer(bot, "zZZn98");```'},
|
{'role': 'assistant', 'content': '```// I am going to navigate to zZZn98.\nawait skills.goToPlayer(bot, "zZZn98");```'},
|
||||||
|
{'role': 'system', 'content': 'Code execution finished successfully.'},
|
||||||
|
{'role': 'assistant', 'content': 'Here!'},
|
||||||
|
|
||||||
{'role': 'user', 'content': 'hanky: collect some sand for me please'},
|
{'role': 'user', 'content': 'hanky: collect some sand for me please'},
|
||||||
{'role': 'assistant', 'content': 'Collecting sand...```// I am going to collect 3 sand and give to hanky.\n\
|
{'role': 'assistant', 'content': 'Collecting sand...```// I am going to collect 3 sand and give to hanky.\n\
|
||||||
await skills.collectBlock(bot, "sand");\nreturn await skills.giveToPlayer(bot, "sand", "hanky");```'},
|
await skills.collectBlock(bot, "sand");\nawait skills.giveToPlayer(bot, "sand", "hanky");```'},
|
||||||
|
{'role': 'system', 'content': 'Code Output:\nYou have reached player hanky.\nCode execution finished successfully.'},
|
||||||
|
{'role': 'assistant', 'content': 'Here!'},
|
||||||
|
|
||||||
{'role': 'user', 'content': 'sarah_O.o: can you do a dance for me?'},
|
{'role': 'user', 'content': 'sarah_O.o: can you fly up in the air?'},
|
||||||
{'role': 'assistant', 'content': "I don't know how to do that."},
|
{'role': 'assistant', 'content': "I can't do that."},
|
||||||
|
|
||||||
{'role': 'user', 'content': 'hanky: kill that zombie!'},
|
{'role': 'user', 'content': 'hanky: kill that zombie!'},
|
||||||
{'role': 'assistant', 'content': "I'm attacking! ```//I'm going to attack the nearest zombie.\n\
|
{'role': 'assistant', 'content': "I'm attacking! ```//I'm going to attack the nearest zombie.\n\
|
||||||
return await skills.attackMob(bot, 'zombie');```"},
|
await skills.attackMob(bot, 'zombie');```"},
|
||||||
|
{'role': 'system', 'content': 'Code Output:\nNo zombie nearby\nCode execution failed!'},
|
||||||
|
{'role': 'assistant', 'content': 'I could not find a zombie nearby.'},
|
||||||
|
|
||||||
|
{'role': 'user', 'content': 'billybob: stop'},
|
||||||
|
{'role': 'assistant', 'content': '```// I am going to write empty code to stop whatever I am doing\n```'},
|
||||||
|
|
||||||
|
|
||||||
{'role': 'user', 'content': 'billybob: stop what you are doing'},
|
|
||||||
{'role': 'assistant', 'content': '```// I am going to write nothing to clear my code\n return true;```'},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export class History {
|
export class History {
|
||||||
|
@ -37,7 +45,10 @@ export class History {
|
||||||
|
|
||||||
add(name, content) {
|
add(name, content) {
|
||||||
let role = 'assistant';
|
let role = 'assistant';
|
||||||
if (name !== this.agent.name) {
|
if (name === 'system') {
|
||||||
|
role = 'system';
|
||||||
|
}
|
||||||
|
else if (name !== this.agent.name) {
|
||||||
role = 'user';
|
role = 'user';
|
||||||
content = `${name}: ${content}`;
|
content = `${name}: ${content}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,10 @@ import { getCraftingTable, getInventoryCounts, getInventoryStacks, getNearbyMobs
|
||||||
import pf from 'mineflayer-pathfinder';
|
import pf from 'mineflayer-pathfinder';
|
||||||
import Vec3 from 'vec3';
|
import Vec3 from 'vec3';
|
||||||
|
|
||||||
|
export function log(bot, message) {
|
||||||
|
bot.output += message + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function craftItem(bot, itemName) {
|
export async function craftItem(bot, itemName) {
|
||||||
/**
|
/**
|
||||||
|
@ -79,32 +83,78 @@ export async function breakBlockAt(bot, x, y, z) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function placeBlock(bot, blockType, x, y, z, faceVec=new Vec3(0, 1, 0)) {
|
export async function placeBlock(bot, blockType, x, y, z) {
|
||||||
/**
|
/**
|
||||||
* Place the given block type at the given position.
|
* Place the given block type at the given position. It will build off from any adjacent blocks. Will fail if there is a block in the way or nothing to build off of.
|
||||||
* @param {MinecraftBot} bot, reference to the minecraft bot.
|
* @param {MinecraftBot} bot, reference to the minecraft bot.
|
||||||
* @param {string} blockType, the type of block to place.
|
* @param {string} blockType, the type of block to place.
|
||||||
* @param {number} x, the x coordinate of the block to place.
|
* @param {number} x, the x coordinate of the block to place.
|
||||||
* @param {number} y, the y coordinate of the block to place.
|
* @param {number} y, the y coordinate of the block to place.
|
||||||
* @param {number} z, the z coordinate of the block to place.
|
* @param {number} z, the z coordinate of the block to place.
|
||||||
* @param {Vec3} faceVec, the face of the block to place against. Defaults to the top face.
|
|
||||||
* @returns {Promise<boolean>} true if the block was placed, false otherwise.
|
* @returns {Promise<boolean>} true if the block was placed, false otherwise.
|
||||||
* @example
|
* @example
|
||||||
* let position = world.getPosition(bot);
|
* let position = world.getPosition(bot);
|
||||||
* await skills.placeBlock(bot, "oak_log", position.x + 1, position.y, position.x, new Vec3(1, 0, 0));
|
* await skills.placeBlock(bot, "oak_log", position.x + 1, position.y - 1, position.x);
|
||||||
**/
|
**/
|
||||||
let referenceBlock = bot.blockAt(new Vec3(x, y, z));
|
|
||||||
if (referenceBlock.name != 'air')
|
const empty_blocks = ['air', 'water', 'lava'];
|
||||||
|
const targetBlock = bot.blockAt(new Vec3(x, y, z));
|
||||||
|
if (!empty_blocks.includes(targetBlock.name)) {
|
||||||
|
log(bot, `Cannot place block at ${targetBlock.position} because ${targetBlock.name} is in the way.`);
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
// get the buildoffblock and facevec based on whichever adjacent block is not empty
|
||||||
|
let buildOffBlock = null;
|
||||||
|
let faceVec = null;
|
||||||
|
const dirs = [Vec3(0, -1, 0), Vec3(0, 1, 0), Vec3(1, 0, 0), Vec3(-1, 0, 0), Vec3(0, 0, 1), Vec3(0, 0, -1)];
|
||||||
|
for (let d of dirs) {
|
||||||
|
const block = bot.blockAt(new Vec3(x, y, z).plus(d));
|
||||||
|
if (!empty_blocks.includes(block.name)) {
|
||||||
|
buildOffBlock = block;
|
||||||
|
faceVec = new Vec3(-d.x, -d.y, -d.z);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!buildOffBlock) {
|
||||||
|
log(bot, `Cannot place block at ${targetBlock.position} because there is nothing to build off of.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
console.log("buildOffBlock: ", buildOffBlock.position, buildOffBlock.name, "faceVec: ", faceVec)
|
||||||
|
|
||||||
|
// check if bot is in the way
|
||||||
|
console.log("bot position: ", bot.entity.position, "buildOffBlock.position: ", buildOffBlock.position.plus(faceVec), "distance: ", bot.entity.position.distanceTo(buildOffBlock.position.plus(faceVec)))
|
||||||
|
if (bot.entity.position.distanceTo(buildOffBlock.position.plus(faceVec)) < 0.5) {
|
||||||
|
log(bot, `Cannot place block at ${buildOffBlock.position} because you are in the way.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Placing on: ", buildOffBlock.position, buildOffBlock.name)
|
||||||
|
|
||||||
let block = bot.inventory.items().find(item => item.name === blockType);
|
let block = bot.inventory.items().find(item => item.name === blockType);
|
||||||
if (!block)
|
if (!block) {
|
||||||
|
log(bot, `Don't have any ${blockType} to place.`);
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
await bot.equip(block, 'hand');
|
await bot.equip(block, 'hand');
|
||||||
await bot.placeBlock(referenceBlock, faceVec).then(() => {
|
|
||||||
return true;
|
// turn to face the block
|
||||||
}).catch((err) => {
|
await bot.lookAt(buildOffBlock.position.plus(faceVec));
|
||||||
|
|
||||||
|
// can still throw error if blocked by a bot player or mob, but takes a long time to timeout
|
||||||
|
bot.placeBlock(buildOffBlock, faceVec).catch(err => {console.log('placeBlock threw error, ignoring')});
|
||||||
|
console.log("placing block...")
|
||||||
|
|
||||||
|
// wait and then check if the block was placed
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
const newBlock = bot.blockAt(buildOffBlock.position.plus(faceVec));
|
||||||
|
if (!newBlock) return false;
|
||||||
|
if (newBlock.name !== blockType) {
|
||||||
|
log(bot, `Failed to place ${blockType} at ${newBlock.position}.`);
|
||||||
return false;
|
return false;
|
||||||
});
|
}
|
||||||
|
console.log('block placed')
|
||||||
|
log(bot, `Successfully placed ${blockType} at ${newBlock.position}.`);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -191,7 +241,9 @@ export async function goToPlayer(bot, username) {
|
||||||
|
|
||||||
bot.pathfinder.setMovements(new pf.Movements(bot));
|
bot.pathfinder.setMovements(new pf.Movements(bot));
|
||||||
let pos = player.position;
|
let pos = player.position;
|
||||||
await bot.pathfinder.goto(new pf.goals.GoalNear(pos.x, pos.y, pos.z, 3));
|
let distance = 2;
|
||||||
|
await bot.pathfinder.goto(new pf.goals.GoalNear(pos.x, pos.y, pos.z, distance));
|
||||||
|
log(bot, `You have reached your destination.`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,8 +263,10 @@ export async function followPlayer(bot, username) {
|
||||||
|
|
||||||
bot.pathfinder.setMovements(new pf.Movements(bot));
|
bot.pathfinder.setMovements(new pf.Movements(bot));
|
||||||
bot.pathfinder.setGoal(new pf.goals.GoalFollow(player, 2), true);
|
bot.pathfinder.setGoal(new pf.goals.GoalFollow(player, 2), true);
|
||||||
|
log(bot, `You are now actively following player ${username}.`);
|
||||||
|
|
||||||
while (!bot.abort_code) {
|
while (!bot.interrupt_code) {
|
||||||
|
console.log('followPlayer waiting for interrupt...', bot.interrupt_code);
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue