diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index e70841c7d..e2d7b80b3 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -343,6 +343,16 @@ } ] }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "enum": [ + "plan", + "pair_programming", + "execute", + "custom" + ], + "type": "string" + }, "ModelProviderInfo": { "additionalProperties": false, "description": "Serializable representation of a provider definition.", @@ -953,6 +963,15 @@ "description": "Enable animations (welcome screen, shimmer effects, spinners). Defaults to `true`.", "type": "boolean" }, + "experimental_mode": { + "allOf": [ + { + "$ref": "#/definitions/ModeKind" + } + ], + "default": null, + "description": "Start the TUI in the specified collaboration mode (plan/execute/etc.). Defaults to unset." + }, "notifications": { "allOf": [ { diff --git a/codex-rs/core/src/config/mod.rs b/codex-rs/core/src/config/mod.rs index 4dde7e1ec..ca2f79edf 100644 --- a/codex-rs/core/src/config/mod.rs +++ b/codex-rs/core/src/config/mod.rs @@ -42,6 +42,7 @@ use codex_app_server_protocol::Tools; use codex_app_server_protocol::UserSavedConfig; use codex_protocol::config_types::AltScreenMode; use codex_protocol::config_types::ForcedLoginMethod; +use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Personality; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::SandboxMode; @@ -199,6 +200,9 @@ pub struct Config { /// Show startup tooltips in the TUI welcome screen. pub show_tooltips: bool, + /// Start the TUI in the specified collaboration mode (plan/execute/etc.). + pub experimental_mode: Option, + /// Controls whether the TUI uses the terminal's alternate screen buffer. /// /// This is the same `tui.alternate_screen` value from `config.toml` (see [`Tui`]). @@ -1560,6 +1564,7 @@ impl Config { .unwrap_or_default(), animations: cfg.tui.as_ref().map(|t| t.animations).unwrap_or(true), show_tooltips: cfg.tui.as_ref().map(|t| t.show_tooltips).unwrap_or(true), + experimental_mode: cfg.tui.as_ref().and_then(|t| t.experimental_mode), tui_alternate_screen: cfg .tui .as_ref() @@ -1812,6 +1817,7 @@ persistence = "none" notifications: Notifications::Enabled(true), animations: true, show_tooltips: true, + experimental_mode: None, alternate_screen: AltScreenMode::Auto, } ); @@ -3697,6 +3703,7 @@ model_verbosity = "high" tui_notifications: Default::default(), animations: true, show_tooltips: true, + experimental_mode: None, analytics_enabled: Some(true), feedback_enabled: true, tui_alternate_screen: AltScreenMode::Auto, @@ -3777,6 +3784,7 @@ model_verbosity = "high" tui_notifications: Default::default(), animations: true, show_tooltips: true, + experimental_mode: None, analytics_enabled: Some(true), feedback_enabled: true, tui_alternate_screen: AltScreenMode::Auto, @@ -3872,6 +3880,7 @@ model_verbosity = "high" tui_notifications: Default::default(), animations: true, show_tooltips: true, + experimental_mode: None, analytics_enabled: Some(false), feedback_enabled: true, tui_alternate_screen: AltScreenMode::Auto, @@ -3953,6 +3962,7 @@ model_verbosity = "high" tui_notifications: Default::default(), animations: true, show_tooltips: true, + experimental_mode: None, analytics_enabled: Some(true), feedback_enabled: true, tui_alternate_screen: AltScreenMode::Auto, diff --git a/codex-rs/core/src/config/types.rs b/codex-rs/core/src/config/types.rs index 350ae49a5..1b3f381d3 100644 --- a/codex-rs/core/src/config/types.rs +++ b/codex-rs/core/src/config/types.rs @@ -5,6 +5,7 @@ use crate::config_loader::RequirementSource; pub use codex_protocol::config_types::AltScreenMode; +pub use codex_protocol::config_types::ModeKind; pub use codex_protocol::config_types::Personality; pub use codex_protocol::config_types::WebSearchMode; use codex_utils_absolute_path::AbsolutePathBuf; @@ -438,6 +439,11 @@ pub struct Tui { #[serde(default = "default_true")] pub show_tooltips: bool, + /// Start the TUI in the specified collaboration mode (plan/execute/etc.). + /// Defaults to unset. + #[serde(default)] + pub experimental_mode: Option, + /// Controls whether the TUI uses the terminal's alternate screen buffer. /// /// - `auto` (default): Disable alternate screen in Zellij, enable elsewhere. diff --git a/codex-rs/protocol/src/config_types.rs b/codex-rs/protocol/src/config_types.rs index c04479719..374a9d1b1 100644 --- a/codex-rs/protocol/src/config_types.rs +++ b/codex-rs/protocol/src/config_types.rs @@ -151,6 +151,16 @@ pub enum AltScreenMode { Never, } +/// Initial collaboration mode to use when the TUI starts. +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, JsonSchema, TS)] +#[serde(rename_all = "snake_case")] +pub enum ModeKind { + Plan, + PairProgramming, + Execute, + Custom, +} + /// Collaboration mode for a Codex session. #[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, JsonSchema, TS)] #[serde(tag = "mode", rename_all = "lowercase")] diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index ba894c3e9..bab19cc94 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -93,6 +93,7 @@ use codex_protocol::ThreadId; use codex_protocol::account::PlanType; use codex_protocol::approvals::ElicitationRequestEvent; use codex_protocol::config_types::CollaborationMode; +use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Settings; use codex_protocol::models::local_image_label_text; use codex_protocol::parse_command::ParsedCommand; @@ -1844,20 +1845,19 @@ impl ChatWidget { let codex_op_tx = spawn_agent(config.clone(), app_event_tx.clone(), thread_manager); let model_for_header = model.unwrap_or_else(|| DEFAULT_MODEL_DISPLAY_NAME.to_string()); + let fallback_custom = Settings { + model: model_for_header.clone(), + reasoning_effort: None, + developer_instructions: None, + }; let stored_collaboration_mode = if config.features.enabled(Feature::CollaborationModes) { - collaboration_modes::default_mode(models_manager.as_ref()).unwrap_or_else(|| { - CollaborationMode::Custom(Settings { - model: model_for_header.clone(), - reasoning_effort: None, - developer_instructions: None, - }) - }) + initial_collaboration_mode( + models_manager.as_ref(), + fallback_custom, + config.experimental_mode, + ) } else { - CollaborationMode::Custom(Settings { - model: model_for_header.clone(), - reasoning_effort: None, - developer_instructions: None, - }) + CollaborationMode::Custom(fallback_custom) }; let active_cell = Some(Self::placeholder_session_header_cell( @@ -1969,20 +1969,19 @@ impl ChatWidget { let codex_op_tx = spawn_agent_from_existing(conversation, session_configured, app_event_tx.clone()); + let fallback_custom = Settings { + model: header_model.clone(), + reasoning_effort: None, + developer_instructions: None, + }; let stored_collaboration_mode = if config.features.enabled(Feature::CollaborationModes) { - collaboration_modes::default_mode(models_manager.as_ref()).unwrap_or_else(|| { - CollaborationMode::Custom(Settings { - model: header_model.clone(), - reasoning_effort: None, - developer_instructions: None, - }) - }) + initial_collaboration_mode( + models_manager.as_ref(), + fallback_custom, + config.experimental_mode, + ) } else { - CollaborationMode::Custom(Settings { - model: header_model.clone(), - reasoning_effort: None, - developer_instructions: None, - }) + CollaborationMode::Custom(fallback_custom) }; let mut widget = Self { @@ -4296,9 +4295,13 @@ impl ChatWidget { | CollaborationMode::Execute(settings) | CollaborationMode::Custom(settings) => settings.clone(), }; + let fallback_custom = settings.clone(); self.stored_collaboration_mode = if enabled { - collaboration_modes::default_mode(self.models_manager.as_ref()) - .unwrap_or(CollaborationMode::Custom(settings)) + initial_collaboration_mode( + self.models_manager.as_ref(), + fallback_custom, + self.config.experimental_mode, + ) } else { CollaborationMode::Custom(settings) }; @@ -5031,6 +5034,24 @@ fn extract_first_bold(s: &str) -> Option { None } +fn initial_collaboration_mode( + models_manager: &ModelsManager, + fallback_custom: Settings, + desired_mode: Option, +) -> CollaborationMode { + if let Some(kind) = desired_mode { + if kind == ModeKind::Custom { + return CollaborationMode::Custom(fallback_custom); + } + if let Some(mode) = collaboration_modes::mode_for_kind(models_manager, kind) { + return mode; + } + } + + collaboration_modes::default_mode(models_manager) + .unwrap_or(CollaborationMode::Custom(fallback_custom)) +} + async fn fetch_rate_limits(base_url: String, auth: CodexAuth) -> Option { match BackendClient::from_auth(base_url, &auth) { Ok(client) => match client.get_rate_limits().await { diff --git a/codex-rs/tui/src/collaboration_modes.rs b/codex-rs/tui/src/collaboration_modes.rs index f48ad92c9..5497f373a 100644 --- a/codex-rs/tui/src/collaboration_modes.rs +++ b/codex-rs/tui/src/collaboration_modes.rs @@ -1,13 +1,6 @@ use codex_core::models_manager::manager::ModelsManager; use codex_protocol::config_types::CollaborationMode; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum ModeKind { - Plan, - PairProgramming, - Execute, - Custom, -} +use codex_protocol::config_types::ModeKind; fn mode_kind(mode: &CollaborationMode) -> ModeKind { match mode { @@ -27,6 +20,14 @@ pub(crate) fn default_mode(models_manager: &ModelsManager) -> Option Option { + let presets = models_manager.list_collaboration_modes(); + presets.into_iter().find(|preset| mode_kind(preset) == kind) +} + pub(crate) fn same_variant(a: &CollaborationMode, b: &CollaborationMode) -> bool { mode_kind(a) == mode_kind(b) }