This commit is contained in:
retoor 2026-01-29 06:01:05 +01:00
parent c343e17f19
commit 77885e73a9
31 changed files with 922 additions and 260 deletions

View File

@ -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)

195
agent_benchmark.py Executable file
View File

@ -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 <retoor@molodetz.nl>'. 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()

View File

@ -5,6 +5,7 @@
#include "messages.h"
#include "r_error.h"
#include "tool.h"
#include <stdbool.h>
#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);

View File

@ -4,6 +4,6 @@
#include <stdbool.h>
char *r_bash_execute(const char *command, bool interactive);
char *r_bash_execute(const char *command, bool interactive, int timeout_seconds);
#endif

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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);
}

View File

@ -8,12 +8,24 @@
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <poll.h>
#include <signal.h>
#include <errno.h>
#include <time.h>
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;
}
}

View File

@ -5,20 +5,40 @@
#include <stdlib.h>
#include <string.h>
#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;
}

View File

@ -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");

View File

@ -1,10 +1,9 @@
#include "config.h"
// retoor <retoor@molodetz.nl>
#include "util/path.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}

View File

@ -1,28 +1,21 @@
// retoor <retoor@molodetz.nl>
#ifndef R_INTERFACES_HTTP_H
#define R_INTERFACES_HTTP_H
#include "r_error.h"
#include <stdbool.h>
#include <stddef.h>
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

View File

@ -1,16 +1,13 @@
// retoor <retoor@molodetz.nl>
#include "logger.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
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);
}

View File

@ -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 "

View File

@ -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);

View File

@ -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;
}

View File

@ -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]);

View File

@ -1,5 +1,4 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "agent.h"
#include "messages.h"
@ -8,139 +7,177 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
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;
}

View File

@ -1,6 +1,7 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "r_config.h"
#include "bash_executor.h"
#include <stdio.h>
#include <stdlib.h>
@ -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;
}

View File

@ -1,6 +1,7 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "r_config.h"
#include "bash_executor.h"
#include <stdio.h>
#include <stdlib.h>
@ -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;
}

View File

@ -1,6 +1,7 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "r_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -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;
}

View File

@ -1,6 +1,7 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "r_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -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;
}

View File

@ -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;
}

View File

@ -1,6 +1,7 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "r_config.h"
#include "r_diff.h"
#include <stdio.h>
#include <stdlib.h>
@ -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;
}

View File

@ -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);

View File

@ -1,6 +1,7 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "r_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -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;
}

View File

@ -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;
}

View File

@ -1,6 +1,7 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "r_config.h"
#include "bash_executor.h"
#include <stdio.h>
#include <stdlib.h>
@ -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;
}

View File

@ -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));

View File

@ -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);