Display YOLO mode permissions if set when launching TUI (#17877)

- When launching the TUI client, if YOLO mode is enabled, display this
in the header.
- Eligibility is determined by `approval_policy = "never"` and
`sandbox_mode = "danger-full-access"`

<img width="886" height="230" alt="image"
src="https://github.com/user-attachments/assets/d7064778-e32c-4123-8e44-ca0c9016ab09"
/>
This commit is contained in:
canvrno-oai
2026-04-15 18:28:11 -07:00
committed by GitHub
Unverified
parent d63ba2d5ec
commit d97bad1272
4 changed files with 81 additions and 11 deletions
+1
View File
@@ -1599,6 +1599,7 @@ impl App {
self.config.cwd.to_path_buf(),
version,
)
.with_yolo_mode(history_cell::is_yolo_mode(&self.config))
.display_lines(width)
}
+11 -8
View File
@@ -9722,14 +9722,17 @@ impl ChatWidget {
/// Build a placeholder header cell while the session is configuring.
fn placeholder_session_header_cell(config: &Config) -> Box<dyn HistoryCell> {
let placeholder_style = Style::default().add_modifier(Modifier::DIM | Modifier::ITALIC);
Box::new(history_cell::SessionHeaderHistoryCell::new_with_style(
DEFAULT_MODEL_DISPLAY_NAME.to_string(),
placeholder_style,
/*reasoning_effort*/ None,
/*show_fast_status*/ false,
config.cwd.to_path_buf(),
CODEX_CLI_VERSION,
))
Box::new(
history_cell::SessionHeaderHistoryCell::new_with_style(
DEFAULT_MODEL_DISPLAY_NAME.to_string(),
placeholder_style,
/*reasoning_effort*/ None,
/*show_fast_status*/ false,
config.cwd.to_path_buf(),
CODEX_CLI_VERSION,
)
.with_yolo_mode(history_cell::is_yolo_mode(config)),
)
}
/// Merge the real session info cell with any placeholder header to avoid double boxes.
+58 -3
View File
@@ -61,9 +61,11 @@ use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
use codex_protocol::plan_tool::PlanItemArg;
use codex_protocol::plan_tool::StepStatus;
use codex_protocol::plan_tool::UpdatePlanArgs;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::FileChange;
use codex_protocol::protocol::McpAuthStatus;
use codex_protocol::protocol::McpInvocation;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionConfiguredEvent;
use codex_protocol::request_user_input::RequestUserInputAnswer;
use codex_protocol::request_user_input::RequestUserInputQuestion;
@@ -1186,6 +1188,8 @@ pub(crate) fn new_session_info(
let SessionConfiguredEvent {
model,
reasoning_effort,
approval_policy,
sandbox_policy,
..
} = event;
// Header box rendered as history (so it appears at the very top)
@@ -1195,7 +1199,8 @@ pub(crate) fn new_session_info(
show_fast_status,
config.cwd.to_path_buf(),
CODEX_CLI_VERSION,
);
)
.with_yolo_mode(has_yolo_permissions(approval_policy, &sandbox_policy));
let mut parts: Vec<Box<dyn HistoryCell>> = vec![Box::new(header)];
if is_first_event {
@@ -1259,6 +1264,17 @@ pub(crate) fn new_session_info(
SessionInfoCell(CompositeHistoryCell { parts })
}
pub(crate) fn is_yolo_mode(config: &Config) -> bool {
has_yolo_permissions(
config.permissions.approval_policy.value(),
config.permissions.sandbox_policy.get(),
)
}
fn has_yolo_permissions(approval_policy: AskForApproval, sandbox_policy: &SandboxPolicy) -> bool {
approval_policy == AskForApproval::Never && *sandbox_policy == SandboxPolicy::DangerFullAccess
}
pub(crate) fn new_user_prompt(
message: String,
text_elements: Vec<TextElement>,
@@ -1281,6 +1297,7 @@ pub(crate) struct SessionHeaderHistoryCell {
reasoning_effort: Option<ReasoningEffortConfig>,
show_fast_status: bool,
directory: PathBuf,
yolo_mode: bool,
}
impl SessionHeaderHistoryCell {
@@ -1316,9 +1333,15 @@ impl SessionHeaderHistoryCell {
reasoning_effort,
show_fast_status,
directory,
yolo_mode: false,
}
}
pub(crate) fn with_yolo_mode(mut self, yolo_mode: bool) -> Self {
self.yolo_mode = yolo_mode;
self
}
fn format_directory(&self, max_width: Option<usize>) -> String {
Self::format_directory_inner(&self.directory, max_width)
}
@@ -1377,7 +1400,12 @@ impl HistoryCell for SessionHeaderHistoryCell {
const CHANGE_MODEL_HINT_COMMAND: &str = "/model";
const CHANGE_MODEL_HINT_EXPLANATION: &str = " to change";
const DIR_LABEL: &str = "directory:";
let label_width = DIR_LABEL.len();
const PERMISSIONS_LABEL: &str = "permissions:";
let label_width = if self.yolo_mode {
DIR_LABEL.len().max(PERMISSIONS_LABEL.len())
} else {
DIR_LABEL.len()
};
let model_label = format!(
"{model_label:<label_width$}",
@@ -1411,13 +1439,21 @@ impl HistoryCell for SessionHeaderHistoryCell {
let dir = self.format_directory(Some(dir_max_width));
let dir_spans = vec![Span::from(dir_prefix).dim(), Span::from(dir)];
let lines = vec![
let mut lines = vec![
make_row(title_spans),
make_row(Vec::new()),
make_row(model_spans),
make_row(dir_spans),
];
if self.yolo_mode {
let permissions_label = format!("{PERMISSIONS_LABEL:<label_width$}");
lines.push(make_row(vec![
Span::from(format!("{permissions_label} ")).dim(),
"YOLO mode".magenta().bold(),
]));
}
with_border(lines)
}
}
@@ -3956,6 +3992,25 @@ mod tests {
assert!(!model_line.contains("fast"));
}
#[test]
#[cfg_attr(
target_os = "windows",
ignore = "snapshot path rendering differs on Windows"
)]
fn session_header_indicates_yolo_mode() {
let cell = SessionHeaderHistoryCell::new(
"gpt-5".to_string(),
/*reasoning_effort*/ None,
/*show_fast_status*/ false,
test_path_buf("/tmp/project").abs().to_path_buf(),
"test",
)
.with_yolo_mode(/*yolo_mode*/ true);
let rendered = render_lines(&cell.display_lines(/*width*/ 80)).join("\n");
insta::assert_snapshot!(rendered);
}
#[test]
fn session_header_directory_center_truncates() {
let mut dir = home_dir().expect("home directory");
@@ -0,0 +1,11 @@
---
source: tui/src/history_cell.rs
expression: rendered
---
╭───────────────────────────────────────╮
│ >_ OpenAI Codex (vtest) │
│ │
│ model: gpt-5 /model to change │
│ directory: /tmp/project │
│ permissions: YOLO mode │
╰───────────────────────────────────────╯