//! Centralized feature flags and metadata. //! //! This crate defines the feature registry plus the logic used to resolve an //! effective feature set from config-like inputs. use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::protocol::Event; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::WarningEvent; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; use std::collections::BTreeMap; use std::collections::BTreeSet; use toml::Table; mod feature_configs; mod legacy; pub use feature_configs::CodeModeConfigToml; pub use feature_configs::CodeModeHostConfigToml; pub use feature_configs::ContextManagementConfigToml; pub use feature_configs::CurrentTimeReminderConfigToml; pub use feature_configs::CurrentTimeReminderDeliveryMode; pub use feature_configs::CurrentTimeSource; pub use feature_configs::GuardianV2ConfigToml; pub use feature_configs::GuardianV2ReviewScopeConfigToml; pub use feature_configs::GuardianV2TranscriptConfigToml; pub use feature_configs::GuardianV2TranscriptSource; pub use feature_configs::MultiAgentV2ConfigToml; pub use feature_configs::NetworkProxyConfigToml; pub use feature_configs::NetworkProxyDomainPermissionToml; pub use feature_configs::NetworkProxyModeToml; pub use feature_configs::NetworkProxyUnixSocketPermissionToml; pub use feature_configs::NonPrefixedMcpToolNamesConfigToml; pub use feature_configs::RemoteMessageBoardConfigToml; use feature_configs::RemovedAppsMcpPathOverrideConfigToml; pub use feature_configs::RolloutBudgetConfigToml; pub use feature_configs::SleepToolConfigToml; pub use feature_configs::SleepToolMode; pub use feature_configs::TokenBudgetConfigToml; pub use feature_configs::ToolRegistryConfigToml; use legacy::LegacyFeatureToggles; pub use legacy::legacy_feature_keys; /// High-level lifecycle stage for a feature. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Stage { /// Features that are still under development, not ready for external use UnderDevelopment, /// Experimental features made available to users through the `/experimental` menu Experimental { name: &'static str, menu_description: &'static str, announcement: &'static str, }, /// Stable features. The feature flag is kept for ad-hoc enabling/disabling Stable, /// Deprecated feature that should not be used anymore. Deprecated, /// The feature flag is useless but kept for backward compatibility reason. Removed, } impl Stage { pub fn experimental_menu_name(self) -> Option<&'static str> { match self { Stage::Experimental { name, .. } => Some(name), Stage::UnderDevelopment | Stage::Stable | Stage::Deprecated | Stage::Removed => None, } } pub fn experimental_menu_description(self) -> Option<&'static str> { match self { Stage::Experimental { menu_description, .. } => Some(menu_description), Stage::UnderDevelopment | Stage::Stable | Stage::Deprecated | Stage::Removed => None, } } pub fn experimental_announcement(self) -> Option<&'static str> { match self { Stage::Experimental { announcement: "", .. } => None, Stage::Experimental { announcement, .. } => Some(announcement), Stage::UnderDevelopment | Stage::Stable | Stage::Deprecated | Stage::Removed => None, } } } /// Unique features toggled via configuration. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Feature { /// Preview consumer five-hour and weekly allowance history. AnalyticsPlanHistory, /// Discover model catalogs for OpenAI API-key authentication. ApiKeyModelDiscovery, /// Forward explicit programs with builtin OpenAI API keys. ApiKeyCyberAccessPrograms, /// Enable Daybreak controls and automatic access-program selection in CLI clients. CliDaybreak, /// Deprecated no-op; use `tui.fullscreen_transcript` instead. TranscriptV2, // Stable. /// Enable the default shell tool. ShellTool, /// Enable the built-in local image viewer. ViewImage, /// Allow registration of the built-in sleep tool. SleepTool, /// Enable Claude-style lifecycle hooks loaded from hooks.json files. CodexHooks, /// Store CLI auth in the encrypted local secrets backend when keyring storage is selected. SecretAuthStorage, /// Automatically start the shared local daemon for eligible interactive launches. DaemonAutoStart, // Experimental /// Send per-content-entry classifications in internal Responses metadata. ContentItemKinds, /// Record model-attempted tool calls in internal Responses metadata. ExecutedToolCallMetadata, /// Enable JavaScript code mode backed by the standalone host process. CodeMode, /// Removed compatibility flag for the configurable code-mode exec yield timeout. CodeModeBufferedExec, /// Run JavaScript code mode in the standalone host process. CodeModeHost, /// Establish the code-mode host connection during session startup. CodeModePrewarm, /// Terminate active code mode cells when their turn is interrupted. CodeModeInterrupt, /// Restrict model-visible tools to code mode entrypoints (`exec`, `wait`). CodeModeOnly, /// Use the single unified PTY-backed exec tool. UnifiedExec, /// Allow unified exec commands to allocate an interactive terminal. UnifiedExecTty, /// Route shell tool execution through the zsh exec bridge. ShellZshFork, /// Allow unified exec to compose with the zsh exec bridge. /// /// This flag is only a composition gate. Enabling it by itself must not turn /// on either `unified_exec` or `shell_zsh_fork` because those features have /// separate rollout and enterprise controls. UnifiedExecZshFork, /// Removed compatibility flag. Transcript scrollback reflow on terminal resize is always on. TerminalResizeReflow, /// Add terminal-specific visualization guidance to TUI developer instructions. TerminalVisualizationInstructions, /// Stream structured progress while apply_patch input is being generated. ApplyPatchStreamingEvents, /// Preserve existing line endings when apply_patch updates files. ApplyPatchPreserveLineEndings, /// Allow exec tools to request additional permissions while staying sandboxed. ExecPermissionApprovals, /// Require approval before writing input to escalated unified-exec terminals. WriteStdinApproval, /// Expose the built-in request_permissions tool. RequestPermissionsTool, /// Allow the model to request web searches that fetch live content. WebSearchRequest, /// Allow the model to request web searches that fetch cached content. /// Takes precedence over `WebSearchRequest`. WebSearchCached, /// Expose the extension-backed standalone web search tool. StandaloneWebSearch, /// Use the legacy Landlock Linux sandbox fallback instead of the default /// bubblewrap pipeline. UseLegacyLandlock, /// Experimental shell snapshotting. ShellSnapshot, /// Restore bundled tools to PATH after Codex's login shell starts. LoginShellPackagePath, /// Expose the selected PowerShell execution host's bounded major/minor version. PowerShellShellVersion, /// Keep policy-filtered shell snapshots entirely in executor memory. ShellSnapshotV2, /// Allow turns to start while selected executors are still starting. DeferredExecutor, /// Use the current working directory for turn diff display paths. CwdRelativeTurnDiffs, /// Enable runtime metrics snapshots via a manual reader. RuntimeMetrics, /// Enable startup memory extraction and file-backed memory consolidation. MemoryTool, /// Enable importing project-scoped memory from external agents. ExternalAgentMemoryImport, /// Compress cold local thread-store rollout files, including shared histories. /// Requires every reader of the Codex home to support compressed shared histories. LocalThreadStoreCompression, /// Removed compatibility flag; local_thread_store_compression controls all rollout files. LocalThreadStoreSharedCompression, /// Migrate legacy local rollout files to paginated history in the background. BackgroundPaginatedRolloutMigration, /// Enable the Chronicle sidecar for passive screen-context memories. Chronicle, /// Compress request bodies (zstd) when sending streaming requests to codex-backend. EnableRequestCompression, /// Keep active sampling turns alive until a failed network connection recovers. UnboundedConnectionRetries, /// Start the managed network proxy for sandboxed sessions. NetworkProxy, /// Enable managed worktree creation and repository-aware sessions. Worktrees, /// Respect host system proxy settings for Codex-owned network clients. RespectSystemProxy, /// Retry eligible bootstrap requests through the system proxy after normal routing fails. SystemProxyFallback, /// Enable collab tools. Collab, /// Enable task-path-based multi-agent routing. MultiAgentV2, /// Keep spawn model choices in append-only context instead of tool descriptions. ModelCatalogInContext, /// Keep sampling through reasoning and commentary boundaries when agent mail arrives. /// Pending mail is delivered at the next normal input boundary instead. DeferMailboxPreemption, /// Preempt responses and yield foreground code-mode observations on new user input. InstantInterrupt, /// Enable shared discussion tools for an agent tree. AgentMessageBoard, /// Removed compatibility flag retained as a no-op. MultiAgentMode, /// Removed compatibility flag for the deleted agent-job tools. SpawnCsv, /// Enable apps. Apps, /// Route first-party ChatGPT requests through PSP. Psp, /// Enable MCP apps. EnableMcpApps, /// Enable MCP protocol version 2026-07-28 support. Mcp20260728, /// Enable MCP protocol version 2026-07-28 for the host-owned Codex Apps server. CodexAppsMcp20260728, /// Let RMCP coordinate OAuth refresh through the configured credential store. McpOAuthRefreshCoordination, /// Enable enterprise refresh-token authorization for configured MCP resources. UseXaa, /// Removed compatibility flag for the legacy Apps MCP path override. AppsMcpPathOverride, /// Removed compatibility flag retained as a no-op now that tool_search is always enabled. ToolSearch, /// Removed compatibility flag. MCP tools are always deferred when tool_search is available. ToolSearchAlwaysDeferMcpTools, /// Describe deferred tool namespaces in the model-visible world state. DeferredToolWorldState, /// Expose MCP model-visible namespaces without the legacy `mcp__` prefix. NonPrefixedMcpToolNames, /// Enable discoverable tool suggestions for apps. ToolSuggest, /// Include recommended plugins in model-visible context. RecommendedPlugins, /// Enable plugins. Plugins, /// Discover selected-root plugin and skill manifests through one high-level exec-server RPC. ExecutorCapabilityDiscovery, /// Skip host skill snapshots when no registered contributor requires them. SkipHostSkillDiscovery, /// Removed compatibility flag for plugin-bundled lifecycle hooks. PluginHooks, /// Allow the in-app browser pane in desktop apps. /// /// Requirements-only gate: this should be set from requirements, not user config. InAppBrowser, /// Allow websites to open and customize annotation tools in desktop apps. /// Ordinary user-driven annotation is independent of this gate. /// /// Requirements-only gate: this should be set from requirements, not user config. BrowserAnnotationApi, /// Allow the in-app chat pane in desktop apps. /// /// Requirements-only gate: this should be set from requirements, not user config. InAppChat, /// Allow in-app dictation in desktop apps. /// /// Requirements-only gate: this should be set from requirements, not user config. InAppDictation, /// Allow in-app Voice in desktop apps. /// /// Requirements-only gate: this should be set from requirements, not user config. /// Permission does not establish Voice availability or provider support. InAppVoice, /// Allow desktop apps to run local automations. /// /// Requirements-only gate: this should be set from requirements, not user config. InAppLocalAutomation, /// Allow desktop apps to perform in-app updates. /// /// Requirements-only gate: this should be set from requirements, not user config. InAppUpdates, /// Allow Browser Use agent integration in desktop apps. /// /// Requirements-only gate: this should be set from requirements, not user config. BrowserUse, /// Allow Browser Use integration to access the full Chrome DevTools Protocol surface. /// /// Requirements-only gate: this should be set from requirements, not user config. BrowserUseFullCdpAccess, /// Allow Browser Use integration with external browsers. /// /// Requirements-only gate: this should be set from requirements, not user config. BrowserUseExternal, /// Allow Codex Computer Use. /// /// Requirements-only gate: this should be set from requirements, not user config. ComputerUse, /// Enable the PS-backed remote plugin catalog. RemotePlugin, /// Enable remote plugin sharing flows. PluginSharing, /// Removed compatibility flag retained as a no-op. ExternalMigration, /// Enable extension-backed image generation. ImageGeneration, /// Omit inline image and audio content from app-server item notifications. OmitAppServerNotificationMedia, /// Tell the model when a prompt image was resized and include its dimensions. ImageResizeNotice, /// Apply one shared pixel and token budget to every image, regardless of legacy detail hints. UnifiedImageBudget, /// Removed compatibility flag for always-on centralized image preparation. ResizeAllImages, /// Removed compatibility flag for always-on response item IDs. ItemIds, /// Request sequential cutoff reasoning summary delivery. ConcurrentReasoningSummaries, /// Allow prompting and installing missing MCP dependencies. SkillMcpDependencyInstall, /// Run cheap skill-search methods in shadow mode and emit experiment metrics. SkillSearch, /// Removed compatibility flag for deleted skill env var dependency prompting. SkillEnvVarDependencyPrompt, /// Enable the unified mention popup used by default in the TUI. MentionsV2, /// Allow request_user_input in Default collaboration mode. DefaultModeRequestUserInput, /// Removed compatibility flag for model-enabled async user messaging. SendAsyncMessage, /// Allow root agents to send async user messages without model catalog support. SendMessageToUserAsync, /// Enable automatic review for approval prompts. GuardianApproval, /// Removed compatibility flag for always-on thread-owned Guardian context. GuardianThreadContext, /// Reuse encrypted parent compaction when restarting Guardian review sessions. /// When disabled, retain an independent review transcript across parent compaction. GuardianReuseParentCompaction, /// Limit worker Guardian root evidence to preceding root communication windows. GuardianRootHandoffContext, /// Include completed node_repl or cua_repl Code Mode responses in Guardian reviews. GuardianEnhancedNodeReplTranscripts, /// Include completed node_repl or cua_repl Code Mode response images in Guardian reviews. GuardianNodeReplTranscriptImages, /// Give Guardian access to the root conversation's message history tools. GuardianConversationHistoryTools, /// Enable Guardian V2 automatic approval reviews. GuardianV2, /// Removed compatibility flag for the unused Guardian extension prototype. GuardianExt, /// Enable persisted thread goals and automatic goal continuation. Goals, /// Add current context-window metadata to model-visible context. TokenBudget, /// Enables experimental context management. ContextManagement, /// Track and report a shared token budget across a session's agent threads. RolloutBudget, /// Append trusted response configuration items when the selected reasoning effort changes. ReasoningEffortOverride, /// Add current-time reminders to model-visible context. CurrentTimeReminder, /// Report failed clock reads to the model without failing the turn. NonfatalClockReadErrors, /// Route MCP tool approval prompts through the MCP elicitation request path. ToolCallMcpElicitation, /// Prompt Codex Apps connector auth failures through MCP URL elicitations. AuthElicitation, /// Offer Amazon Bedrock setup during TUI sign-in onboarding. BedrockSetupWizard, /// Removed compatibility flag retained as a no-op. Personality, /// Enable native artifact tools. Artifact, /// Enable Fast mode selection in the TUI and request layer. FastMode, /// Enable explicitly requested model changes for later step captures. StepModelSwitching, /// Enable voice conversations in the TUI. RealtimeConversation, /// Prevent idle system sleep while a turn is actively running. PreventIdleSleep, /// Removed compatibility key, still advertised to the Responses API. RemoteCompactionV2, /// Include retained images in the remote compaction context budget. CompactionImageBudget, /// Retain client-authored developer messages across compacted context windows. RetainClientDeveloperMessages, /// Use Agent Identity for ChatGPT-authenticated sessions. UseAgentIdentity, /// Enable workspace dependency support. WorkspaceDependencies, // Removed /// Removed compatibility flag retained as a no-op so old configs can /// still parse `undo`. GhostCommit, /// Removed compatibility flag for the deleted JavaScript REPL feature. JsRepl, /// Removed compatibility flag for the deleted JavaScript REPL tool-only mode. JsReplToolsOnly, /// Legacy search-tool feature flag kept for backward compatibility. SearchTool, /// Removed legacy Linux bubblewrap opt-in flag retained as a no-op so old /// wrappers and config can still parse it. UseLinuxSandboxBwrap, /// Allow the model to request approval and propose exec rules. RequestRule, /// Enable Windows sandbox (restricted token) on Windows. WindowsSandbox, /// Use the elevated Windows sandbox pipeline (setup + runner). WindowsSandboxElevated, /// Attempt elevated Windows sandbox provisioning through the installed service. WindowsSandboxService, /// Prefer the local native Windows sandbox when available, retaining legacy fallback. PreferMxc, /// Legacy remote models flag kept for backward compatibility. RemoteModels, /// Removed legacy git commit attribution guidance flag. CodexGitCommit, /// Persist rollout metadata to a local SQLite database. Sqlite, /// Removed compatibility flag for the deleted apply_patch fallback feature. ApplyPatchFreeform, /// Removed compatibility flag for the deleted unavailable-tool placeholder backfill. UnavailableDummyTools, /// Steer feature flag - when enabled, Enter submits immediately instead of queuing. /// Kept for config backward compatibility; behavior is always steer-enabled. Steer, /// Enable collaboration modes (Plan, Default). /// Kept for config backward compatibility; behavior is always collaboration-modes-enabled. CollaborationModes, /// Removed compatibility flag for the deleted remote control feature. RemoteControl, /// Removed compatibility flag retained as a no-op so old wrappers can /// still pass `--enable image_detail_original`. ImageDetailOriginal, /// Removed compatibility flag. The TUI now always uses the app-server implementation. TuiAppServer, /// Removed compatibility flag retained as a no-op now that workspace owner /// usage nudges are always enabled. WorkspaceOwnerUsageNudge, /// Legacy rollout flag for Responses API WebSocket transport experiments. ResponsesWebsockets, /// Legacy rollout flag for Responses API WebSocket transport v2 experiments. ResponsesWebsocketsV2, } impl Feature { pub fn key(self) -> &'static str { self.info().key } pub fn stage(self) -> Stage { self.info().stage } pub fn default_enabled(self) -> bool { self.info().default_enabled } fn info(self) -> &'static FeatureSpec { FEATURES .iter() .find(|spec| spec.id == self) .unwrap_or_else(|| unreachable!("missing FeatureSpec for {self:?}")) } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct LegacyFeatureUsage { pub alias: String, pub feature: Feature, pub summary: String, pub details: Option, } /// Holds the effective set of enabled features. #[derive(Debug, Clone, Default, PartialEq)] pub struct Features { enabled: BTreeSet, legacy_usages: BTreeSet, } #[derive(Debug, Clone, Default)] pub struct FeatureOverrides { pub web_search_request: Option, } #[derive(Debug, Clone, Copy, Default)] pub struct FeatureConfigSource<'a> { pub features: Option<&'a FeaturesToml>, pub experimental_use_unified_exec_tool: Option, } impl FeatureOverrides { fn apply(self, features: &mut Features) { if let Some(enabled) = self.web_search_request { if enabled { features.enable(Feature::WebSearchRequest); } else { features.disable(Feature::WebSearchRequest); } features.record_legacy_usage("web_search_request", Feature::WebSearchRequest); } } } impl Features { /// Starts with built-in defaults. pub fn with_defaults() -> Self { let mut set = BTreeSet::new(); for spec in FEATURES { if spec.default_enabled { set.insert(spec.id); } } Self { enabled: set, legacy_usages: BTreeSet::new(), } } pub fn enabled(&self, f: Feature) -> bool { self.enabled.contains(&f) } /// Returns whether persistent execution is enabled for the selected effort. pub fn persistent_execution_enabled(&self, reasoning_effort: Option<&ReasoningEffort>) -> bool { reasoning_effort == Some(&ReasoningEffort::Persistent) } pub fn apps_enabled_for_auth(&self, has_chatgpt_auth: bool) -> bool { self.enabled(Feature::Apps) && has_chatgpt_auth } pub fn plugin_recommendations_enabled(&self) -> bool { self.enabled(Feature::Apps) && self.enabled(Feature::Plugins) && (self.enabled(Feature::ToolSuggest) || self.enabled(Feature::RecommendedPlugins)) } pub fn use_legacy_landlock(&self) -> bool { self.enabled(Feature::UseLegacyLandlock) } pub fn enable(&mut self, f: Feature) -> &mut Self { self.enabled.insert(f); self } pub fn disable(&mut self, f: Feature) -> &mut Self { self.enabled.remove(&f); self } pub fn set_enabled(&mut self, f: Feature, enabled: bool) -> &mut Self { if enabled { self.enable(f) } else { self.disable(f) } } pub fn record_legacy_usage_force(&mut self, alias: &str, feature: Feature) { let (summary, details) = legacy_usage_notice(alias, feature); self.legacy_usages.insert(LegacyFeatureUsage { alias: alias.to_string(), feature, summary, details, }); } pub fn record_legacy_usage(&mut self, alias: &str, feature: Feature) { if alias == feature.key() { return; } self.record_legacy_usage_force(alias, feature); } pub fn legacy_feature_usages(&self) -> impl Iterator + '_ { self.legacy_usages.iter() } /// Apply a table of key -> bool toggles (e.g. from TOML). pub fn apply_map(&mut self, m: &BTreeMap) { for (k, v) in m { match k.as_str() { "web_search_request" => { self.record_legacy_usage_force( "features.web_search_request", Feature::WebSearchRequest, ); } "web_search_cached" => { self.record_legacy_usage_force( "features.web_search_cached", Feature::WebSearchCached, ); } "transcript_v2" => { self.record_legacy_usage_force("features.transcript_v2", Feature::TranscriptV2); continue; } "tui_app_server" => { continue; } "undo" => { continue; } "js_repl" => { continue; } "js_repl_tools_only" => { continue; } "remote_control" | "remote_compaction_v2" => { continue; } "apply_patch_freeform" => { continue; } "tool_search" | "tool_search_always_defer_mcp_tools" | "apps_mcp_path_override" => { continue; } "image_detail_original" | "resize_all_images" | "item_ids" => { continue; } "personality" | "plugin_hooks" => { continue; } "skill_env_var_dependency_prompt" => { continue; } "terminal_resize_reflow" => { continue; } "use_legacy_landlock" => { self.record_legacy_usage_force( "features.use_legacy_landlock", Feature::UseLegacyLandlock, ); } "guardianv2.thread_context" => { self.record_legacy_usage_force( "features.guardianv2.thread_context", Feature::GuardianThreadContext, ); continue; } _ => {} } if k == "imagegenext" && m.contains_key(Feature::ImageGeneration.key()) { self.record_legacy_usage(k, Feature::ImageGeneration); continue; } match feature_for_key(k) { Some(feat) => { if matches!(feat, Feature::TuiAppServer) { continue; } if k != feat.key() { self.record_legacy_usage(k.as_str(), feat); } if *v { self.enable(feat); } else { self.disable(feat); } } None => { tracing::warn!("unknown feature key in config: {k}"); } } } } pub fn from_sources( base: FeatureConfigSource<'_>, profile: FeatureConfigSource<'_>, overrides: FeatureOverrides, ) -> Self { let mut features = Features::with_defaults(); for source in [base, profile] { LegacyFeatureToggles { experimental_use_unified_exec_tool: source.experimental_use_unified_exec_tool, } .apply(&mut features); if let Some(feature_entries) = source.features { features.apply_toml(feature_entries); } } overrides.apply(&mut features); features.normalize_dependencies(); features } pub fn enabled_features(&self) -> Vec { self.enabled.iter().copied().collect() } pub fn normalize_dependencies(&mut self) { if self.enabled(Feature::CodeModeOnly) && !self.enabled(Feature::CodeMode) { self.enable(Feature::CodeMode); } } } fn legacy_usage_notice(alias: &str, feature: Feature) -> (String, Option) { let canonical = feature.key(); match feature { Feature::GuardianThreadContext => ( "`[features.guardianv2].thread_context` is deprecated and ignored.".to_string(), Some("Thread-owned Guardian context is always enabled. Remove `thread_context` from [features.guardianv2] in config.toml, including profile overrides.".to_string()), ), Feature::TranscriptV2 => ( "`[features].transcript_v2` is deprecated and ignored.".to_string(), Some("Use `[tui].fullscreen_transcript` in config.toml instead.".to_string()), ), Feature::WebSearchRequest | Feature::WebSearchCached => { let label = match alias { "web_search" => "[features].web_search", "features.web_search_request" | "web_search_request" => { "[features].web_search_request" } "features.web_search_cached" | "web_search_cached" => { "[features].web_search_cached" } _ => alias, }; let summary = format!("`{label}` is deprecated because web search is enabled by default."); (summary, Some(web_search_details().to_string())) } Feature::UseLegacyLandlock => { let label = match alias { "features.use_legacy_landlock" | "use_legacy_landlock" => { "[features].use_legacy_landlock" } _ => alias, }; let summary = format!("`{label}` is deprecated and will be removed soon."); let details = "Remove this setting to stop opting into the legacy Linux sandbox behavior." .to_string(); (summary, Some(details)) } _ => { let label = if alias.contains('.') || alias.starts_with('[') { alias.to_string() } else { format!("[features].{alias}") }; let summary = format!("`{label}` is deprecated. Use `[features].{canonical}` instead."); let details = if alias == canonical { None } else { Some(format!( "Enable it with `--enable {canonical}` or `[features].{canonical}` in config.toml. See https://developers.openai.com/codex/config-basic#feature-flags for details." )) }; (summary, details) } } } fn web_search_details() -> &'static str { "Set `web_search` to `\"live\"`, `\"indexed\"`, `\"cached\"`, or `\"disabled\"` at the top level (or under a profile) in config.toml if you want to override it." } /// Keys accepted in `[features]` tables. pub fn feature_for_key(key: &str) -> Option { for spec in FEATURES { if spec.key == key { return Some(spec.id); } } legacy::feature_for_key(key) } pub fn canonical_feature_for_key(key: &str) -> Option { FEATURES .iter() .find(|spec| spec.key == key) .map(|spec| spec.id) } /// Returns `true` if the provided string matches a known `[features]` key. pub fn is_known_feature_key(key: &str) -> bool { key == "tool_registry" || feature_for_key(key).is_some() } /// Deserializable features table for TOML. #[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)] pub struct FeaturesToml { #[serde(default, skip_serializing_if = "Option::is_none")] pub tool_registry: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub code_mode: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub code_mode_host: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub non_prefixed_mcp_tool_names: Option>, #[serde( default, skip_serializing_if = "Option::is_none", deserialize_with = "feature_configs::deserialize_guardian_v2_feature" )] pub guardianv2: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub multi_agent_v2: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub token_budget: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub context_management: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub rollout_budget: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub current_time_reminder: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub sleep_tool: Option>, #[serde(default, rename = "apps_mcp_path_override", skip_serializing)] #[schemars(skip)] removed_apps_mcp_path_override: Option>, pub network_proxy: Option>, /// Boolean feature toggles keyed by canonical or legacy feature name. #[serde(flatten)] entries: BTreeMap, } impl Features { fn apply_toml(&mut self, features: &FeaturesToml) { let entries = features.entries(); self.apply_map(&entries); } } impl FeaturesToml { pub fn entries(&self) -> BTreeMap { let mut entries = self.entries.clone(); if let Some(enabled) = self.code_mode.as_ref().and_then(FeatureToml::enabled) { entries.insert(Feature::CodeMode.key().to_string(), enabled); } if let Some(enabled) = self.code_mode_host.as_ref().and_then(FeatureToml::enabled) { entries.insert(Feature::CodeModeHost.key().to_string(), enabled); } if let Some(enabled) = self .non_prefixed_mcp_tool_names .as_ref() .and_then(FeatureToml::enabled) { entries.insert(Feature::NonPrefixedMcpToolNames.key().to_string(), enabled); } if let Some(enabled) = self.guardianv2.as_ref().and_then(FeatureToml::enabled) { entries.insert(Feature::GuardianV2.key().to_string(), enabled); } if let Some(FeatureToml::Config(config)) = &self.guardianv2 && let Some(enabled) = config.thread_context { entries.insert(Feature::GuardianThreadContext.key().to_string(), enabled); } if let Some(enabled) = self.multi_agent_v2.as_ref().and_then(FeatureToml::enabled) { entries.insert(Feature::MultiAgentV2.key().to_string(), enabled); } if let Some(enabled) = self.token_budget.as_ref().and_then(FeatureToml::enabled) { entries.insert(Feature::TokenBudget.key().to_string(), enabled); } if let Some(enabled) = self .context_management .as_ref() .and_then(FeatureToml::enabled) { entries.insert(Feature::ContextManagement.key().to_string(), enabled); } if let Some(enabled) = self.rollout_budget.as_ref().and_then(FeatureToml::enabled) { entries.insert(Feature::RolloutBudget.key().to_string(), enabled); } if let Some(enabled) = self .current_time_reminder .as_ref() .and_then(FeatureToml::enabled) { entries.insert(Feature::CurrentTimeReminder.key().to_string(), enabled); } if let Some(enabled) = self.network_proxy.as_ref().and_then(FeatureToml::enabled) { entries.insert(Feature::NetworkProxy.key().to_string(), enabled); } if let Some(enabled) = self.sleep_tool.as_ref().and_then(FeatureToml::enabled) { entries.insert(Feature::SleepTool.key().to_string(), enabled); } entries } } impl From> for FeaturesToml { fn from(entries: BTreeMap) -> Self { Self { entries, ..Default::default() } } } // 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)] #[serde(untagged)] pub enum FeatureToml { Enabled(bool), Config(T), } impl FeatureToml { pub fn enabled(&self) -> Option { match self { Self::Enabled(enabled) => Some(*enabled), Self::Config(config) => config.enabled(), } } } // A trait to be implemented by custom feature config structs when defining a feature that needs more configuration than // just enabled/disabled. pub trait FeatureConfig { fn enabled(&self) -> Option; } /// Single, easy-to-read registry of all feature definitions. #[derive(Debug, Clone, Copy)] pub struct FeatureSpec { pub id: Feature, pub key: &'static str, pub stage: Stage, pub default_enabled: bool, } pub const FEATURES: &[FeatureSpec] = &[ FeatureSpec { id: Feature::AnalyticsPlanHistory, key: "analytics_plan_history", stage: Stage::Experimental { name: "Analytics plan history", menu_description: "Preview five-hour and weekly allowance history for consumer accounts in /analytics.", announcement: "", }, default_enabled: false, }, FeatureSpec { id: Feature::DaemonAutoStart, key: "daemon_auto_start", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::TranscriptV2, key: "transcript_v2", stage: Stage::Deprecated, default_enabled: false, }, // Stable features. FeatureSpec { id: Feature::GhostCommit, key: "undo", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::ShellTool, key: "shell_tool", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::ViewImage, key: "view_image", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::SleepTool, key: "sleep_tool", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::SecretAuthStorage, key: "secret_auth_storage", stage: Stage::Stable, default_enabled: cfg!(windows), }, FeatureSpec { id: Feature::UnifiedExec, key: "unified_exec", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::UnifiedExecTty, key: "unified_exec_tty", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::ShellZshFork, key: "shell_zsh_fork", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::UnifiedExecZshFork, key: "unified_exec_zsh_fork", stage: Stage::Removed, default_enabled: true, }, FeatureSpec { id: Feature::ShellSnapshot, key: "shell_snapshot", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::LoginShellPackagePath, key: "login_shell_package_path", stage: Stage::Experimental { name: "Bundled tools in login shells", menu_description: "Keep bundled tools such as ripgrep available when login shell startup resets PATH.", announcement: "", }, default_enabled: false, }, FeatureSpec { id: Feature::PowerShellShellVersion, key: "powershell_shell_version", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::ShellSnapshotV2, key: "shell_snapshot_v2", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::DeferredExecutor, key: "deferred_executor", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::CwdRelativeTurnDiffs, key: "cwd_relative_turn_diffs", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::JsRepl, key: "js_repl", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::ContentItemKinds, key: "content_item_kinds", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::ExecutedToolCallMetadata, key: "executed_tool_call_metadata", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::CodeMode, key: "code_mode", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::CodeModeBufferedExec, key: "code_mode_buffered_exec", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::CodeModeHost, key: "code_mode_host", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::CodeModePrewarm, key: "code_mode_prewarm", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::CodeModeInterrupt, key: "code_mode_interrupt", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::InstantInterrupt, key: "instant_interrupt", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::CodeModeOnly, key: "code_mode_only", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::JsReplToolsOnly, key: "js_repl_tools_only", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::TerminalResizeReflow, key: "terminal_resize_reflow", stage: Stage::Removed, default_enabled: true, }, FeatureSpec { id: Feature::WebSearchRequest, key: "web_search_request", stage: Stage::Deprecated, default_enabled: false, }, FeatureSpec { id: Feature::WebSearchCached, key: "web_search_cached", stage: Stage::Deprecated, default_enabled: false, }, FeatureSpec { id: Feature::StandaloneWebSearch, key: "standalone_web_search", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::SearchTool, key: "search_tool", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::CodexGitCommit, key: "codex_git_commit", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::RuntimeMetrics, key: "runtime_metrics", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::Sqlite, key: "sqlite", stage: Stage::Removed, default_enabled: true, }, FeatureSpec { id: Feature::MemoryTool, key: "memories", stage: Stage::Stable, default_enabled: false, }, FeatureSpec { id: Feature::ExternalAgentMemoryImport, key: "external_agent_memory_import", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::LocalThreadStoreCompression, key: "local_thread_store_compression", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::LocalThreadStoreSharedCompression, key: "local_thread_store_shared_compression", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::BackgroundPaginatedRolloutMigration, key: "background_paginated_rollout_migration", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::Chronicle, key: "chronicle", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::ApplyPatchFreeform, key: "apply_patch_freeform", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::ApplyPatchStreamingEvents, key: "apply_patch_streaming_events", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::ApplyPatchPreserveLineEndings, key: "apply_patch_preserve_line_endings", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::ExecPermissionApprovals, key: "exec_permission_approvals", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::WriteStdinApproval, key: "write_stdin_approval", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::CodexHooks, key: "hooks", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::RequestPermissionsTool, key: "request_permissions_tool", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::UseLinuxSandboxBwrap, key: "use_linux_sandbox_bwrap", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::UseLegacyLandlock, key: "use_legacy_landlock", stage: Stage::Deprecated, default_enabled: false, }, FeatureSpec { id: Feature::RequestRule, key: "request_rule", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::WindowsSandbox, key: "experimental_windows_sandbox", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::WindowsSandboxElevated, key: "elevated_windows_sandbox", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::WindowsSandboxService, key: "windows_sandbox_service", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::PreferMxc, key: "prefer_mxc", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::RemoteModels, key: "remote_models", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::ApiKeyModelDiscovery, key: "api_key_model_discovery", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::CliDaybreak, key: "cli_daybreak", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::ApiKeyCyberAccessPrograms, key: "api_key_cyber_access_programs", stage: Stage::Stable, default_enabled: false, }, FeatureSpec { id: Feature::EnableRequestCompression, key: "enable_request_compression", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::UnboundedConnectionRetries, key: "unbounded_connection_retries", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::NetworkProxy, key: "network_proxy", stage: Stage::Experimental { name: "Network proxy", menu_description: "Apply network proxy restrictions to sandboxed sessions that already have network access.", announcement: "NEW: Network proxy can now be enabled from /experimental. Restart Codex after enabling it.", }, default_enabled: false, }, FeatureSpec { id: Feature::Worktrees, key: "worktrees", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::RespectSystemProxy, key: "respect_system_proxy", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::SystemProxyFallback, key: "system_proxy_fallback", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::Collab, key: "multi_agent", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::MultiAgentV2, key: "multi_agent_v2", stage: Stage::Stable, default_enabled: false, }, FeatureSpec { id: Feature::ModelCatalogInContext, key: "model_catalog_in_context", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::DeferMailboxPreemption, key: "defer_mailbox_preemption", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::AgentMessageBoard, key: "agent_message_board", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::MultiAgentMode, key: "multi_agent_mode", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::SpawnCsv, key: "enable_fanout", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::Apps, key: "apps", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::Psp, key: "psp", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::EnableMcpApps, key: "enable_mcp_apps", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::Mcp20260728, key: "mcp_2026_07_28", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::CodexAppsMcp20260728, key: "codex_apps_mcp_2026_07_28", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::McpOAuthRefreshCoordination, key: "mcp_oauth_refresh_coordination", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::UseXaa, key: "use_xaa", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::AppsMcpPathOverride, key: "apps_mcp_path_override", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::ToolSearch, key: "tool_search", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::ToolSearchAlwaysDeferMcpTools, key: "tool_search_always_defer_mcp_tools", stage: Stage::Removed, default_enabled: true, }, FeatureSpec { id: Feature::DeferredToolWorldState, key: "deferred_tool_world_state", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::NonPrefixedMcpToolNames, key: "non_prefixed_mcp_tool_names", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::UnavailableDummyTools, key: "unavailable_dummy_tools", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::ToolSuggest, key: "tool_suggest", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::RecommendedPlugins, key: "recommended_plugins", stage: Stage::Stable, default_enabled: false, }, FeatureSpec { id: Feature::Plugins, key: "plugins", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::ExecutorCapabilityDiscovery, key: "executor_capability_discovery", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::SkipHostSkillDiscovery, key: "skip_host_skill_discovery", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::PluginHooks, key: "plugin_hooks", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::InAppBrowser, key: "in_app_browser", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::BrowserAnnotationApi, key: "browser_annotation_api", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::InAppChat, key: "in_app_chat", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::InAppDictation, key: "in_app_dictation", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::InAppVoice, key: "in_app_voice", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::InAppLocalAutomation, key: "in_app_local_automation", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::InAppUpdates, key: "in_app_updates", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::BrowserUse, key: "browser_use", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::BrowserUseFullCdpAccess, key: "browser_use_full_cdp_access", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::BrowserUseExternal, key: "browser_use_external", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::ComputerUse, key: "computer_use", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::RemotePlugin, key: "remote_plugin", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::PluginSharing, key: "plugin_sharing", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::ExternalMigration, key: "external_migration", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::ImageGeneration, key: "image_generation", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::OmitAppServerNotificationMedia, key: "omit_app_server_notification_media", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::ImageResizeNotice, key: "image_resize_notice", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::UnifiedImageBudget, key: "unified_image_budget", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::ResizeAllImages, key: "resize_all_images", stage: Stage::Removed, default_enabled: true, }, FeatureSpec { id: Feature::ItemIds, key: "item_ids", stage: Stage::Removed, default_enabled: true, }, FeatureSpec { id: Feature::ConcurrentReasoningSummaries, key: "concurrent_reasoning_summaries", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::SkillMcpDependencyInstall, key: "skill_mcp_dependency_install", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::SkillSearch, key: "skill_search", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::SkillEnvVarDependencyPrompt, key: "skill_env_var_dependency_prompt", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::MentionsV2, key: "mentions_v2", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::Steer, key: "steer", stage: Stage::Removed, default_enabled: true, }, FeatureSpec { id: Feature::DefaultModeRequestUserInput, key: "default_mode_request_user_input", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::SendAsyncMessage, key: "send_async_message", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::SendMessageToUserAsync, key: "send_message_to_user_async", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::TerminalVisualizationInstructions, key: "terminal_visualization_instructions", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::GuardianApproval, key: "guardian_approval", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::GuardianThreadContext, key: "guardianv2.thread_context", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::GuardianReuseParentCompaction, key: "guardian_reuse_parent_compaction", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::GuardianRootHandoffContext, key: "guardian_root_handoff_context", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::GuardianEnhancedNodeReplTranscripts, key: "guardian_enhanced_node_repl_transcripts", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::GuardianNodeReplTranscriptImages, key: "guardian_node_repl_transcript_images", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::GuardianConversationHistoryTools, key: "guardian_conversation_history_tools", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::GuardianV2, key: "guardianv2", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::GuardianExt, key: "guardian_ext", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::Goals, key: "goals", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::TokenBudget, key: "token_budget", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::ContextManagement, key: "context_management", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::RolloutBudget, key: "rollout_budget", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::ReasoningEffortOverride, key: "reasoning_effort_override", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::CurrentTimeReminder, key: "current_time_reminder", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::NonfatalClockReadErrors, key: "nonfatal_clock_read_errors", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::CollaborationModes, key: "collaboration_modes", stage: Stage::Removed, default_enabled: true, }, FeatureSpec { id: Feature::ToolCallMcpElicitation, key: "tool_call_mcp_elicitation", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::AuthElicitation, key: "auth_elicitation", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::BedrockSetupWizard, key: "bedrock_setup_wizard", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::Personality, key: "personality", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::Artifact, key: "artifact", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::FastMode, key: "fast_mode", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::StepModelSwitching, key: "step_model_switching", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::RealtimeConversation, key: "realtime_conversation", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::RemoteControl, key: "remote_control", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::ImageDetailOriginal, key: "image_detail_original", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::TuiAppServer, key: "tui_app_server", stage: Stage::Removed, default_enabled: true, }, FeatureSpec { id: Feature::PreventIdleSleep, key: "prevent_idle_sleep", stage: if cfg!(any( target_os = "macos", target_os = "linux", target_os = "windows" )) { Stage::Experimental { name: "Prevent sleep while running", menu_description: "Keep your computer awake while Codex is running a thread.", announcement: "NEW: Prevent sleep while running is now available in /experimental.", } } else { Stage::UnderDevelopment }, default_enabled: false, }, FeatureSpec { id: Feature::WorkspaceOwnerUsageNudge, key: "workspace_owner_usage_nudge", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::ResponsesWebsockets, key: "responses_websockets", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::ResponsesWebsocketsV2, key: "responses_websockets_v2", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::RemoteCompactionV2, key: "remote_compaction_v2", stage: Stage::Removed, default_enabled: false, }, FeatureSpec { id: Feature::CompactionImageBudget, key: "compaction_image_budget", stage: Stage::Stable, default_enabled: true, }, FeatureSpec { id: Feature::RetainClientDeveloperMessages, key: "retain_client_developer_messages", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::UseAgentIdentity, key: "use_agent_identity", stage: Stage::UnderDevelopment, default_enabled: false, }, FeatureSpec { id: Feature::WorkspaceDependencies, key: "workspace_dependencies", stage: Stage::Stable, default_enabled: true, }, ]; pub fn unstable_features_warning_event( effective_features: Option<&Table>, suppress_unstable_features_warning: bool, features: &Features, config_path: &str, ) -> Option { if suppress_unstable_features_warning { return None; } let mut under_development_feature_keys = Vec::new(); if let Some(table) = effective_features { for (key, value) in table { let is_enabled = value.as_bool() == Some(true) || value .as_table() .and_then(|table| table.get("enabled")) .and_then(toml::Value::as_bool) == Some(true); if !is_enabled { continue; } let Some(spec) = FEATURES.iter().find(|spec| spec.key == key.as_str()) else { continue; }; if !features.enabled(spec.id) { continue; } if matches!(spec.stage, Stage::UnderDevelopment) { under_development_feature_keys.push(spec.key.to_string()); } } } if under_development_feature_keys.is_empty() { return None; } under_development_feature_keys.sort(); let under_development_feature_keys = under_development_feature_keys.join(", "); let message = format!( "Under-development features enabled: {under_development_feature_keys}. Under-development features are incomplete and may behave unpredictably. To suppress this warning, set `suppress_unstable_features_warning = true` in {config_path}." ); Some(Event { id: String::new(), msg: EventMsg::Warning(WarningEvent { message }), }) } #[cfg(test)] mod tests;