Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
a7fa367
Support multiple managed environments
starr-openai Apr 17, 2026
194b9d8
Rename environment manager args constructor
starr-openai Apr 17, 2026
e589fdd
Make default environment lookup infallible
starr-openai Apr 17, 2026
36eb75b
Move lazy exec-server client handle
starr-openai Apr 17, 2026
c215ff4
Remove path-specific environment factory
starr-openai Apr 17, 2026
6b8fc18
Document environment manager behavior
starr-openai Apr 17, 2026
405b9db
Remove local environment convenience method
starr-openai Apr 17, 2026
64a9a98
Document shared environment manager handle
starr-openai Apr 17, 2026
9d3188f
Use optional default environment for disabled mode
starr-openai Apr 17, 2026
a6c81a0
Fix environment manager follow-up compile errors
starr-openai Apr 17, 2026
e354201
Fix environment manager hardening issues
starr-openai Apr 18, 2026
154be3f
codex: remove low-value environment test
starr-openai Apr 20, 2026
0642d36
Share remote environment exec-server client
starr-openai Apr 21, 2026
f748352
Hide environment manager env parsing
starr-openai Apr 21, 2026
6967e3f
Remove redundant environment-backed tools test
starr-openai Apr 21, 2026
fc7a440
Require runtime paths for environments
starr-openai Apr 21, 2026
2c0a752
Drop unused exec-server env var re-export
starr-openai Apr 21, 2026
a2e02d9
Reuse EnvironmentManager for app-server connectors
starr-openai Apr 21, 2026
a8f1090
Pass environment manager to app list task
starr-openai Apr 21, 2026
2ec1ad9
Add turn-scoped environment selections
starr-openai Apr 17, 2026
8b6f131
codex: document turn environments API
starr-openai Apr 20, 2026
bf85976
codex: gate empty experimental fields
starr-openai Apr 20, 2026
2d52988
codex: remove dead experimental helper
starr-openai Apr 20, 2026
3150be4
codex: remove verbose environment docs
starr-openai Apr 21, 2026
675777c
codex: tighten turn environment errors
starr-openai Apr 21, 2026
b49f5c0
Avoid expect in local environment lookup
starr-openai Apr 21, 2026
2b0f0c2
Add sticky thread environment selections
starr-openai Apr 20, 2026
b52987c
Add app-server tests for sticky environments
starr-openai Apr 20, 2026
ed4ac11
Add sticky thread environments
starr-openai Apr 20, 2026
bbad6a8
Fix sticky environment CI fallout
starr-openai Apr 20, 2026
68202b3
Fix sticky environment lint failures
starr-openai Apr 20, 2026
01fabfd
Use parsed apply_patch cwd for shell intercepts
starr-openai Apr 20, 2026
9c337ef
Use selected environment cwd for unified exec
starr-openai Apr 20, 2026
faf7af4
Fix multi-environment unified exec test wait
starr-openai Apr 21, 2026
8bae1c1
Fix unified exec environment id assertion
starr-openai Apr 21, 2026
fd4e5f1
Fix multi-environment unified exec test
starr-openai Apr 21, 2026
7249edf
Fix TUI test environment fixtures
starr-openai Apr 21, 2026
9f8f766
Apply formatting after sticky environment rebase
starr-openai Apr 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions codex-rs/app-server-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ use codex_core::config::Config;
use codex_core::config_loader::CloudRequirementsLoader;
use codex_core::config_loader::LoaderOverrides;
pub use codex_exec_server::EnvironmentManager;
pub use codex_exec_server::EnvironmentManagerArgs;
pub use codex_exec_server::ExecServerRuntimePaths;
use codex_feedback::CodexFeedback;
use codex_protocol::protocol::SessionSource;
Expand Down Expand Up @@ -968,7 +969,7 @@ mod tests {
cloud_requirements: CloudRequirementsLoader::default(),
feedback: CodexFeedback::new(),
log_db: None,
environment_manager: Arc::new(EnvironmentManager::new(/*exec_server_url*/ None)),
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
config_warnings: Vec::new(),
session_source,
enable_codex_api_key_env: false,
Expand Down Expand Up @@ -1969,9 +1970,14 @@ mod tests {
#[tokio::test]
async fn runtime_start_args_forward_environment_manager() {
let config = Arc::new(build_test_config().await);
let environment_manager = Arc::new(EnvironmentManager::new(Some(
"ws://127.0.0.1:8765".to_string(),
)));
let environment_manager = Arc::new(EnvironmentManager::new(EnvironmentManagerArgs {
exec_server_url: Some("ws://127.0.0.1:8765".to_string()),
local_runtime_paths: ExecServerRuntimePaths::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
.expect("runtime paths"),
}));

let runtime_args = InProcessClientStartArgs {
arg0_paths: Arg0DispatchPaths::default(),
Expand All @@ -1998,7 +2004,13 @@ mod tests {
&runtime_args.environment_manager,
&environment_manager
));
assert!(runtime_args.environment_manager.is_remote());
assert!(
runtime_args
.environment_manager
.default_environment()
.expect("default environment")
.is_remote()
);
}

#[tokio::test]
Expand Down
15 changes: 15 additions & 0 deletions codex-rs/app-server-protocol/schema/json/ClientRequest.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions codex-rs/app-server-protocol/schema/json/v2/ThreadStartParams.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions codex-rs/app-server-protocol/schema/json/v2/TurnStartParams.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions codex-rs/app-server-protocol/schema/typescript/v2/index.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions codex-rs/app-server-protocol/src/experimental_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ mod tests {
inners: HashMap<String, EnumVariantShapes>,
}

#[allow(dead_code)]
#[derive(ExperimentalApi)]
struct ExperimentalFieldShape {
#[experimental("field/optionalCollection")]
optional_collection: Option<Vec<EnumVariantShapes>>,
}

#[test]
fn derive_supports_all_enum_variant_shapes() {
assert_eq!(
Expand Down Expand Up @@ -169,4 +176,20 @@ mod tests {
None
);
}

#[test]
fn derive_marks_optional_experimental_fields_when_some() {
assert_eq!(
ExperimentalApiTrait::experimental_reason(&ExperimentalFieldShape {
optional_collection: Some(Vec::new()),
}),
Some("field/optionalCollection")
);
assert_eq!(
ExperimentalApiTrait::experimental_reason(&ExperimentalFieldShape {
optional_collection: None,
}),
None
);
}
}
130 changes: 130 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3074,6 +3074,14 @@ pub struct ThreadStartParams {
pub ephemeral: Option<bool>,
#[ts(optional = nullable)]
pub session_start_source: Option<ThreadStartSource>,
/// Optional sticky environment selections for this thread.
///
/// Omitted uses EnvironmentManager default behavior. Empty disables
/// environment access for turns that do not provide a turn override.
/// Non-empty selects the first environment as the current turn environment.
#[experimental("thread/start.environments")]
#[ts(optional = nullable)]
pub environments: Option<Vec<TurnEnvironmentParams>>,
#[experimental("thread/start.dynamicTools")]
#[ts(optional = nullable)]
pub dynamic_tools: Option<Vec<DynamicToolSpec>>,
Expand Down Expand Up @@ -4641,6 +4649,14 @@ pub enum TurnStatus {
}

// Turn APIs
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct TurnEnvironmentParams {
pub environment_id: String,
pub cwd: AbsolutePathBuf,
}

#[derive(
Serialize, Deserialize, Debug, Default, Clone, PartialEq, JsonSchema, TS, ExperimentalApi,
)]
Expand All @@ -4653,6 +4669,14 @@ pub struct TurnStartParams {
#[experimental("turn/start.responsesapiClientMetadata")]
#[ts(optional = nullable)]
pub responsesapi_client_metadata: Option<HashMap<String, String>>,
/// Optional turn-scoped environment selections.
///
/// Omitted uses the thread sticky environment selections. Empty disables
/// environment access for this turn. Non-empty selects the first
/// environment as the current turn environment for this turn.
#[experimental("turn/start.environments")]
#[ts(optional = nullable)]
pub environments: Option<Vec<TurnEnvironmentParams>>,
/// Override the working directory for this turn and subsequent turns.
#[ts(optional = nullable)]
pub cwd: Option<PathBuf>,
Expand Down Expand Up @@ -9759,6 +9783,7 @@ mod tests {
thread_id: "thread_123".to_string(),
input: vec![],
responsesapi_client_metadata: None,
environments: None,
cwd: None,
approval_policy: None,
approvals_reviewer: None,
Expand All @@ -9775,4 +9800,109 @@ mod tests {
serde_json::to_value(&without_override).expect("params should serialize");
assert_eq!(serialized_without_override.get("serviceTier"), None);
}

#[test]
fn turn_start_params_round_trip_environments() {
let cwd = test_absolute_path();
let params: TurnStartParams = serde_json::from_value(json!({
"threadId": "thread_123",
"input": [],
"environments": [
{
"environmentId": "local",
"cwd": cwd
}
],
}))
.expect("params should deserialize");

assert_eq!(
params.environments,
Some(vec![TurnEnvironmentParams {
environment_id: "local".to_string(),
cwd: cwd.clone(),
}])
);
assert_eq!(
crate::experimental_api::ExperimentalApi::experimental_reason(&params),
Some("turn/start.environments")
);

let serialized = serde_json::to_value(&params).expect("params should serialize");
assert_eq!(
serialized.get("environments"),
Some(&json!([
{
"environmentId": "local",
"cwd": cwd
}
]))
);
}

#[test]
fn turn_start_params_preserve_empty_environments() {
let params: TurnStartParams = serde_json::from_value(json!({
"threadId": "thread_123",
"input": [],
"environments": [],
}))
.expect("params should deserialize");

assert_eq!(params.environments, Some(Vec::new()));
assert_eq!(
crate::experimental_api::ExperimentalApi::experimental_reason(&params),
Some("turn/start.environments")
);

let serialized = serde_json::to_value(&params).expect("params should serialize");
assert_eq!(serialized.get("environments"), Some(&json!([])));
}

#[test]
fn turn_start_params_treat_null_or_omitted_environments_as_default() {
let null_environments: TurnStartParams = serde_json::from_value(json!({
"threadId": "thread_123",
"input": [],
"environments": null,
}))
.expect("params should deserialize");
let omitted_environments: TurnStartParams = serde_json::from_value(json!({
"threadId": "thread_123",
"input": [],
}))
.expect("params should deserialize");

assert_eq!(null_environments.environments, None);
assert_eq!(omitted_environments.environments, None);
assert_eq!(
crate::experimental_api::ExperimentalApi::experimental_reason(&null_environments),
None
);
assert_eq!(
crate::experimental_api::ExperimentalApi::experimental_reason(&omitted_environments),
None
);
}

#[test]
fn turn_start_params_reject_relative_environment_cwd() {
let err = serde_json::from_value::<TurnStartParams>(json!({
"threadId": "thread_123",
"input": [],
"environments": [
{
"environmentId": "local",
"cwd": "relative"
}
],
}))
.expect_err("relative environment cwd should fail");

assert!(
err.to_string()
.contains("AbsolutePathBuf deserialized without a base path"),
"unexpected error: {err}"
);
}
}
4 changes: 4 additions & 0 deletions codex-rs/app-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,10 @@ You can optionally specify config overrides on the new turn. If specified, these
"input": [ { "type": "text", "text": "Run tests" } ],
// Below are optional config overrides
"cwd": "/Users/me/project",
// Experimental: turn-scoped environment selection.
"environments": [
{ "environmentId": "local", "cwd": "/Users/me/project" }
],
"approvalPolicy": "unlessTrusted",
"sandboxPolicy": {
"type": "workspaceWrite",
Expand Down
Loading
Loading