fix(chat): le bouton Stop de la CLI custom arrête l'agent en cascade (#187)

Le Stop du CustomAgentChatView n'annulait que le tour courant sans libérer la
session ni toucher aux tâches enfants ou appels MCP en cours. Il appelle
désormais stop_live_agent sur l'agent vivant, qui:

- ajoute le port StopAgentExecutionCascade (best-effort, idempotent), implémenté
  par OrchestratorService pour annuler les idea_run_in_background rattachés au
  rendez-vous actif (via BackgroundTaskRunner.cancel);
- cascade l'arrêt aux wait-dépendances (stop_turn_watch + preempt) pour
  interrompre les appels MCP et les attentes inter-agents;
- libère et supprime la session structurée.

UI: nouvel état "Arrêt en cours..." (bouton disabled + loading) pendant l'arrêt,
libération du sessionId, label expliquant la cascade demandée. La Tauri command
cancel_agent_chat est alignée sur stop_live_agent + stop_turn_watch agent et
dépendances.

Tests: SpyBackgroundRunner (cascade n'annule que les tâches rattachées),
workstate_actions cascade best-effort, CustomAgentChatView stop+cascade feedback
et fallback NOT_FOUND.
This commit is contained in:
2026-08-11 18:14:47 +02:00
parent 2413aa7473
commit 22ce2f62fd
9 changed files with 293 additions and 46 deletions

View File

@ -12,7 +12,8 @@ use async_trait::async_trait;
use application::{
AttachLiveAgent, AttachLiveAgentInput, CloseTerminal, LiveSessionKind, LiveSessions,
StopLiveAgent, StopLiveAgentInput, StructuredSessions, TerminalSessions,
StopAgentExecutionCascade, StopLiveAgent, StopLiveAgentInput, StructuredSessions,
TerminalSessions,
};
use domain::ports::{
AgentSession, AgentSessionError, ExitStatus, OutputStream, PtyError, PtyHandle, PtyPort,
@ -120,6 +121,18 @@ impl AgentSession for FakeSession {
}
}
#[derive(Default)]
struct FakeCascade {
calls: Mutex<Vec<(ProjectId, AgentId)>>,
}
#[async_trait]
impl StopAgentExecutionCascade for FakeCascade {
async fn stop_agent_execution_children(&self, project: &Project, agent_id: AgentId) {
self.calls.lock().unwrap().push((project.id, agent_id));
}
}
// ---------------------------------------------------------------------------
// wiring helpers
// ---------------------------------------------------------------------------
@ -351,6 +364,30 @@ async fn stop_structured_shuts_down_and_removes_session() {
assert!(f.pty_port.kills().is_empty());
}
#[tokio::test]
async fn stop_structured_runs_execution_cascade_best_effort() {
let f = fixture();
let a = aid(10);
let flag = insert_structured(&f.structured, sid(2), a, nid(100));
let cascade = Arc::new(FakeCascade::default());
let stop = f
.stop
.with_execution_cascade(Arc::clone(&cascade) as Arc<dyn StopAgentExecutionCascade>);
let out = stop
.execute(StopLiveAgentInput {
project: project(),
agent_id: a,
})
.await
.unwrap();
assert_eq!(out.session_id, sid(2));
assert_eq!(*cascade.calls.lock().unwrap(), vec![(project().id, a)]);
assert!(flag.load(Ordering::SeqCst), "session.shutdown() was called");
assert!(f.structured.is_empty(), "live session removed");
}
#[tokio::test]
async fn stop_unknown_agent_is_not_found() {
let f = fixture();