fix(codex): dernier agent_message = Final (bootstrap canal)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 14:49:00 +02:00
parent a9653bc417
commit 62915eec4d

View File

@ -219,22 +219,40 @@ impl AgentSession for CodexExecSession {
let mut events = Vec::new(); let mut events = Vec::new();
let mut captured_id = None; 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<String> = None;
for line in &raw_lines {
let parsed = parse_event(line)?; let parsed = parse_event(line)?;
if let Some(id) = parsed.conversation_id { if let Some(id) = parsed.conversation_id {
captured_id = Some(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 { for event in parsed.events {
let is_final = matches!(event, ReplyEvent::Final { .. }); match event {
events.push(event); ReplyEvent::Final { content } => {
if is_final { // Un `agent_message` précédemment retenu est supersédé : il devient
break 'lines; // 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 { if let Some(id) = captured_id {
*self.conversation_id.lock().expect("mutex sain") = Some(id); *self.conversation_id.lock().expect("mutex sain") = Some(id);
} }