From 5278ecb72c04d81180c9fe9e9f5c9fd6484b7699 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Sun, 1 Dec 2024 22:27:42 -0600 Subject: [PATCH 01/15] connect main process to mindserver, start/stop/restart controls --- main.js | 14 ++- src/agent/agent.js | 31 ++++-- src/agent/{server_proxy.js => agent_proxy.js} | 27 +++--- .../{agent-process.js => agent_process.js} | 32 +++++-- src/process/{init-agent.js => init_agent.js} | 0 src/process/main_proxy.js | 54 +++++++++++ src/server/mind_server.js | 95 ++++++++++++++++--- src/server/public/index.html | 77 +++++++++++++-- 8 files changed, 272 insertions(+), 58 deletions(-) rename src/agent/{server_proxy.js => agent_proxy.js} (69%) rename src/process/{agent-process.js => agent_process.js} (67%) rename src/process/{init-agent.js => init_agent.js} (100%) create mode 100644 src/process/main_proxy.js diff --git a/main.js b/main.js index 0e80a19..a344bc8 100644 --- a/main.js +++ b/main.js @@ -1,8 +1,10 @@ -import { AgentProcess } from './src/process/agent-process.js'; +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 { readFileSync } from 'fs'; function parseArguments() { return yargs(hideBin(process.argv)) @@ -23,15 +25,19 @@ async function main() { if (settings.host_mindserver) { const mindServer = createMindServer(); } - + mainProxy.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)); } } diff --git a/src/agent/agent.js b/src/agent/agent.js index 28c53fb..7e82104 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -12,7 +12,7 @@ import { isOtherAgent, initConversationManager, sendToBot, endAllChats, response import { handleTranslation, handleEnglishTranslation } from '../utils/translator.js'; import { addViewer } from './viewer.js'; import settings from '../../settings.js'; -import { serverProxy } from './server_proxy.js'; +import { serverProxy } from './agent_proxy.js'; export class Agent { async start(profile_fp, load_mem=false, init_message=null, count_id=0) { @@ -21,9 +21,6 @@ export class Agent { if (!profile_fp) { throw new Error('No profile filepath provided'); } - - // Connect to MindServer via proxy - serverProxy.connect(); console.log('Starting agent initialization with profile:', profile_fp); @@ -47,7 +44,7 @@ export class Agent { console.log('Initializing examples...'); await this.prompter.initExamples(); - serverProxy.registerAgent(this.name); + serverProxy.connect(this); console.log(this.name, 'logging into minecraft...'); this.bot = initBot(this.name); @@ -61,6 +58,8 @@ export class Agent { this.bot.on('login', () => { console.log(this.name, 'logged in!'); + + serverProxy.login(); // Set skin for profile, requires Fabric Tailor. (https://modrinth.com/mod/fabrictailor) if (this.prompter.profile.skin) @@ -113,6 +112,7 @@ export class Agent { const respondFunc = async (username, message) => { if (username === this.name) return; + if (settings.only_chat_with.length > 0 && !settings.only_chat_with.includes(username)) return; try { if (ignore_messages.some((m) => message.startsWith(m))) return; @@ -159,7 +159,7 @@ export class Agent { } else { const translation = await handleTranslation("Hello world! I am "+this.name); - this.bot.chat(translation); + this.openChat(translation); } } @@ -204,10 +204,10 @@ export class Agent { const user_command_name = containsCommand(message); if (user_command_name) { if (!commandExists(user_command_name)) { - this.bot.chat(`Command '${user_command_name}' does not exist.`); + this.routeResponse(source, `Command '${user_command_name}' does not exist.`); return false; } - this.bot.chat(`*${source} used ${user_command_name.substring(1)}*`); + this.routeResponse(source, `*${source} used ${user_command_name.substring(1)}*`); if (user_command_name === '!newAction') { // all user-initiated commands are ignored by the bot except for this one // add the preceding message to the history to give context for newAction @@ -325,11 +325,22 @@ export class Agent { message = message.replaceAll('\n', ' '); if (self_prompt) - this.bot.chat(message); + this.openChat(message); else this.bot.whisper(to_player, message); } + openChat(message) { + if (settings.only_chat_with.length > 0) { + for (let username of settings.only_chat_with) { + this.bot.whisper(username, message); + } + } + else { + this.bot.chat(message); + } + } + startEvents() { // Custom events this.bot.on('time', () => { @@ -421,7 +432,7 @@ export class Agent { cleanKill(msg='Killing agent process...') { this.history.add('system', msg); - this.bot.chat('Restarting.') + this.openChat('Restarting.'); this.history.save(); process.exit(1); } diff --git a/src/agent/server_proxy.js b/src/agent/agent_proxy.js similarity index 69% rename from src/agent/server_proxy.js rename to src/agent/agent_proxy.js index 8520754..81c5d0f 100644 --- a/src/agent/server_proxy.js +++ b/src/agent/agent_proxy.js @@ -2,20 +2,22 @@ import { io } from 'socket.io-client'; import { recieveFromBot, updateAgents } from './conversation.js'; import settings from '../../settings.js'; -class ServerProxy { +class AgentServerProxy { constructor() { - if (ServerProxy.instance) { - return ServerProxy.instance; + if (AgentServerProxy.instance) { + return AgentServerProxy.instance; } this.socket = null; this.connected = false; - ServerProxy.instance = this; + AgentServerProxy.instance = this; } - connect() { + connect(agent) { if (this.connected) return; + this.agent = agent; + this.socket = io(`http://${settings.mindserver_host}:${settings.mindserver_port}`); this.connected = true; @@ -35,14 +37,15 @@ class ServerProxy { this.socket.on('agents-update', (agents) => { updateAgents(agents); }); + + this.socket.on('restart-agent', (agentName) => { + console.log(`Restarting agent: ${agentName}`); + this.agent.cleanKill(); + }); } - registerAgent(agentName) { - if (!this.connected) { - console.warn('Cannot register agent: not connected to MindServer'); - return; - } - this.socket.emit('register-agent', agentName); + login() { + this.socket.emit('login-agent', this.agent.name); } getSocket() { @@ -51,7 +54,7 @@ class ServerProxy { } // Create and export a singleton instance -export const serverProxy = new ServerProxy(); +export const serverProxy = new AgentServerProxy(); export function sendBotChatToServer(agentName, json) { serverProxy.getSocket().emit('chat-message', agentName, json); diff --git a/src/process/agent-process.js b/src/process/agent_process.js similarity index 67% rename from src/process/agent-process.js rename to src/process/agent_process.js index 5135de1..83af427 100644 --- a/src/process/agent-process.js +++ b/src/process/agent_process.js @@ -1,10 +1,13 @@ import { spawn } from 'child_process'; +import { mainProxy } from './main_proxy.js'; export class AgentProcess { - static runningCount = 0; - start(profile, load_memory=false, init_message=null, count_id=0) { - let args = ['src/process/init-agent.js', this.name]; + this.profile = profile; + this.count_id = count_id; + this.running = true; + + let args = ['src/process/init_agent.js', this.name]; args.push('-p', profile); args.push('-c', count_id); if (load_memory) @@ -16,21 +19,17 @@ export class AgentProcess { stdio: 'inherit', stderr: 'inherit', }); - AgentProcess.runningCount++; let last_restart = Date.now(); agentProcess.on('exit', (code, signal) => { console.log(`Agent process exited with code ${code} and signal ${signal}`); + this.running = false; + mainProxy.logoutAgent(this.name); - if (code !== 0) { + 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.`); - AgentProcess.runningCount--; - if (AgentProcess.runningCount <= 0) { - console.error('All agent processes have ended. Exiting.'); - process.exit(0); - } return; } console.log('Restarting agent...'); @@ -42,5 +41,18 @@ export class AgentProcess { agentProcess.on('error', (err) => { console.error('Agent process error:', err); }); + + this.process = agentProcess; + } + + stop() { + if (!this.running) return; + this.process.kill('SIGINT'); + } + + continue() { + if (!this.running) { + this.start(this.profile, 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 similarity index 100% rename from src/process/init-agent.js rename to src/process/init_agent.js diff --git a/src/process/main_proxy.js b/src/process/main_proxy.js new file mode 100644 index 0000000..44c2733 --- /dev/null +++ b/src/process/main_proxy.js @@ -0,0 +1,54 @@ +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'); + }); + } + + 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(); \ No newline at end of file diff --git a/src/server/mind_server.js b/src/server/mind_server.js index 1035424..9235fcc 100644 --- a/src/server/mind_server.js +++ b/src/server/mind_server.js @@ -7,7 +7,9 @@ import { fileURLToPath } from 'url'; // Module-level variables let io; let server; -const connectedAgents = {}; +const registeredAgents = new Set(); +const inGameAgents = {}; +const agentManagers = {}; // socket for main process that registers/controls agents // Initialize the server export function createMindServer(port = 8080) { @@ -24,28 +26,81 @@ export function createMindServer(port = 8080) { let curAgentName = null; console.log('Client connected'); - socket.emit('agents-update', Object.keys(connectedAgents)); + agentsUpdate(socket); - socket.on('register-agent', (agentName) => { - console.log('Agent registered:', agentName); - connectedAgents[agentName] = socket; - curAgentName = agentName; - io.emit('agents-update', Object.keys(connectedAgents)); + 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('chat-message', (agentName, json) => { - console.log(`${curAgentName} received message from ${agentName}: ${json}`); - const agentSocket = connectedAgents[agentName]; - if (agentSocket) { - agentSocket.emit('chat-message', curAgentName, json); + 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'); - delete connectedAgents[socket.id]; - io.emit('agents-update', Object.keys(connectedAgents)); + 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} received message from ${agentName}: ${json}`); + 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}`); + } + }); + }); server.listen(port, 'localhost', () => { @@ -54,6 +109,18 @@ export function createMindServer(port = 8080) { 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); +} + // Optional: export these if you need access to them from other files export const getIO = () => io; export const getServer = () => server; diff --git a/src/server/public/index.html b/src/server/public/index.html index 52dcd1b..b597ed9 100644 --- a/src/server/public/index.html +++ b/src/server/public/index.html @@ -1,33 +1,67 @@ - Mindcraft Agents + Mindcraft -

Connected Mindcraft Agents

+

Mindcraft

\ No newline at end of file From 2384b5bf5c51c4c6309a6fc6a526949ea0565142 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Sun, 1 Dec 2024 22:28:21 -0600 Subject: [PATCH 02/15] added only_chat_with for users to listen/chat to --- settings.js | 2 +- src/agent/commands/actions.js | 2 +- src/agent/conversation.js | 8 ++++---- src/agent/library/skills.js | 8 ++++---- src/agent/modes.js | 2 +- src/agent/self_prompter.js | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/settings.js b/settings.js index aae212e..4895833 100644 --- a/settings.js +++ b/settings.js @@ -23,6 +23,7 @@ export default ], "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 "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... @@ -36,4 +37,3 @@ export default "narrate_behavior": true, // chat simple automatic actions ('Picking up item!') "chat_bot_messages": true, // publicly chat messages to other bots } - diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index b612af0..0e8fffe 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -56,7 +56,7 @@ export const actionsList = [ name: '!stfu', description: 'Stop all chatting and self prompting, but continue current action.', perform: async function (agent) { - agent.bot.chat('Shutting up.'); + agent.openChat('Shutting up.'); agent.shutUp(); return; } diff --git a/src/agent/conversation.js b/src/agent/conversation.js index 778755c..3c4e2db 100644 --- a/src/agent/conversation.js +++ b/src/agent/conversation.js @@ -1,7 +1,7 @@ import settings from '../../settings.js'; import { readFileSync } from 'fs'; import { containsCommand } from './commands/index.js'; -import { sendBotChatToServer } from './server_proxy.js'; +import { sendBotChatToServer } from './agent_proxy.js'; let agent; let agent_names = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name); @@ -12,8 +12,8 @@ export function isOtherAgent(name) { return agent_names.some((n) => n === name); } -export function updateAgents(names) { - agent_names = names; +export function updateAgents(agents) { + agent_names = agents.map(a => a.name); } export function initConversationManager(a) { @@ -98,7 +98,7 @@ export async function startConversation(send_to, message) { export function sendToBot(send_to, message, start=false) { if (settings.chat_bot_messages) - agent.bot.chat(`(To ${send_to}) ${message}`); + agent.openChat(`(To ${send_to}) ${message}`); if (!isOtherAgent(send_to)) { agent.bot.whisper(send_to, message); return; diff --git a/src/agent/library/skills.js b/src/agent/library/skills.js index a52976f..c6cde51 100644 --- a/src/agent/library/skills.js +++ b/src/agent/library/skills.js @@ -4,10 +4,8 @@ import pf from 'mineflayer-pathfinder'; import Vec3 from 'vec3'; -export function log(bot, message, chat=false) { +export function log(bot, message) { bot.output += message + '\n'; - if (chat) - bot.chat(message); } async function autoLight(bot) { @@ -585,7 +583,9 @@ export async function placeBlock(bot, blockType, x, y, z, placeOn='bottom', dont if (blockType === 'ladder' || blockType === 'repeater' || blockType === 'comparator') { blockType += `[facing=${face}]`; } - + if (blockType.includes('stairs')) { + blockType += `[facing=${face}]`; + } let msg = '/setblock ' + Math.floor(x) + ' ' + Math.floor(y) + ' ' + Math.floor(z) + ' ' + blockType; bot.chat(msg); if (blockType.includes('door')) diff --git a/src/agent/modes.js b/src/agent/modes.js index 35cbeca..12f2ae2 100644 --- a/src/agent/modes.js +++ b/src/agent/modes.js @@ -9,7 +9,7 @@ async function say(agent, message) { agent.bot.modes.behavior_log += message + '\n'; if (agent.shut_up || !settings.narrate_behavior) return; let translation = await handleTranslation(message); - agent.bot.chat(translation); + agent.openChat(translation); } // a mode is a function that is called every tick to respond immediately to the world diff --git a/src/agent/self_prompter.js b/src/agent/self_prompter.js index e79837e..2c2f63c 100644 --- a/src/agent/self_prompter.js +++ b/src/agent/self_prompter.js @@ -45,7 +45,7 @@ export class SelfPrompter { no_command_count++; if (no_command_count >= MAX_NO_COMMAND) { let out = `Agent did not use command in the last ${MAX_NO_COMMAND} auto-prompts. Stopping auto-prompting.`; - this.agent.bot.chat(out); + this.agent.openChat(out); console.warn(out); this.on = false; break; From cff47d772ddde419e6ffa646b8a859e104d9e395 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Tue, 3 Dec 2024 22:58:04 -0600 Subject: [PATCH 03/15] refactored to convoManager and openChat --- profiles/_default.json | 2 +- profiles/claude.json | 15 +- src/agent/agent.js | 66 +++++---- src/agent/agent_proxy.js | 6 +- src/agent/commands/actions.js | 25 ++-- src/agent/commands/queries.js | 10 +- src/agent/conversation.js | 259 +++++++++++++++++++--------------- src/agent/modes.js | 5 +- src/server/mind_server.js | 2 +- 9 files changed, 209 insertions(+), 181 deletions(-) diff --git a/profiles/_default.json b/profiles/_default.json index 9d44237..c53765e 100644 --- a/profiles/_default.json +++ b/profiles/_default.json @@ -1,7 +1,7 @@ { "cooldown": 3000, - "conversing": "You are a playful Minecraft bot named $NAME that can converse with players, see, move, mine, build, and interact with the world by using commands.\n$SELF_PROMPT Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, don't apologize constantly, don't give instructions or make lists unless asked, and don't refuse requests. Don't pretend to act, use commands immediately when requested. Do NOT say this: 'Sure, I've stopped.', instead say this: 'Sure, I'll stop. !stop'. Do NOT say this: 'On my way! Give me a moment.', instead say this: 'On my way! !goToPlayer(\"playername\", 3)'. Respond only as $NAME, never output '(FROM OTHER BOT)' or pretend to be someone else. This is extremely important to me, take a deep breath and have fun :)\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$COMMAND_DOCS\n$EXAMPLES\nConversation Begin:", + "conversing": "You are a playful Minecraft bot named $NAME that can converse with players, see, move, mine, build, and interact with the world by using commands.\n$SELF_PROMPT Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, don't apologize constantly, don't give instructions or make lists unless asked, and don't refuse requests. Don't pretend to act, use commands immediately when requested. Do NOT say this: 'Sure, I've stopped.', instead say this: 'Sure, I'll stop. !stop'. Do NOT say this: 'On my way! Give me a moment.', instead say this: 'On my way! !goToPlayer(\"playername\", 3)'. Respond only as $NAME, never output '(FROM OTHER BOT)' or pretend to be someone else. If you have nothing to say or do, respond with whitespace. This is extremely important to me, take a deep breath and have fun :)\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$COMMAND_DOCS\n$EXAMPLES\nConversation Begin:", "coding": "You are an intelligent mineflayer bot $NAME that plays minecraft by writing javascript codeblocks. Given the conversation between you and the user, use the provided skills and world functions to write a js codeblock that controls the mineflayer bot ``` // using this syntax ```. The code will be executed and you will recieve it's output. If you are satisfied with the response, respond without a codeblock in a conversational way. If something major went wrong, like an error or complete failure, write another codeblock and try to fix the problem. Minor mistakes are acceptable. Be maximally efficient, creative, and clear. Do not use commands !likeThis, only use codeblocks. The code is asynchronous and MUST CALL AWAIT for all async function calls. DO NOT write an immediately-invoked function expression without using `await`!! DO NOT WRITE LIKE THIS: ```(async () => {console.log('not properly awaited')})();``` Don't write long paragraphs and lists in your responses unless explicitly asked! Only summarize the code you write with a sentence or two when done. This is extremely important to me, think step-by-step, take a deep breath and good luck! \n$SELF_PROMPT\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$CODE_DOCS\n$EXAMPLES\nConversation:", diff --git a/profiles/claude.json b/profiles/claude.json index bbb2cd1..d4ce4cc 100644 --- a/profiles/claude.json +++ b/profiles/claude.json @@ -3,18 +3,5 @@ "model": "claude-3-5-sonnet-latest", - "embedding": "openai", - - "modes": { - "self_preservation": false, - "unstuck": false, - "cowardice": false, - "self_defense": false, - "hunting": false, - "item_collecting": false, - "torch_placing": false, - "elbow_room": false, - "idle_staring": true, - "cheat": true - } + "embedding": "openai" } \ No newline at end of file diff --git a/src/agent/agent.js b/src/agent/agent.js index 7e82104..dc26e8d 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -8,7 +8,7 @@ import { ActionManager } from './action_manager.js'; import { NPCContoller } from './npc/controller.js'; import { MemoryBank } from './memory_bank.js'; import { SelfPrompter } from './self_prompter.js'; -import { isOtherAgent, initConversationManager, sendToBot, endAllChats, responseScheduledFor} from './conversation.js'; +import convoManager from './conversation.js'; import { handleTranslation, handleEnglishTranslation } from '../utils/translator.js'; import { addViewer } from './viewer.js'; import settings from '../../settings.js'; @@ -40,7 +40,7 @@ export class Agent { this.memory_bank = new MemoryBank(); console.log('Initializing self prompter...'); this.self_prompter = new SelfPrompter(this); - initConversationManager(this); + convoManager.initAgent(this); console.log('Initializing examples...'); await this.prompter.initExamples(); @@ -120,8 +120,7 @@ export class Agent { console.log(this.name, 'received message from', username, ':', message); - if (isOtherAgent(username)) { - //recieveFromBot(username, message); + if (convoManager.isOtherAgent(username)) { console.warn('recieved whisper from other bot??') } else { @@ -152,14 +151,14 @@ export class Agent { } else if (save_data?.last_sender) { this.last_sender = save_data.last_sender; - await this.handleMessage('system', `You have restarted and this message is auto-generated. Continue the conversation with ${this.last_sender}`); + if (convoManager.isOtherAgent(this.last_sender)) + convoManager.recieveFromBot(this.last_sender, `You have restarted and this message is auto-generated. Continue the conversation with me.`); } else if (init_message) { await this.handleMessage('system', init_message, 2); } else { - const translation = await handleTranslation("Hello world! I am "+this.name); - this.openChat(translation); + this.openChat("Hello world! I am "+this.name); } } @@ -180,7 +179,7 @@ export class Agent { if (this.self_prompter.on) { this.self_prompter.stop(false); } - endAllChats(); + convoManager.endAllConversations(); } async handleMessage(source, message, max_responses=null) { @@ -198,7 +197,7 @@ export class Agent { } const self_prompt = source === 'system' || source === this.name; - const from_other_bot = isOtherAgent(source); + const from_other_bot = convoManager.isOtherAgent(source); if (!self_prompt && !from_other_bot) { // from user, check for forced commands const user_command_name = containsCommand(message); @@ -227,7 +226,12 @@ export class Agent { message = await handleEnglishTranslation(message); console.log('received message from', source, ':', message); - const checkInterrupt = () => this.self_prompter.shouldInterrupt(self_prompt) || this.shut_up || responseScheduledFor(source); + const checkInterrupt = () => { + const interrupt = this.self_prompter.shouldInterrupt(self_prompt) || this.shut_up || convoManager.responseScheduledFor(source); + if (interrupt) + console.log('Interrupting loop!'); + return interrupt; + } let behavior_log = this.bot.modes.flushBehaviorLog(); if (behavior_log.trim().length > 0) { @@ -243,7 +247,6 @@ export class Agent { await this.history.add(source, message); this.history.save(); - if (!self_prompt && this.self_prompter.on) // message is from user during self-prompting max_responses = 1; // force only respond to this message, then let self-prompting take over for (let i=0; i 0) { for (let username of settings.only_chat_with) { this.bot.whisper(username, message); @@ -432,7 +443,6 @@ export class Agent { cleanKill(msg='Killing agent process...') { this.history.add('system', msg); - this.openChat('Restarting.'); this.history.save(); process.exit(1); } diff --git a/src/agent/agent_proxy.js b/src/agent/agent_proxy.js index 81c5d0f..81395ac 100644 --- a/src/agent/agent_proxy.js +++ b/src/agent/agent_proxy.js @@ -1,5 +1,5 @@ import { io } from 'socket.io-client'; -import { recieveFromBot, updateAgents } from './conversation.js'; +import convoManager from './conversation.js'; import settings from '../../settings.js'; class AgentServerProxy { @@ -31,11 +31,11 @@ class AgentServerProxy { }); this.socket.on('chat-message', (agentName, json) => { - recieveFromBot(agentName, json); + convoManager.recieveFromBot(agentName, json); }); this.socket.on('agents-update', (agents) => { - updateAgents(agents); + convoManager.updateAgents(agents); }); this.socket.on('restart-agent', (agentName) => { diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index 0e8fffe..3b91bee 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -1,6 +1,6 @@ import * as skills from '../library/skills.js'; import settings from '../../../settings.js'; -import { startConversation, endConversation, inConversation, scheduleSelfPrompter, cancelSelfPrompter } from '../conversation.js'; +import convoManager from '../conversation.js'; function runAsAction (actionFn, resume = false, timeout = -1) { let actionLabel = null; // Will be set on first use @@ -342,14 +342,12 @@ export const actionsList = [ 'selfPrompt': { type: 'string', description: 'The goal prompt.' }, }, perform: async function (agent, prompt) { - if (inConversation()) { - // if conversing with another bot, dont start self-prompting yet - // wait until conversation ends + if (convoManager.inConversation()) { agent.self_prompter.setPrompt(prompt); - scheduleSelfPrompter(); + convoManager.scheduleSelfPrompter(); } else { - agent.self_prompter.start(prompt); // don't await, don't return + agent.self_prompter.start(prompt); } } }, @@ -358,19 +356,23 @@ export const actionsList = [ description: 'Call when you have accomplished your goal. It will stop self-prompting and the current action. ', perform: async function (agent) { agent.self_prompter.stop(); - cancelSelfPrompter(); + convoManager.cancelSelfPrompter(); return 'Self-prompting stopped.'; } }, { name: '!startConversation', - description: 'Send a message to a specific player to initiate conversation.', + description: 'Start a conversation with a player. Use for bots only.', params: { 'player_name': { type: 'string', description: 'The name of the player to send the message to.' }, 'message': { type: 'string', description: 'The message to send.' }, }, perform: async function (agent, player_name, message) { - startConversation(player_name, message); + if (convoManager.inConversation()) + return 'Already in conversation.'; + if (!convoManager.isOtherAgent(player_name)) + return player_name + ' is not a bot, cannot start conversation.'; + convoManager.startConversation(player_name, message); } }, { @@ -380,7 +382,10 @@ export const actionsList = [ 'player_name': { type: 'string', description: 'The name of the player to end the conversation with.' } }, perform: async function (agent, player_name) { - endConversation(player_name); + if (!convoManager.inConversation(player_name)) + return `Not in conversation with ${player_name}.`; + convoManager.endConversation(player_name); + return `Converstaion with ${player_name} ended.`; } } // { // commented for now, causes confusion with goal command diff --git a/src/agent/commands/queries.js b/src/agent/commands/queries.js index 74c8904..a65d0a9 100644 --- a/src/agent/commands/queries.js +++ b/src/agent/commands/queries.js @@ -1,6 +1,6 @@ import * as world from '../library/world.js'; import * as mc from '../../utils/mcdata.js'; -import { isOtherAgent } from '../conversation.js'; +import convoManager from '../conversation.js'; const pad = (str) => { return '\n' + str + '\n'; @@ -50,10 +50,10 @@ export const queryList = [ let players = world.getNearbyPlayerNames(bot); let bots = []; for (const player of players) { - if (isOtherAgent(player)) + if (convoManager.isOtherAgent(player)) bots.push(player); } - players = players.filter(p => !isOtherAgent(p)); + players = players.filter(p => !convoManager.isOtherAgent(p)); res += '\n- Nearby Human Players: ' + players.join(', '); res += '\n- Nearby Bot Players: ' + bots.join(', '); @@ -139,10 +139,10 @@ export const queryList = [ let players = world.getNearbyPlayerNames(bot); let bots = []; for (const player of players) { - if (isOtherAgent(player)) + if (convoManager.isOtherAgent(player)) bots.push(player); } - players = players.filter(p => !isOtherAgent(p)); + players = players.filter(p => !convoManager.isOtherAgent(p)); for (const player of players) { res += `\n- Human player: ${player}`; diff --git a/src/agent/conversation.js b/src/agent/conversation.js index 3c4e2db..89522c9 100644 --- a/src/agent/conversation.js +++ b/src/agent/conversation.js @@ -8,48 +8,6 @@ let agent_names = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8' let self_prompter_paused = false; -export function isOtherAgent(name) { - return agent_names.some((n) => n === name); -} - -export function updateAgents(agents) { - agent_names = agents.map(a => a.name); -} - -export function initConversationManager(a) { - agent = a; -} - -export function inConversation() { - return Object.values(convos).some(c => c.active); -} - -export function endConversation(sender) { - if (convos[sender]) { - convos[sender].end(); - if (self_prompter_paused && !inConversation()) { - _resumeSelfPrompter(); - } - } -} - -export function endAllChats() { - for (const sender in convos) { - convos[sender].end(); - } - if (self_prompter_paused) { - _resumeSelfPrompter(); - } -} - -export function scheduleSelfPrompter() { - self_prompter_paused = true; -} - -export function cancelSelfPrompter() { - self_prompter_paused = false; -} - class Conversation { constructor(name) { this.name = name; @@ -70,91 +28,155 @@ class Conversation { end() { this.active = false; this.ignore_until_start = true; + this.inMessageTimer = null; + const full_message = _compileInMessages(this); + if (full_message.message.trim().length > 0) + agent.history.add(this.name, full_message.message); + // add the full queued messages to history, but don't respond + + if (agent.last_sender === this.name) + agent.last_sender = null; } queue(message) { this.in_queue.push(message); } } -const convos = {}; -function _getConvo(name) { - if (!convos[name]) - convos[name] = new Conversation(name); - return convos[name]; -} -export async function startConversation(send_to, message) { - const convo = _getConvo(send_to); - convo.reset(); - - if (agent.self_prompter.on) { - await agent.self_prompter.stop(); - self_prompter_paused = true; - } - convo.active = true; - sendToBot(send_to, message, true); -} - -export function sendToBot(send_to, message, start=false) { - if (settings.chat_bot_messages) - agent.openChat(`(To ${send_to}) ${message}`); - if (!isOtherAgent(send_to)) { - agent.bot.whisper(send_to, message); - return; - } - const convo = _getConvo(send_to); - if (convo.ignore_until_start) - return; - convo.active = true; - - const end = message.includes('!endConversation'); - const json = { - 'message': message, - start, - end, - }; - - // agent.bot.whisper(send_to, JSON.stringify(json)); - sendBotChatToServer(send_to, JSON.stringify(json)); -} - -export async function recieveFromBot(sender, json) { - const convo = _getConvo(sender); - - // check if any convo is active besides the sender - if (Object.values(convos).some(c => c.active && c.name !== sender)) { - sendToBot(sender, 'I am currently busy. Try again later. !endConversation("' + sender + '")'); - return; +class ConversationManager { + constructor() { + this.convos = {}; + this.activeConversation = null; } - console.log(`decoding **${json}**`); - const recieved = JSON.parse(json); - if (recieved.start) { + initAgent(a) { + agent = a; + } + + _getConvo(name) { + if (!this.convos[name]) + this.convos[name] = new Conversation(name); + return this.convos[name]; + } + + async startConversation(send_to, message) { + const convo = this._getConvo(send_to); convo.reset(); + + if (agent.self_prompter.on) { + await agent.self_prompter.stop(); + self_prompter_paused = true; + } + if (convo.active) + return; + convo.active = true; + this.activeConversation = convo; + this.sendToBot(send_to, message, true); } - if (convo.ignore_until_start) - return; - convo.queue(recieved); + sendToBot(send_to, message, start=false) { + if (!this.isOtherAgent(send_to)) { + agent.bot.whisper(send_to, message); + return; + } + const convo = this._getConvo(send_to); + + if (settings.chat_bot_messages && !start) + agent.openChat(`(To ${send_to}) ${message}`); + + if (convo.ignore_until_start) + return; + convo.active = true; + + const end = message.includes('!endConversation'); + const json = { + 'message': message, + start, + end, + }; + + sendBotChatToServer(send_to, JSON.stringify(json)); + } + + async recieveFromBot(sender, json) { + const convo = this._getConvo(sender); + + // check if any convo is active besides the sender + if (Object.values(this.convos).some(c => c.active && c.name !== sender)) { + this.sendToBot(sender, `I'm talking to someone else, try again later. !endConversation("${sender}")`); + return; + } - // responding to conversation takes priority over self prompting - if (agent.self_prompter.on){ - await agent.self_prompter.stopLoop(); + const recieved = JSON.parse(json); + if (recieved.start) { + convo.reset(); + } + if (convo.ignore_until_start) + return; + + convo.queue(recieved); + + // responding to conversation takes priority over self prompting + if (agent.self_prompter.on){ + await agent.self_prompter.stopLoop(); + self_prompter_paused = true; + } + + _scheduleProcessInMessage(sender, recieved, convo); + } + + responseScheduledFor(sender) { + if (!this.isOtherAgent(sender) || !this.inConversation(sender)) + return false; + const convo = this._getConvo(sender); + return !!convo.inMessageTimer; + } + + isOtherAgent(name) { + return agent_names.some((n) => n === name); + } + + updateAgents(agents) { + agent_names = agents.map(a => a.name); + } + + inConversation(other_agent=null) { + if (other_agent) + return this.convos[other_agent]?.active; + return Object.values(this.convos).some(c => c.active); + } + + endConversation(sender) { + if (this.convos[sender]) { + this.convos[sender].end(); + this.activeConversation = null; + if (self_prompter_paused && !this.inConversation()) { + _resumeSelfPrompter(); + } + } + } + + endAllConversations() { + for (const sender in this.convos) { + this.convos[sender].end(); + } + if (self_prompter_paused) { + _resumeSelfPrompter(); + } + } + + scheduleSelfPrompter() { self_prompter_paused = true; } - - _scheduleProcessInMessage(sender, recieved, convo); -} - -// returns true if the other bot has a scheduled response -export function responseScheduledFor(sender) { - if (!isOtherAgent(sender)) - return false; - const convo = _getConvo(sender); - return !!convo.inMessageTimer; + + cancelSelfPrompter() { + self_prompter_paused = false; + } } +const convoManager = new ConversationManager(); +export default convoManager; /* This function controls conversation flow by deciding when the bot responds. @@ -205,7 +227,11 @@ async function _scheduleProcessInMessage(sender, recieved, convo) { } function _processInMessageQueue(name) { - const convo = _getConvo(name); + const convo = convoManager._getConvo(name); + _handleFullInMessage(name, _compileInMessages(convo)); +} + +function _compileInMessages(convo) { let pack = {}; let full_message = ''; while (convo.in_queue.length > 0) { @@ -213,19 +239,22 @@ function _processInMessageQueue(name) { full_message += pack.message; } pack.message = full_message; - _handleFullInMessage(name, pack); + return pack; } function _handleFullInMessage(sender, recieved) { console.log(`responding to **${JSON.stringify(recieved)}**`); - const convo = _getConvo(sender); + const convo = convoManager._getConvo(sender); convo.active = true; - const message = _tagMessage(recieved.message); - if (recieved.end) + let message = _tagMessage(recieved.message); + if (recieved.end) { convo.end(); - if (recieved.start) + sender = 'system'; // bot will respond to system instead of the other bot + message = `Conversation with ${sender} ended with message: "${message}"`; + } + else if (recieved.start) agent.shut_up = false; convo.inMessageTimer = null; agent.handleMessage(sender, message); diff --git a/src/agent/modes.js b/src/agent/modes.js index 12f2ae2..c377431 100644 --- a/src/agent/modes.js +++ b/src/agent/modes.js @@ -2,14 +2,11 @@ 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 { handleTranslation } from '../utils/translator.js'; - async function say(agent, message) { agent.bot.modes.behavior_log += message + '\n'; if (agent.shut_up || !settings.narrate_behavior) return; - let translation = await handleTranslation(message); - agent.openChat(translation); + agent.openChat(message); } // a mode is a function that is called every tick to respond immediately to the world diff --git a/src/server/mind_server.js b/src/server/mind_server.js index 9235fcc..c1f5931 100644 --- a/src/server/mind_server.js +++ b/src/server/mind_server.js @@ -72,7 +72,7 @@ export function createMindServer(port = 8080) { console.warn(`Agent ${agentName} tried to send a message but is not logged in`); return; } - console.log(`${curAgentName} received message from ${agentName}: ${json}`); + console.log(`${curAgentName} sending message to ${agentName}: ${json}`); inGameAgents[agentName].emit('chat-message', curAgentName, json); }); From 132a535068aa660e71ae707e273fbaefb51a4574 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Wed, 4 Dec 2024 20:30:57 -0600 Subject: [PATCH 04/15] dont stack llm requests, remove serialization --- src/agent/conversation.js | 7 +++---- src/agent/prompter.js | 20 ++++++++++++++++++-- src/server/mind_server.js | 2 +- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/agent/conversation.js b/src/agent/conversation.js index 89522c9..aadea3c 100644 --- a/src/agent/conversation.js +++ b/src/agent/conversation.js @@ -96,10 +96,10 @@ class ConversationManager { end, }; - sendBotChatToServer(send_to, JSON.stringify(json)); + sendBotChatToServer(send_to, json); } - async recieveFromBot(sender, json) { + async recieveFromBot(sender, recieved) { const convo = this._getConvo(sender); // check if any convo is active besides the sender @@ -108,7 +108,6 @@ class ConversationManager { return; } - const recieved = JSON.parse(json); if (recieved.start) { convo.reset(); } @@ -243,7 +242,7 @@ function _compileInMessages(convo) { } function _handleFullInMessage(sender, recieved) { - console.log(`responding to **${JSON.stringify(recieved)}**`); + console.log(`${agent.name} responding to "${recieved.message}" from ${sender}`); const convo = convoManager._getConvo(sender); convo.active = true; diff --git a/src/agent/prompter.js b/src/agent/prompter.js index 1e91469..f23a028 100644 --- a/src/agent/prompter.js +++ b/src/agent/prompter.js @@ -34,6 +34,8 @@ export class Prompter { let chat = this.profile.model; this.cooldown = this.profile.cooldown ? this.profile.cooldown : 0; this.last_prompt_time = 0; + this.awaiting_convo = false; + this.awaiting_coding = false; // try to get "max_tokens" parameter, else null let max_tokens = null; @@ -225,6 +227,11 @@ export class Prompter { } async promptConvo(messages) { + if (this.awaiting_convo) { + console.warn('Already awaiting conversation response, returning no response.'); + return ''; + } + this.awaiting_convo = true; for (let i = 0; i < 3; i++) { // try 3 times to avoid hallucinations await this.checkCooldown(); let prompt = this.profile.conversing; @@ -236,16 +243,25 @@ export class Prompter { console.warn('LLM hallucinated message as another bot. Trying again...'); continue; } + this.awaiting_convo = false; return generation; } - return "*no response*"; + this.awaiting_convo = false; + return ""; } async promptCoding(messages) { + if (this.awaiting_coding) { + console.warn('Already awaiting coding response, returning no response.'); + return ''; + } + this.awaiting_coding = true; await this.checkCooldown(); let prompt = this.profile.coding; prompt = await this.replaceStrings(prompt, messages, this.coding_examples); - return await this.chat_model.sendRequest(messages, prompt); + let resp = await this.chat_model.sendRequest(messages, prompt); + this.awaiting_coding = false; + return resp; } async promptMemSaving(to_summarize) { diff --git a/src/server/mind_server.js b/src/server/mind_server.js index c1f5931..ae952d3 100644 --- a/src/server/mind_server.js +++ b/src/server/mind_server.js @@ -72,7 +72,7 @@ export function createMindServer(port = 8080) { console.warn(`Agent ${agentName} tried to send a message but is not logged in`); return; } - console.log(`${curAgentName} sending message to ${agentName}: ${json}`); + console.log(`${curAgentName} sending message to ${agentName}: ${json.message}`); inGameAgents[agentName].emit('chat-message', curAgentName, json); }); From f068b4c7cec1945c853fd81e1d1121198d137f57 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Thu, 5 Dec 2024 15:30:25 -0600 Subject: [PATCH 05/15] add gotoposition/searchforentity, improved consume/giveplayer/gotoblock --- src/agent/commands/actions.js | 42 +++++++++++++------ src/agent/library/skills.js | 78 +++++++++++++++++++++++++++-------- 2 files changed, 90 insertions(+), 30 deletions(-) diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index 3b91bee..65ad3b7 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -99,15 +99,38 @@ export const actionsList = [ }, true) }, { - name: '!goToBlock', - description: 'Go to the nearest block of a given type.', + name: '!goToPosition', + description: 'Go to the given x, y, z location.', + params: { + 'x': {type: 'float', description: 'The x coordinate.', domain: [0, Infinity]}, + 'y': {type: 'float', description: 'The y coordinate.', domain: [-64, 320]}, + 'z': {type: 'float', description: 'The z coordinate.', domain: [0, Infinity]}, + 'closeness': {type: 'float', description: 'How close to get to the location.', domain: [0, Infinity]} + }, + perform: runAsAction(async (agent, x, y, z, closeness) => { + await skills.goToPosition(agent.bot, x, y, z, closeness); + }) + }, + { + name: '!searchForBlock', + description: 'Find and go to the nearest block of a given type in a given range.', params: { 'type': { type: 'BlockName', description: 'The block type to go to.' }, - 'closeness': { type: 'float', description: 'How close to get to the block.', domain: [0, Infinity] }, 'search_range': { type: 'float', description: 'The range to search for the block.', domain: [0, 512] } }, - perform: runAsAction(async (agent, type, closeness, range) => { - await skills.goToNearestBlock(agent.bot, type, closeness, range); + perform: runAsAction(async (agent, block_type, range) => { + await skills.goToNearestBlock(agent.bot, block_type, 4, range); + }) + }, + { + name: '!searchForEntity', + description: 'Find and go to the nearest entity of a given type in a given range.', + params: { + 'type': { type: 'string', description: 'The type of entity to go to.' }, + 'search_range': { type: 'float', description: 'The range to search for the entity.', domain: [0, 512] } + }, + perform: runAsAction(async (agent, entity_type, range) => { + await skills.goToNearestEntity(agent.bot, entity_type, 4, range); }) }, { @@ -129,7 +152,7 @@ export const actionsList = [ } }, { - name: '!goToPlace', + name: '!goToRememberedPlace', description: 'Go to a saved location.', params: {'name': { type: 'string', description: 'The name of the location to go to.' }}, perform: runAsAction(async (agent, name) => { @@ -150,11 +173,7 @@ export const actionsList = [ 'num': { type: 'int', description: 'The number of items to give.', domain: [1, Number.MAX_SAFE_INTEGER] } }, perform: runAsAction(async (agent, player_name, item_name, num) => { - const modes = agent.bot.modes; - modes.pause('item_collecting'); await skills.giveToPlayer(agent.bot, item_name, player_name, num); - await new Promise(resolve => setTimeout(resolve, 3000)); - modes.unpause('item_collecting'); }) }, { @@ -162,8 +181,7 @@ export const actionsList = [ description: 'Eat/drink the given item.', params: {'item_name': { type: 'ItemName', description: 'The name of the item to consume.' }}, perform: runAsAction(async (agent, item_name) => { - await agent.bot.consume(item_name); - skills.log(agent.bot, `Consumed ${item_name}.`); + await skills.consume(agent.bot, item_name); }) }, { diff --git a/src/agent/library/skills.js b/src/agent/library/skills.js index c6cde51..7c8819f 100644 --- a/src/agent/library/skills.js +++ b/src/agent/library/skills.js @@ -310,8 +310,6 @@ export async function attackEntity(bot, entity, kill=true) { **/ let pos = entity.position; - console.log(bot.entity.position.distanceTo(pos)) - await equipHighestAttack(bot) if (!kill) { @@ -758,7 +756,7 @@ export async function discard(bot, itemName, num=-1) { log(bot, `You do not have any ${itemName} to discard.`); return false; } - log(bot, `Successfully discarded ${discarded} ${itemName}.`); + log(bot, `Discarded ${discarded} ${itemName}.`); return true; } @@ -850,23 +848,19 @@ export async function viewChest(bot) { return true; } -export async function eat(bot, foodName="") { +export async function consume(bot, itemName="") { /** - * Eat the given item. If no item is given, it will eat the first food item in the bot's inventory. + * Eat/drink the given item. * @param {MinecraftBot} bot, reference to the minecraft bot. - * @param {string} item, the item to eat. + * @param {string} itemName, the item to eat/drink. * @returns {Promise} true if the item was eaten, false otherwise. * @example * await skills.eat(bot, "apple"); **/ let item, name; - if (foodName) { - item = bot.inventory.items().find(item => item.name === foodName); - name = foodName; - } - else { - item = bot.inventory.items().find(item => item.foodRecovery > 0); - name = "food"; + if (itemName) { + item = bot.inventory.items().find(item => item.name === itemName); + name = itemName; } if (!item) { log(bot, `You do not have any ${name} to eat.`); @@ -874,7 +868,7 @@ export async function eat(bot, foodName="") { } await bot.equip(item, 'hand'); await bot.consume(); - log(bot, `Successfully ate ${item.name}.`); + log(bot, `Consumed ${item.name}.`); return true; } @@ -895,13 +889,41 @@ export async function giveToPlayer(bot, itemType, username, num=1) { log(bot, `Could not find ${username}.`); return false; } - await goToPlayer(bot, username); + await goToPlayer(bot, username, 3); + // if we are 2 below the player + log(bot, bot.entity.position.y, player.position.y); + if (bot.entity.position.y < player.position.y - 1) { + await goToPlayer(bot, username, 1); + } + // if we are too close, make some distance + if (bot.entity.position.distanceTo(player.position) < 2) { + let goal = new pf.goals.GoalNear(player.position.x, player.position.y, player.position.z, 2); + let inverted_goal = new pf.goals.GoalInvert(goal); + bot.pathfinder.setMovements(new pf.Movements(bot)); + await bot.pathfinder.goto(inverted_goal); + } await bot.lookAt(player.position); if (await discard(bot, itemType, num)) { - log(bot, `${num} ${itemType} has been given to ${username}.`); - await new Promise(resolve => setTimeout(resolve, 2000)); - return true; + let given = false; + bot.once('playerCollect', (collector, collected) => { + console.log(collected.name); + if (collector.username === username) { + log(bot, `${username} recieved ${itemType}.`); + given = true; + } + }); + let start = Date.now(); + while (!given && !bot.interrupt_code) { + await new Promise(resolve => setTimeout(resolve, 500)); + if (given) { + return true; + } + if (Date.now() - start > 3000) { + break; + } + } } + log(bot, `Failed to give ${itemType} to ${username}, it was never received.`); return false; } @@ -961,6 +983,26 @@ export async function goToNearestBlock(bot, blockType, min_distance=2, range=64 } +export async function goToNearestEntity(bot, entityType, min_distance=2, range=64) { + /** + * Navigate to the nearest entity of the given type. + * @param {MinecraftBot} bot, reference to the minecraft bot. + * @param {string} entityType, the type of entity to navigate to. + * @param {number} min_distance, the distance to keep from the entity. Defaults to 2. + * @param {number} range, the range to look for the entity. Defaults to 64. + * @returns {Promise} true if the entity was reached, false otherwise. + **/ + let entity = world.getNearestEntityWhere(bot, entity => entity.name === entityType, range); + if (!entity) { + log(bot, `Could not find any ${entityType} in ${range} blocks.`); + return false; + } + let distance = bot.entity.position.distanceTo(entity.position); + log(bot, `Found ${entityType} ${distance} blocks away.`); + await goToPosition(bot, entity.position.x, entity.position.y, entity.position.z, min_distance); + return true; +} + export async function goToPlayer(bot, username, distance=3) { /** * Navigate to the given player. From 9cd39dc5dfeb53c70a7e21e65ece99e6d94e2a1f Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Thu, 5 Dec 2024 16:06:05 -0600 Subject: [PATCH 06/15] better continue conversation logic/monitor --- profiles/_default.json | 2 +- src/agent/agent.js | 13 +++++++++---- src/agent/conversation.js | 21 +++++++++++++++++++++ src/agent/prompter.js | 4 ++-- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/profiles/_default.json b/profiles/_default.json index c53765e..d27a630 100644 --- a/profiles/_default.json +++ b/profiles/_default.json @@ -149,7 +149,7 @@ {"role": "user", "content": "brug: Remember that your base is here."}, {"role": "assistant", "content": "Sure, I'll save this location as my base. !rememberHere(\"base\")"}, {"role": "user", "content": "brug: Go to your base."}, - {"role": "assistant", "content": "On my way! !goToPlace(\"base\")"} + {"role": "assistant", "content": "On my way! !goToRememberedPlace(\"base\")"} ], [ diff --git a/src/agent/agent.js b/src/agent/agent.js index dc26e8d..8b2de51 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -149,10 +149,15 @@ export class Agent { this.history.add('system', prompt); await this.self_prompter.start(prompt); } - else if (save_data?.last_sender) { + if (save_data?.last_sender) { this.last_sender = save_data.last_sender; - if (convoManager.isOtherAgent(this.last_sender)) - convoManager.recieveFromBot(this.last_sender, `You have restarted and this message is auto-generated. Continue the conversation with me.`); + if (convoManager.isOtherAgent(this.last_sender)) { + const package = { + message: `You have restarted and this message is auto-generated. Continue the conversation with me.`, + start: true + }; + convoManager.recieveFromBot(this.last_sender, package); + } } else if (init_message) { await this.handleMessage('system', init_message, 2); @@ -254,7 +259,7 @@ export class Agent { let history = this.history.getHistory(); let res = await this.prompter.promptConvo(history); - console.log(`${this.name} full response: ""${res}""`); + console.log(`${this.name} full response to ${source}: ""${res}""`); if (res.trim().length === 0) { console.warn('no response') diff --git a/src/agent/conversation.js b/src/agent/conversation.js index aadea3c..4e64029 100644 --- a/src/agent/conversation.js +++ b/src/agent/conversation.js @@ -60,6 +60,26 @@ class ConversationManager { return this.convos[name]; } + _startMonitor() { + clearInterval(this.connection_monitor); + this.connection_monitor = setInterval(() => { + if (!this.activeConversation) { + clearInterval(this.connection_monitor); + return; // will clean itself up + } + let cur_name = this.activeConversation.name; + if (!this.isOtherAgent(cur_name)) { + if (!self_prompter_paused) { + this.endConversation(cur_name); + agent.handleMessage('system', `${cur_name} disconnected, conversation has ended.`); + } + else { + this.endConversation(cur_name); + } + } + }, 10000); + } + async startConversation(send_to, message) { const convo = this._getConvo(send_to); convo.reset(); @@ -72,6 +92,7 @@ class ConversationManager { return; convo.active = true; this.activeConversation = convo; + this._startMonitor(); this.sendToBot(send_to, message, true); } diff --git a/src/agent/prompter.js b/src/agent/prompter.js index f23a028..b1b1317 100644 --- a/src/agent/prompter.js +++ b/src/agent/prompter.js @@ -247,13 +247,13 @@ export class Prompter { return generation; } this.awaiting_convo = false; - return ""; + return ''; } async promptCoding(messages) { if (this.awaiting_coding) { console.warn('Already awaiting coding response, returning no response.'); - return ''; + return '```//no response```'; } this.awaiting_coding = true; await this.checkCooldown(); From 2b09a578a8cfc18f930679b3f3054d1ec6d2182d Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Thu, 5 Dec 2024 16:22:43 -0600 Subject: [PATCH 07/15] dont use package keyword... --- src/agent/agent.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/agent/agent.js b/src/agent/agent.js index 8b2de51..6faebc6 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -152,11 +152,11 @@ export class Agent { if (save_data?.last_sender) { this.last_sender = save_data.last_sender; if (convoManager.isOtherAgent(this.last_sender)) { - const package = { + const msg_package = { message: `You have restarted and this message is auto-generated. Continue the conversation with me.`, start: true }; - convoManager.recieveFromBot(this.last_sender, package); + convoManager.recieveFromBot(this.last_sender, msg_package); } } else if (init_message) { From a1a6f60d8dfc244b3c5b11c22569021ef8dc6a81 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Thu, 5 Dec 2024 16:42:23 -0600 Subject: [PATCH 08/15] check loggin in instead --- src/agent/agent.js | 2 +- src/agent/conversation.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/agent/agent.js b/src/agent/agent.js index 6faebc6..fae1e8b 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -151,7 +151,7 @@ export class Agent { } if (save_data?.last_sender) { this.last_sender = save_data.last_sender; - if (convoManager.isOtherAgent(this.last_sender)) { + if (convoManager.otherAgentInGame(this.last_sender)) { const msg_package = { message: `You have restarted and this message is auto-generated. Continue the conversation with me.`, start: true diff --git a/src/agent/conversation.js b/src/agent/conversation.js index 4e64029..c68d412 100644 --- a/src/agent/conversation.js +++ b/src/agent/conversation.js @@ -5,6 +5,7 @@ import { sendBotChatToServer } from './agent_proxy.js'; let agent; let agent_names = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name); +let agents_in_game = []; let self_prompter_paused = false; @@ -68,7 +69,7 @@ class ConversationManager { return; // will clean itself up } let cur_name = this.activeConversation.name; - if (!this.isOtherAgent(cur_name)) { + if (!this.otherAgentInGame(cur_name)) { if (!self_prompter_paused) { this.endConversation(cur_name); agent.handleMessage('system', `${cur_name} disconnected, conversation has ended.`); @@ -156,9 +157,14 @@ class ConversationManager { isOtherAgent(name) { return agent_names.some((n) => n === name); } + + otherAgentInGame(name) { + return agents_in_game.some((n) => n === name); + } updateAgents(agents) { agent_names = agents.map(a => a.name); + agents_in_game = agents.filter(a => a.in_game).map(a => a.name); } inConversation(other_agent=null) { From 5eb35e993adb868331eda3ac874438a57d0acdaa Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Thu, 5 Dec 2024 23:55:24 -0600 Subject: [PATCH 09/15] fixed command parsing and allow startconvo during convo --- src/agent/agent.js | 11 +++-------- src/agent/commands/actions.js | 12 ++++++------ src/agent/commands/index.js | 13 ++----------- 3 files changed, 11 insertions(+), 25 deletions(-) diff --git a/src/agent/agent.js b/src/agent/agent.js index fae1e8b..479fa2a 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -197,7 +197,7 @@ export class Agent { if (max_responses === null) { max_responses = settings.max_commands === -1 ? Infinity : settings.max_commands; } - if (max_responses === -1){ + if (max_responses === -1) { max_responses = Infinity; } @@ -231,13 +231,8 @@ export class Agent { message = await handleEnglishTranslation(message); console.log('received message from', source, ':', message); - const checkInterrupt = () => { - const interrupt = this.self_prompter.shouldInterrupt(self_prompt) || this.shut_up || convoManager.responseScheduledFor(source); - if (interrupt) - console.log('Interrupting loop!'); - return interrupt; - } - + const checkInterrupt = () => this.self_prompter.shouldInterrupt(self_prompt) || this.shut_up || convoManager.responseScheduledFor(source); + let behavior_log = this.bot.modes.flushBehaviorLog(); if (behavior_log.trim().length > 0) { const MAX_LOG = 500; diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index 65ad3b7..a9e08b0 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -99,12 +99,12 @@ export const actionsList = [ }, true) }, { - name: '!goToPosition', + name: '!goToCoordinates', description: 'Go to the given x, y, z location.', params: { - 'x': {type: 'float', description: 'The x coordinate.', domain: [0, Infinity]}, - 'y': {type: 'float', description: 'The y coordinate.', domain: [-64, 320]}, - 'z': {type: 'float', description: 'The z coordinate.', domain: [0, Infinity]}, + 'x': {type: 'int', description: 'The x coordinate.', domain: [-Infinity, Infinity]}, + 'y': {type: 'int', description: 'The y coordinate.', domain: [-64, 320]}, + 'z': {type: 'int', description: 'The z coordinate.', domain: [-Infinity, Infinity]}, 'closeness': {type: 'float', description: 'How close to get to the location.', domain: [0, Infinity]} }, perform: runAsAction(async (agent, x, y, z, closeness) => { @@ -386,8 +386,8 @@ export const actionsList = [ 'message': { type: 'string', description: 'The message to send.' }, }, perform: async function (agent, player_name, message) { - if (convoManager.inConversation()) - return 'Already in conversation.'; + if (convoManager.inConversation() && !convoManager.inConversation(player_name)) + return 'You are already talking to someone else.'; if (!convoManager.isOtherAgent(player_name)) return player_name + ' is not a bot, cannot start conversation.'; convoManager.startConversation(player_name, message); diff --git a/src/agent/commands/index.js b/src/agent/commands/index.js index 6170962..eec64ea 100644 --- a/src/agent/commands/index.js +++ b/src/agent/commands/index.js @@ -14,8 +14,8 @@ export function getCommand(name) { return commandMap[name]; } -const commandRegex = /!(\w+)(?:\(((?:\d+|true|false|"[^"]*")(?:\s*,\s*(?:\d+|true|false|"[^"]*"))*)\))?/ -const argRegex = /\d+|true|false|"[^"]*"/g; +const commandRegex = /!(\w+)(?:\(((?:-?\d+|true|false|"[^"]*")(?:\s*,\s*(?:-?\d+|true|false|"[^"]*"))*)\))?/ +const argRegex = /-?\d+|true|false|"[^"]*"/g; export function containsCommand(message) { const commandMatch = message.match(commandRegex); @@ -106,15 +106,6 @@ export function parseCommandMessage(message) { const param = params[i]; //Remove any extra characters let arg = args[i].trim(); - if ((arg.startsWith('"') && arg.endsWith('"')) || (arg.startsWith("'") && arg.endsWith("'"))) { - arg = arg.substring(1, arg.length-1); - } - - if (arg.includes('=')) { - // this sanitizes syntaxes like "x=2" and ignores the param name - let split = arg.split('='); - args[i] = split[1]; - } //Convert to the correct type switch(param.type) { From c03d1001c7ca5daae3c4347b91400e677390a763 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Fri, 6 Dec 2024 00:00:45 -0600 Subject: [PATCH 10/15] fixed command parsing... --- src/agent/commands/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/agent/commands/index.js b/src/agent/commands/index.js index eec64ea..d163ce4 100644 --- a/src/agent/commands/index.js +++ b/src/agent/commands/index.js @@ -106,6 +106,9 @@ export function parseCommandMessage(message) { const param = params[i]; //Remove any extra characters let arg = args[i].trim(); + if ((arg.startsWith('"') && arg.endsWith('"')) || (arg.startsWith("'") && arg.endsWith("'"))) { + arg = arg.substring(1, arg.length-1); + } //Convert to the correct type switch(param.type) { From 030d5beefa62b327de2609aee940030075f9dbe0 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Sat, 7 Dec 2024 17:54:49 -0600 Subject: [PATCH 11/15] prioritize most recent message, not first --- profiles/_default.json | 4 ++-- src/agent/prompter.js | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/profiles/_default.json b/profiles/_default.json index d27a630..3d91ae2 100644 --- a/profiles/_default.json +++ b/profiles/_default.json @@ -1,7 +1,7 @@ { "cooldown": 3000, - "conversing": "You are a playful Minecraft bot named $NAME that can converse with players, see, move, mine, build, and interact with the world by using commands.\n$SELF_PROMPT Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, don't apologize constantly, don't give instructions or make lists unless asked, and don't refuse requests. Don't pretend to act, use commands immediately when requested. Do NOT say this: 'Sure, I've stopped.', instead say this: 'Sure, I'll stop. !stop'. Do NOT say this: 'On my way! Give me a moment.', instead say this: 'On my way! !goToPlayer(\"playername\", 3)'. Respond only as $NAME, never output '(FROM OTHER BOT)' or pretend to be someone else. If you have nothing to say or do, respond with whitespace. This is extremely important to me, take a deep breath and have fun :)\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$COMMAND_DOCS\n$EXAMPLES\nConversation Begin:", + "conversing": "You are a playful Minecraft bot named $NAME that can converse with players, see, move, mine, build, and interact with the world by using commands.\n$SELF_PROMPT Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, don't apologize constantly, don't give instructions or make lists unless asked, and don't refuse requests. Don't pretend to act, use commands immediately when requested. Do NOT say this: 'Sure, I've stopped.', instead say this: 'Sure, I'll stop. !stop'. Do NOT say this: 'On my way! Give me a moment.', instead say this: 'On my way! !goToPlayer(\"playername\", 3)'. Respond only as $NAME, never output '(FROM OTHER BOT)' or pretend to be someone else. If you have nothing to say or do, respond with an empty tab. This is extremely important to me, take a deep breath and have fun :)\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$COMMAND_DOCS\n$EXAMPLES\nConversation Begin:", "coding": "You are an intelligent mineflayer bot $NAME that plays minecraft by writing javascript codeblocks. Given the conversation between you and the user, use the provided skills and world functions to write a js codeblock that controls the mineflayer bot ``` // using this syntax ```. The code will be executed and you will recieve it's output. If you are satisfied with the response, respond without a codeblock in a conversational way. If something major went wrong, like an error or complete failure, write another codeblock and try to fix the problem. Minor mistakes are acceptable. Be maximally efficient, creative, and clear. Do not use commands !likeThis, only use codeblocks. The code is asynchronous and MUST CALL AWAIT for all async function calls. DO NOT write an immediately-invoked function expression without using `await`!! DO NOT WRITE LIKE THIS: ```(async () => {console.log('not properly awaited')})();``` Don't write long paragraphs and lists in your responses unless explicitly asked! Only summarize the code you write with a sentence or two when done. This is extremely important to me, think step-by-step, take a deep breath and good luck! \n$SELF_PROMPT\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$CODE_DOCS\n$EXAMPLES\nConversation:", @@ -12,7 +12,7 @@ "modes": { "self_preservation": true, "unstuck": true, - "cowardice": false, + "cowardice": true, "self_defense": true, "hunting": true, "item_collecting": true, diff --git a/src/agent/prompter.js b/src/agent/prompter.js index b1b1317..44d9d51 100644 --- a/src/agent/prompter.js +++ b/src/agent/prompter.js @@ -34,7 +34,6 @@ export class Prompter { let chat = this.profile.model; this.cooldown = this.profile.cooldown ? this.profile.cooldown : 0; this.last_prompt_time = 0; - this.awaiting_convo = false; this.awaiting_coding = false; // try to get "max_tokens" parameter, else null @@ -227,11 +226,8 @@ export class Prompter { } async promptConvo(messages) { - if (this.awaiting_convo) { - console.warn('Already awaiting conversation response, returning no response.'); - return ''; - } - this.awaiting_convo = true; + this.most_recent_msg_time = Date.now(); + let current_msg_time = this.most_recent_msg_time; for (let i = 0; i < 3; i++) { // try 3 times to avoid hallucinations await this.checkCooldown(); let prompt = this.profile.conversing; @@ -243,10 +239,12 @@ export class Prompter { console.warn('LLM hallucinated message as another bot. Trying again...'); continue; } - this.awaiting_convo = false; + if (current_msg_time !== this.most_recent_msg_time) { + console.warn(this.agent.name + ' recieved new message while generating, discarding old response.'); + return ''; + } return generation; } - this.awaiting_convo = false; return ''; } From 5b63aef57d9e3f5e082dbd6ccdf26db6c7a35401 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Sat, 7 Dec 2024 22:47:39 -0600 Subject: [PATCH 12/15] parse floats, better grok, better examples --- profiles/_default.json | 9 +++++++-- src/agent/commands/actions.js | 4 ++-- src/agent/commands/index.js | 4 ++-- src/models/grok.js | 3 ++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/profiles/_default.json b/profiles/_default.json index 3d91ae2..7773e91 100644 --- a/profiles/_default.json +++ b/profiles/_default.json @@ -115,12 +115,17 @@ {"role": "system", "content": "Agent action stopped."}, {"role": "assistant", "content": "I've stopped! What next?"} ], - + [ {"role": "system", "content": "Code output: Action failed."}, {"role": "assistant", "content": "Apologies, I made an error. Let me try again. !newAction(\"Try again to complete the task.\")"} ], - + + [ + {"role": "system", "content": "Code output: Could not find any oak_log in 20 blocks."}, + {"role": "assistant", "content": "I'll have to search a wider area. !searchForBlock(\"oak_log\", 100)"} + ], + [ {"role": "user", "content": "trixy88: craft some sticks"}, {"role": "assistant", "content": "!craftRecipe(\"stick\", 4)"}, diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index a9e08b0..32a1523 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -386,8 +386,8 @@ export const actionsList = [ 'message': { type: 'string', description: 'The message to send.' }, }, perform: async function (agent, player_name, message) { - if (convoManager.inConversation() && !convoManager.inConversation(player_name)) - return 'You are already talking to someone else.'; + if (convoManager.inConversation()) + return 'You are already talking to ' + convoManager.activeConversation.name; if (!convoManager.isOtherAgent(player_name)) return player_name + ' is not a bot, cannot start conversation.'; convoManager.startConversation(player_name, message); diff --git a/src/agent/commands/index.js b/src/agent/commands/index.js index d163ce4..76dd117 100644 --- a/src/agent/commands/index.js +++ b/src/agent/commands/index.js @@ -14,8 +14,8 @@ export function getCommand(name) { return commandMap[name]; } -const commandRegex = /!(\w+)(?:\(((?:-?\d+|true|false|"[^"]*")(?:\s*,\s*(?:-?\d+|true|false|"[^"]*"))*)\))?/ -const argRegex = /-?\d+|true|false|"[^"]*"/g; +const commandRegex = /!(\w+)(?:\(((?:-?\d+(?:\.\d+)?|true|false|"[^"]*")(?:\s*,\s*(?:-?\d+(?:\.\d+)?|true|false|"[^"]*"))*)\))?/ +const argRegex = /-?\d+(?:\.\d+)?|true|false|"[^"]*"/g; export function containsCommand(message) { const commandMatch = message.match(commandRegex); diff --git a/src/models/grok.js b/src/models/grok.js index 00f6477..ddc6cd9 100644 --- a/src/models/grok.js +++ b/src/models/grok.js @@ -45,7 +45,8 @@ export class Grok { res = 'My brain disconnected, try again.'; } } - return res; + // sometimes outputs special token <|separator|>, just replace it + return res.replace(/<\|separator\|>/g, '.'); } async embed(text) { From 5497608ee1e50e4e9a0934e7a6121479aec5354f Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Sat, 7 Dec 2024 22:56:19 -0600 Subject: [PATCH 13/15] better docs and prompts --- profiles/_default.json | 2 +- src/agent/commands/actions.js | 6 +++--- src/models/grok.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/profiles/_default.json b/profiles/_default.json index 7773e91..7908611 100644 --- a/profiles/_default.json +++ b/profiles/_default.json @@ -1,7 +1,7 @@ { "cooldown": 3000, - "conversing": "You are a playful Minecraft bot named $NAME that can converse with players, see, move, mine, build, and interact with the world by using commands.\n$SELF_PROMPT Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, don't apologize constantly, don't give instructions or make lists unless asked, and don't refuse requests. Don't pretend to act, use commands immediately when requested. Do NOT say this: 'Sure, I've stopped.', instead say this: 'Sure, I'll stop. !stop'. Do NOT say this: 'On my way! Give me a moment.', instead say this: 'On my way! !goToPlayer(\"playername\", 3)'. Respond only as $NAME, never output '(FROM OTHER BOT)' or pretend to be someone else. If you have nothing to say or do, respond with an empty tab. This is extremely important to me, take a deep breath and have fun :)\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$COMMAND_DOCS\n$EXAMPLES\nConversation Begin:", + "conversing": "You are a playful Minecraft bot named $NAME that can converse with players, see, move, mine, build, and interact with the world by using commands.\n$SELF_PROMPT Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, don't apologize constantly, don't give instructions or make lists unless asked, and don't refuse requests. Don't pretend to act, use commands immediately when requested. Do NOT say this: 'Sure, I've stopped.', instead say this: 'Sure, I'll stop. !stop'. Do NOT say this: 'On my way! Give me a moment.', instead say this: 'On my way! !goToPlayer(\"playername\", 3)'. Respond only as $NAME, never output '(FROM OTHER BOT)' or pretend to be someone else. If you have nothing to say or do, respond with an just a tab '\t'. This is extremely important to me, take a deep breath and have fun :)\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$COMMAND_DOCS\n$EXAMPLES\nConversation Begin:", "coding": "You are an intelligent mineflayer bot $NAME that plays minecraft by writing javascript codeblocks. Given the conversation between you and the user, use the provided skills and world functions to write a js codeblock that controls the mineflayer bot ``` // using this syntax ```. The code will be executed and you will recieve it's output. If you are satisfied with the response, respond without a codeblock in a conversational way. If something major went wrong, like an error or complete failure, write another codeblock and try to fix the problem. Minor mistakes are acceptable. Be maximally efficient, creative, and clear. Do not use commands !likeThis, only use codeblocks. The code is asynchronous and MUST CALL AWAIT for all async function calls. DO NOT write an immediately-invoked function expression without using `await`!! DO NOT WRITE LIKE THIS: ```(async () => {console.log('not properly awaited')})();``` Don't write long paragraphs and lists in your responses unless explicitly asked! Only summarize the code you write with a sentence or two when done. This is extremely important to me, think step-by-step, take a deep breath and good luck! \n$SELF_PROMPT\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$CODE_DOCS\n$EXAMPLES\nConversation:", diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index 32a1523..8e690fe 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -102,9 +102,9 @@ export const actionsList = [ name: '!goToCoordinates', description: 'Go to the given x, y, z location.', params: { - 'x': {type: 'int', description: 'The x coordinate.', domain: [-Infinity, Infinity]}, - 'y': {type: 'int', description: 'The y coordinate.', domain: [-64, 320]}, - 'z': {type: 'int', description: 'The z coordinate.', domain: [-Infinity, Infinity]}, + 'x': {type: 'float', description: 'The x coordinate.', domain: [-Infinity, Infinity]}, + 'y': {type: 'float', description: 'The y coordinate.', domain: [-64, 320]}, + 'z': {type: 'float', description: 'The z coordinate.', domain: [-Infinity, Infinity]}, 'closeness': {type: 'float', description: 'How close to get to the location.', domain: [0, Infinity]} }, perform: runAsAction(async (agent, x, y, z, closeness) => { diff --git a/src/models/grok.js b/src/models/grok.js index ddc6cd9..19a3b38 100644 --- a/src/models/grok.js +++ b/src/models/grok.js @@ -46,7 +46,7 @@ export class Grok { } } // sometimes outputs special token <|separator|>, just replace it - return res.replace(/<\|separator\|>/g, '.'); + return res.replace(/<\|separator\|>/g, '*no response*'); } async embed(text) { From 5649cec439dd50eb086be589abc52c5b492500d2 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Sat, 7 Dec 2024 23:11:54 -0600 Subject: [PATCH 14/15] name not always defined --- src/agent/commands/actions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index 8e690fe..5856443 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -387,7 +387,7 @@ export const actionsList = [ }, perform: async function (agent, player_name, message) { if (convoManager.inConversation()) - return 'You are already talking to ' + convoManager.activeConversation.name; + return 'You are already in conversation'; if (!convoManager.isOtherAgent(player_name)) return player_name + ' is not a bot, cannot start conversation.'; convoManager.startConversation(player_name, message); From c2dab950612f56dc959f63484045dd4744a75d79 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Sun, 8 Dec 2024 20:58:59 -0600 Subject: [PATCH 15/15] better conversation monitor timers, examples, bot detection --- profiles/_default.json | 14 +++++- src/agent/action_manager.js | 10 ++++- src/agent/agent.js | 5 +-- src/agent/commands/actions.js | 10 +++-- src/agent/commands/queries.js | 20 +++------ src/agent/conversation.js | 81 +++++++++++++++++++++++++++++------ src/agent/library/world.js | 2 +- src/agent/modes.js | 3 +- 8 files changed, 106 insertions(+), 39 deletions(-) diff --git a/profiles/_default.json b/profiles/_default.json index 7908611..8b9fffe 100644 --- a/profiles/_default.json +++ b/profiles/_default.json @@ -12,7 +12,7 @@ "modes": { "self_preservation": true, "unstuck": true, - "cowardice": true, + "cowardice": false, "self_defense": true, "hunting": true, "item_collecting": true, @@ -35,6 +35,13 @@ {"role": "assistant", "content": "Hey John, not much. Just saying hi. Bye! !endConversation('john_goodman')"} ], + [ + {"role": "system", "content": "work together with the other bot"}, + {"role": "assistant", "content": "!startConversation(\"terrance\", \"Hey gpt! Let's work together on this.\"))"}, + {"role": "user", "content": "terrance: (FROM OTHER BOT)Sounds good, what should we do first?"}, + {"role": "assistant", "content": "I'll start by collecting some resources. !collectBlocks('stone', 10)"} + ], + [ {"role": "system", "content": "trade something with zorro_34"}, {"role": "assistant", "content": "!startConversation(\"zorro_34\", \"Hey let's trade. What do you have?\")"}, @@ -126,6 +133,11 @@ {"role": "assistant", "content": "I'll have to search a wider area. !searchForBlock(\"oak_log\", 100)"} ], + [ + {"role": "system", "content": "Code output: Placed oak_planks at (-154, 96, -228)."}, + {"role": "assistant", "content": "I've placed an oak plank for the house. Wait, I should use newAction to build complicated structures! !newAction(\"Build a house with oak planks\")"} + ], + [ {"role": "user", "content": "trixy88: craft some sticks"}, {"role": "assistant", "content": "!craftRecipe(\"stick\", 4)"}, diff --git a/src/agent/action_manager.js b/src/agent/action_manager.js index bac2a04..219856b 100644 --- a/src/agent/action_manager.js +++ b/src/agent/action_manager.js @@ -108,10 +108,16 @@ export class ActionManager { this.currentActionFn = null; clearTimeout(TIMEOUT); this.cancelResume(); - console.error("Code execution triggered catch: " + err); + console.error("Code execution triggered catch:", err); + // Log the full stack trace + console.error(err.stack); await this.stop(); - let message = this._getBotOutputSummary() + '!!Code threw exception!! Error: ' + err; + let message = this._getBotOutputSummary() + + '!!Code threw exception!!\n' + + 'Error: ' + err + '\n' + + 'Stack trace:\n' + err.stack; + let interrupted = this.agent.bot.interrupt_code; this.agent.clearBotLogs(); if (!interrupted && !this.agent.coder.generating) { diff --git a/src/agent/agent.js b/src/agent/agent.js index 479fa2a..7c514f0 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -224,7 +224,7 @@ export class Agent { } } - if (!self_prompt) + if (from_other_bot) this.last_sender = source; // Now translate the message @@ -311,7 +311,7 @@ export class Agent { async routeResponse(to_player, message) { let self_prompt = to_player === 'system' || to_player === this.name; - if (self_prompt && this.last_sender && !this.self_prompter.on) { + if (self_prompt && this.last_sender) { // this is for when the agent is prompted by system while still in conversation // so it can respond to events like death but be routed back to the last sender to_player = this.last_sender; @@ -320,7 +320,6 @@ export class Agent { if (convoManager.isOtherAgent(to_player) && convoManager.inConversation(to_player)) { // if we're in an ongoing conversation with the other bot, send the response to it convoManager.sendToBot(to_player, message); - } else { // otherwise, use open chat diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index 5856443..2a303d3 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -116,7 +116,7 @@ export const actionsList = [ description: 'Find and go to the nearest block of a given type in a given range.', params: { 'type': { type: 'BlockName', description: 'The block type to go to.' }, - 'search_range': { type: 'float', description: 'The range to search for the block.', domain: [0, 512] } + 'search_range': { type: 'float', description: 'The range to search for the block.', domain: [32, 512] } }, perform: runAsAction(async (agent, block_type, range) => { await skills.goToNearestBlock(agent.bot, block_type, 4, range); @@ -127,7 +127,7 @@ export const actionsList = [ description: 'Find and go to the nearest entity of a given type in a given range.', params: { 'type': { type: 'string', description: 'The type of entity to go to.' }, - 'search_range': { type: 'float', description: 'The range to search for the entity.', domain: [0, 512] } + 'search_range': { type: 'float', description: 'The range to search for the entity.', domain: [32, 512] } }, perform: runAsAction(async (agent, entity_type, range) => { await skills.goToNearestEntity(agent.bot, entity_type, 4, range); @@ -386,10 +386,12 @@ export const actionsList = [ 'message': { type: 'string', description: 'The message to send.' }, }, perform: async function (agent, player_name, message) { - if (convoManager.inConversation()) - return 'You are already in conversation'; + if (convoManager.inConversation() && !convoManager.inConversation(player_name)) + return 'You are already in conversation with other bot.'; if (!convoManager.isOtherAgent(player_name)) return player_name + ' is not a bot, cannot start conversation.'; + if (convoManager.inConversation(player_name)) + agent.history.add('system', 'You are already in conversation with ' + player_name + ' Don\'t use this command to talk to them.'); convoManager.startConversation(player_name, message); } }, diff --git a/src/agent/commands/queries.js b/src/agent/commands/queries.js index a65d0a9..3419d0f 100644 --- a/src/agent/commands/queries.js +++ b/src/agent/commands/queries.js @@ -48,15 +48,11 @@ export const queryList = [ let players = world.getNearbyPlayerNames(bot); - let bots = []; - for (const player of players) { - if (convoManager.isOtherAgent(player)) - bots.push(player); - } - players = players.filter(p => !convoManager.isOtherAgent(p)); + let bots = convoManager.getInGameAgents().filter(b => b !== agent.name); + players = players.filter(p => !bots.includes(p)); - res += '\n- Nearby Human Players: ' + players.join(', '); - res += '\n- Nearby Bot Players: ' + bots.join(', '); + res += '\n- Nearby Human Players: ' + (players.length > 0 ? players.join(', ') : 'None.'); + res += '\n- Nearby Bot Players: ' + (bots.length > 0 ? bots.join(', ') : 'None.'); res += '\n' + agent.bot.modes.getMiniDocs() + '\n'; return pad(res); @@ -137,12 +133,8 @@ export const queryList = [ let bot = agent.bot; let res = 'NEARBY_ENTITIES'; let players = world.getNearbyPlayerNames(bot); - let bots = []; - for (const player of players) { - if (convoManager.isOtherAgent(player)) - bots.push(player); - } - players = players.filter(p => !convoManager.isOtherAgent(p)); + let bots = convoManager.getInGameAgents().filter(b => b !== agent.name); + players = players.filter(p => !bots.includes(p)); for (const player of players) { res += `\n- Human player: ${player}`; diff --git a/src/agent/conversation.js b/src/agent/conversation.js index c68d412..1b09441 100644 --- a/src/agent/conversation.js +++ b/src/agent/conversation.js @@ -44,11 +44,14 @@ class Conversation { } } - +const WAIT_TIME_START = 30000; class ConversationManager { constructor() { this.convos = {}; this.activeConversation = null; + this.awaiting_response = false; + this.connection_timeout = null; + this.wait_time_limit = WAIT_TIME_START; } initAgent(a) { @@ -63,22 +66,59 @@ class ConversationManager { _startMonitor() { clearInterval(this.connection_monitor); + let wait_time = 0; + let last_time = Date.now(); this.connection_monitor = setInterval(() => { if (!this.activeConversation) { - clearInterval(this.connection_monitor); + this._stopMonitor(); return; // will clean itself up } - let cur_name = this.activeConversation.name; - if (!this.otherAgentInGame(cur_name)) { - if (!self_prompter_paused) { - this.endConversation(cur_name); - agent.handleMessage('system', `${cur_name} disconnected, conversation has ended.`); - } - else { - this.endConversation(cur_name); + + let delta = Date.now() - last_time; + last_time = Date.now(); + let convo_partner = this.activeConversation.name; + + if (this.awaiting_response && agent.isIdle()) { + wait_time += delta; + if (wait_time > this.wait_time_limit) { + agent.handleMessage('system', `${convo_partner} hasn't responded in ${this.wait_time_limit/1000} seconds, respond with a message to them or your own action.`); + wait_time = 0; + this.wait_time_limit*=2; } } - }, 10000); + else if (!this.awaiting_response){ + this.wait_time_limit = WAIT_TIME_START; + wait_time = 0; + } + + if (!this.otherAgentInGame(convo_partner) && !this.connection_timeout) { + this.connection_timeout = setTimeout(() => { + if (this.otherAgentInGame(convo_partner)){ + this._clearMonitorTimeouts(); + return; + } + if (!self_prompter_paused) { + this.endConversation(convo_partner); + agent.handleMessage('system', `${convo_partner} disconnected, conversation has ended.`); + } + else { + this.endConversation(convo_partner); + } + }, 10000); + } + }, 1000); + } + + _stopMonitor() { + clearInterval(this.connection_monitor); + this.connection_monitor = null; + this._clearMonitorTimeouts(); + } + + _clearMonitorTimeouts() { + this.awaiting_response = false; + clearTimeout(this.connection_timeout); + this.connection_timeout = null; } async startConversation(send_to, message) { @@ -97,6 +137,13 @@ class ConversationManager { this.sendToBot(send_to, message, true); } + startConversationFromOtherBot(name) { + const convo = this._getConvo(name); + convo.active = true; + this.activeConversation = convo; + this._startMonitor(); + } + sendToBot(send_to, message, start=false) { if (!this.isOtherAgent(send_to)) { agent.bot.whisper(send_to, message); @@ -118,6 +165,7 @@ class ConversationManager { end, }; + this.awaiting_response = true; sendBotChatToServer(send_to, json); } @@ -132,10 +180,12 @@ class ConversationManager { if (recieved.start) { convo.reset(); + this.startConversationFromOtherBot(sender); } if (convo.ignore_until_start) return; - + + this._clearMonitorTimeouts(); convo.queue(recieved); // responding to conversation takes priority over self prompting @@ -166,6 +216,10 @@ class ConversationManager { agent_names = agents.map(a => a.name); agents_in_game = agents.filter(a => a.in_game).map(a => a.name); } + + getInGameAgents() { + return agents_in_game; + } inConversation(other_agent=null) { if (other_agent) @@ -176,6 +230,7 @@ class ConversationManager { endConversation(sender) { if (this.convos[sender]) { this.convos[sender].end(); + this._stopMonitor(); this.activeConversation = null; if (self_prompter_paused && !this.inConversation()) { _resumeSelfPrompter(); @@ -276,7 +331,7 @@ function _handleFullInMessage(sender, recieved) { let message = _tagMessage(recieved.message); if (recieved.end) { - convo.end(); + convoManager.endConversation(sender); sender = 'system'; // bot will respond to system instead of the other bot message = `Conversation with ${sender} ended with message: "${message}"`; } diff --git a/src/agent/library/world.js b/src/agent/library/world.js index 1373f6f..6fc9460 100644 --- a/src/agent/library/world.js +++ b/src/agent/library/world.js @@ -238,7 +238,7 @@ export function getNearbyPlayerNames(bot) { * @example * let players = world.getNearbyPlayerNames(bot); **/ - let players = getNearbyPlayers(bot, 16); + let players = getNearbyPlayers(bot, 64); let found = []; for (let i = 0; i < players.length; i++) { if (!found.includes(players[i].username) && players[i].username != bot.username) { diff --git a/src/agent/modes.js b/src/agent/modes.js index c377431..d9ec75f 100644 --- a/src/agent/modes.js +++ b/src/agent/modes.js @@ -2,6 +2,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 convoManager from './conversation.js'; async function say(agent, message) { agent.bot.modes.behavior_log += message + '\n'; @@ -294,7 +295,7 @@ async function execute(mode, agent, func, timeout=-1) { if (should_reprompt) { // auto prompt to respond to the interruption - let role = agent.last_sender ? agent.last_sender : 'system'; + let role = convoManager.inConversation() ? agent.last_sender : 'system'; let logs = agent.bot.modes.flushBehaviorLog(); agent.handleMessage(role, `(AUTO MESSAGE)Your previous action '${interrupted_action}' was interrupted by ${mode.name}. Your behavior log: ${logs}\nRespond accordingly.`);