From 62915eec4da51f71fcda77531200a47a61d63c17 Mon Sep 17 00:00:00 2001 From: Blomios Date: Thu, 2 Jul 2026 14:49:00 +0200 Subject: [PATCH] fix(codex): dernier agent_message = Final (bootstrap canal) Co-Authored-By: Claude Opus 4.8 --- crates/infrastructure/src/session/codex.rs | 34 +++++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/crates/infrastructure/src/session/codex.rs b/crates/infrastructure/src/session/codex.rs index 1b7b6bb..721c1fd 100644 --- a/crates/infrastructure/src/session/codex.rs +++ b/crates/infrastructure/src/session/codex.rs @@ -219,22 +219,40 @@ impl AgentSession for CodexExecSession { let mut events = Vec::new(); let mut captured_id = None; - 'lines: for line in &raw_lines { + // Fix canal (B2, bootstrap) : un tour `codex exec` peut émettre PLUSIEURS + // `agent_message` — un préambule (« je vais… ») puis la conclusion. On ne coupe + // donc plus au premier : on retient le **dernier** `agent_message` comme unique + // `Final` terminal (la conclusion du tour), et on dégrade les précédents en + // activité non terminale (readiness préservée). Ainsi le demandeur reçoit la + // conclusion, pas le préambule. Absence d'`agent_message` ⇒ aucun `Final` + // (no-reply inchangé). + let mut last_final: Option = None; + for line in &raw_lines { let parsed = parse_event(line)?; if let Some(id) = parsed.conversation_id { captured_id = Some(id); } - // Le `Final` est **terminal** (contrat de port) : on arrête d'émettre dès - // qu'on l'a vu, pour qu'aucun heartbeat de fin (`turn.completed` postérieur) - // ne le suive dans le flux. for event in parsed.events { - let is_final = matches!(event, ReplyEvent::Final { .. }); - events.push(event); - if is_final { - break 'lines; + match event { + ReplyEvent::Final { content } => { + // Un `agent_message` précédemment retenu est supersédé : il devient + // une activité non terminale, on garde le plus récent en conclusion. + if last_final.is_some() { + events.push(ReplyEvent::ToolActivity { + label: "agent_message".to_owned(), + }); + } + last_final = Some(content); + } + other => events.push(other), } } } + // Le dernier `agent_message` retenu est le seul `Final` terminal, émis en fin de + // flux (après les heartbeats/activités du tour). + if let Some(content) = last_final { + events.push(ReplyEvent::Final { content }); + } if let Some(id) = captured_id { *self.conversation_id.lock().expect("mutex sain") = Some(id); }