diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 1b71ab15ae2e..f4127c437c6a 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -482,15 +482,15 @@ struct ExecServerCommand { #[arg(long = "listen", value_name = "URL", conflicts_with = "remote")] listen: Option, - /// Register this exec-server as a remote executor using the given base URL. - #[arg(long = "remote", value_name = "URL", requires = "executor_id")] + /// Register this exec-server as a remote environment using the given base URL. + #[arg(long = "remote", value_name = "URL", requires = "environment_id")] remote: Option, - /// Executor id to attach to when registering remotely. - #[arg(long = "executor-id", value_name = "ID")] - executor_id: Option, + /// Environment id to attach to when registering remotely. + #[arg(long = "environment-id", value_name = "ID")] + environment_id: Option, - /// Human-readable executor name. + /// Human-readable environment name. #[arg(long = "name", value_name = "NAME")] name: Option, @@ -1491,21 +1491,24 @@ async fn run_exec_server_command( arg0_paths.codex_linux_sandbox_exe.clone(), )?; if let Some(base_url) = cmd.remote { - let executor_id = cmd - .executor_id - .ok_or_else(|| anyhow::anyhow!("--executor-id is required when --remote is set"))?; + let environment_id = cmd + .environment_id + .ok_or_else(|| anyhow::anyhow!("--environment-id is required when --remote is set"))?; let auth_provider = load_exec_server_remote_auth_provider( root_config_overrides, config_profile, cmd.use_agent_identity_auth, ) .await?; - let mut remote_config = - codex_exec_server::RemoteExecutorConfig::new(base_url, executor_id, auth_provider)?; + let mut remote_config = codex_exec_server::RemoteEnvironmentConfig::new( + base_url, + environment_id, + auth_provider, + )?; if let Some(name) = cmd.name { remote_config.name = name; } - codex_exec_server::run_remote_executor(remote_config, runtime_paths).await?; + codex_exec_server::run_remote_environment(remote_config, runtime_paths).await?; return Ok(()); } let listen_url = cmd diff --git a/codex-rs/core/tests/suite/rmcp_client.rs b/codex-rs/core/tests/suite/rmcp_client.rs index 7c811a88e042..210c287c2f47 100644 --- a/codex-rs/core/tests/suite/rmcp_client.rs +++ b/codex-rs/core/tests/suite/rmcp_client.rs @@ -1808,7 +1808,7 @@ async fn streamable_http_tool_call_round_trip() -> anyhow::Result<()> { .await; // Phase 2: start the Streamable HTTP MCP test server in the active - // placement. In full CI this may be the remote executor container; locally + // placement. In full CI this may be the remote environment container; locally // it is a host process. let expected_env_value = "propagated-env-http"; let Some(http_server) = diff --git a/codex-rs/exec-server/README.md b/codex-rs/exec-server/README.md index 01439108f87b..468ac205f72a 100644 --- a/codex-rs/exec-server/README.md +++ b/codex-rs/exec-server/README.md @@ -22,10 +22,10 @@ the wire. The CLI entrypoint supports: - `ws://IP:PORT` (default) -- `--remote URL --executor-id ID [--name NAME]` +- `--remote URL --environment-id ID [--name NAME]` -Remote mode registers the local exec-server with the executor registry, -then reconnects to the service-provided rendezvous websocket as the executor. +Remote mode registers the local exec-server with the environment registry, +then reconnects to the service-provided rendezvous websocket as the environment. It uses the standard Codex ChatGPT sign-in state; run `codex login` first when remote registration needs authentication. Containerized callers that receive an Agent Identity JWT in `CODEX_ACCESS_TOKEN` can opt into that auth path with @@ -39,7 +39,7 @@ Wire framing: ## Remote Relay Message Format -In remote mode, the harness and executor communicate through rendezvous using +In remote mode, the harness and environment communicate through rendezvous using `codex.exec_server.relay.v1.RelayMessageFrame`; the checked-in schema is in `src/proto/codex.exec_server.relay.v1.proto`. The relay frame carries stream identity plus endpoint-owned reliability metadata: @@ -58,8 +58,8 @@ next_seq // resume only: next sender seq reason // reset only: reset reason ``` -`stream_id` identifies one virtual harness/executor JSON-RPC session on the -executor websocket. The harness generates a UUIDv4 `stream_id`; the executor +`stream_id` identifies one virtual harness/environment JSON-RPC session on the +environment websocket. The harness generates a UUIDv4 `stream_id`; the environment demuxes frames by `stream_id` and runs an independent `ConnectionProcessor` per stream. @@ -375,12 +375,12 @@ The crate exports: - `DEFAULT_LISTEN_URL` and `ExecServerListenUrlParseError` - `ExecServerRuntimePaths` - `run_main()` for embedding the websocket server -- `RemoteExecutorConfig` and `run_remote_executor()` for embedding remote +- `RemoteEnvironmentConfig` and `run_remote_environment()` for embedding remote registration mode Callers must pass `ExecServerRuntimePaths` to `run_main()`. The top-level `codex exec-server` command builds these paths from the `codex` arg0 dispatch -state. `RemoteExecutorConfig::new(...)` also takes the auth provider that +state. `RemoteEnvironmentConfig::new(...)` also takes the auth provider that remote registration should use; the CLI builds that provider from Codex auth state before starting remote mode. diff --git a/codex-rs/exec-server/src/client.rs b/codex-rs/exec-server/src/client.rs index 9261a59542c4..bb5a3855dcef 100644 --- a/codex-rs/exec-server/src/client.rs +++ b/codex-rs/exec-server/src/client.rs @@ -162,7 +162,7 @@ struct Inner { // need serialization so concurrent register/remove operations do not // overwrite each other's copy-on-write updates. sessions_write_lock: Mutex<()>, - // Once the transport closes, every executor operation should fail quickly + // Once the transport closes, every environment operation should fail quickly // with the same canonical message. This client never reconnects, so the // latch only moves from unset to set once. disconnected: OnceLock, @@ -261,18 +261,18 @@ pub enum ExecServerError { Protocol(String), #[error("exec-server rejected request ({code}): {message}")] Server { code: i64, message: String }, - #[error("executor registry request failed ({status}{code_suffix}): {message}", code_suffix = .code.as_ref().map(|code| format!(", {code}")).unwrap_or_default())] - ExecutorRegistryHttp { + #[error("environment registry request failed ({status}{code_suffix}): {message}", code_suffix = .code.as_ref().map(|code| format!(", {code}")).unwrap_or_default())] + EnvironmentRegistryHttp { status: reqwest::StatusCode, code: Option, message: String, }, - #[error("executor registry configuration error: {0}")] - ExecutorRegistryConfig(String), - #[error("executor registry authentication error: {0}")] - ExecutorRegistryAuth(String), - #[error("executor registry request failed: {0}")] - ExecutorRegistryRequest(#[from] reqwest::Error), + #[error("environment registry configuration error: {0}")] + EnvironmentRegistryConfig(String), + #[error("environment registry authentication error: {0}")] + EnvironmentRegistryAuth(String), + #[error("environment registry request failed: {0}")] + EnvironmentRegistryRequest(#[from] reqwest::Error), } impl ExecServerClient { @@ -495,7 +495,7 @@ impl ExecServerClient { { // Reject new work before allocating a JSON-RPC request id. MCP tool // calls, process writes, and fs operations all pass through here, so - // this is the shared low-level failure path after executor disconnect. + // this is the shared low-level failure path after environment disconnect. if let Some(error) = self.inner.disconnected_error() { return Err(error); } @@ -720,7 +720,7 @@ impl Inner { session: Arc, ) -> Result<(), ExecServerError> { let _sessions_write_guard = self.sessions_write_lock.lock().await; - // Do not register a process session that can never receive executor + // Do not register a process session that can never receive environment // notifications. Without this check, remote MCP startup could create a // dead session and wait for process output that will never arrive. if let Some(error) = self.disconnected_error() { @@ -795,7 +795,7 @@ async fn fail_all_sessions(inner: &Arc, message: String) { for (_, session) in sessions { // Sessions synthesize a closed read response and emit a pushed Failed // event. That covers both polling consumers and streaming consumers - // such as executor-backed MCP stdio. + // such as environment-backed MCP stdio. session.set_failure(message.clone()).await; } } diff --git a/codex-rs/exec-server/src/lib.rs b/codex-rs/exec-server/src/lib.rs index bd556638ea0f..f2d16f8fac9b 100644 --- a/codex-rs/exec-server/src/lib.rs +++ b/codex-rs/exec-server/src/lib.rs @@ -91,8 +91,8 @@ pub use protocol::TerminateResponse; pub use protocol::WriteParams; pub use protocol::WriteResponse; pub use protocol::WriteStatus; -pub use remote::RemoteExecutorConfig; -pub use remote::run_remote_executor; +pub use remote::RemoteEnvironmentConfig; +pub use remote::run_remote_environment; pub use runtime_paths::ExecServerRuntimePaths; pub use server::DEFAULT_LISTEN_URL; pub use server::ExecServerListenUrlParseError; diff --git a/codex-rs/exec-server/src/process.rs b/codex-rs/exec-server/src/process.rs index 052e4aa64358..cb6c83213806 100644 --- a/codex-rs/exec-server/src/process.rs +++ b/codex-rs/exec-server/src/process.rs @@ -24,7 +24,7 @@ pub struct StartedExecProcess { /// stdout, stderr, or pty bytes. `Exited` reports the process exit status, while /// `Closed` means all output streams have ended and no more output events will /// arrive. `Failed` is used when the process session cannot continue, for -/// example because the remote executor connection disconnected. +/// example because the remote environment connection disconnected. #[derive(Debug, Clone, PartialEq, Eq)] pub enum ExecProcessEvent { Output(ProcessOutputChunk), diff --git a/codex-rs/exec-server/src/relay.rs b/codex-rs/exec-server/src/relay.rs index b3708f1d8371..ae07d7652cb6 100644 --- a/codex-rs/exec-server/src/relay.rs +++ b/codex-rs/exec-server/src/relay.rs @@ -298,7 +298,7 @@ where } } -pub(crate) async fn run_multiplexed_executor( +pub(crate) async fn run_multiplexed_environment( stream: WebSocketStream, processor: ConnectionProcessor, ) where @@ -337,7 +337,7 @@ pub(crate) async fn run_multiplexed_executor( continue; } Some(Err(err)) => { - debug!("multiplexed executor websocket read failed: {err}"); + debug!("multiplexed environment websocket read failed: {err}"); break; } } diff --git a/codex-rs/exec-server/src/remote.rs b/codex-rs/exec-server/src/remote.rs index 2493135c9a3c..de09d94e7c6c 100644 --- a/codex-rs/exec-server/src/remote.rs +++ b/codex-rs/exec-server/src/remote.rs @@ -11,28 +11,28 @@ use codex_utils_rustls_provider::ensure_rustls_crypto_provider; use crate::ExecServerError; use crate::ExecServerRuntimePaths; -use crate::relay::run_multiplexed_executor; +use crate::relay::run_multiplexed_environment; use crate::server::ConnectionProcessor; const ERROR_BODY_PREVIEW_BYTES: usize = 4096; #[derive(Clone)] -struct ExecutorRegistryClient { +struct EnvironmentRegistryClient { base_url: String, auth_provider: SharedAuthProvider, http: reqwest::Client, } -impl std::fmt::Debug for ExecutorRegistryClient { +impl std::fmt::Debug for EnvironmentRegistryClient { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ExecutorRegistryClient") + f.debug_struct("EnvironmentRegistryClient") .field("base_url", &self.base_url) .field("auth_provider", &"") .finish_non_exhaustive() } } -impl ExecutorRegistryClient { +impl EnvironmentRegistryClient { fn new(base_url: String, auth_provider: SharedAuthProvider) -> Result { let base_url = normalize_base_url(base_url)?; Ok(Self { @@ -42,15 +42,15 @@ impl ExecutorRegistryClient { }) } - async fn register_executor( + async fn register_environment( &self, - executor_id: &str, - ) -> Result { + environment_id: &str, + ) -> Result { let response = self .http .post(endpoint_url( &self.base_url, - &format!("/cloud/executor/{executor_id}/register"), + &format!("/cloud/environment/{environment_id}/register"), )) .headers(self.auth_provider.to_auth_headers()) .send() @@ -72,49 +72,49 @@ impl ExecutorRegistryClient { let status = response.status(); let body = response.text().await.unwrap_or_default(); if matches!(status, StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN) { - return Err(executor_registry_auth_error(status, &body)); + return Err(environment_registry_auth_error(status, &body)); } - Err(executor_registry_http_error(status, &body)) + Err(environment_registry_http_error(status, &body)) } } #[derive(Debug, Clone, Eq, PartialEq, Deserialize)] -struct ExecutorRegistryExecutorRegistrationResponse { - executor_id: String, +struct EnvironmentRegistryRegistrationResponse { + environment_id: String, url: String, } /// Configuration for registering an exec-server for remote use. #[derive(Clone)] -pub struct RemoteExecutorConfig { +pub struct RemoteEnvironmentConfig { pub base_url: String, - pub executor_id: String, + pub environment_id: String, pub name: String, auth_provider: SharedAuthProvider, } -impl std::fmt::Debug for RemoteExecutorConfig { +impl std::fmt::Debug for RemoteEnvironmentConfig { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("RemoteExecutorConfig") + f.debug_struct("RemoteEnvironmentConfig") .field("base_url", &self.base_url) - .field("executor_id", &self.executor_id) + .field("environment_id", &self.environment_id) .field("name", &self.name) .field("auth_provider", &"") .finish() } } -impl RemoteExecutorConfig { +impl RemoteEnvironmentConfig { pub fn new( base_url: String, - executor_id: String, + environment_id: String, auth_provider: SharedAuthProvider, ) -> Result { - let executor_id = normalize_executor_id(executor_id)?; + let environment_id = normalize_environment_id(environment_id)?; Ok(Self { base_url, - executor_id, + environment_id, name: "codex-exec-server".to_string(), auth_provider, }) @@ -123,27 +123,27 @@ impl RemoteExecutorConfig { /// Register an exec-server for remote use and serve requests over the returned /// rendezvous websocket. -pub async fn run_remote_executor( - config: RemoteExecutorConfig, +pub async fn run_remote_environment( + config: RemoteEnvironmentConfig, runtime_paths: ExecServerRuntimePaths, ) -> Result<(), ExecServerError> { ensure_rustls_crypto_provider(); let client = - ExecutorRegistryClient::new(config.base_url.clone(), config.auth_provider.clone())?; + EnvironmentRegistryClient::new(config.base_url.clone(), config.auth_provider.clone())?; let processor = ConnectionProcessor::new(runtime_paths); let mut backoff = Duration::from_secs(1); loop { - let response = client.register_executor(&config.executor_id).await?; + let response = client.register_environment(&config.environment_id).await?; eprintln!( - "codex exec-server remote executor registered with executor_id {}", - response.executor_id + "codex exec-server remote environment registered with environment_id {}", + response.environment_id ); match connect_async(response.url.as_str()).await { Ok((websocket, _)) => { backoff = Duration::from_secs(1); - run_multiplexed_executor(websocket, processor.clone()).await; + run_multiplexed_environment(websocket, processor.clone()).await; } Err(err) => { warn!("failed to connect remote exec-server websocket: {err}"); @@ -155,14 +155,14 @@ pub async fn run_remote_executor( } } -fn normalize_executor_id(executor_id: String) -> Result { - let executor_id = executor_id.trim().to_string(); - if executor_id.is_empty() { - return Err(ExecServerError::ExecutorRegistryConfig( - "executor id is required for remote exec-server registration".to_string(), +fn normalize_environment_id(environment_id: String) -> Result { + let environment_id = environment_id.trim().to_string(); + if environment_id.is_empty() { + return Err(ExecServerError::EnvironmentRegistryConfig( + "environment id is required for remote exec-server registration".to_string(), )); } - Ok(executor_id) + Ok(environment_id) } #[derive(Deserialize)] @@ -179,8 +179,8 @@ struct RegistryError { fn normalize_base_url(base_url: String) -> Result { let trimmed = base_url.trim().trim_end_matches('/').to_string(); if trimmed.is_empty() { - return Err(ExecServerError::ExecutorRegistryConfig( - "executor registry base URL is required".to_string(), + return Err(ExecServerError::EnvironmentRegistryConfig( + "environment registry base URL is required".to_string(), )); } Ok(trimmed) @@ -190,14 +190,14 @@ fn endpoint_url(base_url: &str, path: &str) -> String { format!("{base_url}/{}", path.trim_start_matches('/')) } -fn executor_registry_auth_error(status: StatusCode, body: &str) -> ExecServerError { +fn environment_registry_auth_error(status: StatusCode, body: &str) -> ExecServerError { let message = registry_error_message(body).unwrap_or_else(|| "empty error body".to_string()); - ExecServerError::ExecutorRegistryAuth(format!( - "executor registry authentication failed ({status}): {message}" + ExecServerError::EnvironmentRegistryAuth(format!( + "environment registry authentication failed ({status}): {message}" )) } -fn executor_registry_http_error(status: StatusCode, body: &str) -> ExecServerError { +fn environment_registry_http_error(status: StatusCode, body: &str) -> ExecServerError { let parsed = serde_json::from_str::(body).ok(); let (code, message) = parsed .and_then(|body| body.error) @@ -216,7 +216,7 @@ fn executor_registry_http_error(status: StatusCode, body: &str) -> ExecServerErr .unwrap_or_else(|| "empty or malformed error body".to_string()), ) }); - ExecServerError::ExecutorRegistryHttp { + ExecServerError::EnvironmentRegistryHttp { status, code, message, @@ -277,46 +277,46 @@ mod tests { } #[tokio::test] - async fn register_executor_posts_with_auth_provider_headers() { + async fn register_environment_posts_with_auth_provider_headers() { let server = MockServer::start().await; - let config = RemoteExecutorConfig::new( + let config = RemoteEnvironmentConfig::new( server.uri(), - "exec-requested".to_string(), + "environment-requested".to_string(), static_registry_auth_provider(), ) .expect("config"); Mock::given(method("POST")) - .and(path("/cloud/executor/exec-requested/register")) + .and(path("/cloud/environment/environment-requested/register")) .and(header("authorization", "Bearer registry-token")) .and(header("chatgpt-account-id", "workspace-123")) .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ - "executor_id": "exec-1", - "url": "wss://rendezvous.test/executor/exec-1?role=executor&sig=abc" + "environment_id": "env-1", + "url": "wss://rendezvous.test/cloud-agent/default/ws/environment/env-1?role=environment&sig=abc" }))) .mount(&server) .await; - let client = ExecutorRegistryClient::new(server.uri(), static_registry_auth_provider()) + let client = EnvironmentRegistryClient::new(server.uri(), static_registry_auth_provider()) .expect("client"); let response = client - .register_executor(&config.executor_id) + .register_environment(&config.environment_id) .await - .expect("register executor"); + .expect("register environment"); assert_eq!( response, - ExecutorRegistryExecutorRegistrationResponse { - executor_id: "exec-1".to_string(), - url: "wss://rendezvous.test/executor/exec-1?role=executor&sig=abc".to_string(), + EnvironmentRegistryRegistrationResponse { + environment_id: "env-1".to_string(), + url: "wss://rendezvous.test/cloud-agent/default/ws/environment/env-1?role=environment&sig=abc".to_string(), } ); } #[test] fn debug_output_redacts_auth_provider() { - let config = RemoteExecutorConfig::new( + let config = RemoteEnvironmentConfig::new( "https://registry.example".to_string(), - "exec-1".to_string(), + "env-1".to_string(), static_registry_auth_provider(), ) .expect("config"); diff --git a/codex-rs/exec-server/tests/relay.rs b/codex-rs/exec-server/tests/relay.rs index 9f985e698288..ff41dd525edd 100644 --- a/codex-rs/exec-server/tests/relay.rs +++ b/codex-rs/exec-server/tests/relay.rs @@ -20,7 +20,7 @@ use codex_app_server_protocol::RequestId; use codex_exec_server::ExecServerRuntimePaths; use codex_exec_server::InitializeParams; use codex_exec_server::InitializeResponse; -use codex_exec_server::RemoteExecutorConfig; +use codex_exec_server::RemoteEnvironmentConfig; use futures::SinkExt; use futures::StreamExt; use http::HeaderMap; @@ -45,7 +45,7 @@ use wiremock::matchers::header; use wiremock::matchers::method; use wiremock::matchers::path; -const EXECUTOR_ID: &str = "exec-mux-test"; +const ENVIRONMENT_ID: &str = "env-mux-test"; const REGISTRY_TOKEN: &str = "registry-token"; const RELAY_MESSAGE_FRAME_VERSION: u32 = 1; const TEST_TIMEOUT: Duration = Duration::from_secs(5); @@ -67,15 +67,17 @@ fn static_registry_auth_provider() -> codex_api::SharedAuthProvider { } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn multiplexed_remote_executor_routes_independent_virtual_streams() -> Result<()> { +async fn multiplexed_remote_environment_routes_independent_virtual_streams() -> Result<()> { let listener = TcpListener::bind("127.0.0.1:0").await?; let rendezvous_url = format!("ws://{}", listener.local_addr()?); let registry = MockServer::start().await; Mock::given(method("POST")) - .and(path(format!("/cloud/executor/{EXECUTOR_ID}/register"))) + .and(path(format!( + "/cloud/environment/{ENVIRONMENT_ID}/register" + ))) .and(header("authorization", format!("Bearer {REGISTRY_TOKEN}"))) .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({ - "executor_id": EXECUTOR_ID, + "environment_id": ENVIRONMENT_ID, "url": rendezvous_url, }))) .mount(®istry) @@ -83,22 +85,22 @@ async fn multiplexed_remote_executor_routes_independent_virtual_streams() -> Res let (codex_exe, codex_linux_sandbox_exe) = common::current_test_binary_helper_paths()?; let runtime_paths = ExecServerRuntimePaths::new(codex_exe, codex_linux_sandbox_exe)?; - let config = RemoteExecutorConfig::new( + let config = RemoteEnvironmentConfig::new( registry.uri(), - EXECUTOR_ID.to_string(), + ENVIRONMENT_ID.to_string(), static_registry_auth_provider(), )?; - let remote_executor = tokio::spawn(codex_exec_server::run_remote_executor( + let remote_environment = tokio::spawn(codex_exec_server::run_remote_environment( config, runtime_paths, )); let (socket, _peer_addr) = timeout(TEST_TIMEOUT, listener.accept()) .await - .context("remote executor should connect to fake rendezvous")??; + .context("remote environment should connect to fake rendezvous")??; let mut websocket = timeout(TEST_TIMEOUT, accept_async(socket)) .await - .context("fake rendezvous should accept executor websocket")??; + .context("fake rendezvous should accept environment websocket")??; let stream_a = "stream-a"; let stream_b = "stream-b"; @@ -192,8 +194,8 @@ async fn multiplexed_remote_executor_routes_independent_virtual_streams() -> Res )?; websocket.close(None).await?; - remote_executor.abort(); - let _ = remote_executor.await; + remote_environment.abort(); + let _ = remote_environment.await; Ok(()) } @@ -285,7 +287,7 @@ where let frame = timeout(TEST_TIMEOUT, websocket.next()) .await .context("timed out waiting for relay frame")? - .ok_or_else(|| anyhow!("executor websocket closed"))??; + .ok_or_else(|| anyhow!("environment websocket closed"))??; match frame { Message::Binary(bytes) => { let frame = RelayMessageFrame::decode(bytes.as_ref())?; @@ -297,8 +299,8 @@ where return Ok((stream_id, message)); } Message::Ping(_) | Message::Pong(_) => {} - Message::Close(_) => bail!("executor websocket closed"), - Message::Text(_) => bail!("executor sent text frame on relay websocket"), + Message::Close(_) => bail!("environment websocket closed"), + Message::Text(_) => bail!("environment sent text frame on relay websocket"), Message::Frame(_) => {} } } diff --git a/codex-rs/file-system/src/lib.rs b/codex-rs/file-system/src/lib.rs index 026523eb2f24..606f987cbd1b 100644 --- a/codex-rs/file-system/src/lib.rs +++ b/codex-rs/file-system/src/lib.rs @@ -130,7 +130,7 @@ fn file_system_policy_has_cwd_dependent_entries( pub type FileSystemResult = io::Result; /// Abstract filesystem access used by components that may operate locally or via -/// a remote executor. +/// a remote environment. #[async_trait] pub trait ExecutorFileSystem: Send + Sync { async fn read_file(