// retoor #include "tool.h" #include "db.h" #include "messages.h" #include "r_config.h" #include #include #include #include #include #include #include #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 mkdirs_recursive(const char *path) { if (!path || !*path) return -1; char temp[4096]; size_t len = strlen(path); if (len >= sizeof(temp)) return -1; memcpy(temp, path, len + 1); if (temp[len - 1] == '/') temp[len - 1] = '\0'; for (char *p = temp + 1; *p; p++) { if (*p == '/') { *p = '\0'; if (mkdir(temp, 0755) != 0 && errno != EEXIST) return -1; *p = '/'; } } if (mkdir(temp, 0755) != 0 && errno != EEXIST) return -1; return 0; } 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; } int rc = mkdirs_recursive(dir); 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; }