TUI: Remove core protocol dependency [3/7] (#20174)

## Why

This is part 3 of a 7-PR stack to remove direct
`codex_protocol::protocol` usage from `codex-tui` while keeping each
layer reviewable and shippable.

With `AppCommand` now explicit, the internal app event bus can carry TUI
commands directly instead of bouncing through core `Op` values.

## What changed

- Changed `AppEvent::CodexOp` and `AppEvent::SubmitThreadOp` to carry
`AppCommand`.
- Updated app-event senders and direct emitters to submit `AppCommand`
values.
- Adjusted tests to match `AppCommand` or convert back through
`into_core()` where they intentionally assert legacy payload equality.

## Verification

- `cargo test -p codex-tui --no-run`
This commit is contained in:
Eric Traut
2026-04-29 10:52:10 -07:00
committed by GitHub
Unverified
parent 445629815c
commit d0204c3dcc
19 changed files with 165 additions and 152 deletions
+1
View File
@@ -5,6 +5,7 @@
//! changes show up as stable, reviewable diffs.
pub(super) use super::*;
pub(super) use crate::app_command::AppCommand;
pub(super) use crate::app_event::AppEvent;
pub(super) use crate::app_event::ExitMode;
#[cfg(not(target_os = "linux"))]
@@ -217,7 +217,7 @@ async fn exec_approval_uses_approval_id_when_present() {
let mut found = false;
while let Ok(app_ev) = rx.try_recv() {
if let AppEvent::SubmitThreadOp {
op: Op::ExecApproval { id, decision, .. },
op: AppCommand::ExecApproval { id, decision, .. },
..
} = app_ev
{
@@ -114,7 +114,7 @@ async fn exec_approval_uses_approval_id_when_present() {
let mut found = false;
while let Ok(app_ev) = rx.try_recv() {
if let AppEvent::SubmitThreadOp {
op: Op::ExecApproval { id, decision, .. },
op: AppCommand::ExecApproval { id, decision, .. },
..
} = app_ev
{
@@ -1712,7 +1712,7 @@ async fn apply_patch_approval_sends_op_with_call_id() {
let mut found = false;
while let Ok(app_ev) = rx.try_recv() {
if let AppEvent::SubmitThreadOp {
op: Op::PatchApproval { id, decision },
op: AppCommand::PatchApproval { id, decision },
..
} = app_ev
{
@@ -1751,7 +1751,7 @@ async fn apply_patch_full_flow_integration_like() {
let mut maybe_op: Option<Op> = None;
while let Ok(app_ev) = rx.try_recv() {
if let AppEvent::SubmitThreadOp { op, .. } = app_ev {
maybe_op = Some(op);
maybe_op = Some(op.into_core());
break;
}
}
@@ -52,7 +52,7 @@ async fn approving_recent_denial_emits_structured_core_op_once() {
rx.try_recv(),
Ok(AppEvent::SubmitThreadOp {
thread_id: submitted_thread_id,
op: Op::ApproveGuardianDeniedAction { event }
op: AppCommand::ApproveGuardianDeniedAction { event }
}) if submitted_thread_id == thread_id
&& event.id == "auto-review-recent-1"
&& event.status == GuardianAssessmentStatus::Denied
@@ -315,7 +315,7 @@ async fn approvals_popup_navigation_skips_disabled() {
assert!(
app_events.iter().any(|ev| matches!(
ev,
AppEvent::CodexOp(Op::OverrideTurnContext {
AppEvent::CodexOp(AppCommand::OverrideTurnContext {
approval_policy: Some(AskForApproval::OnRequest),
personality: None,
..
@@ -326,7 +326,7 @@ async fn approvals_popup_navigation_skips_disabled() {
assert!(
!app_events.iter().any(|ev| matches!(
ev,
AppEvent::CodexOp(Op::OverrideTurnContext {
AppEvent::CodexOp(AppCommand::OverrideTurnContext {
approval_policy: Some(AskForApproval::Never),
personality: None,
..
@@ -697,7 +697,7 @@ async fn permissions_selection_sends_approvals_reviewer_in_override_turn_context
let op = std::iter::from_fn(|| rx.try_recv().ok())
.find_map(|event| match event {
AppEvent::CodexOp(op @ Op::OverrideTurnContext { .. }) => Some(op),
AppEvent::CodexOp(op @ AppCommand::OverrideTurnContext { .. }) => Some(op),
_ => None,
})
.expect("expected OverrideTurnContext op");
@@ -1221,10 +1221,10 @@ async fn custom_prompt_submit_sends_review_op() {
chat.handle_paste(" please audit dependencies ".to_string());
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
// Expect AppEvent::CodexOp(Op::Review { .. }) with trimmed prompt
// Expect AppEvent::CodexOp(AppCommand::Review { .. }) with trimmed prompt
let evt = rx.try_recv().expect("expected one app event");
match evt {
AppEvent::CodexOp(Op::Review { review_request }) => {
AppEvent::CodexOp(AppCommand::Review { review_request }) => {
assert_eq!(
review_request,
ReviewRequest {
@@ -51,7 +51,7 @@ async fn slash_compact_eagerly_queues_follow_up_before_turn_start() {
assert!(chat.bottom_pane.is_task_running());
match rx.try_recv() {
Ok(AppEvent::CodexOp(Op::Compact)) => {}
Ok(AppEvent::CodexOp(AppCommand::Compact)) => {}
other => panic!("expected compact op to be submitted, got {other:?}"),
}
@@ -103,7 +103,7 @@ async fn queued_slash_compact_dispatches_after_active_turn() {
assert!(
events
.iter()
.any(|event| matches!(event, AppEvent::CodexOp(Op::Compact))),
.any(|event| matches!(event, AppEvent::CodexOp(AppCommand::Compact))),
"expected queued /compact to submit compact op; events: {events:?}"
);
}
@@ -446,7 +446,7 @@ async fn queued_bare_rename_drains_next_input_after_name_update() {
assert!(
events.iter().any(|event| matches!(
event,
AppEvent::CodexOp(Op::SetThreadName { name }) if name == "Queued rename"
AppEvent::CodexOp(AppCommand::SetThreadName { name }) if name == "Queued rename"
)),
"expected rename prompt to submit thread name; events: {events:?}"
);
@@ -500,7 +500,7 @@ async fn queued_inline_rename_does_not_drain_again_before_turn_started() {
assert!(
events.iter().any(|event| matches!(
event,
AppEvent::CodexOp(Op::SetThreadName { name }) if name == "Queued rename"
AppEvent::CodexOp(AppCommand::SetThreadName { name }) if name == "Queued rename"
)),
"expected queued /rename to submit thread name; events: {events:?}"
);
@@ -1137,7 +1137,7 @@ async fn slash_rename_prefills_existing_thread_name() {
assert_matches!(
rx.try_recv(),
Ok(AppEvent::CodexOp(Op::SetThreadName { name })) if name == "Current project title"
Ok(AppEvent::CodexOp(AppCommand::SetThreadName { name })) if name == "Current project title"
);
}
@@ -2056,7 +2056,7 @@ async fn fast_slash_command_updates_and_persists_local_service_tier() {
assert!(
events.iter().any(|event| matches!(
event,
AppEvent::CodexOp(Op::OverrideTurnContext {
AppEvent::CodexOp(AppCommand::OverrideTurnContext {
service_tier: Some(Some(ServiceTier::Fast)),
..
})
@@ -2128,7 +2128,7 @@ async fn queued_fast_slash_applies_before_next_queued_message() {
assert!(
events.iter().any(|event| matches!(
event,
AppEvent::CodexOp(Op::OverrideTurnContext {
AppEvent::CodexOp(AppCommand::OverrideTurnContext {
service_tier: Some(Some(ServiceTier::Fast)),
..
})
@@ -2167,7 +2167,7 @@ async fn user_turn_sends_standard_override_after_fast_is_turned_off() {
assert!(
events.iter().any(|event| matches!(
event,
AppEvent::CodexOp(Op::OverrideTurnContext {
AppEvent::CodexOp(AppCommand::OverrideTurnContext {
service_tier: Some(None),
..
})