diff --git a/codex-rs/state/src/extract.rs b/codex-rs/state/src/extract.rs index a4a0ab0f6a..633eb63404 100644 --- a/codex-rs/state/src/extract.rs +++ b/codex-rs/state/src/extract.rs @@ -75,7 +75,10 @@ fn apply_turn_context(metadata: &mut ThreadMetadata, turn_ctx: &TurnContextItem) } metadata.model = Some(turn_ctx.model.clone()); metadata.reasoning_effort = turn_ctx.effort; - metadata.sandbox_policy = enum_to_string(&turn_ctx.sandbox_policy); + metadata.sandbox_policy = crate::model::legacy_sandbox_policy_string( + &turn_ctx.permission_profile(), + turn_ctx.cwd.as_path(), + ); metadata.approval_mode = enum_to_string(&turn_ctx.approval_policy); } @@ -150,12 +153,12 @@ mod tests { use codex_protocol::ThreadId; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::models::ContentItem; + use codex_protocol::models::PermissionProfile; use codex_protocol::models::ResponseItem; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::EventMsg; use codex_protocol::protocol::RolloutItem; - use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionMeta; use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::SessionSource; @@ -165,6 +168,7 @@ mod tests { use codex_protocol::protocol::UserMessageEvent; use pretty_assertions::assert_eq; + use std::path::Path; use std::path::PathBuf; use uuid::Uuid; @@ -299,8 +303,10 @@ mod tests { current_date: None, timezone: None, approval_policy: AskForApproval::Never, - sandbox_policy: SandboxPolicy::DangerFullAccess, - permission_profile: None, + sandbox_policy: PermissionProfile::read_only() + .to_legacy_sandbox_policy(Path::new("/")) + .expect("read-only profile should project to legacy sandbox"), + permission_profile: Some(PermissionProfile::Disabled), network: None, file_system_sandbox_policy: None, model: "gpt-5".to_string(), @@ -318,10 +324,7 @@ mod tests { ); assert_eq!(metadata.cwd, PathBuf::from("/child/worktree")); - assert_eq!( - metadata.sandbox_policy, - super::enum_to_string(&SandboxPolicy::DangerFullAccess) - ); + assert_eq!(metadata.sandbox_policy, r#"{"type":"danger-full-access"}"#); assert_eq!(metadata.approval_mode, "never"); } @@ -339,7 +342,9 @@ mod tests { current_date: None, timezone: None, approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + sandbox_policy: PermissionProfile::read_only() + .to_legacy_sandbox_policy(Path::new("/")) + .expect("read-only profile should project to legacy sandbox"), permission_profile: None, network: None, file_system_sandbox_policy: None, @@ -373,7 +378,9 @@ mod tests { current_date: None, timezone: None, approval_policy: AskForApproval::OnRequest, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + sandbox_policy: PermissionProfile::read_only() + .to_legacy_sandbox_policy(Path::new("/")) + .expect("read-only profile should project to legacy sandbox"), permission_profile: None, network: None, file_system_sandbox_policy: None, diff --git a/codex-rs/state/src/model/mod.rs b/codex-rs/state/src/model/mod.rs index a431bc64c0..522be4b4b2 100644 --- a/codex-rs/state/src/model/mod.rs +++ b/codex-rs/state/src/model/mod.rs @@ -44,3 +44,4 @@ pub(crate) use thread_metadata::anchor_from_item; pub(crate) use thread_metadata::datetime_to_epoch_millis; pub(crate) use thread_metadata::datetime_to_epoch_seconds; pub(crate) use thread_metadata::epoch_millis_to_datetime; +pub(crate) use thread_metadata::legacy_sandbox_policy_string; diff --git a/codex-rs/state/src/model/thread_metadata.rs b/codex-rs/state/src/model/thread_metadata.rs index c4319fd808..5f7b8bc252 100644 --- a/codex-rs/state/src/model/thread_metadata.rs +++ b/codex-rs/state/src/model/thread_metadata.rs @@ -8,6 +8,7 @@ use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::SessionSource; use sqlx::Row; use sqlx::sqlite::SqliteRow; +use std::path::Path; use std::path::PathBuf; /// The sort key to use when listing threads. @@ -176,11 +177,8 @@ impl ThreadMetadataBuilder { /// Build canonical thread metadata, filling missing values from defaults. pub fn build(&self, default_provider: &str) -> ThreadMetadata { let source = crate::extract::enum_to_string(&self.source); - let sandbox_policy = self - .permission_profile - .to_legacy_sandbox_policy(self.cwd.as_path()) - .map(|policy| crate::extract::enum_to_string(&policy)) - .unwrap_or_else(|_| "custom".to_string()); + let sandbox_policy = + legacy_sandbox_policy_string(&self.permission_profile, self.cwd.as_path()); let approval_mode = crate::extract::enum_to_string(&self.approval_mode); let created_at = canonicalize_datetime(self.created_at); let updated_at = self @@ -220,6 +218,16 @@ impl ThreadMetadataBuilder { } } +pub(crate) fn legacy_sandbox_policy_string( + permission_profile: &PermissionProfile, + cwd: &Path, +) -> String { + permission_profile + .to_legacy_sandbox_policy(cwd) + .map(|policy| crate::extract::enum_to_string(&policy)) + .unwrap_or_else(|_| "custom".to_string()) +} + impl ThreadMetadata { /// Preserve existing non-null Git fields when rollout-derived metadata is reconciled. pub fn prefer_existing_git_info(&mut self, existing: &Self) {