2026-04-28 18:59:10 +00:00
|
|
|
//! 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<AppContext>>` 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<watcher::WatcherEvent>,
|
|
|
|
|
) -> 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 timer_store = Arc::new(crate::service::timer::TimerStore::load(
|
|
|
|
|
project_root.join(".huskies").join("timers.json"),
|
|
|
|
|
));
|
2026-05-14 16:26:49 +00:00
|
|
|
let scheduled_timer_store = Arc::new(crate::service::timer::ScheduledTimerStore::load(
|
|
|
|
|
project_root.join(".huskies").join("scheduled_timers.json"),
|
|
|
|
|
));
|
2026-04-28 18:59:10 +00:00
|
|
|
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())),
|
|
|
|
|
perm_rx: Arc::new(tokio::sync::Mutex::new(perm_rx)),
|
|
|
|
|
pending_perm_replies: Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())),
|
|
|
|
|
permission_timeout_secs: 120,
|
|
|
|
|
status: agents.status_broadcaster(),
|
2026-05-15 11:57:00 +00:00
|
|
|
chat_dispatcher: Arc::new(crate::chat::dispatcher::ChatDispatcher::new(1_500)),
|
2026-04-28 18:59:10 +00:00
|
|
|
});
|
|
|
|
|
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,
|
2026-05-14 16:26:49 +00:00
|
|
|
scheduled_timer_store,
|
2026-05-14 17:00:33 +00:00
|
|
|
event_trigger_store: Arc::new(
|
|
|
|
|
crate::service::event_triggers::store::EventTriggerStore::load(
|
|
|
|
|
project_root.join(".huskies").join("event_triggers.json"),
|
|
|
|
|
),
|
|
|
|
|
),
|
2026-04-28 18:59:10 +00:00
|
|
|
}
|
|
|
|
|
}
|