#![cfg_attr(coverage_nightly, coverage(off))]
use anyhow::Result;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::SystemTime;
use tokio::sync::{mpsc, RwLock};
use tracing::{info, warn};
use super::super::mcp_server::ClaudeCodeAgentMcpServer;
use super::super::quality_monitor::QualityMonitorEngine;
use super::super::state_persistence::StatePersistence;
use super::types::{DaemonConfig, DaemonState, DaemonStatus};
pub struct AgentDaemon {
pub(super) config: DaemonConfig,
pub(super) mcp_server: Option<ClaudeCodeAgentMcpServer>,
pub(super) quality_monitor: Option<QualityMonitorEngine>,
pub(super) state: Arc<RwLock<DaemonState>>,
pub(super) persistence: Option<StatePersistence>,
pub(super) shutdown_tx: Option<mpsc::Sender<()>>,
}
impl AgentDaemon {
#[must_use]
pub fn new(config: DaemonConfig) -> Self {
Self {
config,
mcp_server: None,
quality_monitor: None,
state: Arc::new(RwLock::new(DaemonState {
status: DaemonStatus::Stopped,
started_at: SystemTime::now(),
last_health_check: SystemTime::now(),
active_projects: 0,
events_processed: 0,
memory_usage_mb: 0,
restart_count: 0,
last_error: None,
})),
persistence: None,
shutdown_tx: None,
}
}
pub async fn start(&mut self) -> Result<()> {
info!(
"Starting Claude Code Agent Daemon v{}",
self.config.agent.version
);
{
let mut state = self.state.write().await;
state.status = DaemonStatus::Starting;
state.started_at = SystemTime::now();
}
let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
self.shutdown_tx = Some(shutdown_tx);
self.initialize_components().await?;
{
let mut state = self.state.write().await;
state.status = DaemonStatus::Running;
}
info!("Claude Code Agent Daemon started successfully");
self.run_daemon_loop(shutdown_rx).await
}
pub async fn stop(&mut self) -> Result<()> {
info!("Stopping Claude Code Agent Daemon");
{
let mut state = self.state.write().await;
state.status = DaemonStatus::Stopping;
}
if let Some(sender) = &self.shutdown_tx {
let _ = sender.send(()).await;
}
let timeout = self.config.daemon.shutdown_timeout;
let shutdown_future = self.shutdown_components();
match tokio::time::timeout(timeout, shutdown_future).await {
Ok(result) => {
if let Err(e) = result {
warn!("Error during graceful shutdown: {}", e);
}
}
Err(_) => {
warn!("Shutdown timeout exceeded, forcing stop");
}
}
{
let mut state = self.state.write().await;
state.status = DaemonStatus::Stopped;
}
info!("Claude Code Agent Daemon stopped");
Ok(())
}
pub async fn get_state(&self) -> DaemonState {
self.state.read().await.clone()
}
async fn initialize_components(&mut self) -> Result<()> {
info!("Initializing daemon components");
let mut quality_monitor = QualityMonitorEngine::new(self.config.quality_monitor.clone());
let (event_tx, mut event_rx) = mpsc::channel(100);
quality_monitor.set_event_sender(event_tx);
let mcp_server = ClaudeCodeAgentMcpServer::new(self.config.agent.clone());
let state_dir = PathBuf::from(&self.config.daemon.working_directory).join(".pmat_state");
let persistence = StatePersistence::new(&state_dir)?;
persistence.start_auto_save().await;
let saved_state = persistence.get_state().await;
info!(
"Restored {} monitored projects from persistent state",
saved_state.monitored_projects.len()
);
self.quality_monitor = Some(quality_monitor);
self.mcp_server = Some(mcp_server);
self.persistence = Some(persistence);
let state = self.state.clone();
tokio::spawn(async move {
while let Some(event) = event_rx.recv().await {
Self::process_quality_event(event, &state).await;
}
});
Ok(())
}
pub(super) async fn shutdown_components(&mut self) -> Result<()> {
info!("Shutting down daemon components");
if let Some(_quality_monitor) = &mut self.quality_monitor {
info!("Stopping quality monitor");
}
if let Some(_mcp_server) = &mut self.mcp_server {
info!("Stopping MCP server");
}
self.quality_monitor = None;
self.mcp_server = None;
Ok(())
}
}