protocol: report session permission profiles

This commit is contained in:
Michael Bolin
2026-04-21 22:15:15 -07:00
parent b6c0d5807a
commit dbf88606a9
13 changed files with 203 additions and 0 deletions
+1
View File
@@ -750,6 +750,7 @@ impl Session {
approval_policy: session_configuration.approval_policy.value(),
approvals_reviewer: session_configuration.approvals_reviewer,
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
permission_profile: Some(session_configuration.permission_profile()),
cwd: session_configuration.cwd.clone(),
reasoning_effort: session_configuration.collaboration_mode.reasoning_effort(),
history_log_id,
+4
View File
@@ -1033,6 +1033,7 @@ fn session_configured_from_thread_start_response(
response.approval_policy.to_core(),
response.approvals_reviewer.to_core(),
response.sandbox.to_core(),
response.permission_profile.clone().into(),
response.cwd.clone(),
response.reasoning_effort,
)
@@ -1051,6 +1052,7 @@ fn session_configured_from_thread_resume_response(
response.approval_policy.to_core(),
response.approvals_reviewer.to_core(),
response.sandbox.to_core(),
response.permission_profile.clone().into(),
response.cwd.clone(),
response.reasoning_effort,
)
@@ -1079,6 +1081,7 @@ fn session_configured_from_thread_response(
approval_policy: AskForApproval,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer,
sandbox_policy: SandboxPolicy,
permission_profile: PermissionProfile,
cwd: AbsolutePathBuf,
reasoning_effort: Option<codex_protocol::openai_models::ReasoningEffort>,
) -> Result<SessionConfiguredEvent, String> {
@@ -1095,6 +1098,7 @@ fn session_configured_from_thread_response(
approval_policy,
approvals_reviewer,
sandbox_policy,
permission_profile: Some(permission_profile),
cwd,
reasoning_effort,
history_log_id: 0,
@@ -115,6 +115,7 @@ fn session_configured_produces_thread_started_event() {
approval_policy: AskForApproval::Never,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/tmp/project").abs(),
reasoning_effort: None,
history_log_id: 0,
@@ -305,6 +305,7 @@ mod tests {
approval_policy: AskForApproval::Never,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffort::default()),
history_log_id: 1,
@@ -349,6 +350,7 @@ mod tests {
approval_policy: AskForApproval::Never,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffort::default()),
history_log_id: 1,
@@ -418,6 +420,7 @@ mod tests {
approval_policy: AskForApproval::Never,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffort::default()),
history_log_id: 1,
+5
View File
@@ -3590,6 +3590,10 @@ pub struct SessionConfiguredEvent {
/// How to sandbox commands executed in the system
pub sandbox_policy: SandboxPolicy,
/// Effective permissions for commands executed in the session.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub permission_profile: Option<PermissionProfile>,
/// Working directory that should be treated as the *root* of the
/// session.
pub cwd: AbsolutePathBuf,
@@ -5164,6 +5168,7 @@ mod tests {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
+167
View File
@@ -3463,6 +3463,7 @@ async fn render_clear_ui_header_after_long_transcript_for_snapshot() -> String {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/tmp/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::High),
history_log_id: 0,
@@ -4023,6 +4024,167 @@ fn active_turn_steer_race_extracts_actual_turn_id_from_mismatch() {
);
}
#[tokio::test]
async fn update_reasoning_effort_updates_collaboration_mode() {
let mut app = make_test_app().await;
app.chat_widget
.set_reasoning_effort(Some(ReasoningEffortConfig::Medium));
app.on_update_reasoning_effort(Some(ReasoningEffortConfig::High));
assert_eq!(
app.chat_widget.current_reasoning_effort(),
Some(ReasoningEffortConfig::High)
);
assert_eq!(
app.config.model_reasoning_effort,
Some(ReasoningEffortConfig::High)
);
}
#[tokio::test]
async fn refresh_in_memory_config_from_disk_loads_latest_apps_state() -> Result<()> {
let mut app = make_test_app().await;
let codex_home = tempdir()?;
app.config.codex_home = codex_home.path().to_path_buf().abs();
let app_id = "unit_test_refresh_in_memory_config_connector".to_string();
assert_eq!(app_enabled_in_effective_config(&app.config, &app_id), None);
ConfigEditsBuilder::new(&app.config.codex_home)
.with_edits([
ConfigEdit::SetPath {
segments: vec!["apps".to_string(), app_id.clone(), "enabled".to_string()],
value: false.into(),
},
ConfigEdit::SetPath {
segments: vec![
"apps".to_string(),
app_id.clone(),
"disabled_reason".to_string(),
],
value: "user".into(),
},
])
.apply()
.await
.expect("persist app toggle");
assert_eq!(app_enabled_in_effective_config(&app.config, &app_id), None);
app.refresh_in_memory_config_from_disk().await?;
assert_eq!(
app_enabled_in_effective_config(&app.config, &app_id),
Some(false)
);
Ok(())
}
#[tokio::test]
async fn refresh_in_memory_config_from_disk_best_effort_keeps_current_config_on_error() -> Result<()>
{
let mut app = make_test_app().await;
let codex_home = tempdir()?;
app.config.codex_home = codex_home.path().to_path_buf().abs();
std::fs::write(codex_home.path().join("config.toml"), "[broken")?;
let original_config = app.config.clone();
app.refresh_in_memory_config_from_disk_best_effort("starting a new thread")
.await;
assert_eq!(app.config, original_config);
Ok(())
}
#[tokio::test]
async fn refresh_in_memory_config_from_disk_uses_active_chat_widget_cwd() -> Result<()> {
let mut app = make_test_app().await;
let original_cwd = app.config.cwd.clone();
let next_cwd_tmp = tempdir()?;
let next_cwd = next_cwd_tmp.path().to_path_buf();
app.chat_widget.handle_codex_event(Event {
id: String::new(),
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
session_id: ThreadId::new(),
forked_from_id: None,
thread_name: None,
model: "gpt-test".to_string(),
model_provider_id: "test-provider".to_string(),
service_tier: None,
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: next_cwd.clone().abs(),
reasoning_effort: None,
history_log_id: 0,
history_entry_count: 0,
initial_messages: None,
network_proxy: None,
rollout_path: Some(PathBuf::new()),
}),
});
assert_eq!(app.chat_widget.config_ref().cwd.to_path_buf(), next_cwd);
assert_eq!(app.config.cwd, original_cwd);
app.refresh_in_memory_config_from_disk().await?;
assert_eq!(app.config.cwd, app.chat_widget.config_ref().cwd);
Ok(())
}
#[tokio::test]
async fn rebuild_config_for_resume_or_fallback_uses_current_config_on_same_cwd_error() -> Result<()>
{
let mut app = make_test_app().await;
let codex_home = tempdir()?;
app.config.codex_home = codex_home.path().to_path_buf().abs();
std::fs::write(codex_home.path().join("config.toml"), "[broken")?;
let current_config = app.config.clone();
let current_cwd = current_config.cwd.clone();
let resume_config = app
.rebuild_config_for_resume_or_fallback(&current_cwd, current_cwd.to_path_buf())
.await?;
assert_eq!(resume_config, current_config);
Ok(())
}
#[tokio::test]
async fn rebuild_config_for_resume_or_fallback_errors_when_cwd_changes() -> Result<()> {
let mut app = make_test_app().await;
let codex_home = tempdir()?;
app.config.codex_home = codex_home.path().to_path_buf().abs();
std::fs::write(codex_home.path().join("config.toml"), "[broken")?;
let current_cwd = app.config.cwd.clone();
let next_cwd_tmp = tempdir()?;
let next_cwd = next_cwd_tmp.path().to_path_buf();
let result = app
.rebuild_config_for_resume_or_fallback(&current_cwd, next_cwd)
.await;
assert!(result.is_err());
Ok(())
}
#[tokio::test]
async fn sync_tui_theme_selection_updates_chat_widget_config_copy() {
let mut app = make_test_app().await;
app.sync_tui_theme_selection("dracula".to_string());
assert_eq!(app.config.tui_theme.as_deref(), Some("dracula"));
assert_eq!(
app.chat_widget.config_ref().tui_theme.as_deref(),
Some("dracula")
);
}
#[tokio::test]
async fn fresh_session_config_uses_current_service_tier() {
let mut app = make_test_app().await;
@@ -4071,6 +4233,7 @@ async fn backtrack_selection_with_duplicate_history_targets_unique_turn() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: None,
history_log_id: 0,
@@ -4134,6 +4297,7 @@ async fn backtrack_selection_with_duplicate_history_targets_unique_turn() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: None,
history_log_id: 0,
@@ -4227,6 +4391,7 @@ async fn backtrack_resubmit_preserves_data_image_urls_in_user_turn() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: None,
history_log_id: 0,
@@ -4610,6 +4775,7 @@ async fn new_session_requests_shutdown_for_previous_conversation() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: None,
history_log_id: 0,
@@ -4722,6 +4888,7 @@ async fn clear_only_ui_reset_preserves_chat_session_state() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/tmp/project").abs(),
reasoning_effort: None,
history_log_id: 0,
+1
View File
@@ -1395,6 +1395,7 @@ fn thread_session_state_to_legacy_event(
approval_policy: session.approval_policy,
approvals_reviewer: session.approvals_reviewer,
sandbox_policy: session.sandbox_policy,
permission_profile: Some(session.permission_profile),
cwd: session.cwd,
reasoning_effort: session.reasoning_effort,
history_log_id: session.history_log_id,
@@ -17,6 +17,7 @@ async fn submission_preserves_text_elements_and_local_images() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -101,6 +102,7 @@ async fn submission_with_remote_and_local_images_keeps_local_placeholder_numberi
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -196,6 +198,7 @@ async fn enter_with_only_remote_images_submits_user_turn() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -261,6 +264,7 @@ async fn shift_enter_with_only_remote_images_does_not_submit_user_turn() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -301,6 +305,7 @@ async fn enter_with_only_remote_images_does_not_submit_when_modal_is_active() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -341,6 +346,7 @@ async fn enter_with_only_remote_images_does_not_submit_when_input_disabled() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -384,6 +390,7 @@ async fn submission_prefers_selected_duplicate_skill_path() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -1001,6 +1001,7 @@ async fn bang_shell_enter_while_task_running_submits_run_user_shell_command() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -17,6 +17,7 @@ async fn resumed_initial_messages_render_history() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -130,6 +131,7 @@ async fn replayed_user_message_preserves_text_elements_and_local_images() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -191,6 +193,7 @@ async fn replayed_user_message_preserves_remote_image_urls() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -259,6 +262,7 @@ async fn session_configured_syncs_widget_config_permissions_and_cwd() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: expected_sandbox.clone(),
permission_profile: None,
cwd: expected_cwd.clone(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -302,6 +306,7 @@ async fn replayed_user_message_with_only_remote_images_renders_history_cell() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -355,6 +360,7 @@ async fn replayed_user_message_with_only_local_images_does_not_render_history_ce
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -676,6 +682,7 @@ async fn replayed_reasoning_item_hides_raw_reasoning_when_disabled() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_project_path().abs(),
reasoning_effort: None,
history_log_id: 0,
@@ -723,6 +730,7 @@ async fn replayed_reasoning_item_shows_raw_reasoning_when_enabled() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_project_path().abs(),
reasoning_effort: None,
history_log_id: 0,
@@ -463,6 +463,7 @@ async fn permissions_selection_marks_guardian_approvals_current_after_session_co
approval_policy: AskForApproval::OnRequest,
approvals_reviewer: ApprovalsReviewer::GuardianSubagent,
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
permission_profile: None,
cwd: test_project_path().abs(),
reasoning_effort: None,
history_log_id: 0,
@@ -517,6 +518,7 @@ async fn permissions_selection_marks_guardian_approvals_current_with_custom_work
exclude_tmpdir_env_var: false,
exclude_slash_tmp: false,
},
permission_profile: None,
cwd: test_project_path().abs(),
reasoning_effort: None,
history_log_id: 0,
@@ -1057,6 +1057,7 @@ async fn submit_user_message_emits_structured_plugin_mentions_from_bindings() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
@@ -1302,6 +1303,7 @@ async fn plan_slash_command_with_args_submits_prompt_in_plan_mode() {
approval_policy: AskForApproval::Never,
approvals_reviewer: ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/home/user/project").abs(),
reasoning_effort: Some(ReasoningEffortConfig::default()),
history_log_id: 0,
+1
View File
@@ -3073,6 +3073,7 @@ mod tests {
approval_policy: AskForApproval::Never,
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
permission_profile: None,
cwd: test_path_buf("/tmp/project").abs(),
reasoning_effort: None,
history_log_id: 0,