Box app-server event payloads (#35854)

## What changed

- Store `ServerNotification` and `ServerRequest` payloads behind `Box` in
  `AppServerEvent`, `InProcessServerEvent`, and TUI `ThreadBufferedEvent` values.
- Update app-server delivery, TUI routing and replay, exec, onboarding, and tests
  to borrow or consume the boxed payloads as appropriate.

GitOrigin-RevId: 7d0dab9aba47487114128ce55c914af5bb46c5b5
This commit is contained in:
joeytrasatti-openai authored and copyberry committed 2026-07-29 00:30:11 +00:00
1 parent 8707a35113
commit 07490c7523
16 files changed
+267 -190

No files matched your search

+70 -51
View File
@@ -97,8 +97,8 @@ pub type RequestResult = std::result::Result<JsonRpcResult, JSONRPCErrorError>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum AppServerEvent { pub enum AppServerEvent {
Lagged { skipped: usize }, Lagged { skipped: usize },
ServerNotification(ServerNotification), ServerNotification(Box<ServerNotification>),
ServerRequest(ServerRequest), ServerRequest(Box<ServerRequest>),
Disconnected { message: String }, Disconnected { message: String },
} }
@@ -205,7 +205,7 @@ where
*skipped_events = skipped_events.saturating_add(1); *skipped_events = skipped_events.saturating_add(1);
warn!("dropping in-process app-server event because consumer queue is full"); warn!("dropping in-process app-server event because consumer queue is full");
if let InProcessServerEvent::ServerRequest(request) = event { if let InProcessServerEvent::ServerRequest(request) = event {
reject_server_request(request); reject_server_request(*request);
} }
return ForwardEventResult::Continue; return ForwardEventResult::Continue;
} }
@@ -231,7 +231,7 @@ where
*skipped_events = skipped_events.saturating_add(1); *skipped_events = skipped_events.saturating_add(1);
warn!("dropping in-process app-server event because consumer queue is full"); warn!("dropping in-process app-server event because consumer queue is full");
if let InProcessServerEvent::ServerRequest(request) = event { if let InProcessServerEvent::ServerRequest(request) = event {
reject_server_request(request); reject_server_request(*request);
} }
ForwardEventResult::Continue ForwardEventResult::Continue
} }
@@ -522,9 +522,9 @@ impl InProcessAppServerClient {
let Some(event) = event else { let Some(event) = event else {
break; break;
}; };
if let InProcessServerEvent::ServerRequest( if let InProcessServerEvent::ServerRequest(request) = &event
ServerRequest::ChatgptAuthTokensRefresh { request_id, .. } && let ServerRequest::ChatgptAuthTokensRefresh { request_id, .. } =
) = &event request.as_ref()
{ {
let send_result = request_sender.fail_server_request( let send_result = request_sender.fail_server_request(
request_id.clone(), request_id.clone(),
@@ -1332,9 +1332,9 @@ mod tests {
async fn forward_in_process_event_preserves_transcript_notifications_under_backpressure() { async fn forward_in_process_event_preserves_transcript_notifications_under_backpressure() {
let (event_tx, mut event_rx) = mpsc::channel(1); let (event_tx, mut event_rx) = mpsc::channel(1);
event_tx event_tx
.send(InProcessServerEvent::ServerNotification( .send(InProcessServerEvent::ServerNotification(Box::new(
command_execution_output_delta_notification("stdout-1"), command_execution_output_delta_notification("stdout-1"),
)) )))
.await .await
.expect("initial event should enqueue"); .expect("initial event should enqueue");
@@ -1342,8 +1342,8 @@ mod tests {
let result = forward_in_process_event( let result = forward_in_process_event(
&event_tx, &event_tx,
&mut skipped_events, &mut skipped_events,
InProcessServerEvent::ServerNotification(command_execution_output_delta_notification( InProcessServerEvent::ServerNotification(Box::new(
"stdout-2", command_execution_output_delta_notification("stdout-2"),
)), )),
|_| {}, |_| {},
) )
@@ -1372,7 +1372,7 @@ mod tests {
let result = forward_in_process_event( let result = forward_in_process_event(
&event_tx, &event_tx,
&mut skipped_events, &mut skipped_events,
InProcessServerEvent::ServerNotification(notification), InProcessServerEvent::ServerNotification(Box::new(notification)),
|_| {}, |_| {},
) )
.await; .await;
@@ -1385,9 +1385,12 @@ mod tests {
.expect("receiver task should join successfully"); .expect("receiver task should join successfully");
assert!(matches!( assert!(matches!(
&events[0], &events[0],
InProcessServerEvent::ServerNotification( InProcessServerEvent::ServerNotification(notification)
if matches!(
notification.as_ref(),
ServerNotification::CommandExecutionOutputDelta(notification) ServerNotification::CommandExecutionOutputDelta(notification)
) if notification.delta == "stdout-1" if notification.delta == "stdout-1"
)
)); ));
assert!(matches!( assert!(matches!(
&events[1], &events[1],
@@ -1395,24 +1398,35 @@ mod tests {
)); ));
assert!(matches!( assert!(matches!(
&events[2], &events[2],
InProcessServerEvent::ServerNotification(ServerNotification::AgentMessageDelta( InProcessServerEvent::ServerNotification(notification)
notification if matches!(
)) if notification.delta == "hello" notification.as_ref(),
ServerNotification::AgentMessageDelta(notification)
if notification.delta == "hello"
)
)); ));
assert!(matches!( assert!(matches!(
&events[3], &events[3],
InProcessServerEvent::ServerNotification(ServerNotification::ItemCompleted( InProcessServerEvent::ServerNotification(notification)
notification if matches!(
)) if matches!( notification.as_ref(),
ServerNotification::ItemCompleted(notification)
if matches!(
&notification.item, &notification.item,
codex_app_server_protocol::ThreadItem::AgentMessage { text, .. } if text == "hello" codex_app_server_protocol::ThreadItem::AgentMessage { text, .. }
if text == "hello"
)
) )
)); ));
assert!(matches!( assert!(matches!(
&events[4], &events[4],
InProcessServerEvent::ServerNotification(ServerNotification::TurnCompleted( InProcessServerEvent::ServerNotification(notification)
notification if matches!(
)) if notification.turn.status == codex_app_server_protocol::TurnStatus::Completed notification.as_ref(),
ServerNotification::TurnCompleted(notification)
if notification.turn.status
== codex_app_server_protocol::TurnStatus::Completed
)
)); ));
} }
@@ -1753,7 +1767,8 @@ mod tests {
let event = client.next_event().await.expect("event should arrive"); let event = client.next_event().await.expect("event should arrive");
assert!(matches!( assert!(matches!(
event, event,
AppServerEvent::ServerNotification(ServerNotification::AccountUpdated(_)) AppServerEvent::ServerNotification(notification)
if matches!(notification.as_ref(), ServerNotification::AccountUpdated(_))
)); ));
client.shutdown().await.expect("shutdown should complete"); client.shutdown().await.expect("shutdown should complete");
@@ -1799,9 +1814,12 @@ mod tests {
.expect("event stream should stay open"); .expect("event stream should stay open");
assert!(matches!( assert!(matches!(
first_event, first_event,
AppServerEvent::ServerNotification(ServerNotification::CommandExecutionOutputDelta( AppServerEvent::ServerNotification(notification)
notification if matches!(
)) if notification.delta == "stdout-1" notification.as_ref(),
ServerNotification::CommandExecutionOutputDelta(notification)
if notification.delta == "stdout-1"
)
)); ));
let mut remaining_events = Vec::new(); let mut remaining_events = Vec::new();
@@ -1818,31 +1836,32 @@ mod tests {
for event in &remaining_events { for event in &remaining_events {
match event { match event {
AppServerEvent::Lagged { skipped: 1 } => {} AppServerEvent::Lagged { skipped: 1 } => {}
AppServerEvent::ServerNotification( AppServerEvent::ServerNotification(notification) => match notification.as_ref() {
ServerNotification::CommandExecutionOutputDelta(notification), ServerNotification::CommandExecutionOutputDelta(notification)
) if notification.delta == "stdout-2" => {} if notification.delta == "stdout-2" => {}
AppServerEvent::ServerNotification(ServerNotification::AgentMessageDelta( ServerNotification::AgentMessageDelta(notification)
notification, if notification.delta == "hello" =>
)) if notification.delta == "hello" => { {
transcript_event_names.push("agent_message_delta"); transcript_event_names.push("agent_message_delta");
} }
AppServerEvent::ServerNotification(ServerNotification::ItemCompleted( ServerNotification::ItemCompleted(notification)
notification, if matches!(
)) if matches!(
&notification.item, &notification.item,
codex_app_server_protocol::ThreadItem::AgentMessage { text, .. } if text == "hello" codex_app_server_protocol::ThreadItem::AgentMessage { text, .. }
if text == "hello"
) => ) =>
{ {
transcript_event_names.push("item_completed"); transcript_event_names.push("item_completed");
} }
AppServerEvent::ServerNotification(ServerNotification::TurnCompleted( ServerNotification::TurnCompleted(notification)
notification, if notification.turn.status
)) if notification.turn.status
== codex_app_server_protocol::TurnStatus::Completed => == codex_app_server_protocol::TurnStatus::Completed =>
{ {
transcript_event_names.push("turn_completed"); transcript_event_names.push("turn_completed");
} }
_ => panic!("unexpected remaining event: {event:?}"), _ => panic!("unexpected remaining event: {event:?}"),
},
_ => panic!("unexpected remaining event: {event:?}"),
} }
} }
assert_eq!( assert_eq!(
@@ -2103,7 +2122,7 @@ mod tests {
#[test] #[test]
fn event_requires_delivery_marks_transcript_and_terminal_events() { fn event_requires_delivery_marks_transcript_and_terminal_events() {
assert!(event_requires_delivery( assert!(event_requires_delivery(
&InProcessServerEvent::ServerNotification( &InProcessServerEvent::ServerNotification(Box::new(
codex_app_server_protocol::ServerNotification::TurnCompleted( codex_app_server_protocol::ServerNotification::TurnCompleted(
codex_app_server_protocol::TurnCompletedNotification { codex_app_server_protocol::TurnCompletedNotification {
thread_id: "thread".to_string(), thread_id: "thread".to_string(),
@@ -2119,10 +2138,10 @@ mod tests {
}, },
} }
) )
) ))
)); ));
assert!(event_requires_delivery( assert!(event_requires_delivery(
&InProcessServerEvent::ServerNotification( &InProcessServerEvent::ServerNotification(Box::new(
codex_app_server_protocol::ServerNotification::AgentMessageDelta( codex_app_server_protocol::ServerNotification::AgentMessageDelta(
codex_app_server_protocol::AgentMessageDeltaNotification { codex_app_server_protocol::AgentMessageDeltaNotification {
thread_id: "thread".to_string(), thread_id: "thread".to_string(),
@@ -2131,10 +2150,10 @@ mod tests {
delta: "hello".to_string(), delta: "hello".to_string(),
} }
) )
) ))
)); ));
assert!(event_requires_delivery( assert!(event_requires_delivery(
&InProcessServerEvent::ServerNotification( &InProcessServerEvent::ServerNotification(Box::new(
codex_app_server_protocol::ServerNotification::ItemCompleted( codex_app_server_protocol::ServerNotification::ItemCompleted(
codex_app_server_protocol::ItemCompletedNotification { codex_app_server_protocol::ItemCompletedNotification {
thread_id: "thread".to_string(), thread_id: "thread".to_string(),
@@ -2148,23 +2167,23 @@ mod tests {
}, },
} }
) )
) ))
)); ));
assert!(event_requires_delivery( assert!(event_requires_delivery(
&InProcessServerEvent::ServerNotification( &InProcessServerEvent::ServerNotification(Box::new(
codex_app_server_protocol::ServerNotification::ExternalAgentConfigImportCompleted( codex_app_server_protocol::ServerNotification::ExternalAgentConfigImportCompleted(
codex_app_server_protocol::ExternalAgentConfigImportCompletedNotification { codex_app_server_protocol::ExternalAgentConfigImportCompletedNotification {
import_id: "import".to_string(), import_id: "import".to_string(),
item_type_results: Vec::new(), item_type_results: Vec::new(),
}, },
) )
) ))
)); ));
assert!(!event_requires_delivery(&InProcessServerEvent::Lagged { assert!(!event_requires_delivery(&InProcessServerEvent::Lagged {
skipped: 1 skipped: 1
})); }));
assert!(!event_requires_delivery( assert!(!event_requires_delivery(
&InProcessServerEvent::ServerNotification( &InProcessServerEvent::ServerNotification(Box::new(
codex_app_server_protocol::ServerNotification::CommandExecutionOutputDelta( codex_app_server_protocol::ServerNotification::CommandExecutionOutputDelta(
codex_app_server_protocol::CommandExecutionOutputDeltaNotification { codex_app_server_protocol::CommandExecutionOutputDeltaNotification {
thread_id: "thread".to_string(), thread_id: "thread".to_string(),
@@ -2173,7 +2192,7 @@ mod tests {
delta: "stdout".to_string(), delta: "stdout".to_string(),
} }
) )
) ))
)); ));
} }
+4 -3
View File
@@ -348,7 +348,7 @@ impl RemoteAppServerClient {
Ok(request) => { Ok(request) => {
if let Err(err) = deliver_event( if let Err(err) = deliver_event(
&event_tx, &event_tx,
AppServerEvent::ServerRequest(request), AppServerEvent::ServerRequest(Box::new(request)),
) )
{ {
warn!(%err, "failed to deliver remote app-server server request"); warn!(%err, "failed to deliver remote app-server server request");
@@ -862,7 +862,8 @@ where
let method = request.method.clone(); let method = request.method.clone();
match ServerRequest::try_from(request) { match ServerRequest::try_from(request) {
Ok(request) => { Ok(request) => {
pending_events.push(AppServerEvent::ServerRequest(request)); pending_events
.push(AppServerEvent::ServerRequest(Box::new(request)));
} }
Err(err) => { Err(err) => {
warn!(%err, method, "rejecting unknown remote app-server request during initialize"); warn!(%err, method, "rejecting unknown remote app-server request during initialize");
@@ -940,7 +941,7 @@ where
fn app_server_event_from_notification(notification: JSONRPCNotification) -> Option<AppServerEvent> { fn app_server_event_from_notification(notification: JSONRPCNotification) -> Option<AppServerEvent> {
match ServerNotification::try_from(notification) { match ServerNotification::try_from(notification) {
Ok(notification) => Some(AppServerEvent::ServerNotification(notification)), Ok(notification) => Some(AppServerEvent::ServerNotification(Box::new(notification))),
Err(_) => None, Err(_) => None,
} }
} }
+9 -5
View File
@@ -160,9 +160,9 @@ pub struct InProcessStartArgs {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum InProcessServerEvent { pub enum InProcessServerEvent {
/// Server request that requires client response/rejection. /// Server request that requires client response/rejection.
ServerRequest(ServerRequest), ServerRequest(Box<ServerRequest>),
/// App-server notification directed to the embedded client. /// App-server notification directed to the embedded client.
ServerNotification(ServerNotification), ServerNotification(Box<ServerNotification>),
/// Indicates one or more events were dropped due to backpressure. /// Indicates one or more events were dropped due to backpressure.
Lagged { skipped: usize }, Lagged { skipped: usize },
} }
@@ -663,7 +663,7 @@ async fn start_uninitialized(args: InProcessStartArgs) -> IoResult<InProcessClie
// Send directly to avoid cloning; on failure the // Send directly to avoid cloning; on failure the
// original value is returned inside the error. // original value is returned inside the error.
if let Err(send_error) = event_tx if let Err(send_error) = event_tx
.try_send(InProcessServerEvent::ServerRequest(request)) .try_send(InProcessServerEvent::ServerRequest(Box::new(request)))
{ {
let (error, inner) = match send_error { let (error, inner) = match send_error {
mpsc::error::TrySendError::Full(inner) => ( mpsc::error::TrySendError::Full(inner) => (
@@ -695,14 +695,18 @@ async fn start_uninitialized(args: InProcessStartArgs) -> IoResult<InProcessClie
let notification = envelope.notification; let notification = envelope.notification;
if server_notification_requires_delivery(&notification) { if server_notification_requires_delivery(&notification) {
if event_tx if event_tx
.send(InProcessServerEvent::ServerNotification(notification)) .send(InProcessServerEvent::ServerNotification(Box::new(
notification,
)))
.await .await
.is_err() .is_err()
{ {
break; break;
} }
} else if let Err(send_error) = } else if let Err(send_error) =
event_tx.try_send(InProcessServerEvent::ServerNotification(notification)) event_tx.try_send(InProcessServerEvent::ServerNotification(
Box::new(notification),
))
{ {
match send_error { match send_error {
mpsc::error::TrySendError::Full(_) => { mpsc::error::TrySendError::Full(_) => {
@@ -191,9 +191,8 @@ async fn thread_delete_with_non_local_thread_store_does_not_create_local_persist
let Some(event) = client.next_event().await else { let Some(event) = client.next_event().await else {
anyhow::bail!("in-process app-server stopped before turn/completed"); anyhow::bail!("in-process app-server stopped before turn/completed");
}; };
if let InProcessServerEvent::ServerNotification(ServerNotification::TurnCompleted( if let InProcessServerEvent::ServerNotification(notification) = event
completed, && let ServerNotification::TurnCompleted(completed) = notification.as_ref()
)) = event
&& completed.thread_id == thread.id && completed.thread_id == thread.id
{ {
return Ok::<(), anyhow::Error>(()); return Ok::<(), anyhow::Error>(());
@@ -332,9 +331,8 @@ async fn cold_thread_resume_reuses_non_local_history_probe() -> Result<()> {
let Some(event) = client.next_event().await else { let Some(event) = client.next_event().await else {
anyhow::bail!("in-process app-server stopped before turn/completed"); anyhow::bail!("in-process app-server stopped before turn/completed");
}; };
if let InProcessServerEvent::ServerNotification(ServerNotification::TurnCompleted( if let InProcessServerEvent::ServerNotification(notification) = event
completed, && let ServerNotification::TurnCompleted(completed) = notification.as_ref()
)) = event
&& completed.thread_id == thread.id && completed.thread_id == thread.id
{ {
return Ok::<(), anyhow::Error>(()); return Ok::<(), anyhow::Error>(());
+3 -2
View File
@@ -997,9 +997,10 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
match server_event { match server_event {
InProcessServerEvent::ServerRequest(request) => { InProcessServerEvent::ServerRequest(request) => {
handle_server_request(&client, request, &mut error_seen).await; handle_server_request(&client, *request, &mut error_seen).await;
} }
InProcessServerEvent::ServerNotification(mut notification) => { InProcessServerEvent::ServerNotification(notification) => {
let mut notification = *notification;
if let ServerNotification::Error(payload) = &notification { if let ServerNotification::Error(payload) = &notification {
if payload.thread_id == primary_thread_id_for_requests if payload.thread_id == primary_thread_id_for_requests
&& payload.turn_id == task_id && payload.turn_id == task_id
+6 -8
View File
@@ -85,14 +85,12 @@ impl AgentStatusThreadPreview {
let mut activity = Vec::new(); let mut activity = Vec::new();
for event in events { for event in events {
let item = match event { let item = match event {
ThreadBufferedEvent::Notification(ServerNotification::ItemCompleted(event)) => { ThreadBufferedEvent::Notification(notification) => match notification.as_ref() {
&event.item ServerNotification::ItemCompleted(event) => &event.item,
} ServerNotification::ItemStarted(event) => &event.item,
ThreadBufferedEvent::Notification(ServerNotification::ItemStarted(event)) => { _ => continue,
&event.item },
} ThreadBufferedEvent::Request(_)
ThreadBufferedEvent::Notification(_)
| ThreadBufferedEvent::Request(_)
| ThreadBufferedEvent::HistoryEntryResponse(_) | ThreadBufferedEvent::HistoryEntryResponse(_)
| ThreadBufferedEvent::FeedbackSubmission(_) => continue, | ThreadBufferedEvent::FeedbackSubmission(_) => continue,
}; };
+2 -2
View File
@@ -44,11 +44,11 @@ impl App {
self.chat_widget.finish_mcp_startup_after_lag(); self.chat_widget.finish_mcp_startup_after_lag();
} }
AppServerEvent::ServerNotification(notification) => { AppServerEvent::ServerNotification(notification) => {
self.handle_server_notification_event(app_server_client, notification) self.handle_server_notification_event(app_server_client, *notification)
.await; .await;
} }
AppServerEvent::ServerRequest(request) => { AppServerEvent::ServerRequest(request) => {
self.handle_server_request_event(app_server_client, request) self.handle_server_request_event(app_server_client, *request)
.await; .await;
} }
AppServerEvent::Disconnected { message } => { AppServerEvent::Disconnected { message } => {
+1 -1
View File
@@ -626,7 +626,7 @@ impl App {
{ {
guard guard
.pending_interactive_replay .pending_interactive_replay
.note_evicted_server_request(request); .note_evicted_server_request(request.as_ref());
} }
guard.active guard.active
}; };
@@ -702,8 +702,12 @@ mod tests {
assert_eq!(snapshot.events.len(), 1); assert_eq!(snapshot.events.len(), 1);
assert!(matches!( assert!(matches!(
snapshot.events.first(), snapshot.events.first(),
Some(ThreadBufferedEvent::Request(ServerRequest::ToolRequestUserInput { params, .. })) Some(ThreadBufferedEvent::Request(request))
if matches!(
request.as_ref(),
ServerRequest::ToolRequestUserInput { params, .. }
if params.item_id == "call-1" if params.item_id == "call-1"
)
)); ));
} }
@@ -738,7 +742,11 @@ mod tests {
snapshot.events.iter().all(|event| { snapshot.events.iter().all(|event| {
!matches!( !matches!(
event, event,
ThreadBufferedEvent::Request(ServerRequest::ToolRequestUserInput { .. }) ThreadBufferedEvent::Request(request)
if matches!(
request.as_ref(),
ServerRequest::ToolRequestUserInput { .. }
)
) )
}), }),
"server-resolved request_user_input prompt should not replay on thread switch" "server-resolved request_user_input prompt should not replay on thread switch"
@@ -783,7 +791,9 @@ mod tests {
snapshot.events.iter().all(|event| { snapshot.events.iter().all(|event| {
!matches!( !matches!(
event, event,
ThreadBufferedEvent::Request( ThreadBufferedEvent::Request(request)
if matches!(
request.as_ref(),
ServerRequest::CommandExecutionRequestApproval { .. } ServerRequest::CommandExecutionRequestApproval { .. }
) )
) )
@@ -810,8 +820,12 @@ mod tests {
assert_eq!(snapshot.events.len(), 1); assert_eq!(snapshot.events.len(), 1);
assert!(matches!( assert!(matches!(
snapshot.events.first(), snapshot.events.first(),
Some(ThreadBufferedEvent::Request(ServerRequest::ToolRequestUserInput { params, .. })) Some(ThreadBufferedEvent::Request(request))
if matches!(
request.as_ref(),
ServerRequest::ToolRequestUserInput { params, .. }
if params.item_id == "call-2" if params.item_id == "call-2"
)
)); ));
} }
@@ -832,8 +846,12 @@ mod tests {
assert_eq!(snapshot.events.len(), 1); assert_eq!(snapshot.events.len(), 1);
assert!(matches!( assert!(matches!(
snapshot.events.first(), snapshot.events.first(),
Some(ThreadBufferedEvent::Request(ServerRequest::ToolRequestUserInput { params, .. })) Some(ThreadBufferedEvent::Request(request))
if matches!(
request.as_ref(),
ServerRequest::ToolRequestUserInput { params, .. }
if params.item_id == "call-2" if params.item_id == "call-2"
)
)); ));
} }
@@ -869,8 +887,12 @@ mod tests {
assert!(snapshot.events.iter().all(|event| { assert!(snapshot.events.iter().all(|event| {
!matches!( !matches!(
event, event,
ThreadBufferedEvent::Request(ServerRequest::CommandExecutionRequestApproval { .. }) ThreadBufferedEvent::Request(request)
| ThreadBufferedEvent::Request(ServerRequest::FileChangeRequestApproval { .. }) if matches!(
request.as_ref(),
ServerRequest::CommandExecutionRequestApproval { .. }
| ServerRequest::FileChangeRequestApproval { .. }
)
) )
})); }));
} }
@@ -935,7 +957,11 @@ mod tests {
assert!(store.snapshot().events.iter().all(|event| { assert!(store.snapshot().events.iter().all(|event| {
!matches!( !matches!(
event, event,
ThreadBufferedEvent::Request(ServerRequest::CommandExecutionRequestApproval { .. }) ThreadBufferedEvent::Request(request)
if matches!(
request.as_ref(),
ServerRequest::CommandExecutionRequestApproval { .. }
)
) )
})); }));
} }
+6 -2
View File
@@ -10,7 +10,9 @@ pub(super) fn snapshot_has_pending_interactive_request(snapshot: &ThreadEventSna
snapshot.events.iter().any(|event| { snapshot.events.iter().any(|event| {
matches!( matches!(
event, event,
ThreadBufferedEvent::Request( ThreadBufferedEvent::Request(request)
if matches!(
request.as_ref(),
ServerRequest::CommandExecutionRequestApproval { .. } ServerRequest::CommandExecutionRequestApproval { .. }
| ServerRequest::FileChangeRequestApproval { .. } | ServerRequest::FileChangeRequestApproval { .. }
| ServerRequest::McpServerElicitationRequest { .. } | ServerRequest::McpServerElicitationRequest { .. }
@@ -24,7 +26,9 @@ pub(super) fn snapshot_has_pending_interactive_request(snapshot: &ThreadEventSna
pub(super) fn event_is_notice(event: &ThreadBufferedEvent) -> bool { pub(super) fn event_is_notice(event: &ThreadBufferedEvent) -> bool {
matches!( matches!(
event, event,
ThreadBufferedEvent::Notification( ThreadBufferedEvent::Notification(notification)
if matches!(
notification.as_ref(),
ServerNotification::Warning(_) ServerNotification::Warning(_)
| ServerNotification::GuardianWarning(_) | ServerNotification::GuardianWarning(_)
| ServerNotification::ConfigWarning(_) | ServerNotification::ConfigWarning(_)
+51 -47
View File
@@ -198,9 +198,8 @@ async fn next_thread_settings_updated(
.await .await
.expect("app-server should emit an event") .expect("app-server should emit an event")
.expect("app-server event stream should remain open"); .expect("app-server event stream should remain open");
if let codex_app_server_client::AppServerEvent::ServerNotification( if let codex_app_server_client::AppServerEvent::ServerNotification(notification) = event
ServerNotification::ThreadSettingsUpdated(notification), && let ServerNotification::ThreadSettingsUpdated(notification) = *notification
) = event
&& notification.thread_id == thread_id.to_string() && notification.thread_id == thread_id.to_string()
{ {
return notification; return notification;
@@ -289,10 +288,12 @@ async fn enqueue_primary_thread_session_replays_buffered_approval_after_attach()
assert!(matches!( assert!(matches!(
&event, &event,
ThreadBufferedEvent::Request(ServerRequest::CommandExecutionRequestApproval { ThreadBufferedEvent::Request(request)
params, if matches!(
.. request.as_ref(),
}) if params.turn_id == "turn-1" ServerRequest::CommandExecutionRequestApproval { params, .. }
if params.turn_id == "turn-1"
)
)); ));
app.handle_thread_event_now(event); app.handle_thread_event_now(event);
@@ -353,10 +354,12 @@ async fn resolved_buffered_approval_does_not_become_actionable_after_drain() ->
assert!(matches!( assert!(matches!(
&event, &event,
ThreadBufferedEvent::Request(ServerRequest::CommandExecutionRequestApproval { ThreadBufferedEvent::Request(request)
params, if matches!(
.. request.as_ref(),
}) if params.turn_id == "turn-1" ServerRequest::CommandExecutionRequestApproval { params, .. }
if params.turn_id == "turn-1"
)
)); ));
app.handle_thread_event_now(event); app.handle_thread_event_now(event);
@@ -801,9 +804,9 @@ async fn replayed_turn_complete_submits_restored_queued_follow_up() {
ThreadEventSnapshot { ThreadEventSnapshot {
session: None, session: None,
turns: Vec::new(), turns: Vec::new(),
events: vec![ThreadBufferedEvent::Notification( events: vec![ThreadBufferedEvent::Notification(Box::new(
turn_completed_notification(thread_id, "turn-1", TurnStatus::Completed), turn_completed_notification(thread_id, "turn-1", TurnStatus::Completed),
)], ))],
input_state: Some(input_state), input_state: Some(input_state),
}, },
/*resume_restored_queue*/ true, /*resume_restored_queue*/ true,
@@ -854,9 +857,9 @@ async fn replay_only_thread_keeps_restored_queue_visible() {
ThreadEventSnapshot { ThreadEventSnapshot {
session: None, session: None,
turns: Vec::new(), turns: Vec::new(),
events: vec![ThreadBufferedEvent::Notification( events: vec![ThreadBufferedEvent::Notification(Box::new(
turn_completed_notification(thread_id, "turn-1", TurnStatus::Completed), turn_completed_notification(thread_id, "turn-1", TurnStatus::Completed),
)], ))],
input_state: Some(input_state), input_state: Some(input_state),
}, },
/*resume_restored_queue*/ false, /*resume_restored_queue*/ false,
@@ -1026,12 +1029,14 @@ async fn replay_thread_snapshot_does_not_submit_queue_before_replay_catches_up()
session: None, session: None,
turns: Vec::new(), turns: Vec::new(),
events: vec![ events: vec![
ThreadBufferedEvent::Notification(turn_completed_notification( ThreadBufferedEvent::Notification(Box::new(turn_completed_notification(
thread_id, thread_id,
"turn-0", "turn-0",
TurnStatus::Completed, TurnStatus::Completed,
)), ))),
ThreadBufferedEvent::Notification(turn_started_notification(thread_id, "turn-1")), ThreadBufferedEvent::Notification(Box::new(turn_started_notification(
thread_id, "turn-1",
))),
], ],
input_state: Some(input_state), input_state: Some(input_state),
}, },
@@ -1294,9 +1299,9 @@ async fn replayed_interrupted_turn_restores_queued_input_to_composer() {
ThreadEventSnapshot { ThreadEventSnapshot {
session: None, session: None,
turns: Vec::new(), turns: Vec::new(),
events: vec![ThreadBufferedEvent::Notification( events: vec![ThreadBufferedEvent::Notification(Box::new(
turn_completed_notification(thread_id, "turn-1", TurnStatus::Interrupted), turn_completed_notification(thread_id, "turn-1", TurnStatus::Interrupted),
)], ))],
input_state: Some(input_state), input_state: Some(input_state),
}, },
/*resume_restored_queue*/ true, /*resume_restored_queue*/ true,
@@ -1323,10 +1328,8 @@ async fn token_usage_update_refreshes_status_line_with_runtime_context_window()
assert_eq!(app.chat_widget.status_line_text(), None); assert_eq!(app.chat_widget.status_line_text(), None);
app.handle_thread_event_now(ThreadBufferedEvent::Notification(token_usage_notification( app.handle_thread_event_now(ThreadBufferedEvent::Notification(Box::new(
ThreadId::new(), token_usage_notification(ThreadId::new(), "turn-1", Some(950_000)),
"turn-1",
Some(950_000),
))); )));
assert_eq!( assert_eq!(
@@ -1341,7 +1344,7 @@ async fn collab_receiver_notification_caches_thread_without_app_server_read() {
let receiver_thread_id = let receiver_thread_id =
ThreadId::from_string("00000000-0000-0000-0000-000000000123").expect("valid thread id"); ThreadId::from_string("00000000-0000-0000-0000-000000000123").expect("valid thread id");
app.handle_thread_event_now(ThreadBufferedEvent::Notification( app.handle_thread_event_now(ThreadBufferedEvent::Notification(Box::new(
ServerNotification::ItemStarted(ItemStartedNotification { ServerNotification::ItemStarted(ItemStartedNotification {
thread_id: ThreadId::new().to_string(), thread_id: ThreadId::new().to_string(),
turn_id: "turn-1".to_string(), turn_id: "turn-1".to_string(),
@@ -1358,7 +1361,7 @@ async fn collab_receiver_notification_caches_thread_without_app_server_read() {
agents_states: HashMap::new(), agents_states: HashMap::new(),
}, },
}), }),
)); )));
assert_eq!( assert_eq!(
app.agent_navigation.get(&receiver_thread_id), app.agent_navigation.get(&receiver_thread_id),
@@ -1378,7 +1381,7 @@ async fn collab_receiver_notification_does_not_cache_not_found_thread() {
let receiver_thread_id = let receiver_thread_id =
ThreadId::from_string("00000000-0000-0000-0000-000000000124").expect("valid thread id"); ThreadId::from_string("00000000-0000-0000-0000-000000000124").expect("valid thread id");
app.handle_thread_event_now(ThreadBufferedEvent::Notification( app.handle_thread_event_now(ThreadBufferedEvent::Notification(Box::new(
ServerNotification::ItemCompleted(codex_app_server_protocol::ItemCompletedNotification { ServerNotification::ItemCompleted(codex_app_server_protocol::ItemCompletedNotification {
thread_id: ThreadId::new().to_string(), thread_id: ThreadId::new().to_string(),
turn_id: "turn-1".to_string(), turn_id: "turn-1".to_string(),
@@ -1401,7 +1404,7 @@ async fn collab_receiver_notification_does_not_cache_not_found_thread() {
)]), )]),
}, },
}), }),
)); )));
assert_eq!(app.agent_navigation.get(&receiver_thread_id), None); assert_eq!(app.agent_navigation.get(&receiver_thread_id), None);
} }
@@ -2121,9 +2124,8 @@ async fn handle_start_side_seeds_navigation_before_thread_started() -> Result<()
.await .await
.expect("app-server should emit an event") .expect("app-server should emit an event")
.expect("app-server event stream should remain open"); .expect("app-server event stream should remain open");
if let codex_app_server_client::AppServerEvent::ServerNotification( if let codex_app_server_client::AppServerEvent::ServerNotification(notification) = event
ServerNotification::ThreadStarted(notification), && let ServerNotification::ThreadStarted(notification) = notification.as_ref()
) = event
&& notification.thread.id == side_thread_id.to_string() && notification.thread.id == side_thread_id.to_string()
{ {
saw_thread_started = true; saw_thread_started = true;
@@ -2917,18 +2919,18 @@ async fn replay_snapshot_with_pending_request_suppresses_replay_notices() {
session: Some(test_thread_session(thread_id, test_path_buf("/tmp/main"))), session: Some(test_thread_session(thread_id, test_path_buf("/tmp/main"))),
turns: Vec::new(), turns: Vec::new(),
events: vec![ events: vec![
ThreadBufferedEvent::Notification(ServerNotification::Warning( ThreadBufferedEvent::Notification(Box::new(ServerNotification::Warning(
WarningNotification { WarningNotification {
thread_id: Some(thread_id.to_string()), thread_id: Some(thread_id.to_string()),
message: stale_warning.to_string(), message: stale_warning.to_string(),
}, },
)), ))),
ThreadBufferedEvent::Request(exec_approval_request( ThreadBufferedEvent::Request(Box::new(exec_approval_request(
thread_id, thread_id,
"turn-approval", "turn-approval",
"call-approval", "call-approval",
/*approval_id*/ None, /*approval_id*/ None,
)), ))),
], ],
input_state: None, input_state: None,
}, },
@@ -4082,7 +4084,7 @@ async fn primary_thread_ignores_child_mcp_startup_notifications() {
app.handle_app_server_event( app.handle_app_server_event(
&app_server, &app_server,
codex_app_server_client::AppServerEvent::ServerNotification( codex_app_server_client::AppServerEvent::ServerNotification(Box::new(
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification { ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
thread_id: Some(child_thread_id.to_string()), thread_id: Some(child_thread_id.to_string()),
name: "sentry".to_string(), name: "sentry".to_string(),
@@ -4090,7 +4092,7 @@ async fn primary_thread_ignores_child_mcp_startup_notifications() {
error: Some("sentry is not logged in".to_string()), error: Some("sentry is not logged in".to_string()),
failure_reason: None, failure_reason: None,
}), }),
), )),
) )
.await; .await;
@@ -4106,9 +4108,11 @@ async fn primary_thread_ignores_child_mcp_startup_notifications() {
assert!( assert!(
matches!( matches!(
child_snapshot.events.as_slice(), child_snapshot.events.as_slice(),
[ThreadBufferedEvent::Notification( [ThreadBufferedEvent::Notification(notification)]
if matches!(
notification.as_ref(),
ServerNotification::McpServerStatusUpdated(_) ServerNotification::McpServerStatusUpdated(_)
)] )
), ),
"child MCP startup notification should be buffered for the child thread" "child MCP startup notification should be buffered for the child thread"
); );
@@ -4155,7 +4159,7 @@ async fn app_scoped_mcp_startup_notifications_do_not_render_in_active_thread() {
app.handle_app_server_event( app.handle_app_server_event(
&app_server, &app_server,
codex_app_server_client::AppServerEvent::ServerNotification( codex_app_server_client::AppServerEvent::ServerNotification(Box::new(
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification { ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
thread_id: None, thread_id: None,
name: "sentry".to_string(), name: "sentry".to_string(),
@@ -4163,7 +4167,7 @@ async fn app_scoped_mcp_startup_notifications_do_not_render_in_active_thread() {
error: Some("sentry is not logged in".to_string()), error: Some("sentry is not logged in".to_string()),
failure_reason: None, failure_reason: None,
}), }),
), )),
) )
.await; .await;
@@ -4219,7 +4223,7 @@ async fn active_side_thread_renders_live_mcp_startup_notifications() {
] { ] {
app.handle_app_server_event( app.handle_app_server_event(
&app_server, &app_server,
codex_app_server_client::AppServerEvent::ServerNotification( codex_app_server_client::AppServerEvent::ServerNotification(Box::new(
ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification { ServerNotification::McpServerStatusUpdated(McpServerStatusUpdatedNotification {
thread_id: Some(side_thread_id.to_string()), thread_id: Some(side_thread_id.to_string()),
name: "sentry".to_string(), name: "sentry".to_string(),
@@ -4228,7 +4232,7 @@ async fn active_side_thread_renders_live_mcp_startup_notifications() {
.then(|| "sentry is not logged in".to_string()), .then(|| "sentry is not logged in".to_string()),
failure_reason: None, failure_reason: None,
}), }),
), )),
) )
.await; .await;
} }
@@ -6706,7 +6710,7 @@ async fn replace_chat_widget_reseeds_collab_agent_metadata_for_replay() {
ThreadEventSnapshot { ThreadEventSnapshot {
session: None, session: None,
turns: Vec::new(), turns: Vec::new(),
events: vec![ThreadBufferedEvent::Notification( events: vec![ThreadBufferedEvent::Notification(Box::new(
ServerNotification::ItemStarted( ServerNotification::ItemStarted(
codex_app_server_protocol::ItemStartedNotification { codex_app_server_protocol::ItemStartedNotification {
thread_id: "thread-1".to_string(), thread_id: "thread-1".to_string(),
@@ -6726,7 +6730,7 @@ async fn replace_chat_widget_reseeds_collab_agent_metadata_for_replay() {
}, },
}, },
), ),
)], ))],
input_state: None, input_state: None,
}, },
/*resume_restored_queue*/ false, /*resume_restored_queue*/ false,
@@ -7065,9 +7069,9 @@ async fn override_turn_context_sends_thread_settings_update() {
app.handle_app_server_event( app.handle_app_server_event(
&app_server, &app_server,
codex_app_server_client::AppServerEvent::ServerNotification( codex_app_server_client::AppServerEvent::ServerNotification(Box::new(
ServerNotification::ThreadSettingsUpdated(notification), ServerNotification::ThreadSettingsUpdated(notification),
), )),
) )
.await; .await;
let updated_session = app let updated_session = app
+2 -2
View File
@@ -56,11 +56,11 @@ async fn deliver_rolling_rate_limit_snapshot(
) { ) {
app.handle_app_server_event( app.handle_app_server_event(
app_server, app_server,
codex_app_server_client::AppServerEvent::ServerNotification( codex_app_server_client::AppServerEvent::ServerNotification(Box::new(
ServerNotification::AccountRateLimitsUpdated(AccountRateLimitsUpdatedNotification { ServerNotification::AccountRateLimitsUpdated(AccountRateLimitsUpdatedNotification {
rate_limits: snapshot, rate_limits: snapshot,
}), }),
), )),
) )
.await; .await;
} }
+18 -10
View File
@@ -107,13 +107,13 @@ async fn next_turn_started(
.await .await
.expect("app-server should emit a turn/start event") .expect("app-server should emit a turn/start event")
.expect("app-server event stream should remain open"); .expect("app-server event stream should remain open");
let started_turn_id = match &event { let started_turn_id = if let AppServerEvent::ServerNotification(notification) = &event
AppServerEvent::ServerNotification(ServerNotification::TurnStarted(notification)) && let ServerNotification::TurnStarted(notification) = notification.as_ref()
if notification.thread_id == thread_id.to_string() => && notification.thread_id == thread_id.to_string()
{ {
Some(notification.turn.id.clone()) Some(notification.turn.id.clone())
} } else {
_ => None, None
}; };
app.handle_app_server_event(app_server, event).await; app.handle_app_server_event(app_server, event).await;
drain_active_thread_events(app); drain_active_thread_events(app);
@@ -138,8 +138,12 @@ async fn wait_for_turn_completed(
.expect("app-server event stream should remain open"); .expect("app-server event stream should remain open");
let completed = matches!( let completed = matches!(
&event, &event,
AppServerEvent::ServerNotification(ServerNotification::TurnCompleted(notification)) AppServerEvent::ServerNotification(notification)
if matches!(
notification.as_ref(),
ServerNotification::TurnCompleted(notification)
if notification.thread_id == thread_id.to_string() if notification.thread_id == thread_id.to_string()
)
); );
app.handle_app_server_event(app_server, event).await; app.handle_app_server_event(app_server, event).await;
drain_active_thread_events(app); drain_active_thread_events(app);
@@ -237,12 +241,12 @@ stream_max_retries = 0
} }
while app_event_rx.try_recv().is_ok() {} while app_event_rx.try_recv().is_ok() {}
app.handle_thread_event_now(ThreadBufferedEvent::Notification( app.handle_thread_event_now(ThreadBufferedEvent::Notification(Box::new(
ServerNotification::Warning(WarningNotification { ServerNotification::Warning(WarningNotification {
thread_id: Some(thread_id.to_string()), thread_id: Some(thread_id.to_string()),
message: "event handled while interrupt is pending".to_string(), message: "event handled while interrupt is pending".to_string(),
}), }),
)); )));
assert!(matches!( assert!(matches!(
app_event_rx.try_recv(), app_event_rx.try_recv(),
Ok(AppEvent::InsertHistoryCell(_)) Ok(AppEvent::InsertHistoryCell(_))
@@ -507,7 +511,8 @@ goals = true
app.handle_app_server_event( app.handle_app_server_event(
&app_server, &app_server,
AppServerEvent::ServerNotification(ServerNotification::ModelSafetyBufferingUpdated( AppServerEvent::ServerNotification(Box::new(
ServerNotification::ModelSafetyBufferingUpdated(
ModelSafetyBufferingUpdatedNotification { ModelSafetyBufferingUpdatedNotification {
thread_id: source_thread_id.to_string(), thread_id: source_thread_id.to_string(),
turn_id: active_turn_id.clone(), turn_id: active_turn_id.clone(),
@@ -517,6 +522,7 @@ goals = true
show_buffering_ui: true, show_buffering_ui: true,
faster_model: Some(FASTER_MODEL.to_string()), faster_model: Some(FASTER_MODEL.to_string()),
}, },
),
)), )),
) )
.await; .await;
@@ -584,7 +590,8 @@ goals = true
.await; .await;
app.handle_app_server_event( app.handle_app_server_event(
&app_server, &app_server,
AppServerEvent::ServerNotification(ServerNotification::ModelSafetyBufferingUpdated( AppServerEvent::ServerNotification(Box::new(
ServerNotification::ModelSafetyBufferingUpdated(
ModelSafetyBufferingUpdatedNotification { ModelSafetyBufferingUpdatedNotification {
thread_id: first_retry_thread_id.to_string(), thread_id: first_retry_thread_id.to_string(),
turn_id: first_retry_turn_id.clone(), turn_id: first_retry_turn_id.clone(),
@@ -594,6 +601,7 @@ goals = true
show_buffering_ui: true, show_buffering_ui: true,
faster_model: Some(FASTER_MODEL.to_string()), faster_model: Some(FASTER_MODEL.to_string()),
}, },
),
)), )),
) )
.await; .await;
+31 -24
View File
@@ -18,8 +18,8 @@ pub(super) struct ThreadEventSnapshot {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(super) enum ThreadBufferedEvent { pub(super) enum ThreadBufferedEvent {
Notification(ServerNotification), Notification(Box<ServerNotification>),
Request(ServerRequest), Request(Box<ServerRequest>),
HistoryEntryResponse(HistoryLookupResponse), HistoryEntryResponse(HistoryLookupResponse),
FeedbackSubmission(FeedbackThreadEvent), FeedbackSubmission(FeedbackThreadEvent),
} }
@@ -53,14 +53,16 @@ pub(super) struct ThreadEventStore {
impl ThreadEventStore { impl ThreadEventStore {
pub(super) fn event_survives_session_refresh(event: &ThreadBufferedEvent) -> bool { pub(super) fn event_survives_session_refresh(event: &ThreadBufferedEvent) -> bool {
matches!( match event {
event, ThreadBufferedEvent::Request(_) | ThreadBufferedEvent::FeedbackSubmission(_) => true,
ThreadBufferedEvent::Request(_) ThreadBufferedEvent::Notification(notification) => matches!(
| ThreadBufferedEvent::Notification(ServerNotification::HookStarted(_)) notification.as_ref(),
| ThreadBufferedEvent::Notification(ServerNotification::HookCompleted(_)) ServerNotification::HookStarted(_)
| ThreadBufferedEvent::Notification(ServerNotification::McpServerStatusUpdated(_)) | ServerNotification::HookCompleted(_)
| ThreadBufferedEvent::FeedbackSubmission(_) | ServerNotification::McpServerStatusUpdated(_)
) ),
ThreadBufferedEvent::HistoryEntryResponse(_) => false,
}
} }
pub(super) fn new(capacity: usize) -> Self { pub(super) fn new(capacity: usize) -> Self {
@@ -159,26 +161,29 @@ impl ThreadEventStore {
} }
self.buffer self.buffer
.push_back(ThreadBufferedEvent::Notification(notification.into_owned())); .push_back(ThreadBufferedEvent::Notification(Box::new(
notification.into_owned(),
)));
if self.buffer.len() > self.capacity if self.buffer.len() > self.capacity
&& let Some(removed) = self.buffer.pop_front() && let Some(removed) = self.buffer.pop_front()
&& let ThreadBufferedEvent::Request(request) = &removed && let ThreadBufferedEvent::Request(request) = &removed
{ {
self.pending_interactive_replay self.pending_interactive_replay
.note_evicted_server_request(request); .note_evicted_server_request(request.as_ref());
} }
} }
pub(super) fn push_request(&mut self, request: ServerRequest) { pub(super) fn push_request(&mut self, request: ServerRequest) {
self.pending_interactive_replay self.pending_interactive_replay
.note_server_request(&request); .note_server_request(&request);
self.buffer.push_back(ThreadBufferedEvent::Request(request)); self.buffer
.push_back(ThreadBufferedEvent::Request(Box::new(request)));
if self.buffer.len() > self.capacity if self.buffer.len() > self.capacity
&& let Some(removed) = self.buffer.pop_front() && let Some(removed) = self.buffer.pop_front()
&& let ThreadBufferedEvent::Request(request) = &removed && let ThreadBufferedEvent::Request(request) = &removed
{ {
self.pending_interactive_replay self.pending_interactive_replay
.note_evicted_server_request(request); .note_evicted_server_request(request.as_ref());
} }
} }
@@ -189,9 +194,9 @@ impl ThreadEventStore {
ThreadBufferedEvent::Request(request) ThreadBufferedEvent::Request(request)
if self if self
.pending_interactive_replay .pending_interactive_replay
.should_replay_snapshot_request(request) => .should_replay_snapshot_request(request.as_ref()) =>
{ {
Some(request.clone()) Some(request.as_ref().clone())
} }
ThreadBufferedEvent::Request(_) ThreadBufferedEvent::Request(_)
| ThreadBufferedEvent::Notification(_) | ThreadBufferedEvent::Notification(_)
@@ -210,18 +215,20 @@ impl ThreadEventStore {
.iter() .iter()
.rev() .rev()
.find_map(|event| match event { .find_map(|event| match event {
ThreadBufferedEvent::Notification(ServerNotification::ItemStarted( ThreadBufferedEvent::Notification(notification) => match notification.as_ref() {
notification, ServerNotification::ItemStarted(notification)
)) if turn_id_matches(turn_id, &notification.turn_id) => { if turn_id_matches(turn_id, &notification.turn_id) =>
{
file_change_item_changes(&notification.item, item_id) file_change_item_changes(&notification.item, item_id)
} }
ThreadBufferedEvent::Notification(ServerNotification::ItemCompleted( ServerNotification::ItemCompleted(notification)
notification, if turn_id_matches(turn_id, &notification.turn_id) =>
)) if turn_id_matches(turn_id, &notification.turn_id) => { {
file_change_item_changes(&notification.item, item_id) file_change_item_changes(&notification.item, item_id)
} }
_ => None,
},
ThreadBufferedEvent::Request(_) ThreadBufferedEvent::Request(_)
| ThreadBufferedEvent::Notification(_)
| ThreadBufferedEvent::HistoryEntryResponse(_) | ThreadBufferedEvent::HistoryEntryResponse(_)
| ThreadBufferedEvent::FeedbackSubmission(_) => None, | ThreadBufferedEvent::FeedbackSubmission(_) => None,
}) })
@@ -247,7 +254,7 @@ impl ThreadEventStore {
.filter(|event| match event { .filter(|event| match event {
ThreadBufferedEvent::Request(request) => self ThreadBufferedEvent::Request(request) => self
.pending_interactive_replay .pending_interactive_replay
.should_replay_snapshot_request(request), .should_replay_snapshot_request(request.as_ref()),
ThreadBufferedEvent::Notification(_) ThreadBufferedEvent::Notification(_)
| ThreadBufferedEvent::HistoryEntryResponse(_) | ThreadBufferedEvent::HistoryEntryResponse(_)
| ThreadBufferedEvent::FeedbackSubmission(_) => true, | ThreadBufferedEvent::FeedbackSubmission(_) => true,
+25 -18
View File
@@ -632,7 +632,9 @@ impl App {
}; };
if should_send if should_send
&& let Err(error) = thread_event_tx && let Err(error) = thread_event_tx
.send(ThreadBufferedEvent::Notification(notification)) .send(ThreadBufferedEvent::Notification(Box::new(
notification,
)))
.await .await
{ {
tracing::warn!(error = %error, "thread event channel closed"); tracing::warn!(error = %error, "thread event channel closed");
@@ -1006,7 +1008,7 @@ impl App {
} }
if let Some(notification) = notification { if let Some(notification) = notification {
match sender.try_send(ThreadBufferedEvent::Notification(notification)) { match sender.try_send(ThreadBufferedEvent::Notification(Box::new(notification))) {
Ok(()) => {} Ok(()) => {}
Err(TrySendError::Full(event)) => { Err(TrySendError::Full(event)) => {
tokio::spawn(async move { tokio::spawn(async move {
@@ -1132,7 +1134,7 @@ impl App {
let request_status = SideParentStatus::for_request(&request); let request_status = SideParentStatus::for_request(&request);
if should_send { if should_send {
match sender.try_send(ThreadBufferedEvent::Request(request)) { match sender.try_send(ThreadBufferedEvent::Request(Box::new(request))) {
Ok(()) => {} Ok(()) => {}
Err(TrySendError::Full(event)) => { Err(TrySendError::Full(event)) => {
tokio::spawn(async move { tokio::spawn(async move {
@@ -1183,7 +1185,7 @@ impl App {
{ {
guard guard
.pending_interactive_replay .pending_interactive_replay
.note_evicted_server_request(request); .note_evicted_server_request(request.as_ref());
} }
} }
should_send should_send
@@ -1267,11 +1269,11 @@ impl App {
for pending_event in pending { for pending_event in pending {
match pending_event { match pending_event {
ThreadBufferedEvent::Notification(notification) => { ThreadBufferedEvent::Notification(notification) => {
self.enqueue_thread_notification(thread_id, notification) self.enqueue_thread_notification(thread_id, *notification)
.await?; .await?;
} }
ThreadBufferedEvent::Request(request) => { ThreadBufferedEvent::Request(request) => {
self.enqueue_thread_request(thread_id, request).await?; self.enqueue_thread_request(thread_id, *request).await?;
} }
ThreadBufferedEvent::HistoryEntryResponse(event) => { ThreadBufferedEvent::HistoryEntryResponse(event) => {
self.enqueue_thread_history_entry_response(thread_id, event) self.enqueue_thread_history_entry_response(thread_id, event)
@@ -1298,7 +1300,7 @@ impl App {
.await; .await;
} }
self.pending_primary_events self.pending_primary_events
.push_back(ThreadBufferedEvent::Notification(notification)); .push_back(ThreadBufferedEvent::Notification(Box::new(notification)));
Ok(()) Ok(())
} }
@@ -1310,7 +1312,7 @@ impl App {
return self.enqueue_thread_request(thread_id, request).await; return self.enqueue_thread_request(thread_id, request).await;
} }
self.pending_primary_events self.pending_primary_events
.push_back(ThreadBufferedEvent::Request(request)); .push_back(ThreadBufferedEvent::Request(Box::new(request)));
Ok(()) Ok(())
} }
@@ -1541,22 +1543,26 @@ impl App {
pub(super) fn handle_thread_event_now(&mut self, event: ThreadBufferedEvent) { pub(super) fn handle_thread_event_now(&mut self, event: ThreadBufferedEvent) {
let needs_refresh = matches!( let needs_refresh = matches!(
&event, &event,
ThreadBufferedEvent::Notification(ServerNotification::TurnStarted(_)) ThreadBufferedEvent::Notification(notification)
| ThreadBufferedEvent::Notification(ServerNotification::ThreadTokenUsageUpdated(_)) if matches!(
notification.as_ref(),
ServerNotification::TurnStarted(_)
| ServerNotification::ThreadTokenUsageUpdated(_)
)
); );
match event { match event {
ThreadBufferedEvent::Notification(notification) => { ThreadBufferedEvent::Notification(notification) => {
self.cache_collab_receiver_threads_for_notification(&notification); self.cache_collab_receiver_threads_for_notification(notification.as_ref());
self.chat_widget self.chat_widget
.handle_server_notification(notification, /*replay_kind*/ None); .handle_server_notification(*notification, /*replay_kind*/ None);
} }
ThreadBufferedEvent::Request(request) => { ThreadBufferedEvent::Request(request) => {
if self if self
.pending_app_server_requests .pending_app_server_requests
.contains_server_request(&request) .contains_server_request(request.as_ref())
{ {
self.chat_widget self.chat_widget
.handle_server_request(request, /*replay_kind*/ None); .handle_server_request(*request, /*replay_kind*/ None);
} }
} }
ThreadBufferedEvent::HistoryEntryResponse(event) => { ThreadBufferedEvent::HistoryEntryResponse(event) => {
@@ -1575,10 +1581,10 @@ impl App {
match event { match event {
ThreadBufferedEvent::Notification(notification) => self ThreadBufferedEvent::Notification(notification) => self
.chat_widget .chat_widget
.handle_server_notification(notification, Some(ReplayKind::ThreadSnapshot)), .handle_server_notification(*notification, Some(ReplayKind::ThreadSnapshot)),
ThreadBufferedEvent::Request(request) => self ThreadBufferedEvent::Request(request) => self
.chat_widget .chat_widget
.handle_server_request(request, Some(ReplayKind::ThreadSnapshot)), .handle_server_request(*request, Some(ReplayKind::ThreadSnapshot)),
ThreadBufferedEvent::HistoryEntryResponse(event) => { ThreadBufferedEvent::HistoryEntryResponse(event) => {
self.chat_widget.handle_history_entry_response(event) self.chat_widget.handle_history_entry_response(event)
} }
@@ -1603,7 +1609,8 @@ impl App {
// the exit marker when the currently active thread acknowledges shutdown. // the exit marker when the currently active thread acknowledges shutdown.
let pending_shutdown_exit_completed = matches!( let pending_shutdown_exit_completed = matches!(
&event, &event,
ThreadBufferedEvent::Notification(ServerNotification::ThreadClosed(_)) ThreadBufferedEvent::Notification(notification)
if matches!(notification.as_ref(), ServerNotification::ThreadClosed(_))
) && self.pending_shutdown_exit_thread_id ) && self.pending_shutdown_exit_thread_id
== self.active_thread_id; == self.active_thread_id;
@@ -1617,7 +1624,7 @@ impl App {
// failover, while true sub-agent deaths still do. // failover, while true sub-agent deaths still do.
if let ThreadBufferedEvent::Notification(notification) = &event if let ThreadBufferedEvent::Notification(notification) = &event
&& let Some((closed_thread_id, primary_thread_id)) = && let Some((closed_thread_id, primary_thread_id)) =
self.active_non_primary_shutdown_target(notification) self.active_non_primary_shutdown_target(notification.as_ref())
{ {
self.mark_agent_picker_thread_closed(closed_thread_id); self.mark_agent_picker_thread_closed(closed_thread_id);
if self.side_threads.contains_key(&closed_thread_id) { if self.side_threads.contains_key(&closed_thread_id) {
@@ -556,7 +556,7 @@ pub(crate) async fn run_onboarding_app(
if let Some(event) = event { if let Some(event) = event {
match event { match event {
AppServerEvent::ServerNotification(notification) => { AppServerEvent::ServerNotification(notification) => {
onboarding_screen.handle_app_server_notification(notification); onboarding_screen.handle_app_server_notification(*notification);
} }
AppServerEvent::Disconnected { message } => { AppServerEvent::Disconnected { message } => {
return Err(color_eyre::eyre::eyre!(message)); return Err(color_eyre::eyre::eyre!(message));