mirror of
https://github.com/openai/codex.git
synced 2026-10-21 05:22:21 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,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
|
||||
|
||||
-11
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user