fix(backend,orchestrator,chat): tour humain agent_send survit au self-restart — #290 (le tour humain direct agent_send/ws chat.send s'enregistre au RestartRendezvousCoordinator via StructuredHumanTurnGuard: Final/Error de l'ancien provider devient transition de contrôle et non le Final logique; transition_after_provider_terminal_async attend Ready puis accept_ready acquitte le handoff et libère le scheduler, Resume => drain de la session de remplacement avec SELF_RESTART_CONTINUATION_PROMPT, seul le terminal du remplacement est transmis au chat; Failed/Cancelled surfacés explicitement avec libération Busy/FIFO; send_with_tap en erreur pendant restart actif traité comme teardown attendu (stream vide puis attente du remplacement, plus de Final prématuré ni chat bloqué); StopLiveAgent câblé with_restart_rendezvous: stop utilisateur annule le rendez-vous restart; RestartRendezvousRegistration rendu public (state/changed/accept_ready); QA verte: backend self_restart_contract_tests 3/3 dont 2 nouveaux (old terminal ignoré + handoff accepté, failure explicite + busy libéré), application orchestrator_service restart 8/8 dont registration directe du rendez-vous, workstate_actions 9/9, app-tauri chat_bridge 31/31, web-server websocket_chat_send 3/3, cargo check --workspace, fmt --check, diff --check — reconfirmés par Git avant commit; réserves hors périmètre préexistantes: 7 échecs fixture PTY web-server + 2 échecs plugin application; carnet #290 + clôture store #289 inclus; scripts/ hors périmètre; aucun push)

This commit is contained in:
2026-09-09 08:38:29 +02:00
parent 321ebdaab9
commit d03300a20f
16 changed files with 660 additions and 213 deletions

View File

@ -149,6 +149,7 @@ pub struct StopLiveAgent {
input: Option<Arc<dyn InputMediator>>,
waits: Option<Arc<OrchestratorService>>,
cascade: Option<Arc<dyn StopAgentExecutionCascade>>,
restart_rendezvous: Option<Arc<crate::RestartRendezvousCoordinator>>,
}
impl StopLiveAgent {
@ -161,6 +162,7 @@ impl StopLiveAgent {
input: None,
waits: None,
cascade: None,
restart_rendezvous: None,
}
}
@ -187,6 +189,16 @@ impl StopLiveAgent {
self
}
/// Wires cancellation of a logical turn currently crossing a self-restart.
#[must_use]
pub fn with_restart_rendezvous(
mut self,
coordinator: Arc<crate::RestartRendezvousCoordinator>,
) -> Self {
self.restart_rendezvous = Some(coordinator);
self
}
/// Stops the agent's current work.
///
/// # Errors
@ -198,6 +210,9 @@ impl StopLiveAgent {
) -> Result<StopLiveAgentOutput, AppError> {
self.stop_dependencies(&input.project, input.agent_id)
.await?;
if let Some(coordinator) = &self.restart_rendezvous {
coordinator.cancel(RuntimeAgentKey::new(input.project.id, input.agent_id));
}
if let Some(mediator) = &self.input {
mediator.preempt(RuntimeAgentKey::new(input.project.id, input.agent_id));
}