mirror of
https://github.com/openai/codex.git
synced 2026-05-16 09:12:54 +00:00
## Summary - Add a `response.processed` websocket request payload and sender for Responses API websockets. - Send `response.processed` from `try_run_sampling_request` after a response completes, local turn processing succeeds, and the session-owned feature flag is enabled. - Add websocket coverage for both enabled and disabled feature-flag behavior. ## Validation - `just fmt` - `cargo test -p codex-core response_processed` - `cargo test -p codex-api responses_websocket` - `cargo test -p codex-features responses_websocket_response_processed_is_under_development` - `git diff --check` - `just fix -p codex-api -p codex-core -p codex-features` - `git diff --check origin/main...HEAD`
609 lines
19 KiB
Rust
609 lines
19 KiB
Rust
use crate::Feature;
|
|
use crate::FeatureConfigSource;
|
|
use crate::FeatureOverrides;
|
|
use crate::FeatureToml;
|
|
use crate::Features;
|
|
use crate::FeaturesToml;
|
|
use crate::Stage;
|
|
use crate::feature_for_key;
|
|
use crate::unstable_features_warning_event;
|
|
use codex_protocol::protocol::EventMsg;
|
|
use codex_protocol::protocol::WarningEvent;
|
|
use pretty_assertions::assert_eq;
|
|
use std::collections::BTreeMap;
|
|
use toml::Table;
|
|
use toml::Value as TomlValue;
|
|
|
|
#[test]
|
|
fn under_development_features_are_disabled_by_default() {
|
|
for spec in crate::FEATURES {
|
|
if matches!(spec.stage, Stage::UnderDevelopment) {
|
|
assert_eq!(
|
|
spec.default_enabled, false,
|
|
"feature `{}` is under development and must be disabled by default",
|
|
spec.key
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn default_enabled_features_are_stable() {
|
|
for spec in crate::FEATURES {
|
|
if spec.default_enabled {
|
|
assert!(
|
|
matches!(spec.stage, Stage::Stable | Stage::Removed)
|
|
|| spec.id == Feature::TerminalResizeReflow,
|
|
"feature `{}` is enabled by default but is not stable/removed ({:?})",
|
|
spec.key,
|
|
spec.stage
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn use_legacy_landlock_is_deprecated_and_disabled_by_default() {
|
|
assert_eq!(Feature::UseLegacyLandlock.stage(), Stage::Deprecated);
|
|
assert_eq!(Feature::UseLegacyLandlock.default_enabled(), false);
|
|
}
|
|
|
|
#[test]
|
|
fn use_linux_sandbox_bwrap_is_removed_and_disabled_by_default() {
|
|
assert_eq!(Feature::UseLinuxSandboxBwrap.stage(), Stage::Removed);
|
|
assert_eq!(Feature::UseLinuxSandboxBwrap.default_enabled(), false);
|
|
}
|
|
|
|
#[test]
|
|
fn undo_is_removed_and_disabled_by_default() {
|
|
assert_eq!(Feature::GhostCommit.stage(), Stage::Removed);
|
|
assert_eq!(Feature::GhostCommit.default_enabled(), false);
|
|
}
|
|
|
|
#[test]
|
|
fn image_detail_original_is_removed_and_disabled_by_default() {
|
|
assert_eq!(Feature::ImageDetailOriginal.stage(), Stage::Removed);
|
|
assert_eq!(Feature::ImageDetailOriginal.default_enabled(), false);
|
|
}
|
|
|
|
#[test]
|
|
fn code_mode_only_requires_code_mode() {
|
|
let mut features = Features::with_defaults();
|
|
features.enable(Feature::CodeModeOnly);
|
|
features.normalize_dependencies();
|
|
|
|
assert_eq!(features.enabled(Feature::CodeModeOnly), true);
|
|
assert_eq!(features.enabled(Feature::CodeMode), true);
|
|
}
|
|
|
|
#[test]
|
|
fn guardian_approval_is_stable_and_enabled_by_default() {
|
|
let spec = Feature::GuardianApproval.info();
|
|
|
|
assert_eq!(spec.stage, Stage::Stable);
|
|
assert_eq!(Feature::GuardianApproval.default_enabled(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn external_migration_is_experimental_and_disabled_by_default() {
|
|
let spec = Feature::ExternalMigration.info();
|
|
let stage = spec.stage;
|
|
|
|
assert!(matches!(stage, Stage::Experimental { .. }));
|
|
assert_eq!(stage.experimental_menu_name(), Some("External migration"));
|
|
assert_eq!(
|
|
stage.experimental_menu_description(),
|
|
Some(
|
|
"Show a startup prompt when Codex detects migratable external agent config for this machine or project."
|
|
)
|
|
);
|
|
assert_eq!(stage.experimental_announcement(), None);
|
|
assert_eq!(Feature::ExternalMigration.default_enabled(), false);
|
|
}
|
|
|
|
#[test]
|
|
fn request_permissions_is_under_development() {
|
|
assert_eq!(
|
|
Feature::ExecPermissionApprovals.stage(),
|
|
Stage::UnderDevelopment
|
|
);
|
|
assert_eq!(Feature::ExecPermissionApprovals.default_enabled(), false);
|
|
}
|
|
|
|
#[test]
|
|
fn request_permissions_tool_is_under_development() {
|
|
assert_eq!(
|
|
Feature::RequestPermissionsTool.stage(),
|
|
Stage::UnderDevelopment
|
|
);
|
|
assert_eq!(Feature::RequestPermissionsTool.default_enabled(), false);
|
|
}
|
|
|
|
#[test]
|
|
fn remote_compaction_v2_is_under_development() {
|
|
assert_eq!(Feature::RemoteCompactionV2.stage(), Stage::UnderDevelopment);
|
|
assert_eq!(Feature::RemoteCompactionV2.default_enabled(), false);
|
|
assert_eq!(
|
|
feature_for_key("remote_compaction_v2"),
|
|
Some(Feature::RemoteCompactionV2)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn responses_websocket_response_processed_is_under_development() {
|
|
assert_eq!(
|
|
Feature::ResponsesWebsocketResponseProcessed.stage(),
|
|
Stage::UnderDevelopment
|
|
);
|
|
assert_eq!(
|
|
Feature::ResponsesWebsocketResponseProcessed.default_enabled(),
|
|
false
|
|
);
|
|
assert_eq!(
|
|
feature_for_key("responses_websocket_response_processed"),
|
|
Some(Feature::ResponsesWebsocketResponseProcessed)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn builtin_mcp_is_under_development() {
|
|
assert_eq!(Feature::BuiltInMcp.stage(), Stage::UnderDevelopment);
|
|
assert_eq!(Feature::BuiltInMcp.default_enabled(), false);
|
|
assert_eq!(feature_for_key("builtin_mcp"), Some(Feature::BuiltInMcp));
|
|
}
|
|
|
|
#[test]
|
|
fn terminal_resize_reflow_is_experimental_and_enabled_by_default() {
|
|
assert_eq!(
|
|
feature_for_key("terminal_resize_reflow"),
|
|
Some(Feature::TerminalResizeReflow)
|
|
);
|
|
assert!(matches!(
|
|
Feature::TerminalResizeReflow.stage(),
|
|
Stage::Experimental { .. }
|
|
));
|
|
assert_eq!(Feature::TerminalResizeReflow.default_enabled(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn tool_suggest_is_stable_and_enabled_by_default() {
|
|
assert_eq!(Feature::ToolSuggest.stage(), Stage::Stable);
|
|
assert_eq!(Feature::ToolSuggest.default_enabled(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn tool_search_is_stable_and_enabled_by_default() {
|
|
assert_eq!(Feature::ToolSearch.stage(), Stage::Stable);
|
|
assert_eq!(Feature::ToolSearch.default_enabled(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn browser_controls_are_stable_and_enabled_by_default() {
|
|
assert_eq!(Feature::InAppBrowser.stage(), Stage::Stable);
|
|
assert_eq!(Feature::InAppBrowser.default_enabled(), true);
|
|
assert_eq!(
|
|
feature_for_key("in_app_browser"),
|
|
Some(Feature::InAppBrowser)
|
|
);
|
|
|
|
assert_eq!(Feature::BrowserUse.stage(), Stage::Stable);
|
|
assert_eq!(Feature::BrowserUse.default_enabled(), true);
|
|
assert_eq!(feature_for_key("browser_use"), Some(Feature::BrowserUse));
|
|
|
|
assert_eq!(Feature::BrowserUseExternal.stage(), Stage::Stable);
|
|
assert_eq!(Feature::BrowserUseExternal.default_enabled(), true);
|
|
assert_eq!(
|
|
feature_for_key("browser_use_external"),
|
|
Some(Feature::BrowserUseExternal)
|
|
);
|
|
|
|
assert_eq!(Feature::ComputerUse.stage(), Stage::Stable);
|
|
assert_eq!(Feature::ComputerUse.default_enabled(), true);
|
|
assert_eq!(feature_for_key("computer_use"), Some(Feature::ComputerUse));
|
|
}
|
|
|
|
#[test]
|
|
fn use_linux_sandbox_bwrap_is_a_removed_feature_key() {
|
|
assert_eq!(
|
|
feature_for_key("use_legacy_landlock"),
|
|
Some(Feature::UseLegacyLandlock)
|
|
);
|
|
assert_eq!(
|
|
feature_for_key("use_linux_sandbox_bwrap"),
|
|
Some(Feature::UseLinuxSandboxBwrap)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn image_generation_is_stable_and_enabled_by_default() {
|
|
assert_eq!(Feature::ImageGeneration.stage(), Stage::Stable);
|
|
assert_eq!(Feature::ImageGeneration.default_enabled(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn use_legacy_landlock_config_records_deprecation_notice() {
|
|
let mut entries = BTreeMap::new();
|
|
entries.insert("use_legacy_landlock".to_string(), true);
|
|
|
|
let mut features = Features::with_defaults();
|
|
features.apply_map(&entries);
|
|
|
|
let usages = features.legacy_feature_usages().collect::<Vec<_>>();
|
|
assert_eq!(usages.len(), 1);
|
|
assert_eq!(usages[0].alias, "features.use_legacy_landlock");
|
|
assert_eq!(usages[0].feature, Feature::UseLegacyLandlock);
|
|
assert_eq!(
|
|
usages[0].summary,
|
|
"`[features].use_legacy_landlock` is deprecated and will be removed soon."
|
|
);
|
|
assert_eq!(
|
|
usages[0].details.as_deref(),
|
|
Some("Remove this setting to stop opting into the legacy Linux sandbox behavior.")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn image_detail_original_is_a_removed_feature_key() {
|
|
assert_eq!(
|
|
feature_for_key("image_detail_original"),
|
|
Some(Feature::ImageDetailOriginal)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn js_repl_features_are_removed_feature_keys() {
|
|
assert_eq!(Feature::JsRepl.stage(), Stage::Removed);
|
|
assert_eq!(Feature::JsRepl.default_enabled(), false);
|
|
assert_eq!(feature_for_key("js_repl"), Some(Feature::JsRepl));
|
|
|
|
assert_eq!(Feature::JsReplToolsOnly.stage(), Stage::Removed);
|
|
assert_eq!(Feature::JsReplToolsOnly.default_enabled(), false);
|
|
assert_eq!(
|
|
feature_for_key("js_repl_tools_only"),
|
|
Some(Feature::JsReplToolsOnly)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn tool_call_mcp_elicitation_is_stable_and_enabled_by_default() {
|
|
assert_eq!(Feature::ToolCallMcpElicitation.stage(), Stage::Stable);
|
|
assert_eq!(Feature::ToolCallMcpElicitation.default_enabled(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn auth_elicitation_is_under_development() {
|
|
assert_eq!(Feature::AuthElicitation.stage(), Stage::UnderDevelopment);
|
|
assert_eq!(Feature::AuthElicitation.default_enabled(), false);
|
|
assert_eq!(
|
|
feature_for_key("auth_elicitation"),
|
|
Some(Feature::AuthElicitation)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn remote_control_is_under_development() {
|
|
assert_eq!(Feature::RemoteControl.stage(), Stage::UnderDevelopment);
|
|
assert_eq!(Feature::RemoteControl.default_enabled(), false);
|
|
}
|
|
|
|
#[test]
|
|
fn workspace_dependencies_is_stable_and_enabled_by_default() {
|
|
assert_eq!(Feature::WorkspaceDependencies.stage(), Stage::Stable);
|
|
assert_eq!(Feature::WorkspaceDependencies.default_enabled(), true);
|
|
assert_eq!(
|
|
feature_for_key("workspace_dependencies"),
|
|
Some(Feature::WorkspaceDependencies)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn telepathy_is_legacy_alias_for_chronicle() {
|
|
assert_eq!(Feature::Chronicle.stage(), Stage::UnderDevelopment);
|
|
assert_eq!(Feature::Chronicle.default_enabled(), false);
|
|
assert_eq!(feature_for_key("chronicle"), Some(Feature::Chronicle));
|
|
assert_eq!(feature_for_key("telepathy"), Some(Feature::Chronicle));
|
|
}
|
|
|
|
#[test]
|
|
fn collab_is_legacy_alias_for_multi_agent() {
|
|
assert_eq!(feature_for_key("multi_agent"), Some(Feature::Collab));
|
|
assert_eq!(feature_for_key("collab"), Some(Feature::Collab));
|
|
}
|
|
|
|
#[test]
|
|
fn codex_hooks_is_legacy_alias_for_hooks() {
|
|
assert_eq!(feature_for_key("hooks"), Some(Feature::CodexHooks));
|
|
assert_eq!(feature_for_key("codex_hooks"), Some(Feature::CodexHooks));
|
|
}
|
|
|
|
#[test]
|
|
fn multi_agent_is_stable_and_enabled_by_default() {
|
|
assert_eq!(Feature::Collab.stage(), Stage::Stable);
|
|
assert_eq!(Feature::Collab.default_enabled(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn enable_fanout_is_under_development() {
|
|
assert_eq!(Feature::SpawnCsv.stage(), Stage::UnderDevelopment);
|
|
assert_eq!(Feature::SpawnCsv.default_enabled(), false);
|
|
}
|
|
|
|
#[test]
|
|
fn enable_fanout_normalization_enables_multi_agent_one_way() {
|
|
let mut enable_fanout_features = Features::with_defaults();
|
|
enable_fanout_features.enable(Feature::SpawnCsv);
|
|
enable_fanout_features.normalize_dependencies();
|
|
assert_eq!(enable_fanout_features.enabled(Feature::SpawnCsv), true);
|
|
assert_eq!(enable_fanout_features.enabled(Feature::Collab), true);
|
|
|
|
let mut collab_features = Features::with_defaults();
|
|
collab_features.enable(Feature::Collab);
|
|
collab_features.normalize_dependencies();
|
|
assert_eq!(collab_features.enabled(Feature::Collab), true);
|
|
assert_eq!(collab_features.enabled(Feature::SpawnCsv), false);
|
|
}
|
|
|
|
#[test]
|
|
fn apps_require_feature_flag_and_chatgpt_auth() {
|
|
let mut features = Features::with_defaults();
|
|
assert!(!features.apps_enabled_for_auth(/*has_chatgpt_auth*/ false));
|
|
|
|
features.enable(Feature::Apps);
|
|
assert!(!features.apps_enabled_for_auth(/*has_chatgpt_auth*/ false));
|
|
assert!(features.apps_enabled_for_auth(/*has_chatgpt_auth*/ true));
|
|
}
|
|
|
|
#[test]
|
|
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 mut profile_entries = BTreeMap::new();
|
|
profile_entries.insert("code_mode_only".to_string(), true);
|
|
let profile_features = FeaturesToml {
|
|
entries: profile_entries,
|
|
..Default::default()
|
|
};
|
|
|
|
let features = Features::from_sources(
|
|
FeatureConfigSource {
|
|
features: Some(&base_features),
|
|
..Default::default()
|
|
},
|
|
FeatureConfigSource {
|
|
features: Some(&profile_features),
|
|
include_apply_patch_tool: Some(true),
|
|
..Default::default()
|
|
},
|
|
FeatureOverrides {
|
|
web_search_request: Some(false),
|
|
..Default::default()
|
|
},
|
|
);
|
|
|
|
assert_eq!(features.enabled(Feature::Plugins), true);
|
|
assert_eq!(features.enabled(Feature::CodeModeOnly), true);
|
|
assert_eq!(features.enabled(Feature::CodeMode), true);
|
|
assert_eq!(features.enabled(Feature::ApplyPatchFreeform), true);
|
|
assert_eq!(features.enabled(Feature::WebSearchRequest), false);
|
|
}
|
|
|
|
#[test]
|
|
fn from_sources_ignores_removed_image_detail_original_feature_key() {
|
|
let features_toml = FeaturesToml::from(BTreeMap::from([(
|
|
"image_detail_original".to_string(),
|
|
true,
|
|
)]));
|
|
|
|
let features = Features::from_sources(
|
|
FeatureConfigSource {
|
|
features: Some(&features_toml),
|
|
..Default::default()
|
|
},
|
|
FeatureConfigSource::default(),
|
|
FeatureOverrides::default(),
|
|
);
|
|
|
|
assert_eq!(features, Features::with_defaults());
|
|
}
|
|
|
|
#[test]
|
|
fn from_sources_ignores_removed_undo_feature_key() {
|
|
let features_toml = FeaturesToml::from(BTreeMap::from([("undo".to_string(), true)]));
|
|
|
|
let features = Features::from_sources(
|
|
FeatureConfigSource {
|
|
features: Some(&features_toml),
|
|
..Default::default()
|
|
},
|
|
FeatureConfigSource::default(),
|
|
FeatureOverrides::default(),
|
|
);
|
|
|
|
assert_eq!(features, Features::with_defaults());
|
|
}
|
|
|
|
#[test]
|
|
fn from_sources_ignores_removed_js_repl_feature_keys() {
|
|
let features_toml = FeaturesToml::from(BTreeMap::from([
|
|
("js_repl".to_string(), true),
|
|
("js_repl_tools_only".to_string(), true),
|
|
]));
|
|
|
|
let features = Features::from_sources(
|
|
FeatureConfigSource {
|
|
features: Some(&features_toml),
|
|
..Default::default()
|
|
},
|
|
FeatureConfigSource::default(),
|
|
FeatureOverrides::default(),
|
|
);
|
|
|
|
assert_eq!(features, Features::with_defaults());
|
|
}
|
|
|
|
#[test]
|
|
fn multi_agent_v2_feature_config_deserializes_boolean_toggle() {
|
|
let features: FeaturesToml = toml::from_str(
|
|
r#"
|
|
multi_agent_v2 = true
|
|
"#,
|
|
)
|
|
.expect("features table should deserialize");
|
|
|
|
assert_eq!(
|
|
features.entries(),
|
|
BTreeMap::from([("multi_agent_v2".to_string(), true)])
|
|
);
|
|
assert_eq!(features.multi_agent_v2, Some(FeatureToml::Enabled(true)));
|
|
}
|
|
|
|
#[test]
|
|
fn multi_agent_v2_feature_config_deserializes_table() {
|
|
let features: FeaturesToml = toml::from_str(
|
|
r#"
|
|
[multi_agent_v2]
|
|
enabled = true
|
|
max_concurrent_threads_per_session = 4
|
|
min_wait_timeout_ms = 2500
|
|
usage_hint_enabled = false
|
|
usage_hint_text = "Custom delegation guidance."
|
|
root_agent_usage_hint_text = "Root guidance."
|
|
subagent_usage_hint_text = "Subagent guidance."
|
|
hide_spawn_agent_metadata = true
|
|
"#,
|
|
)
|
|
.expect("features table should deserialize");
|
|
|
|
assert_eq!(
|
|
features.entries(),
|
|
BTreeMap::from([("multi_agent_v2".to_string(), true)])
|
|
);
|
|
assert_eq!(
|
|
features.multi_agent_v2,
|
|
Some(crate::FeatureToml::Config(crate::MultiAgentV2ConfigToml {
|
|
enabled: Some(true),
|
|
max_concurrent_threads_per_session: Some(4),
|
|
min_wait_timeout_ms: Some(2500),
|
|
usage_hint_enabled: Some(false),
|
|
usage_hint_text: Some("Custom delegation guidance.".to_string()),
|
|
root_agent_usage_hint_text: Some("Root guidance.".to_string()),
|
|
subagent_usage_hint_text: Some("Subagent guidance.".to_string()),
|
|
hide_spawn_agent_metadata: Some(true),
|
|
}))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn multi_agent_v2_feature_config_usage_hint_enabled_does_not_enable_feature() {
|
|
let features_toml: FeaturesToml = toml::from_str(
|
|
r#"
|
|
[multi_agent_v2]
|
|
usage_hint_enabled = false
|
|
"#,
|
|
)
|
|
.expect("features table should deserialize");
|
|
let features = Features::from_sources(
|
|
FeatureConfigSource {
|
|
features: Some(&features_toml),
|
|
..Default::default()
|
|
},
|
|
FeatureConfigSource::default(),
|
|
FeatureOverrides::default(),
|
|
);
|
|
|
|
assert_eq!(features.enabled(Feature::MultiAgentV2), false);
|
|
assert_eq!(features_toml.entries(), BTreeMap::new());
|
|
assert_eq!(
|
|
features_toml.multi_agent_v2,
|
|
Some(crate::FeatureToml::Config(crate::MultiAgentV2ConfigToml {
|
|
enabled: None,
|
|
max_concurrent_threads_per_session: None,
|
|
min_wait_timeout_ms: None,
|
|
usage_hint_enabled: Some(false),
|
|
usage_hint_text: None,
|
|
root_agent_usage_hint_text: None,
|
|
subagent_usage_hint_text: None,
|
|
hide_spawn_agent_metadata: None,
|
|
}))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config() {
|
|
let mut features = Features::with_defaults();
|
|
features.enable(Feature::CodeMode);
|
|
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)]),
|
|
..Default::default()
|
|
};
|
|
|
|
features_toml.materialize_resolved_enabled(&features);
|
|
|
|
let entries = features_toml.entries();
|
|
assert_eq!(entries.get("include_apply_patch_tool"), None);
|
|
for spec in crate::FEATURES {
|
|
assert_eq!(
|
|
entries.get(spec.key),
|
|
Some(&features.enabled(spec.id)),
|
|
"{}",
|
|
spec.key
|
|
);
|
|
}
|
|
assert_eq!(
|
|
features_toml.multi_agent_v2,
|
|
Some(FeatureToml::Config(crate::MultiAgentV2ConfigToml {
|
|
enabled: Some(true),
|
|
min_wait_timeout_ms: Some(2500),
|
|
..Default::default()
|
|
}))
|
|
);
|
|
let replayed = Features::from_sources(
|
|
FeatureConfigSource {
|
|
features: Some(&features_toml),
|
|
..Default::default()
|
|
},
|
|
FeatureConfigSource::default(),
|
|
FeatureOverrides::default(),
|
|
);
|
|
assert_eq!(replayed.enabled(Feature::ApplyPatchFreeform), false);
|
|
}
|
|
|
|
#[test]
|
|
fn unstable_warning_event_only_mentions_enabled_under_development_features() {
|
|
let mut configured_features = Table::new();
|
|
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));
|
|
|
|
let mut features = Features::with_defaults();
|
|
features.enable(Feature::ChildAgentsMd);
|
|
|
|
let warning = unstable_features_warning_event(
|
|
Some(&configured_features),
|
|
/*suppress_unstable_features_warning*/ false,
|
|
&features,
|
|
"/tmp/config.toml",
|
|
)
|
|
.expect("warning event");
|
|
|
|
let EventMsg::Warning(WarningEvent { message }) = warning.msg else {
|
|
panic!("expected warning event");
|
|
};
|
|
assert!(message.contains("child_agents_md"));
|
|
assert!(!message.contains("personality"));
|
|
assert!(message.contains("/tmp/config.toml"));
|
|
}
|