fix: add null check for avatar pointer before dereference in user profile loader
This commit is contained in:
+89
-97
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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 = >r_check_vtable, .name = "gtr_check_task" };
|
||||
static tool_t gtr_reg_tool = { .vtable = >r_reg_vtable, .name = "gtr_register_task" };
|
||||
static tool_t gtr_update_tool = { .vtable = >r_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 >r_check_tool; }
|
||||
tool_t *tool_gtr_register_task_create(void) { return >r_reg_tool; }
|
||||
tool_t *tool_gtr_update_task_create(void) { return >r_update_tool; }
|
||||
tool_t *tool_audit_log_order_create(void) { return &audit_log_tool; }
|
||||
@@ -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);
|
||||
|
||||
@@ -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; }
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user