mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
SAI-13224 add Computer Use requirements
This commit is contained in:
+109
@@ -6971,6 +6971,94 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ComputerUseConfig": {
|
||||
"properties": {
|
||||
"allow_persistent_approval": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"macos": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/ComputerUseMacosConfig"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ComputerUseMacosConfig": {
|
||||
"properties": {
|
||||
"allowed_bundle_ids": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"denied_bundle_ids": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ComputerUseMacosRequirements": {
|
||||
"properties": {
|
||||
"allowedBundleIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"deniedBundleIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ComputerUseRequirements": {
|
||||
"properties": {
|
||||
"allowPersistentApproval": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"macos": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/ComputerUseMacosRequirements"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Config": {
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
@@ -7011,6 +7099,17 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"computer_use": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/ComputerUseConfig"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"developer_instructions": {
|
||||
"type": [
|
||||
"string",
|
||||
@@ -7487,6 +7586,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"computerUse": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/ComputerUseRequirements"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"enforceResidency": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
+109
@@ -3469,6 +3469,94 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ComputerUseConfig": {
|
||||
"properties": {
|
||||
"allow_persistent_approval": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"macos": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ComputerUseMacosConfig"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ComputerUseMacosConfig": {
|
||||
"properties": {
|
||||
"allowed_bundle_ids": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"denied_bundle_ids": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ComputerUseMacosRequirements": {
|
||||
"properties": {
|
||||
"allowedBundleIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"deniedBundleIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ComputerUseRequirements": {
|
||||
"properties": {
|
||||
"allowPersistentApproval": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"macos": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ComputerUseMacosRequirements"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Config": {
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
@@ -3509,6 +3597,17 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"computer_use": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ComputerUseConfig"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"developer_instructions": {
|
||||
"type": [
|
||||
"string",
|
||||
@@ -3985,6 +4084,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"computerUse": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ComputerUseRequirements"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"enforceResidency": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -188,6 +188,50 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ComputerUseConfig": {
|
||||
"properties": {
|
||||
"allow_persistent_approval": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"macos": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ComputerUseMacosConfig"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ComputerUseMacosConfig": {
|
||||
"properties": {
|
||||
"allowed_bundle_ids": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"denied_bundle_ids": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Config": {
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
@@ -228,6 +272,17 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"computer_use": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ComputerUseConfig"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"developer_instructions": {
|
||||
"type": [
|
||||
"string",
|
||||
|
||||
+54
@@ -60,6 +60,50 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ComputerUseMacosRequirements": {
|
||||
"properties": {
|
||||
"allowedBundleIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"deniedBundleIds": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ComputerUseRequirements": {
|
||||
"properties": {
|
||||
"allowPersistentApproval": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"macos": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ComputerUseMacosRequirements"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ConfigRequirements": {
|
||||
"properties": {
|
||||
"allowedApprovalPolicies": {
|
||||
@@ -89,6 +133,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"computerUse": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ComputerUseRequirements"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"enforceResidency": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// 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.
|
||||
import type { ComputerUseMacosConfig } from "./ComputerUseMacosConfig";
|
||||
|
||||
export type ComputerUseConfig = { allow_persistent_approval: boolean | null, macos: ComputerUseMacosConfig | null, };
|
||||
@@ -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 ComputerUseMacosConfig = { denied_bundle_ids: Array<string> | null, allowed_bundle_ids: Array<string> | null, };
|
||||
+5
@@ -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 ComputerUseMacosRequirements = { deniedBundleIds: Array<string> | null, allowedBundleIds: Array<string> | null, };
|
||||
@@ -0,0 +1,6 @@
|
||||
// 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.
|
||||
import type { ComputerUseMacosRequirements } from "./ComputerUseMacosRequirements";
|
||||
|
||||
export type ComputerUseRequirements = { allowPersistentApproval: boolean | null, macos: ComputerUseMacosRequirements | null, };
|
||||
+2
-1
@@ -11,6 +11,7 @@ import type { JsonValue } from "../serde_json/JsonValue";
|
||||
import type { AnalyticsConfig } from "./AnalyticsConfig";
|
||||
import type { ApprovalsReviewer } from "./ApprovalsReviewer";
|
||||
import type { AskForApproval } from "./AskForApproval";
|
||||
import type { ComputerUseConfig } from "./ComputerUseConfig";
|
||||
import type { ProfileV2 } from "./ProfileV2";
|
||||
import type { SandboxMode } from "./SandboxMode";
|
||||
import type { SandboxWorkspaceWrite } from "./SandboxWorkspaceWrite";
|
||||
@@ -20,4 +21,4 @@ export type Config = {model: string | null, review_model: string | null, model_c
|
||||
* [UNSTABLE] Optional default for where approval requests are routed for
|
||||
* review.
|
||||
*/
|
||||
approvals_reviewer: ApprovalsReviewer | null, sandbox_mode: SandboxMode | null, sandbox_workspace_write: SandboxWorkspaceWrite | null, forced_chatgpt_workspace_id: string | null, forced_login_method: ForcedLoginMethod | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, profile: string | null, profiles: { [key in string]?: ProfileV2 }, instructions: string | null, developer_instructions: string | null, compact_prompt: string | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, service_tier: ServiceTier | null, analytics: AnalyticsConfig | null} & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });
|
||||
approvals_reviewer: ApprovalsReviewer | null, sandbox_mode: SandboxMode | null, sandbox_workspace_write: SandboxWorkspaceWrite | null, forced_chatgpt_workspace_id: string | null, forced_login_method: ForcedLoginMethod | null, web_search: WebSearchMode | null, tools: ToolsV2 | null, profile: string | null, profiles: { [key in string]?: ProfileV2 }, instructions: string | null, developer_instructions: string | null, compact_prompt: string | null, model_reasoning_effort: ReasoningEffort | null, model_reasoning_summary: ReasoningSummary | null, model_verbosity: Verbosity | null, service_tier: ServiceTier | null, analytics: AnalyticsConfig | null, computer_use: ComputerUseConfig | null} & ({ [key in string]?: number | string | boolean | Array<JsonValue> | { [key in string]?: JsonValue } | null });
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { WebSearchMode } from "../WebSearchMode";
|
||||
import type { AskForApproval } from "./AskForApproval";
|
||||
import type { ComputerUseRequirements } from "./ComputerUseRequirements";
|
||||
import type { ResidencyRequirement } from "./ResidencyRequirement";
|
||||
import type { SandboxMode } from "./SandboxMode";
|
||||
|
||||
export type ConfigRequirements = {allowedApprovalPolicies: Array<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, allowedWebSearchModes: Array<WebSearchMode> | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null};
|
||||
export type ConfigRequirements = {allowedApprovalPolicies: Array<AskForApproval> | null, allowedSandboxModes: Array<SandboxMode> | null, allowedWebSearchModes: Array<WebSearchMode> | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null, computerUse: ComputerUseRequirements | null};
|
||||
|
||||
@@ -61,6 +61,10 @@ export type { CommandExecutionRequestApprovalResponse } from "./CommandExecution
|
||||
export type { CommandExecutionSource } from "./CommandExecutionSource";
|
||||
export type { CommandExecutionStatus } from "./CommandExecutionStatus";
|
||||
export type { CommandMigration } from "./CommandMigration";
|
||||
export type { ComputerUseConfig } from "./ComputerUseConfig";
|
||||
export type { ComputerUseMacosConfig } from "./ComputerUseMacosConfig";
|
||||
export type { ComputerUseMacosRequirements } from "./ComputerUseMacosRequirements";
|
||||
export type { ComputerUseRequirements } from "./ComputerUseRequirements";
|
||||
export type { Config } from "./Config";
|
||||
export type { ConfigBatchWriteParams } from "./ConfigBatchWriteParams";
|
||||
export type { ConfigEdit } from "./ConfigEdit";
|
||||
|
||||
@@ -851,10 +851,29 @@ pub struct Config {
|
||||
#[experimental("config/read.apps")]
|
||||
#[serde(default)]
|
||||
pub apps: Option<AppsConfig>,
|
||||
#[experimental("config/read.computer_use")]
|
||||
#[serde(default)]
|
||||
pub computer_use: Option<ComputerUseConfig>,
|
||||
#[serde(default, flatten)]
|
||||
pub additional: HashMap<String, JsonValue>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ComputerUseMacosConfig {
|
||||
pub denied_bundle_ids: Option<Vec<String>>,
|
||||
pub allowed_bundle_ids: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ComputerUseConfig {
|
||||
pub allow_persistent_approval: Option<bool>,
|
||||
pub macos: Option<ComputerUseMacosConfig>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -946,6 +965,22 @@ pub struct ConfigReadResponse {
|
||||
pub layers: Option<Vec<ConfigLayer>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ComputerUseMacosRequirements {
|
||||
pub denied_bundle_ids: Option<Vec<String>>,
|
||||
pub allowed_bundle_ids: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ComputerUseRequirements {
|
||||
pub allow_persistent_approval: Option<bool>,
|
||||
pub macos: Option<ComputerUseMacosRequirements>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -962,6 +997,7 @@ pub struct ConfigRequirements {
|
||||
pub enforce_residency: Option<ResidencyRequirement>,
|
||||
#[experimental("configRequirements/read.network")]
|
||||
pub network: Option<NetworkRequirements>,
|
||||
pub computer_use: Option<ComputerUseRequirements>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
@@ -9561,6 +9597,7 @@ mod tests {
|
||||
service_tier: None,
|
||||
analytics: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
additional: HashMap::new(),
|
||||
});
|
||||
|
||||
@@ -9594,6 +9631,7 @@ mod tests {
|
||||
service_tier: None,
|
||||
analytics: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
additional: HashMap::new(),
|
||||
});
|
||||
|
||||
@@ -9649,6 +9687,7 @@ mod tests {
|
||||
service_tier: None,
|
||||
analytics: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
additional: HashMap::new(),
|
||||
});
|
||||
|
||||
@@ -9698,6 +9737,7 @@ mod tests {
|
||||
service_tier: None,
|
||||
analytics: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
additional: HashMap::new(),
|
||||
});
|
||||
|
||||
@@ -9722,6 +9762,7 @@ mod tests {
|
||||
hooks: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
computer_use: None,
|
||||
});
|
||||
|
||||
assert_eq!(reason, Some("askForApproval.granular"));
|
||||
|
||||
@@ -226,7 +226,7 @@ Example with notification opt-out:
|
||||
- `externalAgentConfig/import` — apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home) and any plugin/session `details` returned by detect. When a request includes migration items, the server emits `externalAgentConfig/import/completed` once after the full import finishes (immediately after the response when everything completed synchronously, or after background imports finish).
|
||||
- `config/value/write` — write a single config key/value to the user's config.toml on disk.
|
||||
- `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads.
|
||||
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), pinned feature values (`featureRequirements`), managed lifecycle hooks (`hooks`), `enforceResidency`, 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 allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), pinned feature values (`featureRequirements`), managed lifecycle hooks (`hooks`), `enforceResidency`, Computer Use policy (`computerUse`), and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.
|
||||
|
||||
### Example: Start or resume a thread
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ use codex_app_server_protocol::AppConfig;
|
||||
use codex_app_server_protocol::AppToolApproval;
|
||||
use codex_app_server_protocol::AppsConfig;
|
||||
use codex_app_server_protocol::AskForApproval;
|
||||
use codex_app_server_protocol::ComputerUseConfig;
|
||||
use codex_app_server_protocol::ComputerUseMacosConfig;
|
||||
use codex_config::CloudRequirementsLoader;
|
||||
use codex_config::FeatureRequirementsToml;
|
||||
use codex_config::LoaderOverrides;
|
||||
@@ -191,6 +193,44 @@ async fn write_value_supports_nested_app_paths() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn read_returns_computer_use_config() -> Result<()> {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
std::fs::write(
|
||||
tmp.path().join(CONFIG_TOML_FILE),
|
||||
r#"
|
||||
[computer_use]
|
||||
allow_persistent_approval = false
|
||||
|
||||
[computer_use.macos]
|
||||
denied_bundle_ids = ["com.apple.Terminal"]
|
||||
allowed_bundle_ids = ["com.apple.Safari"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let service = ConfigManager::without_managed_config_for_tests(tmp.path().to_path_buf());
|
||||
let read = service
|
||||
.read(ConfigReadParams {
|
||||
include_layers: false,
|
||||
cwd: None,
|
||||
})
|
||||
.await
|
||||
.expect("config read succeeds");
|
||||
|
||||
assert_eq!(
|
||||
read.config.computer_use,
|
||||
Some(ComputerUseConfig {
|
||||
allow_persistent_approval: Some(false),
|
||||
macos: Some(ComputerUseMacosConfig {
|
||||
denied_bundle_ids: Some(vec!["com.apple.Terminal".to_string()]),
|
||||
allowed_bundle_ids: Some(vec!["com.apple.Safari".to_string()]),
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn write_value_supports_custom_mcp_server_default_tool_approval_mode() -> Result<()> {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
|
||||
@@ -11,6 +11,11 @@ use crate::transport::RemoteControlHandle;
|
||||
use codex_analytics::AnalyticsEventsClient;
|
||||
use codex_app_server_protocol::AppListUpdatedNotification;
|
||||
use codex_app_server_protocol::ClientResponsePayload;
|
||||
use codex_app_server_protocol::ComputerUseConfig;
|
||||
use codex_app_server_protocol::ComputerUseMacosConfig;
|
||||
use codex_app_server_protocol::ComputerUseMacosRequirements;
|
||||
use codex_app_server_protocol::ComputerUseRequirements;
|
||||
use codex_app_server_protocol::Config as ApiConfig;
|
||||
use codex_app_server_protocol::ConfigBatchWriteParams;
|
||||
use codex_app_server_protocol::ConfigReadParams;
|
||||
use codex_app_server_protocol::ConfigReadResponse;
|
||||
@@ -97,6 +102,10 @@ impl ConfigRequestProcessor {
|
||||
let fallback_cwd = params.cwd.as_ref().map(PathBuf::from);
|
||||
let mut response = self.config_manager.read(params).await.map_err(map_error)?;
|
||||
let config = self.load_latest_config(fallback_cwd).await?;
|
||||
apply_computer_use_requirements(
|
||||
&mut response.config,
|
||||
config.config_layer_stack.requirements_toml(),
|
||||
);
|
||||
for feature_key in SUPPORTED_EXPERIMENTAL_FEATURE_ENABLEMENT {
|
||||
let Some(feature) = feature_for_key(feature_key) else {
|
||||
continue;
|
||||
@@ -412,6 +421,48 @@ impl ConfigRequestProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_computer_use_requirements(config: &mut ApiConfig, requirements: &ConfigRequirementsToml) {
|
||||
let Some(requirements) = requirements.computer_use.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let computer_use = config.computer_use.get_or_insert(ComputerUseConfig {
|
||||
allow_persistent_approval: None,
|
||||
macos: None,
|
||||
});
|
||||
if requirements.allow_persistent_approval == Some(false)
|
||||
|| computer_use.allow_persistent_approval.is_none()
|
||||
{
|
||||
computer_use.allow_persistent_approval = requirements.allow_persistent_approval;
|
||||
}
|
||||
|
||||
let Some(required_macos) = requirements.macos.as_ref() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let macos = computer_use.macos.get_or_insert(ComputerUseMacosConfig {
|
||||
denied_bundle_ids: None,
|
||||
allowed_bundle_ids: None,
|
||||
});
|
||||
if let Some(denied_bundle_ids) = required_macos.denied_bundle_ids.as_ref()
|
||||
&& !denied_bundle_ids.is_empty()
|
||||
{
|
||||
let config_denied_bundle_ids = macos.denied_bundle_ids.get_or_insert_with(Vec::new);
|
||||
for bundle_id in denied_bundle_ids {
|
||||
if !config_denied_bundle_ids.contains(bundle_id) {
|
||||
config_denied_bundle_ids.push(bundle_id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(allowed_bundle_ids) = required_macos.allowed_bundle_ids.as_ref() {
|
||||
if let Some(config_allowed_bundle_ids) = macos.allowed_bundle_ids.as_mut() {
|
||||
config_allowed_bundle_ids.retain(|bundle_id| allowed_bundle_ids.contains(bundle_id));
|
||||
} else {
|
||||
macos.allowed_bundle_ids = Some(allowed_bundle_ids.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigRequirements {
|
||||
ConfigRequirements {
|
||||
allowed_approval_policies: requirements.allowed_approval_policies.map(|policies| {
|
||||
@@ -450,6 +501,17 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
|
||||
.enforce_residency
|
||||
.map(map_residency_requirement_to_api),
|
||||
network: requirements.network.map(map_network_requirements_to_api),
|
||||
computer_use: requirements
|
||||
.computer_use
|
||||
.map(|requirements| ComputerUseRequirements {
|
||||
allow_persistent_approval: requirements.allow_persistent_approval,
|
||||
macos: requirements
|
||||
.macos
|
||||
.map(|requirements| ComputerUseMacosRequirements {
|
||||
denied_bundle_ids: requirements.denied_bundle_ids,
|
||||
allowed_bundle_ids: requirements.allowed_bundle_ids,
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,3 +682,92 @@ fn config_write_error(code: ConfigWriteErrorCode, message: impl Into<String>) ->
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use codex_config::ComputerUseMacosRequirementsToml as CoreComputerUseMacosRequirementsToml;
|
||||
use codex_config::ComputerUseRequirementsToml as CoreComputerUseRequirementsToml;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn map_requirements_toml_to_api_converts_computer_use() {
|
||||
let requirements = ConfigRequirementsToml {
|
||||
computer_use: Some(CoreComputerUseRequirementsToml {
|
||||
allow_persistent_approval: Some(false),
|
||||
macos: Some(CoreComputerUseMacosRequirementsToml {
|
||||
denied_bundle_ids: Some(vec!["com.apple.Terminal".to_string()]),
|
||||
allowed_bundle_ids: Some(vec!["com.apple.Safari".to_string()]),
|
||||
}),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mapped = map_requirements_toml_to_api(requirements);
|
||||
|
||||
assert_eq!(
|
||||
mapped.computer_use,
|
||||
Some(ComputerUseRequirements {
|
||||
allow_persistent_approval: Some(false),
|
||||
macos: Some(ComputerUseMacosRequirements {
|
||||
denied_bundle_ids: Some(vec!["com.apple.Terminal".to_string()]),
|
||||
allowed_bundle_ids: Some(vec!["com.apple.Safari".to_string()]),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_computer_use_requirements_to_config_is_restrictive() {
|
||||
let mut config: ApiConfig = serde_json::from_value(json!({
|
||||
"computer_use": {
|
||||
"allow_persistent_approval": true,
|
||||
"macos": {
|
||||
"denied_bundle_ids": [
|
||||
"com.example.ConfigDenied",
|
||||
"com.example.SharedDenied"
|
||||
],
|
||||
"allowed_bundle_ids": [
|
||||
"com.example.SharedAllowed",
|
||||
"com.example.ConfigAllowed"
|
||||
]
|
||||
}
|
||||
}
|
||||
}))
|
||||
.expect("valid config");
|
||||
let requirements = ConfigRequirementsToml {
|
||||
computer_use: Some(CoreComputerUseRequirementsToml {
|
||||
allow_persistent_approval: Some(false),
|
||||
macos: Some(CoreComputerUseMacosRequirementsToml {
|
||||
denied_bundle_ids: Some(vec![
|
||||
"com.example.RequiredDenied".to_string(),
|
||||
"com.example.SharedDenied".to_string(),
|
||||
]),
|
||||
allowed_bundle_ids: Some(vec![
|
||||
"com.example.SharedAllowed".to_string(),
|
||||
"com.example.RequiredAllowed".to_string(),
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
apply_computer_use_requirements(&mut config, &requirements);
|
||||
|
||||
assert_eq!(
|
||||
config.computer_use,
|
||||
Some(ComputerUseConfig {
|
||||
allow_persistent_approval: Some(false),
|
||||
macos: Some(ComputerUseMacosConfig {
|
||||
denied_bundle_ids: Some(vec![
|
||||
"com.example.ConfigDenied".to_string(),
|
||||
"com.example.SharedDenied".to_string(),
|
||||
"com.example.RequiredDenied".to_string(),
|
||||
]),
|
||||
allowed_bundle_ids: Some(vec!["com.example.SharedAllowed".to_string()]),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1209,6 +1209,7 @@ mod tests {
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -1291,6 +1292,7 @@ mod tests {
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -1324,6 +1326,7 @@ mod tests {
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -1374,6 +1377,7 @@ mod tests {
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -1490,6 +1494,7 @@ command = "sample-mcp"
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -1570,6 +1575,7 @@ command = "sample-mcp"
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -1648,6 +1654,7 @@ command = "sample-mcp"
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -1854,6 +1861,7 @@ command = "sample-mcp"
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -1894,6 +1902,7 @@ command = "sample-mcp"
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -1954,6 +1963,7 @@ command = "sample-mcp"
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -2010,6 +2020,7 @@ command = "sample-mcp"
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -2068,6 +2079,7 @@ command = "sample-mcp"
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -2127,6 +2139,7 @@ command = "sample-mcp"
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -2186,6 +2199,7 @@ command = "sample-mcp"
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -2275,6 +2289,7 @@ command = "sample-mcp"
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -2306,6 +2321,7 @@ command = "sample-mcp"
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
|
||||
@@ -614,6 +614,77 @@ impl AppsRequirementsToml {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct ComputerUseMacosRequirementsToml {
|
||||
pub denied_bundle_ids: Option<Vec<String>>,
|
||||
pub allowed_bundle_ids: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl ComputerUseMacosRequirementsToml {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.denied_bundle_ids.as_ref().is_none_or(Vec::is_empty)
|
||||
&& self.allowed_bundle_ids.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct ComputerUseRequirementsToml {
|
||||
pub allow_persistent_approval: Option<bool>,
|
||||
pub macos: Option<ComputerUseMacosRequirementsToml>,
|
||||
}
|
||||
|
||||
impl ComputerUseRequirementsToml {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.allow_persistent_approval.is_none()
|
||||
&& self
|
||||
.macos
|
||||
.as_ref()
|
||||
.is_none_or(ComputerUseMacosRequirementsToml::is_empty)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn merge_computer_use_requirements_descending(
|
||||
base: &mut ComputerUseRequirementsToml,
|
||||
incoming: ComputerUseRequirementsToml,
|
||||
) {
|
||||
if base.allow_persistent_approval == Some(false)
|
||||
|| incoming.allow_persistent_approval == Some(false)
|
||||
{
|
||||
base.allow_persistent_approval = Some(false);
|
||||
} else {
|
||||
base.allow_persistent_approval = base
|
||||
.allow_persistent_approval
|
||||
.or(incoming.allow_persistent_approval);
|
||||
}
|
||||
|
||||
let Some(incoming_macos) = incoming.macos else {
|
||||
return;
|
||||
};
|
||||
|
||||
let base_macos = base.macos.get_or_insert_with(Default::default);
|
||||
if let Some(incoming_denied_bundle_ids) = incoming_macos.denied_bundle_ids
|
||||
&& !incoming_denied_bundle_ids.is_empty()
|
||||
{
|
||||
let base_denied_bundle_ids = base_macos
|
||||
.denied_bundle_ids
|
||||
.get_or_insert_with(Default::default);
|
||||
for bundle_id in incoming_denied_bundle_ids {
|
||||
if !base_denied_bundle_ids.contains(&bundle_id) {
|
||||
base_denied_bundle_ids.push(bundle_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(incoming_allowed_bundle_ids) = incoming_macos.allowed_bundle_ids {
|
||||
if let Some(base_allowed_bundle_ids) = base_macos.allowed_bundle_ids.as_mut() {
|
||||
base_allowed_bundle_ids
|
||||
.retain(|bundle_id| incoming_allowed_bundle_ids.contains(bundle_id));
|
||||
} else {
|
||||
base_macos.allowed_bundle_ids = Some(incoming_allowed_bundle_ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@@ -648,6 +719,7 @@ pub struct ConfigRequirementsToml {
|
||||
pub mcp_servers: Option<BTreeMap<String, McpServerRequirement>>,
|
||||
pub plugins: Option<BTreeMap<String, PluginRequirementsToml>>,
|
||||
pub apps: Option<AppsRequirementsToml>,
|
||||
pub computer_use: Option<ComputerUseRequirementsToml>,
|
||||
pub rules: Option<RequirementsExecPolicyToml>,
|
||||
pub enforce_residency: Option<ResidencyRequirement>,
|
||||
#[serde(rename = "experimental_network")]
|
||||
@@ -695,6 +767,7 @@ pub struct ConfigRequirementsWithSources {
|
||||
pub mcp_servers: Option<Sourced<BTreeMap<String, McpServerRequirement>>>,
|
||||
pub plugins: Option<Sourced<BTreeMap<String, PluginRequirementsToml>>>,
|
||||
pub apps: Option<Sourced<AppsRequirementsToml>>,
|
||||
pub computer_use: Option<Sourced<ComputerUseRequirementsToml>>,
|
||||
pub rules: Option<Sourced<RequirementsExecPolicyToml>>,
|
||||
pub enforce_residency: Option<Sourced<ResidencyRequirement>>,
|
||||
pub network: Option<Sourced<NetworkRequirementsToml>>,
|
||||
@@ -731,6 +804,7 @@ impl ConfigRequirementsWithSources {
|
||||
mcp_servers: _,
|
||||
plugins: _,
|
||||
apps: _,
|
||||
computer_use: _,
|
||||
rules: _,
|
||||
enforce_residency: _,
|
||||
network: _,
|
||||
@@ -771,7 +845,18 @@ impl ConfigRequirementsWithSources {
|
||||
if let Some(existing_apps) = self.apps.as_mut() {
|
||||
merge_enablement_settings_descending(&mut existing_apps.value, incoming_apps);
|
||||
} else {
|
||||
self.apps = Some(Sourced::new(incoming_apps, source));
|
||||
self.apps = Some(Sourced::new(incoming_apps, source.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(incoming_computer_use) = other.computer_use.take() {
|
||||
if let Some(existing_computer_use) = self.computer_use.as_mut() {
|
||||
merge_computer_use_requirements_descending(
|
||||
&mut existing_computer_use.value,
|
||||
incoming_computer_use,
|
||||
);
|
||||
} else {
|
||||
self.computer_use = Some(Sourced::new(incoming_computer_use, source));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -787,6 +872,7 @@ impl ConfigRequirementsWithSources {
|
||||
mcp_servers,
|
||||
plugins,
|
||||
apps,
|
||||
computer_use,
|
||||
rules,
|
||||
enforce_residency,
|
||||
network,
|
||||
@@ -804,6 +890,7 @@ impl ConfigRequirementsWithSources {
|
||||
mcp_servers: mcp_servers.map(|sourced| sourced.value),
|
||||
plugins: plugins.map(|sourced| sourced.value),
|
||||
apps: apps.map(|sourced| sourced.value),
|
||||
computer_use: computer_use.map(|sourced| sourced.value),
|
||||
rules: rules.map(|sourced| sourced.value),
|
||||
enforce_residency: enforce_residency.map(|sourced| sourced.value),
|
||||
network: network.map(|sourced| sourced.value),
|
||||
@@ -899,6 +986,10 @@ impl ConfigRequirementsToml {
|
||||
.apps
|
||||
.as_ref()
|
||||
.is_none_or(AppsRequirementsToml::is_empty)
|
||||
&& self
|
||||
.computer_use
|
||||
.as_ref()
|
||||
.is_none_or(ComputerUseRequirementsToml::is_empty)
|
||||
&& self.rules.is_none()
|
||||
&& self.enforce_residency.is_none()
|
||||
&& self.network.is_none()
|
||||
@@ -924,6 +1015,7 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
mcp_servers,
|
||||
plugins,
|
||||
apps: _apps,
|
||||
computer_use: _computer_use,
|
||||
rules,
|
||||
enforce_residency,
|
||||
network,
|
||||
@@ -1231,6 +1323,7 @@ mod tests {
|
||||
mcp_servers,
|
||||
plugins,
|
||||
apps,
|
||||
computer_use,
|
||||
rules,
|
||||
enforce_residency,
|
||||
network,
|
||||
@@ -1252,6 +1345,7 @@ mod tests {
|
||||
mcp_servers: mcp_servers.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
plugins: plugins.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
apps: apps.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
computer_use: computer_use.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
rules: rules.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
enforce_residency: enforce_residency
|
||||
.map(|value| Sourced::new(value, RequirementSource::Unknown)),
|
||||
@@ -1298,6 +1392,7 @@ mod tests {
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: Some(enforce_residency),
|
||||
network: None,
|
||||
@@ -1331,6 +1426,7 @@ mod tests {
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: Some(Sourced::new(enforce_residency, enforce_source)),
|
||||
network: None,
|
||||
@@ -1370,6 +1466,7 @@ mod tests {
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -1417,6 +1514,7 @@ mod tests {
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -1590,6 +1688,32 @@ allowed_approvals_reviewers = ["user"]
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_computer_use_requirements() -> Result<()> {
|
||||
let toml_str = r#"
|
||||
[computer_use]
|
||||
allow_persistent_approval = false
|
||||
|
||||
[computer_use.macos]
|
||||
denied_bundle_ids = ["com.apple.Terminal"]
|
||||
allowed_bundle_ids = ["com.apple.Safari"]
|
||||
"#;
|
||||
let requirements: ConfigRequirementsToml = from_str(toml_str)?;
|
||||
|
||||
assert_eq!(
|
||||
requirements.computer_use,
|
||||
Some(ComputerUseRequirementsToml {
|
||||
allow_persistent_approval: Some(false),
|
||||
macos: Some(ComputerUseMacosRequirementsToml {
|
||||
denied_bundle_ids: Some(vec!["com.apple.Terminal".to_string()]),
|
||||
allowed_bundle_ids: Some(vec!["com.apple.Safari".to_string()]),
|
||||
}),
|
||||
})
|
||||
);
|
||||
assert!(!requirements.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apps_requirements(entries: &[(&str, Option<bool>)]) -> AppsRequirementsToml {
|
||||
AppsRequirementsToml {
|
||||
apps: entries
|
||||
@@ -1736,6 +1860,108 @@ allowed_approvals_reviewers = ["user"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_computer_use_requirements_descending_is_restrictive() {
|
||||
let mut merged = ComputerUseRequirementsToml {
|
||||
allow_persistent_approval: Some(true),
|
||||
macos: Some(ComputerUseMacosRequirementsToml {
|
||||
denied_bundle_ids: Some(vec![
|
||||
"com.example.High".to_string(),
|
||||
"com.example.Shared".to_string(),
|
||||
]),
|
||||
allowed_bundle_ids: Some(vec![
|
||||
"com.example.Shared".to_string(),
|
||||
"com.example.HighOnly".to_string(),
|
||||
]),
|
||||
}),
|
||||
};
|
||||
let lower = ComputerUseRequirementsToml {
|
||||
allow_persistent_approval: Some(false),
|
||||
macos: Some(ComputerUseMacosRequirementsToml {
|
||||
denied_bundle_ids: Some(vec![
|
||||
"com.example.Low".to_string(),
|
||||
"com.example.Shared".to_string(),
|
||||
]),
|
||||
allowed_bundle_ids: Some(vec![
|
||||
"com.example.Shared".to_string(),
|
||||
"com.example.LowOnly".to_string(),
|
||||
]),
|
||||
}),
|
||||
};
|
||||
|
||||
merge_computer_use_requirements_descending(&mut merged, lower);
|
||||
|
||||
assert_eq!(
|
||||
merged,
|
||||
ComputerUseRequirementsToml {
|
||||
allow_persistent_approval: Some(false),
|
||||
macos: Some(ComputerUseMacosRequirementsToml {
|
||||
denied_bundle_ids: Some(vec![
|
||||
"com.example.High".to_string(),
|
||||
"com.example.Shared".to_string(),
|
||||
"com.example.Low".to_string(),
|
||||
]),
|
||||
allowed_bundle_ids: Some(vec!["com.example.Shared".to_string()]),
|
||||
}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_unset_fields_merges_computer_use_across_sources() {
|
||||
let higher_source = RequirementSource::CloudRequirements;
|
||||
let mut target = ConfigRequirementsWithSources::default();
|
||||
|
||||
target.merge_unset_fields(
|
||||
higher_source.clone(),
|
||||
ConfigRequirementsToml {
|
||||
computer_use: Some(ComputerUseRequirementsToml {
|
||||
allow_persistent_approval: Some(true),
|
||||
macos: Some(ComputerUseMacosRequirementsToml {
|
||||
denied_bundle_ids: Some(vec!["com.example.High".to_string()]),
|
||||
allowed_bundle_ids: Some(vec![
|
||||
"com.example.Shared".to_string(),
|
||||
"com.example.HighOnly".to_string(),
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
target.merge_unset_fields(
|
||||
RequirementSource::LegacyManagedConfigTomlFromMdm,
|
||||
ConfigRequirementsToml {
|
||||
computer_use: Some(ComputerUseRequirementsToml {
|
||||
allow_persistent_approval: Some(false),
|
||||
macos: Some(ComputerUseMacosRequirementsToml {
|
||||
denied_bundle_ids: Some(vec!["com.example.Low".to_string()]),
|
||||
allowed_bundle_ids: Some(vec![
|
||||
"com.example.Shared".to_string(),
|
||||
"com.example.LowOnly".to_string(),
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let computer_use = target.computer_use.expect("computer_use should be present");
|
||||
assert_eq!(
|
||||
computer_use.value,
|
||||
ComputerUseRequirementsToml {
|
||||
allow_persistent_approval: Some(false),
|
||||
macos: Some(ComputerUseMacosRequirementsToml {
|
||||
denied_bundle_ids: Some(vec![
|
||||
"com.example.High".to_string(),
|
||||
"com.example.Low".to_string(),
|
||||
]),
|
||||
allowed_bundle_ids: Some(vec!["com.example.Shared".to_string()]),
|
||||
}),
|
||||
}
|
||||
);
|
||||
assert_eq!(computer_use.source, higher_source);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constraint_error_includes_requirement_source() -> Result<()> {
|
||||
let source: ConfigRequirementsToml = from_str(
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::types::AnalyticsConfigToml;
|
||||
use crate::types::ApprovalsReviewer;
|
||||
use crate::types::AppsConfigToml;
|
||||
use crate::types::AuthCredentialsStoreMode;
|
||||
use crate::types::ComputerUseConfigToml;
|
||||
use crate::types::FeedbackConfigToml;
|
||||
use crate::types::History;
|
||||
use crate::types::MarketplaceConfig;
|
||||
@@ -424,6 +425,10 @@ pub struct ConfigToml {
|
||||
#[serde(default)]
|
||||
pub apps: Option<AppsConfigToml>,
|
||||
|
||||
/// Settings for Computer Use controls.
|
||||
#[serde(default)]
|
||||
pub computer_use: Option<ComputerUseConfigToml>,
|
||||
|
||||
/// OTEL configuration.
|
||||
pub otel: Option<OtelConfigToml>,
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ pub use codex_app_server_protocol::ConfigLayerSource;
|
||||
pub use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
pub use config_requirements::AppRequirementToml;
|
||||
pub use config_requirements::AppsRequirementsToml;
|
||||
pub use config_requirements::ComputerUseMacosRequirementsToml;
|
||||
pub use config_requirements::ComputerUseRequirementsToml;
|
||||
pub use config_requirements::ConfigRequirements;
|
||||
pub use config_requirements::ConfigRequirementsToml;
|
||||
pub use config_requirements::ConfigRequirementsWithSources;
|
||||
|
||||
@@ -428,6 +428,32 @@ pub struct AppsConfigToml {
|
||||
pub apps: HashMap<String, AppConfig>,
|
||||
}
|
||||
|
||||
/// macOS-specific Computer Use controls loaded from `config.toml`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct ComputerUseMacosConfigToml {
|
||||
/// Bundle identifiers that Computer Use is not allowed to access.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub denied_bundle_ids: Option<Vec<String>>,
|
||||
|
||||
/// Bundle identifiers that Computer Use is allowed to access.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub allowed_bundle_ids: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
/// Computer Use controls loaded from `config.toml`.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
|
||||
#[schemars(deny_unknown_fields)]
|
||||
pub struct ComputerUseConfigToml {
|
||||
/// Whether approval requests can offer persistent approval.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub allow_persistent_approval: Option<bool>,
|
||||
|
||||
/// macOS-specific Computer Use controls.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub macos: Option<ComputerUseMacosConfigToml>,
|
||||
}
|
||||
|
||||
// ===== OTEL configuration =====
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema)]
|
||||
|
||||
@@ -328,6 +328,46 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ComputerUseConfigToml": {
|
||||
"additionalProperties": false,
|
||||
"description": "Computer Use controls loaded from `config.toml`.",
|
||||
"properties": {
|
||||
"allow_persistent_approval": {
|
||||
"description": "Whether approval requests can offer persistent approval.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"macos": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ComputerUseMacosConfigToml"
|
||||
}
|
||||
],
|
||||
"description": "macOS-specific Computer Use controls."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ComputerUseMacosConfigToml": {
|
||||
"additionalProperties": false,
|
||||
"description": "macOS-specific Computer Use controls loaded from `config.toml`.",
|
||||
"properties": {
|
||||
"allowed_bundle_ids": {
|
||||
"description": "Bundle identifiers that Computer Use is allowed to access.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"denied_bundle_ids": {
|
||||
"description": "Bundle identifiers that Computer Use is not allowed to access.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ConfigProfile": {
|
||||
"additionalProperties": false,
|
||||
"description": "Collection of common configuration options that a user can define as a unit in `config.toml`.",
|
||||
@@ -3756,6 +3796,15 @@
|
||||
"description": "Compact prompt used for history compaction.",
|
||||
"type": "string"
|
||||
},
|
||||
"computer_use": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ComputerUseConfigToml"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Settings for Computer Use controls."
|
||||
},
|
||||
"debug": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -4503,4 +4552,4 @@
|
||||
},
|
||||
"title": "ConfigToml",
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,6 +782,7 @@ allowed_approval_policies = ["on-request"]
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -839,6 +840,7 @@ allowed_approval_policies = ["on-request"]
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -1047,6 +1049,7 @@ async fn load_config_layers_includes_cloud_requirements() -> anyhow::Result<()>
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
|
||||
@@ -6984,6 +6984,7 @@ async fn test_requirements_web_search_mode_allowlist_does_not_warn_when_unset()
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
@@ -7696,6 +7697,7 @@ async fn explicit_sandbox_mode_falls_back_when_disallowed_by_requirements() -> s
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
|
||||
@@ -699,6 +699,7 @@ mod tests {
|
||||
)])),
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: Some(ResidencyRequirement::Us),
|
||||
network: None,
|
||||
@@ -899,6 +900,7 @@ approval_policy = "never"
|
||||
mcp_servers: None,
|
||||
plugins: None,
|
||||
apps: None,
|
||||
computer_use: None,
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
|
||||
Reference in New Issue
Block a user