Jesús Pérez dd68d190ef ci: Update pre-commit hooks configuration
- Exclude problematic markdown files from linting (existing legacy issues)
- Make clippy check less aggressive (warnings only, not -D warnings)
- Move cargo test to manual stage (too slow for pre-commit)
- Exclude SVG files from end-of-file-fixer and trailing-whitespace
- Add markdown linting exclusions for existing documentation

This allows pre-commit hooks to run successfully on new code without
blocking commits due to existing issues in legacy documentation files.
2026-01-11 21:32:56 +00:00

253 lines
7.9 KiB
Rust

// Agent service - Registry and management for the 12 agent roles
use chrono::Utc;
use surrealdb::engine::remote::ws::Client;
use surrealdb::Surreal;
use vapora_shared::models::{Agent, AgentRole, AgentStatus};
use vapora_shared::{Result, VaporaError};
/// Service for managing agents
#[derive(Clone)]
pub struct AgentService {
db: Surreal<Client>,
}
impl AgentService {
/// Create a new AgentService instance
pub fn new(db: Surreal<Client>) -> Self {
Self { db }
}
/// Register a new agent
pub async fn register_agent(&self, mut agent: Agent) -> Result<Agent> {
// Set creation timestamp
agent.created_at = Utc::now();
// Check if agent with this role already exists
let existing = self.get_agent_by_role(&agent.role).await;
if existing.is_ok() {
return Err(VaporaError::InvalidInput(format!(
"Agent with role '{:?}' already exists",
agent.role
)));
}
// Create agent in database
let created: Option<Agent> = self
.db
.create("agents")
.content(agent)
.await?
.into_iter()
.next();
created.ok_or_else(|| VaporaError::DatabaseError("Failed to register agent".to_string()))
}
/// List all agents
pub async fn list_agents(&self) -> Result<Vec<Agent>> {
let mut response = self
.db
.query("SELECT * FROM agents ORDER BY role ASC")
.await?;
let agents: Vec<Agent> = response.take(0)?;
Ok(agents)
}
/// List agents by status
pub async fn list_agents_by_status(&self, status: AgentStatus) -> Result<Vec<Agent>> {
let status_str = match status {
AgentStatus::Active => "active",
AgentStatus::Inactive => "inactive",
AgentStatus::Updating => "updating",
AgentStatus::Error => "error",
};
let mut response = self
.db
.query("SELECT * FROM agents WHERE status = $status ORDER BY role ASC")
.bind(("status", status_str.to_string()))
.await?;
let agents: Vec<Agent> = response.take(0)?;
Ok(agents)
}
/// Get an agent by ID
pub async fn get_agent(&self, id: &str) -> Result<Agent> {
let agent: Option<Agent> = self.db.select(("agents", id)).await?;
agent.ok_or_else(|| VaporaError::NotFound(format!("Agent with id '{}' not found", id)))
}
/// Get an agent by role
pub async fn get_agent_by_role(&self, role: &AgentRole) -> Result<Agent> {
let role_str = match role {
AgentRole::Architect => "architect",
AgentRole::Developer => "developer",
AgentRole::CodeReviewer => "code_reviewer",
AgentRole::Tester => "tester",
AgentRole::Documenter => "documenter",
AgentRole::Marketer => "marketer",
AgentRole::Presenter => "presenter",
AgentRole::DevOps => "dev_ops",
AgentRole::Monitor => "monitor",
AgentRole::Security => "security",
AgentRole::ProjectManager => "project_manager",
AgentRole::DecisionMaker => "decision_maker",
};
let mut response = self
.db
.query("SELECT * FROM agents WHERE role = $role LIMIT 1")
.bind(("role", role_str.to_string()))
.await?;
let agents: Vec<Agent> = response.take(0)?;
agents
.into_iter()
.next()
.ok_or_else(|| VaporaError::NotFound(format!("Agent with role '{:?}' not found", role)))
}
/// Update an agent
pub async fn update_agent(&self, id: &str, mut updates: Agent) -> Result<Agent> {
// Verify agent exists
let existing = self.get_agent(id).await?;
// Preserve certain fields
updates.id = existing.id;
updates.created_at = existing.created_at;
// Update in database
let updated: Option<Agent> = self.db.update(("agents", id)).content(updates).await?;
updated.ok_or_else(|| VaporaError::DatabaseError("Failed to update agent".to_string()))
}
/// Update agent status
pub async fn update_agent_status(&self, id: &str, status: AgentStatus) -> Result<Agent> {
// Verify agent exists
self.get_agent(id).await?;
let updated: Option<Agent> = self
.db
.update(("agents", id))
.merge(serde_json::json!({
"status": status
}))
.await?;
updated
.ok_or_else(|| VaporaError::DatabaseError("Failed to update agent status".to_string()))
}
/// Add capability to an agent
pub async fn add_capability(&self, id: &str, capability: String) -> Result<Agent> {
let mut agent = self.get_agent(id).await?;
// Add capability if not already present
if !agent.capabilities.contains(&capability) {
agent.capabilities.push(capability);
let updated: Option<Agent> = self
.db
.update(("agents", id))
.merge(serde_json::json!({
"capabilities": agent.capabilities
}))
.await?;
return updated
.ok_or_else(|| VaporaError::DatabaseError("Failed to add capability".to_string()));
}
Ok(agent)
}
/// Remove capability from an agent
pub async fn remove_capability(&self, id: &str, capability: &str) -> Result<Agent> {
let mut agent = self.get_agent(id).await?;
// Remove capability
agent.capabilities.retain(|c| c != capability);
let updated: Option<Agent> = self
.db
.update(("agents", id))
.merge(serde_json::json!({
"capabilities": agent.capabilities
}))
.await?;
updated.ok_or_else(|| VaporaError::DatabaseError("Failed to remove capability".to_string()))
}
/// Add skill to an agent
pub async fn add_skill(&self, id: &str, skill: String) -> Result<Agent> {
let mut agent = self.get_agent(id).await?;
// Add skill if not already present
if !agent.skills.contains(&skill) {
agent.skills.push(skill);
let updated: Option<Agent> = self
.db
.update(("agents", id))
.merge(serde_json::json!({
"skills": agent.skills
}))
.await?;
return updated
.ok_or_else(|| VaporaError::DatabaseError("Failed to add skill".to_string()));
}
Ok(agent)
}
/// Deregister an agent
pub async fn deregister_agent(&self, id: &str) -> Result<()> {
// Verify agent exists
self.get_agent(id).await?;
// Delete from database
let _: Option<Agent> = self.db.delete(("agents", id)).await?;
Ok(())
}
/// Get agent health status (checks if agent is active and responding)
pub async fn check_agent_health(&self, id: &str) -> Result<bool> {
let agent = self.get_agent(id).await?;
Ok(agent.status == AgentStatus::Active)
}
/// Get agents available for task assignment (active agents with capacity)
pub async fn get_available_agents(&self) -> Result<Vec<Agent>> {
let mut response = self
.db
.query("SELECT * FROM agents WHERE status = 'active' ORDER BY role ASC")
.await?;
let agents: Vec<Agent> = response.take(0)?;
Ok(agents)
}
}
#[cfg(test)]
mod tests {
use super::*;
// Note: These are placeholder tests. Real tests require a running SurrealDB instance
// or mocking. For Phase 1, we'll add integration tests that use a test database.
#[test]
fn test_agent_service_creation() {
// This test just verifies the service can be created
// Real database tests will be in integration tests
}
}