mirror of
https://github.com/openai/codex.git
synced 2026-11-09 20:50:33 +00:00
Suppress stale cloud stream disconnects after reopening a thread (#60327)
## Why A queued cloud stream disconnect can outlive a successful reopen of the same conversation, causing an obsolete reconnect error to reach the TUI. ## What changed Represent cloud reply stream losses with `AppServerEvent::ThreadStreamDisconnected` and filter queued losses superseded by a successful attachment to the same thread. Preserve losses for other threads and any newer disconnects; failed reopen attempts do not supersede pending losses. Continue displaying current stream disconnects through the TUI's error notification handler. ## Testing Add regression coverage for successful and failed reopens, disconnect ordering across threads, and TUI handling of disconnect events from the active and foreign backends. GitOrigin-RevId: 640332b817970717fc816ab5a32bf658c01233bf
This commit is contained in:
committed by
copyberry
parent
b9796386e5
commit
ffdf2daddb
@@ -156,6 +156,13 @@ impl App {
|
||||
.await;
|
||||
self.repaint_agents_overview();
|
||||
}
|
||||
AppServerEvent::ThreadStreamDisconnected(error) => {
|
||||
self.handle_server_notification_event(
|
||||
app_server_client,
|
||||
ServerNotification::Error(*error),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
AppServerEvent::Disconnected { message } => {
|
||||
// The request result distinguishes a refused start from a lost reply.
|
||||
if self.pending_startup_thread_start
|
||||
|
||||
@@ -11,26 +11,32 @@ use pretty_assertions::assert_eq;
|
||||
#[tokio::test]
|
||||
async fn foreign_backend_events_leave_thread_cache_and_connection_untouched() -> Result<()> {
|
||||
let (mut app, mut events, _) = make_test_app_with_channels().await;
|
||||
let (server, requests, proxy) =
|
||||
let (mut server, requests, proxy) =
|
||||
session_lifecycle_requests::start_recording_remote_app_server(&app.config).await?;
|
||||
let id = ThreadId::new();
|
||||
app.primary_thread_id = Some(id);
|
||||
app.active_thread_id = Some(id);
|
||||
set_test_initial_prompt(&mut app, String::new());
|
||||
app.chat_widget
|
||||
.handle_thread_session(test_thread_session(id, app.config.cwd.to_path_buf()));
|
||||
app.chat_widget
|
||||
.restore_user_message_to_composer("keep draft".into());
|
||||
app.ensure_thread_channel(id);
|
||||
app.activate_thread_channel(id).await;
|
||||
while events.try_recv().is_ok() {}
|
||||
requests.lock().unwrap().clear();
|
||||
let input = app.chat_widget.capture_thread_input_state();
|
||||
let hard_stop_generation = app.rate_limit_hard_stop_generation;
|
||||
let foreign = BackendId::new();
|
||||
let request = exec_approval_request(id, "foreign-turn", "command", /*approval_id*/ None);
|
||||
let loss: codex_app_server_protocol::ErrorNotification =
|
||||
serde_json::from_value(serde_json::json!({
|
||||
"threadId": id.to_string(), "turnId": "", "willRetry": false,
|
||||
"error": { "message": "Cloud stream ended. Reopen the conversation to reconnect." }
|
||||
}))?;
|
||||
for event in [
|
||||
AppServerEvent::ServerNotification(Box::new(turn_started_notification(id, "foreign-turn"))),
|
||||
AppServerEvent::ServerRequest(Box::new(request.clone())),
|
||||
AppServerEvent::ThreadStreamDisconnected(Box::new(loss.clone())),
|
||||
AppServerEvent::Lagged { skipped: 1 },
|
||||
AppServerEvent::Disconnected {
|
||||
message: "foreign disconnected".into(),
|
||||
@@ -107,6 +113,21 @@ async fn foreign_backend_events_leave_thread_cache_and_connection_untouched() ->
|
||||
.active_turn_id(),
|
||||
Some("owned-turn")
|
||||
);
|
||||
let mut tui = crate::tui::test_support::make_test_tui()?;
|
||||
app.drain_active_thread_events(&mut tui).await?;
|
||||
app.handle_backend_event(
|
||||
&server,
|
||||
AppServerSessionEvent {
|
||||
backend_id: server.backend_id(),
|
||||
event: AppServerEvent::ThreadStreamDisconnected(Box::new(loss)),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
app.drain_active_thread_events(&mut tui).await?;
|
||||
let history =
|
||||
super::active_reconnect::drain_history(&mut app, &mut tui, &mut server, &mut events)
|
||||
.await?;
|
||||
insta::assert_snapshot!(history, @"■ Cloud stream ended. Reopen the conversation to reconnect.");
|
||||
server.shutdown().await?;
|
||||
proxy.await??;
|
||||
Ok(())
|
||||
|
||||
@@ -256,6 +256,7 @@ stream_max_retries = 0
|
||||
return Ok::<_, color_eyre::eyre::Report>(());
|
||||
}
|
||||
}
|
||||
codex_app_server_client::AppServerEvent::ThreadStreamDisconnected(_) => {}
|
||||
codex_app_server_client::AppServerEvent::Lagged { .. }
|
||||
| codex_app_server_client::AppServerEvent::Disconnected { .. } => {
|
||||
panic!("lost app-server events during the tool round trip");
|
||||
|
||||
@@ -794,7 +794,8 @@ async fn run_onboarding_screen_inner(
|
||||
AppServerEvent::Disconnected { message } => {
|
||||
return Err(color_eyre::eyre::eyre!(message));
|
||||
}
|
||||
AppServerEvent::Lagged { .. }
|
||||
AppServerEvent::ThreadStreamDisconnected(_)
|
||||
| AppServerEvent::Lagged { .. }
|
||||
| AppServerEvent::ServerRequest(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user