mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
fix(tui/history): demote earlier Answer outputs mid-turn
Add `mid_turn` to assistant history records and cells, and retroactively mark previous Answer streams as mid‑turn when a new Answer id arrives so only the final output gets gutter/bold styling. Also upgrade legacy `gpt-5-codex` slugs to `gpt-5.1-codex`, bump GPT‑5 profile/review defaults to `gpt-5.2`, and refresh related TUI snapshots.
This commit is contained in:
@@ -2333,6 +2333,14 @@ fn upgrade_legacy_model_slug(slug: &str) -> Option<String> {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(rest) = slug.strip_prefix("test-gpt-5-codex") {
|
||||
return Some(format!("test-gpt-5.1-codex{rest}"));
|
||||
}
|
||||
|
||||
if let Some(rest) = slug.strip_prefix("gpt-5-codex") {
|
||||
return Some(format!("gpt-5.1-codex{rest}"));
|
||||
}
|
||||
|
||||
// Upgrade Anthropic Opus 4.1 to 4.5
|
||||
if slug.eq_ignore_ascii_case("claude-opus-4.1") {
|
||||
return Some("claude-opus-4.5".to_string());
|
||||
@@ -2344,11 +2352,7 @@ fn upgrade_legacy_model_slug(slug: &str) -> Option<String> {
|
||||
}
|
||||
|
||||
// Keep codex variants on their existing 5.1 line; there is no 5.2 codex.
|
||||
if slug.starts_with("gpt-5.1-codex")
|
||||
|| slug.starts_with("test-gpt-5.1-codex")
|
||||
|| slug.starts_with("gpt-5-codex")
|
||||
|| slug.starts_with("test-gpt-5-codex")
|
||||
{
|
||||
if slug.starts_with("gpt-5.1-codex") || slug.starts_with("test-gpt-5.1-codex") {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -3934,14 +3938,14 @@ model_verbosity = "high"
|
||||
gpt5_profile_overrides,
|
||||
fixture.code_home(),
|
||||
)?;
|
||||
assert_eq!("gpt-5.1", gpt5_profile_config.model);
|
||||
assert_eq!("gpt-5.2", gpt5_profile_config.model);
|
||||
assert_eq!(OPENAI_DEFAULT_REVIEW_MODEL, gpt5_profile_config.review_model);
|
||||
assert_eq!(
|
||||
ReasoningEffort::High,
|
||||
gpt5_profile_config.review_model_reasoning_effort
|
||||
);
|
||||
assert_eq!(
|
||||
find_family_for_model("gpt-5.1").expect("known model slug"),
|
||||
find_family_for_model("gpt-5.2").expect("known model slug"),
|
||||
gpt5_profile_config.model_family
|
||||
);
|
||||
assert!(gpt5_profile_config.model_context_window.is_some());
|
||||
@@ -4135,7 +4139,7 @@ model_verbosity = "high"
|
||||
upgrade_legacy_model_slugs(&mut cfg);
|
||||
|
||||
assert_eq!(cfg.model.as_deref(), Some("gpt-5.1-codex"));
|
||||
assert_eq!(cfg.review_model.as_deref(), Some("gpt-5.1"));
|
||||
assert_eq!(cfg.review_model.as_deref(), Some("gpt-5.2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -849,6 +849,11 @@ pub struct AssistantMessageState {
|
||||
pub citations: Vec<String>,
|
||||
pub metadata: Option<MessageMetadata>,
|
||||
pub token_usage: Option<TokenUsage>,
|
||||
/// True when this assistant output was emitted before the final Answer
|
||||
/// within the same turn (e.g., mid‑turn progress update). Used by the TUI
|
||||
/// to suppress gutter/bold styling.
|
||||
#[serde(default)]
|
||||
pub mid_turn: bool,
|
||||
pub created_at: SystemTime,
|
||||
}
|
||||
|
||||
@@ -1494,6 +1499,7 @@ impl HistoryState {
|
||||
citations,
|
||||
metadata,
|
||||
token_usage,
|
||||
mid_turn: false,
|
||||
created_at: SystemTime::now(),
|
||||
};
|
||||
let id = self.next_history_id();
|
||||
@@ -2536,6 +2542,7 @@ mod tests {
|
||||
citations: Vec::new(),
|
||||
metadata: None,
|
||||
token_usage: None,
|
||||
mid_turn: false,
|
||||
created_at: SystemTime::UNIX_EPOCH,
|
||||
});
|
||||
|
||||
@@ -2584,6 +2591,7 @@ mod tests {
|
||||
citations: Vec::new(),
|
||||
metadata: None,
|
||||
token_usage: None,
|
||||
mid_turn: false,
|
||||
created_at: SystemTime::UNIX_EPOCH,
|
||||
});
|
||||
|
||||
@@ -2995,6 +3003,7 @@ mod tests {
|
||||
citations: vec!["cite".into()],
|
||||
metadata: Some(metadata.clone()),
|
||||
token_usage: metadata.token_usage.clone(),
|
||||
mid_turn: false,
|
||||
created_at: now,
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
|
||||
@@ -1605,6 +1605,11 @@ pub(crate) struct ChatWidget<'a> {
|
||||
// at once into scrollback so the history contains a single message.
|
||||
// Cache of the last finalized assistant message to suppress immediate duplicates
|
||||
last_assistant_message: Option<String>,
|
||||
// Track the most recent finalized Answer output item within the current turn.
|
||||
// When a new Answer stream id arrives, we retroactively mark the previous
|
||||
// assistant message as a mid-turn update for styling.
|
||||
last_answer_stream_id_in_turn: Option<String>,
|
||||
last_answer_history_id_in_turn: Option<HistoryId>,
|
||||
// Cache of the last user text we submitted (for context passing to review/resolve agents)
|
||||
last_user_message: Option<String>,
|
||||
// Cache of the last developer/system note we injected (hidden messages)
|
||||
@@ -3791,6 +3796,7 @@ impl ChatWidget<'_> {
|
||||
citations: Vec::new(),
|
||||
metadata: None,
|
||||
token_usage: None,
|
||||
mid_turn: false,
|
||||
created_at: SystemTime::now(),
|
||||
};
|
||||
let greeting_cell =
|
||||
@@ -5839,6 +5845,8 @@ impl ChatWidget<'_> {
|
||||
rate_limit_secondary_next_reset_at: None,
|
||||
content_buffer: String::new(),
|
||||
last_assistant_message: None,
|
||||
last_answer_stream_id_in_turn: None,
|
||||
last_answer_history_id_in_turn: None,
|
||||
last_user_message: None,
|
||||
last_developer_message: None,
|
||||
pending_turn_origin: None,
|
||||
@@ -6173,6 +6181,8 @@ impl ChatWidget<'_> {
|
||||
rate_limit_secondary_next_reset_at: None,
|
||||
content_buffer: String::new(),
|
||||
last_assistant_message: None,
|
||||
last_answer_stream_id_in_turn: None,
|
||||
last_answer_history_id_in_turn: None,
|
||||
last_user_message: None,
|
||||
last_developer_message: None,
|
||||
pending_turn_origin: None,
|
||||
@@ -12184,6 +12194,9 @@ impl ChatWidget<'_> {
|
||||
self.ensure_lingering_execs_cleared();
|
||||
|
||||
self.stream_state.seq_answer_final = Some(event.event_seq);
|
||||
if !id.trim().is_empty() {
|
||||
self.maybe_mark_previous_answer_mid_turn(&id);
|
||||
}
|
||||
// Strict order for the stream id
|
||||
let ok = match event.order.as_ref() {
|
||||
Some(om) => self.provider_order_key_from_order_meta(om),
|
||||
@@ -12299,6 +12312,7 @@ impl ChatWidget<'_> {
|
||||
tracing::debug!("Ignoring Answer delta for closed id={}", id);
|
||||
return;
|
||||
}
|
||||
self.maybe_mark_previous_answer_mid_turn(&id);
|
||||
// Seed/refresh order key for this Answer stream id (must have OrderMeta)
|
||||
let ok = match event.order.as_ref() {
|
||||
Some(om) => self.provider_order_key_from_order_meta(om),
|
||||
@@ -12449,6 +12463,8 @@ impl ChatWidget<'_> {
|
||||
// Reset stream headers for new turn
|
||||
self.stream.reset_headers_for_new_turn();
|
||||
self.stream_state.current_kind = None;
|
||||
self.last_answer_stream_id_in_turn = None;
|
||||
self.last_answer_history_id_in_turn = None;
|
||||
// New turn: clear closed id guards
|
||||
self.stream_state.closed_answer_ids.clear();
|
||||
self.stream_state.closed_reasoning_ids.clear();
|
||||
@@ -14078,6 +14094,7 @@ impl ChatWidget<'_> {
|
||||
citations: Vec::new(),
|
||||
metadata: None,
|
||||
token_usage: None,
|
||||
mid_turn: false,
|
||||
created_at: SystemTime::now(),
|
||||
};
|
||||
let assistant_cell =
|
||||
@@ -23815,6 +23832,48 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_mark_previous_answer_mid_turn(&mut self, new_stream_id: &str) {
|
||||
let Some(prev_stream_id) = self.last_answer_stream_id_in_turn.as_deref() else {
|
||||
return;
|
||||
};
|
||||
if prev_stream_id == new_stream_id {
|
||||
return;
|
||||
}
|
||||
let Some(prev_history_id) = self.last_answer_history_id_in_turn else {
|
||||
return;
|
||||
};
|
||||
let mut changed = false;
|
||||
if let Some(idx) = self
|
||||
.history_cell_ids
|
||||
.iter()
|
||||
.rposition(|hid| *hid == Some(prev_history_id))
|
||||
{
|
||||
if let Some(cell) = self.history_cells[idx]
|
||||
.as_any_mut()
|
||||
.downcast_mut::<history_cell::AssistantMarkdownCell>()
|
||||
{
|
||||
if !cell.state().mid_turn {
|
||||
cell.set_mid_turn(true);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(record) = self.history_state.record_mut(prev_history_id) {
|
||||
if let HistoryRecord::AssistantMessage(state) = record {
|
||||
if !state.mid_turn {
|
||||
state.mid_turn = true;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if changed {
|
||||
self.mark_history_dirty();
|
||||
self.request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
fn finalize_answer_stream_state(
|
||||
&mut self,
|
||||
stream_id: Option<&str>,
|
||||
@@ -24005,6 +24064,8 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
|
||||
let cell = history_cell::AssistantMarkdownCell::from_state(state, &self.config);
|
||||
self.history_insert_existing_record(Box::new(cell), key, "answer-review", history_id);
|
||||
self.last_answer_stream_id_in_turn = id.clone();
|
||||
self.last_answer_history_id_in_turn = Some(history_id);
|
||||
// Advance Auto Drive after the assistant message has been finalized.
|
||||
self.auto_on_assistant_final();
|
||||
self.maybe_hide_spinner();
|
||||
@@ -24129,6 +24190,7 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
idx
|
||||
);
|
||||
let state = self.finalize_answer_stream_state(id.as_deref(), &final_source);
|
||||
let history_id = state.id;
|
||||
let cell = history_cell::AssistantMarkdownCell::from_state(state, &self.config);
|
||||
self.history_replace_at(idx, Box::new(cell));
|
||||
if let Some(ref want) = id {
|
||||
@@ -24137,6 +24199,8 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
.insert(StreamId(want.clone()));
|
||||
}
|
||||
self.autoscroll_if_near_bottom();
|
||||
self.last_answer_stream_id_in_turn = id.clone();
|
||||
self.last_answer_history_id_in_turn = Some(history_id);
|
||||
// Final cell committed via replacement; now advance Auto Drive.
|
||||
self.auto_on_assistant_final();
|
||||
self.maybe_hide_spinner();
|
||||
@@ -24163,12 +24227,15 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
);
|
||||
let state =
|
||||
self.finalize_answer_stream_state(id.as_deref(), &final_source);
|
||||
let history_id = state.id;
|
||||
let cell = history_cell::AssistantMarkdownCell::from_state(state, &self.config);
|
||||
self.history_replace_at(idx, Box::new(cell));
|
||||
self.stream_state
|
||||
.closed_answer_ids
|
||||
.insert(StreamId(want.clone()));
|
||||
self.autoscroll_if_near_bottom();
|
||||
self.last_answer_stream_id_in_turn = id.clone();
|
||||
self.last_answer_history_id_in_turn = Some(history_id);
|
||||
// Final cell replaced in-place; advance Auto Drive now.
|
||||
self.auto_on_assistant_final();
|
||||
self.maybe_hide_spinner();
|
||||
@@ -24210,9 +24277,12 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
);
|
||||
let state =
|
||||
self.finalize_answer_stream_state(id.as_deref(), &final_source);
|
||||
let history_id = state.id;
|
||||
let cell = history_cell::AssistantMarkdownCell::from_state(state, &self.config);
|
||||
self.history_replace_at(idx, Box::new(cell));
|
||||
self.autoscroll_if_near_bottom();
|
||||
self.last_answer_stream_id_in_turn = id.clone();
|
||||
self.last_answer_history_id_in_turn = Some(history_id);
|
||||
// Final assistant content revised; advance Auto Drive now.
|
||||
self.auto_on_assistant_final();
|
||||
self.maybe_hide_spinner();
|
||||
@@ -24258,6 +24328,7 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
Self::debug_fmt_order_key(key)
|
||||
);
|
||||
let state = self.finalize_answer_stream_state(id.as_deref(), &final_source);
|
||||
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);
|
||||
if let Some(ref want) = id {
|
||||
@@ -24265,6 +24336,8 @@ Have we met every part of this goal and is there no further work to do?"#
|
||||
.closed_answer_ids
|
||||
.insert(StreamId(want.clone()));
|
||||
}
|
||||
self.last_answer_stream_id_in_turn = id.clone();
|
||||
self.last_answer_history_id_in_turn = Some(history_id);
|
||||
// Ordered insert completed; advance Auto Drive now that the assistant
|
||||
// message is present in history.
|
||||
self.auto_on_assistant_final();
|
||||
@@ -32887,6 +32960,7 @@ impl ChatWidget<'_> {
|
||||
citations: Vec::new(),
|
||||
metadata: None,
|
||||
token_usage: None,
|
||||
mid_turn: false,
|
||||
created_at: SystemTime::now(),
|
||||
};
|
||||
history_cell::AssistantMarkdownCell::from_state(state, &self.config)
|
||||
|
||||
@@ -41,6 +41,14 @@ impl AssistantMarkdownCell {
|
||||
self.layout_cache.borrow_mut().clear();
|
||||
}
|
||||
|
||||
pub(crate) fn set_mid_turn(&mut self, mid_turn: bool) {
|
||||
if self.state.mid_turn == mid_turn {
|
||||
return;
|
||||
}
|
||||
self.state.mid_turn = mid_turn;
|
||||
self.layout_cache.borrow_mut().clear();
|
||||
}
|
||||
|
||||
pub(crate) fn stream_id(&self) -> Option<&str> {
|
||||
self.state.stream_id.as_deref()
|
||||
}
|
||||
@@ -254,6 +262,14 @@ impl HistoryCell for AssistantMarkdownCell {
|
||||
HistoryCellType::Assistant
|
||||
}
|
||||
|
||||
fn gutter_symbol(&self) -> Option<&'static str> {
|
||||
if self.state.mid_turn {
|
||||
None
|
||||
} else {
|
||||
super::gutter_symbol_for_kind(self.kind())
|
||||
}
|
||||
}
|
||||
|
||||
fn display_lines(&self) -> Vec<Line<'static>> {
|
||||
assistant_markdown_lines_with_context(&self.state, self.file_opener, &self.cwd)
|
||||
}
|
||||
@@ -305,7 +321,7 @@ pub(crate) fn assistant_markdown_lines_with_context(
|
||||
&mut out,
|
||||
file_opener,
|
||||
cwd,
|
||||
true,
|
||||
!state.mid_turn,
|
||||
);
|
||||
let bright = crate::colors::text_bright();
|
||||
for line in out.iter_mut().skip(1) {
|
||||
|
||||
@@ -116,6 +116,7 @@ pub(crate) fn stream_lines_from_state_with_context(
|
||||
.metadata
|
||||
.as_ref()
|
||||
.and_then(|meta| meta.token_usage.clone()),
|
||||
mid_turn: false,
|
||||
created_at: state.last_updated_at,
|
||||
};
|
||||
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: tui/tests/vt100_chatwidget_snapshot.rs
|
||||
assertion_line: 2250
|
||||
expression: open
|
||||
---
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
@@ -7,7 +8,7 @@ expression: open
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
+Settings ▸ Overview-----------------------------------------------------------------------------+
|
||||
| |
|
||||
| › Model Model: GPT-5.1-Codex (Medium) |
|
||||
| › Model Model: GPT-5.1-Codex-Max (Medium) |
|
||||
| + Choose the language model used for new completions. |
|
||||
| +----------------------------------------+ |
|
||||
| Theme Theme|Settings Overview |mond |
|
||||
|
||||
+7
-6
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: tui/tests/vt100_chatwidget_snapshot.rs
|
||||
assertion_line: 2267
|
||||
expression: section_open
|
||||
---
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
@@ -13,12 +14,12 @@ expression: section_open
|
||||
| Agents | Reason|Model Shortcuts | | |
|
||||
| Prompts | | | | |
|
||||
| Auto Drive | GPT-5.|• Esc Return to overview | | |
|
||||
| Review | Optimi|• Tab Cycle sections | | |
|
||||
| Planning | Hig|• Shift+Tab Cycle backwards |r complex or ambiguous proble | |
|
||||
| Validation | Med|• ? Toggle this help |sts reasoning based on the ta | |
|
||||
| Chrome | Low| |d reasoning | |
|
||||
| MCP | |Press Esc to close | | |
|
||||
| Notifications | GPT-5.+------------------------------+ | |
|
||||
| Review | Latest|• Tab Cycle sections |s across knowledge, reasoning | |
|
||||
| Planning | XHi|• Shift+Tab Cycle backwards |for complex problems | |
|
||||
| Validation | Hig|• ? Toggle this help |r complex or ambiguous proble | |
|
||||
| Chrome | Med| |f reasoning depth and latency | |
|
||||
| MCP | Low|Press Esc to close |oning; useful for straightfor | |
|
||||
| Notifications | +------------------------------+ | |
|
||||
| Limits | | |
|
||||
| +----------------------------------------------------------------------+ |
|
||||
| ↑ ↓ Move Enter Open Esc Close ? Help |
|
||||
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: tui/tests/vt100_chatwidget_snapshot.rs
|
||||
assertion_line: 2179
|
||||
expression: output
|
||||
---
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
@@ -7,7 +8,7 @@ expression: output
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
+Settings ▸ Overview-----------------------------------------------------------------------------+
|
||||
| |
|
||||
| › Model Model: GPT-5.1-Codex (Medium) |
|
||||
| › Model Model: GPT-5.1-Codex-Max (Medium) |
|
||||
| + Choose the language model used for new completions. |
|
||||
| |
|
||||
| Theme Theme: Light - Photon (16-color) · Spinner: Diamond |
|
||||
@@ -18,7 +19,7 @@ expression: output
|
||||
| |
|
||||
| |
|
||||
|
||||
| Agents Enabled: 9/10 |
|
||||
| Agents Enabled: 10/11 |
|
||||
| + Configure linked agents and default task permissions. |
|
||||
| |
|
||||
| ↑ ↓ Move Enter Open Esc Close ? Help |
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: tui/tests/vt100_chatwidget_snapshot.rs
|
||||
assertion_line: 2191
|
||||
expression: output
|
||||
---
|
||||
+--------------------------------------------------------+
|
||||
@@ -7,7 +8,7 @@ expression: output
|
||||
+--------------------------------------------------------+
|
||||
+Settings ▸ Overview-------------------------------------+
|
||||
| |
|
||||
| › Model Model: GPT-5.1-Codex (Medium) |
|
||||
| › Model Model: GPT-5.1-Codex-Max (Medium) |
|
||||
| + Choose the language model used for new comp... |
|
||||
| |
|
||||
| Theme Theme: Light - Photon (16-colo... |
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: tui/tests/vt100_chatwidget_snapshot.rs
|
||||
assertion_line: 2217
|
||||
expression: output
|
||||
---
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
@@ -7,7 +8,7 @@ expression: output
|
||||
+------------------------------------------------------------------------------------------------+
|
||||
+Settings ▸ Overview-----------------------------------------------------------------------------+
|
||||
| |
|
||||
| › Model Model: GPT-5.1-Codex (Medium) |
|
||||
| › Model Model: GPT-5.1-Codex-Max (Medium) |
|
||||
| + Choose the language model used for new completions. |
|
||||
| |
|
||||
| Theme Theme: Light - Photon (16-color) · Spinner: Diamond |
|
||||
|
||||
Reference in New Issue
Block a user