mirror of
https://github.com/openai/codex.git
synced 2026-11-05 16:29:31 +00:00
fix(tui): hide empty reasoning summaries (#31652)
## Why Reasoning summaries can contain an empty HTML comment placeholder, `<!-- -->`, when a generated summary part has no prose. The TUI treated that placeholder as visible summary content, so it leaked into both the completed conversation history and persisted transcript. Multi-part empty summaries could also leave later generated status headings visible as orphaned transcript content. ## What - Strip the empty reasoning-summary placeholder at the shared `ReasoningSummaryCell` boundary. - Suppress the cell when removing placeholders leaves only generated bold status headings. - Cover the multi-part placeholder-only shape with a rendering regression test for both normal history and transcript output. ## How to Test 1. Start Codex TUI with reasoning summaries visible, for example with `hide_agent_reasoning = false` and `model_reasoning_summary = "detailed"`. 2. Run a turn that receives an empty generated reasoning-summary part. 3. Confirm that the in-progress status heading may appear while the turn is running, but the completed history and transcript contain neither literal `<!-- -->` tokens nor orphaned reasoning headings. 4. Confirm that a reasoning summary with actual prose still renders normally. The model output is nondeterministic, so the targeted regression covers the exact multi-part placeholder shape from the report: - `just test -p codex-tui reasoning_summary_block_hides_empty_html_comment_parts`
This commit is contained in:
1 parent
8dc77faf51
commit
f46b53bd85
11 files changed
+286
-72
No files matched your search
@@ -867,7 +867,7 @@ async fn replayed_thread_closed_notification_does_not_exit_tui() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn replayed_reasoning_item_hides_raw_reasoning_when_disabled() {
|
||||
async fn replayed_reasoning_item_preserves_summary_parts_and_hides_raw_reasoning_when_disabled() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.config.show_raw_agent_reasoning = false;
|
||||
chat.handle_thread_session(crate::session_state::ThreadSessionState {
|
||||
@@ -897,7 +897,10 @@ async fn replayed_reasoning_item_hides_raw_reasoning_when_disabled() {
|
||||
chat.replay_thread_item(
|
||||
AppServerThreadItem::Reasoning {
|
||||
id: "reasoning-1".to_string(),
|
||||
summary: vec!["Summary only".to_string()],
|
||||
summary: vec![
|
||||
"**Plan**\n\ndone".to_string(),
|
||||
"**Checking tests**\n\n<!-- -->".to_string(),
|
||||
],
|
||||
content: vec!["Raw reasoning".to_string()],
|
||||
},
|
||||
"turn-1".to_string(),
|
||||
@@ -910,7 +913,7 @@ async fn replayed_reasoning_item_hides_raw_reasoning_when_disabled() {
|
||||
}
|
||||
other => panic!("expected InsertHistoryCell, got {other:?}"),
|
||||
};
|
||||
assert!(!rendered.trim().is_empty());
|
||||
assert_eq!(rendered, "• done\n");
|
||||
assert!(!rendered.contains("Raw reasoning"));
|
||||
}
|
||||
|
||||
@@ -1047,6 +1050,82 @@ async fn live_reasoning_summary_is_not_rendered_twice_when_item_completes() {
|
||||
assert_eq!(rendered.matches("Summary only").count(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn live_reasoning_summary_drops_empty_parts_without_losing_content() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.show_welcome_banner = false;
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::TurnStarted(TurnStartedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn: AppServerTurn {
|
||||
id: "turn-1".to_string(),
|
||||
items_view: codex_app_server_protocol::TurnItemsView::Full,
|
||||
items: Vec::new(),
|
||||
status: AppServerTurnStatus::InProgress,
|
||||
error: None,
|
||||
started_at: Some(0),
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
},
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
let _ = drain_insert_history(&mut rx);
|
||||
|
||||
for (summary_index, delta) in [
|
||||
(0, "**Plan**\n\ndone"),
|
||||
(1, "**Checking tests**\n\n<!-- -->"),
|
||||
] {
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::ReasoningSummaryPartAdded(
|
||||
codex_app_server_protocol::ReasoningSummaryPartAddedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
item_id: "reasoning-1".to_string(),
|
||||
summary_index,
|
||||
},
|
||||
),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::ReasoningSummaryTextDelta(ReasoningSummaryTextDeltaNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
item_id: "reasoning-1".to_string(),
|
||||
delta: delta.to_string(),
|
||||
summary_index,
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
}
|
||||
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::ItemCompleted(ItemCompletedNotification {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: "turn-1".to_string(),
|
||||
completed_at_ms: 0,
|
||||
item: AppServerThreadItem::Reasoning {
|
||||
id: "reasoning-1".to_string(),
|
||||
summary: vec![
|
||||
"**Plan**\n\ndone".to_string(),
|
||||
"**Checking tests**\n\n<!-- -->".to_string(),
|
||||
],
|
||||
content: Vec::new(),
|
||||
},
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
|
||||
let rendered = match rx.try_recv() {
|
||||
Ok(AppEvent::InsertHistoryCell(cell)) => {
|
||||
lines_to_single_string(&cell.transcript_lines(/*width*/ 80))
|
||||
}
|
||||
other => panic!("expected InsertHistoryCell, got {other:?}"),
|
||||
};
|
||||
assert_eq!(rendered, "• done\n");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_snapshot_replayed_turn_started_marks_task_running() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
|
||||
Reference in new issue
Block a user