// retoor #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 #include #include #include #include #include #include #include #include #include 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) { 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); pclose(fp); return NULL; } output = temp; } } output[total_size] = '\0'; pclose(fp); return output; } static char *get_prompt_from_stdin(char *prompt) { int index = 0; int c; while ((c = getchar()) != EOF) { prompt[index++] = (char)c; } prompt[index] = '\0'; return prompt; } static char *get_prompt_from_args(int argc, char **argv) { 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; } else { strcat(system_msg, argv[i]); strcat(system_msg, (i < argc - 1) ? " " : "."); } } if (get_from_stdin) { if (*system_msg && global_messages) { messages_add(global_messages, "system", system_msg); } prompt = get_prompt_from_stdin(prompt); free(system_msg); } else { free(prompt); prompt = system_msg; } if (!*prompt) { free(prompt); return NULL; } return prompt; } static bool try_prompt(int argc, char *argv[]) { 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; } // response is already printed inside agent_run free(response); free(prompt); return true; } return false; } static bool include_file(const char *path) { char *file_content = read_file(path); if (!file_content) return false; if (global_messages) { messages_add(global_messages, "system", file_content); } free(file_content); return true; } static void repl(void) { 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; } if (!strncmp(line, "!clear", 6)) { messages_clear(global_messages); fprintf(stderr, "Session cleared.\n"); continue; } if (!strncmp(line, "!session", 8)) { printf("Session: %s\n", messages_get_session_id(global_messages)); continue; } if (!strncmp(line, "!new", 4)) { messages_clear(global_messages); char session_id[64]; snprintf(session_id, sizeof(session_id), "session-%d-%ld", getpid(), (long)time(NULL)); messages_set_session_id(global_messages, session_id); fprintf(stderr, "New session: %s\n", session_id); continue; } 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); } while (line && *line != '\n') { char *response = agent_chat(line, global_messages); if (response) { // response is already printed inside agent_run via // parse_markdown_to_ansi free(response); } else { fprintf(stderr, "Agent returned no response\n"); } line = NULL; } } } static void init(void) { curl_global_init(CURL_GLOBAL_DEFAULT); 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" "## The Enterprise Pyramid (Rigid Hierarchy)\n" "You are the **Executive Agent (Apex)**. You MUST enforce a strict top-down " "Chain of Command:\n" "- **Executive (Apex)**: Final arbiter. Owns the Strategic Blueprint. You " "never code or research directly. You evaluate sub-agent depth.\n" "- **Department Heads (Managers)**: Create detailed 'Task Packs'. " "Synthesize sub-agent outputs into 'Department Reports'.\n" "- **Workers (Base)**: Execute atomic tasks. Report literal word counts " "and file sizes upward.\n\n" "### Bureaucratic Protocols (MANDATORY)\n" "1. **Strategic Blueprint**: Your very first turn MUST output a " "blueprint: Mission, Departments Involved, and a 10-step Checklist.\n" "2. **Sequential Handover**: You are FORBIDDEN from spawning a Developer " "until the Researcher has delivered a minimum of 1000 words of " "documented facts to `PROJECT_KNOWLEDGE.md`.\n" "3. **Content Depth Guardrail**: For 'Huge' projects, every page MUST " "contain deep, researched info. Placeholder text (e.g., 'Coming soon', " "'Introduction here') is a failure. You MUST use 'read_file' to audit " "sub-agent work before concluding.\n" "4. **Global Task Registry (GTR)**: Query GTR for every sub-task. If a " "similar task exists, use its result summary. DUPLICATION IS FORBIDDEN.\n" "5. **Fan-Out Architecture (Research)**: Manager calls `web_search` to get " "URLs, then uses `research_dispatcher` to queue them. Workers use " "`fetch_and_scrape` for individual URLs. If a Worker finds new links, it " "MUST use `suggest_subtask` to escalate. NEVER follow rabbit holes " "yourself.\n\n" "### Shared Memory & Data Sharing\n" "- Every turn, you MUST update `PROJECT_KNOWLEDGE.md` with new findings.\n" "- All sub-agents MUST receive the full content of `PROJECT_KNOWLEDGE.md` " "to ensure a shared organizational history.\n\n" "## Multi-Agent Orchestration (MANDATORY)\n" "## Project Scale Rules\n" "- HUGE PROJECTS: If a 'huge' or 'multi-page' project is requested, " "delivering a single file is FORBIDDEN. You MUST create a directory " "structure (e.g., assets/, css/, js/) and multiple linked HTML files.\n" "- CHECKLIST PROTOCOL: Your first response to a complex request MUST " "include the '## Checklist' you intend to fulfill.\n" "- NO Lying: Never claim a task is done or a feature exists unless you " "have the tool output to prove it.\n\n" "## Multi-Agent Orchestration (MANDATORY)\n" "You are the Lead Orchestrator. You MUST delegate specialized work:\n" "- researcher: For ALL information gathering. Never research yourself if " "you can spawn a researcher.\n" "- developer: For ALL coding, testing, and debugging.\n" "- security: For ALL security-related audits.\n\n" "IMPORTANT: When a sub-agent returns a result, you MUST read it, " "synthesize it, and then perform any necessary follow-up actions (like " "writing to a file or spawning another agent). NEVER assume a task is " "done just because a sub-agent finished; YOU must complete the final " "delivery.\n\n" "MANDATORY FINAL ACTION: If the user asked to save results to a file, " "YOU must call the write_file tool yourself with the synthesized data " "from the sub-agent. Do not ask for permission.\n\n" "## Tool Usage\n" "- Use tools proactively. If you say you will do something, you MUST " "call the tool in the SAME or NEXT turn.\n" "- If a tool fails, analyze and retry with a different approach.\n\n" "## CRITICAL OUTPUT RULES\n" "- SHOW THE DATA: Always include the actual content from tool/agent " "results in your response.\n" "- NO PREMATURE COMPLETION: Do not say 'task complete' until you have " "verified all files are written and all steps are finished.\n" "- SEQUENTIAL EXECUTION: Perform one logical step at a time. If you need " "to research AND write a file, spawn the researcher first, wait for the " "result, THEN write the file.\n" "## EXECUTION OF PYTHON CODE\n" "Exclusively use native python without 3rd party packages unless you " "have checked that they're installed on the system.\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" "## 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" "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 excessively.\n" "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 : "{}"); 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"); } static void cleanup(void) { 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(); } static void handle_sigint(int sig) { (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); } else { sigint_count = 1; first_sigint_time = current_time; } } } static void parse_session_arg(int argc, char *argv[]) { 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; } } } int main(int argc, char *argv[]) { signal(SIGINT, handle_sigint); atexit(cleanup); parse_session_arg(argc, argv); init(); char *env_string = get_env_string(); if (env_string && *env_string && global_messages) { messages_add(global_messages, "system", env_string); free(env_string); } messages_load(global_messages); if (try_prompt(argc, argv)) { return 0; } repl(); return 0; }