mirror of
https://github.com/openai/codex.git
synced 2026-11-03 19:53:25 +00:00
permissions: expose reviewer override grant API
This commit is contained in:
@@ -885,6 +885,7 @@ fn sample_effective_permissions_approval_response(
|
||||
permissions,
|
||||
scope,
|
||||
strict_auto_review: false,
|
||||
approvals_reviewer: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
@@ -58,6 +58,15 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ApprovalsReviewer": {
|
||||
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
|
||||
"enum": [
|
||||
"user",
|
||||
"auto_review",
|
||||
"guardian_subagent"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FileSystemAccessMode": {
|
||||
"enum": [
|
||||
"read",
|
||||
@@ -288,6 +297,17 @@
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"approvalsReviewer": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ApprovalsReviewer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Override who reviews later approval prompts for the rest of the current turn."
|
||||
},
|
||||
"permissions": {
|
||||
"$ref": "#/definitions/GrantedPermissionProfile"
|
||||
},
|
||||
|
||||
+11
@@ -3875,6 +3875,17 @@
|
||||
"PermissionsRequestApprovalResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"approvalsReviewer": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/ApprovalsReviewer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Override who reviews later approval prompts for the rest of the current turn."
|
||||
},
|
||||
"permissions": {
|
||||
"$ref": "#/definitions/GrantedPermissionProfile"
|
||||
},
|
||||
|
||||
+6
-1
@@ -1,6 +1,7 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
|
||||
import type { GrantedPermissionProfile } from "./GrantedPermissionProfile";
|
||||
import type { PermissionGrantScope } from "./PermissionGrantScope";
|
||||
|
||||
@@ -8,4 +9,8 @@ export type PermissionsRequestApprovalResponse = { permissions: GrantedPermissio
|
||||
/**
|
||||
* Review every subsequent command in this turn before normal sandboxed execution.
|
||||
*/
|
||||
strictAutoReview?: boolean, };
|
||||
strictAutoReview?: boolean,
|
||||
/**
|
||||
* Override who reviews later approval prompts for the rest of the current turn.
|
||||
*/
|
||||
approvalsReviewer?: ApprovalsReviewer, };
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use super::shared::ApprovalsReviewer;
|
||||
use super::shared::v2_enum_from_core;
|
||||
use codex_protocol::approvals::ExecPolicyAmendment as CoreExecPolicyAmendment;
|
||||
use codex_protocol::approvals::NetworkApprovalContext as CoreNetworkApprovalContext;
|
||||
@@ -693,4 +694,8 @@ pub struct PermissionsRequestApprovalResponse {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub strict_auto_review: Option<bool>,
|
||||
/// Override who reviews later approval prompts for the rest of the current turn.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
}
|
||||
|
||||
@@ -708,6 +708,7 @@ fn permissions_request_approval_response_defaults_scope_to_turn() {
|
||||
|
||||
assert_eq!(response.scope, PermissionGrantScope::Turn);
|
||||
assert_eq!(response.strict_auto_review, None);
|
||||
assert_eq!(response.approvals_reviewer, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -721,6 +722,20 @@ fn permissions_request_approval_response_accepts_strict_auto_review() {
|
||||
assert_eq!(response.strict_auto_review, Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permissions_request_approval_response_accepts_approvals_reviewer() {
|
||||
let response = serde_json::from_value::<PermissionsRequestApprovalResponse>(json!({
|
||||
"permissions": {},
|
||||
"approvalsReviewer": "auto_review",
|
||||
}))
|
||||
.expect("response should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
response.approvals_reviewer,
|
||||
Some(ApprovalsReviewer::AutoReview)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permission_profile_selection_uses_id_string() {
|
||||
let start: ThreadStartParams = serde_json::from_value(json!({
|
||||
|
||||
@@ -1458,13 +1458,14 @@ The built-in `request_permissions` tool sends an `item/permissions/requestApprov
|
||||
}
|
||||
```
|
||||
|
||||
The client responds with `result.permissions`, which should be the granted subset of the requested permission profile. It may also set `result.scope` to `"session"` to make the grant persist for later turns in the same session; omitted or `"turn"` keeps the existing turn-scoped behavior:
|
||||
The client responds with `result.permissions`, which should be the granted subset of the requested permission profile. It may also set `result.scope` to `"session"` to make the grant persist for later turns in the same session; omitted or `"turn"` keeps the existing turn-scoped behavior. `approvalsReviewer` can be set to `"auto_review"` or `"user"` to choose who reviews later approval prompts for the rest of the current turn. This reviewer override is turn-scoped even when `scope` is `"session"` for the permission grant. Legacy clients may still send `strictAutoReview: true`, which is treated as `approvalsReviewer: "auto_review"` for compatibility:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 61,
|
||||
"result": {
|
||||
"scope": "session",
|
||||
"approvalsReviewer": "auto_review",
|
||||
"permissions": {
|
||||
"fileSystem": {
|
||||
"write": ["/Users/me/project"]
|
||||
|
||||
@@ -89,6 +89,7 @@ use codex_core::ThreadManager;
|
||||
use codex_core::review_format::format_review_findings_block;
|
||||
use codex_core::review_prompts;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::config_types::ApprovalsReviewer as CoreApprovalsReviewer;
|
||||
use codex_protocol::items::parse_hook_prompt_message;
|
||||
use codex_protocol::models::AdditionalPermissionProfile as CoreAdditionalPermissionProfile;
|
||||
use codex_protocol::plan_tool::UpdatePlanArgs;
|
||||
@@ -1849,6 +1850,7 @@ fn request_permissions_response_from_client_result(
|
||||
permissions: Default::default(),
|
||||
scope: CorePermissionGrantScope::Turn,
|
||||
strict_auto_review: false,
|
||||
approvals_reviewer: None,
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -1857,6 +1859,7 @@ fn request_permissions_response_from_client_result(
|
||||
permissions: Default::default(),
|
||||
scope: CorePermissionGrantScope::Turn,
|
||||
strict_auto_review: false,
|
||||
approvals_reviewer: None,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1868,22 +1871,14 @@ fn request_permissions_response_from_client_result(
|
||||
permissions: V2GrantedPermissionProfile::default(),
|
||||
scope: codex_app_server_protocol::PermissionGrantScope::Turn,
|
||||
strict_auto_review: None,
|
||||
approvals_reviewer: None,
|
||||
}
|
||||
});
|
||||
let strict_auto_review = response.strict_auto_review.unwrap_or(false);
|
||||
if strict_auto_review
|
||||
&& matches!(
|
||||
response.scope,
|
||||
codex_app_server_protocol::PermissionGrantScope::Session
|
||||
)
|
||||
{
|
||||
error!("strict auto review is only supported for turn-scoped permission grants");
|
||||
return Some(CoreRequestPermissionsResponse {
|
||||
permissions: Default::default(),
|
||||
scope: CorePermissionGrantScope::Turn,
|
||||
strict_auto_review: false,
|
||||
});
|
||||
}
|
||||
let approvals_reviewer = response
|
||||
.approvals_reviewer
|
||||
.map(codex_app_server_protocol::ApprovalsReviewer::to_core)
|
||||
.or_else(|| strict_auto_review.then_some(CoreApprovalsReviewer::AutoReview));
|
||||
let granted_permissions: CoreAdditionalPermissionProfile = response.permissions.into();
|
||||
let permissions = if granted_permissions.is_empty() {
|
||||
CoreRequestPermissionProfile::default()
|
||||
@@ -1894,6 +1889,7 @@ fn request_permissions_response_from_client_result(
|
||||
permissions,
|
||||
scope: response.scope.to_core(),
|
||||
strict_auto_review,
|
||||
approvals_reviewer,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3001,6 +2997,7 @@ mod tests {
|
||||
permissions: expected_permissions,
|
||||
scope: CorePermissionGrantScope::Turn,
|
||||
strict_auto_review: false,
|
||||
approvals_reviewer: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -3024,14 +3021,20 @@ mod tests {
|
||||
permissions: CoreRequestPermissionProfile::default(),
|
||||
scope: CorePermissionGrantScope::Session,
|
||||
strict_auto_review: false,
|
||||
approvals_reviewer: None,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_permissions_response_rejects_session_scoped_strict_auto_review() {
|
||||
fn request_permissions_response_preserves_session_scope_with_auto_review_override() {
|
||||
let response = request_permissions_response_from_client_result(
|
||||
CoreRequestPermissionProfile::default(),
|
||||
CoreRequestPermissionProfile {
|
||||
network: Some(codex_protocol::models::NetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
Ok(Ok(serde_json::json!({
|
||||
"scope": "session",
|
||||
"strictAutoReview": true,
|
||||
@@ -3048,9 +3051,15 @@ mod tests {
|
||||
assert_eq!(
|
||||
response,
|
||||
CoreRequestPermissionsResponse {
|
||||
permissions: CoreRequestPermissionProfile::default(),
|
||||
scope: CorePermissionGrantScope::Turn,
|
||||
strict_auto_review: false,
|
||||
permissions: CoreRequestPermissionProfile {
|
||||
network: Some(codex_protocol::models::NetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
..CoreRequestPermissionProfile::default()
|
||||
},
|
||||
scope: CorePermissionGrantScope::Session,
|
||||
strict_auto_review: true,
|
||||
approvals_reviewer: Some(CoreApprovalsReviewer::AutoReview),
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -3078,6 +3087,29 @@ mod tests {
|
||||
|
||||
assert_eq!(response.scope, CorePermissionGrantScope::Turn);
|
||||
assert!(response.strict_auto_review);
|
||||
assert_eq!(
|
||||
response.approvals_reviewer,
|
||||
Some(CoreApprovalsReviewer::AutoReview)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_permissions_response_preserves_explicit_approvals_reviewer() {
|
||||
let response = request_permissions_response_from_client_result(
|
||||
CoreRequestPermissionProfile::default(),
|
||||
Ok(Ok(serde_json::json!({
|
||||
"approvalsReviewer": "user",
|
||||
"strictAutoReview": true,
|
||||
"permissions": {},
|
||||
}))),
|
||||
std::env::current_dir().expect("current dir").as_path(),
|
||||
)
|
||||
.expect("response should be accepted");
|
||||
|
||||
assert_eq!(
|
||||
response.approvals_reviewer,
|
||||
Some(CoreApprovalsReviewer::User)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -121,6 +121,7 @@ async fn request_permissions_round_trip() -> Result<()> {
|
||||
},
|
||||
scope: PermissionGrantScope::Turn,
|
||||
strict_auto_review: None,
|
||||
approvals_reviewer: None,
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::config_types::ApprovalsReviewer;
|
||||
use crate::models::AdditionalPermissionProfile;
|
||||
use crate::models::FileSystemPermissions;
|
||||
use crate::models::NetworkPermissions;
|
||||
@@ -69,6 +70,19 @@ pub struct RequestPermissionsResponse {
|
||||
/// Review every subsequent command in this turn before normal sandboxed execution.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub strict_auto_review: bool,
|
||||
/// Override who reviews later approval prompts for the rest of the current turn.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
}
|
||||
|
||||
impl RequestPermissionsResponse {
|
||||
pub fn approvals_reviewer_override(&self) -> Option<ApprovalsReviewer> {
|
||||
self.approvals_reviewer.or_else(|| {
|
||||
self.strict_auto_review
|
||||
.then_some(ApprovalsReviewer::AutoReview)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
||||
|
||||
Reference in New Issue
Block a user