Converted all external tool calling to async

This commit is contained in:
Timmy
2026-06-29 16:59:54 +01:00
parent 75f41088b1
commit feb35ddd10
49 changed files with 482 additions and 496 deletions
+45 -16
View File
@@ -10,12 +10,12 @@ use super::types::{agent_info_from_entry, composite_key};
impl AgentPool {
/// Return the names of configured agents for `stage` that are not currently
/// running or pending.
pub fn available_agents_for_stage(
pub async fn available_agents_for_stage(
&self,
config: &ProjectConfig,
stage: &PipelineStage,
) -> Result<Vec<String>, String> {
let agents = self.agents.lock().map_err(|e| e.to_string())?;
let agents = self.agents.lock().await;
Ok(config
.agent
.iter()
@@ -44,8 +44,8 @@ impl AgentPool {
}
/// List all agents with their status.
pub fn list_agents(&self) -> Result<Vec<AgentInfo>, String> {
let agents = self.agents.lock().map_err(|e| e.to_string())?;
pub async fn list_agents(&self) -> Result<Vec<AgentInfo>, String> {
let agents = self.agents.lock().await;
Ok(agents
.iter()
.map(|(key, agent)| {
@@ -59,14 +59,35 @@ impl AgentPool {
.collect())
}
/// Best-effort agent list for sync callers (chat commands, rendering).
///
/// Uses `try_lock()` so it never blocks the thread. Returns an empty list
/// if the lock is currently held — callers that refresh periodically (htop,
/// pipeline board) tolerate this gracefully.
pub fn list_agents_nonblocking(&self) -> Vec<AgentInfo> {
let Ok(agents) = self.agents.try_lock() else {
return Vec::new();
};
agents
.iter()
.map(|(key, agent)| {
let story_id = key
.rsplit_once(':')
.map(|(sid, _)| sid.to_string())
.unwrap_or_else(|| key.clone());
agent_info_from_entry(&story_id, agent)
})
.collect()
}
/// Subscribe to events for a story agent.
pub fn subscribe(
pub async fn subscribe(
&self,
story_id: &str,
agent_name: &str,
) -> Result<broadcast::Receiver<AgentEvent>, String> {
let key = composite_key(story_id, agent_name);
let agents = self.agents.lock().map_err(|e| e.to_string())?;
let agents = self.agents.lock().await;
let agent = agents
.get(&key)
.ok_or_else(|| format!("No agent '{agent_name}' for story '{story_id}'"))?;
@@ -74,13 +95,13 @@ impl AgentPool {
}
/// Drain accumulated events for polling. Returns all events since the last drain.
pub fn drain_events(
pub async fn drain_events(
&self,
story_id: &str,
agent_name: &str,
) -> Result<Vec<AgentEvent>, String> {
let key = composite_key(story_id, agent_name);
let agents = self.agents.lock().map_err(|e| e.to_string())?;
let agents = self.agents.lock().await;
let agent = agents
.get(&key)
.ok_or_else(|| format!("No agent '{agent_name}' for story '{story_id}'"))?;
@@ -91,9 +112,13 @@ impl AgentPool {
/// Get the log session ID and project root for an agent, if available.
///
/// Used by MCP tools to find the persistent log file for a completed agent.
pub fn get_log_info(&self, story_id: &str, agent_name: &str) -> Option<(String, PathBuf)> {
pub async fn get_log_info(
&self,
story_id: &str,
agent_name: &str,
) -> Option<(String, PathBuf)> {
let key = composite_key(story_id, agent_name);
let agents = self.agents.lock().ok()?;
let agents = self.agents.lock().await;
let agent = agents.get(&key)?;
let session_id = agent.log_session_id.clone()?;
let project_root = agent.project_root.clone()?;
@@ -111,8 +136,8 @@ mod tests {
ProjectConfig::parse(toml_str).unwrap()
}
#[test]
fn available_agents_for_stage_returns_idle_agents() {
#[tokio::test]
async fn available_agents_for_stage_returns_idle_agents() {
let config = make_config(
r#"
[[agent]]
@@ -133,17 +158,19 @@ stage = "qa"
let available = pool
.available_agents_for_stage(&config, &PipelineStage::Coder)
.await
.unwrap();
assert_eq!(available, vec!["coder-2"]);
let available_qa = pool
.available_agents_for_stage(&config, &PipelineStage::Qa)
.await
.unwrap();
assert_eq!(available_qa, vec!["qa"]);
}
#[test]
fn available_agents_for_stage_returns_empty_when_all_busy() {
#[tokio::test]
async fn available_agents_for_stage_returns_empty_when_all_busy() {
let config = make_config(
r#"
[[agent]]
@@ -156,12 +183,13 @@ stage = "coder"
let available = pool
.available_agents_for_stage(&config, &PipelineStage::Coder)
.await
.unwrap();
assert!(available.is_empty());
}
#[test]
fn available_agents_for_stage_ignores_completed_agents() {
#[tokio::test]
async fn available_agents_for_stage_ignores_completed_agents() {
let config = make_config(
r#"
[[agent]]
@@ -174,6 +202,7 @@ stage = "coder"
let available = pool
.available_agents_for_stage(&config, &PipelineStage::Coder)
.await
.unwrap();
assert_eq!(available, vec!["coder-1"]);
}