mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-04-21 21:52:07 +02:00
Merge branch 'main' into routine
This commit is contained in:
commit
72aa92352e
9 changed files with 133 additions and 38 deletions
35
agent.js
35
agent.js
|
@ -3,18 +3,19 @@ import { sendRequest } from './utils/gpt.js';
|
||||||
import { History } from './utils/history.js';
|
import { History } from './utils/history.js';
|
||||||
import { Coder } from './utils/coder.js';
|
import { Coder } from './utils/coder.js';
|
||||||
import { getQuery, containsQuery } from './utils/queries.js';
|
import { getQuery, containsQuery } from './utils/queries.js';
|
||||||
import { containsCodeBlock } from './utils/skill_library.js';
|
import { containsCodeBlock } from './utils/skill-library.js';
|
||||||
import { Events } from './utils/events.js';
|
import { Events } from './utils/events.js';
|
||||||
|
|
||||||
|
|
||||||
export class Agent {
|
export class Agent {
|
||||||
constructor(name, save_path, restart_memory=false) {
|
constructor(name, save_path, clear_memory=false, autostart=false) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.bot = initBot(name);
|
this.bot = initBot(name);
|
||||||
this.history = new History(this, save_path);
|
this.history = new History(this, save_path);
|
||||||
|
this.history.loadExamples();
|
||||||
this.coder = new Coder(this);
|
this.coder = new Coder(this);
|
||||||
|
|
||||||
if (!restart_memory) {
|
if (!clear_memory) {
|
||||||
this.history.load();
|
this.history.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,25 +24,21 @@ export class Agent {
|
||||||
this.bot.on('login', () => {
|
this.bot.on('login', () => {
|
||||||
this.bot.chat('Hello world! I am ' + this.name);
|
this.bot.chat('Hello world! I am ' + this.name);
|
||||||
console.log(`${this.name} logged in.`);
|
console.log(`${this.name} logged in.`);
|
||||||
|
|
||||||
|
this.bot.on('chat', (username, message) => {
|
||||||
|
if (username === this.name) return;
|
||||||
|
console.log('received message from', username, ':', message);
|
||||||
|
|
||||||
|
this.history.add(username, message);
|
||||||
|
this.handleMessage();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (autostart)
|
||||||
|
this.respond('system', 'Agent process restarted. Notify the user and decide what to do.');
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async start() {
|
|
||||||
await this.history.loadExamples();
|
|
||||||
|
|
||||||
this.bot.on('chat', (username, message) => {
|
|
||||||
if (username === this.name) return;
|
|
||||||
console.log('received message from', username, ':', message);
|
|
||||||
|
|
||||||
this.history.add(username, message);
|
|
||||||
this.handleMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.history.default) {
|
|
||||||
this.executeCode(this.history.default);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async executeCode(code, triesRemaining=5) {
|
async executeCode(code, triesRemaining=5) {
|
||||||
if (code == 'default')
|
if (code == 'default')
|
||||||
code = this.history.default;
|
code = this.history.default;
|
||||||
|
|
39
controller/agent-process.js
Normal file
39
controller/agent-process.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { spawn } from 'child_process';
|
||||||
|
|
||||||
|
export class AgentProcess {
|
||||||
|
constructor(name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
start(clear_memory=false, autostart=false) {
|
||||||
|
let args = ['controller/init-agent.js', this.name];
|
||||||
|
if (clear_memory)
|
||||||
|
args.push('-c');
|
||||||
|
if (autostart)
|
||||||
|
args.push('-a');
|
||||||
|
|
||||||
|
const agentProcess = spawn('node', args, {
|
||||||
|
stdio: 'inherit',
|
||||||
|
stderr: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
let last_restart = Date.now();
|
||||||
|
agentProcess.on('exit', (code, signal) => {
|
||||||
|
console.log(`Agent process exited with code ${code} and signal ${signal}`);
|
||||||
|
|
||||||
|
if (code !== 0) {
|
||||||
|
// agent must run for at least 30 seconds before restarting
|
||||||
|
if (Date.now() - last_restart < 30 * 1000) {
|
||||||
|
console.error('Agent process exited too quickly. Killing entire process. Goodbye.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log('Restarting agent...');
|
||||||
|
this.start(false, true);
|
||||||
|
last_restart = Date.now();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
agentProcess.on('error', (err) => {
|
||||||
|
console.error('Failed to start agent process:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
27
controller/init-agent.js
Normal file
27
controller/init-agent.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { Agent } from '../agent.js';
|
||||||
|
import yargs from 'yargs';
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
if (args.length < 1) {
|
||||||
|
console.log('Usage: node init_agent.js <agent_name> [-c] [-a]');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const argv = yargs(args)
|
||||||
|
.option('clear_memory', {
|
||||||
|
alias: 'c',
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'restart memory from scratch'
|
||||||
|
})
|
||||||
|
.option('autostart', {
|
||||||
|
alias: 'a',
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'automatically prompt the agent on startup'
|
||||||
|
}).argv
|
||||||
|
|
||||||
|
const name = argv._[0];
|
||||||
|
const clear_memory = !!argv.clear_memory;
|
||||||
|
const autostart = !!argv.autostart;
|
||||||
|
const save_path = './bots/'+name+'.json';
|
||||||
|
|
||||||
|
new Agent(name, save_path, clear_memory, autostart);
|
5
main.js
5
main.js
|
@ -1,4 +1,3 @@
|
||||||
import { Agent } from './agent.js';
|
import { AgentProcess } from './controller/agent-process.js';
|
||||||
|
|
||||||
let agent = new Agent('andy', './bots/andy.json', true);
|
new AgentProcess('andy').start(true, false);
|
||||||
agent.start();
|
|
|
@ -7,6 +7,7 @@
|
||||||
"mineflayer-pathfinder": "^2.4.4",
|
"mineflayer-pathfinder": "^2.4.4",
|
||||||
"mineflayer-pvp": "^1.3.2",
|
"mineflayer-pvp": "^1.3.2",
|
||||||
"openai": "^4.4.0",
|
"openai": "^4.4.0",
|
||||||
"patch-package": "^8.0.0"
|
"patch-package": "^8.0.0",
|
||||||
|
"yargs": "^17.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ export class Coder {
|
||||||
this.executing = false;
|
this.executing = false;
|
||||||
this.agent.bot.output = '';
|
this.agent.bot.output = '';
|
||||||
this.code_template = '';
|
this.code_template = '';
|
||||||
|
this.timedout = false;
|
||||||
|
|
||||||
readFile(this.fp+'template.js', 'utf8', (err, data) => {
|
readFile(this.fp+'template.js', 'utf8', (err, data) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
@ -47,10 +48,10 @@ export class Coder {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// returns {success: bool, message: string, interrupted: bool}
|
// returns {success: bool, message: string, interrupted: bool, timedout: false}
|
||||||
async execute() {
|
async execute() {
|
||||||
if (!this.queued_code) return {success: false, message: "No code to execute.", interrupted: false};
|
if (!this.queued_code) return {success: false, message: "No code to execute.", interrupted: false, timedout: false};
|
||||||
if (!this.code_template) return {success: false, message: "Code template not loaded.", interrupted: false};
|
if (!this.code_template) return {success: false, message: "Code template not loaded.", interrupted: false, timedout: false};
|
||||||
let src = '';
|
let src = '';
|
||||||
|
|
||||||
let code = this.queued_code;
|
let code = this.queued_code;
|
||||||
|
@ -80,9 +81,9 @@ 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, interrupted: false};
|
return {success: false, message: result, interrupted: false, timedout: false};
|
||||||
}
|
}
|
||||||
|
let TIMEOUT;
|
||||||
try {
|
try {
|
||||||
console.log('executing code...\n');
|
console.log('executing code...\n');
|
||||||
let execution_file = await import('.'+filename);
|
let execution_file = await import('.'+filename);
|
||||||
|
@ -90,28 +91,33 @@ export class Coder {
|
||||||
this.current_code = this.queued_code;
|
this.current_code = this.queued_code;
|
||||||
|
|
||||||
this.executing = true;
|
this.executing = true;
|
||||||
await execution_file.main(this.agent.bot);
|
TIMEOUT = this._startTimeout(10);
|
||||||
|
await execution_file.main(this.agent.bot); // open fire
|
||||||
this.executing = false;
|
this.executing = false;
|
||||||
|
clearTimeout(TIMEOUT);
|
||||||
|
|
||||||
this.agent.bot.emit('finished_executing');
|
this.agent.bot.emit('finished_executing');
|
||||||
let output = this.formatOutput(this.agent.bot);
|
let output = this.formatOutput(this.agent.bot);
|
||||||
let interrupted = this.agent.bot.interrupt_code;
|
let interrupted = this.agent.bot.interrupt_code;
|
||||||
|
let timedout = this.timedout;
|
||||||
this.clear();
|
this.clear();
|
||||||
return {success:true, message: output, interrupted};
|
return {success:true, message: output, interrupted, timedout};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.executing = false;
|
this.executing = false;
|
||||||
|
clearTimeout(TIMEOUT);
|
||||||
|
|
||||||
this.agent.bot.emit('finished_executing');
|
this.agent.bot.emit('finished_executing');
|
||||||
console.error("Code execution triggered catch:" + err);
|
console.error("Code execution triggered catch: " + err);
|
||||||
let message = this.formatOutput(this.agent.bot);
|
let message = this.formatOutput(this.agent.bot);
|
||||||
message += '!!Code threw exception!! Error: ' + err;
|
message += '!!Code threw exception!! Error: ' + err;
|
||||||
let interrupted = this.agent.bot.interrupt_code;
|
let interrupted = this.agent.bot.interrupt_code;
|
||||||
await this.stop();
|
await this.stop();
|
||||||
return {success: false, message, interrupted};
|
return {success: false, message, interrupted, timedout: false};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
formatOutput(bot) {
|
formatOutput(bot) {
|
||||||
if (bot.interrupt_code) return '';
|
if (bot.interrupt_code && !this.timedout) return '';
|
||||||
let output = bot.output;
|
let output = bot.output;
|
||||||
const MAX_OUT = 1000;
|
const MAX_OUT = 1000;
|
||||||
if (output.length > MAX_OUT) {
|
if (output.length > MAX_OUT) {
|
||||||
|
@ -125,6 +131,7 @@ export class Coder {
|
||||||
}
|
}
|
||||||
|
|
||||||
async stop() {
|
async stop() {
|
||||||
|
if (!this.executing) return;
|
||||||
while (this.executing) {
|
while (this.executing) {
|
||||||
this.agent.bot.interrupt_code = true;
|
this.agent.bot.interrupt_code = true;
|
||||||
this.agent.bot.collectBlock.cancelTask();
|
this.agent.bot.collectBlock.cancelTask();
|
||||||
|
@ -140,5 +147,26 @@ export class Coder {
|
||||||
this.current_code = '';
|
this.current_code = '';
|
||||||
this.agent.bot.output = '';
|
this.agent.bot.output = '';
|
||||||
this.agent.bot.interrupt_code = false;
|
this.agent.bot.interrupt_code = false;
|
||||||
|
this.timedout = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_startTimeout(TIMEOUT_MINS=10) {
|
||||||
|
return setTimeout(async () => {
|
||||||
|
console.warn(`Code execution timed out after ${TIMEOUT_MINS} minutes. Attempting force stop.`);
|
||||||
|
this.timedout = true;
|
||||||
|
this.agent.bot.output += `\nAction performed for ${TIMEOUT_MINS} minutes and then timed out and stopped. You may want to continue or do something else.`;
|
||||||
|
this.stop(); // last attempt to stop
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 5 * 1000)); // wait 5 seconds
|
||||||
|
if (this.executing) {
|
||||||
|
console.error(`Failed to stop. Killing process. Goodbye.`);
|
||||||
|
this.agent.bot.output += `\nForce stop failed! Process was killed and will be restarted. Goodbye world.`;
|
||||||
|
this.agent.bot.chat('Goodbye world.');
|
||||||
|
let output = this.formatOutput(this.agent.bot);
|
||||||
|
this.agent.history.add('system', output);
|
||||||
|
this.agent.history.save();
|
||||||
|
process.exit(1); // force exit program
|
||||||
|
}
|
||||||
|
console.log('Code execution stopped successfully.');
|
||||||
|
}, TIMEOUT_MINS*60*1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { writeFileSync, readFileSync, mkdirSync } from 'fs';
|
import { writeFileSync, readFileSync, mkdirSync } from 'fs';
|
||||||
import { getQueryDocs } from './queries.js';
|
import { getQueryDocs } from './queries.js';
|
||||||
import { getSkillDocs } from './skill_library.js';
|
import { getSkillDocs } from './skill-library.js';
|
||||||
import { sendRequest, embed, cosineSimilarity } from './gpt.js';
|
import { sendRequest, embed, cosineSimilarity } from './gpt.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -57,13 +57,12 @@ export async function smeltItem(bot, itemName, num=1) {
|
||||||
|
|
||||||
let furnaceBlock = undefined;
|
let furnaceBlock = undefined;
|
||||||
furnaceBlock = getNearestBlock(bot, 'furnace', 6);
|
furnaceBlock = getNearestBlock(bot, 'furnace', 6);
|
||||||
if (furnaceBlock === null){
|
if (!furnaceBlock){
|
||||||
log(bot, `There is no furnace nearby.`)
|
log(bot, `There is no furnace nearby.`)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
await bot.lookAt(furnaceBlock.position);
|
await bot.lookAt(furnaceBlock.position);
|
||||||
|
|
||||||
|
|
||||||
console.log('smelting...');
|
console.log('smelting...');
|
||||||
const furnace = await bot.openFurnace(furnaceBlock);
|
const furnace = await bot.openFurnace(furnaceBlock);
|
||||||
// check if the furnace is already smelting something
|
// check if the furnace is already smelting something
|
||||||
|
@ -227,6 +226,10 @@ export async function collectBlock(bot, blockType, num=1) {
|
||||||
* @example
|
* @example
|
||||||
* await skills.collectBlock(bot, "oak_log");
|
* await skills.collectBlock(bot, "oak_log");
|
||||||
**/
|
**/
|
||||||
|
if (num < 1) {
|
||||||
|
log(bot, `Invalid number of blocks to collect: ${num}.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
let collected = 0;
|
let collected = 0;
|
||||||
const blocks = getNearestBlocks(bot, blockType, 64, num);
|
const blocks = getNearestBlocks(bot, blockType, 64, num);
|
||||||
if (blocks.length === 0) {
|
if (blocks.length === 0) {
|
||||||
|
@ -255,7 +258,9 @@ export async function collectBlock(bot, blockType, num=1) {
|
||||||
log(bot, `Failed to collect ${blockType}: ${err}.`);
|
log(bot, `Failed to collect ${blockType}: ${err}.`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (bot.interrupt_code)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
log(bot, `Collected ${collected} ${blockType}.`);
|
log(bot, `Collected ${collected} ${blockType}.`);
|
||||||
return true;
|
return true;
|
||||||
|
@ -565,7 +570,6 @@ export async function followPlayer(bot, username) {
|
||||||
log(bot, `You are now actively following player ${username}.`);
|
log(bot, `You are now actively following player ${username}.`);
|
||||||
|
|
||||||
while (!bot.interrupt_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