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-02-25 14:13:32 -06:00
import { containsCommand , commandExists , executeCommand , truncCommandMessage } 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' ;
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 ( ) ;
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-04-20 22:18:26 -05:00
if ( load _mem )
this . history . load ( ) ;
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-20 00:23:19 -05:00
this . bot . on ( 'whisper' , ( 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 ) ;
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
2023-12-12 21:35:39 -08:00
if ( init _message ) {
2023-12-20 16:30:05 -08:00
this . handleMessage ( 'system' , init _message ) ;
2023-12-12 21:35:39 -08: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 ) ;
}
2023-12-20 16:30:05 -08:00
async handleMessage ( source , message ) {
2023-12-26 14:42:45 -07:00
if ( ! ! source && ! ! message )
await this . history . add ( source , message ) ;
2023-12-04 23:26:21 -08:00
2024-01-09 22:50:22 -06:00
const user _command _name = containsCommand ( message ) ;
if ( user _command _name ) {
2024-01-27 19:24:02 -06:00
if ( ! commandExists ( user _command _name ) ) {
this . bot . chat ( ` Command ' ${ user _command _name } ' does not exist. ` ) ;
return ;
}
2024-01-09 22:50:22 -06:00
this . bot . chat ( ` * ${ source } used ${ user _command _name . substring ( 1 ) } * ` ) ;
2024-01-11 15:59:52 -06:00
let execute _res = await executeCommand ( this , message ) ;
2024-01-13 12:10:15 -06:00
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
let truncated _msg = message . substring ( 0 , message . indexOf ( user _command _name ) ) . trim ( ) ;
this . history . add ( source , truncated _msg ) ;
}
2024-01-24 17:24:52 -06:00
if ( execute _res )
this . cleanChat ( execute _res ) ;
2024-01-09 22:50:22 -06:00
return ;
}
2023-12-20 16:30:05 -08:00
for ( let i = 0 ; i < 5 ; i ++ ) {
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 ) ) {
this . history . add ( 'system' , ` Command ${ command _name } does not exist. Use !newAction to perform custom actions. ` ) ;
2024-01-15 11:04:50 -06:00
console . log ( 'Agent hallucinated command:' , command _name )
2024-01-14 14:33:25 -06:00
continue ;
}
2024-01-11 15:59:52 -06:00
let pre _message = res . substring ( 0 , res . indexOf ( command _name ) ) . trim ( ) ;
2024-02-25 14:13:32 -06:00
let chat _message = ` *used ${ command _name . substring ( 1 ) } * ` ;
2024-02-18 22:56:38 -06:00
if ( pre _message . length > 0 )
2024-02-25 14:13:32 -06:00
chat _message = ` ${ pre _message } ${ chat _message } ` ;
this . cleanChat ( chat _message ) ;
2024-01-03 22:16:50 -08: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-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 ;
}
2023-12-04 23:26:21 -08:00
}
this . history . save ( ) ;
2023-12-16 12:08:47 -06:00
this . bot . emit ( 'finished_executing' ) ;
2023-11-07 09:44:56 -06:00
}
2024-01-23 18:01:38 -06:00
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 ;
setTimeout ( async ( ) => {
while ( true ) {
let start = Date . now ( ) ;
await this . bot . modes . update ( ) ;
let remaining = INTERVAL - ( Date . now ( ) - start ) ;
if ( remaining > 0 ) {
await new Promise ( ( resolve ) => setTimeout ( resolve , remaining ) ) ;
}
}
} , 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
isIdle ( ) {
return ! this . coder . executing && ! this . coder . generating ;
}
2024-05-20 00:52:08 -05:00
cleanKill ( msg = 'Killing agent process...' ) {
this . history . add ( 'system' , msg ) ;
this . bot . chat ( 'Goodbye world.' )
this . history . save ( ) ;
process . exit ( 1 ) ;
}
2023-11-07 09:44:56 -06:00
}