mirror of
https://github.com/openai/codex.git
synced 2026-10-21 05:22:21 +00:00
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:
+21
@@ -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",
|
||||
|
||||
+21
@@ -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",
|
||||
|
||||
+21
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"]);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user