mirror of
https://github.com/openai/codex.git
synced 2026-05-24 04:54:52 +00:00
Start fresh TUI thread in background
This commit is contained in:
@@ -543,6 +543,7 @@ pub(crate) struct App {
|
||||
primary_session_configured: Option<ThreadSessionState>,
|
||||
pending_primary_events: VecDeque<ThreadBufferedEvent>,
|
||||
pending_app_server_requests: PendingAppServerRequests,
|
||||
pending_startup_thread_start_request_id: Option<String>,
|
||||
// Serialize plugin enablement writes per plugin so stale completions cannot
|
||||
// overwrite a newer toggle, even if the plugin is toggled from different
|
||||
// cwd contexts.
|
||||
@@ -575,6 +576,33 @@ async fn resolve_runtime_model_provider_base_url(provider: &ModelProviderInfo) -
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_startup_thread_start(
|
||||
app_server: &AppServerSession,
|
||||
config: Config,
|
||||
request_id: String,
|
||||
app_event_tx: AppEventSender,
|
||||
) {
|
||||
let request_handle = app_server.request_handle();
|
||||
let thread_params_mode = app_server.thread_params_mode();
|
||||
let remote_cwd_override = app_server.remote_cwd_override().map(Path::to_path_buf);
|
||||
let event_request_id = request_id.clone();
|
||||
tokio::spawn(async move {
|
||||
let result = crate::app_server_session::start_thread_with_request_handle(
|
||||
request_handle,
|
||||
request_id,
|
||||
config,
|
||||
thread_params_mode,
|
||||
remote_cwd_override,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| format!("{err:#}"));
|
||||
app_event_tx.send(AppEvent::StartupThreadStarted {
|
||||
request_id: event_request_id,
|
||||
result,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum ActiveTurnSteerRace {
|
||||
Missing,
|
||||
@@ -778,10 +806,20 @@ impl App {
|
||||
&initial_images,
|
||||
);
|
||||
let thread_and_widget_started_at = Instant::now();
|
||||
let mut pending_startup_thread_start_request_id = None;
|
||||
let (mut chat_widget, initial_started_thread) = match session_selection {
|
||||
SessionSelection::StartFresh | SessionSelection::Exit => {
|
||||
let started = app_server.start_thread(&config).await?;
|
||||
// Only count a startup tooltip once the fresh thread can actually render it.
|
||||
let startup_thread_start_request_id =
|
||||
format!("startup-thread-start-{}", Uuid::new_v4());
|
||||
pending_startup_thread_start_request_id =
|
||||
Some(startup_thread_start_request_id.clone());
|
||||
spawn_startup_thread_start(
|
||||
&app_server,
|
||||
config.clone(),
|
||||
startup_thread_start_request_id,
|
||||
app_event_tx.clone(),
|
||||
);
|
||||
// Count a startup tooltip once the initial chat widget can render it.
|
||||
let startup_tooltip_override =
|
||||
prepare_startup_tooltip_override(&mut config, &available_models, is_first_run)
|
||||
.await;
|
||||
@@ -811,7 +849,7 @@ impl App {
|
||||
.clone(),
|
||||
session_telemetry: session_telemetry.clone(),
|
||||
};
|
||||
(ChatWidget::new_with_app_event(init), Some(started))
|
||||
(ChatWidget::new_with_app_event(init), None)
|
||||
}
|
||||
SessionSelection::Resume(target_session) => {
|
||||
let resumed = app_server
|
||||
@@ -956,6 +994,7 @@ See the Codex keymap documentation for supported actions and examples."
|
||||
primary_session_configured: None,
|
||||
pending_primary_events: VecDeque::new(),
|
||||
pending_app_server_requests: PendingAppServerRequests::default(),
|
||||
pending_startup_thread_start_request_id,
|
||||
pending_plugin_enabled_writes: HashMap::new(),
|
||||
pending_hook_enabled_writes: HashMap::new(),
|
||||
};
|
||||
|
||||
@@ -23,6 +23,10 @@ impl App {
|
||||
)
|
||||
.await;
|
||||
}
|
||||
AppEvent::StartupThreadStarted { request_id, result } => {
|
||||
self.handle_startup_thread_started(app_server, request_id, result)
|
||||
.await?;
|
||||
}
|
||||
AppEvent::ClearUi => {
|
||||
self.clear_terminal_ui(tui, /*redraw_header*/ false)?;
|
||||
self.reset_app_ui_state_after_clear();
|
||||
|
||||
@@ -418,10 +418,46 @@ impl App {
|
||||
self.primary_session_configured = None;
|
||||
self.pending_primary_events.clear();
|
||||
self.pending_app_server_requests.clear();
|
||||
self.pending_startup_thread_start_request_id = None;
|
||||
self.chat_widget.set_pending_thread_approvals(Vec::new());
|
||||
self.sync_active_agent_label();
|
||||
}
|
||||
|
||||
pub(super) async fn handle_startup_thread_started(
|
||||
&mut self,
|
||||
app_server: &mut AppServerSession,
|
||||
request_id: String,
|
||||
result: Result<AppServerStartedThread, String>,
|
||||
) -> Result<()> {
|
||||
if self.pending_startup_thread_start_request_id.as_deref() != Some(request_id.as_str()) {
|
||||
if let Ok(started) = result
|
||||
&& let Err(err) = app_server
|
||||
.thread_unsubscribe(started.session.thread_id)
|
||||
.await
|
||||
{
|
||||
tracing::warn!(
|
||||
thread_id = %started.session.thread_id,
|
||||
"failed to unsubscribe stale startup thread: {err}"
|
||||
);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.pending_startup_thread_start_request_id = None;
|
||||
match result {
|
||||
Ok(started) => {
|
||||
self.enqueue_primary_thread_session(started.session, started.turns)
|
||||
.await?;
|
||||
}
|
||||
Err(err) => {
|
||||
self.chat_widget.add_error_message(format!(
|
||||
"Failed to start a fresh session through the app server: {err}"
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) async fn start_fresh_session_with_summary_hint(
|
||||
&mut self,
|
||||
tui: &mut tui::Tui,
|
||||
|
||||
@@ -60,6 +60,7 @@ pub(super) async fn make_test_app() -> App {
|
||||
primary_session_configured: None,
|
||||
pending_primary_events: VecDeque::new(),
|
||||
pending_app_server_requests: PendingAppServerRequests::default(),
|
||||
pending_startup_thread_start_request_id: None,
|
||||
pending_plugin_enabled_writes: HashMap::new(),
|
||||
pending_hook_enabled_writes: HashMap::new(),
|
||||
}
|
||||
|
||||
@@ -3882,6 +3882,7 @@ async fn make_test_app() -> App {
|
||||
primary_session_configured: None,
|
||||
pending_primary_events: VecDeque::new(),
|
||||
pending_app_server_requests: PendingAppServerRequests::default(),
|
||||
pending_startup_thread_start_request_id: None,
|
||||
pending_plugin_enabled_writes: HashMap::new(),
|
||||
pending_hook_enabled_writes: HashMap::new(),
|
||||
}
|
||||
@@ -3945,6 +3946,7 @@ async fn make_test_app_with_channels() -> (
|
||||
primary_session_configured: None,
|
||||
pending_primary_events: VecDeque::new(),
|
||||
pending_app_server_requests: PendingAppServerRequests::default(),
|
||||
pending_startup_thread_start_request_id: None,
|
||||
pending_plugin_enabled_writes: HashMap::new(),
|
||||
pending_hook_enabled_writes: HashMap::new(),
|
||||
},
|
||||
|
||||
@@ -33,6 +33,7 @@ use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_approval_presets::ApprovalPreset;
|
||||
|
||||
use crate::app_command::AppCommand;
|
||||
use crate::app_server_session::AppServerStartedThread;
|
||||
use crate::bottom_pane::ApprovalRequest;
|
||||
use crate::bottom_pane::StatusLineItem;
|
||||
use crate::bottom_pane::TerminalTitleItem;
|
||||
@@ -180,6 +181,12 @@ pub(crate) enum AppEvent {
|
||||
/// Start a new session.
|
||||
NewSession,
|
||||
|
||||
/// Result of the fresh startup thread that is attached after the input UI is live.
|
||||
StartupThreadStarted {
|
||||
request_id: String,
|
||||
result: Result<AppServerStartedThread, String>,
|
||||
},
|
||||
|
||||
/// Clear the terminal UI (screen + scrollback), start a fresh session, and keep the
|
||||
/// previous chat resumable.
|
||||
ClearUi,
|
||||
|
||||
@@ -167,6 +167,7 @@ impl ThreadParamsMode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AppServerStartedThread {
|
||||
pub(crate) session: ThreadSessionState,
|
||||
pub(crate) turns: Vec<Turn>,
|
||||
@@ -337,6 +338,7 @@ impl AppServerSession {
|
||||
self.client.next_event().await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) async fn start_thread(&mut self, config: &Config) -> Result<AppServerStartedThread> {
|
||||
self.start_thread_with_session_start_source(config, /*session_start_source*/ None)
|
||||
.await
|
||||
@@ -427,7 +429,7 @@ impl AppServerSession {
|
||||
Ok(started)
|
||||
}
|
||||
|
||||
fn thread_params_mode(&self) -> ThreadParamsMode {
|
||||
pub(crate) fn thread_params_mode(&self) -> ThreadParamsMode {
|
||||
self.thread_params_mode
|
||||
}
|
||||
|
||||
@@ -1002,6 +1004,28 @@ impl AppServerSession {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn start_thread_with_request_handle(
|
||||
request_handle: AppServerRequestHandle,
|
||||
request_id: String,
|
||||
config: Config,
|
||||
thread_params_mode: ThreadParamsMode,
|
||||
remote_cwd_override: Option<PathBuf>,
|
||||
) -> Result<AppServerStartedThread> {
|
||||
let response: ThreadStartResponse = request_handle
|
||||
.request_typed(ClientRequest::ThreadStart {
|
||||
request_id: RequestId::String(request_id),
|
||||
params: thread_start_params_from_config(
|
||||
&config,
|
||||
thread_params_mode,
|
||||
remote_cwd_override.as_deref(),
|
||||
/*session_start_source*/ None,
|
||||
),
|
||||
})
|
||||
.await
|
||||
.map_err(|err| bootstrap_request_error("thread/start failed during TUI bootstrap", err))?;
|
||||
started_thread_from_start_response(response, &config, thread_params_mode).await
|
||||
}
|
||||
|
||||
fn thread_realtime_start_params(
|
||||
thread_id: ThreadId,
|
||||
transport: Option<ThreadRealtimeStartTransport>,
|
||||
|
||||
Reference in New Issue
Block a user