mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-08-26 17:03:45 +02:00
commit
43a5b7a0c7
31 changed files with 736 additions and 500 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -26,4 +26,5 @@ tasks/construction_tasks/train_multiagent_construction_tasks.json
|
||||||
tasks/construction_tasks/test/**
|
tasks/construction_tasks/test/**
|
||||||
tasks/construction_tasks/train/**
|
tasks/construction_tasks/train/**
|
||||||
server_data*
|
server_data*
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
|
src/mindcraft-py/__pycache__/
|
||||||
|
|
72
main.js
72
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 settings from './settings.js';
|
||||||
import yargs from 'yargs';
|
import yargs from 'yargs';
|
||||||
import { hideBin } from 'yargs/helpers';
|
import { hideBin } from 'yargs/helpers';
|
||||||
import { createMindServer } from './src/server/mind_server.js';
|
|
||||||
import { mainProxy } from './src/process/main_proxy.js';
|
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
|
|
||||||
function parseArguments() {
|
function parseArguments() {
|
||||||
|
@ -24,35 +22,51 @@ function parseArguments() {
|
||||||
.alias('help', 'h')
|
.alias('help', 'h')
|
||||||
.parse();
|
.parse();
|
||||||
}
|
}
|
||||||
|
const args = parseArguments();
|
||||||
function getProfiles(args) {
|
if (args.profiles) {
|
||||||
return args.profiles || settings.profiles;
|
settings.profiles = args.profiles;
|
||||||
}
|
}
|
||||||
|
if (args.task_path) {
|
||||||
async function main() {
|
let tasks = JSON.parse(readFileSync(args.task_path, 'utf8'));
|
||||||
if (settings.host_mindserver) {
|
if (args.task_id) {
|
||||||
const mindServer = createMindServer(settings.mindserver_port);
|
settings.task = tasks[args.task_id];
|
||||||
|
settings.task.task_id = args.task_id;
|
||||||
}
|
}
|
||||||
mainProxy.connect();
|
else {
|
||||||
|
throw new Error('task_id is required when task_path is provided');
|
||||||
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);
|
|
||||||
mainProxy.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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// these environment variables override certain settings
|
||||||
main();
|
if (process.env.MINECRAFT_PORT) {
|
||||||
} catch (error) {
|
settings.port = process.env.MINECRAFT_PORT;
|
||||||
console.error('An error occurred:', error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
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(false, settings.mindserver_port);
|
||||||
|
|
||||||
|
for (let profile of settings.profiles) {
|
||||||
|
const profile_json = JSON.parse(readFileSync(profile, 'utf8'));
|
||||||
|
settings.profile = profile_json;
|
||||||
|
Mindcraft.createAgent(settings);
|
||||||
|
}
|
|
@ -3,3 +3,4 @@ botocore==1.37.11
|
||||||
pandas==2.2.3
|
pandas==2.2.3
|
||||||
prettytable==3.16.0
|
prettytable==3.16.0
|
||||||
tqdm==4.62.3
|
tqdm==4.62.3
|
||||||
|
python-socketio[client]
|
34
settings.js
34
settings.js
|
@ -5,12 +5,9 @@ const settings = {
|
||||||
"auth": "offline", // or "microsoft"
|
"auth": "offline", // or "microsoft"
|
||||||
|
|
||||||
// the mindserver manages all agents and hosts the UI
|
// the mindserver manages all agents and hosts the UI
|
||||||
"host_mindserver": true, // if true, the mindserver will be hosted on this machine. otherwise, specify a public IP address
|
|
||||||
"mindserver_host": "localhost",
|
|
||||||
"mindserver_port": 8080,
|
"mindserver_port": 8080,
|
||||||
|
|
||||||
// the base profile is shared by all bots for default prompts/examples/modes
|
"base_profile": "survival", // survival, creative, or god_mode
|
||||||
"base_profile": "./profiles/defaults/survival.json", // also see creative.json, god_mode.json
|
|
||||||
"profiles": [
|
"profiles": [
|
||||||
"./andy.json",
|
"./andy.json",
|
||||||
// "./profiles/gpt.json",
|
// "./profiles/gpt.json",
|
||||||
|
@ -25,12 +22,13 @@ const settings = {
|
||||||
// using more than 1 profile requires you to /msg each bot indivually
|
// using more than 1 profile requires you to /msg each bot indivually
|
||||||
// individual profiles override values from the base profile
|
// individual profiles override values from the base profile
|
||||||
],
|
],
|
||||||
|
|
||||||
"load_memory": false, // load memory from previous session
|
"load_memory": false, // load memory from previous session
|
||||||
"init_message": "Respond with hello world and your name", // sends to all on spawn
|
"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
|
"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`
|
"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
|
"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...
|
"render_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_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
|
"allow_vision": false, // allows vision model to interpret screenshots as inputs
|
||||||
|
@ -47,30 +45,4 @@ const settings = {
|
||||||
"log_all_prompts": false, // log ALL prompts to file
|
"log_all_prompts": false, // log ALL prompts to file
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
export default settings;
|
||||||
|
|
|
@ -12,41 +12,28 @@ import { SelfPrompter } from './self_prompter.js';
|
||||||
import convoManager from './conversation.js';
|
import convoManager from './conversation.js';
|
||||||
import { handleTranslation, handleEnglishTranslation } from '../utils/translator.js';
|
import { handleTranslation, handleEnglishTranslation } from '../utils/translator.js';
|
||||||
import { addBrowserViewer } from './vision/browser_viewer.js';
|
import { addBrowserViewer } from './vision/browser_viewer.js';
|
||||||
import settings from '../../settings.js';
|
import { serverProxy } from './mindserver_proxy.js';
|
||||||
import { serverProxy } from './agent_proxy.js';
|
import settings from './settings.js';
|
||||||
import { Task } from './tasks/tasks.js';
|
import { Task } from './tasks/tasks.js';
|
||||||
import { say } from './speak.js';
|
import { say } from './speak.js';
|
||||||
|
|
||||||
export class Agent {
|
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.last_sender = null;
|
||||||
this.count_id = count_id;
|
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
|
// Initialize components with more detailed error handling
|
||||||
console.log('Initializing action manager...');
|
console.log(`Initializing agent ${this.name}...`);
|
||||||
this.actions = new ActionManager(this);
|
this.actions = new ActionManager(this);
|
||||||
console.log('Initializing prompter...');
|
this.prompter = new Prompter(this, settings.profile);
|
||||||
this.prompter = new Prompter(this, profile_fp);
|
|
||||||
this.name = this.prompter.getName();
|
this.name = this.prompter.getName();
|
||||||
console.log('Initializing history...');
|
|
||||||
this.history = new History(this);
|
this.history = new History(this);
|
||||||
console.log('Initializing coder...');
|
|
||||||
this.coder = new Coder(this);
|
this.coder = new Coder(this);
|
||||||
console.log('Initializing npc controller...');
|
|
||||||
this.npc = new NPCContoller(this);
|
this.npc = new NPCContoller(this);
|
||||||
console.log('Initializing memory bank...');
|
|
||||||
this.memory_bank = new MemoryBank();
|
this.memory_bank = new MemoryBank();
|
||||||
console.log('Initializing self prompter...');
|
|
||||||
this.self_prompter = new SelfPrompter(this);
|
this.self_prompter = new SelfPrompter(this);
|
||||||
convoManager.initAgent(this);
|
convoManager.initAgent(this);
|
||||||
console.log('Initializing examples...');
|
|
||||||
await this.prompter.initExamples();
|
await this.prompter.initExamples();
|
||||||
console.log('Initializing task...');
|
|
||||||
|
|
||||||
// load mem first before doing task
|
// load mem first before doing task
|
||||||
let save_data = null;
|
let save_data = null;
|
||||||
|
@ -59,19 +46,15 @@ export class Agent {
|
||||||
} else {
|
} else {
|
||||||
taskStart = Date.now();
|
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 || []);
|
this.blocked_actions = settings.blocked_actions.concat(this.task.blocked_actions || []);
|
||||||
blacklistCommands(this.blocked_actions);
|
blacklistCommands(this.blocked_actions);
|
||||||
|
|
||||||
serverProxy.connect(this);
|
|
||||||
|
|
||||||
console.log(this.name, 'logging into minecraft...');
|
console.log(this.name, 'logging into minecraft...');
|
||||||
this.bot = initBot(this.name);
|
this.bot = initBot(this.name);
|
||||||
|
|
||||||
initModes(this);
|
initModes(this);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.bot.on('login', () => {
|
this.bot.on('login', () => {
|
||||||
console.log(this.name, 'logged in!');
|
console.log(this.name, 'logged in!');
|
||||||
serverProxy.login();
|
serverProxy.login();
|
||||||
|
@ -90,6 +73,8 @@ export class Agent {
|
||||||
try {
|
try {
|
||||||
clearTimeout(spawnTimeout);
|
clearTimeout(spawnTimeout);
|
||||||
addBrowserViewer(this.bot, count_id);
|
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
|
// wait for a bit so stats are not undefined
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
@ -101,22 +86,19 @@ export class Agent {
|
||||||
this.startEvents();
|
this.startEvents();
|
||||||
|
|
||||||
if (!load_mem) {
|
if (!load_mem) {
|
||||||
if (task_path !== null) {
|
if (settings.task) {
|
||||||
this.task.initBotTask();
|
this.task.initBotTask();
|
||||||
this.task.setAgentGoal();
|
this.task.setAgentGoal();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// set the goal without initializing the rest of the task
|
// set the goal without initializing the rest of the task
|
||||||
if (task_path !== null) {
|
if (settings.task) {
|
||||||
this.task.setAgentGoal();
|
this.task.setAgentGoal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10000));
|
await new Promise((resolve) => setTimeout(resolve, 10000));
|
||||||
this.checkAllPlayersPresent();
|
this.checkAllPlayersPresent();
|
||||||
|
|
||||||
console.log('Initializing vision intepreter...');
|
|
||||||
this.vision_interpreter = new VisionInterpreter(this, settings.allow_vision);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in spawn event:', error);
|
console.error('Error in spawn event:', error);
|
||||||
|
@ -160,8 +142,12 @@ export class Agent {
|
||||||
this.respondFunc = respondFunc;
|
this.respondFunc = respondFunc;
|
||||||
|
|
||||||
this.bot.on('whisper', 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
|
// Set up auto-eat
|
||||||
this.bot.autoEat.options = {
|
this.bot.autoEat.options = {
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { writeFile, readFile, mkdirSync } from 'fs';
|
import { writeFile, readFile, mkdirSync } from 'fs';
|
||||||
import settings from '../../settings.js';
|
import { makeCompartment, lockdown } from './library/lockdown.js';
|
||||||
import { makeCompartment } from './library/lockdown.js';
|
|
||||||
import * as skills from './library/skills.js';
|
import * as skills from './library/skills.js';
|
||||||
import * as world from './library/world.js';
|
import * as world from './library/world.js';
|
||||||
import { Vec3 } from 'vec3';
|
import { Vec3 } from 'vec3';
|
||||||
|
@ -27,6 +26,7 @@ export class Coder {
|
||||||
|
|
||||||
async generateCode(agent_history) {
|
async generateCode(agent_history) {
|
||||||
this.agent.bot.modes.pause('unstuck');
|
this.agent.bot.modes.pause('unstuck');
|
||||||
|
lockdown();
|
||||||
// this message history is transient and only maintained in this function
|
// this message history is transient and only maintained in this function
|
||||||
let messages = agent_history.getHistory();
|
let messages = agent_history.getHistory();
|
||||||
messages.push({role: 'system', content: 'Code generation started. Write code in codeblock in your response:'});
|
messages.push({role: 'system', content: 'Code generation started. Write code in codeblock in your response:'});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as skills from '../library/skills.js';
|
import * as skills from '../library/skills.js';
|
||||||
import settings from '../../../settings.js';
|
import settings from '../settings.js';
|
||||||
import convoManager from '../conversation.js';
|
import convoManager from '../conversation.js';
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ export const actionsList = [
|
||||||
result = 'Error generating code: ' + e.toString();
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import settings from '../../settings.js';
|
import settings from './settings.js';
|
||||||
import { readFileSync } from 'fs';
|
|
||||||
import { containsCommand } from './commands/index.js';
|
import { containsCommand } from './commands/index.js';
|
||||||
import { sendBotChatToServer } from './agent_proxy.js';
|
import { sendBotChatToServer } from './mindserver_proxy.js';
|
||||||
|
|
||||||
let agent;
|
let agent;
|
||||||
let agent_names = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name);
|
let agent_names = [];
|
||||||
let agents_in_game = [];
|
let agents_in_game = [];
|
||||||
|
|
||||||
class Conversation {
|
class Conversation {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { writeFileSync, readFileSync, mkdirSync, existsSync } from 'fs';
|
import { writeFileSync, readFileSync, mkdirSync, existsSync } from 'fs';
|
||||||
import { NPCData } from './npc/data.js';
|
import { NPCData } from './npc/data.js';
|
||||||
import settings from '../../settings.js';
|
import settings from './settings.js';
|
||||||
|
|
||||||
|
|
||||||
export class History {
|
export class History {
|
||||||
|
|
|
@ -4,16 +4,22 @@ import 'ses';
|
||||||
// We disable some of the taming to allow for more flexibility
|
// 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
|
// For configuration, see https://github.com/endojs/endo/blob/master/packages/ses/docs/lockdown.md
|
||||||
lockdown({
|
|
||||||
// basic devex and quality of life improvements
|
let lockeddown = false;
|
||||||
localeTaming: 'unsafe',
|
export function lockdown() {
|
||||||
consoleTaming: 'unsafe',
|
if (lockeddown) return;
|
||||||
errorTaming: 'unsafe',
|
lockeddown = true;
|
||||||
stackFiltering: 'verbose',
|
lockdown({
|
||||||
// allow eval outside of created compartments
|
// basic devex and quality of life improvements
|
||||||
// (mineflayer dep "protodef" uses eval)
|
localeTaming: 'unsafe',
|
||||||
evalTaming: 'unsafeEval',
|
consoleTaming: 'unsafe',
|
||||||
});
|
errorTaming: 'unsafe',
|
||||||
|
stackFiltering: 'verbose',
|
||||||
|
// allow eval outside of created compartments
|
||||||
|
// (mineflayer dep "protodef" uses eval)
|
||||||
|
evalTaming: 'unsafeEval',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const makeCompartment = (endowments = {}) => {
|
export const makeCompartment = (endowments = {}) => {
|
||||||
return new Compartment({
|
return new Compartment({
|
||||||
|
|
115
src/agent/mindserver_proxy.js
Normal file
115
src/agent/mindserver_proxy.js
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
import { io } from 'socket.io-client';
|
||||||
|
import convoManager from './conversation.js';
|
||||||
|
import { setSettings } from './settings.js';
|
||||||
|
|
||||||
|
// agents connection to mindserver
|
||||||
|
// always connect to localhost
|
||||||
|
|
||||||
|
class MindServerProxy {
|
||||||
|
constructor() {
|
||||||
|
if (MindServerProxy.instance) {
|
||||||
|
return MindServerProxy.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket = null;
|
||||||
|
this.connected = false;
|
||||||
|
this.agents = [];
|
||||||
|
MindServerProxy.instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async connect(name, port) {
|
||||||
|
if (this.connected) return;
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
this.socket = io(`http://localhost:${port}`);
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
|
||||||
|
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 5 seconds'));
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
import * as skills from './library/skills.js';
|
import * as skills from './library/skills.js';
|
||||||
import * as world from './library/world.js';
|
import * as world from './library/world.js';
|
||||||
import * as mc from '../utils/mcdata.js';
|
import * as mc from '../utils/mcdata.js';
|
||||||
import settings from '../../settings.js'
|
import settings from './settings.js'
|
||||||
import convoManager from './conversation.js';
|
import convoManager from './conversation.js';
|
||||||
|
|
||||||
async function say(agent, message) {
|
async function say(agent, message) {
|
||||||
|
|
7
src/agent/settings.js
Normal file
7
src/agent/settings.js
Normal file
|
@ -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);
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
import { readFileSync , writeFileSync, existsSync} from 'fs';
|
import { readFileSync , writeFileSync, existsSync} from 'fs';
|
||||||
import { executeCommand } from '../commands/index.js';
|
import { executeCommand } from '../commands/index.js';
|
||||||
import { getPosition } from '../library/world.js';
|
import { getPosition } from '../library/world.js';
|
||||||
import settings from '../../../settings.js';
|
|
||||||
import { ConstructionTaskValidator, Blueprint } from './construction_tasks.js';
|
import { ConstructionTaskValidator, Blueprint } from './construction_tasks.js';
|
||||||
import { CookingTaskInitiator } from './cooking_tasks.js';
|
import { CookingTaskInitiator } from './cooking_tasks.js';
|
||||||
|
|
||||||
|
@ -233,27 +232,26 @@ class CookingCraftingTaskValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Task {
|
export class Task {
|
||||||
constructor(agent, task_path, task_id, taskStartTime = null) {
|
constructor(agent, task_data, taskStartTime = null) {
|
||||||
this.agent = agent;
|
this.agent = agent;
|
||||||
this.data = null;
|
this.data = null;
|
||||||
if (taskStartTime !== null)
|
if (taskStartTime !== null)
|
||||||
this.taskStartTime = taskStartTime;
|
this.taskStartTime = taskStartTime;
|
||||||
else
|
else
|
||||||
this.taskStartTime = Date.now();
|
this.taskStartTime = Date.now();
|
||||||
console.log("Task start time set to", this.taskStartTime);
|
|
||||||
this.validator = null;
|
this.validator = null;
|
||||||
this.reset_function = null;
|
this.reset_function = null;
|
||||||
this.blocked_actions = [];
|
this.blocked_actions = [];
|
||||||
this.task_id = task_id;
|
this.task_data = task_data;
|
||||||
|
if (task_data) {
|
||||||
if (task_path && task_id) {
|
console.log('Starting task', task_data.task_id);
|
||||||
console.log('Starting task', task_id);
|
console.log("Task start time set to", this.taskStartTime);
|
||||||
if (task_id.endsWith('hells_kitchen')) {
|
if (task_data.task_id.endsWith('hells_kitchen')) {
|
||||||
// Reset hells_kitchen progress when a new task starts
|
// 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');
|
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;
|
this.task_type = this.data.type;
|
||||||
if (this.task_type === 'construction' && this.data.blueprint) {
|
if (this.task_type === 'construction' && this.data.blueprint) {
|
||||||
this.blueprint = new Blueprint(this.data.blueprint);
|
this.blueprint = new Blueprint(this.data.blueprint);
|
||||||
|
@ -300,7 +298,11 @@ export class Task {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.name = this.agent.name;
|
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
|
// Add this method if you want to manually reset the hells_kitchen progress
|
||||||
|
@ -360,28 +362,6 @@ export class Task {
|
||||||
return null;
|
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() {
|
isDone() {
|
||||||
let res = null;
|
let res = null;
|
||||||
if (this.validator)
|
if (this.validator)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import settings from '../../../settings.js';
|
import settings from '../settings.js';
|
||||||
import prismarineViewer from 'prismarine-viewer';
|
import prismarineViewer from 'prismarine-viewer';
|
||||||
const mineflayerViewer = prismarineViewer.mineflayer;
|
const mineflayerViewer = prismarineViewer.mineflayer;
|
||||||
|
|
||||||
export function addBrowserViewer(bot, count_id) {
|
export function addBrowserViewer(bot, count_id) {
|
||||||
if (settings.show_bot_views)
|
if (settings.render_bot_view)
|
||||||
mineflayerViewer(bot, { port: 3000+count_id, firstPerson: true, });
|
mineflayerViewer(bot, { port: 3000+count_id, firstPerson: true, });
|
||||||
}
|
}
|
27
src/mindcraft-py/example.py
Normal file
27
src/mindcraft-py/example.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import mindcraft
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Initialize Mindcraft, starting the Node.js server
|
||||||
|
# This will also connect to the MindServer via websockets
|
||||||
|
mindcraft.init()
|
||||||
|
|
||||||
|
# Get the directory of the current script
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
profile_path = os.path.abspath(os.path.join(script_dir, '..', '..', 'andy.json'))
|
||||||
|
|
||||||
|
# Load agent settings from a JSON file
|
||||||
|
try:
|
||||||
|
with open(profile_path, 'r') as f:
|
||||||
|
profile_data = json.load(f)
|
||||||
|
|
||||||
|
settings = {"profile": profile_data}
|
||||||
|
mindcraft.create_agent(settings)
|
||||||
|
|
||||||
|
settings_copy = settings.copy()
|
||||||
|
settings_copy['profile']['name'] = 'andy2'
|
||||||
|
mindcraft.create_agent(settings_copy)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: Could not find andy.json at {profile_path}")
|
||||||
|
|
||||||
|
mindcraft.wait()
|
24
src/mindcraft-py/init-mindcraft.js
Normal file
24
src/mindcraft-py/init-mindcraft.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import * as Mindcraft from '../mindcraft/mindcraft.js';
|
||||||
|
import settings from '../../settings.js';
|
||||||
|
import yargs from 'yargs';
|
||||||
|
import { hideBin } from 'yargs/helpers';
|
||||||
|
|
||||||
|
function parseArguments() {
|
||||||
|
return yargs(hideBin(process.argv))
|
||||||
|
.option('mindserver_port', {
|
||||||
|
type: 'number',
|
||||||
|
describe: 'Mindserver port',
|
||||||
|
default: settings.mindserver_port
|
||||||
|
})
|
||||||
|
.help()
|
||||||
|
.alias('help', 'h')
|
||||||
|
.parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = parseArguments();
|
||||||
|
|
||||||
|
settings.mindserver_port = args.mindserver_port;
|
||||||
|
|
||||||
|
Mindcraft.init(settings.mindserver_port);
|
||||||
|
|
||||||
|
console.log(`Mindcraft initialized with MindServer at localhost:${settings.mindserver_port}`);
|
99
src/mindcraft-py/mindcraft.py
Normal file
99
src/mindcraft-py/mindcraft.py
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import subprocess
|
||||||
|
import socketio
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import atexit
|
||||||
|
import threading
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
|
||||||
|
class Mindcraft:
|
||||||
|
def __init__(self):
|
||||||
|
self.sio = socketio.Client()
|
||||||
|
self.process = None
|
||||||
|
self.connected = False
|
||||||
|
self.log_thread = None
|
||||||
|
|
||||||
|
def _log_reader(self):
|
||||||
|
for line in iter(self.process.stdout.readline, ''):
|
||||||
|
sys.stdout.write(f'[Node.js] {line}')
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def init(self, port=8080):
|
||||||
|
if self.process:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.port = port
|
||||||
|
|
||||||
|
node_script_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'init-mindcraft.js'))
|
||||||
|
|
||||||
|
self.process = subprocess.Popen([
|
||||||
|
'node',
|
||||||
|
node_script_path,
|
||||||
|
'--mindserver_port', str(self.port)
|
||||||
|
], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1)
|
||||||
|
|
||||||
|
self.log_thread = threading.Thread(target=self._log_reader)
|
||||||
|
self.log_thread.daemon = True
|
||||||
|
self.log_thread.start()
|
||||||
|
|
||||||
|
atexit.register(self.shutdown)
|
||||||
|
time.sleep(2) # Give server time to start before connecting
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.sio.connect(f'http://localhost:{self.port}')
|
||||||
|
self.connected = True
|
||||||
|
print("Connected to MindServer. Mindcraft is initialized.")
|
||||||
|
except socketio.exceptions.ConnectionError as e:
|
||||||
|
print(f"Failed to connect to MindServer: {e}")
|
||||||
|
self.shutdown()
|
||||||
|
raise
|
||||||
|
|
||||||
|
def create_agent(self, settings_json):
|
||||||
|
if not self.connected:
|
||||||
|
raise Exception("Not connected to MindServer. Call init() first.")
|
||||||
|
|
||||||
|
profile_data = settings_json.get('profile', {})
|
||||||
|
|
||||||
|
def callback(response):
|
||||||
|
if response.get('success'):
|
||||||
|
print(f"Agent '{profile_data.get('name')}' created successfully")
|
||||||
|
else:
|
||||||
|
print(f"Error creating agent: {response.get('error', 'Unknown error')}")
|
||||||
|
|
||||||
|
self.sio.emit('create-agent', settings_json, callback=callback)
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
if self.sio.connected:
|
||||||
|
self.sio.disconnect()
|
||||||
|
self.connected = False
|
||||||
|
if self.process:
|
||||||
|
self.process.terminate()
|
||||||
|
self.process.wait()
|
||||||
|
self.process = None
|
||||||
|
print("Mindcraft shut down.")
|
||||||
|
|
||||||
|
def wait(self):
|
||||||
|
"""Block the main thread until Ctrl+C is pressed so the server stays up,"""
|
||||||
|
print("Server is running. Press Ctrl+C to exit.")
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nCtrl+C detected. Exiting...")
|
||||||
|
self.shutdown()
|
||||||
|
|
||||||
|
mindcraft_instance = Mindcraft()
|
||||||
|
|
||||||
|
def init(port=8080):
|
||||||
|
mindcraft_instance.init(port)
|
||||||
|
|
||||||
|
def create_agent(settings_json):
|
||||||
|
mindcraft_instance.create_agent(settings_json)
|
||||||
|
|
||||||
|
def shutdown():
|
||||||
|
mindcraft_instance.shutdown()
|
||||||
|
|
||||||
|
def wait():
|
||||||
|
mindcraft_instance.wait()
|
25
src/mindcraft/default_settings.json
Normal file
25
src/mindcraft/default_settings.json
Normal 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
|
||||||
|
}
|
64
src/mindcraft/mindcraft.js
Normal file
64
src/mindcraft/mindcraft.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { createMindServer, registerAgent } from './mindserver.js';
|
||||||
|
import { AgentProcess } from '../process/agent_process.js';
|
||||||
|
|
||||||
|
let mindserver;
|
||||||
|
let connected = false;
|
||||||
|
let agent_processes = {};
|
||||||
|
let agent_count = 0;
|
||||||
|
let host = 'localhost';
|
||||||
|
let port = 8080;
|
||||||
|
|
||||||
|
export async function init(host_public=false, port=8080) {
|
||||||
|
if (connected) {
|
||||||
|
console.error('Already initiliazed!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mindserver = createMindServer(host_public, port);
|
||||||
|
port = port;
|
||||||
|
connected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createAgent(settings) {
|
||||||
|
if (!settings.profile.name) {
|
||||||
|
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, port);
|
||||||
|
agentProcess.start(load_memory, init_message, agent_count);
|
||||||
|
agent_count++;
|
||||||
|
agent_processes[settings.profile.name] = agentProcess;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAgentProcess(agentName) {
|
||||||
|
return agent_processes[agentName];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startAgent(agentName) {
|
||||||
|
if (agent_processes[agentName]) {
|
||||||
|
agent_processes[agentName].continue();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error(`Cannot start agent ${agentName}; not found`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopAgent(agentName) {
|
||||||
|
if (agent_processes[agentName]) {
|
||||||
|
agent_processes[agentName].stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shutdown() {
|
||||||
|
console.log('Shutting down');
|
||||||
|
for (let agentName in agent_processes) {
|
||||||
|
agent_processes[agentName].stop();
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
process.exit(0);
|
||||||
|
}, 2000);
|
||||||
|
}
|
181
src/mindcraft/mindserver.js
Normal file
181
src/mindcraft/mindserver.js
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
import { Server } from 'socket.io';
|
||||||
|
import express from 'express';
|
||||||
|
import http from 'http';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
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
|
||||||
|
|
||||||
|
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;
|
||||||
|
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(host_public = false, port = 8080) {
|
||||||
|
const app = express();
|
||||||
|
server = http.createServer(app);
|
||||||
|
io = new Server(server);
|
||||||
|
|
||||||
|
// Serve static files
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
|
||||||
|
// Socket.io connection handling
|
||||||
|
io.on('connection', (socket) => {
|
||||||
|
let curAgentName = null;
|
||||||
|
console.log('Client connected');
|
||||||
|
|
||||||
|
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 });
|
||||||
|
} else {
|
||||||
|
callback({ error: `Agent '${agentName}' not found.` });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('login-agent', (agentName) => {
|
||||||
|
if (agent_connections[agentName]) {
|
||||||
|
agent_connections[agentName].socket = socket;
|
||||||
|
agent_connections[agentName].in_game = true;
|
||||||
|
curAgentName = agentName;
|
||||||
|
agentsUpdate();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.warn(`Unregistered agent ${agentName} tried to login`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
if (agent_connections[curAgentName]) {
|
||||||
|
console.log(`Agent ${curAgentName} disconnected`);
|
||||||
|
agent_connections[curAgentName].in_game = false;
|
||||||
|
agentsUpdate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('chat-message', (agentName, json) => {
|
||||||
|
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}`);
|
||||||
|
agent_connections[agentName].socket.emit('chat-message', curAgentName, json);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('restart-agent', (agentName) => {
|
||||||
|
console.log(`Restarting agent: ${agentName}`);
|
||||||
|
agent_connections[agentName].socket.emit('restart-agent');
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('stop-agent', (agentName) => {
|
||||||
|
mindcraft.stopAgent(agentName);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('start-agent', (agentName) => {
|
||||||
|
mindcraft.startAgent(agentName);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('stop-all-agents', () => {
|
||||||
|
console.log('Killing all agents');
|
||||||
|
for (let agentName in agent_connections) {
|
||||||
|
mindcraft.stopAgent(agentName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('shutdown', () => {
|
||||||
|
console.log('Shutting down');
|
||||||
|
for (let agentName in agent_connections) {
|
||||||
|
mindcraft.stopAgent(agentName);
|
||||||
|
}
|
||||||
|
// wait 2 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('Exiting MindServer');
|
||||||
|
process.exit(0);
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('send-message', (agentName, message) => {
|
||||||
|
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}`);
|
||||||
|
agent_connections[agentName].socket.emit('send-message', agentName, message)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error: ', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let host = host_public ? '0.0.0.0' : 'localhost';
|
||||||
|
server.listen(port, host, () => {
|
||||||
|
console.log(`MindServer running on port ${port}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
function agentsUpdate(socket) {
|
||||||
|
if (!socket) {
|
||||||
|
socket = io;
|
||||||
|
}
|
||||||
|
let agents = [];
|
||||||
|
for (let agentName in agent_connections) {
|
||||||
|
agents.push({name: agentName, in_game: agent_connections[agentName].in_game});
|
||||||
|
};
|
||||||
|
socket.emit('agents-update', agents);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: export these if you need access to them from other files
|
||||||
|
export const getIO = () => io;
|
||||||
|
export const getServer = () => server;
|
|
@ -64,10 +64,44 @@
|
||||||
<h1>Mindcraft</h1>
|
<h1>Mindcraft</h1>
|
||||||
<div id="agents"></div>
|
<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>
|
<script>
|
||||||
const socket = io();
|
const socket = io();
|
||||||
const agentsDiv = document.getElementById('agents');
|
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) => {
|
socket.on('agents-update', (agents) => {
|
||||||
agentsDiv.innerHTML = agents.length ?
|
agentsDiv.innerHTML = agents.length ?
|
||||||
agents.map(agent => `
|
agents.map(agent => `
|
|
@ -4,7 +4,7 @@ import { getCommandDocs } from '../agent/commands/index.js';
|
||||||
import { SkillLibrary } from "../agent/library/skill_library.js";
|
import { SkillLibrary } from "../agent/library/skill_library.js";
|
||||||
import { stringifyTurns } from '../utils/text.js';
|
import { stringifyTurns } from '../utils/text.js';
|
||||||
import { getCommand } from '../agent/commands/index.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 { Gemini } from './gemini.js';
|
||||||
import { GPT } from './gpt.js';
|
import { GPT } from './gpt.js';
|
||||||
|
@ -30,11 +30,18 @@ const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
export class Prompter {
|
export class Prompter {
|
||||||
constructor(agent, fp) {
|
constructor(agent, profile) {
|
||||||
this.agent = agent;
|
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 default_profile = JSON.parse(readFileSync('./profiles/defaults/_default.json', 'utf8'));
|
||||||
let base_fp = settings.base_profile;
|
let base_fp = '';
|
||||||
|
if (settings.base_profile.includes('survival')) {
|
||||||
|
base_fp = './profiles/defaults/survival.json';
|
||||||
|
} else if (settings.base_profile.includes('creative')) {
|
||||||
|
base_fp = './profiles/defaults/creative.json';
|
||||||
|
} else if (settings.base_profile.includes('god_mode')) {
|
||||||
|
base_fp = './profiles/defaults/god_mode.json';
|
||||||
|
}
|
||||||
let base_profile = JSON.parse(readFileSync(base_fp, 'utf8'));
|
let base_profile = JSON.parse(readFileSync(base_fp, 'utf8'));
|
||||||
|
|
||||||
// first use defaults to fill in missing values in the base profile
|
// first use defaults to fill in missing values in the base profile
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
import { mainProxy } from './main_proxy.js';
|
import { logoutAgent } from '../mindcraft/mindserver.js';
|
||||||
|
|
||||||
export class AgentProcess {
|
export class AgentProcess {
|
||||||
start(profile, load_memory=false, init_message=null, count_id=0, task_path=null, task_id=null) {
|
constructor(name, port) {
|
||||||
this.profile = profile;
|
this.name = name;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
start(load_memory=false, init_message=null, count_id=0) {
|
||||||
this.count_id = count_id;
|
this.count_id = count_id;
|
||||||
this.running = true;
|
this.running = true;
|
||||||
|
|
||||||
let args = ['src/process/init_agent.js', this.name];
|
let args = ['src/process/init_agent.js', this.name];
|
||||||
args.push('-p', profile);
|
args.push('-n', this.name);
|
||||||
args.push('-c', count_id);
|
args.push('-c', count_id);
|
||||||
if (load_memory)
|
if (load_memory)
|
||||||
args.push('-l', load_memory);
|
args.push('-l', load_memory);
|
||||||
if (init_message)
|
if (init_message)
|
||||||
args.push('-m', init_message);
|
args.push('-m', init_message);
|
||||||
if (task_path)
|
args.push('-p', this.port);
|
||||||
args.push('-t', task_path);
|
|
||||||
if (task_id)
|
|
||||||
args.push('-i', task_id);
|
|
||||||
|
|
||||||
const agentProcess = spawn('node', args, {
|
const agentProcess = spawn('node', args, {
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
|
@ -28,7 +29,7 @@ export class AgentProcess {
|
||||||
agentProcess.on('exit', (code, signal) => {
|
agentProcess.on('exit', (code, signal) => {
|
||||||
console.log(`Agent process exited with code ${code} and signal ${signal}`);
|
console.log(`Agent process exited with code ${code} and signal ${signal}`);
|
||||||
this.running = false;
|
this.running = false;
|
||||||
mainProxy.logoutAgent(this.name);
|
logoutAgent(this.name);
|
||||||
|
|
||||||
if (code > 1) {
|
if (code > 1) {
|
||||||
console.log(`Ending task`);
|
console.log(`Ending task`);
|
||||||
|
@ -38,11 +39,11 @@ export class AgentProcess {
|
||||||
if (code !== 0 && signal !== 'SIGINT') {
|
if (code !== 0 && signal !== 'SIGINT') {
|
||||||
// agent must run for at least 10 seconds before restarting
|
// agent must run for at least 10 seconds before restarting
|
||||||
if (Date.now() - last_restart < 10000) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
console.log('Restarting agent...');
|
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, this.port);
|
||||||
last_restart = Date.now();
|
last_restart = Date.now();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -61,7 +62,7 @@ export class AgentProcess {
|
||||||
|
|
||||||
continue() {
|
continue() {
|
||||||
if (!this.running) {
|
if (!this.running) {
|
||||||
this.start(this.profile, true, 'Agent process restarted.', this.count_id);
|
this.start(true, 'Agent process restarted.', this.count_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,27 +1,18 @@
|
||||||
import { Agent } from '../agent/agent.js';
|
import { Agent } from '../agent/agent.js';
|
||||||
|
import { serverProxy } from '../agent/mindserver_proxy.js';
|
||||||
import yargs from 'yargs';
|
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);
|
const args = process.argv.slice(2);
|
||||||
if (args.length < 1) {
|
if (args.length < 1) {
|
||||||
console.log('Usage: node init_agent.js <agent_name> [profile] [load_memory] [init_message]');
|
console.log('Usage: node init_agent.js -n <agent_name> -p <port> -l <load_memory> -m <init_message> -c <count_id>');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const argv = yargs(args)
|
const argv = yargs(args)
|
||||||
.option('profile', {
|
.option('name', {
|
||||||
alias: 'p',
|
alias: 'n',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'profile filepath to use for agent'
|
description: 'name of agent'
|
||||||
})
|
})
|
||||||
.option('load_memory', {
|
.option('load_memory', {
|
||||||
alias: 'l',
|
alias: 'l',
|
||||||
|
@ -33,29 +24,27 @@ const argv = yargs(args)
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'automatically prompt the agent on startup'
|
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', {
|
.option('count_id', {
|
||||||
alias: 'c',
|
alias: 'c',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
default: 0,
|
default: 0,
|
||||||
description: 'identifying count for multi-agent scenarios',
|
description: 'identifying count for multi-agent scenarios',
|
||||||
}).argv;
|
})
|
||||||
|
.option('port', {
|
||||||
|
alias: 'p',
|
||||||
|
type: 'number',
|
||||||
|
description: 'port of mindserver'
|
||||||
|
})
|
||||||
|
.argv;
|
||||||
|
|
||||||
// Wrap agent start in async IIFE with proper error handling
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
console.log('Starting agent with profile:', argv.profile);
|
console.log('Connecting to MindServer');
|
||||||
|
await serverProxy.connect(argv.name, argv.port);
|
||||||
|
console.log('Starting agent');
|
||||||
const agent = new 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) {
|
} catch (error) {
|
||||||
console.error('Failed to start agent process:');
|
console.error('Failed to start agent process:');
|
||||||
console.error(error.message);
|
console.error(error.message);
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
import { io } from 'socket.io-client';
|
|
||||||
import settings from '../../settings.js';
|
|
||||||
|
|
||||||
// Singleton mindserver proxy for the main process
|
|
||||||
class MainProxy {
|
|
||||||
constructor() {
|
|
||||||
if (MainProxy.instance) {
|
|
||||||
return MainProxy.instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.socket = null;
|
|
||||||
this.connected = false;
|
|
||||||
this.agent_processes = {};
|
|
||||||
MainProxy.instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
connect() {
|
|
||||||
if (this.connected) return;
|
|
||||||
|
|
||||||
this.socket = io(`http://${settings.mindserver_host}:${settings.mindserver_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 mainProxy = new MainProxy();
|
|
|
@ -1,163 +0,0 @@
|
||||||
import { Server } from 'socket.io';
|
|
||||||
import express from 'express';
|
|
||||||
import http from 'http';
|
|
||||||
import path from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
|
|
||||||
// Module-level variables
|
|
||||||
let io;
|
|
||||||
let server;
|
|
||||||
const registeredAgents = new Set();
|
|
||||||
const inGameAgents = {};
|
|
||||||
const agentManagers = {}; // socket for main process that registers/controls agents
|
|
||||||
|
|
||||||
// Initialize the server
|
|
||||||
export function createMindServer(port = 8080) {
|
|
||||||
const app = express();
|
|
||||||
server = http.createServer(app);
|
|
||||||
io = new Server(server);
|
|
||||||
|
|
||||||
// Serve static files
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
||||||
app.use(express.static(path.join(__dirname, 'public')));
|
|
||||||
|
|
||||||
// Socket.io connection handling
|
|
||||||
io.on('connection', (socket) => {
|
|
||||||
let curAgentName = null;
|
|
||||||
console.log('Client connected');
|
|
||||||
|
|
||||||
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.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)) {
|
|
||||||
curAgentName = agentName;
|
|
||||||
inGameAgents[agentName] = socket;
|
|
||||||
agentsUpdate();
|
|
||||||
} else {
|
|
||||||
console.warn(`Agent ${agentName} not registered`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('logout-agent', (agentName) => {
|
|
||||||
if (inGameAgents[agentName]) {
|
|
||||||
delete inGameAgents[agentName];
|
|
||||||
agentsUpdate();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
|
||||||
console.log('Client disconnected');
|
|
||||||
if (inGameAgents[curAgentName]) {
|
|
||||||
delete inGameAgents[curAgentName];
|
|
||||||
agentsUpdate();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('chat-message', (agentName, json) => {
|
|
||||||
if (!inGameAgents[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);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('restart-agent', (agentName) => {
|
|
||||||
console.log(`Restarting agent: ${agentName}`);
|
|
||||||
inGameAgents[agentName].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}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('start-agent', (agentName) => {
|
|
||||||
let manager = agentManagers[agentName];
|
|
||||||
if (manager) {
|
|
||||||
manager.emit('start-agent', agentName);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.warn(`Starting unregisterd agent ${agentName}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('stop-all-agents', () => {
|
|
||||||
console.log('Killing all agents');
|
|
||||||
stopAllAgents();
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('shutdown', () => {
|
|
||||||
console.log('Shutting down');
|
|
||||||
for (let manager of Object.values(agentManagers)) {
|
|
||||||
manager.emit('shutdown');
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
process.exit(0);
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('send-message', (agentName, message) => {
|
|
||||||
if (!inGameAgents[agentName]) {
|
|
||||||
console.warn(`Agent ${agentName} not logged in, cannot send message via MindServer.`);
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
console.log(`Sending message to agent ${agentName}: ${message}`);
|
|
||||||
inGameAgents[agentName].emit('send-message', agentName, message)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error: ', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
server.listen(port, 'localhost', () => {
|
|
||||||
console.log(`MindServer running on port ${port}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
function agentsUpdate(socket) {
|
|
||||||
if (!socket) {
|
|
||||||
socket = io;
|
|
||||||
}
|
|
||||||
let agents = [];
|
|
||||||
registeredAgents.forEach(name => {
|
|
||||||
agents.push({name, in_game: !!inGameAgents[name]});
|
|
||||||
});
|
|
||||||
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;
|
|
|
@ -1,5 +1,5 @@
|
||||||
import minecraftData from 'minecraft-data';
|
import minecraftData from 'minecraft-data';
|
||||||
import settings from '../../settings.js';
|
import settings from '../agent/settings.js';
|
||||||
import { createBot } from 'mineflayer';
|
import { createBot } from 'mineflayer';
|
||||||
import prismarine_items from 'prismarine-item';
|
import prismarine_items from 'prismarine-item';
|
||||||
import { pathfinder } from 'mineflayer-pathfinder';
|
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 as autoEat } from 'mineflayer-auto-eat';
|
||||||
import plugin from 'mineflayer-armor-manager';
|
import plugin from 'mineflayer-armor-manager';
|
||||||
const armorManager = plugin;
|
const armorManager = plugin;
|
||||||
|
let mc_version = null;
|
||||||
const mc_version = settings.minecraft_version;
|
let mcdata = null;
|
||||||
const mcdata = minecraftData(mc_version);
|
let Item = null;
|
||||||
const Item = prismarine_items(mc_version);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {string} ItemName
|
* @typedef {string} ItemName
|
||||||
|
@ -54,6 +53,9 @@ export const WOOL_COLORS = [
|
||||||
|
|
||||||
|
|
||||||
export function initBot(username) {
|
export function initBot(username) {
|
||||||
|
mc_version = settings.minecraft_version;
|
||||||
|
mcdata = minecraftData(mc_version);
|
||||||
|
Item = prismarine_items(mc_version);
|
||||||
let bot = createBot({
|
let bot = createBot({
|
||||||
username: username,
|
username: username,
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import translate from 'google-translate-api-x';
|
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) {
|
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;
|
return message;
|
||||||
try {
|
try {
|
||||||
const translation = await translate(message, { to: preferred_lang });
|
const translation = await translate(message, { to: preferred_lang });
|
||||||
|
@ -16,7 +17,8 @@ export async function handleTranslation(message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleEnglishTranslation(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;
|
return message;
|
||||||
try {
|
try {
|
||||||
const translation = await translate(message, { to: 'english' });
|
const translation = await translate(message, { to: 'english' });
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "debug",
|
"type": "debug",
|
||||||
"timeout": 60
|
"timeout": 25
|
||||||
},
|
},
|
||||||
"debug_2_agent_timeout": {
|
"debug_2_agent_timeout": {
|
||||||
"goal": "Just stand at a place and don't do anything",
|
"goal": "Just stand at a place and don't do anything",
|
||||||
|
|
Loading…
Add table
Reference in a new issue