This commit is contained in:
Eric Traut
2026-03-14 12:53:03 -06:00
parent fd9fe119df
commit 2ccb3624a1
5 changed files with 15 additions and 154 deletions
+4 -125
View File
@@ -309,6 +309,7 @@ impl ThreadEventStore {
}
}
#[cfg(test)]
fn new_with_session_configured(capacity: usize, event: SessionConfiguredEvent) -> Self {
let mut store = Self::new(capacity);
store.session_configured = Some(event);
@@ -383,6 +384,7 @@ impl ThreadEventChannel {
}
}
#[cfg(test)]
fn new_with_session_configured(capacity: usize, event: SessionConfiguredEvent) -> Self {
let (sender, receiver) = mpsc::channel(capacity);
Self {
@@ -1199,12 +1201,6 @@ impl App {
}
}
fn abort_thread_event_listener(&mut self, thread_id: ThreadId) {
if let Some(handle) = self.thread_event_listener_tasks.remove(&thread_id) {
handle.abort();
}
}
fn abort_all_thread_event_listeners(&mut self) {
for handle in self
.thread_event_listener_tasks
@@ -1889,24 +1885,6 @@ impl App {
///
/// A user-requested shutdown (`ExitMode::ShutdownFirst`) sets
/// `pending_shutdown_exit_thread_id`; matching shutdown completions are ignored
/// here so Ctrl+C-like exits don't accidentally resurrect the main thread.
///
/// Failover is only eligible when all of these are true:
/// 1. the event is `ShutdownComplete`;
/// 2. the active thread differs from the primary thread;
/// 3. the active thread is not the pending shutdown-exit thread.
fn active_non_primary_shutdown_target(&self, msg: &EventMsg) -> Option<(ThreadId, ThreadId)> {
if !matches!(msg, EventMsg::ShutdownComplete) {
return None;
}
let active_thread_id = self.active_thread_id?;
let primary_thread_id = self.primary_thread_id?;
if self.pending_shutdown_exit_thread_id == Some(active_thread_id) {
return None;
}
(active_thread_id != primary_thread_id).then_some((active_thread_id, primary_thread_id))
}
fn active_non_primary_closed_thread_target(
&self,
update: &ThreadUpdate,
@@ -2651,17 +2629,9 @@ impl App {
AppEvent::CodexEvent(event) => {
self.handle_codex_event_now(event);
}
AppEvent::ThreadEvent { thread_id, event } => {
if Some(thread_id) == self.active_thread_id {
self.handle_codex_event_now(event);
}
}
AppEvent::Exit(mode) => {
return Ok(self.handle_exit_mode(app_server, mode).await);
}
AppEvent::FatalExitRequest(message) => {
return Ok(AppRunControl::Exit(ExitReason::Fatal(message)));
}
AppEvent::CodexOp(op) => {
let replay_state_op =
ThreadEventStore::op_can_change_pending_replay_state(&op).then(|| op.clone());
@@ -2741,9 +2711,6 @@ impl App {
AppEvent::FileSearchResult { query, matches } => {
self.chat_widget.apply_file_search_result(query, matches);
}
AppEvent::RateLimitSnapshotFetched(snapshot) => {
self.chat_widget.on_rate_limit_snapshot(Some(snapshot));
}
AppEvent::RefreshRateLimits => {
match Self::read_account_rate_limits_via_app_server(app_server).await {
Ok(response) => {
@@ -2767,9 +2734,6 @@ impl App {
}
}
}
AppEvent::ConnectorsLoaded { result, is_final } => {
self.chat_widget.on_connectors_loaded(result, is_final);
}
AppEvent::UploadFeedback {
category,
thread_id,
@@ -3871,6 +3835,7 @@ impl App {
}
}
#[cfg(test)]
fn handle_codex_event_replay(&mut self, event: Event) {
self.chat_widget.handle_codex_event_replay(event);
}
@@ -3954,13 +3919,6 @@ impl App {
Ok(())
}
async fn handle_thread_created(&mut self, thread_id: ThreadId) -> Result<()> {
tracing::debug!(
"handle_thread_created({thread_id}) should be unreachable after app-server migration"
);
Ok(())
}
fn reasoning_label(reasoning_effort: Option<ReasoningEffortConfig>) -> &'static str {
match reasoning_effort {
Some(ReasoningEffortConfig::Minimal) => "minimal",
@@ -4338,7 +4296,7 @@ mod tests {
reason: Option<&str>,
) -> ThreadUpdate {
ThreadUpdate::CommandExecutionRequestApproval {
request_id: RequestId::String(format!("request-{call_id}")),
_request_id: RequestId::String(format!("request-{call_id}")),
params: codex_app_server_protocol::CommandExecutionRequestApprovalParams {
thread_id: thread_id.to_string(),
turn_id: turn_id.to_string(),
@@ -6155,85 +6113,6 @@ smart_approvals = true
assert_snapshot!("agent_picker_item_name", snapshot);
}
#[tokio::test]
async fn active_non_primary_shutdown_target_returns_none_for_non_shutdown_event() -> Result<()>
{
let mut app = make_test_app().await;
app.active_thread_id = Some(ThreadId::new());
app.primary_thread_id = Some(ThreadId::new());
assert_eq!(
app.active_non_primary_shutdown_target(&EventMsg::SkillsUpdateAvailable),
None
);
Ok(())
}
#[tokio::test]
async fn active_non_primary_shutdown_target_returns_none_for_primary_thread_shutdown()
-> Result<()> {
let mut app = make_test_app().await;
let thread_id = ThreadId::new();
app.active_thread_id = Some(thread_id);
app.primary_thread_id = Some(thread_id);
assert_eq!(
app.active_non_primary_shutdown_target(&EventMsg::ShutdownComplete),
None
);
Ok(())
}
#[tokio::test]
async fn active_non_primary_shutdown_target_returns_ids_for_non_primary_shutdown() -> Result<()>
{
let mut app = make_test_app().await;
let active_thread_id = ThreadId::new();
let primary_thread_id = ThreadId::new();
app.active_thread_id = Some(active_thread_id);
app.primary_thread_id = Some(primary_thread_id);
assert_eq!(
app.active_non_primary_shutdown_target(&EventMsg::ShutdownComplete),
Some((active_thread_id, primary_thread_id))
);
Ok(())
}
#[tokio::test]
async fn active_non_primary_shutdown_target_returns_none_when_shutdown_exit_is_pending()
-> Result<()> {
let mut app = make_test_app().await;
let active_thread_id = ThreadId::new();
let primary_thread_id = ThreadId::new();
app.active_thread_id = Some(active_thread_id);
app.primary_thread_id = Some(primary_thread_id);
app.pending_shutdown_exit_thread_id = Some(active_thread_id);
assert_eq!(
app.active_non_primary_shutdown_target(&EventMsg::ShutdownComplete),
None
);
Ok(())
}
#[tokio::test]
async fn active_non_primary_shutdown_target_still_switches_for_other_pending_exit_thread()
-> Result<()> {
let mut app = make_test_app().await;
let active_thread_id = ThreadId::new();
let primary_thread_id = ThreadId::new();
app.active_thread_id = Some(active_thread_id);
app.primary_thread_id = Some(primary_thread_id);
app.pending_shutdown_exit_thread_id = Some(ThreadId::new());
assert_eq!(
app.active_non_primary_shutdown_target(&EventMsg::ShutdownComplete),
Some((active_thread_id, primary_thread_id))
);
Ok(())
}
async fn render_clear_ui_header_after_long_transcript_for_snapshot() -> String {
let mut app = make_test_app().await;
app.config.cwd = PathBuf::from("/tmp/project");
+5 -5
View File
@@ -1038,14 +1038,14 @@ impl App {
match request.clone() {
ServerRequest::CommandExecutionRequestApproval { request_id, params } => {
self.route_thread_update(ThreadUpdate::CommandExecutionRequestApproval {
request_id,
_request_id: request_id,
params,
})
.await;
}
ServerRequest::FileChangeRequestApproval { request_id, params } => {
self.route_thread_update(ThreadUpdate::FileChangeRequestApproval {
request_id,
_request_id: request_id,
params,
})
.await;
@@ -1059,21 +1059,21 @@ impl App {
}
ServerRequest::PermissionsRequestApproval { request_id, params } => {
self.route_thread_update(ThreadUpdate::PermissionsRequestApproval {
request_id,
_request_id: request_id,
params,
})
.await;
}
ServerRequest::ToolRequestUserInput { request_id, params } => {
self.route_thread_update(ThreadUpdate::ToolRequestUserInput {
request_id,
_request_id: request_id,
params,
})
.await;
}
ServerRequest::DynamicToolCall { request_id, params } => {
self.route_thread_update(ThreadUpdate::DynamicToolCall {
request_id,
_request_id: request_id,
params,
})
.await;
-19
View File
@@ -15,7 +15,6 @@ use codex_file_search::FileMatch;
use codex_protocol::ThreadId;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::protocol::Event;
use codex_protocol::protocol::RateLimitSnapshot;
use codex_utils_approval_presets::ApprovalPreset;
use crate::bottom_pane::ApprovalRequest;
@@ -81,12 +80,6 @@ pub(crate) enum AppEvent {
op: codex_protocol::protocol::Op,
},
/// Forward an event from a non-primary thread into the app-level thread router.
ThreadEvent {
thread_id: ThreadId,
event: Event,
},
/// Start a new session.
NewSession,
@@ -108,9 +101,6 @@ pub(crate) enum AppEvent {
/// background tasks, rollout flush, or child process cleanup).
Exit(ExitMode),
/// Request to exit the application due to a fatal error.
FatalExitRequest(String),
/// Forward an `Op` to the Agent. Using an `AppEvent` for this avoids
/// bubbling channels through layers of widgets.
CodexOp(codex_protocol::protocol::Op),
@@ -128,18 +118,9 @@ pub(crate) enum AppEvent {
matches: Vec<FileMatch>,
},
/// Result of refreshing rate limits
RateLimitSnapshotFetched(RateLimitSnapshot),
/// Request a fresh account rate-limit read via app-server.
RefreshRateLimits,
/// Result of prefetching connectors.
ConnectorsLoaded {
result: Result<ConnectorsSnapshot, String>,
is_final: bool,
},
/// Result of computing a `/diff` command.
DiffResult(String),
+1
View File
@@ -4838,6 +4838,7 @@ impl ChatWidget {
self.dispatch_event_msg(Some(id), msg, None);
}
#[cfg(test)]
pub(crate) fn handle_codex_event_replay(&mut self, event: Event) {
let Event { msg, .. } = event;
if matches!(msg, EventMsg::ShutdownComplete) {
+5 -5
View File
@@ -75,11 +75,11 @@ pub(crate) enum ThreadUpdate {
ThreadRealtimeError(ThreadRealtimeErrorNotification),
ThreadRealtimeClosed(ThreadRealtimeClosedNotification),
CommandExecutionRequestApproval {
request_id: RequestId,
_request_id: RequestId,
params: CommandExecutionRequestApprovalParams,
},
FileChangeRequestApproval {
request_id: RequestId,
_request_id: RequestId,
params: FileChangeRequestApprovalParams,
},
McpServerElicitationRequest {
@@ -87,15 +87,15 @@ pub(crate) enum ThreadUpdate {
params: McpServerElicitationRequestParams,
},
PermissionsRequestApproval {
request_id: RequestId,
_request_id: RequestId,
params: PermissionsRequestApprovalParams,
},
ToolRequestUserInput {
request_id: RequestId,
_request_id: RequestId,
params: ToolRequestUserInputParams,
},
DynamicToolCall {
request_id: RequestId,
_request_id: RequestId,
params: DynamicToolCallParams,
},
ThreadRolledBack {