Merge branch 'main' into routine

This commit is contained in:
Kolby Nottingham 2023-12-12 20:45:53 -08:00
commit 72aa92352e
9 changed files with 133 additions and 38 deletions

View file

@ -3,18 +3,19 @@ import { sendRequest } from './utils/gpt.js';
import { History } from './utils/history.js';
import { Coder } from './utils/coder.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';
export class Agent {
constructor(name, save_path, restart_memory=false) {
constructor(name, save_path, clear_memory=false, autostart=false) {
this.name = name;
this.bot = initBot(name);
this.history = new History(this, save_path);
this.history.loadExamples();
this.coder = new Coder(this);
if (!restart_memory) {
if (!clear_memory) {
this.history.load();
}
@ -23,11 +24,6 @@ export class Agent {
this.bot.on('login', () => {
this.bot.chat('Hello world! I am ' + this.name);
console.log(`${this.name} logged in.`);
});
}
async start() {
await this.history.loadExamples();
this.bot.on('chat', (username, message) => {
if (username === this.name) return;
@ -37,9 +33,10 @@ export class Agent {
this.handleMessage();
});
if (this.history.default) {
this.executeCode(this.history.default);
}
if (autostart)
this.respond('system', 'Agent process restarted. Notify the user and decide what to do.');
});
}
async executeCode(code, triesRemaining=5) {

View 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
View 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);

View file

@ -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);
agent.start();
new AgentProcess('andy').start(true, false);

View file

@ -7,6 +7,7 @@
"mineflayer-pathfinder": "^2.4.4",
"mineflayer-pvp": "^1.3.2",
"openai": "^4.4.0",
"patch-package": "^8.0.0"
"patch-package": "^8.0.0",
"yargs": "^17.7.2"
}
}

View file

@ -11,6 +11,7 @@ export class Coder {
this.executing = false;
this.agent.bot.output = '';
this.code_template = '';
this.timedout = false;
readFile(this.fp+'template.js', 'utf8', (err, data) => {
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() {
if (!this.queued_code) return {success: false, message: "No code to execute.", interrupted: false};
if (!this.code_template) return {success: false, message: "Code template not loaded.", 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, timedout: false};
let src = '';
let code = this.queued_code;
@ -80,9 +81,9 @@ export class Coder {
if (write_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 {
console.log('executing code...\n');
let execution_file = await import('.'+filename);
@ -90,28 +91,33 @@ export class Coder {
this.current_code = this.queued_code;
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;
clearTimeout(TIMEOUT);
this.agent.bot.emit('finished_executing');
let output = this.formatOutput(this.agent.bot);
let interrupted = this.agent.bot.interrupt_code;
let timedout = this.timedout;
this.clear();
return {success:true, message: output, interrupted};
return {success:true, message: output, interrupted, timedout};
} catch (err) {
this.executing = false;
clearTimeout(TIMEOUT);
this.agent.bot.emit('finished_executing');
console.error("Code execution triggered catch: " + 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};
return {success: false, message, interrupted, timedout: false};
}
}
formatOutput(bot) {
if (bot.interrupt_code) return '';
if (bot.interrupt_code && !this.timedout) return '';
let output = bot.output;
const MAX_OUT = 1000;
if (output.length > MAX_OUT) {
@ -125,6 +131,7 @@ export class Coder {
}
async stop() {
if (!this.executing) return;
while (this.executing) {
this.agent.bot.interrupt_code = true;
this.agent.bot.collectBlock.cancelTask();
@ -140,5 +147,26 @@ export class Coder {
this.current_code = '';
this.agent.bot.output = '';
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);
}
}

View file

@ -1,6 +1,6 @@
import { writeFileSync, readFileSync, mkdirSync } from 'fs';
import { getQueryDocs } from './queries.js';
import { getSkillDocs } from './skill_library.js';
import { getSkillDocs } from './skill-library.js';
import { sendRequest, embed, cosineSimilarity } from './gpt.js';

View file

@ -57,13 +57,12 @@ export async function smeltItem(bot, itemName, num=1) {
let furnaceBlock = undefined;
furnaceBlock = getNearestBlock(bot, 'furnace', 6);
if (furnaceBlock === null){
if (!furnaceBlock){
log(bot, `There is no furnace nearby.`)
return false;
}
await bot.lookAt(furnaceBlock.position);
console.log('smelting...');
const furnace = await bot.openFurnace(furnaceBlock);
// check if the furnace is already smelting something
@ -227,6 +226,10 @@ export async function collectBlock(bot, blockType, num=1) {
* @example
* await skills.collectBlock(bot, "oak_log");
**/
if (num < 1) {
log(bot, `Invalid number of blocks to collect: ${num}.`);
return false;
}
let collected = 0;
const blocks = getNearestBlocks(bot, blockType, 64, num);
if (blocks.length === 0) {
@ -256,6 +259,8 @@ export async function collectBlock(bot, blockType, num=1) {
continue;
}
}
if (bot.interrupt_code)
break;
}
log(bot, `Collected ${collected} ${blockType}.`);
return true;
@ -565,7 +570,6 @@ export async function followPlayer(bot, username) {
log(bot, `You are now actively following player ${username}.`);
while (!bot.interrupt_code) {
console.log('followPlayer waiting for interrupt...', bot.interrupt_code);
await new Promise(resolve => setTimeout(resolve, 1000));
}