mirror of
https://github.com/openai/codex.git
synced 2026-04-22 13:44:47 +00:00
Compare commits
1 Commits
dev/shaqay
...
owen/guard
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e59f7fbf69 |
@@ -987,6 +987,14 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"GuardianApprovalReviewOverrideDecision": {
|
||||
"description": "[UNSTABLE] Client decision that preempts a pending guardian approval review.",
|
||||
"enum": [
|
||||
"approve",
|
||||
"decline"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"ImageDetail": {
|
||||
"enum": [
|
||||
"auto",
|
||||
@@ -1038,6 +1046,30 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ItemGuardianApprovalReviewOverrideParams": {
|
||||
"description": "[UNSTABLE] Params for preempting a pending guardian approval review.",
|
||||
"properties": {
|
||||
"decision": {
|
||||
"$ref": "#/definitions/GuardianApprovalReviewOverrideDecision"
|
||||
},
|
||||
"reviewId": {
|
||||
"type": "string"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"turnId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"decision",
|
||||
"reviewId",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ListMcpServerStatusParams": {
|
||||
"properties": {
|
||||
"cursor": {
|
||||
@@ -4402,6 +4434,30 @@
|
||||
"title": "Review/startRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"item/autoApprovalReview/override"
|
||||
],
|
||||
"title": "Item/autoApprovalReview/overrideRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ItemGuardianApprovalReviewOverrideParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Item/autoApprovalReview/overrideRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
@@ -391,7 +391,8 @@
|
||||
"AutoReviewDecisionSource": {
|
||||
"description": "[UNSTABLE] Source that produced a terminal guardian approval review decision.",
|
||||
"enum": [
|
||||
"agent"
|
||||
"agent",
|
||||
"user"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -1058,6 +1058,30 @@
|
||||
"title": "Review/startRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"item/autoApprovalReview/override"
|
||||
],
|
||||
"title": "Item/autoApprovalReview/overrideRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/ItemGuardianApprovalReviewOverrideParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Item/autoApprovalReview/overrideRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -5539,7 +5563,8 @@
|
||||
"AutoReviewDecisionSource": {
|
||||
"description": "[UNSTABLE] Source that produced a terminal guardian approval review decision.",
|
||||
"enum": [
|
||||
"agent"
|
||||
"agent",
|
||||
"user"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -8291,6 +8316,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"GuardianApprovalReviewOverrideDecision": {
|
||||
"description": "[UNSTABLE] Client decision that preempts a pending guardian approval review.",
|
||||
"enum": [
|
||||
"approve",
|
||||
"decline"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"GuardianApprovalReviewStatus": {
|
||||
"description": "[UNSTABLE] Lifecycle state for a guardian approval review.",
|
||||
"enum": [
|
||||
@@ -8619,6 +8652,38 @@
|
||||
"title": "ItemGuardianApprovalReviewCompletedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"ItemGuardianApprovalReviewOverrideParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "[UNSTABLE] Params for preempting a pending guardian approval review.",
|
||||
"properties": {
|
||||
"decision": {
|
||||
"$ref": "#/definitions/v2/GuardianApprovalReviewOverrideDecision"
|
||||
},
|
||||
"reviewId": {
|
||||
"type": "string"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"turnId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"decision",
|
||||
"reviewId",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
"title": "ItemGuardianApprovalReviewOverrideParams",
|
||||
"type": "object"
|
||||
},
|
||||
"ItemGuardianApprovalReviewOverrideResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "[UNSTABLE] Empty response for `item/autoApprovalReview/override`.",
|
||||
"title": "ItemGuardianApprovalReviewOverrideResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ItemGuardianApprovalReviewStartedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.",
|
||||
|
||||
@@ -718,7 +718,8 @@
|
||||
"AutoReviewDecisionSource": {
|
||||
"description": "[UNSTABLE] Source that produced a terminal guardian approval review decision.",
|
||||
"enum": [
|
||||
"agent"
|
||||
"agent",
|
||||
"user"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
@@ -1640,6 +1641,30 @@
|
||||
"title": "Review/startRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"item/autoApprovalReview/override"
|
||||
],
|
||||
"title": "Item/autoApprovalReview/overrideRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/ItemGuardianApprovalReviewOverrideParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Item/autoApprovalReview/overrideRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -5050,6 +5075,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"GuardianApprovalReviewOverrideDecision": {
|
||||
"description": "[UNSTABLE] Client decision that preempts a pending guardian approval review.",
|
||||
"enum": [
|
||||
"approve",
|
||||
"decline"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"GuardianApprovalReviewStatus": {
|
||||
"description": "[UNSTABLE] Lifecycle state for a guardian approval review.",
|
||||
"enum": [
|
||||
@@ -5422,6 +5455,38 @@
|
||||
"title": "ItemGuardianApprovalReviewCompletedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"ItemGuardianApprovalReviewOverrideParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "[UNSTABLE] Params for preempting a pending guardian approval review.",
|
||||
"properties": {
|
||||
"decision": {
|
||||
"$ref": "#/definitions/GuardianApprovalReviewOverrideDecision"
|
||||
},
|
||||
"reviewId": {
|
||||
"type": "string"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"turnId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"decision",
|
||||
"reviewId",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
"title": "ItemGuardianApprovalReviewOverrideParams",
|
||||
"type": "object"
|
||||
},
|
||||
"ItemGuardianApprovalReviewOverrideResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "[UNSTABLE] Empty response for `item/autoApprovalReview/override`.",
|
||||
"title": "ItemGuardianApprovalReviewOverrideResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ItemGuardianApprovalReviewStartedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "[UNSTABLE] Temporary notification payload for guardian automatic approval review. This shape is expected to change soon.",
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"AutoReviewDecisionSource": {
|
||||
"description": "[UNSTABLE] Source that produced a terminal guardian approval review decision.",
|
||||
"enum": [
|
||||
"agent"
|
||||
"agent",
|
||||
"user"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"GuardianApprovalReviewOverrideDecision": {
|
||||
"description": "[UNSTABLE] Client decision that preempts a pending guardian approval review.",
|
||||
"enum": [
|
||||
"approve",
|
||||
"decline"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": "[UNSTABLE] Params for preempting a pending guardian approval review.",
|
||||
"properties": {
|
||||
"decision": {
|
||||
"$ref": "#/definitions/GuardianApprovalReviewOverrideDecision"
|
||||
},
|
||||
"reviewId": {
|
||||
"type": "string"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
},
|
||||
"turnId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"decision",
|
||||
"reviewId",
|
||||
"threadId",
|
||||
"turnId"
|
||||
],
|
||||
"title": "ItemGuardianApprovalReviewOverrideParams",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "[UNSTABLE] Empty response for `item/autoApprovalReview/override`.",
|
||||
"title": "ItemGuardianApprovalReviewOverrideResponse",
|
||||
"type": "object"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -5,4 +5,4 @@
|
||||
/**
|
||||
* [UNSTABLE] Source that produced a terminal guardian approval review decision.
|
||||
*/
|
||||
export type AutoReviewDecisionSource = "agent";
|
||||
export type AutoReviewDecisionSource = "agent" | "user";
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* [UNSTABLE] Client decision that preempts a pending guardian approval review.
|
||||
*/
|
||||
export type GuardianApprovalReviewOverrideDecision = "approve" | "decline";
|
||||
@@ -0,0 +1,9 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { GuardianApprovalReviewOverrideDecision } from "./GuardianApprovalReviewOverrideDecision";
|
||||
|
||||
/**
|
||||
* [UNSTABLE] Params for preempting a pending guardian approval review.
|
||||
*/
|
||||
export type ItemGuardianApprovalReviewOverrideParams = { threadId: string, turnId: string, reviewId: string, decision: GuardianApprovalReviewOverrideDecision, };
|
||||
@@ -0,0 +1,8 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* [UNSTABLE] Empty response for `item/autoApprovalReview/override`.
|
||||
*/
|
||||
export type ItemGuardianApprovalReviewOverrideResponse = Record<string, never>;
|
||||
@@ -125,6 +125,7 @@ export type { GitInfo } from "./GitInfo";
|
||||
export type { GrantedPermissionProfile } from "./GrantedPermissionProfile";
|
||||
export type { GuardianApprovalReview } from "./GuardianApprovalReview";
|
||||
export type { GuardianApprovalReviewAction } from "./GuardianApprovalReviewAction";
|
||||
export type { GuardianApprovalReviewOverrideDecision } from "./GuardianApprovalReviewOverrideDecision";
|
||||
export type { GuardianApprovalReviewStatus } from "./GuardianApprovalReviewStatus";
|
||||
export type { GuardianCommandSource } from "./GuardianCommandSource";
|
||||
export type { GuardianRiskLevel } from "./GuardianRiskLevel";
|
||||
@@ -142,6 +143,8 @@ export type { HookScope } from "./HookScope";
|
||||
export type { HookStartedNotification } from "./HookStartedNotification";
|
||||
export type { ItemCompletedNotification } from "./ItemCompletedNotification";
|
||||
export type { ItemGuardianApprovalReviewCompletedNotification } from "./ItemGuardianApprovalReviewCompletedNotification";
|
||||
export type { ItemGuardianApprovalReviewOverrideParams } from "./ItemGuardianApprovalReviewOverrideParams";
|
||||
export type { ItemGuardianApprovalReviewOverrideResponse } from "./ItemGuardianApprovalReviewOverrideResponse";
|
||||
export type { ItemGuardianApprovalReviewStartedNotification } from "./ItemGuardianApprovalReviewStartedNotification";
|
||||
export type { ItemStartedNotification } from "./ItemStartedNotification";
|
||||
export type { ListMcpServerStatusParams } from "./ListMcpServerStatusParams";
|
||||
|
||||
@@ -424,6 +424,10 @@ client_request_definitions! {
|
||||
params: v2::ReviewStartParams,
|
||||
response: v2::ReviewStartResponse,
|
||||
},
|
||||
ItemGuardianApprovalReviewOverride => "item/autoApprovalReview/override" {
|
||||
params: v2::ItemGuardianApprovalReviewOverrideParams,
|
||||
response: v2::ItemGuardianApprovalReviewOverrideResponse,
|
||||
},
|
||||
|
||||
ModelList => "model/list" {
|
||||
params: v2::ModelListParams,
|
||||
|
||||
@@ -54,6 +54,7 @@ use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot;
|
||||
use codex_protocol::protocol::ExecCommandSource as CoreExecCommandSource;
|
||||
use codex_protocol::protocol::ExecCommandStatus as CoreExecCommandStatus;
|
||||
use codex_protocol::protocol::GranularApprovalConfig as CoreGranularApprovalConfig;
|
||||
use codex_protocol::protocol::GuardianReviewOverrideDecision as CoreGuardianReviewOverrideDecision;
|
||||
use codex_protocol::protocol::GuardianRiskLevel as CoreGuardianRiskLevel;
|
||||
use codex_protocol::protocol::GuardianUserAuthorization as CoreGuardianUserAuthorization;
|
||||
use codex_protocol::protocol::HookEventName as CoreHookEventName;
|
||||
@@ -4541,12 +4542,33 @@ pub enum GuardianApprovalReviewStatus {
|
||||
/// [UNSTABLE] Source that produced a terminal guardian approval review decision.
|
||||
pub enum AutoReviewDecisionSource {
|
||||
Agent,
|
||||
#[serde(alias = "clientOverride")]
|
||||
User,
|
||||
}
|
||||
|
||||
impl From<CoreGuardianAssessmentDecisionSource> for AutoReviewDecisionSource {
|
||||
fn from(value: CoreGuardianAssessmentDecisionSource) -> Self {
|
||||
match value {
|
||||
CoreGuardianAssessmentDecisionSource::Agent => Self::Agent,
|
||||
CoreGuardianAssessmentDecisionSource::User => Self::User,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
/// [UNSTABLE] Client decision that preempts a pending guardian approval review.
|
||||
pub enum GuardianApprovalReviewOverrideDecision {
|
||||
Approve,
|
||||
Decline,
|
||||
}
|
||||
|
||||
impl From<GuardianApprovalReviewOverrideDecision> for CoreGuardianReviewOverrideDecision {
|
||||
fn from(value: GuardianApprovalReviewOverrideDecision) -> Self {
|
||||
match value {
|
||||
GuardianApprovalReviewOverrideDecision::Approve => Self::Approve,
|
||||
GuardianApprovalReviewOverrideDecision::Decline => Self::Decline,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4608,6 +4630,23 @@ pub struct GuardianApprovalReview {
|
||||
pub rationale: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
/// [UNSTABLE] Params for preempting a pending guardian approval review.
|
||||
pub struct ItemGuardianApprovalReviewOverrideParams {
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
pub review_id: String,
|
||||
pub decision: GuardianApprovalReviewOverrideDecision,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
/// [UNSTABLE] Empty response for `item/autoApprovalReview/override`.
|
||||
pub struct ItemGuardianApprovalReviewOverrideResponse {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
|
||||
@@ -69,6 +69,8 @@ use codex_app_server_protocol::GetConversationSummaryParams;
|
||||
use codex_app_server_protocol::GetConversationSummaryResponse;
|
||||
use codex_app_server_protocol::GitDiffToRemoteResponse;
|
||||
use codex_app_server_protocol::GitInfo as ApiGitInfo;
|
||||
use codex_app_server_protocol::ItemGuardianApprovalReviewOverrideParams;
|
||||
use codex_app_server_protocol::ItemGuardianApprovalReviewOverrideResponse;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::ListMcpServerStatusParams;
|
||||
use codex_app_server_protocol::ListMcpServerStatusResponse;
|
||||
@@ -854,6 +856,13 @@ impl CodexMessageProcessor {
|
||||
self.review_start(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::ItemGuardianApprovalReviewOverride { request_id, params } => {
|
||||
self.item_guardian_approval_review_override(
|
||||
to_connection_request_id(request_id),
|
||||
params,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::GetConversationSummary { request_id, params } => {
|
||||
self.get_thread_summary(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
@@ -5350,6 +5359,46 @@ impl CodexMessageProcessor {
|
||||
});
|
||||
}
|
||||
|
||||
async fn item_guardian_approval_review_override(
|
||||
&self,
|
||||
request_id: ConnectionRequestId,
|
||||
params: ItemGuardianApprovalReviewOverrideParams,
|
||||
) {
|
||||
let (_, thread) = match self.load_thread(¶ms.thread_id).await {
|
||||
Ok(thread) => thread,
|
||||
Err(error) => {
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
match thread
|
||||
.override_guardian_review(¶ms.review_id, ¶ms.turn_id, params.decision.into())
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
self.outgoing
|
||||
.send_response(request_id, ItemGuardianApprovalReviewOverrideResponse {})
|
||||
.await;
|
||||
}
|
||||
Err(codex_protocol::error::CodexErr::InvalidRequest(message)) => {
|
||||
self.send_invalid_request_error(request_id, message).await;
|
||||
}
|
||||
Err(err) => {
|
||||
self.outgoing
|
||||
.send_error(
|
||||
request_id,
|
||||
JSONRPCErrorError {
|
||||
code: INTERNAL_ERROR_CODE,
|
||||
message: err.to_string(),
|
||||
data: None,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_invalid_request_error(&self, request_id: ConnectionRequestId, message: String) {
|
||||
let error = JSONRPCErrorError {
|
||||
code: INVALID_REQUEST_ERROR_CODE,
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::codex::Codex;
|
||||
use crate::codex::SteerInputError;
|
||||
use crate::config::ConstraintResult;
|
||||
use crate::file_watcher::WatchRegistration;
|
||||
use crate::guardian::GuardianReviewOverrideError;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::config_types::Personality;
|
||||
@@ -15,6 +16,7 @@ use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::Event;
|
||||
use codex_protocol::protocol::GuardianReviewOverrideDecision;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::SessionSource;
|
||||
@@ -262,6 +264,29 @@ impl CodexThread {
|
||||
|
||||
Ok(*guard)
|
||||
}
|
||||
|
||||
pub async fn override_guardian_review(
|
||||
&self,
|
||||
review_id: &str,
|
||||
turn_id: &str,
|
||||
decision: GuardianReviewOverrideDecision,
|
||||
) -> CodexResult<()> {
|
||||
self.codex
|
||||
.session
|
||||
.guardian_review_session
|
||||
.override_review(review_id, turn_id, decision)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
GuardianReviewOverrideError::NotFound => CodexErr::InvalidRequest(format!(
|
||||
"guardian review not found or already completed: {review_id}"
|
||||
)),
|
||||
GuardianReviewOverrideError::TurnMismatch { expected_turn_id } => {
|
||||
CodexErr::InvalidRequest(format!(
|
||||
"guardian review {review_id} belongs to turn {expected_turn_id}, not {turn_id}"
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn pending_message_input_item(message: &ResponseItem) -> CodexResult<ResponseInputItem> {
|
||||
|
||||
@@ -31,6 +31,7 @@ pub(crate) use review::new_guardian_review_id;
|
||||
pub(crate) use review::review_approval_request;
|
||||
pub(crate) use review::review_approval_request_with_cancel;
|
||||
pub(crate) use review::routes_approval_to_guardian;
|
||||
pub(crate) use review_session::GuardianReviewOverrideError;
|
||||
pub(crate) use review_session::GuardianReviewSessionManager;
|
||||
|
||||
const GUARDIAN_PREFERRED_MODEL: &str = "gpt-5.4";
|
||||
|
||||
@@ -6,12 +6,15 @@ use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::GuardianAssessmentDecisionSource;
|
||||
use codex_protocol::protocol::GuardianAssessmentEvent;
|
||||
use codex_protocol::protocol::GuardianAssessmentStatus;
|
||||
use codex_protocol::protocol::GuardianReviewOverrideDecision;
|
||||
use codex_protocol::protocol::GuardianRiskLevel;
|
||||
use codex_protocol::protocol::GuardianUserAuthorization;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_protocol::protocol::WarningEvent;
|
||||
use tokio::task::JoinError;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::codex::Session;
|
||||
use crate::codex::TurnContext;
|
||||
@@ -60,6 +63,11 @@ pub(crate) async fn guardian_rejection_message(session: &Session, review_id: &st
|
||||
rejection.rationale.trim(),
|
||||
GUARDIAN_REJECTION_INSTRUCTIONS
|
||||
),
|
||||
GuardianAssessmentDecisionSource::User => format!(
|
||||
"This action was declined before the automatic approval review completed.\nReason: {}\n{}",
|
||||
rejection.rationale.trim(),
|
||||
GUARDIAN_REJECTION_INSTRUCTIONS
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +78,22 @@ pub(super) enum GuardianReviewOutcome {
|
||||
Aborted,
|
||||
}
|
||||
|
||||
enum GuardianReviewSelection {
|
||||
Review(GuardianReviewOutcome),
|
||||
Override(GuardianReviewOverrideDecision),
|
||||
OverrideClosed,
|
||||
ExternalCancel,
|
||||
}
|
||||
|
||||
struct GuardianReviewOverrideCompletion {
|
||||
session: Arc<Session>,
|
||||
turn: Arc<TurnContext>,
|
||||
review_id: String,
|
||||
target_item_id: Option<String>,
|
||||
assessment_turn_id: String,
|
||||
action_summary: codex_protocol::protocol::GuardianAssessmentAction,
|
||||
}
|
||||
|
||||
fn guardian_risk_level_str(level: GuardianRiskLevel) -> &'static str {
|
||||
match level {
|
||||
GuardianRiskLevel::Low => "low",
|
||||
@@ -110,6 +134,10 @@ async fn run_guardian_review(
|
||||
let target_item_id = guardian_request_target_item_id(&request).map(str::to_string);
|
||||
let assessment_turn_id = guardian_request_turn_id(&request, &turn.sub_id).to_string();
|
||||
let action_summary = guardian_assessment_action(&request);
|
||||
let override_rx = session
|
||||
.guardian_review_session
|
||||
.register_pending_override(review_id.clone(), assessment_turn_id.clone())
|
||||
.await;
|
||||
session
|
||||
.send_event(
|
||||
turn.as_ref(),
|
||||
@@ -131,6 +159,10 @@ async fn run_guardian_review(
|
||||
.as_ref()
|
||||
.is_some_and(CancellationToken::is_cancelled)
|
||||
{
|
||||
session
|
||||
.guardian_review_session
|
||||
.clear_pending_override(&review_id)
|
||||
.await;
|
||||
session
|
||||
.send_event(
|
||||
turn.as_ref(),
|
||||
@@ -152,15 +184,90 @@ async fn run_guardian_review(
|
||||
|
||||
let schema = guardian_output_schema();
|
||||
let terminal_action = action_summary.clone();
|
||||
let outcome = run_guardian_review_session(
|
||||
let review_cancel = CancellationToken::new();
|
||||
let mut review_task = tokio::spawn(run_guardian_review_session(
|
||||
session.clone(),
|
||||
turn.clone(),
|
||||
request,
|
||||
retry_reason,
|
||||
schema,
|
||||
external_cancel,
|
||||
)
|
||||
.await;
|
||||
Some(review_cancel.clone()),
|
||||
));
|
||||
let external_cancelled = async {
|
||||
if let Some(cancel_token) = external_cancel.as_ref() {
|
||||
cancel_token.cancelled().await;
|
||||
} else {
|
||||
std::future::pending::<()>().await;
|
||||
}
|
||||
};
|
||||
tokio::pin!(external_cancelled);
|
||||
let selection = tokio::select! {
|
||||
result = &mut review_task => {
|
||||
GuardianReviewSelection::Review(guardian_review_task_result(result))
|
||||
}
|
||||
override_decision = override_rx => {
|
||||
match override_decision {
|
||||
Ok(decision) => GuardianReviewSelection::Override(decision),
|
||||
Err(_) => GuardianReviewSelection::OverrideClosed,
|
||||
}
|
||||
}
|
||||
_ = &mut external_cancelled => GuardianReviewSelection::ExternalCancel,
|
||||
};
|
||||
|
||||
let outcome = match selection {
|
||||
GuardianReviewSelection::Review(outcome) => {
|
||||
session
|
||||
.guardian_review_session
|
||||
.clear_pending_override(&review_id)
|
||||
.await;
|
||||
outcome
|
||||
}
|
||||
GuardianReviewSelection::Override(decision) => {
|
||||
review_cancel.cancel();
|
||||
if let Err(err) = review_task.await {
|
||||
warn!("guardian review task failed after client override: {err}");
|
||||
}
|
||||
session
|
||||
.guardian_review_session
|
||||
.clear_pending_override(&review_id)
|
||||
.await;
|
||||
return finish_guardian_review_override(
|
||||
GuardianReviewOverrideCompletion {
|
||||
session,
|
||||
turn,
|
||||
review_id,
|
||||
target_item_id,
|
||||
assessment_turn_id,
|
||||
action_summary,
|
||||
},
|
||||
decision,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
GuardianReviewSelection::OverrideClosed => {
|
||||
warn!("pending guardian review override channel closed before decision");
|
||||
let outcome = match review_task.await {
|
||||
Ok(outcome) => outcome,
|
||||
Err(err) => guardian_review_task_result(Err(err)),
|
||||
};
|
||||
session
|
||||
.guardian_review_session
|
||||
.clear_pending_override(&review_id)
|
||||
.await;
|
||||
outcome
|
||||
}
|
||||
GuardianReviewSelection::ExternalCancel => {
|
||||
review_cancel.cancel();
|
||||
if let Err(err) = review_task.await {
|
||||
warn!("guardian review task failed after external cancellation: {err}");
|
||||
}
|
||||
session
|
||||
.guardian_review_session
|
||||
.clear_pending_override(&review_id)
|
||||
.await;
|
||||
GuardianReviewOutcome::Aborted
|
||||
}
|
||||
};
|
||||
|
||||
let assessment = match outcome {
|
||||
GuardianReviewOutcome::Completed(Ok(assessment)) => assessment,
|
||||
@@ -262,6 +369,75 @@ async fn run_guardian_review(
|
||||
}
|
||||
}
|
||||
|
||||
fn guardian_review_task_result(
|
||||
result: Result<GuardianReviewOutcome, JoinError>,
|
||||
) -> GuardianReviewOutcome {
|
||||
match result {
|
||||
Ok(outcome) => outcome,
|
||||
Err(err) => GuardianReviewOutcome::Completed(Err(anyhow::anyhow!(
|
||||
"guardian review task failed: {err}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn finish_guardian_review_override(
|
||||
completion: GuardianReviewOverrideCompletion,
|
||||
decision: GuardianReviewOverrideDecision,
|
||||
) -> ReviewDecision {
|
||||
let GuardianReviewOverrideCompletion {
|
||||
session,
|
||||
turn,
|
||||
review_id,
|
||||
target_item_id,
|
||||
assessment_turn_id,
|
||||
action_summary,
|
||||
} = completion;
|
||||
let (status, review_decision, rationale) = match decision {
|
||||
GuardianReviewOverrideDecision::Approve => (
|
||||
GuardianAssessmentStatus::Approved,
|
||||
ReviewDecision::Approved,
|
||||
"User preempted the automatic guardian review and approved this review.".to_string(),
|
||||
),
|
||||
GuardianReviewOverrideDecision::Decline => (
|
||||
GuardianAssessmentStatus::Denied,
|
||||
ReviewDecision::Denied,
|
||||
"User preempted the automatic guardian review and declined this review.".to_string(),
|
||||
),
|
||||
};
|
||||
|
||||
{
|
||||
let mut rationales = session.services.guardian_rejections.lock().await;
|
||||
if matches!(decision, GuardianReviewOverrideDecision::Approve) {
|
||||
rationales.remove(&review_id);
|
||||
} else {
|
||||
let rejection = GuardianRejection {
|
||||
rationale: rationale.clone(),
|
||||
source: GuardianAssessmentDecisionSource::User,
|
||||
};
|
||||
rationales.insert(review_id.clone(), rejection);
|
||||
}
|
||||
}
|
||||
|
||||
session
|
||||
.send_event(
|
||||
turn.as_ref(),
|
||||
EventMsg::GuardianAssessment(GuardianAssessmentEvent {
|
||||
id: review_id,
|
||||
target_item_id,
|
||||
turn_id: assessment_turn_id,
|
||||
status,
|
||||
risk_level: None,
|
||||
user_authorization: None,
|
||||
rationale: Some(rationale),
|
||||
decision_source: Some(GuardianAssessmentDecisionSource::User),
|
||||
action: action_summary,
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
review_decision
|
||||
}
|
||||
|
||||
/// Public entrypoint for approval requests that should be reviewed by guardian.
|
||||
pub(crate) async fn review_approval_request(
|
||||
session: &Arc<Session>,
|
||||
|
||||
@@ -12,6 +12,7 @@ use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::GuardianReviewOverrideDecision;
|
||||
use codex_protocol::protocol::InitialHistory;
|
||||
use codex_protocol::protocol::Op;
|
||||
use codex_protocol::protocol::RolloutItem;
|
||||
@@ -19,6 +20,7 @@ use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use serde_json::Value;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::warn;
|
||||
|
||||
@@ -84,6 +86,18 @@ pub(crate) struct GuardianReviewSessionManager {
|
||||
struct GuardianReviewSessionState {
|
||||
trunk: Option<Arc<GuardianReviewSession>>,
|
||||
ephemeral_reviews: Vec<Arc<GuardianReviewSession>>,
|
||||
pending_overrides: HashMap<String, PendingGuardianReviewOverride>,
|
||||
}
|
||||
|
||||
struct PendingGuardianReviewOverride {
|
||||
turn_id: String,
|
||||
sender: oneshot::Sender<GuardianReviewOverrideDecision>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum GuardianReviewOverrideError {
|
||||
NotFound,
|
||||
TurnMismatch { expected_turn_id: String },
|
||||
}
|
||||
|
||||
struct GuardianReviewSession {
|
||||
@@ -247,6 +261,53 @@ impl Drop for EphemeralReviewCleanup {
|
||||
}
|
||||
|
||||
impl GuardianReviewSessionManager {
|
||||
pub(crate) async fn register_pending_override(
|
||||
&self,
|
||||
review_id: String,
|
||||
turn_id: String,
|
||||
) -> oneshot::Receiver<GuardianReviewOverrideDecision> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let mut state = self.state.lock().await;
|
||||
if state
|
||||
.pending_overrides
|
||||
.insert(
|
||||
review_id.clone(),
|
||||
PendingGuardianReviewOverride { turn_id, sender },
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
warn!("replaced pending guardian review override for review {review_id}");
|
||||
}
|
||||
receiver
|
||||
}
|
||||
|
||||
pub(crate) async fn clear_pending_override(&self, review_id: &str) {
|
||||
self.state.lock().await.pending_overrides.remove(review_id);
|
||||
}
|
||||
|
||||
pub(crate) async fn override_review(
|
||||
&self,
|
||||
review_id: &str,
|
||||
turn_id: &str,
|
||||
decision: GuardianReviewOverrideDecision,
|
||||
) -> Result<(), GuardianReviewOverrideError> {
|
||||
let mut state = self.state.lock().await;
|
||||
let Some(pending) = state.pending_overrides.remove(review_id) else {
|
||||
return Err(GuardianReviewOverrideError::NotFound);
|
||||
};
|
||||
if pending.turn_id != turn_id {
|
||||
let expected_turn_id = pending.turn_id.clone();
|
||||
state
|
||||
.pending_overrides
|
||||
.insert(review_id.to_string(), pending);
|
||||
return Err(GuardianReviewOverrideError::TurnMismatch { expected_turn_id });
|
||||
}
|
||||
pending
|
||||
.sender
|
||||
.send(decision)
|
||||
.map_err(|_| GuardianReviewOverrideError::NotFound)
|
||||
}
|
||||
|
||||
pub(crate) async fn shutdown(&self) {
|
||||
let (review_session, ephemeral_reviews) = {
|
||||
let mut state = self.state.lock().await;
|
||||
@@ -414,7 +475,7 @@ impl GuardianReviewSessionManager {
|
||||
let snapshot = state.last_committed_fork_snapshot.as_ref()?;
|
||||
match &snapshot.initial_history {
|
||||
InitialHistory::Forked(items) => Some(items.clone()),
|
||||
InitialHistory::New | InitialHistory::Resumed(_) => None,
|
||||
InitialHistory::New | InitialHistory::Cleared | InitialHistory::Resumed(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,10 @@ use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::GuardianAssessmentDecisionSource;
|
||||
use codex_protocol::protocol::GuardianAssessmentEvent;
|
||||
use codex_protocol::protocol::GuardianAssessmentStatus;
|
||||
use codex_protocol::protocol::GuardianReviewOverrideDecision;
|
||||
use codex_protocol::protocol::GuardianRiskLevel;
|
||||
use codex_protocol::protocol::GuardianUserAuthorization;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
@@ -674,6 +677,170 @@ fn guardian_request_turn_id_prefers_network_access_owner_turn() {
|
||||
);
|
||||
}
|
||||
|
||||
fn guardian_override_test_request(id: &str) -> GuardianApprovalRequest {
|
||||
GuardianApprovalRequest::Shell {
|
||||
id: id.to_string(),
|
||||
command: vec!["git".to_string(), "push".to_string()],
|
||||
cwd: PathBuf::from("/repo/codex-rs/core"),
|
||||
sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault,
|
||||
additional_permissions: None,
|
||||
justification: Some("Need to push the reviewed docs fix.".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn recv_guardian_assessment_with_status(
|
||||
rx: &async_channel::Receiver<codex_protocol::protocol::Event>,
|
||||
status: GuardianAssessmentStatus,
|
||||
) -> GuardianAssessmentEvent {
|
||||
tokio::time::timeout(Duration::from_secs(5), async {
|
||||
loop {
|
||||
let event = rx.recv().await.expect("event channel should remain open");
|
||||
if let EventMsg::GuardianAssessment(assessment) = event.msg
|
||||
&& assessment.status == status
|
||||
{
|
||||
return assessment;
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("guardian assessment event should arrive")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn guardian_review_override_validates_pending_turn_id() {
|
||||
let (session, _) = crate::codex::make_session_and_context().await;
|
||||
let override_rx = session
|
||||
.guardian_review_session
|
||||
.register_pending_override("review-1".to_string(), "turn-1".to_string())
|
||||
.await;
|
||||
|
||||
let err = session
|
||||
.guardian_review_session
|
||||
.override_review(
|
||||
"review-1",
|
||||
"turn-2",
|
||||
GuardianReviewOverrideDecision::Approve,
|
||||
)
|
||||
.await
|
||||
.expect_err("turn mismatch should fail");
|
||||
assert!(matches!(
|
||||
err,
|
||||
super::review_session::GuardianReviewOverrideError::TurnMismatch { expected_turn_id }
|
||||
if expected_turn_id == "turn-1"
|
||||
));
|
||||
|
||||
session
|
||||
.guardian_review_session
|
||||
.override_review(
|
||||
"review-1",
|
||||
"turn-1",
|
||||
GuardianReviewOverrideDecision::Approve,
|
||||
)
|
||||
.await
|
||||
.expect("expected turn should be accepted");
|
||||
assert_eq!(
|
||||
override_rx.await.expect("override decision"),
|
||||
GuardianReviewOverrideDecision::Approve
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn guardian_review_decline_override_preempts_automatic_review() -> anyhow::Result<()> {
|
||||
let (_gate_tx, gate_rx) = tokio::sync::oneshot::channel();
|
||||
let (server, _) = start_streaming_sse_server(vec![vec![
|
||||
StreamingSseChunk {
|
||||
gate: None,
|
||||
body: sse(vec![ev_response_created("resp-guardian-override")]),
|
||||
},
|
||||
StreamingSseChunk {
|
||||
gate: Some(gate_rx),
|
||||
body: sse(vec![
|
||||
ev_assistant_message(
|
||||
"msg-guardian-override",
|
||||
&serde_json::json!({
|
||||
"risk_level": "low",
|
||||
"user_authorization": "high",
|
||||
"outcome": "allow",
|
||||
"rationale": "guardian would allow",
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
ev_completed("resp-guardian-override"),
|
||||
]),
|
||||
},
|
||||
]])
|
||||
.await;
|
||||
let (mut session, mut turn, rx) = crate::codex::make_session_and_context_with_rx().await;
|
||||
let mut config = (*turn.config).clone();
|
||||
config.model_provider.base_url = Some(format!("{}/v1", server.uri()));
|
||||
config.user_instructions = None;
|
||||
let config = Arc::new(config);
|
||||
let models_manager = Arc::new(test_support::models_manager_with_provider(
|
||||
config.codex_home.clone(),
|
||||
Arc::clone(&session.services.auth_manager),
|
||||
config.model_provider.clone(),
|
||||
));
|
||||
Arc::get_mut(&mut session)
|
||||
.expect("session should be uniquely owned")
|
||||
.services
|
||||
.models_manager = models_manager;
|
||||
let turn_mut = Arc::get_mut(&mut turn).expect("turn should be uniquely owned");
|
||||
turn_mut.config = Arc::clone(&config);
|
||||
turn_mut.provider = config.model_provider.clone();
|
||||
turn_mut.user_instructions = None;
|
||||
seed_guardian_parent_history(&session, &turn).await;
|
||||
|
||||
let session_for_review = Arc::clone(&session);
|
||||
let turn_for_review = Arc::clone(&turn);
|
||||
let review = tokio::spawn(async move {
|
||||
review_approval_request(
|
||||
&session_for_review,
|
||||
&turn_for_review,
|
||||
"review-shell-guardian-override".to_string(),
|
||||
guardian_override_test_request("shell-guardian-override"),
|
||||
/*retry_reason*/ None,
|
||||
)
|
||||
.await
|
||||
});
|
||||
let started =
|
||||
recv_guardian_assessment_with_status(&rx, GuardianAssessmentStatus::InProgress).await;
|
||||
session
|
||||
.guardian_review_session
|
||||
.override_review(
|
||||
&started.id,
|
||||
&started.turn_id,
|
||||
GuardianReviewOverrideDecision::Decline,
|
||||
)
|
||||
.await
|
||||
.expect("override should be accepted");
|
||||
|
||||
assert_eq!(review.await?, ReviewDecision::Denied);
|
||||
let completed =
|
||||
recv_guardian_assessment_with_status(&rx, GuardianAssessmentStatus::Denied).await;
|
||||
assert_eq!(completed.id, started.id);
|
||||
assert_eq!(
|
||||
completed.decision_source,
|
||||
Some(GuardianAssessmentDecisionSource::User)
|
||||
);
|
||||
assert_eq!(completed.risk_level, None);
|
||||
assert_eq!(completed.user_authorization, None);
|
||||
assert_eq!(
|
||||
guardian_rejection_message(session.as_ref(), &started.id).await,
|
||||
concat!(
|
||||
"This action was declined before the automatic approval review completed.\n",
|
||||
"Reason: User preempted the automatic guardian review and declined this review.\n",
|
||||
"The agent must not attempt to achieve the same outcome via workaround, ",
|
||||
"indirect execution, or policy circumvention. ",
|
||||
"Proceed only with a materially safer alternative, ",
|
||||
"or if the user explicitly approves the action after being informed of the risk. ",
|
||||
"Otherwise, stop and request user input."
|
||||
)
|
||||
);
|
||||
server.shutdown().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cancelled_guardian_review_emits_terminal_abort_without_warning() {
|
||||
let (session, turn, rx) = crate::codex::make_session_and_context_with_rx().await;
|
||||
|
||||
@@ -112,6 +112,15 @@ pub enum GuardianAssessmentStatus {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GuardianAssessmentDecisionSource {
|
||||
Agent,
|
||||
#[serde(alias = "client_override")]
|
||||
User,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum GuardianReviewOverrideDecision {
|
||||
Approve,
|
||||
Decline,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
||||
|
||||
@@ -69,6 +69,7 @@ pub use crate::approvals::GuardianAssessmentDecisionSource;
|
||||
pub use crate::approvals::GuardianAssessmentEvent;
|
||||
pub use crate::approvals::GuardianAssessmentStatus;
|
||||
pub use crate::approvals::GuardianCommandSource;
|
||||
pub use crate::approvals::GuardianReviewOverrideDecision;
|
||||
pub use crate::approvals::GuardianRiskLevel;
|
||||
pub use crate::approvals::GuardianUserAuthorization;
|
||||
pub use crate::approvals::NetworkApprovalContext;
|
||||
|
||||
@@ -3402,11 +3402,16 @@ impl ChatWidget {
|
||||
}
|
||||
|
||||
if ev.status == GuardianAssessmentStatus::Approved {
|
||||
let actor = if ev.decision_source == Some(GuardianAssessmentDecisionSource::User) {
|
||||
history_cell::ApprovalDecisionActor::User
|
||||
} else {
|
||||
history_cell::ApprovalDecisionActor::Guardian
|
||||
};
|
||||
let cell = if let Some(command) = guardian_command(&ev.action) {
|
||||
history_cell::new_approval_decision_cell(
|
||||
command,
|
||||
codex_protocol::protocol::ReviewDecision::Approved,
|
||||
history_cell::ApprovalDecisionActor::Guardian,
|
||||
actor,
|
||||
)
|
||||
} else if let Some(summary) = guardian_action_summary(&ev.action) {
|
||||
history_cell::new_guardian_approved_action_request(summary)
|
||||
@@ -3424,11 +3429,16 @@ impl ChatWidget {
|
||||
if ev.status != GuardianAssessmentStatus::Denied {
|
||||
return;
|
||||
}
|
||||
let actor = if ev.decision_source == Some(GuardianAssessmentDecisionSource::User) {
|
||||
history_cell::ApprovalDecisionActor::User
|
||||
} else {
|
||||
history_cell::ApprovalDecisionActor::Guardian
|
||||
};
|
||||
let cell = if let Some(command) = guardian_command(&ev.action) {
|
||||
history_cell::new_approval_decision_cell(
|
||||
command,
|
||||
codex_protocol::protocol::ReviewDecision::Denied,
|
||||
history_cell::ApprovalDecisionActor::Guardian,
|
||||
actor,
|
||||
)
|
||||
} else {
|
||||
match &ev.action {
|
||||
@@ -6953,6 +6963,9 @@ impl ChatWidget {
|
||||
codex_app_server_protocol::AutoReviewDecisionSource::Agent => {
|
||||
GuardianAssessmentDecisionSource::Agent
|
||||
}
|
||||
codex_app_server_protocol::AutoReviewDecisionSource::User => {
|
||||
GuardianAssessmentDecisionSource::User
|
||||
}
|
||||
}),
|
||||
action: action.into(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user