feat(core) Persist request_permission data across turns (#14009)

## Summary
request_permissions flows should support persisting results for the
session.

Open Question: Still deciding if we need within-turn approvals - this
adds complexity but I could see it being useful

## Testing
- [x] Updated unit tests

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Dylan Hurd
2026-03-09 14:36:38 -07:00
committed by GitHub
co-authored by Codex
parent 831ee51c86
commit d241dc598c
21 changed files with 638 additions and 8 deletions
@@ -145,11 +145,26 @@
"read_write"
],
"type": "string"
},
"PermissionGrantScope": {
"enum": [
"turn",
"session"
],
"type": "string"
}
},
"properties": {
"permissions": {
"$ref": "#/definitions/GrantedPermissionProfile"
},
"scope": {
"allOf": [
{
"$ref": "#/definitions/PermissionGrantScope"
}
],
"default": "turn"
}
},
"required": [
@@ -6462,6 +6462,13 @@
}
]
},
"PermissionGrantScope": {
"enum": [
"turn",
"session"
],
"type": "string"
},
"PermissionProfile": {
"properties": {
"file_system": {
@@ -6533,6 +6540,14 @@
"properties": {
"permissions": {
"$ref": "#/definitions/GrantedPermissionProfile"
},
"scope": {
"allOf": [
{
"$ref": "#/definitions/PermissionGrantScope"
}
],
"default": "turn"
}
},
"required": [
@@ -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 PermissionGrantScope = "turn" | "session";
@@ -2,5 +2,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { GrantedPermissionProfile } from "./GrantedPermissionProfile";
import type { PermissionGrantScope } from "./PermissionGrantScope";
export type PermissionsRequestApprovalResponse = { permissions: GrantedPermissionProfile, };
export type PermissionsRequestApprovalResponse = { permissions: GrantedPermissionProfile, scope: PermissionGrantScope, };
@@ -161,6 +161,7 @@ export type { NetworkRequirements } from "./NetworkRequirements";
export type { OverriddenMetadata } from "./OverriddenMetadata";
export type { PatchApplyStatus } from "./PatchApplyStatus";
export type { PatchChangeKind } from "./PatchChangeKind";
export type { PermissionGrantScope } from "./PermissionGrantScope";
export type { PermissionsRequestApprovalParams } from "./PermissionsRequestApprovalParams";
export type { PermissionsRequestApprovalResponse } from "./PermissionsRequestApprovalResponse";
export type { PlanDeltaNotification } from "./PlanDeltaNotification";
@@ -68,6 +68,7 @@ use codex_protocol::protocol::SkillToolDependency as CoreSkillToolDependency;
use codex_protocol::protocol::SubAgentSource as CoreSubAgentSource;
use codex_protocol::protocol::TokenUsage as CoreTokenUsage;
use codex_protocol::protocol::TokenUsageInfo as CoreTokenUsageInfo;
use codex_protocol::request_permissions::PermissionGrantScope as CorePermissionGrantScope;
use codex_protocol::user_input::ByteRange as CoreByteRange;
use codex_protocol::user_input::TextElement as CoreTextElement;
use codex_protocol::user_input::UserInput as CoreUserInput;
@@ -83,12 +84,18 @@ use ts_rs::TS;
// tends to use either snake_case or kebab-case.
macro_rules! v2_enum_from_core {
(
pub enum $Name:ident from $Src:path { $( $Variant:ident ),+ $(,)? }
$(#[$enum_meta:meta])*
pub enum $Name:ident from $Src:path {
$( $(#[$variant_meta:meta])* $Variant:ident ),+ $(,)?
}
) => {
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
$(#[$enum_meta])*
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum $Name { $( $Variant ),+ }
pub enum $Name {
$( $(#[$variant_meta])* $Variant ),+
}
impl $Name {
pub fn to_core(self) -> $Src {
@@ -4983,11 +4990,22 @@ pub struct PermissionsRequestApprovalParams {
pub permissions: AdditionalPermissionProfile,
}
v2_enum_from_core!(
#[derive(Default)]
pub enum PermissionGrantScope from CorePermissionGrantScope {
#[default]
Turn,
Session
}
);
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PermissionsRequestApprovalResponse {
pub permissions: GrantedPermissionProfile,
#[serde(default)]
pub scope: PermissionGrantScope,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -5449,6 +5467,7 @@ mod tests {
}),
..Default::default()
},
scope: PermissionGrantScope::Turn,
};
assert_eq!(
@@ -5459,10 +5478,21 @@ mod tests {
"accessibility": true,
},
},
"scope": "turn",
})
);
}
#[test]
fn permissions_request_approval_response_defaults_scope_to_turn() {
let response = serde_json::from_value::<PermissionsRequestApprovalResponse>(json!({
"permissions": {},
}))
.expect("response should deserialize");
assert_eq!(response.scope, PermissionGrantScope::Turn);
}
#[test]
fn command_exec_params_default_optional_streaming_flags() {
let params = serde_json::from_value::<CommandExecParams>(json!({