create-agent endpoint from ui

This commit is contained in:
MaxRobinsonTheGreat 2025-06-11 16:41:54 -05:00
parent 8162fc1ab1
commit 0f5dd0cb07
12 changed files with 167 additions and 215 deletions

101
api.js
View file

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

72
main.js
View file

@ -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<profiles.length; i++) {
const agent_process = new AgentProcess();
const profile = readFileSync(profiles[i], 'utf8');
const agent_json = JSON.parse(profile);
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));
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);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -64,10 +64,44 @@
<h1>Mindcraft</h1>
<div id="agents"></div>
<div style="margin-top: 20px;">
<button id="createAgentBtn" class="start-btn">Create Agent</button>
<input type="file" id="agentConfigFile" accept=".json,application/json" style="display: none">
</div>
<script>
const socket = io();
const agentsDiv = document.getElementById('agents');
document.getElementById('createAgentBtn').addEventListener('click', () => {
document.getElementById('agentConfigFile').click();
});
document.getElementById('agentConfigFile').addEventListener('change', (event) => {
const file = event.target.files[0];
if (!file) {
return;
}
const reader = new FileReader();
reader.onload = (e) => {
try {
const profile = JSON.parse(e.target.result);
socket.emit('create-agent', {profile}, (response) => {
if (response.success) {
alert('Agent created successfully!');
} else {
alert(`Error creating agent: ${response.error}`);
}
});
} catch (error) {
alert('Error parsing JSON file: ' + error.message);
}
};
reader.readAsText(file);
// Reset file input to allow re-uploading the same file
event.target.value = '';
});
socket.on('agents-update', (agents) => {
agentsDiv.innerHTML = agents.length ?
agents.map(agent => `

View file

@ -35,11 +35,11 @@ export class Prompter {
this.profile = profile;
let default_profile = JSON.parse(readFileSync('./profiles/defaults/_default.json', 'utf8'));
let base_fp = '';
if (settings.base_profile === 'survival') {
if (settings.base_profile.includes('survival')) {
base_fp = './profiles/defaults/survival.json';
} else if (settings.base_profile === 'creative') {
} else if (settings.base_profile.includes('creative')) {
base_fp = './profiles/defaults/creative.json';
} else if (settings.base_profile === 'god_mode') {
} else if (settings.base_profile.includes('god_mode')) {
base_fp = './profiles/defaults/god_mode.json';
}
let base_profile = JSON.parse(readFileSync(base_fp, 'utf8'));

View file

@ -1,12 +1,14 @@
import { spawn } from 'child_process';
import { logoutAgent } from '../server/mindserver.js';
import { logoutAgent } from '../mindcraft/mindserver.js';
export class AgentProcess {
constructor(name) {
constructor(name, host, port) {
this.name = name;
this.host = host;
this.port = port;
}
start(load_memory=false, init_message=null, count_id=0, host, port) {
start(load_memory=false, init_message=null, count_id=0) {
this.count_id = count_id;
this.running = true;
@ -17,8 +19,8 @@ export class AgentProcess {
args.push('-l', load_memory);
if (init_message)
args.push('-m', init_message);
args.push('-h', host);
args.push('-p', port);
args.push('-h', this.host);
args.push('-p', this.port);
const agentProcess = spawn('node', args, {
stdio: 'inherit',
@ -43,7 +45,7 @@ export class AgentProcess {
return;
}
console.log('Restarting agent...');
this.start(true, 'Agent process restarted.', count_id, host, port);
this.start(true, 'Agent process restarted.', count_id, this.host, this.port);
last_restart = Date.now();
}
});

View file

@ -53,15 +53,15 @@ export const WOOL_COLORS = [
export function initBot(username) {
mc_version = settings.world.minecraft_version;
mc_version = settings.minecraft_version;
mcdata = minecraftData(mc_version);
Item = prismarine_items(mc_version);
let bot = createBot({
username: username,
host: settings.world.host,
port: settings.world.port,
auth: settings.world.auth,
host: settings.host,
port: settings.port,
auth: settings.auth,
version: mc_version,
});