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 <ae@openai.com>
This commit is contained in:
Ahmed Ibrahim
2025-08-19 22:34:37 -07:00
committed by GitHub
parent ce434b1219
commit 202af12926
8 changed files with 142 additions and 3 deletions

View File

@@ -33,18 +33,19 @@ codex-common = { path = "../common", features = [
"sandbox_summary",
] }
codex-core = { path = "../core" }
codex-protocol = { path = "../protocol" }
codex-file-search = { path = "../file-search" }
codex-login = { path = "../login" }
codex-ollama = { path = "../ollama" }
codex-protocol = { path = "../protocol" }
color-eyre = "0.6.3"
crossterm = { version = "0.28.1", features = ["bracketed-paste"] }
diffy = "0.4.2"
image = { version = "^0.25.6", default-features = false, features = ["jpeg"] }
lazy_static = "1"
once_cell = "1"
mcp-types = { path = "../mcp-types" }
once_cell = "1"
path-clean = "1.0.1"
rand = "0.9"
ratatui = { version = "0.29.0", features = [
"scrolling-regions",
"unstable-rendered-line-info",
@@ -75,7 +76,6 @@ tui-markdown = "0.3.3"
unicode-segmentation = "1.12.0"
unicode-width = "0.1"
uuid = "1"
rand = "0.9"
[target.'cfg(unix)'.dependencies]
libc = "0.2"

View File

@@ -387,6 +387,11 @@ impl App<'_> {
widget.open_model_popup();
}
}
SlashCommand::Approvals => {
if let AppState::Chat { widget } = &mut self.app_state {
widget.open_approvals_popup();
}
}
SlashCommand::Quit => {
break;
}
@@ -514,6 +519,16 @@ impl App<'_> {
widget.set_model(model);
}
}
AppEvent::UpdateAskForApprovalPolicy(policy) => {
if let AppState::Chat { widget } = &mut self.app_state {
widget.set_approval_policy(policy);
}
}
AppEvent::UpdateSandboxPolicy(policy) => {
if let AppState::Chat { widget } = &mut self.app_state {
widget.set_sandbox_policy(policy);
}
}
}
}
terminal.clear()?;

View File

@@ -6,6 +6,8 @@ use std::time::Duration;
use crate::app::ChatWidgetArgs;
use crate::slash_command::SlashCommand;
use codex_core::protocol::AskForApproval;
use codex_core::protocol::SandboxPolicy;
use codex_core::protocol_config_types::ReasoningEffort;
#[allow(clippy::large_enum_variant)]
@@ -70,4 +72,10 @@ pub(crate) enum AppEvent {
/// Update the current model slug in the running app and widget.
UpdateModel(String),
/// Update the current approval policy in the running app and widget.
UpdateAskForApprovalPolicy(AskForApproval),
/// Update the current sandbox policy in the running app and widget.
UpdateSandboxPolicy(SandboxPolicy),
}

View File

@@ -60,9 +60,13 @@ mod agent;
use self::agent::spawn_agent;
use crate::streaming::controller::AppEventHistorySink;
use crate::streaming::controller::StreamController;
use codex_common::approval_presets::ApprovalPreset;
use codex_common::approval_presets::builtin_approval_presets;
use codex_common::model_presets::ModelPreset;
use codex_common::model_presets::builtin_model_presets;
use codex_core::ConversationManager;
use codex_core::protocol::AskForApproval;
use codex_core::protocol::SandboxPolicy;
use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig;
use codex_file_search::FileMatch;
use uuid::Uuid;
@@ -733,6 +737,57 @@ impl ChatWidget<'_> {
);
}
/// Open a popup to choose the approvals mode (ask for approval policy + sandbox policy).
pub(crate) fn open_approvals_popup(&mut self) {
let current_approval = self.config.approval_policy;
let current_sandbox = self.config.sandbox_policy.clone();
let mut items: Vec<SelectionItem> = Vec::new();
let presets: Vec<ApprovalPreset> = builtin_approval_presets();
for preset in presets.into_iter() {
let is_current =
current_approval == preset.approval && current_sandbox == preset.sandbox;
let approval = preset.approval;
let sandbox = preset.sandbox.clone();
let name = preset.label.to_string();
let description = Some(preset.description.to_string());
let actions: Vec<SelectionAction> = vec![Box::new(move |tx| {
tx.send(AppEvent::CodexOp(Op::OverrideTurnContext {
cwd: None,
approval_policy: Some(approval),
sandbox_policy: Some(sandbox.clone()),
model: None,
effort: None,
summary: None,
}));
tx.send(AppEvent::UpdateAskForApprovalPolicy(approval));
tx.send(AppEvent::UpdateSandboxPolicy(sandbox.clone()));
})];
items.push(SelectionItem {
name,
description,
is_current,
actions,
});
}
self.bottom_pane.show_selection_view(
"Select Approvals Mode".to_string(),
None,
Some("Press Enter to confirm or Esc to go back".to_string()),
items,
);
}
/// Set the approval policy in the widget's config copy.
pub(crate) fn set_approval_policy(&mut self, policy: AskForApproval) {
self.config.approval_policy = policy;
}
/// Set the sandbox policy in the widget's config copy.
pub(crate) fn set_sandbox_policy(&mut self, policy: SandboxPolicy) {
self.config.sandbox_policy = policy;
}
/// Set the reasoning effort in the widget's config copy.
pub(crate) fn set_reasoning_effort(&mut self, effort: ReasoningEffortConfig) {
self.config.model_reasoning_effort = effort;

View File

@@ -13,6 +13,7 @@ pub enum SlashCommand {
// DO NOT ALPHA-SORT! Enum order is presentation order in the popup, so
// more frequently used commands should be listed first.
Model,
Approvals,
New,
Init,
Compact,
@@ -38,6 +39,7 @@ impl SlashCommand {
SlashCommand::Mention => "mention a file",
SlashCommand::Status => "show current session configuration and token usage",
SlashCommand::Model => "choose a model preset (model + reasoning effort)",
SlashCommand::Approvals => "choose what Codex can do without approval",
SlashCommand::Mcp => "list configured MCP tools",
SlashCommand::Logout => "log out of Codex",
#[cfg(debug_assertions)]