fix: add null check for avatar pointer before dereference in user profile loader

This commit is contained in:
2026-02-10 03:29:48 +00:00
parent 6b64e96abe
commit 6574db64e1
56 changed files with 13679 additions and 1844 deletions
+89 -97
View File
@@ -1,18 +1,24 @@
// retoor <retoor@molodetz.nl>
#include "agent.h"
#include "http_client.h"
#include "db.h"
#include "r_config.h"
#include "tool.h"
#include "context_manager.h"
#include "markdown.h"
#include <json-c/json.h>
#include <sqlite3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
struct agent_t {
char *agent_id;
char *role;
char *manager_id;
char *department;
long budget_limit;
long used_tokens;
char *goal;
int iteration_count;
int max_iterations;
@@ -28,7 +34,6 @@ struct agent_t {
http_client_handle http;
tool_registry_t *tools;
};
static const char *incomplete_phrases[] = {
"I'll ", "I will ", "Let me ", "I'm going to ",
"Next, I", "Now I'll", "Now I will", "I'll now",
@@ -40,12 +45,10 @@ static const char *incomplete_phrases[] = {
"Do you want", "Shall I",
NULL
};
static const char *incomplete_endings[] = {
"...", ":", "files:", "content:", "implementation:", "?",
NULL
};
static const char *completion_phrases[] = {
"task is complete", "task complete", "tasks complete",
"goal is achieved", "goal achieved",
@@ -56,31 +59,49 @@ static const char *completion_phrases[] = {
"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
};
extern tool_registry_t *tools_get_registry(void);
static void agent_update_heartbeat(agent_handle agent) {
if (!agent || !agent->agent_id) return;
db_handle db = db_open(NULL);
char *sql = sqlite3_mprintf("UPDATE agents SET last_heartbeat = CURRENT_TIMESTAMP WHERE agent_id = %Q", agent->agent_id);
struct json_object *res = NULL;
db_execute(db, sql, &res);
sqlite3_free(sql);
if (res) json_object_put(res);
db_close(db);
}
static bool agent_check_budget(agent_handle agent) {
if (!agent || agent->budget_limit <= 0) return true;
return agent->used_tokens < agent->budget_limit;
}
static void agent_add_tokens(agent_handle agent, long tokens) {
if (!agent || !agent->agent_id) return;
agent->used_tokens += tokens;
db_handle db = db_open(NULL);
char *sql = sqlite3_mprintf("UPDATE agents SET used_tokens = used_tokens + %ld WHERE agent_id = %Q", tokens, agent->agent_id);
struct json_object *res = NULL;
db_execute(db, sql, &res);
sqlite3_free(sql);
if (res) json_object_put(res);
db_close(db);
}
static void agent_set_error(agent_handle agent, const char *error) {
if (!agent) return;
free(agent->last_error);
agent->last_error = error ? strdup(error) : NULL;
}
static char *agent_build_request(agent_handle agent, const char *role, const char *message) {
r_config_handle cfg = r_config_get_instance();
struct json_object *root = json_object_new_object();
if (!root) return NULL;
json_object_object_add(root, "model",
json_object_new_string(r_config_get_model(cfg)));
if (role && message) {
messages_add(agent->messages, role, message);
}
@@ -89,14 +110,12 @@ static char *agent_build_request(agent_handle agent, const char *role, const cha
json_object_object_add(root, "tools",
tool_registry_get_descriptions(agent->tools));
}
json_object_object_add(root, "messages",
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));
if (agent->verbose && !agent->is_subagent) {
fprintf(stderr, "\n[LLM Request]\n%s\n", result);
@@ -104,26 +123,26 @@ static char *agent_build_request(agent_handle agent, const char *role, const cha
json_object_put(root);
return result;
}
static struct json_object *agent_process_response(agent_handle agent, const char *json_data) {
r_config_handle cfg = r_config_get_instance();
char *response = NULL;
r_status_t status = http_post(agent->http, r_config_get_api_url(cfg), json_data, &response);
agent_update_heartbeat(agent);
if (status != R_SUCCESS || !response) {
return NULL;
}
if (agent->verbose && !agent->is_subagent) {
fprintf(stderr, "\n[LLM Response]\n%s\n", response);
}
struct json_object *parsed = json_tokener_parse(response);
// Track tokens
struct json_object *usage;
if (parsed && json_object_object_get_ex(parsed, "usage", &usage)) {
struct json_object *total_tokens;
if (json_object_object_get_ex(usage, "total_tokens", &total_tokens)) {
agent_add_tokens(agent, json_object_get_int64(total_tokens));
}
}
free(response);
if (!parsed) return NULL;
struct json_object *error_obj;
if (json_object_object_get_ex(parsed, "error", &error_obj)) {
const char *err_str = json_object_to_json_string(error_obj);
@@ -143,42 +162,32 @@ static struct json_object *agent_process_response(agent_handle agent, const char
json_object_put(parsed);
return NULL;
}
struct json_object *choices;
if (!json_object_object_get_ex(parsed, "choices", &choices)) {
json_object_put(parsed);
return NULL;
}
struct json_object *first_choice = json_object_array_get_idx(choices, 0);
if (!first_choice) {
json_object_put(parsed);
return NULL;
}
return first_choice;
}
static bool agent_has_tool_calls(struct json_object *choice) {
struct json_object *message_obj;
if (!json_object_object_get_ex(choice, "message", &message_obj)) return false;
struct json_object *tool_calls;
if (!json_object_object_get_ex(message_obj, "tool_calls", &tool_calls)) return false;
return json_object_array_length(tool_calls) > 0;
}
static struct json_object *agent_get_tool_calls(struct json_object *choice) {
struct json_object *message_obj;
if (!json_object_object_get_ex(choice, "message", &message_obj)) return NULL;
struct json_object *tool_calls;
if (!json_object_object_get_ex(message_obj, "tool_calls", &tool_calls)) return NULL;
return tool_calls;
}
static struct json_object *agent_get_message(struct json_object *choice) {
struct json_object *message_obj;
if (json_object_object_get_ex(choice, "message", &message_obj)) {
@@ -186,35 +195,27 @@ static struct json_object *agent_get_message(struct json_object *choice) {
}
return NULL;
}
static char *agent_get_content(struct json_object *choice) {
struct json_object *message_obj;
if (!json_object_object_get_ex(choice, "message", &message_obj)) return NULL;
struct json_object *content_obj;
if (!json_object_object_get_ex(message_obj, "content", &content_obj)) return NULL;
const char *content = json_object_get_string(content_obj);
return content ? strdup(content) : NULL;
}
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)
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 (strcasestr(content, incomplete_phrases[i])) return true;
}
size_t len = strlen(content);
if (len > 3) {
for (int i = 0; incomplete_endings[i]; i++) {
@@ -224,14 +225,11 @@ static bool agent_response_indicates_incomplete(const char *content) {
}
}
}
return false;
}
agent_handle agent_create(const char *goal, messages_handle messages) {
struct agent_t *agent = calloc(1, sizeof(struct agent_t));
if (!agent) return NULL;
if (goal) {
agent->goal = strdup(goal);
if (!agent->goal) {
@@ -239,9 +237,7 @@ agent_handle agent_create(const char *goal, messages_handle messages) {
return NULL;
}
}
r_config_handle cfg = r_config_get_instance();
agent->iteration_count = 0;
agent->max_iterations = AGENT_MAX_ITERATIONS;
agent->tool_retry_count = 0;
@@ -249,7 +245,17 @@ agent_handle agent_create(const char *goal, messages_handle messages) {
agent->state = AGENT_STATE_IDLE;
agent->start_time = time(NULL);
agent->verbose = r_config_is_verbose(cfg);
agent->agent_id = strdup("Executive-Apex");
agent->role = strdup("Executive");
agent->budget_limit = 1000000;
db_handle db = db_open(NULL);
char *sql = sqlite3_mprintf("INSERT OR IGNORE INTO agents (agent_id, role, budget_limit_tokens) VALUES (%Q, %Q, %ld)",
agent->agent_id, agent->role, agent->budget_limit);
struct json_object *res = NULL;
db_execute(db, sql, &res);
sqlite3_free(sql);
if (res) json_object_put(res);
db_close(db);
if (messages) {
agent->messages = messages;
agent->owns_messages = false;
@@ -257,13 +263,11 @@ agent_handle agent_create(const char *goal, messages_handle messages) {
agent->messages = messages_create(r_config_get_session_id(cfg));
agent->owns_messages = true;
}
if (!agent->messages) {
free(agent->goal);
free(agent);
return NULL;
}
const char *system_msg = r_config_get_system_message(cfg);
if (system_msg && *system_msg) {
bool has_system = false;
@@ -282,7 +286,6 @@ agent_handle agent_create(const char *goal, messages_handle messages) {
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) {
@@ -292,77 +295,93 @@ agent_handle agent_create(const char *goal, messages_handle messages) {
free(agent);
return NULL;
}
agent->tools = tools_get_registry();
return agent;
}
void agent_destroy(agent_handle agent) {
if (!agent) return;
if (agent->http) http_client_destroy(agent->http);
if (agent->messages && agent->owns_messages) messages_destroy(agent->messages);
free(agent->agent_id);
free(agent->role);
free(agent->manager_id);
free(agent->department);
free(agent->goal);
free(agent->last_error);
free(agent);
}
void agent_set_max_iterations(agent_handle agent, int max) {
if (agent) agent->max_iterations = max;
}
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;
}
agent_state_t agent_get_state(agent_handle agent) {
return agent ? agent->state : AGENT_STATE_ERROR;
}
const char *agent_get_error(agent_handle agent) {
return agent ? agent->last_error : NULL;
}
int agent_get_iteration_count(agent_handle agent) {
return agent ? agent->iteration_count : 0;
}
void agent_set_id(agent_handle agent, const char *id) {
if (!agent) return;
free(agent->agent_id);
agent->agent_id = id ? strdup(id) : NULL;
}
void agent_set_role(agent_handle agent, const char *role) {
if (!agent) return;
free(agent->role);
agent->role = role ? strdup(role) : NULL;
}
void agent_set_manager_id(agent_handle agent, const char *manager_id) {
if (!agent) return;
free(agent->manager_id);
agent->manager_id = manager_id ? strdup(manager_id) : NULL;
}
const char *agent_get_id(agent_handle agent) {
return agent ? agent->agent_id : NULL;
}
const char *agent_get_role(agent_handle agent) {
return agent ? agent->role : NULL;
}
const char *agent_get_manager_id(agent_handle agent) {
return agent ? agent->manager_id : NULL;
}
char *agent_run(agent_handle agent, const char *user_message) {
if (!agent) return NULL;
agent->state = AGENT_STATE_RUNNING;
agent->iteration_count = 0;
agent->tool_retry_count = 0;
if (!user_message || !*user_message) {
agent->state = AGENT_STATE_ERROR;
agent_set_error(agent, "Empty user message");
return NULL;
}
messages_load(agent->messages);
char *json_data = agent_build_request(agent, "user", user_message);
if (!json_data) {
agent->state = AGENT_STATE_ERROR;
agent_set_error(agent, "Failed to create chat JSON");
return NULL;
}
char *accumulated_response = NULL;
size_t accumulated_len = 0;
while (agent->state == AGENT_STATE_RUNNING || agent->state == AGENT_STATE_EXECUTING_TOOLS) {
agent->iteration_count++;
if (!agent_check_budget(agent)) {
agent->state = AGENT_STATE_ERROR;
agent_set_error(agent, "QUARTERLY_BUDGET_EXCEEDED");
if (agent->verbose) fprintf(stderr, "\033[1;31m[Middleware] Process killed: Token budget exceeded.\033[0m\n");
break;
}
if (agent->iteration_count > agent->max_iterations) {
agent->state = AGENT_STATE_MAX_ITERATIONS;
agent_set_error(agent, "Maximum iterations reached");
@@ -372,12 +391,10 @@ char *agent_run(agent_handle agent, const char *user_message) {
free(json_data);
break;
}
if (agent->verbose && !agent->is_subagent) {
fprintf(stderr, "[Agent] Iteration %d/%d\n",
agent->iteration_count, agent->max_iterations);
}
struct json_object *choice = agent_process_response(agent, json_data);
if (!choice && agent->last_error && strcmp(agent->last_error, "CONTEXT_OVERFLOW") == 0) {
@@ -394,10 +411,8 @@ char *agent_run(agent_handle agent, const char *user_message) {
break;
}
}
free(json_data);
json_data = NULL;
if (!choice) {
agent->tool_retry_count++;
if (agent->tool_retry_count >= agent->max_tool_retries) {
@@ -413,21 +428,17 @@ char *agent_run(agent_handle agent, const char *user_message) {
agent->state = AGENT_STATE_RUNNING;
continue;
}
agent->tool_retry_count = 0;
struct json_object *message_obj = agent_get_message(choice);
if (message_obj) {
messages_add_object(agent->messages, json_object_get(message_obj));
}
char *content = agent_get_content(choice);
if (content && *content) {
if (!agent->is_subagent) {
parse_markdown_to_ansi(content);
printf("\n");
}
size_t content_len = strlen(content);
char *new_acc = realloc(accumulated_response, accumulated_len + content_len + 2);
if (new_acc) {
@@ -440,31 +451,23 @@ char *agent_run(agent_handle agent, const char *user_message) {
accumulated_len += content_len;
}
}
bool has_tools = agent_has_tool_calls(choice);
if (agent->verbose && !agent->is_subagent) {
fprintf(stderr, "[Agent] has_tool_calls=%s\n", has_tools ? "true" : "false");
}
if (has_tools) {
agent->state = AGENT_STATE_EXECUTING_TOOLS;
struct json_object *tool_calls = agent_get_tool_calls(choice);
if (agent->verbose && !agent->is_subagent) {
int num_tools = json_object_array_length(tool_calls);
fprintf(stderr, "[Agent] Executing %d tool(s)\n", num_tools);
}
struct json_object *results = tool_registry_execute(agent->tools, tool_calls, agent->verbose);
int count = json_object_array_length(results);
for (int i = 0; i < count; i++) {
struct json_object *result = json_object_array_get_idx(results, i);
messages_add_tool_call(agent->messages, json_object_get(result));
}
agent->state = AGENT_STATE_RUNNING;
json_data = agent_build_request(agent, NULL, NULL);
if (!json_data) {
@@ -473,12 +476,10 @@ char *agent_run(agent_handle agent, const char *user_message) {
free(content);
break;
}
} else if (content && agent_response_indicates_incomplete(content)) {
if (agent->verbose && !agent->is_subagent) {
fprintf(stderr, "[Agent] Response indicates incomplete work, auto-continuing\n");
}
json_data = agent_build_request(agent, "user",
"Continue. Execute the necessary actions to complete the task.");
agent->state = AGENT_STATE_RUNNING;
@@ -497,36 +498,27 @@ char *agent_run(agent_handle agent, const char *user_message) {
}
free(content);
}
free(json_data);
return accumulated_response;
}
char *agent_chat(const char *user_message, messages_handle messages) {
agent_handle agent = agent_create(user_message, messages);
if (!agent) return NULL;
char *response = agent_run(agent, user_message);
if (agent->verbose && agent->state != AGENT_STATE_COMPLETED && agent->last_error) {
if (!agent->is_subagent) fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
}
agent_destroy(agent);
return response;
}
char *agent_chat_with_limit(const char *user_message, int max_iterations, messages_handle messages) {
agent_handle agent = agent_create(user_message, messages);
if (!agent) return NULL;
agent_set_max_iterations(agent, max_iterations);
char *response = agent_run(agent, user_message);
if (agent->verbose && agent->state != AGENT_STATE_COMPLETED && agent->last_error) {
if (!agent->is_subagent) fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
}
agent_destroy(agent);
return response;
}
}
-24
View File
@@ -1,5 +1,4 @@
// retoor <retoor@molodetz.nl>
#define _GNU_SOURCE
#include "bash_executor.h"
#include <stdio.h>
@@ -14,47 +13,37 @@
#include <signal.h>
#include <errno.h>
#include <time.h>
#define DEFAULT_TIMEOUT 300
void r_process_result_free(r_process_result_t *res) {
if (!res) return;
free(res->output);
free(res->log_path);
free(res);
}
static char *get_log_path(int pid) {
char *path = NULL;
if (asprintf(&path, "/tmp/r_process_%d.log", pid) == -1) return NULL;
return path;
}
r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds, bool async) {
if (!command) return NULL;
r_process_result_t *res = calloc(1, sizeof(r_process_result_t));
if (!res) return NULL;
if (timeout_seconds <= 0) timeout_seconds = DEFAULT_TIMEOUT;
char tmp_script[] = "/tmp/r_bash_XXXXXX.sh";
int script_fd = mkstemps(tmp_script, 3);
if (script_fd == -1) {
res->output = strdup("Error: failed to create temp script");
return res;
}
dprintf(script_fd, "%s\n", command);
close(script_fd);
int pipe_fds[2];
if (pipe(pipe_fds) == -1) {
unlink(tmp_script);
res->output = strdup("Error: pipe failed");
return res;
}
pid_t pid = fork();
if (pid == -1) {
close(pipe_fds[0]);
@@ -63,7 +52,6 @@ r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds,
res->output = strdup("Error: fork failed");
return res;
}
if (pid == 0) {
// Child
setsid(); // New session to prevent signals to parent
@@ -73,7 +61,6 @@ r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds,
char *log_p = get_log_path(getpid());
int log_fd = open(log_p, O_WRONLY | O_CREAT | O_TRUNC, 0644);
free(log_p);
if (log_fd != -1) {
dup2(log_fd, STDOUT_FILENO);
dup2(log_fd, STDERR_FILENO);
@@ -87,30 +74,25 @@ r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds,
// Actually, let's just use log file for everything.
close(pipe_fds[1]);
char *args[] = {"bash", tmp_script, NULL};
execvp("bash", args);
exit(1);
}
// Parent
res->pid = pid;
res->log_path = get_log_path(pid);
res->is_running = true;
close(pipe_fds[1]);
close(pipe_fds[0]);
if (async) {
res->output = strdup("Process started in background.");
usleep(100000); // Give child time to start
unlink(tmp_script);
return res;
}
// Wait for timeout
time_t start_time = time(NULL);
long last_read_pos = 0;
while (true) {
int status;
pid_t ret = waitpid(pid, &status, WNOHANG);
@@ -127,7 +109,6 @@ r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds,
last_read_pos = ftell(f_tail);
fclose(f_tail);
}
if (ret == pid) {
res->is_running = false;
res->exit_status = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
@@ -136,14 +117,12 @@ r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds,
res->is_running = false;
break;
}
if (time(NULL) - start_time >= timeout_seconds) {
res->timed_out = true;
break;
}
usleep(50000); // 100ms -> 50ms for better responsiveness
}
// Read log file for output
FILE *log_f = fopen(res->log_path, "r");
if (log_f) {
@@ -159,13 +138,10 @@ r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds,
}
fclose(log_f);
}
if (!res->output) res->output = strdup("");
unlink(tmp_script);
return res;
}
char *r_bash_execute(const char *command, bool interactive, int timeout_seconds) {
// Legacy support wrapper
r_process_result_t *res = r_bash_execute_ext(command, timeout_seconds, false);
-25
View File
@@ -1,12 +1,10 @@
// retoor <retoor@molodetz.nl>
#include "bash_repair.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h>
static char *ensure_shebang(const char *text) {
if (!text || !*text) return strdup("");
@@ -16,7 +14,6 @@ static char *ensure_shebang(const char *text) {
if (strncmp(p, "#!", 2) == 0 || strncmp(p, ": <<", 4) == 0) {
return strdup(text);
}
// Heuristic: if it has multiple lines, add shebang
if (strchr(text, '\n')) {
char *result = malloc(strlen(text) + 32);
@@ -25,25 +22,20 @@ static char *ensure_shebang(const char *text) {
strcat(result, text);
return result;
}
return strdup(text);
}
static char *normalize_whitespace_and_operators(const char *src) {
if (!src) return NULL;
size_t src_len = strlen(src);
char *result = malloc(src_len * 2 + 1);
if (!result) return NULL;
char *dst = result;
const char *curr = src;
while (*curr) {
if (*curr == '\r') {
curr++;
continue;
}
// Detect operators to normalize spaces around them
const char *ops[] = {"||", "&&", ">>", "|&", "|", ";", ">", "<", NULL};
bool matched_op = false;
@@ -66,18 +58,15 @@ static char *normalize_whitespace_and_operators(const char *src) {
break;
}
}
if (!matched_op) {
*dst++ = *curr++;
}
}
*dst = '\0';
// Second pass to strip trailing spaces on each line
char *s2 = strdup(result);
free(result);
if (!s2) return NULL;
char *final = malloc(strlen(s2) + 1);
char *f_ptr = final;
char *line = s2;
@@ -102,25 +91,20 @@ static char *normalize_whitespace_and_operators(const char *src) {
free(s2);
return final;
}
static char *fix_line_issues(const char *src) {
if (!src) return NULL;
size_t src_len = strlen(src);
char *result = malloc(src_len * 2 + 1024);
if (!result) return NULL;
char *dst = result;
const char *line = src;
while (line && *line) {
const char *next_line = strchr(line, '\n');
size_t line_len = next_line ? (size_t)(next_line - line) : strlen(line);
char line_buf[4096];
if (line_len >= sizeof(line_buf)) line_len = sizeof(line_buf) - 1;
memcpy(line_buf, line, line_len);
line_buf[line_len] = '\0';
// 1. Tiny Shell Lint (else if -> elif)
char *else_if = strstr(line_buf, "else if ");
if (else_if) {
@@ -128,7 +112,6 @@ static char *fix_line_issues(const char *src) {
memmove(else_if + 4, else_if + 8, strlen(else_if + 8) + 1);
memcpy(else_if, "elif", 4);
}
// 2. Fix unbalanced quotes
int single = 0, double_q = 0;
bool escaped = false;
@@ -140,7 +123,6 @@ static char *fix_line_issues(const char *src) {
}
if (single % 2 == 1 && double_q == 0) strcat(line_buf, "'");
else if (double_q % 2 == 1 && single == 0) strcat(line_buf, "\"");
// 3. Fix trailing operators
size_t cur_len = strlen(line_buf);
const char *ops[] = {"||", "&&", ">>", "|&", "|", ">", "<", NULL};
@@ -159,24 +141,20 @@ static char *fix_line_issues(const char *src) {
}
}
}
// 4. Dangerous rm -rf check
if (strstr(line_buf, "sudo rm -rf /") || strstr(line_buf, "rm -rf / ")) {
strcpy(dst, "# WARNING: potentially destructive command detected\n");
dst += strlen(dst);
}
strcpy(dst, line_buf);
dst += strlen(dst);
if (next_line) *dst++ = '\n';
if (next_line) line = next_line + 1;
else break;
}
*dst = '\0';
return result;
}
static char *collapse_nested_bash_c(const char *src) {
if (!src) return NULL;
// Pattern: bash -c "bash -c '...'")
@@ -222,10 +200,8 @@ static char *collapse_nested_bash_c(const char *src) {
}
return s1;
}
char *bash_repair_command(const char *src) {
if (!src) return NULL;
char *s1 = normalize_whitespace_and_operators(src);
char *s2 = fix_line_issues(s1);
free(s1);
@@ -233,6 +209,5 @@ char *bash_repair_command(const char *src) {
free(s2);
char *s4 = ensure_shebang(s3);
free(s3);
return s4;
}
-28
View File
@@ -1,13 +1,10 @@
// retoor <retoor@molodetz.nl>
#include "context_manager.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MIN_KEEP_CHARS 500
#define TRUNCATE_MARKER "\n\n[... content truncated for context management ...]\n\n"
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)) {
@@ -20,7 +17,6 @@ static const char *get_message_role(struct json_object *msg) {
}
return "";
}
static bool has_tool_calls(struct json_object *msg) {
struct json_object *tool_calls;
if (json_object_object_get_ex(msg, "tool_calls", &tool_calls)) {
@@ -28,20 +24,16 @@ static bool has_tool_calls(struct json_object *msg) {
}
return false;
}
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);
@@ -50,63 +42,49 @@ static size_t calculate_total_size(messages_handle msgs) {
}
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;
struct json_object *content_obj;
const char *content = NULL;
bool is_tool_result = false;
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);
is_tool_result = true;
}
if (!content) return R_SUCCESS;
size_t len = strlen(content);
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;
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, keep_each);
new_content[keep_each] = '\0';
strcat(new_content, TRUNCATE_MARKER);
strcat(new_content, content + len - keep_each);
struct json_object *new_msg = json_tokener_parse(json_object_to_json_string(msg));
if (is_tool_result) {
json_object_object_add(new_msg, "tool_result", json_object_new_string(new_content));
} else {
json_object_object_add(new_msg, "content", json_object_new_string(new_content));
}
free(new_content);
return messages_replace_at(msgs, index, new_msg);
}
r_status_t context_manager_shrink(messages_handle msgs) {
if (!msgs) return R_ERROR_INVALID_ARG;
int count = messages_count(msgs);
if (count <= 2) return R_ERROR_API_ERROR;
size_t initial_size = calculate_total_size(msgs);
size_t target_size = (size_t)(initial_size * 0.5);
if (target_size < 20000) target_size = 20000;
fprintf(stderr, " \033[2m-> Context overflow (%zu chars). Middle-out shrinking to %zu...\033[0m\n",
initial_size, target_size);
// Strategy 1: Truncate very large messages first (safe, doesn't break sequence)
for (int i = 0; i < messages_count(msgs); i++) {
struct json_object *msg = messages_get_object(msgs, i);
@@ -114,7 +92,6 @@ r_status_t context_manager_shrink(messages_handle msgs) {
perform_truncate(msgs, i, 0.2);
}
}
// Strategy 2: Remove messages from the middle until size is within target
// We keep:
// - System message (usually index 0)
@@ -123,7 +100,6 @@ r_status_t context_manager_shrink(messages_handle msgs) {
int middle_idx = 1; // Start after system
struct json_object *msg = messages_get_object(msgs, middle_idx);
const char *role = get_message_role(msg);
int remove_count = 1;
if (strcmp(role, "assistant") == 0 && has_tool_calls(msg)) {
// Must also remove the following tool results to maintain sequence
@@ -138,18 +114,14 @@ r_status_t context_manager_shrink(messages_handle msgs) {
}
}
}
// Ensure we don't eat into the "recent" buffer
if (middle_idx + remove_count > messages_count(msgs) - 4) {
break;
}
messages_remove_range(msgs, middle_idx, remove_count);
}
size_t final_size = calculate_total_size(msgs);
fprintf(stderr, " \033[2m-> Context shrunk to %zu chars. Remaining messages: %d\033[0m\n",
final_size, messages_count(msgs));
return R_SUCCESS;
}
+1 -6
View File
@@ -1,8 +1,7 @@
#include "context_summarizer.h"
// retoor <retoor@molodetz.nl>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
// Placeholder for LLM API call
// In a real implementation, this function would call the LLM API to get the summary.
static char* call_llm_to_summarize(const char* messages_concatenated) {
@@ -14,7 +13,6 @@ static char* call_llm_to_summarize(const char* messages_concatenated) {
}
return result;
}
char* summarize_oldest_messages(const char** messages, size_t message_count) {
// Concatenate the oldest 20 messages
size_t total_length = 0;
@@ -25,20 +23,17 @@ char* summarize_oldest_messages(const char** messages, size_t message_count) {
for (size_t i = start_index; i < message_count; ++i) {
total_length += strlen(messages[i]) + 1; // +1 for separator
}
char* concatenated = malloc(total_length + 1);
if (!concatenated) {
return NULL;
}
concatenated[0] = '\0';
for (size_t i = start_index; i < message_count; ++i) {
strcat(concatenated, messages[i]);
if (i < message_count - 1) {
strcat(concatenated, " "); // separator
}
}
// Call the LLM API to get the summary
char* summary = call_llm_to_summarize(concatenated);
free(concatenated);
+63 -60
View File
@@ -1,5 +1,4 @@
// retoor <retoor@molodetz.nl>
#include "db.h"
#include "r_config.h"
#include <sqlite3.h>
@@ -7,65 +6,52 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
struct db_t {
sqlite3 *conn;
char *path;
};
static char *expand_home_directory(const char *path) {
if (!path) return NULL;
if (path[0] != '~') return strdup(path);
const char *home_dir = getenv("HOME");
if (!home_dir) home_dir = getenv("USERPROFILE");
if (!home_dir) return strdup(path);
size_t home_len = strlen(home_dir);
size_t path_len = strlen(path);
char *expanded = malloc(home_len + path_len);
if (!expanded) return NULL;
strcpy(expanded, home_dir);
strcat(expanded, path + 1);
return expanded;
}
db_handle db_open(const char *path) {
struct db_t *db = calloc(1, sizeof(struct db_t));
if (!db) return NULL;
if (!path) {
r_config_handle cfg = r_config_get_instance();
path = r_config_get_db_path(cfg);
}
db->path = expand_home_directory(path);
if (!db->path) {
free(db);
return NULL;
}
if (sqlite3_open(db->path, &db->conn) != SQLITE_OK) {
free(db->path);
free(db);
return NULL;
}
db_init(db);
return db;
}
void db_close(db_handle db) {
if (!db) return;
if (db->conn) sqlite3_close(db->conn);
free(db->path);
free(db);
}
r_status_t db_init(db_handle db) {
if (!db || !db->conn) return R_ERROR_INVALID_ARG;
const char *sql =
"CREATE TABLE IF NOT EXISTS kv ("
" key TEXT PRIMARY KEY,"
@@ -93,8 +79,45 @@ r_status_t db_init(db_handle db) {
" status TEXT DEFAULT 'pending',"
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
" updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
");"
"CREATE TABLE IF NOT EXISTS gtr ("
" task_hash TEXT PRIMARY KEY,"
" task_description TEXT NOT NULL,"
" status TEXT NOT NULL,"
" owner_agent TEXT NOT NULL,"
" result_summary TEXT,"
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
" updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
");"
"CREATE TABLE IF NOT EXISTS audit_log ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
" issuer_agent TEXT NOT NULL,"
" recipient_agent TEXT NOT NULL,"
" order_description TEXT NOT NULL,"
" logic_justification TEXT NOT NULL,"
" status TEXT NOT NULL"
");"
"CREATE TABLE IF NOT EXISTS agents ("
" agent_id TEXT PRIMARY KEY,"
" role TEXT NOT NULL,"
" manager_id TEXT,"
" department TEXT,"
" budget_limit_tokens INTEGER DEFAULT 1000000,"
" used_tokens INTEGER DEFAULT 0,"
" last_heartbeat TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
" status TEXT DEFAULT 'active'"
");"
"CREATE TABLE IF NOT EXISTS research_tasks ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" url_hash TEXT UNIQUE,"
" url TEXT NOT NULL,"
" status TEXT NOT NULL,"
" summary TEXT,"
" batch_id TEXT,"
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
" updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
");";
char *err_msg = NULL;
if (sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg) != SQLITE_OK) {
sqlite3_free(err_msg);
@@ -102,57 +125,43 @@ r_status_t db_init(db_handle db) {
}
return R_SUCCESS;
}
r_status_t db_kv_set(db_handle db, const char *key, const char *value) {
if (!db || !db->conn || !key || !value) return R_ERROR_INVALID_ARG;
char *sql = sqlite3_mprintf(
"INSERT OR REPLACE INTO kv (key, value, updated_at) VALUES (%Q, %Q, CURRENT_TIMESTAMP)",
key, value);
if (!sql) return R_ERROR_OUT_OF_MEMORY;
char *err_msg = NULL;
int rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
sqlite3_free(sql);
if (rc != SQLITE_OK) {
sqlite3_free(err_msg);
return R_ERROR_DB_QUERY;
}
return R_SUCCESS;
}
r_status_t db_kv_get(db_handle db, const char *key, char **value) {
if (!db || !db->conn || !key || !value) return R_ERROR_INVALID_ARG;
*value = NULL;
const char *sql = "SELECT value FROM kv WHERE key = ?";
sqlite3_stmt *stmt = NULL;
if (sqlite3_prepare_v2(db->conn, sql, -1, &stmt, NULL) != SQLITE_OK) {
return R_ERROR_DB_QUERY;
}
sqlite3_bind_text(stmt, 1, key, -1, SQLITE_STATIC);
int rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
const char *val = (const char *)sqlite3_column_text(stmt, 0);
*value = val ? strdup(val) : NULL;
}
sqlite3_finalize(stmt);
return (rc == SQLITE_ROW) ? R_SUCCESS : R_ERROR_DB_NOT_FOUND;
}
r_status_t db_execute(db_handle db, const char *sql, struct json_object **result) {
if (!db || !db->conn || !sql || !result) return R_ERROR_INVALID_ARG;
*result = NULL;
const char *select_check = sql;
while (*select_check == ' ') select_check++;
if (strncasecmp(select_check, "SELECT", 6) != 0) {
char *err_msg = NULL;
int rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
@@ -169,7 +178,6 @@ r_status_t db_execute(db_handle db, const char *sql, struct json_object **result
*result = success_obj;
return R_SUCCESS;
}
sqlite3_stmt *stmt = NULL;
if (sqlite3_prepare_v2(db->conn, sql, -1, &stmt, NULL) != SQLITE_OK) {
struct json_object *error_obj = json_object_new_object();
@@ -178,17 +186,13 @@ r_status_t db_execute(db_handle db, const char *sql, struct json_object **result
*result = error_obj;
return R_ERROR_DB_QUERY;
}
struct json_object *array = json_object_new_array();
int col_count = sqlite3_column_count(stmt);
while (sqlite3_step(stmt) == SQLITE_ROW) {
struct json_object *row = json_object_new_object();
for (int i = 0; i < col_count; i++) {
const char *col_name = sqlite3_column_name(stmt, i);
int col_type = sqlite3_column_type(stmt, i);
switch (col_type) {
case SQLITE_INTEGER:
json_object_object_add(row, col_name,
@@ -211,108 +215,107 @@ r_status_t db_execute(db_handle db, const char *sql, struct json_object **result
break;
}
}
json_object_array_add(array, row);
}
sqlite3_finalize(stmt);
*result = array;
return R_SUCCESS;
}
char *db_get_schema(db_handle db) {
if (!db || !db->conn) return strdup("Database not available");
struct json_object *result = NULL;
r_status_t status = db_execute(db,
"SELECT name, sql FROM sqlite_master WHERE type='table' ORDER BY name",
&result);
if (status != R_SUCCESS || !result) {
return strdup("Failed to get schema");
}
char *schema = strdup(json_object_to_json_string_ext(result, JSON_C_TO_STRING_PRETTY));
json_object_put(result);
return schema;
}
r_status_t db_store_file_version(db_handle db, const char *path) {
if (!db || !db->conn || !path) return R_ERROR_INVALID_ARG;
FILE *fp = fopen(path, "r");
if (!fp) return R_ERROR_FILE_NOT_FOUND;
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
rewind(fp);
if (size <= 0 || size > 1000000) {
fclose(fp);
return R_SUCCESS;
}
char *content = malloc(size + 1);
if (!content) {
fclose(fp);
return R_ERROR_OUT_OF_MEMORY;
}
size_t read_size = fread(content, 1, size, fp);
content[read_size] = '\0';
fclose(fp);
char *sql = sqlite3_mprintf(
"INSERT INTO file_versions (path, content) VALUES (%Q, %Q)",
path, content);
free(content);
if (!sql) return R_ERROR_OUT_OF_MEMORY;
char *err_msg = NULL;
int rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
sqlite3_free(sql);
sqlite3_free(err_msg);
return (rc == SQLITE_OK) ? R_SUCCESS : R_ERROR_DB_QUERY;
}
r_status_t db_save_conversation(db_handle db, const char *session_key, const char *data) {
if (!db || !db->conn || !session_key || !data) return R_ERROR_INVALID_ARG;
char *sql = sqlite3_mprintf(
"INSERT OR REPLACE INTO conversations (session_key, data, updated_at) "
"VALUES (%Q, %Q, CURRENT_TIMESTAMP)",
session_key, data);
if (!sql) return R_ERROR_OUT_OF_MEMORY;
char *err_msg = NULL;
int rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
sqlite3_free(sql);
sqlite3_free(err_msg);
return (rc == SQLITE_OK) ? R_SUCCESS : R_ERROR_DB_QUERY;
}
long long db_get_conversation_age(db_handle db, const char *session_key) {
if (!db || !db->conn || !session_key) return -1;
const char *sql = "SELECT CAST((julianday('now') - julianday(updated_at)) * 86400 AS INTEGER) FROM conversations WHERE session_key = ?";
sqlite3_stmt *stmt = NULL;
if (sqlite3_prepare_v2(db->conn, sql, -1, &stmt, NULL) != SQLITE_OK) {
return -1;
}
sqlite3_bind_text(stmt, 1, session_key, -1, SQLITE_STATIC);
long long age = -1;
if (sqlite3_step(stmt) == SQLITE_ROW) {
age = sqlite3_column_int64(stmt, 0);
}
sqlite3_finalize(stmt);
return age;
}
r_status_t db_delete_conversation(db_handle db, const char *session_key) {
if (!db || !db->conn || !session_key) return R_ERROR_INVALID_ARG;
char *sql = sqlite3_mprintf("DELETE FROM conversations WHERE session_key = %Q", session_key);
if (!sql) return R_ERROR_OUT_OF_MEMORY;
char *err_msg = NULL;
int rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
sqlite3_free(sql);
sqlite3_free(err_msg);
return (rc == SQLITE_OK) ? R_SUCCESS : R_ERROR_DB_QUERY;
}
r_status_t db_load_conversation(db_handle db, const char *session_key, char **data) {
if (!db || !db->conn || !session_key || !data) return R_ERROR_INVALID_ARG;
*data = NULL;
const char *sql = "SELECT data FROM conversations WHERE session_key = ?";
sqlite3_stmt *stmt = NULL;
if (sqlite3_prepare_v2(db->conn, sql, -1, &stmt, NULL) != SQLITE_OK) {
return R_ERROR_DB_QUERY;
}
sqlite3_bind_text(stmt, 1, session_key, -1, SQLITE_STATIC);
int rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
const char *val = (const char *)sqlite3_column_text(stmt, 0);
*data = val ? strdup(val) : NULL;
}
sqlite3_finalize(stmt);
return (rc == SQLITE_ROW) ? R_SUCCESS : R_ERROR_DB_NOT_FOUND;
}
-63
View File
@@ -1,5 +1,4 @@
// retoor <retoor@molodetz.nl>
#include "http_client.h"
#include <curl/curl.h>
#include <pthread.h>
@@ -9,32 +8,26 @@
#include <string.h>
#include <time.h>
#include <unistd.h>
#define HTTP_MAX_RETRIES 3
#define HTTP_RETRY_DELAY_MS 2000
struct http_client_t {
char *bearer_token;
long timeout_seconds;
long connect_timeout_seconds;
bool show_spinner;
};
struct response_buffer_t {
char *data;
size_t size;
};
static struct timespec spinner_start_time = {0, 0};
static volatile int spinner_running = 0;
static double get_elapsed_seconds(void) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
return (now.tv_sec - spinner_start_time.tv_sec) +
(now.tv_nsec - spinner_start_time.tv_nsec) / 1e9;
}
static void *spinner_thread(void *arg) {
(void)arg;
const char *frames[] = {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"};
@@ -48,31 +41,25 @@ static void *spinner_thread(void *arg) {
}
return NULL;
}
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t total_size = size * nmemb;
struct response_buffer_t *response = (struct response_buffer_t *)userp;
if (total_size > SIZE_MAX - response->size - 1) {
return 0;
}
char *ptr = realloc(response->data, response->size + total_size + 1);
if (!ptr) {
return 0;
}
response->data = ptr;
memcpy(&(response->data[response->size]), contents, total_size);
response->size += total_size;
response->data[response->size] = '\0';
return total_size;
}
http_client_handle http_client_create(const char *bearer_token) {
struct http_client_t *client = calloc(1, sizeof(struct http_client_t));
if (!client) return NULL;
if (bearer_token) {
client->bearer_token = strdup(bearer_token);
if (!client->bearer_token) {
@@ -80,74 +67,58 @@ http_client_handle http_client_create(const char *bearer_token) {
return NULL;
}
}
client->timeout_seconds = 300;
client->connect_timeout_seconds = 10;
client->show_spinner = true;
return client;
}
void http_client_destroy(http_client_handle client) {
if (!client) return;
free(client->bearer_token);
free(client);
}
void http_client_set_show_spinner(http_client_handle client, bool show) {
if (client) client->show_spinner = show;
}
void http_client_set_timeout(http_client_handle client, long timeout_seconds) {
if (client) client->timeout_seconds = timeout_seconds;
}
void http_client_set_connect_timeout(http_client_handle client, long timeout_seconds) {
if (client) client->connect_timeout_seconds = timeout_seconds;
}
r_status_t http_post(http_client_handle client, const char *url,
const char *data, char **response) {
if (!client || !url || !response) return R_ERROR_INVALID_ARG;
CURL *curl = NULL;
struct curl_slist *headers = NULL;
struct response_buffer_t resp = {NULL, 0};
int retry_count = 0;
pthread_t spinner_tid = 0;
r_status_t status = R_SUCCESS;
*response = NULL;
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);
}
while (retry_count < HTTP_MAX_RETRIES) {
free(resp.data);
resp.data = malloc(1);
resp.size = 0;
if (!resp.data) {
status = R_ERROR_OUT_OF_MEMORY;
goto cleanup;
}
resp.data[0] = '\0';
curl = curl_easy_init();
if (!curl) {
status = R_ERROR_HTTP_CONNECTION;
goto cleanup;
}
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, client->connect_timeout_seconds);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, client->timeout_seconds);
headers = curl_slist_append(headers, "Content-Type: application/json");
if (client->bearer_token) {
char bearer_header[2048];
@@ -155,42 +126,33 @@ r_status_t http_post(http_client_handle client, const char *url,
client->bearer_token);
headers = curl_slist_append(headers, bearer_header);
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&resp);
CURLcode res = curl_easy_perform(curl);
curl_slist_free_all(headers);
headers = NULL;
curl_easy_cleanup(curl);
curl = NULL;
if (res == CURLE_OK) {
*response = resp.data;
resp.data = NULL;
status = R_SUCCESS;
goto cleanup;
}
retry_count++;
if (actually_show_spinner) {
spinner_running = 0;
pthread_join(spinner_tid, NULL);
spinner_tid = 0;
fprintf(stderr, "\r \r");
}
fprintf(stderr, "Network error: %s (attempt %d/%d)\n",
curl_easy_strerror(res), retry_count, HTTP_MAX_RETRIES);
if (retry_count < HTTP_MAX_RETRIES) {
fprintf(stderr, "Retrying in %d seconds...\n", HTTP_RETRY_DELAY_MS / 1000);
usleep(HTTP_RETRY_DELAY_MS * 1000);
if (actually_show_spinner) {
clock_gettime(CLOCK_MONOTONIC, &spinner_start_time);
spinner_running = 1;
@@ -198,9 +160,7 @@ r_status_t http_post(http_client_handle client, const char *url,
}
}
}
status = R_ERROR_HTTP_TIMEOUT;
cleanup:
if (actually_show_spinner && spinner_tid) {
spinner_running = 0;
@@ -208,46 +168,36 @@ cleanup:
fprintf(stderr, "\r \r");
fflush(stderr);
}
if (headers) curl_slist_free_all(headers);
if (curl) curl_easy_cleanup(curl);
free(resp.data);
return status;
}
r_status_t http_get(http_client_handle client, const char *url, char **response) {
if (!client || !url || !response) return R_ERROR_INVALID_ARG;
CURL *curl = NULL;
struct curl_slist *headers = NULL;
struct response_buffer_t resp = {NULL, 0};
int retry_count = 0;
r_status_t status = R_SUCCESS;
*response = NULL;
while (retry_count < HTTP_MAX_RETRIES) {
free(resp.data);
resp.data = malloc(1);
resp.size = 0;
if (!resp.data) {
status = R_ERROR_OUT_OF_MEMORY;
goto cleanup;
}
resp.data[0] = '\0';
curl = curl_easy_init();
if (!curl) {
status = R_ERROR_HTTP_CONNECTION;
goto cleanup;
}
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, client->connect_timeout_seconds);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L);
headers = curl_slist_append(headers, "Content-Type: application/json");
if (client->bearer_token) {
char bearer_header[2048];
@@ -255,59 +205,46 @@ r_status_t http_get(http_client_handle client, const char *url, char **response)
client->bearer_token);
headers = curl_slist_append(headers, bearer_header);
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&resp);
CURLcode res = curl_easy_perform(curl);
curl_slist_free_all(headers);
headers = NULL;
curl_easy_cleanup(curl);
curl = NULL;
if (res == CURLE_OK) {
*response = resp.data;
resp.data = NULL;
status = R_SUCCESS;
goto cleanup;
}
retry_count++;
fprintf(stderr, "Network error: %s (attempt %d/%d)\n",
curl_easy_strerror(res), retry_count, HTTP_MAX_RETRIES);
if (retry_count < HTTP_MAX_RETRIES) {
fprintf(stderr, "Retrying in %d seconds...\n", HTTP_RETRY_DELAY_MS / 1000);
usleep(HTTP_RETRY_DELAY_MS * 1000);
}
}
status = R_ERROR_HTTP_TIMEOUT;
cleanup:
if (headers) curl_slist_free_all(headers);
if (curl) curl_easy_cleanup(curl);
free(resp.data);
return status;
}
r_status_t http_post_simple(const char *url, const char *bearer_token,
const char *data, char **response) {
http_client_handle client = http_client_create(bearer_token);
if (!client) return R_ERROR_OUT_OF_MEMORY;
r_status_t status = http_post(client, url, data, response);
http_client_destroy(client);
return status;
}
r_status_t http_get_simple(const char *url, const char *bearer_token, char **response) {
http_client_handle client = http_client_create(bearer_token);
if (!client) return R_ERROR_OUT_OF_MEMORY;
http_client_set_show_spinner(client, false);
r_status_t status = http_get(client, url, response);
http_client_destroy(client);
-39
View File
@@ -1,42 +1,35 @@
// retoor <retoor@molodetz.nl>
#include "json_repair.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h>
static char *strip_comments(const char *src) {
if (!src) return NULL;
size_t len = strlen(src);
char *result = malloc(len + 1);
if (!result) return NULL;
char *dst = result;
const char *p = src;
bool in_string = false;
bool escaped = false;
while (*p) {
if (escaped) {
*dst++ = *p++;
escaped = false;
continue;
}
if (*p == '\\') {
*dst++ = *p++;
escaped = true;
continue;
}
if (*p == '"') {
in_string = !in_string;
*dst++ = *p++;
continue;
}
if (!in_string) {
if (*p == '/' && *(p + 1) == '/') {
while (*p && *p != '\n') p++;
@@ -53,25 +46,21 @@ static char *strip_comments(const char *src) {
continue;
}
}
*dst++ = *p++;
}
*dst = '\0';
return result;
}
static char *normalize_quotes(const char *src) {
if (!src) return NULL;
size_t len = strlen(src);
// Over-allocate because single quotes might be replaced by double quotes + escaping
char *result = malloc(len * 2 + 1);
if (!result) return NULL;
char *dst = result;
const char *p = src;
bool in_double_string = false;
bool escaped = false;
while (*p) {
// Smart quote replacement
if ((unsigned char)*p == 0xE2 && (unsigned char)*(p+1) == 0x80) {
@@ -86,25 +75,21 @@ static char *normalize_quotes(const char *src) {
continue;
}
}
if (escaped) {
*dst++ = *p++;
escaped = false;
continue;
}
if (*p == '\\') {
*dst++ = *p++;
escaped = true;
continue;
}
if (*p == '"') {
in_double_string = !in_double_string;
*dst++ = *p++;
continue;
}
if (!in_double_string && *p == '\'') {
// Heuristic: convert '...' to "..."
*dst++ = '"';
@@ -127,24 +112,20 @@ static char *normalize_quotes(const char *src) {
}
continue;
}
*dst++ = *p++;
}
*dst = '\0';
return result;
}
static char *remove_trailing_commas(const char *src) {
if (!src) return NULL;
size_t len = strlen(src);
char *result = malloc(len + 1);
if (!result) return NULL;
char *dst = result;
const char *p = src;
bool in_string = false;
bool escaped = false;
while (*p) {
if (escaped) {
*dst++ = *p++;
@@ -161,7 +142,6 @@ static char *remove_trailing_commas(const char *src) {
*dst++ = *p++;
continue;
}
if (!in_string && *p == ',') {
// Check if next non-ws char is ] or }
const char *next = p + 1;
@@ -176,18 +156,15 @@ static char *remove_trailing_commas(const char *src) {
*dst = '\0';
return result;
}
static char *quote_unquoted_keys(const char *src) {
if (!src) return NULL;
size_t len = strlen(src);
char *result = malloc(len * 2 + 1);
if (!result) return NULL;
char *dst = result;
const char *p = src;
bool in_string = false;
bool escaped = false;
while (*p) {
if (escaped) {
*dst++ = *p++;
@@ -204,7 +181,6 @@ static char *quote_unquoted_keys(const char *src) {
*dst++ = *p++;
continue;
}
if (!in_string && (isalnum((unsigned char)*p) || *p == '_' || *p == '-')) {
// Potential unquoted key?
// A key usually follows '{' or ',' and is followed by ':'
@@ -234,13 +210,11 @@ static char *quote_unquoted_keys(const char *src) {
*dst = '\0';
return result;
}
static char *balance_brackets(const char *src) {
if (!src) return NULL;
size_t len = strlen(src);
char *result = malloc(len + 1024);
if (!result) return NULL;
char stack[1024];
int top = 0;
@@ -248,7 +222,6 @@ static char *balance_brackets(const char *src) {
const char *p = src;
bool in_string = false;
bool escaped = false;
while (*p) {
if (escaped) {
*dst++ = *p++;
@@ -265,7 +238,6 @@ static char *balance_brackets(const char *src) {
*dst++ = *p++;
continue;
}
if (!in_string) {
if (*p == '{' || *p == '[') {
if (top < 1024) stack[top++] = *p;
@@ -284,7 +256,6 @@ static char *balance_brackets(const char *src) {
}
*dst++ = *p++;
}
while (top > 0) {
char opener = stack[--top];
*dst++ = (opener == '{') ? '}' : ']';
@@ -292,18 +263,15 @@ static char *balance_brackets(const char *src) {
*dst = '\0';
return result;
}
static char *compact_json(const char *src) {
if (!src) return NULL;
size_t len = strlen(src);
char *result = malloc(len + 1);
if (!result) return NULL;
char *dst = result;
const char *p = src;
bool in_string = false;
bool escaped = false;
while (*p) {
if (escaped) {
*dst++ = *p++;
@@ -320,7 +288,6 @@ static char *compact_json(const char *src) {
*dst++ = *p++;
continue;
}
if (!in_string && isspace((unsigned char)*p)) {
p++;
continue;
@@ -330,15 +297,12 @@ static char *compact_json(const char *src) {
*dst = '\0';
return result;
}
char *json_repair_string(const char *src) {
if (!src) return NULL;
// Find the first occurrence of { or [
const char *start_ptr = src;
while (*start_ptr && *start_ptr != '{' && *start_ptr != '[') start_ptr++;
if (!*start_ptr) return strdup(src); // No JSON structure found, return as is
char *s1 = strip_comments(start_ptr);
char *s2 = normalize_quotes(s1);
free(s1);
@@ -348,7 +312,6 @@ char *json_repair_string(const char *src) {
free(s3);
char *s5 = balance_brackets(s4);
free(s4);
// Heuristic: truncate after the first complete object/array
int depth = 0;
bool in_str = false;
@@ -370,9 +333,7 @@ char *json_repair_string(const char *src) {
}
p++;
}
char *s6 = compact_json(s5);
free(s5);
return s6;
}
+41 -58
View File
@@ -1,16 +1,14 @@
// retoor <retoor@molodetz.nl>
#include "agent.h"
#include "db.h"
#include "http_client.h"
#include "r_config.h"
#include "r_error.h"
#include "tool.h"
#include "line.h"
#include "markdown.h"
#include "utils.h"
#include <curl/curl.h>
#include <json-c/json.h>
#include <locale.h>
#include <signal.h>
@@ -20,18 +18,14 @@
#include <string.h>
#include <time.h>
#include <unistd.h>
static volatile sig_atomic_t sigint_count = 0;
static time_t first_sigint_time = 0;
static bool syntax_highlight_enabled = true;
static bool api_mode = false;
static db_handle global_db = NULL;
static messages_handle global_messages = NULL;
extern tool_registry_t *tools_get_registry(void);
extern void tools_registry_shutdown(void);
static bool include_file(const char *path);
static char *get_prompt_from_stdin(char *prompt);
static char *get_prompt_from_args(int argc, char **argv);
@@ -40,12 +34,10 @@ static void repl(void);
static void init(void);
static void cleanup(void);
static void handle_sigint(int sig);
static char *get_env_string(void) {
FILE *fp = popen("env", "r");
if (!fp)
return NULL;
size_t buffer_size = 1024;
size_t total_size = 0;
char *output = malloc(buffer_size);
@@ -53,7 +45,6 @@ static char *get_env_string(void) {
pclose(fp);
return NULL;
}
size_t bytes_read;
while ((bytes_read = fread(output + total_size, 1, buffer_size - total_size,
fp)) > 0) {
@@ -69,12 +60,10 @@ static char *get_env_string(void) {
output = temp;
}
}
output[total_size] = '\0';
pclose(fp);
return output;
}
static char *get_prompt_from_stdin(char *prompt) {
int index = 0;
int c;
@@ -84,10 +73,8 @@ static char *get_prompt_from_stdin(char *prompt) {
prompt[index] = '\0';
return prompt;
}
static char *get_prompt_from_args(int argc, char **argv) {
r_config_handle cfg = r_config_get_instance();
char *prompt = malloc(10 * 1024 * 1024 + 1);
char *system_msg = malloc(1024 * 1024);
if (!prompt || !system_msg) {
@@ -95,10 +82,8 @@ static char *get_prompt_from_args(int argc, char **argv) {
free(system_msg);
return NULL;
}
system_msg[0] = '\0';
bool get_from_stdin = false;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--stdin") == 0) {
fprintf(stderr, "Reading from stdin.\n");
@@ -130,7 +115,6 @@ static char *get_prompt_from_args(int argc, char **argv) {
strcat(system_msg, (i < argc - 1) ? " " : ".");
}
}
if (get_from_stdin) {
if (*system_msg && global_messages) {
messages_add(global_messages, "system", system_msg);
@@ -141,14 +125,12 @@ static char *get_prompt_from_args(int argc, char **argv) {
free(prompt);
prompt = system_msg;
}
if (!*prompt) {
free(prompt);
return NULL;
}
return prompt;
}
static bool try_prompt(int argc, char *argv[]) {
char *prompt = get_prompt_from_args(argc, argv);
if (prompt) {
@@ -165,31 +147,25 @@ static bool try_prompt(int argc, char *argv[]) {
}
return false;
}
static bool include_file(const char *path) {
char *file_content = read_file(path);
if (!file_content)
return false;
if (global_messages) {
messages_add(global_messages, "system", file_content);
}
free(file_content);
return true;
}
static void repl(void) {
r_config_handle cfg = r_config_get_instance();
tool_registry_t *tools = tools_get_registry();
line_init();
char *line = NULL;
while (true) {
line = line_read("> ");
if (!line || !*line)
continue;
if (!strncmp(line, "!dump", 5)) {
char *json = messages_to_string(global_messages);
if (json) {
@@ -198,7 +174,6 @@ static void repl(void) {
}
continue;
}
if (!strncmp(line, "!clear", 6)) {
messages_clear(global_messages);
fprintf(stderr, "Session cleared.\n");
@@ -208,6 +183,14 @@ static void repl(void) {
printf("Session: %s\n", messages_get_session_id(global_messages));
continue;
}
if (!strncmp(line, "!new", 4)) {
messages_clear(global_messages);
char session_id[64];
snprintf(session_id, sizeof(session_id), "session-%d-%ld", getpid(), (long)time(NULL));
messages_set_session_id(global_messages, session_id);
fprintf(stderr, "New session: %s\n", session_id);
continue;
}
if (!strncmp(line, "!verbose", 8)) {
bool verbose = !r_config_is_verbose(cfg);
r_config_set_verbose(cfg, verbose);
@@ -248,7 +231,6 @@ static void repl(void) {
if (!strncmp(line, "exit", 4)) {
exit(0);
}
while (line && *line != '\n') {
char *response = agent_chat(line, global_messages);
if (response) {
@@ -262,24 +244,19 @@ static void repl(void) {
}
}
}
static void init(void) {
curl_global_init(CURL_GLOBAL_DEFAULT);
setbuf(stdout, NULL);
line_init();
r_config_handle cfg = r_config_get_instance();
global_db = db_open(NULL);
global_messages = messages_create(r_config_get_session_id(cfg));
char *schema = db_get_schema(global_db);
char payload[1024 * 1024] = {0};
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
char datetime[64];
strftime(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S %Z", tm_info);
char cwd[4096];
if (!getcwd(cwd, sizeof(cwd))) {
strcpy(cwd, "unknown");
@@ -293,17 +270,37 @@ static void init(void) {
"task, "
"select and execute tools when needed, observe results, and continue "
"until the goal is achieved.\n\n"
"## Reasoning Pattern (ReAct)\n"
"For EVERY task, you MUST follow this sequence:\n"
"1. Plan: Break the task into logical sub-tasks. DECIDE which specialized "
"agents to spawn. CREATE a visible CHECKLIST of all deliverables (files, "
"features, pages).\n"
"2. Execute: Spawn agents or use tools. INTEGRATE their results "
"immediately. Update your checklist as you progress.\n"
"3. Verify: Check EVERY item on your checklist. Run code, check file "
"existence, verify links. If an item is missing, go back to Execute.\n"
"4. Conclude: Only after ALL checklist items are verified, provide your "
"final response.\n\n"
"## The Enterprise Pyramid (Rigid Hierarchy)\n"
"You are the **Executive Agent (Apex)**. You MUST enforce a strict top-down "
"Chain of Command:\n"
"- **Executive (Apex)**: Final arbiter. Owns the Strategic Blueprint. You "
"never code or research directly. You evaluate sub-agent depth.\n"
"- **Department Heads (Managers)**: Create detailed 'Task Packs'. "
"Synthesize sub-agent outputs into 'Department Reports'.\n"
"- **Workers (Base)**: Execute atomic tasks. Report literal word counts "
"and file sizes upward.\n\n"
"### Bureaucratic Protocols (MANDATORY)\n"
"1. **Strategic Blueprint**: Your very first turn MUST output a "
"blueprint: Mission, Departments Involved, and a 10-step Checklist.\n"
"2. **Sequential Handover**: You are FORBIDDEN from spawning a Developer "
"until the Researcher has delivered a minimum of 1000 words of "
"documented facts to `PROJECT_KNOWLEDGE.md`.\n"
"3. **Content Depth Guardrail**: For 'Huge' projects, every page MUST "
"contain deep, researched info. Placeholder text (e.g., 'Coming soon', "
"'Introduction here') is a failure. You MUST use 'read_file' to audit "
"sub-agent work before concluding.\n"
"4. **Global Task Registry (GTR)**: Query GTR for every sub-task. If a "
"similar task exists, use its result summary. DUPLICATION IS FORBIDDEN.\n"
"5. **Fan-Out Architecture (Research)**: Manager calls `web_search` to get "
"URLs, then uses `research_dispatcher` to queue them. Workers use "
"`fetch_and_scrape` for individual URLs. If a Worker finds new links, it "
"MUST use `suggest_subtask` to escalate. NEVER follow rabbit holes "
"yourself.\n\n"
"### Shared Memory & Data Sharing\n"
"- Every turn, you MUST update `PROJECT_KNOWLEDGE.md` with new findings.\n"
"- All sub-agents MUST receive the full content of `PROJECT_KNOWLEDGE.md` "
"to ensure a shared organizational history.\n\n"
"## Multi-Agent Orchestration (MANDATORY)\n"
"## Project Scale Rules\n"
"- HUGE PROJECTS: If a 'huge' or 'multi-page' project is requested, "
"delivering a single file is FORBIDDEN. You MUST create a directory "
@@ -372,23 +369,18 @@ static void init(void) {
datetime, cwd, schema ? schema : "{}");
free(schema);
fprintf(stderr, "Loading...");
if (global_messages) {
messages_add(global_messages, "system", payload);
}
const char *env_system_msg = r_config_get_system_message(cfg);
if (env_system_msg && *env_system_msg && global_messages) {
messages_add(global_messages, "system", env_system_msg);
}
if (!include_file(".rcontext.txt")) {
include_file("~/.rcontext.txt");
}
fprintf(stderr, "\r \r");
}
static void cleanup(void) {
if (global_messages) {
messages_destroy(global_messages);
@@ -401,7 +393,6 @@ static void cleanup(void) {
tools_registry_shutdown();
r_config_destroy();
}
static void handle_sigint(int sig) {
(void)sig;
time_t current_time = time(NULL);
@@ -419,10 +410,8 @@ static void handle_sigint(int sig) {
}
}
}
static void parse_session_arg(int argc, char *argv[]) {
r_config_handle cfg = r_config_get_instance();
for (int i = 1; i < argc; i++) {
if (strncmp(argv[i], "--session=", 10) == 0) {
const char *name = argv[i] + 10;
@@ -443,26 +432,20 @@ static void parse_session_arg(int argc, char *argv[]) {
}
}
}
int main(int argc, char *argv[]) {
signal(SIGINT, handle_sigint);
atexit(cleanup);
parse_session_arg(argc, argv);
init();
char *env_string = get_env_string();
if (env_string && *env_string && global_messages) {
messages_add(global_messages, "system", env_string);
free(env_string);
}
messages_load(global_messages);
if (try_prompt(argc, argv)) {
return 0;
}
repl();
return 0;
}
-13
View File
@@ -1,25 +1,20 @@
// retoor <retoor@molodetz.nl>
#include "markdown.h"
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// --- ANSI Escape Codes ---
#define RESET "\033[0m"
#define BOLD "\033[1m"
#define ITALIC "\033[3m"
#define STRIKETHROUGH "\033[9m"
#define FG_YELLOW "\033[33m"
#define FG_BLUE "\033[34m"
#define FG_CYAN "\033[36m"
#define FG_MAGENTA "\033[35m"
#define BG_YELLOW_FG_BLACK "\033[43;30m"
/**
* @brief Checks if a given word is a programming language keyword.
*/
@@ -32,18 +27,15 @@ static int is_keyword(const char *word) {
"implements", "new", "synchronized", "var", "switch", "case", "break", "continue",
"namespace", "template", "typename", "virtual", "override", "friend", "package", "func", "type", "go", "defer", "select",
"then", "elif", "fi", "esac", "do", "done", "using"};
for (size_t i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
if (strcmp(word, keywords[i]) == 0) return 1;
}
return 0;
}
void highlight_code(const char *code) {
const char *ptr = code;
char buffer[4096];
size_t index = 0;
while (*ptr) {
if (isalpha((unsigned char)*ptr) || *ptr == '_') {
while (isalnum((unsigned char)*ptr) || *ptr == '_') {
@@ -68,11 +60,9 @@ void highlight_code(const char *code) {
}
}
}
void parse_markdown_to_ansi(const char *markdown) {
const char *ptr = markdown;
bool is_start_of_line = true;
while (*ptr) {
if (is_start_of_line && strncmp(ptr, "```", 3) == 0) {
ptr += 3;
@@ -97,7 +87,6 @@ void parse_markdown_to_ansi(const char *markdown) {
break;
}
}
if (is_start_of_line) {
const char *line_start_ptr = ptr;
int indent_level = 0;
@@ -133,7 +122,6 @@ void parse_markdown_to_ansi(const char *markdown) {
is_start_of_line = true; continue;
}
}
if (strncmp(ptr, "***", 3) == 0 || strncmp(ptr, "___", 3) == 0) {
const char *marker = strncmp(ptr, "***", 3) == 0 ? "***" : "___";
printf(BOLD ITALIC); ptr += 3;
@@ -184,7 +172,6 @@ void parse_markdown_to_ansi(const char *markdown) {
}
}
}
if (*ptr == '\n') is_start_of_line = true;
else if (!isspace((unsigned char)*ptr)) is_start_of_line = false;
putchar(*ptr);
+36 -62
View File
@@ -1,5 +1,4 @@
// retoor <retoor@molodetz.nl>
#include "messages.h"
#include "db.h"
#include <ctype.h>
@@ -7,21 +6,18 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAX_CONTENT_LENGTH 1048570
#define MAX_TOOL_RESULT_LENGTH 104000
#define SESSION_EXPIRY_SECONDS 86400
struct messages_t {
struct json_object *array;
char *session_id;
db_handle db;
bool loaded;
};
static bool is_valid_session_id(const char *session_id) {
if (!session_id || !*session_id) return false;
if (strlen(session_id) > 200) return false;
for (const char *p = session_id; *p; p++) {
if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_' && *p != '.') {
return false;
@@ -29,42 +25,60 @@ static bool is_valid_session_id(const char *session_id) {
}
return true;
}
static long long get_ppid_starttime(pid_t ppid) {
char proc_path[64];
snprintf(proc_path, sizeof(proc_path), "/proc/%d/stat", ppid);
FILE *fp = fopen(proc_path, "r");
if (!fp) return -1;
char buf[1024];
size_t nread = fread(buf, 1, sizeof(buf) - 1, fp);
fclose(fp);
if (nread == 0) return -1;
buf[nread] = '\0';
char *close_paren = strrchr(buf, ')');
if (!close_paren) return -1;
char *p = close_paren + 2;
int field = 0;
while (*p && field < 19) {
if (*p == ' ') field++;
p++;
}
if (field < 19) return -1;
return strtoll(p, NULL, 10);
}
static char *generate_default_session_id(void) {
char *session_id = malloc(32);
char *session_id = malloc(64);
if (!session_id) return NULL;
snprintf(session_id, 32, "session-%d", getppid());
pid_t ppid = getppid();
long long starttime = get_ppid_starttime(ppid);
if (starttime >= 0) {
snprintf(session_id, 64, "session-%d-%lld", ppid, starttime);
} else {
snprintf(session_id, 64, "session-%d", ppid);
}
return session_id;
}
messages_handle messages_create(const char *session_id) {
struct messages_t *msgs = calloc(1, sizeof(struct messages_t));
if (!msgs) return NULL;
msgs->array = json_object_new_array();
if (!msgs->array) {
free(msgs);
return NULL;
}
if (session_id && is_valid_session_id(session_id)) {
msgs->session_id = strdup(session_id);
} else {
msgs->session_id = generate_default_session_id();
}
if (!msgs->session_id) {
json_object_put(msgs->array);
free(msgs);
return NULL;
}
msgs->db = db_open(NULL);
return msgs;
}
void messages_destroy(messages_handle msgs) {
if (!msgs) return;
if (msgs->array) json_object_put(msgs->array);
@@ -72,150 +86,118 @@ void messages_destroy(messages_handle msgs) {
free(msgs->session_id);
free(msgs);
}
r_status_t messages_set_session_id(messages_handle msgs, const char *session_id) {
if (!msgs || !is_valid_session_id(session_id)) return R_ERROR_INVALID_ARG;
free(msgs->session_id);
msgs->session_id = strdup(session_id);
msgs->loaded = false;
return msgs->session_id ? R_SUCCESS : R_ERROR_OUT_OF_MEMORY;
}
const char *messages_get_session_id(messages_handle msgs) {
return msgs ? msgs->session_id : NULL;
}
r_status_t messages_add(messages_handle msgs, const char *role, const char *content) {
if (!msgs || !msgs->array || !role) return R_ERROR_INVALID_ARG;
struct json_object *message = json_object_new_object();
if (!message) return R_ERROR_OUT_OF_MEMORY;
json_object_object_add(message, "role", json_object_new_string(role));
if (content) {
size_t len = strlen(content);
if (len > MAX_CONTENT_LENGTH) len = MAX_CONTENT_LENGTH;
json_object_object_add(message, "content",
json_object_new_string_len(content, (int)len));
}
json_object_array_add(msgs->array, message);
if (strcmp(role, "system") != 0) {
messages_save(msgs);
}
return R_SUCCESS;
}
r_status_t messages_add_object(messages_handle msgs, struct json_object *message) {
if (!msgs || !msgs->array || !message) return R_ERROR_INVALID_ARG;
json_object_array_add(msgs->array, message);
messages_save(msgs);
return R_SUCCESS;
}
r_status_t messages_add_tool_call(messages_handle msgs, struct json_object *message) {
if (!msgs || !msgs->array || !message) return R_ERROR_INVALID_ARG;
json_object_array_add(msgs->array, message);
messages_save(msgs);
return R_SUCCESS;
}
r_status_t messages_add_tool_result(messages_handle msgs, const char *tool_call_id, const char *result) {
if (!msgs || !msgs->array || !tool_call_id || !result) return R_ERROR_INVALID_ARG;
struct json_object *message = json_object_new_object();
if (!message) return R_ERROR_OUT_OF_MEMORY;
json_object_object_add(message, "tool_call_id", json_object_new_string(tool_call_id));
size_t len = strlen(result);
if (len > MAX_TOOL_RESULT_LENGTH) len = MAX_TOOL_RESULT_LENGTH;
json_object_object_add(message, "tool_result",
json_object_new_string_len(result, (int)len));
json_object_array_add(msgs->array, message);
messages_save(msgs);
return R_SUCCESS;
}
r_status_t messages_remove_last(messages_handle msgs) {
if (!msgs || !msgs->array) return R_ERROR_INVALID_ARG;
int size = json_object_array_length(msgs->array);
if (size == 0) return R_ERROR_NOT_FOUND;
json_object_array_del_idx(msgs->array, size - 1, 1);
messages_save(msgs);
return R_SUCCESS;
}
r_status_t messages_remove_range(messages_handle msgs, int start, int count) {
if (!msgs || !msgs->array) return R_ERROR_INVALID_ARG;
int size = json_object_array_length(msgs->array);
if (start < 0 || start >= size || count < 0) return R_ERROR_INVALID_ARG;
if (start + count > size) count = size - start;
json_object_array_del_idx(msgs->array, start, count);
messages_save(msgs);
return R_SUCCESS;
}
r_status_t messages_clear(messages_handle msgs) {
if (!msgs) return R_ERROR_INVALID_ARG;
if (msgs->array) json_object_put(msgs->array);
msgs->array = json_object_new_array();
if (!msgs->array) return R_ERROR_OUT_OF_MEMORY;
messages_save(msgs);
return R_SUCCESS;
}
r_status_t messages_save(messages_handle msgs) {
if (!msgs || !msgs->array || !msgs->db) return R_ERROR_INVALID_ARG;
char key[512];
snprintf(key, sizeof(key), "session:%s", msgs->session_id);
const char *json_str = json_object_to_json_string_ext(msgs->array, JSON_C_TO_STRING_PLAIN);
if (!json_str) return R_ERROR_OUT_OF_MEMORY;
return db_save_conversation(msgs->db, key, json_str);
}
r_status_t messages_load(messages_handle msgs) {
if (!msgs || !msgs->db) return R_ERROR_INVALID_ARG;
if (msgs->loaded) return R_SUCCESS;
char key[512];
snprintf(key, sizeof(key), "session:%s", msgs->session_id);
long long age = db_get_conversation_age(msgs->db, key);
if (age > SESSION_EXPIRY_SECONDS) {
db_delete_conversation(msgs->db, key);
msgs->loaded = true;
return R_ERROR_NOT_FOUND;
}
char *data = NULL;
r_status_t status = db_load_conversation(msgs->db, key, &data);
if (status != R_SUCCESS || !data) {
if (status == R_SUCCESS) msgs->loaded = true;
return status == R_SUCCESS ? R_ERROR_NOT_FOUND : status;
}
struct json_object *loaded = json_tokener_parse(data);
free(data);
if (!loaded || !json_object_is_type(loaded, json_type_array)) {
if (loaded) json_object_put(loaded);
return R_ERROR_PARSE;
}
int len = json_object_array_length(loaded);
for (int i = 0; i < len; i++) {
struct json_object *msg = json_object_array_get_idx(loaded, i);
struct json_object *role_obj;
if (json_object_object_get_ex(msg, "role", &role_obj)) {
const char *role = json_object_get_string(role_obj);
if (role && strcmp(role, "system") != 0) {
@@ -225,43 +207,35 @@ r_status_t messages_load(messages_handle msgs) {
json_object_array_add(msgs->array, json_object_get(msg));
}
}
json_object_put(loaded);
msgs->loaded = true;
return R_SUCCESS;
}
struct json_object *messages_get_object(messages_handle msgs, int index) {
if (!msgs || !msgs->array) return NULL;
return json_object_array_get_idx(msgs->array, index);
}
r_status_t messages_replace_at(messages_handle msgs, int index, struct json_object *message) {
if (!msgs || !msgs->array || !message) return R_ERROR_INVALID_ARG;
int size = json_object_array_length(msgs->array);
if (index < 0 || index >= size) return R_ERROR_INVALID_ARG;
// json-c doesn't have a direct 'replace' for array by index that handles memory easily
// We'll use json_object_array_put_idx which replaces and puts the old object
json_object_array_put_idx(msgs->array, index, message);
messages_save(msgs);
return R_SUCCESS;
}
struct json_object *messages_to_json(messages_handle msgs) {
return msgs ? msgs->array : NULL;
}
char *messages_to_string(messages_handle msgs) {
if (!msgs || !msgs->array) return NULL;
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);
-34
View File
@@ -1,5 +1,4 @@
// retoor <retoor@molodetz.nl>
#include "python_repair.h"
#include <stdio.h>
#include <stdlib.h>
@@ -7,12 +6,9 @@
#include <stdbool.h>
#include <ctype.h>
#include <math.h>
#define INDENT_WIDTH 4
static char *dedent_code(const char *src) {
if (!src || !*src) return strdup("");
int min_indent = -1;
const char *line = src;
while (line && *line) {
@@ -26,9 +22,7 @@ static char *dedent_code(const char *src) {
line = strchr(line, '\n');
if (line) line++;
}
if (min_indent <= 0) return strdup(src);
size_t src_len = strlen(src);
char *result = malloc(src_len + 1);
if (!result) return strdup(src);
@@ -56,19 +50,16 @@ static char *dedent_code(const char *src) {
*dst = '\0';
return result;
}
static char *normalize_indentation(const char *src) {
if (!src) return NULL;
size_t src_len = strlen(src);
char *result = malloc(src_len * 2 + 1); // Extra space for normalized indents
if (!result) return NULL;
char *dst = result;
const char *line = src;
while (line && *line) {
const char *next_line = strchr(line, '\n');
size_t line_len = next_line ? (size_t)(next_line - line) : strlen(line);
// Check if line is empty or just whitespace
bool is_empty = true;
for (size_t i = 0; i < line_len; i++) {
@@ -77,7 +68,6 @@ static char *normalize_indentation(const char *src) {
break;
}
}
if (is_empty) {
if (next_line) {
*dst++ = '\n';
@@ -87,7 +77,6 @@ static char *normalize_indentation(const char *src) {
}
continue;
}
// Calculate current leading indent
int leading_spaces = 0;
const char *content_ptr = line;
@@ -99,17 +88,14 @@ static char *normalize_indentation(const char *src) {
}
content_ptr++;
}
// Round to nearest INDENT_WIDTH
int normalized_level = (leading_spaces + INDENT_WIDTH / 2) / INDENT_WIDTH;
int target_spaces = normalized_level * INDENT_WIDTH;
for (int i = 0; i < target_spaces; i++) *dst++ = ' ';
size_t content_len = (size_t)((line + line_len) - content_ptr);
memcpy(dst, content_ptr, content_len);
dst += content_len;
if (next_line) {
*dst++ = '\n';
line = next_line + 1;
@@ -120,27 +106,22 @@ static char *normalize_indentation(const char *src) {
*dst = '\0';
return result;
}
static char *repair_strings_and_brackets(const char *src) {
if (!src) return NULL;
size_t src_len = strlen(src);
char *result = malloc(src_len + 2048); // Buffer for extra quotes/brackets
if (!result) return NULL;
char *dst = result;
const char *p = src;
char bracket_stack[1024];
int stack_ptr = 0;
bool in_string = false;
char string_quote = 0;
int quote_type = 0; // 1 for single, 3 for triple
bool escaped = false;
while (*p) {
char ch = *p;
if (!in_string) {
if (ch == '#') {
// Comment, copy until newline
@@ -199,7 +180,6 @@ static char *repair_strings_and_brackets(const char *src) {
}
*dst++ = *p++;
}
if (in_string) {
if (quote_type == 3) {
*dst++ = string_quote; *dst++ = string_quote; *dst++ = string_quote;
@@ -207,7 +187,6 @@ static char *repair_strings_and_brackets(const char *src) {
*dst++ = string_quote;
}
}
// Balance brackets
while (stack_ptr > 0) {
char opener = bracket_stack[--stack_ptr];
@@ -215,29 +194,24 @@ static char *repair_strings_and_brackets(const char *src) {
else if (opener == '[') *dst++ = ']';
else if (opener == '{') *dst++ = '}';
}
*dst = '\0';
return result;
}
static char *add_missing_passes(const char *src) {
if (!src) return NULL;
size_t src_len = strlen(src);
char *result = malloc(src_len * 2 + 1);
if (!result) return NULL;
char *dst = result;
const char *line = src;
while (line && *line) {
const char *next_line = strchr(line, '\n');
size_t line_len = next_line ? (size_t)(next_line - line) : strlen(line);
// Copy current line
memcpy(dst, line, line_len);
dst += line_len;
if (next_line) *dst++ = '\n';
// Check if line ends with ':' (ignoring comments/whitespace)
const char *p = line + line_len - 1;
while (p >= line && isspace((unsigned char)*p)) p--;
@@ -269,7 +243,6 @@ static char *add_missing_passes(const char *src) {
else current_indent++;
line_p++;
}
int line_look_indent = 0;
const char *look_p = lookahead;
while (look_p < (lookahead + look_len) && (*look_p == ' ' || *look_p == '\t')) {
@@ -277,7 +250,6 @@ static char *add_missing_passes(const char *src) {
else line_look_indent++;
look_p++;
}
if (line_look_indent > current_indent) {
needs_pass = false;
}
@@ -287,7 +259,6 @@ static char *add_missing_passes(const char *src) {
if (next_next) lookahead = next_next + 1;
else break;
}
if (needs_pass) {
// Find current indent to place 'pass' correctly
int current_indent = 0;
@@ -304,18 +275,14 @@ static char *add_missing_passes(const char *src) {
dst += 5;
}
}
if (next_line) line = next_line + 1;
else break;
}
*dst = '\0';
return result;
}
char *python_repair_code(const char *src) {
if (!src) return NULL;
char *s1 = dedent_code(src);
char *s2 = normalize_indentation(s1);
free(s1);
@@ -325,6 +292,5 @@ char *python_repair_code(const char *src) {
char *s4 = add_missing_passes(s3);
free(s3);
return s4;
}
-42
View File
@@ -1,11 +1,9 @@
// retoor <retoor@molodetz.nl>
#include "r_config.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct r_config_t {
char *api_url;
char *models_url;
@@ -20,13 +18,10 @@ struct r_config_t {
bool use_strict;
bool verbose;
};
static struct r_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;
@@ -34,31 +29,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("R_KEY");
if (key && *key) return key;
key = getenv("OPENROUTER_API_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;
@@ -66,13 +48,10 @@ static bool is_valid_session_id(const char *session_id) {
}
return true;
}
r_config_handle r_config_get_instance(void) {
if (instance) return instance;
instance = calloc(1, sizeof(struct r_config_t));
if (!instance) return NULL;
const char *base_url = getenv("R_BASE_URL");
if (base_url && *base_url) {
size_t len = strlen(base_url);
@@ -86,10 +65,8 @@ r_config_handle r_config_get_instance(void) {
instance->api_url = strdup("https://api.openai.com/v1/chat/completions");
instance->models_url = strdup("https://api.openai.com/v1/models");
}
const char *model = getenv("R_MODEL");
instance->model = strdup(model && *model ? model : "gpt-4o-mini");
instance->api_key = strdup(resolve_api_key());
instance->db_path = strdup("~/.r.db");
instance->temperature = 0.1;
@@ -98,19 +75,15 @@ r_config_handle r_config_get_instance(void) {
instance->use_tools = resolve_env_bool("R_USE_TOOLS", true);
instance->use_strict = resolve_env_bool("R_USE_STRICT", true);
instance->verbose = false;
const char *session = getenv("R_SESSION");
if (session && is_valid_session_id(session)) {
instance->session_id = strdup(session);
} else {
instance->session_id = NULL;
}
instance->system_message = strdup_safe(getenv("R_SYSTEM_MESSAGE"));
return instance;
}
void r_config_destroy(void) {
if (!instance) return;
free(instance->api_url);
@@ -123,68 +96,53 @@ void r_config_destroy(void) {
free(instance);
instance = NULL;
}
const char *r_config_get_api_url(r_config_handle cfg) {
return cfg ? cfg->api_url : NULL;
}
const char *r_config_get_models_url(r_config_handle cfg) {
return cfg ? cfg->models_url : NULL;
}
const char *r_config_get_model(r_config_handle cfg) {
return cfg ? cfg->model : NULL;
}
void r_config_set_model(r_config_handle cfg, const char *model) {
if (!cfg || !model) return;
free(cfg->model);
cfg->model = strdup(model);
}
const char *r_config_get_api_key(r_config_handle cfg) {
return cfg ? cfg->api_key : NULL;
}
const char *r_config_get_db_path(r_config_handle cfg) {
return cfg ? cfg->db_path : NULL;
}
bool r_config_use_tools(r_config_handle cfg) {
return cfg ? cfg->use_tools : true;
}
bool r_config_use_strict(r_config_handle cfg) {
return cfg ? cfg->use_strict : true;
}
bool r_config_is_verbose(r_config_handle cfg) {
return cfg ? cfg->verbose : false;
}
void r_config_set_verbose(r_config_handle cfg, bool verbose) {
if (cfg) cfg->verbose = verbose;
}
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;
}
bool r_config_set_session_id(r_config_handle cfg, const char *session_id) {
if (!cfg || !is_valid_session_id(session_id)) return false;
free(cfg->session_id);
cfg->session_id = strdup(session_id);
return cfg->session_id != NULL;
}
const char *r_config_get_system_message(r_config_handle cfg) {
return cfg ? cfg->system_message : NULL;
}
+1 -16
View File
@@ -1,12 +1,10 @@
// retoor <retoor@molodetz.nl>
#include "r_diff.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define COLOR_RED "\x1b[31m"
#define COLOR_GREEN "\x1b[32m"
#define COLOR_CYAN "\x1b[36m"
@@ -14,20 +12,16 @@
#define COLOR_DIM "\x1b[2m"
#define COLOR_BG_RED "\x1b[41;37m"
#define COLOR_BG_GREEN "\x1b[42;30m"
typedef struct {
char **lines;
size_t count;
} line_set_t;
static line_set_t split_lines(const char *str) {
line_set_t set = {NULL, 0};
if (!str || !*str) return set;
char *copy = strdup(str);
char *p = copy;
char *line_start = copy;
while (*p) {
if (*p == '\n') {
*p = '\0';
@@ -46,12 +40,10 @@ static line_set_t split_lines(const char *str) {
free(copy);
return set;
}
static void free_line_set(line_set_t set) {
for (size_t i = 0; i < set.count; i++) free(set.lines[i]);
free(set.lines);
}
static void print_truncated(const char *str, int width, const char *color) {
if (width <= 0) return;
int len = (int)strlen(str);
@@ -67,22 +59,18 @@ static void print_truncated(const char *str, int width, const char *color) {
if (color) printf("%s", COLOR_RESET);
}
void r_diff_print(const char *path, const char *old_content, const char *new_content) {
line_set_t old_set = split_lines(old_content);
line_set_t new_set = split_lines(new_content);
struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
int term_width = w.ws_col > 0 ? w.ws_col : 120;
if (term_width < 40) term_width = 40; // Minimum usable width
int col_width = (term_width - 15) / 2;
if (col_width < 5) col_width = 5;
printf("\n%s %s CHANGES: %s %s\n", COLOR_CYAN, COLOR_DIM, path, COLOR_RESET);
printf("%4s %-*s | %4s %-*s\n", "LINE", col_width, " OLD", "LINE", col_width, " NEW");
printf("%.*s\n", term_width, "--------------------------------------------------------------------------------------------------------------------------------------------");
size_t o = 0, n = 0;
while (o < old_set.count || n < new_set.count) {
bool match = false;
@@ -97,7 +85,6 @@ void r_diff_print(const char *path, const char *old_content, const char *new_con
match = true;
}
}
if (!match) {
bool found_o_later = false;
if (o < old_set.count) {
@@ -108,7 +95,6 @@ void r_diff_print(const char *path, const char *old_content, const char *new_con
}
}
}
if (found_o_later) {
// Line added on NEW side
printf("%4s ", "");
@@ -140,7 +126,6 @@ void r_diff_print(const char *path, const char *old_content, const char *new_con
}
printf("\n");
fflush(stdout);
free_line_set(old_set);
free_line_set(new_set);
}
}
-3
View File
@@ -1,7 +1,5 @@
// retoor <retoor@molodetz.nl>
#include "r_error.h"
static const char *error_messages[] = {
[R_SUCCESS] = "Success",
[R_ERROR_INVALID_ARG] = "Invalid argument",
@@ -26,7 +24,6 @@ static const char *error_messages[] = {
[R_ERROR_SESSION_INVALID] = "Invalid session name",
[R_ERROR_UNKNOWN] = "Unknown error"
};
const char *r_status_string(r_status_t status) {
if (status < 0 || status > R_ERROR_UNKNOWN) {
return error_messages[R_ERROR_UNKNOWN];
+60 -50
View File
@@ -1,17 +1,14 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <pthread.h>
typedef struct {
tool_t *tool;
struct json_object *args;
char *output;
} tool_thread_args_t;
static void *tool_thread_func(void *ptr) {
tool_thread_args_t *args = (tool_thread_args_t *)ptr;
if (args->tool->vtable->execute) {
@@ -19,11 +16,9 @@ static void *tool_thread_func(void *ptr) {
}
return NULL;
}
tool_registry_t *tool_registry_create(void) {
tool_registry_t *registry = calloc(1, sizeof(tool_registry_t));
if (!registry) return NULL;
registry->capacity = 32;
registry->tools = calloc(registry->capacity, sizeof(tool_t *));
if (!registry->tools) {
@@ -32,16 +27,13 @@ tool_registry_t *tool_registry_create(void) {
}
return registry;
}
void tool_registry_destroy(tool_registry_t *registry) {
if (!registry) return;
free(registry->tools);
free(registry);
}
r_status_t tool_registry_register(tool_registry_t *registry, tool_t *tool) {
if (!registry || !tool) return R_ERROR_INVALID_ARG;
if (registry->count >= registry->capacity) {
size_t new_capacity = registry->capacity * 2;
tool_t **new_tools = realloc(registry->tools, new_capacity * sizeof(tool_t *));
@@ -49,14 +41,11 @@ r_status_t tool_registry_register(tool_registry_t *registry, tool_t *tool) {
registry->tools = new_tools;
registry->capacity = new_capacity;
}
registry->tools[registry->count++] = tool;
return R_SUCCESS;
}
tool_t *tool_registry_find(tool_registry_t *registry, const char *name) {
if (!registry || !name) return NULL;
for (size_t i = 0; i < registry->count; i++) {
if (strcmp(registry->tools[i]->name, name) == 0) {
return registry->tools[i];
@@ -64,13 +53,10 @@ tool_t *tool_registry_find(tool_registry_t *registry, const char *name) {
}
return NULL;
}
struct json_object *tool_registry_get_descriptions(tool_registry_t *registry) {
if (!registry) return NULL;
struct json_object *array = json_object_new_array();
if (!array) return NULL;
for (size_t i = 0; i < registry->count; i++) {
tool_t *tool = registry->tools[i];
if (tool->vtable->get_description) {
@@ -82,88 +68,112 @@ struct json_object *tool_registry_get_descriptions(tool_registry_t *registry) {
}
return array;
}
struct json_object *tool_registry_execute(tool_registry_t *registry,
struct json_object *tool_calls,
bool verbose) {
if (!registry || !tool_calls) return NULL;
struct json_object *results = json_object_new_array();
if (!results) return NULL;
int len = json_object_array_length(tool_calls);
if (len == 0) return results;
pthread_t *threads = calloc((size_t)len, sizeof(pthread_t));
tool_thread_args_t *t_args = calloc((size_t)len, sizeof(tool_thread_args_t));
struct json_object **result_objs = calloc((size_t)len, sizeof(struct json_object *));
bool *is_duplicate = calloc((size_t)len, sizeof(bool));
for (int i = 0; i < len; i++) {
struct json_object *call = json_object_array_get_idx(tool_calls, i);
result_objs[i] = json_object_new_object();
struct json_object *id_obj = json_object_object_get(call, "id");
if (id_obj) {
json_object_object_add(result_objs[i], "tool_call_id",
json_object_new_string(json_object_get_string(id_obj)));
}
json_object_object_add(result_objs[i], "role", json_object_new_string("tool"));
struct json_object *function_obj;
if (!json_object_object_get_ex(call, "function", &function_obj)) {
json_object_object_add(result_objs[i], "content", json_object_new_string("Error: missing function"));
continue;
}
const char *name = json_object_get_string(json_object_object_get(function_obj, "name"));
const char *args_json = json_object_get_string(json_object_object_get(function_obj, "arguments"));
// DEDUPLICATION LOGIC: Check if this exact call (name + args) appeared earlier in this batch
for (int j = 0; j < i; j++) {
struct json_object *prev_call = json_object_array_get_idx(tool_calls, j);
struct json_object *prev_func;
json_object_object_get_ex(prev_call, "function", &prev_func);
const char *prev_name = json_object_get_string(json_object_object_get(prev_func, "name"));
const char *prev_args = json_object_get_string(json_object_object_get(prev_func, "arguments"));
if (strcmp(name, prev_name) == 0 && strcmp(args_json, prev_args) == 0) {
is_duplicate[i] = true;
if (verbose) {
fprintf(stderr, " \033[1;33m[Registry] Redundant call to %s prevented.\033[0m\n", name);
}
break;
}
}
if (is_duplicate[i]) continue;
tool_t *tool = tool_registry_find(registry, name);
if (!tool) {
json_object_object_add(result_objs[i], "content", json_object_new_string("Error: tool not found"));
continue;
}
struct json_object *args = NULL;
struct json_object *args_obj;
if (json_object_object_get_ex(function_obj, "arguments", &args_obj)) {
args = json_tokener_parse(json_object_get_string(args_obj));
}
struct json_object *args = json_tokener_parse(args_json);
if (tool->vtable->print_action) {
tool->vtable->print_action(tool->name, args);
}
if (verbose && args) {
fprintf(stderr, " \033[2m[parallel] launching %s\033[0m\n", tool->name);
}
t_args[i].tool = tool;
t_args[i].args = args;
t_args[i].output = NULL;
pthread_create(&threads[i], NULL, tool_thread_func, &t_args[i]);
}
for (int i = 0; i < len; i++) {
if (threads[i]) {
pthread_join(threads[i], NULL);
for (int i = 0; i < len; i++) {
if (is_duplicate[i]) {
// Find the original result to copy it
struct json_object *curr_func;
json_object_object_get_ex(json_object_array_get_idx(tool_calls, i), "function", &curr_func);
const char *name = json_object_get_string(json_object_object_get(curr_func, "name"));
const char *args_json = json_object_get_string(json_object_object_get(curr_func, "arguments"));
for (int j = 0; j < i; j++) {
struct json_object *prev_func;
json_object_object_get_ex(json_object_array_get_idx(tool_calls, j), "function", &prev_func);
if (strcmp(name, json_object_get_string(json_object_object_get(prev_func, "name"))) == 0 &&
strcmp(args_json, json_object_get_string(json_object_object_get(prev_func, "arguments"))) == 0) {
struct json_object *orig_content;
if (json_object_object_get_ex(result_objs[j], "content", &orig_content)) {
json_object_object_add(result_objs[i], "content", json_object_get(orig_content));
} else {
// Original hasn't finished yet or failed
json_object_object_add(result_objs[i], "content", json_object_new_string("Result mirrored from previous parallel call."));
}
break;
}
}
} else {
if (threads[i]) {
pthread_join(threads[i], NULL);
}
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);
}
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]);
}
free(threads);
free(t_args);
free(result_objs);
free(is_duplicate);
return results;
}
}
+58 -17
View File
@@ -1,12 +1,14 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "agent.h"
#include "db.h"
#include "messages.h"
#include "r_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sqlite3.h>
typedef struct {
tool_t tool;
} tool_agent_t;
@@ -29,8 +31,25 @@ static struct json_object *tool_spawn_agent_get_description(void) {
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(goal, "description",
json_object_new_string("The specific task for the sub-agent. For Shared Memory, YOU MUST INCLUDE the current findings from PROJECT_KNOWLEDGE.md here so the agent has context."));
json_object_object_add(props, "goal", goal);
struct json_object *role = json_object_new_object();
json_object_object_add(role, "type", json_object_new_string("string"));
json_object_object_add(role, "description", json_object_new_string("The hierarchical role: 'Executive', 'Manager', or 'Worker'."));
struct json_object *role_enum = json_object_new_array();
json_object_array_add(role_enum, json_object_new_string("Executive"));
json_object_array_add(role_enum, json_object_new_string("Manager"));
json_object_array_add(role_enum, json_object_new_string("Worker"));
json_object_object_add(role, "enum", role_enum);
json_object_object_add(props, "role", role);
struct json_object *logic = json_object_new_object();
json_object_object_add(logic, "type", json_object_new_string("string"));
json_object_object_add(logic, "description", json_object_new_string("Formal logic justification for this order (MANDATORY for Audit Log)."));
json_object_object_add(props, "logic_justification", logic);
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."));
@@ -46,6 +65,8 @@ static struct json_object *tool_spawn_agent_get_description(void) {
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("role"));
json_object_array_add(required, json_object_new_string("logic_justification"));
json_object_array_add(required, json_object_new_string("max_subagents"));
json_object_array_add(required, json_object_new_string("async"));
json_object_object_add(params, "required", required);
@@ -74,36 +95,40 @@ static char *tool_spawn_agent_execute(tool_t *self, struct json_object *args) {
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.");
}
struct json_object *role_obj, *logic_obj;
const char *role_str = "Worker";
const char *logic_justification = "Direct assignment.";
if (json_object_object_get_ex(args, "role", &role_obj)) role_str = json_object_get_string(role_obj);
if (json_object_object_get_ex(args, "logic_justification", &logic_obj)) logic_justification = json_object_get_string(logic_obj);
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_base = NULL;
if (strcmp(persona_str, "researcher") == 0) {
type = TOOL_TYPE_RESEARCHER;
system_prompt_base = "You are a specialized Research Agent. Your goal is to find, extract, and summarize information. "
system_prompt_base = "You are a specialized Research Agent (CRO). Your goal is to find, extract, and summarize information. "
"You MUST provide high-density data. Placeholder text is FORBIDDEN. "
"Do not attempt to write or execute code unless it's for data analysis. "
"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"
"- Your output is for an Executive, not the final user.\n"
"- Provide exhaustive RAW DATA and detailed summaries (aim for 500+ words per topic).\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.";
"2. Aggregate results and synthesize with citations.";
} else if (strcmp(persona_str, "developer") == 0) {
type = TOOL_TYPE_DEVELOPER;
system_prompt_base = "You are a specialized Developer Agent. Your goal is to write, test, and debug code. "
system_prompt_base = "You are a specialized Developer Agent (CTO). Your goal is to write, test, and debug code. "
"CRITICAL: You are FORBIDDEN from using placeholder text like 'content here' or 'lorem ipsum'. "
"You MUST use the real data from the Research Report in PROJECT_KNOWLEDGE.md. "
"If data is missing, report a 'Blocker' to the Executive. "
"## 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.";
"- Create professional, content-rich, and functional code.\n"
"- Always verify your changes by reading the files back.";
} else if (strcmp(persona_str, "security") == 0) {
type = TOOL_TYPE_SECURITY;
system_prompt_base = "You are a specialized Security Auditor Agent. Your goal is to find vulnerabilities and perform security analysis. "
@@ -133,11 +158,14 @@ static char *tool_spawn_agent_execute(tool_t *self, struct json_object *args) {
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"
"Current date/time: %s\n\n"
"YOUR HIERARCHICAL ROLE: %s\n"
"LOGIC JUSTIFICATION FOR YOUR ASSIGNMENT: %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);
datetime, role_str, logic_justification, 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);
@@ -148,6 +176,19 @@ static char *tool_spawn_agent_execute(tool_t *self, struct json_object *args) {
messages_destroy(msgs);
return strdup("Error: Failed to create sub-agent");
}
// STATE-DRIVEN REGISTRATION
agent_set_id(agent, session_id);
agent_set_role(agent, role_str);
agent_set_manager_id(agent, "Executive-Apex");
db_handle db = db_open(NULL);
char *sql = sqlite3_mprintf("INSERT INTO agents (agent_id, role, manager_id, budget_limit_tokens) VALUES (%Q, %Q, %Q, 500000)",
agent_get_id(agent), agent_get_role(agent), agent_get_manager_id(agent));
db_execute(db, sql, NULL);
sqlite3_free(sql);
db_close(db);
agent_set_is_subagent(agent, true);
tool_registry_t *specialized_tools = tool_registry_get_specialized(type);
if (specialized_tools) {
+253
View File
@@ -0,0 +1,253 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "db.h"
#include "r_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/sha.h>
#include <sqlite3.h>
extern db_handle db_open(const char *path);
extern void db_close(db_handle db);
extern r_status_t db_execute(db_handle db, const char *sql, struct json_object **result);
static void compute_hash(const char *input, char *output) {
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((unsigned char*)input, strlen(input), hash);
for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(output + (i * 2), "%02x", hash[i]);
}
output[64] = '\0';
}
static char *gtr_check_task_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *desc_obj;
if (!json_object_object_get_ex(args, "task_description", &desc_obj)) return strdup("Error: missing task_description");
const char *desc = json_object_get_string(desc_obj);
char hash[65];
compute_hash(desc, hash);
db_handle db = db_open(NULL);
char *sql = sqlite3_mprintf("SELECT * FROM gtr WHERE task_hash = %Q", hash);
struct json_object *res = NULL;
db_execute(db, sql, &res);
sqlite3_free(sql);
db_close(db);
struct json_object *root = json_object_new_object();
json_object_object_add(root, "task_hash", json_object_new_string(hash));
if (res && json_object_array_length(res) > 0) {
json_object_object_add(root, "exists", json_object_new_boolean(1));
json_object_object_add(root, "task_data", json_object_get(json_object_array_get_idx(res, 0)));
} else {
json_object_object_add(root, "exists", json_object_new_boolean(0));
}
if (res) json_object_put(res);
char *out = strdup(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
json_object_put(root);
return out;
}
static char *gtr_register_task_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *desc_obj, *owner_obj;
if (!json_object_object_get_ex(args, "task_description", &desc_obj) ||
!json_object_object_get_ex(args, "owner_agent", &owner_obj)) return strdup("Error: missing args");
const char *desc = json_object_get_string(desc_obj);
char hash[65];
compute_hash(desc, hash);
db_handle db = db_open(NULL);
char *sql = sqlite3_mprintf("INSERT OR IGNORE INTO gtr (task_hash, task_description, status, owner_agent) VALUES (%Q, %Q, 'running', %Q)",
hash, desc, json_object_get_string(owner_obj));
struct json_object *res = NULL;
db_execute(db, sql, &res);
sqlite3_free(sql);
db_close(db);
if (res) json_object_put(res);
return strdup("Task registered in GTR.");
}
static char *gtr_update_task_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *hash_obj, *status_obj, *summary_obj;
if (!json_object_object_get_ex(args, "task_hash", &hash_obj) ||
!json_object_object_get_ex(args, "status", &status_obj) ||
!json_object_object_get_ex(args, "result_summary", &summary_obj)) return strdup("Error: missing args");
db_handle db = db_open(NULL);
char *sql = sqlite3_mprintf("UPDATE gtr SET status = %Q, result_summary = %Q, updated_at = CURRENT_TIMESTAMP WHERE task_hash = %Q",
json_object_get_string(status_obj),
json_object_get_string(summary_obj),
json_object_get_string(hash_obj));
struct json_object *res = NULL;
db_execute(db, sql, &res);
sqlite3_free(sql);
db_close(db);
if (res) json_object_put(res);
return strdup("GTR updated.");
}
static char *audit_log_order_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *issuer, *recipient, *order, *logic;
if (!json_object_object_get_ex(args, "issuer_agent", &issuer) ||
!json_object_object_get_ex(args, "recipient_agent", &recipient) ||
!json_object_object_get_ex(args, "order_description", &order) ||
!json_object_object_get_ex(args, "logic_justification", &logic)) return strdup("Error: missing args");
db_handle db = db_open(NULL);
char *sql = sqlite3_mprintf("INSERT INTO audit_log (issuer_agent, recipient_agent, order_description, logic_justification, status) VALUES (%Q, %Q, %Q, %Q, 'issued')",
json_object_get_string(issuer), json_object_get_string(recipient),
json_object_get_string(order), json_object_get_string(logic));
struct json_object *res = NULL;
db_execute(db, sql, &res);
sqlite3_free(sql);
db_close(db);
if (res) json_object_put(res);
return strdup("Order logged in Audit Log.");
}
static struct json_object *gtr_check_desc(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *f = json_object_new_object();
json_object_object_add(f, "name", json_object_new_string("gtr_check_task"));
json_object_object_add(f, "description", json_object_new_string("Query the Global Task Registry for a task hash to prevent redundant execution."));
struct json_object *p = json_object_new_object();
json_object_object_add(p, "type", json_object_new_string("object"));
struct json_object *props = json_object_new_object();
struct json_object *d = json_object_new_object();
json_object_object_add(d, "type", json_object_new_string("string"));
json_object_object_add(props, "task_description", d);
json_object_object_add(p, "properties", props);
struct json_object *req = json_object_new_array();
json_object_array_add(req, json_object_new_string("task_description"));
json_object_object_add(p, "required", req);
json_object_object_add(p, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(f, "parameters", p);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) json_object_object_add(f, "strict", json_object_new_boolean(1));
json_object_object_add(root, "function", f);
return root;
}
static struct json_object *gtr_reg_desc(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *f = json_object_new_object();
json_object_object_add(f, "name", json_object_new_string("gtr_register_task"));
json_object_object_add(f, "description", json_object_new_string("Register a new task in the Global Task Registry."));
struct json_object *p = json_object_new_object();
json_object_object_add(p, "type", json_object_new_string("object"));
struct json_object *props = json_object_new_object();
struct json_object *d = json_object_new_object();
json_object_object_add(d, "type", json_object_new_string("string"));
json_object_object_add(props, "task_description", d);
struct json_object *o = json_object_new_object();
json_object_object_add(o, "type", json_object_new_string("string"));
json_object_object_add(props, "owner_agent", o);
json_object_object_add(p, "properties", props);
struct json_object *req = json_object_new_array();
json_object_array_add(req, json_object_new_string("task_description"));
json_object_array_add(req, json_object_new_string("owner_agent"));
json_object_object_add(p, "required", req);
json_object_object_add(p, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(f, "parameters", p);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) json_object_object_add(f, "strict", json_object_new_boolean(1));
json_object_object_add(root, "function", f);
return root;
}
static struct json_object *audit_log_desc(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *f = json_object_new_object();
json_object_object_add(f, "name", json_object_new_string("audit_log_order"));
json_object_object_add(f, "description", json_object_new_string("Log an official order from a higher-level agent to a sub-agent with logic justification."));
struct json_object *p = json_object_new_object();
json_object_object_add(p, "type", json_object_new_string("object"));
struct json_object *props = json_object_new_object();
struct json_object *i = json_object_new_object();
json_object_object_add(i, "type", json_object_new_string("string"));
json_object_object_add(props, "issuer_agent", i);
struct json_object *r_ = json_object_new_object();
json_object_object_add(r_, "type", json_object_new_string("string"));
json_object_object_add(props, "recipient_agent", r_);
struct json_object *ord = json_object_new_object();
json_object_object_add(ord, "type", json_object_new_string("string"));
json_object_object_add(props, "order_description", ord);
struct json_object *log = json_object_new_object();
json_object_object_add(log, "type", json_object_new_string("string"));
json_object_object_add(props, "logic_justification", log);
json_object_object_add(p, "properties", props);
struct json_object *req = json_object_new_array();
json_object_array_add(req, json_object_new_string("issuer_agent"));
json_object_array_add(req, json_object_new_string("recipient_agent"));
json_object_array_add(req, json_object_new_string("order_description"));
json_object_array_add(req, json_object_new_string("logic_justification"));
json_object_object_add(p, "required", req);
json_object_object_add(p, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(f, "parameters", p);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) json_object_object_add(f, "strict", json_object_new_boolean(1));
json_object_object_add(root, "function", f);
return root;
}
static struct json_object *gtr_update_desc(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *f = json_object_new_object();
json_object_object_add(f, "name", json_object_new_string("gtr_update_task"));
json_object_object_add(f, "description", json_object_new_string("Update the status and result summary of an existing task in the Global Task Registry."));
struct json_object *p = json_object_new_object();
json_object_object_add(p, "type", json_object_new_string("object"));
struct json_object *props = json_object_new_object();
struct json_object *h = json_object_new_object();
json_object_object_add(h, "type", json_object_new_string("string"));
json_object_object_add(props, "task_hash", h);
struct json_object *s = json_object_new_object();
json_object_object_add(s, "type", json_object_new_string("string"));
json_object_object_add(props, "status", s);
struct json_object *sum = json_object_new_object();
json_object_object_add(sum, "type", json_object_new_string("string"));
json_object_object_add(props, "result_summary", sum);
json_object_object_add(p, "properties", props);
struct json_object *req = json_object_new_array();
json_object_array_add(req, json_object_new_string("task_hash"));
json_object_array_add(req, json_object_new_string("status"));
json_object_array_add(req, json_object_new_string("result_summary"));
json_object_object_add(p, "required", req);
json_object_object_add(p, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(f, "parameters", p);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) json_object_object_add(f, "strict", json_object_new_boolean(1));
json_object_object_add(root, "function", f);
return root;
}
static const tool_vtable_t gtr_check_vtable = { .get_description = gtr_check_desc, .execute = gtr_check_task_execute };
static const tool_vtable_t gtr_reg_vtable = { .get_description = gtr_reg_desc, .execute = gtr_register_task_execute };
static const tool_vtable_t gtr_update_vtable = { .get_description = gtr_update_desc, .execute = gtr_update_task_execute };
static const tool_vtable_t audit_log_vtable = { .get_description = audit_log_desc, .execute = audit_log_order_execute };
static tool_t gtr_check_tool = { .vtable = &gtr_check_vtable, .name = "gtr_check_task" };
static tool_t gtr_reg_tool = { .vtable = &gtr_reg_vtable, .name = "gtr_register_task" };
static tool_t gtr_update_tool = { .vtable = &gtr_update_vtable, .name = "gtr_update_task" };
static tool_t audit_log_tool = { .vtable = &audit_log_vtable, .name = "audit_log_order" };
tool_t *tool_gtr_check_task_create(void) { return &gtr_check_tool; }
tool_t *tool_gtr_register_task_create(void) { return &gtr_reg_tool; }
tool_t *tool_gtr_update_task_create(void) { return &gtr_update_tool; }
tool_t *tool_audit_log_order_create(void) { return &audit_log_tool; }
+8 -2
View File
@@ -63,6 +63,7 @@ static char *http_fetch_execute(tool_t *self, struct json_object *args) {
http_client_handle client = http_client_create(NULL);
if (!client) return strdup("Failed to create HTTP client.");
http_client_set_show_spinner(client, false);
char *response = NULL;
r_status_t status = http_get(client, json_object_get_string(url_obj), &response);
@@ -123,15 +124,20 @@ static void web_search_print_action(const char *name, struct json_object *args)
static char *do_web_search(const char *query) {
if (!query) return strdup("Query cannot be NULL.");
char *q_encoded = curl_easy_escape(NULL, query, 0);
CURL *curl_handle = curl_easy_init();
if (!curl_handle) return strdup("Failed to initialize curl for encoding.");
char *q_encoded = curl_easy_escape(curl_handle, query, 0);
curl_easy_cleanup(curl_handle);
if (!q_encoded) return strdup("Failed to encode query.");
char url[4096];
snprintf(url, sizeof(url), "https://rexa.molodetz.nl/ai?q=%s", q_encoded);
snprintf(url, sizeof(url), "https://rsearch.app.molodetz.nl/search?query=%s", q_encoded);
curl_free(q_encoded);
http_client_handle client = http_client_create(NULL);
if (!client) return strdup("Failed to create HTTP client.");
http_client_set_show_spinner(client, false);
char *response = NULL;
r_status_t status = http_get(client, url, &response);
+202
View File
@@ -0,0 +1,202 @@
// retoor <retoor@molodetz.nl>
#include "tool.h"
#include "db.h"
#include "http_client.h"
#include "agent.h"
#include "r_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/sha.h>
#include <sqlite3.h>
extern db_handle db_open(const char *path);
extern void db_close(db_handle db);
extern r_status_t db_execute(db_handle db, const char *sql, struct json_object **result);
static void compute_url_hash(const char *url, char *output) {
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256((unsigned char*)url, strlen(url), hash);
for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(output + (i * 2), "%02x", hash[i]);
}
output[64] = '\0';
}
static char *research_dispatcher_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *urls_obj, *batch_id_obj;
if (!json_object_object_get_ex(args, "urls", &urls_obj) ||
!json_object_object_get_ex(args, "batch_id", &batch_id_obj)) return strdup("Error: missing urls or batch_id");
const char *batch_id = json_object_get_string(batch_id_obj);
db_handle db = db_open(NULL);
int count = json_object_array_length(urls_obj);
int dispatched = 0;
for (int i = 0; i < count; i++) {
const char *url = json_object_get_string(json_object_array_get_idx(urls_obj, i));
char hash[65];
compute_url_hash(url, hash);
char *sql = sqlite3_mprintf("INSERT OR IGNORE INTO research_tasks (url_hash, url, status, batch_id) VALUES (%Q, %Q, 'pending', %Q)",
hash, url, batch_id);
db_execute(db, sql, NULL);
sqlite3_free(sql);
dispatched++;
}
db_close(db);
char buf[128];
snprintf(buf, sizeof(buf), "Dispatched %d URLs to research_tasks table for batch %s.", dispatched, batch_id);
return strdup(buf);
}
static char *fetch_and_scrape_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *url_obj;
if (!json_object_object_get_ex(args, "url", &url_obj)) return strdup("Error: missing url");
const char *url = json_object_get_string(url_obj);
char hash[65];
compute_url_hash(url, hash);
db_handle db = db_open(NULL);
// Atomic Lock / Claim
char *lock_sql = sqlite3_mprintf("UPDATE research_tasks SET status = 'processing', updated_at = CURRENT_TIMESTAMP WHERE url_hash = %Q AND (status = 'pending' OR (status = 'processing' AND updated_at < datetime('now', '-10 minutes')))", hash);
db_execute(db, lock_sql, NULL);
sqlite3_free(lock_sql);
http_client_handle http = http_client_create(NULL);
char *content = NULL;
r_status_t st = http_get(http, url, &content);
http_client_destroy(http);
if (st != R_SUCCESS || !content) {
char *fail_sql = sqlite3_mprintf("UPDATE research_tasks SET status = 'failed' WHERE url_hash = %Q", hash);
db_execute(db, fail_sql, NULL);
sqlite3_free(fail_sql);
db_close(db);
return strdup("Error: Failed to fetch URL content.");
}
// Truncate for summary (simplified for MVP)
char summary[2048];
strncpy(summary, content, 2047);
summary[2047] = '\0';
char *update_sql = sqlite3_mprintf("UPDATE research_tasks SET status = 'completed', summary = %Q, updated_at = CURRENT_TIMESTAMP WHERE url_hash = %Q", summary, hash);
db_execute(db, update_sql, NULL);
sqlite3_free(update_sql);
db_close(db);
free(content);
return strdup("URL fetched, scraped, and summary stored in research_tasks.");
}
static char *suggest_subtask_execute(tool_t *self, struct json_object *args) {
(void)self;
struct json_object *url_obj, *reason_obj;
if (!json_object_object_get_ex(args, "url", &url_obj) ||
!json_object_object_get_ex(args, "reason", &reason_obj)) return strdup("Error: missing url or reason");
fprintf(stderr, "\033[1;34m[Escalation] Worker suggests new subtask: %s (Reason: %s)\033[0m\n",
json_object_get_string(url_obj), json_object_get_string(reason_obj));
return strdup("Subtask suggestion logged and escalated to Manager.");
}
static struct json_object *dispatch_desc(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *f = json_object_new_object();
json_object_object_add(f, "name", json_object_new_string("research_dispatcher"));
json_object_object_add(f, "description", json_object_new_string("Manager tool: Fan-out a list of URLs into the research_tasks queue."));
struct json_object *p = json_object_new_object();
json_object_object_add(p, "type", json_object_new_string("object"));
struct json_object *props = json_object_new_object();
struct json_object *u = json_object_new_object();
json_object_object_add(u, "type", json_object_new_string("array"));
json_object_object_add(u, "items", json_object_new_object());
json_object_object_add(json_object_object_get(u, "items"), "type", json_object_new_string("string"));
json_object_object_add(props, "urls", u);
struct json_object *b = json_object_new_object();
json_object_object_add(b, "type", json_object_new_string("string"));
json_object_object_add(props, "batch_id", b);
json_object_object_add(p, "properties", props);
struct json_object *req = json_object_new_array();
json_object_array_add(req, json_object_new_string("urls"));
json_object_array_add(req, json_object_new_string("batch_id"));
json_object_object_add(p, "required", req);
json_object_object_add(p, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(f, "parameters", p);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) json_object_object_add(f, "strict", json_object_new_boolean(1));
json_object_object_add(root, "function", f);
return root;
}
static struct json_object *fetch_desc(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *f = json_object_new_object();
json_object_object_add(f, "name", json_object_new_string("fetch_and_scrape"));
json_object_object_add(f, "description", json_object_new_string("Worker tool: Fetch a single URL, scrape it, and save the summary to the database."));
struct json_object *p = json_object_new_object();
json_object_object_add(p, "type", json_object_new_string("object"));
struct json_object *props = json_object_new_object();
struct json_object *u = json_object_new_object();
json_object_object_add(u, "type", json_object_new_string("string"));
json_object_object_add(props, "url", u);
json_object_object_add(p, "properties", props);
struct json_object *req = json_object_new_array();
json_object_array_add(req, json_object_new_string("url"));
json_object_object_add(p, "required", req);
json_object_object_add(p, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(f, "parameters", p);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) json_object_object_add(f, "strict", json_object_new_boolean(1));
json_object_object_add(root, "function", f);
return root;
}
static struct json_object *suggest_desc(void) {
struct json_object *root = json_object_new_object();
json_object_object_add(root, "type", json_object_new_string("function"));
struct json_object *f = json_object_new_object();
json_object_object_add(f, "name", json_object_new_string("suggest_subtask"));
json_object_object_add(f, "description", json_object_new_string("Worker tool: Suggest a new URL discovered within a page for future research. Escalates to Manager."));
struct json_object *p = json_object_new_object();
json_object_object_add(p, "type", json_object_new_string("object"));
struct json_object *props = json_object_new_object();
struct json_object *u = json_object_new_object();
json_object_object_add(u, "type", json_object_new_string("string"));
json_object_object_add(props, "url", u);
struct json_object *r = json_object_new_object();
json_object_object_add(r, "type", json_object_new_string("string"));
json_object_object_add(props, "reason", r);
json_object_object_add(p, "properties", props);
struct json_object *req = json_object_new_array();
json_object_array_add(req, json_object_new_string("url"));
json_object_array_add(req, json_object_new_string("reason"));
json_object_object_add(p, "required", req);
json_object_object_add(p, "additionalProperties", json_object_new_boolean(0));
json_object_object_add(f, "parameters", p);
r_config_handle cfg = r_config_get_instance();
if (r_config_use_strict(cfg)) json_object_object_add(f, "strict", json_object_new_boolean(1));
json_object_object_add(root, "function", f);
return root;
}
static const tool_vtable_t dispatch_vtable = { .get_description = dispatch_desc, .execute = research_dispatcher_execute };
static const tool_vtable_t fetch_vtable = { .get_description = fetch_desc, .execute = fetch_and_scrape_execute };
static const tool_vtable_t suggest_vtable = { .get_description = suggest_desc, .execute = suggest_subtask_execute };
static tool_t dispatch_tool = { .vtable = &dispatch_vtable, .name = "research_dispatcher" };
static tool_t fetch_tool = { .vtable = &fetch_vtable, .name = "fetch_and_scrape" };
static tool_t suggest_tool = { .vtable = &suggest_vtable, .name = "suggest_subtask" };
tool_t *tool_research_dispatcher_create(void) { return &dispatch_tool; }
tool_t *tool_fetch_and_scrape_create(void) { return &fetch_tool; }
tool_t *tool_suggest_subtask_create(void) { return &suggest_tool; }
+14
View File
@@ -26,6 +26,13 @@ extern tool_t *tool_file_apply_patch_create(void);
extern tool_t *tool_process_monitor_create(void);
extern tool_t *tool_process_get_status_create(void);
extern tool_t *tool_process_terminate_create(void);
extern tool_t *tool_gtr_check_task_create(void);
extern tool_t *tool_gtr_register_task_create(void);
extern tool_t *tool_gtr_update_task_create(void);
extern tool_t *tool_audit_log_order_create(void);
extern tool_t *tool_research_dispatcher_create(void);
extern tool_t *tool_fetch_and_scrape_create(void);
extern tool_t *tool_suggest_subtask_create(void);
extern tool_t *tool_network_check_create(void);
extern tool_t *tool_dns_lookup_create(void);
extern tool_t *tool_network_port_scan_create(void);
@@ -67,6 +74,13 @@ tool_registry_t *tools_get_registry(void) {
tool_registry_register(global_registry, tool_process_monitor_create());
tool_registry_register(global_registry, tool_process_get_status_create());
tool_registry_register(global_registry, tool_process_terminate_create());
tool_registry_register(global_registry, tool_gtr_check_task_create());
tool_registry_register(global_registry, tool_gtr_register_task_create());
tool_registry_register(global_registry, tool_gtr_update_task_create());
tool_registry_register(global_registry, tool_audit_log_order_create());
tool_registry_register(global_registry, tool_research_dispatcher_create());
tool_registry_register(global_registry, tool_fetch_and_scrape_create());
tool_registry_register(global_registry, tool_suggest_subtask_create());
tool_registry_register(global_registry, tool_network_check_create());
tool_registry_register(global_registry, tool_dns_lookup_create());
tool_registry_register(global_registry, tool_network_port_scan_create());