2026-01-28 19:34:39 +01:00
|
|
|
// retoor <retoor@molodetz.nl>
|
|
|
|
|
|
|
|
|
|
#include "tool.h"
|
|
|
|
|
#include "r_config.h"
|
|
|
|
|
#include "bash_executor.h"
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
2026-01-29 06:54:10 +01:00
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
#include <sys/wait.h>
|
2026-01-28 19:34:39 +01:00
|
|
|
|
|
|
|
|
static struct json_object *terminal_get_description(void);
|
|
|
|
|
static char *terminal_execute(tool_t *self, struct json_object *args);
|
|
|
|
|
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);
|
|
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
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);
|
|
|
|
|
|
2026-01-28 19:34:39 +01:00
|
|
|
static const tool_vtable_t terminal_vtable = {
|
|
|
|
|
.get_description = terminal_get_description,
|
|
|
|
|
.execute = terminal_execute,
|
|
|
|
|
.print_action = terminal_print_action
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const tool_vtable_t terminal_interactive_vtable = {
|
|
|
|
|
.get_description = terminal_interactive_get_description,
|
|
|
|
|
.execute = terminal_interactive_execute,
|
|
|
|
|
.print_action = terminal_print_action
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
static const tool_vtable_t terminal_status_vtable = {
|
|
|
|
|
.get_description = terminal_status_get_description,
|
|
|
|
|
.execute = terminal_status_execute,
|
|
|
|
|
.print_action = NULL
|
2026-01-28 19:34:39 +01:00
|
|
|
};
|
|
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
static const tool_vtable_t terminal_terminate_vtable = {
|
|
|
|
|
.get_description = terminal_terminate_get_description,
|
|
|
|
|
.execute = terminal_terminate_execute,
|
|
|
|
|
.print_action = NULL
|
2026-01-28 19:34:39 +01:00
|
|
|
};
|
|
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
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" };
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
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; }
|
2026-01-28 19:34:39 +01:00
|
|
|
|
|
|
|
|
static void terminal_print_action(const char *name, struct json_object *args) {
|
|
|
|
|
if (!args) {
|
|
|
|
|
fprintf(stderr, " -> %s\n", name);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-01-29 06:01:05 +01:00
|
|
|
struct json_object *cmd, *timeout_obj;
|
|
|
|
|
int timeout = 300;
|
|
|
|
|
if (json_object_object_get_ex(args, "timeout", &timeout_obj)) {
|
|
|
|
|
timeout = json_object_get_int(timeout_obj);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-28 19:34:39 +01:00
|
|
|
if (json_object_object_get_ex(args, "command", &cmd)) {
|
2026-01-29 06:54:10 +01:00
|
|
|
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);
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
2026-01-29 06:54:10 +01:00
|
|
|
free(copy);
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char *terminal_execute(tool_t *self, struct json_object *args) {
|
|
|
|
|
(void)self;
|
2026-01-29 06:54:10 +01:00
|
|
|
struct json_object *cmd_obj, *timeout_obj, *async_obj;
|
|
|
|
|
if (!json_object_object_get_ex(args, "command", &cmd_obj)) return strdup("Error: missing 'command'");
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-29 06:01:05 +01:00
|
|
|
int timeout = 300;
|
2026-01-29 06:54:10 +01:00
|
|
|
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);
|
2026-01-29 06:01:05 +01:00
|
|
|
|
2026-01-28 19:34:39 +01:00
|
|
|
const char *command = json_object_get_string(cmd_obj);
|
2026-01-29 06:54:10 +01:00
|
|
|
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;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char *terminal_interactive_execute(tool_t *self, struct json_object *args) {
|
|
|
|
|
(void)self;
|
2026-01-29 06:01:05 +01:00
|
|
|
struct json_object *cmd_obj, *timeout_obj;
|
2026-01-29 06:54:10 +01:00
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
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);
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
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);
|
2026-01-29 06:01:05 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
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;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
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);
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
char log_path[256];
|
|
|
|
|
snprintf(log_path, sizeof(log_path), "/tmp/r_process_%d.log", pid);
|
|
|
|
|
unlink(log_path);
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
return strdup("Process terminated and logs cleaned up.");
|
|
|
|
|
}
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
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 *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();
|
|
|
|
|
|
2026-01-28 19:34:39 +01:00
|
|
|
struct json_object *cmd = json_object_new_object();
|
|
|
|
|
json_object_object_add(cmd, "type", json_object_new_string("string"));
|
2026-01-29 06:54:10 +01:00
|
|
|
json_object_object_add(props, "command", cmd);
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
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);
|
2026-01-29 06:01:05 +01:00
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
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);
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
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);
|
2026-01-28 19:34:39 +01:00
|
|
|
|
|
|
|
|
r_config_handle cfg = r_config_get_instance();
|
2026-01-29 06:54:10 +01:00
|
|
|
if (r_config_use_strict(cfg)) json_object_object_add(f, "strict", json_object_new_boolean(1));
|
|
|
|
|
json_object_object_add(root, "function", f);
|
2026-01-28 19:34:39 +01:00
|
|
|
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"));
|
2026-01-29 06:54:10 +01:00
|
|
|
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();
|
2026-01-28 19:34:39 +01:00
|
|
|
struct json_object *cmd = json_object_new_object();
|
|
|
|
|
json_object_object_add(cmd, "type", json_object_new_string("string"));
|
2026-01-29 06:54:10 +01:00
|
|
|
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(f, "strict", json_object_new_boolean(1));
|
|
|
|
|
json_object_object_add(root, "function", f);
|
|
|
|
|
return root;
|
|
|
|
|
}
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
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);
|
2026-01-28 19:34:39 +01:00
|
|
|
r_config_handle cfg = r_config_get_instance();
|
2026-01-29 06:54:10 +01:00
|
|
|
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;
|
|
|
|
|
}
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-29 06:54:10 +01:00
|
|
|
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);
|
2026-01-28 19:34:39 +01:00
|
|
|
return root;
|
2026-01-29 06:54:10 +01:00
|
|
|
}
|