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-10-02 00:57:28 -05:00
import { handleTranslation , handleEnglishTranslation } from '../utils/translator.js' ;
2024-10-10 17:37:02 -05:00
import { addViewer } from './viewer.js' ;
import settings from '../../settings.js' ;
2023-11-07 09:44:56 -06:00
export class Agent {
2024-10-10 17:37:02 -05:00
async start ( profile _fp , load _mem = false , init _message = null , count _id = 0 ) {
2024-02-25 14:13:32 -06:00
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-10-10 17:37:02 -05:00
addViewer ( this . bot , count _id ) ;
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' ;
2024-08-25 14:06:57 -05:00
this . bot . on ( eventname , async ( 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 ;
2024-08-29 19:18:51 -05:00
let translation = await handleEnglishTranslation ( message ) ;
2024-08-25 14:06:57 -05:00
console . log ( 'received message from' , username , ':' , translation ) ;
2024-08-22 15:57:20 -05:00
this . shut _up = false ;
2023-12-10 20:18:20 -06:00
2024-08-25 14:06:57 -05:00
this . handleMessage ( username , translation ) ;
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-10-02 01:01:22 -05:00
const translation = await handleTranslation ( "Hello world! I am " + this . name ) ;
2024-10-02 00:57:28 -05:00
this . bot . chat ( translation ) ;
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-08-25 14:06:57 -05:00
2024-10-02 00:57:28 -05:00
async cleanChat ( message , translate _up _to = - 1 ) {
2024-10-02 01:01:22 -05:00
let to _translate = message ;
let remainging = '' ;
if ( translate _up _to != - 1 ) {
to _translate = to _translate . substring ( 0 , translate _up _to ) ;
remainging = message . substring ( translate _up _to ) ;
}
message = ( await handleTranslation ( to _translate ) ) . trim ( ) + " " + remainging ;
2024-01-24 17:24:52 -06:00
// newlines are interpreted as separate chats, which triggers spam filters. replace them with spaces
2024-10-02 00:57:28 -05:00
message = message . replaceAll ( '\n' , ' ' ) ;
return this . bot . chat ( message ) ;
2024-01-24 17:24:52 -06:00
}
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-10-10 22:18:10 -05:00
if ( max _responses === - 1 ) {
max _responses = Infinity ;
}
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 ( ) ;
2024-09-29 13:35:15 -07:00
if ( behavior _log . trim ( ) . length > 0 ) {
2024-09-27 17:03:00 -05:00
const MAX _LOG = 500 ;
if ( behavior _log . length > MAX _LOG ) {
2024-09-29 13:35:15 -07:00
behavior _log = '...' + behavior _log . substring ( behavior _log . length - MAX _LOG ) ;
2024-09-27 17:03:00 -05:00
}
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 ) {
2024-10-02 00:57:28 -05:00
this . cleanChat ( res , res . indexOf ( command _name ) ) ;
2024-06-23 20:15:28 -05:00
}
else { // only output command name
2024-10-02 01:01:22 -05:00
let pre _message = res . substring ( 0 , res . indexOf ( command _name ) ) . trim ( ) ;
2024-06-23 20:15:28 -05:00
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 ) ;
2024-10-27 20:35:02 +10:00
let death _pos = this . bot . entity . position ;
this . memory _bank . rememberPlace ( 'last death position' , death _pos ) ;
let death _pos _text = null ;
if ( death _pos ) {
death _pos _text = ` x: ${ death _pos . x . toFixed ( 2 ) } , y: ${ death _pos . y . toFixed ( 2 ) } , z: ${ death _pos . x . toFixed ( 2 ) } ` ;
}
2024-10-30 22:18:17 +10:00
let dimention = this . bot . game . dimension ;
2024-10-29 22:41:43 +10:00
this . history . add ( 'system' , ` You died at position ${ death _pos _text || "unknown" } with the final message: ' ${ message } '. Previous actions were stopped and you have since respawned. ` ) ;
2024-10-30 22:18:17 +10:00
this . handleMessage ( 'system' , ` You died at position ${ death _pos _text || "unknown" } in the dimention ${ dimention } with the final message: ' ${ message } '. Previous actions were stopped and you have respawned. Notify the user and perform any necessary actions. use !goToDeath to return to death position. (this will automaticly take you to your death position and pick up your items) ` ) ;
2024-01-23 18:01:38 -06:00
}
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
}