opencrabs 0.3.25

The autonomous, self-improving AI agent. Single Rust binary. Every channel. Install with: cargo install opencrabs
Documentation
//! Rebuild Tool
//!
//! Lets the agent build OpenCrabs from source and exec() restart automatically.
//! The build runs via `SelfUpdater::build_streaming` — progress lines are forwarded
//! through the ProgressCallback so the TUI shows them live.  On success, a
//! `ProgressEvent::RestartReady` is emitted which triggers an automatic exec() restart
//! (no user prompt needed).

use super::error::Result;
use super::r#trait::{Tool, ToolCapability, ToolExecutionContext, ToolResult};
use crate::brain::SelfUpdater;
use crate::brain::agent::{ProgressCallback, ProgressEvent};
use async_trait::async_trait;
use serde_json::Value;

/// Agent-callable tool that builds the project and auto-restarts via exec().
pub struct RebuildTool {
    progress: Option<ProgressCallback>,
}

impl RebuildTool {
    pub fn new(progress: Option<ProgressCallback>) -> Self {
        Self { progress }
    }
}

#[async_trait]
impl Tool for RebuildTool {
    fn name(&self) -> &str {
        "rebuild"
    }

    fn description(&self) -> &str {
        "Build OpenCrabs from source (cargo build --release) and signal the TUI to hot-restart. \
         Call this after editing source code to apply your changes. On success the binary is \
         exec()-replaced automatically (no prompt). On failure the compiler output is returned."
    }

    fn input_schema(&self) -> Value {
        serde_json::json!({
            "type": "object",
            "properties": {},
            "required": []
        })
    }

    fn capabilities(&self) -> Vec<ToolCapability> {
        vec![ToolCapability::SystemModification]
    }

    async fn execute(&self, _input: Value, context: &ToolExecutionContext) -> Result<ToolResult> {
        let updater = match SelfUpdater::auto_detect() {
            Ok(u) => u,
            Err(e) => {
                return Ok(ToolResult::error(format!(
                    "Cannot detect project root: {}",
                    e
                )));
            }
        };

        let cb = self.progress.clone();
        let sid = context.session_id;

        // Stream build progress as rolling build lines (TUI shows last ~6)
        let result = updater
            .build_streaming(move |line| {
                let trimmed = line.trim();
                // Forward meaningful cargo lines as build progress
                if (trimmed.starts_with("Compiling")
                    || trimmed.starts_with("Finished")
                    || trimmed.starts_with("error")
                    || trimmed.starts_with("warning[")
                    || trimmed.starts_with("-->"))
                    && let Some(ref cb) = cb
                {
                    cb(sid, ProgressEvent::BuildLine(line));
                }
            })
            .await;

        match result {
            Ok(path) => {
                // Signal auto-restart — TuiEvent::RestartReady triggers exec() with no prompt
                if let Some(ref cb) = self.progress {
                    cb(
                        sid,
                        ProgressEvent::RestartReady {
                            status: format!("Build successful: {}", path.display()),
                        },
                    );
                }
                Ok(ToolResult::success(format!(
                    "Build successful: {}. Restarting now.",
                    path.display()
                )))
            }
            Err(output) => Ok(ToolResult::error(format!("Build failed:\n{}", output))),
        }
    }
}