mirror of
https://github.com/openai/codex.git
synced 2026-10-29 19:08:43 +00:00
Merge branch 'rhan/approval-source' into rhan/escalations-approvals-sandbox
# Conflicts: # codex-rs/core/src/codex.rs # codex-rs/core/src/codex_tests.rs # codex-rs/core/src/state/turn.rs
This commit is contained in:
@@ -88,6 +88,7 @@ use codex_protocol::items::TurnItem;
|
||||
use codex_protocol::items::UserMessageItem;
|
||||
use codex_protocol::items::build_hook_prompt_message;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::models::ApprovalSourceMetadata;
|
||||
use codex_protocol::models::BaseInstructions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::ResponseItemMessageMetadata;
|
||||
@@ -209,6 +210,7 @@ use crate::file_watcher::FileWatcher;
|
||||
use crate::file_watcher::FileWatcherEvent;
|
||||
use crate::git_info::get_git_repo_root;
|
||||
use crate::guardian::GuardianReviewSessionManager;
|
||||
use crate::guardian::routes_approval_to_guardian;
|
||||
use crate::hook_runtime::PendingInputHookDisposition;
|
||||
use crate::hook_runtime::inspect_pending_input;
|
||||
use crate::hook_runtime::record_additional_contexts;
|
||||
@@ -1052,6 +1054,13 @@ fn user_message_metadata_patch(
|
||||
}
|
||||
}
|
||||
|
||||
fn approval_source_for_turn(turn_context: &TurnContext) -> ApprovalSourceMetadata {
|
||||
if routes_approval_to_guardian(turn_context) {
|
||||
ApprovalSourceMetadata::Guardian
|
||||
} else {
|
||||
ApprovalSourceMetadata::User
|
||||
}
|
||||
}
|
||||
fn response_item_tool_call_id(item: &ResponseItem) -> Option<&str> {
|
||||
match item {
|
||||
ResponseItem::LocalShellCall {
|
||||
@@ -1135,10 +1144,12 @@ fn stamp_tool_approval_metadata_with_snapshot(
|
||||
Some(outcome) => {
|
||||
metadata.is_tool_call_escalated = Some(true);
|
||||
metadata.review_decision = outcome.review_decision;
|
||||
metadata.approval_source = Some(outcome.approval_source);
|
||||
}
|
||||
None if !has_pending_approval => {
|
||||
metadata.is_tool_call_escalated = Some(false);
|
||||
metadata.review_decision = None;
|
||||
metadata.approval_source = None;
|
||||
}
|
||||
None => {
|
||||
return stamp_tool_metadata_on_response_item(response_item, metadata);
|
||||
@@ -3017,6 +3028,7 @@ impl Session {
|
||||
effective_approval_id.clone(),
|
||||
PendingApprovalMetadata {
|
||||
call_id: call_id.clone(),
|
||||
approval_source: approval_source_for_turn(turn_context),
|
||||
},
|
||||
);
|
||||
ts.insert_pending_approval(effective_approval_id.clone(), tx_approve)
|
||||
@@ -3088,6 +3100,7 @@ impl Session {
|
||||
approval_id.clone(),
|
||||
PendingApprovalMetadata {
|
||||
call_id: call_id.clone(),
|
||||
approval_source: approval_source_for_turn(turn_context),
|
||||
},
|
||||
);
|
||||
ts.insert_pending_approval(approval_id.clone(), tx_approve)
|
||||
@@ -3385,16 +3398,16 @@ impl Session {
|
||||
.remove_pending_approval_call_id(approval_id)
|
||||
.unwrap_or_else(|| PendingApprovalMetadata {
|
||||
call_id: approval_id.to_string(),
|
||||
approval_source: ApprovalSourceMetadata::Unknown,
|
||||
});
|
||||
drop(ts);
|
||||
drop(active);
|
||||
self.record_call_approval_outcome(
|
||||
pending_approval.call_id,
|
||||
ApprovalOutcomeMetadata::reviewed(decision),
|
||||
ApprovalOutcomeMetadata::reviewed(decision, pending_approval.approval_source),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
pub(crate) async fn record_call_approval_outcome(
|
||||
&self,
|
||||
call_id: String,
|
||||
|
||||
@@ -4418,52 +4418,100 @@ async fn task_finish_emits_prompt_queued_metadata_for_injected_user_input_when_f
|
||||
#[test]
|
||||
fn review_decision_metadata_mapping_is_stable() {
|
||||
assert_eq!(
|
||||
ApprovalOutcomeMetadata::reviewed(&ReviewDecision::Approved).review_decision,
|
||||
ApprovalOutcomeMetadata::reviewed(
|
||||
&ReviewDecision::Approved,
|
||||
codex_protocol::models::ApprovalSourceMetadata::User,
|
||||
)
|
||||
.review_decision,
|
||||
Some(codex_protocol::models::ReviewDecisionMetadata::Approved)
|
||||
);
|
||||
assert_eq!(
|
||||
ApprovalOutcomeMetadata::reviewed(&ReviewDecision::Denied).review_decision,
|
||||
ApprovalOutcomeMetadata::reviewed(
|
||||
&ReviewDecision::Denied,
|
||||
codex_protocol::models::ApprovalSourceMetadata::User,
|
||||
)
|
||||
.review_decision,
|
||||
Some(codex_protocol::models::ReviewDecisionMetadata::Denied)
|
||||
);
|
||||
assert_eq!(
|
||||
ApprovalOutcomeMetadata::reviewed(&ReviewDecision::Abort).review_decision,
|
||||
ApprovalOutcomeMetadata::reviewed(
|
||||
&ReviewDecision::Abort,
|
||||
codex_protocol::models::ApprovalSourceMetadata::User,
|
||||
)
|
||||
.review_decision,
|
||||
Some(codex_protocol::models::ReviewDecisionMetadata::Abort)
|
||||
);
|
||||
assert_eq!(
|
||||
ApprovalOutcomeMetadata::reviewed(&ReviewDecision::ApprovedForSession).review_decision,
|
||||
ApprovalOutcomeMetadata::reviewed(
|
||||
&ReviewDecision::ApprovedForSession,
|
||||
codex_protocol::models::ApprovalSourceMetadata::User,
|
||||
)
|
||||
.review_decision,
|
||||
Some(codex_protocol::models::ReviewDecisionMetadata::ApprovedForSession)
|
||||
);
|
||||
assert_eq!(
|
||||
ApprovalOutcomeMetadata::reviewed(&ReviewDecision::ApprovedExecpolicyAmendment {
|
||||
proposed_execpolicy_amendment: codex_protocol::approvals::ExecPolicyAmendment::new(
|
||||
vec!["echo".to_string(), "hi".to_string()],
|
||||
),
|
||||
})
|
||||
ApprovalOutcomeMetadata::reviewed(
|
||||
&ReviewDecision::ApprovedExecpolicyAmendment {
|
||||
proposed_execpolicy_amendment: codex_protocol::approvals::ExecPolicyAmendment::new(
|
||||
vec!["echo".to_string(), "hi".to_string()],
|
||||
),
|
||||
},
|
||||
codex_protocol::models::ApprovalSourceMetadata::User,
|
||||
)
|
||||
.review_decision,
|
||||
Some(codex_protocol::models::ReviewDecisionMetadata::ApprovedWithAmendment)
|
||||
);
|
||||
assert_eq!(
|
||||
ApprovalOutcomeMetadata::reviewed(&ReviewDecision::NetworkPolicyAmendment {
|
||||
network_policy_amendment: codex_protocol::approvals::NetworkPolicyAmendment {
|
||||
host: "example.com".to_string(),
|
||||
action: codex_protocol::protocol::NetworkPolicyRuleAction::Allow,
|
||||
ApprovalOutcomeMetadata::reviewed(
|
||||
&ReviewDecision::NetworkPolicyAmendment {
|
||||
network_policy_amendment: codex_protocol::approvals::NetworkPolicyAmendment {
|
||||
host: "example.com".to_string(),
|
||||
action: NetworkPolicyRuleAction::Allow,
|
||||
},
|
||||
},
|
||||
})
|
||||
codex_protocol::models::ApprovalSourceMetadata::User,
|
||||
)
|
||||
.review_decision,
|
||||
Some(codex_protocol::models::ReviewDecisionMetadata::ApprovedWithNetworkPolicyAllow)
|
||||
);
|
||||
assert_eq!(
|
||||
ApprovalOutcomeMetadata::reviewed(&ReviewDecision::NetworkPolicyAmendment {
|
||||
network_policy_amendment: codex_protocol::approvals::NetworkPolicyAmendment {
|
||||
host: "example.com".to_string(),
|
||||
action: codex_protocol::protocol::NetworkPolicyRuleAction::Deny,
|
||||
ApprovalOutcomeMetadata::reviewed(
|
||||
&ReviewDecision::NetworkPolicyAmendment {
|
||||
network_policy_amendment: codex_protocol::approvals::NetworkPolicyAmendment {
|
||||
host: "example.com".to_string(),
|
||||
action: NetworkPolicyRuleAction::Deny,
|
||||
},
|
||||
},
|
||||
})
|
||||
codex_protocol::models::ApprovalSourceMetadata::User,
|
||||
)
|
||||
.review_decision,
|
||||
Some(codex_protocol::models::ReviewDecisionMetadata::DeniedWithNetworkPolicyDeny)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn approval_source_metadata_mapping_is_stable() {
|
||||
let (_session, mut turn_context) = make_session_and_context().await;
|
||||
|
||||
assert_eq!(
|
||||
approval_source_for_turn(&turn_context),
|
||||
codex_protocol::models::ApprovalSourceMetadata::User
|
||||
);
|
||||
|
||||
turn_context
|
||||
.approval_policy
|
||||
.set(crate::protocol::AskForApproval::OnRequest)
|
||||
.expect("test setup should allow updating approval policy");
|
||||
Arc::get_mut(&mut turn_context.config)
|
||||
.expect("single turn config ref")
|
||||
.approvals_reviewer = codex_protocol::config_types::ApprovalsReviewer::GuardianSubagent;
|
||||
|
||||
assert_eq!(
|
||||
approval_source_for_turn(&turn_context),
|
||||
codex_protocol::models::ApprovalSourceMetadata::Guardian
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_policy_metadata_mapping_is_stable() {
|
||||
assert_eq!(
|
||||
@@ -4527,6 +4575,7 @@ async fn assert_next_emitted_function_call_metadata(
|
||||
expected_sandbox_policy: codex_protocol::models::SandboxPolicyMetadata,
|
||||
expected_escalated: bool,
|
||||
expected_review_decision: Option<codex_protocol::models::ReviewDecisionMetadata>,
|
||||
expected_approval_source: Option<codex_protocol::models::ApprovalSourceMetadata>,
|
||||
) {
|
||||
let event = tokio::time::timeout(std::time::Duration::from_secs(2), rx.recv())
|
||||
.await
|
||||
@@ -4542,6 +4591,7 @@ async fn assert_next_emitted_function_call_metadata(
|
||||
..
|
||||
} if metadata.is_tool_call_escalated == Some(expected_escalated)
|
||||
&& metadata.review_decision == expected_review_decision
|
||||
&& metadata.approval_source == expected_approval_source
|
||||
&& metadata.sandbox_policy == Some(expected_sandbox_policy)
|
||||
)
|
||||
));
|
||||
@@ -4553,7 +4603,10 @@ async fn tool_call_metadata_stamps_escalated_review_decision_when_feature_enable
|
||||
|
||||
sess.record_call_approval_outcome(
|
||||
"call-1".to_string(),
|
||||
ApprovalOutcomeMetadata::reviewed(&ReviewDecision::Denied),
|
||||
ApprovalOutcomeMetadata::reviewed(
|
||||
&ReviewDecision::Denied,
|
||||
codex_protocol::models::ApprovalSourceMetadata::User,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
sess.record_response_item_and_emit_turn_item(tc.as_ref(), function_call_item("call-1"))
|
||||
@@ -4563,6 +4616,7 @@ async fn tool_call_metadata_stamps_escalated_review_decision_when_feature_enable
|
||||
expected_sandbox_policy,
|
||||
true,
|
||||
Some(codex_protocol::models::ReviewDecisionMetadata::Denied),
|
||||
Some(codex_protocol::models::ApprovalSourceMetadata::User),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@@ -4573,7 +4627,62 @@ async fn tool_call_metadata_stamps_non_escalated_false_when_feature_enabled() {
|
||||
|
||||
sess.record_response_item_and_emit_turn_item(tc.as_ref(), function_call_item("call-2"))
|
||||
.await;
|
||||
assert_next_emitted_function_call_metadata(&rx, expected_sandbox_policy, false, None).await;
|
||||
assert_next_emitted_function_call_metadata(&rx, expected_sandbox_policy, false, None, None)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn tool_call_metadata_stamps_guardian_direct_review_when_feature_enabled() {
|
||||
let (sess, tc, rx, expected_sandbox_policy) = setup_tool_call_metadata_runtime_test().await;
|
||||
|
||||
sess.record_call_approval_outcome(
|
||||
"call-guardian-runtime-1".to_string(),
|
||||
ApprovalOutcomeMetadata::reviewed(
|
||||
&ReviewDecision::Denied,
|
||||
codex_protocol::models::ApprovalSourceMetadata::Guardian,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
sess.record_response_item_and_emit_turn_item(
|
||||
tc.as_ref(),
|
||||
function_call_item("call-guardian-runtime-1"),
|
||||
)
|
||||
.await;
|
||||
assert_next_emitted_function_call_metadata(
|
||||
&rx,
|
||||
expected_sandbox_policy,
|
||||
true,
|
||||
Some(codex_protocol::models::ReviewDecisionMetadata::Denied),
|
||||
Some(codex_protocol::models::ApprovalSourceMetadata::Guardian),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn tool_call_metadata_stamps_policy_source_without_review_decision_when_feature_enabled() {
|
||||
let (sess, tc, rx, expected_sandbox_policy) = setup_tool_call_metadata_runtime_test().await;
|
||||
|
||||
sess.record_call_approval_outcome(
|
||||
"call-policy-runtime-1".to_string(),
|
||||
ApprovalOutcomeMetadata {
|
||||
review_decision: None,
|
||||
approval_source: codex_protocol::models::ApprovalSourceMetadata::Policy,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
sess.record_response_item_and_emit_turn_item(
|
||||
tc.as_ref(),
|
||||
function_call_item("call-policy-runtime-1"),
|
||||
)
|
||||
.await;
|
||||
assert_next_emitted_function_call_metadata(
|
||||
&rx,
|
||||
expected_sandbox_policy,
|
||||
true,
|
||||
None,
|
||||
Some(codex_protocol::models::ApprovalSourceMetadata::Policy),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
@@ -4629,6 +4738,7 @@ async fn handle_output_item_done_stamps_tool_call_metadata_when_feature_enabled(
|
||||
} if call_id == "call-stream-1"
|
||||
&& metadata.is_tool_call_escalated == Some(false)
|
||||
&& metadata.review_decision.is_none()
|
||||
&& metadata.approval_source.is_none()
|
||||
&& metadata.sandbox_policy == Some(expected_sandbox_policy.clone())
|
||||
)
|
||||
}));
|
||||
@@ -4671,7 +4781,10 @@ async fn tool_call_metadata_can_be_restamped_after_approval_outcome() {
|
||||
.await;
|
||||
sess.record_call_approval_outcome(
|
||||
"call-restamp-1".to_string(),
|
||||
ApprovalOutcomeMetadata::reviewed(&ReviewDecision::Denied),
|
||||
ApprovalOutcomeMetadata::reviewed(
|
||||
&ReviewDecision::Denied,
|
||||
codex_protocol::models::ApprovalSourceMetadata::User,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -4701,6 +4814,89 @@ async fn tool_call_metadata_can_be_restamped_after_approval_outcome() {
|
||||
} if metadata.is_tool_call_escalated == Some(true)
|
||||
&& metadata.review_decision
|
||||
== Some(codex_protocol::models::ReviewDecisionMetadata::Denied)
|
||||
&& metadata.approval_source
|
||||
== Some(codex_protocol::models::ApprovalSourceMetadata::User)
|
||||
&& metadata.sandbox_policy == Some(expected_sandbox_policy)
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tool_call_metadata_snapshot_stamps_guardian_approval_source() {
|
||||
let (_session, turn_context_raw) = make_session_and_context().await;
|
||||
let turn_context = Arc::new(turn_context_raw);
|
||||
let expected_sandbox_policy = sandbox_policy_to_metadata(turn_context.sandbox_policy.get());
|
||||
let item = ResponseItem::FunctionCall {
|
||||
id: None,
|
||||
name: "shell".to_string(),
|
||||
namespace: None,
|
||||
arguments: "{}".to_string(),
|
||||
call_id: "call-guardian-1".to_string(),
|
||||
metadata: None,
|
||||
};
|
||||
let snapshot = ToolApprovalMetadataSnapshot {
|
||||
approval_outcomes_by_call_id: HashMap::from([(
|
||||
"call-guardian-1".to_string(),
|
||||
ApprovalOutcomeMetadata {
|
||||
review_decision: Some(codex_protocol::models::ReviewDecisionMetadata::Denied),
|
||||
approval_source: codex_protocol::models::ApprovalSourceMetadata::Guardian,
|
||||
},
|
||||
)]),
|
||||
pending_approval_call_ids: HashSet::new(),
|
||||
};
|
||||
|
||||
let stamped =
|
||||
stamp_tool_approval_metadata_with_snapshot(turn_context.as_ref(), item, Some(&snapshot));
|
||||
|
||||
assert!(matches!(
|
||||
stamped,
|
||||
ResponseItem::FunctionCall {
|
||||
metadata: Some(metadata),
|
||||
..
|
||||
} if metadata.is_tool_call_escalated == Some(true)
|
||||
&& metadata.review_decision
|
||||
== Some(codex_protocol::models::ReviewDecisionMetadata::Denied)
|
||||
&& metadata.approval_source
|
||||
== Some(codex_protocol::models::ApprovalSourceMetadata::Guardian)
|
||||
&& metadata.sandbox_policy == Some(expected_sandbox_policy)
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tool_call_metadata_snapshot_stamps_policy_approval_source_without_review_decision() {
|
||||
let (_session, turn_context_raw) = make_session_and_context().await;
|
||||
let turn_context = Arc::new(turn_context_raw);
|
||||
let expected_sandbox_policy = sandbox_policy_to_metadata(turn_context.sandbox_policy.get());
|
||||
let item = ResponseItem::FunctionCall {
|
||||
id: None,
|
||||
name: "shell".to_string(),
|
||||
namespace: None,
|
||||
arguments: "{}".to_string(),
|
||||
call_id: "call-policy-1".to_string(),
|
||||
metadata: None,
|
||||
};
|
||||
let snapshot = ToolApprovalMetadataSnapshot {
|
||||
approval_outcomes_by_call_id: HashMap::from([(
|
||||
"call-policy-1".to_string(),
|
||||
ApprovalOutcomeMetadata {
|
||||
review_decision: None,
|
||||
approval_source: codex_protocol::models::ApprovalSourceMetadata::Policy,
|
||||
},
|
||||
)]),
|
||||
pending_approval_call_ids: HashSet::new(),
|
||||
};
|
||||
|
||||
let stamped =
|
||||
stamp_tool_approval_metadata_with_snapshot(turn_context.as_ref(), item, Some(&snapshot));
|
||||
|
||||
assert!(matches!(
|
||||
stamped,
|
||||
ResponseItem::FunctionCall {
|
||||
metadata: Some(metadata),
|
||||
..
|
||||
} if metadata.is_tool_call_escalated == Some(true)
|
||||
&& metadata.review_decision.is_none()
|
||||
&& metadata.approval_source
|
||||
== Some(codex_protocol::models::ApprovalSourceMetadata::Policy)
|
||||
&& metadata.sandbox_policy == Some(expected_sandbox_policy)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -31,9 +31,11 @@ use crate::protocol::EventMsg;
|
||||
use crate::protocol::McpInvocation;
|
||||
use crate::protocol::McpToolCallBeginEvent;
|
||||
use crate::protocol::McpToolCallEndEvent;
|
||||
use crate::state::ApprovalOutcomeMetadata;
|
||||
use crate::state_db;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::models::ApprovalSourceMetadata;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
@@ -545,6 +547,11 @@ async fn maybe_request_mcp_tool_approval(
|
||||
monitor_reason.clone(),
|
||||
)
|
||||
.await;
|
||||
sess.record_call_approval_outcome(
|
||||
call_id.to_string(),
|
||||
ApprovalOutcomeMetadata::reviewed(&decision, ApprovalSourceMetadata::Guardian),
|
||||
)
|
||||
.await;
|
||||
let decision = mcp_tool_approval_decision_from_guardian(decision);
|
||||
apply_mcp_tool_approval_decision(
|
||||
sess,
|
||||
|
||||
@@ -10,6 +10,7 @@ use tokio_util::sync::CancellationToken;
|
||||
use tokio_util::task::AbortOnDropHandle;
|
||||
|
||||
use codex_protocol::dynamic_tools::DynamicToolResponse;
|
||||
use codex_protocol::models::ApprovalSourceMetadata;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::ReviewDecisionMetadata;
|
||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||
@@ -61,15 +62,20 @@ pub(crate) struct RunningTask {
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) struct PendingApprovalMetadata {
|
||||
pub(crate) call_id: String,
|
||||
pub(crate) approval_source: ApprovalSourceMetadata,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) struct ApprovalOutcomeMetadata {
|
||||
pub(crate) review_decision: Option<ReviewDecisionMetadata>,
|
||||
pub(crate) approval_source: ApprovalSourceMetadata,
|
||||
}
|
||||
|
||||
impl ApprovalOutcomeMetadata {
|
||||
pub(crate) fn reviewed(decision: &ReviewDecision) -> Self {
|
||||
pub(crate) fn reviewed(
|
||||
decision: &ReviewDecision,
|
||||
approval_source: ApprovalSourceMetadata,
|
||||
) -> Self {
|
||||
let review_decision = match decision {
|
||||
ReviewDecision::Approved => ReviewDecisionMetadata::Approved,
|
||||
ReviewDecision::ApprovedExecpolicyAmendment { .. } => {
|
||||
@@ -91,6 +97,7 @@ impl ApprovalOutcomeMetadata {
|
||||
};
|
||||
Self {
|
||||
review_decision: Some(review_decision),
|
||||
approval_source,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::guardian::routes_approval_to_guardian;
|
||||
use crate::sandboxing::CommandSpec;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::sandboxing::execute_env;
|
||||
use crate::state::ApprovalOutcomeMetadata;
|
||||
use crate::tools::sandboxing::Approvable;
|
||||
use crate::tools::sandboxing::ApprovalCtx;
|
||||
use crate::tools::sandboxing::ExecApprovalRequirement;
|
||||
@@ -24,6 +25,7 @@ use crate::tools::sandboxing::ToolRuntime;
|
||||
use crate::tools::sandboxing::with_cached_approval;
|
||||
use codex_apply_patch::ApplyPatchAction;
|
||||
use codex_apply_patch::CODEX_CORE_APPLY_PATCH_ARG1;
|
||||
use codex_protocol::models::ApprovalSourceMetadata;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::FileChange;
|
||||
@@ -142,7 +144,17 @@ impl Approvable<ApplyPatchRequest> for ApplyPatchRuntime {
|
||||
Box::pin(async move {
|
||||
if routes_approval_to_guardian(turn) {
|
||||
let action = ApplyPatchRuntime::build_guardian_review_request(req, ctx.call_id);
|
||||
return review_approval_request(session, turn, action, retry_reason).await;
|
||||
let decision = review_approval_request(session, turn, action, retry_reason).await;
|
||||
session
|
||||
.record_call_approval_outcome(
|
||||
call_id.clone(),
|
||||
ApprovalOutcomeMetadata::reviewed(
|
||||
&decision,
|
||||
ApprovalSourceMetadata::Guardian,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
return decision;
|
||||
}
|
||||
if req.permissions_preapproved && retry_reason.is_none() {
|
||||
return ReviewDecision::Approved;
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::powershell::prefix_powershell_script_with_utf8;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::sandboxing::execute_env;
|
||||
use crate::shell::ShellType;
|
||||
use crate::state::ApprovalOutcomeMetadata;
|
||||
use crate::tools::network_approval::NetworkApprovalMode;
|
||||
use crate::tools::network_approval::NetworkApprovalSpec;
|
||||
use crate::tools::runtimes::build_command_spec;
|
||||
@@ -35,6 +36,7 @@ use crate::tools::sandboxing::sandbox_override_for_first_attempt;
|
||||
use crate::tools::sandboxing::with_cached_approval;
|
||||
use codex_features::Feature;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_protocol::models::ApprovalSourceMetadata;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
use futures::future::BoxFuture;
|
||||
@@ -153,11 +155,11 @@ impl Approvable<ShellRequest> for ShellRuntime {
|
||||
let call_id = ctx.call_id.to_string();
|
||||
Box::pin(async move {
|
||||
if routes_approval_to_guardian(turn) {
|
||||
return review_approval_request(
|
||||
let decision = review_approval_request(
|
||||
session,
|
||||
turn,
|
||||
GuardianApprovalRequest::Shell {
|
||||
id: call_id,
|
||||
id: call_id.clone(),
|
||||
command,
|
||||
cwd,
|
||||
sandbox_permissions: req.sandbox_permissions,
|
||||
@@ -167,6 +169,16 @@ impl Approvable<ShellRequest> for ShellRuntime {
|
||||
retry_reason,
|
||||
)
|
||||
.await;
|
||||
session
|
||||
.record_call_approval_outcome(
|
||||
call_id.clone(),
|
||||
ApprovalOutcomeMetadata::reviewed(
|
||||
&decision,
|
||||
ApprovalSourceMetadata::Guardian,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
return decision;
|
||||
}
|
||||
with_cached_approval(&session.services, "shell", keys, move || async move {
|
||||
let available_decisions = None;
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::sandboxing::ExecRequest;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::shell::ShellType;
|
||||
use crate::skills::SkillMetadata;
|
||||
use crate::state::ApprovalOutcomeMetadata;
|
||||
use crate::tools::runtimes::ExecveSessionApproval;
|
||||
use crate::tools::runtimes::build_command_spec;
|
||||
use crate::tools::sandboxing::SandboxAttempt;
|
||||
@@ -26,6 +27,7 @@ use codex_execpolicy::Policy;
|
||||
use codex_execpolicy::RuleMatch;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::ApprovalSourceMetadata;
|
||||
use codex_protocol::models::MacOsSeatbeltProfileExtensions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
@@ -426,7 +428,7 @@ impl CoreShellActionProvider {
|
||||
Ok(stopwatch
|
||||
.pause_for(async move {
|
||||
if routes_approval_to_guardian(&turn) {
|
||||
return review_approval_request(
|
||||
let decision = review_approval_request(
|
||||
&session,
|
||||
&turn,
|
||||
GuardianApprovalRequest::Execve {
|
||||
@@ -440,6 +442,16 @@ impl CoreShellActionProvider {
|
||||
/*retry_reason*/ None,
|
||||
)
|
||||
.await;
|
||||
session
|
||||
.record_call_approval_outcome(
|
||||
call_id.clone(),
|
||||
ApprovalOutcomeMetadata::reviewed(
|
||||
&decision,
|
||||
ApprovalSourceMetadata::Guardian,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
return decision;
|
||||
}
|
||||
let available_decisions = vec![
|
||||
Some(ReviewDecision::Approved),
|
||||
@@ -522,12 +534,31 @@ impl CoreShellActionProvider {
|
||||
) -> anyhow::Result<EscalationDecision> {
|
||||
let action = match decision {
|
||||
Decision::Forbidden => {
|
||||
self.session
|
||||
.record_call_approval_outcome(
|
||||
self.call_id.clone(),
|
||||
ApprovalOutcomeMetadata {
|
||||
review_decision: None,
|
||||
approval_source: codex_protocol::models::ApprovalSourceMetadata::Policy,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
EscalationDecision::deny(Some("Execution forbidden by policy".to_string()))
|
||||
}
|
||||
Decision::Prompt => {
|
||||
if execve_prompt_is_rejected_by_policy(self.approval_policy, &decision_source)
|
||||
.is_some()
|
||||
{
|
||||
self.session
|
||||
.record_call_approval_outcome(
|
||||
self.call_id.clone(),
|
||||
ApprovalOutcomeMetadata {
|
||||
review_decision: None,
|
||||
approval_source:
|
||||
codex_protocol::models::ApprovalSourceMetadata::Policy,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
EscalationDecision::deny(Some("Execution forbidden by policy".to_string()))
|
||||
} else {
|
||||
match self
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::guardian::routes_approval_to_guardian;
|
||||
use crate::powershell::prefix_powershell_script_with_utf8;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::shell::ShellType;
|
||||
use crate::state::ApprovalOutcomeMetadata;
|
||||
use crate::tools::network_approval::NetworkApprovalMode;
|
||||
use crate::tools::network_approval::NetworkApprovalSpec;
|
||||
use crate::tools::runtimes::build_command_spec;
|
||||
@@ -38,6 +39,7 @@ use crate::unified_exec::UnifiedExecProcess;
|
||||
use crate::unified_exec::UnifiedExecProcessManager;
|
||||
use codex_features::Feature;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_protocol::models::ApprovalSourceMetadata;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
use futures::future::BoxFuture;
|
||||
@@ -121,11 +123,11 @@ impl Approvable<UnifiedExecRequest> for UnifiedExecRuntime<'_> {
|
||||
let reason = retry_reason.clone().or_else(|| req.justification.clone());
|
||||
Box::pin(async move {
|
||||
if routes_approval_to_guardian(turn) {
|
||||
return review_approval_request(
|
||||
let decision = review_approval_request(
|
||||
session,
|
||||
turn,
|
||||
GuardianApprovalRequest::ExecCommand {
|
||||
id: call_id,
|
||||
id: call_id.clone(),
|
||||
command,
|
||||
cwd,
|
||||
sandbox_permissions: req.sandbox_permissions,
|
||||
@@ -136,6 +138,16 @@ impl Approvable<UnifiedExecRequest> for UnifiedExecRuntime<'_> {
|
||||
retry_reason,
|
||||
)
|
||||
.await;
|
||||
session
|
||||
.record_call_approval_outcome(
|
||||
call_id.clone(),
|
||||
ApprovalOutcomeMetadata::reviewed(
|
||||
&decision,
|
||||
ApprovalSourceMetadata::Guardian,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
return decision;
|
||||
}
|
||||
with_cached_approval(&session.services, "unified_exec", keys, || async move {
|
||||
let available_decisions = None;
|
||||
|
||||
Reference in New Issue
Block a user