feat: introduce Reject variant of AskForApproval

This commit is contained in:
Michael Bolin
2026-02-18 16:33:02 -08:00
parent 31cbebd3c2
commit be16f6798d
36 changed files with 1244 additions and 142 deletions
@@ -69,13 +69,46 @@
"type": "object"
},
"AskForApproval": {
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
"oneOf": [
{
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"type": "boolean"
},
"rules": {
"type": "boolean"
},
"sandbox_approval": {
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
}
]
},
"AskForApproval2": {
"description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.",
@@ -101,6 +134,39 @@
],
"type": "string"
},
{
"additionalProperties": false,
"description": "Fine-grained rejection controls for approval prompts.\n\nWhen a field is `true`, prompts of that category are automatically rejected instead of shown to the user.",
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"description": "Reject MCP elicitation prompts.",
"type": "boolean"
},
"rules": {
"description": "Reject prompts triggered by execpolicy `prompt` rules.",
"type": "boolean"
},
"sandbox_approval": {
"description": "Reject approval prompts related to sandbox escalation.",
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval2",
"type": "object"
},
{
"description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.",
"enum": [
@@ -117,6 +117,39 @@
],
"type": "string"
},
{
"additionalProperties": false,
"description": "Fine-grained rejection controls for approval prompts.\n\nWhen a field is `true`, prompts of that category are automatically rejected instead of shown to the user.",
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"description": "Reject MCP elicitation prompts.",
"type": "boolean"
},
"rules": {
"description": "Reject prompts triggered by execpolicy `prompt` rules.",
"type": "boolean"
},
"sandbox_approval": {
"description": "Reject approval prompts related to sandbox escalation.",
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
},
{
"description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.",
"enum": [
@@ -454,6 +454,39 @@
],
"type": "string"
},
{
"additionalProperties": false,
"description": "Fine-grained rejection controls for approval prompts.\n\nWhen a field is `true`, prompts of that category are automatically rejected instead of shown to the user.",
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"description": "Reject MCP elicitation prompts.",
"type": "boolean"
},
"rules": {
"description": "Reject prompts triggered by execpolicy `prompt` rules.",
"type": "boolean"
},
"sandbox_approval": {
"description": "Reject approval prompts related to sandbox escalation.",
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
},
{
"description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.",
"enum": [
@@ -221,6 +221,39 @@
],
"type": "string"
},
{
"additionalProperties": false,
"description": "Fine-grained rejection controls for approval prompts.\n\nWhen a field is `true`, prompts of that category are automatically rejected instead of shown to the user.",
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"description": "Reject MCP elicitation prompts.",
"type": "boolean"
},
"rules": {
"description": "Reject prompts triggered by execpolicy `prompt` rules.",
"type": "boolean"
},
"sandbox_approval": {
"description": "Reject approval prompts related to sandbox escalation.",
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
},
{
"description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.",
"enum": [
@@ -10667,13 +10700,46 @@
"type": "object"
},
"AskForApproval": {
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
"oneOf": [
{
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"type": "boolean"
},
"rules": {
"type": "boolean"
},
"sandbox_approval": {
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
}
]
},
"AuthMode": {
"description": "Authentication mode for OpenAI-backed providers.",
@@ -25,6 +25,39 @@
],
"type": "string"
},
{
"additionalProperties": false,
"description": "Fine-grained rejection controls for approval prompts.\n\nWhen a field is `true`, prompts of that category are automatically rejected instead of shown to the user.",
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"description": "Reject MCP elicitation prompts.",
"type": "boolean"
},
"rules": {
"description": "Reject prompts triggered by execpolicy `prompt` rules.",
"type": "boolean"
},
"sandbox_approval": {
"description": "Reject approval prompts related to sandbox escalation.",
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
},
{
"description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.",
"enum": [
@@ -117,6 +117,39 @@
],
"type": "string"
},
{
"additionalProperties": false,
"description": "Fine-grained rejection controls for approval prompts.\n\nWhen a field is `true`, prompts of that category are automatically rejected instead of shown to the user.",
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"description": "Reject MCP elicitation prompts.",
"type": "boolean"
},
"rules": {
"description": "Reject prompts triggered by execpolicy `prompt` rules.",
"type": "boolean"
},
"sandbox_approval": {
"description": "Reject approval prompts related to sandbox escalation.",
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
},
{
"description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.",
"enum": [
@@ -29,6 +29,39 @@
],
"type": "string"
},
{
"additionalProperties": false,
"description": "Fine-grained rejection controls for approval prompts.\n\nWhen a field is `true`, prompts of that category are automatically rejected instead of shown to the user.",
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"description": "Reject MCP elicitation prompts.",
"type": "boolean"
},
"rules": {
"description": "Reject prompts triggered by execpolicy `prompt` rules.",
"type": "boolean"
},
"sandbox_approval": {
"description": "Reject approval prompts related to sandbox escalation.",
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
},
{
"description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.",
"enum": [
@@ -25,6 +25,39 @@
],
"type": "string"
},
{
"additionalProperties": false,
"description": "Fine-grained rejection controls for approval prompts.\n\nWhen a field is `true`, prompts of that category are automatically rejected instead of shown to the user.",
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"description": "Reject MCP elicitation prompts.",
"type": "boolean"
},
"rules": {
"description": "Reject prompts triggered by execpolicy `prompt` rules.",
"type": "boolean"
},
"sandbox_approval": {
"description": "Reject approval prompts related to sandbox escalation.",
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
},
{
"description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.",
"enum": [
@@ -25,6 +25,39 @@
],
"type": "string"
},
{
"additionalProperties": false,
"description": "Fine-grained rejection controls for approval prompts.\n\nWhen a field is `true`, prompts of that category are automatically rejected instead of shown to the user.",
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"description": "Reject MCP elicitation prompts.",
"type": "boolean"
},
"rules": {
"description": "Reject prompts triggered by execpolicy `prompt` rules.",
"type": "boolean"
},
"sandbox_approval": {
"description": "Reject approval prompts related to sandbox escalation.",
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
},
{
"description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.",
"enum": [
@@ -117,6 +117,39 @@
],
"type": "string"
},
{
"additionalProperties": false,
"description": "Fine-grained rejection controls for approval prompts.\n\nWhen a field is `true`, prompts of that category are automatically rejected instead of shown to the user.",
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"description": "Reject MCP elicitation prompts.",
"type": "boolean"
},
"rules": {
"description": "Reject prompts triggered by execpolicy `prompt` rules.",
"type": "boolean"
},
"sandbox_approval": {
"description": "Reject approval prompts related to sandbox escalation.",
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
},
{
"description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.",
"enum": [
@@ -29,6 +29,39 @@
],
"type": "string"
},
{
"additionalProperties": false,
"description": "Fine-grained rejection controls for approval prompts.\n\nWhen a field is `true`, prompts of that category are automatically rejected instead of shown to the user.",
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"description": "Reject MCP elicitation prompts.",
"type": "boolean"
},
"rules": {
"description": "Reject prompts triggered by execpolicy `prompt` rules.",
"type": "boolean"
},
"sandbox_approval": {
"description": "Reject approval prompts related to sandbox escalation.",
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
},
{
"description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.",
"enum": [
@@ -117,6 +117,39 @@
],
"type": "string"
},
{
"additionalProperties": false,
"description": "Fine-grained rejection controls for approval prompts.\n\nWhen a field is `true`, prompts of that category are automatically rejected instead of shown to the user.",
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"description": "Reject MCP elicitation prompts.",
"type": "boolean"
},
"rules": {
"description": "Reject prompts triggered by execpolicy `prompt` rules.",
"type": "boolean"
},
"sandbox_approval": {
"description": "Reject approval prompts related to sandbox escalation.",
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
},
{
"description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.",
"enum": [
@@ -47,13 +47,46 @@
"type": "object"
},
"AskForApproval": {
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
"oneOf": [
{
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"type": "boolean"
},
"rules": {
"type": "boolean"
},
"sandbox_approval": {
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
}
]
},
"Config": {
"additionalProperties": true,
@@ -2,13 +2,46 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AskForApproval": {
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
"oneOf": [
{
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"type": "boolean"
},
"rules": {
"type": "boolean"
},
"sandbox_approval": {
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
}
]
},
"ConfigRequirements": {
"properties": {
@@ -2,13 +2,46 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AskForApproval": {
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
"oneOf": [
{
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"type": "boolean"
},
"rules": {
"type": "boolean"
},
"sandbox_approval": {
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
}
]
},
"SandboxMode": {
"enum": [
@@ -6,13 +6,46 @@
"type": "string"
},
"AskForApproval": {
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
"oneOf": [
{
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"type": "boolean"
},
"rules": {
"type": "boolean"
},
"sandbox_approval": {
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
}
]
},
"ByteRange": {
"properties": {
@@ -2,13 +2,46 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AskForApproval": {
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
"oneOf": [
{
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"type": "boolean"
},
"rules": {
"type": "boolean"
},
"sandbox_approval": {
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
}
]
},
"ContentItem": {
"oneOf": [
@@ -6,13 +6,46 @@
"type": "string"
},
"AskForApproval": {
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
"oneOf": [
{
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"type": "boolean"
},
"rules": {
"type": "boolean"
},
"sandbox_approval": {
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
}
]
},
"ByteRange": {
"properties": {
@@ -2,13 +2,46 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AskForApproval": {
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
"oneOf": [
{
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"type": "boolean"
},
"rules": {
"type": "boolean"
},
"sandbox_approval": {
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
}
]
},
"DynamicToolSpec": {
"properties": {
@@ -6,13 +6,46 @@
"type": "string"
},
"AskForApproval": {
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
"oneOf": [
{
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"type": "boolean"
},
"rules": {
"type": "boolean"
},
"sandbox_approval": {
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
}
]
},
"ByteRange": {
"properties": {
@@ -6,13 +6,46 @@
"type": "string"
},
"AskForApproval": {
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
"oneOf": [
{
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
},
{
"additionalProperties": false,
"properties": {
"reject": {
"properties": {
"mcp_elicitations": {
"type": "boolean"
},
"rules": {
"type": "boolean"
},
"sandbox_approval": {
"type": "boolean"
}
},
"required": [
"mcp_elicitations",
"rules",
"sandbox_approval"
],
"type": "object"
}
},
"required": [
"reject"
],
"title": "RejectAskForApproval",
"type": "object"
}
]
},
"ByteRange": {
"properties": {
@@ -6,4 +6,16 @@
* Determines the conditions under which the user is consulted to approve
* running the command proposed by Codex.
*/
export type AskForApproval = "untrusted" | "on-failure" | "on-request" | "never";
export type AskForApproval = "untrusted" | "on-failure" | "on-request" | { "reject": {
/**
* Reject approval prompts related to sandbox escalation.
*/
sandbox_approval: boolean,
/**
* Reject prompts triggered by execpolicy `prompt` rules.
*/
rules: boolean,
/**
* Reject MCP elicitation prompts.
*/
mcp_elicitations: boolean, } } | "never";
@@ -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 AskForApproval = "untrusted" | "on-failure" | "on-request" | "never";
export type AskForApproval = "untrusted" | "on-failure" | "on-request" | { "reject": { sandbox_approval: boolean, rules: boolean, mcp_elicitations: boolean, } } | "never";
@@ -36,6 +36,7 @@ use codex_protocol::protocol::PatchApplyStatus as CorePatchApplyStatus;
use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow;
use codex_protocol::protocol::ReadOnlyAccess as CoreReadOnlyAccess;
use codex_protocol::protocol::RejectConfig as CoreRejectConfig;
use codex_protocol::protocol::SessionSource as CoreSessionSource;
use codex_protocol::protocol::SkillDependencies as CoreSkillDependencies;
use codex_protocol::protocol::SkillErrorInfo as CoreSkillErrorInfo;
@@ -161,6 +162,11 @@ pub enum AskForApproval {
UnlessTrusted,
OnFailure,
OnRequest,
Reject {
sandbox_approval: bool,
rules: bool,
mcp_elicitations: bool,
},
Never,
}
@@ -170,6 +176,15 @@ impl AskForApproval {
AskForApproval::UnlessTrusted => CoreAskForApproval::UnlessTrusted,
AskForApproval::OnFailure => CoreAskForApproval::OnFailure,
AskForApproval::OnRequest => CoreAskForApproval::OnRequest,
AskForApproval::Reject {
sandbox_approval,
rules,
mcp_elicitations,
} => CoreAskForApproval::Reject(CoreRejectConfig {
sandbox_approval,
rules,
mcp_elicitations,
}),
AskForApproval::Never => CoreAskForApproval::Never,
}
}
@@ -181,6 +196,11 @@ impl From<CoreAskForApproval> for AskForApproval {
CoreAskForApproval::UnlessTrusted => AskForApproval::UnlessTrusted,
CoreAskForApproval::OnFailure => AskForApproval::OnFailure,
CoreAskForApproval::OnRequest => AskForApproval::OnRequest,
CoreAskForApproval::Reject(reject_config) => AskForApproval::Reject {
sandbox_approval: reject_config.sandbox_approval,
rules: reject_config.rules,
mcp_elicitations: reject_config.mcp_elicitations,
},
CoreAskForApproval::Never => AskForApproval::Never,
}
}
+11 -34
View File
@@ -109,38 +109,15 @@
"description": "App/connector settings loaded from `config.toml`.",
"type": "object"
},
"AskForApproval": {
"description": "Determines the conditions under which the user is consulted to approve running the command proposed by Codex.",
"oneOf": [
{
"description": "Under this policy, only \"known safe\" commands—as determined by `is_safe_command()`—that **only read files** are auto‑approved. Everything else will ask the user to approve.",
"enum": [
"untrusted"
],
"type": "string"
},
{
"description": "DEPRECATED: *All* commands are auto‑approved, but they are expected to run inside a sandbox where network access is disabled and writes are confined to a specific set of paths. If the command fails, it will be escalated to the user to approve execution without a sandbox. Prefer `OnRequest` for interactive runs or `Never` for non-interactive runs.",
"enum": [
"on-failure"
],
"type": "string"
},
{
"description": "The model decides when to ask the user for approval.",
"enum": [
"on-request"
],
"type": "string"
},
{
"description": "Never ask the user to approve commands. Failures are immediately returned to the model, and never escalated to the user for approval.",
"enum": [
"never"
],
"type": "string"
}
]
"AskForApprovalToml": {
"description": "Version of [AskForApproval] that is deserializable from config.toml.",
"enum": [
"untrusted",
"on-failure",
"on-request",
"never"
],
"type": "string"
},
"AuthCredentialsStoreMode": {
"description": "Determine where Codex should store CLI auth credentials.",
@@ -183,7 +160,7 @@
"$ref": "#/definitions/AnalyticsConfigToml"
},
"approval_policy": {
"$ref": "#/definitions/AskForApproval"
"$ref": "#/definitions/AskForApprovalToml"
},
"chatgpt_base_url": {
"type": "string"
@@ -1280,7 +1257,7 @@
"approval_policy": {
"allOf": [
{
"$ref": "#/definitions/AskForApproval"
"$ref": "#/definitions/AskForApprovalToml"
}
],
"description": "Default approval policy for executing commands."
+21 -8
View File
@@ -1263,7 +1263,9 @@ impl Session {
};
let services = SessionServices {
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::default())),
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new(
&config.permissions.approval_policy,
))),
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
unified_exec_manager: UnifiedExecProcessManager::default(),
analytics_events_client: AnalyticsEventsClient::new(
@@ -1380,19 +1382,19 @@ impl Session {
required_mcp_servers.sort();
let cancel_token = sess.mcp_startup_cancellation_token().await;
sess.services
.mcp_connection_manager
.write()
.await
let mut mcp_connection_manager = sess.services.mcp_connection_manager.write().await;
mcp_connection_manager
.initialize(
&mcp_servers,
config.mcp_oauth_credentials_store_mode,
auth_statuses.clone(),
&session_configuration.approval_policy,
tx_event.clone(),
cancel_token,
sandbox_state,
)
.await;
drop(mcp_connection_manager);
if !required_mcp_servers.is_empty() {
let failures = sess
.services
@@ -1828,6 +1830,11 @@ impl Session {
sandbox_policy_changed: bool,
) -> Arc<TurnContext> {
let per_turn_config = Self::build_per_turn_config(&session_configuration);
self.services
.mcp_connection_manager
.read()
.await
.set_approval_policy(&session_configuration.approval_policy);
if sandbox_policy_changed {
let sandbox_state = SandboxState {
@@ -2997,12 +3004,14 @@ impl Session {
};
let cancel_token = self.reset_mcp_startup_cancellation_token().await;
let mut refreshed_manager = McpConnectionManager::default();
let mut refreshed_manager =
McpConnectionManager::new(&turn_context.config.permissions.approval_policy);
refreshed_manager
.initialize(
&mcp_servers,
store_mode,
auth_statuses,
&turn_context.config.permissions.approval_policy,
self.get_tx_event(),
cancel_token,
sandbox_state,
@@ -7265,7 +7274,9 @@ mod tests {
let file_watcher = Arc::new(FileWatcher::noop());
let services = SessionServices {
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::default())),
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new(
&config.permissions.approval_policy,
))),
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
unified_exec_manager: UnifiedExecProcessManager::default(),
analytics_events_client: AnalyticsEventsClient::new(
@@ -7413,7 +7424,9 @@ mod tests {
let file_watcher = Arc::new(FileWatcher::noop());
let services = SessionServices {
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::default())),
mcp_connection_manager: Arc::new(RwLock::new(McpConnectionManager::new(
&config.permissions.approval_policy,
))),
mcp_startup_cancellation_token: Mutex::new(CancellationToken::new()),
unified_exec_manager: UnifiedExecProcessManager::default(),
analytics_events_client: AnalyticsEventsClient::new(
+32 -4
View File
@@ -893,7 +893,7 @@ pub struct ConfigToml {
pub model_auto_compact_token_limit: Option<i64>,
/// Default approval policy for executing commands.
pub approval_policy: Option<AskForApproval>,
pub approval_policy: Option<AskForApprovalToml>,
#[serde(default)]
pub shell_environment_policy: ShellEnvironmentPolicyToml,
@@ -1113,7 +1113,7 @@ impl From<ConfigToml> for UserSavedConfig {
.collect();
Self {
approval_policy: config_toml.approval_policy,
approval_policy: config_toml.approval_policy.map(Into::into),
sandbox_mode: config_toml.sandbox_mode,
sandbox_settings: config_toml.sandbox_workspace_write.map(From::from),
forced_chatgpt_workspace_id: config_toml.forced_chatgpt_workspace_id,
@@ -1129,6 +1129,34 @@ impl From<ConfigToml> for UserSavedConfig {
}
}
/// Version of [AskForApproval] that is deserializable from config.toml.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum AskForApprovalToml {
#[serde(rename = "untrusted")]
UnlessTrusted,
OnFailure,
OnRequest,
Never,
}
impl AskForApprovalToml {
pub fn to_core(self) -> AskForApproval {
match self {
AskForApprovalToml::UnlessTrusted => AskForApproval::UnlessTrusted,
AskForApprovalToml::OnFailure => AskForApproval::OnFailure,
AskForApprovalToml::OnRequest => AskForApproval::OnRequest,
AskForApprovalToml::Never => AskForApproval::Never,
}
}
}
impl From<AskForApprovalToml> for AskForApproval {
fn from(value: AskForApprovalToml) -> Self {
value.to_core()
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct ProjectConfig {
@@ -1570,8 +1598,8 @@ impl Config {
|| config_profile.approval_policy.is_some()
|| cfg.approval_policy.is_some();
let mut approval_policy = approval_policy_override
.or(config_profile.approval_policy)
.or(cfg.approval_policy)
.or(config_profile.approval_policy.map(Into::into))
.or(cfg.approval_policy.map(Into::into))
.unwrap_or_else(|| {
if active_project.is_trusted() {
AskForApproval::OnRequest
+3 -3
View File
@@ -3,9 +3,9 @@ use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use super::AskForApprovalToml;
use crate::config::types::Personality;
use crate::config::types::WindowsToml;
use crate::protocol::AskForApproval;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::config_types::SandboxMode;
use codex_protocol::config_types::Verbosity;
@@ -21,7 +21,7 @@ pub struct ConfigProfile {
/// The key in the `model_providers` map identifying the
/// [`ModelProviderInfo`] to use.
pub model_provider: Option<String>,
pub approval_policy: Option<AskForApproval>,
pub approval_policy: Option<AskForApprovalToml>,
pub sandbox_mode: Option<SandboxMode>,
pub model_reasoning_effort: Option<ReasoningEffort>,
pub model_reasoning_summary: Option<ReasoningSummary>,
@@ -57,7 +57,7 @@ impl From<ConfigProfile> for codex_app_server_protocol::Profile {
Self {
model: config_profile.model,
model_provider: config_profile.model_provider,
approval_policy: config_profile.approval_policy,
approval_policy: config_profile.approval_policy.map(Into::into),
model_reasoning_effort: config_profile.model_reasoning_effort,
model_reasoning_summary: config_profile.model_reasoning_summary,
model_verbosity: config_profile.model_verbosity,
+2 -1
View File
@@ -91,7 +91,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_options(
let auth_status_entries =
compute_auth_statuses(mcp_servers.iter(), config.mcp_oauth_credentials_store_mode).await;
let mut mcp_connection_manager = McpConnectionManager::default();
let mut mcp_connection_manager = McpConnectionManager::new(&config.permissions.approval_policy);
let (tx_event, rx_event) = unbounded();
drop(rx_event);
let cancel_token = CancellationToken::new();
@@ -108,6 +108,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_options(
&mcp_servers,
config.mcp_oauth_credentials_store_mode,
auth_status_entries,
&config.permissions.approval_policy,
tx_event,
cancel_token.clone(),
sandbox_state,
+96 -2
View File
@@ -32,6 +32,10 @@ use shlex::try_join as shlex_try_join;
const PROMPT_CONFLICT_REASON: &str =
"approval required by policy, but AskForApproval is set to Never";
const REJECT_SANDBOX_APPROVAL_REASON: &str =
"approval required by policy, but AskForApproval::Reject.sandbox_approval is set";
const REJECT_RULES_APPROVAL_REASON: &str =
"approval required by policy rule, but AskForApproval::Reject.rules is set";
const RULES_DIR_NAME: &str = "rules";
const RULE_EXTENSION: &str = "rules";
const DEFAULT_POLICY_FILE: &str = "default.rules";
@@ -91,6 +95,32 @@ fn is_policy_match(rule_match: &RuleMatch) -> bool {
}
}
/// Returns a rejection reason when `approval_policy` disallows surfacing the
/// current prompt to the user.
///
/// `prompt_is_rule` distinguishes policy-rule prompts from sandbox/escalation
/// prompts so `Reject.rules` and `Reject.sandbox_approval` are honored
/// independently.
fn prompt_is_rejected_by_policy(
approval_policy: AskForApproval,
prompt_is_rule: bool,
) -> Option<&'static str> {
match approval_policy {
AskForApproval::Never => Some(PROMPT_CONFLICT_REASON),
AskForApproval::Reject(reject_config)
if prompt_is_rule && reject_config.rejects_rules_approval() =>
{
Some(REJECT_RULES_APPROVAL_REASON)
}
AskForApproval::Reject(reject_config)
if !prompt_is_rule && reject_config.rejects_sandbox_approval() =>
{
Some(REJECT_SANDBOX_APPROVAL_REASON)
}
_ => None,
}
}
#[derive(Debug, Error)]
pub enum ExecPolicyError {
#[error("failed to read rules files from {dir}: {source}")]
@@ -196,9 +226,13 @@ impl ExecPolicyManager {
reason: derive_forbidden_reason(command, &evaluation),
},
Decision::Prompt => {
if matches!(approval_policy, AskForApproval::Never) {
let prompt_is_rule = evaluation.matched_rules.iter().any(|rule_match| {
is_policy_match(rule_match) && rule_match.decision() == Decision::Prompt
});
if let Some(reason) = prompt_is_rejected_by_policy(approval_policy, prompt_is_rule)
{
ExecApprovalRequirement::Forbidden {
reason: PROMPT_CONFLICT_REASON.to_string(),
reason: reason.to_string(),
}
} else {
ExecApprovalRequirement::NeedsApproval {
@@ -465,6 +499,20 @@ pub fn render_decision_for_unmatched_command(
}
}
}
AskForApproval::Reject(_) => match sandbox_policy {
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => {
// Mirror on-request behavior for unmatched commands; prompt-vs-reject is handled
// by `prompt_is_rejected_by_policy`.
Decision::Allow
}
SandboxPolicy::ReadOnly { .. } | SandboxPolicy::WorkspaceWrite { .. } => {
if sandbox_permissions.requires_escalated_permissions() {
Decision::Prompt
} else {
Decision::Allow
}
}
},
}
}
@@ -1197,6 +1245,52 @@ prefix_rule(
);
}
#[test]
fn unmatched_reject_policy_still_prompts_for_restricted_sandbox_escalation() {
let command = vec!["madeup-cmd".to_string()];
assert_eq!(
Decision::Prompt,
render_decision_for_unmatched_command(
AskForApproval::Reject(RejectConfig {
sandbox_approval: false,
rules: false,
mcp_elicitations: false,
}),
&SandboxPolicy::new_read_only_policy(),
&command,
SandboxPermissions::RequireEscalated,
false,
)
);
}
#[tokio::test]
async fn exec_approval_requirement_rejects_unmatched_prompt_when_sandbox_rejection_enabled() {
let command = vec!["madeup-cmd".to_string()];
let requirement = ExecPolicyManager::default()
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
command: &command,
approval_policy: AskForApproval::Reject(RejectConfig {
sandbox_approval: true,
rules: false,
mcp_elicitations: false,
}),
sandbox_policy: &SandboxPolicy::new_read_only_policy(),
sandbox_permissions: SandboxPermissions::RequireEscalated,
prefix_rule: None,
})
.await;
assert_eq!(
requirement,
ExecApprovalRequirement::Forbidden {
reason: REJECT_SANDBOX_APPROVAL_REASON.to_string(),
}
);
}
#[tokio::test]
async fn exec_approval_requirement_falls_back_to_heuristics() {
let command = vec!["cargo".to_string(), "build".to_string()];
+2 -1
View File
@@ -191,7 +191,7 @@ pub async fn collect_mcp_snapshot(config: &Config) -> McpListToolsResponseEvent
let auth_status_entries =
compute_auth_statuses(mcp_servers.iter(), config.mcp_oauth_credentials_store_mode).await;
let mut mcp_connection_manager = McpConnectionManager::default();
let mut mcp_connection_manager = McpConnectionManager::new(&config.permissions.approval_policy);
let (tx_event, rx_event) = unbounded();
drop(rx_event);
let cancel_token = CancellationToken::new();
@@ -209,6 +209,7 @@ pub async fn collect_mcp_snapshot(config: &Config) -> McpListToolsResponseEvent
&mcp_servers,
config.mcp_oauth_credentials_store_mode,
auth_status_entries.clone(),
&config.permissions.approval_policy,
tx_event,
cancel_token.clone(),
sandbox_state,
+43 -3
View File
@@ -25,9 +25,11 @@ use anyhow::anyhow;
use async_channel::Sender;
use codex_async_utils::CancelErr;
use codex_async_utils::OrCancelExt;
use codex_config::Constrained;
use codex_protocol::approvals::ElicitationRequestEvent;
use codex_protocol::mcp::CallToolResult;
use codex_protocol::mcp::RequestId as ProtocolRequestId;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::Event;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::McpStartupCompleteEvent;
@@ -44,6 +46,7 @@ use futures::future::FutureExt;
use futures::future::Shared;
use rmcp::model::ClientCapabilities;
use rmcp::model::CreateElicitationRequestParams;
use rmcp::model::ElicitationAction;
use rmcp::model::ElicitationCapability;
use rmcp::model::FormElicitationCapability;
use rmcp::model::Implementation;
@@ -182,12 +185,20 @@ static CODEX_APPS_TOOLS_CACHE: LazyLock<StdMutex<Option<CachedCodexAppsTools>>>
type ResponderMap = HashMap<(String, RequestId), oneshot::Sender<ElicitationResponse>>;
#[derive(Clone, Default)]
#[derive(Clone)]
struct ElicitationRequestManager {
requests: Arc<Mutex<ResponderMap>>,
approval_policy: Arc<StdMutex<AskForApproval>>,
}
impl ElicitationRequestManager {
fn new(approval_policy: AskForApproval) -> Self {
Self {
requests: Arc::new(Mutex::new(HashMap::new())),
approval_policy: Arc::new(StdMutex::new(approval_policy)),
}
}
async fn resolve(
&self,
server_name: String,
@@ -205,11 +216,27 @@ impl ElicitationRequestManager {
fn make_sender(&self, server_name: String, tx_event: Sender<Event>) -> SendElicitation {
let elicitation_requests = self.requests.clone();
let approval_policy = self.approval_policy.clone();
Box::new(move |id, elicitation| {
let elicitation_requests = elicitation_requests.clone();
let tx_event = tx_event.clone();
let server_name = server_name.clone();
let approval_policy = approval_policy.clone();
async move {
if approval_policy.lock().is_ok_and(|policy| {
matches!(*policy, AskForApproval::Never)
|| matches!(
*policy,
AskForApproval::Reject(reject_config)
if reject_config.rejects_mcp_elicitations()
)
}) {
return Ok(ElicitationResponse {
action: ElicitationAction::Decline,
content: None,
});
}
let (tx, rx) = oneshot::channel();
{
let mut lock = elicitation_requests.lock().await;
@@ -346,22 +373,35 @@ pub struct SandboxState {
}
/// A thin wrapper around a set of running [`RmcpClient`] instances.
#[derive(Default)]
pub(crate) struct McpConnectionManager {
clients: HashMap<String, AsyncManagedClient>,
elicitation_requests: ElicitationRequestManager,
}
impl McpConnectionManager {
pub(crate) fn new(approval_policy: &Constrained<AskForApproval>) -> Self {
Self {
clients: HashMap::new(),
elicitation_requests: ElicitationRequestManager::new(approval_policy.value()),
}
}
pub(crate) fn has_servers(&self) -> bool {
!self.clients.is_empty()
}
pub fn set_approval_policy(&self, approval_policy: &Constrained<AskForApproval>) {
if let Ok(mut policy) = self.elicitation_requests.approval_policy.lock() {
*policy = approval_policy.value();
}
}
pub async fn initialize(
&mut self,
mcp_servers: &HashMap<String, McpServerConfig>,
store_mode: OAuthCredentialsStoreMode,
auth_entries: HashMap<String, McpAuthStatusEntry>,
approval_policy: &Constrained<AskForApproval>,
tx_event: Sender<Event>,
cancel_token: CancellationToken,
initial_sandbox_state: SandboxState,
@@ -371,7 +411,7 @@ impl McpConnectionManager {
}
let mut clients = HashMap::new();
let mut join_set = JoinSet::new();
let elicitation_requests = ElicitationRequestManager::default();
let elicitation_requests = ElicitationRequestManager::new(approval_policy.value());
let mcp_servers = mcp_servers.clone();
for (server_name, cfg) in mcp_servers.into_iter().filter(|(_, cfg)| cfg.enabled) {
let cancel_token = cancel_token.child_token();
+26 -4
View File
@@ -38,7 +38,10 @@ pub fn assess_patch_safety(
}
match policy {
AskForApproval::OnFailure | AskForApproval::Never | AskForApproval::OnRequest => {
AskForApproval::OnFailure
| AskForApproval::Never
| AskForApproval::OnRequest
| AskForApproval::Reject(_) => {
// Continue to see if this can be auto-approved.
}
// TODO(ragona): I'm not sure this is actually correct? I believe in this case
@@ -48,11 +51,20 @@ pub fn assess_patch_safety(
}
}
let rejects_sandbox_approval = matches!(policy, AskForApproval::Never)
|| matches!(
policy,
AskForApproval::Reject(reject_config) if reject_config.sandbox_approval
);
// Even though the patch appears to be constrained to writable paths, it is
// possible that paths in the patch are hard links to files outside the
// writable roots, so we should still run `apply_patch` in a sandbox in that case.
if is_write_patch_constrained_to_writable_paths(action, sandbox_policy, cwd)
|| policy == AskForApproval::OnFailure
|| matches!(
policy,
AskForApproval::OnFailure | AskForApproval::Reject(_)
)
{
if matches!(
sandbox_policy,
@@ -72,10 +84,20 @@ pub fn assess_patch_safety(
sandbox_type,
user_explicitly_approved: false,
},
None => SafetyCheck::AskUser,
None => {
if rejects_sandbox_approval {
SafetyCheck::Reject {
reason:
"writing outside of the project; rejected by user approval settings"
.to_string(),
}
} else {
SafetyCheck::AskUser
}
}
}
}
} else if policy == AskForApproval::Never {
} else if rejects_sandbox_approval {
SafetyCheck::Reject {
reason: "writing outside of the project; rejected by user approval settings"
.to_string(),
+66 -3
View File
@@ -156,6 +156,8 @@ impl ExecApprovalRequirement {
/// - Never, OnFailure: do not ask
/// - OnRequest: ask unless sandbox policy is DangerFullAccess
/// - Reject: ask unless sandbox policy is DangerFullAccess, but auto-reject
/// when `sandbox_approval` rejection is enabled.
/// - UnlessTrusted: always ask
pub(crate) fn default_exec_approval_requirement(
policy: AskForApproval,
@@ -163,14 +165,23 @@ pub(crate) fn default_exec_approval_requirement(
) -> ExecApprovalRequirement {
let needs_approval = match policy {
AskForApproval::Never | AskForApproval::OnFailure => false,
AskForApproval::OnRequest => !matches!(
AskForApproval::OnRequest | AskForApproval::Reject(_) => !matches!(
sandbox_policy,
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. }
),
AskForApproval::UnlessTrusted => true,
};
if needs_approval {
if needs_approval
&& matches!(
policy,
AskForApproval::Reject(reject_config) if reject_config.rejects_sandbox_approval()
)
{
ExecApprovalRequirement::Forbidden {
reason: "approval policy rejected sandbox approval prompt".to_string(),
}
} else if needs_approval {
ExecApprovalRequirement::NeedsApproval {
reason: None,
proposed_execpolicy_amendment: None,
@@ -183,6 +194,52 @@ pub(crate) fn default_exec_approval_requirement(
}
}
#[cfg(test)]
mod tests {
use super::*;
use codex_protocol::protocol::RejectConfig;
use pretty_assertions::assert_eq;
#[test]
fn default_exec_approval_requirement_rejects_sandbox_prompt_when_configured() {
let policy = AskForApproval::Reject(RejectConfig {
sandbox_approval: true,
rules: false,
mcp_elicitations: false,
});
let requirement =
default_exec_approval_requirement(policy, &SandboxPolicy::new_read_only_policy());
assert_eq!(
requirement,
ExecApprovalRequirement::Forbidden {
reason: "approval policy rejected sandbox approval prompt".to_string(),
}
);
}
#[test]
fn default_exec_approval_requirement_keeps_prompt_when_sandbox_rejection_is_disabled() {
let policy = AskForApproval::Reject(RejectConfig {
sandbox_approval: false,
rules: true,
mcp_elicitations: true,
});
let requirement =
default_exec_approval_requirement(policy, &SandboxPolicy::new_read_only_policy());
assert_eq!(
requirement,
ExecApprovalRequirement::NeedsApproval {
reason: None,
proposed_execpolicy_amendment: None,
}
);
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum SandboxOverride {
NoOverride,
@@ -224,7 +281,13 @@ pub(crate) trait Approvable<Req> {
/// Decide we can request an approval for no-sandbox execution.
fn wants_no_sandbox_approval(&self, policy: AskForApproval) -> bool {
!matches!(policy, AskForApproval::Never | AskForApproval::OnRequest)
match policy {
AskForApproval::OnFailure => true,
AskForApproval::UnlessTrusted => true,
AskForApproval::Never => false,
AskForApproval::OnRequest => false,
AskForApproval::Reject(reject_config) => !reject_config.sandbox_approval,
}
}
fn start_approval_async<'a>(
+22
View File
@@ -255,6 +255,28 @@ impl DeveloperInstructions {
None => APPROVAL_POLICY_ON_REQUEST_RULE.to_string(),
}
}
AskForApproval::Reject(reject_config) => {
let command_prefixes = format_allow_prefixes(exec_policy.get_allowed_prefixes());
let on_request_instructions = match command_prefixes {
Some(prefixes) => {
format!(
"{APPROVAL_POLICY_ON_REQUEST_RULE}\n## Approved command prefixes\nThe following prefix rules have already been approved: {prefixes}"
)
}
None => APPROVAL_POLICY_ON_REQUEST_RULE.to_string(),
};
let sandbox_approval = reject_config.sandbox_approval;
let rules = reject_config.rules;
let mcp_elicitations = reject_config.mcp_elicitations;
format!(
"{on_request_instructions}\n\n\
Approval policy is `reject`.\n\
- `sandbox_approval`: {sandbox_approval}\n\
- `rules`: {rules}\n\
- `mcp_elicitations`: {mcp_elicitations}\n\
When a category is `true`, requests in that category are auto-rejected instead of prompting the user."
)
}
};
DeveloperInstructions::new(text)
+50
View File
@@ -377,11 +377,41 @@ pub enum AskForApproval {
#[default]
OnRequest,
/// Fine-grained rejection controls for approval prompts.
///
/// When a field is `true`, prompts of that category are automatically
/// rejected instead of shown to the user.
Reject(RejectConfig),
/// Never ask the user to approve commands. Failures are immediately returned
/// to the model, and never escalated to the user for approval.
Never,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, TS)]
pub struct RejectConfig {
/// Reject approval prompts related to sandbox escalation.
pub sandbox_approval: bool,
/// Reject prompts triggered by execpolicy `prompt` rules.
pub rules: bool,
/// Reject MCP elicitation prompts.
pub mcp_elicitations: bool,
}
impl RejectConfig {
pub const fn rejects_sandbox_approval(self) -> bool {
self.sandbox_approval
}
pub const fn rejects_rules_approval(self) -> bool {
self.rules
}
pub const fn rejects_mcp_elicitations(self) -> bool {
self.mcp_elicitations
}
}
/// Represents whether outbound network access is available to the agent.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Display, Default, JsonSchema, TS,
@@ -2831,6 +2861,26 @@ mod tests {
assert!(enabled.has_full_network_access());
}
#[test]
fn reject_config_mcp_elicitation_flag_is_field_driven() {
assert!(
RejectConfig {
sandbox_approval: false,
rules: false,
mcp_elicitations: true,
}
.rejects_mcp_elicitations()
);
assert!(
!RejectConfig {
sandbox_approval: false,
rules: false,
mcp_elicitations: false,
}
.rejects_mcp_elicitations()
);
}
#[test]
fn workspace_write_restricted_read_access_includes_effective_writable_roots() {
let cwd = if cfg!(windows) {