aidaemon 0.11.11

A personal AI agent that runs as a background daemon, accessible via Telegram, Slack, or Discord, with tool use, MCP integration, and persistent memory
Documentation
use super::*;

#[async_trait]
impl crate::traits::DynamicCliAgentStore for SqliteStateStore {
    async fn save_dynamic_cli_agent(
        &self,
        agent: &crate::traits::DynamicCliAgent,
    ) -> anyhow::Result<i64> {
        let result = sqlx::query(
            "INSERT INTO dynamic_cli_agents (name, command, args_json, description, timeout_secs, max_output_chars, enabled, created_at)
             VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))
             ON CONFLICT(name) DO UPDATE SET command=excluded.command, args_json=excluded.args_json,
             description=excluded.description, timeout_secs=excluded.timeout_secs,
             max_output_chars=excluded.max_output_chars, enabled=excluded.enabled",
        )
        .bind(&agent.name)
        .bind(&agent.command)
        .bind(&agent.args_json)
        .bind(&agent.description)
        .bind(agent.timeout_secs.map(|v| v as i64))
        .bind(agent.max_output_chars.map(|v| v as i64))
        .bind(agent.enabled)
        .execute(&self.pool)
        .await?;
        Ok(result.last_insert_rowid())
    }

    async fn list_dynamic_cli_agents(&self) -> anyhow::Result<Vec<crate::traits::DynamicCliAgent>> {
        let rows = sqlx::query(
            "SELECT id, name, command, args_json, description, timeout_secs, max_output_chars, enabled, created_at
             FROM dynamic_cli_agents ORDER BY created_at ASC",
        )
        .fetch_all(&self.pool)
        .await?;

        let mut agents = Vec::new();
        for row in rows {
            agents.push(crate::traits::DynamicCliAgent {
                id: row.get::<i64, _>("id"),
                name: row.get::<String, _>("name"),
                command: row.get::<String, _>("command"),
                args_json: row.get::<String, _>("args_json"),
                description: row.get::<String, _>("description"),
                timeout_secs: row.get::<Option<i64>, _>("timeout_secs").map(|v| v as u64),
                max_output_chars: row
                    .get::<Option<i64>, _>("max_output_chars")
                    .map(|v| v as usize),
                enabled: row.get::<bool, _>("enabled"),
                created_at: row.get::<String, _>("created_at"),
            });
        }
        Ok(agents)
    }

    async fn delete_dynamic_cli_agent(&self, id: i64) -> anyhow::Result<()> {
        sqlx::query("DELETE FROM dynamic_cli_agents WHERE id = ?")
            .bind(id)
            .execute(&self.pool)
            .await?;
        Ok(())
    }

    async fn update_dynamic_cli_agent(
        &self,
        agent: &crate::traits::DynamicCliAgent,
    ) -> anyhow::Result<()> {
        sqlx::query(
            "UPDATE dynamic_cli_agents SET command = ?, args_json = ?, description = ?, timeout_secs = ?, max_output_chars = ?, enabled = ? WHERE id = ?",
        )
        .bind(&agent.command)
        .bind(&agent.args_json)
        .bind(&agent.description)
        .bind(agent.timeout_secs.map(|v| v as i64))
        .bind(agent.max_output_chars.map(|v| v as i64))
        .bind(agent.enabled)
        .bind(agent.id)
        .execute(&self.pool)
        .await?;
        Ok(())
    }

    async fn log_cli_agent_start(
        &self,
        session_id: &str,
        agent_name: &str,
        prompt_summary: &str,
        working_dir: Option<&str>,
    ) -> anyhow::Result<i64> {
        let result = sqlx::query(
            "INSERT INTO cli_agent_invocations (session_id, agent_name, prompt_summary, working_dir, started_at)
             VALUES (?, ?, ?, ?, datetime('now'))",
        )
        .bind(session_id)
        .bind(agent_name)
        .bind(prompt_summary)
        .bind(working_dir)
        .execute(&self.pool)
        .await?;
        Ok(result.last_insert_rowid())
    }

    async fn log_cli_agent_complete(
        &self,
        id: i64,
        exit_code: Option<i32>,
        output_summary: &str,
        success: bool,
        duration_secs: f64,
    ) -> anyhow::Result<()> {
        sqlx::query(
            "UPDATE cli_agent_invocations SET completed_at = datetime('now'), exit_code = ?, output_summary = ?, success = ?, duration_secs = ? WHERE id = ?",
        )
        .bind(exit_code)
        .bind(output_summary)
        .bind(success)
        .bind(duration_secs)
        .bind(id)
        .execute(&self.pool)
        .await?;
        Ok(())
    }

    async fn get_cli_agent_invocations(
        &self,
        limit: usize,
    ) -> anyhow::Result<Vec<crate::traits::CliAgentInvocation>> {
        let rows = sqlx::query(
            "SELECT id, session_id, agent_name, prompt_summary, working_dir, started_at, completed_at, exit_code, output_summary, success, duration_secs
             FROM cli_agent_invocations ORDER BY started_at DESC LIMIT ?",
        )
        .bind(limit as i64)
        .fetch_all(&self.pool)
        .await?;

        let mut invocations = Vec::new();
        for row in rows {
            invocations.push(crate::traits::CliAgentInvocation {
                id: row.get::<i64, _>("id"),
                session_id: row.get::<String, _>("session_id"),
                agent_name: row.get::<String, _>("agent_name"),
                prompt_summary: row.get::<String, _>("prompt_summary"),
                working_dir: row.get::<Option<String>, _>("working_dir"),
                started_at: row.get::<String, _>("started_at"),
                completed_at: row.get::<Option<String>, _>("completed_at"),
                exit_code: row.get::<Option<i32>, _>("exit_code"),
                output_summary: row.get::<Option<String>, _>("output_summary"),
                success: row.get::<Option<bool>, _>("success"),
                duration_secs: row.get::<Option<f64>, _>("duration_secs"),
            });
        }
        Ok(invocations)
    }

    async fn cleanup_stale_cli_agent_invocations(&self, max_age_hours: i64) -> anyhow::Result<u64> {
        if max_age_hours <= 0 {
            return Ok(0);
        }
        let note = format!(
            "Auto-closed stale invocation (no completion recorded; older than {} hour(s)).",
            max_age_hours
        );
        let result = sqlx::query(
            "UPDATE cli_agent_invocations
             SET completed_at = datetime('now'),
                 exit_code = NULL,
                 output_summary = ?,
                 success = 0,
                 duration_secs = (julianday('now') - julianday(started_at)) * 86400.0
             WHERE completed_at IS NULL
               AND started_at < datetime('now', '-' || ? || ' hours')",
        )
        .bind(note)
        .bind(max_age_hours)
        .execute(&self.pool)
        .await?;
        Ok(result.rows_affected())
    }
}