mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
guardian initial feedback / tweaks (#13897)
## Summary - remove the remaining model-visible guardian-specific `on-request` prompt additions so enabling the feature does not change the main approval-policy instructions - neutralize user-facing guardian wording to talk about automatic approval review / approval requests rather than a second reviewer or only sandbox escalations - tighten guardian retry-context handling so agent-authored `justification` stays in the structured action JSON and is not also injected as raw retry context - simplify guardian review plumbing in core by deleting dead prompt-append paths and trimming some request/transcript setup code ## Notable Changes - delete the dead `permissions/approval_policy/guardian.md` append path and stop threading `guardian_approval_enabled` through model-facing developer-instruction builders - rename the experimental feature copy to `Automatic approval review` and update the `/experimental` snapshot text accordingly - make approval-review status strings generic across shell, patch, network, and MCP review types - forward real sandbox/network retry reasons for shell and unified-exec guardian review, but do not pass agent-authored justification as raw retry context - simplify `guardian.rs` by removing the one-field request wrapper, deduping reasoning-effort selection, and cleaning up transcript entry collection ## Testing - `just fmt` - full validation left to CI --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
2bc3e52a91
commit
f23fcd6ced
@@ -3265,7 +3265,6 @@ impl Session {
|
|||||||
DeveloperInstructions::from_policy(
|
DeveloperInstructions::from_policy(
|
||||||
turn_context.sandbox_policy.get(),
|
turn_context.sandbox_policy.get(),
|
||||||
turn_context.approval_policy.value(),
|
turn_context.approval_policy.value(),
|
||||||
turn_context.features.enabled(Feature::GuardianApproval),
|
|
||||||
self.services.exec_policy.current().as_ref(),
|
self.services.exec_policy.current().as_ref(),
|
||||||
&turn_context.cwd,
|
&turn_context.cwd,
|
||||||
turn_context.features.enabled(Feature::RequestPermissions),
|
turn_context.features.enabled(Feature::RequestPermissions),
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ fn build_permissions_update_item(
|
|||||||
Some(DeveloperInstructions::from_policy(
|
Some(DeveloperInstructions::from_policy(
|
||||||
next.sandbox_policy.get(),
|
next.sandbox_policy.get(),
|
||||||
next.approval_policy.value(),
|
next.approval_policy.value(),
|
||||||
next.features.enabled(Feature::GuardianApproval),
|
|
||||||
exec_policy,
|
exec_policy,
|
||||||
&next.cwd,
|
&next.cwd,
|
||||||
next.features.enabled(Feature::RequestPermissions),
|
next.features.enabled(Feature::RequestPermissions),
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ pub enum Feature {
|
|||||||
Steer,
|
Steer,
|
||||||
/// Allow request_user_input in Default collaboration mode.
|
/// Allow request_user_input in Default collaboration mode.
|
||||||
DefaultModeRequestUserInput,
|
DefaultModeRequestUserInput,
|
||||||
/// Enable guardian subagent approvals.
|
/// Enable automatic review for approval prompts.
|
||||||
GuardianApproval,
|
GuardianApproval,
|
||||||
/// Enable collaboration modes (Plan, Default).
|
/// Enable collaboration modes (Plan, Default).
|
||||||
/// Kept for config backward compatibility; behavior is always collaboration-modes-enabled.
|
/// Kept for config backward compatibility; behavior is always collaboration-modes-enabled.
|
||||||
@@ -710,8 +710,8 @@ pub const FEATURES: &[FeatureSpec] = &[
|
|||||||
id: Feature::GuardianApproval,
|
id: Feature::GuardianApproval,
|
||||||
key: "guardian_approval",
|
key: "guardian_approval",
|
||||||
stage: Stage::Experimental {
|
stage: Stage::Experimental {
|
||||||
name: "Guardian approvals",
|
name: "Automatic approval review",
|
||||||
menu_description: "Let a guardian subagent review `on-request` approval prompts instead of showing them to you, including sandbox escapes and blocked network access.",
|
menu_description: "Dispatch `on-request` approval prompts (for e.g. sandbox escapes or blocked network access) to a carefully-prompted security reviewer subagent rather than blocking the agent on your input.",
|
||||||
announcement: "",
|
announcement: "",
|
||||||
},
|
},
|
||||||
default_enabled: false,
|
default_enabled: false,
|
||||||
@@ -917,11 +917,14 @@ mod tests {
|
|||||||
let stage = spec.stage;
|
let stage = spec.stage;
|
||||||
|
|
||||||
assert!(matches!(stage, Stage::Experimental { .. }));
|
assert!(matches!(stage, Stage::Experimental { .. }));
|
||||||
assert_eq!(stage.experimental_menu_name(), Some("Guardian approvals"));
|
assert_eq!(
|
||||||
|
stage.experimental_menu_name(),
|
||||||
|
Some("Automatic approval review")
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stage.experimental_menu_description().map(str::to_owned),
|
stage.experimental_menu_description().map(str::to_owned),
|
||||||
Some(
|
Some(
|
||||||
"Let a guardian subagent review `on-request` approval prompts instead of showing them to you, including sandbox escapes and blocked network access.".to_string()
|
"Dispatch `on-request` approval prompts (for e.g. sandbox escapes or blocked network access) to a carefully-prompted security reviewer subagent rather than blocking the agent on your input.".to_string()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(stage.experimental_announcement(), None);
|
assert_eq!(stage.experimental_announcement(), None);
|
||||||
|
|||||||
+270
-77
@@ -12,15 +12,19 @@
|
|||||||
//! 4. Approve only low- and medium-risk actions (`risk_score < 80`).
|
//! 4. Approve only low- and medium-risk actions (`risk_score < 80`).
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use codex_protocol::approvals::NetworkApprovalProtocol;
|
||||||
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::models::ResponseItem;
|
use codex_protocol::models::ResponseItem;
|
||||||
use codex_protocol::protocol::AskForApproval;
|
use codex_protocol::protocol::AskForApproval;
|
||||||
use codex_protocol::protocol::EventMsg;
|
use codex_protocol::protocol::EventMsg;
|
||||||
use codex_protocol::protocol::SubAgentSource;
|
use codex_protocol::protocol::SubAgentSource;
|
||||||
use codex_protocol::protocol::WarningEvent;
|
use codex_protocol::protocol::WarningEvent;
|
||||||
use codex_protocol::user_input::UserInput;
|
use codex_protocol::user_input::UserInput;
|
||||||
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
@@ -66,7 +70,7 @@ const GUARDIAN_RECENT_ENTRY_LIMIT: usize = 40;
|
|||||||
const GUARDIAN_TRUNCATION_TAG: &str = "guardian_truncated";
|
const GUARDIAN_TRUNCATION_TAG: &str = "guardian_truncated";
|
||||||
|
|
||||||
pub(crate) const GUARDIAN_REJECTION_MESSAGE: &str = concat!(
|
pub(crate) const GUARDIAN_REJECTION_MESSAGE: &str = concat!(
|
||||||
"Guardian rejected this action due to unacceptable risk. ",
|
"This action was rejected due to unacceptable risk. ",
|
||||||
"The agent must not attempt to achieve the same outcome via workaround, ",
|
"The agent must not attempt to achieve the same outcome via workaround, ",
|
||||||
"indirect execution, or policy circumvention. ",
|
"indirect execution, or policy circumvention. ",
|
||||||
"Proceed only with a materially safer alternative, or stop and request user input.",
|
"Proceed only with a materially safer alternative, or stop and request user input.",
|
||||||
@@ -89,12 +93,6 @@ pub(crate) fn is_guardian_subagent_source(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Canonical description of the action the guardian is being asked to review.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub(crate) struct GuardianReviewRequest {
|
|
||||||
pub(crate) action: Value,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Coarse risk label paired with the numeric `risk_score`.
|
/// Coarse risk label paired with the numeric `risk_score`.
|
||||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
@@ -120,6 +118,66 @@ pub(crate) struct GuardianAssessment {
|
|||||||
evidence: Vec<GuardianEvidence>,
|
evidence: Vec<GuardianEvidence>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub(crate) enum GuardianApprovalRequest {
|
||||||
|
Shell {
|
||||||
|
command: Vec<String>,
|
||||||
|
cwd: PathBuf,
|
||||||
|
sandbox_permissions: crate::sandboxing::SandboxPermissions,
|
||||||
|
additional_permissions: Option<PermissionProfile>,
|
||||||
|
justification: Option<String>,
|
||||||
|
},
|
||||||
|
ExecCommand {
|
||||||
|
command: Vec<String>,
|
||||||
|
cwd: PathBuf,
|
||||||
|
sandbox_permissions: crate::sandboxing::SandboxPermissions,
|
||||||
|
additional_permissions: Option<PermissionProfile>,
|
||||||
|
justification: Option<String>,
|
||||||
|
tty: bool,
|
||||||
|
},
|
||||||
|
#[cfg(unix)]
|
||||||
|
Execve {
|
||||||
|
tool_name: String,
|
||||||
|
program: String,
|
||||||
|
argv: Vec<String>,
|
||||||
|
cwd: PathBuf,
|
||||||
|
additional_permissions: Option<PermissionProfile>,
|
||||||
|
},
|
||||||
|
ApplyPatch {
|
||||||
|
cwd: PathBuf,
|
||||||
|
files: Vec<AbsolutePathBuf>,
|
||||||
|
change_count: usize,
|
||||||
|
patch: String,
|
||||||
|
},
|
||||||
|
NetworkAccess {
|
||||||
|
target: String,
|
||||||
|
host: String,
|
||||||
|
protocol: NetworkApprovalProtocol,
|
||||||
|
port: u16,
|
||||||
|
},
|
||||||
|
McpToolCall {
|
||||||
|
server: String,
|
||||||
|
tool_name: String,
|
||||||
|
arguments: Option<Value>,
|
||||||
|
connector_id: Option<String>,
|
||||||
|
connector_name: Option<String>,
|
||||||
|
connector_description: Option<String>,
|
||||||
|
tool_title: Option<String>,
|
||||||
|
tool_description: Option<String>,
|
||||||
|
annotations: Option<GuardianMcpAnnotations>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||||
|
pub(crate) struct GuardianMcpAnnotations {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) destructive_hint: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) open_world_hint: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub(crate) read_only_hint: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Transcript entry retained for guardian review after filtering.
|
/// Transcript entry retained for guardian review after filtering.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
struct GuardianTranscriptEntry {
|
struct GuardianTranscriptEntry {
|
||||||
@@ -164,14 +222,11 @@ impl GuardianTranscriptEntryKind {
|
|||||||
async fn run_guardian_review(
|
async fn run_guardian_review(
|
||||||
session: Arc<Session>,
|
session: Arc<Session>,
|
||||||
turn: Arc<TurnContext>,
|
turn: Arc<TurnContext>,
|
||||||
request: GuardianReviewRequest,
|
request: GuardianApprovalRequest,
|
||||||
retry_reason: Option<String>,
|
retry_reason: Option<String>,
|
||||||
) -> ReviewDecision {
|
) -> ReviewDecision {
|
||||||
session
|
session
|
||||||
.notify_background_event(
|
.notify_background_event(turn.as_ref(), "Reviewing approval request...".to_string())
|
||||||
turn.as_ref(),
|
|
||||||
"Guardian assessing approval request...".to_string(),
|
|
||||||
)
|
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let prompt_items = build_guardian_prompt_items(session.as_ref(), retry_reason, request).await;
|
let prompt_items = build_guardian_prompt_items(session.as_ref(), retry_reason, request).await;
|
||||||
@@ -199,14 +254,15 @@ async fn run_guardian_review(
|
|||||||
Some(Err(err)) => GuardianAssessment {
|
Some(Err(err)) => GuardianAssessment {
|
||||||
risk_level: GuardianRiskLevel::High,
|
risk_level: GuardianRiskLevel::High,
|
||||||
risk_score: 100,
|
risk_score: 100,
|
||||||
rationale: format!("Guardian review failed: {err}"),
|
rationale: format!("Automatic approval review failed: {err}"),
|
||||||
evidence: vec![],
|
evidence: vec![],
|
||||||
},
|
},
|
||||||
None => GuardianAssessment {
|
None => GuardianAssessment {
|
||||||
risk_level: GuardianRiskLevel::High,
|
risk_level: GuardianRiskLevel::High,
|
||||||
risk_score: 100,
|
risk_score: 100,
|
||||||
rationale: "Guardian review timed out while evaluating the requested approval."
|
rationale:
|
||||||
.to_string(),
|
"Automatic approval review timed out while evaluating the requested approval."
|
||||||
|
.to_string(),
|
||||||
evidence: vec![],
|
evidence: vec![],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -216,8 +272,7 @@ async fn run_guardian_review(
|
|||||||
// Emit a concise warning so the parent turn has an auditable summary of the
|
// Emit a concise warning so the parent turn has an auditable summary of the
|
||||||
// guardian decision without needing the full subagent transcript.
|
// guardian decision without needing the full subagent transcript.
|
||||||
let warning = format!(
|
let warning = format!(
|
||||||
"Guardian {verdict} approval request ({}/100, {}): {}",
|
"Automatic approval review {verdict} (risk: {}): {}",
|
||||||
assessment.risk_score,
|
|
||||||
assessment.risk_level.as_str(),
|
assessment.risk_level.as_str(),
|
||||||
assessment.rationale
|
assessment.rationale
|
||||||
);
|
);
|
||||||
@@ -239,7 +294,7 @@ async fn run_guardian_review(
|
|||||||
pub(crate) async fn review_approval_request(
|
pub(crate) async fn review_approval_request(
|
||||||
session: &Arc<Session>,
|
session: &Arc<Session>,
|
||||||
turn: &Arc<TurnContext>,
|
turn: &Arc<TurnContext>,
|
||||||
request: GuardianReviewRequest,
|
request: GuardianApprovalRequest,
|
||||||
retry_reason: Option<String>,
|
retry_reason: Option<String>,
|
||||||
) -> ReviewDecision {
|
) -> ReviewDecision {
|
||||||
run_guardian_review(Arc::clone(session), Arc::clone(turn), request, retry_reason).await
|
run_guardian_review(Arc::clone(session), Arc::clone(turn), request, retry_reason).await
|
||||||
@@ -256,11 +311,11 @@ pub(crate) async fn review_approval_request(
|
|||||||
async fn build_guardian_prompt_items(
|
async fn build_guardian_prompt_items(
|
||||||
session: &Session,
|
session: &Session,
|
||||||
retry_reason: Option<String>,
|
retry_reason: Option<String>,
|
||||||
request: GuardianReviewRequest,
|
request: GuardianApprovalRequest,
|
||||||
) -> Vec<UserInput> {
|
) -> Vec<UserInput> {
|
||||||
let history = session.clone_history().await;
|
let history = session.clone_history().await;
|
||||||
let transcript_entries = collect_guardian_transcript_entries(history.raw_items());
|
let transcript_entries = collect_guardian_transcript_entries(history.raw_items());
|
||||||
let planned_action_json = format_guardian_action_pretty(&request.action);
|
let planned_action_json = format_guardian_action_pretty(&request);
|
||||||
|
|
||||||
let (transcript_entries, omission_note) =
|
let (transcript_entries, omission_note) =
|
||||||
render_guardian_transcript_entries(transcript_entries.as_slice());
|
render_guardian_transcript_entries(transcript_entries.as_slice());
|
||||||
@@ -400,6 +455,13 @@ fn render_guardian_transcript_entries(
|
|||||||
fn collect_guardian_transcript_entries(items: &[ResponseItem]) -> Vec<GuardianTranscriptEntry> {
|
fn collect_guardian_transcript_entries(items: &[ResponseItem]) -> Vec<GuardianTranscriptEntry> {
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
let mut tool_names_by_call_id = HashMap::new();
|
let mut tool_names_by_call_id = HashMap::new();
|
||||||
|
let non_empty_entry = |kind, text: String| {
|
||||||
|
(!text.trim().is_empty()).then_some(GuardianTranscriptEntry { kind, text })
|
||||||
|
};
|
||||||
|
let content_entry =
|
||||||
|
|kind, content| content_items_to_text(content).and_then(|text| non_empty_entry(kind, text));
|
||||||
|
let serialized_entry =
|
||||||
|
|kind, serialized: Option<String>| serialized.and_then(|text| non_empty_entry(kind, text));
|
||||||
|
|
||||||
for item in items {
|
for item in items {
|
||||||
let entry = match item {
|
let entry = match item {
|
||||||
@@ -407,25 +469,16 @@ fn collect_guardian_transcript_entries(items: &[ResponseItem]) -> Vec<GuardianTr
|
|||||||
if is_contextual_user_message_content(content) {
|
if is_contextual_user_message_content(content) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
content_items_to_text(content).map(|text| GuardianTranscriptEntry {
|
content_entry(GuardianTranscriptEntryKind::User, content)
|
||||||
kind: GuardianTranscriptEntryKind::User,
|
|
||||||
text,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ResponseItem::Message { role, content, .. } if role == "assistant" => {
|
ResponseItem::Message { role, content, .. } if role == "assistant" => {
|
||||||
content_items_to_text(content).map(|text| GuardianTranscriptEntry {
|
content_entry(GuardianTranscriptEntryKind::Assistant, content)
|
||||||
kind: GuardianTranscriptEntryKind::Assistant,
|
|
||||||
text,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
ResponseItem::LocalShellCall { action, .. } => serde_json::to_string(action)
|
ResponseItem::LocalShellCall { action, .. } => serialized_entry(
|
||||||
.ok()
|
GuardianTranscriptEntryKind::Tool("tool shell call".to_string()),
|
||||||
.filter(|text| !text.trim().is_empty())
|
serde_json::to_string(action).ok(),
|
||||||
.map(|text| GuardianTranscriptEntry {
|
),
|
||||||
kind: GuardianTranscriptEntryKind::Tool("tool shell call".to_string()),
|
|
||||||
text,
|
|
||||||
}),
|
|
||||||
ResponseItem::FunctionCall {
|
ResponseItem::FunctionCall {
|
||||||
call_id,
|
call_id,
|
||||||
name,
|
name,
|
||||||
@@ -450,28 +503,26 @@ fn collect_guardian_transcript_entries(items: &[ResponseItem]) -> Vec<GuardianTr
|
|||||||
text: input.clone(),
|
text: input.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ResponseItem::WebSearchCall { action, .. } => action
|
ResponseItem::WebSearchCall { action, .. } => action.as_ref().and_then(|action| {
|
||||||
.as_ref()
|
serialized_entry(
|
||||||
.and_then(|action| serde_json::to_string(action).ok())
|
GuardianTranscriptEntryKind::Tool("tool web_search call".to_string()),
|
||||||
.filter(|text| !text.trim().is_empty())
|
serde_json::to_string(action).ok(),
|
||||||
.map(|text| GuardianTranscriptEntry {
|
)
|
||||||
kind: GuardianTranscriptEntryKind::Tool("tool web_search call".to_string()),
|
}),
|
||||||
text,
|
|
||||||
}),
|
|
||||||
ResponseItem::FunctionCallOutput { call_id, output }
|
ResponseItem::FunctionCallOutput { call_id, output }
|
||||||
| ResponseItem::CustomToolCallOutput { call_id, output } => output
|
| ResponseItem::CustomToolCallOutput { call_id, output } => {
|
||||||
.body
|
output.body.to_text().and_then(|text| {
|
||||||
.to_text()
|
non_empty_entry(
|
||||||
.filter(|text| !text.trim().is_empty())
|
GuardianTranscriptEntryKind::Tool(
|
||||||
.map(|text| GuardianTranscriptEntry {
|
tool_names_by_call_id.get(call_id).map_or_else(
|
||||||
kind: GuardianTranscriptEntryKind::Tool(
|
|| "tool result".to_string(),
|
||||||
tool_names_by_call_id.get(call_id).map_or_else(
|
|name| format!("tool {name} result"),
|
||||||
|| "tool result".to_string(),
|
),
|
||||||
|name| format!("tool {name} result"),
|
|
||||||
),
|
),
|
||||||
),
|
text,
|
||||||
text,
|
)
|
||||||
}),
|
})
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -506,6 +557,13 @@ async fn run_guardian_subagent(
|
|||||||
.models_manager
|
.models_manager
|
||||||
.list_models(crate::models_manager::manager::RefreshStrategy::Offline)
|
.list_models(crate::models_manager::manager::RefreshStrategy::Offline)
|
||||||
.await;
|
.await;
|
||||||
|
let preferred_reasoning_effort = |supports_low: bool, fallback| {
|
||||||
|
if supports_low {
|
||||||
|
Some(codex_protocol::openai_models::ReasoningEffort::Low)
|
||||||
|
} else {
|
||||||
|
fallback
|
||||||
|
}
|
||||||
|
};
|
||||||
// Prefer `GUARDIAN_PREFERRED_MODEL` when the active provider exposes it,
|
// Prefer `GUARDIAN_PREFERRED_MODEL` when the active provider exposes it,
|
||||||
// but fall back to the parent turn's active model so guardian does not
|
// but fall back to the parent turn's active model so guardian does not
|
||||||
// become a blanket deny on providers or test environments that do not
|
// become a blanket deny on providers or test environments that do not
|
||||||
@@ -514,28 +572,23 @@ async fn run_guardian_subagent(
|
|||||||
.iter()
|
.iter()
|
||||||
.find(|preset| preset.model == GUARDIAN_PREFERRED_MODEL);
|
.find(|preset| preset.model == GUARDIAN_PREFERRED_MODEL);
|
||||||
let (guardian_model, guardian_reasoning_effort) = if let Some(preset) = preferred_model {
|
let (guardian_model, guardian_reasoning_effort) = if let Some(preset) = preferred_model {
|
||||||
let reasoning_effort = if preset
|
let reasoning_effort = preferred_reasoning_effort(
|
||||||
.supported_reasoning_efforts
|
preset
|
||||||
.iter()
|
.supported_reasoning_efforts
|
||||||
.any(|effort| effort.effort == codex_protocol::openai_models::ReasoningEffort::Low)
|
.iter()
|
||||||
{
|
.any(|effort| effort.effort == codex_protocol::openai_models::ReasoningEffort::Low),
|
||||||
Some(codex_protocol::openai_models::ReasoningEffort::Low)
|
Some(preset.default_reasoning_effort),
|
||||||
} else {
|
);
|
||||||
Some(preset.default_reasoning_effort)
|
|
||||||
};
|
|
||||||
(GUARDIAN_PREFERRED_MODEL.to_string(), reasoning_effort)
|
(GUARDIAN_PREFERRED_MODEL.to_string(), reasoning_effort)
|
||||||
} else {
|
} else {
|
||||||
let reasoning_effort = if turn
|
let reasoning_effort = preferred_reasoning_effort(
|
||||||
.model_info
|
turn.model_info
|
||||||
.supported_reasoning_levels
|
.supported_reasoning_levels
|
||||||
.iter()
|
.iter()
|
||||||
.any(|preset| preset.effort == codex_protocol::openai_models::ReasoningEffort::Low)
|
.any(|preset| preset.effort == codex_protocol::openai_models::ReasoningEffort::Low),
|
||||||
{
|
|
||||||
Some(codex_protocol::openai_models::ReasoningEffort::Low)
|
|
||||||
} else {
|
|
||||||
turn.reasoning_effort
|
turn.reasoning_effort
|
||||||
.or(turn.model_info.default_reasoning_level)
|
.or(turn.model_info.default_reasoning_level),
|
||||||
};
|
);
|
||||||
(turn.model_info.slug.clone(), reasoning_effort)
|
(turn.model_info.slug.clone(), reasoning_effort)
|
||||||
};
|
};
|
||||||
let guardian_config = build_guardian_subagent_config(
|
let guardian_config = build_guardian_subagent_config(
|
||||||
@@ -678,9 +731,149 @@ fn truncate_guardian_action_value(value: Value) -> Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_guardian_action_pretty(action: &Value) -> String {
|
fn format_guardian_action_pretty(action: &GuardianApprovalRequest) -> String {
|
||||||
serde_json::to_string_pretty(&truncate_guardian_action_value(action.clone()))
|
let mut value = match action {
|
||||||
.unwrap_or_else(|_| "null".to_string())
|
GuardianApprovalRequest::Shell {
|
||||||
|
command,
|
||||||
|
cwd,
|
||||||
|
sandbox_permissions,
|
||||||
|
additional_permissions,
|
||||||
|
justification,
|
||||||
|
} => {
|
||||||
|
let mut action = serde_json::json!({
|
||||||
|
"tool": "shell",
|
||||||
|
"command": command,
|
||||||
|
"cwd": cwd,
|
||||||
|
"sandbox_permissions": sandbox_permissions,
|
||||||
|
"additional_permissions": additional_permissions,
|
||||||
|
"justification": justification,
|
||||||
|
});
|
||||||
|
if let Some(action) = action.as_object_mut() {
|
||||||
|
if additional_permissions.is_none() {
|
||||||
|
action.remove("additional_permissions");
|
||||||
|
}
|
||||||
|
if justification.is_none() {
|
||||||
|
action.remove("justification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action
|
||||||
|
}
|
||||||
|
GuardianApprovalRequest::ExecCommand {
|
||||||
|
command,
|
||||||
|
cwd,
|
||||||
|
sandbox_permissions,
|
||||||
|
additional_permissions,
|
||||||
|
justification,
|
||||||
|
tty,
|
||||||
|
} => {
|
||||||
|
let mut action = serde_json::json!({
|
||||||
|
"tool": "exec_command",
|
||||||
|
"command": command,
|
||||||
|
"cwd": cwd,
|
||||||
|
"sandbox_permissions": sandbox_permissions,
|
||||||
|
"additional_permissions": additional_permissions,
|
||||||
|
"justification": justification,
|
||||||
|
"tty": tty,
|
||||||
|
});
|
||||||
|
if let Some(action) = action.as_object_mut() {
|
||||||
|
if additional_permissions.is_none() {
|
||||||
|
action.remove("additional_permissions");
|
||||||
|
}
|
||||||
|
if justification.is_none() {
|
||||||
|
action.remove("justification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
GuardianApprovalRequest::Execve {
|
||||||
|
tool_name,
|
||||||
|
program,
|
||||||
|
argv,
|
||||||
|
cwd,
|
||||||
|
additional_permissions,
|
||||||
|
} => {
|
||||||
|
let mut action = serde_json::json!({
|
||||||
|
"tool": tool_name,
|
||||||
|
"program": program,
|
||||||
|
"argv": argv,
|
||||||
|
"cwd": cwd,
|
||||||
|
"additional_permissions": additional_permissions,
|
||||||
|
});
|
||||||
|
if let Some(action) = action.as_object_mut()
|
||||||
|
&& additional_permissions.is_none()
|
||||||
|
{
|
||||||
|
action.remove("additional_permissions");
|
||||||
|
}
|
||||||
|
action
|
||||||
|
}
|
||||||
|
GuardianApprovalRequest::ApplyPatch {
|
||||||
|
cwd,
|
||||||
|
files,
|
||||||
|
change_count,
|
||||||
|
patch,
|
||||||
|
} => serde_json::json!({
|
||||||
|
"tool": "apply_patch",
|
||||||
|
"cwd": cwd,
|
||||||
|
"files": files,
|
||||||
|
"change_count": change_count,
|
||||||
|
"patch": patch,
|
||||||
|
}),
|
||||||
|
GuardianApprovalRequest::NetworkAccess {
|
||||||
|
target,
|
||||||
|
host,
|
||||||
|
protocol,
|
||||||
|
port,
|
||||||
|
} => serde_json::json!({
|
||||||
|
"tool": "network_access",
|
||||||
|
"target": target,
|
||||||
|
"host": host,
|
||||||
|
"protocol": protocol,
|
||||||
|
"port": port,
|
||||||
|
}),
|
||||||
|
GuardianApprovalRequest::McpToolCall {
|
||||||
|
server,
|
||||||
|
tool_name,
|
||||||
|
arguments,
|
||||||
|
connector_id,
|
||||||
|
connector_name,
|
||||||
|
connector_description,
|
||||||
|
tool_title,
|
||||||
|
tool_description,
|
||||||
|
annotations,
|
||||||
|
} => {
|
||||||
|
let mut action = serde_json::json!({
|
||||||
|
"tool": "mcp_tool_call",
|
||||||
|
"server": server,
|
||||||
|
"tool_name": tool_name,
|
||||||
|
"arguments": arguments,
|
||||||
|
"connector_id": connector_id,
|
||||||
|
"connector_name": connector_name,
|
||||||
|
"connector_description": connector_description,
|
||||||
|
"tool_title": tool_title,
|
||||||
|
"tool_description": tool_description,
|
||||||
|
"annotations": annotations,
|
||||||
|
});
|
||||||
|
if let Some(action) = action.as_object_mut() {
|
||||||
|
for key in [
|
||||||
|
("arguments", arguments.is_none()),
|
||||||
|
("connector_id", connector_id.is_none()),
|
||||||
|
("connector_name", connector_name.is_none()),
|
||||||
|
("connector_description", connector_description.is_none()),
|
||||||
|
("tool_title", tool_title.is_none()),
|
||||||
|
("tool_description", tool_description.is_none()),
|
||||||
|
("annotations", annotations.is_none()),
|
||||||
|
] {
|
||||||
|
if key.1 {
|
||||||
|
action.remove(key.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
action
|
||||||
|
}
|
||||||
|
};
|
||||||
|
value = truncate_guardian_action_value(value);
|
||||||
|
serde_json::to_string_pretty(&value).unwrap_or_else(|_| "null".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn guardian_truncate_text(content: &str, token_cap: usize) -> String {
|
fn guardian_truncate_text(content: &str, token_cap: usize) -> String {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::config_loader::FeatureRequirementsToml;
|
|||||||
use crate::config_loader::NetworkConstraints;
|
use crate::config_loader::NetworkConstraints;
|
||||||
use crate::config_loader::RequirementSource;
|
use crate::config_loader::RequirementSource;
|
||||||
use crate::config_loader::Sourced;
|
use crate::config_loader::Sourced;
|
||||||
|
use crate::test_support;
|
||||||
use codex_network_proxy::NetworkProxyConfig;
|
use codex_network_proxy::NetworkProxyConfig;
|
||||||
use codex_protocol::models::ContentItem;
|
use codex_protocol::models::ContentItem;
|
||||||
use core_test_support::context_snapshot;
|
use core_test_support::context_snapshot;
|
||||||
@@ -22,6 +23,8 @@ use insta::assert_snapshot;
|
|||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_guardian_transcript_keeps_original_numbering() {
|
fn build_guardian_transcript_keeps_original_numbering() {
|
||||||
@@ -154,21 +157,18 @@ fn guardian_truncate_text_keeps_prefix_suffix_and_xml_marker() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn format_guardian_action_pretty_truncates_large_string_fields() {
|
fn format_guardian_action_pretty_truncates_large_string_fields() {
|
||||||
let action = serde_json::json!({
|
let patch = "line\n".repeat(10_000);
|
||||||
"tool": "apply_patch",
|
let action = GuardianApprovalRequest::ApplyPatch {
|
||||||
"cwd": PathBuf::from("/tmp"),
|
cwd: PathBuf::from("/tmp"),
|
||||||
"files": Vec::<String>::new(),
|
files: Vec::new(),
|
||||||
"change_count": 1usize,
|
change_count: 1usize,
|
||||||
"patch": "line\n".repeat(10_000),
|
patch: patch.clone(),
|
||||||
});
|
};
|
||||||
|
|
||||||
let rendered = format_guardian_action_pretty(&action);
|
let rendered = format_guardian_action_pretty(&action);
|
||||||
let original_patch = action["patch"]
|
|
||||||
.as_str()
|
|
||||||
.expect("test patch should serialize as a string");
|
|
||||||
|
|
||||||
assert!(rendered.contains("\"tool\": \"apply_patch\""));
|
assert!(rendered.contains("\"tool\": \"apply_patch\""));
|
||||||
assert!(rendered.len() < original_patch.len());
|
assert!(rendered.len() < patch.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -253,7 +253,7 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
|
|||||||
let mut config = (*turn.config).clone();
|
let mut config = (*turn.config).clone();
|
||||||
config.model_provider.base_url = Some(format!("{}/v1", server.uri()));
|
config.model_provider.base_url = Some(format!("{}/v1", server.uri()));
|
||||||
let config = Arc::new(config);
|
let config = Arc::new(config);
|
||||||
let models_manager = Arc::new(crate::test_support::models_manager_with_provider(
|
let models_manager = Arc::new(test_support::models_manager_with_provider(
|
||||||
config.codex_home.clone(),
|
config.codex_home.clone(),
|
||||||
Arc::clone(&session.services.auth_manager),
|
Arc::clone(&session.services.auth_manager),
|
||||||
config.model_provider.clone(),
|
config.model_provider.clone(),
|
||||||
@@ -307,19 +307,19 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
|
|||||||
let prompt = build_guardian_prompt_items(
|
let prompt = build_guardian_prompt_items(
|
||||||
session.as_ref(),
|
session.as_ref(),
|
||||||
Some("Sandbox denied outbound git push to github.com.".to_string()),
|
Some("Sandbox denied outbound git push to github.com.".to_string()),
|
||||||
GuardianReviewRequest {
|
GuardianApprovalRequest::Shell {
|
||||||
action: serde_json::json!({
|
command: vec![
|
||||||
"tool": "shell",
|
"git".to_string(),
|
||||||
"command": [
|
"push".to_string(),
|
||||||
"git",
|
"origin".to_string(),
|
||||||
"push",
|
"guardian-approval-mvp".to_string(),
|
||||||
"origin",
|
],
|
||||||
"guardian-approval-mvp"
|
cwd: PathBuf::from("/repo/codex-rs/core"),
|
||||||
],
|
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
|
||||||
"cwd": "/repo/codex-rs/core",
|
additional_permissions: None,
|
||||||
"sandbox_permissions": crate::sandboxing::SandboxPermissions::UseDefault,
|
justification: Some(
|
||||||
"justification": "Need to push the reviewed docs fix to the repo remote.",
|
"Need to push the reviewed docs fix to the repo remote.".to_string(),
|
||||||
}),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ use crate::config::edit::ConfigEditsBuilder;
|
|||||||
use crate::config::types::AppToolApproval;
|
use crate::config::types::AppToolApproval;
|
||||||
use crate::connectors;
|
use crate::connectors;
|
||||||
use crate::features::Feature;
|
use crate::features::Feature;
|
||||||
use crate::guardian::GuardianReviewRequest;
|
use crate::guardian::GuardianApprovalRequest;
|
||||||
|
use crate::guardian::GuardianMcpAnnotations;
|
||||||
use crate::guardian::review_approval_request;
|
use crate::guardian::review_approval_request;
|
||||||
use crate::guardian::routes_approval_to_guardian;
|
use crate::guardian::routes_approval_to_guardian;
|
||||||
use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;
|
use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||||
@@ -574,88 +575,23 @@ fn persistent_mcp_tool_approval_key(
|
|||||||
fn build_guardian_mcp_tool_review_request(
|
fn build_guardian_mcp_tool_review_request(
|
||||||
invocation: &McpInvocation,
|
invocation: &McpInvocation,
|
||||||
metadata: Option<&McpToolApprovalMetadata>,
|
metadata: Option<&McpToolApprovalMetadata>,
|
||||||
) -> GuardianReviewRequest {
|
) -> GuardianApprovalRequest {
|
||||||
let mut action = serde_json::Map::from_iter([
|
GuardianApprovalRequest::McpToolCall {
|
||||||
(
|
server: invocation.server.clone(),
|
||||||
"tool".to_string(),
|
tool_name: invocation.tool.clone(),
|
||||||
serde_json::Value::String("mcp_tool_call".to_string()),
|
arguments: invocation.arguments.clone(),
|
||||||
),
|
connector_id: metadata.and_then(|metadata| metadata.connector_id.clone()),
|
||||||
(
|
connector_name: metadata.and_then(|metadata| metadata.connector_name.clone()),
|
||||||
"server".to_string(),
|
connector_description: metadata.and_then(|metadata| metadata.connector_description.clone()),
|
||||||
serde_json::Value::String(invocation.server.clone()),
|
tool_title: metadata.and_then(|metadata| metadata.tool_title.clone()),
|
||||||
),
|
tool_description: metadata.and_then(|metadata| metadata.tool_description.clone()),
|
||||||
(
|
annotations: metadata
|
||||||
"tool_name".to_string(),
|
.and_then(|metadata| metadata.annotations.as_ref())
|
||||||
serde_json::Value::String(invocation.tool.clone()),
|
.map(|annotations| GuardianMcpAnnotations {
|
||||||
),
|
destructive_hint: annotations.destructive_hint,
|
||||||
]);
|
open_world_hint: annotations.open_world_hint,
|
||||||
|
read_only_hint: annotations.read_only_hint,
|
||||||
if let Some(arguments) = invocation.arguments.clone() {
|
}),
|
||||||
action.insert("arguments".to_string(), arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(metadata) = metadata {
|
|
||||||
if let Some(connector_id) = metadata.connector_id.as_ref() {
|
|
||||||
action.insert(
|
|
||||||
"connector_id".to_string(),
|
|
||||||
serde_json::Value::String(connector_id.clone()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(connector_name) = metadata.connector_name.as_ref() {
|
|
||||||
action.insert(
|
|
||||||
"connector_name".to_string(),
|
|
||||||
serde_json::Value::String(connector_name.clone()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(connector_description) = metadata.connector_description.as_ref() {
|
|
||||||
action.insert(
|
|
||||||
"connector_description".to_string(),
|
|
||||||
serde_json::Value::String(connector_description.clone()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(tool_title) = metadata.tool_title.as_ref() {
|
|
||||||
action.insert(
|
|
||||||
"tool_title".to_string(),
|
|
||||||
serde_json::Value::String(tool_title.clone()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(tool_description) = metadata.tool_description.as_ref() {
|
|
||||||
action.insert(
|
|
||||||
"tool_description".to_string(),
|
|
||||||
serde_json::Value::String(tool_description.clone()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(annotations) = metadata.annotations.as_ref() {
|
|
||||||
let mut annotation_map = serde_json::Map::new();
|
|
||||||
if let Some(destructive_hint) = annotations.destructive_hint {
|
|
||||||
annotation_map.insert(
|
|
||||||
"destructive_hint".to_string(),
|
|
||||||
serde_json::Value::Bool(destructive_hint),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(open_world_hint) = annotations.open_world_hint {
|
|
||||||
annotation_map.insert(
|
|
||||||
"open_world_hint".to_string(),
|
|
||||||
serde_json::Value::Bool(open_world_hint),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(read_only_hint) = annotations.read_only_hint {
|
|
||||||
annotation_map.insert(
|
|
||||||
"read_only_hint".to_string(),
|
|
||||||
serde_json::Value::Bool(read_only_hint),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if !annotation_map.is_empty() {
|
|
||||||
action.insert(
|
|
||||||
"annotations".to_string(),
|
|
||||||
serde_json::Value::Object(annotation_map),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GuardianReviewRequest {
|
|
||||||
action: serde_json::Value::Object(action),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1599,20 +1535,18 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
request,
|
request,
|
||||||
GuardianReviewRequest {
|
GuardianApprovalRequest::McpToolCall {
|
||||||
action: serde_json::json!({
|
server: CODEX_APPS_MCP_SERVER_NAME.to_string(),
|
||||||
"tool": "mcp_tool_call",
|
tool_name: "browser_navigate".to_string(),
|
||||||
"server": CODEX_APPS_MCP_SERVER_NAME,
|
arguments: Some(serde_json::json!({
|
||||||
"tool_name": "browser_navigate",
|
"url": "https://example.com",
|
||||||
"arguments": {
|
})),
|
||||||
"url": "https://example.com",
|
connector_id: Some("playwright".to_string()),
|
||||||
},
|
connector_name: Some("Playwright".to_string()),
|
||||||
"connector_id": "playwright",
|
connector_description: Some("Browser automation".to_string()),
|
||||||
"connector_name": "Playwright",
|
tool_title: Some("Navigate".to_string()),
|
||||||
"connector_description": "Browser automation",
|
tool_description: Some("Open a page".to_string()),
|
||||||
"tool_title": "Navigate",
|
annotations: None,
|
||||||
"tool_description": "Open a page",
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1637,16 +1571,19 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
request,
|
request,
|
||||||
GuardianReviewRequest {
|
GuardianApprovalRequest::McpToolCall {
|
||||||
action: serde_json::json!({
|
server: "custom_server".to_string(),
|
||||||
"tool": "mcp_tool_call",
|
tool_name: "dangerous_tool".to_string(),
|
||||||
"server": "custom_server",
|
arguments: None,
|
||||||
"tool_name": "dangerous_tool",
|
connector_id: None,
|
||||||
"annotations": {
|
connector_name: None,
|
||||||
"destructive_hint": true,
|
connector_description: None,
|
||||||
"open_world_hint": true,
|
tool_title: None,
|
||||||
"read_only_hint": false,
|
tool_description: None,
|
||||||
},
|
annotations: Some(GuardianMcpAnnotations {
|
||||||
|
destructive_hint: Some(true),
|
||||||
|
open_world_hint: Some(true),
|
||||||
|
read_only_hint: Some(false),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::codex::Session;
|
use crate::codex::Session;
|
||||||
use crate::guardian::GUARDIAN_REJECTION_MESSAGE;
|
use crate::guardian::GUARDIAN_REJECTION_MESSAGE;
|
||||||
use crate::guardian::GuardianReviewRequest;
|
use crate::guardian::GuardianApprovalRequest;
|
||||||
use crate::guardian::review_approval_request;
|
use crate::guardian::review_approval_request;
|
||||||
use crate::guardian::routes_approval_to_guardian;
|
use crate::guardian::routes_approval_to_guardian;
|
||||||
use crate::network_policy_decision::denied_network_policy_message;
|
use crate::network_policy_decision::denied_network_policy_message;
|
||||||
@@ -21,7 +21,6 @@ use codex_protocol::protocol::EventMsg;
|
|||||||
use codex_protocol::protocol::ReviewDecision;
|
use codex_protocol::protocol::ReviewDecision;
|
||||||
use codex_protocol::protocol::WarningEvent;
|
use codex_protocol::protocol::WarningEvent;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use serde_json::json;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -344,14 +343,11 @@ impl NetworkApprovalService {
|
|||||||
review_approval_request(
|
review_approval_request(
|
||||||
&session,
|
&session,
|
||||||
&turn_context,
|
&turn_context,
|
||||||
GuardianReviewRequest {
|
GuardianApprovalRequest::NetworkAccess {
|
||||||
action: json!({
|
target,
|
||||||
"tool": "network_access",
|
host: request.host,
|
||||||
"target": target,
|
protocol,
|
||||||
"host": request.host,
|
port: key.port,
|
||||||
"protocol": key.protocol,
|
|
||||||
"port": key.port,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
Some(policy_denial_message.clone()),
|
Some(policy_denial_message.clone()),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
//! `codex --codex-run-as-apply-patch`, and runs under the current
|
//! `codex --codex-run-as-apply-patch`, and runs under the current
|
||||||
//! `SandboxAttempt` with a minimal environment.
|
//! `SandboxAttempt` with a minimal environment.
|
||||||
use crate::exec::ExecToolCallOutput;
|
use crate::exec::ExecToolCallOutput;
|
||||||
use crate::guardian::GuardianReviewRequest;
|
use crate::guardian::GuardianApprovalRequest;
|
||||||
use crate::guardian::review_approval_request;
|
use crate::guardian::review_approval_request;
|
||||||
use crate::guardian::routes_approval_to_guardian;
|
use crate::guardian::routes_approval_to_guardian;
|
||||||
use crate::sandboxing::CommandSpec;
|
use crate::sandboxing::CommandSpec;
|
||||||
@@ -28,7 +28,6 @@ use codex_protocol::protocol::FileChange;
|
|||||||
use codex_protocol::protocol::ReviewDecision;
|
use codex_protocol::protocol::ReviewDecision;
|
||||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use serde_json::json;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
@@ -50,15 +49,12 @@ impl ApplyPatchRuntime {
|
|||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_guardian_review_request(req: &ApplyPatchRequest) -> GuardianReviewRequest {
|
fn build_guardian_review_request(req: &ApplyPatchRequest) -> GuardianApprovalRequest {
|
||||||
GuardianReviewRequest {
|
GuardianApprovalRequest::ApplyPatch {
|
||||||
action: json!({
|
cwd: req.action.cwd.clone(),
|
||||||
"tool": "apply_patch",
|
files: req.file_paths.clone(),
|
||||||
"cwd": req.action.cwd,
|
change_count: req.changes.len(),
|
||||||
"files": req.file_paths,
|
patch: req.action.patch.clone(),
|
||||||
"change_count": req.changes.len(),
|
|
||||||
"patch": req.action.patch,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,8 +131,8 @@ impl Approvable<ApplyPatchRequest> for ApplyPatchRuntime {
|
|||||||
let changes = req.changes.clone();
|
let changes = req.changes.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
if routes_approval_to_guardian(turn) {
|
if routes_approval_to_guardian(turn) {
|
||||||
let request = ApplyPatchRuntime::build_guardian_review_request(req);
|
let action = ApplyPatchRuntime::build_guardian_review_request(req);
|
||||||
return review_approval_request(session, turn, request, retry_reason).await;
|
return review_approval_request(session, turn, action, retry_reason).await;
|
||||||
}
|
}
|
||||||
if let Some(reason) = retry_reason {
|
if let Some(reason) = retry_reason {
|
||||||
let rx_approve = session
|
let rx_approve = session
|
||||||
@@ -256,14 +252,11 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
guardian_request,
|
guardian_request,
|
||||||
GuardianReviewRequest {
|
GuardianApprovalRequest::ApplyPatch {
|
||||||
action: json!({
|
cwd: expected_cwd,
|
||||||
"tool": "apply_patch",
|
files: request.file_paths,
|
||||||
"cwd": expected_cwd,
|
change_count: 1usize,
|
||||||
"files": request.file_paths,
|
patch: expected_patch,
|
||||||
"change_count": 1usize,
|
|
||||||
"patch": expected_patch,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ pub(crate) mod zsh_fork_backend;
|
|||||||
use crate::command_canonicalization::canonicalize_command_for_approval;
|
use crate::command_canonicalization::canonicalize_command_for_approval;
|
||||||
use crate::exec::ExecToolCallOutput;
|
use crate::exec::ExecToolCallOutput;
|
||||||
use crate::features::Feature;
|
use crate::features::Feature;
|
||||||
use crate::guardian::GuardianReviewRequest;
|
use crate::guardian::GuardianApprovalRequest;
|
||||||
use crate::guardian::review_approval_request;
|
use crate::guardian::review_approval_request;
|
||||||
use crate::guardian::routes_approval_to_guardian;
|
use crate::guardian::routes_approval_to_guardian;
|
||||||
use crate::powershell::prefix_powershell_script_with_utf8;
|
use crate::powershell::prefix_powershell_script_with_utf8;
|
||||||
@@ -38,7 +38,6 @@ use codex_network_proxy::NetworkProxy;
|
|||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::protocol::ReviewDecision;
|
use codex_protocol::protocol::ReviewDecision;
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use serde_json::json;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
@@ -145,33 +144,26 @@ impl Approvable<ShellRequest> for ShellRuntime {
|
|||||||
let keys = self.approval_keys(req);
|
let keys = self.approval_keys(req);
|
||||||
let command = req.command.clone();
|
let command = req.command.clone();
|
||||||
let cwd = req.cwd.clone();
|
let cwd = req.cwd.clone();
|
||||||
let reason = ctx
|
let retry_reason = ctx.retry_reason.clone();
|
||||||
.retry_reason
|
let reason = retry_reason.clone().or_else(|| req.justification.clone());
|
||||||
.clone()
|
|
||||||
.or_else(|| req.justification.clone());
|
|
||||||
let session = ctx.session;
|
let session = ctx.session;
|
||||||
let turn = ctx.turn;
|
let turn = ctx.turn;
|
||||||
let call_id = ctx.call_id.to_string();
|
let call_id = ctx.call_id.to_string();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
if routes_approval_to_guardian(turn) {
|
if routes_approval_to_guardian(turn) {
|
||||||
let mut action = json!({
|
return review_approval_request(
|
||||||
"tool": "shell",
|
session,
|
||||||
"command": command,
|
turn,
|
||||||
"cwd": cwd,
|
GuardianApprovalRequest::Shell {
|
||||||
"sandbox_permissions": req.sandbox_permissions,
|
command,
|
||||||
"additional_permissions": req.additional_permissions,
|
cwd,
|
||||||
"justification": reason,
|
sandbox_permissions: req.sandbox_permissions,
|
||||||
});
|
additional_permissions: req.additional_permissions.clone(),
|
||||||
if let Some(action) = action.as_object_mut() {
|
justification: req.justification.clone(),
|
||||||
if req.additional_permissions.is_none() {
|
},
|
||||||
action.remove("additional_permissions");
|
retry_reason,
|
||||||
}
|
)
|
||||||
if reason.is_none() {
|
.await;
|
||||||
action.remove("justification");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let request = GuardianReviewRequest { action };
|
|
||||||
return review_approval_request(session, turn, request, None).await;
|
|
||||||
}
|
}
|
||||||
with_cached_approval(&session.services, "shell", keys, move || async move {
|
with_cached_approval(&session.services, "shell", keys, move || async move {
|
||||||
let available_decisions = None;
|
let available_decisions = None;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::exec::SandboxType;
|
|||||||
use crate::exec::is_likely_sandbox_denied;
|
use crate::exec::is_likely_sandbox_denied;
|
||||||
use crate::exec_policy::prompt_is_rejected_by_policy;
|
use crate::exec_policy::prompt_is_rejected_by_policy;
|
||||||
use crate::features::Feature;
|
use crate::features::Feature;
|
||||||
use crate::guardian::GuardianReviewRequest;
|
use crate::guardian::GuardianApprovalRequest;
|
||||||
use crate::guardian::review_approval_request;
|
use crate::guardian::review_approval_request;
|
||||||
use crate::guardian::routes_approval_to_guardian;
|
use crate::guardian::routes_approval_to_guardian;
|
||||||
use crate::sandboxing::ExecRequest;
|
use crate::sandboxing::ExecRequest;
|
||||||
@@ -50,7 +50,6 @@ use codex_shell_escalation::PreparedExec;
|
|||||||
use codex_shell_escalation::ShellCommandExecutor;
|
use codex_shell_escalation::ShellCommandExecutor;
|
||||||
use codex_shell_escalation::Stopwatch;
|
use codex_shell_escalation::Stopwatch;
|
||||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||||
use serde_json::json;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -386,16 +385,19 @@ impl CoreShellActionProvider {
|
|||||||
Ok(stopwatch
|
Ok(stopwatch
|
||||||
.pause_for(async move {
|
.pause_for(async move {
|
||||||
if routes_approval_to_guardian(&turn) {
|
if routes_approval_to_guardian(&turn) {
|
||||||
let request = GuardianReviewRequest {
|
return review_approval_request(
|
||||||
action: json!({
|
&session,
|
||||||
"tool": tool_name,
|
&turn,
|
||||||
"program": program,
|
GuardianApprovalRequest::Execve {
|
||||||
"argv": argv,
|
tool_name: tool_name.to_string(),
|
||||||
"cwd": workdir,
|
program: program.to_string_lossy().into_owned(),
|
||||||
"additional_permissions": additional_permissions,
|
argv: argv.to_vec(),
|
||||||
}),
|
cwd: workdir,
|
||||||
};
|
additional_permissions,
|
||||||
return review_approval_request(&session, &turn, request, None).await;
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
let available_decisions = vec![
|
let available_decisions = vec![
|
||||||
Some(ReviewDecision::Approved),
|
Some(ReviewDecision::Approved),
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::error::CodexErr;
|
|||||||
use crate::error::SandboxErr;
|
use crate::error::SandboxErr;
|
||||||
use crate::exec::ExecExpiration;
|
use crate::exec::ExecExpiration;
|
||||||
use crate::features::Feature;
|
use crate::features::Feature;
|
||||||
use crate::guardian::GuardianReviewRequest;
|
use crate::guardian::GuardianApprovalRequest;
|
||||||
use crate::guardian::review_approval_request;
|
use crate::guardian::review_approval_request;
|
||||||
use crate::guardian::routes_approval_to_guardian;
|
use crate::guardian::routes_approval_to_guardian;
|
||||||
use crate::powershell::prefix_powershell_script_with_utf8;
|
use crate::powershell::prefix_powershell_script_with_utf8;
|
||||||
@@ -41,7 +41,6 @@ use codex_network_proxy::NetworkProxy;
|
|||||||
use codex_protocol::models::PermissionProfile;
|
use codex_protocol::models::PermissionProfile;
|
||||||
use codex_protocol::protocol::ReviewDecision;
|
use codex_protocol::protocol::ReviewDecision;
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use serde_json::json;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
@@ -113,31 +112,24 @@ impl Approvable<UnifiedExecRequest> for UnifiedExecRuntime<'_> {
|
|||||||
let call_id = ctx.call_id.to_string();
|
let call_id = ctx.call_id.to_string();
|
||||||
let command = req.command.clone();
|
let command = req.command.clone();
|
||||||
let cwd = req.cwd.clone();
|
let cwd = req.cwd.clone();
|
||||||
let reason = ctx
|
let retry_reason = ctx.retry_reason.clone();
|
||||||
.retry_reason
|
let reason = retry_reason.clone().or_else(|| req.justification.clone());
|
||||||
.clone()
|
|
||||||
.or_else(|| req.justification.clone());
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
if routes_approval_to_guardian(turn) {
|
if routes_approval_to_guardian(turn) {
|
||||||
let mut action = json!({
|
return review_approval_request(
|
||||||
"tool": "exec_command",
|
session,
|
||||||
"command": command,
|
turn,
|
||||||
"cwd": cwd,
|
GuardianApprovalRequest::ExecCommand {
|
||||||
"sandbox_permissions": req.sandbox_permissions,
|
command,
|
||||||
"additional_permissions": req.additional_permissions,
|
cwd,
|
||||||
"justification": reason,
|
sandbox_permissions: req.sandbox_permissions,
|
||||||
"tty": req.tty,
|
additional_permissions: req.additional_permissions.clone(),
|
||||||
});
|
justification: req.justification.clone(),
|
||||||
if let Some(action) = action.as_object_mut() {
|
tty: req.tty,
|
||||||
if req.additional_permissions.is_none() {
|
},
|
||||||
action.remove("additional_permissions");
|
retry_reason,
|
||||||
}
|
)
|
||||||
if reason.is_none() {
|
.await;
|
||||||
action.remove("justification");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let request = GuardianReviewRequest { action };
|
|
||||||
return review_approval_request(session, turn, request, None).await;
|
|
||||||
}
|
}
|
||||||
with_cached_approval(&session.services, "unified_exec", keys, || async move {
|
with_cached_approval(&session.services, "unified_exec", keys, || async move {
|
||||||
let available_decisions = None;
|
let available_decisions = None;
|
||||||
|
|||||||
@@ -490,7 +490,6 @@ async fn permissions_message_includes_writable_roots() -> Result<()> {
|
|||||||
let expected = DeveloperInstructions::from_policy(
|
let expected = DeveloperInstructions::from_policy(
|
||||||
&sandbox_policy,
|
&sandbox_policy,
|
||||||
AskForApproval::OnRequest,
|
AskForApproval::OnRequest,
|
||||||
false,
|
|
||||||
&Policy::empty(),
|
&Policy::empty(),
|
||||||
test.config.cwd.as_path(),
|
test.config.cwd.as_path(),
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -408,8 +408,6 @@ const APPROVAL_POLICY_ON_REQUEST_RULE: &str =
|
|||||||
include_str!("prompts/permissions/approval_policy/on_request_rule.md");
|
include_str!("prompts/permissions/approval_policy/on_request_rule.md");
|
||||||
const APPROVAL_POLICY_ON_REQUEST_RULE_REQUEST_PERMISSION: &str =
|
const APPROVAL_POLICY_ON_REQUEST_RULE_REQUEST_PERMISSION: &str =
|
||||||
include_str!("prompts/permissions/approval_policy/on_request_rule_request_permission.md");
|
include_str!("prompts/permissions/approval_policy/on_request_rule_request_permission.md");
|
||||||
const GUARDIAN_APPROVAL_FEATURE: &str =
|
|
||||||
include_str!("prompts/permissions/approval_policy/guardian.md");
|
|
||||||
|
|
||||||
const SANDBOX_MODE_DANGER_FULL_ACCESS: &str =
|
const SANDBOX_MODE_DANGER_FULL_ACCESS: &str =
|
||||||
include_str!("prompts/permissions/sandbox_mode/danger_full_access.md");
|
include_str!("prompts/permissions/sandbox_mode/danger_full_access.md");
|
||||||
@@ -427,7 +425,6 @@ impl DeveloperInstructions {
|
|||||||
|
|
||||||
pub fn from(
|
pub fn from(
|
||||||
approval_policy: AskForApproval,
|
approval_policy: AskForApproval,
|
||||||
guardian_approval_enabled: bool,
|
|
||||||
exec_policy: &Policy,
|
exec_policy: &Policy,
|
||||||
request_permission_enabled: bool,
|
request_permission_enabled: bool,
|
||||||
) -> DeveloperInstructions {
|
) -> DeveloperInstructions {
|
||||||
@@ -451,14 +448,7 @@ impl DeveloperInstructions {
|
|||||||
AskForApproval::Never => APPROVAL_POLICY_NEVER.to_string(),
|
AskForApproval::Never => APPROVAL_POLICY_NEVER.to_string(),
|
||||||
AskForApproval::UnlessTrusted => APPROVAL_POLICY_UNLESS_TRUSTED.to_string(),
|
AskForApproval::UnlessTrusted => APPROVAL_POLICY_UNLESS_TRUSTED.to_string(),
|
||||||
AskForApproval::OnFailure => APPROVAL_POLICY_ON_FAILURE.to_string(),
|
AskForApproval::OnFailure => APPROVAL_POLICY_ON_FAILURE.to_string(),
|
||||||
AskForApproval::OnRequest => {
|
AskForApproval::OnRequest => on_request_instructions(),
|
||||||
let mut instructions = on_request_instructions();
|
|
||||||
if guardian_approval_enabled {
|
|
||||||
instructions.push_str("\n\n");
|
|
||||||
instructions.push_str(GUARDIAN_APPROVAL_FEATURE);
|
|
||||||
}
|
|
||||||
instructions
|
|
||||||
}
|
|
||||||
AskForApproval::Reject(reject_config) => {
|
AskForApproval::Reject(reject_config) => {
|
||||||
let on_request_instructions = on_request_instructions();
|
let on_request_instructions = on_request_instructions();
|
||||||
let sandbox_approval = reject_config.sandbox_approval;
|
let sandbox_approval = reject_config.sandbox_approval;
|
||||||
@@ -521,7 +511,6 @@ impl DeveloperInstructions {
|
|||||||
pub fn from_policy(
|
pub fn from_policy(
|
||||||
sandbox_policy: &SandboxPolicy,
|
sandbox_policy: &SandboxPolicy,
|
||||||
approval_policy: AskForApproval,
|
approval_policy: AskForApproval,
|
||||||
guardian_approval_enabled: bool,
|
|
||||||
exec_policy: &Policy,
|
exec_policy: &Policy,
|
||||||
cwd: &Path,
|
cwd: &Path,
|
||||||
request_permission_enabled: bool,
|
request_permission_enabled: bool,
|
||||||
@@ -546,7 +535,6 @@ impl DeveloperInstructions {
|
|||||||
sandbox_mode,
|
sandbox_mode,
|
||||||
network_access,
|
network_access,
|
||||||
approval_policy,
|
approval_policy,
|
||||||
guardian_approval_enabled,
|
|
||||||
exec_policy,
|
exec_policy,
|
||||||
writable_roots,
|
writable_roots,
|
||||||
request_permission_enabled,
|
request_permission_enabled,
|
||||||
@@ -571,7 +559,6 @@ impl DeveloperInstructions {
|
|||||||
sandbox_mode: SandboxMode,
|
sandbox_mode: SandboxMode,
|
||||||
network_access: NetworkAccess,
|
network_access: NetworkAccess,
|
||||||
approval_policy: AskForApproval,
|
approval_policy: AskForApproval,
|
||||||
guardian_approval_enabled: bool,
|
|
||||||
exec_policy: &Policy,
|
exec_policy: &Policy,
|
||||||
writable_roots: Option<Vec<WritableRoot>>,
|
writable_roots: Option<Vec<WritableRoot>>,
|
||||||
request_permission_enabled: bool,
|
request_permission_enabled: bool,
|
||||||
@@ -585,7 +572,6 @@ impl DeveloperInstructions {
|
|||||||
))
|
))
|
||||||
.concat(DeveloperInstructions::from(
|
.concat(DeveloperInstructions::from(
|
||||||
approval_policy,
|
approval_policy,
|
||||||
guardian_approval_enabled,
|
|
||||||
exec_policy,
|
exec_policy,
|
||||||
request_permission_enabled,
|
request_permission_enabled,
|
||||||
))
|
))
|
||||||
@@ -1667,7 +1653,6 @@ mod tests {
|
|||||||
SandboxMode::WorkspaceWrite,
|
SandboxMode::WorkspaceWrite,
|
||||||
NetworkAccess::Enabled,
|
NetworkAccess::Enabled,
|
||||||
AskForApproval::OnRequest,
|
AskForApproval::OnRequest,
|
||||||
false,
|
|
||||||
&Policy::empty(),
|
&Policy::empty(),
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
@@ -1697,7 +1682,6 @@ mod tests {
|
|||||||
let instructions = DeveloperInstructions::from_policy(
|
let instructions = DeveloperInstructions::from_policy(
|
||||||
&policy,
|
&policy,
|
||||||
AskForApproval::UnlessTrusted,
|
AskForApproval::UnlessTrusted,
|
||||||
false,
|
|
||||||
&Policy::empty(),
|
&Policy::empty(),
|
||||||
&PathBuf::from("/tmp"),
|
&PathBuf::from("/tmp"),
|
||||||
false,
|
false,
|
||||||
@@ -1720,7 +1704,6 @@ mod tests {
|
|||||||
SandboxMode::WorkspaceWrite,
|
SandboxMode::WorkspaceWrite,
|
||||||
NetworkAccess::Enabled,
|
NetworkAccess::Enabled,
|
||||||
AskForApproval::OnRequest,
|
AskForApproval::OnRequest,
|
||||||
false,
|
|
||||||
&exec_policy,
|
&exec_policy,
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
@@ -1738,7 +1721,6 @@ mod tests {
|
|||||||
SandboxMode::WorkspaceWrite,
|
SandboxMode::WorkspaceWrite,
|
||||||
NetworkAccess::Enabled,
|
NetworkAccess::Enabled,
|
||||||
AskForApproval::OnRequest,
|
AskForApproval::OnRequest,
|
||||||
false,
|
|
||||||
&Policy::empty(),
|
&Policy::empty(),
|
||||||
None,
|
None,
|
||||||
true,
|
true,
|
||||||
@@ -1749,23 +1731,6 @@ mod tests {
|
|||||||
assert!(text.contains("additional_permissions"));
|
assert!(text.contains("additional_permissions"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn includes_guardian_feature_guidance_for_on_request_when_enabled() {
|
|
||||||
let instructions = DeveloperInstructions::from_permissions_with_network(
|
|
||||||
SandboxMode::WorkspaceWrite,
|
|
||||||
NetworkAccess::Enabled,
|
|
||||||
AskForApproval::OnRequest,
|
|
||||||
true,
|
|
||||||
&Policy::empty(),
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
let text = instructions.into_text();
|
|
||||||
assert!(text.contains("guardian subagent"));
|
|
||||||
assert!(text.contains("approval prompts"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_command_prefix_list_sorts_by_len_then_total_len_then_alphabetical() {
|
fn render_command_prefix_list_sorts_by_len_then_total_len_then_alphabetical() {
|
||||||
let prefixes = vec![
|
let prefixes = vec![
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
Guardian approvals are enabled. While `approval_policy` is still `on-request`, approval prompts are routed to a guardian subagent instead of the user. Use `sandbox_permissions: "require_escalated"` with a concise `justification` when you need unsandboxed execution, and use `sandbox_permissions: "with_additional_permissions"` plus `additional_permissions` when you need broader sandboxed access. Codex will ask the guardian subagent to assess the risk automatically.
|
|
||||||
|
|
||||||
Do not message the user before requesting escalation. If the guardian rejects an action, do not attempt the same outcome via workaround, indirect execution, or policy circumvention. Either choose a materially safer alternative or stop and ask the user for guidance.
|
|
||||||
+3
-2
@@ -11,8 +11,9 @@ expression: popup
|
|||||||
[ ] Multi-agents Ask Codex to spawn multiple agents to parallelize the work and win in efficiency.
|
[ ] Multi-agents Ask Codex to spawn multiple agents to parallelize the work and win in efficiency.
|
||||||
[ ] Apps Use a connected ChatGPT App using "$". Install Apps via /apps command. Restart
|
[ ] Apps Use a connected ChatGPT App using "$". Install Apps via /apps command. Restart
|
||||||
Codex after enabling.
|
Codex after enabling.
|
||||||
[ ] Guardian approvals Let a guardian subagent review `on-request` approval prompts instead of showing
|
[ ] Automatic approval review Dispatch `on-request` approval prompts (for e.g. sandbox escapes or blocked network
|
||||||
them to you, including sandbox escapes and blocked network access.
|
access) to a carefully-prompted security reviewer subagent rather than blocking the
|
||||||
|
agent on your input.
|
||||||
[ ] Prevent sleep while running Keep your computer awake while Codex is running a thread.
|
[ ] Prevent sleep while running Keep your computer awake while Codex is running a thread.
|
||||||
|
|
||||||
Press space to select or enter to save for next conversation
|
Press space to select or enter to save for next conversation
|
||||||
|
|||||||
+3
-2
@@ -12,8 +12,9 @@ expression: popup
|
|||||||
[ ] Multi-agents Ask Codex to spawn multiple agents to parallelize the work and win in efficiency.
|
[ ] Multi-agents Ask Codex to spawn multiple agents to parallelize the work and win in efficiency.
|
||||||
[ ] Apps Use a connected ChatGPT App using "$". Install Apps via /apps command. Restart
|
[ ] Apps Use a connected ChatGPT App using "$". Install Apps via /apps command. Restart
|
||||||
Codex after enabling.
|
Codex after enabling.
|
||||||
[ ] Guardian approvals Let a guardian subagent review `on-request` approval prompts instead of showing
|
[ ] Automatic approval review Dispatch `on-request` approval prompts (for e.g. sandbox escapes or blocked network
|
||||||
them to you, including sandbox escapes and blocked network access.
|
access) to a carefully-prompted security reviewer subagent rather than blocking the
|
||||||
|
agent on your input.
|
||||||
[ ] Prevent sleep while running Keep your computer awake while Codex is running a thread.
|
[ ] Prevent sleep while running Keep your computer awake while Codex is running a thread.
|
||||||
|
|
||||||
Press space to select or enter to save for next conversation
|
Press space to select or enter to save for next conversation
|
||||||
|
|||||||
Reference in New Issue
Block a user