Finish moving codex exec to app-server (#15424)

This PR completes the conversion of non-interactive `codex exec` to use
app server rather than directly using core events and methods.

### Summary
- move `codex-exec` off exec-owned `AuthManager` and `ThreadManager`
state
- route exec bootstrap, resume, and auth refresh through existing
app-server paths
- replace legacy `codex/event/*` decoding in exec with typed app-server
notification handling
- update human and JSONL exec output adapters to translate existing
app-server notifications only
- clean up "app server client" layer by eliminating support for legacy
notifications; this is no longer needed
- remove exposure of `authManager` and `threadManager` from "app server
client" layer

### Testing
- `exec` has pretty extensive unit and integration tests already, and
these all pass
- In addition, I asked Codex to put together a comprehensive manual set
of tests to cover all of the `codex exec` functionality (including
command-line options), and it successfully generated and ran these tests
This commit is contained in:
Eric Traut
2026-03-24 08:51:32 -06:00
committed by GitHub
Unverified
parent 1db6cb9789
commit 45f68843b8
22 changed files with 3438 additions and 4238 deletions
@@ -12,7 +12,6 @@ use crate::models::supported_models;
use crate::outgoing_message::ConnectionId;
use crate::outgoing_message::ConnectionRequestId;
use crate::outgoing_message::OutgoingMessageSender;
use crate::outgoing_message::OutgoingNotification;
use crate::outgoing_message::RequestContext;
use crate::outgoing_message::ThreadScopedOutgoingMessageSender;
use crate::thread_status::ThreadWatchManager;
@@ -6831,43 +6830,9 @@ impl CodexMessageProcessor {
}
};
// For now, we send a notification for every event,
// Legacy `codex/event/*` notifications are still
// produced here because the in-process app-server lane
// (`codex exec` and other in-process consumers) still
// depends on them. External transports now drop
// `OutgoingMessage::Notification` in `transport.rs`,
// so stdio/websocket clients only observe the typed
// `ServerNotification` translations emitted below.
//
// TODO: remove this raw legacy-notification emission
// entirely once the remaining in-process consumers are
// migrated off `codex/event/*`.
let event_formatted = match &event.msg {
EventMsg::TurnStarted(_) => "task_started",
EventMsg::TurnComplete(_) => "task_complete",
_ => &event.msg.to_string(),
};
let request_event_name = format!("codex/event/{event_formatted}");
tracing::trace!(
conversation_id = %conversation_id,
"app-server event: {request_event_name}"
);
let mut params = match serde_json::to_value(event.clone()) {
Ok(serde_json::Value::Object(map)) => map,
Ok(_) => {
error!("event did not serialize to an object");
continue;
}
Err(err) => {
error!("failed to serialize event: {err}");
continue;
}
};
params.insert(
"conversationId".to_string(),
conversation_id.to_string().into(),
);
// Track the event before emitting any typed
// translations so thread-local state such as raw event
// opt-in stays synchronized with the conversation.
let raw_events_enabled = {
let mut thread_state = thread_state.lock().await;
thread_state.track_current_turn_event(&event.msg);
@@ -6880,18 +6845,6 @@ impl CodexMessageProcessor {
continue;
}
if !subscribed_connection_ids.is_empty() {
outgoing_for_task
.send_notification_to_connections(
&subscribed_connection_ids,
OutgoingNotification {
method: request_event_name,
params: Some(params.into()),
},
)
.await;
}
let thread_outgoing = ThreadScopedOutgoingMessageSender::new(
outgoing_for_task.clone(),
subscribed_connection_ids,