fix(application,backend,web-server,app-tauri,frontend): libère l'état busy et le ticket médiatisé avant le Final visible — web-server ws_send_chat: guard.finish() sur Complete et Failed avant l'émission des frames terminales client, et sur l'absence d'ack, pour qu'un read workstate déclenché par Final/Error ne puisse plus voir le tour comme Busy ; app-tauri agent_send: dès la transition Complete, retire le ticket médiatisé exact (guard.finish) et termine le tour structuré (finish_turn) avant send_output_settled, opérations idempotentes et clôturées contre un successeur, épilogue conservé en backstop drop/error — corrige LED projet orange persistante et queue non drainée après Final ; frontend CustomAgentChatView: terminalWorkStatePending maintient effectiveBusy jusqu'à la résolution du refresh workstate post-Final au lieu de forcer busy=false et stopTerminalOverride (plus d'état busy fantôme ni de Stop incorrectement neutralisé) — #369 (QA verte: cargo fmt --all --check OK, cargo test application structured OK, web-server websocket_chat_send_releases_busy_before_visible_final et websocket_chat_send --test-threads=1 OK, app-tauri chat_bridge 36 tests OK, application drain_with_readiness_lot1/session_limit_t4/send_blocking_d1/cancel_agent_chat OK, backend human_turn OK, npm test CustomAgentChatView 92 OK, npm run build OK)

This commit is contained in:
2026-09-17 09:46:21 +02:00
parent 4c86f9bb6f
commit 2d95e20d91
5 changed files with 217 additions and 15 deletions

View File

@ -3323,7 +3323,17 @@ pub async fn agent_send(
concrete_terminal = true;
if let Some(guard) = turn_guard.as_mut() {
match runtime.block_on(guard.transition_after_provider_terminal_async()) {
backend::StructuredHumanTurnTransition::Complete => {}
backend::StructuredHumanTurnTransition::Complete => {
// The terminal chunk is observable by the frontend as
// soon as `send_output_settled` runs below. Retire the
// exact mediated ticket and the structured generation
// first, so a refresh triggered by Final can never read
// the just-finished turn as Busy. Both operations are
// idempotent and fenced against a successor; the common
// epilogue remains as a drop/error backstop.
guard.finish();
structured.finish_turn(&sid, turn_generation);
}
backend::StructuredHumanTurnTransition::Resume { prompt } => {
resume = true;
resume_prompt = Some(prompt);