2023-12-21 15:11:38 -07:00
import { History } from './history.js' ;
import { Coder } from './coder.js' ;
2024-02-25 14:13:32 -06:00
import { Prompter } from './prompter.js' ;
2024-01-23 18:01:38 -06:00
import { initModes } from './modes.js' ;
2024-01-25 13:25:36 -08:00
import { initBot } from '../utils/mcdata.js' ;
2024-04-30 23:07:07 -05:00
import { containsCommand , commandExists , executeCommand , truncCommandMessage , isAction } from './commands/index.js' ;
2024-03-05 12:05:46 -08:00
import { NPCContoller } from './npc/controller.js' ;
2024-04-27 23:28:34 -05:00
import { MemoryBank } from './memory_bank.js' ;
2024-05-04 15:42:30 -05:00
import { SelfPrompter } from './self_prompter.js' ;
2024-05-29 21:49:45 -05:00
import settings from '../../settings.js' ;
2023-11-07 09:44:56 -06:00
export class Agent {
2024-02-25 14:13:32 -06:00
async start ( profile _fp , load _mem = false , init _message = null ) {
this . prompter = new Prompter ( this , profile _fp ) ;
this . name = this . prompter . getName ( ) ;
2023-12-21 20:42:01 -07:00
this . history = new History ( this ) ;
2023-11-07 12:00:55 -06:00
this . coder = new Coder ( this ) ;
2024-03-05 12:05:46 -08:00
this . npc = new NPCContoller ( this ) ;
2024-04-27 23:28:34 -05:00
this . memory _bank = new MemoryBank ( ) ;
2024-05-04 15:42:30 -05:00
this . self _prompter = new SelfPrompter ( this ) ;
2023-11-19 15:34:53 -06:00
2024-02-25 14:13:32 -06:00
await this . prompter . initExamples ( ) ;
2024-01-30 16:43:30 -06:00
console . log ( 'Logging in...' ) ;
2024-02-25 14:13:32 -06:00
this . bot = initBot ( this . name ) ;
2023-12-04 23:26:21 -08:00
2024-01-23 18:01:38 -06:00
initModes ( this ) ;
2023-12-04 23:26:21 -08:00
2024-05-08 23:59:44 -05:00
let save _data = null ;
if ( load _mem ) {
save _data = this . history . load ( ) ;
}
2024-04-20 22:18:26 -05:00
2024-04-02 19:19:30 -05:00
this . bot . once ( 'spawn' , async ( ) => {
2024-04-20 22:18:26 -05:00
// wait for a bit so stats are not undefined
await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 ) ) ;
2024-04-02 19:19:30 -05:00
console . log ( ` ${ this . name } spawned. ` ) ;
2024-01-15 11:04:50 -06:00
this . coder . clear ( ) ;
2023-12-26 14:42:45 -07:00
const ignore _messages = [
"Set own game mode to" ,
"Set the time to" ,
"Set the difficulty to" ,
"Teleported " ,
"Set the weather to" ,
"Gamerule "
] ;
2024-05-29 21:33:29 -05:00
const eventname = settings . profiles . length > 1 ? 'whisper' : 'chat' ;
this . bot . on ( eventname , ( username , message ) => {
2023-12-12 21:35:39 -08:00
if ( username === this . name ) return ;
2023-12-26 14:42:45 -07:00
if ( ignore _messages . some ( ( m ) => message . startsWith ( m ) ) ) return ;
2023-12-12 21:35:39 -08:00
console . log ( 'received message from' , username , ':' , message ) ;
2024-08-22 15:57:20 -05:00
this . shut _up = false ;
2023-12-10 20:18:20 -06:00
2023-12-20 16:30:05 -08:00
this . handleMessage ( username , message ) ;
2023-12-12 21:35:39 -08:00
} ) ;
2024-01-14 14:33:25 -06:00
// set the bot to automatically eat food when hungry
this . bot . autoEat . options = {
priority : 'foodPoints' ,
startAt : 14 ,
2024-01-23 18:01:38 -06:00
bannedFood : [ "rotten_flesh" , "spider_eye" , "poisonous_potato" , "pufferfish" , "chicken" ]
2024-01-14 14:33:25 -06:00
} ;
2023-12-26 14:42:45 -07:00
2024-05-10 12:18:47 -05:00
if ( save _data && save _data . self _prompt ) { // if we're loading memory and self-prompting was on, restart it, ignore init_message
2024-05-08 23:59:44 -05:00
let prompt = save _data . self _prompt ;
2024-05-16 20:24:50 -05:00
// add initial message to history
this . history . add ( 'system' , prompt ) ;
2024-05-08 23:59:44 -05:00
this . self _prompter . start ( prompt ) ;
}
else if ( init _message ) {
2024-06-23 20:15:28 -05:00
this . handleMessage ( 'system' , init _message , 2 ) ;
2024-05-08 23:59:44 -05:00
}
else {
2024-01-14 14:33:25 -06:00
this . bot . chat ( 'Hello world! I am ' + this . name ) ;
2023-12-12 21:35:39 -08:00
this . bot . emit ( 'finished_executing' ) ;
}
2024-01-23 18:01:38 -06:00
2024-01-25 13:25:36 -08:00
this . startEvents ( ) ;
2023-11-07 09:44:56 -06:00
} ) ;
2023-12-04 23:26:21 -08:00
}
2024-01-24 17:24:52 -06:00
cleanChat ( message ) {
// newlines are interpreted as separate chats, which triggers spam filters. replace them with spaces
message = message . replaceAll ( '\n' , ' ' ) ;
return this . bot . chat ( message ) ;
}
2024-08-22 15:57:20 -05:00
shutUp ( ) {
this . shut _up = true ;
if ( this . self _prompter . on ) {
this . self _prompter . stop ( false ) ;
}
}
async handleMessage ( source , message , max _responses = null ) {
2024-04-30 23:07:07 -05:00
let used _command = false ;
2024-08-22 15:57:20 -05:00
if ( max _responses === null ) {
max _responses = settings . max _commands === - 1 ? Infinity : settings . max _commands ;
}
2024-04-30 23:07:07 -05:00
2024-06-23 20:15:28 -05:00
let self _prompt = source === 'system' || source === this . name ;
if ( ! self _prompt ) {
2024-04-30 23:07:07 -05:00
const user _command _name = containsCommand ( message ) ;
if ( user _command _name ) {
if ( ! commandExists ( user _command _name ) ) {
this . bot . chat ( ` Command ' ${ user _command _name } ' does not exist. ` ) ;
return false ;
}
this . bot . chat ( ` * ${ source } used ${ user _command _name . substring ( 1 ) } * ` ) ;
if ( user _command _name === '!newAction' ) {
// all user initiated commands are ignored by the bot except for this one
// add the preceding message to the history to give context for newAction
2024-06-23 18:54:13 -05:00
this . history . add ( source , message ) ;
2024-04-30 23:07:07 -05:00
}
2024-06-23 18:54:13 -05:00
let execute _res = await executeCommand ( this , message ) ;
2024-04-30 23:07:07 -05:00
if ( execute _res )
this . cleanChat ( execute _res ) ;
return true ;
2024-01-13 12:10:15 -06:00
}
2024-01-09 22:50:22 -06:00
}
2024-06-23 20:15:28 -05:00
2024-08-22 15:57:20 -05:00
const checkInterrupt = ( ) => this . self _prompter . shouldInterrupt ( self _prompt ) || this . shut _up ;
2024-09-27 17:03:00 -05:00
let behavior _log = this . bot . modes . flushBehaviorLog ( ) ;
if ( behavior _log !== '' ) {
const MAX _LOG = 500 ;
if ( behavior _log . length > MAX _LOG ) {
behavior _log = behavior _log . substring ( behavior _log . length - MAX _LOG ) + '...' ;
}
behavior _log = 'Recent behaviors log: \n' + behavior _log . substring ( behavior _log . indexOf ( '\n' ) ) ;
await this . history . add ( 'system' , behavior _log ) ;
}
2024-06-23 20:15:28 -05:00
await this . history . add ( source , message ) ;
this . history . save ( ) ;
if ( ! self _prompt && this . self _prompter . on ) // message is from user during self-prompting
max _responses = 1 ; // force only respond to this message, then let self-prompting take over
for ( let i = 0 ; i < max _responses ; i ++ ) {
2024-08-22 15:57:20 -05:00
if ( checkInterrupt ( ) ) break ;
2024-02-25 14:13:32 -06:00
let history = this . history . getHistory ( ) ;
let res = await this . prompter . promptConvo ( history ) ;
2023-12-04 23:26:21 -08:00
2024-01-03 22:16:50 -08:00
let command _name = containsCommand ( res ) ;
2024-01-09 22:50:22 -06:00
if ( command _name ) { // contains query or command
2024-02-25 14:13:32 -06:00
console . log ( ` Full response: "" ${ res } "" ` )
res = truncCommandMessage ( res ) ; // everything after the command is ignored
this . history . add ( this . name , res ) ;
2024-01-14 14:33:25 -06:00
if ( ! commandExists ( command _name ) ) {
2024-06-23 20:15:28 -05:00
this . history . add ( 'system' , ` Command ${ command _name } does not exist. ` ) ;
2024-05-04 15:42:30 -05:00
console . warn ( 'Agent hallucinated command:' , command _name )
continue ;
}
if ( command _name === '!stopSelfPrompt' && self _prompt ) {
this . history . add ( 'system' , ` Cannot stopSelfPrompt unless requested by user. ` ) ;
2024-01-14 14:33:25 -06:00
continue ;
}
2024-05-04 15:42:30 -05:00
2024-08-22 15:57:20 -05:00
if ( checkInterrupt ( ) ) break ;
2024-06-23 20:15:28 -05:00
this . self _prompter . handleUserPromptedCmd ( self _prompt , isAction ( command _name ) ) ;
2024-05-04 15:42:30 -05:00
2024-06-23 20:15:28 -05:00
if ( settings . verbose _commands ) {
this . cleanChat ( res ) ;
}
else { // only output command name
let pre _message = res . substring ( 0 , res . indexOf ( command _name ) ) . trim ( ) ;
let chat _message = ` *used ${ command _name . substring ( 1 ) } * ` ;
if ( pre _message . length > 0 )
chat _message = ` ${ pre _message } ${ chat _message } ` ;
this . cleanChat ( res ) ;
2024-04-30 23:07:07 -05:00
}
2024-01-11 15:59:52 -06:00
let execute _res = await executeCommand ( this , res ) ;
2024-01-03 22:16:50 -08:00
2024-01-09 22:50:22 -06:00
console . log ( 'Agent executed:' , command _name , 'and got:' , execute _res ) ;
2024-04-30 23:07:07 -05:00
used _command = true ;
2024-01-03 22:16:50 -08:00
if ( execute _res )
this . history . add ( 'system' , execute _res ) ;
else
break ;
2023-12-20 16:30:05 -08:00
}
else { // conversation response
2024-02-25 14:13:32 -06:00
this . history . add ( this . name , res ) ;
2024-01-24 17:24:52 -06:00
this . cleanChat ( res ) ;
2023-12-20 16:30:05 -08:00
console . log ( 'Purely conversational response:' , res ) ;
break ;
}
2024-08-22 15:57:20 -05:00
this . history . save ( ) ;
2023-12-04 23:26:21 -08:00
}
2023-12-16 12:08:47 -06:00
this . bot . emit ( 'finished_executing' ) ;
2024-04-30 23:07:07 -05:00
return used _command ;
}
2024-01-25 13:25:36 -08:00
startEvents ( ) {
// Custom events
this . bot . on ( 'time' , ( ) => {
if ( this . bot . time . timeOfDay == 0 )
this . bot . emit ( 'sunrise' ) ;
else if ( this . bot . time . timeOfDay == 6000 )
this . bot . emit ( 'noon' ) ;
else if ( this . bot . time . timeOfDay == 12000 )
this . bot . emit ( 'sunset' ) ;
else if ( this . bot . time . timeOfDay == 18000 )
this . bot . emit ( 'midnight' ) ;
} ) ;
2024-04-20 22:18:26 -05:00
let prev _health = this . bot . health ;
this . bot . lastDamageTime = 0 ;
this . bot . lastDamageTaken = 0 ;
2024-01-25 13:25:36 -08:00
this . bot . on ( 'health' , ( ) => {
2024-04-20 22:18:26 -05:00
if ( this . bot . health < prev _health ) {
this . bot . lastDamageTime = Date . now ( ) ;
this . bot . lastDamageTaken = prev _health - this . bot . health ;
}
prev _health = this . bot . health ;
2024-01-25 13:25:36 -08:00
} ) ;
// Logging callbacks
2024-01-24 17:24:52 -06:00
this . bot . on ( 'error' , ( err ) => {
console . error ( 'Error event!' , err ) ;
} ) ;
this . bot . on ( 'end' , ( reason ) => {
console . warn ( 'Bot disconnected! Killing agent process.' , reason )
2024-05-20 00:52:08 -05:00
this . cleanKill ( 'Bot disconnected! Killing agent process.' ) ;
2024-01-23 18:01:38 -06:00
} ) ;
this . bot . on ( 'death' , ( ) => {
2024-02-05 13:48:02 -06:00
this . coder . cancelResume ( ) ;
2024-01-23 18:01:38 -06:00
this . coder . stop ( ) ;
} ) ;
2024-01-24 17:24:52 -06:00
this . bot . on ( 'kicked' , ( reason ) => {
console . warn ( 'Bot kicked!' , reason ) ;
2024-05-20 00:52:08 -05:00
this . cleanKill ( 'Bot kicked! Killing agent process.' ) ;
2024-01-24 17:24:52 -06:00
} ) ;
2024-01-23 18:01:38 -06:00
this . bot . on ( 'messagestr' , async ( message , _ , jsonMsg ) => {
if ( jsonMsg . translate && jsonMsg . translate . startsWith ( 'death' ) && message . startsWith ( this . name ) ) {
console . log ( 'Agent died: ' , message ) ;
this . handleMessage ( 'system' , ` You died with the final message: ' ${ message } '. Previous actions were stopped and you have respawned. Notify the user and perform any necessary actions. ` ) ;
}
2024-03-05 16:40:06 -08:00
} ) ;
2024-01-26 12:11:32 -08:00
this . bot . on ( 'idle' , ( ) => {
2024-04-20 22:18:26 -05:00
this . bot . clearControlStates ( ) ;
2024-05-04 16:17:41 -05:00
this . bot . pathfinder . stop ( ) ; // clear any lingering pathfinder
2024-02-02 15:34:17 -06:00
this . bot . modes . unPauseAll ( ) ;
2024-03-05 12:05:46 -08:00
this . coder . executeResume ( ) ;
2024-01-26 12:11:32 -08:00
} ) ;
2024-03-05 12:05:46 -08:00
// Init NPC controller
this . npc . init ( ) ;
2024-01-26 13:18:30 -06:00
// This update loop ensures that each update() is called one at a time, even if it takes longer than the interval
const INTERVAL = 300 ;
2024-05-04 15:42:30 -05:00
let last = Date . now ( ) ;
2024-01-26 13:18:30 -06:00
setTimeout ( async ( ) => {
while ( true ) {
let start = Date . now ( ) ;
2024-05-04 15:42:30 -05:00
await this . update ( start - last ) ;
2024-01-26 13:18:30 -06:00
let remaining = INTERVAL - ( Date . now ( ) - start ) ;
if ( remaining > 0 ) {
await new Promise ( ( resolve ) => setTimeout ( resolve , remaining ) ) ;
}
2024-05-04 15:42:30 -05:00
last = start ;
2024-01-26 13:18:30 -06:00
}
} , INTERVAL ) ;
2024-02-12 16:33:28 -08:00
this . bot . emit ( 'idle' ) ;
2024-01-23 18:01:38 -06:00
}
2024-01-26 15:41:55 -06:00
2024-05-04 15:42:30 -05:00
async update ( delta ) {
await this . bot . modes . update ( ) ;
await this . self _prompter . update ( delta ) ;
}
2024-01-26 15:41:55 -06:00
isIdle ( ) {
return ! this . coder . executing && ! this . coder . generating ;
}
2024-05-20 00:52:08 -05:00
2024-05-08 23:59:44 -05:00
cleanKill ( msg = 'Killing agent process...' ) {
this . history . add ( 'system' , msg ) ;
2024-05-20 00:52:08 -05:00
this . bot . chat ( 'Goodbye world.' )
2024-05-08 23:59:44 -05:00
this . history . save ( ) ;
process . exit ( 1 ) ;
}
2023-11-07 09:44:56 -06:00
}
2024-05-08 23:59:44 -05:00