2025-02-08 22:41:07 -08:00
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 ;
}
2025-02-27 21:15:40 -08:00
if ( ! agent . self _prompter . isPaused ( ) ) {
2025-02-08 22:41:07 -08:00
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 ( ) ;
2025-02-27 21:15:40 -08:00
if ( agent . self _prompter . isActive ( ) ) {
await agent . self _prompter . pause ( ) ;
2025-02-08 22:41:07 -08:00
}
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
2025-02-27 21:15:40 -08:00
if ( agent . self _prompter . isActive ( ) ) {
await agent . self _prompter . pause ( ) ;
2025-02-08 22:41:07 -08:00
}
_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 ;
2025-02-27 21:15:40 -08:00
if ( agent . self _prompter . isPaused ( ) && ! this . inConversation ( ) ) {
2025-02-08 22:41:07 -08:00
_resumeSelfPrompter ( ) ;
}
}
}
}
endAllConversations ( ) {
for ( const sender in this . convos ) {
this . endConversation ( sender ) ;
}
2025-02-27 21:15:40 -08:00
if ( agent . self _prompter . isPaused ( ) ) {
2025-02-08 22:41:07 -08:00
_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 ) ) ;
2025-02-27 21:15:40 -08:00
if ( agent . self _prompter . isPaused ( ) && ! convoManager . inConversation ( ) ) {
2025-02-08 22:41:07 -08:00
agent . self _prompter . start ( ) ;
}
}