tui: plumb permission profile selection (#23708)

## Why

The named-profile `/permissions` picker needs a small TUI action path
that can select permission profiles without folding the menu UI and
profile metadata into the same review.

## What changed

- Carry permission-profile selections through the TUI app event flow.
- Persist selected profiles while preserving the existing approval
settings and guardrail prompts.
- Keep the legacy `/permissions` picker behavior in this layer; the
profile-mode menu stays in the follow-up PR.

## Stack

1. [#22931](https://github.com/openai/codex/pull/22931):
runtime/session/network propagation for active permission profiles.
2. **This PR**: TUI selection plumbing and guardrail flow.
3. [#21559](https://github.com/openai/codex/pull/21559): profile-aware
`/permissions` menu and custom profile display.

<img width="1632" height="1186" alt="image"
src="https://github.com/user-attachments/assets/69ddcd5e-b57c-468d-8c1d-246916323c15"
/>

## Validation

- `git diff --cached --check` before commit.
- Full test run skipped at the user request while pushing the split
stack.
This commit is contained in:
viyatb-oai
2026-05-21 12:26:36 -03:00
committed by GitHub
parent e0e304b123
commit fcff0d6c52
13 changed files with 627 additions and 144 deletions
@@ -166,7 +166,9 @@ async fn full_access_confirmation_popup_snapshot() {
.into_iter()
.find(|preset| preset.id == "full-access")
.expect("full access preset");
chat.open_full_access_confirmation(preset, /*return_to_permissions*/ false);
chat.open_full_access_confirmation(
preset, /*return_to_permissions*/ false, /*profile_selection*/ None,
);
let popup = render_bottom_popup(&chat, /*width*/ 80);
assert_chatwidget_snapshot!("full_access_confirmation_popup", popup);
@@ -181,7 +183,7 @@ async fn windows_auto_mode_prompt_requests_enabling_sandbox_feature() {
.into_iter()
.find(|preset| preset.id == "auto")
.expect("auto preset");
chat.open_windows_sandbox_enable_prompt(preset);
chat.open_windows_sandbox_enable_prompt(preset, /*profile_selection*/ None);
let popup = render_bottom_popup(&chat, /*width*/ 120);
assert!(
@@ -799,8 +801,9 @@ async fn permissions_full_access_history_cell_emitted_only_after_confirmation()
AppEvent::OpenFullAccessConfirmation {
preset,
return_to_permissions,
profile_selection,
} => {
open_confirmation_event = Some((preset, return_to_permissions));
open_confirmation_event = Some((preset, return_to_permissions, profile_selection));
}
_ => {}
}
@@ -811,9 +814,9 @@ async fn permissions_full_access_history_cell_emitted_only_after_confirmation()
"did not expect history cell before confirming full access"
);
}
let (preset, return_to_permissions) =
let (preset, return_to_permissions, profile_selection) =
open_confirmation_event.expect("expected full access confirmation event");
chat.open_full_access_confirmation(preset, return_to_permissions);
chat.open_full_access_confirmation(preset, return_to_permissions, profile_selection);
let popup = render_bottom_popup(&chat, /*width*/ 80);
assert!(