Compare commits

...

1 Commits

Author SHA1 Message Date
pakrym-oai
20a2927b9d Allow enabled tables for feature flags 2026-05-06 15:17:33 -07:00
5 changed files with 320 additions and 191 deletions

View File

@@ -43,14 +43,16 @@ pub fn features_schema(schema_gen: &mut SchemaGenerator) -> Schema {
);
continue;
}
validation
.properties
.insert(feature.key.to_string(), schema_gen.subschema_for::<bool>());
validation.properties.insert(
feature.key.to_string(),
schema_gen.subschema_for::<codex_features::FeatureToggleToml>(),
);
}
for legacy_key in legacy_feature_keys() {
validation
.properties
.insert(legacy_key.to_string(), schema_gen.subschema_for::<bool>());
validation.properties.insert(
legacy_key.to_string(),
schema_gen.subschema_for::<codex_features::FeatureToggleToml>(),
);
}
validation.additional_properties = Some(Box::new(Schema::Bool(false)));
object.object = Some(Box::new(validation));

View File

@@ -359,256 +359,256 @@
"description": "Optional feature toggles scoped to this profile.",
"properties": {
"apply_patch_freeform": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"apply_patch_streaming_events": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"apps": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"apps_mcp_path_override": {
"$ref": "#/definitions/FeatureToml_for_AppsMcpPathOverrideConfigToml"
},
"auth_elicitation": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"browser_use": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"browser_use_external": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"builtin_mcp": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"child_agents_md": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"chronicle": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"code_mode": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"code_mode_only": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"codex_git_commit": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"codex_hooks": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"collab": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"collaboration_modes": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"computer_use": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"connectors": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"default_mode_request_user_input": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"elevated_windows_sandbox": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"enable_experimental_windows_sandbox": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"enable_fanout": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"enable_mcp_apps": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"enable_request_compression": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"exec_permission_approvals": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"experimental_use_freeform_apply_patch": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"experimental_use_unified_exec_tool": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"experimental_windows_sandbox": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"external_migration": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"fast_mode": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"goals": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"guardian_approval": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"hooks": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"image_detail_original": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"image_generation": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"in_app_browser": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"include_apply_patch_tool": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"js_repl": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"js_repl_tools_only": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"memories": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"memory_tool": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"multi_agent": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"multi_agent_v2": {
"$ref": "#/definitions/FeatureToml_for_MultiAgentV2ConfigToml"
},
"personality": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"plugin_hooks": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"plugins": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"prevent_idle_sleep": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"realtime_conversation": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"remote_compaction_v2": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"remote_control": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"remote_models": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"remote_plugin": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"request_permissions": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"request_permissions_tool": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"request_rule": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"responses_websocket_response_processed": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"responses_websockets": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"responses_websockets_v2": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"runtime_metrics": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"search_tool": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"shell_snapshot": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"shell_tool": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"shell_zsh_fork": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"skill_env_var_dependency_prompt": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"skill_mcp_dependency_install": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"sqlite": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"steer": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"telepathy": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"terminal_resize_reflow": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"tool_call_mcp_elicitation": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"tool_search": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"tool_search_always_defer_mcp_tools": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"tool_suggest": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"tui_app_server": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"unavailable_dummy_tools": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"undo": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"unified_exec": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"use_legacy_landlock": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"use_linux_sandbox_bwrap": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"web_search": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"web_search_cached": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"web_search_request": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"workspace_dependencies": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"workspace_owner_usage_nudge": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
}
},
"type": "object"
@@ -786,6 +786,28 @@
},
"type": "object"
},
"FeatureToggleConfigToml": {
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean"
}
},
"required": [
"enabled"
],
"type": "object"
},
"FeatureToggleToml": {
"anyOf": [
{
"type": "boolean"
},
{
"$ref": "#/definitions/FeatureToggleConfigToml"
}
]
},
"FeatureToml_for_AppsMcpPathOverrideConfigToml": {
"anyOf": [
{
@@ -3917,256 +3939,256 @@
"description": "Centralized feature flags (new). Prefer this over individual toggles.",
"properties": {
"apply_patch_freeform": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"apply_patch_streaming_events": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"apps": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"apps_mcp_path_override": {
"$ref": "#/definitions/FeatureToml_for_AppsMcpPathOverrideConfigToml"
},
"auth_elicitation": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"browser_use": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"browser_use_external": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"builtin_mcp": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"child_agents_md": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"chronicle": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"code_mode": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"code_mode_only": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"codex_git_commit": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"codex_hooks": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"collab": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"collaboration_modes": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"computer_use": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"connectors": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"default_mode_request_user_input": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"elevated_windows_sandbox": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"enable_experimental_windows_sandbox": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"enable_fanout": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"enable_mcp_apps": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"enable_request_compression": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"exec_permission_approvals": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"experimental_use_freeform_apply_patch": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"experimental_use_unified_exec_tool": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"experimental_windows_sandbox": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"external_migration": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"fast_mode": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"goals": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"guardian_approval": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"hooks": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"image_detail_original": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"image_generation": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"in_app_browser": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"include_apply_patch_tool": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"js_repl": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"js_repl_tools_only": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"memories": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"memory_tool": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"multi_agent": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"multi_agent_v2": {
"$ref": "#/definitions/FeatureToml_for_MultiAgentV2ConfigToml"
},
"personality": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"plugin_hooks": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"plugins": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"prevent_idle_sleep": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"realtime_conversation": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"remote_compaction_v2": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"remote_control": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"remote_models": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"remote_plugin": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"request_permissions": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"request_permissions_tool": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"request_rule": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"responses_websocket_response_processed": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"responses_websockets": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"responses_websockets_v2": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"runtime_metrics": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"search_tool": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"shell_snapshot": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"shell_tool": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"shell_zsh_fork": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"skill_env_var_dependency_prompt": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"skill_mcp_dependency_install": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"sqlite": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"steer": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"telepathy": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"terminal_resize_reflow": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"tool_call_mcp_elicitation": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"tool_search": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"tool_search_always_defer_mcp_tools": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"tool_suggest": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"tui_app_server": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"unavailable_dummy_tools": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"undo": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"unified_exec": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"use_legacy_landlock": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"use_linux_sandbox_bwrap": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"web_search": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"web_search_cached": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"web_search_request": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"workspace_dependencies": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
},
"workspace_owner_usage_nudge": {
"type": "boolean"
"$ref": "#/definitions/FeatureToggleToml"
}
},
"type": "object"

View File

@@ -8822,6 +8822,37 @@ shell_tool = true
Ok(())
}
#[tokio::test]
async fn simple_features_can_be_configured_with_enabled_tables() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
std::fs::write(
codex_home.path().join(CONFIG_TOML_FILE),
r#"profile = "dev"
[features.code_mode]
enabled = true
[features.personality]
enabled = false
[profiles.dev.features.shell_tool]
enabled = false
"#,
)?;
let config = ConfigBuilder::without_managed_config_for_tests()
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(codex_home.path().to_path_buf()))
.build()
.await?;
assert!(config.features.enabled(Feature::CodeMode));
assert!(!config.features.enabled(Feature::Personality));
assert!(!config.features.enabled(Feature::ShellTool));
Ok(())
}
#[tokio::test]
async fn approvals_reviewer_defaults_to_manual_only_without_guardian_feature() -> std::io::Result<()>
{

View File

@@ -13,6 +13,7 @@ use serde::Serialize;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use toml::Table;
use toml::Value as TomlValue;
mod feature_configs;
mod legacy;
@@ -576,7 +577,7 @@ pub struct FeaturesToml {
pub apps_mcp_path_override: Option<FeatureToml<AppsMcpPathOverrideConfigToml>>,
/// Boolean feature toggles keyed by canonical or legacy feature name.
#[serde(flatten)]
entries: BTreeMap<String, bool>,
entries: BTreeMap<String, FeatureToggleToml>,
}
impl Features {
@@ -588,7 +589,11 @@ impl Features {
impl FeaturesToml {
pub fn entries(&self) -> BTreeMap<String, bool> {
let mut entries = self.entries.clone();
let mut entries = self
.entries
.iter()
.map(|(key, feature)| (key.clone(), feature.enabled()))
.collect::<BTreeMap<_, _>>();
if let Some(enabled) = self.multi_agent_v2.as_ref().and_then(FeatureToml::enabled) {
entries.insert(Feature::MultiAgentV2.key().to_string(), enabled);
}
@@ -618,7 +623,7 @@ impl FeaturesToml {
} else if spec.id == Feature::AppsMcpPathOverride {
materialize_resolved_feature_enabled(apps_mcp_path_override, enabled);
} else {
entries.insert(spec.key.to_string(), enabled);
entries.insert(spec.key.to_string(), FeatureToggleToml::Enabled(enabled));
}
}
}
@@ -637,12 +642,38 @@ fn materialize_resolved_feature_enabled<T: FeatureConfig>(
impl From<BTreeMap<String, bool>> for FeaturesToml {
fn from(entries: BTreeMap<String, bool>) -> Self {
Self {
entries,
entries: entries
.into_iter()
.map(|(key, enabled)| (key, FeatureToggleToml::Enabled(enabled)))
.collect(),
..Default::default()
}
}
}
// To be used for features that only need enabled/disabled configuration.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
#[serde(untagged)]
pub enum FeatureToggleToml {
Enabled(bool),
Config(FeatureToggleConfigToml),
}
impl FeatureToggleToml {
fn enabled(&self) -> bool {
match self {
Self::Enabled(enabled) => *enabled,
Self::Config(config) => config.enabled,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct FeatureToggleConfigToml {
pub enabled: bool,
}
// To be used for features that need more configuration than just enabled/disabled and
// require a custom config struct under `[features]`.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
@@ -1174,7 +1205,15 @@ pub fn unstable_features_warning_event(
let mut under_development_feature_keys = Vec::new();
if let Some(table) = effective_features {
for (key, value) in table {
if value.as_bool() != Some(true) {
let enabled = match value {
TomlValue::Boolean(enabled) => *enabled,
TomlValue::Table(table) => table
.get("enabled")
.and_then(TomlValue::as_bool)
.unwrap_or(false),
_ => false,
};
if !enabled {
continue;
}
let Some(spec) = FEATURES.iter().find(|spec| spec.key == key.as_str()) else {

View File

@@ -357,17 +357,11 @@ fn apps_require_feature_flag_and_chatgpt_auth() {
fn from_sources_applies_base_profile_and_overrides() {
let mut base_entries = BTreeMap::new();
base_entries.insert("plugins".to_string(), true);
let base_features = FeaturesToml {
entries: base_entries,
..Default::default()
};
let base_features = FeaturesToml::from(base_entries);
let mut profile_entries = BTreeMap::new();
profile_entries.insert("code_mode_only".to_string(), true);
let profile_features = FeaturesToml {
entries: profile_entries,
..Default::default()
};
let profile_features = FeaturesToml::from(profile_entries);
let features = Features::from_sources(
FeatureConfigSource {
@@ -498,6 +492,38 @@ hide_spawn_agent_metadata = true
);
}
#[test]
fn simple_feature_config_deserializes_enabled_table() {
let features: FeaturesToml = toml::from_str(
r#"
code_mode.enabled = true
[personality]
enabled = false
"#,
)
.expect("features table should deserialize");
assert_eq!(
features.entries(),
BTreeMap::from([
("code_mode".to_string(), true),
("personality".to_string(), false),
])
);
let resolved = Features::from_sources(
FeatureConfigSource {
features: Some(&features),
..Default::default()
},
FeatureConfigSource::default(),
FeatureOverrides::default(),
);
assert_eq!(resolved.enabled(Feature::CodeMode), true);
assert_eq!(resolved.enabled(Feature::Personality), false);
}
#[test]
fn multi_agent_v2_feature_config_usage_hint_enabled_does_not_enable_feature() {
let features_toml: FeaturesToml = toml::from_str(
@@ -540,15 +566,15 @@ fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config(
features.enable(Feature::MultiAgentV2);
features.disable(Feature::ToolSearch);
let mut features_toml = FeaturesToml {
multi_agent_v2: Some(FeatureToml::Config(crate::MultiAgentV2ConfigToml {
enabled: Some(false),
min_wait_timeout_ms: Some(2500),
..Default::default()
})),
entries: BTreeMap::from([("include_apply_patch_tool".to_string(), true)]),
let mut features_toml = FeaturesToml::from(BTreeMap::from([(
"include_apply_patch_tool".to_string(),
true,
)]));
features_toml.multi_agent_v2 = Some(FeatureToml::Config(crate::MultiAgentV2ConfigToml {
enabled: Some(false),
min_wait_timeout_ms: Some(2500),
..Default::default()
};
}));
features_toml.materialize_resolved_enabled(&features);
@@ -587,9 +613,17 @@ fn unstable_warning_event_only_mentions_enabled_under_development_features() {
configured_features.insert("child_agents_md".to_string(), TomlValue::Boolean(true));
configured_features.insert("personality".to_string(), TomlValue::Boolean(true));
configured_features.insert("unknown".to_string(), TomlValue::Boolean(true));
configured_features.insert(
"code_mode".to_string(),
TomlValue::Table(Table::from_iter([(
"enabled".to_string(),
TomlValue::Boolean(true),
)])),
);
let mut features = Features::with_defaults();
features.enable(Feature::ChildAgentsMd);
features.enable(Feature::CodeMode);
let warning = unstable_features_warning_event(
Some(&configured_features),
@@ -603,6 +637,7 @@ fn unstable_warning_event_only_mentions_enabled_under_development_features() {
panic!("expected warning event");
};
assert!(message.contains("child_agents_md"));
assert!(message.contains("code_mode"));
assert!(!message.contains("personality"));
assert!(message.contains("/tmp/config.toml"));
}