pub mod api;
pub mod audit;
pub mod claude_config;
pub mod discover;
pub mod discovery;
pub mod error;
pub mod llm_overseer;
pub mod lock;
pub mod mcp_backend;
pub mod openapi;
pub mod optimizer;
pub mod overseer_compose;
pub mod pairing_store;
pub mod services;
pub mod state;
pub mod tmux;
pub mod watcher;
use std::net::SocketAddr;
use std::sync::Arc;
use tracing::info;
pub use state::DaemonState;
pub async fn run_http(state: Arc<DaemonState>, addr: SocketAddr) -> anyhow::Result<()> {
info!("trusty-mpm daemon starting on {addr}");
let listener = tokio::net::TcpListener::bind(addr).await?;
serve_http(state, listener).await
}
pub async fn serve_http(
state: Arc<DaemonState>,
listener: tokio::net::TcpListener,
) -> anyhow::Result<()> {
if tmux::TmuxDriver::is_available() {
info!("tmux control model available");
} else {
info!("tmux not found — sessions will need the PTY or SDK control model");
}
let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("."));
let addrs = discover::discover_all(&home).await;
info!(
"trusty-memory at {}, trusty-search at {}",
addrs.memory, addrs.search
);
state.set_trusty_addrs(addrs);
let discovered = discovery::discover_all(&state);
if discovered.adopted > 0 {
info!(
"auto-discovered {} Claude Code session(s)",
discovered.adopted
);
}
let fw = watcher::FileWatcher::new(Arc::clone(&state));
tokio::spawn(fw.spawn());
tokio::spawn(reap_loop(Arc::clone(&state)));
let app = api::router(state);
info!("daemon listening; press Ctrl-C to stop");
axum::serve(listener, app).await?;
Ok(())
}
pub fn spawn_secondary_listener(state: Arc<DaemonState>, listener: tokio::net::TcpListener) {
let app = api::router(state);
tokio::spawn(async move {
if let Err(e) = axum::serve(listener, app).await {
tracing::warn!("secondary listener failed: {e}");
}
});
}
const REAP_INTERVAL_SECS: u64 = 60;
async fn reap_loop(state: Arc<DaemonState>) {
let mut tick = tokio::time::interval(std::time::Duration::from_secs(REAP_INTERVAL_SECS));
loop {
tick.tick().await;
if let Ok(driver) = tmux::TmuxDriver::discover() {
let result = state.reap_dead_sessions(&driver);
if result.reaped > 0 {
info!("reaped {} dead session(s)", result.reaped);
}
if result.stopped > 0 {
info!(
"marked {} session(s) stopped (claude process exited)",
result.stopped
);
}
}
}
}
pub async fn run_mcp(state: Arc<DaemonState>) -> anyhow::Result<()> {
info!("trusty-mpm MCP server starting on stdio");
let backend = mcp_backend::StateBackend::new(state);
trusty_mcp_core::run_stdio_loop(move |req| {
let backend = backend.clone();
async move { trusty_mpm_mcp::dispatch(&backend, req).await }
})
.await
}