feat: Constrain values for approval_policy (#7778)

Constrain `approval_policy` through new `admin_policy` config.

This PR will:
1. Add a `admin_policy` section to config, with a single field (for now)
`allowed_approval_policies`. This list constrains the set of
user-settable `approval_policy`s.
2. Introduce a new `Constrained<T>` type, which combines a current value
and a validator function. The validator function ensures disallowed
values are not set.
3. Change the type of `approval_policy` on `Config` and
`SessionConfiguration` from `AskForApproval` to
`Constrained<AskForApproval>`. The validator function is set by the
values passed into `allowed_approval_policies`.
4. `GenericDisplayRow`: add a `disabled_reason: Option<String>`. When
set, it disables selection of the value and indicates as such in the
menu. This also makes it unselectable with arrow keys or numbers. This
is used in the `/approvals` menu.

Follow ups are:
1. Do the same thing to `sandbox_policy`.
2. Propagate the allowed set of values through app-server for the
extension (though already this should prevent app-server from setting
this values, it's just that we want to disable UI elements that are
unsettable).

Happy to split this PR up if you prefer, into the logical numbered areas
above. Especially if there are parts we want to gavel on separately
(e.g. admin_policy).

Disabled full access:
<img width="1680" height="380" alt="image"
src="https://github.com/user-attachments/assets/1fb61c8c-1fcb-4dc4-8355-2293edb52ba0"
/>

Disabled `--yolo` on startup:
<img width="749" height="76" alt="image"
src="https://github.com/user-attachments/assets/0a1211a0-6eb1-40d6-a1d7-439c41e94ddb"
/>

CODEX-4087
This commit is contained in:
gt-oai
2025-12-17 16:19:27 +00:00
committed by GitHub
parent de3fa03e1c
commit 9352c6b235
20 changed files with 572 additions and 112 deletions

View File

@@ -1,3 +1,4 @@
use codex_core::config::Constrained;
use codex_core::features::Feature;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::EventMsg;
@@ -933,7 +934,7 @@ async fn handle_container_exec_autoapprove_from_config_records_tool_decision() {
let TestCodex { codex, .. } = test_codex()
.with_config(|config| {
config.approval_policy = AskForApproval::OnRequest;
config.approval_policy = Constrained::allow_any(AskForApproval::OnRequest);
config.sandbox_policy = SandboxPolicy::DangerFullAccess;
})
.build(&server)
@@ -982,7 +983,7 @@ async fn handle_container_exec_user_approved_records_tool_decision() {
let TestCodex { codex, .. } = test_codex()
.with_config(|config| {
config.approval_policy = AskForApproval::UnlessTrusted;
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
})
.build(&server)
.await
@@ -1040,7 +1041,7 @@ async fn handle_container_exec_user_approved_for_session_records_tool_decision()
let TestCodex { codex, .. } = test_codex()
.with_config(|config| {
config.approval_policy = AskForApproval::UnlessTrusted;
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
})
.build(&server)
.await
@@ -1098,7 +1099,7 @@ async fn handle_sandbox_error_user_approves_retry_records_tool_decision() {
let TestCodex { codex, .. } = test_codex()
.with_config(|config| {
config.approval_policy = AskForApproval::UnlessTrusted;
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
})
.build(&server)
.await
@@ -1156,7 +1157,7 @@ async fn handle_container_exec_user_denies_records_tool_decision() {
.await;
let TestCodex { codex, .. } = test_codex()
.with_config(|config| {
config.approval_policy = AskForApproval::UnlessTrusted;
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
})
.build(&server)
.await
@@ -1214,7 +1215,7 @@ async fn handle_sandbox_error_user_approves_for_session_records_tool_decision()
let TestCodex { codex, .. } = test_codex()
.with_config(|config| {
config.approval_policy = AskForApproval::UnlessTrusted;
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
})
.build(&server)
.await
@@ -1273,7 +1274,7 @@ async fn handle_sandbox_error_user_denies_records_tool_decision() {
let TestCodex { codex, .. } = test_codex()
.with_config(|config| {
config.approval_policy = AskForApproval::UnlessTrusted;
config.approval_policy = Constrained::allow_any(AskForApproval::UnlessTrusted);
})
.build(&server)
.await