mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
feat(core) OverrideActiveTurnContext
This commit is contained in:
@@ -3772,6 +3772,44 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TurnOverrideActiveContextParams": {
|
||||
"properties": {
|
||||
"approvalPolicy": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AskForApproval"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Override the active turn approval policy and the default for subsequent turns."
|
||||
},
|
||||
"approvalsReviewer": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ApprovalsReviewer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Override where approval requests are routed for review on the active turn and subsequent turns."
|
||||
},
|
||||
"expectedTurnId": {
|
||||
"description": "Required active turn id precondition. The request fails when it does not match the currently active turn.",
|
||||
"type": "string"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"expectedTurnId",
|
||||
"threadId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TurnStartParams": {
|
||||
"properties": {
|
||||
"approvalPolicy": {
|
||||
@@ -5051,6 +5089,30 @@
|
||||
"title": "Turn/steerRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"turn/overrideActiveContext"
|
||||
],
|
||||
"title": "Turn/overrideActiveContextRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/TurnOverrideActiveContextParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Turn/overrideActiveContextRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
|
||||
+77
@@ -1169,6 +1169,30 @@
|
||||
"title": "Turn/steerRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/v2/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"turn/overrideActiveContext"
|
||||
],
|
||||
"title": "Turn/overrideActiveContextRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/v2/TurnOverrideActiveContextParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Turn/overrideActiveContextRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -16520,6 +16544,59 @@
|
||||
"title": "TurnInterruptResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"TurnOverrideActiveContextParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"approvalPolicy": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/AskForApproval"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Override the active turn approval policy and the default for subsequent turns."
|
||||
},
|
||||
"approvalsReviewer": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/ApprovalsReviewer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Override where approval requests are routed for review on the active turn and subsequent turns."
|
||||
},
|
||||
"expectedTurnId": {
|
||||
"description": "Required active turn id precondition. The request fails when it does not match the currently active turn.",
|
||||
"type": "string"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"expectedTurnId",
|
||||
"threadId"
|
||||
],
|
||||
"title": "TurnOverrideActiveContextParams",
|
||||
"type": "object"
|
||||
},
|
||||
"TurnOverrideActiveContextResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"turnId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"turnId"
|
||||
],
|
||||
"title": "TurnOverrideActiveContextResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"TurnPlanStep": {
|
||||
"properties": {
|
||||
"status": {
|
||||
|
||||
+77
@@ -1857,6 +1857,30 @@
|
||||
"title": "Turn/steerRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/definitions/RequestId"
|
||||
},
|
||||
"method": {
|
||||
"enum": [
|
||||
"turn/overrideActiveContext"
|
||||
],
|
||||
"title": "Turn/overrideActiveContextRequestMethod",
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"$ref": "#/definitions/TurnOverrideActiveContextParams"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"method",
|
||||
"params"
|
||||
],
|
||||
"title": "Turn/overrideActiveContextRequest",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -14414,6 +14438,59 @@
|
||||
"title": "TurnInterruptResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"TurnOverrideActiveContextParams": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"approvalPolicy": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AskForApproval"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Override the active turn approval policy and the default for subsequent turns."
|
||||
},
|
||||
"approvalsReviewer": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ApprovalsReviewer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Override where approval requests are routed for review on the active turn and subsequent turns."
|
||||
},
|
||||
"expectedTurnId": {
|
||||
"description": "Required active turn id precondition. The request fails when it does not match the currently active turn.",
|
||||
"type": "string"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"expectedTurnId",
|
||||
"threadId"
|
||||
],
|
||||
"title": "TurnOverrideActiveContextParams",
|
||||
"type": "object"
|
||||
},
|
||||
"TurnOverrideActiveContextResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"turnId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"turnId"
|
||||
],
|
||||
"title": "TurnOverrideActiveContextResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"TurnPlanStep": {
|
||||
"properties": {
|
||||
"status": {
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"ApprovalsReviewer": {
|
||||
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `guardian_subagent` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request.",
|
||||
"enum": [
|
||||
"user",
|
||||
"guardian_subagent"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AskForApproval": {
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": [
|
||||
"untrusted",
|
||||
"on-failure",
|
||||
"on-request",
|
||||
"never"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"granular": {
|
||||
"properties": {
|
||||
"mcp_elicitations": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"request_permissions": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"rules": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sandbox_approval": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"skill_approval": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"mcp_elicitations",
|
||||
"rules",
|
||||
"sandbox_approval"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"granular"
|
||||
],
|
||||
"title": "GranularAskForApproval",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"approvalPolicy": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AskForApproval"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Override the active turn approval policy and the default for subsequent turns."
|
||||
},
|
||||
"approvalsReviewer": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/ApprovalsReviewer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Override where approval requests are routed for review on the active turn and subsequent turns."
|
||||
},
|
||||
"expectedTurnId": {
|
||||
"description": "Required active turn id precondition. The request fails when it does not match the currently active turn.",
|
||||
"type": "string"
|
||||
},
|
||||
"threadId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"expectedTurnId",
|
||||
"threadId"
|
||||
],
|
||||
"title": "TurnOverrideActiveContextParams",
|
||||
"type": "object"
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"turnId": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"turnId"
|
||||
],
|
||||
"title": "TurnOverrideActiveContextResponse",
|
||||
"type": "object"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
+22
@@ -0,0 +1,22 @@
|
||||
// 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 { ApprovalsReviewer } from "./ApprovalsReviewer";
|
||||
import type { AskForApproval } from "./AskForApproval";
|
||||
|
||||
export type TurnOverrideActiveContextParams = { threadId: string,
|
||||
/**
|
||||
* Required active turn id precondition. The request fails when it does not
|
||||
* match the currently active turn.
|
||||
*/
|
||||
expectedTurnId: string,
|
||||
/**
|
||||
* Override the active turn approval policy and the default for subsequent
|
||||
* turns.
|
||||
*/
|
||||
approvalPolicy?: AskForApproval | null,
|
||||
/**
|
||||
* Override where approval requests are routed for review on the active
|
||||
* turn and subsequent turns.
|
||||
*/
|
||||
approvalsReviewer?: ApprovalsReviewer | null, };
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// 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.
|
||||
|
||||
export type TurnOverrideActiveContextResponse = { turnId: string, };
|
||||
@@ -381,6 +381,8 @@ export type { TurnEnvironmentParams } from "./TurnEnvironmentParams";
|
||||
export type { TurnError } from "./TurnError";
|
||||
export type { TurnInterruptParams } from "./TurnInterruptParams";
|
||||
export type { TurnInterruptResponse } from "./TurnInterruptResponse";
|
||||
export type { TurnOverrideActiveContextParams } from "./TurnOverrideActiveContextParams";
|
||||
export type { TurnOverrideActiveContextResponse } from "./TurnOverrideActiveContextResponse";
|
||||
export type { TurnPlanStep } from "./TurnPlanStep";
|
||||
export type { TurnPlanStepStatus } from "./TurnPlanStepStatus";
|
||||
export type { TurnPlanUpdatedNotification } from "./TurnPlanUpdatedNotification";
|
||||
|
||||
@@ -439,6 +439,11 @@ client_request_definitions! {
|
||||
inspect_params: true,
|
||||
response: v2::TurnSteerResponse,
|
||||
},
|
||||
TurnOverrideActiveContext => "turn/overrideActiveContext" {
|
||||
params: v2::TurnOverrideActiveContextParams,
|
||||
inspect_params: true,
|
||||
response: v2::TurnOverrideActiveContextResponse,
|
||||
},
|
||||
TurnInterrupt => "turn/interrupt" {
|
||||
params: v2::TurnInterruptParams,
|
||||
response: v2::TurnInterruptResponse,
|
||||
|
||||
@@ -4950,6 +4950,34 @@ pub struct TurnSteerResponse {
|
||||
pub turn_id: String,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS, ExperimentalApi,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnOverrideActiveContextParams {
|
||||
pub thread_id: String,
|
||||
/// Required active turn id precondition. The request fails when it does not
|
||||
/// match the currently active turn.
|
||||
pub expected_turn_id: String,
|
||||
/// Override the active turn approval policy and the default for subsequent
|
||||
/// turns.
|
||||
#[experimental(nested)]
|
||||
#[ts(optional = nullable)]
|
||||
pub approval_policy: Option<AskForApproval>,
|
||||
/// Override where approval requests are routed for review on the active
|
||||
/// turn and subsequent turns.
|
||||
#[ts(optional = nullable)]
|
||||
pub approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct TurnOverrideActiveContextResponse {
|
||||
pub turn_id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -8826,6 +8854,30 @@ mod tests {
|
||||
assert_eq!(reason, Some("askForApproval.granular"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_request_turn_override_active_context_granular_approval_policy_is_marked_experimental()
|
||||
{
|
||||
let reason = crate::experimental_api::ExperimentalApi::experimental_reason(
|
||||
&crate::ClientRequest::TurnOverrideActiveContext {
|
||||
request_id: crate::RequestId::Integer(5),
|
||||
params: TurnOverrideActiveContextParams {
|
||||
thread_id: "thr_123".to_string(),
|
||||
expected_turn_id: "turn_123".to_string(),
|
||||
approval_policy: Some(AskForApproval::Granular {
|
||||
sandbox_approval: false,
|
||||
rules: true,
|
||||
skill_approval: false,
|
||||
request_permissions: false,
|
||||
mcp_elicitations: true,
|
||||
}),
|
||||
approvals_reviewer: None,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(reason, Some("askForApproval.granular"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_server_elicitation_response_round_trips_rmcp_result() {
|
||||
let rmcp_result = rmcp::model::CreateElicitationResult {
|
||||
|
||||
@@ -158,6 +158,7 @@ Example with notification opt-out:
|
||||
- `turn/start` — add user input to a thread and begin Codex generation; responds with the initial `turn` object and streams `turn/started`, `item/*`, and `turn/completed` notifications. For `collaborationMode`, `settings.developer_instructions: null` means "use built-in instructions for the selected mode".
|
||||
- `thread/inject_items` — append raw Responses API items to a loaded thread’s model-visible history without starting a user turn; returns `{}` on success.
|
||||
- `turn/steer` — add user input to an already in-flight regular turn without starting a new turn; returns the active `turnId` that accepted the input. Review and manual compaction turns reject `turn/steer`.
|
||||
- `turn/overrideActiveContext` — update approval policy or approvals reviewer for the currently active regular turn and subsequent turns; returns the active `turnId`.
|
||||
- `turn/interrupt` — request cancellation of an in-flight turn by `(thread_id, turn_id)`; success is an empty `{}` response and the turn finishes with `status: "interrupted"`.
|
||||
- `thread/realtime/start` — start a thread-scoped realtime session (experimental); pass `outputModality: "text"` or `outputModality: "audio"` to choose model output, returns `{}` and streams `thread/realtime/*` notifications. Omit `transport` for the websocket transport, or pass `{ "type": "webrtc", "sdp": "..." }` to create a WebRTC session from a browser-generated SDP offer; the remote answer SDP is emitted as `thread/realtime/sdp`.
|
||||
- `thread/realtime/appendAudio` — append an input audio chunk to the active realtime session (experimental); returns `{}`.
|
||||
@@ -735,6 +736,23 @@ not emit `turn/started` and does not accept turn context overrides.
|
||||
active turn, or the active turn kind does not accept same-turn steering (for example review or
|
||||
manual compaction), the request fails with an `invalid request` error.
|
||||
|
||||
Use `turn/overrideActiveContext` to change approval behavior for an already in-flight regular turn
|
||||
without sending additional input. The same values become the defaults for subsequent turns.
|
||||
|
||||
```json
|
||||
{ "method": "turn/overrideActiveContext", "id": 33, "params": {
|
||||
"threadId": "thr_123",
|
||||
"expectedTurnId": "turn_456",
|
||||
"approvalPolicy": "never",
|
||||
"approvalsReviewer": "user"
|
||||
} }
|
||||
{ "id": 33, "result": { "turnId": "turn_456" } }
|
||||
```
|
||||
|
||||
`expectedTurnId` is required. At least one of `approvalPolicy` or `approvalsReviewer` must be set.
|
||||
If there is no active turn, `expectedTurnId` does not match the active turn, or the active turn kind
|
||||
does not support active context overrides, the request fails with an `invalid request` error.
|
||||
|
||||
### Example: Request a code review
|
||||
|
||||
Use `review/start` to run Codex’s reviewer on the currently checked-out project. The request takes the thread id plus a `target` describing what should be reviewed:
|
||||
|
||||
@@ -201,6 +201,8 @@ use codex_app_server_protocol::Turn;
|
||||
use codex_app_server_protocol::TurnError;
|
||||
use codex_app_server_protocol::TurnInterruptParams;
|
||||
use codex_app_server_protocol::TurnInterruptResponse;
|
||||
use codex_app_server_protocol::TurnOverrideActiveContextParams;
|
||||
use codex_app_server_protocol::TurnOverrideActiveContextResponse;
|
||||
use codex_app_server_protocol::TurnStartParams;
|
||||
use codex_app_server_protocol::TurnStartResponse;
|
||||
use codex_app_server_protocol::TurnStatus;
|
||||
@@ -1016,6 +1018,10 @@ impl CodexMessageProcessor {
|
||||
self.turn_steer(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::TurnOverrideActiveContext { request_id, params } => {
|
||||
self.turn_override_active_context(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
}
|
||||
ClientRequest::TurnInterrupt { request_id, params } => {
|
||||
self.turn_interrupt(to_connection_request_id(request_id), params)
|
||||
.await;
|
||||
@@ -7078,6 +7084,77 @@ impl CodexMessageProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
async fn turn_override_active_context(
|
||||
&self,
|
||||
request_id: ConnectionRequestId,
|
||||
params: TurnOverrideActiveContextParams,
|
||||
) {
|
||||
let (_, thread) = match self.load_thread(¶ms.thread_id).await {
|
||||
Ok(v) => v,
|
||||
Err(error) => {
|
||||
self.track_error_response(&request_id, &error, /*error_type*/ None);
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if params.expected_turn_id.is_empty() {
|
||||
self.send_invalid_request_error(
|
||||
request_id,
|
||||
"expectedTurnId must not be empty".to_string(),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
if params.approval_policy.is_none() && params.approvals_reviewer.is_none() {
|
||||
self.send_invalid_request_error(
|
||||
request_id,
|
||||
"at least one of approvalPolicy or approvalsReviewer must be set".to_string(),
|
||||
)
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
self.outgoing
|
||||
.record_request_turn_id(&request_id, ¶ms.expected_turn_id)
|
||||
.await;
|
||||
|
||||
match thread
|
||||
.override_active_turn_context(
|
||||
¶ms.expected_turn_id,
|
||||
params.approval_policy.map(AskForApproval::to_core),
|
||||
params
|
||||
.approvals_reviewer
|
||||
.map(codex_app_server_protocol::ApprovalsReviewer::to_core),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(turn_id) => {
|
||||
let response = TurnOverrideActiveContextResponse { turn_id };
|
||||
if self.config.features.enabled(Feature::GeneralAnalytics) {
|
||||
self.analytics_events_client.track_response(
|
||||
request_id.connection_id.0,
|
||||
ClientResponse::TurnOverrideActiveContext {
|
||||
request_id: request_id.request_id.clone(),
|
||||
response: response.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
self.outgoing.send_response(request_id, response).await;
|
||||
}
|
||||
Err(CodexErr::InvalidRequest(message)) => {
|
||||
self.send_invalid_request_error(request_id, message).await;
|
||||
}
|
||||
Err(err) => {
|
||||
self.send_internal_error(
|
||||
request_id,
|
||||
format!("failed to override active turn context: {err}"),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn prepare_realtime_conversation_thread(
|
||||
&self,
|
||||
request_id: ConnectionRequestId,
|
||||
|
||||
@@ -37,7 +37,7 @@ pub(crate) async fn apply_patch(
|
||||
) -> InternalApplyPatchInvocation {
|
||||
match assess_patch_safety(
|
||||
&action,
|
||||
turn_context.approval_policy.value(),
|
||||
turn_context.approval_policy(),
|
||||
turn_context.sandbox_policy.get(),
|
||||
file_system_sandbox_policy,
|
||||
&turn_context.cwd,
|
||||
|
||||
@@ -116,6 +116,18 @@ impl CodexThread {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn override_active_turn_context(
|
||||
&self,
|
||||
expected_turn_id: &str,
|
||||
approval_policy: Option<AskForApproval>,
|
||||
approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
) -> CodexResult<String> {
|
||||
self.codex
|
||||
.session
|
||||
.override_active_turn_context(expected_turn_id, approval_policy, approvals_reviewer)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn set_app_server_client_info(
|
||||
&self,
|
||||
app_server_client_name: Option<String>,
|
||||
|
||||
@@ -50,7 +50,7 @@ fn build_permissions_update_item(
|
||||
|
||||
let prev = previous?;
|
||||
if prev.sandbox_policy == *next.sandbox_policy.get()
|
||||
&& prev.approval_policy == next.approval_policy.value()
|
||||
&& prev.approval_policy == next.approval_policy()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
@@ -58,8 +58,8 @@ fn build_permissions_update_item(
|
||||
Some(
|
||||
PermissionsInstructions::from_policy(
|
||||
next.sandbox_policy.get(),
|
||||
next.approval_policy.value(),
|
||||
next.config.approvals_reviewer,
|
||||
next.approval_policy(),
|
||||
next.approvals_reviewer(),
|
||||
exec_policy,
|
||||
&next.cwd,
|
||||
next.features.enabled(Feature::ExecPermissionApprovals),
|
||||
|
||||
@@ -142,9 +142,9 @@ fn guardian_risk_level_str(level: GuardianRiskLevel) -> &'static str {
|
||||
/// earlier in the flow.
|
||||
pub(crate) fn routes_approval_to_guardian(turn: &TurnContext) -> bool {
|
||||
matches!(
|
||||
turn.approval_policy.value(),
|
||||
turn.approval_policy(),
|
||||
AskForApproval::OnRequest | AskForApproval::Granular(_)
|
||||
) && turn.config.approvals_reviewer == ApprovalsReviewer::GuardianSubagent
|
||||
) && turn.approvals_reviewer() == ApprovalsReviewer::GuardianSubagent
|
||||
}
|
||||
|
||||
pub(crate) fn is_guardian_reviewer_source(
|
||||
|
||||
@@ -479,7 +479,7 @@ fn hook_run_metric_tags(run: &HookRunSummary) -> [(&'static str, &'static str);
|
||||
}
|
||||
|
||||
fn hook_permission_mode(turn_context: &TurnContext) -> String {
|
||||
match turn_context.approval_policy.value() {
|
||||
match turn_context.approval_policy() {
|
||||
AskForApproval::Never => "bypassPermissions",
|
||||
AskForApproval::UnlessTrusted
|
||||
| AskForApproval::OnFailure
|
||||
|
||||
@@ -220,7 +220,7 @@ async fn should_install_mcp_dependencies(
|
||||
cancellation_token: &CancellationToken,
|
||||
) -> bool {
|
||||
if mcp_permission_prompt_is_auto_approved(
|
||||
turn_context.approval_policy.value(),
|
||||
turn_context.approval_policy(),
|
||||
turn_context.sandbox_policy.get(),
|
||||
) {
|
||||
return true;
|
||||
|
||||
@@ -818,7 +818,7 @@ async fn maybe_request_mcp_tool_approval(
|
||||
approval_mode: AppToolApproval,
|
||||
) -> Option<McpToolApprovalDecision> {
|
||||
if mcp_permission_prompt_is_auto_approved(
|
||||
turn_context.approval_policy.value(),
|
||||
turn_context.approval_policy(),
|
||||
turn_context.sandbox_policy.get(),
|
||||
) {
|
||||
return None;
|
||||
|
||||
@@ -4,6 +4,8 @@ use crate::realtime_conversation::handle_start as handle_realtime_conversation_s
|
||||
use crate::realtime_conversation::handle_text as handle_realtime_conversation_text;
|
||||
use async_channel::Receiver;
|
||||
use codex_otel::set_parent_from_w3c_trace_context;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::Submission;
|
||||
use tracing::Instrument;
|
||||
use tracing::debug_span;
|
||||
@@ -113,6 +115,28 @@ pub async fn override_turn_context(sess: &Session, sub_id: String, updates: Sess
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn override_active_turn_context(
|
||||
sess: &Session,
|
||||
sub_id: String,
|
||||
turn_id: String,
|
||||
approval_policy: Option<AskForApproval>,
|
||||
approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
) {
|
||||
if let Err(err) = sess
|
||||
.override_active_turn_context(&turn_id, approval_policy, approvals_reviewer)
|
||||
.await
|
||||
{
|
||||
sess.send_event_raw(Event {
|
||||
id: sub_id,
|
||||
msg: EventMsg::Error(ErrorEvent {
|
||||
message: err.to_string(),
|
||||
codex_error_info: Some(CodexErrorInfo::BadRequest),
|
||||
}),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn user_input_or_turn(sess: &Arc<Session>, sub_id: String, op: Op) {
|
||||
user_input_or_turn_inner(
|
||||
sess,
|
||||
@@ -1099,6 +1123,21 @@ pub(super) async fn submission_loop(
|
||||
.await;
|
||||
false
|
||||
}
|
||||
Op::OverrideActiveTurnContext {
|
||||
turn_id,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
} => {
|
||||
override_active_turn_context(
|
||||
&sess,
|
||||
sub.id.clone(),
|
||||
turn_id,
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
)
|
||||
.await;
|
||||
false
|
||||
}
|
||||
Op::UserInput { .. } | Op::UserTurn { .. } => {
|
||||
user_input_or_turn(&sess, sub.id.clone(), sub.op).await;
|
||||
false
|
||||
|
||||
@@ -225,11 +225,12 @@ impl Session {
|
||||
guard.cancel();
|
||||
*guard = CancellationToken::new();
|
||||
}
|
||||
let approval_policy = turn_context.approval_policy_constrained();
|
||||
let (refreshed_manager, cancel_token) = McpConnectionManager::new(
|
||||
&mcp_servers,
|
||||
store_mode,
|
||||
auth_statuses,
|
||||
&turn_context.config.permissions.approval_policy,
|
||||
&approval_policy,
|
||||
turn_context.sub_id.clone(),
|
||||
self.get_tx_event(),
|
||||
turn_context.sandbox_policy.get().clone(),
|
||||
|
||||
@@ -1310,6 +1310,85 @@ impl Session {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn override_active_turn_context(
|
||||
&self,
|
||||
expected_turn_id: &str,
|
||||
approval_policy: Option<AskForApproval>,
|
||||
approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
) -> CodexResult<String> {
|
||||
if expected_turn_id.is_empty() {
|
||||
return Err(CodexErr::InvalidRequest(
|
||||
"expected turn id must not be empty".to_string(),
|
||||
));
|
||||
}
|
||||
if approval_policy.is_none() && approvals_reviewer.is_none() {
|
||||
return Err(CodexErr::InvalidRequest(
|
||||
"at least one of approval_policy or approvals_reviewer must be set".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let turn_contexts = {
|
||||
let active = self.active_turn.lock().await;
|
||||
let active_turn = active.as_ref().ok_or_else(|| {
|
||||
CodexErr::InvalidRequest("no active turn to override".to_string())
|
||||
})?;
|
||||
|
||||
let Some((active_turn_id, active_task)) = active_turn.tasks.first() else {
|
||||
return Err(CodexErr::InvalidRequest(
|
||||
"no active turn to override".to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
if expected_turn_id != active_turn_id {
|
||||
return Err(CodexErr::InvalidRequest(format!(
|
||||
"expected active turn id `{expected_turn_id}` but found `{active_turn_id}`"
|
||||
)));
|
||||
}
|
||||
|
||||
if active_task.kind != crate::state::TaskKind::Regular {
|
||||
return Err(CodexErr::InvalidRequest(
|
||||
"only regular turns support active context overrides".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
active_turn
|
||||
.tasks
|
||||
.values()
|
||||
.map(|task| Arc::clone(&task.turn_context))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let updated_approval_policy = {
|
||||
let updates = SessionSettingsUpdate {
|
||||
approval_policy,
|
||||
approvals_reviewer,
|
||||
..Default::default()
|
||||
};
|
||||
let mut state = self.state.lock().await;
|
||||
let updated = state
|
||||
.session_configuration
|
||||
.apply(&updates)
|
||||
.map_err(|err| CodexErr::InvalidRequest(err.to_string()))?;
|
||||
let updated_approval_policy = approval_policy.map(|_| updated.approval_policy.clone());
|
||||
state.session_configuration = updated;
|
||||
updated_approval_policy
|
||||
};
|
||||
|
||||
for turn_context in turn_contexts {
|
||||
turn_context.override_active_approval_context(approval_policy, approvals_reviewer);
|
||||
}
|
||||
|
||||
if let Some(approval_policy) = updated_approval_policy {
|
||||
self.services
|
||||
.mcp_connection_manager
|
||||
.read()
|
||||
.await
|
||||
.set_approval_policy(&approval_policy);
|
||||
}
|
||||
|
||||
Ok(expected_turn_id.to_string())
|
||||
}
|
||||
|
||||
pub(crate) async fn set_session_startup_prewarm(
|
||||
&self,
|
||||
startup_prewarm: SessionStartupPrewarmHandle,
|
||||
@@ -1882,7 +1961,7 @@ impl Session {
|
||||
cwd: AbsolutePathBuf,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> Option<RequestPermissionsResponse> {
|
||||
match turn_context.as_ref().approval_policy.value() {
|
||||
match turn_context.as_ref().approval_policy() {
|
||||
AskForApproval::Never => {
|
||||
return Some(RequestPermissionsResponse {
|
||||
permissions: RequestPermissionProfile::default(),
|
||||
@@ -2412,8 +2491,8 @@ impl Session {
|
||||
developer_sections.push(
|
||||
PermissionsInstructions::from_policy(
|
||||
turn_context.sandbox_policy.get(),
|
||||
turn_context.approval_policy.value(),
|
||||
turn_context.config.approvals_reviewer,
|
||||
turn_context.approval_policy(),
|
||||
turn_context.approvals_reviewer(),
|
||||
self.services.exec_policy.current().as_ref(),
|
||||
&turn_context.cwd,
|
||||
turn_context
|
||||
|
||||
@@ -63,6 +63,7 @@ pub(super) async fn spawn_review_thread(
|
||||
let mut per_turn_config = (*config).clone();
|
||||
per_turn_config.model = Some(model.clone());
|
||||
per_turn_config.features = review_features.clone();
|
||||
per_turn_config.approvals_reviewer = parent_turn_context.approvals_reviewer();
|
||||
if let Err(err) = per_turn_config.web_search_mode.set(review_web_search_mode) {
|
||||
let fallback_value = per_turn_config.web_search_mode.value();
|
||||
tracing::warn!(
|
||||
@@ -122,7 +123,8 @@ pub(super) async fn spawn_review_thread(
|
||||
compact_prompt: parent_turn_context.compact_prompt.clone(),
|
||||
collaboration_mode: parent_turn_context.collaboration_mode.clone(),
|
||||
personality: parent_turn_context.personality,
|
||||
approval_policy: parent_turn_context.approval_policy.clone(),
|
||||
approval_policy: parent_turn_context.approval_policy_constrained(),
|
||||
active_overrides: Default::default(),
|
||||
sandbox_policy: parent_turn_context.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: parent_turn_context.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: parent_turn_context.network_sandbox_policy,
|
||||
|
||||
@@ -3723,6 +3723,15 @@ fn op_kind_distinguishes_turn_ops() {
|
||||
.kind(),
|
||||
"override_turn_context"
|
||||
);
|
||||
assert_eq!(
|
||||
Op::OverrideActiveTurnContext {
|
||||
turn_id: "turn-1".to_string(),
|
||||
approval_policy: None,
|
||||
approvals_reviewer: None,
|
||||
}
|
||||
.kind(),
|
||||
"override_active_turn_context"
|
||||
);
|
||||
assert_eq!(
|
||||
Op::UserInput {
|
||||
environments: None,
|
||||
@@ -5681,6 +5690,84 @@ async fn steer_input_requires_active_turn() {
|
||||
assert!(matches!(err, SteerInputError::NoActiveTurn(_)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn override_active_turn_context_updates_current_turn_and_defaults() {
|
||||
let (sess, tc, _rx) = make_session_and_context_with_rx().await;
|
||||
let input = vec![UserInput::Text {
|
||||
text: "hello".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}];
|
||||
sess.spawn_task(
|
||||
Arc::clone(&tc),
|
||||
input,
|
||||
NeverEndingTask {
|
||||
kind: TaskKind::Regular,
|
||||
listen_to_cancellation_token: false,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let turn_id = sess
|
||||
.override_active_turn_context(
|
||||
&tc.sub_id,
|
||||
Some(AskForApproval::Never),
|
||||
Some(ApprovalsReviewer::GuardianSubagent),
|
||||
)
|
||||
.await
|
||||
.expect("active context override should succeed");
|
||||
|
||||
assert_eq!(turn_id, tc.sub_id);
|
||||
assert_eq!(tc.approval_policy(), AskForApproval::Never);
|
||||
assert_eq!(tc.approvals_reviewer(), ApprovalsReviewer::GuardianSubagent);
|
||||
|
||||
let state = sess.state.lock().await;
|
||||
assert_eq!(
|
||||
state.session_configuration.approval_policy.value(),
|
||||
AskForApproval::Never
|
||||
);
|
||||
assert_eq!(
|
||||
state.session_configuration.approvals_reviewer,
|
||||
ApprovalsReviewer::GuardianSubagent
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn override_active_turn_context_rejects_missing_or_wrong_active_turn() {
|
||||
let (sess, tc, _rx) = make_session_and_context_with_rx().await;
|
||||
|
||||
let err = sess
|
||||
.override_active_turn_context(&tc.sub_id, Some(AskForApproval::Never), None)
|
||||
.await
|
||||
.expect_err("override without active turn should fail");
|
||||
assert!(matches!(err, CodexErr::InvalidRequest(_)));
|
||||
|
||||
let input = vec![UserInput::Text {
|
||||
text: "hello".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}];
|
||||
sess.spawn_task(
|
||||
Arc::clone(&tc),
|
||||
input,
|
||||
NeverEndingTask {
|
||||
kind: TaskKind::Regular,
|
||||
listen_to_cancellation_token: false,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let err = sess
|
||||
.override_active_turn_context("other-turn", Some(AskForApproval::Never), None)
|
||||
.await
|
||||
.expect_err("mismatched turn id should fail");
|
||||
assert!(matches!(err, CodexErr::InvalidRequest(_)));
|
||||
|
||||
let err = sess
|
||||
.override_active_turn_context(&tc.sub_id, None, None)
|
||||
.await
|
||||
.expect_err("empty override should fail");
|
||||
assert!(matches!(err, CodexErr::InvalidRequest(_)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn steer_input_enforces_expected_turn_id() {
|
||||
let (sess, tc, _rx) = make_session_and_context_with_rx().await;
|
||||
|
||||
@@ -507,7 +507,7 @@ pub(crate) async fn run_turn(
|
||||
|
||||
if !needs_follow_up {
|
||||
last_agent_message = sampling_request_last_agent_message;
|
||||
let stop_hook_permission_mode = match turn_context.approval_policy.value() {
|
||||
let stop_hook_permission_mode = match turn_context.approval_policy() {
|
||||
AskForApproval::Never => "bypassPermissions",
|
||||
AskForApproval::UnlessTrusted
|
||||
| AskForApproval::OnFailure
|
||||
@@ -697,8 +697,8 @@ async fn track_turn_resolved_config_analytics(
|
||||
reasoning_effort: turn_context.reasoning_effort,
|
||||
reasoning_summary: Some(turn_context.reasoning_summary),
|
||||
service_tier: turn_context.config.service_tier,
|
||||
approval_policy: turn_context.approval_policy.value(),
|
||||
approvals_reviewer: turn_context.config.approvals_reviewer,
|
||||
approval_policy: turn_context.approval_policy(),
|
||||
approvals_reviewer: turn_context.approvals_reviewer(),
|
||||
sandbox_network_access: turn_context.network_sandbox_policy.is_enabled(),
|
||||
collaboration_mode: turn_context.collaboration_mode.mode,
|
||||
personality: turn_context.personality,
|
||||
@@ -1871,7 +1871,7 @@ async fn try_run_sampling_request(
|
||||
) -> CodexResult<SamplingRequestResult> {
|
||||
feedback_tags!(
|
||||
model = turn_context.model_info.slug.clone(),
|
||||
approval_policy = turn_context.approval_policy.value(),
|
||||
approval_policy = turn_context.approval_policy(),
|
||||
sandbox_policy = turn_context.sandbox_policy.get(),
|
||||
effort = turn_context.reasoning_effort,
|
||||
auth_mode = sess.services.auth_manager.auth_mode(),
|
||||
|
||||
@@ -3,6 +3,7 @@ use codex_model_provider::SharedModelProvider;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_protocol::protocol::TurnEnvironmentSelection;
|
||||
use codex_sandboxing::policy_transforms::merge_permission_profiles;
|
||||
use std::sync::RwLock;
|
||||
|
||||
pub(super) fn image_generation_tool_auth_allowed(auth_manager: Option<&AuthManager>) -> bool {
|
||||
matches!(
|
||||
@@ -34,6 +35,12 @@ pub(crate) struct TurnEnvironment {
|
||||
pub(crate) cwd: AbsolutePathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub(crate) struct ActiveTurnContextOverrides {
|
||||
approval_policy: Option<AskForApproval>,
|
||||
approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
}
|
||||
|
||||
/// The context needed for a single turn of the thread.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TurnContext {
|
||||
@@ -63,6 +70,7 @@ pub(crate) struct TurnContext {
|
||||
pub(crate) collaboration_mode: CollaborationMode,
|
||||
pub(crate) personality: Option<Personality>,
|
||||
pub(crate) approval_policy: Constrained<AskForApproval>,
|
||||
pub(crate) active_overrides: RwLock<ActiveTurnContextOverrides>,
|
||||
pub(crate) sandbox_policy: Constrained<SandboxPolicy>,
|
||||
pub(crate) file_system_sandbox_policy: FileSystemSandboxPolicy,
|
||||
pub(crate) network_sandbox_policy: NetworkSandboxPolicy,
|
||||
@@ -84,6 +92,47 @@ pub(crate) struct TurnContext {
|
||||
pub(crate) turn_timing_state: Arc<TurnTimingState>,
|
||||
}
|
||||
impl TurnContext {
|
||||
pub(crate) fn approval_policy(&self) -> AskForApproval {
|
||||
self.active_overrides
|
||||
.read()
|
||||
.ok()
|
||||
.and_then(|overrides| overrides.approval_policy)
|
||||
.unwrap_or_else(|| self.approval_policy.value())
|
||||
}
|
||||
|
||||
pub(crate) fn approval_policy_constrained(&self) -> Constrained<AskForApproval> {
|
||||
let mut approval_policy = self.approval_policy.clone();
|
||||
approval_policy
|
||||
.set(self.approval_policy())
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("active approval policy override was validated before being applied: {err}")
|
||||
});
|
||||
approval_policy
|
||||
}
|
||||
|
||||
pub(crate) fn approvals_reviewer(&self) -> ApprovalsReviewer {
|
||||
self.active_overrides
|
||||
.read()
|
||||
.ok()
|
||||
.and_then(|overrides| overrides.approvals_reviewer)
|
||||
.unwrap_or(self.config.approvals_reviewer)
|
||||
}
|
||||
|
||||
pub(crate) fn override_active_approval_context(
|
||||
&self,
|
||||
approval_policy: Option<AskForApproval>,
|
||||
approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
) {
|
||||
if let Ok(mut overrides) = self.active_overrides.write() {
|
||||
if let Some(approval_policy) = approval_policy {
|
||||
overrides.approval_policy = Some(approval_policy);
|
||||
}
|
||||
if let Some(approvals_reviewer) = approvals_reviewer {
|
||||
overrides.approvals_reviewer = Some(approvals_reviewer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn permission_profile(&self) -> PermissionProfile {
|
||||
PermissionProfile::from_runtime_permissions(
|
||||
&self.file_system_sandbox_policy,
|
||||
@@ -144,6 +193,11 @@ impl TurnContext {
|
||||
Some(reasoning_effort),
|
||||
/*developer_instructions*/ None,
|
||||
);
|
||||
let active_overrides = self
|
||||
.active_overrides
|
||||
.read()
|
||||
.map(|overrides| *overrides)
|
||||
.unwrap_or_default();
|
||||
let features = self.features.clone();
|
||||
let tools_config = ToolsConfig::new(&ToolsConfigParams {
|
||||
model_info: &model_info,
|
||||
@@ -197,6 +251,7 @@ impl TurnContext {
|
||||
collaboration_mode,
|
||||
personality: self.personality,
|
||||
approval_policy: self.approval_policy.clone(),
|
||||
active_overrides: RwLock::new(active_overrides),
|
||||
sandbox_policy: self.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: self.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: self.network_sandbox_policy,
|
||||
@@ -270,7 +325,7 @@ impl TurnContext {
|
||||
cwd: self.cwd.to_path_buf(),
|
||||
current_date: self.current_date.clone(),
|
||||
timezone: self.timezone.clone(),
|
||||
approval_policy: self.approval_policy.value(),
|
||||
approval_policy: self.approval_policy(),
|
||||
sandbox_policy: self.sandbox_policy.get().clone(),
|
||||
network: self.turn_context_network_item(),
|
||||
file_system_sandbox_policy: self.non_legacy_file_system_sandbox_policy(),
|
||||
@@ -449,6 +504,7 @@ impl Session {
|
||||
collaboration_mode: session_configuration.collaboration_mode.clone(),
|
||||
personality: session_configuration.personality,
|
||||
approval_policy: session_configuration.approval_policy.clone(),
|
||||
active_overrides: RwLock::new(ActiveTurnContextOverrides::default()),
|
||||
sandbox_policy: session_configuration.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: session_configuration.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: session_configuration.network_sandbox_policy,
|
||||
|
||||
@@ -426,7 +426,7 @@ impl ToolHandler for ApplyPatchHandler {
|
||||
&req,
|
||||
&tool_ctx,
|
||||
turn.as_ref(),
|
||||
turn.approval_policy.value(),
|
||||
turn.approval_policy(),
|
||||
)
|
||||
.await
|
||||
.map(|result| result.output);
|
||||
@@ -534,7 +534,7 @@ pub(crate) async fn intercept_apply_patch(
|
||||
&req,
|
||||
&tool_ctx,
|
||||
turn.as_ref(),
|
||||
turn.approval_policy.value(),
|
||||
turn.approval_policy(),
|
||||
)
|
||||
.await
|
||||
.map(|result| result.output);
|
||||
|
||||
@@ -260,7 +260,7 @@ pub(crate) fn apply_spawn_agent_runtime_overrides(
|
||||
config
|
||||
.permissions
|
||||
.approval_policy
|
||||
.set(turn.approval_policy.value())
|
||||
.set(turn.approval_policy())
|
||||
.map_err(|err| {
|
||||
FunctionCallError::RespondToModel(format!("approval_policy is invalid: {err}"))
|
||||
})?;
|
||||
|
||||
@@ -449,7 +449,7 @@ impl ShellHandler {
|
||||
|| {
|
||||
normalize_and_validate_additional_permissions(
|
||||
additional_permissions_allowed,
|
||||
turn.approval_policy.value(),
|
||||
turn.approval_policy(),
|
||||
effective_additional_permissions.sandbox_permissions,
|
||||
effective_additional_permissions.additional_permissions,
|
||||
effective_additional_permissions.permissions_preapproved,
|
||||
@@ -468,11 +468,11 @@ impl ShellHandler {
|
||||
.requests_sandbox_override()
|
||||
&& !effective_additional_permissions.permissions_preapproved
|
||||
&& !matches!(
|
||||
turn.approval_policy.value(),
|
||||
turn.approval_policy(),
|
||||
codex_protocol::protocol::AskForApproval::OnRequest
|
||||
)
|
||||
{
|
||||
let approval_policy = turn.approval_policy.value();
|
||||
let approval_policy = turn.approval_policy();
|
||||
return Err(FunctionCallError::RespondToModel(format!(
|
||||
"approval policy is {approval_policy:?}; reject command — you should not ask for escalated permissions if the approval policy is {approval_policy:?}"
|
||||
)));
|
||||
@@ -514,7 +514,7 @@ impl ShellHandler {
|
||||
.exec_policy
|
||||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &exec_params.command,
|
||||
approval_policy: turn.approval_policy.value(),
|
||||
approval_policy: turn.approval_policy(),
|
||||
sandbox_policy: turn.sandbox_policy.get(),
|
||||
file_system_sandbox_policy: &turn.file_system_sandbox_policy,
|
||||
sandbox_permissions: if effective_additional_permissions.permissions_preapproved {
|
||||
@@ -559,13 +559,7 @@ impl ShellHandler {
|
||||
tool_name,
|
||||
};
|
||||
let out = orchestrator
|
||||
.run(
|
||||
&mut runtime,
|
||||
&req,
|
||||
&tool_ctx,
|
||||
&turn,
|
||||
turn.approval_policy.value(),
|
||||
)
|
||||
.run(&mut runtime, &req, &tool_ctx, &turn, turn.approval_policy())
|
||||
.await
|
||||
.map(|result| result.output);
|
||||
let event_ctx = ToolEventCtx::new(
|
||||
|
||||
@@ -251,11 +251,11 @@ impl ToolHandler for UnifiedExecHandler {
|
||||
.requests_sandbox_override()
|
||||
&& !effective_additional_permissions.permissions_preapproved
|
||||
&& !matches!(
|
||||
context.turn.approval_policy.value(),
|
||||
context.turn.approval_policy(),
|
||||
codex_protocol::protocol::AskForApproval::OnRequest
|
||||
)
|
||||
{
|
||||
let approval_policy = context.turn.approval_policy.value();
|
||||
let approval_policy = context.turn.approval_policy();
|
||||
manager.release_process_id(process_id).await;
|
||||
return Err(FunctionCallError::RespondToModel(format!(
|
||||
"approval policy is {approval_policy:?}; reject command — you cannot ask for escalated permissions if the approval policy is {approval_policy:?}"
|
||||
@@ -275,7 +275,7 @@ impl ToolHandler for UnifiedExecHandler {
|
||||
|| {
|
||||
normalize_and_validate_additional_permissions(
|
||||
additional_permissions_allowed,
|
||||
context.turn.approval_policy.value(),
|
||||
context.turn.approval_policy(),
|
||||
effective_additional_permissions.sandbox_permissions,
|
||||
effective_additional_permissions.additional_permissions,
|
||||
effective_additional_permissions.permissions_preapproved,
|
||||
|
||||
@@ -359,7 +359,7 @@ impl NetworkApprovalService {
|
||||
.await;
|
||||
return NetworkDecision::deny(REASON_NOT_ALLOWED);
|
||||
}
|
||||
if !allows_network_approval_flow(turn_context.approval_policy.value()) {
|
||||
if !allows_network_approval_flow(turn_context.approval_policy()) {
|
||||
pending.set_decision(PendingApprovalDecision::Deny).await;
|
||||
self.pending_host_approvals.lock().await.remove(&key);
|
||||
self.record_outcome_for_single_active_call(NetworkApprovalOutcome::DeniedByPolicy(
|
||||
|
||||
@@ -199,7 +199,7 @@ pub(super) async fn try_run_zsh_fork(
|
||||
turn: Arc::clone(&ctx.turn),
|
||||
call_id: ctx.call_id.clone(),
|
||||
tool_name: GuardianCommandSource::Shell,
|
||||
approval_policy: ctx.turn.approval_policy.value(),
|
||||
approval_policy: ctx.turn.approval_policy(),
|
||||
sandbox_policy: command_executor.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: command_executor.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: command_executor.network_sandbox_policy,
|
||||
@@ -271,7 +271,7 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
|
||||
turn: Arc::clone(&ctx.turn),
|
||||
call_id: ctx.call_id.clone(),
|
||||
tool_name: GuardianCommandSource::UnifiedExec,
|
||||
approval_policy: ctx.turn.approval_policy.value(),
|
||||
approval_policy: ctx.turn.approval_policy(),
|
||||
sandbox_policy: exec_request.sandbox_policy.clone(),
|
||||
file_system_sandbox_policy: exec_request.file_system_sandbox_policy.clone(),
|
||||
network_sandbox_policy: exec_request.network_sandbox_policy,
|
||||
|
||||
@@ -792,7 +792,7 @@ impl UnifiedExecProcessManager {
|
||||
.exec_policy
|
||||
.create_exec_approval_requirement_for_command(ExecApprovalRequest {
|
||||
command: &request.command,
|
||||
approval_policy: context.turn.approval_policy.value(),
|
||||
approval_policy: context.turn.approval_policy(),
|
||||
sandbox_policy: context.turn.sandbox_policy.get(),
|
||||
file_system_sandbox_policy: &context.turn.file_system_sandbox_policy,
|
||||
sandbox_permissions: if request.additional_permissions_preapproved {
|
||||
@@ -832,7 +832,7 @@ impl UnifiedExecProcessManager {
|
||||
&req,
|
||||
&tool_ctx,
|
||||
&context.turn,
|
||||
context.turn.approval_policy.value(),
|
||||
context.turn.approval_policy(),
|
||||
)
|
||||
.await
|
||||
.map(|result| (result.output, result.deferred_network_approval))
|
||||
|
||||
@@ -569,6 +569,23 @@ pub enum Op {
|
||||
personality: Option<Personality>,
|
||||
},
|
||||
|
||||
/// Override approval settings for the currently active turn and future turns.
|
||||
///
|
||||
/// This is intentionally narrower than [`Op::OverrideTurnContext`]: it only
|
||||
/// affects approval behavior that can safely change while a turn is running.
|
||||
OverrideActiveTurnContext {
|
||||
/// Active turn id that must match before the override is applied.
|
||||
turn_id: String,
|
||||
|
||||
/// Updated command approval policy.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
approval_policy: Option<AskForApproval>,
|
||||
|
||||
/// Updated approval reviewer for approval prompts.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
approvals_reviewer: Option<ApprovalsReviewer>,
|
||||
},
|
||||
|
||||
/// Approve a command execution
|
||||
ExecApproval {
|
||||
/// The id of the submission we are approving
|
||||
@@ -803,6 +820,7 @@ impl Op {
|
||||
Self::UserTurn { .. } => "user_turn",
|
||||
Self::InterAgentCommunication { .. } => "inter_agent_communication",
|
||||
Self::OverrideTurnContext { .. } => "override_turn_context",
|
||||
Self::OverrideActiveTurnContext { .. } => "override_active_turn_context",
|
||||
Self::ExecApproval { .. } => "exec_approval",
|
||||
Self::PatchApproval { .. } => "patch_approval",
|
||||
Self::ResolveElicitation { .. } => "resolve_elicitation",
|
||||
|
||||
Reference in New Issue
Block a user