Carry runtime capabilities into app server deps

This commit is contained in:
starr-openai
2026-05-19 00:49:18 +00:00
parent 34404e9633
commit 7a2c7bb541
22 changed files with 314 additions and 38 deletions

View File

@@ -48,6 +48,7 @@ use codex_config::LoaderOverrides;
use codex_config::NoopThreadConfigLoader;
use codex_config::RemoteThreadConfigLoader;
use codex_config::ThreadConfigLoader;
pub use codex_core::RuntimeCapabilities;
use codex_core::config::Config;
pub use codex_exec_server::EnvironmentManager;
pub use codex_exec_server::ExecServerRuntimePaths;
@@ -348,6 +349,8 @@ pub struct InProcessClientStartArgs {
pub state_db: Option<StateDbHandle>,
/// Environment manager used by core execution and filesystem operations.
pub environment_manager: Arc<EnvironmentManager>,
/// Ambient worker-local capabilities selected for this app-server runtime.
pub runtime_capabilities: Arc<RuntimeCapabilities>,
/// Startup warnings emitted after initialize succeeds.
pub config_warnings: Vec<ConfigWarningNotification>,
/// Session source recorded in app-server thread metadata.
@@ -411,6 +414,7 @@ impl InProcessClientStartArgs {
log_db: self.log_db,
state_db: self.state_db,
environment_manager: self.environment_manager,
runtime_capabilities: self.runtime_capabilities,
config_warnings: self.config_warnings,
session_source: self.session_source,
enable_codex_api_key_env: self.enable_codex_api_key_env,
@@ -1028,6 +1032,7 @@ mod tests {
let state_db = init_state_db(config.as_ref())
.await
.expect("state db should initialize for in-process test");
let environment_manager = Arc::new(EnvironmentManager::default_for_tests());
let client = InProcessAppServerClient::start(InProcessClientStartArgs {
arg0_paths: Arg0DispatchPaths::default(),
config,
@@ -1038,7 +1043,10 @@ mod tests {
feedback: CodexFeedback::new(),
log_db: None,
state_db: Some(state_db),
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
runtime_capabilities: Arc::new(RuntimeCapabilities::local(
environment_manager.as_ref(),
)),
environment_manager,
config_warnings: Vec::new(),
session_source,
enable_codex_api_key_env: false,
@@ -2197,6 +2205,9 @@ mod tests {
feedback: CodexFeedback::new(),
log_db: None,
state_db: None,
runtime_capabilities: Arc::new(RuntimeCapabilities::local(
environment_manager.as_ref(),
)),
environment_manager: environment_manager.clone(),
config_warnings: Vec::new(),
session_source: SessionSource::Exec,
@@ -2228,6 +2239,7 @@ mod tests {
let mut config = build_test_config().await;
config.experimental_thread_config_endpoint = Some("not-a-valid-endpoint".to_string());
let environment_manager = Arc::new(EnvironmentManager::default_for_tests());
let runtime_args = InProcessClientStartArgs {
arg0_paths: Arg0DispatchPaths::default(),
config: Arc::new(config),
@@ -2238,7 +2250,10 @@ mod tests {
feedback: CodexFeedback::new(),
log_db: None,
state_db: None,
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
runtime_capabilities: Arc::new(RuntimeCapabilities::local(
environment_manager.as_ref(),
)),
environment_manager,
config_warnings: Vec::new(),
session_source: SessionSource::Exec,
enable_codex_api_key_env: false,

View File

@@ -5,9 +5,9 @@ use codex_config::ConfigLayerStack;
use codex_config::LoaderOverrides;
use codex_config::ThreadConfigLoader;
use codex_config::loader::load_config_layers_state;
use codex_core::RuntimeCapabilities;
use codex_core::config::Config;
use codex_core::config::ConfigOverrides;
use codex_exec_server::LOCAL_FS;
use codex_features::feature_for_key;
use codex_login::AuthManager;
use codex_login::default_client::set_default_client_residency_requirement;
@@ -33,6 +33,7 @@ pub(crate) struct ConfigManager {
strict_config: bool,
cloud_requirements: Arc<RwLock<CloudRequirementsLoader>>,
arg0_paths: Arg0DispatchPaths,
runtime_capabilities: Arc<RuntimeCapabilities>,
thread_config_loader: Arc<RwLock<Arc<dyn ThreadConfigLoader>>>,
}
@@ -44,6 +45,7 @@ impl ConfigManager {
strict_config: bool,
cloud_requirements: CloudRequirementsLoader,
arg0_paths: Arg0DispatchPaths,
runtime_capabilities: Arc<RuntimeCapabilities>,
thread_config_loader: Arc<dyn ThreadConfigLoader>,
) -> Self {
Self {
@@ -54,6 +56,7 @@ impl ConfigManager {
strict_config,
cloud_requirements: Arc::new(RwLock::new(cloud_requirements)),
arg0_paths,
runtime_capabilities,
thread_config_loader: Arc::new(RwLock::new(thread_config_loader)),
}
}
@@ -258,8 +261,12 @@ impl ConfigManager {
cwd: Option<AbsolutePathBuf>,
) -> std::io::Result<ConfigLayerStack> {
let thread_config_loader = self.current_thread_config_loader();
let file_system = self
.runtime_capabilities
.require_local_filesystem("load app-server config layers")
.map_err(std::io::Error::other)?;
load_config_layers_state(
LOCAL_FS.as_ref(),
file_system.as_ref(),
&self.codex_home,
cwd,
&self.current_cli_overrides(),
@@ -297,6 +304,7 @@ impl ConfigManager {
loader_overrides: LoaderOverrides,
cloud_requirements: CloudRequirementsLoader,
) -> Self {
let environment_manager = codex_exec_server::EnvironmentManager::default_for_tests();
Self::new(
codex_home,
cli_overrides,
@@ -304,6 +312,7 @@ impl ConfigManager {
/*strict_config*/ false,
cloud_requirements,
Arg0DispatchPaths::default(),
Arc::new(RuntimeCapabilities::local(&environment_manager)),
Arc::new(codex_config::NoopThreadConfigLoader),
)
}

View File

@@ -4,12 +4,15 @@ use codex_app_server_protocol::AppConfig;
use codex_app_server_protocol::AppToolApproval;
use codex_app_server_protocol::AppsConfig;
use codex_app_server_protocol::AskForApproval;
use codex_arg0::Arg0DispatchPaths;
use codex_config::CloudRequirementsLoader;
use codex_config::FeatureRequirementsToml;
use codex_config::LoaderOverrides;
use codex_core::RuntimeCapabilities;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use std::collections::BTreeMap;
use std::sync::Arc;
use tempfile::tempdir;
#[test]
@@ -106,6 +109,31 @@ personality = true
Ok(())
}
#[tokio::test]
async fn load_latest_config_rejects_isolated_runtime_without_local_filesystem() {
let tmp = tempdir().expect("tempdir");
let service = ConfigManager::new(
tmp.path().to_path_buf(),
Vec::new(),
LoaderOverrides::without_managed_config_for_tests(),
/*strict_config*/ false,
CloudRequirementsLoader::default(),
Arg0DispatchPaths::default(),
Arc::new(RuntimeCapabilities::isolated()),
Arc::new(codex_config::NoopThreadConfigLoader),
);
let error = service
.load_latest_config(/*fallback_cwd*/ None)
.await
.expect_err("isolated config reload should be unsupported");
assert_eq!(
error.to_string(),
"load app-server config requires ambient worker-local filesystem"
);
}
#[tokio::test]
async fn clear_missing_nested_config_is_noop() -> Result<()> {
let tmp = tempdir().expect("tempdir");

View File

@@ -80,6 +80,7 @@ use codex_arg0::Arg0DispatchPaths;
use codex_config::CloudRequirementsLoader;
use codex_config::LoaderOverrides;
use codex_config::ThreadConfigLoader;
use codex_core::RuntimeCapabilities;
use codex_core::config::Config;
use codex_core::resolve_installation_id;
use codex_exec_server::EnvironmentManager;
@@ -133,6 +134,8 @@ pub struct InProcessStartArgs {
pub state_db: Option<StateDbHandle>,
/// Environment manager used by core execution and filesystem operations.
pub environment_manager: Arc<EnvironmentManager>,
/// Ambient worker-local capabilities selected for this app-server runtime.
pub runtime_capabilities: Arc<RuntimeCapabilities>,
/// Startup warnings emitted after initialize succeeds.
pub config_warnings: Vec<ConfigWarningNotification>,
/// Session source stamped into thread/session metadata.
@@ -414,6 +417,7 @@ async fn start_uninitialized(args: InProcessStartArgs) -> IoResult<InProcessClie
args.strict_config,
args.cloud_requirements,
args.arg0_paths.clone(),
Arc::clone(&args.runtime_capabilities),
args.thread_config_loader,
);
let (processor_tx, mut processor_rx) = mpsc::channel::<ProcessorCommand>(channel_capacity);
@@ -425,6 +429,7 @@ async fn start_uninitialized(args: InProcessStartArgs) -> IoResult<InProcessClie
config: args.config,
config_manager,
environment_manager: args.environment_manager,
runtime_capabilities: args.runtime_capabilities,
feedback: args.feedback,
log_db: args.log_db,
state_db: args.state_db,
@@ -725,7 +730,11 @@ async fn start_uninitialized(args: InProcessStartArgs) -> IoResult<InProcessClie
mod tests {
use super::*;
use codex_app_server_protocol::ClientInfo;
use codex_app_server_protocol::CommandExecParams;
use codex_app_server_protocol::ConfigRequirementsReadResponse;
use codex_app_server_protocol::FsReadFileParams;
use codex_app_server_protocol::GitDiffToRemoteParams;
use codex_app_server_protocol::ProcessSpawnParams;
use codex_app_server_protocol::SessionSource as ApiSessionSource;
use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::ThreadStartResponse;
@@ -734,6 +743,7 @@ mod tests {
use codex_app_server_protocol::TurnItemsView;
use codex_app_server_protocol::TurnStatus;
use codex_core::config::ConfigBuilder;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use std::path::Path;
use tempfile::TempDir;
@@ -757,12 +767,26 @@ mod tests {
async fn start_test_client_with_capacity(
session_source: SessionSource,
channel_capacity: usize,
) -> InProcessClientHandle {
start_test_client_with_runtime_capabilities(
session_source,
channel_capacity,
RuntimeCapabilities::local,
)
.await
}
async fn start_test_client_with_runtime_capabilities(
session_source: SessionSource,
channel_capacity: usize,
runtime_capabilities: impl FnOnce(&EnvironmentManager) -> RuntimeCapabilities,
) -> InProcessClientHandle {
let codex_home = TempDir::new().expect("temp dir");
let config = Arc::new(build_test_config(codex_home.path()).await);
let state_db = codex_rollout::state_db::try_init(config.as_ref())
.await
.expect("state db should initialize for in-process test");
let environment_manager = Arc::new(EnvironmentManager::default_for_tests());
let args = InProcessStartArgs {
arg0_paths: Arg0DispatchPaths::default(),
config,
@@ -774,7 +798,8 @@ mod tests {
feedback: CodexFeedback::new(),
log_db: None,
state_db: Some(state_db),
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
runtime_capabilities: Arc::new(runtime_capabilities(environment_manager.as_ref())),
environment_manager,
config_warnings: Vec::new(),
session_source,
enable_codex_api_key_env: false,
@@ -797,6 +822,37 @@ mod tests {
start_test_client_with_capacity(session_source, DEFAULT_IN_PROCESS_CHANNEL_CAPACITY).await
}
async fn start_isolated_test_client() -> InProcessClientHandle {
start_test_client_with_runtime_capabilities(
SessionSource::Cli,
DEFAULT_IN_PROCESS_CHANNEL_CAPACITY,
|_| RuntimeCapabilities::isolated(),
)
.await
}
async fn isolated_request_error(
client: &InProcessClientHandle,
request: ClientRequest,
) -> JSONRPCErrorError {
client
.request(request)
.await
.expect("request transport should work")
.expect_err("isolated request should be unsupported")
}
fn assert_method_not_found(error: JSONRPCErrorError, message: &str) {
assert_eq!(
error,
JSONRPCErrorError {
code: crate::error_code::METHOD_NOT_FOUND_ERROR_CODE,
message: message.to_string(),
data: None,
}
);
}
#[tokio::test]
async fn in_process_start_initializes_and_handles_typed_v2_request() {
let client = start_test_client(SessionSource::Cli).await;
@@ -873,6 +929,98 @@ mod tests {
.expect("in-process runtime should shutdown cleanly");
}
#[tokio::test]
async fn isolated_in_process_runtime_rejects_ambient_local_requests() {
let client = start_isolated_test_client().await;
let cwd = AbsolutePathBuf::try_from(
client
._test_codex_home
.as_ref()
.expect("test codex home")
.path()
.to_path_buf(),
)
.expect("test codex home should be absolute");
assert_method_not_found(
isolated_request_error(
&client,
ClientRequest::FsReadFile {
request_id: RequestId::Integer(5),
params: FsReadFileParams { path: cwd.clone() },
},
)
.await,
"fs requests require ambient worker-local filesystem",
);
assert_method_not_found(
isolated_request_error(
&client,
ClientRequest::CommandExec {
request_id: RequestId::Integer(6),
params: CommandExecParams {
command: vec!["echo".to_string()],
process_id: None,
tty: false,
stream_stdin: false,
stream_stdout_stderr: false,
output_bytes_cap: None,
disable_output_cap: false,
disable_timeout: false,
timeout_ms: None,
cwd: None,
env: None,
size: None,
sandbox_policy: None,
permission_profile: None,
},
},
)
.await,
"command/exec requires ambient worker-local environment",
);
assert_method_not_found(
isolated_request_error(
&client,
ClientRequest::ProcessSpawn {
request_id: RequestId::Integer(7),
params: ProcessSpawnParams {
command: vec!["echo".to_string()],
process_handle: "process-1".to_string(),
cwd: cwd.clone(),
tty: false,
stream_stdin: false,
stream_stdout_stderr: false,
output_bytes_cap: None,
timeout_ms: None,
env: None,
size: None,
},
},
)
.await,
"process/spawn requires ambient worker-local environment",
);
assert_method_not_found(
isolated_request_error(
&client,
ClientRequest::GitDiffToRemote {
request_id: RequestId::Integer(8),
params: GitDiffToRemoteParams {
cwd: cwd.into_path_buf(),
},
},
)
.await,
"git diff to remote requires ambient worker-local environment",
);
client
.shutdown()
.await
.expect("in-process runtime should shutdown cleanly");
}
#[test]
fn guaranteed_delivery_helpers_cover_terminal_server_notifications() {
assert!(server_notification_requires_delivery(

View File

@@ -47,6 +47,7 @@ use codex_app_server_protocol::TextRange as AppTextRange;
use codex_config::ConfigLoadError;
use codex_config::TextRange as CoreTextRange;
use codex_core::ExecPolicyError;
use codex_core::RuntimeCapabilities;
use codex_core::check_execpolicy_for_warnings;
use codex_core::config::find_codex_home;
use codex_exec_server::EnvironmentManager;
@@ -451,6 +452,7 @@ pub async fn run_main_with_transport_options(
}
.map(Arc::new)
.map_err(std::io::Error::other)?;
let runtime_capabilities = Arc::new(RuntimeCapabilities::local(environment_manager.as_ref()));
let config_manager = ConfigManager::new(
codex_home.to_path_buf(),
cli_kv_overrides.clone(),
@@ -458,6 +460,7 @@ pub async fn run_main_with_transport_options(
strict_config,
Default::default(),
arg0_paths.clone(),
Arc::clone(&runtime_capabilities),
Arc::new(NoopThreadConfigLoader),
);
match config_manager
@@ -791,6 +794,7 @@ pub async fn run_main_with_transport_options(
config: Arc::new(config),
config_manager,
environment_manager,
runtime_capabilities,
feedback: feedback.clone(),
log_db,
state_db: state_db.clone(),

View File

@@ -110,6 +110,7 @@ mod tests {
use codex_config::ThreadConfigLoadErrorCode;
use codex_config::ThreadConfigLoader;
use codex_config::ThreadConfigSource;
use codex_core::RuntimeCapabilities;
use codex_core::config::ConfigOverrides;
use codex_core::init_state_db;
use codex_core::thread_store_from_config;
@@ -210,6 +211,9 @@ mod tests {
/*strict_config*/ false,
CloudRequirementsLoader::default(),
Arg0DispatchPaths::default(),
Arc::new(RuntimeCapabilities::local(
&EnvironmentManager::default_for_tests(),
)),
loader.clone(),
);

View File

@@ -65,6 +65,7 @@ use codex_app_server_protocol::ServerRequestPayload;
use codex_app_server_protocol::experimental_required_message;
use codex_arg0::Arg0DispatchPaths;
use codex_chatgpt::workspace_settings;
use codex_core::RuntimeCapabilities;
use codex_core::ThreadManager;
use codex_core::config::Config;
use codex_exec_server::EnvironmentManager;
@@ -259,6 +260,7 @@ pub(crate) struct MessageProcessorArgs {
pub(crate) config: Arc<Config>,
pub(crate) config_manager: ConfigManager,
pub(crate) environment_manager: Arc<EnvironmentManager>,
pub(crate) runtime_capabilities: Arc<RuntimeCapabilities>,
pub(crate) feedback: CodexFeedback,
pub(crate) log_db: Option<LogDbLayer>,
pub(crate) state_db: Option<StateDbHandle>,
@@ -282,6 +284,7 @@ impl MessageProcessor {
config,
config_manager,
environment_manager,
runtime_capabilities,
feedback,
log_db,
state_db,
@@ -355,8 +358,10 @@ impl MessageProcessor {
Arc::clone(&config),
outgoing.clone(),
config_manager.clone(),
Arc::clone(&runtime_capabilities),
);
let process_exec_processor = ProcessExecRequestProcessor::new(outgoing.clone());
let process_exec_processor =
ProcessExecRequestProcessor::new(outgoing.clone(), Arc::clone(&runtime_capabilities));
let feedback_processor = FeedbackRequestProcessor::new(
auth_manager.clone(),
Arc::clone(&thread_manager),
@@ -365,7 +370,7 @@ impl MessageProcessor {
log_db,
state_db.clone(),
);
let git_processor = GitRequestProcessor::new();
let git_processor = GitRequestProcessor::new(Arc::clone(&runtime_capabilities));
let initialize_processor = InitializeRequestProcessor::new(
outgoing.clone(),
analytics_events_client.clone(),
@@ -461,13 +466,8 @@ impl MessageProcessor {
);
let environment_processor =
EnvironmentRequestProcessor::new(thread_manager.environment_manager());
let fs_processor = FsRequestProcessor::new(
thread_manager
.environment_manager()
.local_environment()
.get_filesystem(),
fs_watch_manager,
);
let fs_processor =
FsRequestProcessor::new(runtime_capabilities.local_filesystem(), fs_watch_manager);
let windows_sandbox_processor = WindowsSandboxRequestProcessor::new(
outgoing.clone(),
Arc::clone(&config),

View File

@@ -25,6 +25,7 @@ use codex_app_server_protocol::UserInput;
use codex_arg0::Arg0DispatchPaths;
use codex_config::CloudRequirementsLoader;
use codex_config::LoaderOverrides;
use codex_core::RuntimeCapabilities;
use codex_core::config::Config;
use codex_core::config::ConfigBuilder;
use codex_exec_server::EnvironmentManager;
@@ -235,6 +236,8 @@ async fn build_test_processor(
let (outgoing_tx, outgoing_rx) = mpsc::channel(16);
let auth_manager =
AuthManager::shared_from_config(config.as_ref(), /*enable_codex_api_key_env*/ false).await;
let environment_manager = Arc::new(EnvironmentManager::default_for_tests());
let runtime_capabilities = Arc::new(RuntimeCapabilities::local(environment_manager.as_ref()));
let config_manager = ConfigManager::new(
config.codex_home.to_path_buf(),
Vec::new(),
@@ -242,6 +245,7 @@ async fn build_test_processor(
/*strict_config*/ false,
CloudRequirementsLoader::default(),
Arg0DispatchPaths::default(),
Arc::clone(&runtime_capabilities),
Arc::new(codex_config::NoopThreadConfigLoader),
);
let analytics_events_client =
@@ -256,7 +260,8 @@ async fn build_test_processor(
arg0_paths: Arg0DispatchPaths::default(),
config,
config_manager,
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
environment_manager,
runtime_capabilities,
feedback: CodexFeedback::new(),
log_db: None,
state_db: None,

View File

@@ -264,6 +264,7 @@ use codex_core::ExternalGoalPreviousStatus;
use codex_core::ExternalGoalSet;
use codex_core::ForkSnapshot;
use codex_core::NewThread;
use codex_core::RuntimeCapabilities;
#[cfg(test)]
use codex_core::SessionMeta;
use codex_core::StartThreadOptions;
@@ -480,6 +481,7 @@ pub(crate) use windows_sandbox_processor::WindowsSandboxRequestProcessor;
use crate::error_code::internal_error;
use crate::error_code::invalid_request;
use crate::error_code::method_not_found;
use crate::filters::compute_source_filters;
use crate::filters::source_kind_matches;
use crate::thread_state::ConnectionCapabilities;

View File

@@ -6,6 +6,7 @@ pub(crate) struct CommandExecRequestProcessor {
config: Arc<Config>,
outgoing: Arc<OutgoingMessageSender>,
config_manager: ConfigManager,
runtime_capabilities: Arc<RuntimeCapabilities>,
command_exec_manager: CommandExecManager,
}
@@ -15,12 +16,14 @@ impl CommandExecRequestProcessor {
config: Arc<Config>,
outgoing: Arc<OutgoingMessageSender>,
config_manager: ConfigManager,
runtime_capabilities: Arc<RuntimeCapabilities>,
) -> Self {
Self {
arg0_paths,
config,
outgoing,
config_manager,
runtime_capabilities,
command_exec_manager: CommandExecManager::default(),
}
}
@@ -89,6 +92,9 @@ impl CommandExecRequestProcessor {
params: CommandExecParams,
) -> Result<(), JSONRPCErrorError> {
tracing::debug!("ExecOneOffCommand params: {params:?}");
self.runtime_capabilities
.require_local_environment("command/exec")
.map_err(|err| method_not_found(err.to_string()))?;
let request = request_id.clone();

View File

@@ -1,5 +1,6 @@
use crate::error_code::internal_error;
use crate::error_code::invalid_request;
use crate::error_code::method_not_found;
use crate::fs_watch::FsWatchManager;
use crate::outgoing_message::ConnectionId;
use base64::Engine;
@@ -33,13 +34,13 @@ use std::sync::Arc;
#[derive(Clone)]
pub(crate) struct FsRequestProcessor {
file_system: Arc<dyn ExecutorFileSystem>,
file_system: Option<Arc<dyn ExecutorFileSystem>>,
fs_watch_manager: FsWatchManager,
}
impl FsRequestProcessor {
pub(crate) fn new(
file_system: Arc<dyn ExecutorFileSystem>,
file_system: Option<Arc<dyn ExecutorFileSystem>>,
fs_watch_manager: FsWatchManager,
) -> Self {
Self {
@@ -48,6 +49,12 @@ impl FsRequestProcessor {
}
}
fn file_system(&self) -> Result<&Arc<dyn ExecutorFileSystem>, JSONRPCErrorError> {
self.file_system
.as_ref()
.ok_or_else(|| method_not_found("fs requests require ambient worker-local filesystem"))
}
pub(crate) async fn connection_closed(&self, connection_id: ConnectionId) {
self.fs_watch_manager.connection_closed(connection_id).await;
}
@@ -57,7 +64,7 @@ impl FsRequestProcessor {
params: FsReadFileParams,
) -> Result<FsReadFileResponse, JSONRPCErrorError> {
let bytes = self
.file_system
.file_system()?
.read_file(&params.path, /*sandbox*/ None)
.await
.map_err(map_fs_error)?;
@@ -75,7 +82,7 @@ impl FsRequestProcessor {
"fs/writeFile requires valid base64 dataBase64: {err}"
))
})?;
self.file_system
self.file_system()?
.write_file(&params.path, bytes, /*sandbox*/ None)
.await
.map_err(map_fs_error)?;
@@ -86,7 +93,7 @@ impl FsRequestProcessor {
&self,
params: FsCreateDirectoryParams,
) -> Result<FsCreateDirectoryResponse, JSONRPCErrorError> {
self.file_system
self.file_system()?
.create_directory(
&params.path,
CreateDirectoryOptions {
@@ -104,7 +111,7 @@ impl FsRequestProcessor {
params: FsGetMetadataParams,
) -> Result<FsGetMetadataResponse, JSONRPCErrorError> {
let metadata = self
.file_system
.file_system()?
.get_metadata(&params.path, /*sandbox*/ None)
.await
.map_err(map_fs_error)?;
@@ -122,7 +129,7 @@ impl FsRequestProcessor {
params: FsReadDirectoryParams,
) -> Result<FsReadDirectoryResponse, JSONRPCErrorError> {
let entries = self
.file_system
.file_system()?
.read_directory(&params.path, /*sandbox*/ None)
.await
.map_err(map_fs_error)?;
@@ -142,7 +149,7 @@ impl FsRequestProcessor {
&self,
params: FsRemoveParams,
) -> Result<FsRemoveResponse, JSONRPCErrorError> {
self.file_system
self.file_system()?
.remove(
&params.path,
RemoveOptions {
@@ -160,7 +167,7 @@ impl FsRequestProcessor {
&self,
params: FsCopyParams,
) -> Result<FsCopyResponse, JSONRPCErrorError> {
self.file_system
self.file_system()?
.copy(
&params.source_path,
&params.destination_path,
@@ -179,6 +186,7 @@ impl FsRequestProcessor {
connection_id: ConnectionId,
params: FsWatchParams,
) -> Result<FsWatchResponse, JSONRPCErrorError> {
self.file_system()?;
self.fs_watch_manager.watch(connection_id, params).await
}

View File

@@ -1,17 +1,24 @@
use super::*;
#[derive(Clone)]
pub(crate) struct GitRequestProcessor;
pub(crate) struct GitRequestProcessor {
runtime_capabilities: Arc<RuntimeCapabilities>,
}
impl GitRequestProcessor {
pub(crate) fn new() -> Self {
Self
pub(crate) fn new(runtime_capabilities: Arc<RuntimeCapabilities>) -> Self {
Self {
runtime_capabilities,
}
}
pub(crate) async fn git_diff_to_remote(
&self,
params: GitDiffToRemoteParams,
) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError> {
self.runtime_capabilities
.require_local_environment("git diff to remote")
.map_err(|err| method_not_found(err.to_string()))?;
self.git_diff_to_origin(params.cwd)
.await
.map(|response| Some(response.into()))

View File

@@ -20,6 +20,7 @@ use codex_app_server_protocol::ProcessTerminalSize;
use codex_app_server_protocol::ProcessWriteStdinParams;
use codex_app_server_protocol::ProcessWriteStdinResponse;
use codex_app_server_protocol::ServerNotification;
use codex_core::RuntimeCapabilities;
use codex_core::exec::ExecExpiration;
use codex_core::exec::ExecExpirationOutcome;
use codex_core::exec::IO_DRAIN_TIMEOUT_MS;
@@ -38,6 +39,7 @@ use tokio_util::sync::CancellationToken;
use crate::error_code::internal_error;
use crate::error_code::invalid_params;
use crate::error_code::invalid_request;
use crate::error_code::method_not_found;
use crate::outgoing_message::ConnectionId;
use crate::outgoing_message::ConnectionRequestId;
use crate::outgoing_message::OutgoingMessageSender;
@@ -48,13 +50,18 @@ const OUTPUT_CHUNK_SIZE_HINT: usize = 64 * 1024;
#[derive(Clone)]
pub(crate) struct ProcessExecRequestProcessor {
outgoing: Arc<OutgoingMessageSender>,
runtime_capabilities: Arc<RuntimeCapabilities>,
process_exec_manager: ProcessExecManager,
}
impl ProcessExecRequestProcessor {
pub(crate) fn new(outgoing: Arc<OutgoingMessageSender>) -> Self {
pub(crate) fn new(
outgoing: Arc<OutgoingMessageSender>,
runtime_capabilities: Arc<RuntimeCapabilities>,
) -> Self {
Self {
outgoing,
runtime_capabilities,
process_exec_manager: ProcessExecManager::default(),
}
}
@@ -78,6 +85,9 @@ impl ProcessExecRequestProcessor {
} = params;
let method_name = "process/spawn";
tracing::debug!("{method_name} command: {command:?}");
self.runtime_capabilities
.require_local_environment(method_name)
.map_err(|err| method_not_found(err.to_string()))?;
if command.is_empty() {
return Err(invalid_request("command must not be empty"));
}

View File

@@ -594,6 +594,9 @@ mod thread_processor_behavior_tests {
/*strict_config*/ false,
CloudRequirementsLoader::default(),
Arg0DispatchPaths::default(),
Arc::new(codex_core::RuntimeCapabilities::local(
&codex_exec_server::EnvironmentManager::default_for_tests(),
)),
Arc::new(StaticThreadConfigLoader::new(vec![
ThreadConfigSource::Session(SessionThreadConfig {
model_provider: Some("session".to_string()),

View File

@@ -17,6 +17,7 @@ use codex_app_server_protocol::RequestId;
use codex_arg0::Arg0DispatchPaths;
use codex_config::CloudRequirementsLoader;
use codex_config::LoaderOverrides;
use codex_core::RuntimeCapabilities;
use codex_core::config::ConfigBuilder;
use codex_exec_server::EnvironmentManager;
use codex_feedback::CodexFeedback;
@@ -144,6 +145,7 @@ async fn get_conversation_summary_by_thread_id_reads_pathless_store_thread() ->
.loader_overrides(loader_overrides.clone())
.build()
.await?;
let environment_manager = Arc::new(EnvironmentManager::default_for_tests());
let client = in_process::start(InProcessStartArgs {
arg0_paths: Arg0DispatchPaths::default(),
config: Arc::new(config),
@@ -155,7 +157,8 @@ async fn get_conversation_summary_by_thread_id_reads_pathless_store_thread() ->
feedback: CodexFeedback::new(),
log_db: None,
state_db: None,
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
runtime_capabilities: Arc::new(RuntimeCapabilities::local(environment_manager.as_ref())),
environment_manager,
config_warnings: Vec::new(),
session_source: SessionSource::Cli,
enable_codex_api_key_env: false,

View File

@@ -23,6 +23,7 @@ use codex_arg0::Arg0DispatchPaths;
use codex_config::CloudRequirementsLoader;
use codex_config::LoaderOverrides;
use codex_config::types::AuthCredentialsStoreMode;
use codex_core::RuntimeCapabilities;
use codex_core::config::ConfigBuilder;
use codex_exec_server::EnvironmentManager;
use codex_feedback::CodexFeedback;
@@ -195,6 +196,7 @@ async fn mcp_resource_read_returns_error_for_unknown_thread() -> Result<()> {
.await?;
// This negative-path test does not need the stdio subprocess; keeping it
// in-process avoids child-process teardown timing in nextest leak detection.
let environment_manager = Arc::new(EnvironmentManager::default_for_tests());
let client = in_process::start(InProcessStartArgs {
arg0_paths: Arg0DispatchPaths::default(),
config: Arc::new(config),
@@ -206,7 +208,8 @@ async fn mcp_resource_read_returns_error_for_unknown_thread() -> Result<()> {
feedback: CodexFeedback::new(),
log_db: None,
state_db: None,
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
runtime_capabilities: Arc::new(RuntimeCapabilities::local(environment_manager.as_ref())),
environment_manager,
config_warnings: Vec::new(),
session_source: SessionSource::Cli,
enable_codex_api_key_env: false,

View File

@@ -37,6 +37,7 @@ use codex_arg0::Arg0DispatchPaths;
use codex_config::CloudRequirementsLoader;
use codex_config::LoaderOverrides;
use codex_config::NoopThreadConfigLoader;
use codex_core::RuntimeCapabilities;
use codex_core::config::ConfigBuilder;
use codex_exec_server::EnvironmentManager;
use codex_feedback::CodexFeedback;
@@ -70,6 +71,7 @@ async fn thread_start_with_non_local_thread_store_does_not_create_local_persiste
let thread_store = InMemoryThreadStore::for_id(store_id.clone());
let _in_memory_store = InMemoryThreadStoreId { store_id };
let environment_manager = Arc::new(EnvironmentManager::default_for_tests());
let mut client = in_process::start(InProcessStartArgs {
arg0_paths: Arg0DispatchPaths::default(),
config: Arc::new(config),
@@ -81,7 +83,8 @@ async fn thread_start_with_non_local_thread_store_does_not_create_local_persiste
feedback: CodexFeedback::new(),
log_db: None,
state_db: None,
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
runtime_capabilities: Arc::new(RuntimeCapabilities::local(environment_manager.as_ref())),
environment_manager,
config_warnings: Vec::new(),
session_source: SessionSource::Cli,
enable_codex_api_key_env: false,

View File

@@ -43,6 +43,7 @@ use codex_arg0::Arg0DispatchPaths;
use codex_config::CloudRequirementsLoader;
use codex_config::LoaderOverrides;
use codex_core::ARCHIVED_SESSIONS_SUBDIR;
use codex_core::RuntimeCapabilities;
use codex_core::config::ConfigBuilder;
use codex_exec_server::EnvironmentManager;
use codex_feedback::CodexFeedback;
@@ -370,6 +371,7 @@ async fn thread_turns_list_reads_store_history_without_rollout_path() -> Result<
.loader_overrides(loader_overrides.clone())
.build()
.await?;
let environment_manager = Arc::new(EnvironmentManager::default_for_tests());
let client = in_process::start(InProcessStartArgs {
arg0_paths: Arg0DispatchPaths::default(),
config: Arc::new(config),
@@ -381,7 +383,8 @@ async fn thread_turns_list_reads_store_history_without_rollout_path() -> Result<
feedback: CodexFeedback::new(),
log_db: None,
state_db: None,
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
runtime_capabilities: Arc::new(RuntimeCapabilities::local(environment_manager.as_ref())),
environment_manager,
config_warnings: Vec::new(),
session_source: SessionSource::Cli.into(),
enable_codex_api_key_env: false,
@@ -436,6 +439,7 @@ async fn thread_read_loaded_include_turns_reads_store_history_without_rollout_pa
.loader_overrides(loader_overrides.clone())
.build()
.await?;
let environment_manager = Arc::new(EnvironmentManager::default_for_tests());
let client = in_process::start(InProcessStartArgs {
arg0_paths: Arg0DispatchPaths::default(),
config: Arc::new(config),
@@ -447,7 +451,8 @@ async fn thread_read_loaded_include_turns_reads_store_history_without_rollout_pa
feedback: CodexFeedback::new(),
log_db: None,
state_db: None,
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
runtime_capabilities: Arc::new(RuntimeCapabilities::local(environment_manager.as_ref())),
environment_manager,
config_warnings: Vec::new(),
session_source: SessionSource::Cli.into(),
enable_codex_api_key_env: false,
@@ -522,6 +527,7 @@ async fn thread_list_includes_store_thread_without_rollout_path() -> Result<()>
.loader_overrides(loader_overrides.clone())
.build()
.await?;
let environment_manager = Arc::new(EnvironmentManager::default_for_tests());
let client = in_process::start(InProcessStartArgs {
arg0_paths: Arg0DispatchPaths::default(),
config: Arc::new(config),
@@ -533,7 +539,8 @@ async fn thread_list_includes_store_thread_without_rollout_path() -> Result<()>
feedback: CodexFeedback::new(),
log_db: None,
state_db: None,
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
runtime_capabilities: Arc::new(RuntimeCapabilities::local(environment_manager.as_ref())),
environment_manager,
config_warnings: Vec::new(),
session_source: SessionSource::Cli.into(),
enable_codex_api_key_env: false,

View File

@@ -24,6 +24,7 @@ use codex_app_server_protocol::UserInput;
use codex_arg0::Arg0DispatchPaths;
use codex_config::CloudRequirementsLoader;
use codex_config::LoaderOverrides;
use codex_core::RuntimeCapabilities;
use codex_core::config::ConfigBuilder;
use codex_core::find_archived_thread_path_by_id_str;
use codex_core::find_thread_path_by_id_str;
@@ -240,6 +241,7 @@ async fn thread_unarchive_preserves_pathless_store_metadata() -> Result<()> {
.loader_overrides(loader_overrides.clone())
.build()
.await?;
let environment_manager = Arc::new(EnvironmentManager::default_for_tests());
let client = in_process::start(InProcessStartArgs {
arg0_paths: Arg0DispatchPaths::default(),
config: Arc::new(config),
@@ -251,7 +253,8 @@ async fn thread_unarchive_preserves_pathless_store_metadata() -> Result<()> {
feedback: CodexFeedback::new(),
log_db: None,
state_db: None,
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
runtime_capabilities: Arc::new(RuntimeCapabilities::local(environment_manager.as_ref())),
environment_manager,
config_warnings: Vec::new(),
session_source: SessionSource::Cli,
enable_codex_api_key_env: false,

View File

@@ -56,6 +56,7 @@ use codex_config::ConfigLoadError;
use codex_config::ConfigLoadOptions;
use codex_config::LoaderOverrides;
use codex_config::format_config_error_with_source;
use codex_core::RuntimeCapabilities;
use codex_core::StateDbHandle;
use codex_core::check_execpolicy_for_warnings;
use codex_core::config::Config;
@@ -536,6 +537,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
feedback: CodexFeedback::new(),
log_db: None,
state_db: state_db.clone(),
runtime_capabilities: std::sync::Arc::new(RuntimeCapabilities::local(&environment_manager)),
environment_manager: std::sync::Arc::new(environment_manager),
config_warnings,
session_source: SessionSource::Exec,

View File

@@ -27,6 +27,7 @@ use codex_app_server_client::InProcessClientStartArgs;
use codex_app_server_client::RemoteAppServerClient;
use codex_app_server_client::RemoteAppServerConnectArgs;
pub use codex_app_server_client::RemoteAppServerEndpoint;
use codex_app_server_client::RuntimeCapabilities;
use codex_app_server_protocol::Account as AppServerAccount;
use codex_app_server_protocol::AskForApproval;
use codex_app_server_protocol::AuthMode as AppServerAuthMode;
@@ -575,6 +576,7 @@ where
feedback,
log_db,
state_db,
runtime_capabilities: Arc::new(RuntimeCapabilities::local(environment_manager.as_ref())),
environment_manager,
config_warnings,
session_source: serde_json::from_value(serde_json::json!("cli"))

View File

@@ -1035,6 +1035,7 @@ mod tests {
use codex_app_server_client::DEFAULT_IN_PROCESS_CHANNEL_CAPACITY;
use codex_app_server_client::InProcessAppServerClient;
use codex_app_server_client::InProcessClientStartArgs;
use codex_app_server_client::RuntimeCapabilities;
use codex_arg0::Arg0DispatchPaths;
use codex_cloud_requirements::cloud_requirements_loader_for_storage;
use codex_config::types::AuthCredentialsStoreMode;
@@ -1051,6 +1052,8 @@ mod tests {
.build()
.await
.unwrap();
let environment_manager =
Arc::new(codex_app_server_client::EnvironmentManager::default_for_tests());
let client = InProcessAppServerClient::start(InProcessClientStartArgs {
arg0_paths: Arg0DispatchPaths::default(),
config: Arc::new(config),
@@ -1067,9 +1070,10 @@ mod tests {
feedback: codex_feedback::CodexFeedback::new(),
log_db: None,
state_db: None,
environment_manager: Arc::new(
codex_app_server_client::EnvironmentManager::default_for_tests(),
),
runtime_capabilities: Arc::new(RuntimeCapabilities::local(
environment_manager.as_ref(),
)),
environment_manager,
config_warnings: Vec::new(),
session_source: serde_json::from_value(serde_json::json!("cli"))
.expect("cli session source should deserialize"),