fix(application,domain,infrastructure,web-server,app-tauri,frontend): arrêt d'agent fiable avec cascade sur l'arbre de propriété et statut cohérent — #320 (QA verte)
Symptôme: stopper un agent (ex Main sur UnityPlugin) laissait des travaux poursuivre, la cascade n'atteignait pas les agents lancés et la pastille restait orange pendant que l'agent continuait à en appeler d'autres. - barrière d'admission par génération d'exécution (ExecutionControl): un stop ferme l'admission de tout le sous-arbre propriété/attente AVANT toute annulation asynchrone — plus aucune nouvelle action inter-agents après stop - arbre de propriété durable en-process (launched_by) + dépendances d'attente actives: la cascade couvre transitivement agents et travaux lancés - commandes d'un agent arrêté rejetées (AppError::AgentStopped); stop lui-même admis pour rester idempotent; réactivation sur relance humaine (génération +1) et enregistrement du propriétaire au spawn - admission exposée aux adaptateurs hors commandes cœur (tickets, templates, plugins) via ensure_agent_execution_active; MCP server/tools, web-server et commandes Tauri alignés - cohérence live-state: statut Idle avec intent « Arrêté » ou « Arrêt incomplet — Certains travaux sont encore actifs » selon l'issue de la cascade; workstate et surfaces UI (AgentsPanel, LayoutGrid, ProjectWorkStatePanel) reflètent l'état réel, plus de pastille orange fantôme - tests: orchestrator_service (cascade/générations/idempotence), workstate_actions, background_task_cancel, error_codes, agents, layout, workstate QA verte sur fix/320-agent-stop-cascade. Aucun push.
This commit is contained in:
@ -130,6 +130,7 @@ impl AgentSession for FakeSession {
|
||||
#[derive(Default)]
|
||||
struct FakeCascade {
|
||||
calls: Mutex<Vec<(ProjectId, AgentId)>>,
|
||||
fail: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -163,8 +164,19 @@ impl AgentSelfRestartScheduler for FakeRestartScheduler {
|
||||
|
||||
#[async_trait]
|
||||
impl StopAgentExecutionCascade for FakeCascade {
|
||||
async fn stop_agent_execution_children(&self, project: &Project, agent_id: AgentId) {
|
||||
async fn stop_agent_execution_children(
|
||||
&self,
|
||||
project: &Project,
|
||||
agent_id: AgentId,
|
||||
) -> Result<(), application::AppError> {
|
||||
self.calls.lock().unwrap().push((project.id, agent_id));
|
||||
if self.fail {
|
||||
Err(application::AppError::Process(
|
||||
"child cancellation failed".to_owned(),
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -534,7 +546,7 @@ async fn stop_structured_shuts_down_and_removes_session() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stop_structured_runs_execution_cascade_best_effort() {
|
||||
async fn stop_structured_runs_execution_cascade_before_shutdown() {
|
||||
let f = fixture();
|
||||
let a = aid(10);
|
||||
let (shutdown_called, cancel_called) = insert_structured(&f.structured, sid(2), a, nid(100));
|
||||
@ -565,6 +577,33 @@ async fn stop_structured_runs_execution_cascade_best_effort() {
|
||||
assert!(f.structured.is_empty(), "live session is removed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cascade_failure_is_reported_after_primary_runtime_is_still_stopped() {
|
||||
let f = fixture();
|
||||
let a = aid(10);
|
||||
let (shutdown_called, _) = insert_structured(&f.structured, sid(2), a, nid(100));
|
||||
let cascade = Arc::new(FakeCascade {
|
||||
fail: true,
|
||||
..Default::default()
|
||||
});
|
||||
let stop = f
|
||||
.stop
|
||||
.with_execution_cascade(cascade as Arc<dyn StopAgentExecutionCascade>);
|
||||
|
||||
let error = stop
|
||||
.execute(StopLiveAgentInput {
|
||||
project: project(),
|
||||
agent_id: a,
|
||||
reason: None,
|
||||
})
|
||||
.await
|
||||
.expect_err("partial cascade must be surfaced");
|
||||
|
||||
assert_eq!(error.code(), "PROCESS");
|
||||
assert!(shutdown_called.load(Ordering::SeqCst));
|
||||
assert!(f.structured.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stop_removes_every_runtime_for_the_agent_across_registries() {
|
||||
let f = fixture();
|
||||
|
||||
Reference in New Issue
Block a user