Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions codex-rs/app-server/tests/suite/v2/turn_interrupt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,17 @@ async fn turn_interrupt_aborts_running_turn() -> Result<()> {

#[tokio::test]
async fn turn_interrupt_resolves_pending_command_approval_request() -> Result<()> {
#[cfg(target_os = "windows")]
let shell_command = vec![
"powershell".to_string(),
"-Command".to_string(),
"Start-Sleep -Seconds 10".to_string(),
];
#[cfg(not(target_os = "windows"))]
let shell_command = vec![
"python3".to_string(),
"-c".to_string(),
"print(42)".to_string(),
"import time; time.sleep(10)".to_string(),
];

let tmp = TempDir::new()?;
Expand All @@ -143,7 +150,7 @@ async fn turn_interrupt_resolves_pending_command_approval_request() -> Result<()
shell_command.clone(),
Some(&working_directory),
Some(10_000),
"call_python_approval",
"call_sleep_approval",
)?])
.await;
create_config_toml(&codex_home, &server.uri(), "untrusted", "read-only")?;
Expand Down Expand Up @@ -172,6 +179,7 @@ async fn turn_interrupt_resolves_pending_command_approval_request() -> Result<()
text_elements: Vec::new(),
}],
cwd: Some(working_directory),
approval_policy: Some(codex_app_server_protocol::AskForApproval::UnlessTrusted),
..Default::default()
})
.await?;
Expand All @@ -190,7 +198,7 @@ async fn turn_interrupt_resolves_pending_command_approval_request() -> Result<()
let ServerRequest::CommandExecutionRequestApproval { request_id, params } = request else {
panic!("expected CommandExecutionRequestApproval request");
};
assert_eq!(params.item_id, "call_python_approval");
assert_eq!(params.item_id, "call_sleep_approval");
assert_eq!(params.thread_id, thread.id);
assert_eq!(params.turn_id, turn.id);

Expand Down Expand Up @@ -251,6 +259,7 @@ fn create_config_toml(
r#"
model = "mock-model"
approval_policy = "{approval_policy}"
approvals_reviewer = "user"
sandbox_mode = "{sandbox_mode}"

model_provider = "mock_provider"
Expand Down
76 changes: 73 additions & 3 deletions codex-rs/tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ use toml::Value as TomlValue;
use uuid::Uuid;
mod agent_navigation;
mod app_server_adapter;
mod app_server_requests;
pub(crate) mod app_server_requests;
mod loaded_threads;
mod pending_interactive_replay;

Expand Down Expand Up @@ -5895,8 +5895,13 @@ impl App {
.handle_server_notification(notification, /*replay_kind*/ None);
}
ThreadBufferedEvent::Request(request) => {
self.chat_widget
.handle_server_request(request, /*replay_kind*/ None);
if self
.pending_app_server_requests
.contains_server_request(&request)
{
self.chat_widget
.handle_server_request(request, /*replay_kind*/ None);
}
}
ThreadBufferedEvent::HistoryEntryResponse(event) => {
self.chat_widget.handle_history_entry_response(event);
Expand Down Expand Up @@ -6838,6 +6843,11 @@ mod tests {
let approval_request =
exec_approval_request(thread_id, "turn-1", "call-1", /*approval_id*/ None);

assert_eq!(
app.pending_app_server_requests
.note_server_request(&approval_request),
None
);
app.enqueue_primary_thread_request(approval_request).await?;
app.enqueue_primary_thread_session(
test_thread_session(thread_id, test_path_buf("/tmp/project")),
Expand Down Expand Up @@ -6880,6 +6890,66 @@ mod tests {
panic!("expected approval action to submit a thread-scoped op");
}

#[tokio::test]
async fn resolved_buffered_approval_does_not_become_actionable_after_drain() -> Result<()> {
let (mut app, mut app_event_rx, _op_rx) = make_test_app_with_channels().await;
let thread_id = ThreadId::new();
let approval_request =
exec_approval_request(thread_id, "turn-1", "call-1", /*approval_id*/ None);

app.enqueue_primary_thread_session(
test_thread_session(thread_id, test_path_buf("/tmp/project")),
Vec::new(),
)
.await?;
while app_event_rx.try_recv().is_ok() {}

assert_eq!(
app.pending_app_server_requests
.note_server_request(&approval_request),
None
);
app.enqueue_thread_request(thread_id, approval_request)
.await?;

let resolved = app
.pending_app_server_requests
.resolve_notification(&AppServerRequestId::Integer(1))
.expect("matching app-server request should resolve");
app.chat_widget.dismiss_app_server_request(&resolved);
while app_event_rx.try_recv().is_ok() {}

let rx = app
.active_thread_rx
.as_mut()
.expect("primary thread receiver should be active");
let event = time::timeout(Duration::from_millis(50), rx.recv())
.await
.expect("timed out waiting for buffered approval event")
.expect("channel closed unexpectedly");

assert!(matches!(
&event,
ThreadBufferedEvent::Request(ServerRequest::CommandExecutionRequestApproval {
params,
..
}) if params.turn_id == "turn-1"
));

app.handle_thread_event_now(event);
app.chat_widget
.handle_key_event(KeyEvent::new(KeyCode::Char('y'), KeyModifiers::NONE));

while let Ok(app_event) = app_event_rx.try_recv() {
assert!(
!matches!(app_event, AppEvent::SubmitThreadOp { .. }),
"resolved buffered approval should not become actionable"
);
}

Ok(())
}

#[tokio::test]
async fn enqueue_primary_thread_session_replays_turns_before_initial_prompt_submit()
-> Result<()> {
Expand Down
8 changes: 6 additions & 2 deletions codex-rs/tui/src/app/app_server_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,12 @@ impl App {
) {
match &notification {
ServerNotification::ServerRequestResolved(notification) => {
self.pending_app_server_requests
.resolve_notification(&notification.request_id);
if let Some(request) = self
Comment thread
ebrevdo marked this conversation as resolved.
.pending_app_server_requests
.resolve_notification(&notification.request_id)
{
self.chat_widget.dismiss_app_server_request(&request);
}
}
ServerNotification::McpServerStatusUpdated(_) => {
self.refresh_mcp_startup_expected_servers_from_config();
Expand Down
Loading
Loading