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 9b4c8826d9..735925eb3c 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 @@ -7387,6 +7387,15 @@ }, "AutoReviewRequirements": { "properties": { + "ignoreRules": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, "requiredOnModels": { "items": { "type": "string" 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 95e19011b5..8091b2fb55 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 @@ -1246,6 +1246,15 @@ }, "AutoReviewRequirements": { "properties": { + "ignoreRules": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, "requiredOnModels": { "items": { "type": "string" 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 407fafa3bd..39491b4051 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json @@ -61,6 +61,15 @@ }, "AutoReviewRequirements": { "properties": { + "ignoreRules": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, "requiredOnModels": { "items": { "type": "string" diff --git a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst index 3b41b1ba53..2bbd3cb0bf 100644 Binary files a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst and b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst differ diff --git a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst index 4cb39eef81..d600220c24 100644 Binary files a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst and b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst differ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AutoReviewRequirements.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AutoReviewRequirements.ts index 26c5ae1636..04036ba234 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/AutoReviewRequirements.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AutoReviewRequirements.ts @@ -2,4 +2,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type AutoReviewRequirements = { requiredOnModels: Array | null, }; +export type AutoReviewRequirements = { requiredOnModels: Array | null, ignoreRules: Array | null, }; 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 3628963027..2f0f26294a 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/config.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/config.rs @@ -413,6 +413,7 @@ pub struct ConfigRequirements { #[ts(export_to = "v2/")] pub struct AutoReviewRequirements { pub required_on_models: Option>, + pub ignore_rules: Option>, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 921b30acca..c997b6a3d5 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -278,7 +278,7 @@ Example with notification opt-out: - `externalAgentConfig/import/readHistories` — read completed import histories and connector candidates detected from successfully imported session histories. Successful session entries include the original imported title when one was available. 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`), Browser Use policy (`browserUse.disableAutoReview`), pinned feature values (`featureRequirements`, including the default-allowed `in_app_updates` policy that administrators can set to `false`), managed lifecycle hooks (`hooks`, including command handlers with optional `additionalContextLimit` and `mcp_tool` handlers with `server`, `tool`, `input`, `timeoutSec`, and `statusMessage`), `enforceResidency`, managed automatic review (`autoReview.requiredOnModels`), model 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`, including the default-allowed `in_app_updates` policy that administrators can set to `false`), managed lifecycle hooks (`hooks`, including command handlers with optional `additionalContextLimit` and `mcp_tool` handlers with `server`, `tool`, `input`, `timeoutSec`, and `statusMessage`), `enforceResidency`, managed automatic review (`autoReview.requiredOnModels` and `autoReview.ignoreRules`), model 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 @@ -871,9 +871,10 @@ Managed `requirements.toml` can require automatic review for specific models: ```toml [auto_review] required_on_models = ["protected-model"] +ignore_rules = ["protected-model"] ``` -Listed models always start with `approvalPolicy: "on-request"` and `approvalsReviewer: "auto_review"`, even when clients provide incompatible startup values. Full Access is automatically downgraded to workspace-write access. Incompatible runtime overrides or disabled Guardian automatic review are rejected. +Models in `required_on_models` always start with `approvalPolicy: "on-request"` and `approvalsReviewer: "auto_review"`, even when clients provide incompatible startup values. Full Access is automatically downgraded to workspace-write access. Incompatible runtime overrides or disabled Guardian automatic review are rejected. Models in `ignore_rules` ignore saved command-prefix approvals. ```json { "method": "turn/start", "id": 30, "params": { 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 a2501855c7..e3e17b30fa 100644 --- a/codex-rs/app-server/src/request_processors/config_processor.rs +++ b/codex-rs/app-server/src/request_processors/config_processor.rs @@ -420,6 +420,7 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR .auto_review .map(|auto_review| AutoReviewRequirements { required_on_models: auto_review.required_on_models, + ignore_rules: auto_review.ignore_rules, }), models: requirements.models.map(|models| ModelsRequirements { new_thread: models.new_thread.map(|new_thread| NewThreadModelDefaults { @@ -652,6 +653,7 @@ fn config_write_error(code: ConfigWriteErrorCode, message: impl Into) -> #[cfg(test)] mod tests { use super::map_requirements_toml_to_api; + use codex_app_server_protocol::AutoReviewRequirements; use codex_app_server_protocol::FeedbackRequirements; use codex_app_server_protocol::WindowsSandboxSetupMode; use codex_config::AutoReviewRequirementsToml; @@ -728,7 +730,7 @@ mod tests { let mapped = map_requirements_toml_to_api(ConfigRequirementsToml { auto_review: Some(AutoReviewRequirementsToml { required_on_models: Some(vec!["gpt-protected".to_string()]), - ignore_rules: None, + ignore_rules: Some(vec!["gpt-protected".to_string()]), }), models: Some(ModelsRequirementsToml { new_thread: Some(NewThreadModelDefaultsToml { @@ -741,11 +743,11 @@ mod tests { }); assert_eq!( - mapped - .auto_review - .expect("managed automatic-review requirements") - .required_on_models, - Some(vec!["gpt-protected".to_string()]) + mapped.auto_review, + Some(AutoReviewRequirements { + required_on_models: Some(vec!["gpt-protected".to_string()]), + ignore_rules: Some(vec!["gpt-protected".to_string()]), + }) ); let models = mapped.models.expect("managed model requirements"); let defaults = models.new_thread.expect("new-thread defaults"); 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 50a6389bc9..76d31d7a5c 100644 --- a/codex-rs/app-server/tests/suite/v2/config_rpc.rs +++ b/codex-rs/app-server/tests/suite/v2/config_rpc.rs @@ -187,6 +187,7 @@ async fn config_requirements_read_includes_model_auto_review_and_new_thread_defa r#" [auto_review] required_on_models = ["gpt-protected", "gpt-sensitive"] +ignore_rules = ["gpt-protected"] [models.new_thread] model = "gpt-managed" @@ -205,16 +206,20 @@ service_tier = "fast" timeout(DEFAULT_READ_TIMEOUT, mcp.read_response(request_id)).await??; let requirements = response.requirements.expect("managed requirements"); + let auto_review = requirements + .auto_review + .expect("managed automatic-review requirements"); assert_eq!( - requirements - .auto_review - .expect("managed automatic-review requirements") - .required_on_models, + auto_review.required_on_models, Some(vec![ "gpt-protected".to_string(), "gpt-sensitive".to_string() ]) ); + assert_eq!( + auto_review.ignore_rules, + Some(vec!["gpt-protected".to_string()]) + ); let models = requirements.models.expect("managed model requirements"); let defaults = models.new_thread.expect("managed new-thread defaults"); assert_eq!(defaults.model.as_deref(), Some("gpt-managed"));