Skip to content

Commit c013a96

Browse files
committed
clients: send permission profiles to app-server
1 parent 0d7a36c commit c013a96

5 files changed

Lines changed: 140 additions & 9 deletions

File tree

codex-rs/exec/src/lib.rs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ use codex_model_provider_info::OLLAMA_OSS_PROVIDER_ID;
7575
use codex_otel::set_parent_from_context;
7676
use codex_otel::traceparent_context_from_env;
7777
use codex_protocol::config_types::SandboxMode;
78+
use codex_protocol::models::PermissionProfile;
7879
use codex_protocol::protocol::AskForApproval;
7980
use codex_protocol::protocol::ReviewRequest;
8081
use codex_protocol::protocol::ReviewTarget;
@@ -737,6 +738,18 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
737738
items,
738739
output_schema,
739740
} => {
741+
let (sandbox_policy, permission_profile) = if matches!(
742+
default_sandbox_policy,
743+
SandboxPolicy::ExternalSandbox { .. }
744+
) {
745+
(Some(default_sandbox_policy.clone().into()), None)
746+
} else {
747+
let permission_profile = PermissionProfile::from_legacy_sandbox_policy(
748+
default_sandbox_policy,
749+
&default_cwd,
750+
);
751+
(None, Some(permission_profile.into()))
752+
};
740753
let response: TurnStartResponse = send_request_with_response(
741754
&client,
742755
ClientRequest::TurnStart {
@@ -748,8 +761,8 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
748761
cwd: Some(default_cwd),
749762
approval_policy: Some(default_approval_policy.into()),
750763
approvals_reviewer: None,
751-
sandbox_policy: Some(default_sandbox_policy.clone().into()),
752-
permission_profile: None,
764+
sandbox_policy,
765+
permission_profile,
753766
model: None,
754767
service_tier: None,
755768
effort: default_effort,
@@ -923,33 +936,58 @@ fn sandbox_mode_from_policy(
923936
}
924937

925938
fn thread_start_params_from_config(config: &Config) -> ThreadStartParams {
939+
let permission_profile = permission_profile_override_from_config(config);
940+
let sandbox = permission_profile
941+
.is_none()
942+
.then(|| sandbox_mode_from_policy(config.permissions.sandbox_policy.get()))
943+
.flatten();
926944
ThreadStartParams {
927945
model: config.model.clone(),
928946
model_provider: Some(config.model_provider_id.clone()),
929947
cwd: Some(config.cwd.to_string_lossy().to_string()),
930948
approval_policy: Some(config.permissions.approval_policy.value().into()),
931949
approvals_reviewer: approvals_reviewer_override_from_config(config),
932-
sandbox: sandbox_mode_from_policy(config.permissions.sandbox_policy.get()),
950+
sandbox,
951+
permission_profile,
933952
config: config_request_overrides_from_config(config),
934953
ephemeral: Some(config.ephemeral),
935954
..ThreadStartParams::default()
936955
}
937956
}
938957

939958
fn thread_resume_params_from_config(config: &Config, thread_id: String) -> ThreadResumeParams {
959+
let permission_profile = permission_profile_override_from_config(config);
960+
let sandbox = permission_profile
961+
.is_none()
962+
.then(|| sandbox_mode_from_policy(config.permissions.sandbox_policy.get()))
963+
.flatten();
940964
ThreadResumeParams {
941965
thread_id,
942966
model: config.model.clone(),
943967
model_provider: Some(config.model_provider_id.clone()),
944968
cwd: Some(config.cwd.to_string_lossy().to_string()),
945969
approval_policy: Some(config.permissions.approval_policy.value().into()),
946970
approvals_reviewer: approvals_reviewer_override_from_config(config),
947-
sandbox: sandbox_mode_from_policy(config.permissions.sandbox_policy.get()),
971+
sandbox,
972+
permission_profile,
948973
config: config_request_overrides_from_config(config),
949974
..ThreadResumeParams::default()
950975
}
951976
}
952977

978+
fn permission_profile_override_from_config(
979+
config: &Config,
980+
) -> Option<codex_app_server_protocol::PermissionProfile> {
981+
if matches!(
982+
config.permissions.sandbox_policy.get(),
983+
SandboxPolicy::ExternalSandbox { .. }
984+
) {
985+
None
986+
} else {
987+
Some(config.permissions.permission_profile().into())
988+
}
989+
}
990+
953991
fn config_request_overrides_from_config(config: &Config) -> Option<HashMap<String, Value>> {
954992
config
955993
.active_profile

codex-rs/exec/src/lib_tests.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,11 @@ async fn thread_start_params_include_review_policy_when_review_policy_is_manual_
361361
params.approvals_reviewer,
362362
Some(codex_app_server_protocol::ApprovalsReviewer::User)
363363
);
364+
assert_eq!(params.sandbox, None);
365+
assert_eq!(
366+
params.permission_profile,
367+
Some(config.permissions.permission_profile().into())
368+
);
364369
}
365370

366371
#[tokio::test]

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2384,6 +2384,10 @@ async fn inactive_thread_approval_bubbles_into_active_view() -> Result<()> {
23842384
ThreadSessionState {
23852385
approval_policy: AskForApproval::OnRequest,
23862386
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
2387+
permission_profile: PermissionProfile::from_legacy_sandbox_policy(
2388+
&SandboxPolicy::new_workspace_write_policy(),
2389+
std::path::Path::new("/tmp/agent"),
2390+
),
23872391
rollout_path: Some(test_path_buf("/tmp/agent-rollout.jsonl")),
23882392
..test_thread_session(agent_thread_id, test_path_buf("/tmp/agent"))
23892393
},
@@ -2543,6 +2547,10 @@ async fn side_defers_subagent_approval_overlay_until_side_exits() -> Result<()>
25432547
ThreadSessionState {
25442548
approval_policy: AskForApproval::OnRequest,
25452549
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
2550+
permission_profile: PermissionProfile::from_legacy_sandbox_policy(
2551+
&SandboxPolicy::new_workspace_write_policy(),
2552+
std::path::Path::new("/tmp/agent"),
2553+
),
25462554
rollout_path: Some(test_path_buf("/tmp/agent-rollout.jsonl")),
25472555
..test_thread_session(agent_thread_id, test_path_buf("/tmp/agent"))
25482556
},
@@ -2764,6 +2772,10 @@ async fn inactive_thread_approval_badge_clears_after_turn_completion_notificatio
27642772
ThreadSessionState {
27652773
approval_policy: AskForApproval::OnRequest,
27662774
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
2775+
permission_profile: PermissionProfile::from_legacy_sandbox_policy(
2776+
&SandboxPolicy::new_workspace_write_policy(),
2777+
std::path::Path::new("/tmp/agent"),
2778+
),
27672779
rollout_path: Some(test_path_buf("/tmp/agent-rollout.jsonl")),
27682780
..test_thread_session(agent_thread_id, test_path_buf("/tmp/agent"))
27692781
},
@@ -2817,6 +2829,10 @@ async fn inactive_thread_started_notification_initializes_replay_session() -> Re
28172829
let primary_session = ThreadSessionState {
28182830
approval_policy: AskForApproval::OnRequest,
28192831
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
2832+
permission_profile: PermissionProfile::from_legacy_sandbox_policy(
2833+
&SandboxPolicy::new_workspace_write_policy(),
2834+
std::path::Path::new("/tmp/main"),
2835+
),
28202836
..test_thread_session(main_thread_id, test_path_buf("/tmp/main"))
28212837
};
28222838

@@ -2928,6 +2944,10 @@ async fn inactive_thread_started_notification_preserves_primary_model_when_path_
29282944
let primary_session = ThreadSessionState {
29292945
approval_policy: AskForApproval::OnRequest,
29302946
sandbox_policy: SandboxPolicy::new_workspace_write_policy(),
2947+
permission_profile: PermissionProfile::from_legacy_sandbox_policy(
2948+
&SandboxPolicy::new_workspace_write_policy(),
2949+
std::path::Path::new("/tmp/main"),
2950+
),
29312951
..test_thread_session(main_thread_id, test_path_buf("/tmp/main"))
29322952
};
29332953

@@ -3916,6 +3936,10 @@ fn test_thread_session(thread_id: ThreadId, cwd: PathBuf) -> ThreadSessionState
39163936
approval_policy: AskForApproval::Never,
39173937
approvals_reviewer: ApprovalsReviewer::User,
39183938
sandbox_policy: SandboxPolicy::new_read_only_policy(),
3939+
permission_profile: PermissionProfile::from_legacy_sandbox_policy(
3940+
&SandboxPolicy::new_read_only_policy(),
3941+
cwd.as_path(),
3942+
),
39193943
cwd: cwd.abs(),
39203944
instruction_source_paths: Vec::new(),
39213945
reasoning_effort: None,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ impl App {
2424
approval_policy: self.config.permissions.approval_policy.value(),
2525
approvals_reviewer: self.config.approvals_reviewer,
2626
sandbox_policy: self.config.permissions.sandbox_policy.get().clone(),
27+
permission_profile: self.config.permissions.permission_profile(),
2728
cwd: thread.cwd.clone(),
2829
instruction_source_paths: Vec::new(),
2930
reasoning_effort: self.chat_widget.current_reasoning_effort(),

codex-rs/tui/src/app_server_session.rs

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ use codex_app_server_protocol::TurnSteerParams;
8383
use codex_app_server_protocol::TurnSteerResponse;
8484
use codex_otel::TelemetryAuthMode;
8585
use codex_protocol::ThreadId;
86+
use codex_protocol::models::PermissionProfile;
8687
use codex_protocol::models::ResponseItem;
8788
use codex_protocol::openai_models::ModelAvailabilityNux;
8889
use codex_protocol::openai_models::ModelPreset;
@@ -146,6 +147,7 @@ pub(crate) struct ThreadSessionState {
146147
pub(crate) approval_policy: AskForApproval,
147148
pub(crate) approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer,
148149
pub(crate) sandbox_policy: SandboxPolicy,
150+
pub(crate) permission_profile: PermissionProfile,
149151
pub(crate) cwd: AbsolutePathBuf,
150152
pub(crate) instruction_source_paths: Vec<AbsolutePathBuf>,
151153
pub(crate) reasoning_effort: Option<codex_protocol::openai_models::ReasoningEffort>,
@@ -525,6 +527,14 @@ impl AppServerSession {
525527
output_schema: Option<serde_json::Value>,
526528
) -> Result<TurnStartResponse> {
527529
let request_id = self.next_request_id();
530+
let (sandbox_policy, permission_profile) = match sandbox_policy {
531+
policy @ SandboxPolicy::ExternalSandbox { .. } => (Some(policy.into()), None),
532+
policy => {
533+
let permission_profile =
534+
PermissionProfile::from_legacy_sandbox_policy(&policy, &cwd);
535+
(None, Some(permission_profile.into()))
536+
}
537+
};
528538
self.client
529539
.request_typed(ClientRequest::TurnStart {
530540
request_id,
@@ -535,8 +545,8 @@ impl AppServerSession {
535545
cwd: Some(cwd),
536546
approval_policy: Some(approval_policy.into()),
537547
approvals_reviewer: Some(approvals_reviewer.into()),
538-
sandbox_policy: Some(sandbox_policy.into()),
539-
permission_profile: None,
548+
sandbox_policy,
549+
permission_profile,
540550
model: Some(model),
541551
service_tier,
542552
effort,
@@ -1004,19 +1014,38 @@ fn sandbox_mode_from_policy(
10041014
}
10051015
}
10061016

1017+
fn permission_profile_override_from_config(
1018+
config: &Config,
1019+
) -> Option<codex_app_server_protocol::PermissionProfile> {
1020+
if matches!(
1021+
config.permissions.sandbox_policy.get(),
1022+
SandboxPolicy::ExternalSandbox { .. }
1023+
) {
1024+
None
1025+
} else {
1026+
Some(config.permissions.permission_profile().into())
1027+
}
1028+
}
1029+
10071030
fn thread_start_params_from_config(
10081031
config: &Config,
10091032
thread_params_mode: ThreadParamsMode,
10101033
remote_cwd_override: Option<&std::path::Path>,
10111034
session_start_source: Option<ThreadStartSource>,
10121035
) -> ThreadStartParams {
1036+
let permission_profile = permission_profile_override_from_config(config);
1037+
let sandbox = permission_profile
1038+
.is_none()
1039+
.then(|| sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()))
1040+
.flatten();
10131041
ThreadStartParams {
10141042
model: config.model.clone(),
10151043
model_provider: thread_params_mode.model_provider_from_config(config),
10161044
cwd: thread_cwd_from_config(config, thread_params_mode, remote_cwd_override),
10171045
approval_policy: Some(config.permissions.approval_policy.value().into()),
10181046
approvals_reviewer: approvals_reviewer_override_from_config(config),
1019-
sandbox: sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()),
1047+
sandbox,
1048+
permission_profile,
10201049
config: config_request_overrides_from_config(config),
10211050
ephemeral: Some(config.ephemeral),
10221051
session_start_source,
@@ -1031,14 +1060,20 @@ fn thread_resume_params_from_config(
10311060
thread_params_mode: ThreadParamsMode,
10321061
remote_cwd_override: Option<&std::path::Path>,
10331062
) -> ThreadResumeParams {
1063+
let permission_profile = permission_profile_override_from_config(&config);
1064+
let sandbox = permission_profile
1065+
.is_none()
1066+
.then(|| sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()))
1067+
.flatten();
10341068
ThreadResumeParams {
10351069
thread_id: thread_id.to_string(),
10361070
model: config.model.clone(),
10371071
model_provider: thread_params_mode.model_provider_from_config(&config),
10381072
cwd: thread_cwd_from_config(&config, thread_params_mode, remote_cwd_override),
10391073
approval_policy: Some(config.permissions.approval_policy.value().into()),
10401074
approvals_reviewer: approvals_reviewer_override_from_config(&config),
1041-
sandbox: sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()),
1075+
sandbox,
1076+
permission_profile,
10421077
config: config_request_overrides_from_config(&config),
10431078
persist_extended_history: true,
10441079
..ThreadResumeParams::default()
@@ -1051,14 +1086,20 @@ fn thread_fork_params_from_config(
10511086
thread_params_mode: ThreadParamsMode,
10521087
remote_cwd_override: Option<&std::path::Path>,
10531088
) -> ThreadForkParams {
1089+
let permission_profile = permission_profile_override_from_config(&config);
1090+
let sandbox = permission_profile
1091+
.is_none()
1092+
.then(|| sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()))
1093+
.flatten();
10541094
ThreadForkParams {
10551095
thread_id: thread_id.to_string(),
10561096
model: config.model.clone(),
10571097
model_provider: thread_params_mode.model_provider_from_config(&config),
10581098
cwd: thread_cwd_from_config(&config, thread_params_mode, remote_cwd_override),
10591099
approval_policy: Some(config.permissions.approval_policy.value().into()),
10601100
approvals_reviewer: approvals_reviewer_override_from_config(&config),
1061-
sandbox: sandbox_mode_from_policy(config.permissions.sandbox_policy.get().clone()),
1101+
sandbox,
1102+
permission_profile,
10621103
config: config_request_overrides_from_config(&config),
10631104
base_instructions: config.base_instructions.clone(),
10641105
developer_instructions: config.developer_instructions.clone(),
@@ -1135,6 +1176,7 @@ async fn thread_session_state_from_thread_start_response(
11351176
response.approval_policy.to_core(),
11361177
response.approvals_reviewer.to_core(),
11371178
response.sandbox.to_core(),
1179+
response.permission_profile.clone().into(),
11381180
response.cwd.clone(),
11391181
response.instruction_sources.clone(),
11401182
response.reasoning_effort,
@@ -1158,6 +1200,7 @@ async fn thread_session_state_from_thread_resume_response(
11581200
response.approval_policy.to_core(),
11591201
response.approvals_reviewer.to_core(),
11601202
response.sandbox.to_core(),
1203+
response.permission_profile.clone().into(),
11611204
response.cwd.clone(),
11621205
response.instruction_sources.clone(),
11631206
response.reasoning_effort,
@@ -1181,6 +1224,7 @@ async fn thread_session_state_from_thread_fork_response(
11811224
response.approval_policy.to_core(),
11821225
response.approvals_reviewer.to_core(),
11831226
response.sandbox.to_core(),
1227+
response.permission_profile.clone().into(),
11841228
response.cwd.clone(),
11851229
response.instruction_sources.clone(),
11861230
response.reasoning_effort,
@@ -1223,6 +1267,7 @@ async fn thread_session_state_from_thread_response(
12231267
approval_policy: AskForApproval,
12241268
approvals_reviewer: codex_protocol::config_types::ApprovalsReviewer,
12251269
sandbox_policy: SandboxPolicy,
1270+
permission_profile: PermissionProfile,
12261271
cwd: AbsolutePathBuf,
12271272
instruction_source_paths: Vec<AbsolutePathBuf>,
12281273
reasoning_effort: Option<codex_protocol::openai_models::ReasoningEffort>,
@@ -1249,6 +1294,7 @@ async fn thread_session_state_from_thread_response(
12491294
approval_policy,
12501295
approvals_reviewer,
12511296
sandbox_policy,
1297+
permission_profile,
12521298
cwd,
12531299
instruction_source_paths,
12541300
reasoning_effort,
@@ -1341,6 +1387,11 @@ mod tests {
13411387
);
13421388

13431389
assert_eq!(params.cwd, Some(config.cwd.to_string_lossy().to_string()));
1390+
assert_eq!(params.sandbox, None);
1391+
assert_eq!(
1392+
params.permission_profile,
1393+
Some(config.permissions.permission_profile().into())
1394+
);
13441395
assert_eq!(params.model_provider, Some(config.model_provider_id));
13451396
}
13461397

@@ -1521,6 +1572,10 @@ mod tests {
15211572
started.session.instruction_source_paths,
15221573
response.instruction_sources
15231574
);
1575+
assert_eq!(
1576+
started.session.permission_profile,
1577+
response.permission_profile.clone().into()
1578+
);
15241579
assert_eq!(started.turns.len(), 1);
15251580
assert_eq!(started.turns[0], response.thread.turns[0]);
15261581
}
@@ -1549,6 +1604,10 @@ mod tests {
15491604
AskForApproval::Never,
15501605
codex_protocol::config_types::ApprovalsReviewer::User,
15511606
SandboxPolicy::new_read_only_policy(),
1607+
PermissionProfile::from_legacy_sandbox_policy(
1608+
&SandboxPolicy::new_read_only_policy(),
1609+
std::path::Path::new("/tmp/project"),
1610+
),
15521611
test_path_buf("/tmp/project").abs(),
15531612
Vec::new(),
15541613
/*reasoning_effort*/ None,
@@ -1579,6 +1638,10 @@ mod tests {
15791638
AskForApproval::Never,
15801639
codex_protocol::config_types::ApprovalsReviewer::User,
15811640
SandboxPolicy::new_read_only_policy(),
1641+
PermissionProfile::from_legacy_sandbox_policy(
1642+
&SandboxPolicy::new_read_only_policy(),
1643+
std::path::Path::new("/tmp/project"),
1644+
),
15821645
test_path_buf("/tmp/project").abs(),
15831646
Vec::new(),
15841647
/*reasoning_effort*/ None,

0 commit comments

Comments
 (0)