diff --git a/example_tasks.json b/example_tasks.json index 0df5d96..8fcff1e 100644 --- a/example_tasks.json +++ b/example_tasks.json @@ -11,7 +11,6 @@ "andy", "randy" ], - "agent_number": 2, "initial_inventory": { "andy": { "iron_ingot": 1 @@ -26,13 +25,8 @@ "type": "construction", "goal": "Build a house" }, - "multiagent_techtree_1_shears_with_2_iron_ingot": { - "goal": "Collaborate with other agents to build a shear.", - "agent_names": [ - "andy", - "randy" - ], - "agent_number": 2, + "techtree_1_shears_with_2_iron_ingot": { + "goal": "Build a shear.", "initial_inventory": { "andy": { "iron_ingot": 1 @@ -48,11 +42,11 @@ }, "multiagent_techtree_1_stone_pickaxe": { "goal": "Collaborate with other agents to build an stone pickaxe", + "conversation": "Let's build a stone pickaxe", "agent_names": [ "andy", "randy" ], - "agent_number": 2, "initial_inventory": { "andy": { "wooden_pickaxe": 1 @@ -65,45 +59,5 @@ "number_of_target": 1, "type": "techtree", "timeout": 300 - }, - "multiagent_build_wooden_pickaxe": { - "goal": "Collaborate with other agents to build a wooden pickaxe.", - "agent_names": [ - "andy", - "randy" - ], - "agent_number": 2, - "initial_inventory": { - "andy": { - "oak_log": 2 - }, - "randy": { - "stick": 2 - } - }, - "target": "wooden_pickaxe", - "number_of_target": 1, - "type": "techtree", - "timeout": 120 - }, - "multiagent_techtree_boat": { - "goal": "Collaborate with other agents to build a birch boat.", - "agent_names": [ - "Bob", - "Alice" - ], - "agent_number": 2, - "initial_inventory": { - "Bob": { - "birch_planks": 3 - }, - "Alice": { - "birch_planks": 2 - } - }, - "target": "birch_boat", - "number_of_target": 1, - "type": "techtree", - "timeout": 60 } } \ No newline at end of file diff --git a/src/agent/agent.js b/src/agent/agent.js index 7964c57..d2adfe2 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -8,7 +8,7 @@ import { ActionManager } from './action_manager.js'; import { NPCContoller } from './npc/controller.js'; import { MemoryBank } from './memory_bank.js'; import { SelfPrompter } from './self_prompter.js'; -import { isOtherAgent, initConversationManager, sendToBot, endAllChats, responseScheduledFor} from './conversation.js'; +import { isOtherAgent, initConversationManager, sendToBot, endAllChats, responseScheduledFor, inConversation } from './conversation.js'; import { handleTranslation, handleEnglishTranslation } from '../utils/translator.js'; import { addViewer } from './viewer.js'; import settings from '../../settings.js'; @@ -18,6 +18,7 @@ import { loadTask, initBotTask, TechTreeHarvestValidator } from '../utils/tasks. export class Agent { async start(profile_fp, load_mem=false, init_message=null, count_id=0, task_path=null, task_id=null) { this.last_sender = null; + this.count_id = count_id; try { if (!profile_fp) { throw new Error('No profile filepath provided'); @@ -66,10 +67,16 @@ export class Agent { this.taskTimeout = this.task.timeout || 300; this.taskStartTime = Date.now(); this.validator = new TechTreeHarvestValidator(this.task, this.bot); + this.blocked_actions = this.task.blocked_actions || []; + if (this.task.goal) + this.blocked_actions.push('!endGoal'); + if (this.task.conversation) + this.blocked_actions.push('!endConversation'); } else { this.task = null; this.taskTimeout = null; this.validator = null; + this.blocked_actions = []; } console.log("Is validated:", this.validator && this.validator.validate()); @@ -96,18 +103,13 @@ export class Agent { console.log(`${this.name} spawned.`); this.clearBotLogs(); - - if (this.task) { - await initBotTask(this.bot, this.task); - await new Promise((resolve) => setTimeout(resolve, 10000)); - if (this.task.agent_names && this.task.agent_names.filter(name => !this.bot.players[name]).length) { - console.log(`Missing players/bots: ${missingPlayers.join(', ')}`); - this.cleanKill('Not all required players/bots are present in the world. Exiting.', 4); - } - } this._setupEventHandlers(save_data, init_message); this.startEvents(); + + if (this.task) + await initBotTask(this); + } catch (error) { console.error('Error in spawn event:', error); process.exit(0); @@ -437,18 +439,24 @@ export class Agent { }, INTERVAL); // Check for task completion - setInterval(async () => { - if (this.task && this.validator && this.validator.validate()) - this.killBots(); - if (this.task && this.taskTimeout) { - const elapsedTime = (Date.now() - this.taskStartTime) / 1000; - if (elapsedTime >= this.taskTimeout) { - console.log('Task timeout reached. Task unsuccessful.'); - this.cleanKill('Task unsuccessful: Timeout reached', 3); + if (this.task) { + setInterval(async () => { + if (this.validator && this.validator.validate()) + this.killBots(); + if (this.task.goal && !this.self_prompter.on) + this.cleanKill('Task unsuccessful: Agent ended goal', 3); + if (this.task.conversation && !inConversation()) + this.cleanKill('Task unsuccessful: Agent ended conversation', 3); + if (this.taskTimeout) { + const elapsedTime = (Date.now() - this.taskStartTime) / 1000; + if (elapsedTime >= this.taskTimeout) { + console.log('Task timeout reached. Task unsuccessful.'); + this.cleanKill('Task unsuccessful: Timeout reached', 3); + } } - } - - }, 1000); + + }, 1000); + } this.bot.emit('idle'); } diff --git a/src/agent/commands/index.js b/src/agent/commands/index.js index 6170962..5696396 100644 --- a/src/agent/commands/index.js +++ b/src/agent/commands/index.js @@ -220,7 +220,7 @@ export async function executeCommand(agent, message) { } } -export function getCommandDocs() { +export function getCommandDocs(blacklist=null) { const typeTranslations = { //This was added to keep the prompt the same as before type checks were implemented. //If the language model is giving invalid inputs changing this might help. @@ -234,6 +234,9 @@ export function getCommandDocs() { Use the commands with the syntax: !commandName or !commandName("arg1", 1.2, ...) if the command takes arguments.\n Do not use codeblocks. Use double quotes for strings. Only use one command in each response, trailing commands and comments will be ignored.\n`; for (let command of commandList) { + if (blacklist && blacklist.includes(command.name)) { + continue; + } docs += command.name + ': ' + command.description + '\n'; if (command.params) { docs += 'Params:\n'; diff --git a/src/agent/prompter.js b/src/agent/prompter.js index 1e91469..2553882 100644 --- a/src/agent/prompter.js +++ b/src/agent/prompter.js @@ -173,7 +173,7 @@ export class Prompter { prompt = prompt.replaceAll('$ACTION', this.agent.actions.currentActionLabel); } if (prompt.includes('$COMMAND_DOCS')) - prompt = prompt.replaceAll('$COMMAND_DOCS', getCommandDocs()); + prompt = prompt.replaceAll('$COMMAND_DOCS', getCommandDocs(this.agent.blocked_actions)); if (prompt.includes('$CODE_DOCS')) prompt = prompt.replaceAll('$CODE_DOCS', getSkillDocs()); if (prompt.includes('$EXAMPLES') && examples !== null) diff --git a/src/process/agent-process.js b/src/process/agent-process.js index 683624c..b3f67fb 100644 --- a/src/process/agent-process.js +++ b/src/process/agent-process.js @@ -26,19 +26,9 @@ export class AgentProcess { agentProcess.on('exit', (code, signal) => { console.log(`Agent process exited with code ${code} and signal ${signal}`); - if (code === 2) { - console.log(`Task completed successfully`); - process.exit(2, signal); - } - - if (code === 3) { - console.log(`Task failed due to reaching timeout`); - process.exit(3); - } - - if (code === 4) { - console.log(`Task failed as all agents weren't correctly spawned `); - process.exit(4); + if (code > 1) { + console.log(`Ending task`); + process.exit(code); } if (code !== 0) { diff --git a/src/utils/tasks.js b/src/utils/tasks.js index 5a5ece2..0b3377c 100644 --- a/src/utils/tasks.js +++ b/src/utils/tasks.js @@ -1,6 +1,7 @@ import yaml from 'js-yaml' import { readFileSync } from 'fs'; -import {getPosition} from './library/world.js' +import { executeCommand } from './commands/index.js'; +import { getPosition } from './library/world.js' export function loadTask(taskId) { try { @@ -19,17 +20,18 @@ export function loadTask(taskId) { } } -export async function initBotTask(bot, task, name) { - if (task) { - bot.chat(`/clear ${bot.username}`); - console.log(`Cleared ${bot.username}'s inventory.`); - } +export async function initBotTask(agent) { + let bot = agent.bot; + let task = agent.task; + + bot.chat(`/clear ${bot.username}`); + console.log(`Cleared ${bot.username}'s inventory.`); //wait for a bit so inventory is cleared await new Promise((resolve) => setTimeout(resolve, 500)); - console.log(task && "agent_number" in task && task.agent_number > 1); - if (task && "agent_number" in task && task.agent_number > 1) { + console.log("agent_number" in task.agent_number > 1); + if ("agent_number" in task.agent_number > 1) { var initial_inventory = task.initial_inventory[bot.username]; console.log("Initial inventory:", initial_inventory); } else if (task) { @@ -37,7 +39,7 @@ export async function initBotTask(bot, task, name) { var initial_inventory = task.initial_inventory; } - if (task && "initial_inventory" in task) { + if ("initial_inventory" in task) { console.log("Setting inventory..."); console.log("Inventory to set:", initial_inventory); for (let key of Object.keys(initial_inventory)) { @@ -70,7 +72,7 @@ export async function initBotTask(bot, task, name) { // teleport near a human player if found by default - if (task && "agent_number" in task) { + if ("agent_number" in task) { var agent_names = task.agent_names; if (human_player_name) { console.log(`Teleporting ${bot.username} to human ${human_player_name}`) @@ -103,7 +105,7 @@ export async function initBotTask(bot, task, name) { This was done by MaxRobinson in one of the youtube videos. */ - if (task && task.type !== 'construction') { + if (task.type !== 'construction') { const pos = getPosition(bot); const xOffset = getRandomOffset(5); const zOffset = getRandomOffset(5); @@ -111,7 +113,22 @@ export async function initBotTask(bot, task, name) { await new Promise((resolve) => setTimeout(resolve, 200)); } + if (task.goal) { + await executeCommand(agent, `!goal("${task.goal}")`); + } + if (task.agent_names) { + await new Promise((resolve) => setTimeout(resolve, 10000)); + if (task.agent_names.filter(name => !bot.players[name]).length) { + console.log(`Missing players/bots: ${missingPlayers.join(', ')}`); + agent.cleanKill('Not all required players/bots are present in the world. Exiting.', 4); + } + } + + if (task.conversation && agent.count_id === 0) { + let other_name = task.agent_names.filter(name => name !== bot.username)[0]; + await executeCommand(agent, `!startConversation("${other_name}", "${task.conversation}")`); + } } export class TechTreeHarvestValidator {