fix(custom-cli): busy/progress/tools/chatbox bloqués après le premier appel — #245 (QA verte)
Backend : StructuredHumanTurnGuard pour partager la comptabilité busy/FIFO entre chat direct humain (agent_send, web chat.send) et asks inter-agents structurés, avec liveness mark_alive et retrait idempotent du tour. Web-server : tap d'événements sur chat.send pour le scrollback temps réel. Frontend : scoping des clés de progression par tour pour éviter la déduplication/ghosting des progress et outils entre appels.
This commit is contained in:
@ -2564,6 +2564,16 @@ pub async fn agent_send(
|
||||
.iter()
|
||||
.map(|attachment| attachment.filename.clone())
|
||||
.collect();
|
||||
let session_meta = state.structured_sessions.meta_for_session(&sid);
|
||||
let turn_guard = session_meta.as_ref().map(|(project_id, agent_id, _, _)| {
|
||||
backend::StructuredHumanTurnGuard::start(
|
||||
std::sync::Arc::clone(&state.turn_watch_input),
|
||||
std::sync::Arc::clone(&state.input_mailbox),
|
||||
*project_id,
|
||||
*agent_id,
|
||||
&prompt_for_model,
|
||||
)
|
||||
});
|
||||
|
||||
// Retain the human submit in the same live scrollback as model chunks so
|
||||
// `reattach_agent_chat` can repaint the whole in-flight conversation.
|
||||
@ -2583,9 +2593,15 @@ pub async fn agent_send(
|
||||
let (tap_tx, tap_rx) = std::sync::mpsc::channel();
|
||||
let tap_bridge = std::sync::Arc::clone(&state.chat_bridge);
|
||||
let tap_sid = sid;
|
||||
let tap_turn_guard = turn_guard.as_ref().map(|guard| guard.liveness_handle());
|
||||
let tap_pump = std::thread::spawn(move || {
|
||||
let mut settler = crate::chat::ProgressSettler::default();
|
||||
for event in tap_rx {
|
||||
if !matches!(event, domain::ports::ReplyEvent::Final { .. }) {
|
||||
if let Some(guard) = &tap_turn_guard {
|
||||
guard.mark_alive();
|
||||
}
|
||||
}
|
||||
let Some(chunk) = crate::chat::chunk_from_event(event) else {
|
||||
continue;
|
||||
};
|
||||
@ -2617,11 +2633,16 @@ pub async fn agent_send(
|
||||
// event then feeds the service (détecter→planifier). Without a conversation id the
|
||||
// service must not pretend an automatic resume is armed.
|
||||
let service = std::sync::Arc::clone(&state.session_limit_service);
|
||||
let meta = state.structured_sessions.meta_for_session(&sid);
|
||||
let meta = session_meta;
|
||||
std::thread::spawn(move || {
|
||||
let mut terminal_visible = false;
|
||||
let mut settler = crate::chat::ProgressSettler::default();
|
||||
for event in stream {
|
||||
if !matches!(event, domain::ports::ReplyEvent::Final { .. }) {
|
||||
if let Some(guard) = &turn_guard {
|
||||
guard.mark_alive();
|
||||
}
|
||||
}
|
||||
// Non-terminal, sans contenu chat : un signal de limite alimente le service
|
||||
// (le badge UI vient du bus `AgentRateLimited`, pas du flux chat) puis on
|
||||
// continue à drainer comme pour un battement.
|
||||
@ -2663,6 +2684,9 @@ pub async fn agent_send(
|
||||
let chunk = crate::chat::unfinished_turn_chunk();
|
||||
let _ = bridge.send_output_settled(&sid, &mut settler, chunk);
|
||||
}
|
||||
if let Some(guard) = &turn_guard {
|
||||
guard.finish();
|
||||
}
|
||||
bridge.detach_if(&sid, gen);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user