Remove personality selection from the TUI (#44935)

## What changed

Remove the `/personality` command, selection popup, tooltip, and associated settings update and persistence handlers. Stop sending a personality override with TUI user turns.

GitOrigin-RevId: 0549a1f9dcfd8118340fad4b60fd24d4eee7f93a
This commit is contained in:
rhan-oai
2026-09-11 23:28:38 +00:00
committed by copyberry
parent e8271aa8b4
commit 12e82f44f8
24 changed files with 36 additions and 440 deletions
-1
View File
@@ -11,7 +11,6 @@ Use /fork to branch the current chat into a new thread.
Use /side to start a side conversation in a temporary fork without polluting the main thread.
Use /init to create an AGENTS.md with project-specific guidance.
Use /mcp to list configured MCP tools.
Use /personality to customize how Codex communicates.
Use /rename to rename your threads for easier thread resuming.
Use the OpenAI docs MCP for API questions; enable it with `codex mcp add openaiDeveloperDocs --url https://developers.openai.com/mcp`.
Join the OpenAI community Discord: http://discord.gg/openai
-1
View File
@@ -152,7 +152,6 @@ use codex_models_manager::model_presets::HIDE_GPT5_1_MIGRATION_PROMPT_CONFIG;
use codex_otel::SessionTelemetry;
use codex_otel::TelemetryAuthMode;
use codex_protocol::ThreadId;
use codex_protocol::config_types::Personality;
use codex_protocol::models::ActivePermissionProfile;
use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_WORKSPACE;
use codex_protocol::models::PermissionProfile;
@@ -1083,11 +1083,6 @@ impl App {
false
}
pub(super) fn on_update_personality(&mut self, personality: Personality) {
self.config.personality = Some(personality);
self.chat_widget.set_personality(personality);
}
pub(super) fn sync_tui_theme_selection(&mut self, name: String) {
self.local_settings.tui.theme = Some(name.clone());
self.chat_widget.set_tui_theme(Some(name));
@@ -1125,14 +1120,6 @@ impl App {
}
}
pub(super) fn personality_label(personality: Personality) -> &'static str {
match personality {
Personality::None => "None",
Personality::Friendly => "Friendly",
Personality::Pragmatic => "Pragmatic",
}
}
fn sync_feature_state_from_effective_config(
&mut self,
effective_config: &ConfigReadResponse,
-31
View File
@@ -1732,11 +1732,6 @@ impl App {
.await;
}
}
AppEvent::UpdatePersonality(personality) => {
self.on_update_personality(personality);
self.sync_active_thread_personality_setting(app_server, personality)
.await;
}
AppEvent::RealtimeWebrtcOfferCreated {
thread_id,
attempt_id,
@@ -2367,32 +2362,6 @@ impl App {
AppEvent::PersistRealtimeVoiceSelection { voice } => {
self.persist_realtime_voice(app_server, voice).await;
}
AppEvent::PersistPersonalitySelection { personality } => {
match crate::config_update::write_config_batch(
app_server.request_handle(),
vec![crate::config_update::replace_config_value(
"personality",
serde_json::json!(personality.to_string()),
)],
)
.await
{
Ok(_) => {
let label = Self::personality_label(personality);
let message = format!("Personality set to {label}");
self.chat_widget.add_info_message(message, /*hint*/ None);
}
Err(err) => {
tracing::error!(
error = %err,
"failed to persist personality selection"
);
self.chat_widget.add_error_message(format!(
"Failed to save default personality: {err}"
));
}
}
}
AppEvent::PersistServiceTierSelection { service_tier } => {
self.refresh_status_line();
self.config.service_tier = service_tier.clone();
-16
View File
@@ -144,22 +144,6 @@ impl App {
self.send_thread_settings_update(app_server, params).await;
}
pub(super) async fn sync_active_thread_personality_setting(
&mut self,
app_server: &mut AppServerSession,
personality: codex_protocol::config_types::Personality,
) {
let Some(thread_id) = self.active_thread_id else {
return;
};
let params = ThreadSettingsUpdateParams {
thread_id: thread_id.to_string(),
personality: Some(personality),
..ThreadSettingsUpdateParams::default()
};
self.send_thread_settings_update(app_server, params).await;
}
pub(super) async fn sync_override_turn_context_settings(
&mut self,
app_server: &mut AppServerSession,
-9
View File
@@ -61,7 +61,6 @@ use codex_config::types::ApprovalsReviewer;
use codex_features::Feature;
use codex_plugin::PluginCapabilitySummary;
use codex_protocol::config_types::CollaborationModeMask;
use codex_protocol::config_types::Personality;
use codex_protocol::models::ActivePermissionProfile;
use codex_realtime_webrtc::StartedRealtimeWebrtcSession;
@@ -1106,9 +1105,6 @@ pub(crate) enum AppEvent {
/// Update the current model slug in the running app and widget.
UpdateModel(String),
/// Update the current personality in the running app and widget.
UpdatePersonality(Personality),
/// Result of creating a TUI-owned WebRTC offer for an active thread.
RealtimeWebrtcOfferCreated {
thread_id: ThreadId,
@@ -1150,11 +1146,6 @@ pub(crate) enum AppEvent {
voice: codex_protocol::protocol::RealtimeVoice,
},
/// Persist the selected personality to the appropriate config.
PersistPersonalitySelection {
personality: Personality,
},
/// Persist the selected service tier to the appropriate config.
PersistServiceTierSelection {
service_tier: Option<String>,
@@ -568,7 +568,6 @@ pub(crate) struct ChatComposer {
service_tier_commands: Vec<ServiceTierCommand>,
mentions_v2_enabled: bool,
goal_command_enabled: bool,
personality_command_enabled: bool,
voice_command_enabled: bool,
worktrees_enabled: bool,
windows_degraded_sandbox_active: bool,
@@ -734,7 +733,6 @@ impl ChatComposer {
service_tier_commands: Vec::new(),
mentions_v2_enabled: false,
goal_command_enabled: false,
personality_command_enabled: false,
voice_command_enabled: false,
worktrees_enabled: false,
windows_degraded_sandbox_active: false,
@@ -1015,10 +1013,6 @@ impl ChatComposer {
self.footer.ide_context_active = active;
}
pub fn set_personality_command_enabled(&mut self, enabled: bool) {
self.personality_command_enabled = enabled;
}
pub fn set_side_conversation_active(&mut self, active: bool) {
self.side_conversation_active = active;
}
@@ -5181,39 +5175,6 @@ mod tests {
);
}
#[test]
fn parent_owned_thread_allows_bare_navigation_commands() {
for (command, expected) in [
("/agents", SlashCommand::Agents),
("/subagents", SlashCommand::MultiAgents),
("/side", SlashCommand::Side),
("/btw", SlashCommand::Btw),
("/diff ", SlashCommand::Diff),
] {
let (mut composer, _rx) = new_test_composer();
composer.set_parent_owned_thread();
composer.set_text_content(command.to_string(), Vec::new(), Vec::new());
assert_eq!(
composer.handle_submission(/*should_queue*/ false).0,
InputResult::Command(expected)
);
}
}
#[test]
fn parent_owned_thread_allows_safe_command_selected_from_prefix() {
let (mut composer, _rx) = new_test_composer();
composer.set_parent_owned_thread();
type_chars_humanlike(&mut composer, &['/', 'a', 'g']);
let result = composer
.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE))
.0;
assert_eq!(result, InputResult::Command(SlashCommand::Agents));
}
#[test]
fn parent_owned_thread_placeholder_snapshot() {
snapshot_composer_state(
@@ -1,9 +1,43 @@
use super::tests::new_test_composer;
use super::tests::snapshot_composer_state_with_width;
use super::tests::type_chars_humanlike;
use super::*;
use assert_matches::assert_matches;
use pretty_assertions::assert_eq;
#[test]
fn parent_owned_thread_allows_bare_navigation_commands() {
for (command, expected) in [
("/agents", SlashCommand::Agents),
("/subagents", SlashCommand::MultiAgents),
("/side", SlashCommand::Side),
("/btw", SlashCommand::Btw),
("/diff ", SlashCommand::Diff),
] {
let (mut composer, _rx) = new_test_composer();
composer.set_parent_owned_thread();
composer.set_text_content(command.to_string(), Vec::new(), Vec::new());
assert_eq!(
composer.handle_submission(/*should_queue*/ false).0,
InputResult::Command(expected)
);
}
}
#[test]
fn parent_owned_thread_allows_safe_command_selected_from_prefix() {
let (mut composer, _rx) = new_test_composer();
composer.set_parent_owned_thread();
type_chars_humanlike(&mut composer, &['/', 'a', 'g']);
let result = composer
.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE))
.0;
assert_eq!(result, InputResult::Command(SlashCommand::Agents));
}
#[test]
fn left_navigates_only_from_an_empty_prompt() {
let (mut composer, mut events) = new_test_composer();
@@ -177,7 +177,6 @@ impl<'a> SlashInput<'a> {
token_activity_command_enabled: self.command_flags.token_activity_command_enabled,
service_tier_commands_enabled: self.command_flags.service_tier_commands_enabled,
goal_command_enabled: self.command_flags.goal_command_enabled,
personality_command_enabled: self.command_flags.personality_command_enabled,
voice_command_enabled: self.command_flags.voice_command_enabled,
worktrees_enabled: self.command_flags.worktrees_enabled,
windows_degraded_sandbox_active: self.command_flags.allow_elevate_sandbox,
@@ -216,7 +215,6 @@ impl ChatComposer {
token_activity_command_enabled: self.token_activity_command_enabled,
service_tier_commands_enabled: self.service_tier_commands_enabled,
goal_command_enabled: self.goal_command_enabled,
personality_command_enabled: self.personality_command_enabled,
voice_command_enabled: self.voice_command_enabled,
worktrees_enabled: self.worktrees_enabled,
allow_elevate_sandbox: self.windows_degraded_sandbox_active,
@@ -47,7 +47,6 @@ pub(crate) struct CommandPopupFlags {
pub(crate) token_activity_command_enabled: bool,
pub(crate) service_tier_commands_enabled: bool,
pub(crate) goal_command_enabled: bool,
pub(crate) personality_command_enabled: bool,
pub(crate) voice_command_enabled: bool,
pub(crate) worktrees_enabled: bool,
pub(crate) windows_degraded_sandbox_active: bool,
@@ -63,7 +62,6 @@ impl From<CommandPopupFlags> for BuiltinCommandFlags {
token_activity_command_enabled: value.token_activity_command_enabled,
service_tier_commands_enabled: value.service_tier_commands_enabled,
goal_command_enabled: value.goal_command_enabled,
personality_command_enabled: value.personality_command_enabled,
voice_command_enabled: value.voice_command_enabled,
worktrees_enabled: value.worktrees_enabled,
allow_elevate_sandbox: value.windows_degraded_sandbox_active,
@@ -563,7 +561,6 @@ mod tests {
token_activity_command_enabled: false,
service_tier_commands_enabled: false,
goal_command_enabled: false,
personality_command_enabled: true,
voice_command_enabled: false,
worktrees_enabled: true,
windows_degraded_sandbox_active: false,
@@ -582,69 +579,6 @@ mod tests {
}
}
#[test]
fn personality_command_hidden_when_disabled() {
let mut popup = CommandPopup::new(
CommandPopupFlags {
collaboration_modes_enabled: true,
connectors_enabled: false,
plugins_command_enabled: false,
token_activity_command_enabled: false,
service_tier_commands_enabled: false,
goal_command_enabled: false,
personality_command_enabled: false,
voice_command_enabled: false,
worktrees_enabled: false,
windows_degraded_sandbox_active: false,
side_conversation_active: false,
},
Vec::new(),
);
popup.on_composer_text_change("/pers".to_string());
let cmds: Vec<String> = popup
.filtered_items()
.into_iter()
.map(|item| match item {
CommandItem::Builtin(cmd) => cmd.command().to_string(),
CommandItem::ServiceTier(command) => command.name,
})
.collect();
assert!(
!cmds.iter().any(|cmd| cmd == "personality"),
"expected '/personality' to be hidden when disabled, got {cmds:?}"
);
}
#[test]
fn personality_command_visible_when_enabled() {
let mut popup = CommandPopup::new(
CommandPopupFlags {
collaboration_modes_enabled: true,
connectors_enabled: false,
plugins_command_enabled: false,
token_activity_command_enabled: false,
service_tier_commands_enabled: false,
goal_command_enabled: false,
personality_command_enabled: true,
voice_command_enabled: false,
worktrees_enabled: true,
windows_degraded_sandbox_active: false,
side_conversation_active: false,
},
Vec::new(),
);
popup.on_composer_text_change("/personality".to_string());
match popup.selected_item() {
Some(CommandItem::Builtin(cmd)) => assert_eq!(cmd.command(), "personality"),
Some(CommandItem::ServiceTier(command)) => {
panic!("expected personality command, got service tier {command:?}")
}
other => panic!("expected personality to be selected for exact match, got {other:?}"),
}
}
#[test]
fn debug_commands_are_hidden_from_popup() {
let popup = CommandPopup::new(CommandPopupFlags::default(), Vec::new());
-5
View File
@@ -531,11 +531,6 @@ impl BottomPane {
self.request_redraw();
}
pub fn set_personality_command_enabled(&mut self, enabled: bool) {
self.composer.set_personality_command_enabled(enabled);
self.request_redraw();
}
pub fn set_service_tier_commands_enabled(&mut self, enabled: bool) {
self.composer.set_service_tier_commands_enabled(enabled);
self.request_redraw();
@@ -61,7 +61,6 @@ pub(crate) struct BuiltinCommandFlags {
pub(crate) token_activity_command_enabled: bool,
pub(crate) service_tier_commands_enabled: bool,
pub(crate) goal_command_enabled: bool,
pub(crate) personality_command_enabled: bool,
pub(crate) voice_command_enabled: bool,
pub(crate) worktrees_enabled: bool,
pub(crate) allow_elevate_sandbox: bool,
@@ -79,7 +78,6 @@ pub(crate) fn builtins_for_input(flags: BuiltinCommandFlags) -> Vec<(&'static st
.filter(|(_, cmd)| flags.token_activity_command_enabled || *cmd != SlashCommand::Usage)
.filter(|(_, cmd)| flags.goal_command_enabled || *cmd != SlashCommand::Goal)
.filter(|(_, cmd)| flags.worktrees_enabled || *cmd != SlashCommand::Worktree)
.filter(|(_, cmd)| flags.personality_command_enabled || *cmd != SlashCommand::Personality)
.filter(|(_, cmd)| flags.voice_command_enabled || *cmd != SlashCommand::Voice)
.filter(|(_, cmd)| !flags.side_conversation_active || cmd.available_in_side_conversation())
.collect()
@@ -174,7 +172,6 @@ mod tests {
token_activity_command_enabled: true,
service_tier_commands_enabled: true,
goal_command_enabled: true,
personality_command_enabled: true,
voice_command_enabled: true,
worktrees_enabled: true,
allow_elevate_sandbox: true,
-1
View File
@@ -141,7 +141,6 @@ use codex_protocol::approvals::GuardianAssessmentStatus;
use codex_protocol::config_types::CollaborationMode;
use codex_protocol::config_types::CollaborationModeMask;
use codex_protocol::config_types::ModeKind;
use codex_protocol::config_types::Personality;
use codex_protocol::config_types::Settings;
#[cfg(any(target_os = "windows", test))]
use codex_protocol::config_types::WindowsSandboxLevel;
@@ -290,7 +290,6 @@ impl ChatWidget {
.bottom_pane
.set_collaboration_modes_enabled(/*enabled*/ true);
widget.sync_service_tier_commands();
widget.sync_personality_command_enabled();
widget.sync_worktrees_enabled();
widget.sync_plugins_command_enabled();
widget.sync_goal_command_enabled();
@@ -382,11 +382,6 @@ impl ChatWidget {
source,
compare_key: Self::pending_steer_compare_key_from_items(&items),
});
let personality = self
.config
.personality
.filter(|_| self.config.features.enabled(Feature::Personality))
.filter(|_| self.current_model_supports_personality());
let service_tier = self.service_tier_update_for_core();
let active_permission_profile = self.config.permissions.active_permission_profile();
let op = AppCommand::user_turn(
@@ -401,7 +396,7 @@ impl ChatWidget {
service_tier,
/*final_output_json_schema*/ None,
collaboration_mode,
personality,
/*personality*/ None,
);
let submitted_message = UserMessage {
text,
@@ -137,7 +137,6 @@ impl ChatWidget {
});
}
self.sync_service_tier_commands();
self.sync_personality_command_enabled();
self.sync_worktrees_enabled();
self.sync_plugins_command_enabled();
self.sync_goal_command_enabled();
-28
View File
@@ -83,9 +83,6 @@ impl ChatWidget {
if feature == Feature::Worktrees {
self.sync_worktrees_enabled();
}
if feature == Feature::Personality {
self.sync_personality_command_enabled();
}
if feature == Feature::Plugins {
self.sync_plugins_command_enabled();
self.refresh_plugin_mentions();
@@ -183,11 +180,6 @@ impl ChatWidget {
self.refresh_model_dependent_surfaces();
}
/// Set the personality in the widget's config copy.
pub(crate) fn set_personality(&mut self, personality: Personality) {
self.config.personality = Some(personality);
}
pub(crate) fn status_account_display(&self) -> Option<&StatusAccountDisplay> {
self.status_account_display.as_ref()
}
@@ -309,11 +301,6 @@ impl ChatWidget {
);
}
pub(super) fn sync_personality_command_enabled(&mut self) {
self.bottom_pane
.set_personality_command_enabled(self.config.features.enabled(Feature::Personality));
}
pub(super) fn sync_plugins_command_enabled(&mut self) {
self.bottom_pane
.set_plugins_command_enabled(self.config.features.enabled(Feature::Plugins));
@@ -329,20 +316,6 @@ impl ChatWidget {
.set_mentions_v2_enabled(self.config.features.enabled(Feature::MentionsV2));
}
pub(super) fn current_model_supports_personality(&self) -> bool {
let model = self.current_model();
self.model_catalog
.try_list_models()
.ok()
.and_then(|models| {
models
.into_iter()
.find(|preset| preset.model == model)
.map(|preset| preset.supports_personality)
})
.unwrap_or(false)
}
/// Return whether the effective model currently advertises image-input support.
///
/// We intentionally default to `true` when model metadata cannot be read so transient catalog
@@ -527,7 +500,6 @@ impl ChatWidget {
self.refresh_effective_service_tier();
self.refresh_status_surfaces();
self.sync_service_tier_commands();
self.sync_personality_command_enabled();
if cwd_changed {
self.invalidate_connector_scope();
self.refresh_skills_for_current_cwd(/*force_reload*/ true);
+1 -87
View File
@@ -1,6 +1,6 @@
//! Settings-adjacent popup surfaces for `ChatWidget`.
//!
//! This keeps theme, personality, and experimental-feature UI out of the main
//! This keeps theme and experimental-feature UI out of the main
//! orchestration module without changing their event wiring.
use super::*;
@@ -16,76 +16,6 @@ impl ChatWidget {
self.bottom_pane.show_selection_view(params);
}
pub(crate) fn open_personality_popup(&mut self) {
if !self.is_session_configured() {
self.add_info_message(
"Personality selection is disabled until startup completes.".to_string(),
/*hint*/ None,
);
return;
}
if !self.current_model_supports_personality() {
let current_model = self.current_model();
self.add_error_message(format!(
"Current model ({current_model}) doesn't support personalities. Try /model to pick a different model."
));
return;
}
self.open_personality_popup_for_current_model();
}
fn open_personality_popup_for_current_model(&mut self) {
let current_personality = self.config.personality.unwrap_or(Personality::Friendly);
let personalities = [Personality::Friendly, Personality::Pragmatic];
let supports_personality = self.current_model_supports_personality();
let items: Vec<SelectionItem> = personalities
.into_iter()
.map(|personality| {
let name = Self::personality_label(personality).to_string();
let description = Some(Self::personality_description(personality).to_string());
let actions: Vec<SelectionAction> = vec![Box::new(move |tx| {
tx.send(AppEvent::CodexOp(AppCommand::override_turn_context(
/*cwd*/ None,
/*approval_policy*/ None,
/*approvals_reviewer*/ None,
/*permission_profile*/ None,
/*active_permission_profile*/ None,
/*windows_sandbox_level*/ None,
/*model*/ None,
/*effort*/ None,
/*summary*/ None,
/*service_tier*/ None,
/*collaboration_mode*/ None,
Some(personality),
)));
tx.send(AppEvent::UpdatePersonality(personality));
tx.send(AppEvent::PersistPersonalitySelection { personality });
})];
SelectionItem {
name,
description,
is_current: current_personality == personality,
is_disabled: !supports_personality,
actions,
dismiss_on_select: true,
..Default::default()
}
})
.collect();
let mut header = ColumnRenderable::new();
header.push(Line::from("Select Personality".bold()));
header.push(Line::from("Choose a communication style for Codex.".dim()));
self.bottom_pane.show_selection_view(SelectionViewParams {
header: Box::new(header),
footer_hint: Some(standard_popup_hint_line()),
items,
..Default::default()
});
}
pub(crate) fn open_experimental_popup(&mut self) {
let Some(thread_id) = self.thread_id() else {
self.add_info_message(
@@ -108,20 +38,4 @@ impl ChatWidget {
);
self.bottom_pane.show_view(Box::new(view));
}
fn personality_label(personality: Personality) -> &'static str {
match personality {
Personality::None => "None",
Personality::Friendly => "Friendly",
Personality::Pragmatic => "Pragmatic",
}
}
fn personality_description(personality: Personality) -> &'static str {
match personality {
Personality::None => "No personality instructions.",
Personality::Friendly => "Warm, collaborative, and helpful.",
Personality::Pragmatic => "Concise, task-focused, and direct.",
}
}
}
@@ -315,10 +315,6 @@ impl ChatWidget {
self.open_model_popup();
self.defer_input_until_settings_applied();
}
SlashCommand::Personality => {
self.open_personality_popup();
self.defer_input_until_settings_applied();
}
SlashCommand::Plan => {
self.apply_plan_slash_command();
}
@@ -1147,7 +1143,6 @@ impl ChatWidget {
token_activity_command_enabled: self.has_codex_backend_auth,
goal_command_enabled: self.config.features.enabled(Feature::Goals),
service_tier_commands_enabled: self.fast_mode_enabled(),
personality_command_enabled: self.config.features.enabled(Feature::Personality),
voice_command_enabled: self.realtime_conversation_available_for_thread,
worktrees_enabled: self.config.features.enabled(Feature::Worktrees)
&& self.local_worktree_operations,
@@ -1214,7 +1209,6 @@ impl ChatWidget {
| SlashCommand::Compact
| SlashCommand::Review
| SlashCommand::Model
| SlashCommand::Personality
| SlashCommand::Plan
| SlashCommand::Goal
| SlashCommand::Side
@@ -1,11 +0,0 @@
---
source: tui/src/chatwidget/tests/popups_and_settings.rs
expression: popup
---
Select Personality
Choose a communication style for Codex.
1. Friendly Warm, collaborative, and helpful.
› 2. Pragmatic (current) Concise, task-focused, and direct.
Press enter to confirm or esc to go back
@@ -1751,54 +1751,6 @@ async fn collab_mode_applies_default_preset() {
assert_eq!(chat.current_collaboration_mode().mode, ModeKind::Default);
}
#[tokio::test]
async fn user_turn_includes_personality_from_config() {
for configured_personality in [
Personality::Friendly,
Personality::Pragmatic,
Personality::None,
] {
let preset = crate::test_support::legacy_personality_model_preset();
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(Some(&preset.model)).await;
Arc::make_mut(&mut chat.model_catalog).models = vec![preset];
chat.set_feature_enabled(Feature::Personality, /*enabled*/ true);
chat.thread_id = Some(ThreadId::new());
chat.set_personality(configured_personality);
chat.bottom_pane
.set_composer_text("hello".to_string(), Vec::new(), Vec::new());
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
let Op::UserTurn { personality, .. } = next_submit_op(&mut op_rx) else {
panic!("expected Op::UserTurn");
};
assert_eq!(personality, Some(configured_personality));
}
}
#[tokio::test]
async fn user_turn_omits_personality_for_fixed_personality_models() {
for model in ["gpt-5.4", "gpt-5.5"] {
for configured_personality in [
Personality::Friendly,
Personality::Pragmatic,
Personality::None,
] {
let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(Some(model)).await;
chat.set_feature_enabled(Feature::Personality, /*enabled*/ true);
chat.thread_id = Some(ThreadId::new());
chat.set_personality(configured_personality);
chat.bottom_pane
.set_composer_text("hello".to_string(), Vec::new(), Vec::new());
chat.handle_key_event(KeyEvent::from(KeyCode::Enter));
let Op::UserTurn { personality, .. } = next_submit_op(&mut op_rx) else {
panic!("expected Op::UserTurn");
};
assert_eq!(personality, None, "model: {model}");
}
}
}
#[tokio::test]
async fn plan_update_renders_history_cell() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
@@ -3589,37 +3589,6 @@ async fn model_picker_refresh_dismisses_empty_choices() {
}
}
#[tokio::test]
async fn personality_selection_popup_snapshot() {
let preset = crate::test_support::legacy_personality_model_preset();
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some(&preset.model)).await;
Arc::make_mut(&mut chat.model_catalog).models = vec![preset];
chat.thread_id = Some(ThreadId::new());
chat.set_personality(Personality::Pragmatic);
chat.open_personality_popup();
let popup = render_bottom_popup(&chat, /*width*/ 80);
assert_chatwidget_snapshot!("personality_selection_popup", popup);
}
#[tokio::test]
async fn personality_selection_is_unavailable_for_fixed_personality_models() {
for model in ["gpt-5.4", "gpt-5.5"] {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some(model)).await;
chat.thread_id = Some(ThreadId::new());
while rx.try_recv().is_ok() {}
chat.open_personality_popup();
assert!(!chat.bottom_pane.has_active_view());
let cell = assert_matches!(rx.try_recv(), Ok(AppEvent::InsertHistoryCell(cell)) => cell);
let message = lines_to_single_string(&cell.display_lines(/*width*/ 120));
assert!(message.contains(&format!(
"Current model ({model}) doesn't support personalities."
)));
}
}
#[tokio::test]
async fn skills_menu_default_mentions_shortcut_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
-3
View File
@@ -72,7 +72,6 @@ pub enum SlashCommand {
#[strum(to_string = "stop", serialize = "clean")]
Stop,
Clear,
Personality,
TestApproval,
#[strum(serialize = "subagents")]
MultiAgents,
@@ -127,7 +126,6 @@ impl SlashCommand {
SlashCommand::Ide => {
"include current selection, open files, and other context from your IDE"
}
SlashCommand::Personality => "choose a communication style for Codex",
SlashCommand::Plan => "switch to Plan mode",
SlashCommand::Voice => "start or stop voice; use /voice settings to choose a voice",
SlashCommand::Goal => "set or view the goal for a long-running task",
@@ -230,7 +228,6 @@ impl SlashCommand {
SlashCommand::Diff
| SlashCommand::Resume
| SlashCommand::Model
| SlashCommand::Personality
| SlashCommand::Permissions
| SlashCommand::Copy
| SlashCommand::Raw
-31
View File
@@ -4,9 +4,6 @@ use std::sync::LazyLock;
use codex_models_manager::bundled_models_response;
use codex_protocol::openai_models::ModelPreset;
use codex_protocol::openai_models::ReasoningEffort;
use codex_protocol::openai_models::ReasoningEffortPreset;
use codex_protocol::openai_models::default_input_modalities;
pub(crate) use codex_utils_absolute_path::test_support::PathBufExt;
pub(crate) use codex_utils_absolute_path::test_support::test_path_buf;
use serde::Serialize;
@@ -21,34 +18,6 @@ pub(crate) static TEST_MODEL_PRESETS: LazyLock<Vec<ModelPreset>> = LazyLock::new
presets
});
/// A legacy catalog entry whose personality remains selectable, independent of bundled models.
pub(crate) fn legacy_personality_model_preset() -> ModelPreset {
ModelPreset {
id: "legacy-personality-model".to_string(),
model: "legacy-personality-model".to_string(),
display_name: "Legacy personality model".to_string(),
description: "Test model with selectable personalities.".to_string(),
model_specialty: None,
default_reasoning_effort: ReasoningEffort::Medium,
supported_reasoning_efforts: vec![ReasoningEffortPreset {
effort: ReasoningEffort::Medium,
description: "medium".to_string(),
}],
supports_personality: true,
additional_speed_tiers: Vec::new(),
service_tiers: Vec::new(),
default_service_tier: None,
available_access_programs: None,
is_default: false,
upgrade: None,
show_in_picker: true,
multi_agent_version: None,
availability_nux: None,
supported_in_api: true,
input_modalities: default_input_modalities(),
}
}
pub(crate) fn test_path_display(path: &str) -> String {
test_path_buf(path).display().to_string()
}