feat: add markdown module header and async bash execution result structs
Introduce `include/markdown.h` with retoor copyright header and add `r_process_result_t` struct plus `r_bash_execute_ext` / `r_process_result_free` declarations to `include/bash_executor.h` for advanced async process handling.
This commit is contained in:
+122
-150
@@ -5,6 +5,7 @@
|
||||
#include "r_config.h"
|
||||
#include "tool.h"
|
||||
#include "context_manager.h"
|
||||
#include "markdown.h"
|
||||
#include <json-c/json.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -65,139 +66,6 @@ static const char *passive_phrases[] = {
|
||||
|
||||
extern tool_registry_t *tools_get_registry(void);
|
||||
|
||||
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) {
|
||||
free(agent);
|
||||
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;
|
||||
agent->max_tool_retries = AGENT_MAX_TOOL_RETRIES;
|
||||
agent->state = AGENT_STATE_IDLE;
|
||||
agent->start_time = time(NULL);
|
||||
agent->verbose = r_config_is_verbose(cfg);
|
||||
|
||||
if (messages) {
|
||||
agent->messages = messages;
|
||||
agent->owns_messages = false;
|
||||
} else {
|
||||
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;
|
||||
for (int i = 0; i < messages_count(agent->messages); i++) {
|
||||
struct json_object *msg = messages_get_object(agent->messages, i);
|
||||
struct json_object *role;
|
||||
if (json_object_object_get_ex(msg, "role", &role)) {
|
||||
const char *role_str = json_object_get_string(role);
|
||||
if (role_str && strcmp(role_str, "system") == 0) {
|
||||
has_system = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_system) {
|
||||
messages_add(agent->messages, "system",
|
||||
"You are an autonomous AI agent with full system access through function calling. "
|
||||
"You have the spawn_agent tool to create specialized sub-agents for different tasks. "
|
||||
"Use spawn_agent extensively for: "
|
||||
"- research tasks (researcher persona) "
|
||||
"- development tasks (developer persona) "
|
||||
"- security audits (security persona) "
|
||||
"When web_search returns results with URLs, spawn researcher agents to fetch and analyze the content. "
|
||||
"Always break complex tasks into sub-tasks using agents for better orchestration. "
|
||||
"Continue iterating until goals are fully achieved, using appropriate tools and agents.");
|
||||
}
|
||||
} else if (system_msg && *system_msg) {
|
||||
bool has_system = false;
|
||||
for (int i = 0; i < messages_count(agent->messages); i++) {
|
||||
struct json_object *msg = messages_get_object(agent->messages, i);
|
||||
struct json_object *role;
|
||||
if (json_object_object_get_ex(msg, "role", &role)) {
|
||||
const char *role_str = json_object_get_string(role);
|
||||
if (role_str && strcmp(role_str, "system") == 0) {
|
||||
has_system = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!has_system) {
|
||||
messages_add(agent->messages, "system", system_msg);
|
||||
}
|
||||
}
|
||||
|
||||
agent->http = http_client_create(r_config_get_api_key(cfg));
|
||||
if (!agent->http) {
|
||||
if (agent->owns_messages) {
|
||||
messages_destroy(agent->messages);
|
||||
}
|
||||
free(agent->goal);
|
||||
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->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;
|
||||
}
|
||||
|
||||
static void agent_set_error(agent_handle agent, const char *error) {
|
||||
if (!agent) return;
|
||||
free(agent->last_error);
|
||||
@@ -215,10 +83,11 @@ static char *agent_build_request(agent_handle agent, const char *role, const cha
|
||||
|
||||
if (role && message) {
|
||||
messages_add(agent->messages, role, message);
|
||||
if (r_config_use_tools(cfg) && agent->tools) {
|
||||
json_object_object_add(root, "tools",
|
||||
tool_registry_get_descriptions(agent->tools));
|
||||
}
|
||||
}
|
||||
|
||||
if (r_config_use_tools(cfg) && agent->tools) {
|
||||
json_object_object_add(root, "tools",
|
||||
tool_registry_get_descriptions(agent->tools));
|
||||
}
|
||||
|
||||
json_object_object_add(root, "messages",
|
||||
@@ -331,7 +200,6 @@ static bool agent_response_indicates_incomplete(const char *content) {
|
||||
}
|
||||
|
||||
// Check for passive/closing phrases (Overrides incomplete indicators)
|
||||
// Example: "I will be here if you need me." -> Contains "I will" but is passive.
|
||||
for (int i = 0; passive_phrases[i]; i++) {
|
||||
if (strcasestr(content, passive_phrases[i])) return false;
|
||||
}
|
||||
@@ -353,6 +221,113 @@ 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) {
|
||||
free(agent);
|
||||
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;
|
||||
agent->max_tool_retries = AGENT_MAX_TOOL_RETRIES;
|
||||
agent->state = AGENT_STATE_IDLE;
|
||||
agent->start_time = time(NULL);
|
||||
agent->verbose = r_config_is_verbose(cfg);
|
||||
|
||||
if (messages) {
|
||||
agent->messages = messages;
|
||||
agent->owns_messages = false;
|
||||
} else {
|
||||
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;
|
||||
for (int i = 0; i < messages_count(agent->messages); i++) {
|
||||
struct json_object *msg = messages_get_object(agent->messages, i);
|
||||
struct json_object *role;
|
||||
if (json_object_object_get_ex(msg, "role", &role)) {
|
||||
const char *role_str = json_object_get_string(role);
|
||||
if (role_str && strcmp(role_str, "system") == 0) {
|
||||
has_system = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!has_system) {
|
||||
messages_add(agent->messages, "system", system_msg);
|
||||
}
|
||||
}
|
||||
|
||||
agent->http = http_client_create(r_config_get_api_key(cfg));
|
||||
if (!agent->http) {
|
||||
if (agent->owns_messages) {
|
||||
messages_destroy(agent->messages);
|
||||
}
|
||||
free(agent->goal);
|
||||
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->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;
|
||||
}
|
||||
|
||||
char *agent_run(agent_handle agent, const char *user_message) {
|
||||
if (!agent) return NULL;
|
||||
|
||||
@@ -384,14 +359,14 @@ char *agent_run(agent_handle agent, const char *user_message) {
|
||||
if (agent->iteration_count > agent->max_iterations) {
|
||||
agent->state = AGENT_STATE_MAX_ITERATIONS;
|
||||
agent_set_error(agent, "Maximum iterations reached");
|
||||
if (agent->verbose) {
|
||||
if (agent->verbose && !agent->is_subagent) {
|
||||
fprintf(stderr, "[Agent] Max iterations (%d) reached\n", agent->max_iterations);
|
||||
}
|
||||
free(json_data);
|
||||
break;
|
||||
}
|
||||
|
||||
if (agent->verbose) {
|
||||
if (agent->verbose && !agent->is_subagent) {
|
||||
fprintf(stderr, "[Agent] Iteration %d/%d\n",
|
||||
agent->iteration_count, agent->max_iterations);
|
||||
}
|
||||
@@ -404,7 +379,6 @@ char *agent_run(agent_handle agent, const char *user_message) {
|
||||
free(json_data);
|
||||
json_data = agent_build_request(agent, NULL, NULL);
|
||||
agent->state = AGENT_STATE_RUNNING;
|
||||
// Don't increment iteration_count for retries due to context
|
||||
agent->iteration_count--;
|
||||
continue;
|
||||
} else {
|
||||
@@ -424,7 +398,7 @@ char *agent_run(agent_handle agent, const char *user_message) {
|
||||
agent_set_error(agent, "API request failed after retries");
|
||||
break;
|
||||
}
|
||||
if (agent->verbose) {
|
||||
if (agent->verbose && !agent->is_subagent) {
|
||||
fprintf(stderr, "[Agent] API error, retry %d/%d\n",
|
||||
agent->tool_retry_count, agent->max_tool_retries);
|
||||
}
|
||||
@@ -442,9 +416,7 @@ char *agent_run(agent_handle agent, const char *user_message) {
|
||||
|
||||
char *content = agent_get_content(choice);
|
||||
if (content && *content) {
|
||||
// Print content immediately to the user (only if NOT a sub-agent)
|
||||
if (!agent->is_subagent) {
|
||||
extern void parse_markdown_to_ansi(const char *content);
|
||||
parse_markdown_to_ansi(content);
|
||||
printf("\n");
|
||||
}
|
||||
@@ -464,7 +436,7 @@ char *agent_run(agent_handle agent, const char *user_message) {
|
||||
|
||||
bool has_tools = agent_has_tool_calls(choice);
|
||||
|
||||
if (agent->verbose) {
|
||||
if (agent->verbose && !agent->is_subagent) {
|
||||
fprintf(stderr, "[Agent] has_tool_calls=%s\n", has_tools ? "true" : "false");
|
||||
}
|
||||
|
||||
@@ -473,7 +445,7 @@ char *agent_run(agent_handle agent, const char *user_message) {
|
||||
|
||||
struct json_object *tool_calls = agent_get_tool_calls(choice);
|
||||
|
||||
if (agent->verbose) {
|
||||
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);
|
||||
}
|
||||
@@ -496,7 +468,7 @@ char *agent_run(agent_handle agent, const char *user_message) {
|
||||
}
|
||||
|
||||
} else if (content && agent_response_indicates_incomplete(content)) {
|
||||
if (agent->verbose) {
|
||||
if (agent->verbose && !agent->is_subagent) {
|
||||
fprintf(stderr, "[Agent] Response indicates incomplete work, auto-continuing\n");
|
||||
}
|
||||
|
||||
@@ -530,7 +502,7 @@ char *agent_chat(const char *user_message, messages_handle messages) {
|
||||
char *response = agent_run(agent, user_message);
|
||||
|
||||
if (agent->verbose && agent->state != AGENT_STATE_COMPLETED && agent->last_error) {
|
||||
fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
|
||||
if (!agent->is_subagent) fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
|
||||
}
|
||||
|
||||
agent_destroy(agent);
|
||||
@@ -545,9 +517,9 @@ char *agent_chat_with_limit(const char *user_message, int max_iterations, messag
|
||||
char *response = agent_run(agent, user_message);
|
||||
|
||||
if (agent->verbose && agent->state != AGENT_STATE_COMPLETED && agent->last_error) {
|
||||
fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
|
||||
if (!agent->is_subagent) fprintf(stderr, "[Agent] Error: %s\n", agent->last_error);
|
||||
}
|
||||
|
||||
agent_destroy(agent);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
+131
-154
@@ -17,182 +17,159 @@
|
||||
|
||||
#define DEFAULT_TIMEOUT 300
|
||||
|
||||
char *r_bash_execute(const char *command, bool interactive, int timeout_seconds) {
|
||||
if (!command) {
|
||||
return strdup("Error: null command");
|
||||
}
|
||||
void r_process_result_free(r_process_result_t *res) {
|
||||
if (!res) return;
|
||||
free(res->output);
|
||||
free(res->log_path);
|
||||
free(res);
|
||||
}
|
||||
|
||||
if (timeout_seconds <= 0) {
|
||||
timeout_seconds = DEFAULT_TIMEOUT;
|
||||
}
|
||||
static char *get_log_path(int pid) {
|
||||
char *path = NULL;
|
||||
if (asprintf(&path, "/tmp/r_process_%d.log", pid) == -1) return NULL;
|
||||
return path;
|
||||
}
|
||||
|
||||
size_t len = strlen(command);
|
||||
char *cmd_with_nl = malloc(len + 2);
|
||||
if (!cmd_with_nl) {
|
||||
return strdup("Error: memory allocation failed");
|
||||
}
|
||||
r_process_result_t *r_bash_execute_ext(const char *command, int timeout_seconds, bool async) {
|
||||
if (!command) return NULL;
|
||||
|
||||
strcpy(cmd_with_nl, command);
|
||||
if (len > 0 && cmd_with_nl[len - 1] != '\n') {
|
||||
cmd_with_nl[len] = '\n';
|
||||
cmd_with_nl[len + 1] = '\0';
|
||||
} else if (len == 0) {
|
||||
cmd_with_nl[0] = '\n';
|
||||
cmd_with_nl[1] = '\0';
|
||||
}
|
||||
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) {
|
||||
free(cmd_with_nl);
|
||||
return strdup("Error: failed to create temp script");
|
||||
res->output = strdup("Error: failed to create temp script");
|
||||
return res;
|
||||
}
|
||||
|
||||
if (write(script_fd, cmd_with_nl, strlen(cmd_with_nl)) == -1) {
|
||||
close(script_fd);
|
||||
free(cmd_with_nl);
|
||||
unlink(tmp_script);
|
||||
return strdup("Error: failed to write to temp script");
|
||||
}
|
||||
|
||||
dprintf(script_fd, "%s\n", command);
|
||||
close(script_fd);
|
||||
free(cmd_with_nl);
|
||||
|
||||
char *output = NULL;
|
||||
size_t total_size = 0;
|
||||
int pipe_fds[2];
|
||||
if (pipe(pipe_fds) == -1) {
|
||||
unlink(tmp_script);
|
||||
res->output = strdup("Error: pipe failed");
|
||||
return res;
|
||||
}
|
||||
|
||||
if (interactive) {
|
||||
// For interactive mode, we still use system() but it doesn't easily support capturing output while timing out
|
||||
// Given the requirement, we'll try to use a simple timeout for interactive too if we can,
|
||||
// but typically interactive means user is at it.
|
||||
// However, user said "prevent hanging processes".
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
close(pipe_fds[0]);
|
||||
close(pipe_fds[1]);
|
||||
unlink(tmp_script);
|
||||
res->output = strdup("Error: fork failed");
|
||||
return res;
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// Child
|
||||
setsid(); // New session to prevent signals to parent
|
||||
close(pipe_fds[0]);
|
||||
|
||||
char *run_cmd = NULL;
|
||||
if (asprintf(&run_cmd, "timeout %ds bash %s", timeout_seconds, tmp_script) == -1) {
|
||||
unlink(tmp_script);
|
||||
return strdup("Error: asprintf failed");
|
||||
}
|
||||
int status = system(run_cmd);
|
||||
free(run_cmd);
|
||||
// Setup log file for child
|
||||
char *log_p = get_log_path(getpid());
|
||||
int log_fd = open(log_p, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
free(log_p);
|
||||
|
||||
if (WIFEXITED(status) && WEXITSTATUS(status) == 124) {
|
||||
output = strdup("Error: Command timed out in interactive mode.");
|
||||
if (log_fd != -1) {
|
||||
dup2(log_fd, STDOUT_FILENO);
|
||||
dup2(log_fd, STDERR_FILENO);
|
||||
close(log_fd);
|
||||
} else {
|
||||
if (asprintf(&output, "Command exited with status %d", status) == -1) {
|
||||
output = strdup("Command completed.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int pipe_fds[2];
|
||||
if (pipe(pipe_fds) == -1) {
|
||||
unlink(tmp_script);
|
||||
return strdup("Error: pipe failed");
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
close(pipe_fds[0]);
|
||||
close(pipe_fds[1]);
|
||||
unlink(tmp_script);
|
||||
return strdup("Error: fork failed");
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
// Child
|
||||
close(pipe_fds[0]);
|
||||
dup2(pipe_fds[1], STDOUT_FILENO);
|
||||
dup2(pipe_fds[1], STDERR_FILENO);
|
||||
close(pipe_fds[1]);
|
||||
|
||||
char *args[] = {"bash", tmp_script, NULL};
|
||||
execvp("bash", args);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Parent
|
||||
close(pipe_fds[1]);
|
||||
int out_fd = pipe_fds[0];
|
||||
|
||||
struct poll_pfd {
|
||||
int fd;
|
||||
short events;
|
||||
short revents;
|
||||
} pfd;
|
||||
pfd.fd = out_fd;
|
||||
pfd.events = POLLIN;
|
||||
// Also pipe back to parent if possible (redundant but safe for short commands)
|
||||
// Actually, let's just use log file for everything.
|
||||
|
||||
close(pipe_fds[1]);
|
||||
|
||||
time_t start_time = time(NULL);
|
||||
bool timed_out = false;
|
||||
|
||||
while (true) {
|
||||
time_t now = time(NULL);
|
||||
int remaining = timeout_seconds - (int)(now - start_time);
|
||||
if (remaining <= 0) {
|
||||
timed_out = true;
|
||||
break;
|
||||
}
|
||||
|
||||
int ret = poll((struct pollfd *)&pfd, 1, remaining * 1000);
|
||||
if (ret == -1) {
|
||||
if (errno == EINTR) continue;
|
||||
break;
|
||||
}
|
||||
if (ret == 0) {
|
||||
timed_out = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pfd.revents & POLLIN) {
|
||||
char buffer[4096];
|
||||
ssize_t bytes = read(out_fd, buffer, sizeof(buffer) - 1);
|
||||
if (bytes <= 0) break;
|
||||
buffer[bytes] = '\0';
|
||||
|
||||
// Print to stderr for user
|
||||
fprintf(stderr, "\033[2m%s\033[0m", buffer);
|
||||
fflush(stderr);
|
||||
|
||||
char *new_output = realloc(output, total_size + (size_t)bytes + 1);
|
||||
if (!new_output) {
|
||||
break;
|
||||
}
|
||||
output = new_output;
|
||||
memcpy(output + total_size, buffer, (size_t)bytes);
|
||||
total_size += (size_t)bytes;
|
||||
output[total_size] = '\0';
|
||||
} else if (pfd.revents & (POLLHUP | POLLERR)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (timed_out) {
|
||||
kill(-pid, SIGKILL); // Kill process group if possible
|
||||
kill(pid, SIGKILL);
|
||||
|
||||
const char *timeout_msg = "\n[Error: Command timed out after %d seconds]\n";
|
||||
char *msg = NULL;
|
||||
if (asprintf(&msg, timeout_msg, timeout_seconds) != -1) {
|
||||
size_t msg_len = strlen(msg);
|
||||
char *new_output = realloc(output, total_size + msg_len + 1);
|
||||
if (new_output) {
|
||||
output = new_output;
|
||||
strcpy(output + total_size, msg);
|
||||
total_size += msg_len;
|
||||
}
|
||||
free(msg);
|
||||
}
|
||||
|
||||
fprintf(stderr, "\033[1;31m%s\033[0m", "\n[Timeout reached, process terminated]\n");
|
||||
}
|
||||
|
||||
close(out_fd);
|
||||
waitpid(pid, NULL, WNOHANG);
|
||||
char *args[] = {"bash", tmp_script, NULL};
|
||||
execvp("bash", args);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!output) {
|
||||
output = strdup("");
|
||||
// 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);
|
||||
|
||||
// Read new content from log file and print to stdout for the user
|
||||
FILE *f_tail = fopen(res->log_path, "r");
|
||||
if (f_tail) {
|
||||
fseek(f_tail, last_read_pos, SEEK_SET);
|
||||
char tail_buf[4096];
|
||||
while (fgets(tail_buf, sizeof(tail_buf), f_tail)) {
|
||||
fprintf(stdout, "[%d]\t %s", pid, tail_buf);
|
||||
fflush(stdout);
|
||||
}
|
||||
last_read_pos = ftell(f_tail);
|
||||
fclose(f_tail);
|
||||
}
|
||||
|
||||
if (ret == pid) {
|
||||
res->is_running = false;
|
||||
res->exit_status = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
|
||||
break;
|
||||
} else if (ret == -1) {
|
||||
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) {
|
||||
fseek(log_f, 0, SEEK_END);
|
||||
long size = ftell(log_f);
|
||||
rewind(log_f);
|
||||
if (size >= 0) {
|
||||
res->output = malloc((size_t)size + 1);
|
||||
if (res->output) {
|
||||
size_t rs = fread(res->output, 1, (size_t)size, log_f);
|
||||
res->output[rs] = '\0';
|
||||
}
|
||||
}
|
||||
fclose(log_f);
|
||||
}
|
||||
|
||||
if (!res->output) res->output = strdup("");
|
||||
|
||||
unlink(tmp_script);
|
||||
return output;
|
||||
}
|
||||
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);
|
||||
char *out = strdup(res->output);
|
||||
r_process_result_free(res);
|
||||
return out;
|
||||
}
|
||||
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
// 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.
|
||||
*/
|
||||
static int is_keyword(const char *word) {
|
||||
const char *keywords[] = {
|
||||
"int", "float", "double", "char", "void", "if", "else", "while", "for",
|
||||
"return", "struct", "printf", "let", "fn", "impl", "match", "enum", "trait", "use", "mod", "pub",
|
||||
"const", "static", "def", "class", "import", "from", "as", "with", "try", "except",
|
||||
"finally", "lambda", "async", "await", "public", "private", "protected", "interface", "extends",
|
||||
"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 == '_') {
|
||||
if (index < sizeof(buffer) - 1) buffer[index++] = *ptr++;
|
||||
else ptr++;
|
||||
}
|
||||
buffer[index] = '\0';
|
||||
if (is_keyword(buffer)) printf(FG_BLUE "%s" RESET FG_YELLOW, buffer);
|
||||
else printf("%s", buffer);
|
||||
index = 0;
|
||||
} else if (isdigit((unsigned char)*ptr)) {
|
||||
while (isdigit((unsigned char)*ptr)) {
|
||||
if (index < sizeof(buffer) - 1) buffer[index++] = *ptr++;
|
||||
else ptr++;
|
||||
}
|
||||
buffer[index] = '\0';
|
||||
printf(FG_CYAN "%s" RESET FG_YELLOW, buffer);
|
||||
index = 0;
|
||||
} else {
|
||||
putchar(*ptr);
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
while (*ptr && *ptr != '\n') ptr++;
|
||||
if (*ptr) ptr++;
|
||||
const char *code_start = ptr;
|
||||
const char *code_end = strstr(code_start, "```");
|
||||
if (code_end) {
|
||||
char *block_buffer = strndup(code_start, (size_t)(code_end - code_start));
|
||||
printf(FG_YELLOW);
|
||||
highlight_code(block_buffer);
|
||||
printf(RESET);
|
||||
free(block_buffer);
|
||||
ptr = code_end + 3;
|
||||
if (*ptr == '\n') ptr++;
|
||||
is_start_of_line = true;
|
||||
continue;
|
||||
} else {
|
||||
printf(FG_YELLOW);
|
||||
highlight_code(code_start);
|
||||
printf(RESET);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_start_of_line) {
|
||||
const char *line_start_ptr = ptr;
|
||||
int indent_level = 0;
|
||||
while (*ptr == ' ') { indent_level++; ptr++; }
|
||||
bool block_processed = true;
|
||||
if (strncmp(ptr, "###### ", 7) == 0) { printf(BOLD FG_YELLOW); ptr += 7; }
|
||||
else if (strncmp(ptr, "##### ", 6) == 0) { printf(BOLD FG_YELLOW); ptr += 6; }
|
||||
else if (strncmp(ptr, "#### ", 5) == 0) { printf(BOLD FG_YELLOW); ptr += 5; }
|
||||
else if (strncmp(ptr, "### ", 4) == 0) { printf(BOLD FG_YELLOW); ptr += 4; }
|
||||
else if (strncmp(ptr, "## ", 3) == 0) { printf(BOLD FG_YELLOW); ptr += 3; }
|
||||
else if (strncmp(ptr, "# ", 2) == 0) { printf(BOLD FG_YELLOW); ptr += 2; }
|
||||
else if ((strncmp(ptr, "---", 3) == 0 || strncmp(ptr, "***", 3) == 0) && (*(ptr + 3) == '\n' || *(ptr + 3) == '\0')) {
|
||||
printf(FG_CYAN "─────────────────────────────────────────────────────────────────────────" RESET "\n");
|
||||
ptr += 3; if (*ptr == '\n') ptr++; is_start_of_line = true; continue;
|
||||
} else if (strncmp(ptr, "> ", 2) == 0) {
|
||||
for (int i = 0; i < indent_level; i++) putchar(' ');
|
||||
printf(ITALIC FG_CYAN "▎ " RESET); ptr += 2; is_start_of_line = false; continue;
|
||||
} else if ((*ptr == '*' || *ptr == '-' || *ptr == '+') && *(ptr + 1) == ' ') {
|
||||
for (int i = 0; i < indent_level; i++) putchar(' ');
|
||||
printf(FG_MAGENTA "• " RESET); ptr += 2; is_start_of_line = false; continue;
|
||||
} else {
|
||||
const char *temp_ptr = ptr;
|
||||
while (isdigit((unsigned char)*temp_ptr)) temp_ptr++;
|
||||
if (temp_ptr > ptr && *temp_ptr == '.' && *(temp_ptr + 1) == ' ') {
|
||||
for (int i = 0; i < indent_level; i++) putchar(' ');
|
||||
printf(FG_MAGENTA); fwrite(ptr, 1, (size_t)(temp_ptr - ptr) + 1, stdout); printf(" " RESET);
|
||||
ptr = temp_ptr + 2; is_start_of_line = false; continue;
|
||||
} else { block_processed = false; ptr = line_start_ptr; }
|
||||
}
|
||||
if (block_processed) {
|
||||
while (*ptr && *ptr != '\n') putchar(*ptr++);
|
||||
printf(RESET "\n"); if (*ptr == '\n') ptr++;
|
||||
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;
|
||||
const char *end = strstr(ptr, marker);
|
||||
if (end) { fwrite(ptr, 1, (size_t)(end - ptr), stdout); ptr = end + 3; }
|
||||
else { fputs(ptr, stdout); ptr += strlen(ptr); }
|
||||
printf(RESET); continue;
|
||||
}
|
||||
if (strncmp(ptr, "**", 2) == 0 || strncmp(ptr, "__", 2) == 0) {
|
||||
const char *marker = strncmp(ptr, "**", 2) == 0 ? "**" : "__";
|
||||
printf(BOLD); ptr += 2;
|
||||
const char *end = strstr(ptr, marker);
|
||||
if (end) { fwrite(ptr, 1, (size_t)(end - ptr), stdout); ptr = end + 2; }
|
||||
else { fputs(ptr, stdout); ptr += strlen(ptr); }
|
||||
printf(RESET); continue;
|
||||
}
|
||||
if (strncmp(ptr, "~~", 2) == 0) {
|
||||
printf(STRIKETHROUGH); ptr += 2;
|
||||
const char *end = strstr(ptr, "~~");
|
||||
if (end) { fwrite(ptr, 1, (size_t)(end - ptr), stdout); ptr = end + 2; }
|
||||
else { fputs(ptr, stdout); ptr += strlen(ptr); }
|
||||
printf(RESET); continue;
|
||||
}
|
||||
if (strncmp(ptr, "==", 2) == 0) {
|
||||
printf(BG_YELLOW_FG_BLACK); ptr += 2;
|
||||
const char *end = strstr(ptr, "==");
|
||||
if (end) { fwrite(ptr, 1, (size_t)(end - ptr), stdout); ptr = end + 2; }
|
||||
else { fputs(ptr, stdout); ptr += strlen(ptr); }
|
||||
printf(RESET); continue;
|
||||
}
|
||||
if (*ptr == '`' && *(ptr + 1) != '`') {
|
||||
printf(FG_YELLOW); ptr++; const char *start = ptr;
|
||||
while (*ptr && *ptr != '`') ptr++;
|
||||
fwrite(start, 1, (size_t)(ptr - start), stdout); if (*ptr == '`') ptr++;
|
||||
printf(RESET); continue;
|
||||
}
|
||||
if (*ptr == '[') {
|
||||
const char *text_start = ptr + 1;
|
||||
const char *text_end = strchr(text_start, ']');
|
||||
if (text_end && *(text_end + 1) == '(') {
|
||||
const char *url_start = text_end + 2;
|
||||
const char *url_end = strchr(url_start, ')');
|
||||
if (url_end) {
|
||||
printf(FG_BLUE); fwrite(text_start, 1, (size_t)(text_end - text_start), stdout);
|
||||
printf(RESET " ("); printf(ITALIC FG_CYAN);
|
||||
fwrite(url_start, 1, (size_t)(url_end - url_start), stdout);
|
||||
printf(RESET ")"); ptr = url_end + 1; continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (*ptr == '\n') is_start_of_line = true;
|
||||
else if (!isspace((unsigned char)*ptr)) is_start_of_line = false;
|
||||
putchar(*ptr);
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
-351
@@ -1,351 +0,0 @@
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.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.
|
||||
* * @param word The word to check.
|
||||
* @return int 1 if it's a keyword, 0 otherwise.
|
||||
*/
|
||||
int is_keyword(const char *word) {
|
||||
// A comprehensive list of keywords from various popular languages.
|
||||
const char *keywords[] = {
|
||||
// C keywords
|
||||
"int", "float", "double", "char", "void", "if", "else", "while", "for",
|
||||
"return", "struct", "printf",
|
||||
// Rust keywords
|
||||
"let", "fn", "impl", "match", "enum", "trait", "use", "mod", "pub",
|
||||
"const", "static",
|
||||
// Python keywords
|
||||
"def", "class", "import", "from", "as", "with", "try", "except",
|
||||
"finally", "lambda", "async", "await",
|
||||
// Java keywords
|
||||
"public", "private", "protected", "class", "interface", "extends",
|
||||
"implements", "new", "static", "final", "synchronized",
|
||||
// JavaScript keywords
|
||||
"var", "let", "const", "function", "async", "await", "if", "else",
|
||||
"switch", "case", "break", "continue", "return",
|
||||
// C++ keywords
|
||||
"namespace", "template", "typename", "class", "public", "private",
|
||||
"protected", "virtual", "override", "friend", "new",
|
||||
// Go keywords
|
||||
"package", "import", "func", "var", "const", "type", "interface",
|
||||
"struct", "go", "defer", "select",
|
||||
// Bash keywords
|
||||
"if", "then", "else", "elif", "fi", "case", "esac", "for", "while",
|
||||
"until", "do", "done", "function",
|
||||
// C# keywords
|
||||
"namespace", "using", "class", "interface", "public", "private",
|
||||
"protected", "static", "void", "new", "override"};
|
||||
|
||||
for (size_t i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
|
||||
if (strcmp(word, keywords[i]) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Applies basic syntax highlighting to a string of code.
|
||||
* * @param code The code string to highlight.
|
||||
*/
|
||||
void highlight_code(const char *code) {
|
||||
const char *ptr = code;
|
||||
char buffer[4096];
|
||||
size_t index = 0;
|
||||
|
||||
while (*ptr) {
|
||||
// Highlight keywords
|
||||
if (isalpha((unsigned char)*ptr) || *ptr == '_') {
|
||||
while (isalnum((unsigned char)*ptr) || *ptr == '_') {
|
||||
buffer[index++] = *ptr++;
|
||||
}
|
||||
buffer[index] = '\0';
|
||||
|
||||
if (is_keyword(buffer)) {
|
||||
printf(FG_BLUE "%s" RESET FG_YELLOW, buffer);
|
||||
} else {
|
||||
printf("%s", buffer);
|
||||
}
|
||||
index = 0;
|
||||
// Highlight numbers
|
||||
} else if (isdigit((unsigned char)*ptr)) {
|
||||
while (isdigit((unsigned char)*ptr)) {
|
||||
buffer[index++] = *ptr++;
|
||||
}
|
||||
buffer[index] = '\0';
|
||||
printf(FG_CYAN "%s" RESET FG_YELLOW, buffer);
|
||||
index = 0;
|
||||
// Print other characters as-is
|
||||
} else {
|
||||
putchar(*ptr);
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parses a Markdown string and prints it to the console with ANSI color
|
||||
* codes.
|
||||
*
|
||||
* This version supports a wide range of Markdown features, including:
|
||||
* - Headers (H1-H6)
|
||||
* - Bold (**, __) and Italic (*, _) text
|
||||
* - Strikethrough (~~) and Highlight (==)
|
||||
* - Blockquotes (>), Nested Ordered (1.) and Unordered lists (*, -, +)
|
||||
* - Inline code (`) and full code blocks (```) with syntax highlighting
|
||||
* - Links ([text](url)) and Horizontal rules (---, ***)
|
||||
* * @param markdown The raw Markdown string to parse.
|
||||
*/
|
||||
void parse_markdown_to_ansi(const char *markdown) {
|
||||
const char *ptr = markdown;
|
||||
bool is_start_of_line = true;
|
||||
|
||||
while (*ptr) {
|
||||
// --- Code Blocks (```) ---
|
||||
if (is_start_of_line && strncmp(ptr, "```", 3) == 0) {
|
||||
ptr += 3;
|
||||
while (*ptr && *ptr != '\n')
|
||||
ptr++;
|
||||
if (*ptr)
|
||||
ptr++;
|
||||
|
||||
const char *code_start = ptr;
|
||||
const char *code_end = strstr(code_start, "```");
|
||||
|
||||
if (code_end) {
|
||||
char block_buffer[code_end - code_start + 1];
|
||||
strncpy(block_buffer, code_start, code_end - code_start);
|
||||
block_buffer[code_end - code_start] = '\0';
|
||||
|
||||
printf(FG_YELLOW);
|
||||
highlight_code(block_buffer);
|
||||
printf(RESET);
|
||||
|
||||
ptr = code_end + 3;
|
||||
if (*ptr == '\n')
|
||||
ptr++;
|
||||
is_start_of_line = true;
|
||||
continue;
|
||||
} else {
|
||||
printf(FG_YELLOW);
|
||||
highlight_code(code_start);
|
||||
printf(RESET);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Block-level Elements (checked at the start of a line) ---
|
||||
if (is_start_of_line) {
|
||||
const char *line_start_ptr = ptr;
|
||||
int indent_level = 0;
|
||||
while (*ptr == ' ') {
|
||||
indent_level++;
|
||||
ptr++;
|
||||
}
|
||||
|
||||
bool block_processed = true;
|
||||
if (strncmp(ptr, "###### ", 7) == 0) {
|
||||
printf(BOLD FG_YELLOW);
|
||||
ptr += 7;
|
||||
} else if (strncmp(ptr, "##### ", 6) == 0) {
|
||||
printf(BOLD FG_YELLOW);
|
||||
ptr += 6;
|
||||
} else if (strncmp(ptr, "#### ", 5) == 0) {
|
||||
printf(BOLD FG_YELLOW);
|
||||
ptr += 5;
|
||||
} else if (strncmp(ptr, "### ", 4) == 0) {
|
||||
printf(BOLD FG_YELLOW);
|
||||
ptr += 4;
|
||||
} else if (strncmp(ptr, "## ", 3) == 0) {
|
||||
printf(BOLD FG_YELLOW);
|
||||
ptr += 3;
|
||||
} else if (strncmp(ptr, "# ", 2) == 0) {
|
||||
printf(BOLD FG_YELLOW);
|
||||
ptr += 2;
|
||||
} else if ((strncmp(ptr, "---", 3) == 0 || strncmp(ptr, "***", 3) == 0) &&
|
||||
(*(ptr + 3) == '\n' || *(ptr + 3) == '\0')) {
|
||||
printf(FG_CYAN "───────────────────────────────────────────────────────"
|
||||
"──────────" RESET "\n");
|
||||
ptr += 3;
|
||||
if (*ptr == '\n')
|
||||
ptr++;
|
||||
is_start_of_line = true;
|
||||
continue;
|
||||
} else if (strncmp(ptr, "> ", 2) == 0) {
|
||||
for (int i = 0; i < indent_level; i++)
|
||||
putchar(' ');
|
||||
printf(ITALIC FG_CYAN "▎ " RESET);
|
||||
ptr += 2;
|
||||
is_start_of_line = false;
|
||||
continue;
|
||||
} else if ((*ptr == '*' || *ptr == '-' || *ptr == '+') &&
|
||||
*(ptr + 1) == ' ') {
|
||||
for (int i = 0; i < indent_level; i++)
|
||||
putchar(' ');
|
||||
printf(FG_MAGENTA "• " RESET);
|
||||
ptr += 2;
|
||||
is_start_of_line = false;
|
||||
continue;
|
||||
} else {
|
||||
const char *temp_ptr = ptr;
|
||||
while (isdigit((unsigned char)*temp_ptr))
|
||||
temp_ptr++;
|
||||
if (temp_ptr > ptr && *temp_ptr == '.' && *(temp_ptr + 1) == ' ') {
|
||||
for (int i = 0; i < indent_level; i++)
|
||||
putchar(' ');
|
||||
printf(FG_MAGENTA);
|
||||
fwrite(ptr, 1, (temp_ptr - ptr) + 1, stdout);
|
||||
printf(" " RESET);
|
||||
ptr = temp_ptr + 2;
|
||||
is_start_of_line = false;
|
||||
continue;
|
||||
} else {
|
||||
block_processed = false;
|
||||
ptr = line_start_ptr;
|
||||
}
|
||||
}
|
||||
if (block_processed) {
|
||||
while (*ptr && *ptr != '\n')
|
||||
putchar(*ptr++);
|
||||
printf(RESET "\n");
|
||||
if (*ptr == '\n')
|
||||
ptr++;
|
||||
is_start_of_line = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Inline Elements (order is important) ---
|
||||
if (strncmp(ptr, "***", 3) == 0 || strncmp(ptr, "___", 3) == 0) {
|
||||
const char *marker = strncmp(ptr, "***", 3) == 0 ? "***" : "___";
|
||||
printf(BOLD ITALIC);
|
||||
ptr += 3;
|
||||
const char *end = strstr(ptr, marker);
|
||||
if (end) {
|
||||
fwrite(ptr, 1, end - ptr, stdout);
|
||||
ptr = end + 3;
|
||||
} else {
|
||||
fputs(ptr, stdout);
|
||||
ptr += strlen(ptr);
|
||||
}
|
||||
printf(RESET);
|
||||
continue;
|
||||
}
|
||||
if (strncmp(ptr, "**", 2) == 0 || strncmp(ptr, "__", 2) == 0) {
|
||||
const char *marker = strncmp(ptr, "**", 2) == 0 ? "**" : "__";
|
||||
printf(BOLD);
|
||||
ptr += 2;
|
||||
const char *end = strstr(ptr, marker);
|
||||
if (end) {
|
||||
fwrite(ptr, 1, end - ptr, stdout);
|
||||
ptr = end + 2;
|
||||
} else {
|
||||
fputs(ptr, stdout);
|
||||
ptr += strlen(ptr);
|
||||
}
|
||||
printf(RESET);
|
||||
continue;
|
||||
}
|
||||
if ((*ptr == '*' || *ptr == '_') && !isspace(*(ptr - 1)) &&
|
||||
!isspace(*(ptr + 1))) {
|
||||
char marker = *ptr;
|
||||
printf(ITALIC);
|
||||
ptr++;
|
||||
const char *start = ptr;
|
||||
while (*ptr && *ptr != marker)
|
||||
ptr++;
|
||||
if (*ptr == marker) {
|
||||
fwrite(start, 1, ptr - start, stdout);
|
||||
ptr++;
|
||||
} else {
|
||||
putchar(marker);
|
||||
ptr = start;
|
||||
}
|
||||
printf(RESET);
|
||||
continue;
|
||||
}
|
||||
if (strncmp(ptr, "~~", 2) == 0) {
|
||||
printf(STRIKETHROUGH);
|
||||
ptr += 2;
|
||||
const char *end = strstr(ptr, "~~");
|
||||
if (end) {
|
||||
fwrite(ptr, 1, end - ptr, stdout);
|
||||
ptr = end + 2;
|
||||
} else {
|
||||
fputs(ptr, stdout);
|
||||
ptr += strlen(ptr);
|
||||
}
|
||||
printf(RESET);
|
||||
continue;
|
||||
}
|
||||
if (strncmp(ptr, "==", 2) == 0) {
|
||||
printf(BG_YELLOW_FG_BLACK);
|
||||
ptr += 2;
|
||||
const char *end = strstr(ptr, "==");
|
||||
if (end) {
|
||||
fwrite(ptr, 1, end - ptr, stdout);
|
||||
ptr = end + 2;
|
||||
} else {
|
||||
fputs(ptr, stdout);
|
||||
ptr += strlen(ptr);
|
||||
}
|
||||
printf(RESET);
|
||||
continue;
|
||||
}
|
||||
if (*ptr == '`' && *(ptr + 1) != '`') {
|
||||
printf(FG_YELLOW);
|
||||
ptr++;
|
||||
const char *start = ptr;
|
||||
while (*ptr && *ptr != '`')
|
||||
ptr++;
|
||||
fwrite(start, 1, ptr - start, stdout);
|
||||
if (*ptr == '`')
|
||||
ptr++;
|
||||
printf(RESET);
|
||||
continue;
|
||||
}
|
||||
if (*ptr == '[') {
|
||||
const char *text_start = ptr + 1;
|
||||
const char *text_end = strchr(text_start, ']');
|
||||
if (text_end && *(text_end + 1) == '(') {
|
||||
const char *url_start = text_end + 2;
|
||||
const char *url_end = strchr(url_start, ')');
|
||||
if (url_end) {
|
||||
printf(FG_BLUE);
|
||||
fwrite(text_start, 1, text_end - text_start, stdout);
|
||||
printf(RESET " (");
|
||||
printf(ITALIC FG_CYAN);
|
||||
fwrite(url_start, 1, url_end - url_start, stdout);
|
||||
printf(RESET ")");
|
||||
ptr = url_end + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Default Character ---
|
||||
if (*ptr == '\n') {
|
||||
is_start_of_line = true;
|
||||
} else if (!isspace((unsigned char)*ptr)) {
|
||||
is_start_of_line = false;
|
||||
}
|
||||
putchar(*ptr);
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
+225
-57
@@ -3,106 +3,274 @@
|
||||
#include "tool.h"
|
||||
#include "r_config.h"
|
||||
#include "bash_executor.h"
|
||||
#include "markdown.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static struct json_object *python_execute_get_description(void);
|
||||
static char *python_execute_execute(tool_t *self, struct json_object *args);
|
||||
static void python_execute_print_action(const char *name, struct json_object *args);
|
||||
|
||||
static struct json_object *python_status_get_description(void);
|
||||
static char *python_status_execute(tool_t *self, struct json_object *args);
|
||||
|
||||
static struct json_object *python_terminate_get_description(void);
|
||||
static char *python_terminate_execute(tool_t *self, struct json_object *args);
|
||||
|
||||
static const tool_vtable_t python_execute_vtable = {
|
||||
.get_description = python_execute_get_description,
|
||||
.execute = python_execute_execute,
|
||||
.print_action = python_execute_print_action
|
||||
};
|
||||
|
||||
static tool_t python_execute_tool = {
|
||||
.vtable = &python_execute_vtable,
|
||||
.name = "python_execute"
|
||||
static const tool_vtable_t python_status_vtable = {
|
||||
.get_description = python_status_get_description,
|
||||
.execute = python_status_execute,
|
||||
.print_action = NULL
|
||||
};
|
||||
|
||||
tool_t *tool_python_execute_create(void) {
|
||||
return &python_execute_tool;
|
||||
}
|
||||
static const tool_vtable_t python_terminate_vtable = {
|
||||
.get_description = python_terminate_get_description,
|
||||
.execute = python_terminate_execute,
|
||||
.print_action = NULL
|
||||
};
|
||||
|
||||
static tool_t python_execute_tool = { .vtable = &python_execute_vtable, .name = "python_execute" };
|
||||
static tool_t python_status_tool = { .vtable = &python_status_vtable, .name = "python_get_status" };
|
||||
static tool_t python_terminate_tool = { .vtable = &python_terminate_vtable, .name = "python_terminate" };
|
||||
|
||||
tool_t *tool_python_execute_create(void) { return &python_execute_tool; }
|
||||
tool_t *tool_python_get_status_create(void) { return &python_status_tool; }
|
||||
tool_t *tool_python_terminate_create(void) { return &python_terminate_tool; }
|
||||
|
||||
static void python_execute_print_action(const char *name, struct json_object *args) {
|
||||
(void)name;
|
||||
(void)args;
|
||||
fprintf(stderr, " \033[1m-> Executing Python code\033[0m\n");
|
||||
struct json_object *source;
|
||||
if (json_object_object_get_ex(args, "source", &source)) {
|
||||
const char *src = json_object_get_string(source);
|
||||
fprintf(stderr, " \033[1;34m┌─── Python Source Code ─────────────────────────────────────\033[0m\n");
|
||||
|
||||
char *copy = strdup(src);
|
||||
char *line;
|
||||
char *saveptr;
|
||||
int line_num = 1;
|
||||
|
||||
line = strtok_r(copy, "\n", &saveptr);
|
||||
while (line) {
|
||||
fprintf(stderr, " \033[1;34m│\033[0m \033[2m%3d |\033[0m ", line_num++);
|
||||
highlight_code(line);
|
||||
fprintf(stderr, "\033[0m\n");
|
||||
line = strtok_r(NULL, "\n", &saveptr);
|
||||
}
|
||||
|
||||
fprintf(stderr, " \033[1;34m└────────────────────────────────────────────────────────────\033[0m\n");
|
||||
free(copy);
|
||||
}
|
||||
}
|
||||
|
||||
static char *python_execute_execute(tool_t *self, struct json_object *args) {
|
||||
(void)self;
|
||||
struct json_object *source_obj, *timeout_obj, *async_obj;
|
||||
if (!json_object_object_get_ex(args, "source", &source_obj)) return strdup("Error: missing 'source'");
|
||||
|
||||
struct json_object *source_obj;
|
||||
if (!json_object_object_get_ex(args, "source", &source_obj)) {
|
||||
return strdup("Error: missing 'source' argument");
|
||||
}
|
||||
int timeout = 30;
|
||||
if (json_object_object_get_ex(args, "timeout", &timeout_obj)) timeout = json_object_get_int(timeout_obj);
|
||||
|
||||
bool async = false;
|
||||
if (json_object_object_get_ex(args, "async", &async_obj)) async = json_object_get_boolean(async_obj);
|
||||
|
||||
const char *source_code = json_object_get_string(source_obj);
|
||||
char *output = NULL;
|
||||
int fd = -1;
|
||||
char tmp_file[] = "/tmp/r_python_XXXXXX.py";
|
||||
int fd = mkstemps(tmp_file, 3);
|
||||
if (fd == -1) return strdup("Error: failed to create temp python file");
|
||||
dprintf(fd, "%s\n", source_code);
|
||||
close(fd);
|
||||
|
||||
char tmp_file[] = "/tmp/r_python_tool_XXXXXX.py";
|
||||
fd = mkstemps(tmp_file, 3);
|
||||
if (fd == -1) {
|
||||
return strdup("Failed to create temporary file for Python code.");
|
||||
char cmd[4096];
|
||||
snprintf(cmd, sizeof(cmd), "python3 '%s' && rm '%s' || { rm '%s'; exit 1; }", tmp_file, tmp_file, tmp_file);
|
||||
|
||||
r_process_result_t *res = r_bash_execute_ext(cmd, timeout, async);
|
||||
|
||||
struct json_object *root = json_object_new_object();
|
||||
json_object_object_add(root, "pid", json_object_new_int(res->pid));
|
||||
json_object_object_add(root, "output", json_object_new_string(res->output));
|
||||
json_object_object_add(root, "is_running", json_object_new_boolean(res->is_running));
|
||||
json_object_object_add(root, "timed_out", json_object_new_boolean(res->timed_out));
|
||||
if (!res->is_running) {
|
||||
json_object_object_add(root, "exit_status", json_object_new_int(res->exit_status));
|
||||
}
|
||||
|
||||
char *out_str = strdup(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
|
||||
json_object_put(root);
|
||||
r_process_result_free(res);
|
||||
return out_str;
|
||||
}
|
||||
|
||||
static char *python_status_execute(tool_t *self, struct json_object *args) {
|
||||
(void)self;
|
||||
struct json_object *pid_obj;
|
||||
if (!json_object_object_get_ex(args, "pid", &pid_obj)) return strdup("Error: missing 'pid'");
|
||||
int pid = json_object_get_int(pid_obj);
|
||||
|
||||
int status;
|
||||
bool running = true;
|
||||
int exit_status = -1;
|
||||
|
||||
pid_t ret = waitpid(pid, &status, WNOHANG);
|
||||
if (ret == pid) {
|
||||
running = false;
|
||||
exit_status = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
|
||||
} else if (ret == -1) {
|
||||
running = (kill(pid, 0) == 0);
|
||||
}
|
||||
|
||||
FILE *fp = fdopen(fd, "w");
|
||||
if (!fp) {
|
||||
close(fd);
|
||||
unlink(tmp_file);
|
||||
return strdup("Failed to open temporary file for writing.");
|
||||
char log_path[256];
|
||||
snprintf(log_path, sizeof(log_path), "/tmp/r_process_%d.log", pid);
|
||||
|
||||
char *content = NULL;
|
||||
FILE *f = fopen(log_path, "r");
|
||||
if (f) {
|
||||
fseek(f, 0, SEEK_END);
|
||||
long size = ftell(f);
|
||||
rewind(f);
|
||||
if (size >= 0) {
|
||||
content = malloc((size_t)size + 1);
|
||||
if (content) {
|
||||
size_t rs = fread(content, 1, (size_t)size, f);
|
||||
content[rs] = '\0';
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
fwrite(source_code, 1, strlen(source_code), fp);
|
||||
fclose(fp);
|
||||
struct json_object *root = json_object_new_object();
|
||||
json_object_object_add(root, "pid", json_object_new_int(pid));
|
||||
json_object_object_add(root, "is_running", json_object_new_boolean(running));
|
||||
json_object_object_add(root, "output", json_object_new_string(content ? content : ""));
|
||||
if (!running) {
|
||||
json_object_object_add(root, "exit_status", json_object_new_int(exit_status));
|
||||
}
|
||||
|
||||
if (content && *content) {
|
||||
char *copy = strdup(content);
|
||||
char *saveptr;
|
||||
char *line = strtok_r(copy, "\n", &saveptr);
|
||||
while (line) {
|
||||
fprintf(stdout, "[%d]\t %s\n", pid, line);
|
||||
line = strtok_r(NULL, "\n", &saveptr);
|
||||
}
|
||||
fflush(stdout);
|
||||
free(copy);
|
||||
}
|
||||
|
||||
char command[4096];
|
||||
snprintf(command, sizeof(command), "python3 '%s'", tmp_file);
|
||||
char *out_str = strdup(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
|
||||
json_object_put(root);
|
||||
free(content);
|
||||
return out_str;
|
||||
}
|
||||
|
||||
output = r_bash_execute(command, false, 300);
|
||||
static char *python_terminate_execute(tool_t *self, struct json_object *args) {
|
||||
(void)self;
|
||||
struct json_object *pid_obj;
|
||||
if (!json_object_object_get_ex(args, "pid", &pid_obj)) return strdup("Error: missing 'pid'");
|
||||
int pid = json_object_get_int(pid_obj);
|
||||
|
||||
unlink(tmp_file);
|
||||
return output;
|
||||
kill(pid, SIGTERM);
|
||||
usleep(100000);
|
||||
if (kill(pid, 0) == 0) kill(pid, SIGKILL);
|
||||
|
||||
char log_path[256];
|
||||
snprintf(log_path, sizeof(log_path), "/tmp/r_process_%d.log", pid);
|
||||
unlink(log_path);
|
||||
|
||||
return strdup("Python process terminated and logs cleaned up.");
|
||||
}
|
||||
|
||||
static struct json_object *python_execute_get_description(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("python_execute"));
|
||||
json_object_object_add(f, "description", json_object_new_string("Execute Python code. If async is true, returns PID immediately."));
|
||||
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 *src = json_object_new_object();
|
||||
json_object_object_add(src, "type", json_object_new_string("string"));
|
||||
json_object_object_add(props, "source", src);
|
||||
|
||||
struct json_object *function = json_object_new_object();
|
||||
json_object_object_add(function, "name", json_object_new_string("python_execute"));
|
||||
json_object_object_add(function, "description",
|
||||
json_object_new_string("Executes Python source code using the python3 interpreter and returns stdout/stderr."));
|
||||
struct json_object *to = json_object_new_object();
|
||||
json_object_object_add(to, "type", json_object_new_string("integer"));
|
||||
json_object_object_add(props, "timeout", to);
|
||||
|
||||
struct json_object *parameters = json_object_new_object();
|
||||
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||
struct json_object *as = json_object_new_object();
|
||||
json_object_object_add(as, "type", json_object_new_string("boolean"));
|
||||
json_object_object_add(props, "async", as);
|
||||
|
||||
struct json_object *properties = json_object_new_object();
|
||||
struct json_object *source = json_object_new_object();
|
||||
json_object_object_add(source, "type", json_object_new_string("string"));
|
||||
json_object_object_add(source, "description",
|
||||
json_object_new_string("Python source code to execute."));
|
||||
json_object_object_add(properties, "source", source);
|
||||
|
||||
json_object_object_add(parameters, "properties", properties);
|
||||
|
||||
struct json_object *required = json_object_new_array();
|
||||
json_object_array_add(required, json_object_new_string("source"));
|
||||
json_object_object_add(parameters, "required", required);
|
||||
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||
|
||||
json_object_object_add(function, "parameters", parameters);
|
||||
json_object_object_add(p, "properties", props);
|
||||
struct json_object *req = json_object_new_array();
|
||||
json_object_array_add(req, json_object_new_string("source"));
|
||||
json_object_array_add(req, json_object_new_string("timeout"));
|
||||
json_object_array_add(req, json_object_new_string("async"));
|
||||
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(function, "strict", json_object_new_boolean(1));
|
||||
}
|
||||
|
||||
json_object_object_add(root, "function", function);
|
||||
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 *python_status_get_description(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("python_get_status"));
|
||||
json_object_object_add(f, "description", json_object_new_string("Get status and logs of a background Python process by PID."));
|
||||
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 *pid = json_object_new_object();
|
||||
json_object_object_add(pid, "type", json_object_new_string("integer"));
|
||||
json_object_object_add(props, "pid", pid);
|
||||
json_object_object_add(p, "properties", props);
|
||||
struct json_object *req = json_object_new_array();
|
||||
json_object_array_add(req, json_object_new_string("pid"));
|
||||
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 *python_terminate_get_description(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("python_terminate"));
|
||||
json_object_object_add(f, "description", json_object_new_string("Terminate a background Python process and clean up."));
|
||||
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 *pid = json_object_new_object();
|
||||
json_object_object_add(pid, "type", json_object_new_string("integer"));
|
||||
json_object_object_add(props, "pid", pid);
|
||||
json_object_object_add(p, "properties", props);
|
||||
struct json_object *req = json_object_new_array();
|
||||
json_object_array_add(req, json_object_new_string("pid"));
|
||||
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;
|
||||
}
|
||||
|
||||
+231
-105
@@ -6,6 +6,10 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static struct json_object *terminal_get_description(void);
|
||||
static char *terminal_execute(tool_t *self, struct json_object *args);
|
||||
@@ -14,6 +18,12 @@ static void terminal_print_action(const char *name, struct json_object *args);
|
||||
static struct json_object *terminal_interactive_get_description(void);
|
||||
static char *terminal_interactive_execute(tool_t *self, struct json_object *args);
|
||||
|
||||
static struct json_object *terminal_status_get_description(void);
|
||||
static char *terminal_status_execute(tool_t *self, struct json_object *args);
|
||||
|
||||
static struct json_object *terminal_terminate_get_description(void);
|
||||
static char *terminal_terminate_execute(tool_t *self, struct json_object *args);
|
||||
|
||||
static const tool_vtable_t terminal_vtable = {
|
||||
.get_description = terminal_get_description,
|
||||
.execute = terminal_execute,
|
||||
@@ -26,23 +36,27 @@ static const tool_vtable_t terminal_interactive_vtable = {
|
||||
.print_action = terminal_print_action
|
||||
};
|
||||
|
||||
static tool_t terminal_tool = {
|
||||
.vtable = &terminal_vtable,
|
||||
.name = "linux_terminal_execute"
|
||||
static const tool_vtable_t terminal_status_vtable = {
|
||||
.get_description = terminal_status_get_description,
|
||||
.execute = terminal_status_execute,
|
||||
.print_action = NULL
|
||||
};
|
||||
|
||||
static tool_t terminal_interactive_tool = {
|
||||
.vtable = &terminal_interactive_vtable,
|
||||
.name = "linux_terminal_execute_interactive"
|
||||
static const tool_vtable_t terminal_terminate_vtable = {
|
||||
.get_description = terminal_terminate_get_description,
|
||||
.execute = terminal_terminate_execute,
|
||||
.print_action = NULL
|
||||
};
|
||||
|
||||
tool_t *tool_terminal_create(void) {
|
||||
return &terminal_tool;
|
||||
}
|
||||
static tool_t terminal_tool = { .vtable = &terminal_vtable, .name = "linux_terminal_execute" };
|
||||
static tool_t terminal_interactive_tool = { .vtable = &terminal_interactive_vtable, .name = "linux_terminal_execute_interactive" };
|
||||
static tool_t terminal_status_tool = { .vtable = &terminal_status_vtable, .name = "linux_terminal_get_status" };
|
||||
static tool_t terminal_terminate_tool = { .vtable = &terminal_terminate_vtable, .name = "linux_terminal_terminate" };
|
||||
|
||||
tool_t *tool_terminal_interactive_create(void) {
|
||||
return &terminal_interactive_tool;
|
||||
}
|
||||
tool_t *tool_terminal_create(void) { return &terminal_tool; }
|
||||
tool_t *tool_terminal_interactive_create(void) { return &terminal_interactive_tool; }
|
||||
tool_t *tool_terminal_get_status_create(void) { return &terminal_status_tool; }
|
||||
tool_t *tool_terminal_terminate_create(void) { return &terminal_terminate_tool; }
|
||||
|
||||
static void terminal_print_action(const char *name, struct json_object *args) {
|
||||
if (!args) {
|
||||
@@ -56,136 +70,248 @@ static void terminal_print_action(const char *name, struct json_object *args) {
|
||||
}
|
||||
|
||||
if (json_object_object_get_ex(args, "command", &cmd)) {
|
||||
if (strcmp(name, "linux_terminal_execute_interactive") == 0) {
|
||||
fprintf(stderr, " \033[1m-> Running interactive (timeout %ds):\033[0m %s\n", timeout, json_object_get_string(cmd));
|
||||
} else {
|
||||
fprintf(stderr, " \033[1m-> Running command (timeout %ds):\033[0m %s\n", timeout, json_object_get_string(cmd));
|
||||
const char *command = json_object_get_string(cmd);
|
||||
fprintf(stderr, " \033[1m-> %s (timeout %ds):\033[0m\n", name, timeout);
|
||||
char *copy = strdup(command);
|
||||
char *saveptr;
|
||||
char *line = strtok_r(copy, "\n", &saveptr);
|
||||
while (line) {
|
||||
fprintf(stderr, " \033[2m%s\033[0m\n", line);
|
||||
line = strtok_r(NULL, "\n", &saveptr);
|
||||
}
|
||||
free(copy);
|
||||
}
|
||||
}
|
||||
|
||||
static char *terminal_execute(tool_t *self, struct json_object *args) {
|
||||
(void)self;
|
||||
|
||||
struct json_object *cmd_obj, *timeout_obj;
|
||||
if (!json_object_object_get_ex(args, "command", &cmd_obj)) {
|
||||
return strdup("Error: missing 'command' argument");
|
||||
}
|
||||
struct json_object *cmd_obj, *timeout_obj, *async_obj;
|
||||
if (!json_object_object_get_ex(args, "command", &cmd_obj)) return strdup("Error: missing 'command'");
|
||||
|
||||
int timeout = 300;
|
||||
if (json_object_object_get_ex(args, "timeout", &timeout_obj)) {
|
||||
timeout = json_object_get_int(timeout_obj);
|
||||
}
|
||||
if (json_object_object_get_ex(args, "timeout", &timeout_obj)) timeout = json_object_get_int(timeout_obj);
|
||||
|
||||
bool async = false;
|
||||
if (json_object_object_get_ex(args, "async", &async_obj)) async = json_object_get_boolean(async_obj);
|
||||
|
||||
const char *command = json_object_get_string(cmd_obj);
|
||||
return r_bash_execute(command, false, timeout);
|
||||
r_process_result_t *res = r_bash_execute_ext(command, timeout, async);
|
||||
|
||||
struct json_object *root = json_object_new_object();
|
||||
json_object_object_add(root, "pid", json_object_new_int(res->pid));
|
||||
json_object_object_add(root, "output", json_object_new_string(res->output));
|
||||
json_object_object_add(root, "is_running", json_object_new_boolean(res->is_running));
|
||||
json_object_object_add(root, "timed_out", json_object_new_boolean(res->timed_out));
|
||||
if (!res->is_running) {
|
||||
json_object_object_add(root, "exit_status", json_object_new_int(res->exit_status));
|
||||
}
|
||||
|
||||
char *out_str = strdup(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
|
||||
json_object_put(root);
|
||||
r_process_result_free(res);
|
||||
return out_str;
|
||||
}
|
||||
|
||||
static char *terminal_interactive_execute(tool_t *self, struct json_object *args) {
|
||||
(void)self;
|
||||
|
||||
struct json_object *cmd_obj, *timeout_obj;
|
||||
if (!json_object_object_get_ex(args, "command", &cmd_obj)) {
|
||||
return strdup("Error: missing 'command' argument");
|
||||
}
|
||||
|
||||
if (!json_object_object_get_ex(args, "command", &cmd_obj)) return strdup("Error: missing 'command'");
|
||||
int timeout = 300;
|
||||
if (json_object_object_get_ex(args, "timeout", &timeout_obj)) {
|
||||
timeout = json_object_get_int(timeout_obj);
|
||||
if (json_object_object_get_ex(args, "timeout", &timeout_obj)) timeout = json_object_get_int(timeout_obj);
|
||||
|
||||
return r_bash_execute(json_object_get_string(cmd_obj), true, timeout);
|
||||
}
|
||||
|
||||
static char *terminal_status_execute(tool_t *self, struct json_object *args) {
|
||||
(void)self;
|
||||
struct json_object *pid_obj;
|
||||
if (!json_object_object_get_ex(args, "pid", &pid_obj)) return strdup("Error: missing 'pid'");
|
||||
int pid = json_object_get_int(pid_obj);
|
||||
|
||||
int status;
|
||||
bool running = true;
|
||||
int exit_status = -1;
|
||||
|
||||
pid_t ret = waitpid(pid, &status, WNOHANG);
|
||||
if (ret == pid) {
|
||||
running = false;
|
||||
exit_status = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
|
||||
} else if (ret == -1) {
|
||||
// Not a child or already reaped
|
||||
running = (kill(pid, 0) == 0);
|
||||
}
|
||||
|
||||
const char *command = json_object_get_string(cmd_obj);
|
||||
return r_bash_execute(command, true, timeout);
|
||||
char log_path[256];
|
||||
snprintf(log_path, sizeof(log_path), "/tmp/r_process_%d.log", pid);
|
||||
|
||||
char *content = NULL;
|
||||
FILE *f = fopen(log_path, "r");
|
||||
if (f) {
|
||||
fseek(f, 0, SEEK_END);
|
||||
long size = ftell(f);
|
||||
rewind(f);
|
||||
if (size >= 0) {
|
||||
content = malloc((size_t)size + 1);
|
||||
if (content) {
|
||||
size_t rs = fread(content, 1, (size_t)size, f);
|
||||
content[rs] = '\0';
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
struct json_object *root = json_object_new_object();
|
||||
json_object_object_add(root, "pid", json_object_new_int(pid));
|
||||
json_object_object_add(root, "is_running", json_object_new_boolean(running));
|
||||
json_object_object_add(root, "output", json_object_new_string(content ? content : ""));
|
||||
if (!running) {
|
||||
json_object_object_add(root, "exit_status", json_object_new_int(exit_status));
|
||||
}
|
||||
|
||||
if (content && *content) {
|
||||
char *copy = strdup(content);
|
||||
char *saveptr;
|
||||
char *line = strtok_r(copy, "\n", &saveptr);
|
||||
while (line) {
|
||||
fprintf(stdout, "[%d]\t %s\n", pid, line);
|
||||
line = strtok_r(NULL, "\n", &saveptr);
|
||||
}
|
||||
fflush(stdout);
|
||||
free(copy);
|
||||
}
|
||||
|
||||
char *out_str = strdup(json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
|
||||
json_object_put(root);
|
||||
free(content);
|
||||
return out_str;
|
||||
}
|
||||
|
||||
static char *terminal_terminate_execute(tool_t *self, struct json_object *args) {
|
||||
(void)self;
|
||||
struct json_object *pid_obj;
|
||||
if (!json_object_object_get_ex(args, "pid", &pid_obj)) return strdup("Error: missing 'pid'");
|
||||
int pid = json_object_get_int(pid_obj);
|
||||
|
||||
kill(pid, SIGTERM);
|
||||
usleep(100000);
|
||||
if (kill(pid, 0) == 0) kill(pid, SIGKILL);
|
||||
|
||||
char log_path[256];
|
||||
snprintf(log_path, sizeof(log_path), "/tmp/r_process_%d.log", pid);
|
||||
unlink(log_path);
|
||||
|
||||
return strdup("Process terminated and logs cleaned up.");
|
||||
}
|
||||
|
||||
static struct json_object *terminal_get_description(void) {
|
||||
struct json_object *root = json_object_new_object();
|
||||
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||
|
||||
struct json_object *function = json_object_new_object();
|
||||
json_object_object_add(function, "name",
|
||||
json_object_new_string("linux_terminal_execute"));
|
||||
json_object_object_add(function, "description",
|
||||
json_object_new_string("Execute a linux_terminal command on user terminal with a timeout."));
|
||||
|
||||
struct json_object *parameters = json_object_new_object();
|
||||
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||
|
||||
struct json_object *properties = json_object_new_object();
|
||||
struct json_object *f = json_object_new_object();
|
||||
json_object_object_add(f, "name", json_object_new_string("linux_terminal_execute"));
|
||||
json_object_object_add(f, "description", json_object_new_string("Execute a command. If async is true, returns immediately with PID."));
|
||||
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 *cmd = json_object_new_object();
|
||||
json_object_object_add(cmd, "type", json_object_new_string("string"));
|
||||
json_object_object_add(cmd, "description",
|
||||
json_object_new_string("Bash command to execute."));
|
||||
json_object_object_add(properties, "command", cmd);
|
||||
json_object_object_add(props, "command", cmd);
|
||||
|
||||
struct json_object *timeout = json_object_new_object();
|
||||
json_object_object_add(timeout, "type", json_object_new_string("integer"));
|
||||
json_object_object_add(timeout, "description",
|
||||
json_object_new_string("Timeout in seconds (default 300)."));
|
||||
json_object_object_add(timeout, "default", json_object_new_int(300));
|
||||
json_object_object_add(properties, "timeout", timeout);
|
||||
struct json_object *to = json_object_new_object();
|
||||
json_object_object_add(to, "type", json_object_new_string("integer"));
|
||||
json_object_object_add(props, "timeout", to);
|
||||
|
||||
json_object_object_add(parameters, "properties", properties);
|
||||
struct json_object *as = json_object_new_object();
|
||||
json_object_object_add(as, "type", json_object_new_string("boolean"));
|
||||
json_object_object_add(props, "async", as);
|
||||
|
||||
struct json_object *required = json_object_new_array();
|
||||
json_object_array_add(required, json_object_new_string("command"));
|
||||
json_object_array_add(required, json_object_new_string("timeout"));
|
||||
json_object_object_add(parameters, "required", required);
|
||||
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||
|
||||
json_object_object_add(function, "parameters", parameters);
|
||||
json_object_object_add(p, "properties", props);
|
||||
struct json_object *req = json_object_new_array();
|
||||
json_object_array_add(req, json_object_new_string("command"));
|
||||
json_object_array_add(req, json_object_new_string("timeout"));
|
||||
json_object_array_add(req, json_object_new_string("async"));
|
||||
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(function, "strict", json_object_new_boolean(1));
|
||||
}
|
||||
|
||||
json_object_object_add(root, "function", function);
|
||||
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 *terminal_interactive_get_description(void) {
|
||||
struct json_object *root = json_object_new_object();
|
||||
json_object_object_add(root, "type", json_object_new_string("function"));
|
||||
|
||||
struct json_object *function = json_object_new_object();
|
||||
json_object_object_add(function, "name",
|
||||
json_object_new_string("linux_terminal_execute_interactive"));
|
||||
json_object_object_add(function, "description",
|
||||
json_object_new_string("Executes interactive terminal for user with a timeout. You will not be able to read the result. Do not use if you need to know output."));
|
||||
|
||||
struct json_object *parameters = json_object_new_object();
|
||||
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||
|
||||
struct json_object *properties = json_object_new_object();
|
||||
struct json_object *f = json_object_new_object();
|
||||
json_object_object_add(f, "name", json_object_new_string("linux_terminal_execute_interactive"));
|
||||
json_object_object_add(f, "description", json_object_new_string("Execute interactive command (vim, top)."));
|
||||
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 *cmd = json_object_new_object();
|
||||
json_object_object_add(cmd, "type", json_object_new_string("string"));
|
||||
json_object_object_add(cmd, "description",
|
||||
json_object_new_string("Executable with parameters to execute interactively."));
|
||||
json_object_object_add(properties, "command", cmd);
|
||||
|
||||
struct json_object *timeout = json_object_new_object();
|
||||
json_object_object_add(timeout, "type", json_object_new_string("integer"));
|
||||
json_object_object_add(timeout, "description",
|
||||
json_object_new_string("Timeout in seconds (default 300)."));
|
||||
json_object_object_add(timeout, "default", json_object_new_int(300));
|
||||
json_object_object_add(properties, "timeout", timeout);
|
||||
|
||||
json_object_object_add(parameters, "properties", properties);
|
||||
|
||||
struct json_object *required = json_object_new_array();
|
||||
json_object_array_add(required, json_object_new_string("command"));
|
||||
json_object_array_add(required, json_object_new_string("timeout"));
|
||||
json_object_object_add(parameters, "required", required);
|
||||
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||
|
||||
json_object_object_add(function, "parameters", parameters);
|
||||
|
||||
json_object_object_add(props, "command", cmd);
|
||||
struct json_object *to = json_object_new_object();
|
||||
json_object_object_add(to, "type", json_object_new_string("integer"));
|
||||
json_object_object_add(props, "timeout", to);
|
||||
json_object_object_add(p, "properties", props);
|
||||
struct json_object *req = json_object_new_array();
|
||||
json_object_array_add(req, json_object_new_string("command"));
|
||||
json_object_array_add(req, json_object_new_string("timeout"));
|
||||
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(function, "strict", json_object_new_boolean(1));
|
||||
}
|
||||
|
||||
json_object_object_add(root, "function", function);
|
||||
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 *terminal_status_get_description(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("linux_terminal_get_status"));
|
||||
json_object_object_add(f, "description", json_object_new_string("Get status and logs of a background process by PID."));
|
||||
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 *pid = json_object_new_object();
|
||||
json_object_object_add(pid, "type", json_object_new_string("integer"));
|
||||
json_object_object_add(props, "pid", pid);
|
||||
json_object_object_add(p, "properties", props);
|
||||
struct json_object *req = json_object_new_array();
|
||||
json_object_array_add(req, json_object_new_string("pid"));
|
||||
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 *terminal_terminate_get_description(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("linux_terminal_terminate"));
|
||||
json_object_object_add(f, "description", json_object_new_string("Terminate a background process and clean up."));
|
||||
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 *pid = json_object_new_object();
|
||||
json_object_object_add(pid, "type", json_object_new_string("integer"));
|
||||
json_object_object_add(props, "pid", pid);
|
||||
json_object_object_add(p, "properties", props);
|
||||
struct json_object *req = json_object_new_array();
|
||||
json_object_array_add(req, json_object_new_string("pid"));
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
extern tool_t *tool_terminal_create(void);
|
||||
extern tool_t *tool_terminal_interactive_create(void);
|
||||
extern tool_t *tool_terminal_get_status_create(void);
|
||||
extern tool_t *tool_terminal_terminate_create(void);
|
||||
extern tool_t *tool_read_file_create(void);
|
||||
extern tool_t *tool_write_file_create(void);
|
||||
extern tool_t *tool_directory_glob_create(void);
|
||||
@@ -18,6 +20,8 @@ extern tool_t *tool_db_get_create(void);
|
||||
extern tool_t *tool_db_set_create(void);
|
||||
extern tool_t *tool_db_query_create(void);
|
||||
extern tool_t *tool_python_execute_create(void);
|
||||
extern tool_t *tool_python_get_status_create(void);
|
||||
extern tool_t *tool_python_terminate_create(void);
|
||||
extern tool_t *tool_index_source_directory_create(void);
|
||||
extern tool_t *tool_code_grep_create(void);
|
||||
extern tool_t *tool_code_symbol_find_create(void);
|
||||
@@ -42,6 +46,8 @@ tool_registry_t *tools_get_registry(void) {
|
||||
|
||||
tool_registry_register(global_registry, tool_terminal_create());
|
||||
tool_registry_register(global_registry, tool_terminal_interactive_create());
|
||||
tool_registry_register(global_registry, tool_terminal_get_status_create());
|
||||
tool_registry_register(global_registry, tool_terminal_terminate_create());
|
||||
tool_registry_register(global_registry, tool_read_file_create());
|
||||
tool_registry_register(global_registry, tool_write_file_create());
|
||||
tool_registry_register(global_registry, tool_directory_glob_create());
|
||||
@@ -55,6 +61,8 @@ tool_registry_t *tools_get_registry(void) {
|
||||
tool_registry_register(global_registry, tool_db_set_create());
|
||||
tool_registry_register(global_registry, tool_db_query_create());
|
||||
tool_registry_register(global_registry, tool_python_execute_create());
|
||||
tool_registry_register(global_registry, tool_python_get_status_create());
|
||||
tool_registry_register(global_registry, tool_python_terminate_create());
|
||||
tool_registry_register(global_registry, tool_index_source_directory_create());
|
||||
|
||||
// New tools
|
||||
|
||||
Reference in New Issue
Block a user