fix(tui/history): hide gutter on mid-turn answers

This commit is contained in:
James Peter
2025-12-12 13:17:35 +10:00
parent 0991bf2463
commit 89e485dbb2
2 changed files with 130 additions and 2 deletions
+57 -2
View File
@@ -12202,6 +12202,11 @@ impl ChatWidget<'_> {
self.stream_state.seq_answer_final = Some(event.event_seq);
if !id.trim().is_empty() {
self.note_answer_stream_seen(&id);
// Any Answer item that completes before TaskComplete is considered
// mid‑turn until we later determine it was the final Answer.
if !self.active_task_ids.is_empty() {
self.mid_turn_answer_ids_in_turn.insert(id.clone());
}
}
// Strict order for the stream id
let ok = match event.order.as_ref() {
@@ -12508,13 +12513,20 @@ impl ChatWidget<'_> {
self.clear_reconnecting();
let had_running_execs = !self.exec.running_commands.is_empty();
// Finalize any active streams
if self.stream.is_write_cycle_active() {
let finalizing_streams = self.stream.is_write_cycle_active();
if finalizing_streams {
// Finalize both streams via streaming facade
streaming::finalize(self, StreamKind::Reasoning, true);
streaming::finalize(self, StreamKind::Answer, true);
}
// Remove this id from the active set (it may be a sub‑agent)
self.active_task_ids.remove(&id);
if !finalizing_streams && self.active_task_ids.is_empty() {
if let Some(last_id) = self.last_seen_answer_stream_id_in_turn.clone() {
self.mid_turn_answer_ids_in_turn.remove(&last_id);
self.maybe_clear_mid_turn_for_last_answer(&last_id);
}
}
if self.auto_resolve_enabled() {
self.auto_resolve_on_task_complete(last_agent_message.clone());
}
@@ -23902,6 +23914,44 @@ Have we met every part of this goal and is there no further work to do?"#
}
}
fn maybe_clear_mid_turn_for_last_answer(&mut self, stream_id: &str) {
let Some(last_history_id) = self.last_answer_history_id_in_turn else {
return;
};
let mut changed = false;
if let Some(record) = self.history_state.record_mut(last_history_id) {
if let HistoryRecord::AssistantMessage(state) = record {
if state.stream_id.as_deref() == Some(stream_id) && state.mid_turn {
state.mid_turn = false;
changed = true;
}
}
}
if let Some(idx) = self
.history_cell_ids
.iter()
.rposition(|hid| *hid == Some(last_history_id))
{
if let Some(cell) = self.history_cells[idx]
.as_any_mut()
.downcast_mut::<history_cell::AssistantMarkdownCell>()
{
if cell.stream_id() == Some(stream_id) && cell.state().mid_turn {
cell.set_mid_turn(false);
changed = true;
}
}
}
if changed {
self.mark_history_dirty();
self.request_redraw();
}
}
fn finalize_answer_stream_state(
&mut self,
stream_id: Option<&str>,
@@ -24363,7 +24413,12 @@ Have we met every part of this goal and is there no further work to do?"#
self.apply_mid_turn_flag(id.as_deref(), &mut state);
let history_id = state.id;
let cell = history_cell::AssistantMarkdownCell::from_state(state, &self.config);
let _ = self.history_insert_with_key_global(Box::new(cell), key);
self.history_insert_existing_record(
Box::new(cell),
key,
"answer-final",
history_id,
);
if let Some(ref want) = id {
self.stream_state
.closed_answer_ids
@@ -0,0 +1,73 @@
//! Regression test: mid-turn Answer outputs should not render assistant gutter/bold styling.
#![cfg(test)]
#![allow(clippy::unwrap_used, clippy::expect_used)]
use code_core::protocol::{AgentMessageEvent, Event, EventMsg, OrderMeta};
use code_tui::test_helpers::{render_chat_widget_to_vt100, ChatWidgetHarness};
#[test]
fn mid_turn_answer_suppresses_bullet_gutter() {
let mut harness = ChatWidgetHarness::new();
// Start a turn.
harness.handle_event(Event {
id: "task-1".into(),
event_seq: 0,
msg: EventMsg::TaskStarted,
order: None,
});
// Simulate two Answer output items in the same turn. The first is a mid-turn
// progress update; the second is the real final answer.
harness.handle_event(Event {
id: "msg-1".into(),
event_seq: 1,
msg: EventMsg::AgentMessage(AgentMessageEvent {
message: "Progress update".into(),
}),
order: Some(OrderMeta {
request_ordinal: 1,
output_index: Some(0),
sequence_number: None,
}),
});
harness.handle_event(Event {
id: "msg-2".into(),
event_seq: 2,
msg: EventMsg::AgentMessage(AgentMessageEvent {
message: "Final answer".into(),
}),
order: Some(OrderMeta {
request_ordinal: 1,
output_index: Some(1),
sequence_number: None,
}),
});
// Flush pending InsertFinalAnswer events so both assistant cells exist.
let _ = render_chat_widget_to_vt100(&mut harness, 80, 24);
// End the turn so the last Answer is treated as final.
harness.handle_event(Event {
id: "task-1".into(),
event_seq: 3,
msg: EventMsg::TaskComplete(code_core::protocol::TaskCompleteEvent {
last_agent_message: None,
}),
order: None,
});
let output = render_chat_widget_to_vt100(&mut harness, 80, 24);
assert!(output.contains("Progress update"));
assert!(
!output.contains(" • Progress update"),
"mid-turn assistant messages should not show bullet gutter"
);
assert!(
output.contains(" • Final answer"),
"final assistant message should retain bullet gutter"
);
}