diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 5a4ad74a7c..c17421a10f 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -7242,6 +7242,17 @@ ], "type": "string" }, + "BrowserUseRequirements": { + "properties": { + "disableAutoReview": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, "ByteRange": { "properties": { "end": { @@ -8637,6 +8648,16 @@ "null" ] }, + "browserUse": { + "anyOf": [ + { + "$ref": "#/definitions/v2/BrowserUseRequirements" + }, + { + "type": "null" + } + ] + }, "checkForUpdateOnStartup": { "type": [ "boolean", diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index e2b97c333b..1fb9fd8024 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -1234,6 +1234,17 @@ ], "type": "string" }, + "BrowserUseRequirements": { + "properties": { + "disableAutoReview": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, "ByteRange": { "properties": { "end": { @@ -4815,6 +4826,16 @@ "null" ] }, + "browserUse": { + "anyOf": [ + { + "$ref": "#/definitions/BrowserUseRequirements" + }, + { + "type": "null" + } + ] + }, "checkForUpdateOnStartup": { "type": [ "boolean", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json index 6863fb468b..db82430b6b 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json @@ -59,6 +59,17 @@ } ] }, + "BrowserUseRequirements": { + "properties": { + "disableAutoReview": { + "type": [ + "boolean", + "null" + ] + } + }, + "type": "object" + }, "ComputerUseRequirements": { "properties": { "allowLockedComputerUse": { @@ -141,6 +152,16 @@ "null" ] }, + "browserUse": { + "anyOf": [ + { + "$ref": "#/definitions/BrowserUseRequirements" + }, + { + "type": "null" + } + ] + }, "checkForUpdateOnStartup": { "type": [ "boolean", diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/BrowserUseRequirements.ts b/codex-rs/app-server-protocol/schema/typescript/v2/BrowserUseRequirements.ts new file mode 100644 index 0000000000..397532d61e --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/BrowserUseRequirements.ts @@ -0,0 +1,5 @@ +// 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. + +export type BrowserUseRequirements = { disableAutoReview: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigRequirements.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigRequirements.ts index 9fcb9c97a1..b1a4c229e1 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigRequirements.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigRequirements.ts @@ -4,6 +4,7 @@ import type { PathUri } from "../PathUri"; import type { WebSearchMode } from "../WebSearchMode"; import type { AskForApproval } from "./AskForApproval"; +import type { BrowserUseRequirements } from "./BrowserUseRequirements"; import type { ComputerUseRequirements } from "./ComputerUseRequirements"; import type { FeedbackRequirements } from "./FeedbackRequirements"; import type { ModelsRequirements } from "./ModelsRequirements"; @@ -11,4 +12,4 @@ import type { ResidencyRequirement } from "./ResidencyRequirement"; import type { SandboxMode } from "./SandboxMode"; import type { WindowsSandboxSetupMode } from "./WindowsSandboxSetupMode"; -export type ConfigRequirements = {allowedApprovalPolicies: Array | null, allowedSandboxModes: Array | null, allowedWindowsSandboxImplementations: Array | null, allowedPermissionProfiles: { [key in string]?: boolean } | null, defaultPermissions: string | null, allowedWebSearchModes: Array | null, allowManagedHooksOnly: boolean | null, allowAppshots: boolean | null, allowRemoteControl: boolean | null, computerUse: ComputerUseRequirements | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null, models: ModelsRequirements | null, sqliteHome: PathUri | null, logDir: PathUri | null, modelCatalogJson: PathUri | null, checkForUpdateOnStartup: boolean | null, allowLoginShell: boolean | null, feedback: FeedbackRequirements | null, windowsSandboxPrivateDesktop: boolean | null}; +export type ConfigRequirements = {allowedApprovalPolicies: Array | null, allowedSandboxModes: Array | null, allowedWindowsSandboxImplementations: Array | null, allowedPermissionProfiles: { [key in string]?: boolean } | null, defaultPermissions: string | null, allowedWebSearchModes: Array | null, allowManagedHooksOnly: boolean | null, allowAppshots: boolean | null, allowRemoteControl: boolean | null, computerUse: ComputerUseRequirements | null, browserUse: BrowserUseRequirements | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null, models: ModelsRequirements | null, sqliteHome: PathUri | null, logDir: PathUri | null, modelCatalogJson: PathUri | null, checkForUpdateOnStartup: boolean | null, allowLoginShell: boolean | null, feedback: FeedbackRequirements | null, windowsSandboxPrivateDesktop: boolean | null}; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index a418fbf3fa..9cdebd57b3 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -41,6 +41,7 @@ export type { AskForApproval } from "./AskForApproval"; export type { AttestationGenerateParams } from "./AttestationGenerateParams"; export type { AttestationGenerateResponse } from "./AttestationGenerateResponse"; export type { AutoReviewDecisionSource } from "./AutoReviewDecisionSource"; +export type { BrowserUseRequirements } from "./BrowserUseRequirements"; export type { ByteRange } from "./ByteRange"; export type { CancelLoginAccountParams } from "./CancelLoginAccountParams"; export type { CancelLoginAccountResponse } from "./CancelLoginAccountResponse"; diff --git a/codex-rs/app-server-protocol/src/protocol/v2/config.rs b/codex-rs/app-server-protocol/src/protocol/v2/config.rs index e196ba7115..cbd9803fb4 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/config.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/config.rs @@ -387,6 +387,7 @@ pub struct ConfigRequirements { pub allow_appshots: Option, pub allow_remote_control: Option, pub computer_use: Option, + pub browser_use: Option, pub feature_requirements: Option>, #[experimental("configRequirements/read.hooks")] pub hooks: Option, @@ -436,6 +437,13 @@ pub struct ComputerUseRequirements { pub allow_locked_computer_use: Option, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct BrowserUseRequirements { + pub disable_auto_review: Option, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs index 21e05d0919..df52062058 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs @@ -1852,6 +1852,7 @@ fn config_requirements_granular_allowed_approval_policy_is_marked_experimental() allow_appshots: None, allow_remote_control: None, computer_use: None, + browser_use: None, feature_requirements: None, hooks: None, enforce_residency: None, diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 2190a337a8..8976316450 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -248,7 +248,7 @@ Example with notification opt-out: - `externalAgentConfig/import/readHistories` — read completed import histories and connector candidates detected from successfully imported session histories. Connector candidates include a normalized display `name`, the number of imported sessions that used the connector, and the source metadata field used for detection. - `config/value/write` — write a single config key/value to the user's config.toml on disk; dotted paths such as `desktop.someKey` use the same generic write surface. Writes that overlap a managed requirement are rejected with `configRequirementReadonly`. - `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads, including multiple `desktop.*` edits. Session-static model, reasoning-effort, Plan-mode reasoning-effort, service-tier, and personality defaults do not reload existing threads. -- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including exact managed values (`sqliteHome`, `logDir`, `modelCatalogJson`, `checkForUpdateOnStartup`, `allowLoginShell`, `feedback.enabled`, and `windowsSandboxPrivateDesktop`), allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), the layered permission-profile allow map (`allowedPermissionProfiles`), the managed permission-profile default (`defaultPermissions`), lifecycle hook lockdown (`allowManagedHooksOnly`), remote-control policy (`allowRemoteControl`; `false` force-disables remote control while `true` or `null` preserves existing behavior), computer use policy (`computerUse`), pinned feature values (`featureRequirements`), managed lifecycle hooks (`hooks`, including each command handler's optional `additionalContextLimit`), `enforceResidency`, managed new-thread defaults (`models.newThread.model`, `models.newThread.modelReasoningEffort`, and `models.newThread.serviceTier`), and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`. +- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including exact managed values (`sqliteHome`, `logDir`, `modelCatalogJson`, `checkForUpdateOnStartup`, `allowLoginShell`, `feedback.enabled`, and `windowsSandboxPrivateDesktop`), allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), the layered permission-profile allow map (`allowedPermissionProfiles`), the managed permission-profile default (`defaultPermissions`), lifecycle hook lockdown (`allowManagedHooksOnly`), remote-control policy (`allowRemoteControl`; `false` force-disables remote control while `true` or `null` preserves existing behavior), computer use policy (`computerUse`), Browser Use policy (`browserUse.disableAutoReview`), pinned feature values (`featureRequirements`), managed lifecycle hooks (`hooks`, including each command handler's optional `additionalContextLimit`), `enforceResidency`, managed new-thread defaults (`models.newThread.model`, `models.newThread.modelReasoningEffort`, and `models.newThread.serviceTier`), and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`. ### Example: Start or resume a thread diff --git a/codex-rs/app-server/src/request_processors/config_processor.rs b/codex-rs/app-server/src/request_processors/config_processor.rs index 1ac5efc8dd..dbd6060404 100644 --- a/codex-rs/app-server/src/request_processors/config_processor.rs +++ b/codex-rs/app-server/src/request_processors/config_processor.rs @@ -7,6 +7,7 @@ use crate::error_code::invalid_request; use crate::outgoing_message::ConnectionRequestId; use crate::outgoing_message::OutgoingMessageSender; use codex_analytics::AnalyticsEventsClient; +use codex_app_server_protocol::BrowserUseRequirements; use codex_app_server_protocol::ClientResponsePayload; use codex_app_server_protocol::ComputerUseRequirements; use codex_app_server_protocol::ConfigBatchWriteParams; @@ -403,6 +404,9 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR computer_use: requirements .computer_use .map(map_computer_use_requirements_to_api), + browser_use: requirements + .browser_use + .map(map_browser_use_requirements_to_api), feature_requirements: requirements .feature_requirements .map(|requirements| requirements.entries), @@ -438,6 +442,14 @@ fn map_computer_use_requirements_to_api( } } +fn map_browser_use_requirements_to_api( + browser_use: codex_config::BrowserUseRequirementsToml, +) -> BrowserUseRequirements { + BrowserUseRequirements { + disable_auto_review: browser_use.disable_auto_review, + } +} + fn map_hooks_requirements_to_api(hooks: ManagedHooksRequirementsToml) -> ManagedHooksRequirements { let ManagedHooksRequirementsToml { managed_dir, diff --git a/codex-rs/app-server/tests/suite/v2/config_rpc.rs b/codex-rs/app-server/tests/suite/v2/config_rpc.rs index 6407005c19..3f6c1c575d 100644 --- a/codex-rs/app-server/tests/suite/v2/config_rpc.rs +++ b/codex-rs/app-server/tests/suite/v2/config_rpc.rs @@ -95,6 +95,36 @@ additionalContextLimit = 4096 Ok(()) } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn config_requirements_read_includes_browser_use_auto_review_setting() -> Result<()> { + let codex_home = TempDir::new()?; + std::fs::write( + codex_home.path().join("requirements.toml"), + r#" +[browser_use] +disable_auto_review = true +"#, + )?; + let mut mcp = TestAppServer::builder() + .with_codex_home(codex_home.path()) + .without_auto_env() + .build() + .await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp.send_config_requirements_read_request().await?; + let response: ConfigRequirementsReadResponse = + timeout(DEFAULT_READ_TIMEOUT, mcp.read_response(request_id)).await??; + assert_eq!( + response + .requirements + .and_then(|requirements| requirements.browser_use) + .and_then(|browser_use| browser_use.disable_auto_review), + Some(true) + ); + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn config_requirements_read_includes_new_thread_model_defaults() -> Result<()> { let codex_home = TempDir::new()?; diff --git a/codex-rs/config/src/config_requirements.rs b/codex-rs/config/src/config_requirements.rs index 52e4424b8f..31ea1df209 100644 --- a/codex-rs/config/src/config_requirements.rs +++ b/codex-rs/config/src/config_requirements.rs @@ -753,6 +753,17 @@ impl ComputerUseRequirementsToml { } } +#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)] +pub struct BrowserUseRequirementsToml { + pub disable_auto_review: Option, +} + +impl BrowserUseRequirementsToml { + pub fn is_empty(&self) -> bool { + self.disable_auto_review.is_none() + } +} + #[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)] pub struct WindowsRequirementsToml { pub allowed_sandbox_implementations: Option>, @@ -879,6 +890,7 @@ pub struct ConfigRequirementsToml { pub allow_appshots: Option, pub allow_remote_control: Option, pub computer_use: Option, + pub browser_use: Option, pub windows: Option, #[serde(rename = "features", alias = "feature_requirements")] pub feature_requirements: Option, @@ -968,6 +980,7 @@ pub struct ConfigRequirementsWithSources { pub allow_appshots: Option>, pub allow_remote_control: Option>, pub computer_use: Option>, + pub browser_use: Option>, pub windows: Option>, pub feature_requirements: Option>, pub hooks: Option>, @@ -1019,6 +1032,7 @@ impl ConfigRequirementsWithSources { allow_appshots: _, allow_remote_control: _, computer_use: _, + browser_use: _, windows: _, feature_requirements: _, hooks: _, @@ -1063,6 +1077,7 @@ impl ConfigRequirementsWithSources { allow_appshots, allow_remote_control, computer_use, + browser_use, windows, feature_requirements, hooks, @@ -1105,6 +1120,7 @@ impl ConfigRequirementsWithSources { allow_appshots, allow_remote_control, computer_use, + browser_use, windows, feature_requirements, hooks, @@ -1137,6 +1153,7 @@ impl ConfigRequirementsWithSources { allow_appshots: allow_appshots.map(|sourced| sourced.value), allow_remote_control: allow_remote_control.map(|sourced| sourced.value), computer_use: computer_use.map(|sourced| sourced.value), + browser_use: browser_use.map(|sourced| sourced.value), windows: windows.map(|sourced| sourced.value), feature_requirements: feature_requirements.map(|sourced| sourced.value), hooks: hooks.map(|sourced| sourced.value), @@ -1241,6 +1258,10 @@ impl ConfigRequirementsToml { .computer_use .as_ref() .is_none_or(ComputerUseRequirementsToml::is_empty) + && self + .browser_use + .as_ref() + .is_none_or(BrowserUseRequirementsToml::is_empty) && self .windows .as_ref() @@ -1411,6 +1432,7 @@ impl TryFrom for ConfigRequirements { allow_appshots, allow_remote_control, computer_use, + browser_use: _, windows, feature_requirements, hooks, @@ -1893,6 +1915,7 @@ mod tests { allow_appshots, allow_remote_control, computer_use, + browser_use, windows, feature_requirements, hooks, @@ -1936,6 +1959,7 @@ mod tests { allow_remote_control: allow_remote_control .map(|value| Sourced::new(value, RequirementSource::Unknown)), computer_use: computer_use.map(|value| Sourced::new(value, RequirementSource::Unknown)), + browser_use: browser_use.map(|value| Sourced::new(value, RequirementSource::Unknown)), windows: windows.map(|value| Sourced::new(value, RequirementSource::Unknown)), feature_requirements: feature_requirements .map(|value| Sourced::new(value, RequirementSource::Unknown)), @@ -2194,6 +2218,7 @@ mod tests { allow_appshots: Some(false), allow_remote_control: Some(false), computer_use: Some(computer_use.clone()), + browser_use: None, windows: Some(windows.clone()), feature_requirements: Some(feature_requirements.clone()), hooks: None, @@ -2251,6 +2276,7 @@ mod tests { enforce_source.clone(), )), computer_use: Some(Sourced::new(computer_use, enforce_source.clone())), + browser_use: None, windows: Some(Sourced::new(windows, enforce_source.clone())), feature_requirements: Some(Sourced::new( feature_requirements, @@ -2302,6 +2328,7 @@ mod tests { allow_appshots: None, allow_remote_control: None, computer_use: None, + browser_use: None, windows: None, feature_requirements: None, hooks: None, @@ -2359,6 +2386,7 @@ mod tests { allow_appshots: None, allow_remote_control: None, computer_use: None, + browser_use: None, windows: None, feature_requirements: None, hooks: None, diff --git a/codex-rs/config/src/lib.rs b/codex-rs/config/src/lib.rs index a6b32ee046..4991f88dc8 100644 --- a/codex-rs/config/src/lib.rs +++ b/codex-rs/config/src/lib.rs @@ -57,6 +57,7 @@ pub use config_requirements::AppRequirementToml; pub use config_requirements::AppToolRequirementToml; pub use config_requirements::AppToolsRequirementsToml; pub use config_requirements::AppsRequirementsToml; +pub use config_requirements::BrowserUseRequirementsToml; pub use config_requirements::ComputerUseRequirementsToml; pub use config_requirements::ConfigRequirements; pub use config_requirements::ConfigRequirementsToml; diff --git a/codex-rs/config/src/requirements_layers/stack.rs b/codex-rs/config/src/requirements_layers/stack.rs index 9c6a6de9c3..6272e63fca 100644 --- a/codex-rs/config/src/requirements_layers/stack.rs +++ b/codex-rs/config/src/requirements_layers/stack.rs @@ -223,6 +223,7 @@ fn populate_merged_regular_fields_with_sources( allow_appshots, allow_remote_control, computer_use, + browser_use, windows, feature_requirements, hooks: _, @@ -263,6 +264,7 @@ fn populate_merged_regular_fields_with_sources( set_sourced!(allow_appshots, &["allow_appshots"]); set_sourced!(allow_remote_control, &["allow_remote_control"]); set_sourced!(computer_use, &["computer_use"]); + set_sourced!(browser_use, &["browser_use"]); set_sourced!(windows, &["windows"]); set_sourced!(feature_requirements, &["features", "feature_requirements"]); set_sourced!(mcp_servers, &["mcp_servers"]); diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 3b4bb448c2..9d8b0ea097 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -9219,6 +9219,7 @@ async fn test_requirements_web_search_mode_allowlist_does_not_warn_when_unset() allow_appshots: None, allow_remote_control: None, computer_use: None, + browser_use: None, windows: None, feature_requirements: None, hooks: None, diff --git a/codex-rs/tui/src/debug_config.rs b/codex-rs/tui/src/debug_config.rs index 7fc1a6c590..5a5e31dc1b 100644 --- a/codex-rs/tui/src/debug_config.rs +++ b/codex-rs/tui/src/debug_config.rs @@ -952,6 +952,7 @@ interrupt_message = false allow_appshots: Some(false), allow_remote_control: Some(false), computer_use: None, + browser_use: None, windows: Some(WindowsRequirementsToml { allowed_sandbox_implementations: None, sandbox_private_desktop: Some(false),