@@ -3541,7 +3541,7 @@ pub struct SessionNetworkProxyRuntime {
35413541 pub socks_addr : String ,
35423542}
35433543
3544- #[ derive( Debug , Clone , Deserialize , Serialize , JsonSchema , TS ) ]
3544+ #[ derive( Debug , Clone , Serialize , JsonSchema , TS ) ]
35453545pub struct SessionConfiguredEvent {
35463546 pub session_id : ThreadId ,
35473547 #[ serde( skip_serializing_if = "Option::is_none" ) ]
@@ -3569,16 +3569,8 @@ pub struct SessionConfiguredEvent {
35693569 #[ serde( default ) ]
35703570 pub approvals_reviewer : ApprovalsReviewer ,
35713571
3572- /// Legacy sandbox projection for commands executed in the system.
3573- ///
3574- /// Consumers should prefer `permission_profile` when it is present. This
3575- /// field remains available as a compatibility fallback for older emitters
3576- /// and sessions that only expose legacy sandbox state.
3577- pub sandbox_policy : SandboxPolicy ,
3578-
35793572 /// Canonical effective permissions for commands executed in the session.
3580- #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
3581- pub permission_profile : Option < PermissionProfile > ,
3573+ pub permission_profile : PermissionProfile ,
35823574
35833575 /// Working directory that should be treated as the *root* of the
35843576 /// session.
@@ -3609,6 +3601,70 @@ pub struct SessionConfiguredEvent {
36093601 pub rollout_path : Option < PathBuf > ,
36103602}
36113603
3604+ impl < ' de > Deserialize < ' de > for SessionConfiguredEvent {
3605+ fn deserialize < D > ( deserializer : D ) -> Result < Self , D :: Error >
3606+ where
3607+ D : serde:: Deserializer < ' de > ,
3608+ {
3609+ #[ derive( Deserialize ) ]
3610+ struct Wire {
3611+ session_id : ThreadId ,
3612+ forked_from_id : Option < ThreadId > ,
3613+ #[ serde( default ) ]
3614+ thread_name : Option < String > ,
3615+ model : String ,
3616+ model_provider_id : String ,
3617+ service_tier : Option < ServiceTier > ,
3618+ approval_policy : AskForApproval ,
3619+ #[ serde( default ) ]
3620+ approvals_reviewer : ApprovalsReviewer ,
3621+ // `SessionConfiguredEvent` is persisted into rollout history. Older
3622+ // rollouts only have `sandbox_policy`, so accept it on deserialize
3623+ // and immediately project it into the canonical `permission_profile`.
3624+ sandbox_policy : Option < SandboxPolicy > ,
3625+ permission_profile : Option < PermissionProfile > ,
3626+ cwd : AbsolutePathBuf ,
3627+ reasoning_effort : Option < ReasoningEffortConfig > ,
3628+ history_log_id : u64 ,
3629+ history_entry_count : usize ,
3630+ initial_messages : Option < Vec < EventMsg > > ,
3631+ network_proxy : Option < SessionNetworkProxyRuntime > ,
3632+ rollout_path : Option < PathBuf > ,
3633+ }
3634+
3635+ let wire = Wire :: deserialize ( deserializer) ?;
3636+ let permission_profile = match ( wire. permission_profile , wire. sandbox_policy ) {
3637+ ( Some ( permission_profile) , _) => permission_profile,
3638+ ( None , Some ( sandbox_policy) ) => PermissionProfile :: from_legacy_sandbox_policy_for_cwd (
3639+ & sandbox_policy,
3640+ wire. cwd . as_path ( ) ,
3641+ ) ,
3642+ ( None , None ) => {
3643+ return Err ( serde:: de:: Error :: missing_field ( "permission_profile" ) ) ;
3644+ }
3645+ } ;
3646+
3647+ Ok ( Self {
3648+ session_id : wire. session_id ,
3649+ forked_from_id : wire. forked_from_id ,
3650+ thread_name : wire. thread_name ,
3651+ model : wire. model ,
3652+ model_provider_id : wire. model_provider_id ,
3653+ service_tier : wire. service_tier ,
3654+ approval_policy : wire. approval_policy ,
3655+ approvals_reviewer : wire. approvals_reviewer ,
3656+ permission_profile,
3657+ cwd : wire. cwd ,
3658+ reasoning_effort : wire. reasoning_effort ,
3659+ history_log_id : wire. history_log_id ,
3660+ history_entry_count : wire. history_entry_count ,
3661+ initial_messages : wire. initial_messages ,
3662+ network_proxy : wire. network_proxy ,
3663+ rollout_path : wire. rollout_path ,
3664+ } )
3665+ }
3666+ }
3667+
36123668#[ derive( Debug , Clone , Deserialize , Serialize , JsonSchema , TS ) ]
36133669pub struct ThreadNameUpdatedEvent {
36143670 pub thread_id : ThreadId ,
@@ -5088,6 +5144,7 @@ mod tests {
50885144 fn serialize_event ( ) -> Result < ( ) > {
50895145 let conversation_id = ThreadId :: from_string ( "67e55044-10b1-426f-9247-bb680e5fe0c8" ) ?;
50905146 let rollout_file = NamedTempFile :: new ( ) ?;
5147+ let permission_profile = PermissionProfile :: read_only ( ) ;
50915148 let event = Event {
50925149 id : "1234" . to_string ( ) ,
50935150 msg : EventMsg :: SessionConfigured ( SessionConfiguredEvent {
@@ -5099,8 +5156,7 @@ mod tests {
50995156 service_tier : None ,
51005157 approval_policy : AskForApproval :: Never ,
51015158 approvals_reviewer : ApprovalsReviewer :: User ,
5102- sandbox_policy : SandboxPolicy :: new_read_only_policy ( ) ,
5103- permission_profile : None ,
5159+ permission_profile : permission_profile. clone ( ) ,
51045160 cwd : test_path_buf ( "/home/user/project" ) . abs ( ) ,
51055161 reasoning_effort : Some ( ReasoningEffortConfig :: default ( ) ) ,
51065162 history_log_id : 0 ,
@@ -5120,9 +5176,7 @@ mod tests {
51205176 "model_provider_id" : "openai" ,
51215177 "approval_policy" : "never" ,
51225178 "approvals_reviewer" : "user" ,
5123- "sandbox_policy" : {
5124- "type" : "read-only"
5125- } ,
5179+ "permission_profile" : permission_profile,
51265180 "cwd" : test_path_buf( "/home/user/project" ) ,
51275181 "reasoning_effort" : "medium" ,
51285182 "history_log_id" : 0 ,
@@ -5134,6 +5188,28 @@ mod tests {
51345188 Ok ( ( ) )
51355189 }
51365190
5191+ #[ test]
5192+ fn deserialize_legacy_session_configured_event_uses_sandbox_policy ( ) -> Result < ( ) > {
5193+ let cwd = test_path_buf ( "/home/user/project" ) ;
5194+ let value = json ! ( {
5195+ "session_id" : "67e55044-10b1-426f-9247-bb680e5fe0c8" ,
5196+ "model" : "codex-mini-latest" ,
5197+ "model_provider_id" : "openai" ,
5198+ "approval_policy" : "never" ,
5199+ "approvals_reviewer" : "user" ,
5200+ "sandbox_policy" : {
5201+ "type" : "read-only"
5202+ } ,
5203+ "cwd" : cwd,
5204+ "history_log_id" : 0 ,
5205+ "history_entry_count" : 0 ,
5206+ } ) ;
5207+
5208+ let event: SessionConfiguredEvent = serde_json:: from_value ( value) ?;
5209+ assert_eq ! ( event. permission_profile, PermissionProfile :: read_only( ) ) ;
5210+ Ok ( ( ) )
5211+ }
5212+
51375213 #[ test]
51385214 fn vec_u8_as_base64_serialization_and_deserialization ( ) -> Result < ( ) > {
51395215 let event = ExecCommandOutputDeltaEvent {
0 commit comments