mirror of
https://github.com/openai/codex.git
synced 2026-05-06 04:17:03 +00:00
Compare commits
1 Commits
starr/exec
...
codex/appr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
219d524c04 |
@@ -5780,6 +5780,32 @@
|
||||
"AppToolsConfig": {
|
||||
"type": "object"
|
||||
},
|
||||
"ApprovalPolicyConstraint": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"untrusted",
|
||||
"on-failure",
|
||||
"on-request",
|
||||
"never"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"granular": {
|
||||
"$ref": "#/definitions/v2/GranularApprovalConstraint"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"granular"
|
||||
],
|
||||
"title": "GranularApprovalPolicyConstraint",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ApprovalsReviewer": {
|
||||
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
|
||||
"enum": [
|
||||
@@ -7211,7 +7237,7 @@
|
||||
"properties": {
|
||||
"allowedApprovalPolicies": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/AskForApproval"
|
||||
"$ref": "#/definitions/v2/ApprovalPolicyConstraint"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -9196,6 +9222,49 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"GranularApprovalConfig": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"request_permissions": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rules": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sandbox_approval": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"skill_approval": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"mcp_elicitations",
|
||||
"rules",
|
||||
"sandbox_approval"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GranularApprovalConstraint": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/GranularApprovalWildcard"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/v2/GranularApprovalConfig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"GranularApprovalWildcard": {
|
||||
"enum": [
|
||||
"any"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"GuardianApprovalReview": {
|
||||
"description": "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.",
|
||||
"properties": {
|
||||
|
||||
@@ -606,6 +606,32 @@
|
||||
"AppToolsConfig": {
|
||||
"type": "object"
|
||||
},
|
||||
"ApprovalPolicyConstraint": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"untrusted",
|
||||
"on-failure",
|
||||
"on-request",
|
||||
"never"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"granular": {
|
||||
"$ref": "#/definitions/GranularApprovalConstraint"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"granular"
|
||||
],
|
||||
"title": "GranularApprovalPolicyConstraint",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ApprovalsReviewer": {
|
||||
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
|
||||
"enum": [
|
||||
@@ -3771,7 +3797,7 @@
|
||||
"properties": {
|
||||
"allowedApprovalPolicies": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AskForApproval"
|
||||
"$ref": "#/definitions/ApprovalPolicyConstraint"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -5867,6 +5893,49 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"GranularApprovalConfig": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"request_permissions": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rules": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sandbox_approval": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"skill_approval": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"mcp_elicitations",
|
||||
"rules",
|
||||
"sandbox_approval"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GranularApprovalConstraint": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/GranularApprovalWildcard"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/GranularApprovalConfig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"GranularApprovalWildcard": {
|
||||
"enum": [
|
||||
"any"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"GuardianApprovalReview": {
|
||||
"description": "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.",
|
||||
"properties": {
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"ApprovalsReviewer": {
|
||||
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
|
||||
"enum": [
|
||||
"user",
|
||||
"auto_review",
|
||||
"guardian_subagent"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AskForApproval": {
|
||||
"ApprovalPolicyConstraint": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
@@ -25,46 +16,31 @@
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"granular": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"request_permissions": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rules": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sandbox_approval": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"skill_approval": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"mcp_elicitations",
|
||||
"rules",
|
||||
"sandbox_approval"
|
||||
],
|
||||
"type": "object"
|
||||
"$ref": "#/definitions/GranularApprovalConstraint"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"granular"
|
||||
],
|
||||
"title": "GranularAskForApproval",
|
||||
"title": "GranularApprovalPolicyConstraint",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ApprovalsReviewer": {
|
||||
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
|
||||
"enum": [
|
||||
"user",
|
||||
"auto_review",
|
||||
"guardian_subagent"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ConfigRequirements": {
|
||||
"properties": {
|
||||
"allowedApprovalPolicies": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AskForApproval"
|
||||
"$ref": "#/definitions/ApprovalPolicyConstraint"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -205,6 +181,49 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GranularApprovalConfig": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"request_permissions": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rules": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sandbox_approval": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"skill_approval": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"mcp_elicitations",
|
||||
"rules",
|
||||
"sandbox_approval"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GranularApprovalConstraint": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/GranularApprovalWildcard"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/GranularApprovalConfig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"GranularApprovalWildcard": {
|
||||
"enum": [
|
||||
"any"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ManagedHooksRequirements": {
|
||||
"properties": {
|
||||
"PermissionRequest": {
|
||||
|
||||
6
codex-rs/app-server-protocol/schema/typescript/v2/ApprovalPolicyConstraint.ts
generated
Normal file
6
codex-rs/app-server-protocol/schema/typescript/v2/ApprovalPolicyConstraint.ts
generated
Normal file
@@ -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 { GranularApprovalConstraint } from "./GranularApprovalConstraint";
|
||||
|
||||
export type ApprovalPolicyConstraint = "untrusted" | "on-failure" | "on-request" | { "granular": GranularApprovalConstraint } | "never";
|
||||
@@ -2,8 +2,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 { ApprovalPolicyConstraint } from "./ApprovalPolicyConstraint";
|
||||
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<ApprovalPolicyConstraint> | null, allowedSandboxModes: Array<SandboxMode> | null, allowedWebSearchModes: Array<WebSearchMode> | null, featureRequirements: { [key in string]?: boolean } | null, enforceResidency: ResidencyRequirement | null};
|
||||
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/GranularApprovalConfig.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/GranularApprovalConfig.ts
generated
Normal file
@@ -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 GranularApprovalConfig = { sandbox_approval: boolean, rules: boolean, skill_approval: boolean, request_permissions: boolean, mcp_elicitations: boolean, };
|
||||
7
codex-rs/app-server-protocol/schema/typescript/v2/GranularApprovalConstraint.ts
generated
Normal file
7
codex-rs/app-server-protocol/schema/typescript/v2/GranularApprovalConstraint.ts
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
// 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 { GranularApprovalConfig } from "./GranularApprovalConfig";
|
||||
import type { GranularApprovalWildcard } from "./GranularApprovalWildcard";
|
||||
|
||||
export type GranularApprovalConstraint = GranularApprovalWildcard | GranularApprovalConfig;
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/GranularApprovalWildcard.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/GranularApprovalWildcard.ts
generated
Normal file
@@ -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 GranularApprovalWildcard = "any";
|
||||
@@ -20,6 +20,7 @@ export type { AppScreenshot } from "./AppScreenshot";
|
||||
export type { AppSummary } from "./AppSummary";
|
||||
export type { AppToolApproval } from "./AppToolApproval";
|
||||
export type { AppToolsConfig } from "./AppToolsConfig";
|
||||
export type { ApprovalPolicyConstraint } from "./ApprovalPolicyConstraint";
|
||||
export type { ApprovalsReviewer } from "./ApprovalsReviewer";
|
||||
export type { AppsConfig } from "./AppsConfig";
|
||||
export type { AppsDefaultConfig } from "./AppsDefaultConfig";
|
||||
@@ -143,6 +144,9 @@ export type { GetAccountRateLimitsResponse } from "./GetAccountRateLimitsRespons
|
||||
export type { GetAccountResponse } from "./GetAccountResponse";
|
||||
export type { GitInfo } from "./GitInfo";
|
||||
export type { GrantedPermissionProfile } from "./GrantedPermissionProfile";
|
||||
export type { GranularApprovalConfig } from "./GranularApprovalConfig";
|
||||
export type { GranularApprovalConstraint } from "./GranularApprovalConstraint";
|
||||
export type { GranularApprovalWildcard } from "./GranularApprovalWildcard";
|
||||
export type { GuardianApprovalReview } from "./GuardianApprovalReview";
|
||||
export type { GuardianApprovalReviewAction } from "./GuardianApprovalReviewAction";
|
||||
export type { GuardianApprovalReviewStatus } from "./GuardianApprovalReviewStatus";
|
||||
|
||||
@@ -270,6 +270,50 @@ pub enum AskForApproval {
|
||||
Never,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS, ExperimentalApi,
|
||||
)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[ts(rename_all = "kebab-case", export_to = "v2/")]
|
||||
pub enum ApprovalPolicyConstraint {
|
||||
#[serde(rename = "untrusted")]
|
||||
#[ts(rename = "untrusted")]
|
||||
UnlessTrusted,
|
||||
OnFailure,
|
||||
OnRequest,
|
||||
#[experimental("askForApproval.granular")]
|
||||
Granular(GranularApprovalConstraint),
|
||||
Never,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(untagged)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum GranularApprovalConstraint {
|
||||
Any(GranularApprovalWildcard),
|
||||
Exact(GranularApprovalConfig),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[ts(rename_all = "kebab-case", export_to = "v2/")]
|
||||
pub enum GranularApprovalWildcard {
|
||||
Any,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(rename_all = "snake_case", export_to = "v2/")]
|
||||
pub struct GranularApprovalConfig {
|
||||
pub sandbox_approval: bool,
|
||||
pub rules: bool,
|
||||
#[serde(default)]
|
||||
pub skill_approval: bool,
|
||||
#[serde(default)]
|
||||
pub request_permissions: bool,
|
||||
pub mcp_elicitations: bool,
|
||||
}
|
||||
|
||||
impl AskForApproval {
|
||||
pub fn to_core(self) -> CoreAskForApproval {
|
||||
match self {
|
||||
@@ -944,7 +988,7 @@ pub struct ConfigReadResponse {
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ConfigRequirements {
|
||||
#[experimental(nested)]
|
||||
pub allowed_approval_policies: Option<Vec<AskForApproval>>,
|
||||
pub allowed_approval_policies: Option<Vec<ApprovalPolicyConstraint>>,
|
||||
#[experimental("configRequirements/read.allowedApprovalsReviewers")]
|
||||
pub allowed_approvals_reviewers: Option<Vec<ApprovalsReviewer>>,
|
||||
pub allowed_sandbox_modes: Option<Vec<SandboxMode>>,
|
||||
@@ -9011,13 +9055,15 @@ mod tests {
|
||||
fn config_requirements_granular_allowed_approval_policy_is_marked_experimental() {
|
||||
let reason =
|
||||
crate::experimental_api::ExperimentalApi::experimental_reason(&ConfigRequirements {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Granular {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
}]),
|
||||
allowed_approval_policies: Some(vec![ApprovalPolicyConstraint::Granular(
|
||||
GranularApprovalConstraint::Exact(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: false,
|
||||
}),
|
||||
)]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
allowed_web_search_modes: None,
|
||||
@@ -9030,6 +9076,31 @@ mod tests {
|
||||
assert_eq!(reason, Some("askForApproval.granular"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approval_policy_constraint_any_granular_uses_granular_any_wire_shape() {
|
||||
let value = serde_json::to_value(ApprovalPolicyConstraint::Granular(
|
||||
GranularApprovalConstraint::Any(GranularApprovalWildcard::Any),
|
||||
))
|
||||
.expect("approval policy constraint should serialize");
|
||||
|
||||
assert_eq!(
|
||||
value,
|
||||
serde_json::json!({
|
||||
"granular": "any",
|
||||
})
|
||||
);
|
||||
|
||||
let decoded: ApprovalPolicyConstraint =
|
||||
serde_json::from_value(value).expect("approval policy constraint should deserialize");
|
||||
|
||||
assert_eq!(
|
||||
decoded,
|
||||
ApprovalPolicyConstraint::Granular(GranularApprovalConstraint::Any(
|
||||
GranularApprovalWildcard::Any
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_request_thread_start_granular_approval_policy_is_marked_experimental() {
|
||||
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(
|
||||
|
||||
@@ -216,7 +216,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 `details` returned by detect. When a request includes plugin imports, the server emits `externalAgentConfig/import/completed` after the full import finishes (immediately after the response when everything completed synchronously, or after background remote 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`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`. `allowedApprovalPolicies` entries mirror concrete approval policies and may also include `{ "granular": "any" }` to allow any granular approval-policy shape.
|
||||
|
||||
### Example: Start or resume a thread
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ use codex_app_server_protocol::ConfiguredHookHandler;
|
||||
use codex_app_server_protocol::ConfiguredHookMatcherGroup;
|
||||
use codex_app_server_protocol::ExperimentalFeatureEnablementSetParams;
|
||||
use codex_app_server_protocol::ExperimentalFeatureEnablementSetResponse;
|
||||
use codex_app_server_protocol::GranularApprovalConfig;
|
||||
use codex_app_server_protocol::GranularApprovalConstraint;
|
||||
use codex_app_server_protocol::GranularApprovalWildcard;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::ManagedHooksRequirements;
|
||||
use codex_app_server_protocol::NetworkDomainPermission;
|
||||
@@ -24,7 +27,9 @@ use codex_app_server_protocol::NetworkUnixSocketPermission;
|
||||
use codex_app_server_protocol::SandboxMode;
|
||||
use codex_core::ThreadManager;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config_loader::ApprovalPolicyConstraint as CoreApprovalPolicyConstraint;
|
||||
use codex_core::config_loader::ConfigRequirementsToml;
|
||||
use codex_core::config_loader::GranularApprovalConstraint as CoreGranularApprovalConstraint;
|
||||
use codex_core::config_loader::HookEventsToml;
|
||||
use codex_core::config_loader::HookHandlerConfig as CoreHookHandlerConfig;
|
||||
use codex_core::config_loader::ManagedHooksRequirementsToml;
|
||||
@@ -269,7 +274,7 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
|
||||
allowed_approval_policies: requirements.allowed_approval_policies.map(|policies| {
|
||||
policies
|
||||
.into_iter()
|
||||
.map(codex_app_server_protocol::AskForApproval::from)
|
||||
.map(map_approval_policy_constraint_to_api)
|
||||
.collect()
|
||||
}),
|
||||
allowed_approvals_reviewers: requirements.allowed_approvals_reviewers.map(|reviewers| {
|
||||
@@ -305,6 +310,49 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR
|
||||
}
|
||||
}
|
||||
|
||||
fn map_approval_policy_constraint_to_api(
|
||||
constraint: CoreApprovalPolicyConstraint,
|
||||
) -> codex_app_server_protocol::ApprovalPolicyConstraint {
|
||||
match constraint {
|
||||
CoreApprovalPolicyConstraint::UnlessTrusted => {
|
||||
codex_app_server_protocol::ApprovalPolicyConstraint::UnlessTrusted
|
||||
}
|
||||
CoreApprovalPolicyConstraint::OnFailure => {
|
||||
codex_app_server_protocol::ApprovalPolicyConstraint::OnFailure
|
||||
}
|
||||
CoreApprovalPolicyConstraint::OnRequest => {
|
||||
codex_app_server_protocol::ApprovalPolicyConstraint::OnRequest
|
||||
}
|
||||
CoreApprovalPolicyConstraint::Granular(granular) => {
|
||||
codex_app_server_protocol::ApprovalPolicyConstraint::Granular(
|
||||
map_granular_approval_constraint_to_api(granular),
|
||||
)
|
||||
}
|
||||
CoreApprovalPolicyConstraint::Never => {
|
||||
codex_app_server_protocol::ApprovalPolicyConstraint::Never
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn map_granular_approval_constraint_to_api(
|
||||
constraint: CoreGranularApprovalConstraint,
|
||||
) -> GranularApprovalConstraint {
|
||||
match constraint {
|
||||
CoreGranularApprovalConstraint::Any => {
|
||||
GranularApprovalConstraint::Any(GranularApprovalWildcard::Any)
|
||||
}
|
||||
CoreGranularApprovalConstraint::Exact(config) => {
|
||||
GranularApprovalConstraint::Exact(GranularApprovalConfig {
|
||||
sandbox_approval: config.sandbox_approval,
|
||||
rules: config.rules,
|
||||
skill_approval: config.skill_approval,
|
||||
request_permissions: config.request_permissions,
|
||||
mcp_elicitations: config.mcp_elicitations,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn map_hooks_requirements_to_api(hooks: ManagedHooksRequirementsToml) -> ManagedHooksRequirements {
|
||||
let ManagedHooksRequirementsToml {
|
||||
managed_dir,
|
||||
@@ -527,8 +575,8 @@ mod tests {
|
||||
fn map_requirements_toml_to_api_converts_core_enums() {
|
||||
let requirements = ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![
|
||||
CoreAskForApproval::Never,
|
||||
CoreAskForApproval::OnRequest,
|
||||
CoreAskForApproval::Never.into(),
|
||||
CoreAskForApproval::OnRequest.into(),
|
||||
]),
|
||||
allowed_approvals_reviewers: Some(vec![
|
||||
CoreApprovalsReviewer::User,
|
||||
@@ -605,8 +653,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
mapped.allowed_approval_policies,
|
||||
Some(vec![
|
||||
codex_app_server_protocol::AskForApproval::Never,
|
||||
codex_app_server_protocol::AskForApproval::OnRequest,
|
||||
codex_app_server_protocol::ApprovalPolicyConstraint::Never,
|
||||
codex_app_server_protocol::ApprovalPolicyConstraint::OnRequest,
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -682,6 +730,28 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_requirements_toml_to_api_preserves_any_granular_approval_constraint() {
|
||||
let requirements: ConfigRequirementsToml = toml::from_str(
|
||||
r#"
|
||||
allowed_approval_policies = ["on-request", { granular = "any" }]
|
||||
"#,
|
||||
)
|
||||
.expect("requirements should parse");
|
||||
|
||||
let mapped = map_requirements_toml_to_api(requirements);
|
||||
|
||||
assert_eq!(
|
||||
mapped.allowed_approval_policies,
|
||||
Some(vec![
|
||||
codex_app_server_protocol::ApprovalPolicyConstraint::OnRequest,
|
||||
codex_app_server_protocol::ApprovalPolicyConstraint::Granular(
|
||||
GranularApprovalConstraint::Any(GranularApprovalWildcard::Any)
|
||||
),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_requirements_toml_to_api_omits_unix_socket_none_entries_from_legacy_network_fields() {
|
||||
let requirements = ConfigRequirementsToml {
|
||||
|
||||
@@ -1172,7 +1172,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
service.fetch().await,
|
||||
Ok(Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -1204,7 +1204,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
service.fetch().await,
|
||||
Ok(Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -1236,7 +1236,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
service.fetch().await,
|
||||
Ok(Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -1285,7 +1285,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -1370,7 +1370,7 @@ enabled = false
|
||||
assert_eq!(
|
||||
handle.await.expect("cloud requirements task"),
|
||||
Ok(Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -1446,7 +1446,7 @@ enabled = false
|
||||
assert_eq!(
|
||||
service.fetch().await,
|
||||
Ok(Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -1520,7 +1520,7 @@ enabled = false
|
||||
assert_eq!(
|
||||
service.fetch().await,
|
||||
Ok(Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -1721,7 +1721,7 @@ enabled = false
|
||||
assert_eq!(
|
||||
service.fetch().await,
|
||||
Ok(Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -1759,7 +1759,7 @@ enabled = false
|
||||
assert_eq!(
|
||||
service.fetch().await,
|
||||
Ok(Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -1817,7 +1817,7 @@ enabled = false
|
||||
assert_eq!(
|
||||
service.fetch().await,
|
||||
Ok(Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::OnRequest]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::OnRequest.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -1870,7 +1870,7 @@ enabled = false
|
||||
assert_eq!(
|
||||
service.fetch().await,
|
||||
Ok(Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::OnRequest]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::OnRequest.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -1927,7 +1927,7 @@ enabled = false
|
||||
assert_eq!(
|
||||
service.fetch().await,
|
||||
Ok(Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -1985,7 +1985,7 @@ enabled = false
|
||||
assert_eq!(
|
||||
service.fetch().await,
|
||||
Ok(Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -2043,7 +2043,7 @@ enabled = false
|
||||
.as_deref()
|
||||
.and_then(|contents| parse_cloud_requirements(contents).ok().flatten()),
|
||||
Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -2131,7 +2131,7 @@ enabled = false
|
||||
assert_eq!(
|
||||
service.fetch().await,
|
||||
Ok(Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -2161,7 +2161,7 @@ enabled = false
|
||||
.as_deref()
|
||||
.and_then(|contents| parse_cloud_requirements(contents).ok().flatten()),
|
||||
Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::OnRequest]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::OnRequest.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
|
||||
@@ -2,6 +2,7 @@ use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use serde::Deserialize;
|
||||
@@ -624,7 +625,7 @@ pub(crate) fn merge_enablement_settings_descending(
|
||||
/// Base config deserialized from system `requirements.toml` or MDM.
|
||||
#[derive(Deserialize, Debug, Clone, Default, PartialEq)]
|
||||
pub struct ConfigRequirementsToml {
|
||||
pub allowed_approval_policies: Option<Vec<AskForApproval>>,
|
||||
pub allowed_approval_policies: Option<Vec<ApprovalPolicyConstraint>>,
|
||||
pub allowed_approvals_reviewers: Option<Vec<ApprovalsReviewer>>,
|
||||
pub allowed_sandbox_modes: Option<Vec<SandboxModeRequirement>>,
|
||||
pub remote_sandbox_config: Option<Vec<RemoteSandboxConfigToml>>,
|
||||
@@ -642,6 +643,118 @@ pub struct ConfigRequirementsToml {
|
||||
pub guardian_policy_config: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum ApprovalPolicyConstraint {
|
||||
#[serde(rename = "untrusted")]
|
||||
UnlessTrusted,
|
||||
OnFailure,
|
||||
OnRequest,
|
||||
Granular(GranularApprovalConstraint),
|
||||
Never,
|
||||
}
|
||||
|
||||
impl ApprovalPolicyConstraint {
|
||||
pub fn initial_policy(self) -> AskForApproval {
|
||||
match self {
|
||||
Self::UnlessTrusted => AskForApproval::UnlessTrusted,
|
||||
Self::OnFailure => AskForApproval::OnFailure,
|
||||
Self::OnRequest => AskForApproval::OnRequest,
|
||||
Self::Granular(GranularApprovalConstraint::Any) => {
|
||||
AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
})
|
||||
}
|
||||
Self::Granular(GranularApprovalConstraint::Exact(config)) => {
|
||||
AskForApproval::Granular(config)
|
||||
}
|
||||
Self::Never => AskForApproval::Never,
|
||||
}
|
||||
}
|
||||
|
||||
fn matches(self, candidate: AskForApproval) -> bool {
|
||||
match self {
|
||||
Self::Granular(GranularApprovalConstraint::Any) => {
|
||||
matches!(candidate, AskForApproval::Granular(_))
|
||||
}
|
||||
Self::Granular(GranularApprovalConstraint::Exact(config)) => {
|
||||
matches!(candidate, AskForApproval::Granular(candidate_config) if candidate_config == config)
|
||||
}
|
||||
_ => candidate == self.initial_policy(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AskForApproval> for ApprovalPolicyConstraint {
|
||||
fn from(value: AskForApproval) -> Self {
|
||||
match value {
|
||||
AskForApproval::UnlessTrusted => Self::UnlessTrusted,
|
||||
AskForApproval::OnFailure => Self::OnFailure,
|
||||
AskForApproval::OnRequest => Self::OnRequest,
|
||||
AskForApproval::Granular(config) => {
|
||||
Self::Granular(GranularApprovalConstraint::Exact(config))
|
||||
}
|
||||
AskForApproval::Never => Self::Never,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ApprovalPolicyConstraint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::UnlessTrusted => write!(f, "untrusted"),
|
||||
Self::OnFailure => write!(f, "on-failure"),
|
||||
Self::OnRequest => write!(f, "on-request"),
|
||||
Self::Granular(granular) => write!(f, "granular:{granular}"),
|
||||
Self::Never => write!(f, "never"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum GranularApprovalConstraint {
|
||||
Any,
|
||||
Exact(GranularApprovalConfig),
|
||||
}
|
||||
|
||||
impl fmt::Display for GranularApprovalConstraint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Any => write!(f, "any"),
|
||||
Self::Exact(_) => write!(f, "exact"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for GranularApprovalConstraint {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum Wire {
|
||||
Any(GranularApprovalWildcard),
|
||||
Exact(GranularApprovalConfig),
|
||||
}
|
||||
|
||||
match Wire::deserialize(deserializer)? {
|
||||
Wire::Any(GranularApprovalWildcard::Any) => Ok(Self::Any),
|
||||
Wire::Exact(config) => Ok(Self::Exact(config)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
enum GranularApprovalWildcard {
|
||||
Any,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct RemoteSandboxConfigToml {
|
||||
pub hostname_patterns: Vec<String>,
|
||||
@@ -672,7 +785,7 @@ impl<T> std::ops::Deref for Sourced<T> {
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct ConfigRequirementsWithSources {
|
||||
pub allowed_approval_policies: Option<Sourced<Vec<AskForApproval>>>,
|
||||
pub allowed_approval_policies: Option<Sourced<Vec<ApprovalPolicyConstraint>>>,
|
||||
pub allowed_approvals_reviewers: Option<Sourced<Vec<ApprovalsReviewer>>>,
|
||||
pub allowed_sandbox_modes: Option<Sourced<Vec<SandboxModeRequirement>>>,
|
||||
pub allowed_web_search_modes: Option<Sourced<Vec<WebSearchModeRequirement>>>,
|
||||
@@ -912,13 +1025,19 @@ impl TryFrom<ConfigRequirementsWithSources> for ConfigRequirements {
|
||||
value: policies,
|
||||
source: requirement_source,
|
||||
}) => {
|
||||
let Some(initial_value) = policies.first().copied() else {
|
||||
let Some(initial_value) = policies
|
||||
.first()
|
||||
.map(|requirement| requirement.initial_policy())
|
||||
else {
|
||||
return Err(ConstraintError::empty_field("allowed_approval_policies"));
|
||||
};
|
||||
|
||||
let requirement_source_for_error = requirement_source.clone();
|
||||
let constrained = Constrained::new(initial_value, move |candidate| {
|
||||
if policies.contains(candidate) {
|
||||
if policies
|
||||
.iter()
|
||||
.any(|requirement| requirement.matches(*candidate))
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ConstraintError::InvalidValue {
|
||||
@@ -1229,7 +1348,10 @@ mod tests {
|
||||
let mut target = ConfigRequirementsWithSources::default();
|
||||
let source = RequirementSource::LegacyManagedConfigTomlFromMdm;
|
||||
|
||||
let allowed_approval_policies = vec![AskForApproval::UnlessTrusted, AskForApproval::Never];
|
||||
let allowed_approval_policies = vec![
|
||||
ApprovalPolicyConstraint::from(AskForApproval::UnlessTrusted),
|
||||
ApprovalPolicyConstraint::from(AskForApproval::Never),
|
||||
];
|
||||
let allowed_approvals_reviewers =
|
||||
vec![ApprovalsReviewer::AutoReview, ApprovalsReviewer::User];
|
||||
let allowed_sandbox_modes = vec![
|
||||
@@ -1319,7 +1441,7 @@ mod tests {
|
||||
empty_target,
|
||||
ConfigRequirementsWithSources {
|
||||
allowed_approval_policies: Some(Sourced::new(
|
||||
vec![AskForApproval::OnRequest],
|
||||
vec![ApprovalPolicyConstraint::from(AskForApproval::OnRequest)],
|
||||
source_location,
|
||||
)),
|
||||
allowed_approvals_reviewers: None,
|
||||
@@ -1365,7 +1487,7 @@ mod tests {
|
||||
populated_target,
|
||||
ConfigRequirementsWithSources {
|
||||
allowed_approval_policies: Some(Sourced::new(
|
||||
vec![AskForApproval::Never],
|
||||
vec![ApprovalPolicyConstraint::from(AskForApproval::Never)],
|
||||
existing_source,
|
||||
)),
|
||||
allowed_approvals_reviewers: None,
|
||||
@@ -1877,6 +1999,100 @@ allowed_approvals_reviewers = ["user"]
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allowed_approval_policies_support_any_granular_requirement() -> Result<()> {
|
||||
let toml_str = r#"
|
||||
allowed_approval_policies = ["on-request", { granular = "any" }]
|
||||
"#;
|
||||
let config: ConfigRequirementsToml = from_str(toml_str)?;
|
||||
let requirements: ConfigRequirements = with_unknown_source(config).try_into()?;
|
||||
|
||||
assert!(
|
||||
requirements
|
||||
.approval_policy
|
||||
.can_set(&AskForApproval::Granular(
|
||||
codex_protocol::protocol::GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: false,
|
||||
}
|
||||
))
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
requirements
|
||||
.approval_policy
|
||||
.can_set(&AskForApproval::Granular(
|
||||
codex_protocol::protocol::GranularApprovalConfig {
|
||||
sandbox_approval: false,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: true,
|
||||
}
|
||||
))
|
||||
.is_ok()
|
||||
);
|
||||
assert_eq!(
|
||||
requirements.approval_policy.can_set(&AskForApproval::Never),
|
||||
Err(ConstraintError::InvalidValue {
|
||||
field_name: "approval_policy",
|
||||
candidate: "Never".into(),
|
||||
allowed: "[OnRequest, Granular(Any)]".into(),
|
||||
requirement_source: RequirementSource::Unknown,
|
||||
})
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allowed_approval_policies_keep_exact_granular_requirement() -> Result<()> {
|
||||
let toml_str = r#"
|
||||
allowed_approval_policies = [
|
||||
{ granular = { sandbox_approval = true, rules = false, skill_approval = false, request_permissions = true, mcp_elicitations = false } }
|
||||
]
|
||||
"#;
|
||||
let config: ConfigRequirementsToml = from_str(toml_str)?;
|
||||
let requirements: ConfigRequirements = with_unknown_source(config).try_into()?;
|
||||
|
||||
assert!(
|
||||
requirements
|
||||
.approval_policy
|
||||
.can_set(&AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: false,
|
||||
}))
|
||||
.is_ok()
|
||||
);
|
||||
assert_eq!(
|
||||
requirements.approval_policy.can_set(&AskForApproval::Granular(
|
||||
GranularApprovalConfig {
|
||||
sandbox_approval: false,
|
||||
rules: false,
|
||||
skill_approval: false,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: false,
|
||||
}
|
||||
)),
|
||||
Err(ConstraintError::InvalidValue {
|
||||
field_name: "approval_policy",
|
||||
candidate: "Granular(GranularApprovalConfig { sandbox_approval: false, rules: false, skill_approval: false, request_permissions: true, mcp_elicitations: false })"
|
||||
.into(),
|
||||
allowed: "[Granular(Exact(GranularApprovalConfig { sandbox_approval: true, rules: false, skill_approval: false, request_permissions: true, mcp_elicitations: false }))]"
|
||||
.into(),
|
||||
requirement_source: RequirementSource::Unknown,
|
||||
})
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_allowed_approvals_reviewers() -> Result<()> {
|
||||
let toml_str = r#"
|
||||
|
||||
@@ -31,6 +31,7 @@ pub use cloud_requirements::CloudRequirementsLoader;
|
||||
pub use codex_app_server_protocol::ConfigLayerSource;
|
||||
pub use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
pub use config_requirements::AppRequirementToml;
|
||||
pub use config_requirements::ApprovalPolicyConstraint;
|
||||
pub use config_requirements::AppsRequirementsToml;
|
||||
pub use config_requirements::ConfigRequirements;
|
||||
pub use config_requirements::ConfigRequirementsToml;
|
||||
@@ -39,6 +40,7 @@ pub use config_requirements::ConstrainedWithSource;
|
||||
pub use config_requirements::FeatureRequirementsToml;
|
||||
pub use config_requirements::FilesystemConstraints;
|
||||
pub use config_requirements::FilesystemDenyReadPattern;
|
||||
pub use config_requirements::GranularApprovalConstraint;
|
||||
pub use config_requirements::McpServerIdentity;
|
||||
pub use config_requirements::McpServerRequirement;
|
||||
pub use config_requirements::NetworkConstraints;
|
||||
|
||||
@@ -6538,7 +6538,7 @@ trust_level = "untrusted"
|
||||
.fallback_cwd(Some(workspace.path().to_path_buf()))
|
||||
.cloud_requirements(CloudRequirementsLoader::new(async {
|
||||
Ok(Some(crate::config_loader::ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::OnRequest]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::OnRequest.into()]),
|
||||
..Default::default()
|
||||
}))
|
||||
}))
|
||||
@@ -6567,7 +6567,7 @@ async fn explicit_approval_policy_falls_back_when_disallowed_by_requirements() -
|
||||
.fallback_cwd(Some(codex_home.path().to_path_buf()))
|
||||
.cloud_requirements(CloudRequirementsLoader::new(async {
|
||||
Ok(Some(crate::config_loader::ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::OnRequest]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::OnRequest.into()]),
|
||||
..Default::default()
|
||||
}))
|
||||
}))
|
||||
|
||||
@@ -30,6 +30,7 @@ use std::path::PathBuf;
|
||||
use toml::Value as TomlValue;
|
||||
|
||||
pub use codex_config::AppRequirementToml;
|
||||
pub use codex_config::ApprovalPolicyConstraint;
|
||||
pub use codex_config::AppsRequirementsToml;
|
||||
pub use codex_config::CloudRequirementsLoadError;
|
||||
pub use codex_config::CloudRequirementsLoadErrorCode;
|
||||
@@ -45,6 +46,7 @@ pub use codex_config::ConstrainedWithSource;
|
||||
pub use codex_config::FeatureRequirementsToml;
|
||||
pub use codex_config::FilesystemConstraints;
|
||||
pub use codex_config::FilesystemDenyReadPattern;
|
||||
pub use codex_config::GranularApprovalConstraint;
|
||||
pub use codex_config::HookEventsToml;
|
||||
pub use codex_config::HookHandlerConfig;
|
||||
pub use codex_config::LoaderOverrides;
|
||||
@@ -1048,7 +1050,7 @@ impl From<LegacyManagedConfigToml> for ConfigRequirementsToml {
|
||||
sandbox_mode,
|
||||
} = legacy;
|
||||
if let Some(approval_policy) = approval_policy {
|
||||
config_requirements_toml.allowed_approval_policies = Some(vec![approval_policy]);
|
||||
config_requirements_toml.allowed_approval_policies = Some(vec![approval_policy.into()]);
|
||||
}
|
||||
if let Some(approvals_reviewer) = approvals_reviewer {
|
||||
let mut allowed_reviewers = vec![approvals_reviewer];
|
||||
|
||||
@@ -681,7 +681,10 @@ personality = true
|
||||
.allowed_approval_policies
|
||||
.as_deref()
|
||||
.cloned(),
|
||||
Some(vec![AskForApproval::Never, AskForApproval::OnRequest])
|
||||
Some(vec![
|
||||
AskForApproval::Never.into(),
|
||||
AskForApproval::OnRequest.into(),
|
||||
])
|
||||
);
|
||||
assert_eq!(
|
||||
config_requirements_toml
|
||||
@@ -771,7 +774,7 @@ allowed_approval_policies = ["on-request"]
|
||||
loader_overrides,
|
||||
CloudRequirementsLoader::new(async {
|
||||
Ok(Some(ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -828,7 +831,7 @@ allowed_approval_policies = ["on-request"]
|
||||
config_requirements_toml.merge_unset_fields(
|
||||
RequirementSource::CloudRequirements,
|
||||
ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
@@ -857,7 +860,7 @@ allowed_approval_policies = ["on-request"]
|
||||
.allowed_approval_policies
|
||||
.as_ref()
|
||||
.map(|sourced| sourced.value.clone()),
|
||||
Some(vec![AskForApproval::Never])
|
||||
Some(vec![AskForApproval::Never.into()])
|
||||
);
|
||||
assert_eq!(
|
||||
config_requirements_toml
|
||||
@@ -1037,7 +1040,7 @@ async fn load_config_layers_includes_cloud_requirements() -> anyhow::Result<()>
|
||||
let cwd = AbsolutePathBuf::from_absolute_path(tmp.path())?;
|
||||
|
||||
let requirements = ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::Never.into()]),
|
||||
allowed_approvals_reviewers: None,
|
||||
allowed_sandbox_modes: None,
|
||||
remote_sandbox_config: None,
|
||||
|
||||
@@ -679,7 +679,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let requirements_toml = ConfigRequirementsToml {
|
||||
allowed_approval_policies: Some(vec![AskForApproval::OnRequest]),
|
||||
allowed_approval_policies: Some(vec![AskForApproval::OnRequest.into()]),
|
||||
allowed_approvals_reviewers: Some(vec![ApprovalsReviewer::AutoReview]),
|
||||
allowed_sandbox_modes: Some(vec![SandboxModeRequirement::ReadOnly]),
|
||||
remote_sandbox_config: None,
|
||||
|
||||
Reference in New Issue
Block a user