Expose Browser Use requirements through the app server (#35033)

## What changed

- Parse the `browser_use.disable_auto_review` setting from layered
  `requirements.toml` configuration.
- Return the setting as `browserUse.disableAutoReview` from
  `configRequirements/read` and publish it in the generated JSON and TypeScript
  schemas.

## Testing

- Add an app-server RPC test covering the Browser Use requirement.

GitOrigin-RevId: 5749d5bc17bcc5b582bf7ed59b8e5b72d6c8f7fc
This commit is contained in:
rafael-oai
2026-07-23 23:21:33 +00:00
committed by copyberry
parent 963316583b
commit 41775559ca
16 changed files with 156 additions and 2 deletions
@@ -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",
@@ -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",
@@ -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",
@@ -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, };
@@ -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<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, allowedWindowsSandboxImplementations: Array<WindowsSandboxSetupMode> | null, allowedPermissionProfiles: { [key in string]?: boolean } | null, defaultPermissions: string | null, allowedWebSearchModes: Array<WebSearchMode> | 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<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, allowedWindowsSandboxImplementations: Array<WindowsSandboxSetupMode> | null, allowedPermissionProfiles: { [key in string]?: boolean } | null, defaultPermissions: string | null, allowedWebSearchModes: Array<WebSearchMode> | 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};
@@ -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";
@@ -387,6 +387,7 @@ pub struct ConfigRequirements {
pub allow_appshots: Option<bool>,
pub allow_remote_control: Option<bool>,
pub computer_use: Option<ComputerUseRequirements>,
pub browser_use: Option<BrowserUseRequirements>,
pub feature_requirements: Option<BTreeMap<String, bool>>,
#[experimental("configRequirements/read.hooks")]
pub hooks: Option<ManagedHooksRequirements>,
@@ -436,6 +437,13 @@ pub struct ComputerUseRequirements {
pub allow_locked_computer_use: Option<bool>,
}
#[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<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -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,
+1 -1
View File
@@ -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
@@ -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,
@@ -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()?;
@@ -753,6 +753,17 @@ impl ComputerUseRequirementsToml {
}
}
#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)]
pub struct BrowserUseRequirementsToml {
pub disable_auto_review: Option<bool>,
}
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<Vec<WindowsSandboxModeToml>>,
@@ -879,6 +890,7 @@ pub struct ConfigRequirementsToml {
pub allow_appshots: Option<bool>,
pub allow_remote_control: Option<bool>,
pub computer_use: Option<ComputerUseRequirementsToml>,
pub browser_use: Option<BrowserUseRequirementsToml>,
pub windows: Option<WindowsRequirementsToml>,
#[serde(rename = "features", alias = "feature_requirements")]
pub feature_requirements: Option<FeatureRequirementsToml>,
@@ -968,6 +980,7 @@ pub struct ConfigRequirementsWithSources {
pub allow_appshots: Option<Sourced<bool>>,
pub allow_remote_control: Option<Sourced<bool>>,
pub computer_use: Option<Sourced<ComputerUseRequirementsToml>>,
pub browser_use: Option<Sourced<BrowserUseRequirementsToml>>,
pub windows: Option<Sourced<WindowsRequirementsToml>>,
pub feature_requirements: Option<Sourced<FeatureRequirementsToml>>,
pub hooks: Option<Sourced<ManagedHooksRequirementsToml>>,
@@ -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<ConfigRequirementsWithSources> 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,
+1
View File
@@ -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;
@@ -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"]);
+1
View File
@@ -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,
+1
View File
@@ -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),