Tui: use collaboration mode instead of model and effort (#9507)

- Only use collaboration modes in the tui state to track model and
effort.
- No behavior change without the collaboration modes flag.
- Change model and effort on /model, /collab (behind a flag), and
shift+tab (behind flag)
This commit is contained in:
Ahmed Ibrahim
2026-01-20 10:26:12 -08:00
committed by GitHub
parent 7b27aa7707
commit 5ae6e70801
15 changed files with 841 additions and 558 deletions

View File

@@ -756,8 +756,27 @@ async fn make_chatwidget_manual(
skills: None,
});
bottom.set_steer_enabled(true);
bottom.set_collaboration_modes_enabled(cfg.features.enabled(Feature::CollaborationModes));
let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("test"));
let codex_home = cfg.codex_home.clone();
let models_manager = Arc::new(ModelsManager::new(codex_home, auth_manager.clone()));
let collaboration_modes_enabled = cfg.features.enabled(Feature::CollaborationModes);
let reasoning_effort = None;
let stored_collaboration_mode = if collaboration_modes_enabled {
collaboration_modes::default_mode(models_manager.as_ref()).unwrap_or_else(|| {
CollaborationMode::Custom(Settings {
model: resolved_model.clone(),
reasoning_effort,
developer_instructions: None,
})
})
} else {
CollaborationMode::Custom(Settings {
model: resolved_model.clone(),
reasoning_effort,
developer_instructions: None,
})
};
let widget = ChatWidget {
app_event_tx,
codex_op_tx: op_tx,
@@ -765,10 +784,9 @@ async fn make_chatwidget_manual(
active_cell: None,
active_cell_revision: 0,
config: cfg,
model: Some(resolved_model.clone()),
collaboration_mode: CollaborationModeSelection::default(),
auth_manager: auth_manager.clone(),
models_manager: Arc::new(ModelsManager::new(codex_home, auth_manager)),
stored_collaboration_mode,
auth_manager,
models_manager,
session_header: SessionHeader::new(resolved_model),
initial_user_message: None,
token_info: None,
@@ -1909,75 +1927,66 @@ async fn slash_init_skips_when_project_doc_exists() {
);
}
#[test]
fn parse_collaboration_mode_selection_accepts_common_aliases() {
assert_eq!(
collaboration_modes::parse_selection("plan"),
Some(CollaborationModeSelection::Plan)
);
assert_eq!(
collaboration_modes::parse_selection("PAIR"),
Some(CollaborationModeSelection::PairProgramming)
);
assert_eq!(
collaboration_modes::parse_selection("pair_programming"),
Some(CollaborationModeSelection::PairProgramming)
);
assert_eq!(
collaboration_modes::parse_selection("pp"),
Some(CollaborationModeSelection::PairProgramming)
);
assert_eq!(
collaboration_modes::parse_selection(" exec "),
Some(CollaborationModeSelection::Execute)
);
assert_eq!(
collaboration_modes::parse_selection("execute"),
Some(CollaborationModeSelection::Execute)
);
assert_eq!(collaboration_modes::parse_selection("unknown"), None);
}
#[tokio::test]
async fn collab_mode_shift_tab_cycles_only_when_enabled_and_idle() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
chat.set_feature_enabled(Feature::CollaborationModes, false);
let initial = chat.collaboration_mode;
let initial = chat.stored_collaboration_mode.clone();
chat.handle_key_event(KeyEvent::from(KeyCode::BackTab));
assert_eq!(chat.collaboration_mode, initial);
assert_eq!(chat.stored_collaboration_mode, initial);
chat.set_feature_enabled(Feature::CollaborationModes, true);
chat.handle_key_event(KeyEvent::from(KeyCode::BackTab));
assert_eq!(chat.collaboration_mode, CollaborationModeSelection::Execute);
assert!(matches!(
chat.stored_collaboration_mode,
CollaborationMode::Execute(_)
));
chat.handle_key_event(KeyEvent::from(KeyCode::BackTab));
assert_eq!(chat.collaboration_mode, CollaborationModeSelection::Plan);
assert!(matches!(
chat.stored_collaboration_mode,
CollaborationMode::Plan(_)
));
chat.on_task_started();
let before = chat.stored_collaboration_mode.clone();
chat.handle_key_event(KeyEvent::from(KeyCode::BackTab));
assert_eq!(chat.collaboration_mode, CollaborationModeSelection::Plan);
assert_eq!(chat.stored_collaboration_mode, before);
}
#[tokio::test]
async fn collab_slash_command_sets_mode_and_next_submit_sends_user_turn() {
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(None).await;
async fn collab_slash_command_opens_picker_and_updates_mode() {
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await;
chat.thread_id = Some(ThreadId::new());
chat.set_feature_enabled(Feature::CollaborationModes, true);
chat.dispatch_command_with_args(SlashCommand::Collab, "plan".to_string());
assert_eq!(chat.collaboration_mode, CollaborationModeSelection::Plan);
chat.dispatch_command(SlashCommand::Collab);
let popup = render_bottom_popup(&chat, 80);
assert!(
popup.contains("Select Collaboration Mode"),
"expected collaboration picker: {popup}"
);
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
let selected_mode = match rx.try_recv() {
Ok(AppEvent::UpdateCollaborationMode(mode)) => mode,
other => panic!("expected UpdateCollaborationMode event, got {other:?}"),
};
chat.set_collaboration_mode(selected_mode);
chat.bottom_pane
.set_composer_text("hello".to_string(), Vec::new(), Vec::new());
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
match next_submit_op(&mut op_rx) {
Op::UserTurn {
collaboration_mode: Some(CollaborationMode::Plan(_)),
collaboration_mode: Some(CollaborationMode::PairProgramming(_)),
..
} => {}
other => panic!("expected Op::UserTurn with plan collab mode, got {other:?}"),
other => {
panic!("expected Op::UserTurn with pair programming collab mode, got {other:?}")
}
}
chat.bottom_pane
@@ -1985,10 +1994,12 @@ async fn collab_slash_command_sets_mode_and_next_submit_sends_user_turn() {
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
match next_submit_op(&mut op_rx) {
Op::UserTurn {
collaboration_mode: Some(CollaborationMode::Plan(_)),
collaboration_mode: Some(CollaborationMode::PairProgramming(_)),
..
} => {}
other => panic!("expected Op::UserTurn with plan collab mode, got {other:?}"),
other => {
panic!("expected Op::UserTurn with pair programming collab mode, got {other:?}")
}
}
}
@@ -2006,10 +2017,22 @@ async fn collab_mode_defaults_to_pair_programming_when_enabled() {
collaboration_mode: Some(CollaborationMode::PairProgramming(_)),
..
} => {}
other => panic!("expected Op::UserTurn with pair programming collab mode, got {other:?}"),
other => {
panic!("expected Op::UserTurn with pair programming collab mode, got {other:?}")
}
}
}
#[tokio::test]
async fn collab_mode_enabling_sets_pair_programming_default() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
chat.set_feature_enabled(Feature::CollaborationModes, true);
assert!(matches!(
chat.stored_collaboration_mode,
CollaborationMode::PairProgramming(_)
));
}
#[tokio::test]
async fn slash_quit_requests_exit() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(None).await;
@@ -2722,7 +2745,7 @@ async fn model_reasoning_selection_popup_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
set_chatgpt_auth(&mut chat);
chat.config.model_reasoning_effort = Some(ReasoningEffortConfig::High);
chat.set_reasoning_effort(Some(ReasoningEffortConfig::High));
let preset = get_available_model(&chat, "gpt-5.1-codex-max");
chat.open_reasoning_popup(preset);
@@ -2736,7 +2759,7 @@ async fn model_reasoning_selection_popup_extra_high_warning_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.1-codex-max")).await;
set_chatgpt_auth(&mut chat);
chat.config.model_reasoning_effort = Some(ReasoningEffortConfig::XHigh);
chat.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
let preset = get_available_model(&chat, "gpt-5.1-codex-max");
chat.open_reasoning_popup(preset);