fix(cli): z-order Cancel #151 + projection live événements avant Final #167 (+ alignement fixture plugin #165/#166) — QA verte
This commit is contained in:
@ -421,7 +421,7 @@ impl AgentSession for CodexExecSession {
|
||||
}
|
||||
|
||||
async fn send(&self, prompt: &str) -> Result<ReplyStream, AgentSessionError> {
|
||||
self.send_inner(prompt, None, true).await
|
||||
self.send_inner(prompt, None).await
|
||||
}
|
||||
|
||||
async fn send_with_tap(
|
||||
@ -429,7 +429,7 @@ impl AgentSession for CodexExecSession {
|
||||
prompt: &str,
|
||||
tap: std::sync::mpsc::Sender<ReplyEvent>,
|
||||
) -> Result<ReplyStream, AgentSessionError> {
|
||||
self.send_inner(prompt, Some(tap), false).await
|
||||
self.send_inner(prompt, Some(tap)).await
|
||||
}
|
||||
|
||||
async fn shutdown(&self) -> Result<(), AgentSessionError> {
|
||||
@ -443,17 +443,17 @@ impl CodexExecSession {
|
||||
&self,
|
||||
prompt: &str,
|
||||
tap: Option<std::sync::mpsc::Sender<ReplyEvent>>,
|
||||
include_stream_announcements: bool,
|
||||
) -> Result<ReplyStream, AgentSessionError> {
|
||||
let spec = self.build_spawn_line(prompt);
|
||||
let has_tap = tap.is_some();
|
||||
let (line_tap, parser_thread) = tap.map_or((None, None), |event_tap| {
|
||||
let (line_tx, line_rx) = std::sync::mpsc::channel::<String>();
|
||||
let parser = std::thread::spawn(move || {
|
||||
for line in line_rx {
|
||||
if let Ok(parsed) = parse_event(&line) {
|
||||
for event in parsed.events {
|
||||
if let ReplyEvent::Final { content } = event {
|
||||
let _ = event_tap.send(ReplyEvent::Announcement { text: content });
|
||||
if !matches!(event, ReplyEvent::Final { .. }) {
|
||||
let _ = event_tap.send(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -488,7 +488,7 @@ impl CodexExecSession {
|
||||
// Un `agent_message` précédemment retenu est supersédé : il devient
|
||||
// une annonce non terminale, on garde le plus récent en conclusion.
|
||||
if last_final.is_some() {
|
||||
if include_stream_announcements {
|
||||
if !has_tap {
|
||||
events.push(ReplyEvent::Announcement {
|
||||
text: last_final.take().expect("présent"),
|
||||
});
|
||||
@ -496,7 +496,11 @@ impl CodexExecSession {
|
||||
}
|
||||
last_final = Some(content);
|
||||
}
|
||||
other => events.push(other),
|
||||
other => {
|
||||
if !has_tap {
|
||||
events.push(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1216,10 +1216,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn codex_send_with_tap_emits_each_agent_message_live_as_announcement() {
|
||||
async fn codex_send_with_tap_emits_provider_events_live_before_final_without_replay() {
|
||||
let fake = FakeCli::printing(&[
|
||||
r#"{"type":"thread.started","thread_id":"c"}"#,
|
||||
r#"{"type":"item.completed","item":{"id":"i0","type":"agent_message","text":"je regarde"}}"#,
|
||||
r#"{"type":"turn.started"}"#,
|
||||
r#"{"type":"item.completed","item":{"id":"tool0","type":"command","text":"cargo test"}}"#,
|
||||
r#"{"type":"turn.completed"}"#,
|
||||
r#"{"type":"item.completed","item":{"id":"i1","type":"agent_message","text":"résultat"}}"#,
|
||||
]);
|
||||
let s = CodexExecSession::new(
|
||||
@ -1237,24 +1239,31 @@ mod tests {
|
||||
let events: Vec<_> = s.send_with_tap("x", tx).await.expect("send").collect();
|
||||
let live: Vec<_> = rx.into_iter().collect();
|
||||
|
||||
assert_eq!(
|
||||
live,
|
||||
vec![
|
||||
ReplyEvent::Announcement {
|
||||
text: "je regarde".into()
|
||||
},
|
||||
ReplyEvent::Announcement {
|
||||
text: "résultat".into()
|
||||
},
|
||||
],
|
||||
"le tap live publie chaque agent_message, y compris celui qui deviendra Final"
|
||||
assert!(
|
||||
matches!(live.first(), Some(ReplyEvent::Progress { progress }) if progress.native_event.as_deref() == Some("turn.started")),
|
||||
"le démarrage du tour provider doit être tapé live en premier"
|
||||
);
|
||||
assert!(
|
||||
live.iter().any(
|
||||
|event| matches!(event, ReplyEvent::ToolActivity { label } if label == "command")
|
||||
),
|
||||
"l'activité outil provider doit être tapée live"
|
||||
);
|
||||
assert!(
|
||||
live.iter().any(|event| matches!(event, ReplyEvent::Progress { progress } if progress.native_event.as_deref() == Some("turn.completed"))),
|
||||
"la fin de tour provider doit être tapée live avant le Final"
|
||||
);
|
||||
assert!(
|
||||
live.iter()
|
||||
.all(|event| !matches!(event, ReplyEvent::Final { .. })),
|
||||
"le Final reste réservé au flux autoritaire"
|
||||
);
|
||||
assert_eq!(
|
||||
without_progress(events.clone()),
|
||||
vec![ReplyEvent::Final {
|
||||
content: "résultat".into()
|
||||
}],
|
||||
"le flux final du chemin tap garde seulement le dernier Final pour le demandeur"
|
||||
"le flux final du chemin tap ne rejoue pas les événements déjà projetés live"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user