From 77885e73a9daa49a0395499f2a636c775cfd8792 Mon Sep 17 00:00:00 2001 From: retoor Date: Thu, 29 Jan 2026 06:01:05 +0100 Subject: [PATCH] Update. --- Makefile | 3 +- agent_benchmark.py | 195 ++++++++++++++++++++++++++++++++++++ include/agent.h | 3 + include/bash_executor.h | 2 +- include/messages.h | 1 + include/r_config.h | 1 + include/tool.h | 9 ++ src/agent.c | 108 ++++++++++++++++++-- src/bash_executor.c | 173 ++++++++++++++++++++++++-------- src/context_manager.c | 140 +++++++++++++++++--------- src/http_client.c | 10 +- src/interfaces/config.c | 41 +------- src/interfaces/http.h | 9 -- src/interfaces/logger.c | 15 --- src/main.c | 46 ++++----- src/messages.c | 5 + src/r_config.c | 13 ++- src/tool_registry.c | 10 +- src/tools/tool_agent.c | 125 +++++++++++++++-------- src/tools/tool_automation.c | 44 ++++++++ src/tools/tool_code.c | 22 +++- src/tools/tool_csv.c | 18 ++++ src/tools/tool_dns.c | 23 +++++ src/tools/tool_file.c | 7 ++ src/tools/tool_file_edit.c | 16 +++ src/tools/tool_http.c | 2 +- src/tools/tool_network.c | 19 ++++ src/tools/tool_python.c | 7 +- src/tools/tool_system.c | 14 ++- src/tools/tool_terminal.c | 49 +++++++-- src/tools/tools_init.c | 52 ++++++++++ 31 files changed, 922 insertions(+), 260 deletions(-) create mode 100755 agent_benchmark.py diff --git a/Makefile b/Makefile index 7469291..b2e5c1f 100755 --- a/Makefile +++ b/Makefile @@ -34,7 +34,8 @@ SRC_TOOLS = $(TOOLSDIR)/tools_init.c \ $(TOOLSDIR)/tool_network.c \ $(TOOLSDIR)/tool_dns.c \ $(TOOLSDIR)/tool_automation.c \ - $(TOOLSDIR)/tool_csv.c + $(TOOLSDIR)/tool_csv.c \ + $(TOOLSDIR)/tool_agent.c SRC = $(SRC_CORE) $(SRC_TOOLS) diff --git a/agent_benchmark.py b/agent_benchmark.py new file mode 100755 index 0000000..68977bd --- /dev/null +++ b/agent_benchmark.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +import subprocess +import json +import os +import time +import logging +import sys +from datetime import datetime +from typing import List, Dict, Any + +# Configure logging +LOG_FILE = "benchmark_results.log" +AGENT_OUTPUT_DIR = "test_results" +os.makedirs(AGENT_OUTPUT_DIR, exist_ok=True) + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s [%(levelname)s] %(message)s', + handlers=[ + logging.FileHandler(LOG_FILE), + logging.StreamHandler(sys.stdout) + ] +) + +class TestCase: + def __init__(self, id: str, name: str, description: str, task: str, validation_fn: Any): + self.id = id + self.name = name + self.description = description + self.task = task + self.validation_fn = validation_fn + self.result = "PENDING" + self.output = "" + self.execution_time = 0 + +def validate_file_exists(path): + return os.path.exists(path) + +def validate_file_contains(path, text): + if not os.path.exists(path): return False + with open(path, 'r') as f: + return text.lower() in f.read().lower() + +class AgentBenchmark: + def __init__(self, binary_path: str = "./r"): + self.binary_path = binary_path + self.test_cases: List[TestCase] = [] + + def add_test(self, test: TestCase): + self.test_cases.append(test) + + def run_all(self): + logging.info(f"Starting benchmark with {len(self.test_cases)} tasks...") + for test in self.test_cases: + self.run_test(test) + + self.summary() + + def run_test(self, test: TestCase): + logging.info(f"--- Running Test {test.id}: {test.name} ---") + start_time = time.time() + + try: + # Execute the agent + process = subprocess.Popen( + [self.binary_path, "--verbose", test.task], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1 + ) + + full_output = [] + logging.info(f"Agent executing Task {test.id}...") + + for line in process.stdout: + full_output.append(line) + print(line, end="", flush=True) # Print to screen in real-time + + process.wait(timeout=600) # 10 minute timeout per task + test.execution_time = time.time() - start_time + test.output = "".join(full_output) + + # Save raw agent output + output_file = os.path.join(AGENT_OUTPUT_DIR, f"{test.id}_output.txt") + with open(output_file, 'w') as f: + f.write(f"TASK: {test.task}\n") + f.write("-" * 40 + "\n") + f.write(test.output) + + # Validate + if test.validation_fn(test): + test.result = "PASSED" + logging.info(f"Test {test.id} PASSED in {test.execution_time:.2f}s") + else: + test.result = "FAILED" + logging.error(f"Test {test.id} FAILED validation") + + except Exception as e: + logging.error(f"Error executing test {test.id}: {str(e)}") + test.result = "ERROR" + + def summary(self): + logging.info("=" * 50) + logging.info("BENCHMARK SUMMARY") + logging.info("=" * 50) + passed = sum(1 for t in self.test_cases if t.result == "PASSED") + for t in self.test_cases: + logging.info(f"[{t.result}] {t.id}: {t.name} ({t.execution_time:.2f}s)") + + logging.info("=" * 50) + logging.info(f"TOTAL PASSED: {passed}/{len(self.test_cases)}") + logging.info("=" * 50) + +# Validation Functions +def v01(t): return validate_file_contains("sorting_algo.py", "def quicksort") +def v02(t): return validate_file_exists("refactor_report.md") +def v03(t): return validate_file_exists("security_scan.txt") +def v04(t): return validate_file_exists("data_export.csv") +def v05(t): return validate_file_exists("system_monitor.py") +def v06(t): return validate_file_exists("cloud_comparison.md") +def v07(t): return validate_file_exists("network_report.txt") +def v08(t): return validate_file_exists("db_migration.sql") +def v09(t): return validate_file_contains("src/main.c", "retoor") # Dummy check +def v10(t): return validate_file_exists("CODE_DOCS.md") +def v11(t): return validate_file_exists("log_analysis.json") +def v12(t): return validate_file_exists("venv_test/bin/python") or validate_file_exists("venv_test/Scripts/python.exe") +def v13(t): return validate_file_exists("git_summary.md") +def v14(t): return validate_file_exists("research_and_demo.py") +def v15(t): return validate_file_exists("stats_summary.txt") + +if __name__ == "__main__": + benchmark = AgentBenchmark() + + # 1. Research & Develop + benchmark.add_test(TestCase("T01", "Research & Develop", "Research Quicksort and implement it", + "Research the Quicksort algorithm and write a robust Python implementation to 'sorting_algo.py'.", v01)) + + # 2. Code Analysis & Refactor + benchmark.add_test(TestCase("T02", "Refactor Suggestion", "Index project and suggest refactor", + "Index the current source directory and identify a complex function in src/agent.c. Suggest a refactor and save it to 'refactor_report.md'.", v02)) + + # 3. Security Audit + benchmark.add_test(TestCase("T03", "Security Audit", "Scan for security issues", + "Perform a security audit of the current directory using your tools. Look for insecure patterns and save findings to 'security_scan.txt'.", v03)) + + # 4. Data ETL Pipeline + benchmark.add_test(TestCase("T04", "Data ETL", "Fetch, process, store, export", + "Fetch data from https://jsonplaceholder.typicode.com/users, process it to extract just names and emails, store it in a local SQLite table named 'bench_users', and export it to 'data_export.csv'.", v04)) + + # 5. System Monitoring + benchmark.add_test(TestCase("T05", "System Monitor", "Create monitoring script", + "Write a Python script 'system_monitor.py' that logs CPU and memory usage to 'usage.log' every 5 seconds. Ensure it handles keyboard interrupts.", v05)) + + # 6. Web Research + benchmark.add_test(TestCase("T06", "Web Research", "Compare cloud providers", + "Research and compare the latest AI offerings from AWS, Azure, and Google Cloud in 2026. Create a comparison table in 'cloud_comparison.md'.", v06)) + + # 7. Network Diagnosis + benchmark.add_test(TestCase("T07", "Network Diagnosis", "Check connectivity and DNS", + "Check network connectivity to google.com and github.com. Perform DNS lookups and save a report with latency to 'network_report.txt'.", v07)) + + # 8. DB Migration + benchmark.add_test(TestCase("T08", "DB Migration", "Create and migrate schema", + "Create an SQLite schema for a library system (books, authors), insert 5 sample records, and generate a SQL dump to 'db_migration.sql'.", v08)) + + # 9. Code Maintenance + benchmark.add_test(TestCase("T09", "Code Maintenance", "Verify headers", + "Ensure all .c and .h files in the src directory start with the comment '// retoor '. If missing, add it.", v09)) + + # 10. Documentation Generator + benchmark.add_test(TestCase("T10", "Docs Generator", "Generate markdown docs", + "Analyze src/agent.c and include/agent.h to extract public function signatures and generate a professional 'CODE_DOCS.md'.", v10)) + + # 11. Log Analysis + benchmark.add_test(TestCase("T11", "Log Analysis", "Parse and categorize logs", + "Create a dummy log file with 20 lines of mixed INFO and ERROR messages. Parse it using Python to count errors and save a JSON summary to 'log_analysis.json'.", v11)) + + # 12. Env Setup + benchmark.add_test(TestCase("T12", "Env Setup", "Create virtualenv", + "Create a Python virtual environment named 'venv_test' in the current directory.", v12)) + + # 13. Git Summary + benchmark.add_test(TestCase("T13", "Git Summary", "Summarize git history", + "Get the last 5 git commit messages and summarize the changes in 'git_summary.md'.", v13)) + + # 14. Multi-agent Collaboration + benchmark.add_test(TestCase("T14", "Agent Collaboration", "Research and Code", + "Spawn a researcher to find the best way to implement a websocket server in Python, then write a functional demo to 'research_and_demo.py'.", v14)) + + # 15. CSV Processing + benchmark.add_test(TestCase("T15", "CSV Stats", "Process large CSV", + "Create a CSV 'test_data.csv' with 100 rows of random numbers, calculate mean and standard deviation using Python, and save results to 'stats_summary.txt'.", v15)) + + benchmark.run_all() diff --git a/include/agent.h b/include/agent.h index 98b42da..f6540f9 100755 --- a/include/agent.h +++ b/include/agent.h @@ -5,6 +5,7 @@ #include "messages.h" #include "r_error.h" +#include "tool.h" #include #define AGENT_MAX_ITERATIONS 300 @@ -26,6 +27,8 @@ void agent_destroy(agent_handle agent); void agent_set_max_iterations(agent_handle agent, int max); void agent_set_verbose(agent_handle agent, bool verbose); +void agent_set_is_subagent(agent_handle agent, bool is_subagent); +void agent_set_tool_registry(agent_handle agent, tool_registry_t *registry); agent_state_t agent_get_state(agent_handle agent); const char *agent_get_error(agent_handle agent); diff --git a/include/bash_executor.h b/include/bash_executor.h index bb09e57..178acb0 100644 --- a/include/bash_executor.h +++ b/include/bash_executor.h @@ -4,6 +4,6 @@ #include -char *r_bash_execute(const char *command, bool interactive); +char *r_bash_execute(const char *command, bool interactive, int timeout_seconds); #endif diff --git a/include/messages.h b/include/messages.h index 192e82c..15ddccc 100755 --- a/include/messages.h +++ b/include/messages.h @@ -32,6 +32,7 @@ r_status_t messages_replace_at(messages_handle msgs, int index, struct json_obje struct json_object *messages_to_json(messages_handle msgs); char *messages_to_string(messages_handle msgs); +char *messages_to_json_string(messages_handle msgs); int messages_count(messages_handle msgs); #endif diff --git a/include/r_config.h b/include/r_config.h index 23bc1ce..31a03df 100755 --- a/include/r_config.h +++ b/include/r_config.h @@ -24,6 +24,7 @@ bool r_config_is_verbose(r_config_handle cfg); void r_config_set_verbose(r_config_handle cfg, bool verbose); double r_config_get_temperature(r_config_handle cfg); +int r_config_get_max_tokens(r_config_handle cfg); const char *r_config_get_session_id(r_config_handle cfg); bool r_config_set_session_id(r_config_handle cfg, const char *session_id); diff --git a/include/tool.h b/include/tool.h index d523ccb..ccabf11 100755 --- a/include/tool.h +++ b/include/tool.h @@ -39,4 +39,13 @@ struct json_object *tool_registry_execute(tool_registry_t *registry, tool_registry_t *tools_get_registry(void); void tools_registry_shutdown(void); +typedef enum { + TOOL_TYPE_ALL, + TOOL_TYPE_RESEARCHER, + TOOL_TYPE_DEVELOPER, + TOOL_TYPE_SECURITY +} tool_registry_type_t; + +tool_registry_t *tool_registry_get_specialized(tool_registry_type_t type); + #endif diff --git a/src/agent.c b/src/agent.c index 5c766cb..84ad3ac 100755 --- a/src/agent.c +++ b/src/agent.c @@ -21,6 +21,7 @@ struct agent_t { time_t start_time; char *last_error; bool verbose; + bool is_subagent; messages_handle messages; bool owns_messages; http_client_handle http; @@ -34,11 +35,31 @@ static const char *incomplete_phrases[] = { "Will now", "Proceeding", "Starting to", "About to ", "First, I", "Then I", "After that", "Following that", "Now let me", "Let's check", "Let me check", + "Would you like", "Should I", "I can also", "I could", + "Do you want", "Shall I", NULL }; static const char *incomplete_endings[] = { - "...", ":", "files:", "content:", "implementation:", + "...", ":", "files:", "content:", "implementation:", "?", + NULL +}; + +static const char *completion_phrases[] = { + "task is complete", "task complete", "tasks complete", + "goal is achieved", "goal achieved", + "all steps completed", "all steps done", + "fully completed", "is now complete", + "has been completed", "have been completed", + "successfully created", "successfully written", + "setup is complete", "is ready to use", + NULL +}; + +static const char *passive_phrases[] = { + "let me know", "feel free", "if you need", "awaiting", + "ready for", "standby", "standing by", "happy to help", + "do not hesitate", "anything else", NULL }; @@ -80,6 +101,51 @@ agent_handle agent_create(const char *goal, messages_handle messages) { return NULL; } + const char *system_msg = r_config_get_system_message(cfg); + if (!system_msg || !*system_msg) { + bool has_system = false; + for (int i = 0; i < messages_count(agent->messages); i++) { + struct json_object *msg = messages_get_object(agent->messages, i); + struct json_object *role; + if (json_object_object_get_ex(msg, "role", &role)) { + const char *role_str = json_object_get_string(role); + if (role_str && strcmp(role_str, "system") == 0) { + has_system = true; + break; + } + } + } + + if (!has_system) { + messages_add(agent->messages, "system", + "You are an autonomous AI agent with full system access through function calling. " + "You have the spawn_agent tool to create specialized sub-agents for different tasks. " + "Use spawn_agent extensively for: " + "- research tasks (researcher persona) " + "- development tasks (developer persona) " + "- security audits (security persona) " + "When web_search returns results with URLs, spawn researcher agents to fetch and analyze the content. " + "Always break complex tasks into sub-tasks using agents for better orchestration. " + "Continue iterating until goals are fully achieved, using appropriate tools and agents."); + } + } else if (system_msg && *system_msg) { + bool has_system = false; + for (int i = 0; i < messages_count(agent->messages); i++) { + struct json_object *msg = messages_get_object(agent->messages, i); + struct json_object *role; + if (json_object_object_get_ex(msg, "role", &role)) { + const char *role_str = json_object_get_string(role); + if (role_str && strcmp(role_str, "system") == 0) { + has_system = true; + break; + } + } + } + if (!has_system) { + messages_add(agent->messages, "system", system_msg); + } + } + agent->http = http_client_create(r_config_get_api_key(cfg)); if (!agent->http) { if (agent->owns_messages) { @@ -112,6 +178,10 @@ void agent_set_verbose(agent_handle agent, bool verbose) { if (agent) agent->verbose = verbose; } +void agent_set_is_subagent(agent_handle agent, bool is_subagent) { + if (agent) agent->is_subagent = is_subagent; +} + void agent_set_tool_registry(agent_handle agent, tool_registry_t *registry) { if (agent && registry) agent->tools = registry; } @@ -155,6 +225,8 @@ static char *agent_build_request(agent_handle agent, const char *role, const cha json_object_get(messages_to_json(agent->messages))); json_object_object_add(root, "temperature", json_object_new_double(r_config_get_temperature(cfg))); + json_object_object_add(root, "max_tokens", + json_object_new_int(r_config_get_max_tokens(cfg))); char *result = strdup(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY)); json_object_put(root); @@ -181,9 +253,12 @@ static struct json_object *agent_process_response(agent_handle agent, const char const char *err_str = json_object_to_json_string(error_obj); // Smart error detection for context overflow - if (strstr(err_str, "too long") || - strstr(err_str, "context_length_exceeded") || - strstr(err_str, "Input is too long")) { + if (strcasestr(err_str, "too long") || + strcasestr(err_str, "context_length_exceeded") || + strcasestr(err_str, "maximum context length") || + strcasestr(err_str, "reduce the length") || + strcasestr(err_str, "context limit") || + strcasestr(err_str, "Input is too long")) { agent_set_error(agent, "CONTEXT_OVERFLOW"); } else { fprintf(stderr, "API Error: %s\n", err_str); @@ -250,8 +325,19 @@ static char *agent_get_content(struct json_object *choice) { static bool agent_response_indicates_incomplete(const char *content) { if (!content) return false; + // Check for explicit completion phrases first (Overrides incomplete indicators) + for (int i = 0; completion_phrases[i]; i++) { + if (strcasestr(content, completion_phrases[i])) return false; + } + + // Check for passive/closing phrases (Overrides incomplete indicators) + // Example: "I will be here if you need me." -> Contains "I will" but is passive. + for (int i = 0; passive_phrases[i]; i++) { + if (strcasestr(content, passive_phrases[i])) return false; + } + for (int i = 0; incomplete_phrases[i]; i++) { - if (strstr(content, incomplete_phrases[i])) return true; + if (strcasestr(content, incomplete_phrases[i])) return true; } size_t len = strlen(content); @@ -356,10 +442,12 @@ char *agent_run(agent_handle agent, const char *user_message) { char *content = agent_get_content(choice); if (content && *content) { - // Print content immediately to the user - extern void parse_markdown_to_ansi(const char *content); - parse_markdown_to_ansi(content); - printf("\n"); + // Print content immediately to the user (only if NOT a sub-agent) + if (!agent->is_subagent) { + extern void parse_markdown_to_ansi(const char *content); + parse_markdown_to_ansi(content); + printf("\n"); + } size_t content_len = strlen(content); char *new_acc = realloc(accumulated_response, accumulated_len + content_len + 2); @@ -423,7 +511,7 @@ char *agent_run(agent_handle agent, const char *user_message) { } } else { agent->state = AGENT_STATE_COMPLETED; - if (agent->verbose) { + if (agent->verbose && !agent->is_subagent) { fprintf(stderr, "[Agent] Completed in %d iteration(s)\n", agent->iteration_count); } diff --git a/src/bash_executor.c b/src/bash_executor.c index f5f5477..56e7ec3 100644 --- a/src/bash_executor.c +++ b/src/bash_executor.c @@ -8,12 +8,24 @@ #include #include #include +#include +#include +#include +#include +#include +#include -char *r_bash_execute(const char *command, bool interactive) { +#define DEFAULT_TIMEOUT 300 + +char *r_bash_execute(const char *command, bool interactive, int timeout_seconds) { if (!command) { return strdup("Error: null command"); } + if (timeout_seconds <= 0) { + timeout_seconds = DEFAULT_TIMEOUT; + } + size_t len = strlen(command); char *cmd_with_nl = malloc(len + 2); if (!cmd_with_nl) { @@ -30,78 +42,157 @@ char *r_bash_execute(const char *command, bool interactive) { } char tmp_script[] = "/tmp/r_bash_XXXXXX.sh"; - int fd = mkstemps(tmp_script, 3); - if (fd == -1) { + int script_fd = mkstemps(tmp_script, 3); + if (script_fd == -1) { free(cmd_with_nl); - perror("mkstemps"); return strdup("Error: failed to create temp script"); } - if (write(fd, cmd_with_nl, strlen(cmd_with_nl)) == -1) { - close(fd); + if (write(script_fd, cmd_with_nl, strlen(cmd_with_nl)) == -1) { + close(script_fd); free(cmd_with_nl); unlink(tmp_script); return strdup("Error: failed to write to temp script"); } - close(fd); + close(script_fd); free(cmd_with_nl); char *output = NULL; + size_t total_size = 0; if (interactive) { + // For interactive mode, we still use system() but it doesn't easily support capturing output while timing out + // Given the requirement, we'll try to use a simple timeout for interactive too if we can, + // but typically interactive means user is at it. + // However, user said "prevent hanging processes". + char *run_cmd = NULL; - if (asprintf(&run_cmd, "bash %s", tmp_script) == -1) { + if (asprintf(&run_cmd, "timeout %ds bash %s", timeout_seconds, tmp_script) == -1) { unlink(tmp_script); return strdup("Error: asprintf failed"); } int status = system(run_cmd); free(run_cmd); - if (asprintf(&output, "Command exited with status %d", status) == -1) { - output = strdup("Command completed (asprintf failed for status)."); + if (WIFEXITED(status) && WEXITSTATUS(status) == 124) { + output = strdup("Error: Command timed out in interactive mode."); + } else { + if (asprintf(&output, "Command exited with status %d", status) == -1) { + output = strdup("Command completed."); + } } } else { - char *run_cmd = NULL; - if (asprintf(&run_cmd, "bash %s 2>&1", tmp_script) == -1) { + int pipe_fds[2]; + if (pipe(pipe_fds) == -1) { unlink(tmp_script); - return strdup("Error: asprintf failed"); + return strdup("Error: pipe failed"); } + + pid_t pid = fork(); + if (pid == -1) { + close(pipe_fds[0]); + close(pipe_fds[1]); + unlink(tmp_script); + return strdup("Error: fork failed"); + } + + if (pid == 0) { + // Child + close(pipe_fds[0]); + dup2(pipe_fds[1], STDOUT_FILENO); + dup2(pipe_fds[1], STDERR_FILENO); + close(pipe_fds[1]); + + char *args[] = {"bash", tmp_script, NULL}; + execvp("bash", args); + exit(1); + } + + // Parent + close(pipe_fds[1]); + int out_fd = pipe_fds[0]; - FILE *fp = popen(run_cmd, "r"); - free(run_cmd); + struct poll_pfd { + int fd; + short events; + short revents; + } pfd; + pfd.fd = out_fd; + pfd.events = POLLIN; - if (!fp) { - unlink(tmp_script); - return strdup("Error: popen failed"); - } + time_t start_time = time(NULL); + bool timed_out = false; - char buffer[1024]; - size_t total_size = 0; - - while (fgets(buffer, sizeof(buffer), fp)) { - // Print to stderr for the user in a subtle dim/gray color - fprintf(stderr, "\033[2m%s\033[0m", buffer); - - size_t chunk_len = strlen(buffer); - char *new_output = realloc(output, total_size + chunk_len + 1); - if (!new_output) { - free(output); - pclose(fp); - unlink(tmp_script); - return strdup("Error: memory allocation failed"); + while (true) { + time_t now = time(NULL); + int remaining = timeout_seconds - (int)(now - start_time); + if (remaining <= 0) { + timed_out = true; + break; } - output = new_output; - strcpy(output + total_size, buffer); - total_size += chunk_len; - } - pclose(fp); - if (!output) { - output = strdup(""); + int ret = poll((struct pollfd *)&pfd, 1, remaining * 1000); + if (ret == -1) { + if (errno == EINTR) continue; + break; + } + if (ret == 0) { + timed_out = true; + break; + } + + if (pfd.revents & POLLIN) { + char buffer[4096]; + ssize_t bytes = read(out_fd, buffer, sizeof(buffer) - 1); + if (bytes <= 0) break; + buffer[bytes] = '\0'; + + // Print to stderr for user + fprintf(stderr, "\033[2m%s\033[0m", buffer); + fflush(stderr); + + char *new_output = realloc(output, total_size + (size_t)bytes + 1); + if (!new_output) { + break; + } + output = new_output; + memcpy(output + total_size, buffer, (size_t)bytes); + total_size += (size_t)bytes; + output[total_size] = '\0'; + } else if (pfd.revents & (POLLHUP | POLLERR)) { + break; + } } + + if (timed_out) { + kill(-pid, SIGKILL); // Kill process group if possible + kill(pid, SIGKILL); + + const char *timeout_msg = "\n[Error: Command timed out after %d seconds]\n"; + char *msg = NULL; + if (asprintf(&msg, timeout_msg, timeout_seconds) != -1) { + size_t msg_len = strlen(msg); + char *new_output = realloc(output, total_size + msg_len + 1); + if (new_output) { + output = new_output; + strcpy(output + total_size, msg); + total_size += msg_len; + } + free(msg); + } + + fprintf(stderr, "\033[1;31m%s\033[0m", "\n[Timeout reached, process terminated]\n"); + } + + close(out_fd); + waitpid(pid, NULL, WNOHANG); + } + + if (!output) { + output = strdup(""); } unlink(tmp_script); return output; -} +} \ No newline at end of file diff --git a/src/context_manager.c b/src/context_manager.c index b469c20..2a85be5 100644 --- a/src/context_manager.c +++ b/src/context_manager.c @@ -5,20 +5,40 @@ #include #include -#define TRUNCATE_THRESHOLD 2000 -#define TRUNCATE_KEEP_START 800 -#define TRUNCATE_KEEP_END 800 +#define MIN_KEEP_CHARS 500 #define TRUNCATE_MARKER "\n\n[... content truncated for context management ...]\n\n" -static bool is_system_message(struct json_object *msg) { +static const char *get_message_role(struct json_object *msg) { struct json_object *role_obj; if (json_object_object_get_ex(msg, "role", &role_obj)) { - return strcmp(json_object_get_string(role_obj), "system") == 0; + return json_object_get_string(role_obj); } - return false; + return ""; } -static r_status_t truncate_middle(messages_handle msgs, int index) { +static size_t get_message_content_len(struct json_object *msg) { + struct json_object *content_obj; + const char *content = NULL; + + if (json_object_object_get_ex(msg, "content", &content_obj)) { + content = json_object_get_string(content_obj); + } else if (json_object_object_get_ex(msg, "tool_result", &content_obj)) { + content = json_object_get_string(content_obj); + } + + return content ? strlen(content) : 0; +} + +static size_t calculate_total_size(messages_handle msgs) { + size_t total = 0; + int count = messages_count(msgs); + for (int i = 0; i < count; i++) { + total += get_message_content_len(messages_get_object(msgs, i)); + } + return total; +} + +static r_status_t perform_truncate(messages_handle msgs, int index, double ratio) { struct json_object *msg = messages_get_object(msgs, index); if (!msg) return R_ERROR_NOT_FOUND; @@ -36,17 +56,21 @@ static r_status_t truncate_middle(messages_handle msgs, int index) { if (!content) return R_SUCCESS; size_t len = strlen(content); - if (len <= TRUNCATE_THRESHOLD) return R_SUCCESS; + size_t target_len = (size_t)(len * ratio); + if (target_len < MIN_KEEP_CHARS * 2) target_len = MIN_KEEP_CHARS * 2; + if (target_len >= len) return R_SUCCESS; - char *new_content = malloc(TRUNCATE_KEEP_START + strlen(TRUNCATE_MARKER) + TRUNCATE_KEEP_END + 1); + size_t keep_each = target_len / 2; + + char *new_content = malloc(keep_each * 2 + strlen(TRUNCATE_MARKER) + 1); if (!new_content) return R_ERROR_OUT_OF_MEMORY; - strncpy(new_content, content, TRUNCATE_KEEP_START); - new_content[TRUNCATE_KEEP_START] = '\0'; + strncpy(new_content, content, keep_each); + new_content[keep_each] = '\0'; strcat(new_content, TRUNCATE_MARKER); - strcat(new_content, content + len - TRUNCATE_KEEP_END); + strcat(new_content, content + len - keep_each); - struct json_object *new_msg = json_object_get(msg); // Increments ref count + struct json_object *new_msg = json_object_get(msg); if (is_tool_result) { json_object_object_add(new_msg, "tool_result", json_object_new_string(new_content)); } else { @@ -61,51 +85,71 @@ r_status_t context_manager_shrink(messages_handle msgs) { if (!msgs) return R_ERROR_INVALID_ARG; int count = messages_count(msgs); - if (count <= 1) return R_ERROR_API_ERROR; // Cannot shrink further + if (count <= 1) return R_ERROR_API_ERROR; - fprintf(stderr, " \033[2m-> Context limit reached, compressing history...\033[0m\n"); + size_t initial_size = calculate_total_size(msgs); + // Target 40% of initial size to be safe and avoid immediate re-overflow + size_t target_size = (size_t)(initial_size * 0.4); + if (target_size < 10000) target_size = 10000; // Don't shrink too much if it's already small - // Phase 1: Truncate large messages in history (middle-cut) - // We skip the last message as it's usually the one we just added or the latest prompt - bool truncated_any = false; - for (int i = 0; i < count - 1; i++) { - struct json_object *msg = messages_get_object(msgs, i); - if (is_system_message(msg)) continue; + fprintf(stderr, " \033[2m-> Context overflow (approx %zu chars). Shrinking to %zu...\033[0m\n", + initial_size, target_size); - struct json_object *content_obj; - const char *content = NULL; - if (json_object_object_get_ex(msg, "content", &content_obj)) { - content = json_object_get_string(content_obj); - } else if (json_object_object_get_ex(msg, "tool_result", &content_obj)) { - content = json_object_get_string(content_obj); - } - - if (content && strlen(content) > TRUNCATE_THRESHOLD) { - if (truncate_middle(msgs, i) == R_SUCCESS) { - truncated_any = true; + int iterations = 0; + while (calculate_total_size(msgs) > target_size && iterations < 50) { + iterations++; + count = messages_count(msgs); + + // Strategy 1: Find largest non-system, non-last message and truncate it + int largest_idx = -1; + size_t largest_size = 0; + + for (int i = 0; i < count - 1; i++) { + struct json_object *msg = messages_get_object(msgs, i); + if (strcmp(get_message_role(msg), "system") == 0) continue; + + size_t s = get_message_content_len(msg); + if (s > largest_size) { + largest_size = s; + largest_idx = i; } } - } - if (truncated_any) return R_SUCCESS; + if (largest_idx != -1 && largest_size > 1000) { + perform_truncate(msgs, largest_idx, 0.3); // Cut to 30% of its size + continue; + } - // Phase 2: Historical Eviction (remove oldest non-system pairs) - // We look for the first non-system message - int first_removable = -1; - for (int i = 0; i < count - 1; i++) { - struct json_object *msg = messages_get_object(msgs, i); - if (!is_system_message(msg)) { - first_removable = i; + // Strategy 2: Remove oldest removable messages (keep sequence) + int first_removable = -1; + for (int i = 0; i < count - 1; i++) { + struct json_object *msg = messages_get_object(msgs, i); + if (strcmp(get_message_role(msg), "system") != 0) { + first_removable = i; + break; + } + } + + if (first_removable != -1 && first_removable < count - 1) { + // Remove 1 message at a time from the front + messages_remove_range(msgs, first_removable, 1); + } else { + // Nothing left to remove but system or last message break; } } - if (first_removable != -1 && first_removable < count - 1) { - // Remove 2 messages to keep user/assistant pairs if possible, - // or just one if it's a tool sequence - int to_remove = (count - first_removable > 2) ? 2 : 1; - return messages_remove_range(msgs, first_removable, to_remove); + // Last Resort: If still too big, truncate the last message + size_t final_size = calculate_total_size(msgs); + if (final_size > target_size) { + count = messages_count(msgs); + if (count > 0) { + perform_truncate(msgs, count - 1, 0.5); + } } - return R_ERROR_API_ERROR; -} + size_t shrunk_size = calculate_total_size(msgs); + fprintf(stderr, " \033[2m-> Context shrunk to approx %zu chars.\033[0m\n", shrunk_size); + + return R_SUCCESS; +} \ No newline at end of file diff --git a/src/http_client.c b/src/http_client.c index 98fd179..42d5444 100755 --- a/src/http_client.c +++ b/src/http_client.c @@ -119,7 +119,9 @@ r_status_t http_post(http_client_handle client, const char *url, *response = NULL; - if (client->show_spinner) { + bool actually_show_spinner = client->show_spinner && isatty(STDERR_FILENO); + + if (actually_show_spinner) { clock_gettime(CLOCK_MONOTONIC, &spinner_start_time); spinner_running = 1; pthread_create(&spinner_tid, NULL, spinner_thread, NULL); @@ -175,7 +177,7 @@ r_status_t http_post(http_client_handle client, const char *url, retry_count++; - if (client->show_spinner) { + if (actually_show_spinner) { spinner_running = 0; pthread_join(spinner_tid, NULL); spinner_tid = 0; @@ -189,7 +191,7 @@ r_status_t http_post(http_client_handle client, const char *url, fprintf(stderr, "Retrying in %d seconds...\n", HTTP_RETRY_DELAY_MS / 1000); usleep(HTTP_RETRY_DELAY_MS * 1000); - if (client->show_spinner) { + if (actually_show_spinner) { clock_gettime(CLOCK_MONOTONIC, &spinner_start_time); spinner_running = 1; pthread_create(&spinner_tid, NULL, spinner_thread, NULL); @@ -200,7 +202,7 @@ r_status_t http_post(http_client_handle client, const char *url, status = R_ERROR_HTTP_TIMEOUT; cleanup: - if (client->show_spinner && spinner_tid) { + if (actually_show_spinner && spinner_tid) { spinner_running = 0; pthread_join(spinner_tid, NULL); fprintf(stderr, "\r \r"); diff --git a/src/interfaces/config.c b/src/interfaces/config.c index abbf2ca..7dc289e 100644 --- a/src/interfaces/config.c +++ b/src/interfaces/config.c @@ -1,10 +1,9 @@ -#include "config.h" +// retoor #include "util/path.h" #include #include #include #include - struct config_t { char *api_url; char *models_url; @@ -18,13 +17,10 @@ struct config_t { bool use_strict; bool verbose; }; - static struct config_t *instance = NULL; - static char *strdup_safe(const char *s) { return s ? strdup(s) : NULL; } - static bool resolve_env_bool(const char *env_name, bool default_val) { const char *val = getenv(env_name); if (!val) return default_val; @@ -32,24 +28,18 @@ static bool resolve_env_bool(const char *env_name, bool default_val) { if (!strcmp(val, "false") || !strcmp(val, "0")) return false; return default_val; } - static const char *resolve_api_key(void) { const char *key = getenv("OPENROUTER_API_KEY"); if (key && *key) return key; - key = getenv("R_KEY"); if (key && *key) return key; - key = getenv("OPENAI_API_KEY"); if (key && *key) return key; - return "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"; } - static bool is_valid_session_id(const char *session_id) { if (!session_id || !*session_id) return false; if (strlen(session_id) > 255) return false; - for (const char *p = session_id; *p; p++) { if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_' && *p != '.') { return false; @@ -57,13 +47,10 @@ static bool is_valid_session_id(const char *session_id) { } return true; } - config_handle config_create(void) { if (instance) return instance; - struct config_t *cfg = calloc(1, sizeof(struct config_t)); if (!cfg) return NULL; - const char *base_url = getenv("R_BASE_URL"); if (base_url && *base_url) { size_t len = strlen(base_url); @@ -77,34 +64,27 @@ config_handle config_create(void) { cfg->api_url = strdup("https://api.openai.com/v1/chat/completions"); cfg->models_url = strdup("https://api.openai.com/v1/models"); } - const char *model = getenv("R_MODEL"); cfg->model = strdup(model && *model ? model : "gpt-4o-mini"); - cfg->api_key = strdup(resolve_api_key()); cfg->db_path = strdup("~/.r.db"); cfg->temperature = 0.1; cfg->use_tools = resolve_env_bool("R_USE_TOOLS", true); cfg->use_strict = resolve_env_bool("R_USE_STRICT", true); cfg->verbose = false; - const char *session = getenv("R_SESSION"); if (session && is_valid_session_id(session)) { cfg->session_id = strdup(session); } else { cfg->session_id = strdup("default"); } - const char *system_msg = getenv("R_SYSTEM_MESSAGE"); cfg->system_message = strdup_safe(system_msg); - instance = cfg; return cfg; } - void config_destroy(config_handle cfg) { if (!cfg) return; - if (cfg != instance) { if (cfg->api_url) free(cfg->api_url); if (cfg->models_url) free(cfg->models_url); @@ -116,73 +96,54 @@ void config_destroy(config_handle cfg) { free(cfg); } } - const char *config_get_api_url(config_handle cfg) { return cfg ? cfg->api_url : NULL; } - const char *config_get_models_url(config_handle cfg) { return cfg ? cfg->models_url : NULL; } - const char *config_get_api_key(config_handle cfg) { return cfg ? cfg->api_key : NULL; } - const char *config_get_model(config_handle cfg) { return cfg ? cfg->model : NULL; } - const char *config_get_db_path(config_handle cfg) { return cfg ? cfg->db_path : NULL; } - const char *config_get_session_id(config_handle cfg) { return cfg ? cfg->session_id : NULL; } - const char *config_get_system_message(config_handle cfg) { return cfg ? cfg->system_message : NULL; } - double config_get_temperature(config_handle cfg) { return cfg ? cfg->temperature : 0.0; } - bool config_use_tools(config_handle cfg) { return cfg ? cfg->use_tools : true; } - bool config_use_strict(config_handle cfg) { return cfg ? cfg->use_strict : true; } - bool config_is_verbose(config_handle cfg) { return cfg ? cfg->verbose : false; } - r_status_t config_set_model(config_handle cfg, const char *model) { if (!cfg || !model) return R_ERROR_INVALID_ARG; - if (cfg->model) free(cfg->model); cfg->model = strdup(model); - return R_SUCCESS; } - r_status_t config_set_session_id(config_handle cfg, const char *session_id) { if (!cfg || !session_id) return R_ERROR_INVALID_ARG; - if (!is_valid_session_id(session_id)) { return R_ERROR_SESSION_INVALID; } - if (cfg->session_id) free(cfg->session_id); cfg->session_id = strdup(session_id); - return R_SUCCESS; } - void config_set_verbose(config_handle cfg, bool verbose) { if (cfg) cfg->verbose = verbose; } diff --git a/src/interfaces/http.h b/src/interfaces/http.h index 036ee6e..5f62c4d 100644 --- a/src/interfaces/http.h +++ b/src/interfaces/http.h @@ -1,28 +1,21 @@ // retoor - #ifndef R_INTERFACES_HTTP_H #define R_INTERFACES_HTTP_H - #include "r_error.h" #include #include - typedef struct http_client_t *http_client_handle; - typedef enum { HTTP_METHOD_GET, HTTP_METHOD_POST, HTTP_METHOD_PUT, HTTP_METHOD_DELETE } http_method_t; - http_client_handle http_create(const char *base_url); void http_destroy(http_client_handle client); - void http_set_bearer_token(http_client_handle client, const char *token); void http_set_timeout(http_client_handle client, long timeout_seconds); void http_set_connect_timeout(http_client_handle client, long timeout_seconds); - r_status_t http_request( http_client_handle client, http_method_t method, @@ -32,9 +25,7 @@ r_status_t http_request( char **response, int *response_code ); - r_status_t http_get(http_client_handle client, const char *path, char **response); r_status_t http_post(http_client_handle client, const char *path, const char *body, char **response); r_status_t http_post_json(http_client_handle client, const char *path, const char *json, char **response); - #endif diff --git a/src/interfaces/logger.c b/src/interfaces/logger.c index 360d2e4..d726847 100644 --- a/src/interfaces/logger.c +++ b/src/interfaces/logger.c @@ -1,16 +1,13 @@ // retoor - #include "logger.h" #include #include #include #include #include - struct logger_t { log_level_t level; }; - static const char *level_strings[] = { "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL" }; @@ -18,49 +15,37 @@ static const char *level_colors[] = { "\033[90m", "\033[36m", "\033[32m", "\033[33m", "\033[31m", "\033[35m" }; static const char *color_reset = "\033[0m"; - logger_handle logger_create(log_level_t level) { struct logger_t *logger = malloc(sizeof(struct logger_t)); if (!logger) return NULL; - logger->level = level; return logger; } - void logger_destroy(logger_handle logger) { if (logger) free(logger); } - void logger_set_level(logger_handle logger, log_level_t level) { if (logger) logger->level = level; } - log_level_t logger_get_level(logger_handle logger) { return logger ? logger->level : LOG_LEVEL_INFO; } - void logger_log(logger_handle logger, log_level_t level, const char *file, int line, const char *fmt, ...) { if (!logger || !fmt) return; - if (level < logger->level) return; - va_list args; va_start(args, fmt); - time_t now = time(NULL); struct tm *tm_info = localtime(&now); char time_buf[32]; strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info); - const char *basename = strrchr(file, '/'); basename = basename ? basename + 1 : file; - fprintf(stderr, "%s[%s%s%s %s:%d] ", level_colors[level], level_strings[level], color_reset, basename, line); vfprintf(stderr, fmt, args); fprintf(stderr, "%s\n", color_reset); - va_end(args); } diff --git a/src/main.c b/src/main.c index f7fe984..f42c305 100755 --- a/src/main.c +++ b/src/main.c @@ -293,35 +293,25 @@ static void init(void) { "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." + "For EVERY task, you MUST follow this sequence:\n" + "1. Plan: Break the task into logical sub-tasks. Decide which specialized agents to spawn.\n" + "2. Execute: Spawn agents or use tools. INTEGRATE their results immediately.\n" + "3. Verify: Ensure the integrated results meet the goal. Perform any final actions (like saving to a file).\n" + "4. Conclude: Only after ALL sub-tasks and final actions are done, provide your final response.\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 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." - "## 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" + "- 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" - "- 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" + "- 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" "## Local Database\n" "You have a local SQLite database accessible via db_query, db_get, and " "db_set tools.\n" @@ -345,7 +335,7 @@ static void init(void) { "## 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" + "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 " diff --git a/src/messages.c b/src/messages.c index 7dbdbc9..a4c8d63 100755 --- a/src/messages.c +++ b/src/messages.c @@ -257,6 +257,11 @@ char *messages_to_string(messages_handle msgs) { return strdup(json_object_to_json_string_ext(msgs->array, JSON_C_TO_STRING_PRETTY)); } +char *messages_to_json_string(messages_handle msgs) { + if (!msgs || !msgs->array) return NULL; + return strdup(json_object_to_json_string_ext(msgs->array, JSON_C_TO_STRING_PLAIN)); +} + int messages_count(messages_handle msgs) { if (!msgs || !msgs->array) return 0; return json_object_array_length(msgs->array); diff --git a/src/r_config.c b/src/r_config.c index 7c42cc4..1ff55bd 100755 --- a/src/r_config.c +++ b/src/r_config.c @@ -15,6 +15,7 @@ struct r_config_t { char *session_id; char *system_message; double temperature; + int max_tokens; bool use_tools; bool use_strict; bool verbose; @@ -36,11 +37,13 @@ static bool resolve_env_bool(const char *env_name, bool default_val) { static const char *resolve_api_key(void) { - const char * key = getenv("OPENROUTER_API_KEY"); + const char * key = getenv("R_KEY"); if (key && *key) return key; - key = getenv("R_KEY"); + + + key = getenv("OPENROUTER_API_KEY"); if (key && *key) return key; @@ -90,6 +93,8 @@ r_config_handle r_config_get_instance(void) { instance->api_key = strdup(resolve_api_key()); instance->db_path = strdup("~/.r.db"); instance->temperature = 0.1; + const char *max_tokens_env = getenv("R_MAX_TOKENS"); + instance->max_tokens = max_tokens_env ? atoi(max_tokens_env) : 4096; instance->use_tools = resolve_env_bool("R_USE_TOOLS", true); instance->use_strict = resolve_env_bool("R_USE_STRICT", true); instance->verbose = false; @@ -165,6 +170,10 @@ double r_config_get_temperature(r_config_handle cfg) { return cfg ? cfg->temperature : 0.1; } +int r_config_get_max_tokens(r_config_handle cfg) { + return cfg ? cfg->max_tokens : 4096; +} + const char *r_config_get_session_id(r_config_handle cfg) { return cfg ? cfg->session_id : NULL; } diff --git a/src/tool_registry.c b/src/tool_registry.c index 597da82..3c2cb56 100755 --- a/src/tool_registry.c +++ b/src/tool_registry.c @@ -148,8 +148,14 @@ struct json_object *tool_registry_execute(tool_registry_t *registry, if (threads[i]) { pthread_join(threads[i], NULL); } - json_object_object_add(result_objs[i], "content", - json_object_new_string(t_args[i].output ? t_args[i].output : "")); + + char *output = t_args[i].output ? t_args[i].output : ""; + json_object_object_add(result_objs[i], "content", json_object_new_string(output)); + + if (output && strncmp(output, "Error:", 6) == 0) { + fprintf(stderr, "\033[1;31m[Tool Error] %s\033[0m\n", output); + } + free(t_args[i].output); if (t_args[i].args) json_object_put(t_args[i].args); json_object_array_add(results, result_objs[i]); diff --git a/src/tools/tool_agent.c b/src/tools/tool_agent.c index df7a483..d5f7ef3 100644 --- a/src/tools/tool_agent.c +++ b/src/tools/tool_agent.c @@ -1,5 +1,4 @@ // retoor - #include "tool.h" #include "agent.h" #include "messages.h" @@ -8,139 +7,177 @@ #include #include #include - typedef struct { tool_t tool; } tool_agent_t; - static struct json_object *tool_spawn_agent_get_description(void) { struct json_object *obj = json_object_new_object(); json_object_object_add(obj, "name", json_object_new_string("spawn_agent")); json_object_object_add(obj, "description", json_object_new_string("Spawn a specialized sub-agent to handle a specific task.")); - struct json_object *params = json_object_new_object(); json_object_object_add(params, "type", json_object_new_string("object")); - struct json_object *props = json_object_new_object(); - struct json_object *persona = json_object_new_object(); json_object_object_add(persona, "type", json_object_new_string("string")); - json_object_object_add(persona, "description", json_object_new_string("The persona of the agent (researcher, developer, security).")); + json_object_object_add(persona, "description", json_object_new_string("The persona of the agent (researcher, developer, security, fetcher).")); struct json_object *persona_enum = json_object_new_array(); json_object_array_add(persona_enum, json_object_new_string("researcher")); json_object_array_add(persona_enum, json_object_new_string("developer")); json_object_array_add(persona_enum, json_object_new_string("security")); + json_object_array_add(persona_enum, json_object_new_string("fetcher")); json_object_object_add(persona, "enum", persona_enum); json_object_object_add(props, "persona", persona); - struct json_object *goal = json_object_new_object(); json_object_object_add(goal, "type", json_object_new_string("string")); json_object_object_add(goal, "description", json_object_new_string("The specific task or goal for the sub-agent.")); json_object_object_add(props, "goal", goal); - + struct json_object *max_subagents = json_object_new_object(); + json_object_object_add(max_subagents, "type", json_object_new_string("integer")); + json_object_object_add(max_subagents, "description", json_object_new_string("Remaining budget for spawning recursive sub-agents. Decrement this by 1 when spawning a sub-agent. Default is 2.")); + json_object_object_add(max_subagents, "default", json_object_new_int(2)); + json_object_object_add(props, "max_subagents", max_subagents); json_object_object_add(params, "properties", props); - struct json_object *required = json_object_new_array(); json_object_array_add(required, json_object_new_string("persona")); json_object_array_add(required, json_object_new_string("goal")); + json_object_array_add(required, json_object_new_string("max_subagents")); json_object_object_add(params, "required", required); - + json_object_object_add(params, "additionalProperties", json_object_new_boolean(0)); json_object_object_add(obj, "parameters", params); - + r_config_handle cfg = r_config_get_instance(); + if (r_config_use_strict(cfg)) { + json_object_object_add(obj, "strict", json_object_new_boolean(1)); + } struct json_object *full_obj = json_object_new_object(); json_object_object_add(full_obj, "type", json_object_new_string("function")); json_object_object_add(full_obj, "function", obj); - return full_obj; } - static char *tool_spawn_agent_execute(tool_t *self, struct json_object *args) { (void)self; - struct json_object *persona_obj, *goal_obj; - + struct json_object *persona_obj, *goal_obj, *max_subagents_obj; if (!json_object_object_get_ex(args, "persona", &persona_obj) || !json_object_object_get_ex(args, "goal", &goal_obj)) { return strdup("Error: Missing persona or goal"); } - + int max_subagents = 2; + if (json_object_object_get_ex(args, "max_subagents", &max_subagents_obj)) { + max_subagents = json_object_get_int(max_subagents_obj); + } + if (max_subagents <= 0) { + return strdup("Error: Spawning limit reached. You are not allowed to spawn more sub-agents. Perform the task yourself using existing tools."); + } const char *persona_str = json_object_get_string(persona_obj); const char *goal_str = json_object_get_string(goal_obj); - tool_registry_type_t type = TOOL_TYPE_ALL; - const char *system_prompt = NULL; - + const char *system_prompt_base = NULL; if (strcmp(persona_str, "researcher") == 0) { type = TOOL_TYPE_RESEARCHER; - system_prompt = "You are a specialized Research Agent. Your goal is to find, extract, and summarize information. " + system_prompt_base = "You are a specialized Research Agent. Your goal is to find, extract, and summarize information. " "Do not attempt to write or execute code unless it's for data analysis. " - "Focus on using web search, http fetch, and reading files."; + "Focus on using web search, http fetch, and reading files. " + "## Sub-Agent Rules\n" + "- Your output is for a master agent, not the final user.\n" + "- DO NOT ask questions or for permission.\n" + "- Provide RAW DATA and summaries.\n" + "- Do not say 'task complete'.\n" + "## Hierarchical Research Workflow\n" + "When web_search returns URLs with content:\n" + "1. Spawn 'fetcher' agents in parallel to fetch URL contents\n" + "2. Each fetcher should use http_fetch tool for individual URLs\n" + "3. Aggregate results and synthesize with citations\n" + "Citation format: [Source N] Title (URL)\n" + "Use spawn_agent extensively for URL fetching from search results."; } else if (strcmp(persona_str, "developer") == 0) { type = TOOL_TYPE_DEVELOPER; - system_prompt = "You are a specialized Developer Agent. Your goal is to write, test, and debug code. " + system_prompt_base = "You are a specialized Developer Agent. Your goal is to write, test, and debug code. " + "## Sub-Agent Rules\n" + "- Your output is for a master agent, not the final user.\n" + "- DO NOT ask questions or for permission.\n" + "- Just perform the requested development task and report results.\n" "Use the terminal, file editing tools, and python execution to fulfill your task. " "Always verify your changes by running tests or the code itself."; } else if (strcmp(persona_str, "security") == 0) { type = TOOL_TYPE_SECURITY; - system_prompt = "You are a specialized Security Auditor Agent. Your goal is to find vulnerabilities and perform security analysis. " + system_prompt_base = "You are a specialized Security Auditor Agent. Your goal is to find vulnerabilities and perform security analysis. " + "## Sub-Agent Rules\n" + "- Your output is for a master agent, not the final user.\n" + "- DO NOT ask questions or for permission.\n" "Be pedantic and thorough. Use fuzzing, port scanning, and code analysis tools. " "Report any findings clearly with potential impact."; + } else if (strcmp(persona_str, "fetcher") == 0) { + type = TOOL_TYPE_ALL; + system_prompt_base = "You are a specialized URL Fetcher Agent. Your goal is to fetch and extract content from multiple URLs. " + "## Sub-Agent Rules\n" + "- Just return the content of the URLs.\n" + "- Do not provide commentary unless necessary.\n" + "Use http_fetch tool to retrieve content from each URL. " + "Handle errors gracefully and continue with other URLs. " + "Clean HTML/extract text suitable for LLM analysis. " + "Truncate content to ~10K chars per URL to stay within token limits."; } else { return strdup("Error: Invalid persona"); } - + 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); + size_t prompt_size = strlen(system_prompt_base) + 1024; + char *system_prompt = malloc(prompt_size); + if (!system_prompt) return strdup("Error: Out of memory"); + snprintf(system_prompt, prompt_size, + "Current date/time: %s\n\n%s\n\n" + "CRITICAL: It is currently %s.\n" + "ORCHESTRATION BUDGET: You are allowed to spawn up to %d more levels of sub-agents. " + "When using spawn_agent, you MUST pass 'max_subagents' as %d.\n", + datetime, system_prompt_base, datetime, max_subagents, max_subagents - 1); char session_id[256]; snprintf(session_id, sizeof(session_id), "subagent-%s-%u", persona_str, (unsigned int)time(NULL)); - messages_handle msgs = messages_create(session_id); messages_add(msgs, "system", system_prompt); - + free(system_prompt); agent_handle agent = agent_create(goal_str, msgs); if (!agent) { messages_destroy(msgs); return strdup("Error: Failed to create sub-agent"); } - + agent_set_is_subagent(agent, true); tool_registry_t *specialized_tools = tool_registry_get_specialized(type); - agent_set_tool_registry(agent, specialized_tools); + if (specialized_tools) { + agent_set_tool_registry(agent, specialized_tools); + } + agent_set_max_iterations(agent, 50); // Sub-agents have lower limit - - char *result = agent_run(agent, goal_str); - + char *agent_response = agent_run(agent, goal_str); + char *result = messages_to_json_string(msgs); agent_destroy(agent); - // specialized_tools should be destroyed? - // In tool_registry_get_specialized we create a new one. - tool_registry_destroy(specialized_tools); - + free(agent_response); + + if (specialized_tools) { + tool_registry_destroy(specialized_tools); + } if (!result) { return strdup("Sub-agent failed to provide a result."); } - return result; } - static void tool_spawn_agent_print_action(const char *name, struct json_object *args) { struct json_object *persona_obj, *goal_obj; const char *persona = "unknown"; const char *goal = "unknown"; - if (json_object_object_get_ex(args, "persona", &persona_obj)) persona = json_object_get_string(persona_obj); if (json_object_object_get_ex(args, "goal", &goal_obj)) goal = json_object_get_string(goal_obj); - printf("\033[1;34m[Agent] Spawning %s agent for: %s\033[0m\n", persona, goal); } - static const tool_vtable_t tool_spawn_agent_vtable = { .get_description = tool_spawn_agent_get_description, .execute = tool_spawn_agent_execute, .print_action = tool_spawn_agent_print_action }; - tool_t *tool_spawn_agent_create(void) { tool_agent_t *tool = calloc(1, sizeof(tool_agent_t)); if (!tool) return NULL; - tool->tool.vtable = &tool_spawn_agent_vtable; tool->tool.name = "spawn_agent"; - return &tool->tool; } diff --git a/src/tools/tool_automation.c b/src/tools/tool_automation.c index a4defae..989c274 100644 --- a/src/tools/tool_automation.c +++ b/src/tools/tool_automation.c @@ -1,6 +1,7 @@ // retoor #include "tool.h" +#include "r_config.h" #include "bash_executor.h" #include #include @@ -139,12 +140,31 @@ static struct json_object *automation_fuzz_get_description(void) { json_object_object_add(port, "type", json_object_new_string("integer")); json_object_object_add(properties, "port", port); + struct json_object *template = json_object_new_object(); + json_object_object_add(template, "type", json_object_new_string("string")); + json_object_object_add(template, "description", json_object_new_string("Base template for fuzzing.")); + json_object_object_add(properties, "template", template); + + struct json_object *iterations = json_object_new_object(); + json_object_object_add(iterations, "type", json_object_new_string("integer")); + json_object_object_add(iterations, "description", json_object_new_string("Number of iterations.")); + json_object_object_add(properties, "iterations", iterations); + json_object_object_add(parameters, "properties", properties); struct json_object *required = json_object_new_array(); json_object_array_add(required, json_object_new_string("host")); json_object_array_add(required, json_object_new_string("port")); + json_object_array_add(required, json_object_new_string("template")); + json_object_array_add(required, json_object_new_string("iterations")); json_object_object_add(parameters, "required", required); + json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0)); json_object_object_add(function, "parameters", parameters); + + r_config_handle cfg = r_config_get_instance(); + if (r_config_use_strict(cfg)) { + json_object_object_add(function, "strict", json_object_new_boolean(1)); + } + json_object_object_add(root, "function", function); return root; } @@ -164,8 +184,32 @@ static struct json_object *automation_exploit_gen_get_description(void) { json_object_object_add(type, "description", json_object_new_string("Type of exploit: reverse, bind.")); json_object_object_add(properties, "type", type); + struct json_object *lhost = json_object_new_object(); + json_object_object_add(lhost, "type", json_object_new_string("string")); + json_object_object_add(lhost, "description", json_object_new_string("Local host for reverse shell.")); + json_object_object_add(properties, "lhost", lhost); + + struct json_object *lport = json_object_new_object(); + json_object_object_add(lport, "type", json_object_new_string("integer")); + json_object_object_add(lport, "description", json_object_new_string("Local port for reverse/bind shell.")); + json_object_object_add(properties, "lport", lport); + json_object_object_add(parameters, "properties", properties); + + struct json_object *required = json_object_new_array(); + json_object_array_add(required, json_object_new_string("type")); + json_object_array_add(required, json_object_new_string("lhost")); + json_object_array_add(required, json_object_new_string("lport")); + json_object_object_add(parameters, "required", required); + json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0)); + json_object_object_add(function, "parameters", parameters); + + r_config_handle cfg = r_config_get_instance(); + if (r_config_use_strict(cfg)) { + json_object_object_add(function, "strict", json_object_new_boolean(1)); + } + json_object_object_add(root, "function", function); return root; } \ No newline at end of file diff --git a/src/tools/tool_code.c b/src/tools/tool_code.c index 827fc8c..0e8587f 100644 --- a/src/tools/tool_code.c +++ b/src/tools/tool_code.c @@ -1,6 +1,7 @@ // retoor #include "tool.h" +#include "r_config.h" #include "bash_executor.h" #include #include @@ -54,7 +55,7 @@ static char *code_grep_execute(tool_t *self, struct json_object *args) { char command[4096]; snprintf(command, sizeof(command), "grep -rnH --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=build -E \"%s\" %s | head -n 100", pattern, path); - return r_bash_execute(command, false); + return r_bash_execute(command, false, 300); } static void code_symbol_find_print_action(const char *name, struct json_object *args) { @@ -80,7 +81,7 @@ static char *code_symbol_find_execute(tool_t *self, struct json_object *args) { "-E \"(function|class|def|struct|enum)[[:space:]]+%s[[:space:]]*[(:{]|^[[:space:]]*%s[[:space:]]*=[[:space:]]*[(]|%s[[:space:]]*\\(\" . | head -n 50", symbol, symbol, symbol); - return r_bash_execute(command, false); + return r_bash_execute(command, false, 300); } static struct json_object *code_grep_get_description(void) { @@ -103,8 +104,17 @@ static struct json_object *code_grep_get_description(void) { json_object_object_add(parameters, "properties", properties); struct json_object *required = json_object_new_array(); json_object_array_add(required, json_object_new_string("pattern")); + json_object_array_add(required, json_object_new_string("path")); json_object_object_add(parameters, "required", required); + json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0)); + json_object_object_add(function, "parameters", parameters); + + r_config_handle cfg = r_config_get_instance(); + if (r_config_use_strict(cfg)) { + json_object_object_add(function, "strict", json_object_new_boolean(1)); + } + json_object_object_add(root, "function", function); return root; } @@ -126,7 +136,15 @@ static struct json_object *code_symbol_find_get_description(void) { struct json_object *required = json_object_new_array(); json_object_array_add(required, json_object_new_string("symbol")); json_object_object_add(parameters, "required", required); + json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0)); + json_object_object_add(function, "parameters", parameters); + + r_config_handle cfg = r_config_get_instance(); + if (r_config_use_strict(cfg)) { + json_object_object_add(function, "strict", json_object_new_boolean(1)); + } + json_object_object_add(root, "function", function); return root; } diff --git a/src/tools/tool_csv.c b/src/tools/tool_csv.c index 3860f92..dc46e3e 100644 --- a/src/tools/tool_csv.c +++ b/src/tools/tool_csv.c @@ -1,6 +1,7 @@ // retoor #include "tool.h" +#include "r_config.h" #include #include #include @@ -70,10 +71,27 @@ static struct json_object *csv_export_get_description(void) { json_object_object_add(data, "type", json_object_new_string("array")); struct json_object *items = json_object_new_object(); json_object_object_add(items, "type", json_object_new_string("object")); + struct json_object *item_props = json_object_new_object(); + json_object_object_add(items, "properties", item_props); + json_object_object_add(items, "required", json_object_new_array()); // Allow empty objects or define later if known + json_object_object_add(items, "additionalProperties", json_object_new_boolean(0)); json_object_object_add(data, "items", items); json_object_object_add(properties, "data", data); json_object_object_add(parameters, "properties", properties); + + struct json_object *required = json_object_new_array(); + json_object_array_add(required, json_object_new_string("path")); + json_object_array_add(required, json_object_new_string("data")); + json_object_object_add(parameters, "required", required); + json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0)); + json_object_object_add(function, "parameters", parameters); + + r_config_handle cfg = r_config_get_instance(); + if (r_config_use_strict(cfg)) { + json_object_object_add(function, "strict", json_object_new_boolean(1)); + } + json_object_object_add(root, "function", function); return root; } \ No newline at end of file diff --git a/src/tools/tool_dns.c b/src/tools/tool_dns.c index 7686d96..1ea8f4f 100644 --- a/src/tools/tool_dns.c +++ b/src/tools/tool_dns.c @@ -1,6 +1,7 @@ // retoor #include "tool.h" +#include "r_config.h" #include #include #include @@ -471,11 +472,33 @@ static struct json_object *dns_lookup_get_description(void) { json_object_object_add(type, "description", json_object_new_string("Record type for 'lookup' (A, NS, MX, etc).")); json_object_object_add(properties, "type", type); + struct json_object *dns_server = json_object_new_object(); + json_object_object_add(dns_server, "type", json_object_new_string("string")); + json_object_object_add(dns_server, "description", json_object_new_string("DNS server to use.")); + json_object_object_add(properties, "dns_server", dns_server); + + struct json_object *enumerate = json_object_new_object(); + json_object_object_add(enumerate, "type", json_object_new_string("boolean")); + json_object_object_add(enumerate, "description", json_object_new_string("Whether to enumerate all record types.")); + json_object_object_add(properties, "enumerate", enumerate); + json_object_object_add(parameters, "properties", properties); struct json_object *required = json_object_new_array(); json_object_array_add(required, json_object_new_string("hostname")); + json_object_array_add(required, json_object_new_string("action")); + json_object_array_add(required, json_object_new_string("type")); + json_object_array_add(required, json_object_new_string("dns_server")); + json_object_array_add(required, json_object_new_string("enumerate")); json_object_object_add(parameters, "required", required); + json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0)); + json_object_object_add(function, "parameters", parameters); + + r_config_handle cfg = r_config_get_instance(); + if (r_config_use_strict(cfg)) { + json_object_object_add(function, "strict", json_object_new_boolean(1)); + } + json_object_object_add(root, "function", function); return root; } diff --git a/src/tools/tool_file.c b/src/tools/tool_file.c index d83e49f..9bba112 100755 --- a/src/tools/tool_file.c +++ b/src/tools/tool_file.c @@ -583,9 +583,16 @@ static struct json_object *getpwd_get_description(void) { struct json_object *parameters = json_object_new_object(); json_object_object_add(parameters, "type", json_object_new_string("object")); json_object_object_add(parameters, "properties", json_object_new_object()); + json_object_object_add(parameters, "required", json_object_new_array()); json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0)); json_object_object_add(function, "parameters", parameters); + + r_config_handle cfg = r_config_get_instance(); + if (r_config_use_strict(cfg)) { + json_object_object_add(function, "strict", json_object_new_boolean(1)); + } + json_object_object_add(root, "function", function); return root; } diff --git a/src/tools/tool_file_edit.c b/src/tools/tool_file_edit.c index 9fe51da..2fba46d 100644 --- a/src/tools/tool_file_edit.c +++ b/src/tools/tool_file_edit.c @@ -1,6 +1,7 @@ // retoor #include "tool.h" +#include "r_config.h" #include "r_diff.h" #include #include @@ -222,7 +223,14 @@ static struct json_object *file_line_replace_get_description(void) { json_object_array_add(required, json_object_new_string("end_line")); json_object_array_add(required, json_object_new_string("replacement")); json_object_object_add(parameters, "required", required); + json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0)); json_object_object_add(function, "parameters", parameters); + + r_config_handle cfg = r_config_get_instance(); + if (r_config_use_strict(cfg)) { + json_object_object_add(function, "strict", json_object_new_boolean(1)); + } + json_object_object_add(root, "function", function); return root; } @@ -250,7 +258,15 @@ static struct json_object *file_apply_patch_get_description(void) { json_object_array_add(required, json_object_new_string("path")); json_object_array_add(required, json_object_new_string("patch")); json_object_object_add(parameters, "required", required); + json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0)); + json_object_object_add(function, "parameters", parameters); + + r_config_handle cfg = r_config_get_instance(); + if (r_config_use_strict(cfg)) { + json_object_object_add(function, "strict", json_object_new_boolean(1)); + } + json_object_object_add(root, "function", function); return root; } diff --git a/src/tools/tool_http.c b/src/tools/tool_http.c index aa041a7..72cc1d0 100755 --- a/src/tools/tool_http.c +++ b/src/tools/tool_http.c @@ -127,7 +127,7 @@ static char *do_web_search(const char *query) { if (!q_encoded) return strdup("Failed to encode query."); char url[4096]; - snprintf(url, sizeof(url), "https://static.molodetz.nl/search.cgi?query=%s", q_encoded); + snprintf(url, sizeof(url), "https://rexa.molodetz.nl/ai?q=%s", q_encoded); curl_free(q_encoded); http_client_handle client = http_client_create(NULL); diff --git a/src/tools/tool_network.c b/src/tools/tool_network.c index e415a6b..19dba5d 100644 --- a/src/tools/tool_network.c +++ b/src/tools/tool_network.c @@ -1,6 +1,7 @@ // retoor #include "tool.h" +#include "r_config.h" #include #include #include @@ -191,8 +192,17 @@ static struct json_object *network_port_scan_get_description(void) { struct json_object *required = json_object_new_array(); json_object_array_add(required, json_object_new_string("host")); json_object_array_add(required, json_object_new_string("start_port")); + json_object_array_add(required, json_object_new_string("end_port")); json_object_object_add(parameters, "required", required); + json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0)); + json_object_object_add(function, "parameters", parameters); + + r_config_handle cfg = r_config_get_instance(); + if (r_config_use_strict(cfg)) { + json_object_object_add(function, "strict", json_object_new_boolean(1)); + } + json_object_object_add(root, "function", function); return root; } @@ -308,8 +318,17 @@ static struct json_object *network_check_get_description(void) { json_object_object_add(parameters, "properties", properties); struct json_object *required = json_object_new_array(); json_object_array_add(required, json_object_new_string("host")); + json_object_array_add(required, json_object_new_string("port")); json_object_object_add(parameters, "required", required); + json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0)); + json_object_object_add(function, "parameters", parameters); + + r_config_handle cfg = r_config_get_instance(); + if (r_config_use_strict(cfg)) { + json_object_object_add(function, "strict", json_object_new_boolean(1)); + } + json_object_object_add(root, "function", function); return root; } diff --git a/src/tools/tool_python.c b/src/tools/tool_python.c index 0631abe..63e7244 100755 --- a/src/tools/tool_python.c +++ b/src/tools/tool_python.c @@ -64,7 +64,7 @@ static char *python_execute_execute(tool_t *self, struct json_object *args) { char command[4096]; snprintf(command, sizeof(command), "python3 '%s'", tmp_file); - output = r_bash_execute(command, false); + output = r_bash_execute(command, false, 300); unlink(tmp_file); return output; @@ -98,6 +98,11 @@ static struct json_object *python_execute_get_description(void) { json_object_object_add(function, "parameters", parameters); + r_config_handle cfg = r_config_get_instance(); + if (r_config_use_strict(cfg)) { + json_object_object_add(function, "strict", json_object_new_boolean(1)); + } + json_object_object_add(root, "function", function); return root; } \ No newline at end of file diff --git a/src/tools/tool_system.c b/src/tools/tool_system.c index 6b7b35c..c44c663 100644 --- a/src/tools/tool_system.c +++ b/src/tools/tool_system.c @@ -1,6 +1,7 @@ // retoor #include "tool.h" +#include "r_config.h" #include "bash_executor.h" #include #include @@ -37,7 +38,7 @@ static char *process_monitor_execute(tool_t *self, struct json_object *args) { const char *action = json_object_get_string(action_obj); if (strcmp(action, "list") == 0) { - return r_bash_execute("ps aux | head -n 50", false); + return r_bash_execute("ps aux | head -n 50", false, 300); } else if (strcmp(action, "kill") == 0) { struct json_object *pid_obj; if (!json_object_object_get_ex(args, "pid", &pid_obj)) { @@ -45,7 +46,7 @@ static char *process_monitor_execute(tool_t *self, struct json_object *args) { } char cmd[256]; snprintf(cmd, sizeof(cmd), "kill -9 %d", json_object_get_int(pid_obj)); - return r_bash_execute(cmd, false); + return r_bash_execute(cmd, false, 300); } return strdup("Error: unknown action"); @@ -74,8 +75,17 @@ static struct json_object *process_monitor_get_description(void) { json_object_object_add(parameters, "properties", properties); struct json_object *required = json_object_new_array(); json_object_array_add(required, json_object_new_string("action")); + json_object_array_add(required, json_object_new_string("pid")); json_object_object_add(parameters, "required", required); + json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0)); + json_object_object_add(function, "parameters", parameters); + + r_config_handle cfg = r_config_get_instance(); + if (r_config_use_strict(cfg)) { + json_object_object_add(function, "strict", json_object_new_boolean(1)); + } + json_object_object_add(root, "function", function); return root; } diff --git a/src/tools/tool_terminal.c b/src/tools/tool_terminal.c index 49037b8..3716fa1 100755 --- a/src/tools/tool_terminal.c +++ b/src/tools/tool_terminal.c @@ -49,12 +49,17 @@ static void terminal_print_action(const char *name, struct json_object *args) { fprintf(stderr, " -> %s\n", name); return; } - struct json_object *cmd; + struct json_object *cmd, *timeout_obj; + int timeout = 300; + if (json_object_object_get_ex(args, "timeout", &timeout_obj)) { + timeout = json_object_get_int(timeout_obj); + } + if (json_object_object_get_ex(args, "command", &cmd)) { if (strcmp(name, "linux_terminal_execute_interactive") == 0) { - fprintf(stderr, " \033[1m-> Running interactive:\033[0m %s\n", json_object_get_string(cmd)); + fprintf(stderr, " \033[1m-> Running interactive (timeout %ds):\033[0m %s\n", timeout, json_object_get_string(cmd)); } else { - fprintf(stderr, " \033[1m-> Running command:\033[0m %s\n", json_object_get_string(cmd)); + fprintf(stderr, " \033[1m-> Running command (timeout %ds):\033[0m %s\n", timeout, json_object_get_string(cmd)); } } } @@ -62,25 +67,35 @@ static void terminal_print_action(const char *name, struct json_object *args) { static char *terminal_execute(tool_t *self, struct json_object *args) { (void)self; - struct json_object *cmd_obj; + struct json_object *cmd_obj, *timeout_obj; if (!json_object_object_get_ex(args, "command", &cmd_obj)) { return strdup("Error: missing 'command' argument"); } + int timeout = 300; + if (json_object_object_get_ex(args, "timeout", &timeout_obj)) { + timeout = json_object_get_int(timeout_obj); + } + const char *command = json_object_get_string(cmd_obj); - return r_bash_execute(command, false); + return r_bash_execute(command, false, timeout); } static char *terminal_interactive_execute(tool_t *self, struct json_object *args) { (void)self; - struct json_object *cmd_obj; + struct json_object *cmd_obj, *timeout_obj; if (!json_object_object_get_ex(args, "command", &cmd_obj)) { return strdup("Error: missing 'command' argument"); } + int timeout = 300; + if (json_object_object_get_ex(args, "timeout", &timeout_obj)) { + timeout = json_object_get_int(timeout_obj); + } + const char *command = json_object_get_string(cmd_obj); - return r_bash_execute(command, true); + return r_bash_execute(command, true, timeout); } static struct json_object *terminal_get_description(void) { @@ -91,7 +106,7 @@ static struct json_object *terminal_get_description(void) { json_object_object_add(function, "name", json_object_new_string("linux_terminal_execute")); json_object_object_add(function, "description", - json_object_new_string("Execute a linux_terminal command on user terminal.")); + json_object_new_string("Execute a linux_terminal command on user terminal with a timeout.")); struct json_object *parameters = json_object_new_object(); json_object_object_add(parameters, "type", json_object_new_string("object")); @@ -103,10 +118,18 @@ static struct json_object *terminal_get_description(void) { json_object_new_string("Bash command to execute.")); json_object_object_add(properties, "command", cmd); + struct json_object *timeout = json_object_new_object(); + json_object_object_add(timeout, "type", json_object_new_string("integer")); + json_object_object_add(timeout, "description", + json_object_new_string("Timeout in seconds (default 300).")); + json_object_object_add(timeout, "default", json_object_new_int(300)); + json_object_object_add(properties, "timeout", timeout); + json_object_object_add(parameters, "properties", properties); struct json_object *required = json_object_new_array(); json_object_array_add(required, json_object_new_string("command")); + json_object_array_add(required, json_object_new_string("timeout")); json_object_object_add(parameters, "required", required); json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0)); @@ -129,7 +152,7 @@ static struct json_object *terminal_interactive_get_description(void) { json_object_object_add(function, "name", json_object_new_string("linux_terminal_execute_interactive")); json_object_object_add(function, "description", - json_object_new_string("Executes interactive terminal for user. You will not be able to read the result. Do not use if you need to know output.")); + json_object_new_string("Executes interactive terminal for user with a timeout. You will not be able to read the result. Do not use if you need to know output.")); struct json_object *parameters = json_object_new_object(); json_object_object_add(parameters, "type", json_object_new_string("object")); @@ -141,10 +164,18 @@ static struct json_object *terminal_interactive_get_description(void) { json_object_new_string("Executable with parameters to execute interactively.")); json_object_object_add(properties, "command", cmd); + struct json_object *timeout = json_object_new_object(); + json_object_object_add(timeout, "type", json_object_new_string("integer")); + json_object_object_add(timeout, "description", + json_object_new_string("Timeout in seconds (default 300).")); + json_object_object_add(timeout, "default", json_object_new_int(300)); + json_object_object_add(properties, "timeout", timeout); + json_object_object_add(parameters, "properties", properties); struct json_object *required = json_object_new_array(); json_object_array_add(required, json_object_new_string("command")); + json_object_array_add(required, json_object_new_string("timeout")); json_object_object_add(parameters, "required", required); json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0)); diff --git a/src/tools/tools_init.c b/src/tools/tools_init.c index 5c01af5..5c059e0 100755 --- a/src/tools/tools_init.c +++ b/src/tools/tools_init.c @@ -30,6 +30,7 @@ extern tool_t *tool_network_port_scan_create(void); extern tool_t *tool_automation_fuzz_create(void); extern tool_t *tool_automation_exploit_gen_create(void); extern tool_t *tool_csv_export_create(void); +extern tool_t *tool_spawn_agent_create(void); static tool_registry_t *global_registry = NULL; @@ -68,10 +69,61 @@ tool_registry_t *tools_get_registry(void) { tool_registry_register(global_registry, tool_automation_fuzz_create()); tool_registry_register(global_registry, tool_automation_exploit_gen_create()); tool_registry_register(global_registry, tool_csv_export_create()); + tool_registry_register(global_registry, tool_spawn_agent_create()); return global_registry; } +tool_registry_t *tool_registry_get_specialized(tool_registry_type_t type) { + tool_registry_t *reg = tool_registry_create(); + if (!reg) return NULL; + + if (type == TOOL_TYPE_RESEARCHER) { + tool_registry_register(reg, tool_web_search_create()); + tool_registry_register(reg, tool_web_search_news_create()); + tool_registry_register(reg, tool_http_fetch_create()); + tool_registry_register(reg, tool_read_file_create()); + tool_registry_register(reg, tool_db_get_create()); + tool_registry_register(reg, tool_db_set_create()); + tool_registry_register(reg, tool_db_query_create()); + tool_registry_register(reg, tool_directory_glob_create()); + tool_registry_register(reg, tool_csv_export_create()); + tool_registry_register(reg, tool_spawn_agent_create()); + } else if (type == TOOL_TYPE_DEVELOPER) { + tool_registry_register(reg, tool_terminal_create()); + tool_registry_register(reg, tool_read_file_create()); + tool_registry_register(reg, tool_write_file_create()); + tool_registry_register(reg, tool_directory_glob_create()); + tool_registry_register(reg, tool_mkdir_create()); + tool_registry_register(reg, tool_chdir_create()); + tool_registry_register(reg, tool_getpwd_create()); + tool_registry_register(reg, tool_python_execute_create()); + tool_registry_register(reg, tool_code_grep_create()); + tool_registry_register(reg, tool_code_symbol_find_create()); + tool_registry_register(reg, tool_file_line_replace_create()); + tool_registry_register(reg, tool_file_apply_patch_create()); + tool_registry_register(reg, tool_spawn_agent_create()); + } else if (type == TOOL_TYPE_SECURITY) { + tool_registry_register(reg, tool_terminal_create()); + tool_registry_register(reg, tool_network_check_create()); + tool_registry_register(reg, tool_dns_lookup_create()); + tool_registry_register(reg, tool_network_port_scan_create()); + tool_registry_register(reg, tool_automation_fuzz_create()); + tool_registry_register(reg, tool_automation_exploit_gen_create()); + tool_registry_register(reg, tool_web_search_create()); + tool_registry_register(reg, tool_http_fetch_create()); + tool_registry_register(reg, tool_process_monitor_create()); + tool_registry_register(reg, tool_spawn_agent_create()); + } else { + // Fallback or TOOL_TYPE_ALL + tool_registry_register(reg, tool_terminal_create()); + tool_registry_register(reg, tool_read_file_create()); + tool_registry_register(reg, tool_spawn_agent_create()); + } + + return reg; +} + void tools_registry_shutdown(void) { if (global_registry) { tool_registry_destroy(global_registry);