Skip to content

Commit 202af12

Browse files
Add a slash command to control permissions (#2474)
A slash command to control permissions https://github.com/user-attachments/assets/c0edafcd-2085-4e09-8009-ba69c4f1c153 --------- Co-authored-by: ae <[email protected]>
1 parent ce434b1 commit 202af12

File tree

8 files changed

+142
-3
lines changed

8 files changed

+142
-3
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use codex_core::protocol::AskForApproval;
2+
use codex_core::protocol::SandboxPolicy;
3+
4+
/// A simple preset pairing an approval policy with a sandbox policy.
5+
#[derive(Debug, Clone)]
6+
pub struct ApprovalPreset {
7+
/// Stable identifier for the preset.
8+
pub id: &'static str,
9+
/// Display label shown in UIs.
10+
pub label: &'static str,
11+
/// Short human description shown next to the label in UIs.
12+
pub description: &'static str,
13+
/// Approval policy to apply.
14+
pub approval: AskForApproval,
15+
/// Sandbox policy to apply.
16+
pub sandbox: SandboxPolicy,
17+
}
18+
19+
/// Built-in list of approval presets that pair approval and sandbox policy.
20+
///
21+
/// Keep this UI-agnostic so it can be reused by both TUI and MCP server.
22+
pub fn builtin_approval_presets() -> Vec<ApprovalPreset> {
23+
vec![
24+
ApprovalPreset {
25+
id: "read-only",
26+
label: "Read Only",
27+
description: "Codex can read files and answer questions. Codex requires approval to make edits, run commands, or access network.",
28+
approval: AskForApproval::OnRequest,
29+
sandbox: SandboxPolicy::ReadOnly,
30+
},
31+
ApprovalPreset {
32+
id: "auto",
33+
label: "Auto",
34+
description: "Codex can read files, make edits, and run commands in the workspace. Codex requires approval to work outside the workspace or access network.",
35+
approval: AskForApproval::OnRequest,
36+
sandbox: SandboxPolicy::new_workspace_write_policy(),
37+
},
38+
ApprovalPreset {
39+
id: "full-access",
40+
label: "Full Access",
41+
description: "Codex can read files, make edits, and run commands with network access, without approval. Exercise caution.",
42+
approval: AskForApproval::Never,
43+
sandbox: SandboxPolicy::DangerFullAccess,
44+
},
45+
]
46+
}

codex-rs/common/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,6 @@ pub use config_summary::create_config_summary_entries;
3131
pub mod fuzzy_match;
3232
// Shared model presets used by TUI and MCP server
3333
pub mod model_presets;
34+
// Shared approval presets (AskForApproval + Sandbox) used by TUI and MCP server
35+
// Not to be confused with AskForApproval, which we should probably rename to EscalationPolicy.
36+
pub mod approval_presets;

codex-rs/config.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,16 @@ This is reasonable to use if Codex is running in an environment that provides it
300300

301301
Though using this option may also be necessary if you try to use Codex in environments where its native sandboxing mechanisms are unsupported, such as older Linux kernels or on Windows.
302302

303+
## Approval presets
304+
305+
Codex provides three main Approval Presets:
306+
307+
- Read Only: Codex can read files and answer questions; edits, running commands, and network access require approval.
308+
- Auto: Codex can read files, make edits, and run commands in the workspace without approval; asks for approval outside the workspace or for network access.
309+
- Full Access: Full disk and network access without prompts; extremely risky.
310+
311+
You can further customize how Codex runs at the command line using the `--ask-for-approval` and `--sandbox` options.
312+
303313
## mcp_servers
304314

305315
Defines the list of MCP servers that Codex can consult for tool use. Currently, only servers that are launched by executing a program that communicate over stdio are supported. For servers that use the SSE transport, consider an adapter like [mcp-proxy](https://github.com/sparfenyuk/mcp-proxy).

codex-rs/tui/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,19 @@ codex-common = { path = "../common", features = [
3333
"sandbox_summary",
3434
] }
3535
codex-core = { path = "../core" }
36-
codex-protocol = { path = "../protocol" }
3736
codex-file-search = { path = "../file-search" }
3837
codex-login = { path = "../login" }
3938
codex-ollama = { path = "../ollama" }
39+
codex-protocol = { path = "../protocol" }
4040
color-eyre = "0.6.3"
4141
crossterm = { version = "0.28.1", features = ["bracketed-paste"] }
4242
diffy = "0.4.2"
4343
image = { version = "^0.25.6", default-features = false, features = ["jpeg"] }
4444
lazy_static = "1"
45-
once_cell = "1"
4645
mcp-types = { path = "../mcp-types" }
46+
once_cell = "1"
4747
path-clean = "1.0.1"
48+
rand = "0.9"
4849
ratatui = { version = "0.29.0", features = [
4950
"scrolling-regions",
5051
"unstable-rendered-line-info",
@@ -75,7 +76,6 @@ tui-markdown = "0.3.3"
7576
unicode-segmentation = "1.12.0"
7677
unicode-width = "0.1"
7778
uuid = "1"
78-
rand = "0.9"
7979

8080
[target.'cfg(unix)'.dependencies]
8181
libc = "0.2"

codex-rs/tui/src/app.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,11 @@ impl App<'_> {
387387
widget.open_model_popup();
388388
}
389389
}
390+
SlashCommand::Approvals => {
391+
if let AppState::Chat { widget } = &mut self.app_state {
392+
widget.open_approvals_popup();
393+
}
394+
}
390395
SlashCommand::Quit => {
391396
break;
392397
}
@@ -514,6 +519,16 @@ impl App<'_> {
514519
widget.set_model(model);
515520
}
516521
}
522+
AppEvent::UpdateAskForApprovalPolicy(policy) => {
523+
if let AppState::Chat { widget } = &mut self.app_state {
524+
widget.set_approval_policy(policy);
525+
}
526+
}
527+
AppEvent::UpdateSandboxPolicy(policy) => {
528+
if let AppState::Chat { widget } = &mut self.app_state {
529+
widget.set_sandbox_policy(policy);
530+
}
531+
}
517532
}
518533
}
519534
terminal.clear()?;

codex-rs/tui/src/app_event.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use std::time::Duration;
66

77
use crate::app::ChatWidgetArgs;
88
use crate::slash_command::SlashCommand;
9+
use codex_core::protocol::AskForApproval;
10+
use codex_core::protocol::SandboxPolicy;
911
use codex_core::protocol_config_types::ReasoningEffort;
1012

1113
#[allow(clippy::large_enum_variant)]
@@ -70,4 +72,10 @@ pub(crate) enum AppEvent {
7072

7173
/// Update the current model slug in the running app and widget.
7274
UpdateModel(String),
75+
76+
/// Update the current approval policy in the running app and widget.
77+
UpdateAskForApprovalPolicy(AskForApproval),
78+
79+
/// Update the current sandbox policy in the running app and widget.
80+
UpdateSandboxPolicy(SandboxPolicy),
7381
}

codex-rs/tui/src/chatwidget.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,13 @@ mod agent;
6060
use self::agent::spawn_agent;
6161
use crate::streaming::controller::AppEventHistorySink;
6262
use crate::streaming::controller::StreamController;
63+
use codex_common::approval_presets::ApprovalPreset;
64+
use codex_common::approval_presets::builtin_approval_presets;
6365
use codex_common::model_presets::ModelPreset;
6466
use codex_common::model_presets::builtin_model_presets;
6567
use codex_core::ConversationManager;
68+
use codex_core::protocol::AskForApproval;
69+
use codex_core::protocol::SandboxPolicy;
6670
use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig;
6771
use codex_file_search::FileMatch;
6872
use uuid::Uuid;
@@ -733,6 +737,57 @@ impl ChatWidget<'_> {
733737
);
734738
}
735739

740+
/// Open a popup to choose the approvals mode (ask for approval policy + sandbox policy).
741+
pub(crate) fn open_approvals_popup(&mut self) {
742+
let current_approval = self.config.approval_policy;
743+
let current_sandbox = self.config.sandbox_policy.clone();
744+
let mut items: Vec<SelectionItem> = Vec::new();
745+
let presets: Vec<ApprovalPreset> = builtin_approval_presets();
746+
for preset in presets.into_iter() {
747+
let is_current =
748+
current_approval == preset.approval && current_sandbox == preset.sandbox;
749+
let approval = preset.approval;
750+
let sandbox = preset.sandbox.clone();
751+
let name = preset.label.to_string();
752+
let description = Some(preset.description.to_string());
753+
let actions: Vec<SelectionAction> = vec![Box::new(move |tx| {
754+
tx.send(AppEvent::CodexOp(Op::OverrideTurnContext {
755+
cwd: None,
756+
approval_policy: Some(approval),
757+
sandbox_policy: Some(sandbox.clone()),
758+
model: None,
759+
effort: None,
760+
summary: None,
761+
}));
762+
tx.send(AppEvent::UpdateAskForApprovalPolicy(approval));
763+
tx.send(AppEvent::UpdateSandboxPolicy(sandbox.clone()));
764+
})];
765+
items.push(SelectionItem {
766+
name,
767+
description,
768+
is_current,
769+
actions,
770+
});
771+
}
772+
773+
self.bottom_pane.show_selection_view(
774+
"Select Approvals Mode".to_string(),
775+
None,
776+
Some("Press Enter to confirm or Esc to go back".to_string()),
777+
items,
778+
);
779+
}
780+
781+
/// Set the approval policy in the widget's config copy.
782+
pub(crate) fn set_approval_policy(&mut self, policy: AskForApproval) {
783+
self.config.approval_policy = policy;
784+
}
785+
786+
/// Set the sandbox policy in the widget's config copy.
787+
pub(crate) fn set_sandbox_policy(&mut self, policy: SandboxPolicy) {
788+
self.config.sandbox_policy = policy;
789+
}
790+
736791
/// Set the reasoning effort in the widget's config copy.
737792
pub(crate) fn set_reasoning_effort(&mut self, effort: ReasoningEffortConfig) {
738793
self.config.model_reasoning_effort = effort;

codex-rs/tui/src/slash_command.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub enum SlashCommand {
1313
// DO NOT ALPHA-SORT! Enum order is presentation order in the popup, so
1414
// more frequently used commands should be listed first.
1515
Model,
16+
Approvals,
1617
New,
1718
Init,
1819
Compact,
@@ -38,6 +39,7 @@ impl SlashCommand {
3839
SlashCommand::Mention => "mention a file",
3940
SlashCommand::Status => "show current session configuration and token usage",
4041
SlashCommand::Model => "choose a model preset (model + reasoning effort)",
42+
SlashCommand::Approvals => "choose what Codex can do without approval",
4143
SlashCommand::Mcp => "list configured MCP tools",
4244
SlashCommand::Logout => "log out of Codex",
4345
#[cfg(debug_assertions)]

0 commit comments

Comments
 (0)