mirror of
https://github.com/openai/codex.git
synced 2026-02-01 22:47:52 +00:00
Have a coding mode and only show coding and plan (#9802)
This commit is contained in:
@@ -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
|
||||
//! effort settings, which keeps the API contract visible in one place.
|
||||
|
||||
#![allow(clippy::unwrap_used)]
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -350,6 +350,7 @@
|
||||
"description": "Initial collaboration mode to use when the TUI starts.",
|
||||
"enum": [
|
||||
"plan",
|
||||
"code",
|
||||
"pair_programming",
|
||||
"execute",
|
||||
"custom"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"))]
|
||||
@@ -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,
|
||||
|
||||
1
codex-rs/core/templates/collaboration_mode/code.md
Normal file
1
codex-rs/core/templates/collaboration_mode/code.md
Normal file
@@ -0,0 +1 @@
|
||||
you are now in code mode.
|
||||
@@ -156,6 +156,7 @@ pub enum AltScreenMode {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ModeKind {
|
||||
Plan,
|
||||
Code,
|
||||
PairProgramming,
|
||||
Execute,
|
||||
Custom,
|
||||
|
||||
@@ -49,6 +49,7 @@ pub(crate) struct FooterProps {
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum CollaborationModeIndicator {
|
||||
Plan,
|
||||
Code,
|
||||
PairProgramming,
|
||||
Execute,
|
||||
}
|
||||
@@ -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}")
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
let actions: Vec<SelectionAction> = vec![Box::new(move |tx| {
|
||||
tx.send(AppEvent::SubmitUserMessageWithMode {
|
||||
text: user_text.clone(),
|
||||
@@ -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,
|
||||
@@ -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(),
|
||||
@@ -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",
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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:?}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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]
|
||||
|
||||
@@ -6,11 +6,27 @@ fn mode_kind(mode: &CollaborationMode) -> ModeKind {
|
||||
mode.mode
|
||||
}
|
||||
|
||||
fn is_tui_mode(kind: ModeKind) -> bool {
|
||||
matches!(kind, ModeKind::Plan | ModeKind::Code)
|
||||
}
|
||||
|
||||
fn filtered_presets(models_manager: &ModelsManager) -> Vec<CollaborationMode> {
|
||||
models_manager
|
||||
.list_collaboration_modes()
|
||||
.into_iter()
|
||||
.filter(|preset| is_tui_mode(mode_kind(preset)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn presets_for_tui(models_manager: &ModelsManager) -> Vec<CollaborationMode> {
|
||||
filtered_presets(models_manager)
|
||||
}
|
||||
|
||||
pub(crate) fn default_mode(models_manager: &ModelsManager) -> Option<CollaborationMode> {
|
||||
let presets = models_manager.list_collaboration_modes();
|
||||
let presets = filtered_presets(models_manager);
|
||||
presets
|
||||
.iter()
|
||||
.find(|preset| preset.mode == ModeKind::PairProgramming)
|
||||
.find(|preset| preset.mode == ModeKind::Code)
|
||||
.cloned()
|
||||
.or_else(|| presets.into_iter().next())
|
||||
}
|
||||
@@ -19,7 +35,10 @@ pub(crate) fn mode_for_kind(
|
||||
models_manager: &ModelsManager,
|
||||
kind: ModeKind,
|
||||
) -> Option<CollaborationMode> {
|
||||
let presets = models_manager.list_collaboration_modes();
|
||||
if !is_tui_mode(kind) {
|
||||
return None;
|
||||
}
|
||||
let presets = filtered_presets(models_manager);
|
||||
presets.into_iter().find(|preset| mode_kind(preset) == kind)
|
||||
}
|
||||
|
||||
@@ -32,7 +51,7 @@ pub(crate) fn next_mode(
|
||||
models_manager: &ModelsManager,
|
||||
current: &CollaborationMode,
|
||||
) -> Option<CollaborationMode> {
|
||||
let presets = models_manager.list_collaboration_modes();
|
||||
let presets = filtered_presets(models_manager);
|
||||
if presets.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -44,9 +63,8 @@ pub(crate) fn next_mode(
|
||||
presets.get(next_index).cloned()
|
||||
}
|
||||
|
||||
pub(crate) fn execute_mode(models_manager: &ModelsManager) -> Option<CollaborationMode> {
|
||||
models_manager
|
||||
.list_collaboration_modes()
|
||||
pub(crate) fn code_mode(models_manager: &ModelsManager) -> Option<CollaborationMode> {
|
||||
filtered_presets(models_manager)
|
||||
.into_iter()
|
||||
.find(|preset| mode_kind(preset) == ModeKind::Execute)
|
||||
.find(|preset| mode_kind(preset) == ModeKind::Code)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user