Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions codex-rs/app-server/tests/suite/v2/collaboration_mode_list.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Validates that the collaboration mode list endpoint returns the expected default presets.
//!
//! The test drives the app server through the MCP harness and asserts that the list response
//! includes the plan, pair programming, and execute modes with their default model and reasoning
//! includes the plan, coding, pair programming, and execute modes with their default model and reasoning
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: still says coding

//! effort settings, which keeps the API contract visible in one place.

#![allow(clippy::unwrap_used)]
Expand Down Expand Up @@ -45,7 +45,12 @@ async fn list_collaboration_modes_returns_presets() -> Result<()> {
let CollaborationModeListResponse { data: items } =
to_response::<CollaborationModeListResponse>(response)?;

let expected = vec![plan_preset(), pair_programming_preset(), execute_preset()];
let expected = vec![
plan_preset(),
code_preset(),
pair_programming_preset(),
execute_preset(),
];
assert_eq!(expected, items);
Ok(())
}
Expand Down Expand Up @@ -74,6 +79,15 @@ fn pair_programming_preset() -> CollaborationMode {
.unwrap()
}

/// Builds the code preset that the list response is expected to return.
fn code_preset() -> CollaborationMode {
let presets = test_builtin_collaboration_mode_presets();
presets
.into_iter()
.find(|p| p.mode == ModeKind::Code)
.unwrap()
}

/// Builds the execute preset that the list response is expected to return.
///
/// The execute preset uses a different reasoning effort to capture the higher-effort
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@
"description": "Initial collaboration mode to use when the TUI starts.",
"enum": [
"plan",
"code",
"pair_programming",
"execute",
"custom"
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ pub enum Feature {
Connectors,
/// Steer feature flag - when enabled, Enter submits immediately instead of queuing.
Steer,
/// Enable collaboration modes (Plan, Pair Programming, Execute).
/// Enable collaboration modes (Plan, Code, Pair Programming, Execute).
CollaborationModes,
/// Use the Responses API WebSocket transport for OpenAI by default.
ResponsesWebsockets,
Expand Down
19 changes: 18 additions & 1 deletion codex-rs/core/src/models_manager/collaboration_mode_presets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ use codex_protocol::config_types::Settings;
use codex_protocol::openai_models::ReasoningEffort;

const COLLABORATION_MODE_PLAN: &str = include_str!("../../templates/collaboration_mode/plan.md");
const COLLABORATION_MODE_CODE: &str = include_str!("../../templates/collaboration_mode/code.md");
const COLLABORATION_MODE_PAIR_PROGRAMMING: &str =
include_str!("../../templates/collaboration_mode/pair_programming.md");
const COLLABORATION_MODE_EXECUTE: &str =
include_str!("../../templates/collaboration_mode/execute.md");

pub(super) fn builtin_collaboration_mode_presets() -> Vec<CollaborationMode> {
vec![plan_preset(), pair_programming_preset(), execute_preset()]
vec![
plan_preset(),
code_preset(),
pair_programming_preset(),
execute_preset(),
]
}

#[cfg(any(test, feature = "test-support"))]
Expand All @@ -29,6 +35,17 @@ fn plan_preset() -> CollaborationMode {
}
}

fn code_preset() -> CollaborationMode {
CollaborationMode {
mode: ModeKind::Code,
settings: Settings {
model: "gpt-5.2-codex".to_string(),
reasoning_effort: Some(ReasoningEffort::Medium),
developer_instructions: Some(COLLABORATION_MODE_CODE.to_string()),
},
}
}

fn pair_programming_preset() -> CollaborationMode {
CollaborationMode {
mode: ModeKind::PairProgramming,
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core/templates/collaboration_mode/code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
you are now in code mode.
1 change: 1 addition & 0 deletions codex-rs/protocol/src/config_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ pub enum AltScreenMode {
#[serde(rename_all = "snake_case")]
pub enum ModeKind {
Plan,
Code,
PairProgramming,
Execute,
Custom,
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/tui/src/bottom_pane/footer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub(crate) struct FooterProps {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum CollaborationModeIndicator {
Plan,
Code,
PairProgramming,
Execute,
}
Expand All @@ -64,6 +65,7 @@ impl CollaborationModeIndicator {
};
match self {
CollaborationModeIndicator::Plan => format!("Plan mode{suffix}"),
CollaborationModeIndicator::Code => format!("Code mode{suffix}"),
CollaborationModeIndicator::PairProgramming => {
format!("Pair Programming mode{suffix}")
}
Expand All @@ -75,6 +77,7 @@ impl CollaborationModeIndicator {
let label = self.label(show_cycle_hint);
match self {
CollaborationModeIndicator::Plan => Span::from(label).magenta(),
CollaborationModeIndicator::Code => Span::from(label).cyan(),
CollaborationModeIndicator::PairProgramming => Span::from(label).cyan(),
CollaborationModeIndicator::Execute => Span::from(label).dim(),
}
Expand Down
19 changes: 11 additions & 8 deletions codex-rs/tui/src/chatwidget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ const DEFAULT_MODEL_DISPLAY_NAME: &str = "loading";
const PLAN_IMPLEMENTATION_TITLE: &str = "Implement this plan?";
const PLAN_IMPLEMENTATION_YES: &str = "Yes, implement this plan";
const PLAN_IMPLEMENTATION_NO: &str = "No, stay in Plan mode";
const PLAN_IMPLEMENTATION_EXECUTE_MESSAGE: &str = "Implement the plan.";
const PLAN_IMPLEMENTATION_CODING_MESSAGE: &str = "Implement the plan.";

use crate::app_event::AppEvent;
use crate::app_event::ExitMode;
Expand Down Expand Up @@ -932,10 +932,10 @@ impl ChatWidget {
}

fn open_plan_implementation_prompt(&mut self) {
let execute_mode = collaboration_modes::execute_mode(self.models_manager.as_ref());
let (implement_actions, implement_disabled_reason) = match execute_mode {
let code_mode = collaboration_modes::code_mode(self.models_manager.as_ref());
let (implement_actions, implement_disabled_reason) = match code_mode {
Some(collaboration_mode) => {
let user_text = PLAN_IMPLEMENTATION_EXECUTE_MESSAGE.to_string();
let user_text = PLAN_IMPLEMENTATION_CODING_MESSAGE.to_string();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: CODING still in this name

let actions: Vec<SelectionAction> = vec![Box::new(move |tx| {
tx.send(AppEvent::SubmitUserMessageWithMode {
text: user_text.clone(),
Expand All @@ -944,13 +944,13 @@ impl ChatWidget {
})];
(actions, None)
}
None => (Vec::new(), Some("Execute mode unavailable".to_string())),
None => (Vec::new(), Some("Code mode unavailable".to_string())),
};

let items = vec![
SelectionItem {
name: PLAN_IMPLEMENTATION_YES.to_string(),
description: Some("Switch to Execute and start coding.".to_string()),
description: Some("Switch to Code and start coding.".to_string()),
selected_description: None,
is_current: false,
actions: implement_actions,
Expand Down Expand Up @@ -3549,7 +3549,7 @@ impl ChatWidget {
}

pub(crate) fn open_collaboration_modes_popup(&mut self) {
let presets = self.models_manager.list_collaboration_modes();
let presets = collaboration_modes::presets_for_tui(self.models_manager.as_ref());
if presets.is_empty() {
self.add_info_message(
"No collaboration modes are available right now.".to_string(),
Expand All @@ -3563,6 +3563,7 @@ impl ChatWidget {
.map(|preset| {
let name = match preset.mode {
ModeKind::Plan => "Plan",
ModeKind::Code => "Code",
ModeKind::PairProgramming => "Pair Programming",
ModeKind::Execute => "Execute",
ModeKind::Custom => "Custom",
Expand Down Expand Up @@ -4641,6 +4642,7 @@ impl ChatWidget {
}
match self.stored_collaboration_mode.mode {
ModeKind::Plan => Some("Plan"),
ModeKind::Code => Some("Code"),
ModeKind::PairProgramming => Some("Pair Programming"),
ModeKind::Execute => Some("Execute"),
ModeKind::Custom => None,
Expand All @@ -4653,6 +4655,7 @@ impl ChatWidget {
}
match self.stored_collaboration_mode.mode {
ModeKind::Plan => Some(CollaborationModeIndicator::Plan),
ModeKind::Code => Some(CollaborationModeIndicator::Code),
ModeKind::PairProgramming => Some(CollaborationModeIndicator::PairProgramming),
ModeKind::Execute => Some(CollaborationModeIndicator::Execute),
ModeKind::Custom => None,
Expand All @@ -4664,7 +4667,7 @@ impl ChatWidget {
self.bottom_pane.set_collaboration_mode_indicator(indicator);
}

/// Cycle to the next collaboration mode variant (Plan -> PairProgramming -> Execute -> Plan).
/// Cycle to the next collaboration mode variant (Plan -> Code -> Plan).
fn cycle_collaboration_mode(&mut self) {
if !self.collaboration_modes_enabled() {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ expression: popup
---
Implement this plan?

› 1. Yes, implement this plan Switch to Execute and start coding.
› 1. Yes, implement this plan Switch to Code and start coding.
2. No, stay in Plan mode Continue planning with the model.

Press enter to confirm or esc to go back
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ expression: popup
---
Implement this plan?

1. Yes, implement this plan Switch to Execute and start coding.
1. Yes, implement this plan Switch to Code and start coding.
› 2. No, stay in Plan mode Continue planning with the model.

Press enter to confirm or esc to go back
41 changes: 19 additions & 22 deletions codex-rs/tui/src/chatwidget/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1213,32 +1213,32 @@ async fn plan_implementation_popup_yes_emits_submit_message_event() {
else {
panic!("expected SubmitUserMessageWithMode, got {event:?}");
};
assert_eq!(text, PLAN_IMPLEMENTATION_EXECUTE_MESSAGE);
assert_eq!(collaboration_mode.mode, ModeKind::Execute);
assert_eq!(text, PLAN_IMPLEMENTATION_CODING_MESSAGE);
assert_eq!(collaboration_mode.mode, ModeKind::Code);
}

#[tokio::test]
async fn submit_user_message_with_mode_sets_execute_collaboration_mode() {
async fn submit_user_message_with_mode_sets_coding_collaboration_mode() {
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(Some("gpt-5")).await;
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, true);

let execute_mode = collaboration_modes::execute_mode(chat.models_manager.as_ref())
.expect("expected execute collaboration mode");
chat.submit_user_message_with_mode("Implement the plan.".to_string(), execute_mode);
let code_mode = collaboration_modes::code_mode(chat.models_manager.as_ref())
.expect("expected code collaboration mode");
chat.submit_user_message_with_mode("Implement the plan.".to_string(), code_mode);

match next_submit_op(&mut op_rx) {
Op::UserTurn {
collaboration_mode:
Some(CollaborationMode {
mode: ModeKind::Execute,
mode: ModeKind::Code,
..
}),
personality: None,
..
} => {}
other => {
panic!("expected Op::UserTurn with execute collab mode, got {other:?}")
panic!("expected Op::UserTurn with code collab mode, got {other:?}")
}
}
}
Expand Down Expand Up @@ -2229,10 +2229,10 @@ async fn collab_mode_shift_tab_cycles_only_when_enabled_and_idle() {
chat.set_feature_enabled(Feature::CollaborationModes, true);

chat.handle_key_event(KeyEvent::from(KeyCode::BackTab));
assert_eq!(chat.stored_collaboration_mode.mode, ModeKind::Execute);
assert_eq!(chat.stored_collaboration_mode.mode, ModeKind::Plan);

chat.handle_key_event(KeyEvent::from(KeyCode::BackTab));
assert_eq!(chat.stored_collaboration_mode.mode, ModeKind::Plan);
assert_eq!(chat.stored_collaboration_mode.mode, ModeKind::Code);

chat.on_task_started();
let before = chat.stored_collaboration_mode.clone();
Expand Down Expand Up @@ -2267,14 +2267,14 @@ async fn collab_slash_command_opens_picker_and_updates_mode() {
Op::UserTurn {
collaboration_mode:
Some(CollaborationMode {
mode: ModeKind::PairProgramming,
mode: ModeKind::Code,
..
}),
personality: None,
..
} => {}
other => {
panic!("expected Op::UserTurn with pair programming collab mode, got {other:?}")
panic!("expected Op::UserTurn with code collab mode, got {other:?}")
}
}

Expand All @@ -2285,20 +2285,20 @@ async fn collab_slash_command_opens_picker_and_updates_mode() {
Op::UserTurn {
collaboration_mode:
Some(CollaborationMode {
mode: ModeKind::PairProgramming,
mode: ModeKind::Code,
..
}),
personality: None,
..
} => {}
other => {
panic!("expected Op::UserTurn with pair programming collab mode, got {other:?}")
panic!("expected Op::UserTurn with code collab mode, got {other:?}")
}
}
}

#[tokio::test]
async fn collab_mode_defaults_to_pair_programming_when_enabled() {
async fn collab_mode_defaults_to_coding_when_enabled() {
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await;
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, true);
Expand All @@ -2310,26 +2310,23 @@ async fn collab_mode_defaults_to_pair_programming_when_enabled() {
Op::UserTurn {
collaboration_mode:
Some(CollaborationMode {
mode: ModeKind::PairProgramming,
mode: ModeKind::Code,
..
}),
personality: None,
..
} => {}
other => {
panic!("expected Op::UserTurn with pair programming collab mode, got {other:?}")
panic!("expected Op::UserTurn with code collab mode, got {other:?}")
}
}
}

#[tokio::test]
async fn collab_mode_enabling_sets_pair_programming_default() {
async fn collab_mode_enabling_sets_coding_default() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
chat.set_feature_enabled(Feature::CollaborationModes, true);
assert_eq!(
chat.stored_collaboration_mode.mode,
ModeKind::PairProgramming
);
assert_eq!(chat.stored_collaboration_mode.mode, ModeKind::Code);
}

#[tokio::test]
Expand Down
Loading
Loading