//! Agent-mode HTTP context construction and gateway registration. use std::path::Path; use std::sync::Arc; use tokio::sync::broadcast; use crate::agents::AgentPool; use crate::io::watcher; use crate::slog; /// Register this build agent with a gateway using a one-time join token. /// /// POSTs `{ token, label, address }` to `{gateway_url}/gateway/register`. On /// success the gateway stores the agent and it will appear in the gateway UI. pub(super) async fn register_with_gateway( gateway_url: &str, token: &str, label: &str, address: &str, ) { let client = reqwest::Client::new(); let url = format!("{}/gateway/register", gateway_url.trim_end_matches('/')); let body = serde_json::json!({ "token": token, "label": label, "address": address, }); match client.post(&url).json(&body).send().await { Ok(resp) if resp.status().is_success() => { slog!("[agent-mode] Registered with gateway at {gateway_url}"); } Ok(resp) => { slog!( "[agent-mode] Gateway registration failed: HTTP {}", resp.status() ); } Err(e) => { slog!("[agent-mode] Gateway registration error: {e}"); } } } /// Build a minimal [`AppContext`] for the agent-mode HTTP server. /// /// The `/crdt-sync` handler receives `Data<&Arc>` but doesn't /// actually use it (the parameter is named `_ctx`). We construct a /// lightweight context with just enough state to satisfy Poem's data /// extractor. pub(super) fn build_agent_app_context( project_root: &Path, port: u16, watcher_tx: broadcast::Sender, ) -> crate::http::context::AppContext { let state = crate::state::SessionState::default(); *state.project_root.lock().unwrap() = Some(project_root.to_path_buf()); let store_path = project_root.join(".huskies").join("store.json"); let store = Arc::new( crate::store::JsonFileStore::from_path(store_path) .unwrap_or_else(|e| panic!("Failed to open store: {e}")), ); let (reconciliation_tx, _) = broadcast::channel(64); let (perm_tx, perm_rx) = tokio::sync::mpsc::unbounded_channel(); let permission_registry = crate::service::permission_router::ResponderRegistry::new(); crate::service::permission_router::spawn_permission_router( perm_rx, Arc::clone(&permission_registry), ); let timer_store = Arc::new(crate::service::timer::TimerStore::load( project_root.join(".huskies").join("timers.json"), )); let scheduled_timer_store = Arc::new(crate::service::timer::ScheduledTimerStore::load( project_root.join(".huskies").join("scheduled_timers.json"), )); let agents = Arc::new(AgentPool::new(port, watcher_tx.clone())); let services = Arc::new(crate::services::Services { project_root: project_root.to_path_buf(), agents: Arc::clone(&agents), bot_name: "Agent".to_string(), bot_user_id: String::new(), ambient_rooms: Arc::new(std::sync::Mutex::new(std::collections::HashSet::new())), permission_registry, pending_perm_replies: crate::service::permission_router::PendingPermReplies::new(), permission_timeout_secs: 120, remembered_permissions: crate::service::permission_router::RememberedPermissions::new(), status: agents.status_broadcaster(), chat_dispatcher: Arc::new(crate::chat::dispatcher::ChatDispatcher::new(1_500)), }); crate::http::context::AppContext { state: Arc::new(state), store, workflow: Arc::new(std::sync::Mutex::new( crate::workflow::WorkflowState::default(), )), services, watcher_tx, reconciliation_tx, perm_tx, qa_app_process: Arc::new(std::sync::Mutex::new(None)), bot_shutdown: None, matrix_shutdown_tx: None, timer_store, scheduled_timer_store, event_trigger_store: Arc::new( crate::service::event_triggers::store::EventTriggerStore::load( project_root.join(".huskies").join("event_triggers.json"), ), ), } }