diff --git a/codex-rs/rollout/src/metadata.rs b/codex-rs/rollout/src/metadata.rs index 58d55a887d..c554854303 100644 --- a/codex-rs/rollout/src/metadata.rs +++ b/codex-rs/rollout/src/metadata.rs @@ -12,7 +12,6 @@ use chrono::Utc; use codex_protocol::ThreadId; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::RolloutItem; -use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionMetaLine; use codex_protocol::protocol::SessionSource; use codex_state::BackfillState; @@ -54,7 +53,6 @@ pub(crate) fn builder_from_session_meta( builder.agent_path = session_meta.meta.agent_path.clone(); builder.cwd = session_meta.meta.cwd.clone(); builder.cli_version = Some(session_meta.meta.cli_version.clone()); - builder.sandbox_policy = SandboxPolicy::new_read_only_policy(); builder.approval_mode = AskForApproval::OnRequest; if let Some(git) = session_meta.git.as_ref() { builder.git_sha = git.commit_hash.as_ref().map(|sha| sha.0.clone()); diff --git a/codex-rs/state/src/model/thread_metadata.rs b/codex-rs/state/src/model/thread_metadata.rs index bddb2fb364..150826a661 100644 --- a/codex-rs/state/src/model/thread_metadata.rs +++ b/codex-rs/state/src/model/thread_metadata.rs @@ -2,9 +2,9 @@ use anyhow::Result; use chrono::DateTime; use chrono::Utc; use codex_protocol::ThreadId; +use codex_protocol::models::PermissionProfile; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::protocol::AskForApproval; -use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionSource; use sqlx::Row; use sqlx::sqlite::SqliteRow; @@ -129,8 +129,9 @@ pub struct ThreadMetadataBuilder { pub cwd: PathBuf, /// Version of the CLI that created the thread. pub cli_version: Option, - /// The sandbox policy. - pub sandbox_policy: SandboxPolicy, + /// Runtime permissions, projected to the legacy `sandbox_policy` string + /// stored in the state DB when metadata is built. + pub permission_profile: PermissionProfile, /// The approval mode. pub approval_mode: AskForApproval, /// The archive timestamp, if the thread is archived. @@ -163,7 +164,7 @@ impl ThreadMetadataBuilder { model_provider: None, cwd: PathBuf::new(), cli_version: None, - sandbox_policy: SandboxPolicy::new_read_only_policy(), + permission_profile: PermissionProfile::read_only(), approval_mode: AskForApproval::OnRequest, archived_at: None, git_sha: None, @@ -175,7 +176,11 @@ 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 = crate::extract::enum_to_string(&self.sandbox_policy); + 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 approval_mode = crate::extract::enum_to_string(&self.approval_mode); let created_at = canonicalize_datetime(self.created_at); let updated_at = self @@ -465,14 +470,26 @@ pub struct BackfillStats { #[cfg(test)] mod tests { use super::ThreadMetadata; + use super::ThreadMetadataBuilder; use super::ThreadRow; use chrono::DateTime; use chrono::Utc; use codex_protocol::ThreadId; + use codex_protocol::models::PermissionProfile; use codex_protocol::openai_models::ReasoningEffort; + use codex_protocol::protocol::SessionSource; use pretty_assertions::assert_eq; use std::path::PathBuf; + fn metadata_builder() -> ThreadMetadataBuilder { + ThreadMetadataBuilder::new( + ThreadId::from_string("00000000-0000-0000-0000-000000000123").expect("valid thread id"), + PathBuf::from("/tmp/rollout-123.jsonl"), + DateTime::::from_timestamp(1_700_000_000, 0).expect("timestamp"), + SessionSource::Cli, + ) + } + fn thread_row(reasoning_effort: Option<&str>) -> ThreadRow { ThreadRow { id: "00000000-0000-0000-0000-000000000123".to_string(), @@ -549,4 +566,28 @@ mod tests { expected_thread_metadata(/*reasoning_effort*/ None) ); } + + #[test] + fn thread_metadata_builder_projects_permission_profile_to_legacy_sandbox_string() { + let mut builder = metadata_builder(); + builder.cwd = PathBuf::from("/tmp/workspace"); + builder.permission_profile = PermissionProfile::workspace_write(); + + let metadata = builder.build("openai"); + + assert_eq!( + metadata.sandbox_policy, + r#"{"exclude_slash_tmp":false,"exclude_tmpdir_env_var":false,"network_access":false,"type":"workspace-write"}"# + ); + } + + #[test] + fn thread_metadata_builder_projects_disabled_profile_to_legacy_sandbox_string() { + let mut builder = metadata_builder(); + builder.permission_profile = PermissionProfile::Disabled; + + let metadata = builder.build("openai"); + + assert_eq!(metadata.sandbox_policy, r#"{"type":"danger-full-access"}"#); + } } diff --git a/codex-rs/state/src/runtime/test_support.rs b/codex-rs/state/src/runtime/test_support.rs index 5f07336853..80330cdcbb 100644 --- a/codex-rs/state/src/runtime/test_support.rs +++ b/codex-rs/state/src/runtime/test_support.rs @@ -9,8 +9,6 @@ use codex_protocol::openai_models::ReasoningEffort; #[cfg(test)] use codex_protocol::protocol::AskForApproval; #[cfg(test)] -use codex_protocol::protocol::SandboxPolicy; -#[cfg(test)] use std::path::Path; #[cfg(test)] use std::path::PathBuf; @@ -57,7 +55,7 @@ pub(super) fn test_thread_metadata( cwd, cli_version: "0.0.0".to_string(), title: String::new(), - sandbox_policy: crate::extract::enum_to_string(&SandboxPolicy::new_read_only_policy()), + sandbox_policy: r#"{"type":"read-only"}"#.to_string(), approval_mode: crate::extract::enum_to_string(&AskForApproval::OnRequest), tokens_used: 0, first_user_message: Some("hello".to_string()),