Skip to content

Commit 39eb1ad

Browse files
committed
remove extra store and extend approvable to work for apply_patch
1 parent b9931c2 commit 39eb1ad

File tree

13 files changed

+189
-222
lines changed

13 files changed

+189
-222
lines changed

codex-rs/core/src/apply_patch.rs

Lines changed: 19 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
use crate::codex::Session;
21
use crate::codex::TurnContext;
32
use crate::function_tool::FunctionCallError;
43
use crate::protocol::FileChange;
5-
use crate::protocol::ReviewDecision;
64
use crate::safety::SafetyCheck;
75
use crate::safety::assess_patch_safety;
6+
use crate::tools::sandboxing::ExecApprovalRequirement;
87
use codex_apply_patch::ApplyPatchAction;
98
use codex_apply_patch::ApplyPatchFileChange;
109
use std::collections::HashMap;
@@ -30,70 +29,43 @@ pub(crate) enum InternalApplyPatchInvocation {
3029
#[derive(Debug)]
3130
pub(crate) struct ApplyPatchExec {
3231
pub(crate) action: ApplyPatchAction,
33-
pub(crate) user_explicitly_approved_this_action: bool,
32+
pub(crate) auto_approved: bool,
33+
pub(crate) exec_approval_requirement: ExecApprovalRequirement,
3434
}
3535

3636
pub(crate) async fn apply_patch(
37-
sess: &Session,
3837
turn_context: &TurnContext,
39-
call_id: &str,
4038
action: ApplyPatchAction,
4139
) -> InternalApplyPatchInvocation {
42-
let session_patch_approved = {
43-
let store = sess.services.apply_patch_approvals.lock().await;
44-
store.is_action_approved(&action, &action.cwd)
45-
};
4640
match assess_patch_safety(
4741
&action,
4842
turn_context.approval_policy,
4943
&turn_context.sandbox_policy,
5044
&turn_context.cwd,
51-
session_patch_approved,
5245
) {
5346
SafetyCheck::AutoApprove {
5447
user_explicitly_approved,
5548
..
5649
} => InternalApplyPatchInvocation::DelegateToExec(ApplyPatchExec {
5750
action,
58-
user_explicitly_approved_this_action: user_explicitly_approved,
51+
auto_approved: !user_explicitly_approved,
52+
exec_approval_requirement: ExecApprovalRequirement::Skip {
53+
bypass_sandbox: false,
54+
proposed_execpolicy_amendment: None,
55+
},
5956
}),
6057
SafetyCheck::AskUser => {
61-
// Compute a readable summary of path changes to include in the
62-
// approval request so the user can make an informed decision.
63-
//
64-
// Note that it might be worth expanding this approval request to
65-
// give the user the option to expand the set of writable roots so
66-
// that similar patches can be auto-approved in the future during
67-
// this session.
68-
let rx_approve = sess
69-
.request_patch_approval(
70-
turn_context,
71-
call_id.to_owned(),
72-
convert_apply_patch_to_protocol(&action),
73-
None,
74-
None,
75-
)
76-
.await;
77-
let decision = rx_approve.await.unwrap_or_default();
78-
if matches!(decision, ReviewDecision::ApprovedForSession) {
79-
let mut store = sess.services.apply_patch_approvals.lock().await;
80-
store.approve_action(&action, &action.cwd);
81-
}
82-
match decision {
83-
ReviewDecision::Approved
84-
| ReviewDecision::ApprovedExecpolicyAmendment { .. }
85-
| ReviewDecision::ApprovedForSession => {
86-
InternalApplyPatchInvocation::DelegateToExec(ApplyPatchExec {
87-
action,
88-
user_explicitly_approved_this_action: true,
89-
})
90-
}
91-
ReviewDecision::Denied | ReviewDecision::Abort => {
92-
InternalApplyPatchInvocation::Output(Err(FunctionCallError::RespondToModel(
93-
"patch rejected by user".to_string(),
94-
)))
95-
}
96-
}
58+
// Delegate the approval prompt (including cached approvals) to the
59+
// tool runtime, consistent with how shell/unified_exec approvals
60+
// are orchestrator-driven.
61+
InternalApplyPatchInvocation::DelegateToExec(ApplyPatchExec {
62+
action,
63+
auto_approved: false,
64+
exec_approval_requirement: ExecApprovalRequirement::NeedsApproval {
65+
reason: None,
66+
proposed_execpolicy_amendment: None,
67+
},
68+
})
9769
}
9870
SafetyCheck::Reject { reason } => InternalApplyPatchInvocation::Output(Err(
9971
FunctionCallError::RespondToModel(format!("patch rejected: {reason}")),
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use codex_apply_patch::ApplyPatchAction;
2+
use codex_apply_patch::ApplyPatchFileChange;
3+
use codex_utils_absolute_path::AbsolutePathBuf;
4+
use serde::Serialize;
5+
use std::path::Path;
6+
7+
#[derive(Serialize, Clone, Debug, Eq, PartialEq, Hash)]
8+
pub(crate) struct ApplyPatchFileApprovalKey {
9+
kind: &'static str,
10+
path: AbsolutePathBuf,
11+
}
12+
13+
impl ApplyPatchFileApprovalKey {
14+
fn new(path: AbsolutePathBuf) -> Self {
15+
Self {
16+
kind: "applyPatchFile",
17+
path,
18+
}
19+
}
20+
}
21+
22+
pub(crate) fn approval_keys_for_action(
23+
action: &ApplyPatchAction,
24+
) -> Vec<ApplyPatchFileApprovalKey> {
25+
let mut keys = Vec::new();
26+
let cwd = action.cwd.as_path();
27+
28+
for (path, change) in action.changes() {
29+
if let Some(key) = approval_key_for_path(cwd, path) {
30+
keys.push(key);
31+
}
32+
33+
if let ApplyPatchFileChange::Update { move_path, .. } = change
34+
&& let Some(dest) = move_path
35+
&& let Some(key) = approval_key_for_path(cwd, dest)
36+
{
37+
keys.push(key);
38+
}
39+
}
40+
41+
keys
42+
}
43+
44+
fn approval_key_for_path(cwd: &Path, path: &Path) -> Option<ApplyPatchFileApprovalKey> {
45+
AbsolutePathBuf::resolve_path_against_base(path, cwd)
46+
.ok()
47+
.map(ApplyPatchFileApprovalKey::new)
48+
}
49+
50+
#[cfg(test)]
51+
mod tests {
52+
use super::*;
53+
use codex_apply_patch::MaybeApplyPatchVerified;
54+
use tempfile::TempDir;
55+
56+
#[test]
57+
fn approval_keys_include_move_destination() {
58+
let tmp = TempDir::new().expect("tmp");
59+
let cwd = tmp.path();
60+
std::fs::create_dir_all(cwd.join("old")).expect("create old dir");
61+
std::fs::create_dir_all(cwd.join("renamed/dir")).expect("create dest dir");
62+
std::fs::write(cwd.join("old/name.txt"), "old content\n").expect("write old file");
63+
let patch = r#"*** Begin Patch
64+
*** Update File: old/name.txt
65+
*** Move to: renamed/dir/name.txt
66+
@@
67+
-old content
68+
+new content
69+
*** End Patch"#;
70+
let argv = vec!["apply_patch".to_string(), patch.to_string()];
71+
let action = match codex_apply_patch::maybe_parse_apply_patch_verified(&argv, cwd) {
72+
MaybeApplyPatchVerified::Body(action) => action,
73+
other => panic!("expected patch body, got: {other:?}"),
74+
};
75+
76+
let keys = approval_keys_for_action(&action);
77+
assert_eq!(keys.len(), 2);
78+
}
79+
}

codex-rs/core/src/apply_patch_approval_store.rs

Lines changed: 0 additions & 98 deletions
This file was deleted.

codex-rs/core/src/codex.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ use tracing::warn;
7575

7676
use crate::ModelProviderInfo;
7777
use crate::WireApi;
78-
use crate::apply_patch_approval_store::ApplyPatchApprovalStore;
7978
use crate::client::ModelClient;
8079
use crate::client_common::Prompt;
8180
use crate::client_common::ResponseEvent;
@@ -688,7 +687,6 @@ impl Session {
688687
otel_manager,
689688
models_manager: Arc::clone(&models_manager),
690689
tool_approvals: Mutex::new(ApprovalStore::default()),
691-
apply_patch_approvals: Mutex::new(ApplyPatchApprovalStore::default()),
692690
skills_manager,
693691
agent_control,
694692
};
@@ -3528,7 +3526,6 @@ mod tests {
35283526
otel_manager: otel_manager.clone(),
35293527
models_manager: Arc::clone(&models_manager),
35303528
tool_approvals: Mutex::new(ApprovalStore::default()),
3531-
apply_patch_approvals: Mutex::new(ApplyPatchApprovalStore::default()),
35323529
skills_manager,
35333530
agent_control,
35343531
};
@@ -3620,7 +3617,6 @@ mod tests {
36203617
otel_manager: otel_manager.clone(),
36213618
models_manager: Arc::clone(&models_manager),
36223619
tool_approvals: Mutex::new(ApprovalStore::default()),
3623-
apply_patch_approvals: Mutex::new(ApplyPatchApprovalStore::default()),
36243620
skills_manager,
36253621
agent_control,
36263622
};

codex-rs/core/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
pub mod api_bridge;
99
mod apply_patch;
10+
mod apply_patch_approval_key;
1011
pub mod auth;
1112
pub mod bash;
1213
mod client;
@@ -16,7 +17,6 @@ mod codex_conversation;
1617
mod compact_remote;
1718
pub use codex_conversation::CodexConversation;
1819
mod agent;
19-
mod apply_patch_approval_store;
2020
mod codex_delegate;
2121
mod command_safety;
2222
pub mod config;

codex-rs/core/src/safety.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,27 +67,13 @@ pub fn assess_patch_safety(
6767
policy: AskForApproval,
6868
sandbox_policy: &SandboxPolicy,
6969
cwd: &Path,
70-
session_patch_approved: bool,
7170
) -> SafetyCheck {
7271
if action.is_empty() {
7372
return SafetyCheck::Reject {
7473
reason: "empty patch".to_string(),
7574
};
7675
}
7776

78-
if session_patch_approved {
79-
let sandbox_type = match sandbox_policy {
80-
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => {
81-
SandboxType::None
82-
}
83-
_ => get_platform_sandbox().unwrap_or(SandboxType::None),
84-
};
85-
return SafetyCheck::AutoApprove {
86-
sandbox_type,
87-
user_explicitly_approved: true,
88-
};
89-
}
90-
9177
match policy {
9278
AskForApproval::OnFailure | AskForApproval::Never | AskForApproval::OnRequest => {
9379
// Continue to see if this can be auto-approved.

codex-rs/core/src/state/service.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::sync::Arc;
33
use crate::AuthManager;
44
use crate::RolloutRecorder;
55
use crate::agent::AgentControl;
6-
use crate::apply_patch_approval_store::ApplyPatchApprovalStore;
76
use crate::exec_policy::ExecPolicyManager;
87
use crate::mcp_connection_manager::McpConnectionManager;
98
use crate::models_manager::manager::ModelsManager;
@@ -29,7 +28,6 @@ pub(crate) struct SessionServices {
2928
pub(crate) models_manager: Arc<ModelsManager>,
3029
pub(crate) otel_manager: OtelManager,
3130
pub(crate) tool_approvals: Mutex<ApprovalStore>,
32-
pub(crate) apply_patch_approvals: Mutex<ApplyPatchApprovalStore>,
3331
pub(crate) skills_manager: Arc<SkillsManager>,
3432
pub(crate) agent_control: AgentControl,
3533
}

codex-rs/core/src/tools/events.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,12 @@ impl ToolEmitter {
305305
// Normalize common rejection messages for exec tools so tests and
306306
// users see a clear, consistent phrase.
307307
let normalized = if msg == "rejected by user" {
308-
"exec command rejected by user".to_string()
308+
match self {
309+
Self::Shell { .. } | Self::UnifiedExec { .. } => {
310+
"exec command rejected by user".to_string()
311+
}
312+
Self::ApplyPatch { .. } => "patch rejected by user".to_string(),
313+
}
309314
} else {
310315
msg
311316
};

0 commit comments

Comments
 (0)