diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index df6766fda2..b93b4109fe 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -2121,6 +2121,7 @@ dependencies = [ "codex-model-provider", "codex-model-provider-info", "codex-models-manager", + "codex-multi-env-copy-file", "codex-network-proxy", "codex-otel", "codex-plugin", @@ -2547,6 +2548,7 @@ dependencies = [ "codex-memories-write", "codex-model-provider", "codex-models-manager", + "codex-multi-env-copy-file", "codex-plugin", "codex-protocol", "codex-responses-api-proxy", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index ee3d8c493a..b5a8ec45c6 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -225,6 +225,7 @@ codex-exec-server-test-support = { path = "exec-server/tests/support" } codex-execpolicy = { path = "execpolicy" } codex-extension-api = { path = "ext/extension-api" } codex-extension-items = { path = "ext/items" } +codex-multi-env-copy-file = { path = "ext/multi-env-copy-file" } codex-goal-extension = { path = "ext/goal" } codex-git-attribution = { path = "ext/git-attribution" } codex-guardian-reviewer = { path = "ext/guardian-reviewer" } diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index 0ef30505cc..b205a05ffb 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -51,6 +51,7 @@ codex-exec-server = { workspace = true } codex-extension-api = { workspace = true } codex-external-agent-migration = { workspace = true } codex-features = { workspace = true } +codex-multi-env-copy-file = { workspace = true } codex-goal-extension = { workspace = true } codex-git-attribution = { workspace = true } codex-guardian-v2 = { workspace = true } diff --git a/codex-rs/app-server/src/extensions.rs b/codex-rs/app-server/src/extensions.rs index c2d6d6d0a2..aa704bae81 100644 --- a/codex-rs/app-server/src/extensions.rs +++ b/codex-rs/app-server/src/extensions.rs @@ -48,6 +48,7 @@ pub(crate) struct ThreadExtensionDependencies { } pub(crate) fn thread_extensions( + config: &Config, dependencies: ThreadExtensionDependencies, ) -> Arc> { let ThreadExtensionDependencies { @@ -65,6 +66,12 @@ pub(crate) fn thread_extensions( turn_start_admission, } = dependencies; let mut builder = ExtensionRegistryBuilder::::with_event_sink(Arc::clone(&event_sink)); + if config + .features + .enabled(codex_features::Feature::MultiEnvCopyFile) + { + codex_multi_env_copy_file::install(&mut builder); + } if let Some(admission) = turn_start_admission { builder.turn_start_admission(admission); } diff --git a/codex-rs/app-server/src/mcp_refresh.rs b/codex-rs/app-server/src/mcp_refresh.rs index ac965523fb..3611ab3992 100644 --- a/codex-rs/app-server/src/mcp_refresh.rs +++ b/codex-rs/app-server/src/mcp_refresh.rs @@ -476,20 +476,23 @@ enabled = false codex_core::CodexAppsToolsCache::default(), SessionSource::Exec, Arc::clone(&environment_manager), - thread_extensions(ThreadExtensionDependencies { - event_sink: Arc::new(NoopExtensionEventSink), - auth_manager: auth_manager.clone(), - state_db: Some(state_db.clone()), - analytics_events_client: codex_analytics::AnalyticsEventsClient::disabled(), - thread_manager: thread_manager.clone(), - goal_service: Arc::new(codex_goal_extension::GoalService::new()), - environment_manager: Arc::clone(&environment_manager), - executor_skill_provider: Arc::clone(&executor_skill_provider), - git_attribution_base_url: good_config.chatgpt_base_url.clone(), - http_client_factory: good_config.http_client_factory(), - queue_service: None, - turn_start_admission: None, - }), + thread_extensions( + &good_config, + ThreadExtensionDependencies { + event_sink: Arc::new(NoopExtensionEventSink), + auth_manager: auth_manager.clone(), + state_db: Some(state_db.clone()), + analytics_events_client: codex_analytics::AnalyticsEventsClient::disabled(), + thread_manager: thread_manager.clone(), + goal_service: Arc::new(codex_goal_extension::GoalService::new()), + environment_manager: Arc::clone(&environment_manager), + executor_skill_provider: Arc::clone(&executor_skill_provider), + git_attribution_base_url: good_config.chatgpt_base_url.clone(), + http_client_factory: good_config.http_client_factory(), + queue_service: None, + turn_start_admission: None, + }, + ), Arc::new(CodexHomeUserInstructionsProvider::new( good_config.codex_home.clone(), )), diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 767c600e07..09d5f1762f 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -344,20 +344,23 @@ impl MessageProcessor { codex_core::CodexAppsToolsCache::default(), session_source, environment_manager, - thread_extensions(ThreadExtensionDependencies { - event_sink: Arc::clone(&extension_event_sink), - auth_manager: auth_manager.clone(), - state_db: state_db.clone(), - analytics_events_client: analytics_events_client.clone(), - thread_manager: thread_manager.clone(), - goal_service: Arc::clone(&goal_service), - environment_manager: Arc::clone(&environment_manager_for_extensions), - executor_skill_provider: Arc::clone(&executor_skill_provider), - git_attribution_base_url: config.chatgpt_base_url.clone(), - http_client_factory: config.http_client_factory(), - queue_service: queue_service.clone(), - turn_start_admission: Some(Arc::clone(&turn_start_admission)), - }), + thread_extensions( + config.as_ref(), + ThreadExtensionDependencies { + event_sink: Arc::clone(&extension_event_sink), + auth_manager: auth_manager.clone(), + state_db: state_db.clone(), + analytics_events_client: analytics_events_client.clone(), + thread_manager: thread_manager.clone(), + goal_service: Arc::clone(&goal_service), + environment_manager: Arc::clone(&environment_manager_for_extensions), + executor_skill_provider: Arc::clone(&executor_skill_provider), + git_attribution_base_url: config.chatgpt_base_url.clone(), + http_client_factory: config.http_client_factory(), + queue_service: queue_service.clone(), + turn_start_admission: Some(Arc::clone(&turn_start_admission)), + }, + ), Arc::new(CodexHomeUserInstructionsProvider::new( config.codex_home.clone(), )), diff --git a/codex-rs/cli/Cargo.toml b/codex-rs/cli/Cargo.toml index 9443824741..26dee62ece 100644 --- a/codex-rs/cli/Cargo.toml +++ b/codex-rs/cli/Cargo.toml @@ -55,6 +55,7 @@ codex-exec-server = { workspace = true } codex-execpolicy = { workspace = true } codex-extension-api = { workspace = true } codex-features = { workspace = true } +codex-multi-env-copy-file = { workspace = true } codex-git-attribution = { workspace = true } codex-git-utils = { workspace = true } codex-install-context = { workspace = true } diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 616c3f0f51..c3beb4f7c0 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -2099,6 +2099,13 @@ async fn run_debug_prompt_input_command( let auth_manager = AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false).await?; let mut extensions = codex_extension_api::ExtensionRegistryBuilder::new(); + // Debug prompt construction bypasses app-server and needs its own extension registry. + if config + .features + .enabled(codex_features::Feature::MultiEnvCopyFile) + { + codex_multi_env_copy_file::install(&mut extensions); + } codex_git_attribution::install( &mut extensions, auth_manager, diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 2331f64ed6..80c0539b5b 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -1066,6 +1066,9 @@ "multi_agent_v2_dynamic_tools": { "type": "boolean" }, + "multi_env_copy_file": { + "type": "boolean" + }, "network_proxy": { "$ref": "#/definitions/FeatureToml_for_NetworkProxyConfigToml" }, @@ -7452,6 +7455,9 @@ "multi_agent_v2_dynamic_tools": { "type": "boolean" }, + "multi_env_copy_file": { + "type": "boolean" + }, "network_proxy": { "$ref": "#/definitions/FeatureToml_for_NetworkProxyConfigToml" }, diff --git a/codex-rs/features/src/lib.rs b/codex-rs/features/src/lib.rs index edcaa6ffd7..df12fcddf4 100644 --- a/codex-rs/features/src/lib.rs +++ b/codex-rs/features/src/lib.rs @@ -122,6 +122,8 @@ pub enum Feature { /// Advertise enabled environment-backed tools and their shell parameters before the /// executor is ready. Actual execution still requires a usable environment and its policy. StableEnvironmentTools, + /// Copy files between explicitly selected execution environments. + MultiEnvCopyFile, /// Send per-content-entry classifications in internal Responses metadata. ContentItemKinds, /// Record model-attempted tool calls in internal Responses metadata. @@ -1060,6 +1062,12 @@ pub const FEATURES: &[FeatureSpec] = &[ stage: Stage::Stable, default_enabled: true, }, + FeatureSpec { + id: Feature::MultiEnvCopyFile, + key: "multi_env_copy_file", + stage: Stage::UnderDevelopment, + default_enabled: false, + }, FeatureSpec { id: Feature::SleepTool, key: "sleep_tool",