mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat(core) Persist request_permission data across turns (#14009)
## Summary request_permissions flows should support persisting results for the session. Open Question: Still deciding if we need within-turn approvals - this adds complexity but I could see it being useful ## Testing - [x] Updated unit tests --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
831ee51c86
commit
d241dc598c
@@ -103,6 +103,7 @@ use codex_protocol::protocol::TurnAbortReason;
|
||||
use codex_protocol::protocol::TurnContextItem;
|
||||
use codex_protocol::protocol::TurnContextNetworkItem;
|
||||
use codex_protocol::protocol::TurnStartedEvent;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionsArgs;
|
||||
use codex_protocol::request_permissions::RequestPermissionsEvent;
|
||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||
@@ -2996,6 +2997,7 @@ impl Session {
|
||||
call_id: &str,
|
||||
response: RequestPermissionsResponse,
|
||||
) {
|
||||
let mut granted_for_session = None;
|
||||
let entry = {
|
||||
let mut active = self.active_turn.lock().await;
|
||||
match active.as_mut() {
|
||||
@@ -3003,13 +3005,24 @@ impl Session {
|
||||
let mut ts = at.turn_state.lock().await;
|
||||
let entry = ts.remove_pending_request_permissions(call_id);
|
||||
if entry.is_some() && !response.permissions.is_empty() {
|
||||
ts.record_granted_permissions(response.permissions.clone());
|
||||
match response.scope {
|
||||
PermissionGrantScope::Turn => {
|
||||
ts.record_granted_permissions(response.permissions.clone());
|
||||
}
|
||||
PermissionGrantScope::Session => {
|
||||
granted_for_session = Some(response.permissions.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
entry
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
};
|
||||
if let Some(permissions) = granted_for_session {
|
||||
let mut state = self.state.lock().await;
|
||||
state.record_granted_permissions(permissions);
|
||||
}
|
||||
match entry {
|
||||
Some(tx_response) => {
|
||||
tx_response.send(response).ok();
|
||||
@@ -3027,6 +3040,11 @@ impl Session {
|
||||
ts.granted_permissions()
|
||||
}
|
||||
|
||||
pub(crate) async fn granted_session_permissions(&self) -> Option<PermissionProfile> {
|
||||
let state = self.state.lock().await;
|
||||
state.granted_permissions()
|
||||
}
|
||||
|
||||
pub async fn notify_dynamic_tool_response(&self, call_id: &str, response: DynamicToolResponse) {
|
||||
let entry = {
|
||||
let mut active = self.active_turn.lock().await;
|
||||
|
||||
@@ -13,6 +13,7 @@ use codex_protocol::protocol::RequestUserInputEvent;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_protocol::protocol::Submission;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionsArgs;
|
||||
use codex_protocol::request_permissions::RequestPermissionsEvent;
|
||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||
@@ -505,6 +506,7 @@ where
|
||||
_ = cancel_token.cancelled() => {
|
||||
let empty = RequestPermissionsResponse {
|
||||
permissions: Default::default(),
|
||||
scope: PermissionGrantScope::Turn,
|
||||
};
|
||||
parent_session
|
||||
.notify_request_permissions_response(call_id, empty.clone())
|
||||
@@ -513,6 +515,7 @@ where
|
||||
}
|
||||
response = fut => response.unwrap_or_else(|| RequestPermissionsResponse {
|
||||
permissions: Default::default(),
|
||||
scope: PermissionGrantScope::Turn,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -698,6 +701,7 @@ mod tests {
|
||||
}),
|
||||
..PermissionProfile::default()
|
||||
},
|
||||
scope: PermissionGrantScope::Turn,
|
||||
};
|
||||
let cancel_token = CancellationToken::new();
|
||||
let request_call_id = call_id.clone();
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::tools::format_exec_output_str;
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::FunctionCallOutputBody;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
use tracing::Span;
|
||||
|
||||
use crate::protocol::CompactedItem;
|
||||
@@ -2156,6 +2157,7 @@ async fn notify_request_permissions_response_ignores_unmatched_call_id() {
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
scope: PermissionGrantScope::Turn,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Session-wide mutable state.
|
||||
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
@@ -32,6 +33,7 @@ pub(crate) struct SessionState {
|
||||
pub(crate) startup_regular_task: Option<JoinHandle<CodexResult<RegularTask>>>,
|
||||
pub(crate) active_mcp_tool_selection: Option<Vec<String>>,
|
||||
pub(crate) active_connector_selection: HashSet<String>,
|
||||
granted_permissions: Option<PermissionProfile>,
|
||||
}
|
||||
|
||||
impl SessionState {
|
||||
@@ -49,6 +51,7 @@ impl SessionState {
|
||||
startup_regular_task: None,
|
||||
active_mcp_tool_selection: None,
|
||||
active_connector_selection: HashSet::new(),
|
||||
granted_permissions: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +221,17 @@ impl SessionState {
|
||||
self.active_mcp_tool_selection = None;
|
||||
}
|
||||
|
||||
pub(crate) fn record_granted_permissions(&mut self, permissions: PermissionProfile) {
|
||||
self.granted_permissions = crate::sandboxing::merge_permission_profiles(
|
||||
self.granted_permissions.as_ref(),
|
||||
Some(&permissions),
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn granted_permissions(&self) -> Option<PermissionProfile> {
|
||||
self.granted_permissions.clone()
|
||||
}
|
||||
|
||||
// Adds connector IDs to the active set and returns the merged selection.
|
||||
pub(crate) fn merge_connector_selection<I>(&mut self, connector_ids: I) -> HashSet<String>
|
||||
where
|
||||
|
||||
@@ -172,7 +172,12 @@ pub(super) async fn apply_granted_turn_permissions(
|
||||
};
|
||||
}
|
||||
|
||||
let granted_permissions = session.granted_turn_permissions().await;
|
||||
let granted_session_permissions = session.granted_session_permissions().await;
|
||||
let granted_turn_permissions = session.granted_turn_permissions().await;
|
||||
let granted_permissions = merge_permission_profiles(
|
||||
granted_session_permissions.as_ref(),
|
||||
granted_turn_permissions.as_ref(),
|
||||
);
|
||||
let effective_permissions = merge_permission_profiles(
|
||||
additional_permissions.as_ref(),
|
||||
granted_permissions.as_ref(),
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::tools::registry::ToolHandler;
|
||||
use crate::tools::registry::ToolKind;
|
||||
|
||||
pub(crate) fn request_permissions_tool_description() -> String {
|
||||
"Request additional permissions from the user and wait for the client to grant a subset of the requested permission profile. Granted permissions apply automatically to later shell-like commands in the current turn."
|
||||
"Request additional permissions from the user and wait for the client to grant a subset of the requested permission profile. Granted permissions apply automatically to later shell-like commands in the current turn, or for the rest of the session if the client approves them at session scope."
|
||||
.to_string()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user