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:
@ -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,
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user