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 { 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;

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

View file

@ -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"
} }
} }

View file

@ -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);
} }
} }

View file

@ -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';

View file

@ -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));
} }