From 6f2bf41e6e088cf176afd0a2f761a91213eab7e2 Mon Sep 17 00:00:00 2001 From: Maximus Date: Mon, 2 Jun 2025 13:47:07 -0600 Subject: [PATCH 1/7] initial refactor --- api.js | 43 +++++++++++++++++++ main.js | 8 ++-- mindcraft.js | 32 ++++++++++++++ settings.js | 25 ++++++----- src/agent/settings.js | 0 src/process/agent_process.js | 4 +- .../{main_proxy.js => mindserver_proxy.js} | 16 +++---- src/server/{mind_server.js => mindserver.js} | 5 +++ 8 files changed, 109 insertions(+), 24 deletions(-) create mode 100644 api.js create mode 100644 mindcraft.js create mode 100644 src/agent/settings.js rename src/process/{main_proxy.js => mindserver_proxy.js} (81%) rename src/server/{mind_server.js => mindserver.js} (96%) diff --git a/api.js b/api.js new file mode 100644 index 0000000..ddd3ea1 --- /dev/null +++ b/api.js @@ -0,0 +1,43 @@ +import * as Mindcraft from './mindcraft.js'; + +await Mindcraft.init('localhost', 8080); // starts server locally +await Mindcraft.connect('ip', 'port') // connects to remote server +// ^ must do one of these before calling anything else + +Mindcraft.addWorld( + { + name: 'test', + minecraft_version: "1.21.1", + host: 'localhost', + port: 55916, + auth: 'offline', + + render_bot_views: false, // show bot's view in browser at localhost:3000, 3001... + allow_insecure_coding: true, // allows newAction command and model can write/run code on server. enable at own risk + code_timeout_mins: -1, // minutes code is allowed to run. -1 for no timeout + verbose_commands: true, // show full command syntax + chat_bot_messages: true, // publicly chat bot-to-bot messages + } +) +// add world for easy reuse. not super necessary, easy for user to copy world def object around. remove? + + +Mindcraft.addAgent( + { + world: 'test', + world: { + minecraft_version: '', + host: '', + port: '', + auth: 'offline' + }, + profile: './profiles/test.json', + // profile: { + // name: 'test', + // prompt: 'test', + // }, + task: './tasks/test.json' + } +) + +Mindcraft.removeAgent() \ No newline at end of file diff --git a/main.js b/main.js index 521aadf..07d3808 100644 --- a/main.js +++ b/main.js @@ -2,8 +2,8 @@ import { AgentProcess } from './src/process/agent_process.js'; import settings from './settings.js'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; -import { createMindServer } from './src/server/mind_server.js'; -import { mainProxy } from './src/process/main_proxy.js'; +import { createMindServer } from './src/server/mindserver.js'; +import { mindserverProxy } from './src/process/mindserver_proxy.js.js'; import { readFileSync } from 'fs'; function parseArguments() { @@ -33,7 +33,7 @@ async function main() { if (settings.host_mindserver) { const mindServer = createMindServer(settings.mindserver_port); } - mainProxy.connect(); + mindserverProxy.connect(); const args = parseArguments(); const profiles = getProfiles(args); @@ -44,7 +44,7 @@ async function main() { const agent_process = new AgentProcess(); const profile = readFileSync(profiles[i], 'utf8'); const agent_json = JSON.parse(profile); - mainProxy.registerAgent(agent_json.name, agent_process); + mindserverProxy.registerAgent(agent_json.name, agent_process); agent_process.start(profiles[i], load_memory, init_message, i, args.task_path, args.task_id); await new Promise(resolve => setTimeout(resolve, 1000)); } diff --git a/mindcraft.js b/mindcraft.js new file mode 100644 index 0000000..e4fae5b --- /dev/null +++ b/mindcraft.js @@ -0,0 +1,32 @@ +import { AgentProcess } from './src/process/agent_process.js'; +import { createMindServer } from './src/server/mindserver.js'; +import { mindserverProxy } from './src/process/mindserver_proxy.js.js'; +import { readFileSync } from 'fs'; + +let mindserver; +let connected = false; + +export async function init(host='localhost', port=8080) { + if (connected) { + console.error('Already initiliazed!'); + return; + } + mindserver = createMindServer(host, port); + mindserverProxy.connect(host, port); + connected = true; +} + +export async function connect() { + if (connected) { + console.error('Already connected!'); + return; + } +} + +export function addWorld(settings) { + +} + +export async function addAgent(settings) { + +} \ No newline at end of file diff --git a/settings.js b/settings.js index b782097..1d6c5a2 100644 --- a/settings.js +++ b/settings.js @@ -10,11 +10,10 @@ const settings = { "mindserver_port": 8080, // the base profile is shared by all bots for default prompts/examples/modes - "base_profile": "./profiles/defaults/survival.json", // also see creative.json, god_mode.json "profiles": [ - "./andy.json", + // "./andy.json", // "./profiles/gpt.json", - // "./profiles/claude.json", + "./profiles/claude.json", // "./profiles/gemini.json", // "./profiles/llama.json", // "./profiles/qwen.json", @@ -25,26 +24,32 @@ const settings = { // using more than 1 profile requires you to /msg each bot indivually // individual profiles override values from the base profile ], + + // agent settings + "base_profile": "./profiles/defaults/god_mode.json", // also see creative.json, god_mode.json "load_memory": false, // load memory from previous session "init_message": "Respond with hello world and your name", // sends to all on spawn "only_chat_with": [], // users that the bots listen to and send general messages to. if empty it will chat publicly "speak": false, // allows all bots to speak through system text-to-speech. works on windows, mac, on linux you need to `apt install espeak` "language": "en", // translate to/from this language. Supports these language names: https://cloud.google.com/translate/docs/languages - "show_bot_views": false, // show bot's view in browser at localhost:3000, 3001... - - "allow_insecure_coding": false, // allows newAction command and model can write/run code on your computer. enable at own risk "allow_vision": false, // allows vision model to interpret screenshots as inputs "blocked_actions" : ["!checkBlueprint", "!checkBlueprintLevel", "!getBlueprint", "!getBlueprintLevel"] , // commands to disable and remove from docs. Ex: ["!setMode"] - "code_timeout_mins": -1, // minutes code is allowed to run. -1 for no timeout "relevant_docs_count": 5, // number of relevant code function docs to select for prompting. -1 for all - "max_messages": 15, // max number of messages to keep in context "num_examples": 2, // number of examples to give to the model "max_commands": -1, // max number of commands that can be used in consecutive responses. -1 for no limit - "verbose_commands": true, // show full command syntax "narrate_behavior": true, // chat simple automatic actions ('Picking up item!') - "chat_bot_messages": true, // publicly chat messages to other bots "log_all_prompts": false, // log ALL prompts to file + "task": {}, + "task_file": "", + "task_name": "", + "verbose_commands": true, // show full command syntax + "chat_bot_messages": true, // publicly chat bot-to-bot messages + + // mindserver settings + "render_bot_views": false, // show bot's view in browser at localhost:3000, 3001... + "allow_insecure_coding": true, // allows newAction command and model can write/run code on your computer. enable at own risk + "code_timeout_mins": -1, // minutes code is allowed to run. -1 for no timeout } // these environment variables override certain settings diff --git a/src/agent/settings.js b/src/agent/settings.js new file mode 100644 index 0000000..e69de29 diff --git a/src/process/agent_process.js b/src/process/agent_process.js index 7418d31..603f348 100644 --- a/src/process/agent_process.js +++ b/src/process/agent_process.js @@ -1,5 +1,5 @@ import { spawn } from 'child_process'; -import { mainProxy } from './main_proxy.js'; +import { mindserverProxy } from './mindserver_proxy.js.js'; export class AgentProcess { start(profile, load_memory=false, init_message=null, count_id=0, task_path=null, task_id=null) { @@ -28,7 +28,7 @@ export class AgentProcess { agentProcess.on('exit', (code, signal) => { console.log(`Agent process exited with code ${code} and signal ${signal}`); this.running = false; - mainProxy.logoutAgent(this.name); + mindserverProxy.logoutAgent(this.name); if (code > 1) { console.log(`Ending task`); diff --git a/src/process/main_proxy.js b/src/process/mindserver_proxy.js similarity index 81% rename from src/process/main_proxy.js rename to src/process/mindserver_proxy.js index 8336458..d2d4a97 100644 --- a/src/process/main_proxy.js +++ b/src/process/mindserver_proxy.js @@ -1,23 +1,23 @@ import { io } from 'socket.io-client'; -import settings from '../../settings.js'; // Singleton mindserver proxy for the main process -class MainProxy { +// recieves commands from mindserver +class MindserverProxy { constructor() { - if (MainProxy.instance) { - return MainProxy.instance; + if (MindserverProxy.instance) { + return MindserverProxy.instance; } this.socket = null; this.connected = false; this.agent_processes = {}; - MainProxy.instance = this; + MindserverProxy.instance = this; } - connect() { + connect(host, port) { if (this.connected) return; - this.socket = io(`http://${settings.mindserver_host}:${settings.mindserver_port}`); + this.socket = io(`http://${host}:${port}`); this.connected = true; this.socket.on('stop-agent', (agentName) => { @@ -61,4 +61,4 @@ class MainProxy { } } -export const mainProxy = new MainProxy(); \ No newline at end of file +export const mindserverProxy = new MindserverProxy(); \ No newline at end of file diff --git a/src/server/mind_server.js b/src/server/mindserver.js similarity index 96% rename from src/server/mind_server.js rename to src/server/mindserver.js index eed71d7..ac54db3 100644 --- a/src/server/mind_server.js +++ b/src/server/mindserver.js @@ -4,6 +4,11 @@ import http from 'http'; import path from 'path'; import { fileURLToPath } from 'url'; +// Mindserver purposes: +// - central hub for inter-process communication between all agent processes +// - api to control from other languages and remote users +// - host for webapp + // Module-level variables let io; let server; From 8162fc1ab183c9756f7d142e0d9b32630d8280aa Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Tue, 10 Jun 2025 17:52:30 -0500 Subject: [PATCH 2/7] major refactor: use mindserver to init settings --- api.js | 120 ++++++++++++++++++++-------- main.js | 2 +- mindcraft.js | 56 ++++++++++--- src/agent/agent.js | 36 ++++----- src/agent/agent_proxy.js | 73 ----------------- src/agent/coder.js | 4 +- src/agent/commands/actions.js | 4 +- src/agent/conversation.js | 7 +- src/agent/history.js | 2 +- src/agent/library/lockdown.js | 26 +++--- src/agent/mindserver_proxy.js | 107 +++++++++++++++++++++++++ src/agent/modes.js | 2 +- src/agent/settings.js | 7 ++ src/agent/tasks/tasks.js | 46 +++-------- src/agent/vision/browser_viewer.js | 4 +- src/models/prompter.js | 15 +++- src/process/agent_process.js | 25 +++--- src/process/init_agent.js | 48 +++++------ src/process/mindserver_proxy.js | 64 --------------- src/server/mindserver.js | 123 +++++++++++++---------------- src/utils/mcdata.js | 18 +++-- src/utils/translator.js | 10 ++- 22 files changed, 423 insertions(+), 376 deletions(-) delete mode 100644 src/agent/agent_proxy.js create mode 100644 src/agent/mindserver_proxy.js delete mode 100644 src/process/mindserver_proxy.js diff --git a/api.js b/api.js index ddd3ea1..25322c4 100644 --- a/api.js +++ b/api.js @@ -1,43 +1,101 @@ import * as Mindcraft from './mindcraft.js'; +import { readFileSync } from 'fs'; + await Mindcraft.init('localhost', 8080); // starts server locally -await Mindcraft.connect('ip', 'port') // connects to remote server +// await Mindcraft.connect('ip', 'port') // connects to remote server // ^ must do one of these before calling anything else -Mindcraft.addWorld( +let profile = JSON.parse(readFileSync('./profiles/gemini.json', 'utf8')); + +Mindcraft.createAgent( { - name: 'test', - minecraft_version: "1.21.1", - host: 'localhost', - port: 55916, - auth: 'offline', - - render_bot_views: false, // show bot's view in browser at localhost:3000, 3001... - allow_insecure_coding: true, // allows newAction command and model can write/run code on server. enable at own risk - code_timeout_mins: -1, // minutes code is allowed to run. -1 for no timeout - verbose_commands: true, // show full command syntax - chat_bot_messages: true, // publicly chat bot-to-bot messages - } -) -// add world for easy reuse. not super necessary, easy for user to copy world def object around. remove? - - -Mindcraft.addAgent( - { - world: 'test', world: { - minecraft_version: '', - host: '', - port: '', - auth: 'offline' + "minecraft_version": "1.21.1", // supports up to 1.21.1 + "host": "127.0.0.1", // or "localhost", "your.ip.address.here" + "port": 55916, + "auth": "offline", // or "microsoft" }, - profile: './profiles/test.json', - // profile: { - // name: 'test', - // prompt: 'test', + profile, + "base_profile": "survival", // survival | creative | god_mode + "load_memory": false, // load memory from previous session + "init_message": "Respond with hello world and your name", // sends to all on spawn + "only_chat_with": [], // users that the bots listen to and send general messages to. if empty it will chat publicly + "speak": false, // allows all bots to speak through system text-to-speech. works on windows, mac, on linux you need to `apt install espeak` + "language": "en", // translate to/from this language. Supports these language names: https://cloud.google.com/translate/docs/languages + "allow_vision": false, // allows vision model to interpret screenshots as inputs + "blocked_actions" : ["!checkBlueprint", "!checkBlueprintLevel", "!getBlueprint", "!getBlueprintLevel"] , // commands to disable and remove from docs. Ex: ["!setMode"] + "relevant_docs_count": 5, // number of relevant code function docs to select for prompting. -1 for all + "max_messages": 15, // max number of messages to keep in context + "num_examples": 2, // number of examples to give to the model + "max_commands": -1, // max number of commands that can be used in consecutive responses. -1 for no limit + "narrate_behavior": true, // chat simple automatic actions ('Picking up item!') + "log_all_prompts": false, // log ALL prompts to file + // "task": { + // "task_id": "multiagent_crafting_pink_wool_full_plan__depth_0", + // "goal": "Collaborate with other agents to craft an pink_wool", + // "conversation": "Let's work together to craft an pink_wool.", + // "initial_inventory": { + // "0": { + // "pink_dye": 1 + // } + // }, + // "agent_count": 1, + // "target": "pink_wool", + // "number_of_target": 1, + // "type": "techtree", + // "max_depth": 1, + // "depth": 0, + // "timeout": 300, + // "blocked_actions": { + // "0": [], + // }, + // "missing_items": [], + // "requires_ctable": false // }, - task: './tasks/test.json' + "verbose_commands": true, // show full command syntax + "chat_bot_messages": true, // publicly chat bot-to-bot messages + + // mindserver settings + "render_bot_view": false, // show bot's view in browser at localhost:3000, 3001... + "allow_insecure_coding": true, // allows newAction command and model can write/run code on your computer. enable at own risk + "code_timeout_mins": -1, // minutes code is allowed to run. -1 for no timeout } ) -Mindcraft.removeAgent() \ No newline at end of file +// profile = JSON.parse(readFileSync('./andy.json', 'utf8')); + +// Mindcraft.createAgent( +// { +// world: { +// "minecraft_version": "1.21.1", // supports up to 1.21.1 +// "host": "127.0.0.1", // or "localhost", "your.ip.address.here" +// "port": 55916, +// "auth": "offline", // or "microsoft" +// }, +// profile, +// "base_profile": "survival", // also see creative.json, god_mode.json +// "load_memory": false, // load memory from previous session +// "init_message": "Respond with hello world and your name", // sends to all on spawn +// "only_chat_with": [], // users that the bots listen to and send general messages to. if empty it will chat publicly +// "speak": false, // allows all bots to speak through system text-to-speech. works on windows, mac, on linux you need to `apt install espeak` +// "language": "en", // translate to/from this language. Supports these language names: https://cloud.google.com/translate/docs/languages +// "allow_vision": false, // allows vision model to interpret screenshots as inputs +// "blocked_actions" : ["!checkBlueprint", "!checkBlueprintLevel", "!getBlueprint", "!getBlueprintLevel"] , // commands to disable and remove from docs. Ex: ["!setMode"] +// "relevant_docs_count": 5, // number of relevant code function docs to select for prompting. -1 for all +// "max_messages": 15, // max number of messages to keep in context +// "num_examples": 2, // number of examples to give to the model +// "max_commands": -1, // max number of commands that can be used in consecutive responses. -1 for no limit +// "narrate_behavior": true, // chat simple automatic actions ('Picking up item!') +// "log_all_prompts": false, // log ALL prompts to file +// "task_file": "", +// "task_name": "", +// "verbose_commands": true, // show full command syntax +// "chat_bot_messages": true, // publicly chat bot-to-bot messages + +// // mindserver settings +// "render_bot_view": false, // show bot's view in browser at localhost:3000, 3001... +// "allow_insecure_coding": true, // allows newAction command and model can write/run code on your computer. enable at own risk +// "code_timeout_mins": -1, // minutes code is allowed to run. -1 for no timeout +// } +// ) \ No newline at end of file diff --git a/main.js b/main.js index 07d3808..c5e3685 100644 --- a/main.js +++ b/main.js @@ -3,7 +3,7 @@ import settings from './settings.js'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import { createMindServer } from './src/server/mindserver.js'; -import { mindserverProxy } from './src/process/mindserver_proxy.js.js'; +import { mindserverProxy } from './src/process/mindserver_proxy.js'; import { readFileSync } from 'fs'; function parseArguments() { diff --git a/mindcraft.js b/mindcraft.js index e4fae5b..6ec88aa 100644 --- a/mindcraft.js +++ b/mindcraft.js @@ -1,10 +1,12 @@ +import { createMindServer, registerAgent } from './src/server/mindserver.js'; import { AgentProcess } from './src/process/agent_process.js'; -import { createMindServer } from './src/server/mindserver.js'; -import { mindserverProxy } from './src/process/mindserver_proxy.js.js'; -import { readFileSync } from 'fs'; let mindserver; let connected = false; +let agent_processes = {}; +let agent_count = 0; +let host = 'localhost'; +let port = 8080; export async function init(host='localhost', port=8080) { if (connected) { @@ -12,21 +14,55 @@ export async function init(host='localhost', port=8080) { return; } mindserver = createMindServer(host, port); - mindserverProxy.connect(host, port); + host = host; + port = port; connected = true; } -export async function connect() { - if (connected) { - console.error('Already connected!'); +export async function createAgent(settings) { + if (!settings.profile.name) { + console.error('Agent name is required in profile'); return; } + let agent_name = settings.profile.name; + registerAgent(settings); + let load_memory = settings.load_memory || false; + let init_message = settings.init_message || null; + const agentProcess = new AgentProcess(agent_name); + agentProcess.start(load_memory, init_message, agent_count, host, port); + agent_count++; + agent_processes[settings.profile.name] = agentProcess; +} + +export function getAgentProcess(agentName) { + return agent_processes[agentName]; +} + +export function startAgent(agentName) { + if (this.agent_processes[agentName]) { + this.agent_processes[agentName].continue(); + } + else { + console.error(`Cannot start agent ${agentName}; not found`); + } } -export function addWorld(settings) { - +export function stopAgent(agentName) { + if (this.agent_processes[agentName]) { + this.agent_processes[agentName].stop(); + } } -export async function addAgent(settings) { +export function shutdown() { + console.log('Shutting down'); + for (let agentName in this.agent_processes) { + this.agent_processes[agentName].stop(); + } + setTimeout(() => { + process.exit(0); + }, 2000); +} +export function logoutAgent(agentName) { + this.socket.emit('logout-agent', agentName); } \ No newline at end of file diff --git a/src/agent/agent.js b/src/agent/agent.js index 3cd671b..d490557 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -12,26 +12,21 @@ import { SelfPrompter } from './self_prompter.js'; import convoManager from './conversation.js'; import { handleTranslation, handleEnglishTranslation } from '../utils/translator.js'; import { addBrowserViewer } from './vision/browser_viewer.js'; -import settings from '../../settings.js'; -import { serverProxy } from './agent_proxy.js'; +import { serverProxy } from './mindserver_proxy.js'; +import settings from './settings.js'; import { Task } from './tasks/tasks.js'; import { say } from './speak.js'; export class Agent { - async start(profile_fp, load_mem=false, init_message=null, count_id=0, task_path=null, task_id=null) { + async start(load_mem=false, init_message=null, count_id=0) { this.last_sender = null; this.count_id = count_id; - if (!profile_fp) { - throw new Error('No profile filepath provided'); - } - - console.log('Starting agent initialization with profile:', profile_fp); // Initialize components with more detailed error handling console.log('Initializing action manager...'); this.actions = new ActionManager(this); console.log('Initializing prompter...'); - this.prompter = new Prompter(this, profile_fp); + this.prompter = new Prompter(this, settings.profile); this.name = this.prompter.getName(); console.log('Initializing history...'); this.history = new History(this); @@ -59,19 +54,15 @@ export class Agent { } else { taskStart = Date.now(); } - this.task = new Task(this, task_path, task_id, taskStart); + this.task = new Task(this, settings.task, taskStart); this.blocked_actions = settings.blocked_actions.concat(this.task.blocked_actions || []); blacklistCommands(this.blocked_actions); - serverProxy.connect(this); - console.log(this.name, 'logging into minecraft...'); this.bot = initBot(this.name); initModes(this); - - this.bot.on('login', () => { console.log(this.name, 'logged in!'); serverProxy.login(); @@ -90,6 +81,8 @@ export class Agent { try { clearTimeout(spawnTimeout); addBrowserViewer(this.bot, count_id); + console.log('Initializing vision intepreter...'); + this.vision_interpreter = new VisionInterpreter(this, settings.allow_vision); // wait for a bit so stats are not undefined await new Promise((resolve) => setTimeout(resolve, 1000)); @@ -101,22 +94,19 @@ export class Agent { this.startEvents(); if (!load_mem) { - if (task_path !== null) { + if (settings.task) { this.task.initBotTask(); this.task.setAgentGoal(); } } else { // set the goal without initializing the rest of the task - if (task_path !== null) { + if (settings.task) { this.task.setAgentGoal(); } } await new Promise((resolve) => setTimeout(resolve, 10000)); this.checkAllPlayersPresent(); - - console.log('Initializing vision intepreter...'); - this.vision_interpreter = new VisionInterpreter(this, settings.allow_vision); } catch (error) { console.error('Error in spawn event:', error); @@ -160,8 +150,12 @@ export class Agent { this.respondFunc = respondFunc; this.bot.on('whisper', respondFunc); - if (settings.profiles.length === 1) - this.bot.on('chat', respondFunc); + + this.bot.on('chat', (username, message) => { + if (serverProxy.getNumOtherAgents() > 0) return; + // only respond to open chat messages when there are no other agents + respondFunc(username, message); + }); // Set up auto-eat this.bot.autoEat.options = { diff --git a/src/agent/agent_proxy.js b/src/agent/agent_proxy.js deleted file mode 100644 index b0333b1..0000000 --- a/src/agent/agent_proxy.js +++ /dev/null @@ -1,73 +0,0 @@ -import { io } from 'socket.io-client'; -import convoManager from './conversation.js'; -import settings from '../../settings.js'; - -class AgentServerProxy { - constructor() { - if (AgentServerProxy.instance) { - return AgentServerProxy.instance; - } - - this.socket = null; - this.connected = false; - AgentServerProxy.instance = this; - } - - connect(agent) { - if (this.connected) return; - - this.agent = agent; - - this.socket = io(`http://${settings.mindserver_host}:${settings.mindserver_port}`); - this.connected = true; - - this.socket.on('connect', () => { - console.log('Connected to MindServer'); - }); - - this.socket.on('disconnect', () => { - console.log('Disconnected from MindServer'); - this.connected = false; - }); - - this.socket.on('chat-message', (agentName, json) => { - convoManager.receiveFromBot(agentName, json); - }); - - this.socket.on('agents-update', (agents) => { - convoManager.updateAgents(agents); - }); - - this.socket.on('restart-agent', (agentName) => { - console.log(`Restarting agent: ${agentName}`); - this.agent.cleanKill(); - }); - - this.socket.on('send-message', (agentName, message) => { - try { - this.agent.respondFunc("NO USERNAME", message); - } catch (error) { - console.error('Error: ', JSON.stringify(error, Object.getOwnPropertyNames(error))); - } - }); - } - - login() { - this.socket.emit('login-agent', this.agent.name); - } - - shutdown() { - this.socket.emit('shutdown'); - } - - getSocket() { - return this.socket; - } -} - -// Create and export a singleton instance -export const serverProxy = new AgentServerProxy(); - -export function sendBotChatToServer(agentName, json) { - serverProxy.getSocket().emit('chat-message', agentName, json); -} diff --git a/src/agent/coder.js b/src/agent/coder.js index 956c8fe..18a5f26 100644 --- a/src/agent/coder.js +++ b/src/agent/coder.js @@ -1,6 +1,5 @@ import { writeFile, readFile, mkdirSync } from 'fs'; -import settings from '../../settings.js'; -import { makeCompartment } from './library/lockdown.js'; +import { makeCompartment, lockdown } from './library/lockdown.js'; import * as skills from './library/skills.js'; import * as world from './library/world.js'; import { Vec3 } from 'vec3'; @@ -27,6 +26,7 @@ export class Coder { async generateCode(agent_history) { this.agent.bot.modes.pause('unstuck'); + lockdown(); // this message history is transient and only maintained in this function let messages = agent_history.getHistory(); messages.push({role: 'system', content: 'Code generation started. Write code in codeblock in your response:'}); diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index b2b3ccb..e321764 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -1,5 +1,5 @@ import * as skills from '../library/skills.js'; -import settings from '../../../settings.js'; +import settings from '../settings.js'; import convoManager from '../conversation.js'; @@ -46,7 +46,7 @@ export const actionsList = [ result = 'Error generating code: ' + e.toString(); } }; - await agent.actions.runAction('action:newAction', actionFn); + await agent.actions.runAction('action:newAction', actionFn, {timeout: settings.code_timeout_mins}); return result; } }, diff --git a/src/agent/conversation.js b/src/agent/conversation.js index 41c6888..1cd781e 100644 --- a/src/agent/conversation.js +++ b/src/agent/conversation.js @@ -1,10 +1,9 @@ -import settings from '../../settings.js'; -import { readFileSync } from 'fs'; +import settings from './settings.js'; import { containsCommand } from './commands/index.js'; -import { sendBotChatToServer } from './agent_proxy.js'; +import { sendBotChatToServer } from './mindserver_proxy.js'; let agent; -let agent_names = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name); +let agent_names = []; let agents_in_game = []; class Conversation { diff --git a/src/agent/history.js b/src/agent/history.js index 13b9c79..04a72f7 100644 --- a/src/agent/history.js +++ b/src/agent/history.js @@ -1,6 +1,6 @@ import { writeFileSync, readFileSync, mkdirSync, existsSync } from 'fs'; import { NPCData } from './npc/data.js'; -import settings from '../../settings.js'; +import settings from './settings.js'; export class History { diff --git a/src/agent/library/lockdown.js b/src/agent/library/lockdown.js index 2d8f79d..2db7e3f 100644 --- a/src/agent/library/lockdown.js +++ b/src/agent/library/lockdown.js @@ -4,16 +4,22 @@ import 'ses'; // We disable some of the taming to allow for more flexibility // For configuration, see https://github.com/endojs/endo/blob/master/packages/ses/docs/lockdown.md -lockdown({ - // basic devex and quality of life improvements - localeTaming: 'unsafe', - consoleTaming: 'unsafe', - errorTaming: 'unsafe', - stackFiltering: 'verbose', - // allow eval outside of created compartments - // (mineflayer dep "protodef" uses eval) - evalTaming: 'unsafeEval', -}); + +let lockeddown = false; +export function lockdown() { + if (lockeddown) return; + lockeddown = true; + lockdown({ + // basic devex and quality of life improvements + localeTaming: 'unsafe', + consoleTaming: 'unsafe', + errorTaming: 'unsafe', + stackFiltering: 'verbose', + // allow eval outside of created compartments + // (mineflayer dep "protodef" uses eval) + evalTaming: 'unsafeEval', + }); +} export const makeCompartment = (endowments = {}) => { return new Compartment({ diff --git a/src/agent/mindserver_proxy.js b/src/agent/mindserver_proxy.js new file mode 100644 index 0000000..59f043a --- /dev/null +++ b/src/agent/mindserver_proxy.js @@ -0,0 +1,107 @@ +import { io } from 'socket.io-client'; +import convoManager from './conversation.js'; +import { setSettings } from './settings.js'; + +class MindServerProxy { + constructor() { + if (MindServerProxy.instance) { + return MindServerProxy.instance; + } + + this.socket = null; + this.connected = false; + this.agents = []; + MindServerProxy.instance = this; + } + + async connect(name, host, port) { + if (this.connected) return; + + this.name = name; + + this.socket = io(`http://${host}:${port}`); + this.connected = true; + + this.socket.on('connect', () => { + console.log(name, 'connected to MindServer'); + }); + + this.socket.on('disconnect', () => { + console.log('Disconnected from MindServer'); + this.connected = false; + }); + + this.socket.on('chat-message', (agentName, json) => { + convoManager.receiveFromBot(agentName, json); + }); + + this.socket.on('agents-update', (agents) => { + this.agents = agents; + convoManager.updateAgents(agents); + if (this.agent?.task) { + console.log(this.agent.name, 'updating available agents'); + this.agent.task.updateAvailableAgents(agents); + } + }); + + this.socket.on('restart-agent', (agentName) => { + console.log(`Restarting agent: ${agentName}`); + this.agent.cleanKill(); + }); + + this.socket.on('send-message', (agentName, message) => { + try { + this.agent.respondFunc("NO USERNAME", message); + } catch (error) { + console.error('Error: ', JSON.stringify(error, Object.getOwnPropertyNames(error))); + } + }); + + // Request settings and wait for response + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error('Settings request timed out after 10 seconds')); + }, 10000); + + this.socket.emit('get-settings', name, (response) => { + clearTimeout(timeout); + if (response.error) { + return reject(new Error(response.error)); + } + setSettings(response.settings); + resolve(); + }); + }); + } + + setAgent(agent) { + this.agent = agent; + } + + getAgents() { + return this.agents; + } + + getNumOtherAgents() { + return this.agents.length - 1; + } + + login() { + this.socket.emit('login-agent', this.agent.name); + } + + shutdown() { + this.socket.emit('shutdown'); + } + + getSocket() { + return this.socket; + } +} + +// Create and export a singleton instance +export const serverProxy = new MindServerProxy(); + +export function sendBotChatToServer(agentName, json) { + serverProxy.getSocket().emit('chat-message', agentName, json); +} diff --git a/src/agent/modes.js b/src/agent/modes.js index 69b2f06..dc2b925 100644 --- a/src/agent/modes.js +++ b/src/agent/modes.js @@ -1,7 +1,7 @@ import * as skills from './library/skills.js'; import * as world from './library/world.js'; import * as mc from '../utils/mcdata.js'; -import settings from '../../settings.js' +import settings from './settings.js' import convoManager from './conversation.js'; async function say(agent, message) { diff --git a/src/agent/settings.js b/src/agent/settings.js index e69de29..e9fd133 100644 --- a/src/agent/settings.js +++ b/src/agent/settings.js @@ -0,0 +1,7 @@ +// extremely lightweight obj that can be imported/modified by any file +let settings = {}; +export default settings; +export function setSettings(new_settings) { + Object.keys(settings).forEach(key => delete settings[key]); + Object.assign(settings, new_settings); +} diff --git a/src/agent/tasks/tasks.js b/src/agent/tasks/tasks.js index 1c00d95..2234d59 100644 --- a/src/agent/tasks/tasks.js +++ b/src/agent/tasks/tasks.js @@ -1,7 +1,6 @@ import { readFileSync , writeFileSync, existsSync} from 'fs'; import { executeCommand } from '../commands/index.js'; import { getPosition } from '../library/world.js'; -import settings from '../../../settings.js'; import { ConstructionTaskValidator, Blueprint } from './construction_tasks.js'; import { CookingTaskInitiator } from './cooking_tasks.js'; @@ -233,27 +232,26 @@ class CookingCraftingTaskValidator { } export class Task { - constructor(agent, task_path, task_id, taskStartTime = null) { + constructor(agent, task_data, taskStartTime = null) { this.agent = agent; this.data = null; if (taskStartTime !== null) this.taskStartTime = taskStartTime; else this.taskStartTime = Date.now(); - console.log("Task start time set to", this.taskStartTime); this.validator = null; this.reset_function = null; this.blocked_actions = []; - this.task_id = task_id; - - if (task_path && task_id) { - console.log('Starting task', task_id); - if (task_id.endsWith('hells_kitchen')) { + this.task_data = task_data; + if (task_data) { + console.log('Starting task', task_data.task_id); + console.log("Task start time set to", this.taskStartTime); + if (task_data.task_id.endsWith('hells_kitchen')) { // Reset hells_kitchen progress when a new task starts - hellsKitchenProgressManager.resetTask(task_id); + hellsKitchenProgressManager.resetTask(task_data.task_id); console.log('Reset Hells Kitchen progress for new task'); } - this.data = this.loadTask(task_path, task_id); + this.data = task_data; this.task_type = this.data.type; if (this.task_type === 'construction' && this.data.blueprint) { this.blueprint = new Blueprint(this.data.blueprint); @@ -300,7 +298,11 @@ export class Task { } this.name = this.agent.name; - this.available_agents = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name); + this.available_agents = [] + } + + updateAvailableAgents(agents) { + this.available_agents = agents } // Add this method if you want to manually reset the hells_kitchen progress @@ -360,28 +362,6 @@ export class Task { return null; } - loadTask(task_path, task_id) { - try { - const tasksFile = readFileSync(task_path, 'utf8'); - const tasks = JSON.parse(tasksFile); - let task = tasks[task_id]; - task['task_id'] = task_id; - console.log(task); - console.log(this.agent.count_id); - if (!task) { - throw new Error(`Task ${task_id} not found`); - } - // if ((!task.agent_count || task.agent_count <= 1) && this.agent.count_id > 0) { - // task = null; - // } - - return task; - } catch (error) { - console.error('Error loading task:', error); - process.exit(1); - } - } - isDone() { let res = null; if (this.validator) diff --git a/src/agent/vision/browser_viewer.js b/src/agent/vision/browser_viewer.js index 9ae7c7b..6cce3ed 100644 --- a/src/agent/vision/browser_viewer.js +++ b/src/agent/vision/browser_viewer.js @@ -1,8 +1,8 @@ -import settings from '../../../settings.js'; +import settings from '../settings.js'; import prismarineViewer from 'prismarine-viewer'; const mineflayerViewer = prismarineViewer.mineflayer; export function addBrowserViewer(bot, count_id) { - if (settings.show_bot_views) + if (settings.render_bot_view) mineflayerViewer(bot, { port: 3000+count_id, firstPerson: true, }); } \ No newline at end of file diff --git a/src/models/prompter.js b/src/models/prompter.js index e05f5a8..ebdf521 100644 --- a/src/models/prompter.js +++ b/src/models/prompter.js @@ -4,7 +4,7 @@ import { getCommandDocs } from '../agent/commands/index.js'; import { SkillLibrary } from "../agent/library/skill_library.js"; import { stringifyTurns } from '../utils/text.js'; import { getCommand } from '../agent/commands/index.js'; -import settings from '../../settings.js'; +import settings from '../agent/settings.js'; import { Gemini } from './gemini.js'; import { GPT } from './gpt.js'; @@ -30,11 +30,18 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export class Prompter { - constructor(agent, fp) { + constructor(agent, profile) { this.agent = agent; - this.profile = JSON.parse(readFileSync(fp, 'utf8')); + this.profile = profile; let default_profile = JSON.parse(readFileSync('./profiles/defaults/_default.json', 'utf8')); - let base_fp = settings.base_profile; + let base_fp = ''; + if (settings.base_profile === 'survival') { + base_fp = './profiles/defaults/survival.json'; + } else if (settings.base_profile === 'creative') { + base_fp = './profiles/defaults/creative.json'; + } else if (settings.base_profile === 'god_mode') { + base_fp = './profiles/defaults/god_mode.json'; + } let base_profile = JSON.parse(readFileSync(base_fp, 'utf8')); // first use defaults to fill in missing values in the base profile diff --git a/src/process/agent_process.js b/src/process/agent_process.js index 603f348..7dc7e05 100644 --- a/src/process/agent_process.js +++ b/src/process/agent_process.js @@ -1,23 +1,24 @@ import { spawn } from 'child_process'; -import { mindserverProxy } from './mindserver_proxy.js.js'; +import { logoutAgent } from '../server/mindserver.js'; export class AgentProcess { - start(profile, load_memory=false, init_message=null, count_id=0, task_path=null, task_id=null) { - this.profile = profile; + constructor(name) { + this.name = name; + } + + start(load_memory=false, init_message=null, count_id=0, host, port) { this.count_id = count_id; this.running = true; let args = ['src/process/init_agent.js', this.name]; - args.push('-p', profile); + args.push('-n', this.name); args.push('-c', count_id); if (load_memory) args.push('-l', load_memory); if (init_message) args.push('-m', init_message); - if (task_path) - args.push('-t', task_path); - if (task_id) - args.push('-i', task_id); + args.push('-h', host); + args.push('-p', port); const agentProcess = spawn('node', args, { stdio: 'inherit', @@ -28,7 +29,7 @@ export class AgentProcess { agentProcess.on('exit', (code, signal) => { console.log(`Agent process exited with code ${code} and signal ${signal}`); this.running = false; - mindserverProxy.logoutAgent(this.name); + logoutAgent(this.name); if (code > 1) { console.log(`Ending task`); @@ -38,11 +39,11 @@ export class AgentProcess { if (code !== 0 && signal !== 'SIGINT') { // agent must run for at least 10 seconds before restarting if (Date.now() - last_restart < 10000) { - console.error(`Agent process ${profile} exited too quickly and will not be restarted.`); + console.error(`Agent process exited too quickly and will not be restarted.`); return; } console.log('Restarting agent...'); - this.start(profile, true, 'Agent process restarted.', count_id, task_path, task_id); + this.start(true, 'Agent process restarted.', count_id, host, port); last_restart = Date.now(); } }); @@ -61,7 +62,7 @@ export class AgentProcess { continue() { if (!this.running) { - this.start(this.profile, true, 'Agent process restarted.', this.count_id); + this.start(true, 'Agent process restarted.', this.count_id); } } } \ No newline at end of file diff --git a/src/process/init_agent.js b/src/process/init_agent.js index da79189..1af31b1 100644 --- a/src/process/init_agent.js +++ b/src/process/init_agent.js @@ -1,16 +1,7 @@ import { Agent } from '../agent/agent.js'; +import { serverProxy } from '../agent/mindserver_proxy.js'; import yargs from 'yargs'; -// Add global unhandled rejection handler -process.on('unhandledRejection', (reason, promise) => { - console.error('Unhandled Rejection at:', { - promise: promise, - reason: reason, - stack: reason?.stack || 'No stack trace' - }); - process.exit(1); -}); - const args = process.argv.slice(2); if (args.length < 1) { console.log('Usage: node init_agent.js [profile] [load_memory] [init_message]'); @@ -18,10 +9,10 @@ if (args.length < 1) { } const argv = yargs(args) - .option('profile', { - alias: 'p', + .option('name', { + alias: 'n', type: 'string', - description: 'profile filepath to use for agent' + description: 'name of agent' }) .option('load_memory', { alias: 'l', @@ -33,29 +24,32 @@ const argv = yargs(args) type: 'string', description: 'automatically prompt the agent on startup' }) - .option('task_path', { - alias: 't', - type: 'string', - description: 'task filepath to use for agent' - }) - .option('task_id', { - alias: 'i', - type: 'string', - description: 'task ID to execute' - }) .option('count_id', { alias: 'c', type: 'number', default: 0, description: 'identifying count for multi-agent scenarios', - }).argv; + }) + .option('host', { + alias: 'h', + type: 'string', + description: 'host of mindserver' + }) + .option('port', { + alias: 'p', + type: 'number', + description: 'port of mindserver' + }) + .argv; -// Wrap agent start in async IIFE with proper error handling (async () => { try { - console.log('Starting agent with profile:', argv.profile); + console.log('Connecting to MindServer'); + await serverProxy.connect(argv.name, argv.host, argv.port); + console.log('Starting agent'); const agent = new Agent(); - await agent.start(argv.profile, argv.load_memory, argv.init_message, argv.count_id, argv.task_path, argv.task_id); + serverProxy.setAgent(agent); + await agent.start(argv.load_memory, argv.init_message, argv.count_id); } catch (error) { console.error('Failed to start agent process:'); console.error(error.message); diff --git a/src/process/mindserver_proxy.js b/src/process/mindserver_proxy.js deleted file mode 100644 index d2d4a97..0000000 --- a/src/process/mindserver_proxy.js +++ /dev/null @@ -1,64 +0,0 @@ -import { io } from 'socket.io-client'; - -// Singleton mindserver proxy for the main process -// recieves commands from mindserver -class MindserverProxy { - constructor() { - if (MindserverProxy.instance) { - return MindserverProxy.instance; - } - - this.socket = null; - this.connected = false; - this.agent_processes = {}; - MindserverProxy.instance = this; - } - - connect(host, port) { - if (this.connected) return; - - this.socket = io(`http://${host}:${port}`); - this.connected = true; - - this.socket.on('stop-agent', (agentName) => { - if (this.agent_processes[agentName]) { - this.agent_processes[agentName].stop(); - } - }); - - this.socket.on('start-agent', (agentName) => { - if (this.agent_processes[agentName]) { - this.agent_processes[agentName].continue(); - } - }); - - this.socket.on('register-agents-success', () => { - console.log('Agents registered'); - }); - - this.socket.on('shutdown', () => { - console.log('Shutting down'); - for (let agentName in this.agent_processes) { - this.agent_processes[agentName].stop(); - } - setTimeout(() => { - process.exit(0); - }, 2000); - }); - } - - addAgent(agent) { - this.agent_processes.push(agent); - } - - logoutAgent(agentName) { - this.socket.emit('logout-agent', agentName); - } - - registerAgent(name, process) { - this.socket.emit('register-agents', [name]); - this.agent_processes[name] = process; - } -} - -export const mindserverProxy = new MindserverProxy(); \ No newline at end of file diff --git a/src/server/mindserver.js b/src/server/mindserver.js index ac54db3..035354e 100644 --- a/src/server/mindserver.js +++ b/src/server/mindserver.js @@ -3,21 +3,41 @@ import express from 'express'; import http from 'http'; import path from 'path'; import { fileURLToPath } from 'url'; +import * as mindcraft from '../../mindcraft.js'; -// Mindserver purposes: -// - central hub for inter-process communication between all agent processes +// Mindserver is: +// - central hub for communication between all agent processes // - api to control from other languages and remote users // - host for webapp // Module-level variables let io; let server; -const registeredAgents = new Set(); -const inGameAgents = {}; -const agentManagers = {}; // socket for main process that registers/controls agents +const agent_connections = {}; + +class AgentConnection { + constructor(settings) { + this.socket = null; + this.settings = settings; + this.in_game = false; + } + +} + +export function registerAgent(settings) { + let agentConnection = new AgentConnection(settings); + agent_connections[settings.profile.name] = agentConnection; +} + +export function logoutAgent(agentName) { + if (agent_connections[agentName]) { + agent_connections[agentName].in_game = false; + agentsUpdate(); + } +} // Initialize the server -export function createMindServer(port = 8080) { +export function createMindServer(host = 'localhost', port = 8080) { const app = express(); server = http.createServer(app); io = new Server(server); @@ -33,109 +53,90 @@ export function createMindServer(port = 8080) { agentsUpdate(socket); - socket.on('register-agents', (agentNames) => { - console.log(`Registering agents: ${agentNames}`); - agentNames.forEach(name => registeredAgents.add(name)); - for (let name of agentNames) { - agentManagers[name] = socket; + socket.on('get-settings', (agentName, callback) => { + if (agent_connections[agentName]) { + callback({ settings: agent_connections[agentName].settings }); + } else { + callback({ error: `Agent '${agentName}' not found.` }); } - socket.emit('register-agents-success'); - agentsUpdate(); }); socket.on('login-agent', (agentName) => { - if (curAgentName && curAgentName !== agentName) { - console.warn(`Agent ${agentName} already logged in as ${curAgentName}`); - return; - } - if (registeredAgents.has(agentName)) { + if (agent_connections[agentName]) { + agent_connections[agentName].socket = socket; + agent_connections[agentName].in_game = true; curAgentName = agentName; - inGameAgents[agentName] = socket; agentsUpdate(); - } else { - console.warn(`Agent ${agentName} not registered`); + } + else { + console.warn(`Unregistered agent ${agentName} tried to login`); } }); socket.on('logout-agent', (agentName) => { - if (inGameAgents[agentName]) { - delete inGameAgents[agentName]; + if (agent_connections[agentName]) { + agent_connections[agentName].in_game = false; agentsUpdate(); } }); socket.on('disconnect', () => { console.log('Client disconnected'); - if (inGameAgents[curAgentName]) { - delete inGameAgents[curAgentName]; + if (agent_connections[curAgentName]) { + agent_connections[curAgentName].in_game = false; agentsUpdate(); } }); socket.on('chat-message', (agentName, json) => { - if (!inGameAgents[agentName]) { + if (!agent_connections[agentName]) { console.warn(`Agent ${agentName} tried to send a message but is not logged in`); return; } console.log(`${curAgentName} sending message to ${agentName}: ${json.message}`); - inGameAgents[agentName].emit('chat-message', curAgentName, json); + agent_connections[agentName].socket.emit('chat-message', curAgentName, json); }); socket.on('restart-agent', (agentName) => { console.log(`Restarting agent: ${agentName}`); - inGameAgents[agentName].emit('restart-agent'); + agent_connections[agentName].socket.emit('restart-agent'); }); socket.on('stop-agent', (agentName) => { - let manager = agentManagers[agentName]; - if (manager) { - manager.emit('stop-agent', agentName); - } - else { - console.warn(`Stopping unregisterd agent ${agentName}`); - } + mindcraft.stopAgent(agentName); }); socket.on('start-agent', (agentName) => { - let manager = agentManagers[agentName]; - if (manager) { - manager.emit('start-agent', agentName); - } - else { - console.warn(`Starting unregisterd agent ${agentName}`); - } + mindcraft.startAgent(agentName); }); socket.on('stop-all-agents', () => { console.log('Killing all agents'); - stopAllAgents(); + for (let agentName in agent_connections) { + mindcraft.stopAgent(agentName); + } }); socket.on('shutdown', () => { console.log('Shutting down'); - for (let manager of Object.values(agentManagers)) { - manager.emit('shutdown'); - } - setTimeout(() => { - process.exit(0); - }, 2000); + process.exit(0); }); socket.on('send-message', (agentName, message) => { - if (!inGameAgents[agentName]) { - console.warn(`Agent ${agentName} not logged in, cannot send message via MindServer.`); + if (!agent_connections[agentName]) { + console.warn(`Agent ${agentName} not in game, cannot send message via MindServer.`); return } try { console.log(`Sending message to agent ${agentName}: ${message}`); - inGameAgents[agentName].emit('send-message', agentName, message) + agent_connections[agentName].socket.emit('send-message', agentName, message) } catch (error) { console.error('Error: ', error); } }); }); - server.listen(port, 'localhost', () => { + server.listen(port, host, () => { console.log(`MindServer running on port ${port}`); }); @@ -147,22 +148,12 @@ function agentsUpdate(socket) { socket = io; } let agents = []; - registeredAgents.forEach(name => { - agents.push({name, in_game: !!inGameAgents[name]}); - }); + for (let agentName in agent_connections) { + agents.push({name: agentName, in_game: agent_connections[agentName].in_game}); + }; socket.emit('agents-update', agents); } -function stopAllAgents() { - for (const agentName in inGameAgents) { - let manager = agentManagers[agentName]; - if (manager) { - manager.emit('stop-agent', agentName); - } - } -} - // Optional: export these if you need access to them from other files export const getIO = () => io; export const getServer = () => server; -export const getConnectedAgents = () => connectedAgents; diff --git a/src/utils/mcdata.js b/src/utils/mcdata.js index 1b79d3e..f79092c 100644 --- a/src/utils/mcdata.js +++ b/src/utils/mcdata.js @@ -1,5 +1,5 @@ import minecraftData from 'minecraft-data'; -import settings from '../../settings.js'; +import settings from '../agent/settings.js'; import { createBot } from 'mineflayer'; import prismarine_items from 'prismarine-item'; import { pathfinder } from 'mineflayer-pathfinder'; @@ -8,10 +8,9 @@ import { plugin as collectblock } from 'mineflayer-collectblock'; import { plugin as autoEat } from 'mineflayer-auto-eat'; import plugin from 'mineflayer-armor-manager'; const armorManager = plugin; - -const mc_version = settings.minecraft_version; -const mcdata = minecraftData(mc_version); -const Item = prismarine_items(mc_version); +let mc_version = null; +let mcdata = null; +let Item = null; /** * @typedef {string} ItemName @@ -54,12 +53,15 @@ export const WOOL_COLORS = [ export function initBot(username) { + mc_version = settings.world.minecraft_version; + mcdata = minecraftData(mc_version); + Item = prismarine_items(mc_version); let bot = createBot({ username: username, - host: settings.host, - port: settings.port, - auth: settings.auth, + host: settings.world.host, + port: settings.world.port, + auth: settings.world.auth, version: mc_version, }); diff --git a/src/utils/translator.js b/src/utils/translator.js index bc9cc77..3ebd566 100644 --- a/src/utils/translator.js +++ b/src/utils/translator.js @@ -1,10 +1,11 @@ import translate from 'google-translate-api-x'; -import settings from '../../settings.js'; +import settings from '../agent/settings.js'; + -const preferred_lang = String(settings.language).toLowerCase(); export async function handleTranslation(message) { - if (preferred_lang === 'en' || preferred_lang === 'english') + let preferred_lang = String(settings.language).toLowerCase(); + if (!preferred_lang || preferred_lang === 'en' || preferred_lang === 'english') return message; try { const translation = await translate(message, { to: preferred_lang }); @@ -16,7 +17,8 @@ export async function handleTranslation(message) { } export async function handleEnglishTranslation(message) { - if (preferred_lang === 'en' || preferred_lang === 'english') + let preferred_lang = String(settings.language).toLowerCase(); + if (!preferred_lang || preferred_lang === 'en' || preferred_lang === 'english') return message; try { const translation = await translate(message, { to: 'english' }); From 0f5dd0cb07c745ab864cc62cd83dcd5284dcae32 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Wed, 11 Jun 2025 16:41:54 -0500 Subject: [PATCH 3/7] create-agent endpoint from ui --- api.js | 101 -------------------- main.js | 72 ++++++++------ settings.js | 37 +------ src/agent/agent.js | 10 +- src/agent/mindserver_proxy.js | 17 ++-- src/mindcraft/default_settings.json | 25 +++++ mindcraft.js => src/mindcraft/mindcraft.js | 25 +++-- src/{server => mindcraft}/mindserver.js | 33 +++++-- src/{server => mindcraft}/public/index.html | 34 +++++++ src/models/prompter.js | 6 +- src/process/agent_process.js | 14 +-- src/utils/mcdata.js | 8 +- 12 files changed, 167 insertions(+), 215 deletions(-) delete mode 100644 api.js create mode 100644 src/mindcraft/default_settings.json rename mindcraft.js => src/mindcraft/mindcraft.js (65%) rename src/{server => mindcraft}/mindserver.js (81%) rename src/{server => mindcraft}/public/index.html (72%) diff --git a/api.js b/api.js deleted file mode 100644 index 25322c4..0000000 --- a/api.js +++ /dev/null @@ -1,101 +0,0 @@ -import * as Mindcraft from './mindcraft.js'; -import { readFileSync } from 'fs'; - - -await Mindcraft.init('localhost', 8080); // starts server locally -// await Mindcraft.connect('ip', 'port') // connects to remote server -// ^ must do one of these before calling anything else - -let profile = JSON.parse(readFileSync('./profiles/gemini.json', 'utf8')); - -Mindcraft.createAgent( - { - world: { - "minecraft_version": "1.21.1", // supports up to 1.21.1 - "host": "127.0.0.1", // or "localhost", "your.ip.address.here" - "port": 55916, - "auth": "offline", // or "microsoft" - }, - profile, - "base_profile": "survival", // survival | creative | god_mode - "load_memory": false, // load memory from previous session - "init_message": "Respond with hello world and your name", // sends to all on spawn - "only_chat_with": [], // users that the bots listen to and send general messages to. if empty it will chat publicly - "speak": false, // allows all bots to speak through system text-to-speech. works on windows, mac, on linux you need to `apt install espeak` - "language": "en", // translate to/from this language. Supports these language names: https://cloud.google.com/translate/docs/languages - "allow_vision": false, // allows vision model to interpret screenshots as inputs - "blocked_actions" : ["!checkBlueprint", "!checkBlueprintLevel", "!getBlueprint", "!getBlueprintLevel"] , // commands to disable and remove from docs. Ex: ["!setMode"] - "relevant_docs_count": 5, // number of relevant code function docs to select for prompting. -1 for all - "max_messages": 15, // max number of messages to keep in context - "num_examples": 2, // number of examples to give to the model - "max_commands": -1, // max number of commands that can be used in consecutive responses. -1 for no limit - "narrate_behavior": true, // chat simple automatic actions ('Picking up item!') - "log_all_prompts": false, // log ALL prompts to file - // "task": { - // "task_id": "multiagent_crafting_pink_wool_full_plan__depth_0", - // "goal": "Collaborate with other agents to craft an pink_wool", - // "conversation": "Let's work together to craft an pink_wool.", - // "initial_inventory": { - // "0": { - // "pink_dye": 1 - // } - // }, - // "agent_count": 1, - // "target": "pink_wool", - // "number_of_target": 1, - // "type": "techtree", - // "max_depth": 1, - // "depth": 0, - // "timeout": 300, - // "blocked_actions": { - // "0": [], - // }, - // "missing_items": [], - // "requires_ctable": false - // }, - "verbose_commands": true, // show full command syntax - "chat_bot_messages": true, // publicly chat bot-to-bot messages - - // mindserver settings - "render_bot_view": false, // show bot's view in browser at localhost:3000, 3001... - "allow_insecure_coding": true, // allows newAction command and model can write/run code on your computer. enable at own risk - "code_timeout_mins": -1, // minutes code is allowed to run. -1 for no timeout - } -) - -// profile = JSON.parse(readFileSync('./andy.json', 'utf8')); - -// Mindcraft.createAgent( -// { -// world: { -// "minecraft_version": "1.21.1", // supports up to 1.21.1 -// "host": "127.0.0.1", // or "localhost", "your.ip.address.here" -// "port": 55916, -// "auth": "offline", // or "microsoft" -// }, -// profile, -// "base_profile": "survival", // also see creative.json, god_mode.json -// "load_memory": false, // load memory from previous session -// "init_message": "Respond with hello world and your name", // sends to all on spawn -// "only_chat_with": [], // users that the bots listen to and send general messages to. if empty it will chat publicly -// "speak": false, // allows all bots to speak through system text-to-speech. works on windows, mac, on linux you need to `apt install espeak` -// "language": "en", // translate to/from this language. Supports these language names: https://cloud.google.com/translate/docs/languages -// "allow_vision": false, // allows vision model to interpret screenshots as inputs -// "blocked_actions" : ["!checkBlueprint", "!checkBlueprintLevel", "!getBlueprint", "!getBlueprintLevel"] , // commands to disable and remove from docs. Ex: ["!setMode"] -// "relevant_docs_count": 5, // number of relevant code function docs to select for prompting. -1 for all -// "max_messages": 15, // max number of messages to keep in context -// "num_examples": 2, // number of examples to give to the model -// "max_commands": -1, // max number of commands that can be used in consecutive responses. -1 for no limit -// "narrate_behavior": true, // chat simple automatic actions ('Picking up item!') -// "log_all_prompts": false, // log ALL prompts to file -// "task_file": "", -// "task_name": "", -// "verbose_commands": true, // show full command syntax -// "chat_bot_messages": true, // publicly chat bot-to-bot messages - -// // mindserver settings -// "render_bot_view": false, // show bot's view in browser at localhost:3000, 3001... -// "allow_insecure_coding": true, // allows newAction command and model can write/run code on your computer. enable at own risk -// "code_timeout_mins": -1, // minutes code is allowed to run. -1 for no timeout -// } -// ) \ No newline at end of file diff --git a/main.js b/main.js index c5e3685..10b3e28 100644 --- a/main.js +++ b/main.js @@ -1,9 +1,7 @@ -import { AgentProcess } from './src/process/agent_process.js'; +import * as Mindcraft from './src/mindcraft/mindcraft.js'; import settings from './settings.js'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; -import { createMindServer } from './src/server/mindserver.js'; -import { mindserverProxy } from './src/process/mindserver_proxy.js'; import { readFileSync } from 'fs'; function parseArguments() { @@ -24,35 +22,51 @@ function parseArguments() { .alias('help', 'h') .parse(); } - -function getProfiles(args) { - return args.profiles || settings.profiles; +const args = parseArguments(); +if (args.profiles) { + settings.profiles = args.profiles; } - -async function main() { - if (settings.host_mindserver) { - const mindServer = createMindServer(settings.mindserver_port); +if (args.task_path) { + let tasks = JSON.parse(readFileSync(args.task_path, 'utf8')); + if (args.task_id) { + settings.task = tasks[args.task_id]; + settings.task.task_id = args.task_id; } - mindserverProxy.connect(); - - const args = parseArguments(); - const profiles = getProfiles(args); - console.log(profiles); - const { load_memory, init_message } = settings; - - for (let i=0; i setTimeout(resolve, 1000)); + else { + throw new Error('task_id is required when task_path is provided'); } } -try { - main(); -} catch (error) { - console.error('An error occurred:', error); - process.exit(1); +// these environment variables override certain settings +if (process.env.MINECRAFT_PORT) { + settings.port = process.env.MINECRAFT_PORT; } +if (process.env.MINDSERVER_PORT) { + settings.mindserver_port = process.env.MINDSERVER_PORT; +} +if (process.env.PROFILES && JSON.parse(process.env.PROFILES).length > 0) { + settings.profiles = JSON.parse(process.env.PROFILES); +} +if (process.env.INSECURE_CODING) { + settings.allow_insecure_coding = true; +} +if (process.env.BLOCKED_ACTIONS) { + settings.blocked_actions = JSON.parse(process.env.BLOCKED_ACTIONS); +} +if (process.env.MAX_MESSAGES) { + settings.max_messages = process.env.MAX_MESSAGES; +} +if (process.env.NUM_EXAMPLES) { + settings.num_examples = process.env.NUM_EXAMPLES; +} +if (process.env.LOG_ALL) { + settings.log_all_prompts = process.env.LOG_ALL; +} + +Mindcraft.init(settings.mindserver_host, settings.mindserver_port); + +for (let profile of settings.profiles) { + const profile_json = JSON.parse(readFileSync(profile, 'utf8')); + settings.profile = profile_json; + Mindcraft.createAgent(settings); +} \ No newline at end of file diff --git a/settings.js b/settings.js index 1d6c5a2..16f8b0f 100644 --- a/settings.js +++ b/settings.js @@ -11,9 +11,9 @@ const settings = { // the base profile is shared by all bots for default prompts/examples/modes "profiles": [ - // "./andy.json", + "./andy.json", // "./profiles/gpt.json", - "./profiles/claude.json", + // "./profiles/claude.json", // "./profiles/gemini.json", // "./profiles/llama.json", // "./profiles/qwen.json", @@ -26,7 +26,7 @@ const settings = { ], // agent settings - "base_profile": "./profiles/defaults/god_mode.json", // also see creative.json, god_mode.json + "base_profile": "survival", // survival, creative, or god_mode "load_memory": false, // load memory from previous session "init_message": "Respond with hello world and your name", // sends to all on spawn "only_chat_with": [], // users that the bots listen to and send general messages to. if empty it will chat publicly @@ -40,42 +40,13 @@ const settings = { "max_commands": -1, // max number of commands that can be used in consecutive responses. -1 for no limit "narrate_behavior": true, // chat simple automatic actions ('Picking up item!') "log_all_prompts": false, // log ALL prompts to file - "task": {}, - "task_file": "", - "task_name": "", "verbose_commands": true, // show full command syntax "chat_bot_messages": true, // publicly chat bot-to-bot messages // mindserver settings - "render_bot_views": false, // show bot's view in browser at localhost:3000, 3001... + "render_bot_view": false, // show bot's view in browser at localhost:3000, 3001... "allow_insecure_coding": true, // allows newAction command and model can write/run code on your computer. enable at own risk "code_timeout_mins": -1, // minutes code is allowed to run. -1 for no timeout } -// these environment variables override certain settings -if (process.env.MINECRAFT_PORT) { - settings.port = process.env.MINECRAFT_PORT; -} -if (process.env.MINDSERVER_PORT) { - settings.mindserver_port = process.env.MINDSERVER_PORT; -} -if (process.env.PROFILES && JSON.parse(process.env.PROFILES).length > 0) { - settings.profiles = JSON.parse(process.env.PROFILES); -} -if (process.env.INSECURE_CODING) { - settings.allow_insecure_coding = true; -} -if (process.env.BLOCKED_ACTIONS) { - settings.blocked_actions = JSON.parse(process.env.BLOCKED_ACTIONS); -} -if (process.env.MAX_MESSAGES) { - settings.max_messages = process.env.MAX_MESSAGES; -} -if (process.env.NUM_EXAMPLES) { - settings.num_examples = process.env.NUM_EXAMPLES; -} -if (process.env.LOG_ALL) { - settings.log_all_prompts = process.env.LOG_ALL; -} - export default settings; diff --git a/src/agent/agent.js b/src/agent/agent.js index d490557..7bc692d 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -23,25 +23,17 @@ export class Agent { this.count_id = count_id; // Initialize components with more detailed error handling - console.log('Initializing action manager...'); + console.log(`Initializing agent ${this.name}...`); this.actions = new ActionManager(this); - console.log('Initializing prompter...'); this.prompter = new Prompter(this, settings.profile); this.name = this.prompter.getName(); - console.log('Initializing history...'); this.history = new History(this); - console.log('Initializing coder...'); this.coder = new Coder(this); - console.log('Initializing npc controller...'); this.npc = new NPCContoller(this); - console.log('Initializing memory bank...'); this.memory_bank = new MemoryBank(); - console.log('Initializing self prompter...'); this.self_prompter = new SelfPrompter(this); convoManager.initAgent(this); - console.log('Initializing examples...'); await this.prompter.initExamples(); - console.log('Initializing task...'); // load mem first before doing task let save_data = null; diff --git a/src/agent/mindserver_proxy.js b/src/agent/mindserver_proxy.js index 59f043a..da098e1 100644 --- a/src/agent/mindserver_proxy.js +++ b/src/agent/mindserver_proxy.js @@ -18,14 +18,19 @@ class MindServerProxy { if (this.connected) return; this.name = name; - this.socket = io(`http://${host}:${port}`); - this.connected = true; - this.socket.on('connect', () => { - console.log(name, 'connected to MindServer'); + await new Promise((resolve, reject) => { + this.socket.on('connect', resolve); + this.socket.on('connect_error', (err) => { + console.error('Connection failed:', err); + reject(err); + }); }); + this.connected = true; + console.log(name, 'connected to MindServer'); + this.socket.on('disconnect', () => { console.log('Disconnected from MindServer'); this.connected = false; @@ -60,8 +65,8 @@ class MindServerProxy { // Request settings and wait for response await new Promise((resolve, reject) => { const timeout = setTimeout(() => { - reject(new Error('Settings request timed out after 10 seconds')); - }, 10000); + reject(new Error('Settings request timed out after 5 seconds')); + }, 5000); this.socket.emit('get-settings', name, (response) => { clearTimeout(timeout); diff --git a/src/mindcraft/default_settings.json b/src/mindcraft/default_settings.json new file mode 100644 index 0000000..3ec448b --- /dev/null +++ b/src/mindcraft/default_settings.json @@ -0,0 +1,25 @@ +{ + "minecraft_version": "1.21.1", + "host": "127.0.0.1", + "port": 55916, + "auth": "offline", + "base_profile": "survival", + "load_memory": false, + "init_message": "Respond with hello world and your name", + "only_chat_with": [], + "speak": false, + "language": "en", + "allow_vision": false, + "blocked_actions" : ["!checkBlueprint", "!checkBlueprintLevel", "!getBlueprint", "!getBlueprintLevel"] , + "relevant_docs_count": 5, + "max_messages": 15, + "num_examples": 2, + "max_commands": -1, + "narrate_behavior": true, + "log_all_prompts": false, + "verbose_commands": true, + "chat_bot_messages": true, + "render_bot_view": false, + "allow_insecure_coding": false, + "code_timeout_mins": -1 +} \ No newline at end of file diff --git a/mindcraft.js b/src/mindcraft/mindcraft.js similarity index 65% rename from mindcraft.js rename to src/mindcraft/mindcraft.js index 6ec88aa..a36bbdc 100644 --- a/mindcraft.js +++ b/src/mindcraft/mindcraft.js @@ -1,5 +1,5 @@ -import { createMindServer, registerAgent } from './src/server/mindserver.js'; -import { AgentProcess } from './src/process/agent_process.js'; +import { createMindServer, registerAgent } from './mindserver.js'; +import { AgentProcess } from '../process/agent_process.js'; let mindserver; let connected = false; @@ -24,12 +24,13 @@ export async function createAgent(settings) { console.error('Agent name is required in profile'); return; } + settings = JSON.parse(JSON.stringify(settings)); let agent_name = settings.profile.name; registerAgent(settings); let load_memory = settings.load_memory || false; let init_message = settings.init_message || null; - const agentProcess = new AgentProcess(agent_name); - agentProcess.start(load_memory, init_message, agent_count, host, port); + const agentProcess = new AgentProcess(agent_name, host, port); + agentProcess.start(load_memory, init_message, agent_count); agent_count++; agent_processes[settings.profile.name] = agentProcess; } @@ -39,8 +40,8 @@ export function getAgentProcess(agentName) { } export function startAgent(agentName) { - if (this.agent_processes[agentName]) { - this.agent_processes[agentName].continue(); + if (agent_processes[agentName]) { + agent_processes[agentName].continue(); } else { console.error(`Cannot start agent ${agentName}; not found`); @@ -48,21 +49,17 @@ export function startAgent(agentName) { } export function stopAgent(agentName) { - if (this.agent_processes[agentName]) { - this.agent_processes[agentName].stop(); + if (agent_processes[agentName]) { + agent_processes[agentName].stop(); } } export function shutdown() { console.log('Shutting down'); - for (let agentName in this.agent_processes) { - this.agent_processes[agentName].stop(); + for (let agentName in agent_processes) { + agent_processes[agentName].stop(); } setTimeout(() => { process.exit(0); }, 2000); } - -export function logoutAgent(agentName) { - this.socket.emit('logout-agent', agentName); -} \ No newline at end of file diff --git a/src/server/mindserver.js b/src/mindcraft/mindserver.js similarity index 81% rename from src/server/mindserver.js rename to src/mindcraft/mindserver.js index 035354e..1d51854 100644 --- a/src/server/mindserver.js +++ b/src/mindcraft/mindserver.js @@ -3,18 +3,21 @@ import express from 'express'; import http from 'http'; import path from 'path'; import { fileURLToPath } from 'url'; -import * as mindcraft from '../../mindcraft.js'; +import * as mindcraft from './mindcraft.js'; +import { readFileSync } from 'fs'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Mindserver is: // - central hub for communication between all agent processes // - api to control from other languages and remote users // - host for webapp -// Module-level variables let io; let server; const agent_connections = {}; +const default_settings = JSON.parse(readFileSync(path.join(__dirname, 'default_settings.json'), 'utf8')); + class AgentConnection { constructor(settings) { this.socket = null; @@ -53,6 +56,23 @@ export function createMindServer(host = 'localhost', port = 8080) { agentsUpdate(socket); + socket.on('create-agent', (settings, callback) => { + console.log('API create agent...'); + settings = { ...default_settings, ...settings }; + if (settings.profile?.name) { + if (settings.profile.name in agent_connections) { + callback({ success: false, error: 'Agent already exists' }); + return; + } + mindcraft.createAgent(settings); + callback({ success: true }); + } + else { + console.error('Agent name is required in profile'); + callback({ success: false, error: 'Agent name is required in profile' }); + } + }); + socket.on('get-settings', (agentName, callback) => { if (agent_connections[agentName]) { callback({ settings: agent_connections[agentName].settings }); @@ -73,16 +93,9 @@ export function createMindServer(host = 'localhost', port = 8080) { } }); - socket.on('logout-agent', (agentName) => { - if (agent_connections[agentName]) { - agent_connections[agentName].in_game = false; - agentsUpdate(); - } - }); - socket.on('disconnect', () => { - console.log('Client disconnected'); if (agent_connections[curAgentName]) { + console.log(`Agent ${curAgentName} disconnected`); agent_connections[curAgentName].in_game = false; agentsUpdate(); } diff --git a/src/server/public/index.html b/src/mindcraft/public/index.html similarity index 72% rename from src/server/public/index.html rename to src/mindcraft/public/index.html index c66a986..f16105c 100644 --- a/src/server/public/index.html +++ b/src/mindcraft/public/index.html @@ -64,10 +64,44 @@

Mindcraft

+
+ + +
+