Update.
This commit is contained in:
parent
33af547b5d
commit
bd9b1b929e
3
Makefile
3
Makefile
@ -40,7 +40,8 @@ SRC_TOOLS = $(TOOLSDIR)/tools_init.c \
|
|||||||
$(TOOLSDIR)/tool_automation.c \
|
$(TOOLSDIR)/tool_automation.c \
|
||||||
$(TOOLSDIR)/tool_csv.c \
|
$(TOOLSDIR)/tool_csv.c \
|
||||||
$(TOOLSDIR)/tool_agent.c \
|
$(TOOLSDIR)/tool_agent.c \
|
||||||
$(TOOLSDIR)/tool_deepsearch.c
|
$(TOOLSDIR)/tool_deepsearch.c \
|
||||||
|
$(TOOLSDIR)/tool_snapshot.c
|
||||||
|
|
||||||
SRC = $(SRC_CORE) $(SRC_TOOLS)
|
SRC = $(SRC_CORE) $(SRC_TOOLS)
|
||||||
|
|
||||||
|
|||||||
11
include/db.h
11
include/db.h
@ -23,4 +23,15 @@ r_status_t db_load_conversation(db_handle db, const char *session_key, char **da
|
|||||||
long long db_get_conversation_age(db_handle db, const char *session_key);
|
long long db_get_conversation_age(db_handle db, const char *session_key);
|
||||||
r_status_t db_delete_conversation(db_handle db, const char *session_key);
|
r_status_t db_delete_conversation(db_handle db, const char *session_key);
|
||||||
|
|
||||||
|
r_status_t db_snapshot_create(db_handle db, const char *session_id, const char *description,
|
||||||
|
const char **paths, const char **contents, int file_count,
|
||||||
|
long long *snapshot_id_out);
|
||||||
|
r_status_t db_snapshot_list(db_handle db, const char *session_id, struct json_object **result);
|
||||||
|
r_status_t db_snapshot_get_files(db_handle db, long long snapshot_id, struct json_object **result);
|
||||||
|
|
||||||
|
r_status_t db_snapshot_ensure_live(db_handle db, const char *session_id,
|
||||||
|
const char *description, long long *snapshot_id_out);
|
||||||
|
r_status_t db_snapshot_upsert_file(db_handle db, long long snapshot_id,
|
||||||
|
const char *path, const char *content);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -35,4 +35,6 @@ char *messages_to_string(messages_handle msgs);
|
|||||||
char *messages_to_json_string(messages_handle msgs);
|
char *messages_to_json_string(messages_handle msgs);
|
||||||
int messages_count(messages_handle msgs);
|
int messages_count(messages_handle msgs);
|
||||||
|
|
||||||
|
char *messages_generate_session_id(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -36,4 +36,7 @@ int r_config_get_max_total_spawns(r_config_handle cfg);
|
|||||||
|
|
||||||
const char *r_config_get_deepsearch_system_message(void);
|
const char *r_config_get_deepsearch_system_message(void);
|
||||||
|
|
||||||
|
void r_config_set_current_prompt(r_config_handle cfg, const char *prompt);
|
||||||
|
const char *r_config_get_current_prompt(r_config_handle cfg);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -48,4 +48,6 @@ typedef enum {
|
|||||||
|
|
||||||
tool_registry_t *tool_registry_get_specialized(tool_registry_type_t type);
|
tool_registry_t *tool_registry_get_specialized(tool_registry_type_t type);
|
||||||
|
|
||||||
|
void snapshot_live_record(const char *path, const char *content);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -393,6 +393,7 @@ char *agent_run(agent_handle agent, const char *user_message) {
|
|||||||
agent_set_error(agent, "Empty user message");
|
agent_set_error(agent, "Empty user message");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
r_config_set_current_prompt(r_config_get_instance(), user_message);
|
||||||
messages_load(agent->messages);
|
messages_load(agent->messages);
|
||||||
char *json_data = agent_build_request(agent, "user", user_message);
|
char *json_data = agent_build_request(agent, "user", user_message);
|
||||||
if (!json_data) {
|
if (!json_data) {
|
||||||
|
|||||||
140
src/db.c
140
src/db.c
@ -117,7 +117,22 @@ r_status_t db_init(db_handle db) {
|
|||||||
" batch_id TEXT,"
|
" batch_id TEXT,"
|
||||||
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
|
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"
|
||||||
" updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
" updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
||||||
");";
|
");"
|
||||||
|
"CREATE TABLE IF NOT EXISTS snapshots ("
|
||||||
|
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||||
|
" session_id TEXT NOT NULL,"
|
||||||
|
" description TEXT,"
|
||||||
|
" created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
||||||
|
");"
|
||||||
|
"CREATE TABLE IF NOT EXISTS snapshot_files ("
|
||||||
|
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
|
||||||
|
" snapshot_id INTEGER NOT NULL,"
|
||||||
|
" path TEXT NOT NULL,"
|
||||||
|
" content TEXT NOT NULL,"
|
||||||
|
" FOREIGN KEY (snapshot_id) REFERENCES snapshots(id) ON DELETE CASCADE"
|
||||||
|
");"
|
||||||
|
"CREATE UNIQUE INDEX IF NOT EXISTS idx_snapshot_files_unique "
|
||||||
|
"ON snapshot_files(snapshot_id, path);";
|
||||||
char *err_msg = NULL;
|
char *err_msg = NULL;
|
||||||
if (sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg) != SQLITE_OK) {
|
if (sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg) != SQLITE_OK) {
|
||||||
sqlite3_free(err_msg);
|
sqlite3_free(err_msg);
|
||||||
@ -319,3 +334,126 @@ r_status_t db_load_conversation(db_handle db, const char *session_key, char **da
|
|||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
return (rc == SQLITE_ROW) ? R_SUCCESS : R_ERROR_DB_NOT_FOUND;
|
return (rc == SQLITE_ROW) ? R_SUCCESS : R_ERROR_DB_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
r_status_t db_snapshot_create(db_handle db, const char *session_id, const char *description,
|
||||||
|
const char **paths, const char **contents, int file_count,
|
||||||
|
long long *snapshot_id_out) {
|
||||||
|
if (!db || !db->conn || !session_id || !paths || !contents || file_count <= 0 || !snapshot_id_out)
|
||||||
|
return R_ERROR_INVALID_ARG;
|
||||||
|
char *err_msg = NULL;
|
||||||
|
if (sqlite3_exec(db->conn, "BEGIN TRANSACTION", NULL, NULL, &err_msg) != SQLITE_OK) {
|
||||||
|
sqlite3_free(err_msg);
|
||||||
|
return R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
char *sql = sqlite3_mprintf(
|
||||||
|
"INSERT INTO snapshots (session_id, description) VALUES (%Q, %Q)",
|
||||||
|
session_id, description ? description : "");
|
||||||
|
if (!sql) {
|
||||||
|
sqlite3_exec(db->conn, "ROLLBACK", NULL, NULL, NULL);
|
||||||
|
return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
int rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
|
||||||
|
sqlite3_free(sql);
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
sqlite3_free(err_msg);
|
||||||
|
sqlite3_exec(db->conn, "ROLLBACK", NULL, NULL, NULL);
|
||||||
|
return R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
long long sid = sqlite3_last_insert_rowid(db->conn);
|
||||||
|
for (int i = 0; i < file_count; i++) {
|
||||||
|
sql = sqlite3_mprintf(
|
||||||
|
"INSERT INTO snapshot_files (snapshot_id, path, content) VALUES (%lld, %Q, %Q)",
|
||||||
|
sid, paths[i], contents[i]);
|
||||||
|
if (!sql) {
|
||||||
|
sqlite3_exec(db->conn, "ROLLBACK", NULL, NULL, NULL);
|
||||||
|
return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
|
||||||
|
sqlite3_free(sql);
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
sqlite3_free(err_msg);
|
||||||
|
sqlite3_exec(db->conn, "ROLLBACK", NULL, NULL, NULL);
|
||||||
|
return R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sqlite3_exec(db->conn, "COMMIT", NULL, NULL, &err_msg) != SQLITE_OK) {
|
||||||
|
sqlite3_free(err_msg);
|
||||||
|
sqlite3_exec(db->conn, "ROLLBACK", NULL, NULL, NULL);
|
||||||
|
return R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
*snapshot_id_out = sid;
|
||||||
|
return R_SUCCESS;
|
||||||
|
}
|
||||||
|
r_status_t db_snapshot_list(db_handle db, const char *session_id, struct json_object **result) {
|
||||||
|
if (!db || !db->conn || !session_id || !result) return R_ERROR_INVALID_ARG;
|
||||||
|
char *sql = sqlite3_mprintf(
|
||||||
|
"SELECT s.id, s.session_id, s.description, s.created_at, "
|
||||||
|
"(SELECT COUNT(*) FROM snapshot_files sf WHERE sf.snapshot_id = s.id) AS file_count "
|
||||||
|
"FROM snapshots s WHERE s.session_id = %Q ORDER BY s.created_at DESC",
|
||||||
|
session_id);
|
||||||
|
if (!sql) return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
r_status_t status = db_execute(db, sql, result);
|
||||||
|
sqlite3_free(sql);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
r_status_t db_snapshot_get_files(db_handle db, long long snapshot_id, struct json_object **result) {
|
||||||
|
if (!db || !db->conn || !result) return R_ERROR_INVALID_ARG;
|
||||||
|
char *sql = sqlite3_mprintf(
|
||||||
|
"SELECT path, content FROM snapshot_files WHERE snapshot_id = %lld",
|
||||||
|
snapshot_id);
|
||||||
|
if (!sql) return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
r_status_t status = db_execute(db, sql, result);
|
||||||
|
sqlite3_free(sql);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
r_status_t db_snapshot_ensure_live(db_handle db, const char *session_id,
|
||||||
|
const char *description, long long *snapshot_id_out) {
|
||||||
|
if (!db || !db->conn || !session_id || !snapshot_id_out) return R_ERROR_INVALID_ARG;
|
||||||
|
const char *select_sql = "SELECT id FROM snapshots WHERE session_id = ? LIMIT 1";
|
||||||
|
sqlite3_stmt *stmt = NULL;
|
||||||
|
if (sqlite3_prepare_v2(db->conn, select_sql, -1, &stmt, NULL) != SQLITE_OK) {
|
||||||
|
return R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
sqlite3_bind_text(stmt, 1, session_id, -1, SQLITE_STATIC);
|
||||||
|
int rc = sqlite3_step(stmt);
|
||||||
|
if (rc == SQLITE_ROW) {
|
||||||
|
*snapshot_id_out = sqlite3_column_int64(stmt, 0);
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
char *update_sql = sqlite3_mprintf(
|
||||||
|
"UPDATE snapshots SET description = %Q WHERE id = %lld",
|
||||||
|
description ? description : "", *snapshot_id_out);
|
||||||
|
if (!update_sql) return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
char *err_msg = NULL;
|
||||||
|
rc = sqlite3_exec(db->conn, update_sql, NULL, NULL, &err_msg);
|
||||||
|
sqlite3_free(update_sql);
|
||||||
|
sqlite3_free(err_msg);
|
||||||
|
return (rc == SQLITE_OK) ? R_SUCCESS : R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
char *insert_sql = sqlite3_mprintf(
|
||||||
|
"INSERT INTO snapshots (session_id, description) VALUES (%Q, %Q)",
|
||||||
|
session_id, description ? description : "");
|
||||||
|
if (!insert_sql) return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
char *err_msg = NULL;
|
||||||
|
rc = sqlite3_exec(db->conn, insert_sql, NULL, NULL, &err_msg);
|
||||||
|
sqlite3_free(insert_sql);
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
sqlite3_free(err_msg);
|
||||||
|
return R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
*snapshot_id_out = sqlite3_last_insert_rowid(db->conn);
|
||||||
|
return R_SUCCESS;
|
||||||
|
}
|
||||||
|
r_status_t db_snapshot_upsert_file(db_handle db, long long snapshot_id,
|
||||||
|
const char *path, const char *content) {
|
||||||
|
if (!db || !db->conn || !path || !content) return R_ERROR_INVALID_ARG;
|
||||||
|
char *sql = sqlite3_mprintf(
|
||||||
|
"INSERT OR REPLACE INTO snapshot_files (snapshot_id, path, content) "
|
||||||
|
"VALUES (%lld, %Q, %Q)",
|
||||||
|
snapshot_id, path, content);
|
||||||
|
if (!sql) return R_ERROR_OUT_OF_MEMORY;
|
||||||
|
char *err_msg = NULL;
|
||||||
|
int rc = sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg);
|
||||||
|
sqlite3_free(sql);
|
||||||
|
sqlite3_free(err_msg);
|
||||||
|
return (rc == SQLITE_OK) ? R_SUCCESS : R_ERROR_DB_QUERY;
|
||||||
|
}
|
||||||
|
|||||||
175
src/line.h
175
src/line.h
@ -1,27 +1,17 @@
|
|||||||
// Written by retoor@molodetz.nl
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
// This source code provides command-line input functionalities with
|
|
||||||
// autocomplete and history features using the readline library. It allows users
|
|
||||||
// to complete commands and manage input history.
|
|
||||||
|
|
||||||
// External includes:
|
|
||||||
// - <readline/readline.h>
|
|
||||||
// - <readline/history.h>
|
|
||||||
// - <glob.h>
|
|
||||||
|
|
||||||
// MIT License: Permission is hereby granted, free of charge, to any person
|
|
||||||
// obtaining a copy of this software and associated documentation files (the
|
|
||||||
// "Software"), to deal in the Software without restriction.
|
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include <glob.h>
|
#include <glob.h>
|
||||||
#include <readline/history.h>
|
#include <readline/history.h>
|
||||||
#include <readline/readline.h>
|
#include <readline/readline.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#define HISTORY_FILE "~/.r_history"
|
#define HISTORY_FILE "~/.r_history"
|
||||||
|
#define LINE_BUFFER_INITIAL 4096
|
||||||
|
|
||||||
bool line_initialized = false;
|
static bool line_initialized = false;
|
||||||
|
static int line_continuation_requested = 0;
|
||||||
|
|
||||||
char *get_history_file() {
|
char *get_history_file() {
|
||||||
static char result[4096];
|
static char result[4096];
|
||||||
@ -41,7 +31,8 @@ char *line_command_generator(const char *text, int state) {
|
|||||||
static int list_index, len = 0;
|
static int list_index, len = 0;
|
||||||
const char *commands[] = {"help", "exit", "list", "review",
|
const char *commands[] = {"help", "exit", "list", "review",
|
||||||
"refactor", "obfuscate", "!verbose", "!dump",
|
"refactor", "obfuscate", "!verbose", "!dump",
|
||||||
"!model", "!debug", NULL};
|
"!model", "!debug", "!vi", "!emacs",
|
||||||
|
"!new", "!clear", "!session", NULL};
|
||||||
|
|
||||||
if (!state) {
|
if (!state) {
|
||||||
list_index = 0;
|
list_index = 0;
|
||||||
@ -100,21 +91,163 @@ char **line_command_completion(const char *text, int start, int end) {
|
|||||||
return rl_completion_matches(text, line_command_generator);
|
return rl_completion_matches(text, line_command_generator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int line_toggle_editing_mode(int count, int key) {
|
||||||
|
(void)count;
|
||||||
|
(void)key;
|
||||||
|
if (rl_editing_mode == 1) {
|
||||||
|
rl_variable_bind("editing-mode", "vi");
|
||||||
|
} else {
|
||||||
|
rl_variable_bind("editing-mode", "emacs");
|
||||||
|
}
|
||||||
|
rl_set_keymap_from_edit_mode();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int line_request_continuation(int count, int key) {
|
||||||
|
(void)count;
|
||||||
|
(void)key;
|
||||||
|
line_continuation_requested = 1;
|
||||||
|
rl_crlf();
|
||||||
|
rl_done = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *line_build_prompt(void) {
|
||||||
|
if (rl_editing_mode == 0) {
|
||||||
|
return "\001\033[38;5;208m\002>\001\033[0m\002 ";
|
||||||
|
}
|
||||||
|
return "> ";
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *line_build_numbered_prompt(int line_number) {
|
||||||
|
static char prompt[64];
|
||||||
|
if (rl_editing_mode == 0) {
|
||||||
|
snprintf(prompt, sizeof(prompt),
|
||||||
|
"\001\033[38;5;208m\002%d>\001\033[0m\002 ", line_number);
|
||||||
|
} else {
|
||||||
|
snprintf(prompt, sizeof(prompt), "%d> ", line_number);
|
||||||
|
}
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
|
||||||
void line_init() {
|
void line_init() {
|
||||||
if (!line_initialized) {
|
if (!line_initialized) {
|
||||||
rl_attempted_completion_function = line_command_completion;
|
rl_attempted_completion_function = line_command_completion;
|
||||||
|
|
||||||
|
rl_variable_bind("editing-mode", "vi");
|
||||||
|
rl_variable_bind("enable-bracketed-paste", "on");
|
||||||
|
|
||||||
|
rl_add_defun("toggle-editing-mode", line_toggle_editing_mode, -1);
|
||||||
|
rl_bind_key_in_map(CTRL('T'), line_toggle_editing_mode,
|
||||||
|
vi_insertion_keymap);
|
||||||
|
rl_bind_key_in_map(CTRL('T'), line_toggle_editing_mode,
|
||||||
|
vi_movement_keymap);
|
||||||
|
rl_bind_key_in_map(CTRL('T'), line_toggle_editing_mode,
|
||||||
|
emacs_standard_keymap);
|
||||||
|
|
||||||
|
rl_add_defun("request-continuation", line_request_continuation, -1);
|
||||||
|
|
||||||
|
rl_bind_key_in_map(CTRL('J'), line_request_continuation,
|
||||||
|
vi_insertion_keymap);
|
||||||
|
rl_bind_key_in_map(CTRL('J'), line_request_continuation,
|
||||||
|
vi_movement_keymap);
|
||||||
|
rl_bind_key_in_map(CTRL('J'), line_request_continuation,
|
||||||
|
emacs_standard_keymap);
|
||||||
|
|
||||||
|
rl_bind_keyseq_in_map("\e\r", line_request_continuation,
|
||||||
|
vi_insertion_keymap);
|
||||||
|
rl_bind_keyseq_in_map("\e\r", line_request_continuation,
|
||||||
|
vi_movement_keymap);
|
||||||
|
rl_bind_keyseq_in_map("\e\r", line_request_continuation,
|
||||||
|
emacs_standard_keymap);
|
||||||
|
rl_bind_keyseq_in_map("\e\n", line_request_continuation,
|
||||||
|
vi_insertion_keymap);
|
||||||
|
rl_bind_keyseq_in_map("\e\n", line_request_continuation,
|
||||||
|
vi_movement_keymap);
|
||||||
|
rl_bind_keyseq_in_map("\e\n", line_request_continuation,
|
||||||
|
emacs_standard_keymap);
|
||||||
|
|
||||||
|
rl_bind_keyseq_in_map("\033[13;2u", line_request_continuation,
|
||||||
|
vi_insertion_keymap);
|
||||||
|
rl_bind_keyseq_in_map("\033[13;2u", line_request_continuation,
|
||||||
|
vi_movement_keymap);
|
||||||
|
rl_bind_keyseq_in_map("\033[13;2u", line_request_continuation,
|
||||||
|
emacs_standard_keymap);
|
||||||
|
rl_bind_keyseq_in_map("\033[13;5u", line_request_continuation,
|
||||||
|
vi_insertion_keymap);
|
||||||
|
rl_bind_keyseq_in_map("\033[13;5u", line_request_continuation,
|
||||||
|
vi_movement_keymap);
|
||||||
|
rl_bind_keyseq_in_map("\033[13;5u", line_request_continuation,
|
||||||
|
emacs_standard_keymap);
|
||||||
|
rl_bind_keyseq_in_map("\033[27;5;13~", line_request_continuation,
|
||||||
|
vi_insertion_keymap);
|
||||||
|
rl_bind_keyseq_in_map("\033[27;5;13~", line_request_continuation,
|
||||||
|
vi_movement_keymap);
|
||||||
|
rl_bind_keyseq_in_map("\033[27;5;13~", line_request_continuation,
|
||||||
|
emacs_standard_keymap);
|
||||||
|
|
||||||
line_initialized = true;
|
line_initialized = true;
|
||||||
read_history(get_history_file());
|
read_history(get_history_file());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char *line_read(char *prefix) {
|
char *line_read(const char *prompt) {
|
||||||
char *data = readline(prefix);
|
size_t capacity = LINE_BUFFER_INITIAL;
|
||||||
if (!(data && *data)) {
|
size_t length = 0;
|
||||||
free(data);
|
char *buffer = (char *)malloc(capacity);
|
||||||
|
if (!buffer) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return data;
|
buffer[0] = '\0';
|
||||||
|
|
||||||
|
const char *active_prompt = prompt;
|
||||||
|
int line_number = 1;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
line_continuation_requested = 0;
|
||||||
|
char *segment = readline(active_prompt);
|
||||||
|
if (!segment) {
|
||||||
|
if (length > 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
free(buffer);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t seg_len = strlen(segment);
|
||||||
|
size_t needed = length + seg_len + 2;
|
||||||
|
if (needed > capacity) {
|
||||||
|
capacity = needed * 2;
|
||||||
|
char *grown = (char *)realloc(buffer, capacity);
|
||||||
|
if (!grown) {
|
||||||
|
free(segment);
|
||||||
|
free(buffer);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
buffer = grown;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length > 0) {
|
||||||
|
buffer[length++] = '\n';
|
||||||
|
}
|
||||||
|
memcpy(buffer + length, segment, seg_len);
|
||||||
|
length += seg_len;
|
||||||
|
buffer[length] = '\0';
|
||||||
|
free(segment);
|
||||||
|
segment = NULL;
|
||||||
|
|
||||||
|
if (!line_continuation_requested) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
line_number++;
|
||||||
|
active_prompt = line_build_numbered_prompt(line_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length == 0) {
|
||||||
|
free(buffer);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void line_add_history(char *data) {
|
void line_add_history(char *data) {
|
||||||
|
|||||||
17
src/main.c
17
src/main.c
@ -164,7 +164,7 @@ static void repl(void) {
|
|||||||
line_init();
|
line_init();
|
||||||
char *line = NULL;
|
char *line = NULL;
|
||||||
while (true) {
|
while (true) {
|
||||||
line = line_read("> ");
|
line = line_read(line_build_prompt());
|
||||||
if (!line || !*line)
|
if (!line || !*line)
|
||||||
continue;
|
continue;
|
||||||
if (!strncmp(line, "!dump", 5)) {
|
if (!strncmp(line, "!dump", 5)) {
|
||||||
@ -192,6 +192,16 @@ static void repl(void) {
|
|||||||
fprintf(stderr, "New session: %s\n", session_id);
|
fprintf(stderr, "New session: %s\n", session_id);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!strncmp(line, "!vi", 3)) {
|
||||||
|
rl_variable_bind("editing-mode", "vi");
|
||||||
|
rl_set_keymap_from_edit_mode();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!strncmp(line, "!emacs", 6)) {
|
||||||
|
rl_variable_bind("editing-mode", "emacs");
|
||||||
|
rl_set_keymap_from_edit_mode();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!strncmp(line, "!verbose", 8)) {
|
if (!strncmp(line, "!verbose", 8)) {
|
||||||
bool verbose = !r_config_is_verbose(cfg);
|
bool verbose = !r_config_is_verbose(cfg);
|
||||||
r_config_set_verbose(cfg, verbose);
|
r_config_set_verbose(cfg, verbose);
|
||||||
@ -375,6 +385,11 @@ static void init(void) {
|
|||||||
"Copy relevant data from tool results into your response.\n"
|
"Copy relevant data from tool results into your response.\n"
|
||||||
"## Backup\n"
|
"## Backup\n"
|
||||||
"Make a .bak backup before editing files you did not create.\n"
|
"Make a .bak backup before editing files you did not create.\n"
|
||||||
|
"## Snapshots\n"
|
||||||
|
"File modifications through write_file, file_line_replace, and file_apply_patch are "
|
||||||
|
"automatically recorded in a live snapshot for this session. Use list_snapshots to see "
|
||||||
|
"snapshots and restore_snapshot to restore files. You can also use create_snapshot to "
|
||||||
|
"manually capture additional files before risky changes.\n"
|
||||||
"## Terminal\n"
|
"## Terminal\n"
|
||||||
"You have bash access. Prefer commands that do not require root.\n"
|
"You have bash access. Prefer commands that do not require root.\n"
|
||||||
"## RULE #2: DELIVERABLE FIRST\n"
|
"## RULE #2: DELIVERABLE FIRST\n"
|
||||||
|
|||||||
@ -25,7 +25,7 @@ static bool is_valid_session_id(const char *session_id) {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
static long long get_ppid_starttime(pid_t ppid) {
|
long long messages_get_ppid_starttime(pid_t ppid) {
|
||||||
char proc_path[64];
|
char proc_path[64];
|
||||||
snprintf(proc_path, sizeof(proc_path), "/proc/%d/stat", ppid);
|
snprintf(proc_path, sizeof(proc_path), "/proc/%d/stat", ppid);
|
||||||
FILE *fp = fopen(proc_path, "r");
|
FILE *fp = fopen(proc_path, "r");
|
||||||
@ -46,11 +46,11 @@ static long long get_ppid_starttime(pid_t ppid) {
|
|||||||
if (field < 19) return -1;
|
if (field < 19) return -1;
|
||||||
return strtoll(p, NULL, 10);
|
return strtoll(p, NULL, 10);
|
||||||
}
|
}
|
||||||
static char *generate_default_session_id(void) {
|
char *messages_generate_session_id(void) {
|
||||||
char *session_id = malloc(64);
|
char *session_id = malloc(64);
|
||||||
if (!session_id) return NULL;
|
if (!session_id) return NULL;
|
||||||
pid_t ppid = getppid();
|
pid_t ppid = getppid();
|
||||||
long long starttime = get_ppid_starttime(ppid);
|
long long starttime = messages_get_ppid_starttime(ppid);
|
||||||
if (starttime >= 0) {
|
if (starttime >= 0) {
|
||||||
snprintf(session_id, 64, "session-%d-%lld", ppid, starttime);
|
snprintf(session_id, 64, "session-%d-%lld", ppid, starttime);
|
||||||
} else {
|
} else {
|
||||||
@ -69,7 +69,7 @@ messages_handle messages_create(const char *session_id) {
|
|||||||
if (session_id && is_valid_session_id(session_id)) {
|
if (session_id && is_valid_session_id(session_id)) {
|
||||||
msgs->session_id = strdup(session_id);
|
msgs->session_id = strdup(session_id);
|
||||||
} else {
|
} else {
|
||||||
msgs->session_id = generate_default_session_id();
|
msgs->session_id = messages_generate_session_id();
|
||||||
}
|
}
|
||||||
if (!msgs->session_id) {
|
if (!msgs->session_id) {
|
||||||
json_object_put(msgs->array);
|
json_object_put(msgs->array);
|
||||||
|
|||||||
@ -12,6 +12,7 @@ struct r_config_t {
|
|||||||
char *db_path;
|
char *db_path;
|
||||||
char *session_id;
|
char *session_id;
|
||||||
char *system_message;
|
char *system_message;
|
||||||
|
char *current_prompt;
|
||||||
double temperature;
|
double temperature;
|
||||||
int max_tokens;
|
int max_tokens;
|
||||||
int max_spawn_depth;
|
int max_spawn_depth;
|
||||||
@ -99,6 +100,7 @@ void r_config_destroy(void) {
|
|||||||
free(instance->db_path);
|
free(instance->db_path);
|
||||||
free(instance->session_id);
|
free(instance->session_id);
|
||||||
free(instance->system_message);
|
free(instance->system_message);
|
||||||
|
free(instance->current_prompt);
|
||||||
free(instance);
|
free(instance);
|
||||||
instance = NULL;
|
instance = NULL;
|
||||||
}
|
}
|
||||||
@ -158,6 +160,14 @@ int r_config_get_max_spawn_depth(r_config_handle cfg) {
|
|||||||
int r_config_get_max_total_spawns(r_config_handle cfg) {
|
int r_config_get_max_total_spawns(r_config_handle cfg) {
|
||||||
return cfg ? cfg->max_total_spawns : 20;
|
return cfg ? cfg->max_total_spawns : 20;
|
||||||
}
|
}
|
||||||
|
void r_config_set_current_prompt(r_config_handle cfg, const char *prompt) {
|
||||||
|
if (!cfg) return;
|
||||||
|
free(cfg->current_prompt);
|
||||||
|
cfg->current_prompt = prompt ? strdup(prompt) : NULL;
|
||||||
|
}
|
||||||
|
const char *r_config_get_current_prompt(r_config_handle cfg) {
|
||||||
|
return cfg ? cfg->current_prompt : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Deepsearch Algorithm System Instructions
|
* Deepsearch Algorithm System Instructions
|
||||||
|
|||||||
@ -293,6 +293,7 @@ static char *write_file_execute(tool_t *self, struct json_object *args) {
|
|||||||
|
|
||||||
fwrite(new_content, 1, strlen(new_content), fp);
|
fwrite(new_content, 1, strlen(new_content), fp);
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
snapshot_live_record(path, new_content);
|
||||||
return strdup("File successfully written.");
|
return strdup("File successfully written.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -127,6 +127,7 @@ static char *file_line_replace_execute(tool_t *self, struct json_object *args) {
|
|||||||
if (fp) {
|
if (fp) {
|
||||||
fputs(new_content, fp);
|
fputs(new_content, fp);
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
snapshot_live_record(path, new_content);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < count; i++) free(lines[i]);
|
for (int i = 0; i < count; i++) free(lines[i]);
|
||||||
@ -183,6 +184,9 @@ static char *file_apply_patch_execute(tool_t *self, struct json_object *args) {
|
|||||||
if (old_content && new_content) {
|
if (old_content && new_content) {
|
||||||
r_diff_print(path, old_content, new_content);
|
r_diff_print(path, old_content, new_content);
|
||||||
}
|
}
|
||||||
|
if (new_content) {
|
||||||
|
snapshot_live_record(path, new_content);
|
||||||
|
}
|
||||||
|
|
||||||
free(old_content);
|
free(old_content);
|
||||||
free(new_content);
|
free(new_content);
|
||||||
|
|||||||
459
src/tools/tool_snapshot.c
Normal file
459
src/tools/tool_snapshot.c
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
// retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
#include "tool.h"
|
||||||
|
#include "db.h"
|
||||||
|
#include "messages.h"
|
||||||
|
#include "r_config.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <libgen.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define SNAPSHOT_MAX_FILE_SIZE 1048576
|
||||||
|
|
||||||
|
static char *snapshot_resolve_session_id(void) {
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
const char *session_id = r_config_get_session_id(cfg);
|
||||||
|
if (session_id && *session_id) return strdup(session_id);
|
||||||
|
return messages_generate_session_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int snapshot_mkdirs(const char *filepath) {
|
||||||
|
char *path_copy = strdup(filepath);
|
||||||
|
if (!path_copy) return -1;
|
||||||
|
char *dir = dirname(path_copy);
|
||||||
|
if (!dir || strcmp(dir, ".") == 0 || strcmp(dir, "/") == 0) {
|
||||||
|
free(path_copy);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
char mkdir_cmd[4096];
|
||||||
|
snprintf(mkdir_cmd, sizeof(mkdir_cmd), "mkdir -p '%s'", dir);
|
||||||
|
int rc = system(mkdir_cmd);
|
||||||
|
free(path_copy);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *create_snapshot_get_description(void);
|
||||||
|
static char *create_snapshot_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void create_snapshot_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static struct json_object *list_snapshots_get_description(void);
|
||||||
|
static char *list_snapshots_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void list_snapshots_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static struct json_object *restore_snapshot_get_description(void);
|
||||||
|
static char *restore_snapshot_execute(tool_t *self, struct json_object *args);
|
||||||
|
static void restore_snapshot_print_action(const char *name, struct json_object *args);
|
||||||
|
|
||||||
|
static const tool_vtable_t create_snapshot_vtable = {
|
||||||
|
.get_description = create_snapshot_get_description,
|
||||||
|
.execute = create_snapshot_execute,
|
||||||
|
.print_action = create_snapshot_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static const tool_vtable_t list_snapshots_vtable = {
|
||||||
|
.get_description = list_snapshots_get_description,
|
||||||
|
.execute = list_snapshots_execute,
|
||||||
|
.print_action = list_snapshots_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static const tool_vtable_t restore_snapshot_vtable = {
|
||||||
|
.get_description = restore_snapshot_get_description,
|
||||||
|
.execute = restore_snapshot_execute,
|
||||||
|
.print_action = restore_snapshot_print_action
|
||||||
|
};
|
||||||
|
|
||||||
|
static tool_t create_snapshot_tool = { .vtable = &create_snapshot_vtable, .name = "create_snapshot" };
|
||||||
|
static tool_t list_snapshots_tool = { .vtable = &list_snapshots_vtable, .name = "list_snapshots" };
|
||||||
|
static tool_t restore_snapshot_tool = { .vtable = &restore_snapshot_vtable, .name = "restore_snapshot" };
|
||||||
|
|
||||||
|
tool_t *tool_create_snapshot_create(void) { return &create_snapshot_tool; }
|
||||||
|
tool_t *tool_list_snapshots_create(void) { return &list_snapshots_tool; }
|
||||||
|
tool_t *tool_restore_snapshot_create(void) { return &restore_snapshot_tool; }
|
||||||
|
|
||||||
|
void snapshot_live_record(const char *path, const char *content) {
|
||||||
|
if (!path || !content) return;
|
||||||
|
char *session_id = snapshot_resolve_session_id();
|
||||||
|
if (!session_id) return;
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
const char *prompt = r_config_get_current_prompt(cfg);
|
||||||
|
if (!prompt || !*prompt) prompt = "auto";
|
||||||
|
db_handle db = db_open(NULL);
|
||||||
|
if (!db) {
|
||||||
|
free(session_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long long snapshot_id = 0;
|
||||||
|
if (db_snapshot_ensure_live(db, session_id, prompt, &snapshot_id) == R_SUCCESS) {
|
||||||
|
db_snapshot_upsert_file(db, snapshot_id, path, content);
|
||||||
|
}
|
||||||
|
db_close(db);
|
||||||
|
free(session_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void create_snapshot_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
if (!args) return;
|
||||||
|
struct json_object *desc;
|
||||||
|
if (json_object_object_get_ex(args, "description", &desc)) {
|
||||||
|
fprintf(stderr, " -> Creating snapshot: %s\n", json_object_get_string(desc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *create_snapshot_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
struct json_object *paths_obj = NULL;
|
||||||
|
struct json_object *desc_obj = NULL;
|
||||||
|
|
||||||
|
if (!json_object_object_get_ex(args, "paths", &paths_obj) ||
|
||||||
|
!json_object_is_type(paths_obj, json_type_array)) {
|
||||||
|
return strdup("Error: missing or invalid 'paths' array argument");
|
||||||
|
}
|
||||||
|
if (!json_object_object_get_ex(args, "description", &desc_obj)) {
|
||||||
|
return strdup("Error: missing 'description' argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
int path_count = json_object_array_length(paths_obj);
|
||||||
|
if (path_count <= 0) {
|
||||||
|
return strdup("Error: 'paths' array is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char **paths = calloc((size_t)path_count, sizeof(char *));
|
||||||
|
const char **contents = calloc((size_t)path_count, sizeof(char *));
|
||||||
|
char **allocated_contents = calloc((size_t)path_count, sizeof(char *));
|
||||||
|
struct json_object *skipped = json_object_new_array();
|
||||||
|
if (!paths || !contents || !allocated_contents || !skipped) {
|
||||||
|
free(paths);
|
||||||
|
free(contents);
|
||||||
|
free(allocated_contents);
|
||||||
|
json_object_put(skipped);
|
||||||
|
return strdup("Error: out of memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
int captured = 0;
|
||||||
|
for (int i = 0; i < path_count; i++) {
|
||||||
|
const char *path = json_object_get_string(json_object_array_get_idx(paths_obj, i));
|
||||||
|
if (!path) continue;
|
||||||
|
|
||||||
|
FILE *fp = fopen(path, "r");
|
||||||
|
if (!fp) {
|
||||||
|
json_object_array_add(skipped, json_object_new_string(path));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
long size = ftell(fp);
|
||||||
|
rewind(fp);
|
||||||
|
|
||||||
|
if (size < 0 || size > SNAPSHOT_MAX_FILE_SIZE) {
|
||||||
|
fclose(fp);
|
||||||
|
json_object_array_add(skipped, json_object_new_string(path));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *buf = malloc((size_t)size + 1);
|
||||||
|
if (!buf) {
|
||||||
|
fclose(fp);
|
||||||
|
json_object_array_add(skipped, json_object_new_string(path));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t read_bytes = fread(buf, 1, (size_t)size, fp);
|
||||||
|
buf[read_bytes] = '\0';
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
paths[captured] = path;
|
||||||
|
contents[captured] = buf;
|
||||||
|
allocated_contents[captured] = buf;
|
||||||
|
captured++;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *response = NULL;
|
||||||
|
if (captured == 0) {
|
||||||
|
response = strdup("Error: no files could be read");
|
||||||
|
} else {
|
||||||
|
char *session_id = snapshot_resolve_session_id();
|
||||||
|
if (!session_id) {
|
||||||
|
response = strdup("Error: could not determine session ID");
|
||||||
|
} else {
|
||||||
|
db_handle db = db_open(NULL);
|
||||||
|
if (!db) {
|
||||||
|
response = strdup("Error: failed to open database");
|
||||||
|
} else {
|
||||||
|
long long snapshot_id = 0;
|
||||||
|
r_status_t status = db_snapshot_create(db, session_id,
|
||||||
|
json_object_get_string(desc_obj),
|
||||||
|
paths, contents, captured, &snapshot_id);
|
||||||
|
db_close(db);
|
||||||
|
|
||||||
|
if (status != R_SUCCESS) {
|
||||||
|
response = strdup("Error: failed to create snapshot in database");
|
||||||
|
} else {
|
||||||
|
struct json_object *result = json_object_new_object();
|
||||||
|
json_object_object_add(result, "snapshot_id", json_object_new_int64(snapshot_id));
|
||||||
|
json_object_object_add(result, "files_captured", json_object_new_int(captured));
|
||||||
|
json_object_object_add(result, "description",
|
||||||
|
json_object_new_string(json_object_get_string(desc_obj)));
|
||||||
|
json_object_object_add(result, "skipped", json_object_get(skipped));
|
||||||
|
response = strdup(json_object_to_json_string(result));
|
||||||
|
json_object_put(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(session_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < captured; i++) {
|
||||||
|
free(allocated_contents[i]);
|
||||||
|
}
|
||||||
|
free(paths);
|
||||||
|
free(contents);
|
||||||
|
free(allocated_contents);
|
||||||
|
json_object_put(skipped);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *create_snapshot_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("create_snapshot"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Creates a snapshot of specified files, storing their contents "
|
||||||
|
"in the database for later restoration. Use before making significant changes."));
|
||||||
|
|
||||||
|
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 *paths_prop = json_object_new_object();
|
||||||
|
json_object_object_add(paths_prop, "type", json_object_new_string("array"));
|
||||||
|
struct json_object *items = json_object_new_object();
|
||||||
|
json_object_object_add(items, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(paths_prop, "items", items);
|
||||||
|
json_object_object_add(paths_prop, "description",
|
||||||
|
json_object_new_string("Array of absolute file paths to snapshot."));
|
||||||
|
json_object_object_add(properties, "paths", paths_prop);
|
||||||
|
|
||||||
|
struct json_object *desc_prop = json_object_new_object();
|
||||||
|
json_object_object_add(desc_prop, "type", json_object_new_string("string"));
|
||||||
|
json_object_object_add(desc_prop, "description",
|
||||||
|
json_object_new_string("Description of what this snapshot captures and why."));
|
||||||
|
json_object_object_add(properties, "description", desc_prop);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("paths"));
|
||||||
|
json_object_array_add(required, json_object_new_string("description"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void list_snapshots_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
(void)args;
|
||||||
|
fprintf(stderr, " -> Listing snapshots\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *list_snapshots_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
(void)args;
|
||||||
|
|
||||||
|
char *session_id = snapshot_resolve_session_id();
|
||||||
|
if (!session_id) return strdup("Error: could not determine session ID");
|
||||||
|
|
||||||
|
db_handle db = db_open(NULL);
|
||||||
|
if (!db) {
|
||||||
|
free(session_id);
|
||||||
|
return strdup("Error: failed to open database");
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *result = NULL;
|
||||||
|
r_status_t status = db_snapshot_list(db, session_id, &result);
|
||||||
|
db_close(db);
|
||||||
|
free(session_id);
|
||||||
|
|
||||||
|
if (status != R_SUCCESS || !result) {
|
||||||
|
return strdup("Error: failed to list snapshots");
|
||||||
|
}
|
||||||
|
|
||||||
|
char *response = strdup(json_object_to_json_string(result));
|
||||||
|
json_object_put(result);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *list_snapshots_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("list_snapshots"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Lists all file snapshots for the current session, "
|
||||||
|
"showing ID, description, creation time, and file count."));
|
||||||
|
|
||||||
|
struct json_object *parameters = json_object_new_object();
|
||||||
|
json_object_object_add(parameters, "type", json_object_new_string("object"));
|
||||||
|
json_object_object_add(parameters, "properties", json_object_new_object());
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void restore_snapshot_print_action(const char *name, struct json_object *args) {
|
||||||
|
(void)name;
|
||||||
|
if (!args) return;
|
||||||
|
struct json_object *id_obj;
|
||||||
|
if (json_object_object_get_ex(args, "snapshot_id", &id_obj)) {
|
||||||
|
fprintf(stderr, " -> Restoring snapshot #%lld\n",
|
||||||
|
(long long)json_object_get_int64(id_obj));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *restore_snapshot_execute(tool_t *self, struct json_object *args) {
|
||||||
|
(void)self;
|
||||||
|
struct json_object *id_obj = NULL;
|
||||||
|
|
||||||
|
if (!json_object_object_get_ex(args, "snapshot_id", &id_obj)) {
|
||||||
|
return strdup("Error: missing 'snapshot_id' argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
long long snapshot_id = json_object_get_int64(id_obj);
|
||||||
|
|
||||||
|
db_handle db = db_open(NULL);
|
||||||
|
if (!db) return strdup("Error: failed to open database");
|
||||||
|
|
||||||
|
struct json_object *files = NULL;
|
||||||
|
r_status_t status = db_snapshot_get_files(db, snapshot_id, &files);
|
||||||
|
db_close(db);
|
||||||
|
|
||||||
|
if (status != R_SUCCESS || !files) {
|
||||||
|
return strdup("Error: failed to retrieve snapshot files");
|
||||||
|
}
|
||||||
|
|
||||||
|
int file_count = json_object_array_length(files);
|
||||||
|
if (file_count == 0) {
|
||||||
|
json_object_put(files);
|
||||||
|
return strdup("Error: snapshot contains no files");
|
||||||
|
}
|
||||||
|
|
||||||
|
int restored = 0;
|
||||||
|
struct json_object *failed = json_object_new_array();
|
||||||
|
|
||||||
|
for (int i = 0; i < file_count; i++) {
|
||||||
|
struct json_object *entry = json_object_array_get_idx(files, i);
|
||||||
|
struct json_object *path_obj = NULL;
|
||||||
|
struct json_object *content_obj = NULL;
|
||||||
|
|
||||||
|
if (!json_object_object_get_ex(entry, "path", &path_obj) ||
|
||||||
|
!json_object_object_get_ex(entry, "content", &content_obj)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *path = json_object_get_string(path_obj);
|
||||||
|
const char *content = json_object_get_string(content_obj);
|
||||||
|
if (!path || !content) continue;
|
||||||
|
|
||||||
|
snapshot_mkdirs(path);
|
||||||
|
|
||||||
|
char tmp_path[4096];
|
||||||
|
snprintf(tmp_path, sizeof(tmp_path), "%s.snapshot_tmp", path);
|
||||||
|
|
||||||
|
FILE *fp = fopen(tmp_path, "w");
|
||||||
|
if (!fp) {
|
||||||
|
json_object_array_add(failed, json_object_new_string(path));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t content_len = strlen(content);
|
||||||
|
size_t written = fwrite(content, 1, content_len, fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
if (written != content_len) {
|
||||||
|
unlink(tmp_path);
|
||||||
|
json_object_array_add(failed, json_object_new_string(path));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rename(tmp_path, path) != 0) {
|
||||||
|
unlink(tmp_path);
|
||||||
|
json_object_array_add(failed, json_object_new_string(path));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
restored++;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object *result = json_object_new_object();
|
||||||
|
json_object_object_add(result, "snapshot_id", json_object_new_int64(snapshot_id));
|
||||||
|
json_object_object_add(result, "restored", json_object_new_int(restored));
|
||||||
|
json_object_object_add(result, "failed", failed);
|
||||||
|
|
||||||
|
char *response = strdup(json_object_to_json_string(result));
|
||||||
|
json_object_put(result);
|
||||||
|
json_object_put(files);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct json_object *restore_snapshot_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("restore_snapshot"));
|
||||||
|
json_object_object_add(function, "description",
|
||||||
|
json_object_new_string("Restores files from a previously created snapshot. "
|
||||||
|
"Writes each file back to its original path atomically."));
|
||||||
|
|
||||||
|
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 *id_prop = json_object_new_object();
|
||||||
|
json_object_object_add(id_prop, "type", json_object_new_string("integer"));
|
||||||
|
json_object_object_add(id_prop, "description",
|
||||||
|
json_object_new_string("The snapshot ID to restore. Use list_snapshots to find available IDs."));
|
||||||
|
json_object_object_add(properties, "snapshot_id", id_prop);
|
||||||
|
|
||||||
|
json_object_object_add(parameters, "properties", properties);
|
||||||
|
|
||||||
|
struct json_object *required = json_object_new_array();
|
||||||
|
json_object_array_add(required, json_object_new_string("snapshot_id"));
|
||||||
|
json_object_object_add(parameters, "required", required);
|
||||||
|
json_object_object_add(parameters, "additionalProperties", json_object_new_boolean(0));
|
||||||
|
|
||||||
|
json_object_object_add(function, "parameters", parameters);
|
||||||
|
|
||||||
|
r_config_handle cfg = r_config_get_instance();
|
||||||
|
if (r_config_use_strict(cfg)) {
|
||||||
|
json_object_object_add(function, "strict", json_object_new_boolean(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "function", function);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
@ -41,6 +41,9 @@ extern tool_t *tool_automation_exploit_gen_create(void);
|
|||||||
extern tool_t *tool_csv_export_create(void);
|
extern tool_t *tool_csv_export_create(void);
|
||||||
extern tool_t *tool_spawn_agent_create(void);
|
extern tool_t *tool_spawn_agent_create(void);
|
||||||
extern tool_t *tool_deepsearch_create(void);
|
extern tool_t *tool_deepsearch_create(void);
|
||||||
|
extern tool_t *tool_create_snapshot_create(void);
|
||||||
|
extern tool_t *tool_list_snapshots_create(void);
|
||||||
|
extern tool_t *tool_restore_snapshot_create(void);
|
||||||
|
|
||||||
static tool_registry_t *global_registry = NULL;
|
static tool_registry_t *global_registry = NULL;
|
||||||
|
|
||||||
@ -90,6 +93,9 @@ tool_registry_t *tools_get_registry(void) {
|
|||||||
tool_registry_register(global_registry, tool_csv_export_create());
|
tool_registry_register(global_registry, tool_csv_export_create());
|
||||||
tool_registry_register(global_registry, tool_spawn_agent_create());
|
tool_registry_register(global_registry, tool_spawn_agent_create());
|
||||||
tool_registry_register(global_registry, tool_deepsearch_create());
|
tool_registry_register(global_registry, tool_deepsearch_create());
|
||||||
|
tool_registry_register(global_registry, tool_create_snapshot_create());
|
||||||
|
tool_registry_register(global_registry, tool_list_snapshots_create());
|
||||||
|
tool_registry_register(global_registry, tool_restore_snapshot_create());
|
||||||
|
|
||||||
return global_registry;
|
return global_registry;
|
||||||
}
|
}
|
||||||
@ -127,6 +133,9 @@ tool_registry_t *tool_registry_get_specialized(tool_registry_type_t type) {
|
|||||||
tool_registry_register(reg, tool_process_get_status_create());
|
tool_registry_register(reg, tool_process_get_status_create());
|
||||||
tool_registry_register(reg, tool_process_terminate_create());
|
tool_registry_register(reg, tool_process_terminate_create());
|
||||||
tool_registry_register(reg, tool_spawn_agent_create());
|
tool_registry_register(reg, tool_spawn_agent_create());
|
||||||
|
tool_registry_register(reg, tool_create_snapshot_create());
|
||||||
|
tool_registry_register(reg, tool_list_snapshots_create());
|
||||||
|
tool_registry_register(reg, tool_restore_snapshot_create());
|
||||||
} else if (type == TOOL_TYPE_SECURITY) {
|
} else if (type == TOOL_TYPE_SECURITY) {
|
||||||
tool_registry_register(reg, tool_terminal_create());
|
tool_registry_register(reg, tool_terminal_create());
|
||||||
tool_registry_register(reg, tool_network_check_create());
|
tool_registry_register(reg, tool_network_check_create());
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user