import settings from '../../settings.js'; import { readFileSync } from 'fs'; import { containsCommand } from './commands/index.js'; import { sendBotChatToServer } from './server_proxy.js'; let agent; let agent_names = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name); 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 { 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; } queue(message) { this.in_queue.push(message); } } const convos = {}; function _getConvo(name) { if (!convos[name]) convos[name] = new Conversation(name); return convos[name]; } export async function startConversation(send_to, message) { const convo = _getConvo(send_to); convo.reset(); if (agent.self_prompter.on) { await agent.self_prompter.stop(); self_prompter_paused = true; } convo.active = true; sendToBot(send_to, message, true); } export function sendToBot(send_to, message, start=false) { if (settings.chat_bot_messages) agent.bot.chat(`(To ${send_to}) ${message}`); if (!isOtherAgent(send_to)) { agent.bot.whisper(send_to, message); return; } const convo = _getConvo(send_to); if (convo.ignore_until_start) return; const end = message.includes('!endConversation'); const json = { 'message': message, start, end, }; // agent.bot.whisper(send_to, JSON.stringify(json)); sendBotChatToServer(send_to, JSON.stringify(json)); } export async function recieveFromBot(sender, json) { const convo = _getConvo(sender); console.log(`decoding **${json}**`); const recieved = JSON.parse(json); if (recieved.start) { convo.reset(); } if (convo.ignore_until_start) return; convo.queue(recieved); // responding to conversation takes priority over self prompting if (agent.self_prompter.on){ await agent.self_prompter.stopLoop(); self_prompter_paused = true; } _scheduleProcessInMessage(sender, recieved, convo); } /* 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 recieved during the delay will reset the delay following this logic, and be queued to respond in bulk */ const talkOverActions = ['stay', 'followPlayer']; const fastDelay = 200; const longDelay = 5000; async function _scheduleProcessInMessage(sender, recieved, convo) { if (convo.inMessageTimer) clearTimeout(convo.inMessageTimer); let otherAgentBusy = containsCommand(recieved.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 shouldRespond = await agent.prompter.promptShouldRespondToBot(recieved.message); console.log(`${agent.name} decided to ${shouldRespond?'respond':'not respond'} to ${sender}`); if (shouldRespond) scheduleResponse(fastDelay); } else { // neither are busy scheduleResponse(fastDelay); } } export function _processInMessageQueue(name) { const convo = _getConvo(name); let pack = null; let full_message = ''; while (convo.in_queue.length > 0) { pack = convo.in_queue.shift(); full_message += pack.message; } pack.message = full_message; _handleFullInMessage(name, pack); } export function _handleFullInMessage(sender, recieved) { console.log(`responding to **${JSON.stringify(recieved)}**`); const convo = _getConvo(sender); const message = _tagMessage(recieved.message); if (recieved.end) { convo.end(); // if end signal from other bot, add to history but don't respond agent.history.add(sender, message); return; } if (recieved.start) agent.shut_up = false; agent.handleMessage(sender, message); } function _tagMessage(message) { return "(FROM OTHER BOT)" + message; } async function _resumeSelfPrompter() { await new Promise(resolve => setTimeout(resolve, 5000)); self_prompter_paused = false; agent.self_prompter.start(); }