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. "
chore: remove entire agent, auth, browse, chat, db_utils, and http_curl source trees
This commit deletes the complete implementation of the agent loop, authentication module, web browsing utilities, chat prompt builder, database helper, and HTTP client. All corresponding header files and test harnesses are also removed. The diff shows the deletion of 6 source files and 6 header files totaling over 1000 lines of code, including the agent state machine, API key resolution logic, curl-based HTTP functions, SQLite database initialization and CRUD operations, and the JSON chat prompt construction.
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
}