feat: experimental menu (#8071)

This will automatically render any `Stage::Beta` features.

The change only gets applied to the *next session*. This started as a
bug but actually this is a good thing to prevent out of distribution
push

<img width="986" height="288" alt="Screenshot 2025-12-15 at 15 38 35"
src="https://github.com/user-attachments/assets/78b7a71d-0e43-4828-a118-91c5237909c7"
/>


<img width="509" height="109" alt="Screenshot 2025-12-15 at 17 35 44"
src="https://github.com/user-attachments/assets/6933de52-9b66-4abf-b58b-a5f26d5747e2"
/>
This commit is contained in:
jif-oai
2025-12-17 17:08:03 +00:00
committed by GitHub
parent 9352c6b235
commit ac6ba286aa
15 changed files with 577 additions and 38 deletions

View File

@@ -23,6 +23,7 @@ use codex_ansi_escape::ansi_escape_line;
use codex_core::AuthManager;
use codex_core::ConversationManager;
use codex_core::config::Config;
use codex_core::config::edit::ConfigEdit;
use codex_core::config::edit::ConfigEditsBuilder;
#[cfg(target_os = "windows")]
use codex_core::features::Feature;
@@ -944,6 +945,42 @@ impl App {
}
}
}
AppEvent::UpdateFeatureFlags { updates } => {
if updates.is_empty() {
return Ok(true);
}
let mut builder = ConfigEditsBuilder::new(&self.config.codex_home)
.with_profile(self.active_profile.as_deref());
for (feature, enabled) in &updates {
let feature_key = feature.key();
if *enabled {
// Update the in-memory configs.
self.config.features.enable(*feature);
self.chat_widget.set_feature_enabled(*feature, true);
builder = builder.set_feature_enabled(feature_key, true);
} else {
// Update the in-memory configs.
self.config.features.disable(*feature);
self.chat_widget.set_feature_enabled(*feature, false);
if feature.default_enabled() {
builder = builder.set_feature_enabled(feature_key, false);
} else {
// If the feature already default to `false`, we drop the key
// in the config file so that the user does not miss the feature
// once it gets globally released.
builder = builder.with_edits(vec![ConfigEdit::ClearPath {
segments: vec!["features".to_string(), feature_key.to_string()],
}]);
}
}
}
if let Err(err) = builder.apply().await {
tracing::error!(error = %err, "failed to persist feature flags");
self.chat_widget.add_error_message(format!(
"Failed to update experimental features: {err}"
));
}
}
AppEvent::SkipNextWorldWritableScan => {
self.skip_world_writable_scan_once = true;
}