Rename reject approval policy to granular (#14516)
This commit is contained in:
parent
d32820ab07
commit
b7dba72dbd
46 changed files with 456 additions and 419 deletions
|
|
@ -52,7 +52,7 @@
|
|||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"reject": {
|
||||
"granular": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
|
|
@ -81,9 +81,9 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"reject"
|
||||
"granular"
|
||||
],
|
||||
"title": "RejectAskForApproval",
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5192,7 +5192,7 @@
|
|||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"reject": {
|
||||
"granular": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
|
|
@ -5221,9 +5221,9 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"reject"
|
||||
"granular"
|
||||
],
|
||||
"title": "RejectAskForApproval",
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -638,7 +638,7 @@
|
|||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"reject": {
|
||||
"granular": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
|
|
@ -667,9 +667,9 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"reject"
|
||||
"granular"
|
||||
],
|
||||
"title": "RejectAskForApproval",
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@
|
|||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"reject": {
|
||||
"granular": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
|
|
@ -172,9 +172,9 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"reject"
|
||||
"granular"
|
||||
],
|
||||
"title": "RejectAskForApproval",
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"reject": {
|
||||
"granular": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
|
|
@ -44,9 +44,9 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"reject"
|
||||
"granular"
|
||||
],
|
||||
"title": "RejectAskForApproval",
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"reject": {
|
||||
"granular": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
|
|
@ -44,9 +44,9 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"reject"
|
||||
"granular"
|
||||
],
|
||||
"title": "RejectAskForApproval",
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"reject": {
|
||||
"granular": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
|
|
@ -48,9 +48,9 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"reject"
|
||||
"granular"
|
||||
],
|
||||
"title": "RejectAskForApproval",
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"reject": {
|
||||
"granular": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
|
|
@ -44,9 +44,9 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"reject"
|
||||
"granular"
|
||||
],
|
||||
"title": "RejectAskForApproval",
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"reject": {
|
||||
"granular": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
|
|
@ -48,9 +48,9 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"reject"
|
||||
"granular"
|
||||
],
|
||||
"title": "RejectAskForApproval",
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"reject": {
|
||||
"granular": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
|
|
@ -44,9 +44,9 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"reject"
|
||||
"granular"
|
||||
],
|
||||
"title": "RejectAskForApproval",
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"reject": {
|
||||
"granular": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
|
|
@ -48,9 +48,9 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"reject"
|
||||
"granular"
|
||||
],
|
||||
"title": "RejectAskForApproval",
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"reject": {
|
||||
"granular": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
|
|
@ -48,9 +48,9 @@
|
|||
}
|
||||
},
|
||||
"required": [
|
||||
"reject"
|
||||
"granular"
|
||||
],
|
||||
"title": "RejectAskForApproval",
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type AskForApproval = "untrusted" | "on-failure" | "on-request" | { "reject": { sandbox_approval: boolean, rules: boolean, skill_approval: boolean, request_permissions: boolean, mcp_elicitations: boolean, } } | "never";
|
||||
export type AskForApproval = "untrusted" | "on-failure" | "on-request" | { "granular": { sandbox_approval: boolean, rules: boolean, skill_approval: boolean, request_permissions: boolean, mcp_elicitations: boolean, } } | "never";
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ use codex_protocol::protocol::AskForApproval as CoreAskForApproval;
|
|||
use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo;
|
||||
use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot;
|
||||
use codex_protocol::protocol::ExecCommandStatus as CoreExecCommandStatus;
|
||||
use codex_protocol::protocol::GranularApprovalConfig as CoreGranularApprovalConfig;
|
||||
use codex_protocol::protocol::HookEventName as CoreHookEventName;
|
||||
use codex_protocol::protocol::HookExecutionMode as CoreHookExecutionMode;
|
||||
use codex_protocol::protocol::HookHandlerType as CoreHookHandlerType;
|
||||
|
|
@ -65,7 +66,6 @@ use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
|
|||
use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow;
|
||||
use codex_protocol::protocol::ReadOnlyAccess as CoreReadOnlyAccess;
|
||||
use codex_protocol::protocol::RealtimeAudioFrame as CoreRealtimeAudioFrame;
|
||||
use codex_protocol::protocol::RejectConfig as CoreRejectConfig;
|
||||
use codex_protocol::protocol::ReviewDecision as CoreReviewDecision;
|
||||
use codex_protocol::protocol::SessionSource as CoreSessionSource;
|
||||
use codex_protocol::protocol::SkillDependencies as CoreSkillDependencies;
|
||||
|
|
@ -201,8 +201,8 @@ pub enum AskForApproval {
|
|||
UnlessTrusted,
|
||||
OnFailure,
|
||||
OnRequest,
|
||||
#[experimental("askForApproval.reject")]
|
||||
Reject {
|
||||
#[experimental("askForApproval.granular")]
|
||||
Granular {
|
||||
sandbox_approval: bool,
|
||||
rules: bool,
|
||||
#[serde(default)]
|
||||
|
|
@ -220,13 +220,13 @@ impl AskForApproval {
|
|||
AskForApproval::UnlessTrusted => CoreAskForApproval::UnlessTrusted,
|
||||
AskForApproval::OnFailure => CoreAskForApproval::OnFailure,
|
||||
AskForApproval::OnRequest => CoreAskForApproval::OnRequest,
|
||||
AskForApproval::Reject {
|
||||
AskForApproval::Granular {
|
||||
sandbox_approval,
|
||||
rules,
|
||||
skill_approval,
|
||||
request_permissions,
|
||||
mcp_elicitations,
|
||||
} => CoreAskForApproval::Reject(CoreRejectConfig {
|
||||
} => CoreAskForApproval::Granular(CoreGranularApprovalConfig {
|
||||
sandbox_approval,
|
||||
rules,
|
||||
skill_approval,
|
||||
|
|
@ -244,12 +244,12 @@ impl From<CoreAskForApproval> for AskForApproval {
|
|||
CoreAskForApproval::UnlessTrusted => AskForApproval::UnlessTrusted,
|
||||
CoreAskForApproval::OnFailure => AskForApproval::OnFailure,
|
||||
CoreAskForApproval::OnRequest => AskForApproval::OnRequest,
|
||||
CoreAskForApproval::Reject(reject_config) => AskForApproval::Reject {
|
||||
sandbox_approval: reject_config.sandbox_approval,
|
||||
rules: reject_config.rules,
|
||||
skill_approval: reject_config.skill_approval,
|
||||
request_permissions: reject_config.request_permissions,
|
||||
mcp_elicitations: reject_config.mcp_elicitations,
|
||||
CoreAskForApproval::Granular(granular_config) => AskForApproval::Granular {
|
||||
sandbox_approval: granular_config.sandbox_approval,
|
||||
rules: granular_config.rules,
|
||||
skill_approval: granular_config.skill_approval,
|
||||
request_permissions: granular_config.request_permissions,
|
||||
mcp_elicitations: granular_config.mcp_elicitations,
|
||||
},
|
||||
CoreAskForApproval::Never => AskForApproval::Never,
|
||||
}
|
||||
|
|
@ -6192,8 +6192,8 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn ask_for_approval_reject_round_trips_request_permissions_flag() {
|
||||
let v2_policy = AskForApproval::Reject {
|
||||
fn ask_for_approval_granular_round_trips_request_permissions_flag() {
|
||||
let v2_policy = AskForApproval::Granular {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
|
|
@ -6204,7 +6204,7 @@ mod tests {
|
|||
let core_policy = v2_policy.to_core();
|
||||
assert_eq!(
|
||||
core_policy,
|
||||
CoreAskForApproval::Reject(CoreRejectConfig {
|
||||
CoreAskForApproval::Granular(CoreGranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
|
|
@ -6218,19 +6218,19 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn ask_for_approval_reject_defaults_missing_optional_flags_to_false() {
|
||||
fn ask_for_approval_granular_defaults_missing_optional_flags_to_false() {
|
||||
let decoded = serde_json::from_value::<AskForApproval>(serde_json::json!({
|
||||
"reject": {
|
||||
"granular": {
|
||||
"sandbox_approval": true,
|
||||
"rules": false,
|
||||
"mcp_elicitations": true,
|
||||
}
|
||||
}))
|
||||
.expect("legacy reject approval policy should deserialize");
|
||||
.expect("granular approval policy should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
decoded,
|
||||
AskForApproval::Reject {
|
||||
AskForApproval::Granular {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
|
|
@ -6241,9 +6241,9 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn ask_for_approval_reject_is_marked_experimental() {
|
||||
fn ask_for_approval_granular_is_marked_experimental() {
|
||||
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(
|
||||
&AskForApproval::Reject {
|
||||
&AskForApproval::Granular {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
|
|
@ -6252,7 +6252,7 @@ mod tests {
|
|||
},
|
||||
);
|
||||
|
||||
assert_eq!(reason, Some("askForApproval.reject"));
|
||||
assert_eq!(reason, Some("askForApproval.granular"));
|
||||
assert_eq!(
|
||||
crate::experimental_api::ExperimentalApi::experimental_reason(
|
||||
&AskForApproval::OnRequest,
|
||||
|
|
@ -6262,11 +6262,11 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn profile_v2_reject_approval_policy_is_marked_experimental() {
|
||||
fn profile_v2_granular_approval_policy_is_marked_experimental() {
|
||||
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&ProfileV2 {
|
||||
model: None,
|
||||
model_provider: None,
|
||||
approval_policy: Some(AskForApproval::Reject {
|
||||
approval_policy: Some(AskForApproval::Granular {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
|
|
@ -6283,18 +6283,18 @@ mod tests {
|
|||
additional: HashMap::new(),
|
||||
});
|
||||
|
||||
assert_eq!(reason, Some("askForApproval.reject"));
|
||||
assert_eq!(reason, Some("askForApproval.granular"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_reject_approval_policy_is_marked_experimental() {
|
||||
fn config_granular_approval_policy_is_marked_experimental() {
|
||||
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&Config {
|
||||
model: None,
|
||||
review_model: None,
|
||||
model_context_window: None,
|
||||
model_auto_compact_token_limit: None,
|
||||
model_provider: None,
|
||||
approval_policy: Some(AskForApproval::Reject {
|
||||
approval_policy: Some(AskForApproval::Granular {
|
||||
sandbox_approval: false,
|
||||
rules: true,
|
||||
skill_approval: false,
|
||||
|
|
@ -6321,11 +6321,11 @@ mod tests {
|
|||
additional: HashMap::new(),
|
||||
});
|
||||
|
||||
assert_eq!(reason, Some("askForApproval.reject"));
|
||||
assert_eq!(reason, Some("askForApproval.granular"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_nested_profile_reject_approval_policy_is_marked_experimental() {
|
||||
fn config_nested_profile_granular_approval_policy_is_marked_experimental() {
|
||||
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(&Config {
|
||||
model: None,
|
||||
review_model: None,
|
||||
|
|
@ -6345,7 +6345,7 @@ mod tests {
|
|||
ProfileV2 {
|
||||
model: None,
|
||||
model_provider: None,
|
||||
approval_policy: Some(AskForApproval::Reject {
|
||||
approval_policy: Some(AskForApproval::Granular {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
|
|
@ -6374,14 +6374,14 @@ mod tests {
|
|||
additional: HashMap::new(),
|
||||
});
|
||||
|
||||
assert_eq!(reason, Some("askForApproval.reject"));
|
||||
assert_eq!(reason, Some("askForApproval.granular"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_requirements_reject_allowed_approval_policy_is_marked_experimental() {
|
||||
fn config_requirements_granular_allowed_approval_policy_is_marked_experimental() {
|
||||
let reason =
|
||||
crate::experimental_api::ExperimentalApi::experimental_reason(&ConfigRequirements {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Reject {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Granular {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: false,
|
||||
|
|
@ -6395,16 +6395,16 @@ mod tests {
|
|||
network: None,
|
||||
});
|
||||
|
||||
assert_eq!(reason, Some("askForApproval.reject"));
|
||||
assert_eq!(reason, Some("askForApproval.granular"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_request_thread_start_reject_approval_policy_is_marked_experimental() {
|
||||
fn client_request_thread_start_granular_approval_policy_is_marked_experimental() {
|
||||
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(
|
||||
&crate::ClientRequest::ThreadStart {
|
||||
request_id: crate::RequestId::Integer(1),
|
||||
params: ThreadStartParams {
|
||||
approval_policy: Some(AskForApproval::Reject {
|
||||
approval_policy: Some(AskForApproval::Granular {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
|
|
@ -6416,17 +6416,17 @@ mod tests {
|
|||
},
|
||||
);
|
||||
|
||||
assert_eq!(reason, Some("askForApproval.reject"));
|
||||
assert_eq!(reason, Some("askForApproval.granular"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_request_thread_resume_reject_approval_policy_is_marked_experimental() {
|
||||
fn client_request_thread_resume_granular_approval_policy_is_marked_experimental() {
|
||||
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(
|
||||
&crate::ClientRequest::ThreadResume {
|
||||
request_id: crate::RequestId::Integer(2),
|
||||
params: ThreadResumeParams {
|
||||
thread_id: "thr_123".to_string(),
|
||||
approval_policy: Some(AskForApproval::Reject {
|
||||
approval_policy: Some(AskForApproval::Granular {
|
||||
sandbox_approval: false,
|
||||
rules: true,
|
||||
skill_approval: false,
|
||||
|
|
@ -6438,17 +6438,17 @@ mod tests {
|
|||
},
|
||||
);
|
||||
|
||||
assert_eq!(reason, Some("askForApproval.reject"));
|
||||
assert_eq!(reason, Some("askForApproval.granular"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_request_thread_fork_reject_approval_policy_is_marked_experimental() {
|
||||
fn client_request_thread_fork_granular_approval_policy_is_marked_experimental() {
|
||||
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(
|
||||
&crate::ClientRequest::ThreadFork {
|
||||
request_id: crate::RequestId::Integer(3),
|
||||
params: ThreadForkParams {
|
||||
thread_id: "thr_456".to_string(),
|
||||
approval_policy: Some(AskForApproval::Reject {
|
||||
approval_policy: Some(AskForApproval::Granular {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
|
|
@ -6460,18 +6460,18 @@ mod tests {
|
|||
},
|
||||
);
|
||||
|
||||
assert_eq!(reason, Some("askForApproval.reject"));
|
||||
assert_eq!(reason, Some("askForApproval.granular"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_request_turn_start_reject_approval_policy_is_marked_experimental() {
|
||||
fn client_request_turn_start_granular_approval_policy_is_marked_experimental() {
|
||||
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(
|
||||
&crate::ClientRequest::TurnStart {
|
||||
request_id: crate::RequestId::Integer(4),
|
||||
params: TurnStartParams {
|
||||
thread_id: "thr_123".to_string(),
|
||||
input: Vec::new(),
|
||||
approval_policy: Some(AskForApproval::Reject {
|
||||
approval_policy: Some(AskForApproval::Granular {
|
||||
sandbox_approval: false,
|
||||
rules: true,
|
||||
skill_approval: false,
|
||||
|
|
@ -6483,7 +6483,7 @@ mod tests {
|
|||
},
|
||||
);
|
||||
|
||||
assert_eq!(reason, Some("askForApproval.reject"));
|
||||
assert_eq!(reason, Some("askForApproval.granular"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -928,7 +928,7 @@ Only the granted subset matters on the wire. Any permissions omitted from `resul
|
|||
|
||||
Within the same turn, granted permissions are sticky: later shell-like tool calls can automatically reuse the granted subset without reissuing a separate permission request.
|
||||
|
||||
If the session approval policy uses `Reject` with `request_permissions: true`, standalone `request_permissions` tool calls are auto-denied and no `item/permissions/requestApproval` prompt is sent. Inline `with_additional_permissions` command requests remain controlled by `sandbox_approval`, and any previously granted permissions remain sticky for later shell-like calls in the same turn.
|
||||
If the session approval policy uses `Granular` with `request_permissions: false`, standalone `request_permissions` tool calls are auto-denied and no `item/permissions/requestApproval` prompt is sent. Inline `with_additional_permissions` command requests remain controlled by `sandbox_approval`, and any previously granted permissions remain sticky for later shell-like calls in the same turn.
|
||||
|
||||
### Dynamic tool calls (experimental)
|
||||
|
||||
|
|
@ -1319,7 +1319,7 @@ Examples of descriptor strings:
|
|||
|
||||
- `mock/experimentalMethod` (method-level gate)
|
||||
- `thread/start.mockExperimentalField` (field-level gate)
|
||||
- `askForApproval.reject` (enum-variant gate, for `approvalPolicy: { "reject": ... }`)
|
||||
- `askForApproval.granular` (enum-variant gate, for `approvalPolicy: { "granular": ... }`)
|
||||
|
||||
### For maintainers: Adding experimental fields and methods
|
||||
|
||||
|
|
@ -1341,8 +1341,8 @@ Enum variants can be gated too:
|
|||
```rust
|
||||
#[derive(ExperimentalApi)]
|
||||
enum AskForApproval {
|
||||
#[experimental("askForApproval.reject")]
|
||||
Reject { /* ... */ },
|
||||
#[experimental("askForApproval.granular")]
|
||||
Granular { /* ... */ },
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -159,7 +159,8 @@ async fn thread_start_without_dynamic_tools_allows_without_experimental_api_capa
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_start_reject_approval_policy_requires_experimental_api_capability() -> Result<()> {
|
||||
async fn thread_start_granular_approval_policy_requires_experimental_api_capability() -> Result<()>
|
||||
{
|
||||
let server = create_mock_responses_server_sequence_unchecked(Vec::new()).await;
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(codex_home.path(), &server.uri())?;
|
||||
|
|
@ -180,7 +181,7 @@ async fn thread_start_reject_approval_policy_requires_experimental_api_capabilit
|
|||
|
||||
let request_id = mcp
|
||||
.send_thread_start_request(ThreadStartParams {
|
||||
approval_policy: Some(AskForApproval::Reject {
|
||||
approval_policy: Some(AskForApproval::Granular {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
|
|
@ -196,7 +197,7 @@ async fn thread_start_reject_approval_policy_requires_experimental_api_capabilit
|
|||
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
assert_experimental_capability_error(error, "askForApproval.reject");
|
||||
assert_experimental_capability_error(error, "askForApproval.granular");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -231,14 +231,14 @@
|
|||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"description": "Fine-grained rejection controls for approval prompts.\n\nWhen a field is `true`, prompts of that category are automatically rejected instead of shown to the user.",
|
||||
"description": "Fine-grained controls for individual approval flows.\n\nWhen a field is `true`, commands in that category are allowed. When it is `false`, those requests are automatically rejected instead of shown to the user.",
|
||||
"properties": {
|
||||
"reject": {
|
||||
"$ref": "#/definitions/RejectConfig"
|
||||
"granular": {
|
||||
"$ref": "#/definitions/GranularApprovalConfig"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"reject"
|
||||
"granular"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
|
@ -369,6 +369,9 @@
|
|||
"enable_request_compression": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"exec_permission_approvals": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"experimental_use_freeform_apply_patch": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
|
@ -651,6 +654,38 @@
|
|||
},
|
||||
"type": "object"
|
||||
},
|
||||
"GranularApprovalConfig": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"description": "Whether to allow MCP elicitation prompts.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"request_permissions": {
|
||||
"default": false,
|
||||
"description": "Whether to allow prompts triggered by the `request_permissions` tool.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"rules": {
|
||||
"description": "Whether to allow prompts triggered by execpolicy `prompt` rules.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"sandbox_approval": {
|
||||
"description": "Whether to allow shell command approval requests, including inline `with_additional_permissions` and `require_escalated` requests.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"skill_approval": {
|
||||
"default": false,
|
||||
"description": "Whether to allow approval prompts triggered by skill script execution.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"mcp_elicitations",
|
||||
"rules",
|
||||
"sandbox_approval"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"History": {
|
||||
"additionalProperties": false,
|
||||
"description": "Settings that govern if and what will be written to `~/.codex/history.jsonl`.",
|
||||
|
|
@ -1336,38 +1371,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"RejectConfig": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"description": "Reject MCP elicitation prompts.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"request_permissions": {
|
||||
"default": false,
|
||||
"description": "Reject `request_permissions` tool requests.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"rules": {
|
||||
"description": "Reject prompts triggered by execpolicy `prompt` rules.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"sandbox_approval": {
|
||||
"description": "Reject shell command approval requests, including inline `with_additional_permissions` and `require_escalated` requests.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"skill_approval": {
|
||||
"default": false,
|
||||
"description": "Reject approval prompts triggered by skill script execution.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"mcp_elicitations",
|
||||
"rules",
|
||||
"sandbox_approval"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SandboxMode": {
|
||||
"enum": [
|
||||
"read-only",
|
||||
|
|
@ -1877,6 +1880,9 @@
|
|||
"enable_request_compression": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"exec_permission_approvals": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"experimental_use_freeform_apply_patch": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2912,8 +2912,8 @@ impl Session {
|
|||
scope: PermissionGrantScope::Turn,
|
||||
});
|
||||
}
|
||||
AskForApproval::Reject(reject_config)
|
||||
if reject_config.rejects_request_permissions() =>
|
||||
AskForApproval::Granular(granular_config)
|
||||
if !granular_config.allows_request_permissions() =>
|
||||
{
|
||||
return Some(RequestPermissionsResponse {
|
||||
permissions: PermissionProfile::default(),
|
||||
|
|
@ -2923,7 +2923,7 @@ impl Session {
|
|||
AskForApproval::OnFailure
|
||||
| AskForApproval::OnRequest
|
||||
| AskForApproval::UnlessTrusted
|
||||
| AskForApproval::Reject(_) => {}
|
||||
| AskForApproval::Granular(_) => {}
|
||||
}
|
||||
|
||||
let (tx_response, rx_response) = oneshot::channel();
|
||||
|
|
@ -3381,7 +3381,9 @@ impl Session {
|
|||
turn_context.approval_policy.value(),
|
||||
self.services.exec_policy.current().as_ref(),
|
||||
&turn_context.cwd,
|
||||
turn_context.features.enabled(Feature::RequestPermissions),
|
||||
turn_context
|
||||
.features
|
||||
.enabled(Feature::ExecPermissionApprovals),
|
||||
)
|
||||
.into_text(),
|
||||
);
|
||||
|
|
@ -5565,7 +5567,7 @@ pub(crate) async fn run_turn(
|
|||
AskForApproval::UnlessTrusted
|
||||
| AskForApproval::OnFailure
|
||||
| AskForApproval::OnRequest
|
||||
| AskForApproval::Reject(_) => "default",
|
||||
| AskForApproval::Granular(_) => "default",
|
||||
}
|
||||
.to_string();
|
||||
let session_start_request = codex_hooks::SessionStartRequest {
|
||||
|
|
@ -5714,7 +5716,7 @@ pub(crate) async fn run_turn(
|
|||
AskForApproval::UnlessTrusted
|
||||
| AskForApproval::OnFailure
|
||||
| AskForApproval::OnRequest
|
||||
| AskForApproval::Reject(_) => "default",
|
||||
| AskForApproval::Granular(_) => "default",
|
||||
}
|
||||
.to_string();
|
||||
let stop_request = codex_hooks::StopRequest {
|
||||
|
|
|
|||
|
|
@ -2231,18 +2231,18 @@ async fn notify_request_permissions_response_ignores_unmatched_call_id() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_permissions_emits_event_when_reject_policy_allows_requests() {
|
||||
async fn request_permissions_emits_event_when_granular_policy_allows_requests() {
|
||||
let (session, mut turn_context, rx) = make_session_and_context_with_rx().await;
|
||||
*session.active_turn.lock().await = Some(ActiveTurn::default());
|
||||
Arc::get_mut(&mut turn_context)
|
||||
.expect("single turn context ref")
|
||||
.approval_policy
|
||||
.set(crate::protocol::AskForApproval::Reject(
|
||||
crate::protocol::RejectConfig {
|
||||
.set(crate::protocol::AskForApproval::Granular(
|
||||
crate::protocol::GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
},
|
||||
))
|
||||
|
|
@ -2306,19 +2306,19 @@ async fn request_permissions_emits_event_when_reject_policy_allows_requests() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_permissions_is_auto_denied_when_reject_policy_blocks_tool_requests() {
|
||||
async fn request_permissions_is_auto_denied_when_granular_policy_blocks_tool_requests() {
|
||||
let (session, mut turn_context, rx) = make_session_and_context_with_rx().await;
|
||||
*session.active_turn.lock().await = Some(ActiveTurn::default());
|
||||
Arc::get_mut(&mut turn_context)
|
||||
.expect("single turn context ref")
|
||||
.approval_policy
|
||||
.set(crate::protocol::AskForApproval::Reject(
|
||||
crate::protocol::RejectConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: false,
|
||||
.set(crate::protocol::AskForApproval::Granular(
|
||||
crate::protocol::GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: true,
|
||||
},
|
||||
))
|
||||
.expect("test setup should allow updating approval policy");
|
||||
|
|
@ -2355,7 +2355,7 @@ async fn request_permissions_is_auto_denied_when_reject_policy_blocks_tool_reque
|
|||
tokio::time::timeout(StdDuration::from_millis(100), rx.recv())
|
||||
.await
|
||||
.is_err(),
|
||||
"request_permissions should not emit an event when reject.request_permissions is set"
|
||||
"request_permissions should not emit an event when granular.request_permissions is false"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ async fn guardian_allows_shell_additional_permissions_requests_past_policy_valid
|
|||
.expect("test setup should allow enabling guardian approvals");
|
||||
session
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test setup should allow enabling request permissions");
|
||||
turn_context_raw
|
||||
.sandbox_policy
|
||||
|
|
@ -191,7 +191,7 @@ async fn guardian_allows_unified_exec_additional_permissions_requests_past_polic
|
|||
.expect("test setup should allow enabling guardian approvals");
|
||||
session
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test setup should allow enabling request permissions");
|
||||
let session = Arc::new(session);
|
||||
let turn_context = Arc::new(turn_context_raw);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ fn build_permissions_update_item(
|
|||
next.approval_policy.value(),
|
||||
exec_policy,
|
||||
&next.cwd,
|
||||
next.features.enabled(Feature::RequestPermissions),
|
||||
next.features.enabled(Feature::ExecPermissionApprovals),
|
||||
))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ use shlex::try_join as shlex_try_join;
|
|||
const PROMPT_CONFLICT_REASON: &str =
|
||||
"approval required by policy, but AskForApproval is set to Never";
|
||||
const REJECT_SANDBOX_APPROVAL_REASON: &str =
|
||||
"approval required by policy, but AskForApproval::Reject.sandbox_approval is set";
|
||||
"approval required by policy, but AskForApproval::Granular.sandbox_approval is false";
|
||||
const REJECT_RULES_APPROVAL_REASON: &str =
|
||||
"approval required by policy rule, but AskForApproval::Reject.rules is set";
|
||||
"approval required by policy rule, but AskForApproval::Granular.rules is false";
|
||||
const RULES_DIR_NAME: &str = "rules";
|
||||
const RULE_EXTENSION: &str = "rules";
|
||||
const DEFAULT_POLICY_FILE: &str = "default.rules";
|
||||
|
|
@ -104,7 +104,7 @@ fn is_policy_match(rule_match: &RuleMatch) -> bool {
|
|||
/// current prompt to the user.
|
||||
///
|
||||
/// `prompt_is_rule` distinguishes policy-rule prompts from sandbox/escalation
|
||||
/// prompts so `Reject.rules` and `Reject.sandbox_approval` are honored
|
||||
/// prompts so granular `rules` and `sandbox_approval` settings are honored
|
||||
/// independently. When both are present, policy-rule prompts take precedence.
|
||||
pub(crate) fn prompt_is_rejected_by_policy(
|
||||
approval_policy: AskForApproval,
|
||||
|
|
@ -115,14 +115,14 @@ pub(crate) fn prompt_is_rejected_by_policy(
|
|||
AskForApproval::OnFailure => None,
|
||||
AskForApproval::OnRequest => None,
|
||||
AskForApproval::UnlessTrusted => None,
|
||||
AskForApproval::Reject(reject_config) => {
|
||||
AskForApproval::Granular(granular_config) => {
|
||||
if prompt_is_rule {
|
||||
if reject_config.rejects_rules_approval() {
|
||||
if !granular_config.allows_rules_approval() {
|
||||
Some(REJECT_RULES_APPROVAL_REASON)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if reject_config.rejects_sandbox_approval() {
|
||||
} else if !granular_config.allows_sandbox_approval() {
|
||||
Some(REJECT_SANDBOX_APPROVAL_REASON)
|
||||
} else {
|
||||
None
|
||||
|
|
@ -519,7 +519,7 @@ pub fn render_decision_for_unmatched_command(
|
|||
AskForApproval::OnFailure
|
||||
| AskForApproval::OnRequest
|
||||
| AskForApproval::UnlessTrusted
|
||||
| AskForApproval::Reject(_) => Decision::Prompt,
|
||||
| AskForApproval::Granular(_) => Decision::Prompt,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -554,7 +554,7 @@ pub fn render_decision_for_unmatched_command(
|
|||
}
|
||||
}
|
||||
}
|
||||
AskForApproval::Reject(_) => match file_system_sandbox_policy.kind {
|
||||
AskForApproval::Granular(_) => match file_system_sandbox_policy.kind {
|
||||
FileSystemSandboxKind::Unrestricted | FileSystemSandboxKind::ExternalSandbox => {
|
||||
// Mirror on-request behavior for unmatched commands; prompt-vs-reject is handled
|
||||
// by `prompt_is_rejected_by_policy`.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use codex_protocol::permissions::FileSystemPath;
|
|||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::RejectConfig;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
|
@ -766,18 +766,18 @@ async fn exec_approval_requirement_respects_approval_policy() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn unmatched_reject_policy_still_prompts_for_restricted_sandbox_escalation() {
|
||||
fn unmatched_granular_policy_still_prompts_for_restricted_sandbox_escalation() {
|
||||
let command = vec!["madeup-cmd".to_string()];
|
||||
|
||||
assert_eq!(
|
||||
Decision::Prompt,
|
||||
render_decision_for_unmatched_command(
|
||||
AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
}),
|
||||
&SandboxPolicy::new_read_only_policy(),
|
||||
&read_only_file_system_sandbox_policy(),
|
||||
|
|
@ -807,19 +807,19 @@ fn unmatched_on_request_uses_split_filesystem_policy_for_escalation_prompts() {
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn exec_approval_requirement_rejects_unmatched_sandbox_escalation_when_sandbox_rejection_enabled()
|
||||
async fn exec_approval_requirement_rejects_unmatched_sandbox_escalation_when_granular_sandbox_is_disabled()
|
||||
{
|
||||
let command = vec!["madeup-cmd".to_string()];
|
||||
|
||||
let requirement = ExecPolicyManager::default()
|
||||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &command,
|
||||
approval_policy: AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
approval_policy: AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: false,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
}),
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
file_system_sandbox_policy: &read_only_file_system_sandbox_policy(),
|
||||
|
|
@ -853,12 +853,12 @@ async fn mixed_rule_and_sandbox_prompt_prioritizes_rule_for_rejection_decision()
|
|||
let requirement = manager
|
||||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &command,
|
||||
approval_policy: AskForApproval::Reject(RejectConfig {
|
||||
approval_policy: AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
}),
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
file_system_sandbox_policy: &read_only_file_system_sandbox_policy(),
|
||||
|
|
@ -874,7 +874,7 @@ async fn mixed_rule_and_sandbox_prompt_prioritizes_rule_for_rejection_decision()
|
|||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mixed_rule_and_sandbox_prompt_rejects_when_rules_rejection_enabled() {
|
||||
async fn mixed_rule_and_sandbox_prompt_rejects_when_granular_rules_are_disabled() {
|
||||
let policy_src = r#"prefix_rule(pattern=["git"], decision="prompt")"#;
|
||||
let mut parser = PolicyParser::new();
|
||||
parser
|
||||
|
|
@ -890,12 +890,12 @@ async fn mixed_rule_and_sandbox_prompt_rejects_when_rules_rejection_enabled() {
|
|||
let requirement = manager
|
||||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &command,
|
||||
approval_policy: AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: false,
|
||||
rules: true,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
approval_policy: AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
}),
|
||||
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
|
||||
file_system_sandbox_policy: &read_only_file_system_sandbox_policy(),
|
||||
|
|
|
|||
|
|
@ -95,8 +95,8 @@ pub enum Feature {
|
|||
ShellZshFork,
|
||||
/// Include the freeform apply_patch tool.
|
||||
ApplyPatchFreeform,
|
||||
/// Allow requesting additional filesystem permissions while staying sandboxed.
|
||||
RequestPermissions,
|
||||
/// Allow exec tools to request additional permissions while staying sandboxed.
|
||||
ExecPermissionApprovals,
|
||||
/// Enable Claude-style lifecycle hooks loaded from hooks.json files.
|
||||
CodexHooks,
|
||||
/// Expose the built-in request_permissions tool.
|
||||
|
|
@ -626,8 +626,8 @@ pub const FEATURES: &[FeatureSpec] = &[
|
|||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::RequestPermissions,
|
||||
key: "request_permissions",
|
||||
id: Feature::ExecPermissionApprovals,
|
||||
key: "exec_permission_approvals",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ const ALIASES: &[Alias] = &[
|
|||
legacy_key: "include_apply_patch_tool",
|
||||
feature: Feature::ApplyPatchFreeform,
|
||||
},
|
||||
Alias {
|
||||
legacy_key: "request_permissions",
|
||||
feature: Feature::ExecPermissionApprovals,
|
||||
},
|
||||
Alias {
|
||||
legacy_key: "web_search",
|
||||
feature: Feature::WebSearchRequest,
|
||||
|
|
|
|||
|
|
@ -80,8 +80,11 @@ fn guardian_approval_is_experimental_and_user_toggleable() {
|
|||
|
||||
#[test]
|
||||
fn request_permissions_is_under_development() {
|
||||
assert_eq!(Feature::RequestPermissions.stage(), Stage::UnderDevelopment);
|
||||
assert_eq!(Feature::RequestPermissions.default_enabled(), false);
|
||||
assert_eq!(
|
||||
Feature::ExecPermissionApprovals.stage(),
|
||||
Stage::UnderDevelopment
|
||||
);
|
||||
assert_eq!(Feature::ExecPermissionApprovals.default_enabled(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ fn elicitation_is_rejected_by_policy(approval_policy: AskForApproval) -> bool {
|
|||
AskForApproval::OnFailure => false,
|
||||
AskForApproval::OnRequest => false,
|
||||
AskForApproval::UnlessTrusted => false,
|
||||
AskForApproval::Reject(reject_config) => reject_config.rejects_mcp_elicitations(),
|
||||
AskForApproval::Granular(granular_config) => !granular_config.allows_mcp_elicitations(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use super::*;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_protocol::protocol::McpAuthStatus;
|
||||
use codex_protocol::protocol::RejectConfig;
|
||||
use rmcp::model::JsonObject;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -61,7 +61,7 @@ fn create_codex_apps_tools_cache_context(
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn elicitation_reject_policy_defaults_to_prompting() {
|
||||
fn elicitation_granular_policy_defaults_to_prompting() {
|
||||
assert!(!elicitation_is_rejected_by_policy(
|
||||
AskForApproval::OnFailure
|
||||
));
|
||||
|
|
@ -71,27 +71,27 @@ fn elicitation_reject_policy_defaults_to_prompting() {
|
|||
assert!(!elicitation_is_rejected_by_policy(
|
||||
AskForApproval::UnlessTrusted
|
||||
));
|
||||
assert!(!elicitation_is_rejected_by_policy(AskForApproval::Reject(
|
||||
RejectConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
assert!(elicitation_is_rejected_by_policy(AskForApproval::Granular(
|
||||
GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: false,
|
||||
}
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn elicitation_reject_policy_respects_never_and_reject_config() {
|
||||
fn elicitation_granular_policy_respects_never_and_config() {
|
||||
assert!(elicitation_is_rejected_by_policy(AskForApproval::Never));
|
||||
assert!(elicitation_is_rejected_by_policy(AskForApproval::Reject(
|
||||
RejectConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: true,
|
||||
assert!(elicitation_is_rejected_by_policy(AskForApproval::Granular(
|
||||
GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: false,
|
||||
}
|
||||
)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ pub fn assess_patch_safety(
|
|||
AskForApproval::OnFailure
|
||||
| AskForApproval::Never
|
||||
| AskForApproval::OnRequest
|
||||
| AskForApproval::Reject(_) => {
|
||||
| AskForApproval::Granular(_) => {
|
||||
// Continue to see if this can be auto-approved.
|
||||
}
|
||||
// TODO(ragona): I'm not sure this is actually correct? I believe in this case
|
||||
|
|
@ -56,7 +56,7 @@ pub fn assess_patch_safety(
|
|||
let rejects_sandbox_approval = matches!(policy, AskForApproval::Never)
|
||||
|| matches!(
|
||||
policy,
|
||||
AskForApproval::Reject(reject_config) if reject_config.sandbox_approval
|
||||
AskForApproval::Granular(granular_config) if !granular_config.sandbox_approval
|
||||
);
|
||||
|
||||
// Even though the patch appears to be constrained to writable paths, it is
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use codex_protocol::protocol::FileSystemAccessMode;
|
|||
use codex_protocol::protocol::FileSystemPath;
|
||||
use codex_protocol::protocol::FileSystemSandboxEntry;
|
||||
use codex_protocol::protocol::FileSystemSpecialPath;
|
||||
use codex_protocol::protocol::RejectConfig;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
|
|
@ -87,7 +87,7 @@ fn external_sandbox_auto_approves_in_on_request() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn reject_with_all_flags_false_matches_on_request_for_out_of_root_patch() {
|
||||
fn granular_with_all_flags_true_matches_on_request_for_out_of_root_patch() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let cwd = tmp.path().to_path_buf();
|
||||
let parent = cwd.parent().unwrap().to_path_buf();
|
||||
|
|
@ -115,12 +115,12 @@ fn reject_with_all_flags_false_matches_on_request_for_out_of_root_patch() {
|
|||
assert_eq!(
|
||||
assess_patch_safety(
|
||||
&add_outside,
|
||||
AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
}),
|
||||
&policy_workspace_only,
|
||||
&FileSystemSandboxPolicy::from(&policy_workspace_only),
|
||||
|
|
@ -132,7 +132,7 @@ fn reject_with_all_flags_false_matches_on_request_for_out_of_root_patch() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn reject_sandbox_approval_rejects_out_of_root_patch() {
|
||||
fn granular_sandbox_approval_false_rejects_out_of_root_patch() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let cwd = tmp.path().to_path_buf();
|
||||
let parent = cwd.parent().unwrap().to_path_buf();
|
||||
|
|
@ -149,12 +149,12 @@ fn reject_sandbox_approval_rejects_out_of_root_patch() {
|
|||
assert_eq!(
|
||||
assess_patch_safety(
|
||||
&add_outside,
|
||||
AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: false,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
}),
|
||||
&policy_workspace_only,
|
||||
&FileSystemSandboxPolicy::from(&policy_workspace_only),
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ pub(crate) fn normalize_and_validate_additional_permissions(
|
|||
&& (uses_additional_permissions || additional_permissions.is_some())
|
||||
{
|
||||
return Err(
|
||||
"additional permissions are disabled; enable `features.request_permissions` before using `with_additional_permissions`"
|
||||
"additional permissions are disabled; enable `features.exec_permission_approvals` before using `with_additional_permissions`"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
|
@ -239,7 +239,7 @@ mod tests {
|
|||
use codex_protocol::models::NetworkPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::RejectConfig;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::tempdir;
|
||||
|
|
@ -266,18 +266,18 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn preapproved_permissions_work_when_request_permissions_tool_is_enabled_without_inline_feature()
|
||||
fn preapproved_permissions_work_when_request_permissions_tool_is_enabled_without_exec_permission_approvals_feature()
|
||||
{
|
||||
let cwd = tempdir().expect("tempdir");
|
||||
|
||||
let normalized = normalize_and_validate_additional_permissions(
|
||||
false,
|
||||
AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: false,
|
||||
AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: true,
|
||||
}),
|
||||
SandboxPermissions::WithAdditionalPermissions,
|
||||
Some(network_permissions()),
|
||||
|
|
@ -290,7 +290,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn fresh_additional_permissions_still_require_request_permissions_feature() {
|
||||
fn fresh_additional_permissions_still_require_exec_permission_approvals_feature() {
|
||||
let cwd = tempdir().expect("tempdir");
|
||||
|
||||
let err = normalize_and_validate_additional_permissions(
|
||||
|
|
@ -305,7 +305,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
err,
|
||||
"additional permissions are disabled; enable `features.request_permissions` before using `with_additional_permissions`"
|
||||
"additional permissions are disabled; enable `features.exec_permission_approvals` before using `with_additional_permissions`"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -336,7 +336,8 @@ impl ShellHandler {
|
|||
}
|
||||
}
|
||||
|
||||
let request_permission_enabled = session.features().enabled(Feature::RequestPermissions);
|
||||
let exec_permission_approvals_enabled =
|
||||
session.features().enabled(Feature::ExecPermissionApprovals);
|
||||
let requested_additional_permissions = additional_permissions.clone();
|
||||
let effective_additional_permissions = apply_granted_turn_permissions(
|
||||
session.as_ref(),
|
||||
|
|
@ -344,7 +345,7 @@ impl ShellHandler {
|
|||
additional_permissions,
|
||||
)
|
||||
.await;
|
||||
let additional_permissions_allowed = request_permission_enabled
|
||||
let additional_permissions_allowed = exec_permission_approvals_enabled
|
||||
|| (session.features().enabled(Feature::RequestPermissionsTool)
|
||||
&& effective_additional_permissions.permissions_preapproved);
|
||||
let normalized_additional_permissions = implicit_granted_permissions(
|
||||
|
|
|
|||
|
|
@ -170,8 +170,8 @@ impl ToolHandler for UnifiedExecHandler {
|
|||
..
|
||||
} = args;
|
||||
|
||||
let request_permission_enabled =
|
||||
session.features().enabled(Feature::RequestPermissions);
|
||||
let exec_permission_approvals_enabled =
|
||||
session.features().enabled(Feature::ExecPermissionApprovals);
|
||||
let requested_additional_permissions = additional_permissions.clone();
|
||||
let effective_additional_permissions = apply_granted_turn_permissions(
|
||||
context.session.as_ref(),
|
||||
|
|
@ -179,7 +179,7 @@ impl ToolHandler for UnifiedExecHandler {
|
|||
additional_permissions,
|
||||
)
|
||||
.await;
|
||||
let additional_permissions_allowed = request_permission_enabled
|
||||
let additional_permissions_allowed = exec_permission_approvals_enabled
|
||||
|| (session.features().enabled(Feature::RequestPermissionsTool)
|
||||
&& effective_additional_permissions.permissions_preapproved);
|
||||
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ impl Approvable<ApplyPatchRequest> for ApplyPatchRuntime {
|
|||
fn wants_no_sandbox_approval(&self, policy: AskForApproval) -> bool {
|
||||
match policy {
|
||||
AskForApproval::Never => false,
|
||||
AskForApproval::Reject(reject_config) => !reject_config.rejects_sandbox_approval(),
|
||||
AskForApproval::Granular(granular_config) => granular_config.allows_sandbox_approval(),
|
||||
AskForApproval::OnFailure => true,
|
||||
AskForApproval::OnRequest => true,
|
||||
AskForApproval::UnlessTrusted => true,
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
use super::*;
|
||||
use codex_protocol::protocol::RejectConfig;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn wants_no_sandbox_approval_reject_respects_sandbox_flag() {
|
||||
fn wants_no_sandbox_approval_granular_respects_sandbox_flag() {
|
||||
let runtime = ApplyPatchRuntime::new();
|
||||
assert!(runtime.wants_no_sandbox_approval(AskForApproval::OnRequest));
|
||||
assert!(
|
||||
!runtime.wants_no_sandbox_approval(AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
!runtime.wants_no_sandbox_approval(AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: false,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
}))
|
||||
);
|
||||
assert!(
|
||||
runtime.wants_no_sandbox_approval(AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
runtime.wants_no_sandbox_approval(AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,11 +65,11 @@ pub(crate) struct PreparedUnifiedExecZshFork {
|
|||
const PROMPT_CONFLICT_REASON: &str =
|
||||
"approval required by policy, but AskForApproval is set to Never";
|
||||
const REJECT_SANDBOX_APPROVAL_REASON: &str =
|
||||
"approval required by policy, but AskForApproval::Reject.sandbox_approval is set";
|
||||
"approval required by policy, but AskForApproval::Granular.sandbox_approval is false";
|
||||
const REJECT_RULES_APPROVAL_REASON: &str =
|
||||
"approval required by policy rule, but AskForApproval::Reject.rules is set";
|
||||
"approval required by policy rule, but AskForApproval::Granular.rules is false";
|
||||
const REJECT_SKILL_APPROVAL_REASON: &str =
|
||||
"approval required by skill, but AskForApproval::Reject.skill_approval is set";
|
||||
"approval required by skill, but AskForApproval::Granular.skill_approval is false";
|
||||
|
||||
fn approval_sandbox_permissions(
|
||||
sandbox_permissions: SandboxPermissions,
|
||||
|
|
@ -358,18 +358,18 @@ fn execve_prompt_is_rejected_by_policy(
|
|||
) -> Option<&'static str> {
|
||||
match (approval_policy, decision_source) {
|
||||
(AskForApproval::Never, _) => Some(PROMPT_CONFLICT_REASON),
|
||||
(AskForApproval::Reject(reject_config), DecisionSource::SkillScript { .. })
|
||||
if reject_config.rejects_skill_approval() =>
|
||||
(AskForApproval::Granular(granular_config), DecisionSource::SkillScript { .. })
|
||||
if !granular_config.allows_skill_approval() =>
|
||||
{
|
||||
Some(REJECT_SKILL_APPROVAL_REASON)
|
||||
}
|
||||
(AskForApproval::Reject(reject_config), DecisionSource::PrefixRule)
|
||||
if reject_config.rejects_rules_approval() =>
|
||||
(AskForApproval::Granular(granular_config), DecisionSource::PrefixRule)
|
||||
if !granular_config.allows_rules_approval() =>
|
||||
{
|
||||
Some(REJECT_RULES_APPROVAL_REASON)
|
||||
}
|
||||
(AskForApproval::Reject(reject_config), DecisionSource::UnmatchedCommandFallback)
|
||||
if reject_config.rejects_sandbox_approval() =>
|
||||
(AskForApproval::Granular(granular_config), DecisionSource::UnmatchedCommandFallback)
|
||||
if !granular_config.allows_sandbox_approval() =>
|
||||
{
|
||||
Some(REJECT_SANDBOX_APPROVAL_REASON)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ use crate::config::Permissions;
|
|||
use crate::config::types::ShellEnvironmentPolicy;
|
||||
use crate::exec::SandboxType;
|
||||
use crate::protocol::AskForApproval;
|
||||
use crate::protocol::GranularApprovalConfig;
|
||||
use crate::protocol::ReadOnlyAccess;
|
||||
use crate::protocol::RejectConfig;
|
||||
use crate::protocol::SandboxPolicy;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
#[cfg(target_os = "macos")]
|
||||
|
|
@ -105,12 +105,12 @@ fn execve_prompt_rejection_uses_skill_approval_for_skill_scripts() {
|
|||
|
||||
assert_eq!(
|
||||
super::execve_prompt_is_rejected_by_policy(
|
||||
AskForApproval::Reject(RejectConfig {
|
||||
AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
}),
|
||||
&decision_source,
|
||||
),
|
||||
|
|
@ -118,16 +118,16 @@ fn execve_prompt_rejection_uses_skill_approval_for_skill_scripts() {
|
|||
);
|
||||
assert_eq!(
|
||||
super::execve_prompt_is_rejected_by_policy(
|
||||
AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: true,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: false,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
}),
|
||||
&decision_source,
|
||||
),
|
||||
Some("approval required by skill, but AskForApproval::Reject.skill_approval is set"),
|
||||
Some("approval required by skill, but AskForApproval::Granular.skill_approval is false"),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -135,16 +135,16 @@ fn execve_prompt_rejection_uses_skill_approval_for_skill_scripts() {
|
|||
fn execve_prompt_rejection_keeps_prefix_rules_on_rules_flag() {
|
||||
assert_eq!(
|
||||
super::execve_prompt_is_rejected_by_policy(
|
||||
AskForApproval::Reject(RejectConfig {
|
||||
AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
rules: false,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
}),
|
||||
&super::DecisionSource::PrefixRule,
|
||||
),
|
||||
Some("approval required by policy rule, but AskForApproval::Reject.rules is set"),
|
||||
Some("approval required by policy rule, but AskForApproval::Granular.rules is false"),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -152,16 +152,16 @@ fn execve_prompt_rejection_keeps_prefix_rules_on_rules_flag() {
|
|||
fn execve_prompt_rejection_keeps_unmatched_commands_on_sandbox_flag() {
|
||||
assert_eq!(
|
||||
super::execve_prompt_is_rejected_by_policy(
|
||||
AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: false,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
}),
|
||||
&super::DecisionSource::UnmatchedCommandFallback,
|
||||
),
|
||||
Some("approval required by policy, but AskForApproval::Reject.sandbox_approval is set"),
|
||||
Some("approval required by policy, but AskForApproval::Granular.sandbox_approval is false"),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -161,8 +161,8 @@ impl ExecApprovalRequirement {
|
|||
|
||||
/// - Never, OnFailure: do not ask
|
||||
/// - OnRequest: ask unless filesystem access is unrestricted
|
||||
/// - Reject: ask unless filesystem access is unrestricted, but auto-reject
|
||||
/// when `sandbox_approval` rejection is enabled.
|
||||
/// - Granular: ask unless filesystem access is unrestricted, but auto-reject
|
||||
/// when granular sandbox approval is disabled.
|
||||
/// - UnlessTrusted: always ask
|
||||
pub(crate) fn default_exec_approval_requirement(
|
||||
policy: AskForApproval,
|
||||
|
|
@ -170,7 +170,7 @@ pub(crate) fn default_exec_approval_requirement(
|
|||
) -> ExecApprovalRequirement {
|
||||
let needs_approval = match policy {
|
||||
AskForApproval::Never | AskForApproval::OnFailure => false,
|
||||
AskForApproval::OnRequest | AskForApproval::Reject(_) => {
|
||||
AskForApproval::OnRequest | AskForApproval::Granular(_) => {
|
||||
matches!(
|
||||
file_system_sandbox_policy.kind,
|
||||
FileSystemSandboxKind::Restricted
|
||||
|
|
@ -182,11 +182,12 @@ pub(crate) fn default_exec_approval_requirement(
|
|||
if needs_approval
|
||||
&& matches!(
|
||||
policy,
|
||||
AskForApproval::Reject(reject_config) if reject_config.rejects_sandbox_approval()
|
||||
AskForApproval::Granular(granular_config)
|
||||
if !granular_config.allows_sandbox_approval()
|
||||
)
|
||||
{
|
||||
ExecApprovalRequirement::Forbidden {
|
||||
reason: "approval policy rejected sandbox approval prompt".to_string(),
|
||||
reason: "approval policy disallowed sandbox approval prompt".to_string(),
|
||||
}
|
||||
} else if needs_approval {
|
||||
ExecApprovalRequirement::NeedsApproval {
|
||||
|
|
@ -268,7 +269,7 @@ pub(crate) trait Approvable<Req> {
|
|||
AskForApproval::UnlessTrusted => true,
|
||||
AskForApproval::Never => false,
|
||||
AskForApproval::OnRequest => false,
|
||||
AskForApproval::Reject(reject_config) => !reject_config.sandbox_approval,
|
||||
AskForApproval::Granular(granular_config) => granular_config.sandbox_approval,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use super::*;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_protocol::protocol::NetworkAccess;
|
||||
use codex_protocol::protocol::RejectConfig;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
|
|
@ -37,13 +37,13 @@ fn restricted_sandbox_requires_exec_approval_on_request() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn default_exec_approval_requirement_rejects_sandbox_prompt_when_configured() {
|
||||
let policy = AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
fn default_exec_approval_requirement_rejects_sandbox_prompt_when_granular_disables_it() {
|
||||
let policy = AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: false,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
});
|
||||
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
|
|
@ -53,19 +53,19 @@ fn default_exec_approval_requirement_rejects_sandbox_prompt_when_configured() {
|
|||
assert_eq!(
|
||||
requirement,
|
||||
ExecApprovalRequirement::Forbidden {
|
||||
reason: "approval policy rejected sandbox approval prompt".to_string(),
|
||||
reason: "approval policy disallowed sandbox approval prompt".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_exec_approval_requirement_keeps_prompt_when_sandbox_rejection_is_disabled() {
|
||||
let policy = AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: false,
|
||||
rules: true,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: true,
|
||||
fn default_exec_approval_requirement_keeps_prompt_when_granular_allows_sandbox_approval() {
|
||||
let policy = AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: false,
|
||||
});
|
||||
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ pub(crate) struct ToolsConfig {
|
|||
pub agent_roles: BTreeMap<String, AgentRoleConfig>,
|
||||
pub search_tool: bool,
|
||||
pub tool_suggest: bool,
|
||||
pub request_permission_enabled: bool,
|
||||
pub exec_permission_approvals_enabled: bool,
|
||||
pub request_permissions_tool_enabled: bool,
|
||||
pub code_mode_enabled: bool,
|
||||
pub js_repl_enabled: bool,
|
||||
|
|
@ -184,7 +184,7 @@ impl ToolsConfig {
|
|||
features.enabled(Feature::Artifact) && codex_artifacts::can_manage_artifact_runtime();
|
||||
let include_image_gen_tool =
|
||||
features.enabled(Feature::ImageGeneration) && supports_image_generation(model_info);
|
||||
let request_permission_enabled = features.enabled(Feature::RequestPermissions);
|
||||
let exec_permission_approvals_enabled = features.enabled(Feature::ExecPermissionApprovals);
|
||||
let request_permissions_tool_enabled = features.enabled(Feature::RequestPermissionsTool);
|
||||
let shell_command_backend =
|
||||
if features.enabled(Feature::ShellTool) && features.enabled(Feature::ShellZshFork) {
|
||||
|
|
@ -255,7 +255,7 @@ impl ToolsConfig {
|
|||
agent_roles: BTreeMap::new(),
|
||||
search_tool: include_search_tool,
|
||||
tool_suggest: include_tool_suggest,
|
||||
request_permission_enabled,
|
||||
exec_permission_approvals_enabled,
|
||||
request_permissions_tool_enabled,
|
||||
code_mode_enabled: include_code_mode,
|
||||
js_repl_enabled: include_js_repl,
|
||||
|
|
@ -441,13 +441,15 @@ fn create_permissions_schema() -> JsonSchema {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_approval_parameters(request_permission_enabled: bool) -> BTreeMap<String, JsonSchema> {
|
||||
fn create_approval_parameters(
|
||||
exec_permission_approvals_enabled: bool,
|
||||
) -> BTreeMap<String, JsonSchema> {
|
||||
let mut properties = BTreeMap::from([
|
||||
(
|
||||
"sandbox_permissions".to_string(),
|
||||
JsonSchema::String {
|
||||
description: Some(
|
||||
if request_permission_enabled {
|
||||
if exec_permission_approvals_enabled {
|
||||
"Sandbox permissions for the command. Use \"with_additional_permissions\" to request additional sandboxed filesystem, network, or macOS permissions (preferred), or \"require_escalated\" to request running without sandbox restrictions; defaults to \"use_default\"."
|
||||
} else {
|
||||
"Sandbox permissions for the command. Set to \"require_escalated\" to request running without sandbox restrictions; defaults to \"use_default\"."
|
||||
|
|
@ -482,7 +484,7 @@ fn create_approval_parameters(request_permission_enabled: bool) -> BTreeMap<Stri
|
|||
)
|
||||
]);
|
||||
|
||||
if request_permission_enabled {
|
||||
if exec_permission_approvals_enabled {
|
||||
properties.insert(
|
||||
"additional_permissions".to_string(),
|
||||
create_permissions_schema(),
|
||||
|
|
@ -492,7 +494,10 @@ fn create_approval_parameters(request_permission_enabled: bool) -> BTreeMap<Stri
|
|||
properties
|
||||
}
|
||||
|
||||
fn create_exec_command_tool(allow_login_shell: bool, request_permission_enabled: bool) -> ToolSpec {
|
||||
fn create_exec_command_tool(
|
||||
allow_login_shell: bool,
|
||||
exec_permission_approvals_enabled: bool,
|
||||
) -> ToolSpec {
|
||||
let mut properties = BTreeMap::from([
|
||||
(
|
||||
"cmd".to_string(),
|
||||
|
|
@ -552,7 +557,9 @@ fn create_exec_command_tool(allow_login_shell: bool, request_permission_enabled:
|
|||
},
|
||||
);
|
||||
}
|
||||
properties.extend(create_approval_parameters(request_permission_enabled));
|
||||
properties.extend(create_approval_parameters(
|
||||
exec_permission_approvals_enabled,
|
||||
));
|
||||
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: "exec_command".to_string(),
|
||||
|
|
@ -669,7 +676,7 @@ fn create_exec_wait_tool() -> ToolSpec {
|
|||
})
|
||||
}
|
||||
|
||||
fn create_shell_tool(request_permission_enabled: bool) -> ToolSpec {
|
||||
fn create_shell_tool(exec_permission_approvals_enabled: bool) -> ToolSpec {
|
||||
let mut properties = BTreeMap::from([
|
||||
(
|
||||
"command".to_string(),
|
||||
|
|
@ -691,7 +698,9 @@ fn create_shell_tool(request_permission_enabled: bool) -> ToolSpec {
|
|||
},
|
||||
),
|
||||
]);
|
||||
properties.extend(create_approval_parameters(request_permission_enabled));
|
||||
properties.extend(create_approval_parameters(
|
||||
exec_permission_approvals_enabled,
|
||||
));
|
||||
|
||||
let description = if cfg!(windows) {
|
||||
r#"Runs a Powershell command (Windows) and returns its output. Arguments to `shell` will be passed to CreateProcessW(). Most commands should be prefixed with ["powershell.exe", "-Command"].
|
||||
|
|
@ -726,7 +735,7 @@ Examples of valid command strings:
|
|||
|
||||
fn create_shell_command_tool(
|
||||
allow_login_shell: bool,
|
||||
request_permission_enabled: bool,
|
||||
exec_permission_approvals_enabled: bool,
|
||||
) -> ToolSpec {
|
||||
let mut properties = BTreeMap::from([
|
||||
(
|
||||
|
|
@ -761,7 +770,9 @@ fn create_shell_command_tool(
|
|||
},
|
||||
);
|
||||
}
|
||||
properties.extend(create_approval_parameters(request_permission_enabled));
|
||||
properties.extend(create_approval_parameters(
|
||||
exec_permission_approvals_enabled,
|
||||
));
|
||||
|
||||
let description = if cfg!(windows) {
|
||||
r#"Runs a Powershell command (Windows) and returns its output.
|
||||
|
|
@ -2359,7 +2370,7 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
|||
let js_repl_handler = Arc::new(JsReplHandler);
|
||||
let js_repl_reset_handler = Arc::new(JsReplResetHandler);
|
||||
let artifacts_handler = Arc::new(ArtifactsHandler);
|
||||
let request_permission_enabled = config.request_permission_enabled;
|
||||
let exec_permission_approvals_enabled = config.exec_permission_approvals_enabled;
|
||||
|
||||
if config.code_mode_enabled {
|
||||
let nested_config = config.for_code_mode_nested_tools();
|
||||
|
|
@ -2399,7 +2410,7 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
|||
ConfigShellToolType::Default => {
|
||||
push_tool_spec(
|
||||
&mut builder,
|
||||
create_shell_tool(request_permission_enabled),
|
||||
create_shell_tool(exec_permission_approvals_enabled),
|
||||
true,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
|
|
@ -2415,7 +2426,10 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
|||
ConfigShellToolType::UnifiedExec => {
|
||||
push_tool_spec(
|
||||
&mut builder,
|
||||
create_exec_command_tool(config.allow_login_shell, request_permission_enabled),
|
||||
create_exec_command_tool(
|
||||
config.allow_login_shell,
|
||||
exec_permission_approvals_enabled,
|
||||
),
|
||||
true,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
|
|
@ -2434,7 +2448,10 @@ pub(crate) fn build_specs_with_discoverable_tools(
|
|||
ConfigShellToolType::ShellCommand => {
|
||||
push_tool_spec(
|
||||
&mut builder,
|
||||
create_shell_command_tool(config.allow_login_shell, request_permission_enabled),
|
||||
create_shell_command_tool(
|
||||
config.allow_login_shell,
|
||||
exec_permission_approvals_enabled,
|
||||
),
|
||||
true,
|
||||
config.code_mode_enabled,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -461,7 +461,7 @@ fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() {
|
|||
expected.insert(tool_name(&spec).to_string(), spec);
|
||||
}
|
||||
|
||||
if config.request_permission_enabled {
|
||||
if config.exec_permission_approvals_enabled {
|
||||
let spec = create_request_permissions_tool();
|
||||
expected.insert(tool_name(&spec).to_string(), spec);
|
||||
}
|
||||
|
|
@ -744,7 +744,7 @@ fn request_permissions_tool_is_independent_from_additional_permissions() {
|
|||
let config = test_config();
|
||||
let model_info = ModelsManager::construct_model_info_offline_for_tests("gpt-5-codex", &config);
|
||||
let mut features = Features::with_defaults();
|
||||
features.enable(Feature::RequestPermissions);
|
||||
features.enable(Feature::ExecPermissionApprovals);
|
||||
let available_models = Vec::new();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ use codex_protocol::models::PermissionProfile;
|
|||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::ExecApprovalRequestEvent;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::RejectConfig;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
|
|
@ -323,7 +323,7 @@ async fn with_additional_permissions_requires_approval_under_on_request() -> Res
|
|||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test config should allow feature update");
|
||||
config
|
||||
.features
|
||||
|
|
@ -397,18 +397,18 @@ async fn with_additional_permissions_requires_approval_under_on_request() -> Res
|
|||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn request_permissions_tool_is_auto_denied_when_reject_request_permissions_is_enabled()
|
||||
async fn request_permissions_tool_is_auto_denied_when_granular_request_permissions_is_disabled()
|
||||
-> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
skip_if_sandbox!(Ok(()));
|
||||
|
||||
let server = start_mock_server().await;
|
||||
let approval_policy = AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: false,
|
||||
let approval_policy = AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: true,
|
||||
});
|
||||
let sandbox_policy = SandboxPolicy::new_read_only_policy();
|
||||
let sandbox_policy_for_config = sandbox_policy.clone();
|
||||
|
|
@ -453,7 +453,7 @@ async fn request_permissions_tool_is_auto_denied_when_reject_request_permissions
|
|||
|
||||
submit_turn(
|
||||
&test,
|
||||
"request permissions under reject.request_permissions",
|
||||
"request permissions under granular.request_permissions = false",
|
||||
approval_policy,
|
||||
sandbox_policy,
|
||||
)
|
||||
|
|
@ -468,7 +468,7 @@ async fn request_permissions_tool_is_auto_denied_when_reject_request_permissions
|
|||
.await;
|
||||
assert!(
|
||||
matches!(event, EventMsg::TurnComplete(_)),
|
||||
"request_permissions should not emit a prompt when reject.request_permissions is set: {event:?}"
|
||||
"request_permissions should not emit a prompt when granular.request_permissions is false: {event:?}"
|
||||
);
|
||||
|
||||
let call_output = results.single_request().function_call_output(call_id);
|
||||
|
|
@ -500,7 +500,7 @@ async fn relative_additional_permissions_resolve_against_tool_workdir() -> Resul
|
|||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test config should allow feature update");
|
||||
config
|
||||
.features
|
||||
|
|
@ -601,7 +601,7 @@ async fn read_only_with_additional_permissions_does_not_widen_to_unrequested_cwd
|
|||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test config should allow feature update");
|
||||
config
|
||||
.features
|
||||
|
|
@ -701,7 +701,7 @@ async fn read_only_with_additional_permissions_does_not_widen_to_unrequested_tmp
|
|||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test config should allow feature update");
|
||||
config
|
||||
.features
|
||||
|
|
@ -800,7 +800,7 @@ async fn workspace_write_with_additional_permissions_can_write_outside_cwd() ->
|
|||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test config should allow feature update");
|
||||
config
|
||||
.features
|
||||
|
|
@ -904,7 +904,7 @@ async fn with_additional_permissions_denied_approval_blocks_execution() -> Resul
|
|||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test config should allow feature update");
|
||||
config
|
||||
.features
|
||||
|
|
@ -1009,7 +1009,7 @@ async fn request_permissions_grants_apply_to_later_exec_command_calls() -> Resul
|
|||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test config should allow feature update");
|
||||
config
|
||||
.features
|
||||
|
|
@ -1132,7 +1132,7 @@ async fn request_permissions_preapprove_explicit_exec_permissions_outside_on_req
|
|||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test config should allow feature update");
|
||||
config
|
||||
.features
|
||||
|
|
@ -1249,7 +1249,7 @@ async fn request_permissions_grants_apply_to_later_shell_command_calls() -> Resu
|
|||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test config should allow feature update");
|
||||
config
|
||||
.features
|
||||
|
|
@ -1471,7 +1471,7 @@ async fn partial_request_permissions_grants_do_not_preapprove_new_permissions()
|
|||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test config should allow feature update");
|
||||
config
|
||||
.features
|
||||
|
|
@ -1631,7 +1631,7 @@ async fn request_permissions_grants_do_not_carry_across_turns() -> Result<()> {
|
|||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test config should allow feature update");
|
||||
config
|
||||
.features
|
||||
|
|
@ -1743,7 +1743,7 @@ async fn request_permissions_session_grants_carry_across_turns() -> Result<()> {
|
|||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test config should allow feature update");
|
||||
config
|
||||
.features
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ async fn approved_folder_write_request_permissions_unblocks_later_exec_without_s
|
|||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test config should allow feature update");
|
||||
config
|
||||
.features
|
||||
|
|
@ -318,7 +318,7 @@ async fn approved_folder_write_request_permissions_unblocks_later_apply_patch_wi
|
|||
config.permissions.sandbox_policy = Constrained::allow_any(sandbox_policy_for_config);
|
||||
config
|
||||
.features
|
||||
.enable(Feature::RequestPermissions)
|
||||
.enable(Feature::ExecPermissionApprovals)
|
||||
.expect("test config should allow feature update");
|
||||
config
|
||||
.features
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ use codex_protocol::protocol::AskForApproval;
|
|||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::ExecApprovalRequestEvent;
|
||||
use codex_protocol::protocol::ExecApprovalRequestSkillMetadata;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::RejectConfig;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
|
|
@ -285,12 +285,12 @@ async fn shell_zsh_fork_skill_script_reject_policy_with_sandbox_approval_false_s
|
|||
return Ok(());
|
||||
};
|
||||
|
||||
let approval_policy = AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: false,
|
||||
let approval_policy = AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
});
|
||||
let server = start_mock_server().await;
|
||||
let tool_call_id = "zsh-fork-skill-reject-false";
|
||||
|
|
@ -381,12 +381,12 @@ async fn shell_zsh_fork_skill_script_reject_policy_with_sandbox_approval_true_st
|
|||
return Ok(());
|
||||
};
|
||||
|
||||
let approval_policy = AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
let approval_policy = AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: false,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
});
|
||||
let server = start_mock_server().await;
|
||||
let tool_call_id = "zsh-fork-skill-reject-true";
|
||||
|
|
@ -475,12 +475,12 @@ async fn shell_zsh_fork_skill_script_reject_policy_with_skill_approval_true_skip
|
|||
return Ok(());
|
||||
};
|
||||
|
||||
let approval_policy = AskForApproval::Reject(RejectConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: true,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
let approval_policy = AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: false,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
});
|
||||
let server = start_mock_server().await;
|
||||
let tool_call_id = "zsh-fork-skill-reject-skill-approval-true";
|
||||
|
|
|
|||
|
|
@ -483,10 +483,10 @@ impl DeveloperInstructions {
|
|||
pub fn from(
|
||||
approval_policy: AskForApproval,
|
||||
exec_policy: &Policy,
|
||||
request_permission_enabled: bool,
|
||||
exec_permission_approvals_enabled: bool,
|
||||
) -> DeveloperInstructions {
|
||||
let on_request_instructions = || {
|
||||
let on_request_rule = if request_permission_enabled {
|
||||
let on_request_rule = if exec_permission_approvals_enabled {
|
||||
APPROVAL_POLICY_ON_REQUEST_RULE_REQUEST_PERMISSION
|
||||
} else {
|
||||
APPROVAL_POLICY_ON_REQUEST_RULE
|
||||
|
|
@ -506,22 +506,22 @@ impl DeveloperInstructions {
|
|||
AskForApproval::UnlessTrusted => APPROVAL_POLICY_UNLESS_TRUSTED.to_string(),
|
||||
AskForApproval::OnFailure => APPROVAL_POLICY_ON_FAILURE.to_string(),
|
||||
AskForApproval::OnRequest => on_request_instructions(),
|
||||
AskForApproval::Reject(reject_config) => {
|
||||
AskForApproval::Granular(granular_config) => {
|
||||
let on_request_instructions = on_request_instructions();
|
||||
let sandbox_approval = reject_config.sandbox_approval;
|
||||
let rules = reject_config.rules;
|
||||
let skill_approval = reject_config.skill_approval;
|
||||
let request_permissions = reject_config.request_permissions;
|
||||
let mcp_elicitations = reject_config.mcp_elicitations;
|
||||
let sandbox_approval = granular_config.sandbox_approval;
|
||||
let rules = granular_config.rules;
|
||||
let skill_approval = granular_config.skill_approval;
|
||||
let request_permissions = granular_config.request_permissions;
|
||||
let mcp_elicitations = granular_config.mcp_elicitations;
|
||||
format!(
|
||||
"{on_request_instructions}\n\n\
|
||||
Approval policy is `reject`.\n\
|
||||
Approval policy is `granular`.\n\
|
||||
- `sandbox_approval`: {sandbox_approval}\n\
|
||||
- `rules`: {rules}\n\
|
||||
- `skill_approval`: {skill_approval}\n\
|
||||
- `request_permissions`: {request_permissions}\n\
|
||||
- `mcp_elicitations`: {mcp_elicitations}\n\
|
||||
When a category is `true`, requests in that category are auto-rejected instead of prompting the user."
|
||||
When a category is `true`, requests in that category are allowed. When it is `false`, they are auto-rejected instead of prompting the user."
|
||||
)
|
||||
}
|
||||
};
|
||||
|
|
@ -577,7 +577,7 @@ impl DeveloperInstructions {
|
|||
approval_policy: AskForApproval,
|
||||
exec_policy: &Policy,
|
||||
cwd: &Path,
|
||||
request_permission_enabled: bool,
|
||||
exec_permission_approvals_enabled: bool,
|
||||
) -> Self {
|
||||
let network_access = if sandbox_policy.has_full_network_access() {
|
||||
NetworkAccess::Enabled
|
||||
|
|
@ -601,7 +601,7 @@ impl DeveloperInstructions {
|
|||
approval_policy,
|
||||
exec_policy,
|
||||
writable_roots,
|
||||
request_permission_enabled,
|
||||
exec_permission_approvals_enabled,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -625,7 +625,7 @@ impl DeveloperInstructions {
|
|||
approval_policy: AskForApproval,
|
||||
exec_policy: &Policy,
|
||||
writable_roots: Option<Vec<WritableRoot>>,
|
||||
request_permission_enabled: bool,
|
||||
exec_permission_approvals_enabled: bool,
|
||||
) -> Self {
|
||||
let start_tag = DeveloperInstructions::new("<permissions instructions>");
|
||||
let end_tag = DeveloperInstructions::new("</permissions instructions>");
|
||||
|
|
@ -637,7 +637,7 @@ impl DeveloperInstructions {
|
|||
.concat(DeveloperInstructions::from(
|
||||
approval_policy,
|
||||
exec_policy,
|
||||
request_permission_enabled,
|
||||
exec_permission_approvals_enabled,
|
||||
))
|
||||
.concat(DeveloperInstructions::from_writable_roots(writable_roots))
|
||||
.concat(end_tag)
|
||||
|
|
|
|||
|
|
@ -516,11 +516,13 @@ pub enum AskForApproval {
|
|||
#[default]
|
||||
OnRequest,
|
||||
|
||||
/// Fine-grained rejection controls for approval prompts.
|
||||
/// Fine-grained controls for individual approval flows.
|
||||
///
|
||||
/// When a field is `true`, prompts of that category are automatically
|
||||
/// rejected instead of shown to the user.
|
||||
Reject(RejectConfig),
|
||||
/// When a field is `true`, commands in that category are allowed. When it
|
||||
/// is `false`, those requests are automatically rejected instead of shown
|
||||
/// to the user.
|
||||
#[strum(serialize = "granular")]
|
||||
Granular(GranularApprovalConfig),
|
||||
|
||||
/// Never ask the user to approve commands. Failures are immediately returned
|
||||
/// to the model, and never escalated to the user for approval.
|
||||
|
|
@ -528,40 +530,40 @@ pub enum AskForApproval {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, TS)]
|
||||
pub struct RejectConfig {
|
||||
/// Reject shell command approval requests, including inline
|
||||
pub struct GranularApprovalConfig {
|
||||
/// Whether to allow shell command approval requests, including inline
|
||||
/// `with_additional_permissions` and `require_escalated` requests.
|
||||
pub sandbox_approval: bool,
|
||||
/// Reject prompts triggered by execpolicy `prompt` rules.
|
||||
/// Whether to allow prompts triggered by execpolicy `prompt` rules.
|
||||
pub rules: bool,
|
||||
/// Reject approval prompts triggered by skill script execution.
|
||||
/// Whether to allow approval prompts triggered by skill script execution.
|
||||
#[serde(default)]
|
||||
pub skill_approval: bool,
|
||||
/// Reject `request_permissions` tool requests.
|
||||
/// Whether to allow prompts triggered by the `request_permissions` tool.
|
||||
#[serde(default)]
|
||||
pub request_permissions: bool,
|
||||
/// Reject MCP elicitation prompts.
|
||||
/// Whether to allow MCP elicitation prompts.
|
||||
pub mcp_elicitations: bool,
|
||||
}
|
||||
|
||||
impl RejectConfig {
|
||||
pub const fn rejects_sandbox_approval(self) -> bool {
|
||||
impl GranularApprovalConfig {
|
||||
pub const fn allows_sandbox_approval(self) -> bool {
|
||||
self.sandbox_approval
|
||||
}
|
||||
|
||||
pub const fn rejects_rules_approval(self) -> bool {
|
||||
pub const fn allows_rules_approval(self) -> bool {
|
||||
self.rules
|
||||
}
|
||||
|
||||
pub const fn rejects_skill_approval(self) -> bool {
|
||||
pub const fn allows_skill_approval(self) -> bool {
|
||||
self.skill_approval
|
||||
}
|
||||
|
||||
pub const fn rejects_request_permissions(self) -> bool {
|
||||
pub const fn allows_request_permissions(self) -> bool {
|
||||
self.request_permissions
|
||||
}
|
||||
|
||||
pub const fn rejects_mcp_elicitations(self) -> bool {
|
||||
pub const fn allows_mcp_elicitations(self) -> bool {
|
||||
self.mcp_elicitations
|
||||
}
|
||||
}
|
||||
|
|
@ -3466,89 +3468,89 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn reject_config_mcp_elicitation_flag_is_field_driven() {
|
||||
fn granular_approval_config_mcp_elicitation_flag_is_field_driven() {
|
||||
assert!(
|
||||
RejectConfig {
|
||||
GranularApprovalConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: true,
|
||||
}
|
||||
.rejects_mcp_elicitations()
|
||||
.allows_mcp_elicitations()
|
||||
);
|
||||
assert!(
|
||||
!RejectConfig {
|
||||
!GranularApprovalConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
}
|
||||
.rejects_mcp_elicitations()
|
||||
.allows_mcp_elicitations()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reject_config_skill_approval_flag_is_field_driven() {
|
||||
fn granular_approval_config_skill_approval_flag_is_field_driven() {
|
||||
assert!(
|
||||
RejectConfig {
|
||||
GranularApprovalConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: true,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
}
|
||||
.rejects_skill_approval()
|
||||
.allows_skill_approval()
|
||||
);
|
||||
assert!(
|
||||
!RejectConfig {
|
||||
!GranularApprovalConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
}
|
||||
.rejects_skill_approval()
|
||||
.allows_skill_approval()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reject_config_request_permissions_flag_is_field_driven() {
|
||||
fn granular_approval_config_request_permissions_flag_is_field_driven() {
|
||||
assert!(
|
||||
RejectConfig {
|
||||
GranularApprovalConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: false,
|
||||
}
|
||||
.rejects_request_permissions()
|
||||
.allows_request_permissions()
|
||||
);
|
||||
assert!(
|
||||
!RejectConfig {
|
||||
!GranularApprovalConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
}
|
||||
.rejects_request_permissions()
|
||||
.allows_request_permissions()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reject_config_defaults_missing_optional_flags_to_false() {
|
||||
let decoded = serde_json::from_value::<RejectConfig>(serde_json::json!({
|
||||
fn granular_approval_config_defaults_missing_optional_flags_to_false() {
|
||||
let decoded = serde_json::from_value::<GranularApprovalConfig>(serde_json::json!({
|
||||
"sandbox_approval": true,
|
||||
"rules": false,
|
||||
"mcp_elicitations": true,
|
||||
}))
|
||||
.expect("legacy reject config should deserialize");
|
||||
.expect("granular approval config should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
decoded,
|
||||
RejectConfig {
|
||||
GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue