@@ -369,26 +369,67 @@ export async function loadCharacters(
369369 return loadedCharacters ;
370370}
371371
372+ interface ReceivedCharacter {
373+ name : string ;
374+ token_id : string ;
375+ }
376+
377+ export async function loadCharactersFromServer ( ) : Promise < Character [ ] > {
378+ const loadedCharacters : Character [ ] = [ ] ;
379+ try {
380+ const response = await fetch ( `${ process . env . CHARACTER_SERVER_URL } /fetch-characters` ) ;
381+ if ( ! response . ok ) {
382+ throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
383+ }
384+ const characterData = await response . json ( ) ;
385+ const characters : ReceivedCharacter [ ] = characterData . characterNames as ReceivedCharacter [ ] ;
386+
387+ // iterate over the characters and load them
388+ for ( const char of characters ) {
389+ const tokenId = char . token_id ;
390+ const character = await loadCharacterFromServer ( tokenId ) ;
391+ loadedCharacters . push ( character ) ;
392+ }
393+
394+ } catch ( error ) {
395+ elizaLogger . error ( `Error loading characters from server: ${ error } ` ) ;
396+ elizaLogger . error ( `Error details: ${ error instanceof Error ? error . message : 'Unknown error' } ` ) ;
397+ elizaLogger . error ( `Stack trace: ${ error instanceof Error ? error . stack : 'No stack trace' } ` ) ;
398+ throw error ;
399+ }
400+
401+ return loadedCharacters ;
402+ }
403+
404+ async function loadCharacterFromServer ( token_id : string ) : Promise < Character > {
405+ const response = await fetch ( `${ process . env . CHARACTER_SERVER_URL } /character/${ token_id } ` ) ;
406+ const characterData = await response . json ( ) ;
407+ const character : Character = characterData . character ;
408+ return character ;
409+ }
410+
372411async function handlePluginImporting ( plugins : string [ ] ) {
373412 if ( plugins . length > 0 ) {
374413 // this logging should happen before calling, so we can include important context
375414 //elizaLogger.info("Plugins are: ", plugins);
376415 const importedPlugins = await Promise . all (
377416 plugins . map ( async ( plugin ) => {
378417 try {
379- const importedPlugin :Plugin = await import ( plugin ) ;
418+ const importedPlugin : Plugin = await import ( plugin ) ;
380419 const functionName =
381420 plugin
382421 . replace ( "@elizaos/plugin-" , "" )
383422 . replace ( "@elizaos-plugins/plugin-" , "" )
384423 . replace ( / - ./ g, ( x ) => x [ 1 ] . toUpperCase ( ) ) +
385424 "Plugin" ; // Assumes plugin function is camelCased with Plugin suffix
386425 if ( ! importedPlugin [ functionName ] && ! importedPlugin . default ) {
387- elizaLogger . warn ( plugin , 'does not have an default export or' , functionName )
426+ elizaLogger . warn ( plugin , 'does not have an default export or' , functionName )
388427 }
389- return { ...(
390- importedPlugin . default || importedPlugin [ functionName ]
391- ) , npmName : plugin } ;
428+ return {
429+ ...(
430+ importedPlugin . default || importedPlugin [ functionName ]
431+ ) , npmName : plugin
432+ } ;
392433 } catch ( importError ) {
393434 console . error (
394435 `Failed to import plugin: ${ plugin } ` ,
@@ -709,23 +750,23 @@ function initializeCache(
709750}
710751
711752async function findDatabaseAdapter ( runtime : AgentRuntime ) {
712- const { adapters } = runtime ;
713- let adapter : Adapter | undefined ;
714- // if not found, default to sqlite
715- if ( adapters . length === 0 ) {
716- const sqliteAdapterPlugin = await import ( '@elizaos-plugins/adapter-sqlite' ) ;
717- const sqliteAdapterPluginDefault = sqliteAdapterPlugin . default ;
718- adapter = sqliteAdapterPluginDefault . adapters [ 0 ] ;
719- if ( ! adapter ) {
720- throw new Error ( "Internal error: No database adapter found for default adapter-sqlite" ) ;
721- }
722- } else if ( adapters . length === 1 ) {
723- adapter = adapters [ 0 ] ;
724- } else {
725- throw new Error ( "Multiple database adapters found. You must have no more than one. Adjust your plugins configuration." ) ;
726- }
727- const adapterInterface = adapter ?. init ( runtime ) ;
728- return adapterInterface ;
753+ const { adapters } = runtime ;
754+ let adapter : Adapter | undefined ;
755+ // if not found, default to sqlite
756+ if ( adapters . length === 0 ) {
757+ const sqliteAdapterPlugin = await import ( '@elizaos-plugins/adapter-sqlite' ) ;
758+ const sqliteAdapterPluginDefault = sqliteAdapterPlugin . default ;
759+ adapter = sqliteAdapterPluginDefault . adapters [ 0 ] ;
760+ if ( ! adapter ) {
761+ throw new Error ( "Internal error: No database adapter found for default adapter-sqlite" ) ;
762+ }
763+ } else if ( adapters . length === 1 ) {
764+ adapter = adapters [ 0 ] ;
765+ } else {
766+ throw new Error ( "Multiple database adapters found. You must have no more than one. Adjust your plugins configuration." ) ;
767+ }
768+ const adapterInterface = adapter ?. init ( runtime ) ;
769+ return adapterInterface ;
729770}
730771
731772async function startAgent (
@@ -828,16 +869,152 @@ const handlePostCharacterLoaded = async (character: Character): Promise<Characte
828869 return processedCharacter ;
829870}
830871
872+ async function createAdminServer ( directClient : DirectClient , charactersArg : string | undefined , adminPort : number ) {
873+ const http = await import ( 'http' ) ;
874+ const adminServer = http . createServer ( async ( req , res ) => {
875+ res . setHeader ( 'Content-Type' , 'application/json' ) ;
876+
877+ // Check if request is from localhost
878+ const requestIP = req . socket . remoteAddress ;
879+ if ( ! requestIP || ! [ '127.0.0.1' , '::1' , '::ffff:127.0.0.1' ] . includes ( requestIP ) ) {
880+ res . writeHead ( 403 ) ;
881+ res . end ( JSON . stringify ( { success : false , message : 'Access denied. Only localhost requests are allowed.' } ) ) ;
882+ return ;
883+ }
884+
885+ if ( req . method === 'POST' ) {
886+ if ( req . url === '/restart' ) {
887+ await restartAllAgents ( directClient , charactersArg ) ;
888+ res . writeHead ( 200 ) ;
889+ res . end ( JSON . stringify ( { success : true , message : 'All agents restarted successfully' } ) ) ;
890+ } else if ( req . url ?. startsWith ( '/restart/' ) ) {
891+ const characterName = req . url . split ( '/' ) [ 2 ] ?. toLowerCase ( ) ;
892+ if ( characterName ) {
893+ await restartSpecificCharacter ( directClient , characterName , charactersArg ) ;
894+ res . writeHead ( 200 ) ;
895+ res . end ( JSON . stringify ( { success : true , message : `Character ${ characterName } restarted successfully` } ) ) ;
896+ } else {
897+ res . writeHead ( 400 ) ;
898+ res . end ( JSON . stringify ( { success : false , message : 'Character name required' } ) ) ;
899+ }
900+ } else if ( req . url === '/add' ) {
901+ let body = '' ;
902+ req . on ( 'data' , chunk => {
903+ body += chunk . toString ( ) ;
904+ } ) ;
905+ req . on ( 'end' , async ( ) => {
906+ try {
907+ const { characterPath } = JSON . parse ( body ) ;
908+ if ( ! characterPath ) {
909+ res . writeHead ( 400 ) ;
910+ res . end ( JSON . stringify ( { success : false , message : 'Character path is required' } ) ) ;
911+ return ;
912+ }
913+ await addNewCharacter ( directClient , characterPath ) ;
914+ res . writeHead ( 200 ) ;
915+ res . end ( JSON . stringify ( { success : true , message : 'Character added successfully' } ) ) ;
916+ } catch ( error ) {
917+ res . writeHead ( 500 ) ;
918+ res . end ( JSON . stringify ( { success : false , message : error . message } ) ) ;
919+ }
920+ } ) ;
921+ } else {
922+ res . writeHead ( 404 ) ;
923+ res . end ( JSON . stringify ( { success : false , message : 'Endpoint not found' } ) ) ;
924+ }
925+ } else {
926+ res . writeHead ( 405 ) ;
927+ res . end ( JSON . stringify ( { success : false , message : 'Method not allowed' } ) ) ;
928+ }
929+ } ) ;
930+
931+ // Start admin server
932+ adminServer . listen ( adminPort , ( ) => {
933+ elizaLogger . log ( `Admin server listening on port ${ adminPort } ` ) ;
934+ elizaLogger . log ( `Available endpoints:` ) ;
935+ elizaLogger . log ( ` POST http://localhost:${ adminPort } /restart - Restart all agents` ) ;
936+ elizaLogger . log ( ` POST http://localhost:${ adminPort } /restart/{characterName} - Restart specific character` ) ;
937+ elizaLogger . log ( ` POST http://localhost:${ adminPort } /add - Add new character (requires characterPath in body)` ) ;
938+ } ) ;
939+
940+ return adminServer ;
941+ }
942+
831943const startAgents = async ( ) => {
832944 const directClient = new DirectClient ( ) ;
833945 let serverPort = Number . parseInt ( settings . SERVER_PORT || "3000" ) ;
946+ const adminPort = 8082 ; //TODO: move to .env
834947 const args = parseArguments ( ) ;
835948 const charactersArg = args . characters || args . character ;
836949 let characters = [ defaultCharacter ] ;
837950
838- if ( ( charactersArg ) || hasValidRemoteUrls ( ) ) {
839- characters = await loadCharacters ( charactersArg ) ;
840- }
951+ characters = await loadCharactersFromServer ( ) ;
952+
953+ /*try {
954+ for (const character of characters) {
955+ const processedCharacter = await handlePostCharacterLoaded(character);
956+ await startAgent(processedCharacter, directClient);
957+ }
958+ // Add restart command handling to DirectClient's message system
959+ const originalHandleMessage = directClient.handleMessage;
960+ directClient.handleMessage = async (message) => {
961+ if (message.content === '/restart') {
962+ await restartAllAgents(directClient, charactersArg);
963+ return { type: 'success', content: 'All agents restarted successfully' };
964+ } else if (message.content.startsWith('/restart ')) {
965+ const characterName = message.content.split(' ')[1].toLowerCase();
966+ await restartSpecificCharacter(directClient, characterName, charactersArg);
967+ return { type: 'success', content: `Character ${characterName} restarted successfully` };
968+ }
969+ // Call the original handler for all other messages
970+ return originalHandleMessage.call(directClient, message);
971+ };
972+
973+ // Create and start the admin server
974+ await createAdminServer(directClient, charactersArg, adminPort);
975+
976+ // Listen for restart command from stdin
977+ process.stdin.on('data', async (data) => {
978+ const command = data.toString().trim();
979+
980+ if (command === 'restart') {
981+ await restartAllAgents(directClient, charactersArg);
982+ } else if (command.startsWith('restart ')) {
983+ const characterName = command.split(' ')[1].toLowerCase();
984+ await restartSpecificCharacter(directClient, characterName, charactersArg);
985+ }
986+ });
987+
988+ // Store the charactersArg for use in restarts
989+ directClient.charactersArg = charactersArg;
990+
991+ // Find available port
992+ while (!(await checkPortAvailable(serverPort))) {
993+ elizaLogger.warn(`Port ${serverPort} is in use, trying ${serverPort + 1}`);
994+ serverPort++;
995+ }
996+
997+ // upload some agent functionality into directClient
998+ directClient.startAgent = async (character) => {
999+ // Handle plugins
1000+ character.plugins = await handlePluginImporting(character.plugins);
1001+ return startAgent(character, directClient);
1002+ };
1003+
1004+ directClient.start(serverPort);
1005+
1006+ if (serverPort !== parseInt(settings.SERVER_PORT || "3000")) {
1007+ elizaLogger.log(`Server started on alternate port ${serverPort}`);
1008+ }
1009+
1010+ elizaLogger.log("Run `pnpm start:client` to start the client and visit the outputted URL (http://localhost:5173) to chat with your agents.");
1011+ elizaLogger.log("Type 'restart' and press Enter to restart all agents.");
1012+ } catch (error) {
1013+ elizaLogger.error("Error starting agents:", error);
1014+ }*/
1015+
1016+ // Create and start the admin server
1017+ await createAdminServer ( directClient , charactersArg , adminPort ) ;
8411018
8421019 try {
8431020 for ( const character of characters ) {
@@ -889,6 +1066,103 @@ const startAgents = async () => {
8891066 ) ;
8901067} ;
8911068
1069+ // Add these new functions above startAgents
1070+ async function restartAllAgents ( directClient : DirectClient , charactersArg : string | undefined ) {
1071+ try {
1072+ elizaLogger . info ( 'Restarting all agents...' ) ;
1073+
1074+ // Stop existing agents
1075+ for ( const [ id , agent ] of Object . entries ( directClient . agents ) ) {
1076+ elizaLogger . info ( `Stopping agent: ${ id } ` ) ;
1077+ if ( typeof agent . stop === 'function' ) {
1078+ await agent . stop ( ) ;
1079+ }
1080+ }
1081+
1082+ // Clear all agents
1083+ directClient . agents = { } ;
1084+
1085+ const characters = await loadCharactersFromServer ( ) ;
1086+
1087+ // Reload and restart agents
1088+ for ( const character of characters ) {
1089+ const processedCharacter = await handlePostCharacterLoaded ( character ) ;
1090+ elizaLogger . info ( `Restarting agent for character: ${ character . name } ` ) ;
1091+ await startAgent ( processedCharacter , directClient ) ;
1092+ }
1093+ elizaLogger . info ( 'All agents restarted successfully' ) ;
1094+ } catch ( error ) {
1095+ elizaLogger . error ( 'Error during restart:' , error ) ;
1096+ elizaLogger . info ( 'Attempting to continue with existing agents...' ) ;
1097+ }
1098+ }
1099+
1100+ async function restartSpecificCharacter ( directClient : DirectClient , characterTokenId : string , charactersArg : string | undefined ) {
1101+ let characterName = '' ;
1102+ try {
1103+ // input should be tokenId
1104+ // fetch character from server
1105+ const character = await loadCharacterFromServer ( characterTokenId ) ;
1106+
1107+ // Process the character with post processors
1108+ const processedCharacter = await handlePostCharacterLoaded ( character ) ;
1109+
1110+ characterName = character . name ;
1111+
1112+ // Find and stop the existing agent
1113+ let foundAgent = false ;
1114+ for ( const [ id , agent ] of Object . entries ( directClient . agents ) ) {
1115+ elizaLogger . info ( `Checking agent: ${ id } ` ) ;
1116+ if ( id . toLowerCase ( ) . includes ( characterName . toLowerCase ( ) ) ) {
1117+ elizaLogger . info ( `Stopping agent: ${ id } ` ) ;
1118+ if ( typeof agent . stop === 'function' ) {
1119+ await agent . stop ( ) ;
1120+ }
1121+ delete directClient . agents [ id ] ;
1122+ foundAgent = true ;
1123+ break ;
1124+ }
1125+ }
1126+
1127+ if ( ! foundAgent ) {
1128+ elizaLogger . warn ( `No existing agent found for character: ${ characterName } ` ) ;
1129+ }
1130+
1131+ // Start the new agent
1132+ elizaLogger . info ( `Starting new agent for character: ${ processedCharacter . name } ` ) ;
1133+ await startAgent ( processedCharacter , directClient ) ;
1134+ elizaLogger . info ( `Character ${ processedCharacter . name } restarted successfully` ) ;
1135+ } catch ( error ) {
1136+ elizaLogger . error ( `Error restarting character ${ characterName } :` , error ) ;
1137+ throw error ; // Re-throw to be handled by caller
1138+ }
1139+ }
1140+
1141+ // Add this new function above startAgents
1142+ async function addNewCharacter ( directClient : DirectClient , characterPath : string ) {
1143+ try {
1144+ elizaLogger . info ( `Adding new character from: ${ characterPath } ` ) ;
1145+
1146+ // Load the new character
1147+ const newCharacter = ( await loadCharacters ( characterPath ) ) [ 0 ] ;
1148+ if ( ! newCharacter ) {
1149+ elizaLogger . error ( `Failed to load character from file: ${ characterPath } ` ) ;
1150+ return ;
1151+ }
1152+
1153+ // Process the character with post processors
1154+ const processedCharacter = await handlePostCharacterLoaded ( newCharacter ) ;
1155+
1156+ // Start the new agent
1157+ elizaLogger . info ( `Starting new agent for character: ${ processedCharacter . name } ` ) ;
1158+ await startAgent ( processedCharacter , directClient ) ;
1159+ elizaLogger . info ( `Character ${ processedCharacter . name } added successfully` ) ;
1160+ } catch ( error ) {
1161+ elizaLogger . error ( `Error adding character from ${ characterPath } :` , error ) ;
1162+ throw error ;
1163+ }
1164+ }
1165+
8921166startAgents ( ) . catch ( ( error ) => {
8931167 elizaLogger . error ( "Unhandled error in startAgents:" , error ) ;
8941168 process . exit ( 1 ) ;
0 commit comments