fix(custom-cli): le bouton Stop annule le tour courant sans fermer la session et cascade l'annulation aux agents délégués — stop_agent suit l'arbre des dépendances actives (cancel_current_turn + préemption runtime + enfants d'exécution), TurnCancel tue le process du tour en cours, autorisation requester/sous-arbre sur idea_stop_agent, fixtures DTO runtime_state/node_id alignées — #257 (QA verte)
This commit is contained in:
@ -6,7 +6,7 @@
|
||||
//! that turns a request into the *same* calls the UI makes:
|
||||
//!
|
||||
//! - `spawn_agent` → [`CreateAgentFromScratch`] (if unknown) then [`LaunchAgent`],
|
||||
//! - `stop_agent` → stop the agent's live PTY or structured runtime,
|
||||
//! - `stop_agent` → stop the agent's live PTY or cancel its structured turn,
|
||||
//! - `update_agent_context` → [`UpdateAgentContext`].
|
||||
//!
|
||||
//! It talks **only** to use cases and ports ([`ProfileStore`], [`TerminalSessions`]):
|
||||
@ -1224,7 +1224,9 @@ impl OrchestratorService {
|
||||
result,
|
||||
} => self.reply(project, from, ticket, result),
|
||||
OrchestratorCommand::ListAgents => self.list_agents(project).await,
|
||||
OrchestratorCommand::StopAgent { name } => self.stop_agent(project, name).await,
|
||||
OrchestratorCommand::StopAgent { name, requester } => {
|
||||
self.stop_agent(project, name, requester).await
|
||||
}
|
||||
OrchestratorCommand::UpdateAgentContext { name, context } => {
|
||||
self.update_agent_context(project, name, context).await
|
||||
}
|
||||
@ -2713,23 +2715,65 @@ impl OrchestratorService {
|
||||
})
|
||||
}
|
||||
|
||||
/// `stop_agent`: translate the agent name → its live runtime → stop it.
|
||||
/// `stop_agent`: translate the agent name → its live runtime → stop/cancel it.
|
||||
async fn stop_agent(
|
||||
&self,
|
||||
project: &Project,
|
||||
name: String,
|
||||
requester: Option<AgentId>,
|
||||
) -> Result<OrchestratorOutcome, AppError> {
|
||||
let agent_id = self
|
||||
.find_agent_id_by_name(project, &name)
|
||||
.await?
|
||||
.ok_or_else(|| AppError::NotFound(format!("agent {name}")))?;
|
||||
|
||||
self.stop_agent_execution_children(project, agent_id).await;
|
||||
if let Some(requester) = requester {
|
||||
let allowed = requester == agent_id
|
||||
|| self
|
||||
.active_wait_dependencies(requester)
|
||||
.into_iter()
|
||||
.any(|dep| dep == agent_id);
|
||||
if !allowed {
|
||||
return Err(AppError::Invalid(format!(
|
||||
"agent {requester} cannot stop agent {agent_id} outside its active subtree"
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let structured = self
|
||||
.structured
|
||||
.clone()
|
||||
.unwrap_or_else(|| Arc::new(StructuredSessions::new()));
|
||||
let dependencies = self.active_wait_dependencies(agent_id);
|
||||
for dep in &dependencies {
|
||||
self.stop_agent_execution_children(project, *dep).await;
|
||||
if let Some(input) = &self.input {
|
||||
input.preempt(runtime_key(project, *dep));
|
||||
}
|
||||
if let Some(session) = structured.session_for_agent_in_project(project.id, dep) {
|
||||
session
|
||||
.cancel_current_turn()
|
||||
.await
|
||||
.map_err(|err| AppError::Process(err.to_string()))?;
|
||||
}
|
||||
}
|
||||
|
||||
self.stop_agent_execution_children(project, agent_id).await;
|
||||
if let Some(input) = &self.input {
|
||||
input.preempt(runtime_key(project, agent_id));
|
||||
}
|
||||
|
||||
if let Some(session) = structured.session_for_agent_in_project(project.id, &agent_id) {
|
||||
session
|
||||
.cancel_current_turn()
|
||||
.await
|
||||
.map_err(|err| AppError::Process(err.to_string()))?;
|
||||
return Ok(OrchestratorOutcome {
|
||||
detail: format!("cancelled current work for agent {name}"),
|
||||
reply: None,
|
||||
});
|
||||
}
|
||||
|
||||
let live = LiveSessions::new(Arc::clone(&self.sessions), structured);
|
||||
if stop_live_agent_runtime_in_project(project, agent_id, &live, &self.close_terminal)
|
||||
.await?
|
||||
|
||||
Reference in New Issue
Block a user