137 lines
5.2 KiB
Rust
137 lines
5.2 KiB
Rust
//! Legacy `report_completion` — retained for backwards compatibility and testing.
|
|
use std::sync::Arc;
|
|
|
|
use super::super::super::super::{AgentEvent, AgentStatus, CompletionReport};
|
|
use super::super::super::{AgentPool, composite_key};
|
|
|
|
impl AgentPool {
|
|
/// Internal: report that an agent has finished work on a story.
|
|
///
|
|
/// **Note:** This is no longer exposed as an MCP tool. The server now
|
|
/// automatically runs completion gates when an agent process exits
|
|
/// (see `run_server_owned_completion`). This method is retained for
|
|
/// backwards compatibility and testing.
|
|
///
|
|
/// - Rejects with an error if the worktree has uncommitted changes.
|
|
/// - Runs acceptance gates (cargo clippy + cargo nextest run / cargo test).
|
|
/// - Stores the `CompletionReport` on the agent record.
|
|
/// - Transitions status to `Completed` (gates passed) or `Failed` (gates failed).
|
|
/// - Emits a `Done` event so `wait_for_agent` unblocks.
|
|
#[allow(dead_code)]
|
|
pub async fn report_completion(
|
|
&self,
|
|
story_id: &str,
|
|
agent_name: &str,
|
|
summary: &str,
|
|
) -> Result<CompletionReport, String> {
|
|
let key = composite_key(story_id, agent_name);
|
|
|
|
// Verify agent exists, is Running, and grab its worktree path.
|
|
let worktree_path = {
|
|
let agents = self.agents.lock().map_err(|e| e.to_string())?;
|
|
let agent = agents
|
|
.get(&key)
|
|
.ok_or_else(|| format!("No agent '{agent_name}' for story '{story_id}'"))?;
|
|
|
|
if agent.status != AgentStatus::Running {
|
|
return Err(format!(
|
|
"Agent '{agent_name}' for story '{story_id}' is not running (status: {}). \
|
|
report_completion can only be called by a running agent.",
|
|
agent.status
|
|
));
|
|
}
|
|
|
|
agent
|
|
.worktree_info
|
|
.as_ref()
|
|
.map(|wt| wt.path.clone())
|
|
.ok_or_else(|| {
|
|
format!(
|
|
"Agent '{agent_name}' for story '{story_id}' has no worktree. \
|
|
Cannot run acceptance gates."
|
|
)
|
|
})?
|
|
};
|
|
|
|
let path = worktree_path.clone();
|
|
|
|
// Run gate checks in a blocking thread to avoid stalling the async runtime.
|
|
let (gates_passed, gate_output) = tokio::task::spawn_blocking(move || {
|
|
// Step 1: Reject if worktree is dirty.
|
|
crate::agents::gates::check_uncommitted_changes(&path)?;
|
|
// Step 2: Run clippy + tests and return (passed, output).
|
|
crate::agents::gates::run_acceptance_gates(&path)
|
|
})
|
|
.await
|
|
.map_err(|e| format!("Gate check task panicked: {e}"))??;
|
|
|
|
let report = CompletionReport {
|
|
summary: summary.to_string(),
|
|
gates_passed,
|
|
gate_output,
|
|
};
|
|
|
|
// Extract data for pipeline advance, then remove the entry so
|
|
// completed agents never appear in list_agents.
|
|
let (
|
|
tx,
|
|
session_id,
|
|
project_root_for_advance,
|
|
wt_path_for_advance,
|
|
merge_failure_reported_for_advance,
|
|
session_id_for_advance,
|
|
) = {
|
|
let mut agents = self.agents.lock().map_err(|e| e.to_string())?;
|
|
let agent = agents.get_mut(&key).ok_or_else(|| {
|
|
format!("Agent '{agent_name}' for story '{story_id}' disappeared during gate check")
|
|
})?;
|
|
agent.completion = Some(report.clone());
|
|
let tx = agent.tx.clone();
|
|
let sid = agent.session_id.clone();
|
|
let pr = agent.project_root.clone();
|
|
let wt = agent.worktree_info.as_ref().map(|w| w.path.clone());
|
|
let mfr = agent.merge_failure_reported;
|
|
let sid_advance = agent.session_id.clone();
|
|
agents.remove(&key);
|
|
(tx, sid, pr, wt, mfr, sid_advance)
|
|
};
|
|
|
|
// Emit Done so wait_for_agent unblocks.
|
|
let _ = tx.send(AgentEvent::Done {
|
|
story_id: story_id.to_string(),
|
|
agent_name: agent_name.to_string(),
|
|
session_id,
|
|
});
|
|
|
|
// Notify WebSocket clients that the agent is gone.
|
|
Self::notify_agent_state_changed(&self.watcher_tx);
|
|
|
|
// Advance the pipeline state machine in a background task.
|
|
let pool_clone = Self {
|
|
agents: Arc::clone(&self.agents),
|
|
port: self.port,
|
|
child_killers: Arc::clone(&self.child_killers),
|
|
watcher_tx: self.watcher_tx.clone(),
|
|
status_broadcaster: Arc::clone(&self.status_broadcaster),
|
|
};
|
|
let sid = story_id.to_string();
|
|
let aname = agent_name.to_string();
|
|
let report_for_advance = report.clone();
|
|
tokio::spawn(async move {
|
|
pool_clone
|
|
.run_pipeline_advance(
|
|
&sid,
|
|
&aname,
|
|
report_for_advance,
|
|
project_root_for_advance,
|
|
wt_path_for_advance,
|
|
merge_failure_reported_for_advance,
|
|
session_id_for_advance,
|
|
)
|
|
.await;
|
|
});
|
|
|
|
Ok(report)
|
|
}
|
|
}
|