mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-08-04 22:35:35 +02:00
merge with main
This commit is contained in:
commit
2332bb3e62
22 changed files with 717 additions and 304 deletions
14
main.js
14
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 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 { createMindServer } from './src/server/mind_server.js';
|
||||||
|
import { mainProxy } from './src/process/main_proxy.js';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
|
||||||
function parseArguments() {
|
function parseArguments() {
|
||||||
return yargs(hideBin(process.argv))
|
return yargs(hideBin(process.argv))
|
||||||
|
@ -32,15 +34,19 @@ async function main() {
|
||||||
if (settings.host_mindserver) {
|
if (settings.host_mindserver) {
|
||||||
const mindServer = createMindServer();
|
const mindServer = createMindServer();
|
||||||
}
|
}
|
||||||
|
mainProxy.connect();
|
||||||
|
|
||||||
const args = parseArguments();
|
const args = parseArguments();
|
||||||
const profiles = getProfiles(args);
|
const profiles = getProfiles(args);
|
||||||
console.log(profiles);
|
console.log(profiles);
|
||||||
var { load_memory, init_message } = settings;
|
const { load_memory, init_message } = settings;
|
||||||
|
|
||||||
for (let i=0; i<profiles.length; i++) {
|
for (let i=0; i<profiles.length; i++) {
|
||||||
const agent = new AgentProcess();
|
const agent_process = new AgentProcess();
|
||||||
agent.start(profiles[i], load_memory, init_message, i, args.task_path, args.task_id);
|
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));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"cooldown": 3000,
|
"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 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:",
|
"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:",
|
||||||
|
|
||||||
|
@ -35,6 +35,13 @@
|
||||||
{"role": "assistant", "content": "Hey John, not much. Just saying hi. Bye! !endConversation('john_goodman')"}
|
{"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": "system", "content": "trade something with zorro_34"},
|
||||||
{"role": "assistant", "content": "!startConversation(\"zorro_34\", \"Hey let's trade. What do you have?\")"},
|
{"role": "assistant", "content": "!startConversation(\"zorro_34\", \"Hey let's trade. What do you have?\")"},
|
||||||
|
@ -121,6 +128,16 @@
|
||||||
{"role": "assistant", "content": "Apologies, I made an error. Let me try again. !newAction(\"Try again to complete the task.\")"}
|
{"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": "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": "user", "content": "trixy88: craft some sticks"},
|
||||||
{"role": "assistant", "content": "!craftRecipe(\"stick\", 4)"},
|
{"role": "assistant", "content": "!craftRecipe(\"stick\", 4)"},
|
||||||
|
@ -149,7 +166,7 @@
|
||||||
{"role": "user", "content": "brug: Remember that your base is here."},
|
{"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": "assistant", "content": "Sure, I'll save this location as my base. !rememberHere(\"base\")"},
|
||||||
{"role": "user", "content": "brug: Go to your 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\")"}
|
||||||
],
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
|
|
|
@ -3,18 +3,5 @@
|
||||||
|
|
||||||
"model": "claude-3-5-sonnet-latest",
|
"model": "claude-3-5-sonnet-latest",
|
||||||
|
|
||||||
"embedding": "openai",
|
"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
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -23,6 +23,7 @@ export default
|
||||||
],
|
],
|
||||||
"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
|
||||||
|
|
||||||
"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...
|
"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!')
|
"narrate_behavior": true, // chat simple automatic actions ('Picking up item!')
|
||||||
"chat_bot_messages": true, // publicly chat messages to other bots
|
"chat_bot_messages": true, // publicly chat messages to other bots
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -108,10 +108,16 @@ export class ActionManager {
|
||||||
this.currentActionFn = null;
|
this.currentActionFn = null;
|
||||||
clearTimeout(TIMEOUT);
|
clearTimeout(TIMEOUT);
|
||||||
this.cancelResume();
|
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();
|
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;
|
let interrupted = this.agent.bot.interrupt_code;
|
||||||
this.agent.clearBotLogs();
|
this.agent.clearBotLogs();
|
||||||
if (!interrupted && !this.agent.coder.generating) {
|
if (!interrupted && !this.agent.coder.generating) {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { ActionManager } from './action_manager.js';
|
||||||
import { NPCContoller } from './npc/controller.js';
|
import { NPCContoller } from './npc/controller.js';
|
||||||
import { MemoryBank } from './memory_bank.js';
|
import { MemoryBank } from './memory_bank.js';
|
||||||
import { SelfPrompter } from './self_prompter.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 { handleTranslation, handleEnglishTranslation } from '../utils/translator.js';
|
||||||
import { addViewer } from './viewer.js';
|
import { addViewer } from './viewer.js';
|
||||||
import settings from '../../settings.js';
|
import settings from '../../settings.js';
|
||||||
|
@ -24,9 +24,6 @@ export class Agent {
|
||||||
throw new Error('No profile filepath provided');
|
throw new Error('No profile filepath provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to MindServer via proxy
|
|
||||||
serverProxy.connect();
|
|
||||||
|
|
||||||
console.log('Starting agent initialization with profile:', profile_fp);
|
console.log('Starting agent initialization with profile:', profile_fp);
|
||||||
|
|
||||||
// Initialize components with more detailed error handling
|
// Initialize components with more detailed error handling
|
||||||
|
@ -45,11 +42,11 @@ export class Agent {
|
||||||
this.memory_bank = new MemoryBank();
|
this.memory_bank = new MemoryBank();
|
||||||
console.log('Initializing self prompter...');
|
console.log('Initializing self prompter...');
|
||||||
this.self_prompter = new SelfPrompter(this);
|
this.self_prompter = new SelfPrompter(this);
|
||||||
initConversationManager(this);
|
convoManager.initAgent(this);
|
||||||
console.log('Initializing examples...');
|
console.log('Initializing examples...');
|
||||||
await this.prompter.initExamples();
|
await this.prompter.initExamples();
|
||||||
|
|
||||||
serverProxy.registerAgent(this.name);
|
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);
|
||||||
|
@ -82,6 +79,8 @@ export class Agent {
|
||||||
this.bot.on('login', () => {
|
this.bot.on('login', () => {
|
||||||
console.log(this.name, 'logged in!');
|
console.log(this.name, 'logged in!');
|
||||||
|
|
||||||
|
serverProxy.login();
|
||||||
|
|
||||||
// Set skin for profile, requires Fabric Tailor. (https://modrinth.com/mod/fabrictailor)
|
// Set skin for profile, requires Fabric Tailor. (https://modrinth.com/mod/fabrictailor)
|
||||||
if (this.prompter.profile.skin)
|
if (this.prompter.profile.skin)
|
||||||
this.bot.chat(`/skin set URL ${this.prompter.profile.skin.model} ${this.prompter.profile.skin.path}`);
|
this.bot.chat(`/skin set URL ${this.prompter.profile.skin.model} ${this.prompter.profile.skin.path}`);
|
||||||
|
@ -137,6 +136,7 @@ export class Agent {
|
||||||
|
|
||||||
const respondFunc = async (username, message) => {
|
const respondFunc = async (username, message) => {
|
||||||
if (username === this.name) return;
|
if (username === this.name) return;
|
||||||
|
if (settings.only_chat_with.length > 0 && !settings.only_chat_with.includes(username)) return;
|
||||||
try {
|
try {
|
||||||
if (ignore_messages.some((m) => message.startsWith(m))) return;
|
if (ignore_messages.some((m) => message.startsWith(m))) return;
|
||||||
|
|
||||||
|
@ -144,8 +144,7 @@ export class Agent {
|
||||||
|
|
||||||
console.log(this.name, 'received message from', username, ':', message);
|
console.log(this.name, 'received message from', username, ':', message);
|
||||||
|
|
||||||
if (isOtherAgent(username)) {
|
if (convoManager.isOtherAgent(username)) {
|
||||||
//recieveFromBot(username, message);
|
|
||||||
console.warn('recieved whisper from other bot??')
|
console.warn('recieved whisper from other bot??')
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -174,16 +173,21 @@ export class Agent {
|
||||||
this.history.add('system', prompt);
|
this.history.add('system', prompt);
|
||||||
await this.self_prompter.start(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;
|
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.otherAgentInGame(this.last_sender)) {
|
||||||
|
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, msg_package);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (init_message) {
|
else if (init_message) {
|
||||||
await this.handleMessage('system', init_message, 2);
|
await this.handleMessage('system', init_message, 2);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const translation = await handleTranslation("Hello world! I am "+this.name);
|
this.openChat("Hello world! I am "+this.name);
|
||||||
this.bot.chat(translation);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +208,7 @@ export class Agent {
|
||||||
if (this.self_prompter.on) {
|
if (this.self_prompter.on) {
|
||||||
this.self_prompter.stop(false);
|
this.self_prompter.stop(false);
|
||||||
}
|
}
|
||||||
endAllChats();
|
convoManager.endAllConversations();
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleMessage(source, message, max_responses=null) {
|
async handleMessage(source, message, max_responses=null) {
|
||||||
|
@ -222,16 +226,16 @@ export class Agent {
|
||||||
}
|
}
|
||||||
|
|
||||||
const self_prompt = source === 'system' || source === this.name;
|
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
|
if (!self_prompt && !from_other_bot) { // from user, check for forced commands
|
||||||
const user_command_name = containsCommand(message);
|
const user_command_name = containsCommand(message);
|
||||||
if (user_command_name) {
|
if (user_command_name) {
|
||||||
if (!commandExists(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;
|
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') {
|
if (user_command_name === '!newAction') {
|
||||||
// all user-initiated commands are ignored by the bot except for this one
|
// 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
|
// add the preceding message to the history to give context for newAction
|
||||||
|
@ -244,14 +248,14 @@ export class Agent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!self_prompt)
|
if (from_other_bot)
|
||||||
this.last_sender = source;
|
this.last_sender = source;
|
||||||
|
|
||||||
// Now translate the message
|
// Now translate the message
|
||||||
message = await handleEnglishTranslation(message);
|
message = await handleEnglishTranslation(message);
|
||||||
console.log('received message from', source, ':', message);
|
console.log('received message from', source, ':', message);
|
||||||
|
|
||||||
const checkInterrupt = () => this.self_prompter.shouldInterrupt(self_prompt) || this.shut_up || responseScheduledFor(source);
|
const checkInterrupt = () => this.self_prompter.shouldInterrupt(self_prompt) || this.shut_up || convoManager.responseScheduledFor(source);
|
||||||
|
|
||||||
let behavior_log = this.bot.modes.flushBehaviorLog();
|
let behavior_log = this.bot.modes.flushBehaviorLog();
|
||||||
if (behavior_log.trim().length > 0) {
|
if (behavior_log.trim().length > 0) {
|
||||||
|
@ -267,7 +271,6 @@ export class Agent {
|
||||||
await this.history.add(source, message);
|
await this.history.add(source, message);
|
||||||
this.history.save();
|
this.history.save();
|
||||||
|
|
||||||
|
|
||||||
if (!self_prompt && this.self_prompter.on) // message is from user during self-prompting
|
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
|
max_responses = 1; // force only respond to this message, then let self-prompting take over
|
||||||
for (let i=0; i<max_responses; i++) {
|
for (let i=0; i<max_responses; i++) {
|
||||||
|
@ -275,10 +278,16 @@ export class Agent {
|
||||||
let history = this.history.getHistory();
|
let history = this.history.getHistory();
|
||||||
let res = await this.prompter.promptConvo(history);
|
let res = await this.prompter.promptConvo(history);
|
||||||
|
|
||||||
|
console.log(`${this.name} full response to ${source}: ""${res}""`);
|
||||||
|
|
||||||
|
if (res.trim().length === 0) {
|
||||||
|
console.warn('no response')
|
||||||
|
break; // empty response ends loop
|
||||||
|
}
|
||||||
|
|
||||||
let command_name = containsCommand(res);
|
let command_name = containsCommand(res);
|
||||||
|
|
||||||
if (command_name) { // contains query or command
|
if (command_name) { // contains query or command
|
||||||
console.log(`Full response: ""${res}""`)
|
|
||||||
res = truncCommandMessage(res); // everything after the command is ignored
|
res = truncCommandMessage(res); // everything after the command is ignored
|
||||||
this.history.add(this.name, res);
|
this.history.add(this.name, res);
|
||||||
|
|
||||||
|
@ -292,7 +301,7 @@ export class Agent {
|
||||||
this.self_prompter.handleUserPromptedCmd(self_prompt, isAction(command_name));
|
this.self_prompter.handleUserPromptedCmd(self_prompt, isAction(command_name));
|
||||||
|
|
||||||
if (settings.verbose_commands) {
|
if (settings.verbose_commands) {
|
||||||
this.routeResponse(source, res, res.indexOf(command_name));
|
this.routeResponse(source, res);
|
||||||
}
|
}
|
||||||
else { // only output command name
|
else { // only output command name
|
||||||
let pre_message = res.substring(0, res.indexOf(command_name)).trim();
|
let pre_message = res.substring(0, res.indexOf(command_name)).trim();
|
||||||
|
@ -315,7 +324,6 @@ export class Agent {
|
||||||
else { // conversation response
|
else { // conversation response
|
||||||
this.history.add(this.name, res);
|
this.history.add(this.name, res);
|
||||||
this.routeResponse(source, res);
|
this.routeResponse(source, res);
|
||||||
console.log('Purely conversational response:', res);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,22 +333,31 @@ export class Agent {
|
||||||
return used_command;
|
return used_command;
|
||||||
}
|
}
|
||||||
|
|
||||||
async routeResponse(to_player, message, translate_up_to=-1) {
|
async routeResponse(to_player, message) {
|
||||||
let self_prompt = to_player === 'system' || to_player === this.name;
|
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
|
// 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
|
// so it can respond to events like death but be routed back to the last sender
|
||||||
to_player = this.last_sender;
|
to_player = this.last_sender;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOtherAgent(to_player)) {
|
if (convoManager.isOtherAgent(to_player) && convoManager.inConversation(to_player)) {
|
||||||
sendToBot(to_player, message);
|
// if we're in an ongoing conversation with the other bot, send the response to it
|
||||||
return;
|
convoManager.sendToBot(to_player, message);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// otherwise, use open chat
|
||||||
|
this.openChat(message);
|
||||||
|
// note that to_player could be another bot, but if we get here the conversation has ended
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async openChat(message) {
|
||||||
let to_translate = message;
|
let to_translate = message;
|
||||||
let remaining = '';
|
let remaining = '';
|
||||||
if (translate_up_to != -1) {
|
let command_name = containsCommand(message);
|
||||||
|
let translate_up_to = command_name ? message.indexOf(command_name) : -1;
|
||||||
|
if (translate_up_to != -1) { // don't translate the command
|
||||||
to_translate = to_translate.substring(0, translate_up_to);
|
to_translate = to_translate.substring(0, translate_up_to);
|
||||||
remaining = message.substring(translate_up_to);
|
remaining = message.substring(translate_up_to);
|
||||||
}
|
}
|
||||||
|
@ -348,10 +365,14 @@ export class Agent {
|
||||||
// newlines are interpreted as separate chats, which triggers spam filters. replace them with spaces
|
// newlines are interpreted as separate chats, which triggers spam filters. replace them with spaces
|
||||||
message = message.replaceAll('\n', ' ');
|
message = message.replaceAll('\n', ' ');
|
||||||
|
|
||||||
if (self_prompt)
|
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);
|
this.bot.chat(message);
|
||||||
else
|
}
|
||||||
this.bot.whisper(to_player, message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startEvents() {
|
startEvents() {
|
||||||
|
@ -491,7 +512,10 @@ export class Agent {
|
||||||
|
|
||||||
cleanKill(msg='Killing agent process...', code=1) {
|
cleanKill(msg='Killing agent process...', code=1) {
|
||||||
this.history.add('system', msg);
|
this.history.add('system', msg);
|
||||||
|
<<<<<<< HEAD
|
||||||
this.bot.chat(code > 1 ? 'Restarting.': 'Exiting.');
|
this.bot.chat(code > 1 ? 'Restarting.': 'Exiting.');
|
||||||
|
=======
|
||||||
|
>>>>>>> upstream/main
|
||||||
this.history.save();
|
this.history.save();
|
||||||
process.exit(code);
|
process.exit(code);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
import { io } from 'socket.io-client';
|
import { io } from 'socket.io-client';
|
||||||
import { recieveFromBot, updateAgents } from './conversation.js';
|
import convoManager from './conversation.js';
|
||||||
import settings from '../../settings.js';
|
import settings from '../../settings.js';
|
||||||
|
|
||||||
class ServerProxy {
|
class AgentServerProxy {
|
||||||
constructor() {
|
constructor() {
|
||||||
if (ServerProxy.instance) {
|
if (AgentServerProxy.instance) {
|
||||||
return ServerProxy.instance;
|
return AgentServerProxy.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
this.connected = false;
|
this.connected = false;
|
||||||
ServerProxy.instance = this;
|
AgentServerProxy.instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
connect() {
|
connect(agent) {
|
||||||
if (this.connected) return;
|
if (this.connected) return;
|
||||||
|
|
||||||
|
this.agent = agent;
|
||||||
|
|
||||||
this.socket = io(`http://${settings.mindserver_host}:${settings.mindserver_port}`);
|
this.socket = io(`http://${settings.mindserver_host}:${settings.mindserver_port}`);
|
||||||
this.connected = true;
|
this.connected = true;
|
||||||
|
|
||||||
|
@ -29,20 +31,21 @@ class ServerProxy {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on('chat-message', (agentName, json) => {
|
this.socket.on('chat-message', (agentName, json) => {
|
||||||
recieveFromBot(agentName, json);
|
convoManager.recieveFromBot(agentName, json);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on('agents-update', (agents) => {
|
this.socket.on('agents-update', (agents) => {
|
||||||
updateAgents(agents);
|
convoManager.updateAgents(agents);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('restart-agent', (agentName) => {
|
||||||
|
console.log(`Restarting agent: ${agentName}`);
|
||||||
|
this.agent.cleanKill();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerAgent(agentName) {
|
login() {
|
||||||
if (!this.connected) {
|
this.socket.emit('login-agent', this.agent.name);
|
||||||
console.warn('Cannot register agent: not connected to MindServer');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.socket.emit('register-agent', agentName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getSocket() {
|
getSocket() {
|
||||||
|
@ -51,7 +54,7 @@ class ServerProxy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and export a singleton instance
|
// Create and export a singleton instance
|
||||||
export const serverProxy = new ServerProxy();
|
export const serverProxy = new AgentServerProxy();
|
||||||
|
|
||||||
export function sendBotChatToServer(agentName, json) {
|
export function sendBotChatToServer(agentName, json) {
|
||||||
serverProxy.getSocket().emit('chat-message', agentName, json);
|
serverProxy.getSocket().emit('chat-message', agentName, json);
|
|
@ -1,6 +1,6 @@
|
||||||
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 { startConversation, endConversation, inConversation, scheduleSelfPrompter, cancelSelfPrompter } from '../conversation.js';
|
import convoManager from '../conversation.js';
|
||||||
|
|
||||||
function runAsAction (actionFn, resume = false, timeout = -1) {
|
function runAsAction (actionFn, resume = false, timeout = -1) {
|
||||||
let actionLabel = null; // Will be set on first use
|
let actionLabel = null; // Will be set on first use
|
||||||
|
@ -56,7 +56,7 @@ export const actionsList = [
|
||||||
name: '!stfu',
|
name: '!stfu',
|
||||||
description: 'Stop all chatting and self prompting, but continue current action.',
|
description: 'Stop all chatting and self prompting, but continue current action.',
|
||||||
perform: async function (agent) {
|
perform: async function (agent) {
|
||||||
agent.bot.chat('Shutting up.');
|
agent.openChat('Shutting up.');
|
||||||
agent.shutUp();
|
agent.shutUp();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -99,15 +99,38 @@ export const actionsList = [
|
||||||
}, true)
|
}, true)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '!goToBlock',
|
name: '!goToCoordinates',
|
||||||
description: 'Go to the nearest block of a given type.',
|
description: 'Go to the given x, y, z location.',
|
||||||
|
params: {
|
||||||
|
'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) => {
|
||||||
|
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: {
|
params: {
|
||||||
'type': { type: 'BlockName', description: 'The block type to go to.' },
|
'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: [32, 512] }
|
||||||
'search_range': { type: 'float', description: 'The range to search for the block.', domain: [0, 512] }
|
|
||||||
},
|
},
|
||||||
perform: runAsAction(async (agent, type, closeness, range) => {
|
perform: runAsAction(async (agent, block_type, range) => {
|
||||||
await skills.goToNearestBlock(agent.bot, type, closeness, 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: [32, 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.',
|
description: 'Go to a saved location.',
|
||||||
params: {'name': { type: 'string', description: 'The name of the location to go to.' }},
|
params: {'name': { type: 'string', description: 'The name of the location to go to.' }},
|
||||||
perform: runAsAction(async (agent, name) => {
|
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] }
|
'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) => {
|
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 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.',
|
description: 'Eat/drink the given item.',
|
||||||
params: {'item_name': { type: 'ItemName', description: 'The name of the item to consume.' }},
|
params: {'item_name': { type: 'ItemName', description: 'The name of the item to consume.' }},
|
||||||
perform: runAsAction(async (agent, item_name) => {
|
perform: runAsAction(async (agent, item_name) => {
|
||||||
await agent.bot.consume(item_name);
|
await skills.consume(agent.bot, item_name);
|
||||||
skills.log(agent.bot, `Consumed ${item_name}.`);
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -342,14 +360,12 @@ export const actionsList = [
|
||||||
'selfPrompt': { type: 'string', description: 'The goal prompt.' },
|
'selfPrompt': { type: 'string', description: 'The goal prompt.' },
|
||||||
},
|
},
|
||||||
perform: async function (agent, prompt) {
|
perform: async function (agent, prompt) {
|
||||||
if (inConversation()) {
|
if (convoManager.inConversation()) {
|
||||||
// if conversing with another bot, dont start self-prompting yet
|
|
||||||
// wait until conversation ends
|
|
||||||
agent.self_prompter.setPrompt(prompt);
|
agent.self_prompter.setPrompt(prompt);
|
||||||
scheduleSelfPrompter();
|
convoManager.scheduleSelfPrompter();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
agent.self_prompter.start(prompt); // don't await, don't return
|
agent.self_prompter.start(prompt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -358,19 +374,25 @@ export const actionsList = [
|
||||||
description: 'Call when you have accomplished your goal. It will stop self-prompting and the current action. ',
|
description: 'Call when you have accomplished your goal. It will stop self-prompting and the current action. ',
|
||||||
perform: async function (agent) {
|
perform: async function (agent) {
|
||||||
agent.self_prompter.stop();
|
agent.self_prompter.stop();
|
||||||
cancelSelfPrompter();
|
convoManager.cancelSelfPrompter();
|
||||||
return 'Self-prompting stopped.';
|
return 'Self-prompting stopped.';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '!startConversation',
|
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: {
|
params: {
|
||||||
'player_name': { type: 'string', description: 'The name of the player to send the message to.' },
|
'player_name': { type: 'string', description: 'The name of the player to send the message to.' },
|
||||||
'message': { type: 'string', description: 'The message to send.' },
|
'message': { type: 'string', description: 'The message to send.' },
|
||||||
},
|
},
|
||||||
perform: async function (agent, player_name, message) {
|
perform: async function (agent, player_name, message) {
|
||||||
startConversation(player_name, message);
|
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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -380,7 +402,10 @@ export const actionsList = [
|
||||||
'player_name': { type: 'string', description: 'The name of the player to end the conversation with.' }
|
'player_name': { type: 'string', description: 'The name of the player to end the conversation with.' }
|
||||||
},
|
},
|
||||||
perform: async function (agent, player_name) {
|
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
|
// { // commented for now, causes confusion with goal command
|
||||||
|
|
|
@ -14,8 +14,8 @@ export function getCommand(name) {
|
||||||
return commandMap[name];
|
return commandMap[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
const commandRegex = /!(\w+)(?:\(((?:\d+|true|false|"[^"]*")(?:\s*,\s*(?:\d+|true|false|"[^"]*"))*)\))?/
|
const commandRegex = /!(\w+)(?:\(((?:-?\d+(?:\.\d+)?|true|false|"[^"]*")(?:\s*,\s*(?:-?\d+(?:\.\d+)?|true|false|"[^"]*"))*)\))?/
|
||||||
const argRegex = /\d+|true|false|"[^"]*"/g;
|
const argRegex = /-?\d+(?:\.\d+)?|true|false|"[^"]*"/g;
|
||||||
|
|
||||||
export function containsCommand(message) {
|
export function containsCommand(message) {
|
||||||
const commandMatch = message.match(commandRegex);
|
const commandMatch = message.match(commandRegex);
|
||||||
|
@ -110,12 +110,6 @@ export function parseCommandMessage(message) {
|
||||||
arg = arg.substring(1, arg.length-1);
|
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
|
//Convert to the correct type
|
||||||
switch(param.type) {
|
switch(param.type) {
|
||||||
case 'int':
|
case 'int':
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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 { isOtherAgent } from '../conversation.js';
|
import convoManager from '../conversation.js';
|
||||||
|
|
||||||
const pad = (str) => {
|
const pad = (str) => {
|
||||||
return '\n' + str + '\n';
|
return '\n' + str + '\n';
|
||||||
|
@ -48,15 +48,11 @@ export const queryList = [
|
||||||
|
|
||||||
|
|
||||||
let players = world.getNearbyPlayerNames(bot);
|
let players = world.getNearbyPlayerNames(bot);
|
||||||
let bots = [];
|
let bots = convoManager.getInGameAgents().filter(b => b !== agent.name);
|
||||||
for (const player of players) {
|
players = players.filter(p => !bots.includes(p));
|
||||||
if (isOtherAgent(player))
|
|
||||||
bots.push(player);
|
|
||||||
}
|
|
||||||
players = players.filter(p => !isOtherAgent(p));
|
|
||||||
|
|
||||||
res += '\n- Nearby Human Players: ' + players.join(', ');
|
res += '\n- Nearby Human Players: ' + (players.length > 0 ? players.join(', ') : 'None.');
|
||||||
res += '\n- Nearby Bot Players: ' + bots.join(', ');
|
res += '\n- Nearby Bot Players: ' + (bots.length > 0 ? bots.join(', ') : 'None.');
|
||||||
|
|
||||||
res += '\n' + agent.bot.modes.getMiniDocs() + '\n';
|
res += '\n' + agent.bot.modes.getMiniDocs() + '\n';
|
||||||
return pad(res);
|
return pad(res);
|
||||||
|
@ -137,12 +133,8 @@ export const queryList = [
|
||||||
let bot = agent.bot;
|
let bot = agent.bot;
|
||||||
let res = 'NEARBY_ENTITIES';
|
let res = 'NEARBY_ENTITIES';
|
||||||
let players = world.getNearbyPlayerNames(bot);
|
let players = world.getNearbyPlayerNames(bot);
|
||||||
let bots = [];
|
let bots = convoManager.getInGameAgents().filter(b => b !== agent.name);
|
||||||
for (const player of players) {
|
players = players.filter(p => !bots.includes(p));
|
||||||
if (isOtherAgent(player))
|
|
||||||
bots.push(player);
|
|
||||||
}
|
|
||||||
players = players.filter(p => !isOtherAgent(p));
|
|
||||||
|
|
||||||
for (const player of players) {
|
for (const player of players) {
|
||||||
res += `\n- Human player: ${player}`;
|
res += `\n- Human player: ${player}`;
|
||||||
|
|
|
@ -1,55 +1,14 @@
|
||||||
import settings from '../../settings.js';
|
import settings from '../../settings.js';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { containsCommand } from './commands/index.js';
|
import { containsCommand } from './commands/index.js';
|
||||||
import { sendBotChatToServer } from './server_proxy.js';
|
import { sendBotChatToServer } from './agent_proxy.js';
|
||||||
|
|
||||||
let agent;
|
let agent;
|
||||||
let agent_names = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name);
|
let agent_names = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name);
|
||||||
|
let agents_in_game = [];
|
||||||
|
|
||||||
let self_prompter_paused = false;
|
let self_prompter_paused = false;
|
||||||
|
|
||||||
export function isOtherAgent(name) {
|
|
||||||
return agent_names.some((n) => n === name);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateAgents(names) {
|
|
||||||
agent_names = names;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
class Conversation {
|
||||||
constructor(name) {
|
constructor(name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
@ -70,40 +29,131 @@ class Conversation {
|
||||||
end() {
|
end() {
|
||||||
this.active = false;
|
this.active = false;
|
||||||
this.ignore_until_start = true;
|
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) {
|
queue(message) {
|
||||||
this.in_queue.push(message);
|
this.in_queue.push(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const convos = {};
|
|
||||||
|
|
||||||
function _getConvo(name) {
|
const WAIT_TIME_START = 30000;
|
||||||
if (!convos[name])
|
class ConversationManager {
|
||||||
convos[name] = new Conversation(name);
|
constructor() {
|
||||||
return convos[name];
|
this.convos = {};
|
||||||
|
this.activeConversation = null;
|
||||||
|
this.awaiting_response = false;
|
||||||
|
this.connection_timeout = null;
|
||||||
|
this.wait_time_limit = WAIT_TIME_START;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startConversation(send_to, message) {
|
initAgent(a) {
|
||||||
const convo = _getConvo(send_to);
|
agent = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getConvo(name) {
|
||||||
|
if (!this.convos[name])
|
||||||
|
this.convos[name] = new Conversation(name);
|
||||||
|
return this.convos[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
_startMonitor() {
|
||||||
|
clearInterval(this.connection_monitor);
|
||||||
|
let wait_time = 0;
|
||||||
|
let last_time = Date.now();
|
||||||
|
this.connection_monitor = setInterval(() => {
|
||||||
|
if (!this.activeConversation) {
|
||||||
|
this._stopMonitor();
|
||||||
|
return; // will clean itself up
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
const convo = this._getConvo(send_to);
|
||||||
convo.reset();
|
convo.reset();
|
||||||
|
|
||||||
if (agent.self_prompter.on) {
|
if (agent.self_prompter.on) {
|
||||||
await agent.self_prompter.stop();
|
await agent.self_prompter.stop();
|
||||||
self_prompter_paused = true;
|
self_prompter_paused = true;
|
||||||
}
|
}
|
||||||
|
if (convo.active)
|
||||||
|
return;
|
||||||
convo.active = true;
|
convo.active = true;
|
||||||
sendToBot(send_to, message, true);
|
this.activeConversation = convo;
|
||||||
|
this._startMonitor();
|
||||||
|
this.sendToBot(send_to, message, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendToBot(send_to, message, start=false) {
|
startConversationFromOtherBot(name) {
|
||||||
if (settings.chat_bot_messages)
|
const convo = this._getConvo(name);
|
||||||
agent.bot.chat(`(To ${send_to}) ${message}`);
|
convo.active = true;
|
||||||
if (!isOtherAgent(send_to)) {
|
this.activeConversation = convo;
|
||||||
|
this._startMonitor();
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToBot(send_to, message, start=false) {
|
||||||
|
if (!this.isOtherAgent(send_to)) {
|
||||||
agent.bot.whisper(send_to, message);
|
agent.bot.whisper(send_to, message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const convo = _getConvo(send_to);
|
const convo = this._getConvo(send_to);
|
||||||
|
|
||||||
|
if (settings.chat_bot_messages && !start)
|
||||||
|
agent.openChat(`(To ${send_to}) ${message}`);
|
||||||
|
|
||||||
if (convo.ignore_until_start)
|
if (convo.ignore_until_start)
|
||||||
return;
|
return;
|
||||||
convo.active = true;
|
convo.active = true;
|
||||||
|
@ -115,27 +165,27 @@ export function sendToBot(send_to, message, start=false) {
|
||||||
end,
|
end,
|
||||||
};
|
};
|
||||||
|
|
||||||
// agent.bot.whisper(send_to, JSON.stringify(json));
|
this.awaiting_response = true;
|
||||||
sendBotChatToServer(send_to, JSON.stringify(json));
|
sendBotChatToServer(send_to, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function recieveFromBot(sender, json) {
|
async recieveFromBot(sender, recieved) {
|
||||||
const convo = _getConvo(sender);
|
const convo = this._getConvo(sender);
|
||||||
|
|
||||||
// check if any convo is active besides the sender
|
// check if any convo is active besides the sender
|
||||||
if (Object.values(convos).some(c => c.active && c.name !== sender)) {
|
if (Object.values(this.convos).some(c => c.active && c.name !== sender)) {
|
||||||
sendToBot(sender, 'I am currently busy. Try again later. !endConversation("' + sender + '")');
|
this.sendToBot(sender, `I'm talking to someone else, try again later. !endConversation("${sender}")`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`decoding **${json}**`);
|
|
||||||
const recieved = JSON.parse(json);
|
|
||||||
if (recieved.start) {
|
if (recieved.start) {
|
||||||
convo.reset();
|
convo.reset();
|
||||||
|
this.startConversationFromOtherBot(sender);
|
||||||
}
|
}
|
||||||
if (convo.ignore_until_start)
|
if (convo.ignore_until_start)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
this._clearMonitorTimeouts();
|
||||||
convo.queue(recieved);
|
convo.queue(recieved);
|
||||||
|
|
||||||
// responding to conversation takes priority over self prompting
|
// responding to conversation takes priority over self prompting
|
||||||
|
@ -147,14 +197,67 @@ export async function recieveFromBot(sender, json) {
|
||||||
_scheduleProcessInMessage(sender, recieved, convo);
|
_scheduleProcessInMessage(sender, recieved, convo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns true if the other bot has a scheduled response
|
responseScheduledFor(sender) {
|
||||||
export function responseScheduledFor(sender) {
|
if (!this.isOtherAgent(sender) || !this.inConversation(sender))
|
||||||
if (!isOtherAgent(sender))
|
|
||||||
return false;
|
return false;
|
||||||
const convo = _getConvo(sender);
|
const convo = this._getConvo(sender);
|
||||||
return !!convo.inMessageTimer;
|
return !!convo.inMessageTimer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInGameAgents() {
|
||||||
|
return agents_in_game;
|
||||||
|
}
|
||||||
|
|
||||||
|
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._stopMonitor();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelSelfPrompter() {
|
||||||
|
self_prompter_paused = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const convoManager = new ConversationManager();
|
||||||
|
export default convoManager;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This function controls conversation flow by deciding when the bot responds.
|
This function controls conversation flow by deciding when the bot responds.
|
||||||
|
@ -205,7 +308,11 @@ async function _scheduleProcessInMessage(sender, recieved, convo) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _processInMessageQueue(name) {
|
function _processInMessageQueue(name) {
|
||||||
const convo = _getConvo(name);
|
const convo = convoManager._getConvo(name);
|
||||||
|
_handleFullInMessage(name, _compileInMessages(convo));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _compileInMessages(convo) {
|
||||||
let pack = {};
|
let pack = {};
|
||||||
let full_message = '';
|
let full_message = '';
|
||||||
while (convo.in_queue.length > 0) {
|
while (convo.in_queue.length > 0) {
|
||||||
|
@ -213,19 +320,22 @@ function _processInMessageQueue(name) {
|
||||||
full_message += pack.message;
|
full_message += pack.message;
|
||||||
}
|
}
|
||||||
pack.message = full_message;
|
pack.message = full_message;
|
||||||
_handleFullInMessage(name, pack);
|
return pack;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _handleFullInMessage(sender, recieved) {
|
function _handleFullInMessage(sender, recieved) {
|
||||||
console.log(`responding to **${JSON.stringify(recieved)}**`);
|
console.log(`${agent.name} responding to "${recieved.message}" from ${sender}`);
|
||||||
|
|
||||||
const convo = _getConvo(sender);
|
const convo = convoManager._getConvo(sender);
|
||||||
convo.active = true;
|
convo.active = true;
|
||||||
|
|
||||||
const message = _tagMessage(recieved.message);
|
let message = _tagMessage(recieved.message);
|
||||||
if (recieved.end)
|
if (recieved.end) {
|
||||||
convo.end();
|
convoManager.endConversation(sender);
|
||||||
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;
|
agent.shut_up = false;
|
||||||
convo.inMessageTimer = null;
|
convo.inMessageTimer = null;
|
||||||
agent.handleMessage(sender, message);
|
agent.handleMessage(sender, message);
|
||||||
|
|
|
@ -4,10 +4,8 @@ import pf from 'mineflayer-pathfinder';
|
||||||
import Vec3 from 'vec3';
|
import Vec3 from 'vec3';
|
||||||
|
|
||||||
|
|
||||||
export function log(bot, message, chat=false) {
|
export function log(bot, message) {
|
||||||
bot.output += message + '\n';
|
bot.output += message + '\n';
|
||||||
if (chat)
|
|
||||||
bot.chat(message);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function autoLight(bot) {
|
async function autoLight(bot) {
|
||||||
|
@ -312,8 +310,6 @@ export async function attackEntity(bot, entity, kill=true) {
|
||||||
**/
|
**/
|
||||||
|
|
||||||
let pos = entity.position;
|
let pos = entity.position;
|
||||||
console.log(bot.entity.position.distanceTo(pos))
|
|
||||||
|
|
||||||
await equipHighestAttack(bot)
|
await equipHighestAttack(bot)
|
||||||
|
|
||||||
if (!kill) {
|
if (!kill) {
|
||||||
|
@ -585,7 +581,9 @@ export async function placeBlock(bot, blockType, x, y, z, placeOn='bottom', dont
|
||||||
if (blockType === 'ladder' || blockType === 'repeater' || blockType === 'comparator') {
|
if (blockType === 'ladder' || blockType === 'repeater' || blockType === 'comparator') {
|
||||||
blockType += `[facing=${face}]`;
|
blockType += `[facing=${face}]`;
|
||||||
}
|
}
|
||||||
|
if (blockType.includes('stairs')) {
|
||||||
|
blockType += `[facing=${face}]`;
|
||||||
|
}
|
||||||
let msg = '/setblock ' + Math.floor(x) + ' ' + Math.floor(y) + ' ' + Math.floor(z) + ' ' + blockType;
|
let msg = '/setblock ' + Math.floor(x) + ' ' + Math.floor(y) + ' ' + Math.floor(z) + ' ' + blockType;
|
||||||
bot.chat(msg);
|
bot.chat(msg);
|
||||||
if (blockType.includes('door'))
|
if (blockType.includes('door'))
|
||||||
|
@ -758,7 +756,7 @@ export async function discard(bot, itemName, num=-1) {
|
||||||
log(bot, `You do not have any ${itemName} to discard.`);
|
log(bot, `You do not have any ${itemName} to discard.`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
log(bot, `Successfully discarded ${discarded} ${itemName}.`);
|
log(bot, `Discarded ${discarded} ${itemName}.`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -850,23 +848,19 @@ export async function viewChest(bot) {
|
||||||
return true;
|
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 {MinecraftBot} bot, reference to the minecraft bot.
|
||||||
* @param {string} item, the item to eat.
|
* @param {string} itemName, the item to eat/drink.
|
||||||
* @returns {Promise<boolean>} true if the item was eaten, false otherwise.
|
* @returns {Promise<boolean>} true if the item was eaten, false otherwise.
|
||||||
* @example
|
* @example
|
||||||
* await skills.eat(bot, "apple");
|
* await skills.eat(bot, "apple");
|
||||||
**/
|
**/
|
||||||
let item, name;
|
let item, name;
|
||||||
if (foodName) {
|
if (itemName) {
|
||||||
item = bot.inventory.items().find(item => item.name === foodName);
|
item = bot.inventory.items().find(item => item.name === itemName);
|
||||||
name = foodName;
|
name = itemName;
|
||||||
}
|
|
||||||
else {
|
|
||||||
item = bot.inventory.items().find(item => item.foodRecovery > 0);
|
|
||||||
name = "food";
|
|
||||||
}
|
}
|
||||||
if (!item) {
|
if (!item) {
|
||||||
log(bot, `You do not have any ${name} to eat.`);
|
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.equip(item, 'hand');
|
||||||
await bot.consume();
|
await bot.consume();
|
||||||
log(bot, `Successfully ate ${item.name}.`);
|
log(bot, `Consumed ${item.name}.`);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -895,13 +889,41 @@ export async function giveToPlayer(bot, itemType, username, num=1) {
|
||||||
log(bot, `Could not find ${username}.`);
|
log(bot, `Could not find ${username}.`);
|
||||||
return false;
|
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);
|
await bot.lookAt(player.position);
|
||||||
if (await discard(bot, itemType, num)) {
|
if (await discard(bot, itemType, num)) {
|
||||||
log(bot, `${num} ${itemType} has been given to ${username}.`);
|
let given = false;
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (Date.now() - start > 3000) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log(bot, `Failed to give ${itemType} to ${username}, it was never received.`);
|
||||||
return false;
|
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<boolean>} 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) {
|
export async function goToPlayer(bot, username, distance=3) {
|
||||||
/**
|
/**
|
||||||
* Navigate to the given player.
|
* Navigate to the given player.
|
||||||
|
|
|
@ -238,7 +238,7 @@ export function getNearbyPlayerNames(bot) {
|
||||||
* @example
|
* @example
|
||||||
* let players = world.getNearbyPlayerNames(bot);
|
* let players = world.getNearbyPlayerNames(bot);
|
||||||
**/
|
**/
|
||||||
let players = getNearbyPlayers(bot, 16);
|
let players = getNearbyPlayers(bot, 64);
|
||||||
let found = [];
|
let found = [];
|
||||||
for (let i = 0; i < players.length; i++) {
|
for (let i = 0; i < players.length; i++) {
|
||||||
if (!found.includes(players[i].username) && players[i].username != bot.username) {
|
if (!found.includes(players[i].username) && players[i].username != bot.username) {
|
||||||
|
|
|
@ -2,14 +2,12 @@ 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 { handleTranslation } from '../utils/translator.js';
|
import convoManager from './conversation.js';
|
||||||
|
|
||||||
|
|
||||||
async function say(agent, message) {
|
async function say(agent, message) {
|
||||||
agent.bot.modes.behavior_log += message + '\n';
|
agent.bot.modes.behavior_log += message + '\n';
|
||||||
if (agent.shut_up || !settings.narrate_behavior) return;
|
if (agent.shut_up || !settings.narrate_behavior) return;
|
||||||
let translation = await handleTranslation(message);
|
agent.openChat(message);
|
||||||
agent.bot.chat(translation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// a mode is a function that is called every tick to respond immediately to the world
|
// a mode is a function that is called every tick to respond immediately to the world
|
||||||
|
@ -297,7 +295,7 @@ async function execute(mode, agent, func, timeout=-1) {
|
||||||
|
|
||||||
if (should_reprompt) {
|
if (should_reprompt) {
|
||||||
// auto prompt to respond to the interruption
|
// 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();
|
let logs = agent.bot.modes.flushBehaviorLog();
|
||||||
agent.handleMessage(role, `(AUTO MESSAGE)Your previous action '${interrupted_action}' was interrupted by ${mode.name}.
|
agent.handleMessage(role, `(AUTO MESSAGE)Your previous action '${interrupted_action}' was interrupted by ${mode.name}.
|
||||||
Your behavior log: ${logs}\nRespond accordingly.`);
|
Your behavior log: ${logs}\nRespond accordingly.`);
|
||||||
|
|
|
@ -34,6 +34,7 @@ export class Prompter {
|
||||||
let chat = this.profile.model;
|
let chat = this.profile.model;
|
||||||
this.cooldown = this.profile.cooldown ? this.profile.cooldown : 0;
|
this.cooldown = this.profile.cooldown ? this.profile.cooldown : 0;
|
||||||
this.last_prompt_time = 0;
|
this.last_prompt_time = 0;
|
||||||
|
this.awaiting_coding = false;
|
||||||
|
|
||||||
// try to get "max_tokens" parameter, else null
|
// try to get "max_tokens" parameter, else null
|
||||||
let max_tokens = null;
|
let max_tokens = null;
|
||||||
|
@ -225,6 +226,8 @@ export class Prompter {
|
||||||
}
|
}
|
||||||
|
|
||||||
async promptConvo(messages) {
|
async promptConvo(messages) {
|
||||||
|
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
|
for (let i = 0; i < 3; i++) { // try 3 times to avoid hallucinations
|
||||||
await this.checkCooldown();
|
await this.checkCooldown();
|
||||||
let prompt = this.profile.conversing;
|
let prompt = this.profile.conversing;
|
||||||
|
@ -236,16 +239,27 @@ export class Prompter {
|
||||||
console.warn('LLM hallucinated message as another bot. Trying again...');
|
console.warn('LLM hallucinated message as another bot. Trying again...');
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
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;
|
return generation;
|
||||||
}
|
}
|
||||||
return "*no response*";
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
async promptCoding(messages) {
|
async promptCoding(messages) {
|
||||||
|
if (this.awaiting_coding) {
|
||||||
|
console.warn('Already awaiting coding response, returning no response.');
|
||||||
|
return '```//no response```';
|
||||||
|
}
|
||||||
|
this.awaiting_coding = true;
|
||||||
await this.checkCooldown();
|
await this.checkCooldown();
|
||||||
let prompt = this.profile.coding;
|
let prompt = this.profile.coding;
|
||||||
prompt = await this.replaceStrings(prompt, messages, this.coding_examples);
|
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) {
|
async promptMemSaving(to_summarize) {
|
||||||
|
|
|
@ -45,7 +45,7 @@ export class SelfPrompter {
|
||||||
no_command_count++;
|
no_command_count++;
|
||||||
if (no_command_count >= MAX_NO_COMMAND) {
|
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.`;
|
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);
|
console.warn(out);
|
||||||
this.on = false;
|
this.on = false;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -45,7 +45,8 @@ export class Grok {
|
||||||
res = 'My brain disconnected, try again.';
|
res = 'My brain disconnected, try again.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
// sometimes outputs special token <|separator|>, just replace it
|
||||||
|
return res.replace(/<\|separator\|>/g, '*no response*');
|
||||||
}
|
}
|
||||||
|
|
||||||
async embed(text) {
|
async embed(text) {
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
|
import { mainProxy } from './main_proxy.js';
|
||||||
|
|
||||||
export class AgentProcess {
|
export class AgentProcess {
|
||||||
static runningCount = 0;
|
|
||||||
|
|
||||||
start(profile, load_memory=false, init_message=null, count_id=0, task_path=null, task_id=null) {
|
start(profile, load_memory=false, init_message=null, count_id=0, task_path=null, task_id=null) {
|
||||||
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('-p', profile);
|
||||||
args.push('-c', count_id);
|
args.push('-c', count_id);
|
||||||
if (load_memory)
|
if (load_memory)
|
||||||
|
@ -20,26 +23,22 @@ export class AgentProcess {
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
stderr: 'inherit',
|
stderr: 'inherit',
|
||||||
});
|
});
|
||||||
AgentProcess.runningCount++;
|
|
||||||
|
|
||||||
let last_restart = Date.now();
|
let last_restart = Date.now();
|
||||||
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;
|
||||||
|
mainProxy.logoutAgent(this.name);
|
||||||
|
|
||||||
if (code > 1) {
|
if (code > 1) {
|
||||||
console.log(`Ending task`);
|
console.log(`Ending task`);
|
||||||
process.exit(code);
|
process.exit(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code !== 0) {
|
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 ${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;
|
return;
|
||||||
}
|
}
|
||||||
console.log('Restarting agent...');
|
console.log('Restarting agent...');
|
||||||
|
@ -51,5 +50,18 @@ export class AgentProcess {
|
||||||
agentProcess.on('error', (err) => {
|
agentProcess.on('error', (err) => {
|
||||||
console.error('Agent process 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
54
src/process/main_proxy.js
Normal file
54
src/process/main_proxy.js
Normal file
|
@ -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();
|
|
@ -7,7 +7,9 @@ import { fileURLToPath } from 'url';
|
||||||
// Module-level variables
|
// Module-level variables
|
||||||
let io;
|
let io;
|
||||||
let server;
|
let server;
|
||||||
const connectedAgents = {};
|
const registeredAgents = new Set();
|
||||||
|
const inGameAgents = {};
|
||||||
|
const agentManagers = {}; // socket for main process that registers/controls agents
|
||||||
|
|
||||||
// Initialize the server
|
// Initialize the server
|
||||||
export function createMindServer(port = 8080) {
|
export function createMindServer(port = 8080) {
|
||||||
|
@ -24,28 +26,81 @@ export function createMindServer(port = 8080) {
|
||||||
let curAgentName = null;
|
let curAgentName = null;
|
||||||
console.log('Client connected');
|
console.log('Client connected');
|
||||||
|
|
||||||
socket.emit('agents-update', Object.keys(connectedAgents));
|
agentsUpdate(socket);
|
||||||
|
|
||||||
socket.on('register-agent', (agentName) => {
|
socket.on('register-agents', (agentNames) => {
|
||||||
console.log('Agent registered:', agentName);
|
console.log(`Registering agents: ${agentNames}`);
|
||||||
connectedAgents[agentName] = socket;
|
agentNames.forEach(name => registeredAgents.add(name));
|
||||||
curAgentName = agentName;
|
for (let name of agentNames) {
|
||||||
io.emit('agents-update', Object.keys(connectedAgents));
|
agentManagers[name] = socket;
|
||||||
|
}
|
||||||
|
socket.emit('register-agents-success');
|
||||||
|
agentsUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('chat-message', (agentName, json) => {
|
socket.on('login-agent', (agentName) => {
|
||||||
console.log(`${curAgentName} received message from ${agentName}: ${json}`);
|
if (curAgentName && curAgentName !== agentName) {
|
||||||
const agentSocket = connectedAgents[agentName];
|
console.warn(`Agent ${agentName} already logged in as ${curAgentName}`);
|
||||||
if (agentSocket) {
|
return;
|
||||||
agentSocket.emit('chat-message', curAgentName, json);
|
}
|
||||||
|
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', () => {
|
socket.on('disconnect', () => {
|
||||||
console.log('Client disconnected');
|
console.log('Client disconnected');
|
||||||
delete connectedAgents[socket.id];
|
if (inGameAgents[curAgentName]) {
|
||||||
io.emit('agents-update', Object.keys(connectedAgents));
|
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}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(port, 'localhost', () => {
|
server.listen(port, 'localhost', () => {
|
||||||
|
@ -54,6 +109,18 @@ export function createMindServer(port = 8080) {
|
||||||
|
|
||||||
return server;
|
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
|
// Optional: export these if you need access to them from other files
|
||||||
export const getIO = () => io;
|
export const getIO = () => io;
|
||||||
export const getServer = () => server;
|
export const getServer = () => server;
|
||||||
|
|
|
@ -1,33 +1,67 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Mindcraft Agents</title>
|
<title>Mindcraft</title>
|
||||||
<script src="/socket.io/socket.io.js"></script>
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
background: #f0f0f0;
|
background: #1a1a1a;
|
||||||
|
color: #e0e0e0;
|
||||||
}
|
}
|
||||||
#agents {
|
#agents {
|
||||||
background: white;
|
background: #2d2d2d;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
color: #333;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
.agent {
|
.agent {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: #f8f8f8;
|
background: #363636;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.restart-btn, .start-btn, .stop-btn {
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.restart-btn {
|
||||||
|
background: #4CAF50;
|
||||||
|
}
|
||||||
|
.start-btn {
|
||||||
|
background: #2196F3;
|
||||||
|
}
|
||||||
|
.stop-btn {
|
||||||
|
background: #f44336;
|
||||||
|
}
|
||||||
|
.restart-btn:hover { background: #45a049; }
|
||||||
|
.start-btn:hover { background: #1976D2; }
|
||||||
|
.stop-btn:hover { background: #d32f2f; }
|
||||||
|
.status-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.status-icon.online {
|
||||||
|
color: #4CAF50;
|
||||||
|
}
|
||||||
|
.status-icon.offline {
|
||||||
|
color: #f44336;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Connected Mindcraft Agents</h1>
|
<h1>Mindcraft</h1>
|
||||||
<div id="agents"></div>
|
<div id="agents"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -36,9 +70,36 @@
|
||||||
|
|
||||||
socket.on('agents-update', (agents) => {
|
socket.on('agents-update', (agents) => {
|
||||||
agentsDiv.innerHTML = agents.length ?
|
agentsDiv.innerHTML = agents.length ?
|
||||||
agents.map(name => `<div class="agent">${name}</div>`).join('') :
|
agents.map(agent => `
|
||||||
|
<div class="agent">
|
||||||
|
<span>
|
||||||
|
<span class="status-icon ${agent.in_game ? 'online' : 'offline'}">●</span>
|
||||||
|
${agent.name}
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
${agent.in_game ? `
|
||||||
|
<button class="stop-btn" onclick="stopAgent('${agent.name}')">Stop</button>
|
||||||
|
<button class="restart-btn" onclick="restartAgent('${agent.name}')">Restart</button>
|
||||||
|
` : `
|
||||||
|
<button class="start-btn" onclick="startAgent('${agent.name}')">Start</button>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('') :
|
||||||
'<div class="agent">No agents connected</div>';
|
'<div class="agent">No agents connected</div>';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function restartAgent(agentName) {
|
||||||
|
socket.emit('restart-agent', agentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startAgent(agentName) {
|
||||||
|
socket.emit('start-agent', agentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopAgent(agentName) {
|
||||||
|
socket.emit('stop-agent', agentName);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
Add table
Reference in a new issue