2026-01-28 19:34:39 +01:00
// retoor <retoor@molodetz.nl>
# include "agent.h"
# include "db.h"
# include "http_client.h"
# include "r_config.h"
# include "r_error.h"
# include "tool.h"
# include "../line.h"
# include "../markdown.h"
# include "../utils.h"
# include <json-c/json.h>
# include <locale.h>
# include <signal.h>
# include <stdbool.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <time.h>
# include <unistd.h>
static volatile sig_atomic_t sigint_count = 0 ;
static time_t first_sigint_time = 0 ;
static bool syntax_highlight_enabled = true ;
static bool api_mode = false ;
static db_handle global_db = NULL ;
static messages_handle global_messages = NULL ;
extern tool_registry_t * tools_get_registry ( void ) ;
extern void tools_registry_shutdown ( void ) ;
static bool include_file ( const char * path ) ;
static char * get_prompt_from_stdin ( char * prompt ) ;
static char * get_prompt_from_args ( int argc , char * * argv ) ;
static bool try_prompt ( int argc , char * argv [ ] ) ;
static void repl ( void ) ;
static void init ( void ) ;
static void cleanup ( void ) ;
static void handle_sigint ( int sig ) ;
static char * get_env_string ( void ) {
2026-01-28 19:47:14 +01:00
FILE * fp = popen ( " env " , " r " ) ;
if ( ! fp )
return NULL ;
size_t buffer_size = 1024 ;
size_t total_size = 0 ;
char * output = malloc ( buffer_size ) ;
if ( ! output ) {
pclose ( fp ) ;
return NULL ;
}
size_t bytes_read ;
while ( ( bytes_read = fread ( output + total_size , 1 , buffer_size - total_size ,
fp ) ) > 0 ) {
total_size + = bytes_read ;
if ( total_size > = buffer_size ) {
buffer_size * = 2 ;
char * temp = realloc ( output , buffer_size ) ;
if ( ! temp ) {
free ( output ) ;
2026-01-28 19:34:39 +01:00
pclose ( fp ) ;
return NULL ;
2026-01-28 19:47:14 +01:00
}
output = temp ;
2026-01-28 19:34:39 +01:00
}
2026-01-28 19:47:14 +01:00
}
2026-01-28 19:34:39 +01:00
2026-01-28 19:47:14 +01:00
output [ total_size ] = ' \0 ' ;
pclose ( fp ) ;
return output ;
2026-01-28 19:34:39 +01:00
}
static char * get_prompt_from_stdin ( char * prompt ) {
2026-01-28 19:47:14 +01:00
int index = 0 ;
int c ;
while ( ( c = getchar ( ) ) ! = EOF ) {
prompt [ index + + ] = ( char ) c ;
}
prompt [ index ] = ' \0 ' ;
return prompt ;
2026-01-28 19:34:39 +01:00
}
static char * get_prompt_from_args ( int argc , char * * argv ) {
2026-01-28 19:47:14 +01:00
r_config_handle cfg = r_config_get_instance ( ) ;
char * prompt = malloc ( 10 * 1024 * 1024 + 1 ) ;
char * system_msg = malloc ( 1024 * 1024 ) ;
if ( ! prompt | | ! system_msg ) {
free ( prompt ) ;
free ( system_msg ) ;
return NULL ;
}
system_msg [ 0 ] = ' \0 ' ;
bool get_from_stdin = false ;
for ( int i = 1 ; i < argc ; i + + ) {
if ( strcmp ( argv [ i ] , " --stdin " ) = = 0 ) {
fprintf ( stderr , " Reading from stdin. \n " ) ;
get_from_stdin = true ;
} else if ( strcmp ( argv [ i ] , " --verbose " ) = = 0 ) {
r_config_set_verbose ( cfg , true ) ;
} else if ( strcmp ( argv [ i ] , " --py " ) = = 0 & & i + 1 < argc ) {
char * py_file_path = expand_home_directory ( argv [ + + i ] ) ;
fprintf ( stderr , " Including \" %s \" . \n " , py_file_path ) ;
include_file ( py_file_path ) ;
free ( py_file_path ) ;
} else if ( strcmp ( argv [ i ] , " --context " ) = = 0 & & i + 1 < argc ) {
char * context_file_path = argv [ + + i ] ;
fprintf ( stderr , " Including \" %s \" . \n " , context_file_path ) ;
include_file ( context_file_path ) ;
} else if ( strcmp ( argv [ i ] , " --api " ) = = 0 ) {
api_mode = true ;
} else if ( strcmp ( argv [ i ] , " --nh " ) = = 0 ) {
syntax_highlight_enabled = false ;
fprintf ( stderr , " Syntax highlighting disabled. \n " ) ;
} else if ( strncmp ( argv [ i ] , " --session= " , 10 ) = = 0 ) {
continue ;
} else if ( strcmp ( argv [ i ] , " -s " ) = = 0 | |
strcmp ( argv [ i ] , " --session " ) = = 0 ) {
i + + ;
continue ;
2026-01-28 19:34:39 +01:00
} else {
2026-01-28 19:47:14 +01:00
strcat ( system_msg , argv [ i ] ) ;
strcat ( system_msg , ( i < argc - 1 ) ? " " : " . " ) ;
2026-01-28 19:34:39 +01:00
}
2026-01-28 19:47:14 +01:00
}
2026-01-28 19:34:39 +01:00
2026-01-28 19:47:14 +01:00
if ( get_from_stdin ) {
if ( * system_msg & & global_messages ) {
messages_add ( global_messages , " system " , system_msg ) ;
2026-01-28 19:34:39 +01:00
}
2026-01-28 19:47:14 +01:00
prompt = get_prompt_from_stdin ( prompt ) ;
free ( system_msg ) ;
} else {
free ( prompt ) ;
prompt = system_msg ;
}
if ( ! * prompt ) {
free ( prompt ) ;
return NULL ;
}
return prompt ;
2026-01-28 19:34:39 +01:00
}
static bool try_prompt ( int argc , char * argv [ ] ) {
2026-01-28 19:47:14 +01:00
char * prompt = get_prompt_from_args ( argc , argv ) ;
if ( prompt ) {
char * response = agent_chat ( prompt , global_messages ) ;
if ( ! response ) {
printf ( " Could not get response from server \n " ) ;
free ( prompt ) ;
return false ;
2026-01-28 19:34:39 +01:00
}
2026-01-28 20:06:18 +01:00
// response is already printed inside agent_run
2026-01-28 19:47:14 +01:00
free ( response ) ;
free ( prompt ) ;
return true ;
}
return false ;
2026-01-28 19:34:39 +01:00
}
static bool include_file ( const char * path ) {
2026-01-28 19:47:14 +01:00
char * file_content = read_file ( path ) ;
if ( ! file_content )
return false ;
2026-01-28 19:34:39 +01:00
2026-01-28 19:47:14 +01:00
if ( global_messages ) {
messages_add ( global_messages , " system " , file_content ) ;
}
free ( file_content ) ;
return true ;
2026-01-28 19:34:39 +01:00
}
static void repl ( void ) {
2026-01-28 19:47:14 +01:00
r_config_handle cfg = r_config_get_instance ( ) ;
tool_registry_t * tools = tools_get_registry ( ) ;
line_init ( ) ;
char * line = NULL ;
while ( true ) {
line = line_read ( " > " ) ;
if ( ! line | | ! * line )
continue ;
if ( ! strncmp ( line , " !dump " , 5 ) ) {
char * json = messages_to_string ( global_messages ) ;
if ( json ) {
printf ( " %s \n " , json ) ;
free ( json ) ;
}
continue ;
2026-01-28 19:34:39 +01:00
}
2026-01-28 19:47:14 +01:00
if ( ! strncmp ( line , " !clear " , 6 ) ) {
messages_clear ( global_messages ) ;
fprintf ( stderr , " Session cleared. \n " ) ;
continue ;
2026-01-28 19:34:39 +01:00
}
2026-01-28 19:47:14 +01:00
if ( ! strncmp ( line , " !session " , 8 ) ) {
printf ( " Session: %s \n " , messages_get_session_id ( global_messages ) ) ;
continue ;
2026-01-28 19:34:39 +01:00
}
2026-01-28 19:47:14 +01:00
if ( ! strncmp ( line , " !verbose " , 8 ) ) {
bool verbose = ! r_config_is_verbose ( cfg ) ;
r_config_set_verbose ( cfg , verbose ) ;
fprintf ( stderr , " %s \n " ,
verbose ? " Verbose mode enabled " : " Verbose mode disabled " ) ;
continue ;
}
if ( line & & * line ! = ' \n ' ) {
line_add_history ( line ) ;
}
if ( ! strncmp ( line , " !tools " , 6 ) ) {
struct json_object * descs = tool_registry_get_descriptions ( tools ) ;
printf ( " Available tools: %s \n " , json_object_to_json_string ( descs ) ) ;
continue ;
}
if ( ! strncmp ( line , " !models " , 7 ) ) {
http_client_handle http = http_client_create ( r_config_get_api_key ( cfg ) ) ;
if ( http ) {
http_client_set_show_spinner ( http , false ) ;
char * response = NULL ;
if ( http_get ( http , r_config_get_models_url ( cfg ) , & response ) = =
R_SUCCESS & &
response ) {
printf ( " Models: %s \n " , response ) ;
free ( response ) ;
}
http_client_destroy ( http ) ;
}
continue ;
}
if ( ! strncmp ( line , " !model " , 6 ) ) {
if ( line [ 6 ] = = ' ' ) {
r_config_set_model ( cfg , line + 7 ) ;
}
printf ( " Current model: %s \n " , r_config_get_model ( cfg ) ) ;
continue ;
}
if ( ! strncmp ( line , " exit " , 4 ) ) {
exit ( 0 ) ;
2026-01-28 19:34:39 +01:00
}
2026-01-28 19:47:14 +01:00
while ( line & & * line ! = ' \n ' ) {
char * response = agent_chat ( line , global_messages ) ;
if ( response ) {
2026-01-28 20:06:18 +01:00
// response is already printed inside agent_run via parse_markdown_to_ansi
2026-01-28 19:47:14 +01:00
free ( response ) ;
} else {
fprintf ( stderr , " Agent returned no response \n " ) ;
}
2026-01-28 20:06:18 +01:00
line = NULL ;
2026-01-28 19:34:39 +01:00
}
2026-01-28 19:47:14 +01:00
}
}
2026-01-28 19:34:39 +01:00
2026-01-28 19:47:14 +01:00
static void init ( void ) {
setbuf ( stdout , NULL ) ;
line_init ( ) ;
r_config_handle cfg = r_config_get_instance ( ) ;
global_db = db_open ( NULL ) ;
global_messages = messages_create ( r_config_get_session_id ( cfg ) ) ;
char * schema = db_get_schema ( global_db ) ;
char payload [ 1024 * 1024 ] = { 0 } ;
time_t now = time ( NULL ) ;
struct tm * tm_info = localtime ( & now ) ;
char datetime [ 64 ] ;
strftime ( datetime , sizeof ( datetime ) , " %Y-%m-%d %H:%M:%S %Z " , tm_info ) ;
char cwd [ 4096 ] ;
if ( ! getcwd ( cwd , sizeof ( cwd ) ) ) {
strcpy ( cwd , " unknown " ) ;
}
snprintf (
payload , sizeof ( payload ) ,
" # AUTONOMOUS AGENT INSTRUCTIONS \n "
" Current date/time: %s \n "
" Working directory: %s \n \n "
" You are an autonomous AI agent. You operate in a loop: reason about the "
" task, "
" select and execute tools when needed, observe results, and continue "
" until the goal is achieved. \n \n "
" ## Reasoning Pattern (ReAct) \n "
" For complex tasks, think step-by-step: \n "
" 1. Thought: What do I need to accomplish? What information do I have? \n "
" 2. Action: Which tool should I use? With what parameters? \n "
" 3. Observation: What did the tool return? What does this tell me? \n "
" 4. Repeat until the goal is complete. \n \n "
" 5. Do not ask questions, do assumptions autonomously. "
" ## Tool Usage \n "
" - Use tools proactively to gather information and take actions \n "
" - If a tool fails, analyze the error and try a different approach \n "
" - You can call multiple tools in sequence to accomplish complex "
" tasks \n \n "
" - Take decissions based on tool output yourself autonomously. "
2026-01-29 02:21:11 +01:00
" ## Multi-Agent Orchestration \n "
" You can delegate tasks to specialized agents using the `spawn_agent` tool. \n "
" Personas: \n "
" - researcher: Best for web search and data gathering. \n "
" - developer: Best for writing and testing code. \n "
" - security: Best for security audits and vulnerability analysis. \n \n "
" Use the `blackboard` table in the database to share state and pass detailed instructions or results between agents if they exceed tool parameter limits. \n \n "
2026-01-28 19:47:14 +01:00
" ## CRITICAL OUTPUT RULES \n "
" - You MUST include the actual content/data from tool results in your "
" response \n "
" - When you search the web, QUOTE the relevant information found \n "
" - When you run a command, SHOW the output \n "
" - NEVER say 'I found information' without showing what you found \n "
" - NEVER say 'task complete' or 'report provided' - SHOW THE ACTUAL "
" DATA \n "
" - The user cannot see tool results - only YOUR response. Include "
" everything relevant. \n \n "
" ## Local Database \n "
" You have a local SQLite database accessible via db_query, db_get, and "
" db_set tools. \n "
" Use stemmed, lowercase keys to prevent duplicates. \n "
" Schema: %s \n \n "
" ## Response Format \n "
" Your response IS the only thing the user sees. Tool outputs are hidden "
" from them. \n "
" You MUST copy/paste relevant data from tool results into your "
" response. \n "
" Bad: 'I searched and found information about X.' \n "
" Good: 'Here is what I found: [actual content from search results]' \n "
2026-01-28 19:55:47 +01:00
" ## Tool calls \n "
" As long you're not done, your last response must be always a tool call "
" to keep going. \n "
" Complete the whole tasks before bothering or informing user. \n "
" Backup \n "
2026-01-28 19:47:14 +01:00
" You have the power to delete and modify files whatever your want to but "
" always make a .bak file with backup before editing unless you've "
" created the file yourself. "
" ## Terminal execution \n "
" You are in a bash environment, so can also execute bash commands. \n "
" Everything is allowed. \n "
" Be very creative with your bash tool use, use it exessively. \n "
2026-01-28 19:55:47 +01:00
" Prefer commands that do not require root access. \n "
" ## COMMUNICATION \n "
" You are only allowed to talk once, so do that absolutely last with your "
" conclusion. \n " , datetime ,
cwd , schema ? schema : " {} " ) ;
2026-01-28 19:47:14 +01:00
free ( schema ) ;
fprintf ( stderr , " Loading... " ) ;
if ( global_messages ) {
messages_add ( global_messages , " system " , payload ) ;
}
const char * env_system_msg = r_config_get_system_message ( cfg ) ;
if ( env_system_msg & & * env_system_msg & & global_messages ) {
messages_add ( global_messages , " system " , env_system_msg ) ;
}
if ( ! include_file ( " .rcontext.txt " ) ) {
include_file ( " ~/.rcontext.txt " ) ;
}
fprintf ( stderr , " \r \r " ) ;
2026-01-28 19:34:39 +01:00
}
static void cleanup ( void ) {
2026-01-28 19:47:14 +01:00
if ( global_messages ) {
messages_destroy ( global_messages ) ;
global_messages = NULL ;
}
if ( global_db ) {
db_close ( global_db ) ;
global_db = NULL ;
}
tools_registry_shutdown ( ) ;
r_config_destroy ( ) ;
2026-01-28 19:34:39 +01:00
}
static void handle_sigint ( int sig ) {
2026-01-28 19:47:14 +01:00
( void ) sig ;
time_t current_time = time ( NULL ) ;
printf ( " \n " ) ;
if ( sigint_count = = 0 ) {
first_sigint_time = current_time ;
sigint_count + + ;
} else {
if ( difftime ( current_time , first_sigint_time ) < = 1 ) {
cleanup ( ) ;
exit ( 0 ) ;
2026-01-28 19:34:39 +01:00
} else {
2026-01-28 19:47:14 +01:00
sigint_count = 1 ;
first_sigint_time = current_time ;
2026-01-28 19:34:39 +01:00
}
2026-01-28 19:47:14 +01:00
}
2026-01-28 19:34:39 +01:00
}
static void parse_session_arg ( int argc , char * argv [ ] ) {
2026-01-28 19:47:14 +01:00
r_config_handle cfg = r_config_get_instance ( ) ;
for ( int i = 1 ; i < argc ; i + + ) {
if ( strncmp ( argv [ i ] , " --session= " , 10 ) = = 0 ) {
const char * name = argv [ i ] + 10 ;
if ( ! r_config_set_session_id ( cfg , name ) ) {
fprintf ( stderr , " Error: Invalid session name '%s' \n " , name ) ;
exit ( 1 ) ;
}
return ;
}
if ( ( strcmp ( argv [ i ] , " -s " ) = = 0 | | strcmp ( argv [ i ] , " --session " ) = = 0 ) & &
i + 1 < argc ) {
const char * name = argv [ + + i ] ;
if ( ! r_config_set_session_id ( cfg , name ) ) {
fprintf ( stderr , " Error: Invalid session name '%s' \n " , name ) ;
exit ( 1 ) ;
}
return ;
2026-01-28 19:34:39 +01:00
}
2026-01-28 19:47:14 +01:00
}
2026-01-28 19:34:39 +01:00
}
int main ( int argc , char * argv [ ] ) {
2026-01-28 19:47:14 +01:00
signal ( SIGINT , handle_sigint ) ;
atexit ( cleanup ) ;
2026-01-28 19:34:39 +01:00
2026-01-28 19:47:14 +01:00
parse_session_arg ( argc , argv ) ;
init ( ) ;
2026-01-28 19:34:39 +01:00
2026-01-28 19:47:14 +01:00
char * env_string = get_env_string ( ) ;
if ( env_string & & * env_string & & global_messages ) {
messages_add ( global_messages , " system " , env_string ) ;
free ( env_string ) ;
}
2026-01-28 19:34:39 +01:00
2026-01-28 19:47:14 +01:00
messages_load ( global_messages ) ;
2026-01-28 19:34:39 +01:00
2026-01-28 19:47:14 +01:00
if ( try_prompt ( argc , argv ) ) {
2026-01-28 19:34:39 +01:00
return 0 ;
2026-01-28 19:47:14 +01:00
}
repl ( ) ;
return 0 ;
2026-01-28 19:34:39 +01:00
}