Merge pull request #281 from kolbytn/action-refactor

Action refactor
This commit is contained in:
Max Robinson 2024-11-03 22:31:39 -06:00 committed by GitHub
commit 4c61ec4713
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 244 additions and 199 deletions

148
src/agent/action_manager.js Normal file
View file

@ -0,0 +1,148 @@
export class ActionManager {
constructor(agent) {
this.agent = agent;
this.executing = false;
this.currentActionLabel = '';
this.currentActionFn = null;
this.timedout = false;
this.resume_func = null;
this.resume_name = '';
}
async resumeAction(actionFn, timeout) {
return this._executeResume(actionFn, timeout);
}
async runAction(actionLabel, actionFn, { timeout, resume = false } = {}) {
if (resume) {
return this._executeResume(actionLabel, actionFn, timeout);
} else {
return this._executeAction(actionLabel, actionFn, timeout);
}
}
async stop() {
if (!this.executing) return;
const timeout = setTimeout(() => {
this.agent.cleanKill('Code execution refused stop after 10 seconds. Killing process.');
}, 10000);
while (this.executing) {
this.agent.requestInterrupt();
console.log('waiting for code to finish executing...');
await new Promise(resolve => setTimeout(resolve, 300));
}
clearTimeout(timeout);
}
cancelResume() {
this.resume_func = null;
this.resume_name = null;
}
async _executeResume(actionLabel = null, actionFn = null, timeout = 10) {
const new_resume = actionFn != null;
if (new_resume) { // start new resume
this.resume_func = actionFn;
assert(actionLabel != null, 'actionLabel is required for new resume');
this.resume_name = actionLabel;
}
if (this.resume_func != null && this.agent.isIdle() && (!this.agent.self_prompter.on || new_resume)) {
this.currentActionLabel = this.resume_name;
let res = await this._executeAction(this.resume_name, this.resume_func, timeout);
this.currentActionLabel = '';
return res;
} else {
return { success: false, message: null, interrupted: false, timedout: false };
}
}
async _executeAction(actionLabel, actionFn, timeout = 10) {
let TIMEOUT;
try {
console.log('executing code...\n');
// await current action to finish (executing=false), with 10 seconds timeout
// also tell agent.bot to stop various actions
if (this.executing) {
console.log(`action "${actionLabel}" trying to interrupt current action "${this.currentActionLabel}"`);
}
await this.stop();
// clear bot logs and reset interrupt code
this.agent.clearBotLogs();
this.executing = true;
this.currentActionLabel = actionLabel;
this.currentActionFn = actionFn;
// timeout in minutes
if (timeout > 0) {
TIMEOUT = this._startTimeout(timeout);
}
// start the action
await actionFn();
// mark action as finished + cleanup
this.executing = false;
this.currentActionLabel = '';
this.currentActionFn = null;
clearTimeout(TIMEOUT);
// get bot activity summary
let output = this._getBotOutputSummary();
let interrupted = this.agent.bot.interrupt_code;
let timedout = this.timedout;
this.agent.clearBotLogs();
// if not interrupted and not generating, emit idle event
if (!interrupted && !this.agent.coder.generating) {
this.agent.bot.emit('idle');
}
// return action status report
return { success: true, message: output, interrupted, timedout };
} catch (err) {
this.executing = false;
this.currentActionLabel = '';
this.currentActionFn = null;
clearTimeout(TIMEOUT);
this.cancelResume();
console.error("Code execution triggered catch: " + err);
await this.stop();
let message = this._getBotOutputSummary() + '!!Code threw exception!! Error: ' + err;
let interrupted = this.agent.bot.interrupt_code;
this.agent.clearBotLogs();
if (!interrupted && !this.agent.coder.generating) {
this.agent.bot.emit('idle');
}
return { success: false, message, interrupted, timedout: false };
}
}
_getBotOutputSummary() {
const { bot } = this.agent;
if (bot.interrupt_code && !this.timedout) return '';
let output = bot.output;
const MAX_OUT = 500;
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;
}
_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.history.add('system', `Code execution timed out after ${TIMEOUT_MINS} minutes. Attempting force stop.`);
await this.stop(); // last attempt to stop
}, TIMEOUT_MINS * 60 * 1000);
}
}

View file

@ -4,6 +4,7 @@ import { Prompter } from './prompter.js';
import { initModes } from './modes.js'; import { initModes } from './modes.js';
import { initBot } from '../utils/mcdata.js'; import { initBot } from '../utils/mcdata.js';
import { containsCommand, commandExists, executeCommand, truncCommandMessage, isAction } from './commands/index.js'; import { containsCommand, commandExists, executeCommand, truncCommandMessage, isAction } from './commands/index.js';
import { ActionManager } from './action_manager.js';
import { NPCContoller } from './npc/controller.js'; import { NPCContoller } from './npc/controller.js';
import { MemoryBank } from './memory_bank.js'; import { MemoryBank } from './memory_bank.js';
import { SelfPrompter } from './self_prompter.js'; import { SelfPrompter } from './self_prompter.js';
@ -13,6 +14,7 @@ import settings from '../../settings.js';
export class Agent { export class Agent {
async start(profile_fp, load_mem=false, init_message=null, count_id=0) { async start(profile_fp, load_mem=false, init_message=null, count_id=0) {
this.actions = new ActionManager(this);
this.prompter = new Prompter(this, profile_fp); this.prompter = new Prompter(this, profile_fp);
this.name = this.prompter.getName(); this.name = this.prompter.getName();
this.history = new History(this); this.history = new History(this);
@ -40,7 +42,7 @@ export class Agent {
await new Promise((resolve) => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, 1000));
console.log(`${this.name} spawned.`); console.log(`${this.name} spawned.`);
this.coder.clear(); this.clearBotLogs();
const ignore_messages = [ const ignore_messages = [
"Set own game mode to", "Set own game mode to",
@ -91,6 +93,17 @@ export class Agent {
}); });
} }
requestInterrupt() {
this.bot.interrupt_code = true;
this.bot.collectBlock.cancelTask();
this.bot.pathfinder.stop();
this.bot.pvp.stop();
}
clearBotLogs() {
this.bot.output = '';
this.bot.interrupt_code = false;
}
async cleanChat(message, translate_up_to=-1) { async cleanChat(message, translate_up_to=-1) {
let to_translate = message; let to_translate = message;
@ -250,8 +263,8 @@ export class Agent {
this.cleanKill('Bot disconnected! Killing agent process.'); this.cleanKill('Bot disconnected! Killing agent process.');
}); });
this.bot.on('death', () => { this.bot.on('death', () => {
this.coder.cancelResume(); this.actions.cancelResume();
this.coder.stop(); this.actions.stop();
}); });
this.bot.on('kicked', (reason) => { this.bot.on('kicked', (reason) => {
console.warn('Bot kicked!', reason); console.warn('Bot kicked!', reason);
@ -267,7 +280,7 @@ export class Agent {
this.bot.clearControlStates(); this.bot.clearControlStates();
this.bot.pathfinder.stop(); // clear any lingering pathfinder this.bot.pathfinder.stop(); // clear any lingering pathfinder
this.bot.modes.unPauseAll(); this.bot.modes.unPauseAll();
this.coder.executeResume(); this.actions.resumeAction();
}); });
// Init NPC controller // Init NPC controller
@ -297,7 +310,7 @@ export class Agent {
} }
isIdle() { isIdle() {
return !this.coder.executing && !this.coder.generating; return !this.actions.executing && !this.coder.generating;
} }
cleanKill(msg='Killing agent process...') { cleanKill(msg='Killing agent process...') {

View file

@ -10,11 +10,8 @@ export class Coder {
this.agent = agent; this.agent = agent;
this.file_counter = 0; this.file_counter = 0;
this.fp = '/bots/'+agent.name+'/action-code/'; this.fp = '/bots/'+agent.name+'/action-code/';
this.executing = false;
this.generating = false; this.generating = false;
this.code_template = ''; this.code_template = '';
this.timedout = false;
this.cur_action_name = '';
readFile('./bots/template.js', 'utf8', (err, data) => { readFile('./bots/template.js', 'utf8', (err, data) => {
if (err) throw err; if (err) throw err;
@ -97,7 +94,7 @@ export class Coder {
async generateCode(agent_history) { async generateCode(agent_history) {
// wrapper to prevent overlapping code generation loops // wrapper to prevent overlapping code generation loops
await this.stop(); await this.agent.actions.stop();
this.generating = true; this.generating = true;
let res = await this.generateCodeLoop(agent_history); let res = await this.generateCodeLoop(agent_history);
this.generating = false; this.generating = false;
@ -133,7 +130,7 @@ export class Coder {
} }
if (failures >= 3) { if (failures >= 3) {
return {success: false, message: 'Action failed, agent would not write code.', interrupted: false, timedout: false}; return { success: false, message: 'Action failed, agent would not write code.', interrupted: false, timedout: false };
} }
messages.push({ messages.push({
role: 'system', role: 'system',
@ -144,25 +141,22 @@ export class Coder {
} }
code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```')); code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```'));
let codeStagingResult; const executionModuleExports = await this.stageCode(code);
try { if (!executionModuleExports) {
codeStagingResult = await this.stageCode(code);
} catch (err) {
console.error('Error staging code:', err);
agent_history.add('system', 'Failed to stage code, something is wrong.'); agent_history.add('system', 'Failed to stage code, something is wrong.');
return {success: false, message: null, interrupted: false, timedout: false}; return {success: false, message: null, interrupted: false, timedout: false};
} }
code_return = await this.execute(async ()=>{ code_return = await this.agent.actions.runAction('newAction', async () => {
return await codeStagingResult.main(this.agent.bot); return await executionModuleExports.main(this.agent.bot);
}, settings.code_timeout_mins); }, { timeout: settings.code_timeout_mins });
if (code_return.interrupted && !code_return.timedout) if (code_return.interrupted && !code_return.timedout)
return {success: false, message: null, interrupted: true, timedout: false}; return { success: false, message: null, interrupted: true, timedout: false };
console.log("Code generation result:", code_return.success, code_return.message); console.log("Code generation result:", code_return.success, code_return.message);
if (code_return.success) { if (code_return.success) {
const summary = "Summary of newAction\nAgent wrote this code: \n```" + this.sanitizeCode(code) + "```\nCode Output:\n" + code_return.message; const summary = "Summary of newAction\nAgent wrote this code: \n```" + this.sanitizeCode(code) + "```\nCode Output:\n" + code_return.message;
return {success: true, message: summary, interrupted: false, timedout: false}; return { success: true, message: summary, interrupted: false, timedout: false };
} }
messages.push({ messages.push({
@ -174,114 +168,7 @@ export class Coder {
content: code_return.message + '\nCode failed. Please try again:' content: code_return.message + '\nCode failed. Please try again:'
}); });
} }
return {success: false, message: null, interrupted: false, timedout: true}; return { success: false, message: null, interrupted: false, timedout: true };
} }
async executeResume(func=null, timeout=10) {
const new_resume = func != null;
if (new_resume) { // start new resume
this.resume_func = func;
this.resume_name = this.cur_action_name;
}
if (this.resume_func != null && this.agent.isIdle() && (!this.agent.self_prompter.on || new_resume)) {
this.cur_action_name = this.resume_name;
let res = await this.execute(this.resume_func, timeout);
this.cur_action_name = '';
return res;
} else {
return {success: false, message: null, interrupted: false, timedout: false};
}
}
cancelResume() {
this.resume_func = null;
this.resume_name = null;
}
setCurActionName(name) {
this.cur_action_name = name.replace(/!/g, '');
}
// returns {success: bool, message: string, interrupted: bool, timedout: false}
async execute(func, timeout=10) {
if (!this.code_template) return {success: false, message: "Code template not loaded.", interrupted: false, timedout: false};
let TIMEOUT;
try {
console.log('executing code...\n');
await this.stop();
this.clear();
this.executing = true;
if (timeout > 0)
TIMEOUT = this._startTimeout(timeout);
await func(); // open fire
this.executing = false;
clearTimeout(TIMEOUT);
let output = this.formatOutput(this.agent.bot);
let interrupted = this.agent.bot.interrupt_code;
let timedout = this.timedout;
this.clear();
if (!interrupted && !this.generating) this.agent.bot.emit('idle');
return {success:true, message: output, interrupted, timedout};
} catch (err) {
this.executing = false;
clearTimeout(TIMEOUT);
this.cancelResume();
console.error("Code execution triggered catch: " + err);
await this.stop();
let message = this.formatOutput(this.agent.bot) + '!!Code threw exception!! Error: ' + err;
let interrupted = this.agent.bot.interrupt_code;
this.clear();
if (!interrupted && !this.generating) this.agent.bot.emit('idle');
return {success: false, message, interrupted, timedout: false};
}
}
formatOutput(bot) {
if (bot.interrupt_code && !this.timedout) return '';
let output = bot.output;
const MAX_OUT = 500;
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() {
if (!this.executing) return;
const start = Date.now();
while (this.executing) {
this.agent.bot.interrupt_code = true;
this.agent.bot.collectBlock.cancelTask();
this.agent.bot.pathfinder.stop();
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) {
this.agent.cleanKill('Code execution refused stop after 10 seconds. Killing process.');
}
}
}
clear() {
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.history.add('system', `Code execution timed out after ${TIMEOUT_MINS} minutes. Attempting force stop.`);
await this.stop(); // last attempt to stop
}, TIMEOUT_MINS*60*1000);
}
} }

View file

@ -1,21 +1,26 @@
import * as skills from '../library/skills.js'; import * as skills from '../library/skills.js';
import settings from '../../../settings.js'; import settings from '../../../settings.js';
function wrapExecution(func, resume=false, timeout=-1) { function runAsAction (actionFn, resume = false, timeout = -1) {
return async function (agent, ...args) { let actionLabel = null; // Will be set on first use
let code_return;
const wrappedFunction = async () => { const wrappedAction = async function (agent, ...args) {
await func(agent, ...args); // Set actionLabel only once, when the action is first created
}; if (!actionLabel) {
if (resume) { const actionObj = actionsList.find(a => a.perform === wrappedAction);
code_return = await agent.coder.executeResume(wrappedFunction, timeout); actionLabel = actionObj.name.substring(1); // Remove the ! prefix
} else {
code_return = await agent.coder.execute(wrappedFunction, timeout);
} }
const actionFnWithAgent = async () => {
await actionFn(agent, ...args);
};
const code_return = await agent.actions.runAction(`action:${actionLabel}`, actionFnWithAgent, { timeout, resume });
if (code_return.interrupted && !code_return.timedout) if (code_return.interrupted && !code_return.timedout)
return; return;
return code_return.message; return code_return.message;
} }
return wrappedAction;
} }
export const actionsList = [ export const actionsList = [
@ -36,9 +41,9 @@ export const actionsList = [
name: '!stop', name: '!stop',
description: 'Force stop all actions and commands that are currently executing.', description: 'Force stop all actions and commands that are currently executing.',
perform: async function (agent) { perform: async function (agent) {
await agent.coder.stop(); await agent.actions.stop();
agent.coder.clear(); agent.clearBotLogs();
agent.coder.cancelResume(); agent.actions.cancelResume();
agent.bot.emit('idle'); agent.bot.emit('idle');
let msg = 'Agent stopped.'; let msg = 'Agent stopped.';
if (agent.self_prompter.on) if (agent.self_prompter.on)
@ -78,18 +83,18 @@ export const actionsList = [
'player_name': {type: 'string', description: 'The name of the player to go to.'}, 'player_name': {type: 'string', description: 'The name of the player to go to.'},
'closeness': {type: 'float', description: 'How close to get to the player.', domain: [0, Infinity]} 'closeness': {type: 'float', description: 'How close to get to the player.', domain: [0, Infinity]}
}, },
perform: wrapExecution(async (agent, player_name, closeness) => { perform: runAsAction(async (agent, player_name, closeness) => {
return await skills.goToPlayer(agent.bot, player_name, closeness); return await skills.goToPlayer(agent.bot, player_name, closeness);
}) })
}, },
{ {
name: '!followPlayer', name: '!followPlayer',
description: 'Endlessly follow the given player. Will defend that player if self_defense mode is on.', description: 'Endlessly follow the given player.',
params: { params: {
'player_name': {type: 'string', description: 'name of the player to follow.'}, 'player_name': {type: 'string', description: 'name of the player to follow.'},
'follow_dist': {type: 'float', description: 'The distance to follow from.', domain: [0, Infinity]} 'follow_dist': {type: 'float', description: 'The distance to follow from.', domain: [0, Infinity]}
}, },
perform: wrapExecution(async (agent, player_name, follow_dist) => { perform: runAsAction(async (agent, player_name, follow_dist) => {
await skills.followPlayer(agent.bot, player_name, follow_dist); await skills.followPlayer(agent.bot, player_name, follow_dist);
}, true) }, true)
}, },
@ -99,9 +104,9 @@ export const actionsList = [
params: { params: {
'type': { type: 'BlockName', description: 'The block type to go to.' }, 'type': { type: 'BlockName', description: 'The block type to go to.' },
'closeness': { type: 'float', description: 'How close to get to the block.', domain: [0, Infinity] }, 'closeness': { type: 'float', description: 'How close to get to the block.', domain: [0, Infinity] },
'search_range': { type: 'float', description: 'The distance to search for the block.', domain: [0, Infinity] } 'search_range': { type: 'float', description: 'The range to search for the block.', domain: [0, 512] }
}, },
perform: wrapExecution(async (agent, type, closeness, range) => { perform: runAsAction(async (agent, type, closeness, range) => {
await skills.goToNearestBlock(agent.bot, type, closeness, range); await skills.goToNearestBlock(agent.bot, type, closeness, range);
}) })
}, },
@ -109,7 +114,7 @@ export const actionsList = [
name: '!moveAway', name: '!moveAway',
description: 'Move away from the current location in any direction by a given distance.', description: 'Move away from the current location in any direction by a given distance.',
params: {'distance': { type: 'float', description: 'The distance to move away.', domain: [0, Infinity] }}, params: {'distance': { type: 'float', description: 'The distance to move away.', domain: [0, Infinity] }},
perform: wrapExecution(async (agent, distance) => { perform: runAsAction(async (agent, distance) => {
await skills.moveAway(agent.bot, distance); await skills.moveAway(agent.bot, distance);
}) })
}, },
@ -127,11 +132,11 @@ export const actionsList = [
name: '!goToPlace', name: '!goToPlace',
description: 'Go to a saved location.', description: 'Go to a saved location.',
params: {'name': { type: 'string', description: 'The name of the location to go to.' }}, params: {'name': { type: 'string', description: 'The name of the location to go to.' }},
perform: wrapExecution(async (agent, name) => { perform: runAsAction(async (agent, name) => {
const pos = agent.memory_bank.recallPlace(name); const pos = agent.memory_bank.recallPlace(name);
if (!pos) { if (!pos) {
skills.log(agent.bot, `No location named "${name}" saved.`); skills.log(agent.bot, `No location named "${name}" saved.`);
return; return;
} }
await skills.goToPosition(agent.bot, pos[0], pos[1], pos[2], 1); await skills.goToPosition(agent.bot, pos[0], pos[1], pos[2], 1);
}) })
@ -144,7 +149,7 @@ export const actionsList = [
'item_name': { type: 'ItemName', description: 'The name of the item to give.' }, 'item_name': { type: 'ItemName', description: 'The name of the item to give.' },
'num': { type: 'int', description: 'The number of items to give.', domain: [1, Number.MAX_SAFE_INTEGER] } 'num': { type: 'int', description: 'The number of items to give.', domain: [1, Number.MAX_SAFE_INTEGER] }
}, },
perform: wrapExecution(async (agent, player_name, item_name, num) => { perform: runAsAction(async (agent, player_name, item_name, num) => {
await skills.giveToPlayer(agent.bot, item_name, player_name, num); await skills.giveToPlayer(agent.bot, item_name, player_name, num);
}) })
}, },
@ -152,7 +157,7 @@ export const actionsList = [
name: '!consume', name: '!consume',
description: 'Eat/drink the given item.', description: 'Eat/drink the given item.',
params: {'item_name': { type: 'ItemName', description: 'The name of the item to consume.' }}, params: {'item_name': { type: 'ItemName', description: 'The name of the item to consume.' }},
perform: wrapExecution(async (agent, item_name) => { perform: runAsAction(async (agent, item_name) => {
await agent.bot.consume(item_name); await agent.bot.consume(item_name);
skills.log(agent.bot, `Consumed ${item_name}.`); skills.log(agent.bot, `Consumed ${item_name}.`);
}) })
@ -161,7 +166,7 @@ export const actionsList = [
name: '!equip', name: '!equip',
description: 'Equip the given item.', description: 'Equip the given item.',
params: {'item_name': { type: 'ItemName', description: 'The name of the item to equip.' }}, params: {'item_name': { type: 'ItemName', description: 'The name of the item to equip.' }},
perform: wrapExecution(async (agent, item_name) => { perform: runAsAction(async (agent, item_name) => {
await skills.equip(agent.bot, item_name); await skills.equip(agent.bot, item_name);
}) })
}, },
@ -172,7 +177,7 @@ export const actionsList = [
'item_name': { type: 'ItemName', description: 'The name of the item to put in the chest.' }, 'item_name': { type: 'ItemName', description: 'The name of the item to put in the chest.' },
'num': { type: 'int', description: 'The number of items to put in the chest.', domain: [1, Number.MAX_SAFE_INTEGER] } 'num': { type: 'int', description: 'The number of items to put in the chest.', domain: [1, Number.MAX_SAFE_INTEGER] }
}, },
perform: wrapExecution(async (agent, item_name, num) => { perform: runAsAction(async (agent, item_name, num) => {
await skills.putInChest(agent.bot, item_name, num); await skills.putInChest(agent.bot, item_name, num);
}) })
}, },
@ -183,7 +188,7 @@ export const actionsList = [
'item_name': { type: 'ItemName', description: 'The name of the item to take.' }, 'item_name': { type: 'ItemName', description: 'The name of the item to take.' },
'num': { type: 'int', description: 'The number of items to take.', domain: [1, Number.MAX_SAFE_INTEGER] } 'num': { type: 'int', description: 'The number of items to take.', domain: [1, Number.MAX_SAFE_INTEGER] }
}, },
perform: wrapExecution(async (agent, item_name, num) => { perform: runAsAction(async (agent, item_name, num) => {
await skills.takeFromChest(agent.bot, item_name, num); await skills.takeFromChest(agent.bot, item_name, num);
}) })
}, },
@ -191,7 +196,7 @@ export const actionsList = [
name: '!viewChest', name: '!viewChest',
description: 'View the items/counts of the nearest chest.', description: 'View the items/counts of the nearest chest.',
params: { }, params: { },
perform: wrapExecution(async (agent) => { perform: runAsAction(async (agent) => {
await skills.viewChest(agent.bot); await skills.viewChest(agent.bot);
}) })
}, },
@ -202,7 +207,7 @@ export const actionsList = [
'item_name': { type: 'ItemName', description: 'The name of the item to discard.' }, 'item_name': { type: 'ItemName', description: 'The name of the item to discard.' },
'num': { type: 'int', description: 'The number of items to discard.', domain: [1, Number.MAX_SAFE_INTEGER] } 'num': { type: 'int', description: 'The number of items to discard.', domain: [1, Number.MAX_SAFE_INTEGER] }
}, },
perform: wrapExecution(async (agent, item_name, num) => { perform: runAsAction(async (agent, item_name, num) => {
const start_loc = agent.bot.entity.position; const start_loc = agent.bot.entity.position;
await skills.moveAway(agent.bot, 5); await skills.moveAway(agent.bot, 5);
await skills.discard(agent.bot, item_name, num); await skills.discard(agent.bot, item_name, num);
@ -216,7 +221,7 @@ export const actionsList = [
'type': { type: 'BlockName', description: 'The block type to collect.' }, 'type': { type: 'BlockName', description: 'The block type to collect.' },
'num': { type: 'int', description: 'The number of blocks to collect.', domain: [1, Number.MAX_SAFE_INTEGER] } 'num': { type: 'int', description: 'The number of blocks to collect.', domain: [1, Number.MAX_SAFE_INTEGER] }
}, },
perform: wrapExecution(async (agent, type, num) => { perform: runAsAction(async (agent, type, num) => {
await skills.collectBlock(agent.bot, type, num); await skills.collectBlock(agent.bot, type, num);
}, false, 10) // 10 minute timeout }, false, 10) // 10 minute timeout
}, },
@ -226,10 +231,10 @@ export const actionsList = [
params: { params: {
'type': { type: 'BlockName', description: 'The block type to collect.' } 'type': { type: 'BlockName', description: 'The block type to collect.' }
}, },
perform: wrapExecution(async (agent, type) => { perform: runAsAction(async (agent, type) => {
let success = await skills.collectBlock(agent.bot, type, 1); let success = await skills.collectBlock(agent.bot, type, 1);
if (!success) if (!success)
agent.coder.cancelResume(); agent.actions.cancelResume();
}, true, 3) // 3 minute timeout }, true, 3) // 3 minute timeout
}, },
{ {
@ -239,7 +244,7 @@ export const actionsList = [
'recipe_name': { type: 'ItemName', description: 'The name of the output item to craft.' }, 'recipe_name': { type: 'ItemName', description: 'The name of the output item to craft.' },
'num': { type: 'int', description: 'The number of times to craft the recipe. This is NOT the number of output items, as it may craft many more items depending on the recipe.', domain: [1, Number.MAX_SAFE_INTEGER] } 'num': { type: 'int', description: 'The number of times to craft the recipe. This is NOT the number of output items, as it may craft many more items depending on the recipe.', domain: [1, Number.MAX_SAFE_INTEGER] }
}, },
perform: wrapExecution(async (agent, recipe_name, num) => { perform: runAsAction(async (agent, recipe_name, num) => {
await skills.craftRecipe(agent.bot, recipe_name, num); await skills.craftRecipe(agent.bot, recipe_name, num);
}) })
}, },
@ -250,32 +255,29 @@ export const actionsList = [
'item_name': { type: 'ItemName', description: 'The name of the input item to smelt.' }, 'item_name': { type: 'ItemName', description: 'The name of the input item to smelt.' },
'num': { type: 'int', description: 'The number of times to smelt the item.', domain: [1, Number.MAX_SAFE_INTEGER] } 'num': { type: 'int', description: 'The number of times to smelt the item.', domain: [1, Number.MAX_SAFE_INTEGER] }
}, },
perform: async function (agent, item_name, num) { perform: runAsAction(async (agent, item_name, num) => {
let response = await wrapExecution(async (agent) => { let response = await skills.smeltItem(agent.bot, item_name, num);
console.log('smelting item');
return await skills.smeltItem(agent.bot, item_name, num);
})(agent);
if (response.indexOf('Successfully') !== -1) { if (response.indexOf('Successfully') !== -1) {
// there is a bug where the bot's inventory is not updated after smelting // there is a bug where the bot's inventory is not updated after smelting
// only updates after a restart // only updates after a restart
agent.cleanKill(response + ' Safely restarting to update inventory.'); agent.cleanKill(response + ' Safely restarting to update inventory.');
} }
return response; return response;
} })
}, },
{ {
name: '!clearFurnace', name: '!clearFurnace',
description: 'Take all items out of the nearest furnace.', description: 'Take all items out of the nearest furnace.',
params: { }, params: { },
perform: wrapExecution(async (agent) => { perform: runAsAction(async (agent) => {
await skills.clearNearestFurnace(agent.bot); await skills.clearNearestFurnace(agent.bot);
}) })
}, },
{ {
name: '!placeHere', name: '!placeHere',
description: 'Place a given block in the current location. Do NOT use to build structures, only use for single blocks/torches.', description: 'Place a given block in the current location. Do NOT use to build structures, only use for single blocks/torches.',
params: {'type': { type: 'BlockName', description: 'The block type to place.' }}, params: {'type': { type: 'BlockName', description: 'The block type to place.' }},
perform: wrapExecution(async (agent, type) => { perform: runAsAction(async (agent, type) => {
let pos = agent.bot.entity.position; let pos = agent.bot.entity.position;
await skills.placeBlock(agent.bot, type, pos.x, pos.y, pos.z); await skills.placeBlock(agent.bot, type, pos.x, pos.y, pos.z);
}) })
@ -284,14 +286,14 @@ export const actionsList = [
name: '!attack', name: '!attack',
description: 'Attack and kill the nearest entity of a given type.', description: 'Attack and kill the nearest entity of a given type.',
params: {'type': { type: 'string', description: 'The type of entity to attack.'}}, params: {'type': { type: 'string', description: 'The type of entity to attack.'}},
perform: wrapExecution(async (agent, type) => { perform: runAsAction(async (agent, type) => {
await skills.attackNearest(agent.bot, type, true); await skills.attackNearest(agent.bot, type, true);
}) })
}, },
{ {
name: '!goToBed', name: '!goToBed',
description: 'Go to the nearest bed and sleep.', description: 'Go to the nearest bed and sleep.',
perform: wrapExecution(async (agent) => { perform: runAsAction(async (agent) => {
await skills.goToBed(agent.bot); await skills.goToBed(agent.bot);
}) })
}, },
@ -299,7 +301,7 @@ export const actionsList = [
name: '!activate', name: '!activate',
description: 'Activate the nearest object of a given type.', description: 'Activate the nearest object of a given type.',
params: {'type': { type: 'BlockName', description: 'The type of object to activate.' }}, params: {'type': { type: 'BlockName', description: 'The type of object to activate.' }},
perform: wrapExecution(async (agent, type) => { perform: runAsAction(async (agent, type) => {
await skills.activateNearestBlock(agent.bot, type); await skills.activateNearestBlock(agent.bot, type);
}) })
}, },
@ -307,7 +309,7 @@ export const actionsList = [
name: '!stay', name: '!stay',
description: 'Stay in the current location no matter what. Pauses all modes.', description: 'Stay in the current location no matter what. Pauses all modes.',
params: {'type': { type: 'int', description: 'The number of seconds to stay. -1 for forever.', domain: [-1, Number.MAX_SAFE_INTEGER] }}, params: {'type': { type: 'int', description: 'The number of seconds to stay. -1 for forever.', domain: [-1, Number.MAX_SAFE_INTEGER] }},
perform: wrapExecution(async (agent, seconds) => { perform: runAsAction(async (agent, seconds) => {
await skills.stay(agent.bot, seconds); await skills.stay(agent.bot, seconds);
}) })
}, },
@ -321,9 +323,9 @@ export const actionsList = [
perform: async function (agent, mode_name, on) { perform: async function (agent, mode_name, on) {
const modes = agent.bot.modes; const modes = agent.bot.modes;
if (!modes.exists(mode_name)) if (!modes.exists(mode_name))
return `Mode ${mode_name} does not exist.` + modes.getDocs(); return `Mode ${mode_name} does not exist.` + modes.getDocs();
if (modes.isOn(mode_name) === on) if (modes.isOn(mode_name) === on)
return `Mode ${mode_name} is already ${on ? 'on' : 'off'}.`; return `Mode ${mode_name} is already ${on ? 'on' : 'off'}.`;
modes.setOn(mode_name, on); modes.setOn(mode_name, on);
return `Mode ${mode_name} is now ${on ? 'on' : 'off'}.`; return `Mode ${mode_name} is now ${on ? 'on' : 'off'}.`;
} }

View file

@ -207,7 +207,6 @@ export async function executeCommand(agent, message) {
else { else {
console.log('parsed command:', parsed); console.log('parsed command:', parsed);
const command = getCommand(parsed.commandName); const command = getCommand(parsed.commandName);
const is_action = isAction(command.name);
let numArgs = 0; let numArgs = 0;
if (parsed.args) { if (parsed.args) {
numArgs = parsed.args.length; numArgs = parsed.args.length;
@ -215,11 +214,7 @@ export async function executeCommand(agent, message) {
if (numArgs !== numParams(command)) if (numArgs !== numParams(command))
return `Command ${command.name} was given ${numArgs} args, but requires ${numParams(command)} args.`; return `Command ${command.name} was given ${numArgs} args, but requires ${numParams(command)} args.`;
else { else {
if (is_action)
agent.coder.setCurActionName(command.name);
const result = await command.perform(agent, ...parsed.args); const result = await command.perform(agent, ...parsed.args);
if (is_action)
agent.coder.setCurActionName('');
return result; return result;
} }
} }

View file

@ -162,7 +162,7 @@ const modes = [
{ {
name: 'item_collecting', name: 'item_collecting',
description: 'Collect nearby items when idle.', description: 'Collect nearby items when idle.',
interrupts: ['followPlayer'], interrupts: ['action:followPlayer'],
on: true, on: true,
active: false, active: false,
@ -193,7 +193,7 @@ const modes = [
{ {
name: 'torch_placing', name: 'torch_placing',
description: 'Place torches when idle and there are no torches nearby.', description: 'Place torches when idle and there are no torches nearby.',
interrupts: ['followPlayer'], interrupts: ['action:followPlayer'],
on: true, on: true,
active: false, active: false,
cooldown: 5, cooldown: 5,
@ -260,9 +260,9 @@ async function execute(mode, agent, func, timeout=-1) {
if (agent.self_prompter.on) if (agent.self_prompter.on)
agent.self_prompter.stopLoop(); agent.self_prompter.stopLoop();
mode.active = true; mode.active = true;
let code_return = await agent.coder.execute(async () => { let code_return = await agent.actions.runAction(`mode:${mode.name}`, async () => {
await func(); await func();
}, timeout); }, { timeout });
mode.active = false; mode.active = false;
console.log(`Mode ${mode.name} finished executing, code_return: ${code_return.message}`); console.log(`Mode ${mode.name} finished executing, code_return: ${code_return.message}`);
} }
@ -328,7 +328,7 @@ class ModeController {
this.unPauseAll(); this.unPauseAll();
} }
for (let mode of this.modes_list) { for (let mode of this.modes_list) {
let interruptible = mode.interrupts.some(i => i === 'all') || mode.interrupts.some(i => i === this.agent.coder.cur_action_name); let interruptible = mode.interrupts.some(i => i === 'all') || mode.interrupts.some(i => i === this.agent.actions.currentActionLabel);
if (mode.on && !mode.paused && !mode.active && (this.agent.isIdle() || interruptible)) { if (mode.on && !mode.paused && !mode.active && (this.agent.isIdle() || interruptible)) {
await mode.update(this.agent); await mode.update(this.agent);
} }

View file

@ -13,7 +13,7 @@ export class BuildGoal {
async wrapSkill(func) { async wrapSkill(func) {
if (!this.agent.isIdle()) if (!this.agent.isIdle())
return false; return false;
let res = await this.agent.coder.execute(func); let res = await this.agent.actions.runAction('BuildGoal', func);
return !res.interrupted; return !res.interrupted;
} }

View file

@ -72,7 +72,7 @@ export class NPCContoller {
if (!this.agent.isIdle()) return; if (!this.agent.isIdle()) return;
// Persue goal // Persue goal
if (!this.agent.coder.resume_func) { if (!this.agent.actions.resume_func) {
this.executeNext(); this.executeNext();
this.agent.history.save(); this.agent.history.save();
} }
@ -104,7 +104,7 @@ export class NPCContoller {
async executeNext() { async executeNext() {
if (!this.agent.isIdle()) return; if (!this.agent.isIdle()) return;
await this.agent.coder.execute(async () => { await this.agent.actions.runAction('npc:moveAway', async () => {
await skills.moveAway(this.agent.bot, 2); await skills.moveAway(this.agent.bot, 2);
}); });
@ -114,7 +114,7 @@ export class NPCContoller {
if (building == this.data.home) { if (building == this.data.home) {
let door_pos = this.getBuildingDoor(building); let door_pos = this.getBuildingDoor(building);
if (door_pos) { if (door_pos) {
await this.agent.coder.execute(async () => { await this.agent.actions.runAction('npc:exitBuilding', async () => {
await skills.useDoor(this.agent.bot, door_pos); await skills.useDoor(this.agent.bot, door_pos);
await skills.moveAway(this.agent.bot, 2); // If the bot is too close to the building it will try to enter again await skills.moveAway(this.agent.bot, 2); // If the bot is too close to the building it will try to enter again
}); });
@ -132,13 +132,13 @@ export class NPCContoller {
let building = this.currentBuilding(); let building = this.currentBuilding();
if (this.data.home !== null && (building === null || building != this.data.home)) { if (this.data.home !== null && (building === null || building != this.data.home)) {
let door_pos = this.getBuildingDoor(this.data.home); let door_pos = this.getBuildingDoor(this.data.home);
await this.agent.coder.execute(async () => { await this.agent.actions.runAction('npc:returnHome', async () => {
await skills.useDoor(this.agent.bot, door_pos); await skills.useDoor(this.agent.bot, door_pos);
}); });
} }
// Go to bed // Go to bed
await this.agent.coder.execute(async () => { await this.agent.actions.runAction('npc:bed', async () => {
await skills.goToBed(this.agent.bot); await skills.goToBed(this.agent.bot);
}); });
} }

View file

@ -322,7 +322,7 @@ export class ItemGoal {
// If the bot has failed to obtain the block before, explore // If the bot has failed to obtain the block before, explore
if (this.failed.includes(next.name)) { if (this.failed.includes(next.name)) {
this.failed = this.failed.filter((item) => item !== next.name); this.failed = this.failed.filter((item) => item !== next.name);
await this.agent.coder.execute(async () => { await this.agent.actions.runAction('itemGoal:explore', async () => {
await skills.moveAway(this.agent.bot, 8); await skills.moveAway(this.agent.bot, 8);
}); });
} else { } else {
@ -339,7 +339,7 @@ export class ItemGoal {
// Execute the next goal // Execute the next goal
let init_quantity = world.getInventoryCounts(this.agent.bot)[next.name] || 0; let init_quantity = world.getInventoryCounts(this.agent.bot)[next.name] || 0;
await this.agent.coder.execute(async () => { await this.agent.actions.runAction('itemGoal:next', async () => {
await next.execute(quantity); await next.execute(quantity);
}); });
let final_quantity = world.getInventoryCounts(this.agent.bot)[next.name] || 0; let final_quantity = world.getInventoryCounts(this.agent.bot)[next.name] || 0;

View file

@ -87,7 +87,7 @@ export class SelfPrompter {
async stop(stop_action=true) { async stop(stop_action=true) {
this.interrupt = true; this.interrupt = true;
if (stop_action) if (stop_action)
await this.agent.coder.stop(); await this.agent.actions.stop();
await this.stopLoop(); await this.stopLoop();
this.on = false; this.on = false;
} }