mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
fix(tui/history): hide gutter on mid-turn answers
This commit is contained in:
@@ -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"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user