From d0fa2d81d894c38bf4ab14f3d025ce19f8b5cbd8 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Mon, 11 May 2026 12:08:26 -0700 Subject: [PATCH] feat(connectors): support managed app tool approval requirements (#21061) ## Why Managed requirements can already centrally disable apps, but they could not express the per-tool app approval rules that normal config already supports. That left admins without a way to enforce connector tool approvals through `/etc/codex/requirements.toml` or cloud requirements. ## What changed - Extend app requirements with per-tool `approval_mode` entries. - Merge managed app tool requirements across managed sources while preserving higher-precedence exact tool settings. - Apply managed tool approvals separately from user app config so managed policy is matched only on raw MCP `tool.name`, while user config keeps the existing raw-name-then-title convenience fallback. - Add coverage for local requirements, cloud requirements parsing, managed-over-user precedence, and a title-collision case that must not widen managed auto-approval. ## Configuration shape Local `/etc/codex/requirements.toml` and cloud requirements use the same TOML shape: ```toml [apps.connector_123123.tools."calendar/list_events"] approval_mode = "approve" ``` This is a per-tool approval rule keyed by app ID and raw MCP tool name, not an app-level boolean such as `apps.connector_123123.approve = true`. --- codex-rs/cloud-requirements/src/lib.rs | 35 +++ codex-rs/config/src/config_requirements.rs | 183 ++++++++++++-- codex-rs/config/src/lib.rs | 2 + codex-rs/core/src/connectors.rs | 39 ++- codex-rs/core/src/connectors_tests.rs | 223 ++++++++++++++++++ .../chatwidget/tests/popups_and_settings.rs | 2 + 6 files changed, 460 insertions(+), 24 deletions(-) diff --git a/codex-rs/cloud-requirements/src/lib.rs b/codex-rs/cloud-requirements/src/lib.rs index 6f283b43d0..360e99a217 100644 --- a/codex-rs/cloud-requirements/src/lib.rs +++ b/codex-rs/cloud-requirements/src/lib.rs @@ -829,6 +829,7 @@ mod tests { use super::*; use base64::Engine; use base64::engine::general_purpose::URL_SAFE_NO_PAD; + use codex_config::AppToolApproval; use codex_config::types::AuthCredentialsStoreMode; use codex_login::auth::AgentIdentityAuth; use codex_login::auth::AgentIdentityAuthRecord; @@ -1399,6 +1400,40 @@ enabled = false "connector_5f3c8c41a1e54ad7a76272c89e2554fa".to_string(), codex_config::AppRequirementToml { enabled: Some(false), + tools: None, + }, + )]), + }), + ..Default::default() + }) + ); + } + + #[tokio::test] + async fn fetch_cloud_requirements_parses_apps_tool_requirements_toml() { + let result = parse_for_fetch(Some( + r#" +[apps.connector_5f3c8c41a1e54ad7a76272c89e2554fa.tools."calendar/list_events"] +approval_mode = "approve" +"#, + )); + + assert_eq!( + result, + Some(ConfigRequirementsToml { + apps: Some(codex_config::AppsRequirementsToml { + apps: BTreeMap::from([( + "connector_5f3c8c41a1e54ad7a76272c89e2554fa".to_string(), + codex_config::AppRequirementToml { + enabled: None, + tools: Some(codex_config::AppToolsRequirementsToml { + tools: BTreeMap::from([( + "calendar/list_events".to_string(), + codex_config::AppToolRequirementToml { + approval_mode: Some(AppToolApproval::Approve), + }, + )]), + }), }, )]), }), diff --git a/codex-rs/config/src/config_requirements.rs b/codex-rs/config/src/config_requirements.rs index 59d1cde9ea..8bf3148ba8 100644 --- a/codex-rs/config/src/config_requirements.rs +++ b/codex-rs/config/src/config_requirements.rs @@ -18,6 +18,7 @@ use super::requirements_exec_policy::RequirementsExecPolicyToml; use crate::Constrained; use crate::ConstraintError; use crate::ManagedHooksRequirementsToml; +use crate::mcp_types::AppToolApproval; #[derive(Debug, Clone, PartialEq, Eq)] pub enum RequirementSource { @@ -597,9 +598,43 @@ impl FeatureRequirementsToml { } } +#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)] +pub struct AppToolRequirementToml { + pub approval_mode: Option, +} + +impl AppToolRequirementToml { + pub fn is_empty(&self) -> bool { + self.approval_mode.is_none() + } +} + +#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)] +pub struct AppToolsRequirementsToml { + #[serde(default, flatten)] + pub tools: BTreeMap, +} + +impl AppToolsRequirementsToml { + pub fn is_empty(&self) -> bool { + self.tools.values().all(AppToolRequirementToml::is_empty) + } +} + #[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)] pub struct AppRequirementToml { pub enabled: Option, + pub tools: Option, +} + +impl AppRequirementToml { + pub fn is_empty(&self) -> bool { + self.enabled.is_none() + && self + .tools + .as_ref() + .is_none_or(AppToolsRequirementsToml::is_empty) + } } #[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)] @@ -610,14 +645,14 @@ pub struct AppsRequirementsToml { impl AppsRequirementsToml { pub fn is_empty(&self) -> bool { - self.apps.values().all(|app| app.enabled.is_none()) + self.apps.values().all(AppRequirementToml::is_empty) } } -/// Merge `enabled` configs from a lower-precedence source into an existing higher-precedence set. -/// This lets managed sources (for example Cloud/MDM) enforce setting disablement across layers. -/// Implemented with AppsRequirementsToml for now, could be abstracted if we have more enablement-style configs in the future. -pub(crate) fn merge_enablement_settings_descending( +/// Merge app requirements from a lower-precedence source into an existing higher-precedence set. +/// This lets managed sources (for example Cloud/MDM) enforce setting disablement across layers, +/// while exact tool approval settings keep the higher-precedence value when present. +pub(crate) fn merge_app_requirements_descending( base: &mut AppsRequirementsToml, incoming: AppsRequirementsToml, ) { @@ -631,6 +666,17 @@ pub(crate) fn merge_enablement_settings_descending( } else { higher_precedence.or(lower_precedence) }; + + let Some(incoming_tools) = incoming_requirement.tools else { + continue; + }; + let base_tools = base_requirement.tools.get_or_insert_with(Default::default); + for (tool_name, incoming_tool) in incoming_tools.tools { + let base_tool = base_tools.tools.entry(tool_name).or_default(); + if base_tool.approval_mode.is_none() { + base_tool.approval_mode = incoming_tool.approval_mode; + } + } } } @@ -769,7 +815,7 @@ impl ConfigRequirementsWithSources { if let Some(incoming_apps) = other.apps.take() { if let Some(existing_apps) = self.apps.as_mut() { - merge_enablement_settings_descending(&mut existing_apps.value, incoming_apps); + merge_app_requirements_descending(&mut existing_apps.value, incoming_apps); } else { self.apps = Some(Sourced::new(incoming_apps, source)); } @@ -1583,6 +1629,37 @@ allowed_approvals_reviewers = ["user"] "connector_123123".to_string(), AppRequirementToml { enabled: Some(false), + tools: None, + }, + )]), + }) + ); + Ok(()) + } + + #[test] + fn deserialize_apps_tool_requirements() -> Result<()> { + let toml_str = r#" + [apps.connector_123123.tools."calendar/list_events"] + approval_mode = "approve" + "#; + let requirements: ConfigRequirementsToml = from_str(toml_str)?; + + assert_eq!( + requirements.apps, + Some(AppsRequirementsToml { + apps: BTreeMap::from([( + "connector_123123".to_string(), + AppRequirementToml { + enabled: None, + tools: Some(AppToolsRequirementsToml { + tools: BTreeMap::from([( + "calendar/list_events".to_string(), + AppToolRequirementToml { + approval_mode: Some(AppToolApproval::Approve), + }, + )]), + }), }, )]), }) @@ -1597,19 +1674,45 @@ allowed_approvals_reviewers = ["user"] .map(|(app_id, enabled)| { ( (*app_id).to_string(), - AppRequirementToml { enabled: *enabled }, + AppRequirementToml { + enabled: *enabled, + tools: None, + }, ) }) .collect(), } } + fn app_tool_requirements( + app_id: &str, + tool_name: &str, + approval_mode: AppToolApproval, + ) -> AppsRequirementsToml { + AppsRequirementsToml { + apps: BTreeMap::from([( + app_id.to_string(), + AppRequirementToml { + enabled: None, + tools: Some(AppToolsRequirementsToml { + tools: BTreeMap::from([( + tool_name.to_string(), + AppToolRequirementToml { + approval_mode: Some(approval_mode), + }, + )]), + }), + }, + )]), + } + } + #[test] - fn merge_enablement_settings_descending_unions_distinct_apps() { + fn merge_app_requirements_descending_unions_distinct_apps() { let mut merged = apps_requirements(&[("connector_high", Some(false))]); let lower = apps_requirements(&[("connector_low", Some(true))]); - merge_enablement_settings_descending(&mut merged, lower); + merge_app_requirements_descending(&mut merged, lower); assert_eq!( merged, @@ -1621,11 +1724,11 @@ allowed_approvals_reviewers = ["user"] } #[test] - fn merge_enablement_settings_descending_prefers_false_from_lower_precedence() { + fn merge_app_requirements_descending_prefers_false_from_lower_precedence() { let mut merged = apps_requirements(&[("connector_123123", Some(true))]); let lower = apps_requirements(&[("connector_123123", Some(false))]); - merge_enablement_settings_descending(&mut merged, lower); + merge_app_requirements_descending(&mut merged, lower); assert_eq!( merged, @@ -1634,11 +1737,11 @@ allowed_approvals_reviewers = ["user"] } #[test] - fn merge_enablement_settings_descending_keeps_higher_true_when_lower_is_unset() { + fn merge_app_requirements_descending_keeps_higher_true_when_lower_is_unset() { let mut merged = apps_requirements(&[("connector_123123", Some(true))]); let lower = apps_requirements(&[("connector_123123", None)]); - merge_enablement_settings_descending(&mut merged, lower); + merge_app_requirements_descending(&mut merged, lower); assert_eq!( merged, @@ -1647,11 +1750,11 @@ allowed_approvals_reviewers = ["user"] } #[test] - fn merge_enablement_settings_descending_uses_lower_value_when_higher_missing() { + fn merge_app_requirements_descending_uses_lower_value_when_higher_missing() { let mut merged = apps_requirements(&[]); let lower = apps_requirements(&[("connector_123123", Some(true))]); - merge_enablement_settings_descending(&mut merged, lower); + merge_app_requirements_descending(&mut merged, lower); assert_eq!( merged, @@ -1660,11 +1763,11 @@ allowed_approvals_reviewers = ["user"] } #[test] - fn merge_enablement_settings_descending_preserves_higher_false_when_lower_missing_app() { + fn merge_app_requirements_descending_preserves_higher_false_when_lower_missing_app() { let mut merged = apps_requirements(&[("connector_123123", Some(false))]); let lower = apps_requirements(&[]); - merge_enablement_settings_descending(&mut merged, lower); + merge_app_requirements_descending(&mut merged, lower); assert_eq!( merged, @@ -1672,6 +1775,52 @@ allowed_approvals_reviewers = ["user"] ); } + #[test] + fn merge_app_requirements_descending_preserves_higher_tool_approval_mode() { + let mut merged = app_tool_requirements( + "connector_123123", + "calendar/list_events", + AppToolApproval::Approve, + ); + let lower = app_tool_requirements( + "connector_123123", + "calendar/list_events", + AppToolApproval::Prompt, + ); + + merge_app_requirements_descending(&mut merged, lower); + + assert_eq!( + merged, + app_tool_requirements( + "connector_123123", + "calendar/list_events", + AppToolApproval::Approve, + ) + ); + } + + #[test] + fn merge_app_requirements_descending_uses_lower_tool_approval_when_higher_missing() { + let mut merged = apps_requirements(&[("connector_123123", None)]); + let lower = app_tool_requirements( + "connector_123123", + "calendar/list_events", + AppToolApproval::Approve, + ); + + merge_app_requirements_descending(&mut merged, lower); + + assert_eq!( + merged, + app_tool_requirements( + "connector_123123", + "calendar/list_events", + AppToolApproval::Approve, + ) + ); + } + #[test] fn merge_unset_fields_merges_apps_across_sources_with_enabled_evaluation() { let higher_source = RequirementSource::CloudRequirements; diff --git a/codex-rs/config/src/lib.rs b/codex-rs/config/src/lib.rs index e88c736db0..2d4c62013e 100644 --- a/codex-rs/config/src/lib.rs +++ b/codex-rs/config/src/lib.rs @@ -33,6 +33,8 @@ pub use cloud_requirements::CloudRequirementsLoader; pub use codex_app_server_protocol::ConfigLayerSource; pub use codex_utils_absolute_path::AbsolutePathBuf; pub use config_requirements::AppRequirementToml; +pub use config_requirements::AppToolRequirementToml; +pub use config_requirements::AppToolsRequirementsToml; pub use config_requirements::AppsRequirementsToml; pub use config_requirements::ConfigRequirements; pub use config_requirements::ConfigRequirementsToml; diff --git a/codex-rs/core/src/connectors.rs b/codex-rs/core/src/connectors.rs index 7a66e4ffa6..944e67b5b7 100644 --- a/codex-rs/core/src/connectors.rs +++ b/codex-rs/core/src/connectors.rs @@ -535,12 +535,18 @@ pub(crate) fn app_tool_policy( annotations: Option<&ToolAnnotations>, ) -> AppToolPolicy { let apps_config = read_apps_config(config); + let managed_approval = managed_app_tool_approval( + config.config_layer_stack.requirements_toml().apps.as_ref(), + connector_id, + tool_name, + ); app_tool_policy_from_apps_config( apps_config.as_ref(), connector_id, tool_name, tool_title, annotations, + managed_approval, ) } @@ -593,14 +599,29 @@ fn apply_requirements_apps_constraints( }; for (app_id, requirement) in &requirements_apps_config.apps { - if requirement.enabled != Some(false) { - continue; + if requirement.enabled == Some(false) { + let app = apps_config.apps.entry(app_id.clone()).or_default(); + app.enabled = false; } - let app = apps_config.apps.entry(app_id.clone()).or_default(); - app.enabled = false; } } +fn managed_app_tool_approval( + requirements_apps_config: Option<&AppsRequirementsToml>, + connector_id: Option<&str>, + tool_name: &str, +) -> Option { + let connector_id = connector_id?; + requirements_apps_config? + .apps + .get(connector_id)? + .tools + .as_ref()? + .tools + .get(tool_name)? + .approval_mode +} + fn app_is_enabled(apps_config: &AppsConfigToml, connector_id: Option<&str>) -> bool { let default_enabled = apps_config .default @@ -620,9 +641,13 @@ fn app_tool_policy_from_apps_config( tool_name: &str, tool_title: Option<&str>, annotations: Option<&ToolAnnotations>, + managed_approval: Option, ) -> AppToolPolicy { let Some(apps_config) = apps_config else { - return AppToolPolicy::default(); + return AppToolPolicy { + approval: managed_approval.unwrap_or(AppToolApproval::Auto), + ..Default::default() + }; }; let app = connector_id.and_then(|connector_id| apps_config.apps.get(connector_id)); @@ -633,8 +658,8 @@ fn app_tool_policy_from_apps_config( .get(tool_name) .or_else(|| tool_title.and_then(|title| tools.tools.get(title))) }); - let approval = tool_config - .and_then(|tool| tool.approval_mode) + let approval = managed_approval + .or_else(|| tool_config.and_then(|tool| tool.approval_mode)) .or_else(|| app.and_then(|app| app.default_tools_approval_mode)) .unwrap_or(AppToolApproval::Auto); diff --git a/codex-rs/core/src/connectors_tests.rs b/codex-rs/core/src/connectors_tests.rs index 6ded1610af..1f6f0e1ea5 100644 --- a/codex-rs/core/src/connectors_tests.rs +++ b/codex-rs/core/src/connectors_tests.rs @@ -2,6 +2,8 @@ use super::*; use crate::config::CONFIG_TOML_FILE; use crate::config::ConfigBuilder; use codex_config::AppRequirementToml; +use codex_config::AppToolRequirementToml; +use codex_config::AppToolsRequirementsToml; use codex_config::AppsRequirementsToml; use codex_config::CloudRequirementsLoader; use codex_config::ConfigLayerStack; @@ -359,6 +361,7 @@ fn app_tool_policy_uses_global_defaults_for_destructive_hints() { "events/create", /*tool_title*/ None, Some(&annotations(Some(true), /*open_world_hint*/ None)), + /*managed_approval*/ None, ); assert_eq!( @@ -387,6 +390,7 @@ fn app_tool_policy_defaults_missing_destructive_hint_to_true() { "events/create", /*tool_title*/ None, Some(&annotations(/*destructive_hint*/ None, Some(false))), + /*managed_approval*/ None, ); assert_eq!( @@ -415,6 +419,7 @@ fn app_tool_policy_defaults_missing_open_world_hint_to_true() { "events/create", /*tool_title*/ None, Some(&annotations(Some(false), /*open_world_hint*/ None)), + /*managed_approval*/ None, ); assert_eq!( @@ -483,6 +488,7 @@ fn requirements_disabled_connector_overrides_enabled_connector() { "connector_123123".to_string(), AppRequirementToml { enabled: Some(false), + tools: None, }, )]), }; @@ -515,6 +521,7 @@ fn requirements_enabled_does_not_override_disabled_connector() { "connector_123123".to_string(), AppRequirementToml { enabled: Some(true), + tools: None, }, )]), }; @@ -548,6 +555,7 @@ enabled = true "connector_123123".to_string(), AppRequirementToml { enabled: Some(false), + tools: None, }, )]), }), @@ -591,6 +599,7 @@ async fn cloud_requirements_disable_connector_applies_without_user_apps_table() "connector_123123".to_string(), AppRequirementToml { enabled: Some(false), + tools: None, }, )]), }), @@ -641,6 +650,7 @@ async fn local_requirements_disable_connector_overrides_user_apps_config() { "connector_123123".to_string(), AppRequirementToml { enabled: Some(false), + tools: None, }, )]), }), @@ -692,6 +702,7 @@ async fn local_requirements_disable_connector_applies_without_user_apps_table() "connector_123123".to_string(), AppRequirementToml { enabled: Some(false), + tools: None, }, )]), }), @@ -733,6 +744,7 @@ async fn with_app_enabled_state_preserves_unrelated_disabled_connector() { "connector_drive".to_string(), AppRequirementToml { enabled: Some(false), + tools: None, }, )]), }), @@ -773,6 +785,7 @@ fn app_tool_policy_honors_default_app_enabled_false() { Some(&annotations( /*destructive_hint*/ None, /*open_world_hint*/ None, )), + /*managed_approval*/ None, ); assert_eq!( @@ -784,6 +797,210 @@ fn app_tool_policy_honors_default_app_enabled_false() { ); } +#[test] +fn app_tool_policy_uses_managed_approval_without_apps_config() { + let policy = app_tool_policy_from_apps_config( + /*apps_config*/ None, + Some("calendar"), + "events/list", + /*tool_title*/ None, + /*annotations*/ None, + Some(AppToolApproval::Approve), + ); + + assert_eq!( + policy, + AppToolPolicy { + enabled: true, + approval: AppToolApproval::Approve, + } + ); +} + +fn app_tool_requirements( + app_id: &str, + tool_name: &str, + approval_mode: AppToolApproval, +) -> AppsRequirementsToml { + AppsRequirementsToml { + apps: BTreeMap::from([( + app_id.to_string(), + AppRequirementToml { + enabled: None, + tools: Some(AppToolsRequirementsToml { + tools: BTreeMap::from([( + tool_name.to_string(), + AppToolRequirementToml { + approval_mode: Some(approval_mode), + }, + )]), + }), + }, + )]), + } +} + +#[test] +fn managed_app_tool_approval_uses_raw_tool_name() { + let requirements_apps = app_tool_requirements( + "connector_123123", + "calendar/list_events", + AppToolApproval::Approve, + ); + + assert_eq!( + managed_app_tool_approval( + Some(&requirements_apps), + Some("connector_123123"), + "calendar/list_events", + ), + Some(AppToolApproval::Approve) + ); + assert_eq!( + managed_app_tool_approval( + Some(&requirements_apps), + Some("connector_123123"), + "calendar/create_event", + ), + None + ); +} + +#[tokio::test] +async fn cloud_requirements_tool_approval_overrides_user_apps_config() { + let codex_home = tempdir().expect("tempdir should succeed"); + std::fs::write( + codex_home.path().join(CONFIG_TOML_FILE), + r#" +[apps.connector_123123.tools."calendar/list_events"] +approval_mode = "prompt" +"#, + ) + .expect("write config"); + + let requirements = ConfigRequirementsToml { + apps: Some(app_tool_requirements( + "connector_123123", + "calendar/list_events", + AppToolApproval::Approve, + )), + ..Default::default() + }; + + let config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .fallback_cwd(Some(codex_home.path().to_path_buf())) + .cloud_requirements(CloudRequirementsLoader::new(async move { + Ok(Some(requirements)) + })) + .build() + .await + .expect("config should build"); + + let policy = app_tool_policy( + &config, + Some("connector_123123"), + "calendar/list_events", + /*tool_title*/ None, + /*annotations*/ None, + ); + assert_eq!( + policy, + AppToolPolicy { + enabled: true, + approval: AppToolApproval::Approve, + } + ); +} + +#[tokio::test] +async fn local_requirements_tool_approval_overrides_user_apps_config() { + let codex_home = tempdir().expect("tempdir should succeed"); + let config_toml_path = + AbsolutePathBuf::try_from(codex_home.path().join(CONFIG_TOML_FILE)).expect("abs path"); + let mut config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .fallback_cwd(Some(codex_home.path().to_path_buf())) + .build() + .await + .expect("config should build"); + + let requirements = ConfigRequirementsToml { + apps: Some(app_tool_requirements( + "connector_123123", + "calendar/list_events", + AppToolApproval::Approve, + )), + ..Default::default() + }; + config.config_layer_stack = + ConfigLayerStack::new(Vec::new(), ConfigRequirements::default(), requirements) + .expect("requirements stack") + .with_user_config( + &config_toml_path, + toml::from_str::( + r#" +[apps.connector_123123.tools."calendar/list_events"] +approval_mode = "prompt" +"#, + ) + .expect("apps config"), + ); + + let policy = app_tool_policy( + &config, + Some("connector_123123"), + "calendar/list_events", + /*tool_title*/ None, + /*annotations*/ None, + ); + assert_eq!( + policy, + AppToolPolicy { + enabled: true, + approval: AppToolApproval::Approve, + } + ); +} + +#[tokio::test] +async fn local_requirements_tool_approval_does_not_match_tool_title() { + let codex_home = tempdir().expect("tempdir should succeed"); + let mut config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .fallback_cwd(Some(codex_home.path().to_path_buf())) + .build() + .await + .expect("config should build"); + + let requirements = ConfigRequirementsToml { + apps: Some(app_tool_requirements( + "connector_123123", + "calendar/list_events", + AppToolApproval::Approve, + )), + ..Default::default() + }; + config.config_layer_stack = + ConfigLayerStack::new(Vec::new(), ConfigRequirements::default(), requirements) + .expect("requirements stack"); + + let policy = app_tool_policy( + &config, + Some("connector_123123"), + "calendar/create_event", + Some("calendar/list_events"), + /*annotations*/ None, + ); + assert_eq!( + policy, + AppToolPolicy { + enabled: true, + approval: AppToolApproval::Auto, + } + ); +} + #[test] fn app_tool_policy_allows_per_app_enable_when_default_is_disabled() { let apps_config = AppsConfigToml { @@ -813,6 +1030,7 @@ fn app_tool_policy_allows_per_app_enable_when_default_is_disabled() { Some(&annotations( /*destructive_hint*/ None, /*open_world_hint*/ None, )), + /*managed_approval*/ None, ); assert_eq!( @@ -855,6 +1073,7 @@ fn app_tool_policy_per_tool_enabled_true_overrides_app_level_disable_flags() { "events/create", /*tool_title*/ None, Some(&annotations(Some(true), Some(true))), + /*managed_approval*/ None, ); assert_eq!( @@ -889,6 +1108,7 @@ fn app_tool_policy_default_tools_enabled_true_overrides_app_level_tool_hints() { "events/create", /*tool_title*/ None, Some(&annotations(Some(true), Some(true))), + /*managed_approval*/ None, ); assert_eq!( @@ -925,6 +1145,7 @@ fn app_tool_policy_default_tools_enabled_false_overrides_app_level_tool_hints() Some(&annotations( /*destructive_hint*/ None, /*open_world_hint*/ None, )), + /*managed_approval*/ None, ); assert_eq!( @@ -963,6 +1184,7 @@ fn app_tool_policy_uses_default_tools_approval_mode() { Some(&annotations( /*destructive_hint*/ None, /*open_world_hint*/ None, )), + /*managed_approval*/ None, ); assert_eq!( @@ -1005,6 +1227,7 @@ fn app_tool_policy_matches_prefix_stripped_tool_name_for_tool_config() { "calendar_events/create", Some("events/create"), Some(&annotations(Some(true), Some(true))), + /*managed_approval*/ None, ); assert_eq!( diff --git a/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs b/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs index cce83ad6d8..a2dd3b9955 100644 --- a/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs +++ b/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs @@ -1926,6 +1926,7 @@ async fn apps_initial_load_applies_enabled_state_from_requirements_with_user_ove "connector_1".to_string(), AppRequirementToml { enabled: Some(false), + tools: None, }, )]), }), @@ -1999,6 +2000,7 @@ async fn apps_initial_load_applies_enabled_state_from_requirements_without_user_ "connector_1".to_string(), AppRequirementToml { enabled: Some(false), + tools: None, }, )]), }),