Simplify permissions available in request permissions tool (#14529)
This commit is contained in:
parent
3e8f47169e
commit
7c7e267501
14 changed files with 203 additions and 260 deletions
|
|
@ -123,6 +123,7 @@ use codex_protocol::protocol::ReviewOutputEvent;
|
|||
use codex_protocol::protocol::TokenCountEvent;
|
||||
use codex_protocol::protocol::TurnDiffEvent;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope as CorePermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile as CoreRequestPermissionProfile;
|
||||
use codex_protocol::request_permissions::RequestPermissionsResponse as CoreRequestPermissionsResponse;
|
||||
use codex_protocol::request_user_input::RequestUserInputAnswer as CoreRequestUserInputAnswer;
|
||||
use codex_protocol::request_user_input::RequestUserInputResponse as CoreRequestUserInputResponse;
|
||||
|
|
@ -699,7 +700,7 @@ pub(crate) async fn apply_bespoke_event_handling(
|
|||
turn_id: request.turn_id.clone(),
|
||||
item_id: request.call_id.clone(),
|
||||
reason: request.reason,
|
||||
permissions: request.permissions.into(),
|
||||
permissions: CorePermissionProfile::from(request.permissions).into(),
|
||||
};
|
||||
let (pending_request_id, rx) = outgoing
|
||||
.send_request(ServerRequestPayload::PermissionsRequestApproval(params))
|
||||
|
|
@ -2227,7 +2228,7 @@ fn mcp_server_elicitation_response_from_client_result(
|
|||
|
||||
async fn on_request_permissions_response(
|
||||
call_id: String,
|
||||
requested_permissions: CorePermissionProfile,
|
||||
requested_permissions: CoreRequestPermissionProfile,
|
||||
pending_request_id: RequestId,
|
||||
receiver: oneshot::Receiver<ClientRequestResult>,
|
||||
conversation: Arc<CodexThread>,
|
||||
|
|
@ -2255,7 +2256,7 @@ async fn on_request_permissions_response(
|
|||
}
|
||||
|
||||
fn request_permissions_response_from_client_result(
|
||||
requested_permissions: CorePermissionProfile,
|
||||
requested_permissions: CoreRequestPermissionProfile,
|
||||
response: std::result::Result<ClientRequestResult, oneshot::error::RecvError>,
|
||||
) -> Option<CoreRequestPermissionsResponse> {
|
||||
let value = match response {
|
||||
|
|
@ -2287,9 +2288,10 @@ fn request_permissions_response_from_client_result(
|
|||
});
|
||||
Some(CoreRequestPermissionsResponse {
|
||||
permissions: intersect_permission_profiles(
|
||||
requested_permissions,
|
||||
requested_permissions.into(),
|
||||
response.permissions.into(),
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
scope: response.scope.to_core(),
|
||||
})
|
||||
}
|
||||
|
|
@ -2646,10 +2648,8 @@ mod tests {
|
|||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::TurnPlanStepStatus;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::models::MacOsAutomationPermission;
|
||||
use codex_protocol::models::MacOsContactsPermission;
|
||||
use codex_protocol::models::MacOsPreferencesPermission;
|
||||
use codex_protocol::models::MacOsSeatbeltProfileExtensions;
|
||||
use codex_protocol::models::FileSystemPermissions as CoreFileSystemPermissions;
|
||||
use codex_protocol::models::NetworkPermissions as CoreNetworkPermissions;
|
||||
use codex_protocol::plan_tool::PlanItemArg;
|
||||
use codex_protocol::plan_tool::StepStatus;
|
||||
use codex_protocol::protocol::CollabResumeBeginEvent;
|
||||
|
|
@ -2660,6 +2660,7 @@ mod tests {
|
|||
use codex_protocol::protocol::RateLimitWindow;
|
||||
use codex_protocol::protocol::TokenUsage;
|
||||
use codex_protocol::protocol::TokenUsageInfo;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use rmcp::model::Content;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
|
@ -2721,7 +2722,7 @@ mod tests {
|
|||
};
|
||||
|
||||
let response = request_permissions_response_from_client_result(
|
||||
CorePermissionProfile::default(),
|
||||
CoreRequestPermissionProfile::default(),
|
||||
Ok(Err(error)),
|
||||
);
|
||||
|
||||
|
|
@ -2729,156 +2730,91 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn request_permissions_response_accepts_partial_macos_grants() {
|
||||
let requested_permissions = CorePermissionProfile {
|
||||
macos: Some(MacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: MacOsPreferencesPermission::ReadWrite,
|
||||
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string(),
|
||||
"com.apple.Reminders".to_string(),
|
||||
]),
|
||||
macos_launch_services: true,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: true,
|
||||
macos_reminders: true,
|
||||
macos_contacts: MacOsContactsPermission::ReadWrite,
|
||||
fn request_permissions_response_accepts_partial_network_and_file_system_grants() {
|
||||
let input_path = if cfg!(target_os = "windows") {
|
||||
r"C:\tmp\input"
|
||||
} else {
|
||||
"/tmp/input"
|
||||
};
|
||||
let output_path = if cfg!(target_os = "windows") {
|
||||
r"C:\tmp\output"
|
||||
} else {
|
||||
"/tmp/output"
|
||||
};
|
||||
let ignored_path = if cfg!(target_os = "windows") {
|
||||
r"C:\tmp\ignored"
|
||||
} else {
|
||||
"/tmp/ignored"
|
||||
};
|
||||
let absolute_path = |path: &str| {
|
||||
AbsolutePathBuf::try_from(std::path::PathBuf::from(path)).expect("absolute path")
|
||||
};
|
||||
let requested_permissions = CoreRequestPermissionProfile {
|
||||
network: Some(CoreNetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
file_system: Some(CoreFileSystemPermissions {
|
||||
read: Some(vec![absolute_path(input_path)]),
|
||||
write: Some(vec![absolute_path(output_path)]),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
let cases = vec![
|
||||
(serde_json::json!({}), CorePermissionProfile::default()),
|
||||
(
|
||||
serde_json::json!({
|
||||
"preferences": "read_only",
|
||||
}),
|
||||
CorePermissionProfile {
|
||||
macos: Some(MacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: MacOsPreferencesPermission::ReadOnly,
|
||||
macos_automation: MacOsAutomationPermission::None,
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
macos_reminders: false,
|
||||
macos_contacts: MacOsContactsPermission::None,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
serde_json::json!({}),
|
||||
CoreRequestPermissionProfile::default(),
|
||||
),
|
||||
(
|
||||
serde_json::json!({
|
||||
"automations": {
|
||||
"bundle_ids": ["com.apple.Notes"],
|
||||
"network": {
|
||||
"enabled": true,
|
||||
},
|
||||
}),
|
||||
CorePermissionProfile {
|
||||
macos: Some(MacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: MacOsPreferencesPermission::None,
|
||||
macos_automation: MacOsAutomationPermission::BundleIds(vec![
|
||||
"com.apple.Notes".to_string(),
|
||||
]),
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
macos_reminders: false,
|
||||
macos_contacts: MacOsContactsPermission::None,
|
||||
CoreRequestPermissionProfile {
|
||||
network: Some(CoreNetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
..Default::default()
|
||||
..CoreRequestPermissionProfile::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
serde_json::json!({
|
||||
"launchServices": true,
|
||||
"fileSystem": {
|
||||
"write": [output_path],
|
||||
},
|
||||
}),
|
||||
CorePermissionProfile {
|
||||
macos: Some(MacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: MacOsPreferencesPermission::None,
|
||||
macos_automation: MacOsAutomationPermission::None,
|
||||
macos_launch_services: true,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
macos_reminders: false,
|
||||
macos_contacts: MacOsContactsPermission::None,
|
||||
CoreRequestPermissionProfile {
|
||||
file_system: Some(CoreFileSystemPermissions {
|
||||
read: None,
|
||||
write: Some(vec![absolute_path(output_path)]),
|
||||
}),
|
||||
..Default::default()
|
||||
..CoreRequestPermissionProfile::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
serde_json::json!({
|
||||
"accessibility": true,
|
||||
"fileSystem": {
|
||||
"read": [input_path],
|
||||
"write": [output_path, ignored_path],
|
||||
},
|
||||
"macos": {
|
||||
"calendar": true,
|
||||
},
|
||||
}),
|
||||
CorePermissionProfile {
|
||||
macos: Some(MacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: MacOsPreferencesPermission::None,
|
||||
macos_automation: MacOsAutomationPermission::None,
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: true,
|
||||
macos_calendar: false,
|
||||
macos_reminders: false,
|
||||
macos_contacts: MacOsContactsPermission::None,
|
||||
CoreRequestPermissionProfile {
|
||||
file_system: Some(CoreFileSystemPermissions {
|
||||
read: Some(vec![absolute_path(input_path)]),
|
||||
write: Some(vec![absolute_path(output_path)]),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
serde_json::json!({
|
||||
"calendar": true,
|
||||
}),
|
||||
CorePermissionProfile {
|
||||
macos: Some(MacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: MacOsPreferencesPermission::None,
|
||||
macos_automation: MacOsAutomationPermission::None,
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: true,
|
||||
macos_reminders: false,
|
||||
macos_contacts: MacOsContactsPermission::None,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
serde_json::json!({
|
||||
"reminders": true,
|
||||
}),
|
||||
CorePermissionProfile {
|
||||
macos: Some(MacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: MacOsPreferencesPermission::None,
|
||||
macos_automation: MacOsAutomationPermission::None,
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
macos_reminders: true,
|
||||
macos_contacts: MacOsContactsPermission::None,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
serde_json::json!({
|
||||
"contacts": "read_only",
|
||||
}),
|
||||
CorePermissionProfile {
|
||||
macos: Some(MacOsSeatbeltProfileExtensions {
|
||||
macos_preferences: MacOsPreferencesPermission::None,
|
||||
macos_automation: MacOsAutomationPermission::None,
|
||||
macos_launch_services: false,
|
||||
macos_accessibility: false,
|
||||
macos_calendar: false,
|
||||
macos_reminders: false,
|
||||
macos_contacts: MacOsContactsPermission::ReadOnly,
|
||||
}),
|
||||
..Default::default()
|
||||
..CoreRequestPermissionProfile::default()
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
for (granted_macos, expected_permissions) in cases {
|
||||
for (granted_permissions, expected_permissions) in cases {
|
||||
let response = request_permissions_response_from_client_result(
|
||||
requested_permissions.clone(),
|
||||
Ok(Ok(serde_json::json!({
|
||||
"permissions": {
|
||||
"macos": granted_macos,
|
||||
},
|
||||
"permissions": granted_permissions,
|
||||
}))),
|
||||
)
|
||||
.expect("response should be accepted");
|
||||
|
|
@ -2896,7 +2832,7 @@ mod tests {
|
|||
#[test]
|
||||
fn request_permissions_response_preserves_session_scope() {
|
||||
let response = request_permissions_response_from_client_result(
|
||||
CorePermissionProfile::default(),
|
||||
CoreRequestPermissionProfile::default(),
|
||||
Ok(Ok(serde_json::json!({
|
||||
"scope": "session",
|
||||
"permissions": {},
|
||||
|
|
@ -2907,7 +2843,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
response,
|
||||
CoreRequestPermissionsResponse {
|
||||
permissions: CorePermissionProfile::default(),
|
||||
permissions: CoreRequestPermissionProfile::default(),
|
||||
scope: CorePermissionGrantScope::Session,
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ use codex_protocol::protocol::TurnContextNetworkItem;
|
|||
use codex_protocol::protocol::TurnStartedEvent;
|
||||
use codex_protocol::protocol::W3cTraceContext;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||
use codex_protocol::request_permissions::RequestPermissionsArgs;
|
||||
use codex_protocol::request_permissions::RequestPermissionsEvent;
|
||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||
|
|
@ -2908,7 +2909,7 @@ impl Session {
|
|||
match turn_context.approval_policy.value() {
|
||||
AskForApproval::Never => {
|
||||
return Some(RequestPermissionsResponse {
|
||||
permissions: PermissionProfile::default(),
|
||||
permissions: RequestPermissionProfile::default(),
|
||||
scope: PermissionGrantScope::Turn,
|
||||
});
|
||||
}
|
||||
|
|
@ -2916,7 +2917,7 @@ impl Session {
|
|||
if !granular_config.allows_request_permissions() =>
|
||||
{
|
||||
return Some(RequestPermissionsResponse {
|
||||
permissions: PermissionProfile::default(),
|
||||
permissions: RequestPermissionProfile::default(),
|
||||
scope: PermissionGrantScope::Turn,
|
||||
});
|
||||
}
|
||||
|
|
@ -3102,7 +3103,7 @@ impl Session {
|
|||
if entry.is_some() && !response.permissions.is_empty() {
|
||||
match response.scope {
|
||||
PermissionGrantScope::Turn => {
|
||||
ts.record_granted_permissions(response.permissions.clone());
|
||||
ts.record_granted_permissions(response.permissions.clone().into());
|
||||
}
|
||||
PermissionGrantScope::Session => {
|
||||
granted_for_session = Some(response.permissions.clone());
|
||||
|
|
@ -3116,7 +3117,7 @@ impl Session {
|
|||
};
|
||||
if let Some(permissions) = granted_for_session {
|
||||
let mut state = self.state.lock().await;
|
||||
state.record_granted_permissions(permissions);
|
||||
state.record_granted_permissions(permissions.into());
|
||||
}
|
||||
match entry {
|
||||
Some(tx_response) => {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
use super::*;
|
||||
use async_channel::bounded;
|
||||
use codex_protocol::models::NetworkPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::AgentStatus;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::RawResponseItemEvent;
|
||||
use codex_protocol::protocol::TurnAbortReason;
|
||||
use codex_protocol::protocol::TurnAbortedEvent;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||
use codex_protocol::request_permissions::RequestPermissionsEvent;
|
||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
|
@ -150,11 +150,11 @@ async fn handle_request_permissions_uses_tool_call_id_for_round_trip() {
|
|||
|
||||
let call_id = "tool-call-1".to_string();
|
||||
let expected_response = RequestPermissionsResponse {
|
||||
permissions: PermissionProfile {
|
||||
permissions: RequestPermissionProfile {
|
||||
network: Some(NetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
..PermissionProfile::default()
|
||||
..RequestPermissionProfile::default()
|
||||
},
|
||||
scope: PermissionGrantScope::Turn,
|
||||
};
|
||||
|
|
@ -175,11 +175,11 @@ async fn handle_request_permissions_uses_tool_call_id_for_round_trip() {
|
|||
call_id: request_call_id,
|
||||
turn_id: "child-turn-1".to_string(),
|
||||
reason: Some("need access".to_string()),
|
||||
permissions: PermissionProfile {
|
||||
permissions: RequestPermissionProfile {
|
||||
network: Some(NetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
..PermissionProfile::default()
|
||||
..RequestPermissionProfile::default()
|
||||
},
|
||||
},
|
||||
&cancel_token,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ use codex_protocol::permissions::FileSystemSpecialPath;
|
|||
use codex_protocol::protocol::ReadOnlyAccess;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||
use tracing::Span;
|
||||
|
||||
use crate::protocol::CompactedItem;
|
||||
|
|
@ -2216,11 +2217,11 @@ async fn notify_request_permissions_response_ignores_unmatched_call_id() {
|
|||
.notify_request_permissions_response(
|
||||
"missing",
|
||||
codex_protocol::request_permissions::RequestPermissionsResponse {
|
||||
permissions: codex_protocol::models::PermissionProfile {
|
||||
permissions: RequestPermissionProfile {
|
||||
network: Some(codex_protocol::models::NetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
..Default::default()
|
||||
..RequestPermissionProfile::default()
|
||||
},
|
||||
scope: PermissionGrantScope::Turn,
|
||||
},
|
||||
|
|
@ -2252,11 +2253,11 @@ async fn request_permissions_emits_event_when_granular_policy_allows_requests()
|
|||
let turn_context = Arc::new(turn_context);
|
||||
let call_id = "call-1".to_string();
|
||||
let expected_response = codex_protocol::request_permissions::RequestPermissionsResponse {
|
||||
permissions: codex_protocol::models::PermissionProfile {
|
||||
permissions: RequestPermissionProfile {
|
||||
network: Some(codex_protocol::models::NetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
..Default::default()
|
||||
..RequestPermissionProfile::default()
|
||||
},
|
||||
scope: PermissionGrantScope::Turn,
|
||||
};
|
||||
|
|
@ -2272,11 +2273,11 @@ async fn request_permissions_emits_event_when_granular_policy_allows_requests()
|
|||
call_id,
|
||||
codex_protocol::request_permissions::RequestPermissionsArgs {
|
||||
reason: Some("need network".to_string()),
|
||||
permissions: codex_protocol::models::PermissionProfile {
|
||||
permissions: RequestPermissionProfile {
|
||||
network: Some(codex_protocol::models::NetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
..Default::default()
|
||||
..RequestPermissionProfile::default()
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
@ -2332,11 +2333,11 @@ async fn request_permissions_is_auto_denied_when_granular_policy_blocks_tool_req
|
|||
call_id,
|
||||
codex_protocol::request_permissions::RequestPermissionsArgs {
|
||||
reason: Some("need network".to_string()),
|
||||
permissions: codex_protocol::models::PermissionProfile {
|
||||
permissions: RequestPermissionProfile {
|
||||
network: Some(codex_protocol::models::NetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
..Default::default()
|
||||
..RequestPermissionProfile::default()
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
@ -2346,7 +2347,7 @@ async fn request_permissions_is_auto_denied_when_granular_policy_blocks_tool_req
|
|||
response,
|
||||
Some(
|
||||
codex_protocol::request_permissions::RequestPermissionsResponse {
|
||||
permissions: codex_protocol::models::PermissionProfile::default(),
|
||||
permissions: RequestPermissionProfile::default(),
|
||||
scope: PermissionGrantScope::Turn,
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,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, or for the rest of the session if the client approves them at session scope."
|
||||
"Request additional filesystem or network 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()
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +45,8 @@ impl ToolHandler for RequestPermissionsHandler {
|
|||
|
||||
let mut args: RequestPermissionsArgs =
|
||||
parse_arguments_with_base_path(&arguments, turn.cwd.as_path())?;
|
||||
args.permissions = normalize_additional_permissions(args.permissions)
|
||||
args.permissions = normalize_additional_permissions(args.permissions.into())
|
||||
.map(codex_protocol::request_permissions::RequestPermissionProfile::from)
|
||||
.map_err(FunctionCallError::RespondToModel)?;
|
||||
if args.permissions.is_empty() {
|
||||
return Err(FunctionCallError::RespondToModel(
|
||||
|
|
|
|||
|
|
@ -494,44 +494,21 @@ fn create_file_system_permissions_schema() -> JsonSchema {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_macos_permissions_schema() -> JsonSchema {
|
||||
JsonSchema::Object {
|
||||
properties: BTreeMap::from([
|
||||
(
|
||||
"preferences".to_string(),
|
||||
JsonSchema::String {
|
||||
description: Some(
|
||||
"macOS preferences access. Supported values: `none`, `read_only`, or `read_write`."
|
||||
.to_string(),
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"automations".to_string(),
|
||||
JsonSchema::Array {
|
||||
items: Box::new(JsonSchema::String { description: None }),
|
||||
description: Some("macOS automation access as app bundle identifiers.".to_string()),
|
||||
},
|
||||
),
|
||||
(
|
||||
"accessibility".to_string(),
|
||||
JsonSchema::Boolean {
|
||||
description: Some("Whether to request macOS accessibility access.".to_string()),
|
||||
},
|
||||
),
|
||||
(
|
||||
"calendar".to_string(),
|
||||
JsonSchema::Boolean {
|
||||
description: Some("Whether to request macOS calendar access.".to_string()),
|
||||
},
|
||||
),
|
||||
]),
|
||||
required: None,
|
||||
additional_properties: Some(false.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_permissions_schema() -> JsonSchema {
|
||||
fn create_additional_permissions_schema() -> JsonSchema {
|
||||
JsonSchema::Object {
|
||||
properties: BTreeMap::from([
|
||||
("network".to_string(), create_network_permissions_schema()),
|
||||
(
|
||||
"file_system".to_string(),
|
||||
create_file_system_permissions_schema(),
|
||||
),
|
||||
]),
|
||||
required: None,
|
||||
additional_properties: Some(false.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_request_permissions_schema() -> JsonSchema {
|
||||
JsonSchema::Object {
|
||||
properties: BTreeMap::from([
|
||||
("network".to_string(), create_network_permissions_schema()),
|
||||
|
|
@ -539,7 +516,6 @@ fn create_permissions_schema() -> JsonSchema {
|
|||
"file_system".to_string(),
|
||||
create_file_system_permissions_schema(),
|
||||
),
|
||||
("macos".to_string(), create_macos_permissions_schema()),
|
||||
]),
|
||||
required: None,
|
||||
additional_properties: Some(false.into()),
|
||||
|
|
@ -555,7 +531,7 @@ fn create_approval_parameters(
|
|||
JsonSchema::String {
|
||||
description: Some(
|
||||
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\"."
|
||||
"Sandbox permissions for the command. Use \"with_additional_permissions\" to request additional sandboxed filesystem or network 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\"."
|
||||
}
|
||||
|
|
@ -592,7 +568,7 @@ fn create_approval_parameters(
|
|||
if exec_permission_approvals_enabled {
|
||||
properties.insert(
|
||||
"additional_permissions".to_string(),
|
||||
create_permissions_schema(),
|
||||
create_additional_permissions_schema(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1455,7 +1431,10 @@ fn create_request_permissions_tool() -> ToolSpec {
|
|||
),
|
||||
},
|
||||
);
|
||||
properties.insert("permissions".to_string(), create_permissions_schema());
|
||||
properties.insert(
|
||||
"permissions".to_string(),
|
||||
create_request_permissions_schema(),
|
||||
);
|
||||
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: "request_permissions".to_string(),
|
||||
|
|
|
|||
|
|
@ -2205,7 +2205,7 @@ fn shell_tool_with_request_permission_includes_additional_permissions() {
|
|||
panic!("expected sandbox_permissions description");
|
||||
};
|
||||
assert!(description.contains("with_additional_permissions"));
|
||||
assert!(description.contains("macOS permissions"));
|
||||
assert!(description.contains("filesystem or network permissions"));
|
||||
|
||||
let Some(JsonSchema::Object {
|
||||
properties: additional_properties,
|
||||
|
|
@ -2216,7 +2216,7 @@ fn shell_tool_with_request_permission_includes_additional_permissions() {
|
|||
};
|
||||
assert!(additional_properties.contains_key("network"));
|
||||
assert!(additional_properties.contains_key("file_system"));
|
||||
assert!(additional_properties.contains_key("macos"));
|
||||
assert!(!additional_properties.contains_key("macos"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -2240,7 +2240,7 @@ fn request_permissions_tool_includes_full_permission_schema() {
|
|||
assert_eq!(additional_properties, &Some(false.into()));
|
||||
assert!(permission_properties.contains_key("network"));
|
||||
assert!(permission_properties.contains_key("file_system"));
|
||||
assert!(permission_properties.contains_key("macos"));
|
||||
assert!(!permission_properties.contains_key("macos"));
|
||||
|
||||
let Some(JsonSchema::Object {
|
||||
properties: network_properties,
|
||||
|
|
@ -2264,20 +2264,6 @@ fn request_permissions_tool_includes_full_permission_schema() {
|
|||
assert_eq!(additional_properties, &Some(false.into()));
|
||||
assert!(file_system_properties.contains_key("read"));
|
||||
assert!(file_system_properties.contains_key("write"));
|
||||
|
||||
let Some(JsonSchema::Object {
|
||||
properties: macos_properties,
|
||||
additional_properties,
|
||||
..
|
||||
}) = permission_properties.get("macos")
|
||||
else {
|
||||
panic!("expected macos object");
|
||||
};
|
||||
assert_eq!(additional_properties, &Some(false.into()));
|
||||
assert!(macos_properties.contains_key("preferences"));
|
||||
assert!(macos_properties.contains_key("automations"));
|
||||
assert!(macos_properties.contains_key("accessibility"));
|
||||
assert!(macos_properties.contains_key("calendar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use codex_protocol::protocol::Op;
|
|||
use codex_protocol::protocol::ReviewDecision;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
|
@ -85,10 +86,10 @@ fn parse_result(item: &Value) -> CommandResult {
|
|||
}
|
||||
}
|
||||
|
||||
fn shell_event_with_request_permissions(
|
||||
fn shell_event_with_request_permissions<S: serde::Serialize>(
|
||||
call_id: &str,
|
||||
command: &str,
|
||||
additional_permissions: &PermissionProfile,
|
||||
additional_permissions: &S,
|
||||
) -> Result<Value> {
|
||||
let args = json!({
|
||||
"command": command,
|
||||
|
|
@ -103,7 +104,7 @@ fn shell_event_with_request_permissions(
|
|||
fn request_permissions_tool_event(
|
||||
call_id: &str,
|
||||
reason: &str,
|
||||
permissions: &PermissionProfile,
|
||||
permissions: &RequestPermissionProfile,
|
||||
) -> Result<Value> {
|
||||
let args = json!({
|
||||
"reason": reason,
|
||||
|
|
@ -131,10 +132,10 @@ fn exec_command_event(call_id: &str, command: &str) -> Result<Value> {
|
|||
Ok(ev_function_call(call_id, "exec_command", &args_str))
|
||||
}
|
||||
|
||||
fn exec_command_event_with_request_permissions(
|
||||
fn exec_command_event_with_request_permissions<S: serde::Serialize>(
|
||||
call_id: &str,
|
||||
command: &str,
|
||||
additional_permissions: &PermissionProfile,
|
||||
additional_permissions: &S,
|
||||
) -> Result<Value> {
|
||||
let args = json!({
|
||||
"cmd": command,
|
||||
|
|
@ -259,7 +260,7 @@ async fn wait_for_exec_approval_or_completion(
|
|||
async fn expect_request_permissions_event(
|
||||
test: &TestCodex,
|
||||
expected_call_id: &str,
|
||||
) -> PermissionProfile {
|
||||
) -> RequestPermissionProfile {
|
||||
let event = wait_for_event(&test.codex, |event| {
|
||||
matches!(
|
||||
event,
|
||||
|
|
@ -288,23 +289,23 @@ fn workspace_write_excluding_tmp() -> SandboxPolicy {
|
|||
}
|
||||
}
|
||||
|
||||
fn requested_directory_write_permissions(path: &Path) -> PermissionProfile {
|
||||
PermissionProfile {
|
||||
fn requested_directory_write_permissions(path: &Path) -> RequestPermissionProfile {
|
||||
RequestPermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
read: Some(vec![]),
|
||||
write: Some(vec![absolute_path(path)]),
|
||||
}),
|
||||
..Default::default()
|
||||
..RequestPermissionProfile::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn normalized_directory_write_permissions(path: &Path) -> Result<PermissionProfile> {
|
||||
Ok(PermissionProfile {
|
||||
fn normalized_directory_write_permissions(path: &Path) -> Result<RequestPermissionProfile> {
|
||||
Ok(RequestPermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
read: Some(vec![]),
|
||||
write: Some(vec![AbsolutePathBuf::try_from(path.canonicalize()?)?]),
|
||||
}),
|
||||
..Default::default()
|
||||
..RequestPermissionProfile::default()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -477,7 +478,7 @@ async fn request_permissions_tool_is_auto_denied_when_granular_request_permissio
|
|||
assert_eq!(
|
||||
result,
|
||||
RequestPermissionsResponse {
|
||||
permissions: PermissionProfile::default(),
|
||||
permissions: RequestPermissionProfile::default(),
|
||||
scope: PermissionGrantScope::Turn,
|
||||
}
|
||||
);
|
||||
|
|
@ -820,21 +821,21 @@ async fn workspace_write_with_additional_permissions_can_write_outside_cwd() ->
|
|||
"printf {:?} > {:?} && cat {:?}",
|
||||
"outside-cwd-ok", outside_write, outside_write
|
||||
);
|
||||
let requested_permissions = PermissionProfile {
|
||||
let requested_permissions = RequestPermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
read: Some(vec![]),
|
||||
write: Some(vec![absolute_path(outside_dir.path())]),
|
||||
}),
|
||||
..Default::default()
|
||||
..RequestPermissionProfile::default()
|
||||
};
|
||||
let normalized_requested_permissions = PermissionProfile {
|
||||
let normalized_requested_permissions = RequestPermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
read: Some(vec![]),
|
||||
write: Some(vec![AbsolutePathBuf::try_from(
|
||||
outside_dir.path().canonicalize()?,
|
||||
)?]),
|
||||
}),
|
||||
..Default::default()
|
||||
..RequestPermissionProfile::default()
|
||||
};
|
||||
let event = shell_event_with_request_permissions(call_id, &command, &requested_permissions)?;
|
||||
|
||||
|
|
@ -861,7 +862,7 @@ async fn workspace_write_with_additional_permissions_can_write_outside_cwd() ->
|
|||
let approval = expect_exec_approval(&test, &command).await;
|
||||
assert_eq!(
|
||||
approval.additional_permissions,
|
||||
Some(normalized_requested_permissions)
|
||||
Some(normalized_requested_permissions.into())
|
||||
);
|
||||
test.codex
|
||||
.submit(Op::ExecApproval {
|
||||
|
|
@ -1024,14 +1025,14 @@ async fn request_permissions_grants_apply_to_later_exec_command_calls() -> Resul
|
|||
"printf {:?} > {:?} && cat {:?}",
|
||||
"sticky-grant-ok", outside_write, outside_write
|
||||
);
|
||||
let requested_permissions = PermissionProfile {
|
||||
let requested_permissions = RequestPermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
read: Some(vec![]),
|
||||
write: Some(vec![absolute_path(outside_dir.path())]),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
let normalized_requested_permissions = PermissionProfile {
|
||||
let normalized_requested_permissions = RequestPermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
read: Some(vec![]),
|
||||
write: Some(vec![AbsolutePathBuf::try_from(
|
||||
|
|
@ -1092,7 +1093,7 @@ async fn request_permissions_grants_apply_to_later_exec_command_calls() -> Resul
|
|||
if let Some(approval) = wait_for_exec_approval_or_completion(&test).await {
|
||||
assert_eq!(
|
||||
approval.additional_permissions,
|
||||
Some(normalized_requested_permissions.clone())
|
||||
Some(normalized_requested_permissions.clone().into())
|
||||
);
|
||||
test.codex
|
||||
.submit(Op::ExecApproval {
|
||||
|
|
@ -1488,7 +1489,7 @@ async fn partial_request_permissions_grants_do_not_preapprove_new_permissions()
|
|||
"partial-grant-ok", second_write, second_write
|
||||
);
|
||||
|
||||
let requested_permissions = PermissionProfile {
|
||||
let requested_permissions = RequestPermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
read: Some(vec![]),
|
||||
write: Some(vec![
|
||||
|
|
@ -1496,9 +1497,9 @@ async fn partial_request_permissions_grants_do_not_preapprove_new_permissions()
|
|||
absolute_path(second_dir.path()),
|
||||
]),
|
||||
}),
|
||||
..Default::default()
|
||||
..RequestPermissionProfile::default()
|
||||
};
|
||||
let normalized_requested_permissions = PermissionProfile {
|
||||
let normalized_requested_permissions = RequestPermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
read: Some(vec![]),
|
||||
write: Some(vec![
|
||||
|
|
@ -1506,7 +1507,7 @@ async fn partial_request_permissions_grants_do_not_preapprove_new_permissions()
|
|||
AbsolutePathBuf::try_from(second_dir.path().canonicalize()?)?,
|
||||
]),
|
||||
}),
|
||||
..Default::default()
|
||||
..RequestPermissionProfile::default()
|
||||
};
|
||||
let granted_permissions = normalized_directory_write_permissions(first_dir.path())?;
|
||||
let second_dir_permissions = requested_directory_write_permissions(second_dir.path());
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ use anyhow::Result;
|
|||
use codex_core::config::Constrained;
|
||||
use codex_core::features::Feature;
|
||||
use codex_protocol::models::FileSystemPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||
use codex_protocol::user_input::UserInput;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
|
|
@ -42,7 +42,7 @@ fn absolute_path(path: &Path) -> AbsolutePathBuf {
|
|||
fn request_permissions_tool_event(
|
||||
call_id: &str,
|
||||
reason: &str,
|
||||
permissions: &PermissionProfile,
|
||||
permissions: &RequestPermissionProfile,
|
||||
) -> Result<Value> {
|
||||
let args = json!({
|
||||
"reason": reason,
|
||||
|
|
@ -79,23 +79,23 @@ fn workspace_write_excluding_tmp() -> SandboxPolicy {
|
|||
}
|
||||
}
|
||||
|
||||
fn requested_directory_write_permissions(path: &Path) -> PermissionProfile {
|
||||
PermissionProfile {
|
||||
fn requested_directory_write_permissions(path: &Path) -> RequestPermissionProfile {
|
||||
RequestPermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
read: Some(vec![]),
|
||||
write: Some(vec![absolute_path(path)]),
|
||||
}),
|
||||
..Default::default()
|
||||
..RequestPermissionProfile::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn normalized_directory_write_permissions(path: &Path) -> Result<PermissionProfile> {
|
||||
Ok(PermissionProfile {
|
||||
fn normalized_directory_write_permissions(path: &Path) -> Result<RequestPermissionProfile> {
|
||||
Ok(RequestPermissionProfile {
|
||||
file_system: Some(FileSystemPermissions {
|
||||
read: Some(vec![]),
|
||||
write: Some(vec![AbsolutePathBuf::try_from(path.canonicalize()?)?]),
|
||||
}),
|
||||
..Default::default()
|
||||
..RequestPermissionProfile::default()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ async fn submit_turn(
|
|||
async fn expect_request_permissions_event(
|
||||
test: &TestCodex,
|
||||
expected_call_id: &str,
|
||||
) -> PermissionProfile {
|
||||
) -> RequestPermissionProfile {
|
||||
let event = wait_for_event(&test.codex, |event| {
|
||||
matches!(
|
||||
event,
|
||||
|
|
|
|||
|
|
@ -11,10 +11,8 @@ When you need extra sandboxed permissions for one command, use:
|
|||
- `network.enabled`: set to `true` to enable network access
|
||||
- `file_system.read`: list of paths that need read access
|
||||
- `file_system.write`: list of paths that need write access
|
||||
- `macos.preferences`: `readonly` or `readwrite`
|
||||
- `macos.automations`: list of bundle IDs that need Apple Events access
|
||||
- `macos.accessibility`: set to `true` to allow accessibility APIs
|
||||
- `macos.calendar`: set to `true` to allow Calendar access
|
||||
|
||||
When using the `request_permissions` tool directly, only request `network` and `file_system` permissions.
|
||||
|
||||
This keeps execution inside the current sandbox policy, while adding only the requested permissions for that command, unless an exec-policy allow rule applies and authorizes running the command outside the sandbox.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use crate::models::FileSystemPermissions;
|
||||
use crate::models::NetworkPermissions;
|
||||
use crate::models::PermissionProfile;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
|
@ -12,16 +14,48 @@ pub enum PermissionGrantScope {
|
|||
Session,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct RequestPermissionProfile {
|
||||
pub network: Option<NetworkPermissions>,
|
||||
pub file_system: Option<FileSystemPermissions>,
|
||||
}
|
||||
|
||||
impl RequestPermissionProfile {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.network.is_none() && self.file_system.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RequestPermissionProfile> for PermissionProfile {
|
||||
fn from(value: RequestPermissionProfile) -> Self {
|
||||
Self {
|
||||
network: value.network,
|
||||
file_system: value.file_system,
|
||||
macos: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PermissionProfile> for RequestPermissionProfile {
|
||||
fn from(value: PermissionProfile) -> Self {
|
||||
Self {
|
||||
network: value.network,
|
||||
file_system: value.file_system,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
||||
pub struct RequestPermissionsArgs {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub reason: Option<String>,
|
||||
pub permissions: PermissionProfile,
|
||||
pub permissions: RequestPermissionProfile,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
||||
pub struct RequestPermissionsResponse {
|
||||
pub permissions: PermissionProfile,
|
||||
pub permissions: RequestPermissionProfile,
|
||||
#[serde(default)]
|
||||
pub scope: PermissionGrantScope,
|
||||
}
|
||||
|
|
@ -36,5 +70,5 @@ pub struct RequestPermissionsEvent {
|
|||
pub turn_id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub reason: Option<String>,
|
||||
pub permissions: PermissionProfile,
|
||||
pub permissions: RequestPermissionProfile,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3306,7 +3306,7 @@ impl App {
|
|||
lines.push(Line::from(""));
|
||||
}
|
||||
if let Some(rule_line) =
|
||||
crate::bottom_pane::format_additional_permissions_rule(&permissions)
|
||||
crate::bottom_pane::format_requested_permissions_rule(&permissions)
|
||||
{
|
||||
lines.push(Line::from(vec![
|
||||
"Permission rule: ".into(),
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ use codex_protocol::protocol::NetworkPolicyRuleAction;
|
|||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyEventKind;
|
||||
|
|
@ -60,7 +61,7 @@ pub(crate) enum ApprovalRequest {
|
|||
thread_label: Option<String>,
|
||||
call_id: String,
|
||||
reason: Option<String>,
|
||||
permissions: PermissionProfile,
|
||||
permissions: RequestPermissionProfile,
|
||||
},
|
||||
ApplyPatch {
|
||||
thread_id: ThreadId,
|
||||
|
|
@ -272,7 +273,7 @@ impl ApprovalOverlay {
|
|||
fn handle_permissions_decision(
|
||||
&self,
|
||||
call_id: &str,
|
||||
permissions: &PermissionProfile,
|
||||
permissions: &RequestPermissionProfile,
|
||||
decision: ReviewDecision,
|
||||
) {
|
||||
let Some(request) = self.current_request.as_ref() else {
|
||||
|
|
@ -567,7 +568,7 @@ fn build_header(request: &ApprovalRequest) -> Box<dyn Renderable> {
|
|||
header.push(Line::from(vec!["Reason: ".into(), reason.clone().italic()]));
|
||||
header.push(Line::from(""));
|
||||
}
|
||||
if let Some(rule_line) = format_additional_permissions_rule(permissions) {
|
||||
if let Some(rule_line) = format_requested_permissions_rule(permissions) {
|
||||
header.push(Line::from(vec![
|
||||
"Permission rule: ".into(),
|
||||
rule_line.cyan(),
|
||||
|
|
@ -821,6 +822,12 @@ pub(crate) fn format_additional_permissions_rule(
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn format_requested_permissions_rule(
|
||||
permissions: &RequestPermissionProfile,
|
||||
) -> Option<String> {
|
||||
format_additional_permissions_rule(&permissions.clone().into())
|
||||
}
|
||||
|
||||
fn patch_options() -> Vec<ApprovalOption> {
|
||||
vec![
|
||||
ApprovalOption {
|
||||
|
|
@ -957,7 +964,7 @@ mod tests {
|
|||
thread_label: None,
|
||||
call_id: "test".to_string(),
|
||||
reason: Some("need workspace access".to_string()),
|
||||
permissions: PermissionProfile {
|
||||
permissions: RequestPermissionProfile {
|
||||
network: Some(NetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
|
|
@ -965,7 +972,6 @@ mod tests {
|
|||
read: Some(vec![absolute_path("/tmp/readme.txt")]),
|
||||
write: Some(vec![absolute_path("/tmp/out.txt")]),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ pub(crate) use app_link_view::AppLinkView;
|
|||
pub(crate) use app_link_view::AppLinkViewParams;
|
||||
pub(crate) use approval_overlay::ApprovalOverlay;
|
||||
pub(crate) use approval_overlay::ApprovalRequest;
|
||||
pub(crate) use approval_overlay::format_additional_permissions_rule;
|
||||
pub(crate) use approval_overlay::format_requested_permissions_rule;
|
||||
pub(crate) use mcp_server_elicitation::McpServerElicitationFormRequest;
|
||||
pub(crate) use mcp_server_elicitation::McpServerElicitationOverlay;
|
||||
pub(crate) use request_user_input::RequestUserInputOverlay;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue