fix(application,backend,app-tauri): libère l'état busy après Final et draine la queue des messages agents — Final/Error terminent immédiatement le pump structuré via reply_event_ends_turn (le provider n'est pas tenu de fermer son itérateur), release busy/FIFO idempotente bornée au ticket observé (release_turn_accounting: cancel_head + mark_idle seulement si le ticket appartient toujours au tour), cleanup tardif Final/close/timeout/cancel sans libérer un tour successeur, factoring CancelAgentChat sur la même primitive, régression #357 préservée — #362 (QA verte: cargo fmt --check, chat_bridge 36 tests, cancel_agent_chat 4 tests, backend 106 tests, drain_with_readiness_lot1 7 tests)

This commit is contained in:
2026-09-16 20:46:38 +02:00
parent 208e14961b
commit 12f7272fbe
7 changed files with 160 additions and 29 deletions

View File

@ -275,11 +275,11 @@ pub use template::{
SyncAgentWithTemplateOutput, UpdateTemplate, UpdateTemplateInput, UpdateTemplateOutput,
};
pub use terminal::{
CancelAgentChat, CancelAgentChatOutput, CloseTerminal, CloseTerminalInput, CloseTerminalOutput,
LiveAgentRegistry, LiveRuntimeState, LiveRuntimeStatus, LiveSessionKind, LiveSessionSnapshot,
LiveSessions, OpenTerminal, OpenTerminalInput, OpenTerminalOutput, ResizeTerminal,
ResizeTerminalInput, StructuredSessions, TerminalSessions, WriteToTerminal,
WriteToTerminalInput,
release_turn_accounting, CancelAgentChat, CancelAgentChatOutput, CloseTerminal,
CloseTerminalInput, CloseTerminalOutput, LiveAgentRegistry, LiveRuntimeState,
LiveRuntimeStatus, LiveSessionKind, LiveSessionSnapshot, LiveSessions, OpenTerminal,
OpenTerminalInput, OpenTerminalOutput, ResizeTerminal, ResizeTerminalInput, StructuredSessions,
TerminalSessions, WriteToTerminal, WriteToTerminalInput,
};
pub use ticket_assistant::{
CloseTicketAssistant, CloseTicketAssistantInput, OpenTicketAssistant, OpenTicketAssistantInput,

View File

@ -33,8 +33,8 @@ pub use registry::{
LiveSessions, StructuredSessions, TerminalSessions,
};
pub use usecases::{
stop_live_agent_runtime_in_project, CancelAgentChat, CancelAgentChatOutput, CloseTerminal,
CloseTerminalInput, CloseTerminalOutput, OpenTerminal, OpenTerminalInput, OpenTerminalOutput,
ResizeTerminal, ResizeTerminalInput, StoppedAgentRuntime, WriteToTerminal,
WriteToTerminalInput,
release_turn_accounting, stop_live_agent_runtime_in_project, CancelAgentChat,
CancelAgentChatOutput, CloseTerminal, CloseTerminalInput, CloseTerminalOutput, OpenTerminal,
OpenTerminalInput, OpenTerminalOutput, ResizeTerminal, ResizeTerminalInput,
StoppedAgentRuntime, WriteToTerminal, WriteToTerminalInput,
};

View File

@ -26,6 +26,28 @@ pub struct CancelAgentChatOutput {
pub outcome: CancelCurrentTurnOutcome,
}
/// Releases exactly one mediated turn without allowing late terminal cleanup to
/// affect a successor that already owns the agent.
///
/// `cancel_head` and `mark_idle` are individually idempotent, while the ticket
/// checks fence Final/close/timeout/cancel races across successive turns.
pub fn release_turn_accounting(
input: &dyn InputMediator,
mailbox: &dyn AgentMailbox,
agent: domain::RuntimeAgentKey,
ticket: domain::mailbox::TicketId,
) -> bool {
if input.busy_state(agent).ticket() != Some(ticket) {
return false;
}
mailbox.cancel_head(agent, ticket);
if input.busy_state(agent).ticket() != Some(ticket) {
return false;
}
input.mark_idle(agent);
true
}
/// Cancels one structured chat turn without stopping its reusable runtime.
pub struct CancelAgentChat {
sessions: Arc<StructuredSessions>,
@ -72,14 +94,7 @@ impl CancelAgentChat {
if let (Some((agent, ticket)), Some((input, mailbox))) =
(active_ticket, self.turn_accounting.as_ref())
{
// The provider kill can race the old pump's normal guard cleanup and
// a caller may already have started the next turn. Retire only the
// exact ticket observed before cancellation; never mark a successor
// idle when it has already replaced that ticket.
if input.busy_state(agent).ticket() == Some(ticket) {
mailbox.cancel_head(agent, ticket);
input.mark_idle(agent);
}
release_turn_accounting(input.as_ref(), mailbox.as_ref(), agent, ticket);
}
}
Ok(CancelAgentChatOutput {