mindcraft/src/agent/coder.js

269 lines
10 KiB
JavaScript
Raw Normal View History

2024-01-03 22:16:50 -08:00
import { writeFile, readFile, mkdirSync } from 'fs';
export class Coder {
constructor(agent) {
this.agent = agent;
2023-11-07 12:46:55 -06:00
this.file_counter = 0;
2023-12-21 15:11:38 -07:00
this.fp = '/bots/'+agent.name+'/action-code/';
2023-11-13 00:57:20 -06:00
this.executing = false;
this.generating = false;
this.code_template = '';
2023-12-06 22:15:09 -06:00
this.timedout = false;
2023-12-21 15:11:38 -07:00
readFile('./bots/template.js', 'utf8', (err, data) => {
if (err) throw err;
this.code_template = data;
});
2023-12-21 20:42:01 -07:00
mkdirSync('.' + this.fp, { recursive: true });
}
// write custom code to file and import it
async stageCode(code) {
2024-02-16 11:57:48 -06:00
code = this.sanitizeCode(code);
let src = '';
code = code.replaceAll('console.log(', 'log(bot,');
code = code.replaceAll('log("', 'log(bot,"');
// this may cause problems in callback functions
code = code.replaceAll(';\n', '; if(bot.interrupt_code) {log(bot, "Code interrupted.");return;}\n');
for (let line of code.split('\n')) {
src += ` ${line}\n`;
}
src = this.code_template.replace('/* CODE HERE */', src);
console.log("writing to file...", src)
let filename = this.file_counter + '.js';
// if (this.file_counter > 0) {
// let prev_filename = this.fp + (this.file_counter-1) + '.js';
// unlink(prev_filename, (err) => {
// console.log("deleted file " + prev_filename);
// if (err) console.error(err);
// });
// } commented for now, useful to keep files for debugging
this.file_counter++;
let write_result = await this.writeFilePromise('.' + this.fp + filename, src)
if (write_result) {
console.error('Error writing code execution file: ' + result);
return null;
}
return await import('../..' + this.fp + filename);
}
2024-02-16 11:57:48 -06:00
sanitizeCode(code) {
code = code.trim();
const remove_strs = ['Javascript', 'javascript', 'js']
for (let r of remove_strs) {
if (code.startsWith(r)) {
code = code.slice(r.length);
return code;
}
}
return code;
}
2023-11-07 12:46:55 -06:00
writeFilePromise(filename, src) {
// makes it so we can await this function
return new Promise((resolve, reject) => {
writeFile(filename, src, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
2024-01-03 22:16:50 -08:00
async generateCode(agent_history) {
// wrapper to prevent overlapping code generation loops
await this.stop();
this.generating = true;
2024-01-26 14:10:09 -08:00
let res = await this.generateCodeLoop(agent_history);
this.generating = false;
2024-01-26 14:10:09 -08:00
if (!res.interrupted) this.agent.bot.emit('idle');
2024-02-16 11:57:48 -06:00
return res.message;
}
async generateCodeLoop(agent_history) {
let messages = agent_history.getHistory();
2024-01-03 22:16:50 -08:00
let code_return = null;
let failures = 0;
for (let i=0; i<5; i++) {
if (this.agent.bot.interrupt_code)
2024-02-16 11:57:48 -06:00
return {success: true, message: null, interrupted: true, timedout: false};
console.log(messages)
let res = await this.agent.prompter.promptCoding(messages);
let contains_code = res.indexOf('```') !== -1;
if (!contains_code) {
2024-03-23 11:15:53 -05:00
if (res.indexOf('!newAction') !== -1) {
messages.push({
role: 'assistant',
content: res.substring(0, res.indexOf('!newAction'))
});
continue; // using newaction will continue the loop
}
if (code_return) {
agent_history.add('system', code_return.message);
agent_history.add(this.agent.name, res);
this.agent.bot.chat(res);
2024-01-26 14:10:09 -08:00
return {success: true, message: null, interrupted: false, timedout: false};
}
if (failures >= 1) {
2024-02-16 11:57:48 -06:00
return {success: false, message: 'Action failed, agent would not write code.', interrupted: false, timedout: false};
}
messages.push({
role: 'system',
content: 'Error: no code provided. Write code in codeblock in your response. ``` // example ```'}
);
failures++;
continue;
}
2024-01-03 22:16:50 -08:00
let code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```'));
const execution_file = await this.stageCode(code);
if (!execution_file) {
agent_history.add('system', 'Failed to stage code, something is wrong.');
2024-01-26 14:10:09 -08:00
return {success: false, message: null, interrupted: false, timedout: false};
}
code_return = await this.execute(async ()=>{
return await execution_file.main(this.agent.bot);
});
2024-01-03 22:16:50 -08:00
if (code_return.interrupted && !code_return.timedout)
2024-01-26 14:10:09 -08:00
return {success: false, message: null, interrupted: true, timedout: false};
2024-02-16 11:57:48 -06:00
console.log("Code generation result:", code_return.success, code_return.message);
2024-01-03 22:16:50 -08:00
messages.push({
role: 'assistant',
content: res
});
messages.push({
role: 'system',
2024-01-03 22:16:50 -08:00
content: code_return.message
});
}
2024-01-26 14:10:09 -08:00
return {success: false, message: null, interrupted: false, timedout: true};
2024-01-03 22:16:50 -08:00
}
2023-11-13 00:57:20 -06:00
async executeResume(func=null, name=null, timeout=10) {
2024-01-26 12:11:32 -08:00
if (func != null) {
this.resume_func = func;
this.resume_name = name;
2024-01-26 12:11:32 -08:00
}
2024-02-03 16:19:20 -08:00
if (this.resume_func != null && this.agent.isIdle()) {
2024-03-05 12:50:13 -08:00
console.log('resuming code...')
2024-01-26 12:11:32 -08:00
this.interruptible = true;
let res = await this.execute(this.resume_func, timeout);
2024-01-26 12:11:32 -08:00
this.interruptible = false;
return res;
} else {
return {success: false, message: null, interrupted: false, timedout: false};
}
}
2024-02-05 13:21:32 -06:00
cancelResume() {
this.resume_func = null;
this.resume_name = null;
}
2023-12-08 16:18:20 -06:00
// returns {success: bool, message: string, interrupted: bool, timedout: false}
2024-01-16 15:53:27 -06:00
async execute(func, timeout=10) {
2023-12-06 22:15:09 -06:00
if (!this.code_template) return {success: false, message: "Code template not loaded.", interrupted: false, timedout: false};
2023-12-04 21:33:40 -06:00
2023-12-10 20:19:07 -06:00
let TIMEOUT;
2023-11-07 12:46:55 -06:00
try {
console.log('executing code...\n');
2023-11-13 00:57:20 -06:00
await this.stop();
this.clear();
2023-11-15 17:00:02 -06:00
2023-11-08 21:05:18 -08:00
this.executing = true;
if (timeout > 0)
TIMEOUT = this._startTimeout(timeout);
await func(); // open fire
2023-11-08 21:05:18 -08:00
this.executing = false;
2023-12-10 20:19:07 -06:00
clearTimeout(TIMEOUT);
2023-12-09 21:40:53 -06:00
2023-11-13 00:57:20 -06:00
let output = this.formatOutput(this.agent.bot);
2023-11-13 11:09:37 -06:00
let interrupted = this.agent.bot.interrupt_code;
2023-12-06 22:15:09 -06:00
let timedout = this.timedout;
2023-11-07 22:23:42 -06:00
this.clear();
2024-01-26 14:10:09 -08:00
if (!interrupted && !this.generating) this.agent.bot.emit('idle');
2023-12-06 22:15:09 -06:00
return {success:true, message: output, interrupted, timedout};
2023-11-07 12:46:55 -06:00
} catch (err) {
this.executing = false;
2024-01-25 15:52:07 -08:00
clearTimeout(TIMEOUT);
2024-02-05 13:21:32 -06:00
this.cancelResume();
console.error("Code execution triggered catch: " + err);
await this.stop();
2024-01-25 15:52:07 -08:00
let message = this.formatOutput(this.agent.bot) + '!!Code threw exception!! Error: ' + err;
2023-11-13 11:09:37 -06:00
let interrupted = this.agent.bot.interrupt_code;
this.clear();
2024-01-26 14:10:09 -08:00
if (!interrupted && !this.generating) this.agent.bot.emit('idle');
2023-12-06 22:15:09 -06:00
return {success: false, message, interrupted, timedout: false};
2023-11-07 12:46:55 -06:00
}
}
2023-11-07 22:23:42 -06:00
2023-11-13 00:57:20 -06:00
formatOutput(bot) {
2023-12-08 16:18:20 -06:00
if (bot.interrupt_code && !this.timedout) return '';
2023-11-13 00:57:20 -06:00
let output = bot.output;
2024-02-16 11:57:48 -06:00
const MAX_OUT = 500;
2023-11-13 00:57:20 -06:00
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;
}
2023-11-13 00:57:20 -06:00
async stop() {
2023-12-06 22:15:09 -06:00
if (!this.executing) return;
const start = Date.now();
2023-11-08 21:05:18 -08:00
while (this.executing) {
2023-11-13 11:09:37 -06:00
this.agent.bot.interrupt_code = true;
2023-11-08 21:05:18 -08:00
this.agent.bot.collectBlock.cancelTask();
this.agent.bot.pathfinder.stop();
2023-11-27 21:07:52 -06:00
this.agent.bot.pvp.stop();
console.log('waiting for code to finish executing...');
await new Promise(resolve => setTimeout(resolve, 1000));
if (Date.now() - start > 10 * 1000) {
process.exit(1); // force exit program after 10 seconds of failing to stop
}
2023-11-08 21:05:18 -08:00
}
2023-11-13 00:57:20 -06:00
}
clear() {
this.agent.bot.output = '';
2023-11-13 11:09:37 -06:00
this.agent.bot.interrupt_code = false;
2023-12-06 22:15:09 -06:00
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;
2023-12-08 16:18:20 -06:00
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.`;
2023-12-06 22:15:09 -06:00
this.stop(); // last attempt to stop
2023-12-08 16:18:20 -06:00
await new Promise(resolve => setTimeout(resolve, 5 * 1000)); // wait 5 seconds
2023-12-06 22:15:09 -06:00
if (this.executing) {
console.error(`Failed to stop. Killing process. Goodbye.`);
2023-12-09 21:40:53 -06:00
this.agent.bot.output += `\nForce stop failed! Process was killed and will be restarted. Goodbye world.`;
2023-12-12 13:39:35 -06:00
this.agent.bot.chat('Goodbye world.');
2023-12-08 16:18:20 -06:00
let output = this.formatOutput(this.agent.bot);
this.agent.history.add('system', output);
this.agent.history.save();
2023-12-06 22:15:09 -06:00
process.exit(1); // force exit program
}
console.log('Code execution stopped successfully.');
}, TIMEOUT_MINS*60*1000);
2023-11-07 22:23:42 -06:00
}
}