mirror of
https://github.com/openai/codex.git
synced 2026-09-09 15:58:47 +00:00
Require confirmation for safety-buffered retries (#42380)
## What changed - Show a confirmation before stopping a safety-buffered attempt and retrying with the server-selected faster model. - Explain that the retry starts a new thread, preserves actions already taken, and may use a less capable model; allow the user to keep waiting instead. - Dismiss the buffering or confirmation view when the response starts or the turn is no longer eligible for retry. - Refresh the buffering copy and wrap confirmation text correctly in narrow terminals. ## Testing - Cover retry confirmation, cancellation, turn completion, response startup, stale updates, and narrow-terminal rendering. GitOrigin-RevId: 013bc47174811dd104b421774577c2808c732bfa
This commit is contained in:
@@ -770,6 +770,16 @@ impl App {
|
||||
tracing::error!(error = ?err, "failed to start turn through app server");
|
||||
}
|
||||
}
|
||||
AppEvent::ConfirmSafetyBufferedRetry {
|
||||
thread_id,
|
||||
turn_id,
|
||||
model,
|
||||
turn,
|
||||
prompt,
|
||||
} => {
|
||||
self.chat_widget
|
||||
.confirm_safety_buffered_retry(thread_id, turn_id, model, turn, prompt);
|
||||
}
|
||||
AppEvent::RetrySafetyBufferedTurn {
|
||||
thread_id,
|
||||
turn_id,
|
||||
|
||||
@@ -41,10 +41,6 @@ impl App {
|
||||
return;
|
||||
}
|
||||
if !self.chat_widget.can_retry_safety_buffered_turn(&turn_id) {
|
||||
self.app_event_tx.send(AppEvent::UpdateModel(model));
|
||||
self.app_event_tx.send(AppEvent::UpdateReasoningEffort(Some(
|
||||
ReasoningEffortConfig::Low,
|
||||
)));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -714,6 +714,13 @@ goals = true
|
||||
.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
let second_retry = loop {
|
||||
match app_event_rx.try_recv() {
|
||||
Ok(event @ AppEvent::ConfirmSafetyBufferedRetry { .. }) => {
|
||||
Box::pin(app.handle_event(&mut tui, &mut app_server, event)).await?;
|
||||
app.chat_widget
|
||||
.handle_key_event(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
|
||||
app.chat_widget
|
||||
.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
}
|
||||
Ok(AppEvent::RetrySafetyBufferedTurn {
|
||||
thread_id,
|
||||
turn_id,
|
||||
|
||||
@@ -300,6 +300,15 @@ pub(crate) enum AppEvent {
|
||||
op: AppCommand,
|
||||
},
|
||||
|
||||
/// Confirm retrying a safety-buffered turn with the server-selected model.
|
||||
ConfirmSafetyBufferedRetry {
|
||||
thread_id: ThreadId,
|
||||
turn_id: String,
|
||||
model: String,
|
||||
turn: AppCommand,
|
||||
prompt: UserMessage,
|
||||
},
|
||||
|
||||
/// Interrupt, fork, and retry a safety-buffered turn with the server-selected model.
|
||||
RetrySafetyBufferedTurn {
|
||||
thread_id: ThreadId,
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
//! Safety-buffering status and retry UI for active turns.
|
||||
//! Safety-buffering status and retry confirmation UI for active turns.
|
||||
//! Both views share an ID so response streaming, completion, or buffering updates dismiss either one.
|
||||
|
||||
use super::*;
|
||||
use crate::wrapping::word_wrap_lines;
|
||||
use codex_app_server_protocol::ModelSafetyBufferingUpdatedNotification;
|
||||
|
||||
const SAFETY_BUFFERING_PROMPT_VIEW_ID: &str = "safety-buffering-prompt";
|
||||
const SAFETY_BUFFERING_LEARN_MORE_URL: &str = "https://help.openai.com/en/articles/20001326";
|
||||
|
||||
const SAFETY_BUFFERING_HEADER: &str =
|
||||
"Our systems are thinking a bit more about this request before responding.";
|
||||
const SAFETY_BUFFERING_MESSAGE_WITH_RETRY: &str = "Hang tight or retry with a faster model for a quicker response, though it may be less capable of handling complex requests.";
|
||||
const SAFETY_BUFFERING_HEADER: &str = "Giving this request a little extra thought";
|
||||
const SAFETY_BUFFERING_MESSAGE_WITH_RETRY: &str = "If you'd rather not wait, retry with a faster model. It may be less capable of handling complex requests.";
|
||||
const SAFETY_BUFFERING_FOOTER: &str = "No action is required. Codex will keep waiting, and this menu will close when the response is ready.";
|
||||
|
||||
struct SafetyBufferingHeader(Vec<Line<'static>>);
|
||||
|
||||
impl Renderable for SafetyBufferingHeader {
|
||||
fn render(&self, area: Rect, buf: &mut Buffer) {
|
||||
Renderable::render(
|
||||
&Paragraph::new(word_wrap_lines(&self.0, usize::from(area.width))),
|
||||
area,
|
||||
buf,
|
||||
);
|
||||
}
|
||||
|
||||
fn desired_height(&self, width: u16) -> u16 {
|
||||
word_wrap_lines(&self.0, usize::from(width)).len() as u16
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ActiveSafetyBuffering {
|
||||
turn_id: String,
|
||||
@@ -42,8 +59,13 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
pub(super) fn mark_safety_buffering_agent_message_started(&mut self) {
|
||||
if let Some(active) = self.safety_buffering.active.as_mut() {
|
||||
if let Some(active) = self.safety_buffering.active.as_mut()
|
||||
&& !active.agent_message_started
|
||||
{
|
||||
active.agent_message_started = true;
|
||||
self.bottom_pane
|
||||
.dismiss_view_by_id(SAFETY_BUFFERING_PROMPT_VIEW_ID);
|
||||
self.restore_reasoning_status_header();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +115,11 @@ impl ChatWidget {
|
||||
if matches!(replay_kind, Some(ReplayKind::ResumeInitialMessages))
|
||||
|| !self.turn_lifecycle.agent_turn_running
|
||||
|| self.turn_lifecycle.last_turn_id.as_deref() != Some(turn_id.as_str())
|
||||
|| self
|
||||
.safety_buffering
|
||||
.active
|
||||
.as_ref()
|
||||
.is_some_and(|active| active.turn_id == turn_id && active.agent_message_started)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -135,12 +162,10 @@ impl ChatWidget {
|
||||
.filter(|active| active.turn_id == turn_id);
|
||||
let should_show_prompt =
|
||||
previous_active.is_none_or(|active| active.last_prompt_had_retry != can_offer_retry);
|
||||
let agent_message_started =
|
||||
previous_active.is_some_and(|active| active.agent_message_started);
|
||||
self.safety_buffering.active = Some(ActiveSafetyBuffering {
|
||||
turn_id: turn_id.clone(),
|
||||
last_prompt_had_retry: can_offer_retry,
|
||||
agent_message_started,
|
||||
agent_message_started: false,
|
||||
});
|
||||
|
||||
let status_details = if can_offer_retry {
|
||||
@@ -162,16 +187,10 @@ impl ChatWidget {
|
||||
self.bottom_pane
|
||||
.dismiss_view_by_id(SAFETY_BUFFERING_PROMPT_VIEW_ID);
|
||||
|
||||
let mut header = vec![Box::new(
|
||||
Paragraph::new(Line::from(SAFETY_BUFFERING_HEADER).bold()).wrap(Wrap { trim: false }),
|
||||
) as Box<dyn Renderable>];
|
||||
let mut header = vec![Line::from(SAFETY_BUFFERING_HEADER).bold()];
|
||||
if can_offer_retry {
|
||||
header.push(Box::new(
|
||||
Paragraph::new(Line::from(SAFETY_BUFFERING_MESSAGE_WITH_RETRY).dim())
|
||||
.wrap(Wrap { trim: false }),
|
||||
));
|
||||
header.push(Line::from(SAFETY_BUFFERING_MESSAGE_WITH_RETRY).dim());
|
||||
}
|
||||
let header = ColumnRenderable::with(header);
|
||||
let mut items = Vec::new();
|
||||
if let (Some(faster_model), Some(turn), Some(prompt), Some(thread_id)) =
|
||||
(faster_model, retry_turn, retry_prompt, thread_id)
|
||||
@@ -179,7 +198,7 @@ impl ChatWidget {
|
||||
items.push(SelectionItem {
|
||||
name: "Retry with a faster model".to_string(),
|
||||
actions: vec![Box::new(move |tx| {
|
||||
tx.send(AppEvent::RetrySafetyBufferedTurn {
|
||||
tx.send(AppEvent::ConfirmSafetyBufferedRetry {
|
||||
thread_id,
|
||||
turn_id: turn_id.clone(),
|
||||
model: faster_model.clone(),
|
||||
@@ -209,11 +228,68 @@ impl ChatWidget {
|
||||
]);
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
view_id: Some(SAFETY_BUFFERING_PROMPT_VIEW_ID),
|
||||
header: Box::new(header),
|
||||
header: Box::new(SafetyBufferingHeader(header)),
|
||||
footer_note: Some(Line::from(SAFETY_BUFFERING_FOOTER).dim()),
|
||||
footer_hint: Some(Line::default()),
|
||||
items,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn confirm_safety_buffered_retry(
|
||||
&mut self,
|
||||
thread_id: ThreadId,
|
||||
turn_id: String,
|
||||
model: String,
|
||||
turn: AppCommand,
|
||||
prompt: UserMessage,
|
||||
) {
|
||||
if self.thread_id != Some(thread_id) || !self.can_retry_safety_buffered_turn(&turn_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let model_name = self
|
||||
.model_catalog
|
||||
.try_list_models()
|
||||
.ok()
|
||||
.and_then(|models| models.into_iter().find(|preset| preset.model == model))
|
||||
.map(|preset| preset.display_name)
|
||||
.unwrap_or_else(|| model.clone());
|
||||
self.bottom_pane
|
||||
.dismiss_view_by_id(SAFETY_BUFFERING_PROMPT_VIEW_ID);
|
||||
self.bottom_pane.show_selection_view(SelectionViewParams {
|
||||
view_id: Some(SAFETY_BUFFERING_PROMPT_VIEW_ID),
|
||||
header: Box::new(SafetyBufferingHeader(vec![
|
||||
"Stop this attempt and retry?".bold().into(),
|
||||
Line::default(),
|
||||
"This will stop the current attempt and retry in a new thread. Any file changes or other actions already taken will remain.".dim().into(),
|
||||
Line::default(),
|
||||
format!("Your message will be sent again using {model_name}, which may be less capable on complex tasks.").dim().into(),
|
||||
])),
|
||||
footer_hint: Some(Line::default()),
|
||||
items: vec![
|
||||
SelectionItem {
|
||||
name: "Keep waiting".to_string(),
|
||||
dismiss_on_select: true,
|
||||
..Default::default()
|
||||
},
|
||||
SelectionItem {
|
||||
name: "Stop and retry".to_string(),
|
||||
actions: vec![Box::new(move |tx| {
|
||||
tx.send(AppEvent::RetrySafetyBufferedTurn {
|
||||
thread_id,
|
||||
turn_id: turn_id.clone(),
|
||||
model: model.clone(),
|
||||
turn: turn.clone(),
|
||||
prompt: prompt.clone(),
|
||||
});
|
||||
})],
|
||||
dismiss_on_select: true,
|
||||
require_explicit_confirmation: true,
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests/app_server.rs
|
||||
expression: "render_bottom_popup(&chat, 80)"
|
||||
---
|
||||
Stop this attempt and retry?
|
||||
|
||||
This will stop the current attempt and retry in a new thread. Any file
|
||||
changes or other actions already taken will remain.
|
||||
|
||||
Your message will be sent again using Faster Model, which may be less
|
||||
capable on complex tasks.
|
||||
|
||||
› 1. Keep waiting
|
||||
2. Stop and retry
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests/app_server.rs
|
||||
expression: "render_bottom_popup(&chat, 40)"
|
||||
---
|
||||
Stop this attempt and retry?
|
||||
|
||||
This will stop the current attempt
|
||||
and retry in a new thread. Any file
|
||||
changes or other actions already
|
||||
taken will remain.
|
||||
|
||||
Your message will be sent again
|
||||
using Faster Model, which may be
|
||||
less capable on complex tasks.
|
||||
|
||||
› 1. Keep waiting
|
||||
2. Stop and retry
|
||||
@@ -2,9 +2,9 @@
|
||||
source: tui/src/chatwidget/tests/app_server.rs
|
||||
expression: popup
|
||||
---
|
||||
Our systems are thinking a bit more about this request before responding.
|
||||
Hang tight or retry with a faster model for a quicker response, though it
|
||||
may be less capable of handling complex requests.
|
||||
Giving this request a little extra thought
|
||||
If you'd rather not wait, retry with a faster model. It may be less capable
|
||||
of handling complex requests.
|
||||
|
||||
› 1. Retry with a faster model
|
||||
2. Dismiss and keep waiting
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
source: tui/src/chatwidget/tests/app_server.rs
|
||||
expression: popup
|
||||
---
|
||||
Our systems are thinking a bit more about this request before responding.
|
||||
Giving this request a little extra thought
|
||||
|
||||
› 1. Dismiss and keep waiting
|
||||
2. Learn more
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
source: tui/src/chatwidget/tests/app_server.rs
|
||||
expression: popup
|
||||
---
|
||||
Our systems are thinking a bit more about this request before responding.
|
||||
Giving this request a little extra thought
|
||||
|
||||
› 1. Dismiss and keep waiting
|
||||
2. Learn more
|
||||
|
||||
@@ -4,8 +4,7 @@ use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::CodexErrorDetails;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
const SAFETY_BUFFERING_HEADER_TEXT: &str =
|
||||
"Our systems are thinking a bit more about this request before responding.";
|
||||
const SAFETY_BUFFERING_HEADER_TEXT: &str = "Giving this request a little extra thought";
|
||||
|
||||
fn thread_settings_for_test(
|
||||
model: &str,
|
||||
@@ -113,9 +112,41 @@ fn safety_buffering_notification(
|
||||
}
|
||||
}
|
||||
|
||||
fn open_safety_buffering_retry_confirmation(
|
||||
chat: &mut ChatWidget,
|
||||
rx: &mut tokio::sync::mpsc::UnboundedReceiver<AppEvent>,
|
||||
) {
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
loop {
|
||||
match rx.try_recv() {
|
||||
Ok(AppEvent::ConfirmSafetyBufferedRetry {
|
||||
thread_id,
|
||||
turn_id,
|
||||
model,
|
||||
turn,
|
||||
prompt,
|
||||
}) => {
|
||||
chat.confirm_safety_buffered_retry(thread_id, turn_id, model, turn, prompt);
|
||||
break;
|
||||
}
|
||||
Ok(AppEvent::RetrySafetyBufferedTurn { .. }) => {
|
||||
panic!("retry must wait for confirmation");
|
||||
}
|
||||
Ok(_) => continue,
|
||||
Err(err) => panic!("expected safety-buffering confirmation event: {err}"),
|
||||
}
|
||||
}
|
||||
assert!(chat.turn_lifecycle.agent_turn_running);
|
||||
assert!(render_bottom_popup(chat, /*width*/ 80).contains("Stop this attempt and retry?"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn safety_buffering_offers_one_retry_with_app_wording() {
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
let mut preset = get_available_model(&chat, "gpt-5.4");
|
||||
preset.model = "faster-model".to_string();
|
||||
preset.display_name = "Faster Model".to_string();
|
||||
chat.model_catalog = Arc::new(ModelCatalog::new(vec![preset]));
|
||||
let (thread_id, turn_id, _) = start_safety_buffering_test_turn(&mut chat, &mut op_rx);
|
||||
|
||||
let notification = safety_buffering_notification(thread_id, turn_id, Some("faster-model"));
|
||||
@@ -146,6 +177,21 @@ async fn safety_buffering_offers_one_retry_with_app_wording() {
|
||||
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
|
||||
open_safety_buffering_retry_confirmation(&mut chat, &mut rx);
|
||||
assert_chatwidget_snapshot!(
|
||||
"safety_buffering_retry_confirmation",
|
||||
render_bottom_popup(&chat, /*width*/ 80)
|
||||
);
|
||||
assert_chatwidget_snapshot!(
|
||||
"safety_buffering_retry_confirmation_narrow",
|
||||
render_bottom_popup(&chat, /*width*/ 40)
|
||||
);
|
||||
assert!(op_rx.try_recv().is_err());
|
||||
while let Ok(event) = rx.try_recv() {
|
||||
assert!(!matches!(event, AppEvent::RetrySafetyBufferedTurn { .. }));
|
||||
}
|
||||
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
|
||||
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
let (event_thread_id, event_turn_id, model, turn, prompt) = loop {
|
||||
match rx.try_recv() {
|
||||
@@ -171,6 +217,60 @@ async fn safety_buffering_offers_one_retry_with_app_wording() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn safety_buffering_retry_confirmation_can_keep_waiting() {
|
||||
for key in [KeyCode::Enter, KeyCode::Esc] {
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
let (thread_id, turn_id, _) = start_safety_buffering_test_turn(&mut chat, &mut op_rx);
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::ModelSafetyBufferingUpdated(safety_buffering_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
Some("faster-model"),
|
||||
)),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
open_safety_buffering_retry_confirmation(&mut chat, &mut rx);
|
||||
|
||||
chat.handle_key_event(KeyEvent::new(key, KeyModifiers::NONE));
|
||||
|
||||
assert!(!render_bottom_popup(&chat, /*width*/ 80).contains("Stop this attempt and retry?"));
|
||||
assert!(chat.can_retry_safety_buffered_turn(turn_id));
|
||||
assert!(op_rx.try_recv().is_err());
|
||||
while let Ok(event) = rx.try_recv() {
|
||||
assert!(!matches!(event, AppEvent::RetrySafetyBufferedTurn { .. }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn safety_buffering_retry_confirmation_closes_when_turn_completes() {
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
let (thread_id, turn_id, turn) = start_safety_buffering_test_turn(&mut chat, &mut op_rx);
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::ModelSafetyBufferingUpdated(safety_buffering_notification(
|
||||
thread_id,
|
||||
turn_id,
|
||||
Some("faster-model"),
|
||||
)),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
open_safety_buffering_retry_confirmation(&mut chat, &mut rx);
|
||||
|
||||
handle_turn_completed(&mut chat, turn_id, /*duration_ms*/ None);
|
||||
// A queued request to open the confirmation must not reopen it after completion.
|
||||
chat.confirm_safety_buffered_retry(
|
||||
thread_id,
|
||||
turn_id.to_string(),
|
||||
"faster-model".to_string(),
|
||||
turn,
|
||||
UserMessage::from("Explain the request"),
|
||||
);
|
||||
|
||||
assert!(!render_bottom_popup(&chat, /*width*/ 80).contains("Stop this attempt and retry?"));
|
||||
assert!(!chat.can_retry_safety_buffered_turn(turn_id));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn safety_buffering_does_not_offer_retry_in_side_conversation() {
|
||||
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
@@ -191,8 +291,8 @@ async fn safety_buffering_does_not_offer_retry_in_side_conversation() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn safety_buffering_remains_visible_until_turn_completes() {
|
||||
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
async fn safety_buffering_retry_confirmation_closes_when_response_starts() {
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
let (thread_id, turn_id, _) = start_safety_buffering_test_turn(&mut chat, &mut op_rx);
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::ModelSafetyBufferingUpdated(safety_buffering_notification(
|
||||
@@ -203,11 +303,30 @@ async fn safety_buffering_remains_visible_until_turn_completes() {
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
assert!(chat.can_retry_safety_buffered_turn(turn_id));
|
||||
open_safety_buffering_retry_confirmation(&mut chat, &mut rx);
|
||||
|
||||
chat.on_agent_message_delta("Visible response".to_string());
|
||||
|
||||
assert!(!chat.can_retry_safety_buffered_turn(turn_id));
|
||||
assert!(render_bottom_popup(&chat, /*width*/ 80).contains(SAFETY_BUFFERING_HEADER_TEXT));
|
||||
let popup = render_bottom_popup(&chat, /*width*/ 80);
|
||||
assert!(!popup.contains("Stop this attempt and retry?"));
|
||||
assert!(!popup.contains(SAFETY_BUFFERING_HEADER_TEXT));
|
||||
|
||||
for (show_buffering_ui, faster_model) in [
|
||||
(true, Some("faster-model")),
|
||||
(true, None),
|
||||
(false, None),
|
||||
(true, Some("faster-model")),
|
||||
] {
|
||||
let mut notification = safety_buffering_notification(thread_id, turn_id, faster_model);
|
||||
notification.show_buffering_ui = show_buffering_ui;
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::ModelSafetyBufferingUpdated(notification),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
assert!(!chat.can_retry_safety_buffered_turn(turn_id));
|
||||
assert_eq!(render_bottom_popup(&chat, /*width*/ 80), popup);
|
||||
}
|
||||
|
||||
handle_turn_completed(&mut chat, turn_id, /*duration_ms*/ None);
|
||||
|
||||
@@ -256,7 +375,7 @@ async fn safety_buffering_without_retry_shows_short_app_message() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn safety_buffering_ignores_hidden_stale_and_historical_updates() {
|
||||
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
let (thread_id, turn_id, _) = start_safety_buffering_test_turn(&mut chat, &mut op_rx);
|
||||
|
||||
let mut hidden = safety_buffering_notification(thread_id, turn_id, Some("faster-model"));
|
||||
@@ -289,6 +408,7 @@ async fn safety_buffering_ignores_hidden_stale_and_historical_updates() {
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
assert!(render_bottom_popup(&chat, /*width*/ 80).contains(SAFETY_BUFFERING_HEADER_TEXT));
|
||||
open_safety_buffering_retry_confirmation(&mut chat, &mut rx);
|
||||
hidden.show_buffering_ui = false;
|
||||
chat.handle_server_notification(
|
||||
ServerNotification::ModelSafetyBufferingUpdated(hidden),
|
||||
@@ -303,6 +423,7 @@ async fn safety_buffering_ignores_hidden_stale_and_historical_updates() {
|
||||
None
|
||||
);
|
||||
assert!(!render_bottom_popup(&chat, /*width*/ 80).contains(SAFETY_BUFFERING_HEADER_TEXT));
|
||||
assert!(!render_bottom_popup(&chat, /*width*/ 80).contains("Stop this attempt and retry?"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -21,7 +21,10 @@ async fn misalignment_policy_failure_stops_the_thread_and_renders_once() {
|
||||
}),
|
||||
/*replay_kind*/ None,
|
||||
);
|
||||
assert!(render_bottom_popup(&chat, /*width*/ 80).contains("thinking a bit more"));
|
||||
assert!(
|
||||
render_bottom_popup(&chat, /*width*/ 80)
|
||||
.contains("Giving this request a little extra thought")
|
||||
);
|
||||
chat.queue_user_message(UserMessage::from("queued follow-up"));
|
||||
chat.bottom_pane
|
||||
.set_composer_text("stale draft".to_string(), Vec::new(), Vec::new());
|
||||
|
||||
Reference in New Issue
Block a user