Skip to content

Commit 44eb85a

Browse files
committed
[draft] first pass at tui integration
1 parent f4ccc75 commit 44eb85a

File tree

31 files changed

+556
-63
lines changed

31 files changed

+556
-63
lines changed

codex-rs/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codex-rs/app-server-protocol/src/protocol/common.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,7 @@ mod tests {
614614
parsed_cmd: vec![ParsedCommand::Unknown {
615615
cmd: "echo hello".to_string(),
616616
}],
617+
allow_prefix: None,
617618
};
618619
let request = ServerRequest::ExecCommandApproval {
619620
request_id: RequestId::Integer(7),

codex-rs/app-server-protocol/src/protocol/v1.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ pub struct ExecCommandApprovalParams {
228228
pub reason: Option<String>,
229229
pub risk: Option<SandboxCommandAssessment>,
230230
pub parsed_cmd: Vec<ParsedCommand>,
231+
#[serde(skip_serializing_if = "Option::is_none", default)]
232+
pub allow_prefix: Option<Vec<String>>,
231233
}
232234

233235
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]

codex-rs/app-server-protocol/src/protocol/v2.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,8 @@ pub struct CommandExecutionRequestApprovalParams {
960960
pub reason: Option<String>,
961961
/// Optional model-provided risk assessment describing the blocked command.
962962
pub risk: Option<SandboxCommandAssessment>,
963+
#[serde(skip_serializing_if = "Option::is_none", default)]
964+
pub allow_prefix: Option<Vec<String>>,
963965
}
964966

965967
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -969,6 +971,9 @@ pub struct CommandExecutionRequestAcceptSettings {
969971
/// If true, automatically approve this command for the duration of the session.
970972
#[serde(default)]
971973
pub for_session: bool,
974+
/// If true, persist an allow rule for the provided prefix.
975+
#[serde(default)]
976+
pub allow_prefix_rule: bool,
972977
}
973978

974979
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]

codex-rs/app-server/src/bespoke_event_handling.rs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ pub(crate) async fn apply_bespoke_event_handling(
9696
reason,
9797
risk,
9898
parsed_cmd,
99+
allow_prefix,
99100
}) => match api_version {
100101
ApiVersion::V1 => {
101102
let params = ExecCommandApprovalParams {
@@ -106,6 +107,7 @@ pub(crate) async fn apply_bespoke_event_handling(
106107
reason,
107108
risk,
108109
parsed_cmd,
110+
allow_prefix,
109111
};
110112
let rx = outgoing
111113
.send_request(ServerRequestPayload::ExecCommandApproval(params))
@@ -123,6 +125,7 @@ pub(crate) async fn apply_bespoke_event_handling(
123125
item_id: call_id.clone(),
124126
reason,
125127
risk: risk.map(V2SandboxCommandAssessment::from),
128+
allow_prefix,
126129
};
127130
let rx = outgoing
128131
.send_request(ServerRequestPayload::CommandExecutionRequestApproval(
@@ -540,13 +543,20 @@ async fn on_command_execution_request_approval_response(
540543
accept_settings,
541544
} = response;
542545

543-
let decision = match (decision, accept_settings) {
544-
(ApprovalDecision::Accept, Some(settings)) if settings.for_session => {
545-
ReviewDecision::ApprovedForSession
546-
}
547-
(ApprovalDecision::Accept, _) => ReviewDecision::Approved,
548-
(ApprovalDecision::Decline, _) => ReviewDecision::Denied,
549-
(ApprovalDecision::Cancel, _) => ReviewDecision::Abort,
546+
let allow_prefix_rule = accept_settings
547+
.as_ref()
548+
.is_some_and(|settings| settings.allow_prefix_rule);
549+
let for_session = accept_settings
550+
.as_ref()
551+
.map(|settings| settings.for_session)
552+
.unwrap_or(false);
553+
554+
let decision = match (decision, allow_prefix_rule, for_session) {
555+
(ApprovalDecision::Accept, true, _) => ReviewDecision::ApprovedAllowPrefix,
556+
(ApprovalDecision::Accept, false, true) => ReviewDecision::ApprovedForSession,
557+
(ApprovalDecision::Accept, false, false) => ReviewDecision::Approved,
558+
(ApprovalDecision::Decline, _, _) => ReviewDecision::Denied,
559+
(ApprovalDecision::Cancel, _, _) => ReviewDecision::Abort,
550560
};
551561
if let Err(err) = conversation
552562
.submit(Op::ExecApproval {

codex-rs/app-server/tests/suite/codex_message_processor_flow.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ async fn test_send_user_turn_changes_approval_policy_behavior() -> Result<()> {
278278
parsed_cmd: vec![ParsedCommand::Unknown {
279279
cmd: "python3 -c 'print(42)'".to_string()
280280
}],
281+
allow_prefix: None,
281282
},
282283
params
283284
);

codex-rs/core/src/apply_patch.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ pub(crate) async fn apply_patch(
7070
)
7171
.await;
7272
match rx_approve.await.unwrap_or_default() {
73-
ReviewDecision::Approved | ReviewDecision::ApprovedForSession => {
73+
ReviewDecision::Approved
74+
| ReviewDecision::ApprovedAllowPrefix
75+
| ReviewDecision::ApprovedForSession => {
7476
InternalApplyPatchInvocation::DelegateToExec(ApplyPatchExec {
7577
action,
7678
user_explicitly_approved_this_action: true,

codex-rs/core/src/codex.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,7 @@ impl Session {
574574
auth_manager: Arc::clone(&auth_manager),
575575
otel_event_manager,
576576
tool_approvals: Mutex::new(ApprovalStore::default()),
577+
exec_policy_overrides: Mutex::new(Vec::new()),
577578
};
578579

579580
let sess = Arc::new(Session {
@@ -846,11 +847,35 @@ impl Session {
846847
.await
847848
}
848849

850+
pub(crate) async fn remember_allowed_prefix(&self, prefix: &[String]) {
851+
{
852+
let mut overrides = self.services.exec_policy_overrides.lock().await;
853+
if overrides.iter().any(|existing| existing == prefix) {
854+
return;
855+
}
856+
overrides.push(prefix.to_vec());
857+
}
858+
859+
let codex_home = {
860+
let state = self.state.lock().await;
861+
state
862+
.session_configuration
863+
.original_config_do_not_use
864+
.codex_home
865+
.clone()
866+
};
867+
868+
if let Err(err) = crate::exec_policy::persist_allow_rule(&codex_home, prefix).await {
869+
warn!("failed to persist execpolicy allow rule: {err}");
870+
}
871+
}
872+
849873
/// Emit an exec approval request event and await the user's decision.
850874
///
851875
/// The request is keyed by `sub_id`/`call_id` so matching responses are delivered
852876
/// to the correct in-flight turn. If the task is aborted, this returns the
853877
/// default `ReviewDecision` (`Denied`).
878+
#[allow(clippy::too_many_arguments)]
854879
pub async fn request_command_approval(
855880
&self,
856881
turn_context: &TurnContext,
@@ -859,6 +884,7 @@ impl Session {
859884
cwd: PathBuf,
860885
reason: Option<String>,
861886
risk: Option<SandboxCommandAssessment>,
887+
allow_prefix: Option<Vec<String>>,
862888
) -> ReviewDecision {
863889
let sub_id = turn_context.sub_id.clone();
864890
// Add the tx_approve callback to the map before sending the request.
@@ -887,6 +913,7 @@ impl Session {
887913
reason,
888914
risk,
889915
parsed_cmd,
916+
allow_prefix,
890917
});
891918
self.send_event(turn_context, event).await;
892919
rx_approve.await.unwrap_or_default()
@@ -2636,6 +2663,7 @@ mod tests {
26362663
auth_manager: Arc::clone(&auth_manager),
26372664
otel_event_manager: otel_event_manager.clone(),
26382665
tool_approvals: Mutex::new(ApprovalStore::default()),
2666+
exec_policy_overrides: Mutex::new(Vec::new()),
26392667
};
26402668

26412669
let turn_context = Session::make_turn_context(
@@ -2714,6 +2742,7 @@ mod tests {
27142742
auth_manager: Arc::clone(&auth_manager),
27152743
otel_event_manager: otel_event_manager.clone(),
27162744
tool_approvals: Mutex::new(ApprovalStore::default()),
2745+
exec_policy_overrides: Mutex::new(Vec::new()),
27172746
};
27182747

27192748
let turn_context = Arc::new(Session::make_turn_context(

codex-rs/core/src/codex_delegate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ async fn handle_exec_approval(
235235
event.cwd,
236236
event.reason,
237237
event.risk,
238+
event.allow_prefix,
238239
);
239240
let decision = await_approval_with_cancel(
240241
approval_fut,

0 commit comments

Comments
 (0)