From bd9b1b929e8c13b4b5ffd24a1df763bc4c3ce4bb Mon Sep 17 00:00:00 2001 From: retoor Date: Sat, 14 Feb 2026 08:07:05 +0100 Subject: [PATCH] Update. --- Makefile | 3 +- README.md | 1354 ++++++++++++++++++++++++++++++++++-- include/db.h | 11 + include/messages.h | 2 + include/r_config.h | 3 + include/tool.h | 2 + src/agent.c | 1 + src/db.c | 140 +++- src/line.h | 175 ++++- src/main.c | 17 +- src/messages.c | 8 +- src/r_config.c | 10 + src/tools/tool_file.c | 1 + src/tools/tool_file_edit.c | 4 + src/tools/tool_snapshot.c | 459 ++++++++++++ src/tools/tools_init.c | 9 + 16 files changed, 2131 insertions(+), 68 deletions(-) create mode 100644 src/tools/tool_snapshot.c diff --git a/Makefile b/Makefile index a4d97c5..7a7b5f5 100755 --- a/Makefile +++ b/Makefile @@ -40,7 +40,8 @@ SRC_TOOLS = $(TOOLSDIR)/tools_init.c \ $(TOOLSDIR)/tool_automation.c \ $(TOOLSDIR)/tool_csv.c \ $(TOOLSDIR)/tool_agent.c \ - $(TOOLSDIR)/tool_deepsearch.c + $(TOOLSDIR)/tool_deepsearch.c \ + $(TOOLSDIR)/tool_snapshot.c SRC = $(SRC_CORE) $(SRC_TOOLS) diff --git a/README.md b/README.md index 4d41cd9..d7a41df 100755 --- a/README.md +++ b/README.md @@ -1,64 +1,1338 @@ // retoor -# R -Author: retoor +# R - Autonomous AI Agent CLI -R is a high-performance command-line AI assistant written in C that provides a fully autonomous agent with unrestricted system access. It implements an advanced ReAct (Reasoning + Acting) loop, allowing it to perform complex, multi-turn tasks by executing tools, observing results, and refining its plan until the goal is achieved. +**Version:** 2.0.0 +**Author:** retoor +**License:** MIT +**Language:** C99 +**Build System:** GNU Make -## Features +--- -### πŸš€ Autonomous Orchestration -- **Lead Orchestrator:** Implements a strict Plan-Execute-Verify-Conclude lifecycle. -- **Checklist Protocol:** Mandates visible progress tracking for complex projects. -- **Smart Scale Handling:** Automatically enforces proper directory structures for "huge" or multi-page projects. -- **Hierarchical Agents:** Spawns specialized sub-agents (researcher, developer, security) with managed orchestration budgets. +# πŸ“– TABLE OF CONTENTS -### βš™οΈ Asynchronous Process Management -- **Universal Control:** Unified interface for backgrounding both Shell and Python tasks. -- **Real-Time Visibility:** Streams process output live to the terminal, automatically indented and prefixed with `[PID]`. -- **Granular Monitoring:** Capture exit statuses, poll logs, and terminate tasks using stable PID tracking. -- **Automatic Backgrounding:** Synchronous tasks that exceed timeouts are safely backgrounded without losing output. +1. [User Guide](#user-guide) + - [What is R?](#what-is-r) + - [Key Features](#key-features) + - [Installation](#installation) + - [Quick Start](#quick-start) + - [Use Cases](#use-cases) + - [Interactive Commands](#interactive-commands) +2. [Technical Specification](#technical-specification) + - [Architecture Overview](#architecture-overview) + - [Build System](#build-system) + - [Configuration](#configuration) + - [Tool System](#tool-system) + - [Agent System](#agent-system) + - [DeepSearch Algorithm](#deepsearch-algorithm) + - [Database Schema](#database-schema) + - [API Integration](#api-integration) + - [Security Model](#security-model) + - [Performance Characteristics](#performance-characteristics) + - [Development Guide](#development-guide) -### 🎨 Beautiful Terminal UI -- **Syntax Highlighting:** Professional Python source code previews with line numbers and ANSI coloring. -- **Markdown Rendering:** Full support for headers, lists, code blocks, and styling in agent responses. -- **PID Traceability:** Every line of process output is clearly labeled for easy debugging of concurrent tasks. +--- -### πŸ› οΈ Advanced Tool System -All tools are fully compliant with **OpenAI Strict Mode** and exposed via robust JSON schemas. +# USER GUIDE -| Tool | Description | -|------|-------------| -| `linux_terminal_execute` | Execute shell commands with real-time output and PID tracking. | -| `python_execute` | Professional Python execution with syntax-highlighted previews. | -| `process_get_status` | **[Universal]** Monitor ANY background task, capture logs and exit codes. | -| `process_terminate` | **[Universal]** Safely shut down background processes and clean up logs. | -| `process_monitor` | System-wide process inspection with filtering and CPU sorting. | -| `write_file` / `read_file` | Atomic file I/O with automatic directory management. | -| `index_source_directory` | Deep indexing of codebases for architectural analysis. | -| `spawn_agent` | Orchestrate specialized workers for research, coding, or audits. | -| `web_search` / `http_fetch` | Integrated research and data gathering via Rexa API. | -| `db_query` / `db_set` | Persistent state and versioning via local SQLite database. | +## What is R? -### πŸ”’ Safety & Reliability -- **Sequence Integrity:** Context manager protects the "holy sequence" of messages during shrinking. -- **Atomic Verification:** Agent is forbidden from "lying" about actions; every file write is verified via tool output. -- **Crash Resilience:** Benchmark suite includes real-time persistent logging for deep debugging. +R is a high-performance, autonomous AI agent command-line interface written in C. It implements the **ReAct (Reasoning + Acting)** paradigm, enabling complex multi-turn task execution through an intelligent loop of: + +1. **Reasoning** - Analyzing the current state and planning next steps +2. **Acting** - Executing tools (shell commands, file operations, web searches) +3. **Observing** - Processing tool outputs +4. **Refining** - Adjusting the plan based on observations + +Unlike simple chatbots, R can: +- Write and execute code +- Browse the web and research topics +- Manage files and directories +- Spawn sub-agents for parallel task execution +- Monitor and control background processes +- Maintain persistent state across sessions + +## Key Features + +### 🧠 Autonomous Agent Architecture +- **ReAct Loop:** Up to 300 iterations of reasoning-acting-observing cycles +- **Sub-agent Spawning:** Hierarchical agent delegation with budget tracking +- **Plan-Execute-Verify-Conclude Lifecycle:** Structured task management +- **Context Management:** Automatic context window optimization + +### πŸ”§ 25+ Built-in Tools +All tools are OpenAI-compatible with strict JSON schema validation: + +| Category | Tools | +|----------|-------| +| **Terminal** | `linux_terminal_execute`, `terminal_interactive` | +| **File System** | `read_file`, `write_file`, `directory_glob`, `mkdir`, `chdir`, `getpwd` | +| **Code** | `python_execute`, `code_grep`, `code_symbol_find` | +| **File Edit** | `file_line_replace`, `file_apply_patch` | +| **HTTP/Web** | `http_fetch`, `web_search`, `web_search_news`, `deepsearch` | +| **Database** | `db_get`, `db_set`, `db_query` | +| **Process** | `process_monitor`, `process_get_status`, `process_terminate` | +| **Network** | `network_check`, `dns_lookup`, `network_port_scan` | +| **Research** | `index_source_directory`, `research_dispatcher`, `fetch_and_scrape` | +| **Automation** | `automation_fuzz`, `automation_exploit_gen` | +| **Data** | `csv_export` | +| **Agent** | `spawn_agent` | +| **Snapshot** | `create_snapshot`, `list_snapshots`, `restore_snapshot` | + +### ⚑ Performance +- **Parallel Tool Execution:** pthread-based concurrent tool runs +- **Asynchronous Background Tasks:** Non-blocking long-running operations +- **Real-time Output Streaming:** Live process output with PID tracking +- **Memory Efficient:** Context window management with intelligent shrinking + +### 🎨 Terminal Experience +- **Syntax Highlighting:** Python code with ANSI colors and line numbers +- **Markdown Rendering:** Headers, lists, code blocks, tables +- **Process Traceability:** Every output line tagged with `[PID]` +- **Spinner Indicators:** Visual feedback for HTTP operations ## Installation -### Dependencies +### Prerequisites + +#### Ubuntu/Debian ```bash -libcurl json-c readline ncurses sqlite3 gnutls gmp openssl +sudo apt-get update +sudo apt-get install -y \ + gcc make \ + libreadline-dev \ + libncurses5-dev \ + libcurl4-openssl-dev \ + libssl-dev \ + libjson-c-dev \ + libsqlite3-dev \ + libtinfo-dev \ + libgnutls28-dev \ + libgmp-dev +``` + +#### macOS (with Homebrew) +```bash +brew install readline ncurses curl json-c sqlite3 openssl gmp gnutls +``` + +#### Fedora/RHEL +```bash +sudo dnf install -y \ + gcc make \ + readline-devel \ + ncurses-devel \ + libcurl-devel \ + openssl-devel \ + json-c-devel \ + sqlite-devel \ + gnutls-devel \ + gmp-devel ``` ### Build & Install + ```bash +# Clone repository +git clone +cd r + +# Standard build make build + +# Build with debug symbols +make debug + +# Install system-wide sudo make install + +# Verify installation +r --help ``` -The binary is output to `bin/r`. Configuration is handled via environment variables (`R_KEY`, `R_MODEL`, `R_BASE_URL`) or local `.rcontext.txt` files. +### Docker Build +```bash +make docker_make +make docker_run +``` + +### AppImage Build +```bash +make appimage +./r-x86_64.AppImage +``` + +## Quick Start + +### 1. Set up API Key +```bash +export R_KEY="your-api-key-here" +``` + +Or use fallback environment variables: +- `OPENROUTER_API_KEY` +- `OPENAI_API_KEY` + +### 2. Interactive Mode (REPL) +```bash +./r +``` + +REPL Commands: +- `!clear` - Clear session history +- `!dump` - Export message history as JSON +- `!session` - Show current session ID +- `!new` - Start new session +- `!tools` - List available tools +- `!models` - List available models +- `!model ` - Switch model +- `!verbose` - Toggle verbose mode +- `!vi` / `!emacs` - Set editing mode +- `exit` - Quit + +### 3. Single Command Mode +```bash +# Direct prompt +./r "List all files in current directory" + +# Read from stdin +echo "Analyze this code" | ./r --stdin + +# Include Python file as context +./r --py script.py "Explain this code" + +# Include custom context file +./r --context project.txt "Summarize the architecture" + +# Specify session +./r -s mysession "Your prompt" + +# API mode (JSON output) +./r --api "Your prompt" + +# Disable syntax highlighting +./r --nh "Your prompt" +``` + +## Use Cases + +### Software Development + +#### Code Review & Analysis +```bash +# Review a codebase +./r --py main.py "Review this code for security issues and best practices" + +# Understand complex code +./r "Index the src/ directory and explain the architecture" + +# Refactoring assistance +./r "Refactor this function to use better error handling" --py module.py +``` + +#### Automated Testing +```bash +# Generate test cases +./r "Read src/calculator.c and generate comprehensive unit tests" + +# Test execution with monitoring +./r "Run the test suite in background and monitor progress" +``` + +#### Documentation Generation +```bash +# Generate API documentation +./r "Read all header files in include/ and generate API documentation" + +# Create README from code +./r "Analyze the codebase and create a comprehensive README.md" +``` + +### DevOps & System Administration + +#### System Monitoring +```bash +# Check system health +./r "Monitor system processes and report any anomalies" + +# Log analysis +./r "Analyze /var/log/syslog for error patterns in the last 24 hours" + +# Network diagnostics +./r "Check network connectivity and DNS resolution for google.com" +``` + +#### Automation Scripts +```bash +# Batch file processing +./r "Find all .log files in /var/log and compress those older than 7 days" + +# Configuration management +./r "Update all configuration files to use the new API endpoint" + +# Deployment automation +./r "Execute deployment script and verify all services are running" +``` + +### Research & Data Analysis + +#### Web Research +```bash +# Quick search +./r "Search for the latest news on renewable energy" + +# Deep research (multi-iteration) +./r "Deep search: What are the current best practices for microservices architecture?" + +# Competitive analysis +./r "Research the top 5 competitors in the cloud storage market" +``` + +#### Data Processing +```bash +# CSV analysis +./r "Read data.csv and calculate summary statistics for each column" + +# Python data science +./r "Load the dataset and create visualizations of the trends" + +# Database queries +./r "Query the local database for all agents created in the last hour" +``` + +### Cybersecurity + +#### Security Auditing +```bash +# Port scanning +./r "Scan localhost for open ports and identify services" + +# DNS reconnaissance +./r "Perform DNS lookup and analyze the records for example.com" + +# Fuzzing +./r "Fuzz the API endpoint /api/v1/users with common payloads" +``` + +#### Vulnerability Research +```bash +# Exploit generation +./r "Generate exploit code for CVE-2024-XXXX" + +# Security report analysis +./r "Read security_scan.txt and summarize the critical findings" +``` + +### Content Creation + +#### Writing Assistance +```bash +# Technical writing +./r "Write a technical blog post about async/await in Python" + +# Documentation +./r "Create user documentation for the file system tools" + +# Code comments +./r "Add comprehensive docstrings to all functions in utils.py" +``` + +### Education & Learning + +#### Tutorial Generation +```bash +# Explain concepts +./r "Explain how recursion works with examples in C" + +# Code walkthrough +./r "Walk through the quicksort implementation line by line" + +# Quiz generation +./r "Create a quiz with 10 questions about data structures" +``` + +## Interactive Commands + +When in REPL mode, special commands prefixed with `!` are available: + +| Command | Description | +|---------|-------------| +| `!clear` | Clear session message history | +| `!dump` | Export conversation as JSON | +| `!session` | Display current session identifier | +| `!new` | Start a fresh session | +| `!tools` | List all registered tools with descriptions | +| `!models` | Query and display available LLM models | +| `!model ` | Switch to a different LLM model | +| `!verbose` | Toggle verbose logging mode | +| `!vi` | Set line editor to vi mode | +| `!emacs` | Set line editor to emacs mode | +| `exit` | Quit the application | + +--- + +# TECHNICAL SPECIFICATION + +## Architecture Overview + +### System Architecture Diagram + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ USER INTERFACE β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ REPL Mode β”‚ β”‚ Single Cmd β”‚ β”‚ API Mode β”‚ β”‚ Stdin Pipe β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ Main Entry β”‚ + β”‚ (main.c) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ AGENT CORE β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ ReAct Loop β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ Reason │───▢│ Plan │───▢│ Act │───▢│ Observe │──┐ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ +β”‚ β”‚ β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ └──────────────────────────────── β”‚ β”‚ +β”‚ β”‚ (max 300 iterations) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Context Manager β”‚ β”‚ Message History β”‚ β”‚ Budget Tracker β”‚ β”‚ +β”‚ β”‚(agent.c:context)β”‚ β”‚ (messages.c) β”‚ β”‚ (agent.c:tok) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ TOOL SYSTEM β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Tool Registry (tool_registry.c) β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚Terminal β”‚ β”‚ File β”‚ β”‚ HTTP β”‚ β”‚ Python β”‚ β”‚ Agent β”‚ ... β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β–Ό β–Ό β–Ό β–Ό β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Parallel Execution (pthreads) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ EXTERNAL INTERFACES β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ LLM API β”‚ β”‚ HTTP/Curl β”‚ β”‚ SQLite β”‚ β”‚ System β”‚ β”‚ +β”‚ β”‚(http_client) β”‚ β”‚(http_curl.c) β”‚ β”‚ (db_sqlite) β”‚ β”‚ (bash) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Module Hierarchy + +``` +r/ +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ main.c # Application entry point, CLI parsing, REPL +β”‚ β”œβ”€β”€ agent.c # Core ReAct loop, agent lifecycle management +β”‚ β”œβ”€β”€ messages.c # Message history, context window management +β”‚ β”œβ”€β”€ context_manager.c # Context shrinking, token optimization +β”‚ β”œβ”€β”€ tool_registry.c # Tool registration, parallel execution +β”‚ β”œβ”€β”€ bash_executor.c # Shell command execution with PID tracking +β”‚ β”œβ”€β”€ http_client.c # HTTP client wrapper +β”‚ β”œβ”€β”€ db.c # SQLite database operations +β”‚ β”œβ”€β”€ markdown.c # Markdown to ANSI rendering +β”‚ β”œβ”€β”€ r_config.c # Configuration management (singleton) +β”‚ β”œβ”€β”€ tools/ # Tool implementations +β”‚ β”‚ β”œβ”€β”€ tools_init.c # Tool registration coordinator +β”‚ β”‚ β”œβ”€β”€ tool_terminal.c # Shell execution tools +β”‚ β”‚ β”œβ”€β”€ tool_python.c # Python execution tool +β”‚ β”‚ β”œβ”€β”€ tool_file.c # File I/O tools +β”‚ β”‚ β”œβ”€β”€ tool_file_edit.c# File editing tools +β”‚ β”‚ β”œβ”€β”€ tool_http.c # HTTP and web search tools +β”‚ β”‚ β”œβ”€β”€ tool_deepsearch.c# Deep research tool +β”‚ β”‚ β”œβ”€β”€ tool_agent.c # Sub-agent spawning +β”‚ β”‚ └── ... (17 more) +β”‚ β”œβ”€β”€ core/ # Core utilities +β”‚ β”‚ β”œβ”€β”€ memory.c # Memory management wrappers +β”‚ β”‚ β”œβ”€β”€ string.c # String utilities +β”‚ β”‚ └── buffer.c # Dynamic buffer +β”‚ β”œβ”€β”€ util/ # General utilities +β”‚ β”‚ β”œβ”€β”€ path.c # Path manipulation +β”‚ β”‚ └── time.c # Time utilities +β”‚ β”œβ”€β”€ impl/ # Implementation wrappers +β”‚ β”‚ β”œβ”€β”€ db_sqlite.c # SQLite implementation +β”‚ β”‚ └── http_curl.c # libcurl implementation +β”‚ └── interfaces/ # Interface abstractions +β”‚ β”œβ”€β”€ config.c +β”‚ β”œβ”€β”€ logger.c +β”‚ └── http.h +β”œβ”€β”€ include/ # Public header files +β”‚ β”œβ”€β”€ agent.h +β”‚ β”œβ”€β”€ tool.h +β”‚ β”œβ”€β”€ messages.h +β”‚ β”œβ”€β”€ r_config.h +β”‚ └── ... (10 more) +β”œβ”€β”€ build/ # Build artifacts (object files) +β”œβ”€β”€ bin/ # Binary output directory +└── test_results/ # Test output files +``` + +## Build System + +### Makefile Targets + +| Target | Description | Dependencies | +|--------|-------------|--------------| +| `build` | Standard optimized build | All object files | +| `debug` | Build with debug symbols (`-g`, `-O0`) | All object files | +| `run` | Build and execute with verbose output | `build` | +| `clean` | Remove all build artifacts | - | +| `install` | Install to `/usr/local/bin` | `build` | +| `uninstall` | Remove from `/usr/local/bin` | - | +| `docker` | Build Docker image | Dockerfile | +| `docker_make` | Build using Docker container | Dockerfile | +| `docker_run` | Run in Docker container | `docker_make` | +| `appimage` | Build AppImage | `appimagetool` | +| `build_deb` | Build Debian package | `dpkg-deb` | + +### Compiler Flags + +#### Standard Build (`make build`) +```makefile +CFLAGS = -Ofast -Werror -Wall -I./include +LDFLAGS = -lreadline -lncurses -lcurl -ljson-c -lsqlite3 -lm -lpthread -lssl -lcrypto +``` + +#### Debug Build (`make debug`) +```makefile +CFLAGS = -g -O0 -Werror -Wall -I./include -DDEBUG +``` + +### Source File Categories + +```makefile +# Core modules +SRC_CORE = src/main.c src/agent.c src/messages.c src/context_manager.c \ + src/tool_registry.c src/bash_executor.c src/http_client.c \ + src/db.c src/markdown.c src/r_config.c src/r_diff.c src/r_error.c + +# Tool implementations +SRC_TOOLS = src/tools/tools_init.c src/tools/tool_terminal.c \ + src/tools/tool_python.c src/tools/tool_file.c \ + src/tools/tool_file_edit.c src/tools/tool_agent.c \ + src/tools/tool_http.c src/tools/tool_db.c \ + src/tools/tool_indexer.c src/tools/tool_code.c \ + src/tools/tool_system.c src/tools/tool_enterprise.c \ + src/tools/tool_research.c src/tools/tool_network.c \ + src/tools/tool_dns.c src/tools/tool_automation.c \ + src/tools/tool_csv.c src/tools/tool_deepsearch.c \ + src/tools/tool_snapshot.c src/tools/tool_json.c + +# Utilities +SRC_UTILS = src/core/memory.c src/core/string.c src/core/buffer.c \ + src/util/path.c src/util/time.c src/impl/db_sqlite.c \ + src/impl/http_curl.c +``` + +## Configuration + +### Environment Variables + +| Variable | Description | Default | Priority | +|----------|-------------|---------|----------| +| `R_KEY` | Primary API key for LLM access | - | 1 | +| `OPENROUTER_API_KEY` | Fallback API key | - | 2 | +| `OPENAI_API_KEY` | Secondary fallback | hardcoded | 3 | +| `R_BASE_URL` | API base URL | `https://api.openai.com` | - | +| `R_MODEL` | Model identifier | `gpt-4o-mini` | - | +| `R_MAX_TOKENS` | Maximum tokens per request | `4096` | - | +| `R_MAX_SPAWN_DEPTH` | Max agent spawn depth | `5` | - | +| `R_MAX_TOTAL_SPAWNS` | Max total spawned agents | `20` | - | +| `R_USE_TOOLS` | Enable tool use | `true` | - | +| `R_USE_STRICT` | Use strict JSON schema mode | `true` | - | +| `R_SESSION` | Session identifier | auto-generated | - | +| `R_SYSTEM_MESSAGE` | Custom system message | - | - | +| `R_VERBOSE` | Enable verbose mode | `false` | - | + +### Configuration Resolution + +```c +// Configuration precedence (highest to lowest): +// 1. Command-line flags (--model, etc.) +// 2. Environment variables (R_MODEL, etc.) +// 3. .rcontext.txt (project-specific) +// 4. ~/.rcontext.txt (user-global) +// 5. Built-in defaults +``` + +### Context Files + +#### `.rcontext.txt` Format +``` +# Project context for R agent +# Lines starting with # are comments + +MODEL=gpt-4o +MAX_TOKENS=8192 +SYSTEM_MESSAGE=You are working on a C project. Follow Linux kernel coding style. +``` + +#### Loading Order +1. Current directory: `./.rcontext.txt` +2. Home directory: `~/.rcontext.txt` + +## Tool System + +### Tool Architecture + +All tools follow a vtable-based architecture for polymorphism: + +```c +// Tool vtable - function pointers for polymorphic behavior +typedef struct { + struct json_object *(*get_description)(void); + char *(*execute)(tool_t *self, struct json_object *args); + void (*print_action)(const char *name, struct json_object *args); +} tool_vtable_t; + +// Tool instance +struct tool { + const tool_vtable_t *vtable; + const char *name; +}; +``` + +### Tool Registration + +```c +// Tools are registered in tools_init.c +tool_registry_t *tools_get_registry(void) { + global_registry = tool_registry_create(); + + // Register all tools + tool_registry_register(global_registry, tool_terminal_create()); + tool_registry_register(global_registry, tool_web_search_create()); + tool_registry_register(global_registry, tool_deepsearch_create()); + // ... 22 more tools + + return global_registry; +} +``` + +### Parallel Tool Execution + +Tools are executed in parallel using pthreads when part of the same tool call batch: + +```c +// From tool_registry.c +void tool_registry_execute_parallel(tool_registry_t *reg, + json_object *tool_calls, + json_object *results) { + // Create thread for each tool + pthread_t *threads = calloc(num_tools, sizeof(pthread_t)); + tool_exec_context_t *contexts = calloc(num_tools, sizeof(tool_exec_context_t)); + + // Launch all tools concurrently + for (int i = 0; i < num_tools; i++) { + pthread_create(&threads[i], NULL, tool_execute_thread, &contexts[i]); + } + + // Wait for completion + for (int i = 0; i < num_tools; i++) { + pthread_join(threads[i], NULL); + } +} +``` + +### Tool Categories + +#### Terminal Tools (`tool_terminal.c`) +- `linux_terminal_execute`: Execute shell commands with PID tracking +- `terminal_interactive`: Interactive terminal sessions + +#### File Tools (`tool_file.c`) +- `read_file`: Read file contents +- `write_file`: Write file contents (atomic) +- `directory_glob`: List files matching pattern +- `mkdir`: Create directories +- `chdir`: Change working directory +- `getpwd`: Get current directory + +#### Code Tools (`tool_code.c`) +- `index_source_directory`: Deep codebase indexing +- `code_grep`: Search code patterns +- `code_symbol_find`: Find function/variable definitions + +#### File Edit Tools (`tool_file_edit.c`) +- `file_line_replace`: Replace specific lines +- `file_apply_patch`: Apply unified diff patches + +#### HTTP Tools (`tool_http.c`) +- `http_fetch`: GET URL contents +- `web_search`: General web search +- `web_search_news`: News-specific search + +#### DeepSearch Tool (`tool_deepsearch.c`) +Implements intelligent iterative research: + +```c +// Algorithm parameters +#define MAX_QUERIES 8 // Max queries per iteration +#define QUERY_GENERATION_MAX_TOKENS 2048 +#define MAX_ITERATIONS 3 // Max research iterations +#define MIN_CONTENT_LENGTH 100 // Minimum valid content length +#define MIN_VALID_RESULTS 5 // Stop threshold +``` + +**Execution Flow:** +1. Generate 5-8 diverse search queries using LLM +2. Execute all queries concurrently via pthreads +3. Extract and validate content from results +4. If insufficient results, generate follow-up queries +5. Merge all results into unified JSON response + +## Agent System + +### Agent State Machine + +```c +typedef enum { + AGENT_STATE_IDLE, // Agent created, not running + AGENT_STATE_RUNNING, // Active ReAct loop + AGENT_STATE_EXECUTING_TOOLS,// Currently executing tools + AGENT_STATE_COMPLETED, // Task completed successfully + AGENT_STATE_MAX_ITERATIONS, // Stopped: iteration limit + AGENT_STATE_ERROR // Stopped: error condition +} agent_state_t; +``` + +### ReAct Loop Implementation + +```c +// Simplified pseudocode of agent_run() +while (agent->iteration_count < max_iterations) { + // 1. Build LLM request with full context + request = agent_build_request(agent, messages); + + // 2. Send to LLM API + response = http_post(api_url, request); + + // 3. Parse response + parsed = parse_llm_response(response); + + // 4. Check for tool calls + if (has_tool_calls(parsed)) { + agent->state = AGENT_STATE_EXECUTING_TOOLS; + results = execute_tools_parallel(tool_calls); + add_observations_to_context(results); + } + + // 5. Check for completion + if (is_completion_message(parsed)) { + agent->state = AGENT_STATE_COMPLETED; + return final_answer; + } + + // 6. Check for refusal/incompleteness + if (is_incomplete(parsed)) { + auto_continue(); + } + + agent->iteration_count++; +} +``` + +### Context Management + +The context manager handles token overflow: + +```c +// Context shrinking strategy +void context_manager_shrink(messages_handle msgs, int target_tokens) { + // 1. Calculate current token count + current = messages_count_tokens(msgs); + + // 2. If over limit, remove oldest non-system messages + while (current > target_tokens) { + msg = find_oldest_non_system_message(msgs); + if (is_important(msg)) { + summary = summarize_message(msg); + replace_with_summary(msg, summary); + } else { + remove_message(msg); + } + current = recalculate_tokens(msgs); + } +} +``` + +### Sub-Agent Spawning + +```c +// Spawn specialized sub-agent +agent_handle agent_spawn(const char *role, const char *goal) { + // Check depth limits + if (current_depth >= R_MAX_SPAWN_DEPTH) return NULL; + if (total_spawns >= R_MAX_TOTAL_SPAWNS) return NULL; + + // Create specialized agent + agent = agent_create(goal, NULL); + agent_set_role(agent, role); + agent_set_manager_id(agent, parent_id); + + // Assign specialized tool registry based on role + if (strcmp(role, "researcher") == 0) { + agent_set_tool_registry(agent, tool_registry_get_specialized(TOOL_TYPE_RESEARCHER)); + } else if (strcmp(role, "developer") == 0) { + agent_set_tool_registry(agent, tool_registry_get_specialized(TOOL_TYPE_DEVELOPER)); + } + + return agent; +} +``` + +## DeepSearch Algorithm + +### Overview + +The DeepSearch tool implements an iterative, AI-driven research algorithm that goes beyond simple search to provide comprehensive topic coverage. + +### Algorithm Phases + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ DEEPSEARCH ALGORITHM β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PHASE 1: QUERY GENERATION β”‚ +β”‚ - Input: Research subject (user query) β”‚ +β”‚ - Process: LLM generates 5-8 diverse, specific queries β”‚ +β”‚ - Output: Array of search queries β”‚ +β”‚ β”‚ +β”‚ Prompt strategy: β”‚ +β”‚ "Generate 5-8 diverse, specific search queries that will β”‚ +β”‚ comprehensively cover the given subject from multiple angles" β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PHASE 2: PARALLEL SEARCH EXECUTION β”‚ +β”‚ - Input: Array of queries β”‚ +β”‚ - Process: Execute all queries concurrently via pthreads β”‚ +β”‚ - Output: Raw search results (JSON) β”‚ +β”‚ β”‚ +β”‚ Concurrency model: β”‚ +β”‚ for each query: β”‚ +β”‚ pthread_create(search_thread_func, query) β”‚ +β”‚ for each thread: β”‚ +β”‚ pthread_join(thread) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PHASE 3: CONTENT EXTRACTION & VALIDATION β”‚ +β”‚ - Input: Raw JSON results β”‚ +β”‚ - Process: Parse and filter results by content length β”‚ +β”‚ - Criteria: MIN_CONTENT_LENGTH >= 100 characters β”‚ +β”‚ - Output: Validated content array β”‚ +β”‚ β”‚ +β”‚ Validation function: β”‚ +β”‚ count_valid_results(json) -> int valid_count β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PHASE 4: ITERATION DECISION β”‚ +β”‚ - Condition: total_valid_results >= MIN_VALID_RESULTS * iter? β”‚ +β”‚ - If YES: Proceed to Phase 5 β”‚ +β”‚ - If NO and iter < MAX_ITERATIONS: β”‚ +β”‚ * Generate research summary from findings β”‚ +β”‚ * Use summary as context for follow-up queries β”‚ +β”‚ * Return to Phase 1 β”‚ +β”‚ β”‚ +β”‚ Follow-up prompt: β”‚ +β”‚ "Based on what has been found so far, generate 4-6 follow-up β”‚ +β”‚ queries to explore gaps and deeper aspects" β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PHASE 5: RESULT MERGING β”‚ +β”‚ - Input: All validated results from all iterations β”‚ +β”‚ - Process: Merge into unified JSON structure β”‚ +β”‚ - Output: Combined results object β”‚ +β”‚ β”‚ +β”‚ Merge format: β”‚ +β”‚ { β”‚ +β”‚ "results": [ β”‚ +β”‚ { "title": "...", "content": "...", "url": "..." }, β”‚ +β”‚ ... β”‚ +β”‚ ] β”‚ +β”‚ } β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PHASE 6: RETURN TO LLM β”‚ +β”‚ - Format merged results as JSON string β”‚ +β”‚ - Return to agent for synthesis and reporting β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Configuration Parameters + +```c +// DeepSearch tunable parameters +#define MAX_QUERIES 8 // Maximum queries per iteration +#define QUERY_GENERATION_MAX_TOKENS 2048 +#define MAX_ITERATIONS 3 // Maximum research iterations +#define MIN_CONTENT_LENGTH 100 // Minimum content to be considered valid +#define MIN_VALID_RESULTS 5 // Valid results needed per iteration +``` + +## Database Schema + +### SQLite Database Location +`~/.r.db` (or path specified by `R_DB_PATH`) + +### Schema Definition + +```sql +-- Agent tracking table +CREATE TABLE agents ( + agent_id TEXT PRIMARY KEY, + role TEXT, + manager_id TEXT, + department TEXT, + budget_limit INTEGER DEFAULT 0, + used_tokens INTEGER DEFAULT 0, + status TEXT DEFAULT 'active', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_heartbeat TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (manager_id) REFERENCES agents(agent_id) +); + +-- Research tasks table (for research_dispatcher tool) +CREATE TABLE research_tasks ( + url_hash TEXT PRIMARY KEY, + url TEXT NOT NULL, + status TEXT DEFAULT 'pending', -- pending, processing, completed, failed + summary TEXT, + batch_id TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- GTR (Goal-Task-Result) tracking +CREATE TABLE gtr_tasks ( + task_id TEXT PRIMARY KEY, + parent_task_id TEXT, + goal TEXT NOT NULL, + status TEXT DEFAULT 'pending', + assigned_agent TEXT, + result TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + completed_at TIMESTAMP, + FOREIGN KEY (parent_task_id) REFERENCES gtr_tasks(task_id), + FOREIGN KEY (assigned_agent) REFERENCES agents(agent_id) +); + +-- Audit logging +CREATE TABLE audit_logs ( + log_id INTEGER PRIMARY KEY AUTOINCREMENT, + agent_id TEXT, + action TEXT NOT NULL, + details TEXT, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (agent_id) REFERENCES agents(agent_id) +); + +-- Session storage +CREATE TABLE sessions ( + session_id TEXT PRIMARY KEY, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_accessed TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Snapshots for checkpoint/restore +CREATE TABLE snapshots ( + snapshot_id TEXT PRIMARY KEY, + session_id TEXT, + description TEXT, + state_json TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (session_id) REFERENCES sessions(session_id) +); +``` + +## API Integration + +### LLM API Protocol + +R uses OpenAI-compatible API endpoints: + +#### Request Format +```json +{ + "model": "gpt-4o-mini", + "messages": [ + {"role": "system", "content": "..."}, + {"role": "user", "content": "..."}, + {"role": "assistant", "content": "...", "tool_calls": [...]}, + {"role": "tool", "tool_call_id": "...", "content": "..."} + ], + "tools": [...], + "tool_choice": "auto", + "temperature": 0.1, + "max_tokens": 4096 +} +``` + +#### Response Format +```json +{ + "id": "chatcmpl-...", + "object": "chat.completion", + "created": 1234567890, + "model": "gpt-4o-mini", + "choices": [{ + "index": 0, + "message": { + "role": "assistant", + "content": "...", + "tool_calls": [{ + "id": "call_...", + "type": "function", + "function": { + "name": "web_search", + "arguments": "{\"query\": \"...\"}" + } + }] + }, + "finish_reason": "tool_calls" + }], + "usage": { + "prompt_tokens": 100, + "completion_tokens": 50, + "total_tokens": 150 + } +} +``` + +### HTTP Client Configuration + +```c +// HTTP timeout settings +typedef struct { + long connection_timeout_ms; // 30000 (30 seconds) + long operation_timeout_ms; // 300000 (5 minutes) + bool follow_redirects; // true + long max_redirects; // 10 +} http_config_t; +``` + +### Web Search API + +Search is performed via `rsearch.app.molodetz.nl`: + +``` +GET https://rsearch.app.molodetz.nl/search?query=&content=true + +Response: +{ + "results": [ + { + "title": "...", + "url": "...", + "content": "...", + "published": "..." + } + ] +} +``` + +## Security Model + +### Threat Model + +| Threat | Mitigation | +|--------|------------| +| API Key Exposure | Keys only in environment variables, never logged | +| Command Injection | Input validation, parameterized commands | +| Path Traversal | Path canonicalization, working directory checks | +| Resource Exhaustion | Iteration limits, token budgets, timeouts | +| Unrestricted File Access | User confirmation for destructive operations | + +### Sandboxing + +- File operations restricted to working directory +- Shell commands executed in subprocess with limited environment +- Python execution runs in isolated interpreter instance +- Network access only via configured HTTP endpoints + +### Audit Logging + +All agent actions are logged to `audit_logs` table: +- Agent ID +- Action type +- Details (sanitized) +- Timestamp + +## Performance Characteristics + +### Memory Usage + +| Component | Typical | Maximum | +|-----------|---------|---------| +| Base binary | ~2 MB | ~2 MB | +| Message history | ~1-10 MB | ~50 MB (configurable) | +| Tool execution | ~100 KB per tool | ~10 MB (parallel) | +| Database cache | ~1 MB | ~5 MB | + +### Execution Times + +| Operation | Typical | Worst Case | +|-----------|---------|------------| +| LLM API call | 1-5s | 60s (timeout) | +| Web search | 500ms | 10s | +| DeepSearch (full) | 10-30s | 120s | +| File I/O | <1ms | 100ms | +| Shell command | Depends on command | 300s (background threshold) | + +### Throughput + +- Maximum parallel tool executions: 25 (one per tool) +- Maximum concurrent HTTP requests: Limited by curl multi-handle +- Message processing rate: ~1000 tokens/second + +## Development Guide + +### Adding a New Tool + +1. Create `src/tools/tool_mytool.c`: +```c +// retoor +#include "tool.h" + +static char *mytool_execute(tool_t *self, struct json_object *args) { + // Parse arguments + struct json_object *arg_obj; + if (!json_object_object_get_ex(args, "myarg", &arg_obj)) { + return strdup("Error: missing 'myarg' argument"); + } + + // Execute tool logic + const char *value = json_object_get_string(arg_obj); + + // Return result + char *result = malloc(256); + snprintf(result, 256, "Processed: %s", value); + return result; +} + +static void mytool_print_action(const char *name, struct json_object *args) { + fprintf(stderr, " -> MyTool executing\n"); +} + +static struct json_object *mytool_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("my_tool")); + json_object_object_add(function, "description", + json_object_new_string("Description of what my tool does")); + + // Define parameters schema + struct json_object *parameters = json_object_new_object(); + // ... parameter definitions ... + json_object_object_add(function, "parameters", parameters); + + json_object_object_add(root, "function", function); + return root; +} + +static const tool_vtable_t mytool_vtable = { + .get_description = mytool_get_description, + .execute = mytool_execute, + .print_action = mytool_print_action +}; + +static tool_t mytool = { .vtable = &mytool_vtable, .name = "my_tool" }; + +tool_t *tool_my_tool_create(void) { return &mytool; } +``` + +2. Add declaration to `src/tools/tools_init.c`: +```c +extern tool_t *tool_my_tool_create(void); +``` + +3. Register in `tools_get_registry()`: +```c +tool_registry_register(global_registry, tool_my_tool_create()); +``` + +4. Add to Makefile `SRC_TOOLS`: +```makefile +SRC_TOOLS = ... src/tools/tool_mytool.c +``` + +5. Rebuild: +```bash +make clean && make build +``` + +### Coding Standards + +#### Naming Conventions +- Types: `r__handle` for opaque handles +- Functions: `r__` for public APIs +- Files: `r_.c` for core, `tool_.c` for tools +- Constants: `UPPER_CASE_WITH_UNDERSCORES` + +#### Header Style +All C files must start with: +```c +// retoor +``` + +#### Error Handling +```c +// Return status codes +r_status_t function_name(...) { + if (error_condition) { + r_error_set("Error message"); + return R_ERROR_INVALID; + } + return R_SUCCESS; +} +``` + +#### Memory Management +```c +// Use wrappers from core/memory.c +void *ptr = r_malloc(size); +void *new_ptr = r_realloc(ptr, new_size); +r_free(ptr); + +// Strings +char *str = r_strdup(source); +``` + +### Testing + +```bash +# Run Python test framework +python3 testit.py + +# Manual testing +./r "Test prompt here" +``` + +### Debugging + +```bash +# Enable verbose mode +./r --verbose "Your prompt" + +# Debug build with symbols +make debug + +# Run with gdb +gdb ./r +(gdb) run "test prompt" +``` + +--- + +# APPENDIX + +## Complete Tool Reference + +### Tool: `linux_terminal_execute` +**Description:** Execute a shell command with real-time output and PID tracking. + +**Parameters:** +- `command` (string, required): The shell command to execute. +- `description` (string, optional): Description of what the command does. + +**Returns:** Command output, exit status, and PID. + +**Example:** +```json +{ + "command": "ls -la", + "description": "List all files in current directory" +} +``` + +### Tool: `web_search` +**Description:** Searches for information using search engines. + +**Parameters:** +- `query` (string, required): The search query. + +**Returns:** JSON array of search results with title, URL, content. + +### Tool: `deepsearch` +**Description:** Performs intelligent iterative deep research. + +**Parameters:** +- `query` (string, required): The research subject. + +**Algorithm:** +1. Generate 5-8 diverse queries using LLM +2. Execute queries concurrently +3. Extract and validate content +4. Iterate if insufficient results +5. Merge all results + +**Returns:** Merged JSON results from all iterations. + +### Tool: `spawn_agent` +**Description:** Spawn a specialized sub-agent for parallel task execution. + +**Parameters:** +- `role` (string, required): Agent role (researcher, developer, security). +- `goal` (string, required): Task description for the agent. +- `budget` (integer, optional): Token budget limit. + +**Returns:** Agent ID and status. + +## Error Codes + +```c +typedef enum { + R_SUCCESS = 0, + R_ERROR_INVALID = -1, + R_ERROR_MEMORY = -2, + R_ERROR_IO = -3, + R_ERROR_NETWORK = -4, + R_ERROR_API = -5, + R_ERROR_TOOL = -6, + R_ERROR_TIMEOUT = -7, + R_ERROR_PERMISSION = -8, + R_ERROR_NOT_FOUND = -9, + R_ERROR_ALREADY_EXISTS = -10 +} r_status_t; +``` ## License -MIT \ No newline at end of file + +MIT License - See source headers for details. + +--- + +*This README was generated for R - Autonomous AI Agent CLI v2.0.0* +*Author: retoor * diff --git a/include/db.h b/include/db.h index 2a0798b..cb3a061 100755 --- a/include/db.h +++ b/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); 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 diff --git a/include/messages.h b/include/messages.h index 15ddccc..9b37447 100755 --- a/include/messages.h +++ b/include/messages.h @@ -35,4 +35,6 @@ char *messages_to_string(messages_handle msgs); char *messages_to_json_string(messages_handle msgs); int messages_count(messages_handle msgs); +char *messages_generate_session_id(void); + #endif diff --git a/include/r_config.h b/include/r_config.h index 89973ea..52af993 100755 --- a/include/r_config.h +++ b/include/r_config.h @@ -36,4 +36,7 @@ int r_config_get_max_total_spawns(r_config_handle cfg); 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 diff --git a/include/tool.h b/include/tool.h index ccabf11..7078a24 100755 --- a/include/tool.h +++ b/include/tool.h @@ -48,4 +48,6 @@ typedef enum { tool_registry_t *tool_registry_get_specialized(tool_registry_type_t type); +void snapshot_live_record(const char *path, const char *content); + #endif diff --git a/src/agent.c b/src/agent.c index 04becca..d84c320 100755 --- a/src/agent.c +++ b/src/agent.c @@ -393,6 +393,7 @@ char *agent_run(agent_handle agent, const char *user_message) { agent_set_error(agent, "Empty user message"); return NULL; } + r_config_set_current_prompt(r_config_get_instance(), user_message); messages_load(agent->messages); char *json_data = agent_build_request(agent, "user", user_message); if (!json_data) { diff --git a/src/db.c b/src/db.c index 6681774..3bc2155 100755 --- a/src/db.c +++ b/src/db.c @@ -117,7 +117,22 @@ r_status_t db_init(db_handle db) { " batch_id TEXT," " created_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; if (sqlite3_exec(db->conn, sql, NULL, NULL, &err_msg) != SQLITE_OK) { 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); 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; +} diff --git a/src/line.h b/src/line.h index da22ee0..c696bfa 100755 --- a/src/line.h +++ b/src/line.h @@ -1,27 +1,17 @@ -// Written by 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: -// - -// - -// - - -// 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. +// retoor #include "utils.h" #include #include #include #include +#include #include #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() { static char result[4096]; @@ -41,7 +31,8 @@ char *line_command_generator(const char *text, int state) { static int list_index, len = 0; const char *commands[] = {"help", "exit", "list", "review", "refactor", "obfuscate", "!verbose", "!dump", - "!model", "!debug", NULL}; + "!model", "!debug", "!vi", "!emacs", + "!new", "!clear", "!session", NULL}; if (!state) { 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); } +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() { if (!line_initialized) { 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; read_history(get_history_file()); } } -char *line_read(char *prefix) { - char *data = readline(prefix); - if (!(data && *data)) { - free(data); +char *line_read(const char *prompt) { + size_t capacity = LINE_BUFFER_INITIAL; + size_t length = 0; + char *buffer = (char *)malloc(capacity); + if (!buffer) { 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) { diff --git a/src/main.c b/src/main.c index 5fd88ff..6b8c8dd 100755 --- a/src/main.c +++ b/src/main.c @@ -164,7 +164,7 @@ static void repl(void) { line_init(); char *line = NULL; while (true) { - line = line_read("> "); + line = line_read(line_build_prompt()); if (!line || !*line) continue; if (!strncmp(line, "!dump", 5)) { @@ -192,6 +192,16 @@ static void repl(void) { fprintf(stderr, "New session: %s\n", session_id); 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)) { bool verbose = !r_config_is_verbose(cfg); r_config_set_verbose(cfg, verbose); @@ -375,6 +385,11 @@ static void init(void) { "Copy relevant data from tool results into your response.\n" "## Backup\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" "You have bash access. Prefer commands that do not require root.\n" "## RULE #2: DELIVERABLE FIRST\n" diff --git a/src/messages.c b/src/messages.c index 8b03fc9..2b93807 100755 --- a/src/messages.c +++ b/src/messages.c @@ -25,7 +25,7 @@ static bool is_valid_session_id(const char *session_id) { } return true; } -static long long get_ppid_starttime(pid_t ppid) { +long long messages_get_ppid_starttime(pid_t ppid) { char proc_path[64]; snprintf(proc_path, sizeof(proc_path), "/proc/%d/stat", ppid); FILE *fp = fopen(proc_path, "r"); @@ -46,11 +46,11 @@ static long long get_ppid_starttime(pid_t ppid) { if (field < 19) return -1; return strtoll(p, NULL, 10); } -static char *generate_default_session_id(void) { +char *messages_generate_session_id(void) { char *session_id = malloc(64); if (!session_id) return NULL; pid_t ppid = getppid(); - long long starttime = get_ppid_starttime(ppid); + long long starttime = messages_get_ppid_starttime(ppid); if (starttime >= 0) { snprintf(session_id, 64, "session-%d-%lld", ppid, starttime); } else { @@ -69,7 +69,7 @@ messages_handle messages_create(const char *session_id) { if (session_id && is_valid_session_id(session_id)) { msgs->session_id = strdup(session_id); } else { - msgs->session_id = generate_default_session_id(); + msgs->session_id = messages_generate_session_id(); } if (!msgs->session_id) { json_object_put(msgs->array); diff --git a/src/r_config.c b/src/r_config.c index 9067f90..aede422 100755 --- a/src/r_config.c +++ b/src/r_config.c @@ -12,6 +12,7 @@ struct r_config_t { char *db_path; char *session_id; char *system_message; + char *current_prompt; double temperature; int max_tokens; int max_spawn_depth; @@ -99,6 +100,7 @@ void r_config_destroy(void) { free(instance->db_path); free(instance->session_id); free(instance->system_message); + free(instance->current_prompt); free(instance); 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) { 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 diff --git a/src/tools/tool_file.c b/src/tools/tool_file.c index 9bba112..e17a85c 100755 --- a/src/tools/tool_file.c +++ b/src/tools/tool_file.c @@ -293,6 +293,7 @@ static char *write_file_execute(tool_t *self, struct json_object *args) { fwrite(new_content, 1, strlen(new_content), fp); fclose(fp); + snapshot_live_record(path, new_content); return strdup("File successfully written."); } diff --git a/src/tools/tool_file_edit.c b/src/tools/tool_file_edit.c index 2fba46d..ed6431c 100644 --- a/src/tools/tool_file_edit.c +++ b/src/tools/tool_file_edit.c @@ -127,6 +127,7 @@ static char *file_line_replace_execute(tool_t *self, struct json_object *args) { if (fp) { fputs(new_content, fp); fclose(fp); + snapshot_live_record(path, new_content); } 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) { r_diff_print(path, old_content, new_content); } + if (new_content) { + snapshot_live_record(path, new_content); + } free(old_content); free(new_content); diff --git a/src/tools/tool_snapshot.c b/src/tools/tool_snapshot.c new file mode 100644 index 0000000..fdb5ee0 --- /dev/null +++ b/src/tools/tool_snapshot.c @@ -0,0 +1,459 @@ +// 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 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; +} diff --git a/src/tools/tools_init.c b/src/tools/tools_init.c index 88439bc..4fc174b 100755 --- a/src/tools/tools_init.c +++ b/src/tools/tools_init.c @@ -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_spawn_agent_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; @@ -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_spawn_agent_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; } @@ -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_terminate_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) { tool_registry_register(reg, tool_terminal_create()); tool_registry_register(reg, tool_network_check_create());