Skip to content

Commit 99782a9

Browse files
committed
protocol: report session permission profiles
1 parent 8349d4f commit 99782a9

13 files changed

Lines changed: 203 additions & 0 deletions

File tree

codex-rs/core/src/session/session.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,7 @@ impl Session {
804804
approval_policy: session_configuration.approval_policy.value(),
805805
approvals_reviewer: session_configuration.approvals_reviewer,
806806
sandbox_policy: session_configuration.sandbox_policy.get().clone(),
807+
permission_profile: Some(session_configuration.permission_profile()),
807808
cwd: session_configuration.cwd.clone(),
808809
reasoning_effort: session_configuration.collaboration_mode.reasoning_effort(),
809810
history_log_id,

codex-rs/exec/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,7 @@ fn session_configured_from_thread_start_response(
10241024
response.approval_policy.to_core(),
10251025
response.approvals_reviewer.to_core(),
10261026
response.sandbox.to_core(),
1027+
response.permission_profile.clone().into(),
10271028
response.cwd.clone(),
10281029
response.reasoning_effort,
10291030
)
@@ -1042,6 +1043,7 @@ fn session_configured_from_thread_resume_response(
10421043
response.approval_policy.to_core(),
10431044
response.approvals_reviewer.to_core(),
10441045
response.sandbox.to_core(),
1046+
response.permission_profile.clone().into(),
10451047
response.cwd.clone(),
10461048
response.reasoning_effort,
10471049
)
@@ -1070,6 +1072,7 @@ fn session_configured_from_thread_response(
10701072
approval_policy: AskForApproval,
10711073
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer,
10721074
sandbox_policy: SandboxPolicy,
1075+
permission_profile: PermissionProfile,
10731076
cwd: AbsolutePathBuf,
10741077
reasoning_effort: Option<codex_protocol::openai_models::ReasoningEffort>,
10751078
) -> Result<SessionConfiguredEvent, String> {
@@ -1086,6 +1089,7 @@ fn session_configured_from_thread_response(
10861089
approval_policy,
10871090
approvals_reviewer,
10881091
sandbox_policy,
1092+
permission_profile: Some(permission_profile),
10891093
cwd,
10901094
reasoning_effort,
10911095
history_log_id: 0,

codex-rs/exec/tests/event_processor_with_json_output.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ fn session_configured_produces_thread_started_event() {
115115
approval_policy: AskForApproval::Never,
116116
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
117117
sandbox_policy: SandboxPolicy::new_read_only_policy(),
118+
permission_profile: None,
118119
cwd: test_path_buf("/tmp/project").abs(),
119120
reasoning_effort: None,
120121
history_log_id: 0,

codex-rs/mcp-server/src/outgoing_message.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ mod tests {
305305
approval_policy: AskForApproval::Never,
306306
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
307307
sandbox_policy: SandboxPolicy::new_read_only_policy(),
308+
permission_profile: None,
308309
cwd: test_path_buf("/home/user/project").abs(),
309310
reasoning_effort: Some(ReasoningEffort::default()),
310311
history_log_id: 1,
@@ -349,6 +350,7 @@ mod tests {
349350
approval_policy: AskForApproval::Never,
350351
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
351352
sandbox_policy: SandboxPolicy::new_read_only_policy(),
353+
permission_profile: None,
352354
cwd: test_path_buf("/home/user/project").abs(),
353355
reasoning_effort: Some(ReasoningEffort::default()),
354356
history_log_id: 1,
@@ -418,6 +420,7 @@ mod tests {
418420
approval_policy: AskForApproval::Never,
419421
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer::User,
420422
sandbox_policy: SandboxPolicy::new_read_only_policy(),
423+
permission_profile: None,
421424
cwd: test_path_buf("/home/user/project").abs(),
422425
reasoning_effort: Some(ReasoningEffort::default()),
423426
history_log_id: 1,

codex-rs/protocol/src/protocol.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3672,6 +3672,10 @@ pub struct SessionConfiguredEvent {
36723672
/// How to sandbox commands executed in the system
36733673
pub sandbox_policy: SandboxPolicy,
36743674

3675+
/// Effective permissions for commands executed in the session.
3676+
#[serde(default, skip_serializing_if = "Option::is_none")]
3677+
pub permission_profile: Option<PermissionProfile>,
3678+
36753679
/// Working directory that should be treated as the *root* of the
36763680
/// session.
36773681
pub cwd: AbsolutePathBuf,
@@ -5246,6 +5250,7 @@ mod tests {
52465250
approval_policy: AskForApproval::Never,
52475251
approvals_reviewer: ApprovalsReviewer::User,
52485252
sandbox_policy: SandboxPolicy::new_read_only_policy(),
5253+
permission_profile: None,
52495254
cwd: test_path_buf("/home/user/project").abs(),
52505255
reasoning_effort: Some(ReasoningEffortConfig::default()),
52515256
history_log_id: 0,

codex-rs/tui/src/app/tests.rs

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3463,6 +3463,7 @@ async fn render_clear_ui_header_after_long_transcript_for_snapshot() -> String {
34633463
approval_policy: AskForApproval::Never,
34643464
approvals_reviewer: ApprovalsReviewer::User,
34653465
sandbox_policy: SandboxPolicy::new_read_only_policy(),
3466+
permission_profile: None,
34663467
cwd: test_path_buf("/tmp/project").abs(),
34673468
reasoning_effort: Some(ReasoningEffortConfig::High),
34683469
history_log_id: 0,
@@ -4023,6 +4024,167 @@ fn active_turn_steer_race_extracts_actual_turn_id_from_mismatch() {
40234024
);
40244025
}
40254026

4027+
#[tokio::test]
4028+
async fn update_reasoning_effort_updates_collaboration_mode() {
4029+
let mut app = make_test_app().await;
4030+
app.chat_widget
4031+
.set_reasoning_effort(Some(ReasoningEffortConfig::Medium));
4032+
4033+
app.on_update_reasoning_effort(Some(ReasoningEffortConfig::High));
4034+
4035+
assert_eq!(
4036+
app.chat_widget.current_reasoning_effort(),
4037+
Some(ReasoningEffortConfig::High)
4038+
);
4039+
assert_eq!(
4040+
app.config.model_reasoning_effort,
4041+
Some(ReasoningEffortConfig::High)
4042+
);
4043+
}
4044+
4045+
#[tokio::test]
4046+
async fn refresh_in_memory_config_from_disk_loads_latest_apps_state() -> Result<()> {
4047+
let mut app = make_test_app().await;
4048+
let codex_home = tempdir()?;
4049+
app.config.codex_home = codex_home.path().to_path_buf().abs();
4050+
let app_id = "unit_test_refresh_in_memory_config_connector".to_string();
4051+
4052+
assert_eq!(app_enabled_in_effective_config(&app.config, &app_id), None);
4053+
4054+
ConfigEditsBuilder::new(&app.config.codex_home)
4055+
.with_edits([
4056+
ConfigEdit::SetPath {
4057+
segments: vec!["apps".to_string(), app_id.clone(), "enabled".to_string()],
4058+
value: false.into(),
4059+
},
4060+
ConfigEdit::SetPath {
4061+
segments: vec![
4062+
"apps".to_string(),
4063+
app_id.clone(),
4064+
"disabled_reason".to_string(),
4065+
],
4066+
value: "user".into(),
4067+
},
4068+
])
4069+
.apply()
4070+
.await
4071+
.expect("persist app toggle");
4072+
4073+
assert_eq!(app_enabled_in_effective_config(&app.config, &app_id), None);
4074+
4075+
app.refresh_in_memory_config_from_disk().await?;
4076+
4077+
assert_eq!(
4078+
app_enabled_in_effective_config(&app.config, &app_id),
4079+
Some(false)
4080+
);
4081+
Ok(())
4082+
}
4083+
4084+
#[tokio::test]
4085+
async fn refresh_in_memory_config_from_disk_best_effort_keeps_current_config_on_error() -> Result<()>
4086+
{
4087+
let mut app = make_test_app().await;
4088+
let codex_home = tempdir()?;
4089+
app.config.codex_home = codex_home.path().to_path_buf().abs();
4090+
std::fs::write(codex_home.path().join("config.toml"), "[broken")?;
4091+
let original_config = app.config.clone();
4092+
4093+
app.refresh_in_memory_config_from_disk_best_effort("starting a new thread")
4094+
.await;
4095+
4096+
assert_eq!(app.config, original_config);
4097+
Ok(())
4098+
}
4099+
4100+
#[tokio::test]
4101+
async fn refresh_in_memory_config_from_disk_uses_active_chat_widget_cwd() -> Result<()> {
4102+
let mut app = make_test_app().await;
4103+
let original_cwd = app.config.cwd.clone();
4104+
let next_cwd_tmp = tempdir()?;
4105+
let next_cwd = next_cwd_tmp.path().to_path_buf();
4106+
4107+
app.chat_widget.handle_codex_event(Event {
4108+
id: String::new(),
4109+
msg: EventMsg::SessionConfigured(SessionConfiguredEvent {
4110+
session_id: ThreadId::new(),
4111+
forked_from_id: None,
4112+
thread_name: None,
4113+
model: "gpt-test".to_string(),
4114+
model_provider_id: "test-provider".to_string(),
4115+
service_tier: None,
4116+
approval_policy: AskForApproval::Never,
4117+
approvals_reviewer: ApprovalsReviewer::User,
4118+
sandbox_policy: SandboxPolicy::new_read_only_policy(),
4119+
permission_profile: None,
4120+
cwd: next_cwd.clone().abs(),
4121+
reasoning_effort: None,
4122+
history_log_id: 0,
4123+
history_entry_count: 0,
4124+
initial_messages: None,
4125+
network_proxy: None,
4126+
rollout_path: Some(PathBuf::new()),
4127+
}),
4128+
});
4129+
4130+
assert_eq!(app.chat_widget.config_ref().cwd.to_path_buf(), next_cwd);
4131+
assert_eq!(app.config.cwd, original_cwd);
4132+
4133+
app.refresh_in_memory_config_from_disk().await?;
4134+
4135+
assert_eq!(app.config.cwd, app.chat_widget.config_ref().cwd);
4136+
Ok(())
4137+
}
4138+
4139+
#[tokio::test]
4140+
async fn rebuild_config_for_resume_or_fallback_uses_current_config_on_same_cwd_error() -> Result<()>
4141+
{
4142+
let mut app = make_test_app().await;
4143+
let codex_home = tempdir()?;
4144+
app.config.codex_home = codex_home.path().to_path_buf().abs();
4145+
std::fs::write(codex_home.path().join("config.toml"), "[broken")?;
4146+
let current_config = app.config.clone();
4147+
let current_cwd = current_config.cwd.clone();
4148+
4149+
let resume_config = app
4150+
.rebuild_config_for_resume_or_fallback(&current_cwd, current_cwd.to_path_buf())
4151+
.await?;
4152+
4153+
assert_eq!(resume_config, current_config);
4154+
Ok(())
4155+
}
4156+
4157+
#[tokio::test]
4158+
async fn rebuild_config_for_resume_or_fallback_errors_when_cwd_changes() -> Result<()> {
4159+
let mut app = make_test_app().await;
4160+
let codex_home = tempdir()?;
4161+
app.config.codex_home = codex_home.path().to_path_buf().abs();
4162+
std::fs::write(codex_home.path().join("config.toml"), "[broken")?;
4163+
let current_cwd = app.config.cwd.clone();
4164+
let next_cwd_tmp = tempdir()?;
4165+
let next_cwd = next_cwd_tmp.path().to_path_buf();
4166+
4167+
let result = app
4168+
.rebuild_config_for_resume_or_fallback(&current_cwd, next_cwd)
4169+
.await;
4170+
4171+
assert!(result.is_err());
4172+
Ok(())
4173+
}
4174+
4175+
#[tokio::test]
4176+
async fn sync_tui_theme_selection_updates_chat_widget_config_copy() {
4177+
let mut app = make_test_app().await;
4178+
4179+
app.sync_tui_theme_selection("dracula".to_string());
4180+
4181+
assert_eq!(app.config.tui_theme.as_deref(), Some("dracula"));
4182+
assert_eq!(
4183+
app.chat_widget.config_ref().tui_theme.as_deref(),
4184+
Some("dracula")
4185+
);
4186+
}
4187+
40264188
#[tokio::test]
40274189
async fn fresh_session_config_uses_current_service_tier() {
40284190
let mut app = make_test_app().await;
@@ -4071,6 +4233,7 @@ async fn backtrack_selection_with_duplicate_history_targets_unique_turn() {
40714233
approval_policy: AskForApproval::Never,
40724234
approvals_reviewer: ApprovalsReviewer::User,
40734235
sandbox_policy: SandboxPolicy::new_read_only_policy(),
4236+
permission_profile: None,
40744237
cwd: test_path_buf("/home/user/project").abs(),
40754238
reasoning_effort: None,
40764239
history_log_id: 0,
@@ -4134,6 +4297,7 @@ async fn backtrack_selection_with_duplicate_history_targets_unique_turn() {
41344297
approval_policy: AskForApproval::Never,
41354298
approvals_reviewer: ApprovalsReviewer::User,
41364299
sandbox_policy: SandboxPolicy::new_read_only_policy(),
4300+
permission_profile: None,
41374301
cwd: test_path_buf("/home/user/project").abs(),
41384302
reasoning_effort: None,
41394303
history_log_id: 0,
@@ -4227,6 +4391,7 @@ async fn backtrack_resubmit_preserves_data_image_urls_in_user_turn() {
42274391
approval_policy: AskForApproval::Never,
42284392
approvals_reviewer: ApprovalsReviewer::User,
42294393
sandbox_policy: SandboxPolicy::new_read_only_policy(),
4394+
permission_profile: None,
42304395
cwd: test_path_buf("/home/user/project").abs(),
42314396
reasoning_effort: None,
42324397
history_log_id: 0,
@@ -4610,6 +4775,7 @@ async fn new_session_requests_shutdown_for_previous_conversation() {
46104775
approval_policy: AskForApproval::Never,
46114776
approvals_reviewer: ApprovalsReviewer::User,
46124777
sandbox_policy: SandboxPolicy::new_read_only_policy(),
4778+
permission_profile: None,
46134779
cwd: test_path_buf("/home/user/project").abs(),
46144780
reasoning_effort: None,
46154781
history_log_id: 0,
@@ -4722,6 +4888,7 @@ async fn clear_only_ui_reset_preserves_chat_session_state() {
47224888
approval_policy: AskForApproval::Never,
47234889
approvals_reviewer: ApprovalsReviewer::User,
47244890
sandbox_policy: SandboxPolicy::new_read_only_policy(),
4891+
permission_profile: None,
47254892
cwd: test_path_buf("/tmp/project").abs(),
47264893
reasoning_effort: None,
47274894
history_log_id: 0,

codex-rs/tui/src/chatwidget.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,6 +1395,7 @@ fn thread_session_state_to_legacy_event(
13951395
approval_policy: session.approval_policy,
13961396
approvals_reviewer: session.approvals_reviewer,
13971397
sandbox_policy: session.sandbox_policy,
1398+
permission_profile: Some(session.permission_profile),
13981399
cwd: session.cwd,
13991400
reasoning_effort: session.reasoning_effort,
14001401
history_log_id: session.history_log_id,

codex-rs/tui/src/chatwidget/tests/composer_submission.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ async fn submission_preserves_text_elements_and_local_images() {
1717
approval_policy: AskForApproval::Never,
1818
approvals_reviewer: ApprovalsReviewer::User,
1919
sandbox_policy: SandboxPolicy::new_read_only_policy(),
20+
permission_profile: None,
2021
cwd: test_path_buf("/home/user/project").abs(),
2122
reasoning_effort: Some(ReasoningEffortConfig::default()),
2223
history_log_id: 0,
@@ -101,6 +102,7 @@ async fn submission_with_remote_and_local_images_keeps_local_placeholder_numberi
101102
approval_policy: AskForApproval::Never,
102103
approvals_reviewer: ApprovalsReviewer::User,
103104
sandbox_policy: SandboxPolicy::new_read_only_policy(),
105+
permission_profile: None,
104106
cwd: test_path_buf("/home/user/project").abs(),
105107
reasoning_effort: Some(ReasoningEffortConfig::default()),
106108
history_log_id: 0,
@@ -196,6 +198,7 @@ async fn enter_with_only_remote_images_submits_user_turn() {
196198
approval_policy: AskForApproval::Never,
197199
approvals_reviewer: ApprovalsReviewer::User,
198200
sandbox_policy: SandboxPolicy::new_read_only_policy(),
201+
permission_profile: None,
199202
cwd: test_path_buf("/home/user/project").abs(),
200203
reasoning_effort: Some(ReasoningEffortConfig::default()),
201204
history_log_id: 0,
@@ -261,6 +264,7 @@ async fn shift_enter_with_only_remote_images_does_not_submit_user_turn() {
261264
approval_policy: AskForApproval::Never,
262265
approvals_reviewer: ApprovalsReviewer::User,
263266
sandbox_policy: SandboxPolicy::new_read_only_policy(),
267+
permission_profile: None,
264268
cwd: test_path_buf("/home/user/project").abs(),
265269
reasoning_effort: Some(ReasoningEffortConfig::default()),
266270
history_log_id: 0,
@@ -301,6 +305,7 @@ async fn enter_with_only_remote_images_does_not_submit_when_modal_is_active() {
301305
approval_policy: AskForApproval::Never,
302306
approvals_reviewer: ApprovalsReviewer::User,
303307
sandbox_policy: SandboxPolicy::new_read_only_policy(),
308+
permission_profile: None,
304309
cwd: test_path_buf("/home/user/project").abs(),
305310
reasoning_effort: Some(ReasoningEffortConfig::default()),
306311
history_log_id: 0,
@@ -341,6 +346,7 @@ async fn enter_with_only_remote_images_does_not_submit_when_input_disabled() {
341346
approval_policy: AskForApproval::Never,
342347
approvals_reviewer: ApprovalsReviewer::User,
343348
sandbox_policy: SandboxPolicy::new_read_only_policy(),
349+
permission_profile: None,
344350
cwd: test_path_buf("/home/user/project").abs(),
345351
reasoning_effort: Some(ReasoningEffortConfig::default()),
346352
history_log_id: 0,
@@ -384,6 +390,7 @@ async fn submission_prefers_selected_duplicate_skill_path() {
384390
approval_policy: AskForApproval::Never,
385391
approvals_reviewer: ApprovalsReviewer::User,
386392
sandbox_policy: SandboxPolicy::new_read_only_policy(),
393+
permission_profile: None,
387394
cwd: test_path_buf("/home/user/project").abs(),
388395
reasoning_effort: Some(ReasoningEffortConfig::default()),
389396
history_log_id: 0,

codex-rs/tui/src/chatwidget/tests/exec_flow.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,7 @@ async fn bang_shell_enter_while_task_running_submits_run_user_shell_command() {
10051005
approval_policy: AskForApproval::Never,
10061006
approvals_reviewer: ApprovalsReviewer::User,
10071007
sandbox_policy: SandboxPolicy::new_read_only_policy(),
1008+
permission_profile: None,
10081009
cwd: test_path_buf("/home/user/project").abs(),
10091010
reasoning_effort: Some(ReasoningEffortConfig::default()),
10101011
history_log_id: 0,

0 commit comments

Comments
 (0)