mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
Add file-backed personality support and listing
This commit is contained in:
+20
-14
@@ -33,6 +33,7 @@ use crate::models_manager::manager::RefreshStrategy;
|
||||
use crate::parse_command::parse_command;
|
||||
use crate::parse_turn_item;
|
||||
use crate::path_utils::normalize_for_native_workdir;
|
||||
use crate::personalities::catalog_for_config;
|
||||
use crate::realtime_conversation::RealtimeConversationManager;
|
||||
use crate::realtime_conversation::handle_audio as handle_realtime_conversation_audio;
|
||||
use crate::realtime_conversation::handle_close as handle_realtime_conversation_close;
|
||||
@@ -572,7 +573,7 @@ impl Codex {
|
||||
.base_instructions
|
||||
.clone()
|
||||
.or_else(|| conversation_history.get_base_instructions().map(|s| s.text))
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality));
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality.clone()));
|
||||
|
||||
// Respect thread-start tools. When missing (resumed/forked threads), read from the db
|
||||
// first, then fall back to rollout-file tools.
|
||||
@@ -618,7 +619,7 @@ impl Codex {
|
||||
service_tier: config.service_tier,
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions,
|
||||
personality: config.personality,
|
||||
personality: config.personality.clone(),
|
||||
base_instructions,
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
@@ -970,7 +971,7 @@ impl TurnContext {
|
||||
compact_prompt: self.compact_prompt.clone(),
|
||||
user_instructions: self.user_instructions.clone(),
|
||||
collaboration_mode,
|
||||
personality: self.personality,
|
||||
personality: self.personality.clone(),
|
||||
approval_policy: self.approval_policy.clone(),
|
||||
sandbox_policy: self.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: self.file_system_sandbox_policy.clone(),
|
||||
@@ -1017,7 +1018,7 @@ impl TurnContext {
|
||||
sandbox_policy: self.sandbox_policy.get().clone(),
|
||||
network: self.turn_context_network_item(),
|
||||
model: self.model_info.slug.clone(),
|
||||
personality: self.personality,
|
||||
personality: self.personality.clone(),
|
||||
collaboration_mode: Some(self.collaboration_mode.clone()),
|
||||
realtime_active: Some(self.realtime_active),
|
||||
effort: self.reasoning_effort,
|
||||
@@ -1133,7 +1134,7 @@ impl SessionConfiguration {
|
||||
cwd: self.cwd.to_path_buf(),
|
||||
ephemeral: self.original_config_do_not_use.ephemeral,
|
||||
reasoning_effort: self.collaboration_mode.reasoning_effort(),
|
||||
personality: self.personality,
|
||||
personality: self.personality.clone(),
|
||||
session_source: self.session_source.clone(),
|
||||
}
|
||||
}
|
||||
@@ -1154,7 +1155,7 @@ impl SessionConfiguration {
|
||||
if let Some(service_tier) = updates.service_tier {
|
||||
next_configuration.service_tier = service_tier;
|
||||
}
|
||||
if let Some(personality) = updates.personality {
|
||||
if let Some(personality) = updates.personality.clone() {
|
||||
next_configuration.personality = Some(personality);
|
||||
}
|
||||
if let Some(approval_policy) = updates.approval_policy {
|
||||
@@ -1297,7 +1298,7 @@ impl Session {
|
||||
session_configuration.collaboration_mode.reasoning_effort();
|
||||
per_turn_config.model_reasoning_summary = session_configuration.model_reasoning_summary;
|
||||
per_turn_config.service_tier = session_configuration.service_tier;
|
||||
per_turn_config.personality = session_configuration.personality;
|
||||
per_turn_config.personality = session_configuration.personality.clone();
|
||||
per_turn_config.approvals_reviewer = session_configuration.approvals_reviewer;
|
||||
let resolved_web_search_mode = resolve_web_search_mode_for_turn(
|
||||
&per_turn_config.web_search_mode,
|
||||
@@ -1436,7 +1437,7 @@ impl Session {
|
||||
compact_prompt: session_configuration.compact_prompt.clone(),
|
||||
user_instructions: session_configuration.user_instructions.clone(),
|
||||
collaboration_mode: session_configuration.collaboration_mode.clone(),
|
||||
personality: session_configuration.personality,
|
||||
personality: session_configuration.personality.clone(),
|
||||
approval_policy: session_configuration.approval_policy.clone(),
|
||||
sandbox_policy: session_configuration.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: session_configuration.file_system_sandbox_policy.clone(),
|
||||
@@ -1884,6 +1885,7 @@ impl Session {
|
||||
auth_manager: Arc::clone(&auth_manager),
|
||||
session_telemetry,
|
||||
models_manager: Arc::clone(&models_manager),
|
||||
personality_catalog: Arc::new(catalog_for_config(config.as_ref())),
|
||||
tool_approvals: Mutex::new(ApprovalStore::default()),
|
||||
skills_manager,
|
||||
plugins_manager: Arc::clone(&plugins_manager),
|
||||
@@ -2628,6 +2630,7 @@ impl Session {
|
||||
current_context,
|
||||
shell.as_ref(),
|
||||
exec_policy.as_ref(),
|
||||
self.services.personality_catalog.as_ref(),
|
||||
self.features.enabled(Feature::Personality),
|
||||
)
|
||||
}
|
||||
@@ -3613,16 +3616,19 @@ impl Session {
|
||||
developer_sections.push(realtime_update.into_text());
|
||||
}
|
||||
if self.features.enabled(Feature::Personality)
|
||||
&& let Some(personality) = turn_context.personality
|
||||
&& let Some(personality) = turn_context.personality.clone()
|
||||
{
|
||||
let model_info = turn_context.model_info.clone();
|
||||
let has_baked_personality = model_info.supports_personality()
|
||||
&& base_instructions == model_info.get_model_instructions(Some(personality));
|
||||
let has_baked_personality = personality.is_builtin()
|
||||
&& model_info.supports_personality()
|
||||
&& base_instructions
|
||||
== model_info.get_model_instructions(Some(personality.clone()));
|
||||
if !has_baked_personality
|
||||
&& let Some(personality_message) =
|
||||
crate::context_manager::updates::personality_message_for(
|
||||
&model_info,
|
||||
personality,
|
||||
&self.services.personality_catalog,
|
||||
&personality,
|
||||
)
|
||||
{
|
||||
developer_sections.push(
|
||||
@@ -5541,7 +5547,7 @@ async fn spawn_review_thread(
|
||||
user_instructions: None,
|
||||
compact_prompt: parent_turn_context.compact_prompt.clone(),
|
||||
collaboration_mode: parent_turn_context.collaboration_mode.clone(),
|
||||
personality: parent_turn_context.personality,
|
||||
personality: parent_turn_context.personality.clone(),
|
||||
approval_policy: parent_turn_context.approval_policy.clone(),
|
||||
sandbox_policy: parent_turn_context.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: parent_turn_context.file_system_sandbox_policy.clone(),
|
||||
@@ -6418,7 +6424,7 @@ pub(crate) fn build_prompt(
|
||||
tools,
|
||||
parallel_tool_calls: turn_context.model_info.supports_parallel_tool_calls,
|
||||
base_instructions,
|
||||
personality: turn_context.personality,
|
||||
personality: turn_context.personality.clone(),
|
||||
output_schema: turn_context.final_output_json_schema.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ async fn record_initial_history_resumed_bare_turn_context_does_not_hydrate_previ
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
network: None,
|
||||
model: previous_model.to_string(),
|
||||
personality: turn_context.personality,
|
||||
personality: turn_context.personality.clone(),
|
||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
effort: turn_context.reasoning_effort,
|
||||
@@ -108,7 +108,7 @@ async fn record_initial_history_resumed_hydrates_previous_turn_settings_from_lif
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
network: None,
|
||||
model: previous_model.to_string(),
|
||||
personality: turn_context.personality,
|
||||
personality: turn_context.personality.clone(),
|
||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
effort: turn_context.reasoning_effort,
|
||||
@@ -858,7 +858,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
network: None,
|
||||
model: previous_model.to_string(),
|
||||
personality: turn_context.personality,
|
||||
personality: turn_context.personality.clone(),
|
||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
effort: turn_context.reasoning_effort,
|
||||
@@ -930,7 +930,7 @@ async fn record_initial_history_resumed_turn_context_after_compaction_reestablis
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
network: None,
|
||||
model: previous_model.to_string(),
|
||||
personality: turn_context.personality,
|
||||
personality: turn_context.personality.clone(),
|
||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
effort: turn_context.reasoning_effort,
|
||||
@@ -959,7 +959,7 @@ async fn record_initial_history_resumed_aborted_turn_without_id_clears_active_tu
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
network: None,
|
||||
model: previous_model.to_string(),
|
||||
personality: turn_context.personality,
|
||||
personality: turn_context.personality.clone(),
|
||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
effort: turn_context.reasoning_effort,
|
||||
@@ -1065,7 +1065,7 @@ async fn record_initial_history_resumed_unmatched_abort_preserves_active_turn_fo
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
network: None,
|
||||
model: current_model.to_string(),
|
||||
personality: turn_context.personality,
|
||||
personality: turn_context.personality.clone(),
|
||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
effort: turn_context.reasoning_effort,
|
||||
@@ -1167,7 +1167,7 @@ async fn record_initial_history_resumed_trailing_incomplete_turn_compaction_clea
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
network: None,
|
||||
model: previous_model.to_string(),
|
||||
personality: turn_context.personality,
|
||||
personality: turn_context.personality.clone(),
|
||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
effort: turn_context.reasoning_effort,
|
||||
@@ -1311,7 +1311,7 @@ async fn record_initial_history_resumed_replaced_incomplete_compacted_turn_clear
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
network: None,
|
||||
model: previous_model.to_string(),
|
||||
personality: turn_context.personality,
|
||||
personality: turn_context.personality.clone(),
|
||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
effort: turn_context.reasoning_effort,
|
||||
|
||||
@@ -1268,7 +1268,7 @@ async fn record_initial_history_forked_hydrates_previous_turn_settings() {
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
network: None,
|
||||
model: previous_model.to_string(),
|
||||
personality: turn_context.personality,
|
||||
personality: turn_context.personality.clone(),
|
||||
collaboration_mode: Some(turn_context.collaboration_mode.clone()),
|
||||
realtime_active: Some(turn_context.realtime_active),
|
||||
effort: turn_context.reasoning_effort,
|
||||
@@ -1801,11 +1801,11 @@ async fn set_rate_limits_retains_previous_credits() {
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
service_tier: None,
|
||||
personality: config.personality,
|
||||
personality: config.personality.clone(),
|
||||
base_instructions: config
|
||||
.base_instructions
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality.clone())),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
@@ -1899,11 +1899,11 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
service_tier: None,
|
||||
personality: config.personality,
|
||||
personality: config.personality.clone(),
|
||||
base_instructions: config
|
||||
.base_instructions
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality.clone())),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
@@ -2243,11 +2243,11 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
service_tier: None,
|
||||
personality: config.personality,
|
||||
personality: config.personality.clone(),
|
||||
base_instructions: config
|
||||
.base_instructions
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality.clone())),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
@@ -2505,11 +2505,11 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
service_tier: None,
|
||||
personality: config.personality,
|
||||
personality: config.personality.clone(),
|
||||
base_instructions: config
|
||||
.base_instructions
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality.clone())),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
@@ -2602,11 +2602,11 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
service_tier: None,
|
||||
personality: config.personality,
|
||||
personality: config.personality.clone(),
|
||||
base_instructions: config
|
||||
.base_instructions
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality.clone())),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
@@ -2682,6 +2682,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
auth_manager: auth_manager.clone(),
|
||||
session_telemetry: session_telemetry.clone(),
|
||||
models_manager: Arc::clone(&models_manager),
|
||||
personality_catalog: Arc::new(crate::personalities::catalog_for_config(config.as_ref())),
|
||||
tool_approvals: Mutex::new(ApprovalStore::default()),
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
@@ -3104,7 +3105,7 @@ async fn user_turn_updates_approvals_reviewer() {
|
||||
service_tier: None,
|
||||
final_output_json_schema: None,
|
||||
collaboration_mode: None,
|
||||
personality: config.personality,
|
||||
personality: config.personality.clone(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@@ -3445,11 +3446,11 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
developer_instructions: config.developer_instructions.clone(),
|
||||
user_instructions: config.user_instructions.clone(),
|
||||
service_tier: None,
|
||||
personality: config.personality,
|
||||
personality: config.personality.clone(),
|
||||
base_instructions: config
|
||||
.base_instructions
|
||||
.clone()
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality)),
|
||||
.unwrap_or_else(|| model_info.get_model_instructions(config.personality.clone())),
|
||||
compact_prompt: config.compact_prompt.clone(),
|
||||
approval_policy: config.permissions.approval_policy.clone(),
|
||||
approvals_reviewer: config.approvals_reviewer,
|
||||
@@ -3525,6 +3526,7 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
auth_manager: Arc::clone(&auth_manager),
|
||||
session_telemetry: session_telemetry.clone(),
|
||||
models_manager: Arc::clone(&models_manager),
|
||||
personality_catalog: Arc::new(crate::personalities::catalog_for_config(config.as_ref())),
|
||||
tool_approvals: Mutex::new(ApprovalStore::default()),
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
@@ -4944,13 +4946,16 @@ async fn sample_rollout(
|
||||
&& content.iter().any(|c| {
|
||||
matches!(c, ContentItem::InputText { text } if text.contains("<personality_spec>"))
|
||||
}))
|
||||
}) && let Some(p) = reconstruction_turn.personality
|
||||
}) && let Some(p) = reconstruction_turn.personality.as_ref()
|
||||
&& session.features.enabled(Feature::Personality)
|
||||
&& let Some(personality_message) = reconstruction_turn
|
||||
.model_info
|
||||
.model_messages
|
||||
.as_ref()
|
||||
.and_then(|m| m.get_personality_message(Some(p)).filter(|s| !s.is_empty()))
|
||||
.and_then(|m| {
|
||||
m.get_personality_message(Some(p.clone()))
|
||||
.filter(|s| !s.is_empty())
|
||||
})
|
||||
{
|
||||
let msg = DeveloperInstructions::personality_spec_message(personality_message).into();
|
||||
let insert_at = initial_context
|
||||
|
||||
@@ -122,7 +122,7 @@ async fn run_compact_task_inner(
|
||||
let prompt = Prompt {
|
||||
input: turn_input,
|
||||
base_instructions: sess.get_base_instructions().await,
|
||||
personality: turn_context.personality,
|
||||
personality: turn_context.personality.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
let turn_metadata_header = turn_context.turn_metadata_state.current_header_value();
|
||||
|
||||
@@ -110,7 +110,7 @@ async fn run_remote_compact_task_inner_impl(
|
||||
tools: tool_router.model_visible_specs(),
|
||||
parallel_tool_calls: turn_context.model_info.supports_parallel_tool_calls,
|
||||
base_instructions,
|
||||
personality: turn_context.personality,
|
||||
personality: turn_context.personality.clone(),
|
||||
output_schema: None,
|
||||
};
|
||||
|
||||
|
||||
@@ -4457,7 +4457,7 @@ fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
|
||||
model_supports_reasoning_summaries: None,
|
||||
model_catalog: None,
|
||||
model_verbosity: None,
|
||||
personality: Some(Personality::Pragmatic),
|
||||
personality: Some(Personality::pragmatic()),
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
realtime_audio: RealtimeAudioConfig::default(),
|
||||
experimental_realtime_start_instructions: None,
|
||||
@@ -4599,7 +4599,7 @@ fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
|
||||
model_supports_reasoning_summaries: None,
|
||||
model_catalog: None,
|
||||
model_verbosity: None,
|
||||
personality: Some(Personality::Pragmatic),
|
||||
personality: Some(Personality::pragmatic()),
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
realtime_audio: RealtimeAudioConfig::default(),
|
||||
experimental_realtime_start_instructions: None,
|
||||
@@ -4739,7 +4739,7 @@ fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
|
||||
model_supports_reasoning_summaries: None,
|
||||
model_catalog: None,
|
||||
model_verbosity: None,
|
||||
personality: Some(Personality::Pragmatic),
|
||||
personality: Some(Personality::pragmatic()),
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
realtime_audio: RealtimeAudioConfig::default(),
|
||||
experimental_realtime_start_instructions: None,
|
||||
@@ -4865,7 +4865,7 @@ fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
|
||||
model_supports_reasoning_summaries: None,
|
||||
model_catalog: None,
|
||||
model_verbosity: Some(Verbosity::High),
|
||||
personality: Some(Personality::Pragmatic),
|
||||
personality: Some(Personality::pragmatic()),
|
||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||
realtime_audio: RealtimeAudioConfig::default(),
|
||||
experimental_realtime_start_instructions: None,
|
||||
|
||||
@@ -383,7 +383,9 @@ impl ConfigDocument {
|
||||
)),
|
||||
ConfigEdit::SetModelPersonality { personality } => Ok(self.write_profile_value(
|
||||
&["personality"],
|
||||
personality.map(|personality| value(personality.to_string())),
|
||||
personality
|
||||
.clone()
|
||||
.map(|personality| value(personality.to_string())),
|
||||
)),
|
||||
ConfigEdit::SetNoticeHideFullAccessWarning(acknowledged) => Ok(self.write_value(
|
||||
Scope::Global,
|
||||
|
||||
@@ -2431,7 +2431,7 @@ impl Config {
|
||||
.or_else(|| {
|
||||
features
|
||||
.enabled(Feature::Personality)
|
||||
.then_some(Personality::Pragmatic)
|
||||
.then_some(Personality::pragmatic())
|
||||
});
|
||||
|
||||
let experimental_compact_prompt_path = config_profile
|
||||
|
||||
@@ -130,7 +130,10 @@ impl ContextManager {
|
||||
// This is a coarse lower bound, not a tokenizer-accurate count.
|
||||
pub(crate) fn estimate_token_count(&self, turn_context: &TurnContext) -> Option<i64> {
|
||||
let model_info = &turn_context.model_info;
|
||||
let personality = turn_context.personality.or(turn_context.config.personality);
|
||||
let personality = turn_context
|
||||
.personality
|
||||
.clone()
|
||||
.or(turn_context.config.personality.clone());
|
||||
let base_instructions = BaseInstructions {
|
||||
text: model_info.get_model_instructions(personality),
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::codex::PreviousTurnSettings;
|
||||
use crate::codex::TurnContext;
|
||||
use crate::environment_context::EnvironmentContext;
|
||||
use crate::personalities::PersonalityCatalog;
|
||||
use crate::shell::Shell;
|
||||
use codex_execpolicy::Policy;
|
||||
use codex_features::Feature;
|
||||
@@ -107,6 +108,7 @@ pub(crate) fn build_initial_realtime_item(
|
||||
fn build_personality_update_item(
|
||||
previous: Option<&TurnContextItem>,
|
||||
next: &TurnContext,
|
||||
personality_catalog: &PersonalityCatalog,
|
||||
personality_feature_enabled: bool,
|
||||
) -> Option<DeveloperInstructions> {
|
||||
if !personality_feature_enabled {
|
||||
@@ -117,11 +119,12 @@ fn build_personality_update_item(
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(personality) = next.personality
|
||||
if let Some(personality) = next.personality.clone()
|
||||
&& next.personality != previous.personality
|
||||
{
|
||||
let model_info = &next.model_info;
|
||||
let personality_message = personality_message_for(model_info, personality);
|
||||
let personality_message =
|
||||
personality_message_for(model_info, personality_catalog, &personality);
|
||||
personality_message.map(DeveloperInstructions::personality_spec_message)
|
||||
} else {
|
||||
None
|
||||
@@ -130,13 +133,22 @@ fn build_personality_update_item(
|
||||
|
||||
pub(crate) fn personality_message_for(
|
||||
model_info: &ModelInfo,
|
||||
personality: Personality,
|
||||
personality_catalog: &PersonalityCatalog,
|
||||
personality: &Personality,
|
||||
) -> Option<String> {
|
||||
model_info
|
||||
let builtin_message = model_info
|
||||
.model_messages
|
||||
.as_ref()
|
||||
.and_then(|spec| spec.get_personality_message(Some(personality)))
|
||||
.filter(|message| !message.is_empty())
|
||||
.and_then(|spec| spec.get_personality_message(Some(personality.clone())))
|
||||
.filter(|message| !message.is_empty());
|
||||
if builtin_message.is_some() {
|
||||
builtin_message
|
||||
} else {
|
||||
personality_catalog
|
||||
.instructions_for(personality)
|
||||
.map(ToOwned::to_owned)
|
||||
.filter(|message| !message.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build_model_instructions_update_item(
|
||||
@@ -148,7 +160,9 @@ pub(crate) fn build_model_instructions_update_item(
|
||||
return None;
|
||||
}
|
||||
|
||||
let model_instructions = next.model_info.get_model_instructions(next.personality);
|
||||
let model_instructions = next
|
||||
.model_info
|
||||
.get_model_instructions(next.personality.clone());
|
||||
if model_instructions.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -191,6 +205,7 @@ pub(crate) fn build_settings_update_items(
|
||||
next: &TurnContext,
|
||||
shell: &Shell,
|
||||
exec_policy: &Policy,
|
||||
personality_catalog: &PersonalityCatalog,
|
||||
personality_feature_enabled: bool,
|
||||
) -> Vec<ResponseItem> {
|
||||
// TODO(ccunningham): build_settings_update_items still does not cover every
|
||||
@@ -205,7 +220,12 @@ pub(crate) fn build_settings_update_items(
|
||||
build_permissions_update_item(previous, next, exec_policy),
|
||||
build_collaboration_mode_update_item(previous, next),
|
||||
build_realtime_update_item(previous, previous_turn_settings, next),
|
||||
build_personality_update_item(previous, next, personality_feature_enabled),
|
||||
build_personality_update_item(
|
||||
previous,
|
||||
next,
|
||||
personality_catalog,
|
||||
personality_feature_enabled,
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
|
||||
@@ -331,7 +331,7 @@ pub(super) async fn run_guardian_review_session(
|
||||
model: guardian_model,
|
||||
reasoning_effort: guardian_reasoning_effort,
|
||||
reasoning_summary: turn.reasoning_summary,
|
||||
personality: turn.personality,
|
||||
personality: turn.personality.clone(),
|
||||
external_cancel,
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -522,7 +522,7 @@ async fn run_review_on_session(
|
||||
service_tier: None,
|
||||
final_output_json_schema: Some(params.schema.clone()),
|
||||
collaboration_mode: None,
|
||||
personality: params.personality,
|
||||
personality: params.personality.clone(),
|
||||
})
|
||||
.await
|
||||
}),
|
||||
|
||||
@@ -63,6 +63,7 @@ pub mod message_history;
|
||||
mod model_provider_info;
|
||||
pub mod utils;
|
||||
pub use utils::path_utils;
|
||||
pub mod personalities;
|
||||
pub mod personality_migration;
|
||||
pub mod plugins;
|
||||
mod provider_auth;
|
||||
|
||||
@@ -0,0 +1,487 @@
|
||||
/*!
|
||||
Loads built-in and file-backed personality definitions, resolves precedence
|
||||
between repo and user scopes, and exposes the catalog used by config, prompt
|
||||
construction, and the app-server personality picker.
|
||||
*/
|
||||
|
||||
use crate::config::Config;
|
||||
use codex_app_server_protocol::ConfigLayerSource;
|
||||
use codex_config::ConfigLayerStack;
|
||||
use codex_config::ConfigLayerStackOrdering;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use tracing::warn;
|
||||
|
||||
const PERSONALITIES_DIR_NAME: &str = "personalities";
|
||||
const MAX_NAME_LEN: usize = 64;
|
||||
const MAX_DESCRIPTION_LEN: usize = 1024;
|
||||
const BUILTIN_FRIENDLY_INSTRUCTIONS: &str =
|
||||
include_str!("../templates/personalities/gpt-5.2-codex_friendly.md");
|
||||
const BUILTIN_PRAGMATIC_INSTRUCTIONS: &str =
|
||||
include_str!("../templates/personalities/gpt-5.2-codex_pragmatic.md");
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PersonalityScope {
|
||||
Builtin,
|
||||
User,
|
||||
Repo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PersonalityDefinition {
|
||||
pub name: Personality,
|
||||
pub description: String,
|
||||
pub instructions: Option<String>,
|
||||
pub scope: PersonalityScope,
|
||||
pub is_builtin: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PersonalityCatalog {
|
||||
personalities: Vec<PersonalityDefinition>,
|
||||
by_name: HashMap<Personality, PersonalityDefinition>,
|
||||
}
|
||||
|
||||
impl PersonalityCatalog {
|
||||
pub fn personalities(&self) -> &[PersonalityDefinition] {
|
||||
self.personalities.as_slice()
|
||||
}
|
||||
|
||||
pub fn get(&self, personality: &Personality) -> Option<&PersonalityDefinition> {
|
||||
self.by_name.get(personality)
|
||||
}
|
||||
|
||||
pub fn instructions_for(&self, personality: &Personality) -> Option<&str> {
|
||||
self.get(personality)
|
||||
.and_then(|definition| definition.instructions.as_deref())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct PersonalityLoadOutcome {
|
||||
personalities: Vec<PersonalityDefinition>,
|
||||
errors: Vec<PersonalityError>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct PersonalityError {
|
||||
path: PathBuf,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct PersonalityFrontmatter {
|
||||
name: String,
|
||||
description: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum PersonalityParseError {
|
||||
Read(std::io::Error),
|
||||
MissingFrontmatter,
|
||||
InvalidYaml(serde_yaml::Error),
|
||||
MissingBody,
|
||||
InvalidName(String),
|
||||
InvalidField { field: &'static str, reason: String },
|
||||
}
|
||||
|
||||
impl fmt::Display for PersonalityParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
PersonalityParseError::Read(error) => write!(f, "failed to read file: {error}"),
|
||||
PersonalityParseError::MissingFrontmatter => {
|
||||
write!(f, "missing YAML frontmatter delimited by ---")
|
||||
}
|
||||
PersonalityParseError::InvalidYaml(error) => write!(f, "invalid YAML: {error}"),
|
||||
PersonalityParseError::MissingBody => write!(f, "missing markdown body"),
|
||||
PersonalityParseError::InvalidName(reason) => write!(f, "invalid name: {reason}"),
|
||||
PersonalityParseError::InvalidField { field, reason } => {
|
||||
write!(f, "invalid {field}: {reason}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for PersonalityParseError {}
|
||||
|
||||
pub fn catalog_for_config(config: &Config) -> PersonalityCatalog {
|
||||
catalog_from_layer_stack(&config.config_layer_stack)
|
||||
}
|
||||
|
||||
pub fn catalog_from_layer_stack(config_layer_stack: &ConfigLayerStack) -> PersonalityCatalog {
|
||||
let mut outcome = load_personalities_from_layer_stack(config_layer_stack);
|
||||
for error in &outcome.errors {
|
||||
warn!(
|
||||
path = %error.path.display(),
|
||||
error = %error.message,
|
||||
"ignoring invalid personality definition"
|
||||
);
|
||||
}
|
||||
|
||||
outcome.personalities.sort_by(|left, right| {
|
||||
left.is_builtin
|
||||
.cmp(&right.is_builtin)
|
||||
.reverse()
|
||||
.then_with(|| {
|
||||
left.name
|
||||
.as_str()
|
||||
.to_ascii_lowercase()
|
||||
.cmp(&right.name.as_str().to_ascii_lowercase())
|
||||
})
|
||||
.then_with(|| left.name.as_str().cmp(right.name.as_str()))
|
||||
});
|
||||
|
||||
let mut by_name = HashMap::new();
|
||||
for personality in outcome.personalities.iter().cloned() {
|
||||
by_name.insert(personality.name.clone(), personality);
|
||||
}
|
||||
PersonalityCatalog {
|
||||
personalities: outcome.personalities,
|
||||
by_name,
|
||||
}
|
||||
}
|
||||
|
||||
fn load_personalities_from_layer_stack(
|
||||
config_layer_stack: &ConfigLayerStack,
|
||||
) -> PersonalityLoadOutcome {
|
||||
let mut outcome = PersonalityLoadOutcome {
|
||||
personalities: builtin_personalities(),
|
||||
..Default::default()
|
||||
};
|
||||
let mut seen: HashSet<Personality> = outcome
|
||||
.personalities
|
||||
.iter()
|
||||
.map(|personality| personality.name.clone())
|
||||
.collect();
|
||||
|
||||
for (root, scope) in personality_roots(config_layer_stack) {
|
||||
discover_personalities_under_root(root.as_path(), scope, &mut seen, &mut outcome);
|
||||
}
|
||||
|
||||
outcome
|
||||
}
|
||||
|
||||
fn builtin_personalities() -> Vec<PersonalityDefinition> {
|
||||
vec![
|
||||
PersonalityDefinition {
|
||||
name: Personality::none(),
|
||||
description: "No personality instructions.".to_string(),
|
||||
instructions: Some(String::new()),
|
||||
scope: PersonalityScope::Builtin,
|
||||
is_builtin: true,
|
||||
},
|
||||
PersonalityDefinition {
|
||||
name: Personality::friendly(),
|
||||
description: "Warm, collaborative, and helpful.".to_string(),
|
||||
instructions: Some(BUILTIN_FRIENDLY_INSTRUCTIONS.to_string()),
|
||||
scope: PersonalityScope::Builtin,
|
||||
is_builtin: true,
|
||||
},
|
||||
PersonalityDefinition {
|
||||
name: Personality::pragmatic(),
|
||||
description: "Concise, task-focused, and direct.".to_string(),
|
||||
instructions: Some(BUILTIN_PRAGMATIC_INSTRUCTIONS.to_string()),
|
||||
scope: PersonalityScope::Builtin,
|
||||
is_builtin: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn personality_roots(config_layer_stack: &ConfigLayerStack) -> Vec<(PathBuf, PersonalityScope)> {
|
||||
let mut roots = Vec::new();
|
||||
for layer in config_layer_stack.get_layers(
|
||||
ConfigLayerStackOrdering::HighestPrecedenceFirst,
|
||||
/*include_disabled*/ true,
|
||||
) {
|
||||
let Some(config_folder) = layer.config_folder() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let scope = match &layer.name {
|
||||
ConfigLayerSource::Project { .. } => PersonalityScope::Repo,
|
||||
ConfigLayerSource::User { .. } => PersonalityScope::User,
|
||||
_ => continue,
|
||||
};
|
||||
roots.push((config_folder.as_path().join(PERSONALITIES_DIR_NAME), scope));
|
||||
}
|
||||
|
||||
let mut seen = HashSet::new();
|
||||
roots.retain(|(path, _)| seen.insert(path.clone()));
|
||||
roots
|
||||
}
|
||||
|
||||
fn discover_personalities_under_root(
|
||||
root: &Path,
|
||||
scope: PersonalityScope,
|
||||
seen: &mut HashSet<Personality>,
|
||||
outcome: &mut PersonalityLoadOutcome,
|
||||
) {
|
||||
let entries = match fs::read_dir(root) {
|
||||
Ok(entries) => entries,
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return,
|
||||
Err(error) => {
|
||||
outcome.errors.push(PersonalityError {
|
||||
path: root.to_path_buf(),
|
||||
message: error.to_string(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if !path.is_file()
|
||||
|| !path
|
||||
.extension()
|
||||
.and_then(|extension| extension.to_str())
|
||||
.is_some_and(|extension| extension.eq_ignore_ascii_case("md"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
match parse_personality_file(path.as_path(), scope) {
|
||||
Ok(personality) => {
|
||||
if seen.insert(personality.name.clone()) {
|
||||
outcome.personalities.push(personality);
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
outcome.errors.push(PersonalityError {
|
||||
path,
|
||||
message: error.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_personality_file(
|
||||
path: &Path,
|
||||
scope: PersonalityScope,
|
||||
) -> Result<PersonalityDefinition, PersonalityParseError> {
|
||||
let contents = fs::read_to_string(path).map_err(PersonalityParseError::Read)?;
|
||||
let (frontmatter, body) =
|
||||
extract_frontmatter_and_body(&contents).ok_or(PersonalityParseError::MissingFrontmatter)?;
|
||||
let parsed: PersonalityFrontmatter =
|
||||
serde_yaml::from_str(frontmatter.as_str()).map_err(PersonalityParseError::InvalidYaml)?;
|
||||
|
||||
let name = sanitize_single_line(parsed.name.as_str());
|
||||
validate_name(name.as_str())?;
|
||||
|
||||
let description = sanitize_single_line(parsed.description.as_str());
|
||||
validate_len(description.as_str(), MAX_DESCRIPTION_LEN, "description")?;
|
||||
|
||||
let instructions = body.trim();
|
||||
if instructions.is_empty() {
|
||||
return Err(PersonalityParseError::MissingBody);
|
||||
}
|
||||
|
||||
Ok(PersonalityDefinition {
|
||||
name: Personality::from(name),
|
||||
description,
|
||||
instructions: Some(instructions.to_string()),
|
||||
scope,
|
||||
is_builtin: false,
|
||||
})
|
||||
}
|
||||
|
||||
fn validate_name(name: &str) -> Result<(), PersonalityParseError> {
|
||||
validate_len(name, MAX_NAME_LEN, "name")?;
|
||||
let normalized = name.to_ascii_lowercase();
|
||||
if Personality::builtin_names().contains(&normalized.as_str()) {
|
||||
return Err(PersonalityParseError::InvalidName(format!(
|
||||
"`{name}` is reserved for a built-in personality"
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_len(
|
||||
value: &str,
|
||||
max_len: usize,
|
||||
field_name: &'static str,
|
||||
) -> Result<(), PersonalityParseError> {
|
||||
if value.is_empty() {
|
||||
return Err(PersonalityParseError::InvalidField {
|
||||
field: field_name,
|
||||
reason: "value is empty".to_string(),
|
||||
});
|
||||
}
|
||||
if value.chars().count() > max_len {
|
||||
return Err(PersonalityParseError::InvalidField {
|
||||
field: field_name,
|
||||
reason: format!("exceeds maximum length of {max_len} characters"),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sanitize_single_line(raw: &str) -> String {
|
||||
raw.split_whitespace().collect::<Vec<_>>().join(" ")
|
||||
}
|
||||
|
||||
fn extract_frontmatter_and_body(contents: &str) -> Option<(String, String)> {
|
||||
let mut lines = contents.lines();
|
||||
if !matches!(lines.next(), Some(line) if line.trim() == "---") {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut frontmatter_lines = Vec::new();
|
||||
let mut found_closing = false;
|
||||
for line in lines.by_ref() {
|
||||
if line.trim() == "---" {
|
||||
found_closing = true;
|
||||
break;
|
||||
}
|
||||
frontmatter_lines.push(line);
|
||||
}
|
||||
|
||||
if frontmatter_lines.is_empty() || !found_closing {
|
||||
return None;
|
||||
}
|
||||
|
||||
let body = lines.collect::<Vec<_>>().join("\n");
|
||||
Some((frontmatter_lines.join("\n"), body))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::catalog_from_layer_stack;
|
||||
use crate::config_loader::CloudRequirementsLoader;
|
||||
use crate::config_loader::LoaderOverrides;
|
||||
use crate::config_loader::load_config_layers_state;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::fs;
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn write_personality(root: &std::path::Path, name: &str, description: &str, body: &str) {
|
||||
fs::create_dir_all(root).unwrap();
|
||||
let path = root.join(format!("{name}.md"));
|
||||
fs::write(
|
||||
path,
|
||||
format!("---\nname: {name}\ndescription: {description}\n---\n\n{body}\n"),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn repo_personality_overrides_user_personality_name() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let codex_home = temp.path().join(".codex-home");
|
||||
let repo_root = temp.path().join("repo");
|
||||
fs::create_dir_all(repo_root.join(".codex")).unwrap();
|
||||
fs::create_dir_all(&codex_home).unwrap();
|
||||
|
||||
write_personality(
|
||||
codex_home.join("personalities").as_path(),
|
||||
"night-owl",
|
||||
"User personality",
|
||||
"user body",
|
||||
);
|
||||
write_personality(
|
||||
repo_root.join(".codex/personalities").as_path(),
|
||||
"night-owl",
|
||||
"Repo personality",
|
||||
"repo body",
|
||||
);
|
||||
|
||||
let stack = load_config_layers_state(
|
||||
&codex_home,
|
||||
Some(repo_root.clone().try_into().unwrap()),
|
||||
&[],
|
||||
LoaderOverrides::default(),
|
||||
CloudRequirementsLoader::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let catalog = catalog_from_layer_stack(&stack);
|
||||
let personality = catalog.get(&Personality::from("night-owl")).unwrap();
|
||||
assert_eq!(personality.description, "Repo personality");
|
||||
assert_eq!(personality.instructions.as_deref(), Some("repo body"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_personality_is_ignored() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let codex_home = temp.path().join(".codex-home");
|
||||
let repo_root = temp.path().join("repo");
|
||||
fs::create_dir_all(repo_root.join(".codex/personalities")).unwrap();
|
||||
fs::create_dir_all(&codex_home).unwrap();
|
||||
fs::write(
|
||||
repo_root.join(".codex/personalities/bad.md"),
|
||||
"---\nname: friendly\ndescription: bad\n---\n\nbody\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let stack = load_config_layers_state(
|
||||
&codex_home,
|
||||
Some(repo_root.clone().try_into().unwrap()),
|
||||
&[],
|
||||
LoaderOverrides::default(),
|
||||
CloudRequirementsLoader::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let catalog = catalog_from_layer_stack(&stack);
|
||||
assert!(
|
||||
catalog
|
||||
.get(&Personality::from("friendly"))
|
||||
.unwrap()
|
||||
.is_builtin
|
||||
);
|
||||
assert!(catalog.get(&Personality::from("bad")).is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn personalities_are_sorted_with_builtins_first_then_name() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
let codex_home = temp.path().join(".codex-home");
|
||||
let repo_root = temp.path().join("repo");
|
||||
fs::create_dir_all(repo_root.join(".codex")).unwrap();
|
||||
fs::create_dir_all(&codex_home).unwrap();
|
||||
|
||||
write_personality(
|
||||
codex_home.join("personalities").as_path(),
|
||||
"zebra",
|
||||
"User personality",
|
||||
"zebra body",
|
||||
);
|
||||
write_personality(
|
||||
repo_root.join(".codex/personalities").as_path(),
|
||||
"alpha",
|
||||
"Repo personality",
|
||||
"alpha body",
|
||||
);
|
||||
|
||||
let stack = load_config_layers_state(
|
||||
&codex_home,
|
||||
Some(repo_root.clone().try_into().unwrap()),
|
||||
&[],
|
||||
LoaderOverrides::default(),
|
||||
CloudRequirementsLoader::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let catalog = catalog_from_layer_stack(&stack);
|
||||
let names = catalog
|
||||
.personalities()
|
||||
.iter()
|
||||
.map(|personality| personality.name.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
names,
|
||||
vec!["friendly", "none", "pragmatic", "alpha", "zebra"]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ pub async fn maybe_migrate_personality(
|
||||
}
|
||||
|
||||
ConfigEditsBuilder::new(codex_home)
|
||||
.set_personality(Some(Personality::Pragmatic))
|
||||
.set_personality(Some(Personality::pragmatic()))
|
||||
.apply()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
|
||||
@@ -81,7 +81,7 @@ async fn applies_when_sessions_exist_and_no_personality() -> io::Result<()> {
|
||||
assert!(temp.path().join(PERSONALITY_MIGRATION_FILENAME).exists());
|
||||
|
||||
let persisted = read_config_toml(temp.path()).await?;
|
||||
assert_eq!(persisted.personality, Some(Personality::Pragmatic));
|
||||
assert_eq!(persisted.personality, Some(Personality::pragmatic()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ async fn skips_when_marker_exists() -> io::Result<()> {
|
||||
async fn skips_when_personality_explicit() -> io::Result<()> {
|
||||
let temp = TempDir::new()?;
|
||||
ConfigEditsBuilder::new(temp.path())
|
||||
.set_personality(Some(Personality::Friendly))
|
||||
.set_personality(Some(Personality::friendly()))
|
||||
.apply()
|
||||
.await
|
||||
.map_err(|err| io::Error::other(format!("failed to write config: {err}")))?;
|
||||
@@ -117,7 +117,7 @@ async fn skips_when_personality_explicit() -> io::Result<()> {
|
||||
assert!(temp.path().join(PERSONALITY_MIGRATION_FILENAME).exists());
|
||||
|
||||
let persisted = read_config_toml(temp.path()).await?;
|
||||
assert_eq!(persisted.personality, Some(Personality::Friendly));
|
||||
assert_eq!(persisted.personality, Some(Personality::friendly()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use crate::exec_policy::ExecPolicyManager;
|
||||
use crate::mcp::McpManager;
|
||||
use crate::mcp_connection_manager::McpConnectionManager;
|
||||
use crate::models_manager::manager::ModelsManager;
|
||||
use crate::personalities::PersonalityCatalog;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::skills_watcher::SkillsWatcher;
|
||||
use crate::state_db::StateDbHandle;
|
||||
@@ -44,6 +45,7 @@ pub(crate) struct SessionServices {
|
||||
pub(crate) exec_policy: Arc<ExecPolicyManager>,
|
||||
pub(crate) auth_manager: Arc<AuthManager>,
|
||||
pub(crate) models_manager: Arc<ModelsManager>,
|
||||
pub(crate) personality_catalog: Arc<PersonalityCatalog>,
|
||||
pub(crate) session_telemetry: SessionTelemetry,
|
||||
pub(crate) tool_approvals: Mutex<ApprovalStore>,
|
||||
pub(crate) skills_manager: Arc<SkillsManager>,
|
||||
|
||||
Reference in New Issue
Block a user