From 4ea99de8aec67acedb884e683b223d71622fbf27 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Wed, 6 May 2026 19:19:53 -0700 Subject: [PATCH] feat: activate managed artifact requirements Co-authored-by: Codex noreply@openai.com --- .../codex_app_server_protocol.schemas.json | 44 +++++++++ .../codex_app_server_protocol.v2.schemas.json | 44 +++++++++ .../v2/ConfigRequirementsReadResponse.json | 44 +++++++++ .../v2/PluginMarketplaceRequirements.ts | 5 + .../typescript/v2/SkillSourceRequirement.ts | 5 + .../typescript/v2/SkillsRequirements.ts | 6 ++ .../schema/typescript/v2/index.ts | 3 + .../src/protocol/v2/config.rs | 30 ++++++ .../src/protocol/v2/tests.rs | 2 + codex-rs/app-server/README.md | 2 +- .../request_processors/config_processor.rs | 42 +++++++++ codex-rs/cloud-requirements/src/lib.rs | 67 ++++++++++++++ codex-rs/config/src/config_requirements.rs | 92 +++++++++++++++++-- .../core/src/config/config_loader_tests.rs | 6 ++ codex-rs/core/src/config/config_tests.rs | 4 + codex-rs/tui/src/debug_config.rs | 4 + 16 files changed, 393 insertions(+), 7 deletions(-) create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/PluginMarketplaceRequirements.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillSourceRequirement.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/SkillsRequirements.ts diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 24b8ea9cc008..0014b784a3c1 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -12457,6 +12457,26 @@ ], "type": "object" }, + "PluginMarketplaceRequirements": { + "properties": { + "allowUserAdditions": { + "type": [ + "boolean", + "null" + ] + }, + "allowedNames": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + } + }, + "type": "object" + }, "PluginReadParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -14898,6 +14918,16 @@ ], "type": "string" }, + "SkillSourceRequirement": { + "enum": [ + "user", + "repo", + "system", + "admin", + "plugin" + ], + "type": "string" + }, "SkillSummary": { "properties": { "description": { @@ -15120,6 +15150,20 @@ "title": "SkillsListResponse", "type": "object" }, + "SkillsRequirements": { + "properties": { + "allowedSources": { + "items": { + "$ref": "#/definitions/v2/SkillSourceRequirement" + }, + "type": [ + "array", + "null" + ] + } + }, + "type": "object" + }, "SortDirection": { "enum": [ "asc", diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 76d81c729ec1..9876e0138ef4 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -9068,6 +9068,26 @@ ], "type": "object" }, + "PluginMarketplaceRequirements": { + "properties": { + "allowUserAdditions": { + "type": [ + "boolean", + "null" + ] + }, + "allowedNames": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + } + }, + "type": "object" + }, "PluginReadParams": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -12784,6 +12804,16 @@ ], "type": "string" }, + "SkillSourceRequirement": { + "enum": [ + "user", + "repo", + "system", + "admin", + "plugin" + ], + "type": "string" + }, "SkillSummary": { "properties": { "description": { @@ -13006,6 +13036,20 @@ "title": "SkillsListResponse", "type": "object" }, + "SkillsRequirements": { + "properties": { + "allowedSources": { + "items": { + "$ref": "#/definitions/SkillSourceRequirement" + }, + "type": [ + "array", + "null" + ] + } + }, + "type": "object" + }, "SortDirection": { "enum": [ "asc", diff --git a/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json index 545d8dc9b406..ec041ab479fd 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json @@ -388,6 +388,26 @@ ], "type": "string" }, + "PluginMarketplaceRequirements": { + "properties": { + "allowUserAdditions": { + "type": [ + "boolean", + "null" + ] + }, + "allowedNames": { + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + } + }, + "type": "object" + }, "ResidencyRequirement": { "enum": [ "us" @@ -402,6 +422,30 @@ ], "type": "string" }, + "SkillSourceRequirement": { + "enum": [ + "user", + "repo", + "system", + "admin", + "plugin" + ], + "type": "string" + }, + "SkillsRequirements": { + "properties": { + "allowedSources": { + "items": { + "$ref": "#/definitions/SkillSourceRequirement" + }, + "type": [ + "array", + "null" + ] + } + }, + "type": "object" + }, "WebSearchMode": { "enum": [ "disabled", diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/PluginMarketplaceRequirements.ts b/codex-rs/app-server-protocol/schema/typescript/v2/PluginMarketplaceRequirements.ts new file mode 100644 index 000000000000..02e41235e1b0 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/PluginMarketplaceRequirements.ts @@ -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 PluginMarketplaceRequirements = { allowedNames: Array | null, allowUserAdditions: boolean | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillSourceRequirement.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillSourceRequirement.ts new file mode 100644 index 000000000000..ef98f464bdb8 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillSourceRequirement.ts @@ -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 SkillSourceRequirement = "user" | "repo" | "system" | "admin" | "plugin"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/SkillsRequirements.ts b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsRequirements.ts new file mode 100644 index 000000000000..6678e10fcdd7 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/SkillsRequirements.ts @@ -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 { SkillSourceRequirement } from "./SkillSourceRequirement"; + +export type SkillsRequirements = { allowedSources: Array | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index e624d704e69d..cd1816c8b775 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -282,6 +282,7 @@ export type { PluginListMarketplaceKind } from "./PluginListMarketplaceKind"; export type { PluginListParams } from "./PluginListParams"; export type { PluginListResponse } from "./PluginListResponse"; export type { PluginMarketplaceEntry } from "./PluginMarketplaceEntry"; +export type { PluginMarketplaceRequirements } from "./PluginMarketplaceRequirements"; export type { PluginReadParams } from "./PluginReadParams"; export type { PluginReadResponse } from "./PluginReadResponse"; export type { PluginShareContext } from "./PluginShareContext"; @@ -341,6 +342,7 @@ export type { SkillErrorInfo } from "./SkillErrorInfo"; export type { SkillInterface } from "./SkillInterface"; export type { SkillMetadata } from "./SkillMetadata"; export type { SkillScope } from "./SkillScope"; +export type { SkillSourceRequirement } from "./SkillSourceRequirement"; export type { SkillSummary } from "./SkillSummary"; export type { SkillToolDependency } from "./SkillToolDependency"; export type { SkillsChangedNotification } from "./SkillsChangedNotification"; @@ -350,6 +352,7 @@ export type { SkillsListEntry } from "./SkillsListEntry"; export type { SkillsListExtraRootsForCwd } from "./SkillsListExtraRootsForCwd"; export type { SkillsListParams } from "./SkillsListParams"; export type { SkillsListResponse } from "./SkillsListResponse"; +export type { SkillsRequirements } from "./SkillsRequirements"; export type { SortDirection } from "./SortDirection"; export type { SubagentMigration } from "./SubagentMigration"; export type { TerminalInteractionNotification } from "./TerminalInteractionNotification"; diff --git a/codex-rs/app-server-protocol/src/protocol/v2/config.rs b/codex-rs/app-server-protocol/src/protocol/v2/config.rs index da4f493d77d9..7054919380b3 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/config.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/config.rs @@ -360,11 +360,41 @@ pub struct ConfigRequirements { pub feature_requirements: Option>, #[experimental("configRequirements/read.hooks")] pub hooks: Option, + #[experimental("configRequirements/read.skills")] + pub skills: Option, + #[experimental("configRequirements/read.pluginMarketplaces")] + pub plugin_marketplaces: Option, pub enforce_residency: Option, #[experimental("configRequirements/read.network")] pub network: Option, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct SkillsRequirements { + pub allowed_sources: Option>, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "lowercase")] +#[ts(export_to = "v2/")] +pub enum SkillSourceRequirement { + User, + Repo, + System, + Admin, + Plugin, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct PluginMarketplaceRequirements { + pub allowed_names: Option>, + pub allow_user_additions: Option, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs index ba6f4e0eebcc..b2ccf39d53a0 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/tests.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/tests.rs @@ -1809,6 +1809,8 @@ fn config_requirements_granular_allowed_approval_policy_is_marked_experimental() allowed_web_search_modes: None, feature_requirements: None, hooks: None, + skills: None, + plugin_marketplaces: None, enforce_residency: None, network: None, }); diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index ddc381795272..a403c8221e50 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -232,7 +232,7 @@ Example with notification opt-out: - `externalAgentConfig/import` — apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home) and any plugin/session `details` returned by detect. When a request includes migration items, the server emits `externalAgentConfig/import/completed` once after the full import finishes (immediately after the response when everything completed synchronously, or after background imports finish). - `config/value/write` — write a single config key/value to the user's config.toml on disk. - `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads. -- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), pinned feature values (`featureRequirements`), managed lifecycle hooks (`hooks`), `enforceResidency`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`. +- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml`, cloud requirements, and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), pinned feature values (`featureRequirements`), managed lifecycle hooks (`hooks`), skill source restrictions (`skills`), plugin marketplace restrictions (`pluginMarketplaces`), `enforceResidency`, and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`. ### Example: Start or resume a thread diff --git a/codex-rs/app-server/src/request_processors/config_processor.rs b/codex-rs/app-server/src/request_processors/config_processor.rs index f08486771938..71aab67db4ea 100644 --- a/codex-rs/app-server/src/request_processors/config_processor.rs +++ b/codex-rs/app-server/src/request_processors/config_processor.rs @@ -28,16 +28,22 @@ use codex_app_server_protocol::ModelProviderCapabilitiesReadResponse; use codex_app_server_protocol::NetworkDomainPermission; use codex_app_server_protocol::NetworkRequirements; use codex_app_server_protocol::NetworkUnixSocketPermission; +use codex_app_server_protocol::PluginMarketplaceRequirements; use codex_app_server_protocol::SandboxMode; use codex_app_server_protocol::ServerNotification; +use codex_app_server_protocol::SkillSourceRequirement; +use codex_app_server_protocol::SkillsRequirements; use codex_chatgpt::connectors; use codex_config::ConfigRequirementsToml; use codex_config::HookEventsToml; use codex_config::HookHandlerConfig as CoreHookHandlerConfig; use codex_config::ManagedHooksRequirementsToml; use codex_config::MatcherGroup as CoreMatcherGroup; +use codex_config::PluginMarketplaceRequirementsToml; use codex_config::ResidencyRequirement as CoreResidencyRequirement; use codex_config::SandboxModeRequirement as CoreSandboxModeRequirement; +use codex_config::SkillSourceRequirement as CoreSkillSourceRequirement; +use codex_config::SkillsRequirementsToml; use codex_core::ThreadManager; use codex_features::Feature; use codex_features::canonical_feature_for_key; @@ -445,6 +451,10 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR .feature_requirements .map(|requirements| requirements.entries), hooks: requirements.hooks.map(map_hooks_requirements_to_api), + skills: requirements.skills.map(map_skills_requirements_to_api), + plugin_marketplaces: requirements + .plugin_marketplaces + .map(map_plugin_marketplace_requirements_to_api), enforce_residency: requirements .enforce_residency .map(map_residency_requirement_to_api), @@ -452,6 +462,38 @@ fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigR } } +fn map_skills_requirements_to_api(requirements: SkillsRequirementsToml) -> SkillsRequirements { + SkillsRequirements { + allowed_sources: requirements.allowed_sources.map(|sources| { + sources + .into_iter() + .map(map_skill_source_requirement_to_api) + .collect() + }), + } +} + +fn map_skill_source_requirement_to_api( + source: CoreSkillSourceRequirement, +) -> SkillSourceRequirement { + match source { + CoreSkillSourceRequirement::User => SkillSourceRequirement::User, + CoreSkillSourceRequirement::Repo => SkillSourceRequirement::Repo, + CoreSkillSourceRequirement::System => SkillSourceRequirement::System, + CoreSkillSourceRequirement::Admin => SkillSourceRequirement::Admin, + CoreSkillSourceRequirement::Plugin => SkillSourceRequirement::Plugin, + } +} + +fn map_plugin_marketplace_requirements_to_api( + requirements: PluginMarketplaceRequirementsToml, +) -> PluginMarketplaceRequirements { + PluginMarketplaceRequirements { + allowed_names: requirements.allowed_names, + allow_user_additions: requirements.allow_user_additions, + } +} + fn map_hooks_requirements_to_api(hooks: ManagedHooksRequirementsToml) -> ManagedHooksRequirements { let ManagedHooksRequirementsToml { managed_dir, diff --git a/codex-rs/cloud-requirements/src/lib.rs b/codex-rs/cloud-requirements/src/lib.rs index 6f283b43d023..762361cab36d 100644 --- a/codex-rs/cloud-requirements/src/lib.rs +++ b/codex-rs/cloud-requirements/src/lib.rs @@ -1208,6 +1208,8 @@ mod tests { hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -1290,6 +1292,8 @@ mod tests { hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -1323,6 +1327,8 @@ mod tests { hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -1373,6 +1379,8 @@ mod tests { hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -1437,6 +1445,41 @@ command = "sample-mcp" ); } + #[tokio::test] + async fn fetch_cloud_requirements_parses_artifact_policy_toml() { + let result = parse_for_fetch(Some( + r#" +[skills] +allowed_sources = ["system", "admin", "plugin"] + +[plugin_marketplaces] +allowed_names = ["openai-curated", "arm-internal"] +allow_user_additions = false +"#, + )); + + assert_eq!( + result, + Some(ConfigRequirementsToml { + skills: Some(codex_config::SkillsRequirementsToml { + allowed_sources: Some(vec![ + codex_config::SkillSourceRequirement::System, + codex_config::SkillSourceRequirement::Admin, + codex_config::SkillSourceRequirement::Plugin, + ]), + }), + plugin_marketplaces: Some(codex_config::PluginMarketplaceRequirementsToml { + allowed_names: Some(vec![ + "openai-curated".to_string(), + "arm-internal".to_string(), + ]), + allow_user_additions: Some(false), + }), + ..Default::default() + }) + ); + } + #[tokio::test(start_paused = true)] async fn fetch_cloud_requirements_times_out() { let auth_manager = auth_manager_with_plan("enterprise").await; @@ -1489,6 +1532,8 @@ command = "sample-mcp" hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -1569,6 +1614,8 @@ command = "sample-mcp" hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -1647,6 +1694,8 @@ command = "sample-mcp" hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -1853,6 +1902,8 @@ command = "sample-mcp" hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -1893,6 +1944,8 @@ command = "sample-mcp" hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -1953,6 +2006,8 @@ command = "sample-mcp" hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -2009,6 +2064,8 @@ command = "sample-mcp" hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -2067,6 +2124,8 @@ command = "sample-mcp" hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -2126,6 +2185,8 @@ command = "sample-mcp" hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -2185,6 +2246,8 @@ command = "sample-mcp" hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -2274,6 +2337,8 @@ command = "sample-mcp" hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -2305,6 +2370,8 @@ command = "sample-mcp" hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, diff --git a/codex-rs/config/src/config_requirements.rs b/codex-rs/config/src/config_requirements.rs index 2567a9a6a32a..802d4f17bce1 100644 --- a/codex-rs/config/src/config_requirements.rs +++ b/codex-rs/config/src/config_requirements.rs @@ -702,6 +702,8 @@ pub struct ConfigRequirementsToml { pub hooks: Option, pub mcp_servers: Option>, pub plugins: Option>, + pub skills: Option, + pub plugin_marketplaces: Option, pub apps: Option, pub rules: Option, pub enforce_residency: Option, @@ -787,6 +789,8 @@ impl ConfigRequirementsWithSources { hooks: _, mcp_servers: _, plugins: _, + skills: _, + plugin_marketplaces: _, apps: _, rules: _, enforce_residency: _, @@ -816,6 +820,8 @@ impl ConfigRequirementsWithSources { hooks, mcp_servers, plugins, + skills, + plugin_marketplaces, rules, enforce_residency, network, @@ -843,8 +849,8 @@ impl ConfigRequirementsWithSources { hooks, mcp_servers, plugins, - skills: _, - plugin_marketplaces: _, + skills, + plugin_marketplaces, apps, rules, enforce_residency, @@ -862,6 +868,8 @@ impl ConfigRequirementsWithSources { hooks: hooks.map(|sourced| sourced.value), mcp_servers: mcp_servers.map(|sourced| sourced.value), plugins: plugins.map(|sourced| sourced.value), + skills: skills.map(|sourced| sourced.value), + plugin_marketplaces: plugin_marketplaces.map(|sourced| sourced.value), apps: apps.map(|sourced| sourced.value), rules: rules.map(|sourced| sourced.value), enforce_residency: enforce_residency.map(|sourced| sourced.value), @@ -954,6 +962,14 @@ impl ConfigRequirementsToml { .plugins .as_ref() .is_none_or(|plugins| plugins.values().all(PluginRequirementsToml::is_empty)) + && self + .skills + .as_ref() + .is_none_or(SkillsRequirementsToml::is_empty) + && self + .plugin_marketplaces + .as_ref() + .is_none_or(PluginMarketplaceRequirementsToml::is_empty) && self .apps .as_ref() @@ -1293,6 +1309,8 @@ mod tests { hooks, mcp_servers, plugins, + skills, + plugin_marketplaces, apps, rules, enforce_residency, @@ -1314,8 +1332,9 @@ mod tests { hooks: hooks.map(|value| Sourced::new(value, RequirementSource::Unknown)), mcp_servers: mcp_servers.map(|value| Sourced::new(value, RequirementSource::Unknown)), plugins: plugins.map(|value| Sourced::new(value, RequirementSource::Unknown)), - skills: None, - plugin_marketplaces: None, + skills: skills.map(|value| Sourced::new(value, RequirementSource::Unknown)), + plugin_marketplaces: plugin_marketplaces + .map(|value| Sourced::new(value, RequirementSource::Unknown)), apps: apps.map(|value| Sourced::new(value, RequirementSource::Unknown)), rules: rules.map(|value| Sourced::new(value, RequirementSource::Unknown)), enforce_residency: enforce_residency @@ -1346,6 +1365,20 @@ mod tests { let feature_requirements = FeatureRequirementsToml { entries: BTreeMap::from([("personality".to_string(), true)]), }; + let skills = SkillsRequirementsToml { + allowed_sources: Some(vec![ + SkillSourceRequirement::System, + SkillSourceRequirement::Admin, + SkillSourceRequirement::Plugin, + ]), + }; + let plugin_marketplaces = PluginMarketplaceRequirementsToml { + allowed_names: Some(vec![ + "openai-curated".to_string(), + "arm-internal".to_string(), + ]), + allow_user_additions: Some(false), + }; let enforce_residency = ResidencyRequirement::Us; let enforce_source = source.clone(); let guardian_policy_config = "Use the company-managed guardian policy.".to_string(); @@ -1362,6 +1395,8 @@ mod tests { hooks: None, mcp_servers: None, plugins: None, + skills: Some(skills.clone()), + plugin_marketplaces: Some(plugin_marketplaces.clone()), apps: None, rules: None, enforce_residency: Some(enforce_residency), @@ -1395,8 +1430,11 @@ mod tests { hooks: None, mcp_servers: None, plugins: None, - skills: None, - plugin_marketplaces: None, + skills: Some(Sourced::new(skills, enforce_source.clone())), + plugin_marketplaces: Some(Sourced::new( + plugin_marketplaces, + enforce_source.clone(), + )), apps: None, rules: None, enforce_residency: Some(Sourced::new(enforce_residency, enforce_source)), @@ -2851,6 +2889,48 @@ command = "python3 /enterprise/hooks/pre.py" Ok(()) } + #[test] + fn deserialize_skill_and_plugin_marketplace_requirements() -> Result<()> { + let toml_str = r#" + [skills] + allowed_sources = ["system", "admin", "plugin"] + + [plugin_marketplaces] + allowed_names = ["openai-curated", "arm-internal"] + allow_user_additions = false + "#; + let requirements: ConfigRequirements = + with_unknown_source(from_str(toml_str)?).try_into()?; + + assert_eq!( + requirements.skills, + Some(Sourced::new( + SkillsRequirementsToml { + allowed_sources: Some(vec![ + SkillSourceRequirement::System, + SkillSourceRequirement::Admin, + SkillSourceRequirement::Plugin, + ]), + }, + RequirementSource::Unknown, + )) + ); + assert_eq!( + requirements.plugin_marketplaces, + Some(Sourced::new( + PluginMarketplaceRequirementsToml { + allowed_names: Some(vec![ + "openai-curated".to_string(), + "arm-internal".to_string(), + ]), + allow_user_additions: Some(false), + }, + RequirementSource::Unknown, + )) + ); + Ok(()) + } + #[test] fn deserialize_exec_policy_requirements() -> Result<()> { let toml_str = r#" diff --git a/codex-rs/core/src/config/config_loader_tests.rs b/codex-rs/core/src/config/config_loader_tests.rs index 6fcd5f872db1..296552645734 100644 --- a/codex-rs/core/src/config/config_loader_tests.rs +++ b/codex-rs/core/src/config/config_loader_tests.rs @@ -781,6 +781,8 @@ allowed_approval_policies = ["on-request"] hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -838,6 +840,8 @@ allowed_approval_policies = ["on-request"] hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -1046,6 +1050,8 @@ async fn load_config_layers_includes_cloud_requirements() -> anyhow::Result<()> hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 9b785ff3373f..7df0f5ec406d 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -7671,6 +7671,8 @@ async fn test_requirements_web_search_mode_allowlist_does_not_warn_when_unset() hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, @@ -8383,6 +8385,8 @@ async fn explicit_sandbox_mode_falls_back_when_disallowed_by_requirements() -> s hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None, diff --git a/codex-rs/tui/src/debug_config.rs b/codex-rs/tui/src/debug_config.rs index 1d4bf2b858ec..608d7f2fc580 100644 --- a/codex-rs/tui/src/debug_config.rs +++ b/codex-rs/tui/src/debug_config.rs @@ -698,6 +698,8 @@ mod tests { }, )])), plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: Some(ResidencyRequirement::Us), @@ -898,6 +900,8 @@ approval_policy = "never" hooks: None, mcp_servers: None, plugins: None, + skills: None, + plugin_marketplaces: None, apps: None, rules: None, enforce_residency: None,