fix(session): un item.completed tardif n'efface plus le final agent_message Codex — #240

event_supersedes_pending_final ne fait plus annuler le final en attente par
tout événement autre que Heartbeat/RateLimited/turn-progress: seuls un progress
non-turn Started/Delta, un ToolActivity ou un Error annulent. Le buffering
agent_message survive ainsi jusqu'au flush a turn.completed (regréssion
codex_agent_message_survives_late_completed_item_as_final).
This commit is contained in:
2026-08-22 17:27:13 +02:00
parent af90c66e48
commit 1962408eda
2 changed files with 50 additions and 11 deletions

View File

@ -1385,6 +1385,41 @@ mod tests {
);
}
#[tokio::test]
async fn codex_agent_message_survives_late_completed_item_as_final() {
let fake = FakeCli::printing(&[
r#"{"type":"thread.started","thread_id":"c"}"#,
r#"{"type":"turn.started"}"#,
r#"{"type":"item.completed","item":{"id":"i0","type":"agent_message","text":"réponse exploitable"}}"#,
r#"{"type":"item.completed","item":{"id":"i1","type":"reasoning","text":"post-traitement"}}"#,
r#"{"type":"turn.completed","usage":{}}"#,
]);
let s = CodexExecSession::new(
SessionId::new_random(),
fake.command(),
"/",
None,
Vec::new(),
Vec::new(),
None,
None,
);
let events: Vec<_> = s.send("x").await.expect("send").collect();
let finals: Vec<_> = events
.iter()
.filter_map(|event| match event {
ReplyEvent::Final { content } => Some(content.as_str()),
_ => None,
})
.collect();
assert_eq!(
finals,
vec!["réponse exploitable"],
"a late completed Codex item must not erase the last agent_message"
);
}
#[tokio::test]
async fn codex_zero_agent_message_has_no_final() {
let fake = FakeCli::printing(&[