fix(cli): progress agent_message émis en live + anti-doublon running/done par progressId — #187 (QA verte)

- Codex: les items agent_message sont désormais tapés en live comme progress (Message/Delta) dès item.completed, au lieu d'être bufferisés dans pending_final jusqu'à l'événement tool/MCP suivant.
- Corrélation stable progressId (codex:item:<id>) ajoutée sur ReplyProgress, propagée jusqu'au DTO (ReplyProgressDto.progressId) et au frontend ; le ProgressSettler et la vue procèdent par upsert running -> done au lieu d'empiler des lignes dupliquées.
- Tests: chat_bridge (settler par progressId), dto_chat, session codex, CustomAgentChatView (upsert par progressId).
This commit is contained in:
2026-08-13 12:45:32 +02:00
parent 1d393ffd4f
commit a3ee2814d6
10 changed files with 250 additions and 15 deletions

View File

@ -70,6 +70,28 @@ fn progress_chunk(
) -> ReplyChunk {
ReplyChunk::Progress {
progress: ReplyProgressDto {
progress_id: None,
source: ReplyProgressSourceDto::ProviderNative,
kind,
stage,
label: label.to_owned(),
text: None,
provider: None,
native_event: None,
tool_name: Some(label.to_owned()),
},
}
}
fn progress_chunk_with_id(
kind: ReplyProgressKindDto,
stage: ReplyProgressStageDto,
label: &str,
progress_id: &str,
) -> ReplyChunk {
ReplyChunk::Progress {
progress: ReplyProgressDto {
progress_id: Some(progress_id.to_owned()),
source: ReplyProgressSourceDto::ProviderNative,
kind,
stage,
@ -824,6 +846,32 @@ fn progress_settler_finish_completes_active_progress_at_stream_end() {
assert!(settler.finish().is_none());
}
#[test]
fn progress_settler_matches_running_and_done_by_progress_id_before_label() {
let bridge = ChatBridge::new();
let session = sid();
let sink = Arc::new(Mutex::new(Vec::new()));
bridge.register(session, capturing_channel(Arc::clone(&sink)));
let mut settler = ProgressSettler::default();
let started = progress_chunk_with_id(
ReplyProgressKindDto::Message,
ReplyProgressStageDto::Delta,
"message intermédiaire",
"codex:item:msg-1",
);
let completed = progress_chunk_with_id(
ReplyProgressKindDto::Message,
ReplyProgressStageDto::Completed,
"message terminé",
"codex:item:msg-1",
);
assert!(bridge.send_output_settled(&session, &mut settler, started.clone()));
assert!(bridge.send_output_settled(&session, &mut settler, completed.clone()));
assert!(settler.finish().is_none());
assert_eq!(sink.lock().unwrap().clone(), vec![started, completed]);
}
// ---------------------------------------------------------------------------
// Registry basics, mirrored from pty_bridge.rs for parity
// ---------------------------------------------------------------------------