mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-07-19 06:25:17 +02:00
354 lines
11 KiB
JavaScript
354 lines
11 KiB
JavaScript
import settings from '../../settings.js';
|
|
import { readFileSync } from 'fs';
|
|
import { containsCommand } from './commands/index.js';
|
|
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 = [];
|
|
|
|
class Conversation {
|
|
constructor(name) {
|
|
this.name = name;
|
|
this.active = false;
|
|
this.ignore_until_start = false;
|
|
this.blocked = false;
|
|
this.in_queue = [];
|
|
this.inMessageTimer = null;
|
|
}
|
|
|
|
reset() {
|
|
this.active = false;
|
|
this.ignore_until_start = false;
|
|
this.in_queue = [];
|
|
this.inMessageTimer = null;
|
|
}
|
|
|
|
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 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) {
|
|
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 (!agent.self_prompter.isPaused()) {
|
|
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();
|
|
|
|
if (agent.self_prompter.isActive()) {
|
|
await agent.self_prompter.pause();
|
|
}
|
|
if (convo.active)
|
|
return;
|
|
convo.active = true;
|
|
this.activeConversation = convo;
|
|
this._startMonitor();
|
|
this.sendToBot(send_to, message, true, false);
|
|
}
|
|
|
|
startConversationFromOtherBot(name) {
|
|
const convo = this._getConvo(name);
|
|
convo.active = true;
|
|
this.activeConversation = convo;
|
|
this._startMonitor();
|
|
}
|
|
|
|
sendToBot(send_to, message, start=false, open_chat=true) {
|
|
if (!this.isOtherAgent(send_to)) {
|
|
console.warn(`${agent.name} tried to send bot message to non-bot ${send_to}`);
|
|
return;
|
|
}
|
|
const convo = this._getConvo(send_to);
|
|
|
|
if (settings.chat_bot_messages && open_chat)
|
|
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,
|
|
};
|
|
|
|
this.awaiting_response = true;
|
|
sendBotChatToServer(send_to, json);
|
|
}
|
|
|
|
async receiveFromBot(sender, received) {
|
|
const convo = this._getConvo(sender);
|
|
|
|
if (convo.ignore_until_start && !received.start)
|
|
return;
|
|
|
|
// check if any convo is active besides the sender
|
|
if (this.inConversation() && !this.inConversation(sender)) {
|
|
this.sendToBot(sender, `I'm talking to someone else, try again later. !endConversation("${sender}")`, false, false);
|
|
this.endConversation(sender);
|
|
return;
|
|
}
|
|
|
|
if (received.start) {
|
|
convo.reset();
|
|
this.startConversationFromOtherBot(sender);
|
|
}
|
|
|
|
this._clearMonitorTimeouts();
|
|
convo.queue(received);
|
|
|
|
// responding to conversation takes priority over self prompting
|
|
if (agent.self_prompter.isActive()){
|
|
await agent.self_prompter.pause();
|
|
}
|
|
|
|
_scheduleProcessInMessage(sender, received, 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);
|
|
}
|
|
|
|
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();
|
|
if (this.activeConversation.name === sender) {
|
|
this._stopMonitor();
|
|
this.activeConversation = null;
|
|
if (agent.self_prompter.isPaused() && !this.inConversation()) {
|
|
_resumeSelfPrompter();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
endAllConversations() {
|
|
for (const sender in this.convos) {
|
|
this.endConversation(sender);
|
|
}
|
|
if (agent.self_prompter.isPaused()) {
|
|
_resumeSelfPrompter();
|
|
}
|
|
}
|
|
|
|
forceEndCurrentConversation() {
|
|
if (this.activeConversation) {
|
|
let sender = this.activeConversation.name;
|
|
this.sendToBot(sender, '!endConversation("' + sender + '")', false, false);
|
|
this.endConversation(sender);
|
|
}
|
|
}
|
|
}
|
|
|
|
const convoManager = new ConversationManager();
|
|
export default convoManager;
|
|
|
|
/*
|
|
This function controls conversation flow by deciding when the bot responds.
|
|
The logic is as follows:
|
|
- If neither bot is busy, respond quickly with a small delay.
|
|
- If only the other bot is busy, respond with a long delay to allow it to finish short actions (ex check inventory)
|
|
- If I'm busy but other bot isn't, let LLM decide whether to respond
|
|
- If both bots are busy, don't respond until someone is done, excluding a few actions that allow fast responses
|
|
- New messages received during the delay will reset the delay following this logic, and be queued to respond in bulk
|
|
*/
|
|
const talkOverActions = ['stay', 'followPlayer', 'mode:']; // all mode actions
|
|
const fastDelay = 200;
|
|
const longDelay = 5000;
|
|
async function _scheduleProcessInMessage(sender, received, convo) {
|
|
if (convo.inMessageTimer)
|
|
clearTimeout(convo.inMessageTimer);
|
|
let otherAgentBusy = containsCommand(received.message);
|
|
|
|
const scheduleResponse = (delay) => convo.inMessageTimer = setTimeout(() => _processInMessageQueue(sender), delay);
|
|
|
|
if (!agent.isIdle() && otherAgentBusy) {
|
|
// both are busy
|
|
let canTalkOver = talkOverActions.some(a => agent.actions.currentActionLabel.includes(a));
|
|
if (canTalkOver)
|
|
scheduleResponse(fastDelay)
|
|
// otherwise don't respond
|
|
}
|
|
else if (otherAgentBusy)
|
|
// other bot is busy but I'm not
|
|
scheduleResponse(longDelay);
|
|
else if (!agent.isIdle()) {
|
|
// I'm busy but other bot isn't
|
|
let canTalkOver = talkOverActions.some(a => agent.actions.currentActionLabel.includes(a));
|
|
if (canTalkOver) {
|
|
scheduleResponse(fastDelay);
|
|
}
|
|
else {
|
|
let shouldRespond = await agent.prompter.promptShouldRespondToBot(received.message);
|
|
console.log(`${agent.name} decided to ${shouldRespond?'respond':'not respond'} to ${sender}`);
|
|
if (shouldRespond)
|
|
scheduleResponse(fastDelay);
|
|
}
|
|
}
|
|
else {
|
|
// neither are busy
|
|
scheduleResponse(fastDelay);
|
|
}
|
|
}
|
|
|
|
function _processInMessageQueue(name) {
|
|
const convo = convoManager._getConvo(name);
|
|
_handleFullInMessage(name, _compileInMessages(convo));
|
|
}
|
|
|
|
function _compileInMessages(convo) {
|
|
let pack = {};
|
|
let full_message = '';
|
|
while (convo.in_queue.length > 0) {
|
|
pack = convo.in_queue.shift();
|
|
full_message += pack.message;
|
|
}
|
|
pack.message = full_message;
|
|
return pack;
|
|
}
|
|
|
|
function _handleFullInMessage(sender, received) {
|
|
console.log(`${agent.name} responding to "${received.message}" from ${sender}`);
|
|
|
|
const convo = convoManager._getConvo(sender);
|
|
convo.active = true;
|
|
|
|
let message = _tagMessage(received.message);
|
|
if (received.end) {
|
|
convoManager.endConversation(sender);
|
|
message = `Conversation with ${sender} ended with message: "${message}"`;
|
|
sender = 'system'; // bot will respond to system instead of the other bot
|
|
}
|
|
else if (received.start)
|
|
agent.shut_up = false;
|
|
convo.inMessageTimer = null;
|
|
agent.handleMessage(sender, message);
|
|
}
|
|
|
|
|
|
function _tagMessage(message) {
|
|
return "(FROM OTHER BOT)" + message;
|
|
}
|
|
|
|
async function _resumeSelfPrompter() {
|
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
if (agent.self_prompter.isPaused() && !convoManager.inConversation()) {
|
|
agent.self_prompter.start();
|
|
}
|
|
}
|