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);
|
self.stream_state.seq_answer_final = Some(event.event_seq);
|
||||||
if !id.trim().is_empty() {
|
if !id.trim().is_empty() {
|
||||||
self.note_answer_stream_seen(&id);
|
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
|
// Strict order for the stream id
|
||||||
let ok = match event.order.as_ref() {
|
let ok = match event.order.as_ref() {
|
||||||
@@ -12508,13 +12513,20 @@ impl ChatWidget<'_> {
|
|||||||
self.clear_reconnecting();
|
self.clear_reconnecting();
|
||||||
let had_running_execs = !self.exec.running_commands.is_empty();
|
let had_running_execs = !self.exec.running_commands.is_empty();
|
||||||
// Finalize any active streams
|
// 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
|
// Finalize both streams via streaming facade
|
||||||
streaming::finalize(self, StreamKind::Reasoning, true);
|
streaming::finalize(self, StreamKind::Reasoning, true);
|
||||||
streaming::finalize(self, StreamKind::Answer, true);
|
streaming::finalize(self, StreamKind::Answer, true);
|
||||||
}
|
}
|
||||||
// Remove this id from the active set (it may be a sub‑agent)
|
// Remove this id from the active set (it may be a sub‑agent)
|
||||||
self.active_task_ids.remove(&id);
|
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() {
|
if self.auto_resolve_enabled() {
|
||||||
self.auto_resolve_on_task_complete(last_agent_message.clone());
|
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(
|
fn finalize_answer_stream_state(
|
||||||
&mut self,
|
&mut self,
|
||||||
stream_id: Option<&str>,
|
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);
|
self.apply_mid_turn_flag(id.as_deref(), &mut state);
|
||||||
let history_id = state.id;
|
let history_id = state.id;
|
||||||
let cell = history_cell::AssistantMarkdownCell::from_state(state, &self.config);
|
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 {
|
if let Some(ref want) = id {
|
||||||
self.stream_state
|
self.stream_state
|
||||||
.closed_answer_ids
|
.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